dsh-select-quote 0.3.6 → 0.3.7
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.md +6 -4
- package/lib/client.js +454 -226
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,9 +38,9 @@ dsh web --patch "$PWD/dev.patch.yml"
|
|
|
38
38
|
|
|
39
39
|
## 用法
|
|
40
40
|
|
|
41
|
-
1.
|
|
42
|
-
2.
|
|
43
|
-
3.
|
|
41
|
+
1. 在对话区选中一段文本,点「添加到任务」——到这一步批注就已经保存,输入框上方同步出现摘要卡片
|
|
42
|
+
2. 想补充的话在弹出的评论框里写一句,点「添加评论」或 `⌘/Ctrl + Enter`;不写就直接点空白处或「暂不评论」,批注同样保留
|
|
43
|
+
3. 悬停摘要卡片展开列表,可逐条编辑、删除或一键清空,点条目跳回正文对应文字
|
|
44
44
|
4. 正常打字发送,批注随消息一起交给模型,卡片随之消失
|
|
45
45
|
5. 模型回答里引用某条批注的位置显示为可点击 chip
|
|
46
46
|
|
|
@@ -69,7 +69,9 @@ dsh web --patch "$PWD/dev.patch.yml"
|
|
|
69
69
|
|
|
70
70
|
**升级 dsh 之后功能不对?** 先 `pnpm run check`;产物没问题就对照 [`docs/internals.md`](docs/internals.md) 的「依赖的产品内部约定」逐条复核,那里列了所有产品内部名。
|
|
71
71
|
|
|
72
|
-
**划词没有工具条?** 确认选区在对话区而不是编辑器里;刷新页面(样式与观察者是幂等注入的);宿主 boot 日志里应出现 `dsh-select-quote
|
|
72
|
+
**划词没有工具条?** 确认选区在对话区而不是编辑器里;刷新页面(样式与观察者是幂等注入的);宿主 boot 日志里应出现 `dsh-select-quote`。工具条挂在对话滚动区里,随正文一起滚动,滚到顶部会像正文一样滑进 header 下面;选中行完全滚出视口后收起,滚回去重新出现。
|
|
73
|
+
|
|
74
|
+
**点空白处批注会丢吗?** 不会。点「添加到任务」时批注已经保存,空白点击只关闭评论框;已经写了一半的评论会一并保存到那条批注上。
|
|
73
75
|
|
|
74
76
|
**输入框里出现了协议原文?** 发送前的注入没生效,一般是 composer 结构变了——检查 `[data-composer-card]` 是否还在。
|
|
75
77
|
|
package/lib/client.js
CHANGED
|
@@ -5,8 +5,8 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
|
-
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
8
|
let react_dom = require("react-dom");
|
|
9
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
10
10
|
//#region src/client/dom/composer-host.ts
|
|
11
11
|
/**
|
|
12
12
|
* Host DOM contract for the resident composer.
|
|
@@ -138,6 +138,28 @@ ${atomRules}
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
/* Same entrance for the below-anchor variant (only the content top uses it). */
|
|
142
|
+
@keyframes dsq-toolbar-in-below {
|
|
143
|
+
from {
|
|
144
|
+
transform: translate(-50%, 0) translateY(-4px) scale(0.96);
|
|
145
|
+
}
|
|
146
|
+
to {
|
|
147
|
+
transform: translate(-50%, 0) translateY(0) scale(1);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Comment phase: the hint sits above the popover's top-left corner (explicit
|
|
152
|
+
top-left placement comes from placeCommentPop), so the entrance no longer
|
|
153
|
+
owns the centering shift. */
|
|
154
|
+
@keyframes dsq-toolbar-in-popover-top {
|
|
155
|
+
from {
|
|
156
|
+
transform: translateY(4px) scale(0.96);
|
|
157
|
+
}
|
|
158
|
+
to {
|
|
159
|
+
transform: translateY(0) scale(1);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
141
163
|
/* 内容层的 rise:悬停面板的列表在用;工具条改用容器 scale 后这条在这里待命。 */
|
|
142
164
|
@keyframes dsq-rise-in {
|
|
143
165
|
from {
|
|
@@ -184,10 +206,35 @@ ${atomRules}
|
|
|
184
206
|
--dsq-shadow: var(--dsw-elevation-prominent, 0 0 0 .5px rgba(0, 0, 0, 0.04), 0 3px 8px rgba(0, 0, 0, 0.04), 0 0 20px rgba(0, 0, 0, 0.05));
|
|
185
207
|
}
|
|
186
208
|
|
|
209
|
+
/* 浮层挂载与层带(2026-10-20 定,改前先读):
|
|
210
|
+
工具条/评论泡/状态不再 portal 到 body,而是由 dom/floating-layer.ts 挂进对话
|
|
211
|
+
真正的滚动容器(桌面抽屉布局是 ST7X_W_scrollBody,普通布局是 .icaHSq_scroll)
|
|
212
|
+
里的一个 absolute host,坐标用**内容坐标**。
|
|
213
|
+
|
|
214
|
+
为什么不是 body:header(对话标题那一排)是滚动容器之外的非定位元素,任何
|
|
215
|
+
body 级 positioned + z-index 的浮层都天然压过它(这就是 z-index 降到 0 也仍在
|
|
216
|
+
header 之上的原因);composer 座位是 sticky + z-index 7,只有低于它才不会被
|
|
217
|
+
输入框穿透。
|
|
218
|
+
|
|
219
|
+
现在把两件事交回宿主布局:
|
|
220
|
+
- 滚动容器负责裁剪:浮层随内容上移,到容器上边缘就被裁掉,自然滑进 header
|
|
221
|
+
下面,和正文一致;
|
|
222
|
+
- host 的 z-index 固定 6(见 floating-layer.ts):低于 composer 的 7,高于
|
|
223
|
+
内容里吸顶元素的 6(同 z 时 host 追加在滚动容器末尾,DOM 次序压过它们)。
|
|
224
|
+
host 内部次序:status 3 > toolbar 2 > commentPop 1。
|
|
225
|
+
|
|
226
|
+
摘要行与批注卡片在内容普通流里,不参与这套层带;悬停面板仍是 body 级 fixed
|
|
227
|
+
(原因见 AnnotationCardView 里 contain:layout 的说明)。 */
|
|
228
|
+
|
|
187
229
|
.dsq_toolbar {
|
|
188
|
-
position:
|
|
189
|
-
z-index:
|
|
190
|
-
|
|
230
|
+
position: absolute;
|
|
231
|
+
z-index: 2;
|
|
232
|
+
/* The layer host is 0×0, so an auto-width absolute child would shrink to
|
|
233
|
+
min-content (Chinese hint text then wraps one character per line).
|
|
234
|
+
max-content keeps the bubble sized by its row regardless of the host. */
|
|
235
|
+
width: max-content;
|
|
236
|
+
max-width: calc(100vw - 24px);
|
|
237
|
+
transform: translate(-50%, var(--dsq-anchor-y, -100%));
|
|
191
238
|
gap: 2px;
|
|
192
239
|
padding: 4px;
|
|
193
240
|
/* 无边框:--dsq-shadow(宿主的 elevation-prominent)自带 0 0 0 .5px 的描边环,
|
|
@@ -202,6 +249,18 @@ ${atomRules}
|
|
|
202
249
|
animation: dsq-toolbar-in 0.16s cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
203
250
|
}
|
|
204
251
|
|
|
252
|
+
/* Hangs below the selection: no room above at the very top of the transcript. */
|
|
253
|
+
.dsq_toolbar[data-anchor='below'] {
|
|
254
|
+
--dsq-anchor-y: 0%;
|
|
255
|
+
animation-name: dsq-toolbar-in-below;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* Comment phase: placed at an explicit top-left, 6px above the comment popover. */
|
|
259
|
+
.dsq_toolbar[data-anchor='popover-top'] {
|
|
260
|
+
transform: none;
|
|
261
|
+
animation-name: dsq-toolbar-in-popover-top;
|
|
262
|
+
}
|
|
263
|
+
|
|
205
264
|
/* 按钮行:只负责布局;入场动画在容器上(见 .dsq_toolbar 的 A/B 说明)。 */
|
|
206
265
|
.dsq_toolbarInner {
|
|
207
266
|
display: inline-flex;
|
|
@@ -248,8 +307,8 @@ ${atomRules}
|
|
|
248
307
|
}
|
|
249
308
|
|
|
250
309
|
.dsq_status {
|
|
251
|
-
position:
|
|
252
|
-
z-index:
|
|
310
|
+
position: absolute;
|
|
311
|
+
z-index: 3;
|
|
253
312
|
transform: translate(-50%, 8px);
|
|
254
313
|
padding: 4px 10px;
|
|
255
314
|
border-radius: 999px;
|
|
@@ -266,7 +325,7 @@ ${atomRules}
|
|
|
266
325
|
position: absolute;
|
|
267
326
|
top: 8px;
|
|
268
327
|
left: 8px;
|
|
269
|
-
z-index:
|
|
328
|
+
z-index: 12;
|
|
270
329
|
width: 20%;
|
|
271
330
|
min-width: 148px;
|
|
272
331
|
max-width: 220px;
|
|
@@ -406,7 +465,7 @@ ${atomRules}
|
|
|
406
465
|
/* Qoder-style annotation summary (“N 条批注”) inside the composer. */
|
|
407
466
|
.dsq_summary {
|
|
408
467
|
position: relative;
|
|
409
|
-
z-index:
|
|
468
|
+
z-index: 13;
|
|
410
469
|
gap: 10px;
|
|
411
470
|
width: 100%;
|
|
412
471
|
max-width: 100%;
|
|
@@ -450,7 +509,7 @@ ${atomRules}
|
|
|
450
509
|
flex atoms — the open class below turns display back on. */
|
|
451
510
|
display: none;
|
|
452
511
|
position: fixed;
|
|
453
|
-
z-index:
|
|
512
|
+
z-index: 14;
|
|
454
513
|
width: min(320px, calc(100vw - 16px));
|
|
455
514
|
flex-direction: column;
|
|
456
515
|
padding: 12px;
|
|
@@ -671,15 +730,19 @@ ${atomRules}
|
|
|
671
730
|
|
|
672
731
|
/* Inline comment composer under the floating selection toolbar. */
|
|
673
732
|
.dsq_commentPop {
|
|
674
|
-
position:
|
|
675
|
-
z-index:
|
|
676
|
-
|
|
733
|
+
position: absolute;
|
|
734
|
+
z-index: 1;
|
|
735
|
+
/* Vertical offset comes from placeCommentPop (below the selection); only
|
|
736
|
+
the horizontal centering belongs to the transform. */
|
|
737
|
+
transform: translateX(-50%);
|
|
677
738
|
width: min(320px, calc(100vw - 24px));
|
|
678
739
|
gap: 8px;
|
|
679
740
|
padding: 8px;
|
|
680
741
|
border-radius: 16px;
|
|
681
742
|
/* 底色与模糊都在材质图层里 */
|
|
682
743
|
box-shadow: var(--dsq-shadow);
|
|
744
|
+
/* The layer host is pointer-events:none; interactive surfaces opt back in. */
|
|
745
|
+
pointer-events: auto;
|
|
683
746
|
}
|
|
684
747
|
|
|
685
748
|
.dsq_commentInput {
|
|
@@ -849,6 +912,7 @@ ${atomRules}
|
|
|
849
912
|
.dsq_annMark {
|
|
850
913
|
--dsq-mark-color: var(--dsq-accent);
|
|
851
914
|
--dsq-mark-tint: var(--dsq-accent-tint);
|
|
915
|
+
position: relative;
|
|
852
916
|
background: var(--dsq-mark-tint);
|
|
853
917
|
text-decoration: underline;
|
|
854
918
|
text-decoration-style: dashed;
|
|
@@ -862,10 +926,17 @@ ${atomRules}
|
|
|
862
926
|
|
|
863
927
|
/* Ordinal as a superscript corner badge at the END of the marked text, so it
|
|
864
928
|
never sits between the reader and the sentence. Only the last fragment of a
|
|
865
|
-
multi-fragment mark carries the index, so the number appears exactly once.
|
|
929
|
+
multi-fragment mark carries the index, so the number appears exactly once.
|
|
930
|
+
Absolute so the badge adds no inline advance width: annotating never reflows
|
|
931
|
+
the paragraph or moves following words. Bottom (not top) anchors to the last
|
|
932
|
+
fragment: for a mark spanning several lines the containing block is the union
|
|
933
|
+
of the first and last fragments, so its bottom edge is the last line. */
|
|
866
934
|
.dsq_annMark[data-dsq-ann-index]::after {
|
|
867
935
|
content: attr(data-dsq-ann-index);
|
|
868
|
-
|
|
936
|
+
position: absolute;
|
|
937
|
+
bottom: 8px;
|
|
938
|
+
left: 100%;
|
|
939
|
+
display: grid;
|
|
869
940
|
place-items: center;
|
|
870
941
|
min-width: 15px;
|
|
871
942
|
height: 15px;
|
|
@@ -877,7 +948,7 @@ ${atomRules}
|
|
|
877
948
|
font-size: 10px;
|
|
878
949
|
font-weight: 600;
|
|
879
950
|
line-height: 1;
|
|
880
|
-
|
|
951
|
+
pointer-events: none;
|
|
881
952
|
}
|
|
882
953
|
|
|
883
954
|
.dsq_annMark:hover {
|
|
@@ -1163,6 +1234,12 @@ function createMark(job, withIndex) {
|
|
|
1163
1234
|
* selections that cross inline elements or paragraph boundaries never reorder
|
|
1164
1235
|
* the DOM; fragments of one annotation share the mark id, and the ordinal
|
|
1165
1236
|
* badge rides on the final fragment (end of the annotation).
|
|
1237
|
+
*
|
|
1238
|
+
* `surroundContents` is load-bearing: `extractContents()` leaves the text
|
|
1239
|
+
* before **and after** the range in the same node, so inserting the mark at
|
|
1240
|
+
* `node.nextSibling` (the old implementation) appended every partial selection
|
|
1241
|
+
* to the end of its parent — a selection starting at offset 0, such as the
|
|
1242
|
+
* sentence after a colon, visibly jumped to the paragraph's end.
|
|
1166
1243
|
*/
|
|
1167
1244
|
function wrapRange(item, job, from, to) {
|
|
1168
1245
|
const located = cleanTextRange(item, from, to);
|
|
@@ -1172,15 +1249,12 @@ function wrapRange(item, job, from, to) {
|
|
|
1172
1249
|
let wrapped = false;
|
|
1173
1250
|
pieces.forEach((piece, i) => {
|
|
1174
1251
|
const node = piece.entry.node;
|
|
1175
|
-
|
|
1176
|
-
if (!parent) return;
|
|
1252
|
+
if (!node.parentElement) return;
|
|
1177
1253
|
const range = document.createRange();
|
|
1178
1254
|
range.setStart(node, piece.start - piece.entry.start);
|
|
1179
1255
|
range.setEnd(node, piece.end - piece.entry.start);
|
|
1180
1256
|
const mark = createMark(job, i === pieces.length - 1);
|
|
1181
|
-
|
|
1182
|
-
parent.insertBefore(mark, node.nextSibling);
|
|
1183
|
-
if (node.nodeValue === "") node.remove();
|
|
1257
|
+
range.surroundContents(mark);
|
|
1184
1258
|
wrapped = true;
|
|
1185
1259
|
});
|
|
1186
1260
|
return wrapped;
|
|
@@ -1715,20 +1789,24 @@ const GAP = 8;
|
|
|
1715
1789
|
const MARGIN = 8;
|
|
1716
1790
|
/**
|
|
1717
1791
|
* Place `panel` near `anchor`, flipping vertical side when the preferred side
|
|
1718
|
-
* lacks room. Keeps the panel inside the viewport
|
|
1792
|
+
* lacks room. Keeps the panel inside `bounds` (the viewport by default).
|
|
1719
1793
|
*/
|
|
1720
|
-
function placePopover(anchor, panel, preferred = "bottom", gap = GAP) {
|
|
1721
|
-
const
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1794
|
+
function placePopover(anchor, panel, preferred = "bottom", gap = GAP, bounds) {
|
|
1795
|
+
const box = bounds ?? {
|
|
1796
|
+
top: 0,
|
|
1797
|
+
left: 0,
|
|
1798
|
+
right: window.innerWidth,
|
|
1799
|
+
bottom: window.innerHeight
|
|
1800
|
+
};
|
|
1801
|
+
const spaceBelow = box.bottom - anchor.bottom;
|
|
1802
|
+
const spaceAbove = anchor.top - box.top;
|
|
1725
1803
|
let side = preferred;
|
|
1726
1804
|
const need = panel.height + gap;
|
|
1727
1805
|
if (preferred === "bottom" && spaceBelow < need && spaceAbove > spaceBelow) side = "top";
|
|
1728
1806
|
else if (preferred === "top" && spaceAbove < need && spaceBelow > spaceAbove) side = "bottom";
|
|
1729
1807
|
return {
|
|
1730
|
-
top: side === "bottom" ? Math.min(anchor.bottom + gap,
|
|
1731
|
-
left: Math.min(Math.max(anchor.left, MARGIN), Math.max(MARGIN,
|
|
1808
|
+
top: side === "bottom" ? Math.min(anchor.bottom + gap, box.bottom - panel.height - MARGIN) : Math.max(anchor.top - panel.height - gap, box.top + MARGIN),
|
|
1809
|
+
left: Math.min(Math.max(anchor.left, box.left + MARGIN), Math.max(box.left + MARGIN, box.right - panel.width - MARGIN)),
|
|
1732
1810
|
side
|
|
1733
1811
|
};
|
|
1734
1812
|
}
|
|
@@ -1746,78 +1824,32 @@ const SVG_ATTRS = {
|
|
|
1746
1824
|
focusable: false
|
|
1747
1825
|
};
|
|
1748
1826
|
const SHAPES = {
|
|
1749
|
-
copy: (0, react_jsx_runtime.jsxs)("g", { children: [(0, react_jsx_runtime.jsx)("rect", {
|
|
1750
|
-
key: "r",
|
|
1827
|
+
copy: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
|
|
1751
1828
|
width: 14,
|
|
1752
1829
|
height: 14,
|
|
1753
1830
|
x: 8,
|
|
1754
1831
|
y: 8,
|
|
1755
1832
|
rx: 2,
|
|
1756
1833
|
ry: 2
|
|
1757
|
-
}), (0, react_jsx_runtime.jsx)("path", {
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
key: "p2",
|
|
1766
|
-
d: "m15 5 4 4"
|
|
1767
|
-
})] }),
|
|
1768
|
-
"trash-2": (0, react_jsx_runtime.jsxs)("g", { children: [
|
|
1769
|
-
(0, react_jsx_runtime.jsx)("path", {
|
|
1770
|
-
key: "p1",
|
|
1771
|
-
d: "M10 11v6"
|
|
1772
|
-
}),
|
|
1773
|
-
(0, react_jsx_runtime.jsx)("path", {
|
|
1774
|
-
key: "p2",
|
|
1775
|
-
d: "M14 11v6"
|
|
1776
|
-
}),
|
|
1777
|
-
(0, react_jsx_runtime.jsx)("path", {
|
|
1778
|
-
key: "p3",
|
|
1779
|
-
d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"
|
|
1780
|
-
}),
|
|
1781
|
-
(0, react_jsx_runtime.jsx)("path", {
|
|
1782
|
-
key: "p4",
|
|
1783
|
-
d: "M3 6h18"
|
|
1784
|
-
}),
|
|
1785
|
-
(0, react_jsx_runtime.jsx)("path", {
|
|
1786
|
-
key: "p5",
|
|
1787
|
-
d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
|
|
1788
|
-
})
|
|
1834
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })] }),
|
|
1835
|
+
pencil: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m15 5 4 4" })] }),
|
|
1836
|
+
"trash-2": /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [
|
|
1837
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M10 11v6" }),
|
|
1838
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M14 11v6" }),
|
|
1839
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" }),
|
|
1840
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M3 6h18" }),
|
|
1841
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" })
|
|
1789
1842
|
] }),
|
|
1790
|
-
x: (0, react_jsx_runtime.jsxs)("g", { children: [(0, react_jsx_runtime.jsx)("path", {
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
d: "
|
|
1796
|
-
})] }),
|
|
1797
|
-
plus: (0, react_jsx_runtime.jsxs)("g", { children: [(0, react_jsx_runtime.jsx)("path", {
|
|
1798
|
-
key: "p1",
|
|
1799
|
-
d: "M5 12h14"
|
|
1800
|
-
}), (0, react_jsx_runtime.jsx)("path", {
|
|
1801
|
-
key: "p2",
|
|
1802
|
-
d: "M12 5v14"
|
|
1803
|
-
})] }),
|
|
1804
|
-
"external-link": (0, react_jsx_runtime.jsxs)("g", { children: [
|
|
1805
|
-
(0, react_jsx_runtime.jsx)("path", {
|
|
1806
|
-
key: "p1",
|
|
1807
|
-
d: "M15 3h6v6"
|
|
1808
|
-
}),
|
|
1809
|
-
(0, react_jsx_runtime.jsx)("path", {
|
|
1810
|
-
key: "p2",
|
|
1811
|
-
d: "M10 14 21 3"
|
|
1812
|
-
}),
|
|
1813
|
-
(0, react_jsx_runtime.jsx)("path", {
|
|
1814
|
-
key: "p3",
|
|
1815
|
-
d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"
|
|
1816
|
-
})
|
|
1843
|
+
x: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M18 6 6 18" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m6 6 12 12" })] }),
|
|
1844
|
+
plus: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M5 12h14" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M12 5v14" })] }),
|
|
1845
|
+
"external-link": /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("g", { children: [
|
|
1846
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M15 3h6v6" }),
|
|
1847
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M10 14 21 3" }),
|
|
1848
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" })
|
|
1817
1849
|
] })
|
|
1818
1850
|
};
|
|
1819
1851
|
function LucideIcon({ name, size = 15 }) {
|
|
1820
|
-
return (0, react_jsx_runtime.jsx)("svg", {
|
|
1852
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
1821
1853
|
...SVG_ATTRS,
|
|
1822
1854
|
width: size,
|
|
1823
1855
|
height: size,
|
|
@@ -2029,23 +2061,23 @@ function AnnotationCardView({ title, text, comment, variant, index, onRemove, on
|
|
|
2029
2061
|
useLocale();
|
|
2030
2062
|
const composer = variant === "composer";
|
|
2031
2063
|
const subtitle = comment ? composer ? t("card.commentSubtitle", { comment }) : comment : composer ? t("common.selectedText") : t("summary.noComment");
|
|
2032
|
-
return (0, react_jsx_runtime.jsxs)("div", {
|
|
2064
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2033
2065
|
className: cx(A.box, A.row, styles.card, !composer && styles.tCard),
|
|
2034
2066
|
role: "group",
|
|
2035
2067
|
"aria-label": index ? t("card.ariaIndexed", { index }) : t("card.ariaPlain"),
|
|
2036
2068
|
children: [
|
|
2037
|
-
(0, react_jsx_runtime.jsx)("div", {
|
|
2069
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2038
2070
|
className: cx(A.keep, A.gridMid, styles.cardIcon, !composer && styles.tCardIcon),
|
|
2039
|
-
"aria-hidden": true,
|
|
2071
|
+
"aria-hidden": "true",
|
|
2040
2072
|
children: index ? String(index) : "AI"
|
|
2041
2073
|
}),
|
|
2042
|
-
(0, react_jsx_runtime.jsxs)("div", {
|
|
2074
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2043
2075
|
className: cx(A.grow, A.col, styles.cardBody),
|
|
2044
|
-
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
2076
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2045
2077
|
className: cx(A.ell, styles.cardTitle),
|
|
2046
2078
|
title: text,
|
|
2047
2079
|
children: title
|
|
2048
|
-
}), (0, react_jsx_runtime.jsx)("div", {
|
|
2080
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2049
2081
|
className: cx(A.ell, styles.cardSubtitle),
|
|
2050
2082
|
...comment ? {
|
|
2051
2083
|
title: comment,
|
|
@@ -2054,14 +2086,14 @@ function AnnotationCardView({ title, text, comment, variant, index, onRemove, on
|
|
|
2054
2086
|
children: subtitle
|
|
2055
2087
|
})]
|
|
2056
2088
|
}),
|
|
2057
|
-
composer && onEditComment ? (0, react_jsx_runtime.jsx)("button", {
|
|
2089
|
+
composer && onEditComment ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2058
2090
|
type: "button",
|
|
2059
2091
|
className: cx(A.plain, A.gridMid, styles.cardAction),
|
|
2060
2092
|
"aria-label": t("card.editComment"),
|
|
2061
2093
|
onClick: onEditComment,
|
|
2062
2094
|
children: "✎"
|
|
2063
2095
|
}) : null,
|
|
2064
|
-
composer && onRemove ? (0, react_jsx_runtime.jsx)("button", {
|
|
2096
|
+
composer && onRemove ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2065
2097
|
type: "button",
|
|
2066
2098
|
className: cx(A.plain, A.gridMid, styles.cardClose),
|
|
2067
2099
|
"aria-label": t("card.remove"),
|
|
@@ -2239,62 +2271,62 @@ function AnnotationSummary({ count, items, onRemoveAll, onItemSelect, onItemRemo
|
|
|
2239
2271
|
editDraft,
|
|
2240
2272
|
onItemEdit
|
|
2241
2273
|
]);
|
|
2242
|
-
const panel = open ? (0, react_jsx_runtime.jsx)("div", {
|
|
2274
|
+
const panel = open ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2243
2275
|
ref: popRef,
|
|
2244
2276
|
className: `${styles.summaryHover} ${styles.summaryHoverOpen}`,
|
|
2245
2277
|
role: "dialog",
|
|
2246
2278
|
"aria-label": t("summary.dialogAria", { count }),
|
|
2247
2279
|
onPointerEnter: openPop,
|
|
2248
2280
|
onPointerLeave,
|
|
2249
|
-
children: (0, react_jsx_runtime.jsx)("div", {
|
|
2281
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2250
2282
|
className: cx(A.col, styles.summaryHoverInner),
|
|
2251
|
-
children: (0, react_jsx_runtime.jsx)("div", {
|
|
2283
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2252
2284
|
className: cx(A.col, styles.summaryList),
|
|
2253
2285
|
children: items.map((item, i) => {
|
|
2254
2286
|
const isEditing = editing === i;
|
|
2255
|
-
return (0, react_jsx_runtime.jsxs)("div", {
|
|
2287
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2256
2288
|
className: styles.summaryItem,
|
|
2257
|
-
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
2289
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2258
2290
|
className: cx(A.row, styles.summaryItemHead),
|
|
2259
2291
|
children: [
|
|
2260
|
-
(0, react_jsx_runtime.jsx)("div", {
|
|
2292
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2261
2293
|
className: cx(A.keep, A.gridMid, styles.summaryItemBadge),
|
|
2262
|
-
"aria-hidden": true,
|
|
2294
|
+
"aria-hidden": "true",
|
|
2263
2295
|
children: String(i + 1)
|
|
2264
2296
|
}),
|
|
2265
|
-
(0, react_jsx_runtime.jsx)("div", {
|
|
2297
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2266
2298
|
className: styles.summaryItemLabel,
|
|
2267
2299
|
children: t("common.selectedText")
|
|
2268
2300
|
}),
|
|
2269
|
-
!isEditing && (onItemRemove || onItemEdit) ? (0, react_jsx_runtime.jsxs)("div", {
|
|
2301
|
+
!isEditing && (onItemRemove || onItemEdit) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2270
2302
|
className: cx(A.row, styles.summaryItemActions),
|
|
2271
|
-
children: [onItemEdit ? (0, react_jsx_runtime.jsx)("button", {
|
|
2303
|
+
children: [onItemEdit ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2272
2304
|
type: "button",
|
|
2273
2305
|
className: cx(A.plain, A.gridMid, styles.summaryItemAction),
|
|
2274
2306
|
"aria-label": t("summary.edit", { index: i + 1 }),
|
|
2275
2307
|
onClick: () => startEdit(i),
|
|
2276
|
-
children: (0, react_jsx_runtime.jsx)(LucideIcon, { name: "pencil" })
|
|
2277
|
-
}) : null, onItemRemove ? (0, react_jsx_runtime.jsx)("button", {
|
|
2308
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LucideIcon, { name: "pencil" })
|
|
2309
|
+
}) : null, onItemRemove ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2278
2310
|
type: "button",
|
|
2279
2311
|
className: cx(A.plain, A.gridMid, styles.summaryItemAction),
|
|
2280
2312
|
"aria-label": t("summary.remove", { index: i + 1 }),
|
|
2281
2313
|
onClick: () => onItemRemove(i, item),
|
|
2282
|
-
children: (0, react_jsx_runtime.jsx)(LucideIcon, { name: "trash-2" })
|
|
2314
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LucideIcon, { name: "trash-2" })
|
|
2283
2315
|
}) : null]
|
|
2284
2316
|
}) : null
|
|
2285
2317
|
]
|
|
2286
|
-
}), isEditing ? (0, react_jsx_runtime.jsxs)("div", {
|
|
2318
|
+
}), isEditing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2287
2319
|
className: styles.summaryItemMain,
|
|
2288
2320
|
children: [
|
|
2289
|
-
(0, react_jsx_runtime.jsx)("div", {
|
|
2321
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2290
2322
|
className: styles.annEditText,
|
|
2291
2323
|
children: item.text
|
|
2292
2324
|
}),
|
|
2293
|
-
(0, react_jsx_runtime.jsx)("div", {
|
|
2325
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2294
2326
|
className: styles.summaryItemLabel,
|
|
2295
2327
|
children: t("summary.userComment")
|
|
2296
2328
|
}),
|
|
2297
|
-
(0, react_jsx_runtime.jsx)("textarea", {
|
|
2329
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
2298
2330
|
ref: editInputRef,
|
|
2299
2331
|
className: cx(A.box, styles.annEditInput),
|
|
2300
2332
|
placeholder: t("common.commentPlaceholder"),
|
|
@@ -2312,14 +2344,14 @@ function AnnotationSummary({ count, items, onRemoveAll, onItemSelect, onItemRemo
|
|
|
2312
2344
|
}
|
|
2313
2345
|
}
|
|
2314
2346
|
}),
|
|
2315
|
-
(0, react_jsx_runtime.jsxs)("div", {
|
|
2347
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2316
2348
|
className: cx(A.row, styles.annEditActions),
|
|
2317
|
-
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
2349
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2318
2350
|
type: "button",
|
|
2319
2351
|
className: cx(A.plain, styles.annEditCancel),
|
|
2320
2352
|
onClick: () => setEditing(null),
|
|
2321
2353
|
children: t("common.cancel")
|
|
2322
|
-
}), (0, react_jsx_runtime.jsx)("button", {
|
|
2354
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2323
2355
|
type: "button",
|
|
2324
2356
|
className: styles.annEditSave,
|
|
2325
2357
|
onClick: commitEdit,
|
|
@@ -2327,9 +2359,9 @@ function AnnotationSummary({ count, items, onRemoveAll, onItemSelect, onItemRemo
|
|
|
2327
2359
|
})]
|
|
2328
2360
|
})
|
|
2329
2361
|
]
|
|
2330
|
-
}) : (0, react_jsx_runtime.jsx)("div", {
|
|
2362
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2331
2363
|
className: styles.summaryItemMain,
|
|
2332
|
-
children: (0, react_jsx_runtime.jsxs)("button", {
|
|
2364
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2333
2365
|
type: "button",
|
|
2334
2366
|
className: cx(A.plain, styles.summaryItemButton),
|
|
2335
2367
|
onClick: () => {
|
|
@@ -2337,15 +2369,15 @@ function AnnotationSummary({ count, items, onRemoveAll, onItemSelect, onItemRemo
|
|
|
2337
2369
|
closePop();
|
|
2338
2370
|
},
|
|
2339
2371
|
children: [
|
|
2340
|
-
(0, react_jsx_runtime.jsx)("div", {
|
|
2372
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2341
2373
|
className: styles.summaryItemText,
|
|
2342
2374
|
children: item.title
|
|
2343
2375
|
}),
|
|
2344
|
-
(0, react_jsx_runtime.jsx)("div", {
|
|
2376
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2345
2377
|
className: styles.summaryItemLabel,
|
|
2346
2378
|
children: t("summary.userComment")
|
|
2347
2379
|
}),
|
|
2348
|
-
(0, react_jsx_runtime.jsx)("div", {
|
|
2380
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2349
2381
|
className: item.comment ? styles.summaryItemText : `${styles.summaryItemText} ${styles.summaryItemTextEmpty}`,
|
|
2350
2382
|
children: item.comment || t("summary.noComment")
|
|
2351
2383
|
})
|
|
@@ -2357,7 +2389,7 @@ function AnnotationSummary({ count, items, onRemoveAll, onItemSelect, onItemRemo
|
|
|
2357
2389
|
})
|
|
2358
2390
|
})
|
|
2359
2391
|
}) : null;
|
|
2360
|
-
return (0, react_jsx_runtime.jsxs)("div", {
|
|
2392
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2361
2393
|
ref: rootRef,
|
|
2362
2394
|
className: cx(A.row, styles.summary),
|
|
2363
2395
|
tabIndex: 0,
|
|
@@ -2370,27 +2402,27 @@ function AnnotationSummary({ count, items, onRemoveAll, onItemSelect, onItemRemo
|
|
|
2370
2402
|
onBlur,
|
|
2371
2403
|
onKeyDown,
|
|
2372
2404
|
children: [
|
|
2373
|
-
(0, react_jsx_runtime.jsx)("div", {
|
|
2405
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2374
2406
|
className: cx(A.keep, A.gridMid, styles.summaryIcon),
|
|
2375
|
-
"aria-hidden": true,
|
|
2407
|
+
"aria-hidden": "true",
|
|
2376
2408
|
children: String(count)
|
|
2377
2409
|
}),
|
|
2378
|
-
(0, react_jsx_runtime.jsxs)("div", {
|
|
2410
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2379
2411
|
className: cx(A.grow, styles.summaryBody),
|
|
2380
|
-
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
2412
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2381
2413
|
className: styles.summaryTitle,
|
|
2382
2414
|
children: t("summary.title", { count })
|
|
2383
|
-
}), (0, react_jsx_runtime.jsx)("div", {
|
|
2415
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2384
2416
|
className: styles.summaryHint,
|
|
2385
2417
|
children: t("summary.hint")
|
|
2386
2418
|
})]
|
|
2387
2419
|
}),
|
|
2388
|
-
onRemoveAll ? (0, react_jsx_runtime.jsx)("button", {
|
|
2420
|
+
onRemoveAll ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
2389
2421
|
type: "button",
|
|
2390
2422
|
className: cx(A.plain, A.gridMid, styles.cardClose),
|
|
2391
2423
|
"aria-label": t("summary.removeAll"),
|
|
2392
2424
|
onClick: onRemoveAll,
|
|
2393
|
-
children: (0, react_jsx_runtime.jsx)(LucideIcon, {
|
|
2425
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LucideIcon, {
|
|
2394
2426
|
name: "x",
|
|
2395
2427
|
size: 13
|
|
2396
2428
|
})
|
|
@@ -2605,10 +2637,10 @@ function AnnotationPanel({ useInput, inputActions, sessionId }) {
|
|
|
2605
2637
|
sync();
|
|
2606
2638
|
}, [sync]);
|
|
2607
2639
|
if (!sessionId || items.length === 0) return null;
|
|
2608
|
-
return (0, react_jsx_runtime.jsx)("div", {
|
|
2640
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2609
2641
|
ref: stackRef,
|
|
2610
2642
|
className: cx(A.col, styles.cardStack),
|
|
2611
|
-
children: (0, react_jsx_runtime.jsx)(AnnotationSummary, {
|
|
2643
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationSummary, {
|
|
2612
2644
|
count: items.length,
|
|
2613
2645
|
items: items.map((item) => ({
|
|
2614
2646
|
id: item.id,
|
|
@@ -2626,9 +2658,116 @@ function AnnotationPanel({ useInput, inputActions, sessionId }) {
|
|
|
2626
2658
|
/** Alias kept for overlay registration. */
|
|
2627
2659
|
const QuoteCard = AnnotationPanel;
|
|
2628
2660
|
//#endregion
|
|
2661
|
+
//#region src/client/dom/floating-layer.ts
|
|
2662
|
+
/**
|
|
2663
|
+
* Mount point for the selection UI: a zero-size host inside the transcript's
|
|
2664
|
+
* real scroll container.
|
|
2665
|
+
*
|
|
2666
|
+
* The host is `position: absolute` in **content coordinates**, so floating UI
|
|
2667
|
+
* scrolls with the selected text and the scroller clips it at its own edges.
|
|
2668
|
+
* That is what keeps the toolbar out of the app header and the composer:
|
|
2669
|
+
* clipping happens in the scroller's stacking context instead of competing
|
|
2670
|
+
* with the header (which is not positioned and therefore loses to any
|
|
2671
|
+
* body-level positioned portal, whatever its z-index).
|
|
2672
|
+
*
|
|
2673
|
+
* Two host layouts exist in the product:
|
|
2674
|
+
* - desktop / overlay composer: `ST7X_W_scrollBody` scrolls; the chat frame is
|
|
2675
|
+
* content-sized and the chat's own `.icaHSq_scroll` is `overflow: visible`;
|
|
2676
|
+
* - regular layout: `.icaHSq_scroll` is the scroller.
|
|
2677
|
+
* Walking up from the selected flow item finds whichever one is live.
|
|
2678
|
+
*/
|
|
2679
|
+
const HOST_ATTR = "data-dsq-layer";
|
|
2680
|
+
/** Below the composer seat (sticky, z-index 7); above in-content stickies (6). */
|
|
2681
|
+
const HOST_Z = 6;
|
|
2682
|
+
const layers = /* @__PURE__ */ new WeakMap();
|
|
2683
|
+
/** flow item → layer, so scrolling does not re-walk the ancestor chain. */
|
|
2684
|
+
const itemLayers = /* @__PURE__ */ new WeakMap();
|
|
2685
|
+
let fallback = null;
|
|
2686
|
+
function contentCoords(scroller, left, top) {
|
|
2687
|
+
const rect = scroller.getBoundingClientRect();
|
|
2688
|
+
return {
|
|
2689
|
+
left: left - rect.left - scroller.clientLeft + scroller.scrollLeft,
|
|
2690
|
+
top: top - rect.top - scroller.clientTop + scroller.scrollTop
|
|
2691
|
+
};
|
|
2692
|
+
}
|
|
2693
|
+
function layerFor(scroller) {
|
|
2694
|
+
const cached = layers.get(scroller);
|
|
2695
|
+
if (cached?.host.isConnected) return cached;
|
|
2696
|
+
if (getComputedStyle(scroller).position === "static") scroller.style.position = "relative";
|
|
2697
|
+
let host = scroller.querySelector(`:scope > [${HOST_ATTR}]`);
|
|
2698
|
+
if (!host) {
|
|
2699
|
+
host = document.createElement("div");
|
|
2700
|
+
host.setAttribute(HOST_ATTR, "");
|
|
2701
|
+
host.style.cssText = `position:absolute;left:0;top:0;width:0;height:0;z-index:${HOST_Z};pointer-events:none;`;
|
|
2702
|
+
scroller.appendChild(host);
|
|
2703
|
+
}
|
|
2704
|
+
const layer = {
|
|
2705
|
+
host,
|
|
2706
|
+
scroller,
|
|
2707
|
+
bounds: () => {
|
|
2708
|
+
const rect = scroller.getBoundingClientRect();
|
|
2709
|
+
return new DOMRect(rect.left + scroller.clientLeft, rect.top + scroller.clientTop, scroller.clientWidth, scroller.clientHeight);
|
|
2710
|
+
},
|
|
2711
|
+
toContent: (left, top) => contentCoords(scroller, left, top),
|
|
2712
|
+
fromContent: (left, top) => {
|
|
2713
|
+
const rect = scroller.getBoundingClientRect();
|
|
2714
|
+
return {
|
|
2715
|
+
left: left + rect.left + scroller.clientLeft - scroller.scrollLeft,
|
|
2716
|
+
top: top + rect.top + scroller.clientTop - scroller.scrollTop
|
|
2717
|
+
};
|
|
2718
|
+
}
|
|
2719
|
+
};
|
|
2720
|
+
layers.set(scroller, layer);
|
|
2721
|
+
return layer;
|
|
2722
|
+
}
|
|
2723
|
+
/** Fallback for hosts whose transcript has no scrollable ancestor. */
|
|
2724
|
+
function fallbackLayer() {
|
|
2725
|
+
if (fallback?.host.isConnected) return fallback;
|
|
2726
|
+
const host = document.createElement("div");
|
|
2727
|
+
host.setAttribute(HOST_ATTR, "");
|
|
2728
|
+
host.style.cssText = `position:absolute;left:0;top:0;width:0;height:0;z-index:${HOST_Z};pointer-events:none;`;
|
|
2729
|
+
document.body.appendChild(host);
|
|
2730
|
+
fallback = {
|
|
2731
|
+
host,
|
|
2732
|
+
scroller: document.body,
|
|
2733
|
+
bounds: () => new DOMRect(0, 0, window.innerWidth, window.innerHeight),
|
|
2734
|
+
toContent: (left, top) => ({
|
|
2735
|
+
left,
|
|
2736
|
+
top
|
|
2737
|
+
}),
|
|
2738
|
+
fromContent: (left, top) => ({
|
|
2739
|
+
left,
|
|
2740
|
+
top
|
|
2741
|
+
})
|
|
2742
|
+
};
|
|
2743
|
+
return fallback;
|
|
2744
|
+
}
|
|
2745
|
+
/** Layer for a node inside the transcript (falls back to a body host). */
|
|
2746
|
+
function floatingLayerFor(node) {
|
|
2747
|
+
const item = flowItemOf(node);
|
|
2748
|
+
const cached = item ? itemLayers.get(item) : void 0;
|
|
2749
|
+
if (cached?.host.isConnected) return cached;
|
|
2750
|
+
let el = item;
|
|
2751
|
+
while (el && el !== document.body) {
|
|
2752
|
+
const { overflowY } = getComputedStyle(el);
|
|
2753
|
+
if (overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") {
|
|
2754
|
+
const layer = layerFor(el);
|
|
2755
|
+
if (item) itemLayers.set(item, layer);
|
|
2756
|
+
return layer;
|
|
2757
|
+
}
|
|
2758
|
+
el = el.parentElement;
|
|
2759
|
+
}
|
|
2760
|
+
const layer = fallbackLayer();
|
|
2761
|
+
if (item) itemLayers.set(item, layer);
|
|
2762
|
+
return layer;
|
|
2763
|
+
}
|
|
2764
|
+
//#endregion
|
|
2629
2765
|
//#region src/client/dom/selection.ts
|
|
2630
2766
|
/** Selection snapshot used by the floating toolbar. */
|
|
2631
2767
|
const TOOLBAR_OFFSET = 12;
|
|
2768
|
+
const BELOW_GAP = 8;
|
|
2769
|
+
/** Estimated toolbar height; only used to decide above vs below at content top. */
|
|
2770
|
+
const TOOLBAR_HEIGHT = 40;
|
|
2632
2771
|
const VIEWPORT_MARGIN = 12;
|
|
2633
2772
|
const LINE_TOP_TOLERANCE = 3;
|
|
2634
2773
|
/**
|
|
@@ -2669,12 +2808,24 @@ function getRangeFirstLineRect(range) {
|
|
|
2669
2808
|
const firstTop = Math.min(...rects.map((rect) => rect.top));
|
|
2670
2809
|
return mergeRects(rects.filter((rect) => Math.abs(rect.top - firstTop) <= LINE_TOP_TOLERANCE));
|
|
2671
2810
|
}
|
|
2672
|
-
|
|
2811
|
+
/** Keep the centered toolbar inside the visible horizontal band. */
|
|
2812
|
+
function anchorX(rect, bounds) {
|
|
2673
2813
|
const center = rect.left + rect.width / 2;
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2814
|
+
const min = bounds.left + VIEWPORT_MARGIN;
|
|
2815
|
+
const max = Math.max(min, bounds.right - VIEWPORT_MARGIN);
|
|
2816
|
+
return Math.min(Math.max(center, min), max);
|
|
2817
|
+
}
|
|
2818
|
+
/**
|
|
2819
|
+
* True when the selection's first line still touches the viewport.
|
|
2820
|
+
*
|
|
2821
|
+
* The toolbar anchors to that line, so once it has scrolled fully out of
|
|
2822
|
+
* view there is nothing to anchor to and the snapshot reads as "gone" — the
|
|
2823
|
+
* toolbar hides and comes back when the user scrolls to the selection again
|
|
2824
|
+
* (scroll re-reads the selection). A partially visible line still counts:
|
|
2825
|
+
* `toolbarPosition` parks the toolbar at the viewport edge in that case.
|
|
2826
|
+
*/
|
|
2827
|
+
function isFirstLineOnScreen(rect) {
|
|
2828
|
+
return rect.bottom > 0 && rect.top < window.innerHeight && rect.right > 0 && rect.left < window.innerWidth;
|
|
2678
2829
|
}
|
|
2679
2830
|
/**
|
|
2680
2831
|
* Read the live selection when it is a non-empty range inside a conversation
|
|
@@ -2692,13 +2843,24 @@ function readSelectionSnapshot(toolbarRoot) {
|
|
|
2692
2843
|
if (!text) return null;
|
|
2693
2844
|
const rect = getRangeFirstLineRect(selection.getRangeAt(0));
|
|
2694
2845
|
if (!rect) return null;
|
|
2695
|
-
|
|
2696
|
-
const
|
|
2846
|
+
if (!isFirstLineOnScreen(rect)) return null;
|
|
2847
|
+
const layer = floatingLayerFor(anchorNode);
|
|
2848
|
+
const x = anchorX(rect, layer.bounds());
|
|
2849
|
+
const above = layer.toContent(x, rect.top - TOOLBAR_OFFSET);
|
|
2850
|
+
const below = above.top < TOOLBAR_HEIGHT;
|
|
2851
|
+
const anchor = below ? layer.toContent(x, rect.bottom + BELOW_GAP) : above;
|
|
2852
|
+
const range = selection.getRangeAt(0);
|
|
2853
|
+
const union = range.getBoundingClientRect();
|
|
2854
|
+
const selectionBottom = layer.toContent(0, union.height > 0 ? union.bottom : rect.bottom).top;
|
|
2855
|
+
const source = rangeOffsetsInFlowItem(range);
|
|
2697
2856
|
const anchorHref = (anchorNode instanceof Element ? anchorNode : anchorNode.parentElement)?.closest("a[href]")?.getAttribute("href") ?? void 0;
|
|
2698
2857
|
return {
|
|
2699
2858
|
text,
|
|
2700
|
-
left,
|
|
2701
|
-
top,
|
|
2859
|
+
left: anchor.left,
|
|
2860
|
+
top: anchor.top,
|
|
2861
|
+
below,
|
|
2862
|
+
selectionBottom,
|
|
2863
|
+
layer,
|
|
2702
2864
|
...source ? { source } : {},
|
|
2703
2865
|
...anchorHref ? { anchorHref } : {}
|
|
2704
2866
|
};
|
|
@@ -2775,35 +2937,72 @@ function SelectionToolbar({ sessionId }) {
|
|
|
2775
2937
|
const [status, setStatus] = (0, react.useState)(null);
|
|
2776
2938
|
const activeRef = (0, react.useRef)(null);
|
|
2777
2939
|
activeRef.current = active;
|
|
2940
|
+
/** Annotation saved when the comment step opened; the box only edits it. */
|
|
2941
|
+
const pendingIdRef = (0, react.useRef)(null);
|
|
2778
2942
|
(0, react.useEffect)(() => {
|
|
2779
2943
|
ensureToolbarStyles();
|
|
2780
2944
|
if (sessionId) bindAnnotationSession(sessionId);
|
|
2781
2945
|
}, [sessionId]);
|
|
2782
2946
|
const hide = (0, react.useCallback)(() => {
|
|
2947
|
+
pendingIdRef.current = null;
|
|
2783
2948
|
setActive(null);
|
|
2784
2949
|
setPhase("idle");
|
|
2785
2950
|
setComment("");
|
|
2786
2951
|
setStatus(null);
|
|
2787
2952
|
}, []);
|
|
2953
|
+
/**
|
|
2954
|
+
* Keep what was typed when the comment popover goes away without an explicit
|
|
2955
|
+
* cancel. The annotation itself was saved when the step opened, so dropping
|
|
2956
|
+
* the text would also lose the only optional part of the interaction.
|
|
2957
|
+
*/
|
|
2958
|
+
const flushComment = (0, react.useCallback)(() => {
|
|
2959
|
+
const id = pendingIdRef.current;
|
|
2960
|
+
pendingIdRef.current = null;
|
|
2961
|
+
if (!id) return;
|
|
2962
|
+
const typed = commentRef.current?.value ?? "";
|
|
2963
|
+
if (typed.trim()) updateAnnotationComment(void 0, id, typed);
|
|
2964
|
+
}, []);
|
|
2965
|
+
/** Save the current selection without a comment; returns the store id. */
|
|
2966
|
+
const saveSelection = (0, react.useCallback)(() => {
|
|
2967
|
+
const snapshot = activeRef.current;
|
|
2968
|
+
if (!snapshot) return null;
|
|
2969
|
+
return saveAnnotation(void 0, {
|
|
2970
|
+
text: snapshot.text,
|
|
2971
|
+
sources: snapshot.source ? [{
|
|
2972
|
+
text: snapshot.text,
|
|
2973
|
+
messageId: snapshot.source.messageId,
|
|
2974
|
+
messageKind: snapshot.source.messageKind,
|
|
2975
|
+
startOffset: snapshot.source.startOffset,
|
|
2976
|
+
endOffset: snapshot.source.endOffset
|
|
2977
|
+
}] : void 0
|
|
2978
|
+
}).id;
|
|
2979
|
+
}, []);
|
|
2788
2980
|
const refresh = (0, react.useCallback)(() => {
|
|
2789
2981
|
const next = readSelectionSnapshot(toolbarRef.current);
|
|
2790
2982
|
if (!next) {
|
|
2791
2983
|
if (phaseRef.current === "idle") setActive(null);
|
|
2792
2984
|
return;
|
|
2793
2985
|
}
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
}, []);
|
|
2986
|
+
const current = activeRef.current;
|
|
2987
|
+
if (phaseRef.current === "comment" && current && current.text !== next.text) {
|
|
2988
|
+
flushComment();
|
|
2989
|
+
setPhase("idle");
|
|
2990
|
+
setComment("");
|
|
2991
|
+
}
|
|
2992
|
+
setActive((prev) => prev && prev.text === next.text && prev.left === next.left && prev.top === next.top && prev.below === next.below && prev.layer === next.layer ? prev : next);
|
|
2993
|
+
}, [flushComment]);
|
|
2803
2994
|
const phaseRef = (0, react.useRef)(phase);
|
|
2804
2995
|
phaseRef.current = phase;
|
|
2805
2996
|
(0, react.useEffect)(() => {
|
|
2806
|
-
|
|
2997
|
+
let scheduled = false;
|
|
2998
|
+
const schedule = () => {
|
|
2999
|
+
if (scheduled) return;
|
|
3000
|
+
scheduled = true;
|
|
3001
|
+
window.setTimeout(() => {
|
|
3002
|
+
scheduled = false;
|
|
3003
|
+
refresh();
|
|
3004
|
+
}, 0);
|
|
3005
|
+
};
|
|
2807
3006
|
let dragging = false;
|
|
2808
3007
|
const onPointerDown = (event) => {
|
|
2809
3008
|
const target = event.target;
|
|
@@ -2813,6 +3012,7 @@ function SelectionToolbar({ sessionId }) {
|
|
|
2813
3012
|
if (inToolbar || inComment) return;
|
|
2814
3013
|
dragging = true;
|
|
2815
3014
|
if (phaseRef.current === "comment") {
|
|
3015
|
+
flushComment();
|
|
2816
3016
|
hide();
|
|
2817
3017
|
return;
|
|
2818
3018
|
}
|
|
@@ -2834,7 +3034,7 @@ function SelectionToolbar({ sessionId }) {
|
|
|
2834
3034
|
const onScroll = (event) => {
|
|
2835
3035
|
const target = event.target;
|
|
2836
3036
|
if (target instanceof Node && (toolbarRef.current?.contains(target) === true || commentWrapRef.current?.contains(target) === true)) return;
|
|
2837
|
-
|
|
3037
|
+
schedule();
|
|
2838
3038
|
};
|
|
2839
3039
|
document.addEventListener("pointerdown", onPointerDown, true);
|
|
2840
3040
|
document.addEventListener("pointerup", onPointerUp, true);
|
|
@@ -2850,12 +3050,27 @@ function SelectionToolbar({ sessionId }) {
|
|
|
2850
3050
|
window.removeEventListener("resize", schedule);
|
|
2851
3051
|
document.removeEventListener("scroll", onScroll, true);
|
|
2852
3052
|
};
|
|
2853
|
-
}, [
|
|
3053
|
+
}, [
|
|
3054
|
+
flushComment,
|
|
3055
|
+
hide,
|
|
3056
|
+
refresh
|
|
3057
|
+
]);
|
|
2854
3058
|
const placeCommentPop = (0, react.useCallback)(() => {
|
|
3059
|
+
const snapshot = activeRef.current;
|
|
3060
|
+
const layer = snapshot?.layer;
|
|
2855
3061
|
const toolbar = toolbarRef.current;
|
|
2856
3062
|
const pop = commentWrapRef.current;
|
|
2857
|
-
if (!toolbar || !pop) return;
|
|
2858
|
-
const
|
|
3063
|
+
if (!snapshot || !layer || !toolbar || !pop) return;
|
|
3064
|
+
const bounds = layer.bounds();
|
|
3065
|
+
const toolbarW = toolbar.offsetWidth;
|
|
3066
|
+
const toolbarH = toolbar.offsetHeight;
|
|
3067
|
+
const home = layer.fromContent(snapshot.left, snapshot.top);
|
|
3068
|
+
const anchor = {
|
|
3069
|
+
left: home.left - toolbarW / 2,
|
|
3070
|
+
top: home.top - (snapshot.below ? 0 : toolbarH),
|
|
3071
|
+
width: toolbarW,
|
|
3072
|
+
height: toolbarH
|
|
3073
|
+
};
|
|
2859
3074
|
pop.style.display = "flex";
|
|
2860
3075
|
pop.style.visibility = "hidden";
|
|
2861
3076
|
const panel = {
|
|
@@ -2863,24 +3078,45 @@ function SelectionToolbar({ sessionId }) {
|
|
|
2863
3078
|
height: pop.offsetHeight || 180
|
|
2864
3079
|
};
|
|
2865
3080
|
pop.style.visibility = "";
|
|
2866
|
-
const
|
|
3081
|
+
const gap = 8;
|
|
3082
|
+
const margin = 8;
|
|
3083
|
+
const belowTop = layer.fromContent(0, snapshot.selectionBottom).top + gap;
|
|
3084
|
+
const aboveTop = anchor.top - panel.height - gap;
|
|
3085
|
+
const top = belowTop <= bounds.bottom - margin - panel.height ? belowTop : aboveTop >= bounds.top + margin ? aboveTop : Math.max(bounds.top + margin, Math.min(belowTop, bounds.bottom - margin - panel.height));
|
|
2867
3086
|
const half = panel.width / 2 + 8;
|
|
2868
|
-
const
|
|
2869
|
-
|
|
2870
|
-
|
|
3087
|
+
const min = bounds.left + half;
|
|
3088
|
+
const center = Math.min(Math.max(anchor.left + anchor.width / 2, min), Math.max(min, bounds.right - half));
|
|
3089
|
+
const at = layer.toContent(center, top);
|
|
3090
|
+
pop.style.top = `${at.top}px`;
|
|
3091
|
+
pop.style.left = `${at.left}px`;
|
|
3092
|
+
const sideGap = 6;
|
|
3093
|
+
const popLeftView = layer.fromContent(at.left, at.top).left - panel.width / 2;
|
|
3094
|
+
const popTopView = top;
|
|
3095
|
+
let leftView = popLeftView;
|
|
3096
|
+
let topView = popTopView - sideGap - toolbarH;
|
|
3097
|
+
if (topView < bounds.top + margin) topView = popTopView + panel.height + sideGap;
|
|
3098
|
+
leftView = Math.max(bounds.left + margin, Math.min(leftView, bounds.right - margin - toolbarW));
|
|
3099
|
+
topView = Math.max(bounds.top + margin, Math.min(topView, bounds.bottom - margin - toolbarH));
|
|
3100
|
+
const toolbarAt = layer.toContent(leftView, topView);
|
|
3101
|
+
toolbar.style.left = `${toolbarAt.left}px`;
|
|
3102
|
+
toolbar.style.top = `${toolbarAt.top}px`;
|
|
2871
3103
|
}, []);
|
|
3104
|
+
(0, react.useLayoutEffect)(() => {
|
|
3105
|
+
if (phase === "comment") placeCommentPop();
|
|
3106
|
+
}, [
|
|
3107
|
+
active,
|
|
3108
|
+
phase,
|
|
3109
|
+
placeCommentPop
|
|
3110
|
+
]);
|
|
2872
3111
|
(0, react.useEffect)(() => {
|
|
2873
|
-
if (phase === "comment")
|
|
2874
|
-
|
|
2875
|
-
commentRef.current?.focus();
|
|
2876
|
-
}
|
|
2877
|
-
}, [phase, placeCommentPop]);
|
|
3112
|
+
if (phase === "comment") commentRef.current?.focus();
|
|
3113
|
+
}, [phase]);
|
|
2878
3114
|
(0, react.useEffect)(() => {
|
|
2879
3115
|
const onResize = () => placeCommentPop();
|
|
2880
3116
|
window.addEventListener("resize", onResize);
|
|
2881
3117
|
return () => window.removeEventListener("resize", onResize);
|
|
2882
3118
|
}, [placeCommentPop]);
|
|
2883
|
-
(0, react.
|
|
3119
|
+
(0, react.useLayoutEffect)(() => {
|
|
2884
3120
|
if (phase !== "comment") return;
|
|
2885
3121
|
const area = commentRef.current;
|
|
2886
3122
|
if (!area) return;
|
|
@@ -2907,12 +3143,18 @@ function SelectionToolbar({ sessionId }) {
|
|
|
2907
3143
|
setStatus(t("toolbar.copyFailed"));
|
|
2908
3144
|
}
|
|
2909
3145
|
}, [hide]);
|
|
3146
|
+
/**
|
|
3147
|
+
* "Add to task" is the commit: the quote is saved immediately, with or
|
|
3148
|
+
* without a comment. The popover only edits that saved entry, so dismissing
|
|
3149
|
+
* it (outside click, new selection) can never undo the annotation.
|
|
3150
|
+
*/
|
|
2910
3151
|
const openComment = (0, react.useCallback)(() => {
|
|
2911
3152
|
if (!activeRef.current) return;
|
|
3153
|
+
pendingIdRef.current = saveSelection();
|
|
2912
3154
|
setPhase("comment");
|
|
2913
3155
|
setComment("");
|
|
2914
3156
|
setStatus(null);
|
|
2915
|
-
}, []);
|
|
3157
|
+
}, [saveSelection]);
|
|
2916
3158
|
/**
|
|
2917
3159
|
* Hand the selected link to the system browser.
|
|
2918
3160
|
*
|
|
@@ -2930,89 +3172,75 @@ function SelectionToolbar({ sessionId }) {
|
|
|
2930
3172
|
clearNativeSelection();
|
|
2931
3173
|
hide();
|
|
2932
3174
|
}, [hide]);
|
|
2933
|
-
/**
|
|
2934
|
-
const
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
hide();
|
|
2938
|
-
return;
|
|
2939
|
-
}
|
|
2940
|
-
saveAnnotation(void 0, {
|
|
2941
|
-
text: snapshot.text,
|
|
2942
|
-
...body && body.trim() ? { comment: body.trim() } : {},
|
|
2943
|
-
sources: snapshot.source ? [{
|
|
2944
|
-
text: snapshot.text,
|
|
2945
|
-
messageId: snapshot.source.messageId,
|
|
2946
|
-
messageKind: snapshot.source.messageKind,
|
|
2947
|
-
startOffset: snapshot.source.startOffset,
|
|
2948
|
-
endOffset: snapshot.source.endOffset
|
|
2949
|
-
}] : void 0
|
|
2950
|
-
});
|
|
2951
|
-
getAnnotations().length;
|
|
3175
|
+
/** Close the optional comment step; the annotation stays saved either way. */
|
|
3176
|
+
const finishComment = (0, react.useCallback)((keepComment) => {
|
|
3177
|
+
if (keepComment) flushComment();
|
|
3178
|
+
else pendingIdRef.current = null;
|
|
2952
3179
|
clearNativeSelection();
|
|
2953
3180
|
hide();
|
|
2954
|
-
setStatus(null);
|
|
2955
3181
|
window.setTimeout(focusComposer, 16);
|
|
2956
|
-
}, [hide]);
|
|
3182
|
+
}, [flushComment, hide]);
|
|
2957
3183
|
if (!active) return null;
|
|
2958
3184
|
const toolbarStyle = {
|
|
2959
3185
|
left: active.left,
|
|
2960
3186
|
top: active.top
|
|
2961
3187
|
};
|
|
2962
3188
|
const link = linkTarget(active.text, active.anchorHref);
|
|
2963
|
-
return (0, react_dom.createPortal)((0, react_jsx_runtime.jsxs)(
|
|
2964
|
-
(0, react_jsx_runtime.jsx)("div", {
|
|
3189
|
+
return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
3190
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2965
3191
|
ref: toolbarRef,
|
|
2966
3192
|
className: cx(A.row, styles.toolbar),
|
|
2967
|
-
style: toolbarStyle,
|
|
3193
|
+
style: phase === "comment" ? void 0 : toolbarStyle,
|
|
2968
3194
|
role: "toolbar",
|
|
2969
3195
|
"aria-label": t("toolbar.aria"),
|
|
2970
3196
|
"data-dsq-toolbar": "true",
|
|
3197
|
+
"data-anchor": phase === "comment" ? "popover-top" : active.below ? "below" : "above",
|
|
2971
3198
|
onPointerDown: handleToolbarPointerDown,
|
|
2972
|
-
children: phase === "comment" ? (0, react_jsx_runtime.jsx)("div", {
|
|
3199
|
+
children: phase === "comment" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2973
3200
|
className: cx(A.inlineRow, styles.toolbarInner),
|
|
2974
|
-
children: (0, react_jsx_runtime.jsx)("span", {
|
|
3201
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2975
3202
|
style: {
|
|
2976
3203
|
padding: "0 8px",
|
|
2977
3204
|
fontSize: 13,
|
|
2978
|
-
opacity: .8
|
|
3205
|
+
opacity: .8,
|
|
3206
|
+
whiteSpace: "nowrap"
|
|
2979
3207
|
},
|
|
2980
3208
|
children: t("toolbar.commentHint")
|
|
2981
3209
|
})
|
|
2982
|
-
}) : (0, react_jsx_runtime.jsxs)("div", {
|
|
3210
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2983
3211
|
className: cx(A.inlineRow, styles.toolbarInner),
|
|
2984
3212
|
children: [
|
|
2985
|
-
link ? (0, react_jsx_runtime.
|
|
3213
|
+
link ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2986
3214
|
type: "button",
|
|
2987
3215
|
className: cx(A.plain, A.inlineRow, styles.button),
|
|
2988
3216
|
onClick: handleOpenLink,
|
|
2989
|
-
children: [(0, react_jsx_runtime.jsx)(LucideIcon, {
|
|
3217
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LucideIcon, {
|
|
2990
3218
|
name: "external-link",
|
|
2991
3219
|
size: 14
|
|
2992
3220
|
}), t("toolbar.openLink")]
|
|
2993
3221
|
}) : null,
|
|
2994
|
-
link ? (0, react_jsx_runtime.jsx)("span", {
|
|
3222
|
+
link ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2995
3223
|
className: styles.divider,
|
|
2996
|
-
"aria-hidden": true
|
|
3224
|
+
"aria-hidden": "true"
|
|
2997
3225
|
}) : null,
|
|
2998
|
-
(0, react_jsx_runtime.
|
|
3226
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2999
3227
|
type: "button",
|
|
3000
3228
|
className: cx(A.plain, A.inlineRow, styles.button),
|
|
3001
3229
|
onClick: () => void handleCopy(),
|
|
3002
|
-
children: [(0, react_jsx_runtime.jsx)(LucideIcon, {
|
|
3230
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LucideIcon, {
|
|
3003
3231
|
name: "copy",
|
|
3004
3232
|
size: 14
|
|
3005
3233
|
}), t("toolbar.copy")]
|
|
3006
3234
|
}),
|
|
3007
|
-
(0, react_jsx_runtime.jsx)("span", {
|
|
3235
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3008
3236
|
className: styles.divider,
|
|
3009
|
-
"aria-hidden": true
|
|
3237
|
+
"aria-hidden": "true"
|
|
3010
3238
|
}),
|
|
3011
|
-
(0, react_jsx_runtime.
|
|
3239
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3012
3240
|
type: "button",
|
|
3013
3241
|
className: cx(A.plain, A.inlineRow, styles.button),
|
|
3014
3242
|
onClick: openComment,
|
|
3015
|
-
children: [(0, react_jsx_runtime.jsx)(LucideIcon, {
|
|
3243
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(LucideIcon, {
|
|
3016
3244
|
name: "plus",
|
|
3017
3245
|
size: 14
|
|
3018
3246
|
}), t("toolbar.addToTask")]
|
|
@@ -3020,14 +3248,13 @@ function SelectionToolbar({ sessionId }) {
|
|
|
3020
3248
|
]
|
|
3021
3249
|
})
|
|
3022
3250
|
}),
|
|
3023
|
-
phase === "comment" ? (0, react_jsx_runtime.jsxs)("div", {
|
|
3251
|
+
phase === "comment" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3024
3252
|
ref: commentWrapRef,
|
|
3025
3253
|
className: cx(A.col, styles.commentPop),
|
|
3026
|
-
style: void 0,
|
|
3027
3254
|
role: "dialog",
|
|
3028
3255
|
"aria-label": t("toolbar.commentDialogAria"),
|
|
3029
3256
|
onPointerDown: handleToolbarPointerDown,
|
|
3030
|
-
children: [(0, react_jsx_runtime.jsx)("textarea", {
|
|
3257
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
3031
3258
|
ref: commentRef,
|
|
3032
3259
|
className: cx(A.box, styles.commentInput),
|
|
3033
3260
|
placeholder: t("common.commentPlaceholder"),
|
|
@@ -3036,37 +3263,38 @@ function SelectionToolbar({ sessionId }) {
|
|
|
3036
3263
|
onKeyDown: (e) => {
|
|
3037
3264
|
if (e.key === "Escape") {
|
|
3038
3265
|
e.preventDefault();
|
|
3266
|
+
pendingIdRef.current = null;
|
|
3039
3267
|
hide();
|
|
3040
3268
|
return;
|
|
3041
3269
|
}
|
|
3042
3270
|
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
3043
3271
|
e.preventDefault();
|
|
3044
|
-
|
|
3272
|
+
finishComment(true);
|
|
3045
3273
|
}
|
|
3046
3274
|
}
|
|
3047
|
-
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
3275
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3048
3276
|
className: styles.commentActions,
|
|
3049
|
-
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
3277
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3050
3278
|
type: "button",
|
|
3051
3279
|
className: cx(A.plain, A.inlineRow, styles.button),
|
|
3052
|
-
onClick: () =>
|
|
3280
|
+
onClick: () => finishComment(false),
|
|
3053
3281
|
children: t("toolbar.skipComment")
|
|
3054
|
-
}), (0, react_jsx_runtime.jsx)("button", {
|
|
3282
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3055
3283
|
type: "button",
|
|
3056
3284
|
className: cx(A.plain, A.inlineRow, styles.button, styles.buttonAddComment),
|
|
3057
3285
|
disabled: !comment.trim(),
|
|
3058
|
-
onClick: () =>
|
|
3286
|
+
onClick: () => finishComment(true),
|
|
3059
3287
|
children: t("toolbar.addComment")
|
|
3060
3288
|
})]
|
|
3061
3289
|
})]
|
|
3062
3290
|
}) : null,
|
|
3063
|
-
status ? (0, react_jsx_runtime.jsx)("div", {
|
|
3291
|
+
status ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3064
3292
|
className: styles.status,
|
|
3065
3293
|
style: toolbarStyle,
|
|
3066
3294
|
role: "status",
|
|
3067
3295
|
children: status
|
|
3068
3296
|
}) : null
|
|
3069
|
-
] }),
|
|
3297
|
+
] }), active.layer.host);
|
|
3070
3298
|
}
|
|
3071
3299
|
//#endregion
|
|
3072
3300
|
//#region src/client/dom/decorateAnnotationDirectives.ts
|
|
@@ -3196,11 +3424,11 @@ function TranscriptQuoteCard({ node }) {
|
|
|
3196
3424
|
scrollToAnnotation(index + 1, text, markIdOf(index));
|
|
3197
3425
|
}, [markIdOf]);
|
|
3198
3426
|
if (quotes.length === 0) return null;
|
|
3199
|
-
return (0, react_jsx_runtime.jsx)("div", {
|
|
3427
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3200
3428
|
ref: stackRef,
|
|
3201
3429
|
className: styles.tCardStack,
|
|
3202
3430
|
"data-dsq-deco": "true",
|
|
3203
|
-
children: quotes.map((quote, index) => (0, react_jsx_runtime.jsx)("div", {
|
|
3431
|
+
children: quotes.map((quote, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3204
3432
|
className: cx(A.plain, styles.tCardButton),
|
|
3205
3433
|
role: "button",
|
|
3206
3434
|
tabIndex: 0,
|
|
@@ -3211,7 +3439,7 @@ function TranscriptQuoteCard({ node }) {
|
|
|
3211
3439
|
event.preventDefault();
|
|
3212
3440
|
onSelect(index, quote.text);
|
|
3213
3441
|
},
|
|
3214
|
-
children: (0, react_jsx_runtime.jsx)(AnnotationCardView, {
|
|
3442
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationCardView, {
|
|
3215
3443
|
variant: "transcript",
|
|
3216
3444
|
title: quote.title,
|
|
3217
3445
|
text: quote.text,
|
|
@@ -3258,12 +3486,12 @@ function UserMessageDisplay({ node, renderMessageImages }) {
|
|
|
3258
3486
|
const quotes = scanned.annotations;
|
|
3259
3487
|
const images = contentImages(node.data?.content);
|
|
3260
3488
|
if (!text && images.length === 0 && quotes.length === 0) return null;
|
|
3261
|
-
return (0, react_jsx_runtime.jsx)("div", {
|
|
3489
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3262
3490
|
className: cx(A.colEnd, styles.userRow),
|
|
3263
|
-
children: (0, react_jsx_runtime.jsxs)("div", {
|
|
3491
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3264
3492
|
className: cx(A.colEnd, styles.userStack),
|
|
3265
3493
|
children: [
|
|
3266
|
-
images.length > 0 && renderMessageImages !== void 0 ? (0, react_jsx_runtime.jsx)("div", {
|
|
3494
|
+
images.length > 0 && renderMessageImages !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3267
3495
|
className: cx(A.colEnd, styles.userImages),
|
|
3268
3496
|
"data-message-attachments": true,
|
|
3269
3497
|
children: renderMessageImages({
|
|
@@ -3271,12 +3499,12 @@ function UserMessageDisplay({ node, renderMessageImages }) {
|
|
|
3271
3499
|
align: "end",
|
|
3272
3500
|
compact: images.length > 1
|
|
3273
3501
|
})
|
|
3274
|
-
}
|
|
3275
|
-
quotes.length > 0 ? (0, react_jsx_runtime.jsx)(TranscriptQuoteCard, { node: { data: { quotes } } }
|
|
3276
|
-
text ? (0, react_jsx_runtime.jsx)("div", {
|
|
3502
|
+
}) : null,
|
|
3503
|
+
quotes.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TranscriptQuoteCard, { node: { data: { quotes } } }) : null,
|
|
3504
|
+
text ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3277
3505
|
className: styles.userBubble,
|
|
3278
3506
|
children: text
|
|
3279
|
-
}
|
|
3507
|
+
}) : null
|
|
3280
3508
|
]
|
|
3281
3509
|
})
|
|
3282
3510
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-select-quote",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "DeepSeek Harness web plugin: annotate text selected in the conversation — the selection rides along with your next message, is marked in the source text, and the model points back at it.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek",
|