@khorsheed/dsh-message-timeline 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.en.md +1 -1
- package/README.i18n.yaml +2 -2
- package/README.md +1 -1
- package/lib/client.js +41 -14
- package/lib/types/client/TimelineRail.js +29 -13
- package/lib/types/client/rail-tracker.d.ts +1 -1
- package/lib/types/client/rail-tracker.js +23 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
# 变更记录
|
|
2
2
|
|
|
3
|
+
## 0.2.6(2026-10-24)
|
|
4
|
+
|
|
5
|
+
- 窄布局下退化为刻度条而不是整个隐藏;测量轨道高度时跳过隐藏的输入卡片;面板高度贴合行数,不再撑满整条轨道。
|
|
6
|
+
|
|
3
7
|
## 0.2.5(2026-10-22)
|
|
4
8
|
|
|
5
9
|
- **整会话一次列出,首条消息即刻可达**:列表改读宿主 `turnOutline` 投影(每轮一条:轮次号、`turn/start` 序号、50 字首条预览),与已加载窗口合并——不再靠逐页翻历史才能看到更早消息。未加载轮次的行点击后经会话 jump loader(`ISession.loadThrough`)回翻历史再落位;行物化即落位,翻页停滞或轮次不可达时落到其后最近可见行。
|
|
6
10
|
- **静止态对比度调整**:所有行统一压淡灰刻度(不再区分已加载/未加载),只有当前阅读位置为蓝色加长刻度加粗行文字(无选中时默认最新一条);回翻中的行以刻度脉动提示。
|
|
7
11
|
- **无文本预览的消息保留展示**(标记「无文本预览」),不再过滤;**修复自动轮次点击无反应**:重启恢复、定时任务等轮次在聊天视图渲染为 turn-trigger 卡片而非 user 行,原落位只找 user 行导致永远落不到。落位改为官方同款 `user ?? loaded[0]`:优先该轮首个 user 行,没有则落该轮第一个可见节点(触发卡片、图片气泡均可达)。
|
|
8
12
|
- **自动轮次成为一等行,选中不再跑偏**:turn-trigger 纳入时间轴行 kind——已加载时直接显示触发正文,未加载时标记「第 N 轮」;阅读位置锚点也认触发卡片,跳转后选中保持在该轮,不再跳到下一条用户消息。图片/附件消息分别标记「图片消息」「附件消息」。
|
|
13
|
+
- **窄布局不再隐藏时间轴**:右侧栏打开或窗口窄导致左沟槽不足最小宽度时,不再整条隐藏,而是退为左缘刻度条(24px,落在消息流留白内),悬停/聚焦时展开带底色的浮层;宽布局仍按沟槽封顶宽度。修复 Room 会话右侧栏展开时时间轴消失的问题。
|
|
14
|
+
- **修复 Room 会话时间轴高度为零**:Room 输入框以 overlay 接管输入区时,官方输入栏仍以 `display:none` 留在 DOM 里,测高误取它的零尺寸卡片,面板被压成几像素(只露一截蓝色刻度、打开时一闪而过)。现在跳过未布局的卡片,退到输入区顶边。
|
|
15
|
+
- **悬停浮层高度随消息条数**:窄布局悬停展开时,底色不再铺满从标签条到输入框的整段高度;面板高度随行数、在原位置垂直居中,条数多时才铺满并滚动。
|
|
9
16
|
- 装配未挂载 turn-outline 单元时退回原窗口分页行为(打开预取 + 面板滚顶翻页),`initialPages` 只作用于该兜底路径。
|
|
10
17
|
- 注入面变化:`jumpTo` 现在返回是否命中(供落位重试),新增 `loadThrough`。
|
|
11
18
|
- **适配宿主 0.2.1 线**:`@deepseek-ai/dsh-*` peer 区间追加 `^0.2.1-rc.1`(`^0.2.0-rc.1` 在 npm/pnpm 严格解析下不覆盖 0.2.1 预发布三元组);宿主 0.2.1 移除 invariants 体系,随包 invariant 伴生退役、兼容判定改由 package.json `engines.dsh` 声明;`dsh.compat.verifiedHost` 前移至 `0.2.1-rc.1`(全量构建+测试在该树通过,无其它表面变化)。
|
package/README.en.md
CHANGED
|
@@ -16,7 +16,7 @@ Once a session gets long, finding that requirement you mentioned three messages
|
|
|
16
16
|
- **Reading position tracking** — the current position's tick stays blue and lengthened with bold text (defaulting to the latest message while nothing is selected); every other row rests grey. A long answer anchors the lit tick to the user message it replies to.
|
|
17
17
|
- **Click to jump** — a loaded row scrolls the transcript straight to it; an outline row goes through the session's jump loader and lands on the freshly loaded message.
|
|
18
18
|
- **Long-history friendly** — a short list centers vertically; a long one scrolls, its bottom-most row flush with the chat input box; goal/todo dock cards never push the timeline up. Assemblies without the turn-outline unit fall back to window paging: a few pages prefetch on open, and older history loads when the panel is scrolled to its top.
|
|
19
|
-
- **
|
|
19
|
+
- **Width adapts to the layout** — on wide layouts the panel's width is capped by the scrollport's left gutter; on narrow ones (a right sidebar or a small window, e.g. a Room's member panel open) it degrades to a tick strip in the flow's left padding and expands into a solid-background overlay on hover/focus instead of hiding.
|
|
20
20
|
|
|
21
21
|
<img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/message-timeline2.png" width="640" alt="the timeline at rest: a strip of dimmed ticks that stays out of the way, expanding on hover">
|
|
22
22
|
|
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# last confirmed-consistent state. Both languages carry equal authority; after
|
|
3
3
|
# editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/message-timeline/README.en.md
|
|
5
|
-
packages/message-timeline/README.en.md:
|
|
6
|
-
packages/message-timeline/README.md:
|
|
5
|
+
packages/message-timeline/README.en.md: 812b2b960137a976e0d91267308f0854ed96e20d
|
|
6
|
+
packages/message-timeline/README.md: ba8eaffbfabed8162a8ff26256143de743759019
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
- **阅读位置跟踪**——当前位置的刻度保持蓝色、加长且文字加粗(未选中时默认最新一条),其余行一律灰色;停在超长回复中时锚定在所回答的用户消息上。
|
|
17
17
|
- **点击跳转**——点击已加载的行直接滚动到位;点击未加载的行经会话的 jump loader 回翻历史后落位。
|
|
18
18
|
- **长历史友好**——列表短时垂直居中;长时隐形滚动,最底部贴着聊天输入框;目标/任务等 dock 卡片不会把时间轴顶上去。投影缺席的装配(未挂载 turn-outline 单元)退回窗口分页:打开时预取数页,更早历史在面板滚动到顶部时按需加载。
|
|
19
|
-
-
|
|
19
|
+
- **宽度随布局自适应**——宽布局下面板宽度受滚动区左缘沟槽约束;窄布局(右侧栏打开、窗口窄,如 Room 的成员面板展开时)退为左缘刻度条,悬停或聚焦时展开带底色的浮层,不再直接隐藏。
|
|
20
20
|
|
|
21
21
|
<img src="https://raw.githubusercontent.com/Khorsheed/dsh-basic/main/docs/screenshots/message-timeline2.png" width="640" alt="时间轴的静止态:收成一排压淡的刻度条,不挡视线,悬停才展开">
|
|
22
22
|
|
package/lib/client.js
CHANGED
|
@@ -95,6 +95,25 @@ window.__ModuleLoader__.load({
|
|
|
95
95
|
chatView: false
|
|
96
96
|
};
|
|
97
97
|
/**
|
|
98
|
+
* The rect of the first laid-out chat input card in the scrollport. A
|
|
99
|
+
* business composer (the Room composer) takes the `conversation.composer`
|
|
100
|
+
* chain as an overlay: the official input bar stays mounted under a
|
|
101
|
+
* `display: none` fallback wrapper, so its card is still the first marker in
|
|
102
|
+
* the DOM but measures as a zero rect at y=0 — ending the box there collapses
|
|
103
|
+
* the panel to zero height. Unlaid-out cards are skipped, so such a layout
|
|
104
|
+
* falls through to the seat top.
|
|
105
|
+
* @param scrollport - the official conversation scrollport element.
|
|
106
|
+
* @returns the card's rect, or null when no laid-out card is rendered.
|
|
107
|
+
*/
|
|
108
|
+
function laidOutCard(scrollport) {
|
|
109
|
+
for (const card of scrollport.querySelectorAll("[data-composer-card]")) {
|
|
110
|
+
const rect = card.getBoundingClientRect();
|
|
111
|
+
if (rect.width === 0 && rect.height === 0) continue;
|
|
112
|
+
return rect;
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
98
117
|
* Measure the panel's viewport box from one scrollport: its rect inset by the
|
|
99
118
|
* panel padding, minus the chat input card at the bottom and the conversation
|
|
100
119
|
* tab strip at the top. The tabs render just above the scrollport, but
|
|
@@ -104,7 +123,7 @@ window.__ModuleLoader__.load({
|
|
|
104
123
|
* whole `[data-composer-seat]` top: dock cards (goal/todo/queue) sit above
|
|
105
124
|
* the input inside the seat, and counting them would push the timeline up off
|
|
106
125
|
* the conversation. Falls back to the seat top, then the column bottom, when
|
|
107
|
-
*
|
|
126
|
+
* no laid-out card is rendered (see {@link laidOutCard}).
|
|
108
127
|
* The scrollport's own width rides along for the width-cap fallback when the
|
|
109
128
|
* message-flow probe is unanswered.
|
|
110
129
|
* @param scrollport - the official conversation scrollport element.
|
|
@@ -122,9 +141,9 @@ window.__ModuleLoader__.load({
|
|
|
122
141
|
break;
|
|
123
142
|
}
|
|
124
143
|
}
|
|
125
|
-
const card = scrollport
|
|
144
|
+
const card = laidOutCard(scrollport);
|
|
126
145
|
const seat = scrollport.querySelector("[data-composer-seat]");
|
|
127
|
-
const bottom = card !== null ? card.
|
|
146
|
+
const bottom = card !== null ? card.top - RAIL_VERTICAL_PADDING : seat !== null ? seat.getBoundingClientRect().top - RAIL_VERTICAL_PADDING : rect.bottom - RAIL_VERTICAL_PADDING;
|
|
128
147
|
return {
|
|
129
148
|
left: rect.left + RAIL_LEFT_INSET,
|
|
130
149
|
top: rect.top + topInset,
|
|
@@ -532,7 +551,7 @@ window.__ModuleLoader__.load({
|
|
|
532
551
|
}
|
|
533
552
|
//#endregion
|
|
534
553
|
//#region \0dsh-css:src/client/TimelineRail.module.css.mjs
|
|
535
|
-
const css = "._8u17xW_panel{scrollbar-width:none;z-index:100;pointer-events:none;flex-direction:column;padding:4px 8px;display:flex;position:fixed;overflow-y:auto}._8u17xW_panel
|
|
554
|
+
const css = "._8u17xW_panel{scrollbar-width:none;z-index:100;pointer-events:none;flex-direction:column;padding:4px 8px;display:flex;position:fixed;overflow-y:auto}._8u17xW_panel::-webkit-scrollbar{display:none}._8u17xW_item{width:100%;max-width:20px;color:var(--dsw-alias-label-tertiary);text-align:left;cursor:pointer;-webkit-user-select:none;user-select:none;pointer-events:auto;opacity:.55;background:0 0;border:none;flex:none;align-items:center;gap:8px;padding:5px 0;font-size:13px;line-height:1.5;transition:opacity .12s;display:flex}._8u17xW_panel:hover ._8u17xW_item,._8u17xW_panel:focus-within ._8u17xW_item{opacity:1;max-width:100%}._8u17xW_tick{background:var(--dsw-alias-label-tertiary);border-radius:2px;flex:none;width:3px;height:14px}._8u17xW_itemCurrent{color:var(--dsw-static-deepseek-500);opacity:1;font-weight:600}._8u17xW_tickCurrent{background:var(--dsw-static-deepseek-500);height:18px}._8u17xW_itemText{white-space:nowrap;text-overflow:ellipsis;opacity:0;transition:opacity .12s;overflow:hidden}._8u17xW_panel:hover ._8u17xW_itemText,._8u17xW_panel:focus-within ._8u17xW_itemText{opacity:1;transition-delay:90ms}._8u17xW_itemFocused{color:var(--dsw-alias-label-primary);font-weight:600}._8u17xW_item:focus:not(:focus-visible){outline:none}._8u17xW_item:focus-visible{outline:1px solid var(--dsw-alias-state-business-primary);outline-offset:1px}._8u17xW_itemPending ._8u17xW_tick{background:var(--dsw-static-deepseek-400);animation:1s ease-in-out infinite _8u17xW_railPending}@keyframes _8u17xW_railPending{50%{opacity:.2}}@media (prefers-reduced-motion:reduce){._8u17xW_itemPending ._8u17xW_tick{animation:none}}._8u17xW_panelNarrow{z-index:101}._8u17xW_panelNarrow:hover,._8u17xW_panelNarrow:focus-within{background:var(--dsw-alias-bg-layer-1);border-radius:8px;box-shadow:0 4px 16px #0000002e;width:var(--panel-wide)!important}";
|
|
536
555
|
const tagId = "@khorsheed/dsh-message-timeline/TimelineRail.module.css";
|
|
537
556
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
538
557
|
const tag = document.createElement("style");
|
|
@@ -542,15 +561,16 @@ window.__ModuleLoader__.load({
|
|
|
542
561
|
document.head.appendChild(tag);
|
|
543
562
|
}
|
|
544
563
|
var TimelineRail_module_css_default = {
|
|
564
|
+
"panelNarrow": "_8u17xW_panelNarrow",
|
|
545
565
|
"itemText": "_8u17xW_itemText",
|
|
546
|
-
"railPending": "_8u17xW_railPending",
|
|
547
566
|
"panel": "_8u17xW_panel",
|
|
567
|
+
"itemPending": "_8u17xW_itemPending",
|
|
548
568
|
"tickCurrent": "_8u17xW_tickCurrent",
|
|
549
569
|
"item": "_8u17xW_item",
|
|
550
570
|
"itemCurrent": "_8u17xW_itemCurrent",
|
|
551
|
-
"
|
|
571
|
+
"itemFocused": "_8u17xW_itemFocused",
|
|
552
572
|
"tick": "_8u17xW_tick",
|
|
553
|
-
"
|
|
573
|
+
"railPending": "_8u17xW_railPending"
|
|
554
574
|
};
|
|
555
575
|
//#endregion
|
|
556
576
|
//#region src/client/TimelineRail.tsx
|
|
@@ -613,6 +633,10 @@ window.__ModuleLoader__.load({
|
|
|
613
633
|
const PANEL_GAP = 16;
|
|
614
634
|
/** Degraded width cap (fraction of the scrollport) while the flow probe is unanswered. */
|
|
615
635
|
const DEGRADED_WIDTH_RATIO = .4;
|
|
636
|
+
/** Rest width of the narrow-layout tick strip (px) — fits the flow's left padding. */
|
|
637
|
+
const PANEL_NARROW_REST_WIDTH = 24;
|
|
638
|
+
/** Expanded width of the narrow-layout hover overlay (fraction of the scrollport). */
|
|
639
|
+
const NARROW_WIDE_RATIO = .6;
|
|
616
640
|
/**
|
|
617
641
|
* Frames an outline jump retries the DOM scroll after its row materializes in
|
|
618
642
|
* the chat store: the transcript renders the new page in the same commit or a
|
|
@@ -746,8 +770,9 @@ window.__ModuleLoader__.load({
|
|
|
746
770
|
const active = rail.chatView && rail.ready && rail.sessionId === sessionId;
|
|
747
771
|
const visible = active && items.length > 0;
|
|
748
772
|
const gutter = rail.flowLeft === null ? null : rail.flowLeft - rail.left - PANEL_PADDING_X - PANEL_GAP;
|
|
749
|
-
const
|
|
750
|
-
const
|
|
773
|
+
const narrow = gutter !== null && gutter < 120;
|
|
774
|
+
const width = gutter === null ? Math.min(panelWidth, Math.max(120, rail.scrollportWidth * DEGRADED_WIDTH_RATIO)) : narrow ? PANEL_NARROW_REST_WIDTH : Math.min(panelWidth, gutter);
|
|
775
|
+
const wideWidth = Math.min(panelWidth, Math.max(120, rail.scrollportWidth * NARROW_WIDE_RATIO));
|
|
751
776
|
(0, react.useEffect)(() => {
|
|
752
777
|
panelRef.current?.querySelector(`[data-item-key=${JSON.stringify(current)}]`)?.scrollIntoView({ block: "nearest" });
|
|
753
778
|
}, [current]);
|
|
@@ -792,19 +817,21 @@ window.__ModuleLoader__.load({
|
|
|
792
817
|
fromHead: null
|
|
793
818
|
});
|
|
794
819
|
};
|
|
795
|
-
if (!visible
|
|
820
|
+
if (!visible) return null;
|
|
796
821
|
return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
797
822
|
ref: panelRef,
|
|
798
|
-
className: TimelineRail_module_css_default.panel
|
|
823
|
+
className: `${TimelineRail_module_css_default.panel} ${narrow ? TimelineRail_module_css_default.panelNarrow : ""}`,
|
|
799
824
|
role: "navigation",
|
|
800
825
|
"aria-label": t("rail.panel"),
|
|
801
826
|
tabIndex: 0,
|
|
802
827
|
"data-timeline-panel": "",
|
|
803
828
|
style: {
|
|
804
829
|
left: rail.left,
|
|
805
|
-
top: rail.top,
|
|
806
|
-
|
|
807
|
-
|
|
830
|
+
top: rail.top + rail.height / 2,
|
|
831
|
+
maxHeight: rail.height,
|
|
832
|
+
transform: "translateY(-50%)",
|
|
833
|
+
width,
|
|
834
|
+
...narrow ? { "--panel-wide": `${wideWidth}px` } : {}
|
|
808
835
|
},
|
|
809
836
|
onScroll: onPanelScroll,
|
|
810
837
|
onKeyDown: (event) => {
|
|
@@ -74,6 +74,10 @@ const PANEL_PADDING_X = 8;
|
|
|
74
74
|
const PANEL_GAP = 16;
|
|
75
75
|
/** Degraded width cap (fraction of the scrollport) while the flow probe is unanswered. */
|
|
76
76
|
const DEGRADED_WIDTH_RATIO = 0.4;
|
|
77
|
+
/** Rest width of the narrow-layout tick strip (px) — fits the flow's left padding. */
|
|
78
|
+
const PANEL_NARROW_REST_WIDTH = 24;
|
|
79
|
+
/** Expanded width of the narrow-layout hover overlay (fraction of the scrollport). */
|
|
80
|
+
const NARROW_WIDE_RATIO = 0.6;
|
|
77
81
|
/**
|
|
78
82
|
* Frames an outline jump retries the DOM scroll after its row materializes in
|
|
79
83
|
* the chat store: the transcript renders the new page in the same commit or a
|
|
@@ -260,20 +264,21 @@ export function TimelineRail({ sessionId, includeSteering, panelWidth, initialPa
|
|
|
260
264
|
// pages until one materializes — hence `active` splits from `visible`.
|
|
261
265
|
const active = rail.chatView && rail.ready && rail.sessionId === sessionId;
|
|
262
266
|
const visible = active && items.length > 0;
|
|
263
|
-
// The panel must never cover the message flow: its width is the
|
|
264
|
-
// preferred width capped by the scrollport's left gutter — the
|
|
265
|
-
// flow's left edge minus the panel's left edge, less the panel's
|
|
266
|
-
// padding and the visible breathing gap. A gutter too small for the
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
267
|
+
// The panel must never cover the message flow AT REST: its width is the
|
|
268
|
+
// configured preferred width capped by the scrollport's left gutter — the
|
|
269
|
+
// message flow's left edge minus the panel's left edge, less the panel's
|
|
270
|
+
// right padding and the visible breathing gap. A gutter too small for the
|
|
271
|
+
// minimum width no longer hides the panel: it degrades to a tick strip in
|
|
272
|
+
// the flow's left padding and expands over the flow on hover/focus with a
|
|
273
|
+
// solid background (right sidebars and narrow windows keep the rail
|
|
274
|
+
// usable). When the flow probe is unanswered (official structure change),
|
|
275
|
+
// the width degrades to a fraction of the scrollport instead.
|
|
272
276
|
const gutter = rail.flowLeft === null ? null : rail.flowLeft - rail.left - PANEL_PADDING_X - PANEL_GAP;
|
|
277
|
+
const narrow = gutter !== null && gutter < PANEL_WIDTH_MIN;
|
|
273
278
|
const width = gutter === null
|
|
274
279
|
? Math.min(panelWidth, Math.max(PANEL_WIDTH_MIN, rail.scrollportWidth * DEGRADED_WIDTH_RATIO))
|
|
275
|
-
: Math.min(panelWidth,
|
|
276
|
-
const
|
|
280
|
+
: narrow ? PANEL_NARROW_REST_WIDTH : Math.min(panelWidth, gutter);
|
|
281
|
+
const wideWidth = Math.min(panelWidth, Math.max(PANEL_WIDTH_MIN, rail.scrollportWidth * NARROW_WIDE_RATIO));
|
|
277
282
|
// Keep the lit row in view: the panel follows the reading position (a new
|
|
278
283
|
// message scrolls its row in), and mouse browsing is never yanked because
|
|
279
284
|
// the hovered row is the current one and always visible under the pointer.
|
|
@@ -326,9 +331,20 @@ export function TimelineRail({ sessionId, includeSteering, panelWidth, initialPa
|
|
|
326
331
|
// landing scroll. Re-clicking another outline row retargets the jump.
|
|
327
332
|
setPendingJump({ sessionId, turn: row.turn, seq: row.seq, requested: false, awaiting: false, fromHead: null });
|
|
328
333
|
};
|
|
329
|
-
if (!visible
|
|
334
|
+
if (!visible)
|
|
330
335
|
return null;
|
|
331
|
-
return createPortal(_jsx("div", { ref: panelRef, className: css.panel
|
|
336
|
+
return createPortal(_jsx("div", { ref: panelRef, className: `${css.panel} ${narrow ? css.panelNarrow : ''}`, role: "navigation", "aria-label": t('rail.panel'), tabIndex: 0, "data-timeline-panel": "", style: {
|
|
337
|
+
// The box fits its rows (up to the rail height) and centers on the
|
|
338
|
+
// rail's midline, so the narrow-mode hover background wraps the list
|
|
339
|
+
// instead of painting the whole column; a longer list fills the rail
|
|
340
|
+
// and scrolls.
|
|
341
|
+
left: rail.left,
|
|
342
|
+
top: rail.top + rail.height / 2,
|
|
343
|
+
maxHeight: rail.height,
|
|
344
|
+
transform: 'translateY(-50%)',
|
|
345
|
+
width,
|
|
346
|
+
...(narrow ? { '--panel-wide': `${wideWidth}px` } : {}),
|
|
347
|
+
}, onScroll: onPanelScroll, onKeyDown: (event) => {
|
|
332
348
|
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
333
349
|
event.preventDefault();
|
|
334
350
|
const anchor = items.findIndex(item => item.id === current);
|
|
@@ -25,7 +25,7 @@ import type { TimelineRailState } from './slots.ts';
|
|
|
25
25
|
* whole `[data-composer-seat]` top: dock cards (goal/todo/queue) sit above
|
|
26
26
|
* the input inside the seat, and counting them would push the timeline up off
|
|
27
27
|
* the conversation. Falls back to the seat top, then the column bottom, when
|
|
28
|
-
*
|
|
28
|
+
* no laid-out card is rendered (see {@link laidOutCard}).
|
|
29
29
|
* The scrollport's own width rides along for the width-cap fallback when the
|
|
30
30
|
* message-flow probe is unanswered.
|
|
31
31
|
* @param scrollport - the official conversation scrollport element.
|
|
@@ -14,6 +14,26 @@ const IDLE = {
|
|
|
14
14
|
sessionId: undefined, ready: false, left: 0, top: 0, height: 0, scrollportWidth: 0, flowLeft: null,
|
|
15
15
|
activeKey: null, chatView: false,
|
|
16
16
|
};
|
|
17
|
+
/**
|
|
18
|
+
* The rect of the first laid-out chat input card in the scrollport. A
|
|
19
|
+
* business composer (the Room composer) takes the `conversation.composer`
|
|
20
|
+
* chain as an overlay: the official input bar stays mounted under a
|
|
21
|
+
* `display: none` fallback wrapper, so its card is still the first marker in
|
|
22
|
+
* the DOM but measures as a zero rect at y=0 — ending the box there collapses
|
|
23
|
+
* the panel to zero height. Unlaid-out cards are skipped, so such a layout
|
|
24
|
+
* falls through to the seat top.
|
|
25
|
+
* @param scrollport - the official conversation scrollport element.
|
|
26
|
+
* @returns the card's rect, or null when no laid-out card is rendered.
|
|
27
|
+
*/
|
|
28
|
+
function laidOutCard(scrollport) {
|
|
29
|
+
for (const card of scrollport.querySelectorAll('[data-composer-card]')) {
|
|
30
|
+
const rect = card.getBoundingClientRect();
|
|
31
|
+
if (rect.width === 0 && rect.height === 0)
|
|
32
|
+
continue;
|
|
33
|
+
return rect;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
17
37
|
/**
|
|
18
38
|
* Measure the panel's viewport box from one scrollport: its rect inset by the
|
|
19
39
|
* panel padding, minus the chat input card at the bottom and the conversation
|
|
@@ -24,7 +44,7 @@ const IDLE = {
|
|
|
24
44
|
* whole `[data-composer-seat]` top: dock cards (goal/todo/queue) sit above
|
|
25
45
|
* the input inside the seat, and counting them would push the timeline up off
|
|
26
46
|
* the conversation. Falls back to the seat top, then the column bottom, when
|
|
27
|
-
*
|
|
47
|
+
* no laid-out card is rendered (see {@link laidOutCard}).
|
|
28
48
|
* The scrollport's own width rides along for the width-cap fallback when the
|
|
29
49
|
* message-flow probe is unanswered.
|
|
30
50
|
* @param scrollport - the official conversation scrollport element.
|
|
@@ -44,10 +64,10 @@ export function measureGeometry(scrollport) {
|
|
|
44
64
|
break;
|
|
45
65
|
}
|
|
46
66
|
}
|
|
47
|
-
const card = scrollport
|
|
67
|
+
const card = laidOutCard(scrollport);
|
|
48
68
|
const seat = scrollport.querySelector('[data-composer-seat]');
|
|
49
69
|
const bottom = card !== null
|
|
50
|
-
? card.
|
|
70
|
+
? card.top - RAIL_VERTICAL_PADDING
|
|
51
71
|
: seat !== null
|
|
52
72
|
? seat.getBoundingClientRect().top - RAIL_VERTICAL_PADDING
|
|
53
73
|
: rect.bottom - RAIL_VERTICAL_PADDING;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khorsheed/dsh-message-timeline",
|
|
3
3
|
"description": "Message timeline: a flat floating list over the conversation scrollport that jumps to any user message, with a tick-only rest state",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.6",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|