dsh-auto-collapse 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +98 -80
- package/README.md +99 -80
- package/lib/client.js +1093 -125
- package/lib/index.js +27 -8
- package/lib/types/client.d.ts +13 -0
- package/lib/types/fold.d.ts +264 -0
- package/lib/types/index.d.ts +11 -10
- package/lib/types/settings.d.ts +41 -0
- package/package.json +61 -54
- package/lib/types/client/index.d.ts +0 -15
package/lib/client.js
CHANGED
|
@@ -5,6 +5,12 @@ var __dshcfBundle = (() => {
|
|
|
5
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
9
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
10
|
+
}) : x)(function(x) {
|
|
11
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
12
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
13
|
+
});
|
|
8
14
|
var __export = (target, all) => {
|
|
9
15
|
for (var name2 in all)
|
|
10
16
|
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
@@ -29,6 +35,12 @@ var __dshcfBundle = (() => {
|
|
|
29
35
|
|
|
30
36
|
// src/fold.ts
|
|
31
37
|
var STYLE_ID = "dshcf-style";
|
|
38
|
+
var DEFAULT_STATUS_TEXT = "Deep sleeping...";
|
|
39
|
+
var ANIM_DURATION_MS = 180;
|
|
40
|
+
var ANIM_EASING = "ease-out";
|
|
41
|
+
var DISPLAY_OWNED_PROP = "--dshcf-display-owned";
|
|
42
|
+
var AUDIT_TICK_MS = 1e3;
|
|
43
|
+
var STICK_BOTTOM_THRESHOLD_PX = 24;
|
|
32
44
|
var TOOL_LABELS = {
|
|
33
45
|
bash: "Bash",
|
|
34
46
|
pwsh: "Pwsh",
|
|
@@ -68,6 +80,12 @@ var __dshcfBundle = (() => {
|
|
|
68
80
|
text-align: left;
|
|
69
81
|
cursor: pointer;
|
|
70
82
|
user-select: none;
|
|
83
|
+
/* \u5C55\u5F00\u6001\u8865\u7684 margin-bottom 16px \u7531 aria-expanded/has-body \u7FFB\u8F6C\u9A71\u52A8\uFF0C
|
|
84
|
+
\u4E00\u5E27\u77AC\u5F00\uFF08\u4E0E\u4E09\u7EA7\u884C display \u7FFB\u8F6C\u540C pass \u540C\u5E27\uFF0C\u65E0\u4E0B\u63A8\uFF09\uFF1B\u6536\u8D77\u65B9\u5411
|
|
85
|
+
\u7531 JS \u4FA7\u9489\u4F4F\u95F4\u8DDD\uFF08\u6536\u8D77 fade \u671F\u95F4\u5185\u8054 16px\uFF0C\u6700\u540E\u4E00\u6761\u5728\u9014\u6E10\u9690 settle
|
|
86
|
+
\u540E\u5F52\u96F6\uFF0C\u89C1 reconcileBlock / hasPendingCollapse\uFF09\u3002\u4E0D\u8BBE CSS transition
|
|
87
|
+
\u2014\u2014v13 \u7684\u8FC7\u6E21\u4E0E chip \u5143\u7D20\u751F\u547D\u5468\u671F\u968F\u673A\u4EA4\u4E92\uFF0C\u4EA7\u751F\u5C55\u5F00\u65B9\u5411\u53CC\u91CD\u4EBA\u683C
|
|
88
|
+
\uFF08\u590D\u7528\u5143\u7D20\u7F13\u52A8\u4E0B\u63A8\u4E09\u7EA7\u884C vs \u65B0\u5EFA\u5143\u7D20\u77AC\u5F00\uFF09\uFF0C\u540C\u7C7B\u578B\u5757\u4E0D\u4E00\u81F4\u3002 */
|
|
71
89
|
}
|
|
72
90
|
.dshcf-chip[aria-expanded="true"],
|
|
73
91
|
.dshcf-chip.dshcf-has-body {
|
|
@@ -328,8 +346,16 @@ var __dshcfBundle = (() => {
|
|
|
328
346
|
}
|
|
329
347
|
`;
|
|
330
348
|
var FoldController = class {
|
|
331
|
-
constructor() {
|
|
349
|
+
constructor(statusTextProvider2, options) {
|
|
332
350
|
this.observer = null;
|
|
351
|
+
/** 防止同一个控制器重复注册 observer、定时器和可见性监听。 */
|
|
352
|
+
this.started = false;
|
|
353
|
+
/** body 尚未创建时等待 DOMContentLoaded,再补一次启动。 */
|
|
354
|
+
this.waitingForBody = false;
|
|
355
|
+
this.onDomContentLoaded = () => {
|
|
356
|
+
this.waitingForBody = false;
|
|
357
|
+
this.start();
|
|
358
|
+
};
|
|
333
359
|
this.raf = 0;
|
|
334
360
|
this.timer = 0;
|
|
335
361
|
this.disposed = false;
|
|
@@ -351,18 +377,72 @@ var __dshcfBundle = (() => {
|
|
|
351
377
|
this.segmentStates = /* @__PURE__ */ new Map();
|
|
352
378
|
/** segment 首次观察到 running 的时间,用于没有官方时长的实时回合。 */
|
|
353
379
|
this.runningSince = /* @__PURE__ */ new Map();
|
|
380
|
+
/** 曾完成过的 segment key:段恢复运行时据此重开本地计时,防止重新结算
|
|
381
|
+
* 的本地时长吞掉完成间隙。 */
|
|
382
|
+
this.completedOnce = /* @__PURE__ */ new Set();
|
|
354
383
|
/** 插件改写 display 前的精确原值;受控集合用于分类漂移和 stop() 恢复。 */
|
|
355
384
|
this.originalDisplay = /* @__PURE__ */ new WeakMap();
|
|
356
385
|
this.controlledDisplay = /* @__PURE__ */ new Set();
|
|
357
|
-
/**
|
|
386
|
+
/** 元素 → 插件最后确保的 display 值:恢复前与当前内联值比对,漂移即视为
|
|
387
|
+
* 外部介入(镜像 turnStatusTexts 的 original/written 双快照守卫,issue #11)。 */
|
|
388
|
+
this.writtenDisplay = /* @__PURE__ */ new WeakMap();
|
|
389
|
+
/** 被改写为状态提示词的原生状态文本:original = 宿主原文(卸载还原用),
|
|
390
|
+
* written = 插件最后一次写入的值(仅当节点仍等于它时才还原,避免覆盖
|
|
391
|
+
* 宿主在插件写入之后的状态更新)。 */
|
|
358
392
|
this.turnStatusTexts = /* @__PURE__ */ new Map();
|
|
393
|
+
/** 正文判定缓存(消息元素 → 有无正文):流式期间只有被 mutation 命中的
|
|
394
|
+
* 消息失效重算,历史消息跨 pass 复用,避免每帧全量 TreeWalker。 */
|
|
395
|
+
this.bodyTextCache = /* @__PURE__ */ new WeakMap();
|
|
396
|
+
/** 自上次 pass 以来子树发生变化的 flow 顶层消息;pass 开头统一失效。 */
|
|
397
|
+
this.dirtyMessages = /* @__PURE__ */ new Set();
|
|
398
|
+
/** 在途显示动画(元素 → 记录):冲突仲裁、记账对齐与生命周期清理的依据。
|
|
399
|
+
* 用 Map 不用 WeakMap——switchFlow/stop 需要遍历全量 cancel。 */
|
|
400
|
+
this.pendingAnims = /* @__PURE__ */ new Map();
|
|
401
|
+
/** 手势点击的一次性可动画 block key;segment 级点击另保留中间正文的门控。 */
|
|
402
|
+
this.animatableKeys = /* @__PURE__ */ new Set();
|
|
403
|
+
/** segment 点击时只让点击前已存在的 block 播放 reveal;流式中新出现的
|
|
404
|
+
* 临时分裂块直接显示,避免分类收敛时留下半透明 stale chip。 */
|
|
405
|
+
this.animatableSegmentBlocks = /* @__PURE__ */ new Map();
|
|
406
|
+
/** 外部变更对账定时器句柄(自重排 setTimeout 链,见 armAuditLoop)。 */
|
|
407
|
+
this.auditTimer = 0;
|
|
408
|
+
/** 上一轮 pass 记录的关键元素内联 display,用于 audit 轻量检测漂移。
|
|
409
|
+
* audit 只读这份快照,不在页面稳定时重新执行完整 pass。 */
|
|
410
|
+
this.auditDisplays = /* @__PURE__ */ new Map();
|
|
411
|
+
/** 自上次 pass 以来 flow 子树发生过结构变化(childList)或正文判定翻转:
|
|
412
|
+
* 为 true 时 pass 重建分块快照,否则复用 currentBlocks——characterData/
|
|
413
|
+
* attributes 批次(流式文本、data-state 翻转)不改变块结构,跳过全量
|
|
414
|
+
* querySelectorAll 重扫(issue #14:长会话下每轮重扫造成主线程卡顿)。 */
|
|
415
|
+
this.structureDirty = true;
|
|
416
|
+
/** 滚动稳定化(issue #14):flow 最近的滚动容器缓存(按 flow 身份失效)。 */
|
|
417
|
+
this.scrollContainer = null;
|
|
418
|
+
this.scrollContainerFlow = null;
|
|
419
|
+
/** 回到前台立即补一轮对账;后台 tab 由 document.hidden 门控跳过。 */
|
|
420
|
+
this.onVisibilityChange = () => {
|
|
421
|
+
if (typeof document === "undefined" || document.hidden !== true) this.schedule();
|
|
422
|
+
};
|
|
423
|
+
this.statusTextProvider = statusTextProvider2 ?? (() => DEFAULT_STATUS_TEXT);
|
|
424
|
+
this.auditIntervalMs = options?.auditIntervalMs ?? AUDIT_TICK_MS;
|
|
425
|
+
}
|
|
426
|
+
/** 设置变更后重跑一轮,让状态提示词立即生效。 */
|
|
427
|
+
refresh() {
|
|
428
|
+
this.schedule();
|
|
359
429
|
}
|
|
360
430
|
start() {
|
|
361
|
-
if (this.disposed) return;
|
|
431
|
+
if (this.disposed || this.started || this.waitingForBody) return;
|
|
432
|
+
if (typeof document === "undefined") return;
|
|
433
|
+
if (document.body === null) {
|
|
434
|
+
if (typeof document.addEventListener !== "function") return;
|
|
435
|
+
this.waitingForBody = true;
|
|
436
|
+
document.addEventListener("DOMContentLoaded", this.onDomContentLoaded, { once: true });
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
362
439
|
injectStyle();
|
|
363
440
|
try {
|
|
364
441
|
this.observer = new MutationObserver((records) => {
|
|
365
|
-
if (this.shouldSchedule(records))
|
|
442
|
+
if (this.shouldSchedule(records)) {
|
|
443
|
+
this.markDirty(records);
|
|
444
|
+
this.schedule();
|
|
445
|
+
}
|
|
366
446
|
});
|
|
367
447
|
this.observer.observe(document.body, {
|
|
368
448
|
childList: true,
|
|
@@ -375,16 +455,86 @@ var __dshcfBundle = (() => {
|
|
|
375
455
|
// 不会自激。
|
|
376
456
|
characterData: true
|
|
377
457
|
});
|
|
458
|
+
this.started = true;
|
|
459
|
+
this.armAuditLoop();
|
|
378
460
|
this.schedule();
|
|
379
461
|
} catch (error) {
|
|
462
|
+
this.observer?.disconnect();
|
|
463
|
+
this.observer = null;
|
|
464
|
+
this.started = false;
|
|
380
465
|
this.reportError(error);
|
|
381
466
|
throw error;
|
|
382
467
|
}
|
|
383
468
|
}
|
|
469
|
+
/** 外部显示变更对账循环(issue #11 Bug B):外部对宿主行的 style 写入不产生
|
|
470
|
+
* observer record(style 不在 attributeFilter 内,监听会因插件自身直写 style
|
|
471
|
+
* 自激),改用低频轻量对账兜底——发现漂移后才由 pass 收敛;
|
|
472
|
+
* 后台 tab 由 document.hidden 门控跳过,回前台由 visibilitychange 立即补一轮。
|
|
473
|
+
* 用自重排 setTimeout 链而非 setInterval:与 schedule 的兜底定时器同源,
|
|
474
|
+
* 测试桩 clearTimers 后链条自然熄灭。 */
|
|
475
|
+
armAuditLoop() {
|
|
476
|
+
if (typeof document !== "undefined" && typeof document.addEventListener === "function") {
|
|
477
|
+
document.addEventListener("visibilitychange", this.onVisibilityChange);
|
|
478
|
+
}
|
|
479
|
+
this.rearmAudit();
|
|
480
|
+
}
|
|
481
|
+
rearmAudit() {
|
|
482
|
+
if (this.disposed || this.auditTimer !== 0) return;
|
|
483
|
+
this.auditTimer = setTimeout(() => {
|
|
484
|
+
this.auditTimer = 0;
|
|
485
|
+
if (this.disposed) return;
|
|
486
|
+
if (typeof document !== "undefined" && document.hidden === true) {
|
|
487
|
+
this.rearmAudit();
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
this.audit();
|
|
491
|
+
this.rearmAudit();
|
|
492
|
+
}, this.auditIntervalMs);
|
|
493
|
+
}
|
|
494
|
+
/** 低成本显示状态对账:只有发现外部漂移时才启动完整 pass。
|
|
495
|
+
*
|
|
496
|
+
* 外部 style.display 写入不会产生当前 observer 的 attribute 记录,
|
|
497
|
+
* 因此仍保留 audit;但稳定页面不应每秒重扫整个 flow。快照只覆盖
|
|
498
|
+
* flow 顶层行、插件控制中的宿主行和插件自有展示行,避免引入布局读取。 */
|
|
499
|
+
audit() {
|
|
500
|
+
if (this.disposed) return;
|
|
501
|
+
const flow = this.flow;
|
|
502
|
+
if (flow === null || !flow.isConnected) {
|
|
503
|
+
if (flow !== null) this.schedule();
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const current = this.collectAuditDisplays(flow);
|
|
507
|
+
if (current.size !== this.auditDisplays.size) {
|
|
508
|
+
this.schedule();
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
for (const [el, display] of current) {
|
|
512
|
+
if (this.auditDisplays.get(el) !== display) {
|
|
513
|
+
this.schedule();
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (this.controlledDisplay.has(el) && this.displayForeign(el)) {
|
|
517
|
+
this.schedule();
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
384
522
|
stop() {
|
|
385
523
|
this.disposed = true;
|
|
524
|
+
this.started = false;
|
|
525
|
+
this.waitingForBody = false;
|
|
526
|
+
if (typeof document !== "undefined" && typeof document.removeEventListener === "function") {
|
|
527
|
+
document.removeEventListener("DOMContentLoaded", this.onDomContentLoaded);
|
|
528
|
+
}
|
|
386
529
|
if (this.raf !== 0) cancelAnimationFrame(this.raf);
|
|
387
530
|
if (this.timer !== 0) clearTimeout(this.timer);
|
|
531
|
+
if (this.auditTimer !== 0) {
|
|
532
|
+
clearTimeout(this.auditTimer);
|
|
533
|
+
this.auditTimer = 0;
|
|
534
|
+
}
|
|
535
|
+
if (typeof document !== "undefined" && typeof document.removeEventListener === "function") {
|
|
536
|
+
document.removeEventListener("visibilitychange", this.onVisibilityChange);
|
|
537
|
+
}
|
|
388
538
|
this.observer?.disconnect();
|
|
389
539
|
this.switchFlow(null);
|
|
390
540
|
removeStyle();
|
|
@@ -392,7 +542,51 @@ var __dshcfBundle = (() => {
|
|
|
392
542
|
/** body 级 observer 只负责发现 flow 替换;已有 flow 外的文本变化不再触发全量扫描。 */
|
|
393
543
|
shouldSchedule(records) {
|
|
394
544
|
if (records.length === 0 || this.flow === null || !this.flow.isConnected) return true;
|
|
395
|
-
return records.some((record) =>
|
|
545
|
+
return records.some((record) => this.isRelevantMutation(record));
|
|
546
|
+
}
|
|
547
|
+
/** 判断 mutation 是否会影响宿主 flow;插件自有节点的回写直接忽略,
|
|
548
|
+
* 避免“pass 插入 chip → observer 再开一轮 pass”的自激循环。 */
|
|
549
|
+
isRelevantMutation(record) {
|
|
550
|
+
const flow = this.flow;
|
|
551
|
+
if (flow === null || !flow.isConnected) return true;
|
|
552
|
+
if (!nodeWithin(record.target, flow) && !nodeWithin(flow, record.target)) return false;
|
|
553
|
+
if (record.type === "characterData" && record.target instanceof Text && this.turnStatusTexts.has(record.target)) {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
const changed = [
|
|
557
|
+
...Array.from(record.addedNodes ?? []),
|
|
558
|
+
...Array.from(record.removedNodes ?? [])
|
|
559
|
+
];
|
|
560
|
+
if (changed.length > 0 && changed.every(isPluginOwnedNode)) return false;
|
|
561
|
+
if (changed.length === 0 && isPluginOwnedNode(record.target)) return false;
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
564
|
+
/** 记录本批 mutation 命中的 flow 顶层消息,供正文判定缓存定向失效。
|
|
565
|
+
* 从 record.target 沿 parentNode 走到 flow 的直接子级即所属消息。
|
|
566
|
+
* 失效粒度(issue #14):只有 childList 使分块快照失效;flow 直挂层的
|
|
567
|
+
* 插件节点/文本节点与 flow 外的混批记录不影响任何消息的正文判定,跳过
|
|
568
|
+
* 而非全量失效——旧逻辑把它们全部放大成 O(全会话) 的 TreeWalker 重扫,
|
|
569
|
+
* 长会话流式期间每帧如此。空批次仍保守全量失效(测试桩的调度通知)。 */
|
|
570
|
+
markDirty(records) {
|
|
571
|
+
const flow = this.flow;
|
|
572
|
+
if (flow === null || !flow.isConnected) return;
|
|
573
|
+
if (records.length === 0) {
|
|
574
|
+
this.bodyTextCache = /* @__PURE__ */ new WeakMap();
|
|
575
|
+
this.dirtyMessages.clear();
|
|
576
|
+
this.structureDirty = true;
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
for (const record of records.filter((record2) => this.isRelevantMutation(record2))) {
|
|
580
|
+
if (record.type === "childList") this.structureDirty = true;
|
|
581
|
+
if (record.target === flow) {
|
|
582
|
+
for (const node of record.addedNodes ?? []) {
|
|
583
|
+
if (node instanceof HTMLElement) this.dirtyMessages.add(node);
|
|
584
|
+
}
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
const owner = flowChildOwner(record.target, flow);
|
|
588
|
+
if (owner !== null) this.dirtyMessages.add(owner);
|
|
589
|
+
}
|
|
396
590
|
}
|
|
397
591
|
schedule() {
|
|
398
592
|
if (this.disposed || this.raf !== 0) return;
|
|
@@ -424,6 +618,9 @@ var __dshcfBundle = (() => {
|
|
|
424
618
|
style?.removeAttribute("data-dshcf-error");
|
|
425
619
|
} catch (error) {
|
|
426
620
|
this.reportError(error);
|
|
621
|
+
} finally {
|
|
622
|
+
this.animatableKeys.clear();
|
|
623
|
+
this.animatableSegmentBlocks.clear();
|
|
427
624
|
}
|
|
428
625
|
}
|
|
429
626
|
reportError(error) {
|
|
@@ -442,12 +639,27 @@ var __dshcfBundle = (() => {
|
|
|
442
639
|
if (nextFlow !== this.flow) this.switchFlow(nextFlow);
|
|
443
640
|
const flow = this.flow;
|
|
444
641
|
if (flow === null) return;
|
|
445
|
-
|
|
642
|
+
let bodyFlipped = false;
|
|
643
|
+
for (const el of this.dirtyMessages) {
|
|
644
|
+
const prev = this.bodyTextCache.get(el);
|
|
645
|
+
this.bodyTextCache.delete(el);
|
|
646
|
+
if (prev !== void 0 && this.hasBodyCached(el) !== prev) bodyFlipped = true;
|
|
647
|
+
}
|
|
648
|
+
this.dirtyMessages.clear();
|
|
649
|
+
const rebuildBlocks = this.structureDirty || bodyFlipped || this.currentBlocks.size === 0;
|
|
650
|
+
this.structureDirty = false;
|
|
651
|
+
const blocks = rebuildBlocks ? findBlocks(flow, (el) => this.hasBodyCached(el)) : [...this.currentBlocks.values()];
|
|
446
652
|
this.currentBlocks = new Map(blocks.map((block) => [block.key, block]));
|
|
447
|
-
const segments = buildSegments(flow, blocks);
|
|
653
|
+
const segments = buildSegments(flow, blocks, (el) => this.hasBodyCached(el));
|
|
448
654
|
const liveSegmentKeys = new Set(segments.map((segment) => segment.key));
|
|
655
|
+
const scrollAnchor = this.captureScrollAnchor(flow);
|
|
449
656
|
for (const segment of segments) {
|
|
450
|
-
if (segment.running
|
|
657
|
+
if (!segment.running) continue;
|
|
658
|
+
if (this.completedOnce.has(segment.key)) {
|
|
659
|
+
this.completedOnce.delete(segment.key);
|
|
660
|
+
this.runningSince.delete(segment.key);
|
|
661
|
+
}
|
|
662
|
+
if (!this.runningSince.has(segment.key)) {
|
|
451
663
|
this.runningSince.set(segment.key, Date.now());
|
|
452
664
|
}
|
|
453
665
|
}
|
|
@@ -455,6 +667,7 @@ var __dshcfBundle = (() => {
|
|
|
455
667
|
for (const snapshot of segments) {
|
|
456
668
|
if (!snapshot.closed || snapshot.running || !snapshot.hasWork) continue;
|
|
457
669
|
completedKeys.add(snapshot.key);
|
|
670
|
+
this.completedOnce.add(snapshot.key);
|
|
458
671
|
let state = this.segmentStates.get(snapshot.key);
|
|
459
672
|
if (state === void 0) {
|
|
460
673
|
state = { key: snapshot.key, row: null, expanded: false, snapshot };
|
|
@@ -465,7 +678,7 @@ var __dshcfBundle = (() => {
|
|
|
465
678
|
const started = this.runningSince.get(snapshot.key);
|
|
466
679
|
const parsed = snapshot.boundary === null ? void 0 : parseTurnDuration(snapshot.boundary);
|
|
467
680
|
if (parsed !== void 0) state.duration = parsed;
|
|
468
|
-
else if (started !== void 0) state.duration = Date.now() - started;
|
|
681
|
+
else if (state.duration === void 0 && started !== void 0) state.duration = Date.now() - started;
|
|
469
682
|
if (state.row === null || !state.row.isConnected) state.row = this.createProcessedRow(state);
|
|
470
683
|
this.syncProcessedRow(state);
|
|
471
684
|
}
|
|
@@ -487,9 +700,10 @@ var __dshcfBundle = (() => {
|
|
|
487
700
|
for (const segment of segments) {
|
|
488
701
|
const state = this.segmentStates.get(segment.key);
|
|
489
702
|
const collapse = state !== void 0 && !state.expanded;
|
|
703
|
+
const animate = this.animatableKeys.has(segment.key);
|
|
490
704
|
for (const middle of segment.middleSteps) {
|
|
491
|
-
if (collapse) this.hideElement(middle, desiredHidden);
|
|
492
|
-
else this.restoreElement(middle);
|
|
705
|
+
if (collapse) this.hideElement(middle, desiredHidden, animate);
|
|
706
|
+
else this.restoreElement(middle, animate);
|
|
493
707
|
}
|
|
494
708
|
if (segment.finalStep !== null) this.restoreElement(segment.finalStep);
|
|
495
709
|
}
|
|
@@ -507,17 +721,81 @@ var __dshcfBundle = (() => {
|
|
|
507
721
|
this.cleanupStaleChips(seenBlocks);
|
|
508
722
|
this.restoreUnusedDisplays(desiredHidden);
|
|
509
723
|
for (const state of this.segmentStates.values()) this.placeProcessedRow(flow, state);
|
|
724
|
+
this.stabilizeScrollAfterFold(scrollAnchor);
|
|
510
725
|
for (const key of [...this.runningSince.keys()]) {
|
|
511
726
|
if (!liveSegmentKeys.has(key)) this.runningSince.delete(key);
|
|
512
727
|
}
|
|
728
|
+
for (const key of [...this.completedOnce]) {
|
|
729
|
+
if (!liveSegmentKeys.has(key)) this.completedOnce.delete(key);
|
|
730
|
+
}
|
|
513
731
|
for (const [node] of [...this.turnStatusTexts]) {
|
|
514
732
|
if (!node.isConnected) this.turnStatusTexts.delete(node);
|
|
515
733
|
}
|
|
516
|
-
|
|
734
|
+
for (const [el] of [...this.pendingAnims]) {
|
|
735
|
+
if (!el.isConnected) this.pendingAnims.delete(el);
|
|
736
|
+
}
|
|
737
|
+
const statusText = this.statusTextProvider();
|
|
738
|
+
if (statusText === void 0 || statusText === "") {
|
|
739
|
+
restoreTurnStatus(this.turnStatusTexts);
|
|
740
|
+
} else {
|
|
741
|
+
replaceTurnStatus(flow, this.turnStatusTexts, statusText);
|
|
742
|
+
}
|
|
743
|
+
this.captureAuditDisplays(flow);
|
|
744
|
+
}
|
|
745
|
+
/** flow 最近的滚动容器(issue #14 滚动稳定化的测量基准):沿父链找第一个
|
|
746
|
+
* overflow-y 为 auto/scroll 且实际可滚动的祖先。结果按 flow 身份缓存;
|
|
747
|
+
* 找不到时不缓存(内容增长后祖先可能变为可滚动),每 pass 重探的代价
|
|
748
|
+
* 只是几次 clean-layout 的 computed style / scrollHeight 读取。 */
|
|
749
|
+
findScrollContainer(flow) {
|
|
750
|
+
if (this.scrollContainerFlow === flow && this.scrollContainer !== null && this.scrollContainer.isConnected) {
|
|
751
|
+
return this.scrollContainer;
|
|
752
|
+
}
|
|
753
|
+
let node = flow.parentElement;
|
|
754
|
+
while (node !== null) {
|
|
755
|
+
if (typeof getComputedStyle === "function") {
|
|
756
|
+
const oy = getComputedStyle(node).overflowY;
|
|
757
|
+
if ((oy === "auto" || oy === "scroll") && node.scrollHeight > node.clientHeight) break;
|
|
758
|
+
}
|
|
759
|
+
node = node.parentElement;
|
|
760
|
+
}
|
|
761
|
+
if (node === null) return null;
|
|
762
|
+
this.scrollContainer = node;
|
|
763
|
+
this.scrollContainerFlow = flow;
|
|
764
|
+
return node;
|
|
765
|
+
}
|
|
766
|
+
/** 几何写入前捕捉贴底意图(issue #14):视口距底 ≤ 上游 FOLLOW_THRESHOLD
|
|
767
|
+
* 时返回锚点,stabilizeScrollAfterFold 在写入后同帧钉回底部。远离底部
|
|
768
|
+
* (用户正在滚动浏览)时不干预——视口上方的高度变化由浏览器 scroll
|
|
769
|
+
* anchoring 补偿,视口下方的折叠不可见,插件再写 scrollTop 只会加入
|
|
770
|
+
* 上游吸底回写的拉锯。 */
|
|
771
|
+
captureScrollAnchor(flow) {
|
|
772
|
+
const scroller = this.findScrollContainer(flow);
|
|
773
|
+
if (scroller === null) return null;
|
|
774
|
+
const dist = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight;
|
|
775
|
+
return dist <= STICK_BOTTOM_THRESHOLD_PX ? { el: scroller } : null;
|
|
776
|
+
}
|
|
777
|
+
/** 几何写入后把贴底视口钉回底部:折叠让 scrollHeight 缩小时若不在此帧
|
|
778
|
+
* 补写 scrollTop,宿主 ChatView 要到下一帧 ResizeObserver 才吸底回写,
|
|
779
|
+
* 中间的空档让触控板惯性滚动乘虚而入,反复折叠时表现为上下抽搐(用户
|
|
780
|
+
* 实测:长会话滚到底部附近无法稳定定位)。同一帧内钉回后,宿主的吸底
|
|
781
|
+
* 回写成为幂等 no-op,不再是第二个 scrollTop 写入方。 */
|
|
782
|
+
stabilizeScrollAfterFold(anchor) {
|
|
783
|
+
if (anchor === null) return;
|
|
784
|
+
const el = anchor.el;
|
|
785
|
+
if (!el.isConnected) return;
|
|
786
|
+
const dist = el.scrollHeight - el.scrollTop - el.clientHeight;
|
|
787
|
+
if (dist > STICK_BOTTOM_THRESHOLD_PX) el.scrollTop = el.scrollHeight - el.clientHeight;
|
|
517
788
|
}
|
|
518
789
|
/** flow 元素变化即视为会话切换:完整恢复旧 flow,再从新 DOM 重建。 */
|
|
519
790
|
switchFlow(next) {
|
|
520
791
|
if (next === this.flow) return;
|
|
792
|
+
for (const [el, record] of this.pendingAnims) {
|
|
793
|
+
record.anim.cancel();
|
|
794
|
+
if (record.kind === "height") this.clearCollapseLock(el);
|
|
795
|
+
}
|
|
796
|
+
this.pendingAnims.clear();
|
|
797
|
+
this.animatableKeys.clear();
|
|
798
|
+
this.animatableSegmentBlocks.clear();
|
|
521
799
|
for (const record of this.chips.values()) record.chip.remove();
|
|
522
800
|
this.chips.clear();
|
|
523
801
|
for (const host of [...this.mergedThinks.keys()]) this.removeMergedThink(host);
|
|
@@ -526,6 +804,11 @@ var __dshcfBundle = (() => {
|
|
|
526
804
|
this.currentBlocks.clear();
|
|
527
805
|
this.blockExpanded.clear();
|
|
528
806
|
this.runningSince.clear();
|
|
807
|
+
this.completedOnce.clear();
|
|
808
|
+
this.bodyTextCache = /* @__PURE__ */ new WeakMap();
|
|
809
|
+
this.dirtyMessages.clear();
|
|
810
|
+
this.auditDisplays.clear();
|
|
811
|
+
this.structureDirty = true;
|
|
529
812
|
this.restoreAllDisplays();
|
|
530
813
|
restoreTurnStatus(this.turnStatusTexts);
|
|
531
814
|
this.flow = next;
|
|
@@ -534,6 +817,8 @@ var __dshcfBundle = (() => {
|
|
|
534
817
|
const row = createProcessedRowElement(state.duration);
|
|
535
818
|
row.addEventListener("click", () => {
|
|
536
819
|
state.expanded = !state.expanded;
|
|
820
|
+
this.animatableKeys.add(state.key);
|
|
821
|
+
this.animatableSegmentBlocks.set(state.key, new Set(state.snapshot.blocks.map((block) => block.key)));
|
|
537
822
|
if (state.expanded) {
|
|
538
823
|
for (const block of state.snapshot.blocks) {
|
|
539
824
|
this.blockExpanded.set(block.key, false);
|
|
@@ -564,7 +849,11 @@ var __dshcfBundle = (() => {
|
|
|
564
849
|
return;
|
|
565
850
|
}
|
|
566
851
|
let target = state.snapshot.firstWork ?? state.snapshot.finalStep ?? state.snapshot.boundary;
|
|
567
|
-
if (target === null || target.parentElement !== flow)
|
|
852
|
+
if (target === null || target.parentElement !== flow) {
|
|
853
|
+
row.remove();
|
|
854
|
+
state.row = null;
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
568
857
|
while (target.previousElementSibling?.classList.contains("dshcf-flow-chip") === true) {
|
|
569
858
|
target = target.previousElementSibling;
|
|
570
859
|
}
|
|
@@ -572,40 +861,68 @@ var __dshcfBundle = (() => {
|
|
|
572
861
|
}
|
|
573
862
|
reconcileBlock(block, segment, desiredHidden) {
|
|
574
863
|
const state = segment === null ? void 0 : this.segmentStates.get(segment.key);
|
|
864
|
+
const segmentAnimatableBlocks = segment === null ? void 0 : this.animatableSegmentBlocks.get(segment.key);
|
|
865
|
+
const animate = this.animatableKeys.has(block.key) || segment !== null && this.animatableKeys.has(segment.key) && (segmentAnimatableBlocks === void 0 || segmentAnimatableBlocks.has(block.key));
|
|
575
866
|
const levelCollapsed = state !== void 0 && !state.expanded;
|
|
576
867
|
if (levelCollapsed) {
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
for (const row of block.rows) this.hideElement(row, desiredHidden);
|
|
580
|
-
for (const container of block.containers) this.hideElement(container, desiredHidden);
|
|
581
|
-
const keepHost = segment?.finalStep === block.host && hasBodyContent(block.host);
|
|
868
|
+
const keepHost = segment?.finalStep === block.host && this.hasBodyCached(block.host);
|
|
869
|
+
let hostFade = false;
|
|
582
870
|
if (keepHost) this.restoreElement(block.host);
|
|
583
|
-
else this.hideElement(block.host, desiredHidden);
|
|
584
|
-
|
|
871
|
+
else hostFade = this.hideElement(block.host, desiredHidden, animate);
|
|
872
|
+
for (const container of block.containers) this.hideElement(container, desiredHidden, animate);
|
|
873
|
+
for (const row of block.rows) this.hideElement(row, desiredHidden, animate);
|
|
874
|
+
const existing = this.chips.get(block.key)?.chip;
|
|
875
|
+
if (existing !== void 0 && existing.style.display !== "none") {
|
|
876
|
+
existing.style.marginBottom = "";
|
|
877
|
+
if (block.mount === "before" || keepHost) {
|
|
878
|
+
if (animate && this.canAnimate(existing)) this.startFadeCollapse(existing);
|
|
879
|
+
else existing.style.display = "none";
|
|
880
|
+
} else if (!hostFade) {
|
|
881
|
+
existing.style.display = "none";
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
this.releaseMergedThink(block.host, animate);
|
|
585
885
|
return;
|
|
586
886
|
}
|
|
587
|
-
const chip = this.ensureChip(block);
|
|
588
|
-
this.restoreElement(block.host);
|
|
589
|
-
if (chip.style.display !== "") chip.style.display = "";
|
|
590
887
|
let expanded = this.blockExpanded.get(block.key) ?? false;
|
|
591
888
|
if (!expanded && block.rows.some((row) => row.hasAttribute("data-selected"))) {
|
|
592
889
|
expanded = true;
|
|
593
890
|
this.blockExpanded.set(block.key, true);
|
|
594
891
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
892
|
+
const chip = this.ensureChip(block);
|
|
893
|
+
const hostIsCollapsedRow = !expanded && block.rows.includes(block.host);
|
|
894
|
+
const hostWasHidden = block.host.style.display === "none";
|
|
895
|
+
const hostAnimate = !hostIsCollapsedRow && hostWasHidden && animate;
|
|
896
|
+
if (!hostIsCollapsedRow) this.restoreElement(block.host, hostAnimate);
|
|
897
|
+
const pendingChip = this.pendingAnims.get(chip);
|
|
898
|
+
if (pendingChip?.target === "hidden") this.cancelPendingSync(chip);
|
|
899
|
+
const chipWasHidden = chip.style.display === "none";
|
|
900
|
+
if (chip.style.display !== "") chip.style.display = "";
|
|
901
|
+
if (chipWasHidden && animate && !(hostAnimate && block.mount === "inside")) this.revealVisual(chip);
|
|
902
|
+
if (expanded || !this.hasPendingCollapse(block)) this.unpinChipMargin(chip);
|
|
903
|
+
const chipSettle = () => {
|
|
904
|
+
if (!this.hasPendingCollapse(block)) this.unpinChipMargin(chip);
|
|
905
|
+
};
|
|
599
906
|
for (const container of block.containers) {
|
|
600
|
-
if (expanded) this.restoreElement(container);
|
|
601
|
-
else
|
|
907
|
+
if (expanded) this.restoreElement(container, animate);
|
|
908
|
+
else {
|
|
909
|
+
const started = this.hideElement(container, desiredHidden, animate, chipSettle);
|
|
910
|
+
if (started) this.pinChipMargin(chip);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
for (const row of block.rows) {
|
|
914
|
+
if (expanded) this.restoreElement(row, animate);
|
|
915
|
+
else {
|
|
916
|
+
const started = this.hideElement(row, desiredHidden, animate, chipSettle);
|
|
917
|
+
if (started) this.pinChipMargin(chip);
|
|
918
|
+
}
|
|
602
919
|
}
|
|
603
920
|
if (expanded && block.rows.length > 1 && block.rows.every((row) => isThinkRow(row))) {
|
|
604
|
-
this.syncMergedThink(block.host, block.rows, desiredHidden);
|
|
921
|
+
this.syncMergedThink(block.host, block.rows, desiredHidden, animate);
|
|
605
922
|
} else {
|
|
606
|
-
this.
|
|
923
|
+
if (this.releaseMergedThink(block.host, animate, chipSettle)) this.pinChipMargin(chip);
|
|
607
924
|
}
|
|
608
|
-
chip.classList.toggle("dshcf-has-body", block.mount === "inside" &&
|
|
925
|
+
chip.classList.toggle("dshcf-has-body", block.mount === "inside" && this.hasBodyCached(block.host));
|
|
609
926
|
updateChip(chip, block.rows, expanded);
|
|
610
927
|
}
|
|
611
928
|
ensureChip(block) {
|
|
@@ -623,14 +940,16 @@ var __dshcfBundle = (() => {
|
|
|
623
940
|
chip2.setAttribute("data-dshcf-block-key", block.key);
|
|
624
941
|
const leading = document.createElement("span");
|
|
625
942
|
leading.className = "dshcf-leading";
|
|
626
|
-
leading.appendChild(
|
|
943
|
+
leading.appendChild(createCommandIcon());
|
|
627
944
|
chip2.appendChild(leading);
|
|
628
945
|
chip2.appendChild(createSpan("dshcf-chip-title"));
|
|
629
946
|
chip2.appendChild(createSpan("dshcf-chip-sep"));
|
|
630
947
|
chip2.appendChild(createSpan("dshcf-chip-summary"));
|
|
631
948
|
chip2.appendChild(createChevronIcon("dshcf-chevron"));
|
|
949
|
+
chip2.style.display = "none";
|
|
632
950
|
chip2.addEventListener("click", () => {
|
|
633
951
|
this.blockExpanded.set(block.key, !(this.blockExpanded.get(block.key) ?? false));
|
|
952
|
+
this.animatableKeys.add(block.key);
|
|
634
953
|
this.schedule();
|
|
635
954
|
});
|
|
636
955
|
record = { host: block.host, chip: chip2 };
|
|
@@ -648,7 +967,10 @@ var __dshcfBundle = (() => {
|
|
|
648
967
|
}
|
|
649
968
|
suppressBlock(block, desiredHidden) {
|
|
650
969
|
const existing = this.chips.get(block.key)?.chip;
|
|
651
|
-
if (existing !== void 0 && existing.style.display !== "none")
|
|
970
|
+
if (existing !== void 0 && existing.style.display !== "none") {
|
|
971
|
+
existing.style.marginBottom = "";
|
|
972
|
+
existing.style.display = "none";
|
|
973
|
+
}
|
|
652
974
|
this.removeMergedThink(block.host);
|
|
653
975
|
this.retainDisplayControl(block.host, desiredHidden);
|
|
654
976
|
for (const row of block.rows) this.retainDisplayControl(row, desiredHidden);
|
|
@@ -668,7 +990,7 @@ var __dshcfBundle = (() => {
|
|
|
668
990
|
}
|
|
669
991
|
/** 连续思考合并行:插在第一个思考行前,标题用第一行思考内容;
|
|
670
992
|
* 点击切换显示/隐藏全部原始思考行。 */
|
|
671
|
-
syncMergedThink(host, rows, desiredHidden) {
|
|
993
|
+
syncMergedThink(host, rows, desiredHidden, animate = false) {
|
|
672
994
|
let row = this.mergedThinks.get(host);
|
|
673
995
|
if (row === void 0 || !row.isConnected) {
|
|
674
996
|
row = document.createElement("button");
|
|
@@ -682,14 +1004,21 @@ var __dshcfBundle = (() => {
|
|
|
682
1004
|
title.className = "dshcf-merged-title";
|
|
683
1005
|
const chevron = createChevronIcon("dshcf-chevron");
|
|
684
1006
|
row.append(leading, title, chevron);
|
|
1007
|
+
row.style.display = "none";
|
|
685
1008
|
const btn = row;
|
|
686
1009
|
btn.addEventListener("click", () => {
|
|
1010
|
+
if (this.mergedThinks.get(host) !== btn) return;
|
|
687
1011
|
const next = !this.mergedExpanded.has(host);
|
|
688
|
-
if (next)
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
1012
|
+
if (next) {
|
|
1013
|
+
if (this.expandMergedBody(host, btn)) {
|
|
1014
|
+
this.mergedExpanded.add(host);
|
|
1015
|
+
btn.setAttribute("aria-expanded", "true");
|
|
1016
|
+
}
|
|
1017
|
+
} else {
|
|
1018
|
+
this.mergedExpanded.delete(host);
|
|
1019
|
+
btn.setAttribute("aria-expanded", "false");
|
|
1020
|
+
this.collapseMergedBody(host);
|
|
1021
|
+
}
|
|
693
1022
|
});
|
|
694
1023
|
rows[0].before(row);
|
|
695
1024
|
this.mergedThinks.set(host, row);
|
|
@@ -711,42 +1040,155 @@ var __dshcfBundle = (() => {
|
|
|
711
1040
|
}
|
|
712
1041
|
const expanded = this.mergedExpanded.has(host);
|
|
713
1042
|
if (row.getAttribute("aria-expanded") !== String(expanded)) row.setAttribute("aria-expanded", String(expanded));
|
|
1043
|
+
const rowWasHidden = row.style.display === "none";
|
|
714
1044
|
if (row.style.display !== "") row.style.display = "";
|
|
1045
|
+
if (rowWasHidden && animate) this.revealVisual(row);
|
|
715
1046
|
for (const r of rows) this.hideElement(r, desiredHidden);
|
|
716
|
-
if (expanded)
|
|
1047
|
+
if (expanded) {
|
|
1048
|
+
const result = this.ensureMergedBody(host, row, false);
|
|
1049
|
+
if (result !== null && result.created && animate) this.revealMergedBody(result.body);
|
|
1050
|
+
}
|
|
717
1051
|
}
|
|
718
1052
|
/** 展开合并行:直接读各思考行文本合成内容块(不依赖原生行展开:
|
|
719
|
-
* 程序化 click 不触发 React 展开,且后台 tab 的 rAF 不执行)。
|
|
1053
|
+
* 程序化 click 不触发 React 展开,且后台 tab 的 rAF 不执行)。
|
|
1054
|
+
* 返回是否成功——思考行已不可读(parts 为空)时返回 false,调用方
|
|
1055
|
+
* 据此保持收起态,避免展开状态与内容块脱节。 */
|
|
720
1056
|
expandMergedBody(host, btn) {
|
|
721
1057
|
const cached = this.mergedBodyTexts.get(host);
|
|
722
|
-
if (cached
|
|
723
|
-
this.
|
|
724
|
-
return;
|
|
1058
|
+
if (cached === void 0) {
|
|
1059
|
+
const parts = this.currentThinkRows(host).map((r) => r.textContent.replace(/^Think\s*/, "").trim()).filter(Boolean);
|
|
1060
|
+
if (parts.length === 0) return false;
|
|
1061
|
+
this.mergedBodyTexts.set(host, parts.join("\n\n"));
|
|
1062
|
+
}
|
|
1063
|
+
const result = this.ensureMergedBody(host, btn, true);
|
|
1064
|
+
if (result === null) return false;
|
|
1065
|
+
if (result.created) {
|
|
1066
|
+
this.revealMergedBody(result.body);
|
|
1067
|
+
} else {
|
|
1068
|
+
this.cancelPendingSync(result.body);
|
|
725
1069
|
}
|
|
726
|
-
|
|
727
|
-
if (parts.length === 0) return;
|
|
728
|
-
this.mergedBodyTexts.set(host, parts.join("\n\n"));
|
|
729
|
-
this.ensureMergedBody(host, btn, true);
|
|
1070
|
+
return true;
|
|
730
1071
|
}
|
|
731
|
-
/** 创建/更新合并内容块(缓存优先,不重新展开原生行)。
|
|
1072
|
+
/** 创建/更新合并内容块(缓存优先,不重新展开原生行)。
|
|
1073
|
+
* 返回内容块与其是否为本次新建(新建才走展开动画)。 */
|
|
732
1074
|
ensureMergedBody(host, btn, force) {
|
|
733
1075
|
const cached = this.mergedBodyTexts.get(host);
|
|
734
|
-
if (cached === void 0) return;
|
|
1076
|
+
if (cached === void 0) return null;
|
|
735
1077
|
let body = btn.nextElementSibling;
|
|
1078
|
+
let created = false;
|
|
736
1079
|
if (body === null || !body.classList.contains("dshcf-merged-body")) {
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
btn.after(
|
|
1080
|
+
const next = document.createElement("div");
|
|
1081
|
+
next.className = "dshcf-merged-body";
|
|
1082
|
+
btn.after(next);
|
|
1083
|
+
body = next;
|
|
1084
|
+
created = true;
|
|
740
1085
|
}
|
|
741
1086
|
if (force || body.textContent !== cached) body.textContent = cached;
|
|
1087
|
+
return { body, created };
|
|
742
1088
|
}
|
|
743
|
-
/**
|
|
1089
|
+
/** 清理合并 think 行(v12):状态 map 立即清除;DOM 在手势动画路径下
|
|
1090
|
+
* 渐隐后移除(settle 回调),其余路径瞬删。渐隐中途被反向取消时元素
|
|
1091
|
+
* 保留,由后续 pass 的 syncMergedThink 重建/复用。
|
|
1092
|
+
* settle 透传给每个渐隐目标的移除回调之后(chip 间距钉住的结算探测点,
|
|
1093
|
+
* AI 评审 P0:merged 行渐隐不走 block.rows,必须纳入同一钉住体系)。 */
|
|
1094
|
+
releaseMergedThink(host, animate = false, settle) {
|
|
1095
|
+
const row = this.mergedThinks.get(host);
|
|
1096
|
+
this.mergedExpanded.delete(host);
|
|
1097
|
+
this.mergedBodyTexts.delete(host);
|
|
1098
|
+
if (row === void 0) return false;
|
|
1099
|
+
this.mergedThinks.delete(host);
|
|
1100
|
+
const body = row.nextElementSibling;
|
|
1101
|
+
const targets = body !== null && body.classList.contains("dshcf-merged-body") ? [row, body] : [row];
|
|
1102
|
+
if (animate && this.canAnimate(row)) {
|
|
1103
|
+
for (const t of targets) this.startFadeCollapse(t, () => {
|
|
1104
|
+
t.remove();
|
|
1105
|
+
settle?.();
|
|
1106
|
+
});
|
|
1107
|
+
return true;
|
|
1108
|
+
} else {
|
|
1109
|
+
for (const t of targets) t.remove();
|
|
1110
|
+
return false;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
/** merged-body 展开高度动画(机制样板:插件全资 DOM)。
|
|
1114
|
+
* 关键帧含 marginBottom 0→16px——其 CSS 有常量 margin-bottom:16px,
|
|
1115
|
+
* 高度从 0 起步时这 16px 会先占位产生小跳变。fill:'forwards' 托住终态,
|
|
1116
|
+
* onfinish 清内联后 cancel 释放,无闪烁窗口。收起由 collapseMergedBody
|
|
1117
|
+
* 做镜像高度卷下(同款账本与身份守卫),开合对称。 */
|
|
1118
|
+
revealMergedBody(body) {
|
|
1119
|
+
if (!this.canAnimate(body)) return;
|
|
1120
|
+
this.cancelPendingSync(body);
|
|
1121
|
+
const targetHeight = body.getBoundingClientRect().height;
|
|
1122
|
+
if (!(targetHeight > 0)) return;
|
|
1123
|
+
body.style.height = "0px";
|
|
1124
|
+
body.style.overflow = "hidden";
|
|
1125
|
+
body.style.marginBottom = "0px";
|
|
1126
|
+
const anim = body.animate(
|
|
1127
|
+
[
|
|
1128
|
+
{ height: "0px", marginBottom: "0px" },
|
|
1129
|
+
{ height: `${targetHeight}px`, marginBottom: "16px" }
|
|
1130
|
+
],
|
|
1131
|
+
{ duration: ANIM_DURATION_MS, easing: ANIM_EASING, fill: "forwards" }
|
|
1132
|
+
);
|
|
1133
|
+
const record = { anim, target: "visible", kind: "height" };
|
|
1134
|
+
this.pendingAnims.set(body, record);
|
|
1135
|
+
anim.onfinish = () => {
|
|
1136
|
+
if (this.pendingAnims.get(body) !== record) return;
|
|
1137
|
+
this.pendingAnims.delete(body);
|
|
1138
|
+
body.style.height = "";
|
|
1139
|
+
body.style.overflow = "";
|
|
1140
|
+
body.style.marginBottom = "";
|
|
1141
|
+
anim.cancel();
|
|
1142
|
+
this.schedule();
|
|
1143
|
+
};
|
|
1144
|
+
anim.oncancel = () => {
|
|
1145
|
+
if (this.pendingAnims.get(body) !== record) return;
|
|
1146
|
+
this.pendingAnims.delete(body);
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
/** 收起合并行:内容块高度卷下后移除——镜像 revealMergedBody 的唯一几何动画,
|
|
1150
|
+
* 开合对称。插件全资静态文本 DOM、无 React 协调竞争,可安全做几何收起
|
|
1151
|
+
* (与 seat 级拒绝盲卷的场景不同:那里是 React 混杂多卡片)。
|
|
1152
|
+
* reduced-motion / 无 WAAPI / 零高度降级为同步 remove()。 */
|
|
744
1153
|
collapseMergedBody(host) {
|
|
745
1154
|
const btn = this.mergedThinks.get(host);
|
|
746
|
-
if (btn
|
|
747
|
-
|
|
748
|
-
|
|
1155
|
+
if (btn === void 0) return;
|
|
1156
|
+
const body = btn.nextElementSibling;
|
|
1157
|
+
if (body === null || !body.classList.contains("dshcf-merged-body")) return;
|
|
1158
|
+
const el = body;
|
|
1159
|
+
if (!this.canAnimate(el)) {
|
|
1160
|
+
el.remove();
|
|
1161
|
+
return;
|
|
749
1162
|
}
|
|
1163
|
+
this.cancelPendingSync(el);
|
|
1164
|
+
const current = el.getBoundingClientRect().height;
|
|
1165
|
+
if (!(current > 0)) {
|
|
1166
|
+
el.remove();
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
el.style.height = `${current}px`;
|
|
1170
|
+
el.style.overflow = "hidden";
|
|
1171
|
+
const anim = el.animate(
|
|
1172
|
+
[
|
|
1173
|
+
{ height: `${current}px`, marginBottom: "16px" },
|
|
1174
|
+
{ height: "0px", marginBottom: "0px" }
|
|
1175
|
+
],
|
|
1176
|
+
{ duration: ANIM_DURATION_MS, easing: ANIM_EASING, fill: "forwards" }
|
|
1177
|
+
);
|
|
1178
|
+
const record = { anim, target: "hidden", kind: "height" };
|
|
1179
|
+
this.pendingAnims.set(el, record);
|
|
1180
|
+
anim.onfinish = () => {
|
|
1181
|
+
if (this.pendingAnims.get(el) !== record) return;
|
|
1182
|
+
this.pendingAnims.delete(el);
|
|
1183
|
+
el.remove();
|
|
1184
|
+
anim.cancel();
|
|
1185
|
+
this.schedule();
|
|
1186
|
+
};
|
|
1187
|
+
anim.oncancel = () => {
|
|
1188
|
+
if (this.pendingAnims.get(el) !== record) return;
|
|
1189
|
+
this.pendingAnims.delete(el);
|
|
1190
|
+
this.clearCollapseLock(el);
|
|
1191
|
+
};
|
|
750
1192
|
}
|
|
751
1193
|
/** 当前宿主内的思考行(现取,React 重渲染后引用仍然有效)。 */
|
|
752
1194
|
currentThinkRows(host) {
|
|
@@ -767,29 +1209,242 @@ var __dshcfBundle = (() => {
|
|
|
767
1209
|
this.mergedExpanded.delete(host);
|
|
768
1210
|
this.mergedBodyTexts.delete(host);
|
|
769
1211
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
1212
|
+
/** 正文判定(带缓存):同一消息子树未变时直接复用上次结果。失效由
|
|
1213
|
+
* markDirty(mutation 定向)与 switchFlow(整体重置)驱动;缓存的是
|
|
1214
|
+
* 纯文本/媒体存在性判定,与 display 状态无关,插件自身的显隐切换
|
|
1215
|
+
* 不会产生脏数据。 */
|
|
1216
|
+
hasBodyCached(el) {
|
|
1217
|
+
const cached = this.bodyTextCache.get(el);
|
|
1218
|
+
if (cached !== void 0) return cached;
|
|
1219
|
+
const value = hasBodyContent(el);
|
|
1220
|
+
this.bodyTextCache.set(el, value);
|
|
1221
|
+
return value;
|
|
1222
|
+
}
|
|
1223
|
+
/** 本块是否有在途收起渐隐(rows/containers/merged 行/body 任一)。
|
|
1224
|
+
* 基于 pendingAnims 账本无状态判定:onfinish/oncancel 都会即时清账,
|
|
1225
|
+
* 取消路径天然解锁(计数器/最后注册者会卡死)。merged 行渐隐时已被
|
|
1226
|
+
* releaseMergedThink 摘出 mergedThinks,按 DOM 类名现查。 */
|
|
1227
|
+
hasPendingCollapse(block) {
|
|
1228
|
+
const check = (el) => el !== null && el !== void 0 && this.pendingAnims.get(el)?.target === "hidden";
|
|
1229
|
+
if (block.containers.some(check)) return true;
|
|
1230
|
+
if (block.rows.some(check)) return true;
|
|
1231
|
+
const mergedRow = block.host.querySelector(".dshcf-merged-think");
|
|
1232
|
+
if (check(mergedRow)) return true;
|
|
1233
|
+
const mergedBody = mergedRow?.nextElementSibling;
|
|
1234
|
+
if (mergedBody instanceof HTMLElement && check(mergedBody)) return true;
|
|
1235
|
+
return false;
|
|
1236
|
+
}
|
|
1237
|
+
/** 钉住 chip 与首行的 16px 间距(收起 fade 期间;内联优先于 aria=false 的 0)。
|
|
1238
|
+
* flow-chip(context 等 before-mounted)豁免:其间距由宿主 row-gap 16px
|
|
1239
|
+
* 提供、自身 CSS 恒 0,钉住 16px 会叠加成 32px(真机实测:收起上下文
|
|
1240
|
+
* 注入时二级与三级间距瞬间扩大)。
|
|
1241
|
+
*/
|
|
1242
|
+
pinChipMargin(chip) {
|
|
1243
|
+
if (chip.classList.contains("dshcf-flow-chip")) return;
|
|
1244
|
+
if (chip.style.marginBottom !== "16px") chip.style.marginBottom = "16px";
|
|
1245
|
+
}
|
|
1246
|
+
/** 解除钉住(aria=true 的 16px 或 aria=false 的 0 由 CSS 接管)。 */
|
|
1247
|
+
unpinChipMargin(chip) {
|
|
1248
|
+
if (chip.style.marginBottom !== "") chip.style.marginBottom = "";
|
|
1249
|
+
}
|
|
1250
|
+
/** 外部介入检测(issue #11 Bug A):当前内联值 ≠ 插件最后确保值,或所有权
|
|
1251
|
+
* 哨兵被 style 整体改写抹除。返回 true 时调用方放弃本次写回并交还账本——
|
|
1252
|
+
* 属性级改写由值比对捕获,整体改写(cssText / setAttribute('style'))由
|
|
1253
|
+
* 哨兵缺失捕获,两层合起来覆盖外部介入的两种形态。 */
|
|
1254
|
+
displayForeign(el) {
|
|
1255
|
+
const written = this.writtenDisplay.get(el);
|
|
1256
|
+
if (written === void 0) return false;
|
|
1257
|
+
return el.style.getPropertyValue(DISPLAY_OWNED_PROP) === "" || el.style.display !== written;
|
|
1258
|
+
}
|
|
1259
|
+
/** 清空单个元素的显示账本(三账本 + 所有权哨兵)。 */
|
|
1260
|
+
releaseDisplayLedger(el) {
|
|
1261
|
+
this.originalDisplay.delete(el);
|
|
1262
|
+
this.writtenDisplay.delete(el);
|
|
1263
|
+
this.controlledDisplay.delete(el);
|
|
1264
|
+
el.style.removeProperty(DISPLAY_OWNED_PROP);
|
|
1265
|
+
}
|
|
1266
|
+
/** 返回 true 表示启动了渐隐动画(调用方可据此决定内部元素的处置)。
|
|
1267
|
+
* settle 在渐隐自然结束时调用(onfinish 链;反向取消不触发)。 */
|
|
1268
|
+
hideElement(el, desired, animate = false, settle) {
|
|
774
1269
|
desired.add(el);
|
|
775
|
-
|
|
1270
|
+
const pending = this.pendingAnims.get(el);
|
|
1271
|
+
if (pending !== void 0) {
|
|
1272
|
+
if (pending.target === "hidden") return false;
|
|
1273
|
+
this.cancelPendingSync(el);
|
|
1274
|
+
}
|
|
1275
|
+
if (!this.originalDisplay.has(el) && !isDisplayed(el)) return false;
|
|
1276
|
+
if (this.hasAnimatingAncestor(el)) return false;
|
|
1277
|
+
if (!this.originalDisplay.has(el) || this.displayForeign(el)) {
|
|
1278
|
+
this.originalDisplay.set(el, el.style.display);
|
|
1279
|
+
this.writtenDisplay.set(el, el.style.display);
|
|
1280
|
+
el.style.setProperty(DISPLAY_OWNED_PROP, "1");
|
|
1281
|
+
}
|
|
1282
|
+
this.controlledDisplay.add(el);
|
|
1283
|
+
if (el.style.display === "none") return false;
|
|
1284
|
+
if (animate && this.canAnimate(el)) {
|
|
1285
|
+
this.startFadeCollapse(el, settle);
|
|
1286
|
+
return true;
|
|
1287
|
+
}
|
|
1288
|
+
el.style.display = "none";
|
|
1289
|
+
this.writtenDisplay.set(el, "none");
|
|
1290
|
+
return false;
|
|
776
1291
|
}
|
|
777
|
-
restoreElement(el) {
|
|
1292
|
+
restoreElement(el, animate = false) {
|
|
1293
|
+
const pending = this.pendingAnims.get(el);
|
|
1294
|
+
if (pending !== void 0) {
|
|
1295
|
+
if (pending.target === "visible") return;
|
|
1296
|
+
this.cancelPendingSync(el);
|
|
1297
|
+
}
|
|
778
1298
|
if (!this.originalDisplay.has(el)) return;
|
|
1299
|
+
if (this.displayForeign(el)) {
|
|
1300
|
+
this.releaseDisplayLedger(el);
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
779
1303
|
const original = this.originalDisplay.get(el);
|
|
1304
|
+
if (!animate || !this.canAnimate(el) || this.hasAnimatingAncestor(el)) {
|
|
1305
|
+
if (el.style.display !== original) el.style.display = original;
|
|
1306
|
+
this.releaseDisplayLedger(el);
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
780
1309
|
if (el.style.display !== original) el.style.display = original;
|
|
781
|
-
this.
|
|
782
|
-
this.
|
|
1310
|
+
this.writtenDisplay.set(el, original);
|
|
1311
|
+
this.startReveal(el);
|
|
1312
|
+
}
|
|
1313
|
+
/** 是否可动画:WAAPI 特性检测 + reduced-motion 门控(均做 typeof 防桩缺失)。 */
|
|
1314
|
+
canAnimate(el) {
|
|
1315
|
+
if (typeof el.animate !== "function") return false;
|
|
1316
|
+
if (typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches) return false;
|
|
1317
|
+
return true;
|
|
1318
|
+
}
|
|
1319
|
+
/** 展开方向淡入(opacity + 4px 微位移):无高度分量、零布局读取。
|
|
1320
|
+
* onfinish 按终态可见对齐账本(双删除)并 schedule() 幂等重同步;
|
|
1321
|
+
* oncancel 只做身份守卫删除——取消方的终态写入自己负责。 */
|
|
1322
|
+
startReveal(el) {
|
|
1323
|
+
const anim = el.animate(
|
|
1324
|
+
[
|
|
1325
|
+
{ opacity: "0", transform: "translateY(4px)" },
|
|
1326
|
+
{ opacity: "1", transform: "translateY(0)" }
|
|
1327
|
+
],
|
|
1328
|
+
{ duration: ANIM_DURATION_MS, easing: ANIM_EASING }
|
|
1329
|
+
);
|
|
1330
|
+
const record = { anim, target: "visible", kind: "fade" };
|
|
1331
|
+
this.pendingAnims.set(el, record);
|
|
1332
|
+
anim.onfinish = () => {
|
|
1333
|
+
if (this.pendingAnims.get(el) !== record) return;
|
|
1334
|
+
this.pendingAnims.delete(el);
|
|
1335
|
+
this.releaseDisplayLedger(el);
|
|
1336
|
+
this.schedule();
|
|
1337
|
+
};
|
|
1338
|
+
anim.oncancel = () => {
|
|
1339
|
+
if (this.pendingAnims.get(el) !== record) return;
|
|
1340
|
+
this.pendingAnims.delete(el);
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
/** 同步取消在途动画并清账:收起动画需同时清锁高内联(height/overflow/
|
|
1344
|
+
* marginBottom),否则取消方写完终态后元素仍被锁高裁剪一帧以上。 */
|
|
1345
|
+
cancelPendingSync(el) {
|
|
1346
|
+
const pending = this.pendingAnims.get(el);
|
|
1347
|
+
if (pending === void 0) return;
|
|
1348
|
+
pending.anim.cancel();
|
|
1349
|
+
this.pendingAnims.delete(el);
|
|
1350
|
+
if (pending.kind === "height") this.clearCollapseLock(el);
|
|
1351
|
+
}
|
|
1352
|
+
clearCollapseLock(el) {
|
|
1353
|
+
el.style.height = "";
|
|
1354
|
+
el.style.overflow = "";
|
|
1355
|
+
el.style.marginBottom = "";
|
|
1356
|
+
el.style.boxSizing = "";
|
|
1357
|
+
}
|
|
1358
|
+
/** 祖先 seat 在途动画检测:沿 parentNode 走到 flow,任一祖先在 pendingAnims
|
|
1359
|
+
* 即视为在途。分层规则——同一视觉变化只动画一层。 */
|
|
1360
|
+
hasAnimatingAncestor(el) {
|
|
1361
|
+
const flow = this.flow;
|
|
1362
|
+
if (flow === null) return false;
|
|
1363
|
+
let node = el.parentElement;
|
|
1364
|
+
while (node !== null && node !== flow) {
|
|
1365
|
+
if (this.pendingAnims.has(node)) return true;
|
|
1366
|
+
node = node.parentElement;
|
|
1367
|
+
}
|
|
1368
|
+
return false;
|
|
1369
|
+
}
|
|
1370
|
+
/** 收起方向渐隐动画(v11 定稿):镜像 reveal 的 opacity + 4px 微位移,
|
|
1371
|
+
* 淡完 onfinish 写 display:none 并保持双条目(镜像 hideElement 终态契约)。
|
|
1372
|
+
* fill:'forwards' 占位到终态写入后释放;无几何锁、无 gap 补偿。 */
|
|
1373
|
+
startFadeCollapse(el, settle) {
|
|
1374
|
+
const anim = el.animate(
|
|
1375
|
+
[
|
|
1376
|
+
{ opacity: "1", transform: "translateY(0)" },
|
|
1377
|
+
{ opacity: "0", transform: "translateY(4px)" }
|
|
1378
|
+
],
|
|
1379
|
+
{ duration: ANIM_DURATION_MS, easing: ANIM_EASING, fill: "forwards" }
|
|
1380
|
+
);
|
|
1381
|
+
const record = { anim, target: "hidden", kind: "fade" };
|
|
1382
|
+
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
|
+
};
|
|
1398
|
+
anim.oncancel = () => {
|
|
1399
|
+
if (this.pendingAnims.get(el) !== record) return;
|
|
1400
|
+
this.pendingAnims.delete(el);
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
/** 轻量视觉 reveal(opacity + 4px 微位移):用于插件全资元素的即时显示
|
|
1404
|
+
* 路径——chip(一级展开时出现)与 merged-think 行(二级展开时出现)。
|
|
1405
|
+
* 这些元素的 display 完全由插件直写、无 React 协调竞争,因此不入
|
|
1406
|
+
* pendingAnims 账本、无仲裁;收起同为直写 display:none,无 fill 的在途
|
|
1407
|
+
* 动画残留在隐藏元素上自然失效。门控沿用 animate 布尔(手势路径才调)。 */
|
|
1408
|
+
revealVisual(el) {
|
|
1409
|
+
if (!this.canAnimate(el)) return;
|
|
1410
|
+
el.animate(
|
|
1411
|
+
[
|
|
1412
|
+
{ opacity: "0", transform: "translateY(4px)" },
|
|
1413
|
+
{ opacity: "1", transform: "translateY(0)" }
|
|
1414
|
+
],
|
|
1415
|
+
{ duration: ANIM_DURATION_MS, easing: ANIM_EASING }
|
|
1416
|
+
);
|
|
783
1417
|
}
|
|
784
1418
|
restoreUnusedDisplays(desired) {
|
|
785
1419
|
for (const el of [...this.controlledDisplay]) {
|
|
786
1420
|
if (!desired.has(el)) this.restoreElement(el);
|
|
787
1421
|
}
|
|
788
1422
|
}
|
|
1423
|
+
/** 收集 audit 需要观察的 display 集合;只读取内联样式,不触发布局计算。 */
|
|
1424
|
+
collectAuditDisplays(flow) {
|
|
1425
|
+
const nodes = new Set(flowItems(flow));
|
|
1426
|
+
for (const el of this.controlledDisplay) {
|
|
1427
|
+
if (el.isConnected && nodeWithin(el, flow)) nodes.add(el);
|
|
1428
|
+
}
|
|
1429
|
+
for (const { chip } of this.chips.values()) {
|
|
1430
|
+
if (chip.isConnected && nodeWithin(chip, flow)) nodes.add(chip);
|
|
1431
|
+
}
|
|
1432
|
+
for (const row of this.mergedThinks.values()) {
|
|
1433
|
+
if (row.isConnected && nodeWithin(row, flow)) nodes.add(row);
|
|
1434
|
+
}
|
|
1435
|
+
const displays = /* @__PURE__ */ new Map();
|
|
1436
|
+
for (const el of nodes) displays.set(el, el.style.display);
|
|
1437
|
+
return displays;
|
|
1438
|
+
}
|
|
1439
|
+
/** 在完整 pass 完成后保存 display 基线,供下一次轻量 audit 比对。 */
|
|
1440
|
+
captureAuditDisplays(flow) {
|
|
1441
|
+
this.auditDisplays = this.collectAuditDisplays(flow);
|
|
1442
|
+
}
|
|
789
1443
|
restoreAllDisplays() {
|
|
790
1444
|
for (const el of [...this.controlledDisplay]) this.restoreElement(el);
|
|
791
1445
|
this.controlledDisplay.clear();
|
|
792
1446
|
this.originalDisplay = /* @__PURE__ */ new WeakMap();
|
|
1447
|
+
this.writtenDisplay = /* @__PURE__ */ new WeakMap();
|
|
793
1448
|
}
|
|
794
1449
|
};
|
|
795
1450
|
function createSpan(cls) {
|
|
@@ -797,28 +1452,20 @@ var __dshcfBundle = (() => {
|
|
|
797
1452
|
span.className = cls;
|
|
798
1453
|
return span;
|
|
799
1454
|
}
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
prompt.setAttribute("x", "2");
|
|
815
|
-
prompt.setAttribute("y", "7.5");
|
|
816
|
-
prompt.setAttribute("font-size", "7");
|
|
817
|
-
prompt.setAttribute("fill", "currentColor");
|
|
818
|
-
prompt.textContent = ">_";
|
|
819
|
-
svg.append(rect, prompt);
|
|
820
|
-
return svg;
|
|
821
|
-
}
|
|
1455
|
+
var COMMAND_ICON_PATHS = [
|
|
1456
|
+
{
|
|
1457
|
+
transform: "translate(0.6689 1.073)",
|
|
1458
|
+
d: "M11.4818 5.57813C11.4818 4.45301 11.4807 3.66237 11.4075 3.05908C11.3359 2.46953 11.2024 2.13852 10.9939 1.89441C10.9247 1.81341 10.8493 1.73801 10.7683 1.66882C10.5242 1.46033 10.1932 1.32686 9.60364 1.25525C9.00034 1.18198 8.20974 1.18091 7.0846 1.18091L5.57813 1.18091C4.45301 1.18091 3.66238 1.18198 3.05908 1.25525C2.46953 1.32686 2.13852 1.46033 1.89441 1.66882C1.81341 1.73801 1.73801 1.81341 1.66882 1.89441C1.46033 2.13852 1.32686 2.46953 1.25525 3.05908C1.18198 3.66238 1.18091 4.45301 1.18091 5.57813L1.18091 6.2771C1.18091 7.40218 1.18197 8.19288 1.25525 8.79614C1.32687 9.38553 1.46036 9.71674 1.66882 9.96082C1.73797 10.0417 1.81347 10.1173 1.89441 10.1864C2.13851 10.3948 2.13965 10.5275 3.05908 10.5991C3.66238 10.6724 4.45298 10.6735 5.57813 10.6735L7.0846 10.6735C8.20977 10.6735 9.00033 10.6724 9.60364 10.5991C10.1931 10.5275 10.5242 10.3948 10.7683 10.1864C10.8493 10.1173 10.9247 10.0417 10.9939 9.96082C11.2024 9.71674 11.3358 9.38553 11.4075 8.79614C11.4808 8.19288 11.4818 7.40218 11.4818 6.2771L11.4818 5.57813ZM12.6627 6.2771C12.6627 7.37222 12.6637 8.247 12.5798 8.93799C12.4942 9.64284 12.3133 10.2359 11.8928 10.7282C11.7834 10.8562 11.6637 10.9751 11.5356 11.0845C11.0434 11.5049 10.4511 11.6867 9.74634 11.7723C9.05525 11.8563 8.17999 11.8552 7.0846 11.8552L5.57813 11.8552C4.48273 11.8552 3.60747 11.8563 2.91638 11.7723C2.21157 11.6867 1.61933 11.5049 1.12708 11.0845C0.99901 10.9751 0.879281 10.8562 0.769898 10.7282C0.349454 10.2359 0.168506 9.64284 0.0828864 8.93799C-0.00101964 8.247 4.88512e-07 7.37222 6.47206e-07 6.2771L6.47206e-07 5.57813C6.47206e-07 4.48273 -0.00106163 3.60747 0.0828864 2.91638C0.168502 2.21168 0.349594 1.61928 0.769898 1.12708C0.879302 0.998981 0.998981 0.879302 1.12708 0.769898C1.61928 0.349594 2.21168 0.168502 2.91638 0.0828864C3.60747 -0.00106163 4.48273 6.47206e-07 5.57813 6.47206e-07L7.0846 6.47206e-07C8.17999 6.47206e-07 9.05525 -0.00106163 9.74634 0.0828864C10.451 0.168505 11.0434 0.349587 11.5356 0.769898C11.6637 0.879302 11.7834 0.998981 11.8928 1.12708C12.3131 1.61928 12.4942 2.21169 12.5798 2.91638C12.6638 3.60747 12.6627 4.48273 12.6627 5.57813L12.6627 6.2771Z"
|
|
1459
|
+
},
|
|
1460
|
+
{
|
|
1461
|
+
transform: "translate(0.6689 1.073)",
|
|
1462
|
+
d: "M6.02607 5.50955L6.44306 5.9274L3.84284 8.52762L3.425 8.11063L3.00715 7.69278L4.77253 5.9274L3.00715 4.16202L3.84284 3.32633L6.02607 5.50955Z"
|
|
1463
|
+
},
|
|
1464
|
+
{
|
|
1465
|
+
transform: "translate(0.6689 1.073)",
|
|
1466
|
+
d: "M9.23789 7.35397L9.23789 8.53488L6.96238 8.53488L6.96238 7.35397L9.23789 7.35397Z"
|
|
1467
|
+
}
|
|
1468
|
+
];
|
|
822
1469
|
var NATIVE_CHEVRON_DOWN_PATH = "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z";
|
|
823
1470
|
function createChevronIcon(className) {
|
|
824
1471
|
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
@@ -886,10 +1533,31 @@ var __dshcfBundle = (() => {
|
|
|
886
1533
|
const vb = (svg.getAttribute("viewBox") ?? "").trim().split(/\s+/);
|
|
887
1534
|
return vb.length === 4 && Number(vb[2]) === 14 && Number(vb[3]) === 14;
|
|
888
1535
|
}
|
|
1536
|
+
var cachedNativeCommandSvg = null;
|
|
889
1537
|
function createCommandIcon() {
|
|
1538
|
+
if (cachedNativeCommandSvg !== null) return cachedNativeCommandSvg.cloneNode(true);
|
|
890
1539
|
const native = findNativeCommandSvg();
|
|
891
|
-
if (native !== null)
|
|
892
|
-
|
|
1540
|
+
if (native !== null) {
|
|
1541
|
+
cachedNativeCommandSvg = native;
|
|
1542
|
+
return native.cloneNode(true);
|
|
1543
|
+
}
|
|
1544
|
+
return createCommandIconFallback();
|
|
1545
|
+
}
|
|
1546
|
+
function createCommandIconFallback() {
|
|
1547
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1548
|
+
svg.setAttribute("viewBox", "0 0 14 14");
|
|
1549
|
+
svg.setAttribute("width", "14");
|
|
1550
|
+
svg.setAttribute("height", "14");
|
|
1551
|
+
svg.setAttribute("fill", "none");
|
|
1552
|
+
svg.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
|
1553
|
+
for (const p of COMMAND_ICON_PATHS) {
|
|
1554
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1555
|
+
path.setAttribute("transform", p.transform);
|
|
1556
|
+
path.setAttribute("d", p.d);
|
|
1557
|
+
path.setAttribute("fill", "currentColor");
|
|
1558
|
+
svg.appendChild(path);
|
|
1559
|
+
}
|
|
1560
|
+
return svg;
|
|
893
1561
|
}
|
|
894
1562
|
var CONTEXT_ICON_PATHS = [
|
|
895
1563
|
{
|
|
@@ -928,13 +1596,41 @@ var __dshcfBundle = (() => {
|
|
|
928
1596
|
}
|
|
929
1597
|
return svg;
|
|
930
1598
|
}
|
|
1599
|
+
var WRITE_ICON_PATH = "M9.94076 1.34942C10.7047 0.90231 11.6503 0.902415 12.4143 1.34942C12.7061 1.52015 12.9688 1.79118 13.3104 2.13284C13.6521 2.47448 13.9231 2.73721 14.0939 3.02894C14.5408 3.79294 14.5409 4.73856 14.0939 5.50251C13.9231 5.79415 13.652 6.05704 13.3104 6.39861L6.65932 13.0497C6.28068 13.4284 6.00695 13.7108 5.66543 13.9097C5.32391 14.1085 4.94315 14.2074 4.42705 14.3498L3.24394 14.6761C2.77527 14.8054 2.34538 14.9262 2.00131 14.9684C1.65196 15.0112 1.17964 15.0013 0.810764 14.6325C0.441921 14.2637 0.432107 13.7913 0.47486 13.442C0.517035 13.0979 0.6379 12.668 0.767181 12.1993L1.09352 11.0162C1.23588 10.5001 1.33481 10.1193 1.5336 9.77784C1.7325 9.43632 2.0149 9.1626 2.39355 8.78395L9.04466 2.13284C9.38625 1.79126 9.64911 1.52016 9.94076 1.34942ZM15.5427 14.8398H7.55223L8.96707 13.425H15.5427V14.8398ZM3.39382 9.78422C2.965 10.213 2.84244 10.3436 2.75709 10.49C2.67183 10.6366 2.61862 10.8079 2.45733 11.3925L2.13099 12.5756C2.00183 13.0439 1.92194 13.3419 1.88863 13.5536C2.10041 13.5204 2.39872 13.4416 2.86764 13.3123L4.05075 12.9859C4.63544 12.8246 4.80669 12.7715 4.95323 12.6862C5.09968 12.6008 5.23022 12.4783 5.65905 12.0494L10.721 6.98644L8.45577 4.72121L3.39382 9.78422ZM11.7 2.57079C11.3774 2.38198 10.9777 2.38198 10.6551 2.57079C10.5602 2.62647 10.4487 2.72931 10.0449 3.13311L9.45604 3.72094L11.7213 5.98617L12.3102 5.39833C12.7139 4.99457 12.8168 4.88307 12.8725 4.78818C13.0613 4.46561 13.0612 4.06585 12.8725 3.74326C12.8169 3.64827 12.7146 3.53752 12.3102 3.13311C11.9057 2.72863 11.795 2.6264 11.7 2.57079Z";
|
|
1600
|
+
function findNativeWriteSvg() {
|
|
1601
|
+
const selector = '[data-tool="write"] [data-disclosure-row], [data-tool="edit"] [data-disclosure-row]';
|
|
1602
|
+
for (const drow of document.querySelectorAll(selector)) {
|
|
1603
|
+
for (const svg of drow.querySelectorAll("svg")) {
|
|
1604
|
+
if (svg.querySelectorAll("path").length === 1 && isIcon16(svg)) return svg;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
return null;
|
|
1608
|
+
}
|
|
1609
|
+
function isIcon16(svg) {
|
|
1610
|
+
const vb = (svg.getAttribute("viewBox") ?? "").trim().split(/\s+/);
|
|
1611
|
+
return vb.length === 4 && Number(vb[2]) === 16 && Number(vb[3]) === 16;
|
|
1612
|
+
}
|
|
1613
|
+
function createWriteIcon() {
|
|
1614
|
+
const native = findNativeWriteSvg();
|
|
1615
|
+
if (native !== null) return native.cloneNode(true);
|
|
1616
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
1617
|
+
svg.setAttribute("viewBox", "0 0 16 16");
|
|
1618
|
+
svg.setAttribute("width", "14");
|
|
1619
|
+
svg.setAttribute("height", "14");
|
|
1620
|
+
svg.setAttribute("fill", "none");
|
|
1621
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
1622
|
+
path.setAttribute("d", WRITE_ICON_PATH);
|
|
1623
|
+
path.setAttribute("fill", "currentColor");
|
|
1624
|
+
svg.appendChild(path);
|
|
1625
|
+
return svg;
|
|
1626
|
+
}
|
|
931
1627
|
function syncLeadingIcon(chip, kind) {
|
|
932
1628
|
const leading = chip.querySelector(".dshcf-leading");
|
|
933
1629
|
if (leading === null) return;
|
|
934
1630
|
const existing = leading.querySelector("svg");
|
|
935
1631
|
if (existing !== null && existing.getAttribute("data-dshcf-icon") === kind) return;
|
|
936
1632
|
for (const child of [...leading.childNodes]) child.remove();
|
|
937
|
-
const svg = kind === "think" ? createThinkIcon() : kind === "context" ? createContextIcon() : createCommandIcon();
|
|
1633
|
+
const svg = kind === "think" ? createThinkIcon() : kind === "context" ? createContextIcon() : kind === "write" ? createWriteIcon() : createCommandIcon();
|
|
938
1634
|
svg.setAttribute("data-dshcf-icon", kind);
|
|
939
1635
|
leading.appendChild(svg);
|
|
940
1636
|
}
|
|
@@ -951,6 +1647,15 @@ var __dshcfBundle = (() => {
|
|
|
951
1647
|
}
|
|
952
1648
|
return false;
|
|
953
1649
|
}
|
|
1650
|
+
function isPluginOwnedNode(node) {
|
|
1651
|
+
const element = node.nodeType === 1 ? node : node.parentElement;
|
|
1652
|
+
return element !== null && element !== void 0 && element.closest(".dshcf-chip, .dshcf-processed, .dshcf-merged-think, .dshcf-merged-body") !== null;
|
|
1653
|
+
}
|
|
1654
|
+
function flowChildOwner(node, flow) {
|
|
1655
|
+
let current = node;
|
|
1656
|
+
while (current !== null && current.parentNode !== flow) current = current.parentNode;
|
|
1657
|
+
return current instanceof HTMLElement ? current : null;
|
|
1658
|
+
}
|
|
954
1659
|
function flowItems(flow) {
|
|
955
1660
|
return [...flow.children].filter((el) => el instanceof HTMLElement && !el.classList.contains("dshcf-processed") && !el.classList.contains("dshcf-flow-chip"));
|
|
956
1661
|
}
|
|
@@ -969,7 +1674,7 @@ var __dshcfBundle = (() => {
|
|
|
969
1674
|
return kind === "assistant-step" || kind === "assistant" || kind === "tool-call" || kind === "command" || kind === "manual-compaction";
|
|
970
1675
|
});
|
|
971
1676
|
}
|
|
972
|
-
function buildSegments(flow, blocks) {
|
|
1677
|
+
function buildSegments(flow, blocks, hasBody) {
|
|
973
1678
|
const items = flowItems(flow);
|
|
974
1679
|
const itemIndex = new Map(items.map((el, index) => [el, index]));
|
|
975
1680
|
const snapshots = [];
|
|
@@ -982,7 +1687,7 @@ var __dshcfBundle = (() => {
|
|
|
982
1687
|
const segmentBlocks = blocks.filter((block) => inRange.has(block.host));
|
|
983
1688
|
const bodySteps = range.filter((el) => {
|
|
984
1689
|
const kind = el.getAttribute("data-chat-flow-kind");
|
|
985
|
-
return (kind === "assistant-step" || kind === "assistant") &&
|
|
1690
|
+
return (kind === "assistant-step" || kind === "assistant") && hasBody(el);
|
|
986
1691
|
});
|
|
987
1692
|
const finalStep = bodySteps.length > 0 ? bodySteps[bodySteps.length - 1] : null;
|
|
988
1693
|
const middleSteps = new Set(bodySteps.slice(0, -1));
|
|
@@ -1043,7 +1748,7 @@ var __dshcfBundle = (() => {
|
|
|
1043
1748
|
if (segment.finalStep !== null) workHosts.add(segment.finalStep);
|
|
1044
1749
|
return [...workHosts].some(isDisplayed);
|
|
1045
1750
|
}
|
|
1046
|
-
function findBlocks(flow) {
|
|
1751
|
+
function findBlocks(flow, hasBody) {
|
|
1047
1752
|
const blocks = [];
|
|
1048
1753
|
const children = flowItems(flow);
|
|
1049
1754
|
let run = null;
|
|
@@ -1080,7 +1785,7 @@ var __dshcfBundle = (() => {
|
|
|
1080
1785
|
const workRows = [...callRowsIn(el), ...commandRowsIn(el)];
|
|
1081
1786
|
const isToolPile = workRows.length > 0;
|
|
1082
1787
|
const isContext = kind === "context";
|
|
1083
|
-
const
|
|
1788
|
+
const msgHasBody = !isToolPile ? hasBody(el) : false;
|
|
1084
1789
|
if (isContext) {
|
|
1085
1790
|
flushCarry();
|
|
1086
1791
|
if (run === null || run.category !== "context") run = makeBlock(el, "context");
|
|
@@ -1088,7 +1793,7 @@ var __dshcfBundle = (() => {
|
|
|
1088
1793
|
run.mount = "before";
|
|
1089
1794
|
continue;
|
|
1090
1795
|
}
|
|
1091
|
-
if (isToolPile || thinkRows.length > 0 && !
|
|
1796
|
+
if (isToolPile || thinkRows.length > 0 && !msgHasBody) {
|
|
1092
1797
|
if (run === null || run.category !== "work") run = makeBlock(el, "work");
|
|
1093
1798
|
if (carry.length > 0) {
|
|
1094
1799
|
run.rows.push(...carry);
|
|
@@ -1100,7 +1805,7 @@ var __dshcfBundle = (() => {
|
|
|
1100
1805
|
run.containers.push(el);
|
|
1101
1806
|
}
|
|
1102
1807
|
if (workRows.includes(el)) run.mount = "before";
|
|
1103
|
-
} else if (el.hasAttribute("data-chat-anchor-key") && (thinkRows.length > 0 ||
|
|
1808
|
+
} else if (el.hasAttribute("data-chat-anchor-key") && (thinkRows.length > 0 || msgHasBody) || msgHasBody && kind !== null) {
|
|
1104
1809
|
flushCarry();
|
|
1105
1810
|
if (thinkRows.length > 0) {
|
|
1106
1811
|
const segments = splitThinkByBody(el, thinkRows);
|
|
@@ -1128,11 +1833,29 @@ var __dshcfBundle = (() => {
|
|
|
1128
1833
|
return blocks;
|
|
1129
1834
|
}
|
|
1130
1835
|
function splitThinkByBody(el, rows) {
|
|
1836
|
+
const texts = [];
|
|
1837
|
+
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
1838
|
+
let node;
|
|
1839
|
+
while ((node = walker.nextNode()) !== null) {
|
|
1840
|
+
if (node.data.trim() === "") continue;
|
|
1841
|
+
const parent = node.parentElement;
|
|
1842
|
+
if (parent !== null && parent.closest('[data-variant="think"], [data-chat-call-id], .dshcf-chip, .dshcf-merged-think, .dshcf-merged-body') !== null) continue;
|
|
1843
|
+
texts.push(node);
|
|
1844
|
+
}
|
|
1845
|
+
const hasBetween = (a, b) => {
|
|
1846
|
+
for (const t of texts) {
|
|
1847
|
+
const posA = a.compareDocumentPosition(t);
|
|
1848
|
+
if ((posA & Node.DOCUMENT_POSITION_FOLLOWING) === 0) continue;
|
|
1849
|
+
const posB = b.compareDocumentPosition(t);
|
|
1850
|
+
return (posB & Node.DOCUMENT_POSITION_PRECEDING) !== 0;
|
|
1851
|
+
}
|
|
1852
|
+
return false;
|
|
1853
|
+
};
|
|
1131
1854
|
const segments = [];
|
|
1132
1855
|
let current = [];
|
|
1133
1856
|
for (let i = 0; i < rows.length; i++) {
|
|
1134
1857
|
current.push(rows[i]);
|
|
1135
|
-
if (i + 1 < rows.length &&
|
|
1858
|
+
if (i + 1 < rows.length && hasBetween(rows[i], rows[i + 1])) {
|
|
1136
1859
|
segments.push(current);
|
|
1137
1860
|
current = [];
|
|
1138
1861
|
}
|
|
@@ -1140,21 +1863,6 @@ var __dshcfBundle = (() => {
|
|
|
1140
1863
|
if (current.length > 0) segments.push(current);
|
|
1141
1864
|
return segments.length > 0 ? segments : [rows];
|
|
1142
1865
|
}
|
|
1143
|
-
function hasBodyBetween(el, a, b) {
|
|
1144
|
-
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
1145
|
-
let node;
|
|
1146
|
-
while ((node = walker.nextNode()) !== null) {
|
|
1147
|
-
if (node.data.trim() === "") continue;
|
|
1148
|
-
const parent = node.parentElement;
|
|
1149
|
-
if (parent !== null && parent.closest('[data-variant="think"], [data-chat-call-id], .dshcf-chip, .dshcf-merged-think, .dshcf-merged-body') !== null) continue;
|
|
1150
|
-
const posA = a.compareDocumentPosition(node);
|
|
1151
|
-
const posB = b.compareDocumentPosition(node);
|
|
1152
|
-
if ((posA & Node.DOCUMENT_POSITION_FOLLOWING) !== 0 && (posB & Node.DOCUMENT_POSITION_PRECEDING) !== 0) {
|
|
1153
|
-
return true;
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
return false;
|
|
1157
|
-
}
|
|
1158
1866
|
function hasBodyText(el) {
|
|
1159
1867
|
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
1160
1868
|
let node;
|
|
@@ -1269,7 +1977,6 @@ var __dshcfBundle = (() => {
|
|
|
1269
1977
|
runningTool,
|
|
1270
1978
|
runningThink,
|
|
1271
1979
|
tools,
|
|
1272
|
-
count: rows.length,
|
|
1273
1980
|
hasError: infos.some((i) => i.state === "error"),
|
|
1274
1981
|
hasStopped: infos.some((i) => i.state === "stopped"),
|
|
1275
1982
|
allContext: infos.length > 0 && infos.every((i) => i.label === "\u4E0A\u4E0B\u6587\u6CE8\u5165")
|
|
@@ -1298,14 +2005,19 @@ var __dshcfBundle = (() => {
|
|
|
1298
2005
|
titleText = "\u5DF2\u601D\u8003";
|
|
1299
2006
|
summaryText = "";
|
|
1300
2007
|
}
|
|
1301
|
-
|
|
2008
|
+
let kind = running !== null ? running.kind : info.allContext ? "context" : info.tools.length > 0 ? "tool" : "think";
|
|
2009
|
+
if (running === null && info.tools.some((tool) => tool === "Edit" || tool === "Write")) kind = "write";
|
|
1302
2010
|
if (title.textContent !== titleText) title.textContent = titleText;
|
|
1303
2011
|
if (summary.textContent !== summaryText) summary.textContent = summaryText;
|
|
1304
2012
|
if (sep !== null) {
|
|
1305
2013
|
const sepDisplay = summaryText === "" ? "none" : "";
|
|
1306
2014
|
if (sep.style.display !== sepDisplay) sep.style.display = sepDisplay;
|
|
1307
2015
|
}
|
|
1308
|
-
|
|
2016
|
+
if (running !== null) {
|
|
2017
|
+
summary.scrollLeft = summary.scrollWidth - summary.clientWidth;
|
|
2018
|
+
} else if (chip.classList.contains("running") && summary.scrollLeft !== 0) {
|
|
2019
|
+
summary.scrollLeft = 0;
|
|
2020
|
+
}
|
|
1309
2021
|
const expandedAttr = String(expanded);
|
|
1310
2022
|
if (chip.getAttribute("aria-expanded") !== expandedAttr) {
|
|
1311
2023
|
chip.setAttribute("aria-expanded", expandedAttr);
|
|
@@ -1333,8 +2045,16 @@ var __dshcfBundle = (() => {
|
|
|
1333
2045
|
function isThinkRow(row) {
|
|
1334
2046
|
return row.matches('[data-variant="think"]') && !row.hasAttribute("data-tool");
|
|
1335
2047
|
}
|
|
2048
|
+
var turnDurationCache = /* @__PURE__ */ new WeakMap();
|
|
1336
2049
|
function parseTurnDuration(boundary) {
|
|
1337
2050
|
const text = boundary.textContent ?? "";
|
|
2051
|
+
const cached = turnDurationCache.get(boundary);
|
|
2052
|
+
if (cached !== void 0 && cached.text === text) return cached.duration;
|
|
2053
|
+
const duration = parseTurnDurationText(boundary, text);
|
|
2054
|
+
turnDurationCache.set(boundary, { text, duration });
|
|
2055
|
+
return duration;
|
|
2056
|
+
}
|
|
2057
|
+
function parseTurnDurationText(boundary, text) {
|
|
1338
2058
|
const m = text.match(/用时\s*(\d+)分(\d+)秒|用时\s*(\d+)秒/);
|
|
1339
2059
|
if (m !== null) {
|
|
1340
2060
|
if (m[1] !== void 0 && m[2] !== void 0) return Number(m[1]) * 6e4 + Number(m[2]) * 1e3;
|
|
@@ -1403,21 +2123,29 @@ var __dshcfBundle = (() => {
|
|
|
1403
2123
|
style.textContent = CHIP_CSS;
|
|
1404
2124
|
document.head.appendChild(style);
|
|
1405
2125
|
}
|
|
1406
|
-
function replaceTurnStatus(flow, originals) {
|
|
2126
|
+
function replaceTurnStatus(flow, originals, statusText) {
|
|
1407
2127
|
const statuses = flow.matches('[role="status"]') ? [flow, ...flow.querySelectorAll('[role="status"]')] : [...flow.querySelectorAll('[role="status"]')];
|
|
1408
2128
|
for (const status of statuses) {
|
|
1409
2129
|
for (const node of status.childNodes) {
|
|
1410
2130
|
if (node instanceof Text && node.data.includes("Deep diving")) {
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
2131
|
+
let record = originals.get(node);
|
|
2132
|
+
if (record === void 0) {
|
|
2133
|
+
record = { original: node.data, written: "" };
|
|
2134
|
+
originals.set(node, record);
|
|
2135
|
+
}
|
|
2136
|
+
if (node.data !== record.written) record.original = node.data;
|
|
2137
|
+
const next = node.data.replace(/Deep diving[.…]*/, statusText);
|
|
2138
|
+
if (node.data !== next) {
|
|
2139
|
+
node.data = next;
|
|
2140
|
+
record.written = next;
|
|
2141
|
+
}
|
|
1414
2142
|
}
|
|
1415
2143
|
}
|
|
1416
2144
|
}
|
|
1417
2145
|
}
|
|
1418
2146
|
function restoreTurnStatus(originals) {
|
|
1419
|
-
for (const [node,
|
|
1420
|
-
if (node.isConnected && node.data.
|
|
2147
|
+
for (const [node, record] of originals) {
|
|
2148
|
+
if (node.isConnected && node.data === record.written && node.data !== record.original) node.data = record.original;
|
|
1421
2149
|
}
|
|
1422
2150
|
originals.clear();
|
|
1423
2151
|
}
|
|
@@ -1425,15 +2153,255 @@ var __dshcfBundle = (() => {
|
|
|
1425
2153
|
document.getElementById(STYLE_ID)?.remove();
|
|
1426
2154
|
}
|
|
1427
2155
|
|
|
2156
|
+
// src/settings.ts
|
|
2157
|
+
var AUTO_COLLAPSE_NS = "dsh-auto-collapse";
|
|
2158
|
+
var DEFAULT_STATUS_TEXT2 = "Deep sleeping...";
|
|
2159
|
+
function statusTextProvider(scope) {
|
|
2160
|
+
return () => {
|
|
2161
|
+
if (scope === void 0) return DEFAULT_STATUS_TEXT2;
|
|
2162
|
+
const snapshot = scope.getSnapshot();
|
|
2163
|
+
const value = snapshot.value;
|
|
2164
|
+
return value?.statusText ?? DEFAULT_STATUS_TEXT2;
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
var CARD_CSS = `
|
|
2168
|
+
.dshcf-settings-card {
|
|
2169
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
2170
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
2171
|
+
border-radius: 12px;
|
|
2172
|
+
list-style: none;
|
|
2173
|
+
transition: border-color .16s, background .16s;
|
|
2174
|
+
}
|
|
2175
|
+
.dshcf-settings-card:hover { border-color: var(--dsw-alias-label-dimmed); }
|
|
2176
|
+
.dshcf-settings-cardOpen {
|
|
2177
|
+
background: var(--dsw-alias-bg-layer-2);
|
|
2178
|
+
border-color: var(--dsw-alias-label-dimmed);
|
|
2179
|
+
}
|
|
2180
|
+
.dshcf-settings-header {
|
|
2181
|
+
appearance: none;
|
|
2182
|
+
width: 100%;
|
|
2183
|
+
font: inherit;
|
|
2184
|
+
color: inherit;
|
|
2185
|
+
text-align: left;
|
|
2186
|
+
cursor: pointer;
|
|
2187
|
+
background: 0 0;
|
|
2188
|
+
border: 0;
|
|
2189
|
+
border-radius: 12px;
|
|
2190
|
+
align-items: center;
|
|
2191
|
+
gap: 12px;
|
|
2192
|
+
padding: 14px 16px;
|
|
2193
|
+
display: flex;
|
|
2194
|
+
}
|
|
2195
|
+
.dshcf-settings-header:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: -2px; }
|
|
2196
|
+
.dshcf-settings-headText { flex-direction: column; flex: 1; gap: 4px; min-width: 0; display: flex; }
|
|
2197
|
+
.dshcf-settings-name { color: var(--dsw-alias-label-primary); font-size: 15px; font-weight: 600; line-height: 1.4; }
|
|
2198
|
+
.dshcf-settings-description { color: var(--dsw-alias-label-tertiary); font-size: 13px; line-height: 1.5; }
|
|
2199
|
+
.dshcf-settings-chevron { color: var(--dsw-alias-label-tertiary); flex: none; transition: transform .16s; }
|
|
2200
|
+
.dshcf-settings-chevronOpen { transform: rotate(180deg); }
|
|
2201
|
+
.dshcf-settings-pending {
|
|
2202
|
+
white-space: nowrap;
|
|
2203
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
2204
|
+
color: var(--dsw-alias-label-secondary);
|
|
2205
|
+
border-radius: 999px;
|
|
2206
|
+
flex: none;
|
|
2207
|
+
padding: 1px 8px;
|
|
2208
|
+
font-size: 11px;
|
|
2209
|
+
font-weight: 500;
|
|
2210
|
+
line-height: 17px;
|
|
2211
|
+
}
|
|
2212
|
+
.dshcf-settings-body { border-top: 1px solid var(--dsw-alias-border-l2); margin: 0 16px; padding-bottom: 8px; }
|
|
2213
|
+
.dshcf-settings-readOnly { color: var(--dsw-alias-label-tertiary); margin: 12px 0 0; font-size: 12px; line-height: 1.5; }
|
|
2214
|
+
.dshcf-settings-field { flex-direction: column; gap: 6px; padding: 12px 0; display: flex; }
|
|
2215
|
+
.dshcf-settings-fieldHead { align-items: center; gap: 8px; display: flex; }
|
|
2216
|
+
.dshcf-settings-fieldLabel { min-width: 0; color: var(--dsw-alias-label-primary); flex: 1; font-size: 13px; font-weight: 500; line-height: 1.5; }
|
|
2217
|
+
.dshcf-settings-badges { align-items: center; gap: 8px; display: inline-flex; }
|
|
2218
|
+
.dshcf-settings-badge {
|
|
2219
|
+
white-space: nowrap;
|
|
2220
|
+
background: var(--dsw-alias-bg-module-platform);
|
|
2221
|
+
color: var(--dsw-alias-label-secondary);
|
|
2222
|
+
border-radius: 999px;
|
|
2223
|
+
padding: 1px 8px;
|
|
2224
|
+
font-size: 11px;
|
|
2225
|
+
font-weight: 500;
|
|
2226
|
+
line-height: 17px;
|
|
2227
|
+
}
|
|
2228
|
+
.dshcf-settings-reset { font: inherit; color: var(--dsw-alias-label-secondary); cursor: pointer; background: 0 0; border: none; padding: 0; font-size: 12px; line-height: 1.5; }
|
|
2229
|
+
.dshcf-settings-reset:hover:not(:disabled) { color: var(--dsw-alias-label-primary); }
|
|
2230
|
+
.dshcf-settings-reset:disabled { cursor: default; }
|
|
2231
|
+
.dshcf-settings-input {
|
|
2232
|
+
border: 1px solid var(--dsw-alias-border-l2);
|
|
2233
|
+
background: var(--dsw-alias-bg-layer-3);
|
|
2234
|
+
height: 34px;
|
|
2235
|
+
font: inherit;
|
|
2236
|
+
color: var(--dsw-alias-label-primary);
|
|
2237
|
+
border-radius: 8px;
|
|
2238
|
+
padding: 0 12px;
|
|
2239
|
+
font-size: 13px;
|
|
2240
|
+
line-height: 1.5;
|
|
2241
|
+
box-sizing: border-box;
|
|
2242
|
+
width: 100%;
|
|
2243
|
+
}
|
|
2244
|
+
.dshcf-settings-input:focus-visible { border-color: var(--dsw-alias-brand-primary); outline: none; }
|
|
2245
|
+
.dshcf-settings-input:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
|
|
2246
|
+
.dshcf-settings-hint { color: var(--dsw-alias-label-tertiary); margin: 0; font-size: 12px; line-height: 1.5; }
|
|
2247
|
+
.dshcf-settings-footer { border-top: 1px solid var(--dsw-alias-border-l2); justify-content: flex-end; align-items: center; gap: 8px; padding: 12px 0 4px; display: flex; }
|
|
2248
|
+
.dshcf-settings-failed { min-width: 0; color: var(--dsw-alias-label-error); flex: 1; margin: 0; font-size: 12px; line-height: 1.5; }
|
|
2249
|
+
.dshcf-settings-discard,
|
|
2250
|
+
.dshcf-settings-save { appearance: none; font: inherit; cursor: pointer; border: 1px solid #0000; border-radius: 8px; padding: 5px 14px; font-size: 13px; line-height: 1.5; }
|
|
2251
|
+
.dshcf-settings-discard { border-color: var(--dsw-alias-border-l2); color: var(--dsw-alias-label-secondary); background: 0 0; }
|
|
2252
|
+
.dshcf-settings-discard:hover:not(:disabled) { color: var(--dsw-alias-label-primary); border-color: var(--dsw-alias-label-dimmed); }
|
|
2253
|
+
.dshcf-settings-save { background: var(--dsw-alias-label-primary); color: var(--dsw-alias-bg-layer-3); }
|
|
2254
|
+
.dshcf-settings-discard:disabled,
|
|
2255
|
+
.dshcf-settings-save:disabled { opacity: .4; cursor: default; }
|
|
2256
|
+
.dshcf-settings-discard:focus-visible,
|
|
2257
|
+
.dshcf-settings-save:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }
|
|
2258
|
+
`;
|
|
2259
|
+
var STYLE_ID2 = "dshcf-settings-style";
|
|
2260
|
+
function injectCardStyle() {
|
|
2261
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID2) !== null) return;
|
|
2262
|
+
const style = document.createElement("style");
|
|
2263
|
+
style.id = STYLE_ID2;
|
|
2264
|
+
style.textContent = CARD_CSS;
|
|
2265
|
+
document.head.appendChild(style);
|
|
2266
|
+
}
|
|
2267
|
+
function ChevronIcon(open) {
|
|
2268
|
+
const React = __require("react");
|
|
2269
|
+
const className = open ? "dshcf-settings-chevron dshcf-settings-chevronOpen" : "dshcf-settings-chevron";
|
|
2270
|
+
return React.createElement(
|
|
2271
|
+
"svg",
|
|
2272
|
+
{ width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": true, className },
|
|
2273
|
+
React.createElement("path", {
|
|
2274
|
+
d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
|
|
2275
|
+
fill: "currentColor"
|
|
2276
|
+
})
|
|
2277
|
+
);
|
|
2278
|
+
}
|
|
2279
|
+
function StatusTextCard(props) {
|
|
2280
|
+
const React = __require("react");
|
|
2281
|
+
const scope = props.scope;
|
|
2282
|
+
const [open, setOpen] = React.useState(false);
|
|
2283
|
+
const [snapshot, setSnapshot] = React.useState(scope.getSnapshot());
|
|
2284
|
+
const [pending, setPending] = React.useState(null);
|
|
2285
|
+
const [saving, setSaving] = React.useState(false);
|
|
2286
|
+
const [failed, setFailed] = React.useState(false);
|
|
2287
|
+
React.useEffect(() => scope.subscribe(() => setSnapshot(scope.getSnapshot())), [scope]);
|
|
2288
|
+
if (snapshot.status !== "ready") return null;
|
|
2289
|
+
const value = snapshot.value;
|
|
2290
|
+
const base = snapshot.base;
|
|
2291
|
+
const user = snapshot.user;
|
|
2292
|
+
const currentText = value?.statusText ?? "";
|
|
2293
|
+
const defaultText = base?.statusText ?? DEFAULT_STATUS_TEXT2;
|
|
2294
|
+
const text = pending ? pending.text : currentText;
|
|
2295
|
+
const userHas = user !== void 0 && Object.prototype.hasOwnProperty.call(user, "statusText");
|
|
2296
|
+
const overridden = pending ? !pending.reset : userHas;
|
|
2297
|
+
const dirty = pending !== null && (pending.reset ? userHas : pending.text.trim() !== currentText);
|
|
2298
|
+
const writable = snapshot.writable;
|
|
2299
|
+
const discard = () => {
|
|
2300
|
+
setPending(null);
|
|
2301
|
+
setFailed(false);
|
|
2302
|
+
};
|
|
2303
|
+
const resetField = () => {
|
|
2304
|
+
setPending({ text: defaultText, reset: true });
|
|
2305
|
+
setFailed(false);
|
|
2306
|
+
};
|
|
2307
|
+
const edit = (next) => {
|
|
2308
|
+
setPending({ text: next, reset: false });
|
|
2309
|
+
setFailed(false);
|
|
2310
|
+
};
|
|
2311
|
+
const save = async () => {
|
|
2312
|
+
if (pending === null) return;
|
|
2313
|
+
setSaving(true);
|
|
2314
|
+
setFailed(false);
|
|
2315
|
+
try {
|
|
2316
|
+
if (pending.reset) await scope.unset("statusText");
|
|
2317
|
+
else await scope.set("statusText", pending.text.trim());
|
|
2318
|
+
setPending(null);
|
|
2319
|
+
} catch {
|
|
2320
|
+
setFailed(true);
|
|
2321
|
+
} finally {
|
|
2322
|
+
setSaving(false);
|
|
2323
|
+
}
|
|
2324
|
+
};
|
|
2325
|
+
const blocked = !dirty || saving;
|
|
2326
|
+
const cardClass = `dshcf-settings-card${open ? " dshcf-settings-cardOpen" : ""}`;
|
|
2327
|
+
return React.createElement("li", { className: cardClass }, [
|
|
2328
|
+
React.createElement(
|
|
2329
|
+
"button",
|
|
2330
|
+
{
|
|
2331
|
+
type: "button",
|
|
2332
|
+
className: "dshcf-settings-header",
|
|
2333
|
+
"aria-expanded": open,
|
|
2334
|
+
"aria-label": `${open ? "\u6536\u8D77\u8BBE\u7F6E" : "\u5C55\u5F00\u8BBE\u7F6E"}: \u72B6\u6001\u63D0\u793A\u8BCD`,
|
|
2335
|
+
onClick: () => setOpen(!open)
|
|
2336
|
+
},
|
|
2337
|
+
[
|
|
2338
|
+
React.createElement("span", { className: "dshcf-settings-headText" }, [
|
|
2339
|
+
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\u7684Deep diving...\u4E00\u884C\uFF0C\u7531\u63D2\u4EF6dsh-auto-collapse\u63D0\u4F9B")
|
|
2341
|
+
]),
|
|
2342
|
+
dirty ? React.createElement("span", { className: "dshcf-settings-pending" }, "\u672A\u4FDD\u5B58") : null,
|
|
2343
|
+
ChevronIcon(open)
|
|
2344
|
+
]
|
|
2345
|
+
),
|
|
2346
|
+
open ? React.createElement("div", { className: "dshcf-settings-body" }, [
|
|
2347
|
+
!writable ? React.createElement("p", { className: "dshcf-settings-readOnly", role: "status" }, "\u672C\u90E8\u7F72\u7684\u8BBE\u7F6E\u4E3A\u53EA\u8BFB\u3002") : null,
|
|
2348
|
+
React.createElement("div", { className: "dshcf-settings-field" }, [
|
|
2349
|
+
React.createElement("div", { className: "dshcf-settings-fieldHead" }, [
|
|
2350
|
+
React.createElement("label", { className: "dshcf-settings-fieldLabel", htmlFor: "dshcf-status-text" }, "\u81EA\u5B9A\u4E49\u72B6\u6001\u63D0\u793A\u8BCD"),
|
|
2351
|
+
overridden ? React.createElement("span", { className: "dshcf-settings-badges" }, [
|
|
2352
|
+
React.createElement("span", { className: "dshcf-settings-badge" }, "\u5DF2\u8986\u76D6"),
|
|
2353
|
+
React.createElement("button", { type: "button", className: "dshcf-settings-reset", disabled: !writable, onClick: resetField }, "\u6062\u590D\u9ED8\u8BA4")
|
|
2354
|
+
]) : null
|
|
2355
|
+
]),
|
|
2356
|
+
React.createElement("input", {
|
|
2357
|
+
id: "dshcf-status-text",
|
|
2358
|
+
className: "dshcf-settings-input",
|
|
2359
|
+
type: "text",
|
|
2360
|
+
value: text,
|
|
2361
|
+
placeholder: "Deep diving...",
|
|
2362
|
+
disabled: !writable,
|
|
2363
|
+
onChange: (event) => edit(event.target.value)
|
|
2364
|
+
}),
|
|
2365
|
+
React.createElement("p", { className: "dshcf-settings-hint" }, "\u4E3A\u7A7A\u65F6\u6062\u590D\u9ED8\u8BA4Deep diving...\u63D0\u793A\u8BCD\u72B6\u6001")
|
|
2366
|
+
]),
|
|
2367
|
+
React.createElement("div", { className: "dshcf-settings-footer" }, [
|
|
2368
|
+
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,
|
|
2369
|
+
React.createElement("button", { type: "button", className: "dshcf-settings-discard", disabled: !dirty || saving, onClick: discard }, "\u653E\u5F03\u4FEE\u6539"),
|
|
2370
|
+
React.createElement("button", { type: "button", className: "dshcf-settings-save", disabled: blocked, onClick: save }, saving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58")
|
|
2371
|
+
])
|
|
2372
|
+
]) : null
|
|
2373
|
+
]);
|
|
2374
|
+
}
|
|
2375
|
+
function setupSettingsCard(ctx, scope) {
|
|
2376
|
+
injectCardStyle();
|
|
2377
|
+
return ctx.slots.inject("settings.plugin.item", () => ctx.slots.register(
|
|
2378
|
+
{
|
|
2379
|
+
name: "settings.plugin.item",
|
|
2380
|
+
key: AUTO_COLLAPSE_NS,
|
|
2381
|
+
inject: () => ({ scope })
|
|
2382
|
+
},
|
|
2383
|
+
StatusTextCard
|
|
2384
|
+
));
|
|
2385
|
+
}
|
|
2386
|
+
|
|
1428
2387
|
// src/client.ts
|
|
1429
2388
|
var name = "dsh-auto-collapse";
|
|
1430
2389
|
var inject = [];
|
|
1431
2390
|
function apply(ctx) {
|
|
1432
2391
|
ctx.effect(() => {
|
|
1433
|
-
const
|
|
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));
|
|
1434
2396
|
controller.start();
|
|
1435
|
-
|
|
1436
|
-
|
|
2397
|
+
const offScope = scope?.subscribe(() => controller.refresh());
|
|
2398
|
+
const offSettings = slots === void 0 || scope === void 0 ? void 0 : setupSettingsCard({ slots }, scope);
|
|
2399
|
+
return () => {
|
|
2400
|
+
offScope?.();
|
|
2401
|
+
offSettings?.();
|
|
2402
|
+
controller.stop();
|
|
2403
|
+
};
|
|
2404
|
+
}, "dsh-auto-collapse: fold observer + settings card");
|
|
1437
2405
|
}
|
|
1438
2406
|
return __toCommonJS(client_exports);
|
|
1439
2407
|
})();
|