dsh-select-quote 0.3.0 → 0.3.2
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 +67 -55
- package/lib/client.js +489 -486
- package/package.json +6 -2
package/lib/client.js
CHANGED
|
@@ -51,60 +51,122 @@ function lastEditable() {
|
|
|
51
51
|
}
|
|
52
52
|
//#endregion
|
|
53
53
|
//#region src/client/styles.ts
|
|
54
|
+
/**
|
|
55
|
+
* Every style the plugin ships, in four layers.
|
|
56
|
+
*
|
|
57
|
+
* 1. tokens — product design tokens, aliased once with their fallbacks
|
|
58
|
+
* 2. atoms — shared shapes (`.dsq_a_*`), no numbers, no colors
|
|
59
|
+
* 3. components — one block per UI piece; everything measurable lives here
|
|
60
|
+
* 4. host — rules that reach into product DOM, theme flips, keyframes
|
|
61
|
+
*
|
|
62
|
+
* Injection order is the cascade order, so a component rule always wins over an
|
|
63
|
+
* atom on the same element — which is what lets atoms stay shape-only and the
|
|
64
|
+
* component blocks stay the single source of numbers.
|
|
65
|
+
*/
|
|
66
|
+
/** Layer 2 — atoms. Shape only: adding a number here is a layering violation. */
|
|
67
|
+
const ATOMS = {
|
|
68
|
+
row: "display: flex; align-items: center;",
|
|
69
|
+
inlineRow: "display: inline-flex; align-items: center;",
|
|
70
|
+
col: "display: flex; flex-direction: column;",
|
|
71
|
+
colEnd: "display: flex; flex-direction: column; align-items: flex-end;",
|
|
72
|
+
gridMid: "display: grid; place-items: center;",
|
|
73
|
+
ell: "overflow: hidden; text-overflow: ellipsis; white-space: nowrap;",
|
|
74
|
+
plain: "appearance: none; border: none; background: transparent; cursor: pointer;",
|
|
75
|
+
keep: "flex: none;",
|
|
76
|
+
grow: "flex: 1; min-width: 0;",
|
|
77
|
+
box: "box-sizing: border-box;"
|
|
78
|
+
};
|
|
79
|
+
const toKebab = (name) => name.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
80
|
+
const atomRules = Object.entries(ATOMS).map(([name, declarations]) => `.dsq_a_${toKebab(name)} { ${declarations} }`).join("\n");
|
|
81
|
+
/** Atom class names, for `cx(styles.card, A.row)`. */
|
|
82
|
+
const A = Object.fromEntries(Object.keys(ATOMS).map((name) => [name, `dsq_a_${toKebab(name)}`]));
|
|
83
|
+
/** Join class parts, dropping the falsy ones so a variant can be `cond && A.x`. */
|
|
84
|
+
function cx(...parts) {
|
|
85
|
+
return parts.filter(Boolean).join(" ");
|
|
86
|
+
}
|
|
54
87
|
const CSS = `
|
|
88
|
+
/* Layer 1 — tokens. Every product token this plugin reads is aliased here once,
|
|
89
|
+
with the fallback it used to repeat inline at each use site. Scoped to \`body\`,
|
|
90
|
+
not \`:root\`: the product flips its palette on body[data-ds-dark-theme], so an
|
|
91
|
+
alias computed on the root element would freeze the light values and inherit
|
|
92
|
+
them into the dark page. */
|
|
93
|
+
body {
|
|
94
|
+
--dsq-chat-width: var(--dsh-chat-content-width, 748px);
|
|
95
|
+
--dsq-font-size: var(--dsh-content-font-size, 14px);
|
|
96
|
+
--dsq-font-delta: var(--dsh-content-font-delta, 0px);
|
|
97
|
+
--dsq-bg-raised: var(--dsw-alias-bg-layer-1, #fff);
|
|
98
|
+
--dsq-bg-panel: var(--dsw-alias-bg-layer-2, #fff);
|
|
99
|
+
--dsq-bg-toast: var(--dsw-alias-bg-layer-3, rgba(0, 0, 0, 0.78));
|
|
100
|
+
--dsq-line: var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.1));
|
|
101
|
+
--dsq-line-divider: var(--dsw-alias-border-l4, rgba(0, 0, 0, 0.12));
|
|
102
|
+
--dsq-focus-ring: var(--dsw-alias-button-primary-fill, #1677ff);
|
|
103
|
+
--dsq-fill-hover: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05));
|
|
104
|
+
--dsq-label: var(--dsw-alias-label-primary, rgba(0, 0, 0, 0.88));
|
|
105
|
+
--dsq-label-inverted: var(--dsw-alias-label-primary-inverted, #fff);
|
|
106
|
+
--dsq-label-2: var(--dsw-alias-label-secondary, rgba(0, 0, 0, 0.65));
|
|
107
|
+
--dsq-label-3: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, 0.45));
|
|
108
|
+
--dsq-link: var(--dsw-alias-link, rgba(0, 0, 0, 0.65));
|
|
109
|
+
--dsq-shadow-pop: var(--dsw-elevation-prominent, 0 3px 8px rgba(0, 0, 0, 0.04), 0 0 20px rgba(0, 0, 0, 0.05));
|
|
110
|
+
--dsq-shadow-soft: var(--dsw-elevation-soft, 0 1px 2px rgba(0, 0, 0, 0.03), 0 6px 20px rgba(0, 0, 0, 0.06));
|
|
111
|
+
--dsq-bubble: var(--dsw-specific-bubble, rgba(0, 0, 0, 0.05));
|
|
112
|
+
--dsq-menu: var(--dsw-specific-menu, #fff);
|
|
113
|
+
--dsq-accent-400: var(--dsw-static-blue-400, #1677ff);
|
|
114
|
+
--dsq-accent: var(--dsw-static-blue-500, #3b82f6);
|
|
115
|
+
--dsq-accent-tint: var(--dsw-static-blue-50p, #eaf3ff);
|
|
116
|
+
--dsq-accent-tint-hover: var(--dsw-static-blue-75, #e5f0ff);
|
|
117
|
+
--dsq-accent-tint-dark: var(--dsw-static-blue-950, #172554);
|
|
118
|
+
--dsq-neutral-50: var(--dsw-static-neutral-50, #fafafa);
|
|
119
|
+
--dsq-neutral-100: var(--dsw-static-neutral-100, #f5f5f5);
|
|
120
|
+
--dsq-neutral-800: var(--dsw-static-neutral-800, #292929);
|
|
121
|
+
--dsq-neutral-850: var(--dsw-static-neutral-850, #212123);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Layer 2 — atoms (generated from ATOMS above; the class names and the rules
|
|
125
|
+
cannot drift apart because both come from the same table). */
|
|
126
|
+
${atomRules}
|
|
127
|
+
|
|
128
|
+
/* Layer 3 — components. Floating selection toolbar. */
|
|
55
129
|
.dsq_toolbar {
|
|
56
130
|
position: fixed;
|
|
57
131
|
z-index: 10060;
|
|
58
132
|
transform: translate(-50%, -100%);
|
|
59
|
-
display: flex;
|
|
60
|
-
align-items: center;
|
|
61
133
|
gap: 2px;
|
|
62
134
|
padding: 4px;
|
|
63
|
-
/* 无边框:--
|
|
135
|
+
/* 无边框:--dsq-shadow-soft 自带 0 0 0 .5px 的描边环,由它勾勒轮廓,
|
|
64
136
|
所以这里不要再画 border,否则描边会和环叠成两条线。 */
|
|
65
137
|
border: 0;
|
|
66
|
-
border-radius:
|
|
67
|
-
background: var(--
|
|
68
|
-
box-shadow: var(--
|
|
138
|
+
border-radius: 12px;
|
|
139
|
+
background: var(--dsq-bg-panel);
|
|
140
|
+
box-shadow: var(--dsq-shadow-soft);
|
|
69
141
|
pointer-events: auto;
|
|
70
142
|
/* Mounts once per selection, after the drag ends: soft rise + fade. */
|
|
71
143
|
animation: dsq-toolbar-in 0.16s cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
72
144
|
}
|
|
73
145
|
|
|
74
|
-
@keyframes dsq-toolbar-in {
|
|
75
|
-
from {
|
|
76
|
-
opacity: 0;
|
|
77
|
-
transform: translate(-50%, -100%) translateY(4px);
|
|
78
|
-
}
|
|
79
|
-
to {
|
|
80
|
-
opacity: 1;
|
|
81
|
-
transform: translate(-50%, -100%);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
146
|
.dsq_button {
|
|
86
|
-
|
|
87
|
-
border: none;
|
|
88
|
-
background: transparent;
|
|
89
|
-
color: var(--dsw-alias-label-primary, rgba(0, 0, 0, 0.88));
|
|
147
|
+
color: var(--dsq-label);
|
|
90
148
|
font: inherit;
|
|
91
|
-
font-size:
|
|
149
|
+
font-size: 12px;
|
|
92
150
|
line-height: 20px;
|
|
93
|
-
|
|
94
|
-
border-radius: 999px;
|
|
151
|
+
border-radius: 8px;
|
|
95
152
|
padding: 4px 10px;
|
|
96
|
-
display: inline-flex;
|
|
97
|
-
align-items: center;
|
|
98
153
|
gap: 6px;
|
|
99
154
|
white-space: nowrap;
|
|
100
155
|
}
|
|
101
|
-
|
|
102
156
|
.dsq_button:hover {
|
|
103
|
-
background: var(--
|
|
157
|
+
background: var(--dsq-fill-hover);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.dsq_buttonAddComment {
|
|
161
|
+
background: var(--dsq-accent);
|
|
162
|
+
color:#fff;
|
|
163
|
+
}
|
|
164
|
+
.dsq_buttonAddComment:hover {
|
|
165
|
+
background: var(--dsq-accent-400);
|
|
104
166
|
}
|
|
105
167
|
|
|
106
168
|
.dsq_button:focus-visible {
|
|
107
|
-
outline: 2px solid var(--
|
|
169
|
+
outline: 2px solid var(--dsq-focus-ring);
|
|
108
170
|
outline-offset: 1px;
|
|
109
171
|
}
|
|
110
172
|
|
|
@@ -115,7 +177,7 @@ const CSS = `
|
|
|
115
177
|
.dsq_divider {
|
|
116
178
|
width: 1px;
|
|
117
179
|
height: 14px;
|
|
118
|
-
background: var(--
|
|
180
|
+
background: var(--dsq-line-divider);
|
|
119
181
|
margin: 0 2px;
|
|
120
182
|
}
|
|
121
183
|
|
|
@@ -125,8 +187,8 @@ const CSS = `
|
|
|
125
187
|
transform: translate(-50%, 8px);
|
|
126
188
|
padding: 4px 10px;
|
|
127
189
|
border-radius: 999px;
|
|
128
|
-
background: var(--
|
|
129
|
-
color: var(--
|
|
190
|
+
background: var(--dsq-bg-toast);
|
|
191
|
+
color: var(--dsq-label-inverted);
|
|
130
192
|
font-size: 12px;
|
|
131
193
|
line-height: 18px;
|
|
132
194
|
pointer-events: none;
|
|
@@ -142,23 +204,20 @@ const CSS = `
|
|
|
142
204
|
width: 20%;
|
|
143
205
|
min-width: 148px;
|
|
144
206
|
max-width: 220px;
|
|
145
|
-
display: flex;
|
|
146
|
-
flex-direction: column;
|
|
147
207
|
align-items: stretch;
|
|
148
208
|
gap: 6px;
|
|
149
209
|
pointer-events: auto;
|
|
150
210
|
}
|
|
151
211
|
|
|
152
|
-
/* One
|
|
153
|
-
|
|
154
|
-
|
|
212
|
+
/* One annotation card, shared by the composer and the transcript: the base
|
|
213
|
+
carries the shape, and \`.dsq_tCard\` / \`.dsq_tCardIcon\` override only the
|
|
214
|
+
numbers where the transcript really differs.
|
|
215
|
+
Geometry mirrors the product's own file card (\`.nyYjTG_file\` in
|
|
216
|
+
ui-deliverables): hairline border, neutral fill, 18px radius, fixed 72px row. */
|
|
155
217
|
.dsq_card {
|
|
156
|
-
--dsq-card-fill: var(--
|
|
157
|
-
--dsq-card-hover: var(--
|
|
158
|
-
box-sizing: border-box;
|
|
218
|
+
--dsq-card-fill: var(--dsq-neutral-50);
|
|
219
|
+
--dsq-card-hover: var(--dsq-neutral-100);
|
|
159
220
|
flex: 0 0 auto;
|
|
160
|
-
display: flex;
|
|
161
|
-
align-items: center;
|
|
162
221
|
gap: 10px;
|
|
163
222
|
width: fit-content;
|
|
164
223
|
max-width: calc(25% - 5px);
|
|
@@ -166,10 +225,10 @@ const CSS = `
|
|
|
166
225
|
height: 64px;
|
|
167
226
|
margin: 0;
|
|
168
227
|
padding: 10px;
|
|
169
|
-
border: 0.5px solid var(--
|
|
228
|
+
border: 0.5px solid var(--dsq-line);
|
|
170
229
|
border-radius: 18px;
|
|
171
230
|
background: var(--dsq-card-fill);
|
|
172
|
-
color: var(--
|
|
231
|
+
color: var(--dsq-label);
|
|
173
232
|
transition: background-color 0.12s;
|
|
174
233
|
position: relative;
|
|
175
234
|
overflow: hidden;
|
|
@@ -185,15 +244,12 @@ const CSS = `
|
|
|
185
244
|
quarter of the input, so 48px would leave no room for the quote text. */
|
|
186
245
|
.dsq_cardIcon {
|
|
187
246
|
z-index: 2;
|
|
188
|
-
flex: none;
|
|
189
247
|
width: 36px;
|
|
190
248
|
height: 36px;
|
|
191
|
-
border: 0.5px solid var(--
|
|
249
|
+
border: 0.5px solid var(--dsq-line);
|
|
192
250
|
border-radius: 12px;
|
|
193
|
-
display: grid;
|
|
194
|
-
place-items: center;
|
|
195
251
|
background: var(--dsq-card-fill);
|
|
196
|
-
color: var(--
|
|
252
|
+
color: var(--dsq-link);
|
|
197
253
|
font-size: 12px;
|
|
198
254
|
font-weight: 600;
|
|
199
255
|
letter-spacing: -0.02em;
|
|
@@ -201,13 +257,13 @@ const CSS = `
|
|
|
201
257
|
|
|
202
258
|
.dsq_cardBody {
|
|
203
259
|
z-index: 2;
|
|
204
|
-
flex: 1;
|
|
205
|
-
min-width: 0;
|
|
206
|
-
display: flex;
|
|
207
|
-
flex-direction: column;
|
|
208
260
|
justify-content: center;
|
|
209
261
|
gap: 2px;
|
|
210
|
-
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* Keeps the ellipsised title clear of the corner remove button, which only the
|
|
265
|
+
composer stack renders. */
|
|
266
|
+
.dsq_cardStack .dsq_cardBody {
|
|
211
267
|
padding-right: 14px;
|
|
212
268
|
}
|
|
213
269
|
|
|
@@ -215,19 +271,13 @@ const CSS = `
|
|
|
215
271
|
font-size: 14px;
|
|
216
272
|
font-weight: 500;
|
|
217
273
|
line-height: 22px;
|
|
218
|
-
color: var(--
|
|
219
|
-
overflow: hidden;
|
|
220
|
-
text-overflow: ellipsis;
|
|
221
|
-
white-space: nowrap;
|
|
274
|
+
color: var(--dsq-label);
|
|
222
275
|
}
|
|
223
276
|
|
|
224
277
|
.dsq_cardSubtitle {
|
|
225
278
|
font-size: 12px;
|
|
226
279
|
line-height: 18px;
|
|
227
|
-
color: var(--
|
|
228
|
-
overflow: hidden;
|
|
229
|
-
text-overflow: ellipsis;
|
|
230
|
-
white-space: nowrap;
|
|
280
|
+
color: var(--dsq-label-3);
|
|
231
281
|
}
|
|
232
282
|
|
|
233
283
|
/* Corner remove button: hidden until the card is hovered or focused. */
|
|
@@ -236,16 +286,10 @@ const CSS = `
|
|
|
236
286
|
top: 4px;
|
|
237
287
|
right: 4px;
|
|
238
288
|
z-index: 3;
|
|
239
|
-
appearance: none;
|
|
240
|
-
border: none;
|
|
241
|
-
background: transparent;
|
|
242
289
|
width: 20px;
|
|
243
290
|
height: 20px;
|
|
244
291
|
border-radius: 6px;
|
|
245
|
-
|
|
246
|
-
color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, 0.45));
|
|
247
|
-
display: grid;
|
|
248
|
-
place-items: center;
|
|
292
|
+
color: var(--dsq-label-3);
|
|
249
293
|
font-size: 14px;
|
|
250
294
|
line-height: 1;
|
|
251
295
|
opacity: 0;
|
|
@@ -262,8 +306,8 @@ const CSS = `
|
|
|
262
306
|
}
|
|
263
307
|
|
|
264
308
|
.dsq_cardClose:hover {
|
|
265
|
-
background: var(--
|
|
266
|
-
color: var(--
|
|
309
|
+
background: var(--dsq-fill-hover);
|
|
310
|
+
color: var(--dsq-label);
|
|
267
311
|
}
|
|
268
312
|
|
|
269
313
|
/* Secondary corner action (edit comment). */
|
|
@@ -272,16 +316,10 @@ const CSS = `
|
|
|
272
316
|
top: 4px;
|
|
273
317
|
right: 28px;
|
|
274
318
|
z-index: 3;
|
|
275
|
-
appearance: none;
|
|
276
|
-
border: none;
|
|
277
|
-
background: transparent;
|
|
278
319
|
width: 20px;
|
|
279
320
|
height: 20px;
|
|
280
321
|
border-radius: 6px;
|
|
281
|
-
|
|
282
|
-
color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, 0.45));
|
|
283
|
-
display: grid;
|
|
284
|
-
place-items: center;
|
|
322
|
+
color: var(--dsq-label-3);
|
|
285
323
|
font-size: 12px;
|
|
286
324
|
line-height: 1;
|
|
287
325
|
opacity: 0;
|
|
@@ -296,42 +334,35 @@ const CSS = `
|
|
|
296
334
|
}
|
|
297
335
|
|
|
298
336
|
.dsq_cardAction:hover {
|
|
299
|
-
background: var(--
|
|
337
|
+
background: var(--dsq-fill-hover);
|
|
300
338
|
}
|
|
301
339
|
|
|
302
340
|
/* Qoder-style annotation summary (“N 条批注”) inside the composer. */
|
|
303
341
|
.dsq_summary {
|
|
304
342
|
position: relative;
|
|
305
343
|
z-index: 10040;
|
|
306
|
-
display: flex;
|
|
307
|
-
align-items: center;
|
|
308
344
|
gap: 10px;
|
|
309
345
|
width: 100%;
|
|
310
346
|
max-width: 100%;
|
|
311
347
|
padding: 8px 10px;
|
|
312
|
-
border: 0.5px solid var(--
|
|
348
|
+
border: 0.5px solid var(--dsq-line);
|
|
313
349
|
border-radius: 18px;
|
|
314
|
-
background: var(--
|
|
350
|
+
background: var(--dsq-neutral-50);
|
|
315
351
|
pointer-events: auto;
|
|
316
352
|
user-select: none;
|
|
317
353
|
}
|
|
318
354
|
|
|
319
355
|
.dsq_summaryIcon {
|
|
320
|
-
flex: none;
|
|
321
356
|
width: 32px;
|
|
322
357
|
height: 32px;
|
|
323
358
|
border-radius: 10px;
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.05));
|
|
327
|
-
color: var(--dsw-alias-link, rgba(0, 0, 0, 0.65));
|
|
359
|
+
background: var(--dsq-fill-hover);
|
|
360
|
+
color: var(--dsq-link);
|
|
328
361
|
font-size: 13px;
|
|
329
362
|
font-weight: 600;
|
|
330
363
|
}
|
|
331
364
|
|
|
332
365
|
.dsq_summaryBody {
|
|
333
|
-
flex: 1;
|
|
334
|
-
min-width: 0;
|
|
335
366
|
position: relative;
|
|
336
367
|
}
|
|
337
368
|
|
|
@@ -339,16 +370,18 @@ const CSS = `
|
|
|
339
370
|
font-size: 14px;
|
|
340
371
|
font-weight: 500;
|
|
341
372
|
line-height: 20px;
|
|
342
|
-
color: var(--
|
|
373
|
+
color: var(--dsq-label);
|
|
343
374
|
}
|
|
344
375
|
|
|
345
376
|
.dsq_summaryHint {
|
|
346
377
|
font-size: 12px;
|
|
347
378
|
line-height: 18px;
|
|
348
|
-
color: var(--
|
|
379
|
+
color: var(--dsq-label-3);
|
|
349
380
|
}
|
|
350
381
|
|
|
351
382
|
.dsq_summaryHover {
|
|
383
|
+
/* \`display: none\` here is the closed state, so this rule must not take the
|
|
384
|
+
flex atoms — the open class below turns display back on. */
|
|
352
385
|
display: none;
|
|
353
386
|
position: fixed;
|
|
354
387
|
z-index: 10050;
|
|
@@ -378,63 +411,47 @@ const CSS = `
|
|
|
378
411
|
transform-origin: bottom center;
|
|
379
412
|
}
|
|
380
413
|
|
|
381
|
-
@keyframes dsq-panel-in {
|
|
382
|
-
from {
|
|
383
|
-
transform: scale(0.96);
|
|
384
|
-
}
|
|
385
|
-
to {
|
|
386
|
-
transform: scale(1);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
414
|
.dsq_summaryHoverInner {
|
|
391
|
-
display: flex;
|
|
392
|
-
flex-direction: column;
|
|
393
415
|
gap: 4px;
|
|
394
416
|
max-height: min(420px, calc(100vh - 120px));
|
|
395
417
|
overflow-y: auto;
|
|
396
418
|
overscroll-behavior: contain;
|
|
397
|
-
padding:
|
|
398
|
-
border-radius:
|
|
399
|
-
border: 0.5px solid var(--
|
|
419
|
+
padding: 4px;
|
|
420
|
+
border-radius: 12px;
|
|
421
|
+
border: 0.5px solid var(--dsq-line);
|
|
400
422
|
/* Frosted panel: the menu token at partial alpha over a blur. */
|
|
401
|
-
background: color-mix(in srgb, var(--
|
|
423
|
+
background: color-mix(in srgb, var(--dsq-bg-raised) 50%, transparent);
|
|
402
424
|
backdrop-filter: blur(14px) saturate(1.4);
|
|
403
425
|
-webkit-backdrop-filter: blur(14px) saturate(1.4);
|
|
404
|
-
box-shadow: var(--
|
|
426
|
+
box-shadow: var(--dsq-shadow-pop);
|
|
405
427
|
}
|
|
406
428
|
|
|
407
429
|
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
|
|
408
430
|
.dsq_summaryHoverInner {
|
|
409
|
-
background: var(--
|
|
431
|
+
background: var(--dsq-menu);
|
|
410
432
|
}
|
|
411
433
|
}
|
|
412
434
|
|
|
413
435
|
.dsq_summaryItem {
|
|
414
436
|
display: block;
|
|
415
437
|
padding: 8px 10px;
|
|
416
|
-
border-radius:
|
|
438
|
+
border-radius: 8px;
|
|
417
439
|
transition: background-color 0.12s;
|
|
418
440
|
}
|
|
419
441
|
|
|
420
442
|
.dsq_summaryItem:hover {
|
|
421
|
-
background: var(--
|
|
443
|
+
background: color-mix(in srgb, var(--dsq-fill-hover) 50%, transparent) ;
|
|
422
444
|
}
|
|
423
445
|
|
|
424
446
|
.dsq_summaryItemHead {
|
|
425
|
-
display: flex;
|
|
426
|
-
align-items: center;
|
|
427
447
|
gap: 6px;
|
|
428
448
|
}
|
|
429
449
|
|
|
430
450
|
.dsq_summaryItemBadge {
|
|
431
|
-
flex: none;
|
|
432
|
-
display: grid;
|
|
433
|
-
place-items: center;
|
|
434
451
|
width: 18px;
|
|
435
452
|
height: 18px;
|
|
436
453
|
border-radius: 999px;
|
|
437
|
-
background: var(--
|
|
454
|
+
background: var(--dsq-accent);
|
|
438
455
|
color: #fff;
|
|
439
456
|
font-size: 11px;
|
|
440
457
|
font-weight: 600;
|
|
@@ -442,8 +459,6 @@ const CSS = `
|
|
|
442
459
|
}
|
|
443
460
|
|
|
444
461
|
.dsq_summaryItemActions {
|
|
445
|
-
display: flex;
|
|
446
|
-
align-items: center;
|
|
447
462
|
gap: 2px;
|
|
448
463
|
margin-left: auto;
|
|
449
464
|
opacity: 0.5;
|
|
@@ -456,21 +471,15 @@ const CSS = `
|
|
|
456
471
|
}
|
|
457
472
|
|
|
458
473
|
.dsq_summaryItemAction {
|
|
459
|
-
appearance: none;
|
|
460
|
-
border: none;
|
|
461
|
-
background: transparent;
|
|
462
474
|
width: 26px;
|
|
463
475
|
height: 26px;
|
|
464
476
|
border-radius: 7px;
|
|
465
|
-
|
|
466
|
-
color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, 0.45));
|
|
467
|
-
display: grid;
|
|
468
|
-
place-items: center;
|
|
477
|
+
color: var(--dsq-label-3);
|
|
469
478
|
}
|
|
470
479
|
|
|
471
480
|
.dsq_summaryItemAction:hover {
|
|
472
|
-
background: var(--
|
|
473
|
-
color: var(--
|
|
481
|
+
background: var(--dsq-fill-hover);
|
|
482
|
+
color: var(--dsq-label);
|
|
474
483
|
}
|
|
475
484
|
|
|
476
485
|
.dsq_summaryItemMain {
|
|
@@ -478,14 +487,10 @@ const CSS = `
|
|
|
478
487
|
}
|
|
479
488
|
|
|
480
489
|
.dsq_summaryItemButton {
|
|
481
|
-
appearance: none;
|
|
482
490
|
display: block;
|
|
483
491
|
width: 100%;
|
|
484
492
|
text-align: left;
|
|
485
493
|
font: inherit;
|
|
486
|
-
border: none;
|
|
487
|
-
background: transparent;
|
|
488
|
-
cursor: pointer;
|
|
489
494
|
border-radius: 8px;
|
|
490
495
|
padding: 2px 0 0;
|
|
491
496
|
}
|
|
@@ -501,13 +506,13 @@ const CSS = `
|
|
|
501
506
|
.dsq_summaryItemLabel {
|
|
502
507
|
font-size: 11px;
|
|
503
508
|
line-height: 16px;
|
|
504
|
-
color: var(--
|
|
509
|
+
color: var(--dsq-label-3);
|
|
505
510
|
}
|
|
506
511
|
|
|
507
512
|
.dsq_summaryItemText {
|
|
508
513
|
font-size: 13px;
|
|
509
514
|
line-height: 18px;
|
|
510
|
-
color: var(--
|
|
515
|
+
color: var(--dsq-label);
|
|
511
516
|
white-space: pre-wrap;
|
|
512
517
|
overflow-wrap: anywhere;
|
|
513
518
|
display: -webkit-box;
|
|
@@ -517,14 +522,14 @@ const CSS = `
|
|
|
517
522
|
}
|
|
518
523
|
|
|
519
524
|
.dsq_summaryItemTextEmpty {
|
|
520
|
-
color: var(--
|
|
525
|
+
color: var(--dsq-label-3);
|
|
521
526
|
}
|
|
522
527
|
|
|
523
528
|
/* Inline edit form rendered inside the hover panel item (no extra layer). */
|
|
524
529
|
.dsq_annEditText {
|
|
525
530
|
font-size: 13px;
|
|
526
531
|
line-height: 20px;
|
|
527
|
-
color: var(--
|
|
532
|
+
color: var(--dsq-label);
|
|
528
533
|
white-space: pre-wrap;
|
|
529
534
|
overflow-wrap: anywhere;
|
|
530
535
|
max-height: 132px;
|
|
@@ -533,7 +538,6 @@ const CSS = `
|
|
|
533
538
|
}
|
|
534
539
|
|
|
535
540
|
.dsq_annEditInput {
|
|
536
|
-
box-sizing: border-box;
|
|
537
541
|
width: 100%;
|
|
538
542
|
min-height: 72px;
|
|
539
543
|
resize: vertical;
|
|
@@ -543,54 +547,49 @@ const CSS = `
|
|
|
543
547
|
font: inherit;
|
|
544
548
|
font-size: 13px;
|
|
545
549
|
line-height: 20px;
|
|
546
|
-
color: var(--
|
|
547
|
-
background: var(--
|
|
550
|
+
color: var(--dsq-label);
|
|
551
|
+
background: var(--dsq-fill-hover);
|
|
548
552
|
outline: none;
|
|
549
553
|
}
|
|
550
554
|
|
|
551
555
|
.dsq_annEditInput::placeholder {
|
|
552
|
-
color: var(--
|
|
556
|
+
color: var(--dsq-label-3);
|
|
553
557
|
}
|
|
554
558
|
|
|
555
559
|
.dsq_annEditActions {
|
|
556
|
-
display: flex;
|
|
557
560
|
justify-content: flex-end;
|
|
558
|
-
align-items: center;
|
|
559
561
|
gap: 8px;
|
|
562
|
+
margin-top: 4px;
|
|
560
563
|
}
|
|
561
564
|
|
|
562
565
|
.dsq_annEditCancel {
|
|
563
|
-
|
|
564
|
-
border: none;
|
|
565
|
-
background: transparent;
|
|
566
|
-
color: var(--dsw-alias-label-secondary, rgba(0, 0, 0, 0.65));
|
|
566
|
+
color: var(--dsq-label-2);
|
|
567
567
|
font: inherit;
|
|
568
568
|
font-size: 13px;
|
|
569
|
-
padding:
|
|
570
|
-
border-radius:
|
|
571
|
-
cursor: pointer;
|
|
569
|
+
padding: 4px 12px;
|
|
570
|
+
border-radius: 10px;
|
|
572
571
|
}
|
|
573
572
|
|
|
574
573
|
.dsq_annEditCancel:hover {
|
|
575
|
-
background: var(--
|
|
574
|
+
background: var(--dsq-fill-hover);
|
|
576
575
|
}
|
|
577
576
|
|
|
578
577
|
.dsq_annEditSave {
|
|
579
578
|
appearance: none;
|
|
580
579
|
border: none;
|
|
581
|
-
background: var(--
|
|
580
|
+
background: var(--dsq-accent);
|
|
582
581
|
color: #fff;
|
|
583
582
|
font: inherit;
|
|
584
583
|
font-size: 13px;
|
|
585
584
|
font-weight: 500;
|
|
586
|
-
padding:
|
|
587
|
-
border-radius:
|
|
585
|
+
padding: 4px 12px;
|
|
586
|
+
border-radius: 10px;
|
|
588
587
|
cursor: pointer;
|
|
589
588
|
transition: filter 0.12s;
|
|
590
589
|
}
|
|
591
590
|
|
|
592
591
|
.dsq_annEditSave:hover {
|
|
593
|
-
|
|
592
|
+
background: var(--dsq-accent-400);
|
|
594
593
|
}
|
|
595
594
|
|
|
596
595
|
.dsq_annEditSave:disabled {
|
|
@@ -604,36 +603,32 @@ const CSS = `
|
|
|
604
603
|
z-index: 10050;
|
|
605
604
|
transform: translate(-50%, 4px);
|
|
606
605
|
width: min(320px, calc(100vw - 24px));
|
|
607
|
-
display: flex;
|
|
608
|
-
flex-direction: column;
|
|
609
606
|
gap: 8px;
|
|
610
|
-
padding:
|
|
611
|
-
border:
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
backdrop-filter: blur(14px) saturate(1.4);
|
|
607
|
+
padding: 8px;
|
|
608
|
+
border-radius: 16px;
|
|
609
|
+
background: color-mix(in srgb, var(--dsq-bg-raised) 75%, transparent);
|
|
610
|
+
backdrop-filter: blur(10px) saturate(1.4);
|
|
615
611
|
-webkit-backdrop-filter: blur(14px) saturate(1.4);
|
|
616
|
-
box-shadow: var(--
|
|
612
|
+
box-shadow: var(--dsq-shadow-pop);
|
|
617
613
|
}
|
|
618
614
|
|
|
619
615
|
.dsq_commentInput {
|
|
620
|
-
box-sizing: border-box;
|
|
621
616
|
width: 100%;
|
|
622
617
|
min-height: 64px;
|
|
623
618
|
resize: none;
|
|
624
619
|
border: none;
|
|
625
|
-
border-radius:
|
|
620
|
+
border-radius: 8px;
|
|
626
621
|
padding: 8px 10px;
|
|
627
622
|
font: inherit;
|
|
628
623
|
font-size: 13px;
|
|
629
624
|
line-height: 20px;
|
|
630
|
-
color: var(--
|
|
631
|
-
background: var(--
|
|
625
|
+
color: var(--dsq-label);
|
|
626
|
+
background: var(--dsq-fill-hover);
|
|
632
627
|
outline: none;
|
|
633
628
|
}
|
|
634
629
|
|
|
635
630
|
.dsq_commentInput::placeholder {
|
|
636
|
-
color: var(--
|
|
631
|
+
color: var(--dsq-label-3);
|
|
637
632
|
}
|
|
638
633
|
|
|
639
634
|
.dsq_commentActions {
|
|
@@ -659,104 +654,38 @@ const CSS = `
|
|
|
659
654
|
margin-right: 0;
|
|
660
655
|
}
|
|
661
656
|
|
|
657
|
+
/* Transcript-only differences from the shared card. Unlike the composer
|
|
658
|
+
summary (one collapsed row per message) each history card shows its own
|
|
659
|
+
comment, so the fixed height and single-line clamps are lifted on \`.dsq_tCardButton\`. */
|
|
662
660
|
.dsq_tCard {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
box-sizing: border-box;
|
|
666
|
-
display: flex;
|
|
667
|
-
align-items: center;
|
|
668
|
-
gap: 10px;
|
|
669
|
-
width: fit-content;
|
|
670
|
-
max-width: min(calc(var(--dsh-chat-content-width, 748px) * 0.72), 520px);
|
|
671
|
-
min-width: 0;
|
|
672
|
-
height: 64px;
|
|
673
|
-
margin: 0 0 0 auto;
|
|
674
|
-
padding: 10px;
|
|
675
|
-
border: 0.5px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.1));
|
|
676
|
-
border-radius: 18px;
|
|
677
|
-
background: var(--dsq-card-fill);
|
|
678
|
-
color: var(--dsw-alias-label-primary, rgba(0, 0, 0, 0.88));
|
|
679
|
-
transition: background-color 0.12s;
|
|
680
|
-
position: relative;
|
|
681
|
-
overflow: hidden;
|
|
682
|
-
user-select: none;
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
.dsq_tCard:hover {
|
|
686
|
-
background: var(--dsq-card-hover);
|
|
661
|
+
max-width: min(calc(var(--dsq-chat-width) * 0.72), 520px);
|
|
662
|
+
margin-left: auto;
|
|
687
663
|
}
|
|
688
664
|
|
|
689
665
|
.dsq_tCardIcon {
|
|
690
|
-
z-index: 2;
|
|
691
|
-
flex: none;
|
|
692
666
|
width: 40px;
|
|
693
667
|
height: 40px;
|
|
694
|
-
border: 0.5px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.1));
|
|
695
|
-
border-radius: 12px;
|
|
696
|
-
display: grid;
|
|
697
|
-
place-items: center;
|
|
698
|
-
background: var(--dsq-card-fill);
|
|
699
|
-
color: var(--dsw-alias-link, rgba(0, 0, 0, 0.65));
|
|
700
668
|
font-size: 14px;
|
|
701
|
-
font-weight: 600;
|
|
702
|
-
letter-spacing: -0.02em;
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
.dsq_tCardBody {
|
|
706
|
-
z-index: 2;
|
|
707
|
-
flex: 1;
|
|
708
|
-
min-width: 0;
|
|
709
|
-
display: flex;
|
|
710
|
-
flex-direction: column;
|
|
711
|
-
justify-content: center;
|
|
712
|
-
gap: 2px;
|
|
713
669
|
}
|
|
714
670
|
|
|
715
|
-
.dsq_tCardTitle {
|
|
716
|
-
font-size: 14px;
|
|
717
|
-
font-weight: 500;
|
|
718
|
-
line-height: 22px;
|
|
719
|
-
color: var(--dsw-alias-label-primary, rgba(0, 0, 0, 0.88));
|
|
720
|
-
overflow: hidden;
|
|
721
|
-
text-overflow: ellipsis;
|
|
722
|
-
white-space: nowrap;
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
.dsq_tCardSubtitle {
|
|
726
|
-
font-size: 12px;
|
|
727
|
-
line-height: 18px;
|
|
728
|
-
color: var(--dsw-alias-label-tertiary, rgba(0, 0, 0, 0.45));
|
|
729
|
-
overflow: hidden;
|
|
730
|
-
text-overflow: ellipsis;
|
|
731
|
-
white-space: nowrap;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
/* Transcript card wrapper: the card itself is a click target that scrolls back
|
|
735
|
-
to the marked text. Unlike the composer summary (one collapsed row per
|
|
736
|
-
message) each history card shows its own comment, so the fixed 64px height
|
|
737
|
-
and the single-line clamps are lifted here. */
|
|
738
671
|
.dsq_tCardButton {
|
|
739
|
-
appearance: none;
|
|
740
|
-
display: block;
|
|
741
672
|
/* Compact cards: several fit in one row before wrapping. */
|
|
673
|
+
display: block;
|
|
742
674
|
flex: 0 1 auto;
|
|
743
675
|
width: 230px;
|
|
744
676
|
max-width: 100%;
|
|
745
677
|
padding: 0;
|
|
746
|
-
border: none;
|
|
747
|
-
background: transparent;
|
|
748
678
|
font: inherit;
|
|
749
679
|
text-align: left;
|
|
750
|
-
cursor: pointer;
|
|
751
680
|
border-radius: 14px;
|
|
752
681
|
}
|
|
753
682
|
|
|
754
683
|
.dsq_tCardButton:focus-visible {
|
|
755
|
-
outline: 2px solid var(--
|
|
684
|
+
outline: 2px solid var(--dsq-accent);
|
|
756
685
|
outline-offset: 2px;
|
|
757
686
|
}
|
|
758
687
|
|
|
759
|
-
.dsq_tCardButton .
|
|
688
|
+
.dsq_tCardButton .dsq_card {
|
|
760
689
|
height: auto;
|
|
761
690
|
min-height: 0;
|
|
762
691
|
align-items: flex-start;
|
|
@@ -766,14 +695,14 @@ const CSS = `
|
|
|
766
695
|
border-radius: 14px;
|
|
767
696
|
}
|
|
768
697
|
|
|
769
|
-
.dsq_tCardButton .
|
|
698
|
+
.dsq_tCardButton .dsq_cardIcon {
|
|
770
699
|
width: 22px;
|
|
771
700
|
height: 22px;
|
|
772
701
|
border-radius: 8px;
|
|
773
702
|
font-size: 11px;
|
|
774
703
|
}
|
|
775
704
|
|
|
776
|
-
.dsq_tCardButton .
|
|
705
|
+
.dsq_tCardButton .dsq_cardTitle {
|
|
777
706
|
display: -webkit-box;
|
|
778
707
|
-webkit-line-clamp: 2;
|
|
779
708
|
-webkit-box-orient: vertical;
|
|
@@ -784,7 +713,7 @@ const CSS = `
|
|
|
784
713
|
line-height: 18px;
|
|
785
714
|
}
|
|
786
715
|
|
|
787
|
-
.dsq_tCardButton .
|
|
716
|
+
.dsq_tCardButton .dsq_cardSubtitle {
|
|
788
717
|
display: -webkit-box;
|
|
789
718
|
-webkit-line-clamp: 3;
|
|
790
719
|
-webkit-box-orient: vertical;
|
|
@@ -798,24 +727,18 @@ const CSS = `
|
|
|
798
727
|
|
|
799
728
|
/* A comment is the user's own words — give it more presence than the neutral
|
|
800
729
|
"no comment" placeholder. */
|
|
801
|
-
.
|
|
802
|
-
color: var(--
|
|
730
|
+
.dsq_cardSubtitle[data-dsq-has-comment='true'] {
|
|
731
|
+
color: var(--dsq-label-2);
|
|
803
732
|
}
|
|
804
733
|
|
|
805
734
|
/* Replacement user bubble (quote block stripped from display). */
|
|
806
735
|
.dsq_userRow {
|
|
807
|
-
display: flex;
|
|
808
|
-
flex-direction: column;
|
|
809
|
-
align-items: flex-end;
|
|
810
736
|
gap: 6px;
|
|
811
737
|
}
|
|
812
738
|
|
|
813
739
|
.dsq_userStack {
|
|
814
740
|
min-width: 0;
|
|
815
|
-
max-width: min(calc(var(--
|
|
816
|
-
display: flex;
|
|
817
|
-
flex-direction: column;
|
|
818
|
-
align-items: flex-end;
|
|
741
|
+
max-width: min(calc(var(--dsq-chat-width) * 0.702), 82%);
|
|
819
742
|
gap: 8px;
|
|
820
743
|
}
|
|
821
744
|
|
|
@@ -823,48 +746,28 @@ const CSS = `
|
|
|
823
746
|
max-width: 100%;
|
|
824
747
|
padding: 10px 16px;
|
|
825
748
|
border-radius: 22px;
|
|
826
|
-
background: var(--
|
|
827
|
-
color: var(--
|
|
828
|
-
font-size: var(--
|
|
829
|
-
line-height: calc(22px + var(--
|
|
749
|
+
background: var(--dsq-bubble);
|
|
750
|
+
color: var(--dsq-label);
|
|
751
|
+
font-size: var(--dsq-font-size);
|
|
752
|
+
line-height: calc(22px + var(--dsq-font-delta));
|
|
830
753
|
white-space: pre-wrap;
|
|
831
754
|
word-break: break-word;
|
|
832
755
|
}
|
|
833
756
|
|
|
834
|
-
/* Reserve room for the floating quote cards inside the composer card, so the
|
|
835
|
-
attachment rail and the editor flow below them instead of being covered. */
|
|
836
|
-
.dsq_cardPad {
|
|
837
|
-
padding-top: var(--dsq-quote-pad, 88px) !important;
|
|
838
|
-
}
|
|
839
|
-
|
|
840
757
|
/* Durable message images of a replacement user bubble. */
|
|
841
758
|
.dsq_userImages {
|
|
842
|
-
display: flex;
|
|
843
|
-
flex-direction: column;
|
|
844
|
-
align-items: flex-end;
|
|
845
|
-
gap: 8px;
|
|
846
759
|
max-width: 100%;
|
|
847
760
|
}
|
|
848
761
|
|
|
849
|
-
/* Dark theme: the product flips its palette on body[data-ds-dark-theme], and
|
|
850
|
-
every --dsw-alias-* token follows it. Only the card fill is ours. */
|
|
851
|
-
body[data-ds-dark-theme] .dsq_card,
|
|
852
|
-
body[data-ds-dark-theme] .dsq_tCard {
|
|
853
|
-
--dsq-card-fill: var(--dsw-static-neutral-850, #212123);
|
|
854
|
-
--dsq-card-hover: var(--dsw-static-neutral-800, #292929);
|
|
855
|
-
}
|
|
856
|
-
|
|
857
762
|
/* Model-written :dsh-annotation{index="N"} chips. */
|
|
858
763
|
.dsq_annDirective {
|
|
859
764
|
appearance: none;
|
|
860
|
-
display: inline-flex;
|
|
861
|
-
align-items: center;
|
|
862
765
|
margin: 0 2px;
|
|
863
766
|
padding: 0 6px;
|
|
864
|
-
border: 0.5px solid var(--
|
|
767
|
+
border: 0.5px solid var(--dsq-line);
|
|
865
768
|
border-radius: 6px;
|
|
866
|
-
background: var(--
|
|
867
|
-
color: var(--
|
|
769
|
+
background: var(--dsq-fill-hover);
|
|
770
|
+
color: var(--dsq-link);
|
|
868
771
|
font: inherit;
|
|
869
772
|
font-size: 12px;
|
|
870
773
|
line-height: 18px;
|
|
@@ -872,14 +775,10 @@ body[data-ds-dark-theme] .dsq_tCard {
|
|
|
872
775
|
vertical-align: baseline;
|
|
873
776
|
}
|
|
874
777
|
|
|
875
|
-
.dsq_annDirective:hover {
|
|
876
|
-
background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.08));
|
|
877
|
-
}
|
|
878
|
-
|
|
879
778
|
/* Inline annotation mark in message bodies: highlight underline + ordinal badge. */
|
|
880
779
|
.dsq_annMark {
|
|
881
|
-
--dsq-mark-color: var(--
|
|
882
|
-
--dsq-mark-tint: var(--
|
|
780
|
+
--dsq-mark-color: var(--dsq-accent);
|
|
781
|
+
--dsq-mark-tint: var(--dsq-accent-tint);
|
|
883
782
|
background: var(--dsq-mark-tint);
|
|
884
783
|
text-decoration: underline;
|
|
885
784
|
text-decoration-style: dashed;
|
|
@@ -912,13 +811,36 @@ body[data-ds-dark-theme] .dsq_tCard {
|
|
|
912
811
|
}
|
|
913
812
|
|
|
914
813
|
.dsq_annMark:hover {
|
|
915
|
-
background: var(--
|
|
814
|
+
background: var(--dsq-accent-tint-hover);
|
|
916
815
|
}
|
|
917
816
|
|
|
918
817
|
.dsq_annMarkFlash {
|
|
919
818
|
animation: dsq-ann-flash 1.4s ease-out;
|
|
920
819
|
}
|
|
921
820
|
|
|
821
|
+
/* Layer 4 — host surface: keyframes, theme flips, and the two rules that reach
|
|
822
|
+
into product-owned DOM. */
|
|
823
|
+
|
|
824
|
+
@keyframes dsq-toolbar-in {
|
|
825
|
+
from {
|
|
826
|
+
opacity: 0;
|
|
827
|
+
transform: translate(-50%, -100%) translateY(4px);
|
|
828
|
+
}
|
|
829
|
+
to {
|
|
830
|
+
opacity: 1;
|
|
831
|
+
transform: translate(-50%, -100%);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
@keyframes dsq-panel-in {
|
|
836
|
+
from {
|
|
837
|
+
transform: scale(0.96);
|
|
838
|
+
}
|
|
839
|
+
to {
|
|
840
|
+
transform: scale(1);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
922
844
|
@keyframes dsq-ann-flash {
|
|
923
845
|
0% {
|
|
924
846
|
background: var(--dsq-mark-color);
|
|
@@ -930,8 +852,22 @@ body[data-ds-dark-theme] .dsq_tCard {
|
|
|
930
852
|
}
|
|
931
853
|
}
|
|
932
854
|
|
|
855
|
+
/* The product flips its palette on body[data-ds-dark-theme], and every
|
|
856
|
+
--dsq-* alias follows it. Only values we own ourselves need restating — the
|
|
857
|
+
transcript card carries .dsq_card too, so one selector covers both. */
|
|
858
|
+
body[data-ds-dark-theme] .dsq_card {
|
|
859
|
+
--dsq-card-fill: var(--dsq-neutral-850);
|
|
860
|
+
--dsq-card-hover: var(--dsq-neutral-800);
|
|
861
|
+
}
|
|
862
|
+
|
|
933
863
|
body[data-ds-dark-theme] .dsq_annMark {
|
|
934
|
-
--dsq-mark-tint: var(--
|
|
864
|
+
--dsq-mark-tint: var(--dsq-accent-tint-dark);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/* Reserve room for the floating quote cards inside the composer card, so the
|
|
868
|
+
attachment rail and the editor flow below them instead of being covered. */
|
|
869
|
+
.dsq_cardPad {
|
|
870
|
+
padding-top: var(--dsq-quote-pad, 88px) !important;
|
|
935
871
|
}
|
|
936
872
|
`;
|
|
937
873
|
const TAG_ID = "dsh-select-quote/toolbar.css";
|
|
@@ -947,6 +883,7 @@ function ensureToolbarStyles() {
|
|
|
947
883
|
const styles = {
|
|
948
884
|
toolbar: "dsq_toolbar",
|
|
949
885
|
button: "dsq_button",
|
|
886
|
+
buttonAddComment: "dsq_buttonAddComment",
|
|
950
887
|
divider: "dsq_divider",
|
|
951
888
|
status: "dsq_status",
|
|
952
889
|
cardStack: "dsq_cardStack",
|
|
@@ -992,9 +929,6 @@ const styles = {
|
|
|
992
929
|
tCardButton: "dsq_tCardButton",
|
|
993
930
|
tCard: "dsq_tCard",
|
|
994
931
|
tCardIcon: "dsq_tCardIcon",
|
|
995
|
-
tCardBody: "dsq_tCardBody",
|
|
996
|
-
tCardTitle: "dsq_tCardTitle",
|
|
997
|
-
tCardSubtitle: "dsq_tCardSubtitle",
|
|
998
932
|
userRow: "dsq_userRow",
|
|
999
933
|
userStack: "dsq_userStack",
|
|
1000
934
|
userImages: "dsq_userImages",
|
|
@@ -1765,195 +1699,224 @@ function LucideIcon({ name, size = 15 }) {
|
|
|
1765
1699
|
}
|
|
1766
1700
|
//#endregion
|
|
1767
1701
|
//#region src/client/i18n.ts
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
}
|
|
1812
|
-
en: {
|
|
1813
|
-
"common.selectedText": "Selected text",
|
|
1814
|
-
"common.commentPlaceholder": "Add an optional comment…",
|
|
1815
|
-
"common.cancel": "Cancel",
|
|
1816
|
-
"common.save": "Save",
|
|
1817
|
-
"toolbar.aria": "Selection actions",
|
|
1818
|
-
"toolbar.commentHint": "Add annotation…",
|
|
1819
|
-
"toolbar.copy": "Copy",
|
|
1820
|
-
"toolbar.copyFailed": "Copy failed",
|
|
1821
|
-
"toolbar.addToTask": "Add to task",
|
|
1822
|
-
"toolbar.commentDialogAria": "Add an optional comment",
|
|
1823
|
-
"toolbar.skipComment": "Skip comment",
|
|
1824
|
-
"toolbar.addComment": "Add comment",
|
|
1825
|
-
"card.commentSubtitle": "Comment · {comment}",
|
|
1826
|
-
"card.ariaIndexed": "Annotation {index}",
|
|
1827
|
-
"card.ariaPlain": "Selection annotation",
|
|
1828
|
-
"card.editComment": "Edit comment",
|
|
1829
|
-
"card.remove": "Remove annotation",
|
|
1830
|
-
"summary.hint": "Hover to view annotations",
|
|
1831
|
-
"summary.dialogAria": "Annotation details: {count}",
|
|
1832
|
-
"summary.edit": "Edit annotation {index}",
|
|
1833
|
-
"summary.remove": "Remove annotation {index}",
|
|
1834
|
-
"summary.userComment": "User comment",
|
|
1835
|
-
"summary.noComment": "No comment added",
|
|
1836
|
-
"summary.aria": {
|
|
1837
|
-
one: "{count} annotation. Hover to view details.",
|
|
1838
|
-
other: "{count} annotations. Hover to view details."
|
|
1839
|
-
},
|
|
1840
|
-
"summary.title": {
|
|
1841
|
-
one: "{count} annotation",
|
|
1842
|
-
other: "{count} annotations"
|
|
1843
|
-
},
|
|
1844
|
-
"summary.removeAll": "Remove all annotations",
|
|
1845
|
-
"chip.annotation": "Annotation {index}"
|
|
1846
|
-
},
|
|
1847
|
-
zh: {
|
|
1848
|
-
"common.selectedText": "选中的文本",
|
|
1849
|
-
"common.commentPlaceholder": "添加可选评论…",
|
|
1850
|
-
"common.cancel": "取消",
|
|
1851
|
-
"common.save": "保存",
|
|
1852
|
-
"toolbar.aria": "划词操作",
|
|
1853
|
-
"toolbar.commentHint": "添加批注…",
|
|
1854
|
-
"toolbar.copy": "复制",
|
|
1855
|
-
"toolbar.copyFailed": "复制失败",
|
|
1856
|
-
"toolbar.addToTask": "添加到任务",
|
|
1857
|
-
"toolbar.commentDialogAria": "添加可选评论",
|
|
1858
|
-
"toolbar.skipComment": "暂不评论",
|
|
1859
|
-
"toolbar.addComment": "添加评论",
|
|
1860
|
-
"card.commentSubtitle": "评论 · {comment}",
|
|
1861
|
-
"card.ariaIndexed": "批注 {index}",
|
|
1862
|
-
"card.ariaPlain": "划词批注",
|
|
1863
|
-
"card.editComment": "编辑评论",
|
|
1864
|
-
"card.remove": "移除批注",
|
|
1865
|
-
"summary.hint": "悬停查看批注",
|
|
1866
|
-
"summary.dialogAria": "{count} 条划词批注详情",
|
|
1867
|
-
"summary.edit": "编辑批注 {index}",
|
|
1868
|
-
"summary.remove": "移除批注 {index}",
|
|
1869
|
-
"summary.userComment": "用户评论",
|
|
1870
|
-
"summary.noComment": "未添加评论",
|
|
1871
|
-
"summary.aria": { other: "{count} 条划词批注。悬停查看详情。" },
|
|
1872
|
-
"summary.title": { other: "{count} 条批注" },
|
|
1873
|
-
"summary.removeAll": "移除全部划词批注",
|
|
1874
|
-
"chip.annotation": "批注 {index}"
|
|
1875
|
-
}
|
|
1702
|
+
/**
|
|
1703
|
+
* Copy for dsh-select-quote, served through the host locale service.
|
|
1704
|
+
*
|
|
1705
|
+
* The active language is owned by `@deepseek-ai/dsh-client-locale` (Settings →
|
|
1706
|
+
* General → Language, persisted as `locale.preference`). This module owns only
|
|
1707
|
+
* the dictionaries and the plural form selection that the host's plain-template
|
|
1708
|
+
* lookup cannot express; it never resolves a language on its own.
|
|
1709
|
+
*
|
|
1710
|
+
* NOT localized on purpose:
|
|
1711
|
+
* - wire markers (`> [选中文本]`, `<response-annotations>`,
|
|
1712
|
+
* `:dsh-annotation{index="N"}`) — they are the message format the model
|
|
1713
|
+
* and the transcript parser agree on;
|
|
1714
|
+
* - the `# Response annotations:` header — model-facing instruction prose,
|
|
1715
|
+
* not UI text.
|
|
1716
|
+
*/
|
|
1717
|
+
const LOCALE_NS = "select-quote";
|
|
1718
|
+
const zh = {
|
|
1719
|
+
"common.selectedText": "选中的文本",
|
|
1720
|
+
"common.commentPlaceholder": "添加可选评论…",
|
|
1721
|
+
"common.cancel": "取消",
|
|
1722
|
+
"common.save": "保存",
|
|
1723
|
+
"toolbar.aria": "划词操作",
|
|
1724
|
+
"toolbar.commentHint": "添加批注…",
|
|
1725
|
+
"toolbar.copy": "复制",
|
|
1726
|
+
"toolbar.copyFailed": "复制失败",
|
|
1727
|
+
"toolbar.addToTask": "添加到任务",
|
|
1728
|
+
"toolbar.commentDialogAria": "添加可选评论",
|
|
1729
|
+
"toolbar.skipComment": "暂不评论",
|
|
1730
|
+
"toolbar.addComment": "添加评论",
|
|
1731
|
+
"card.commentSubtitle": "评论 · {comment}",
|
|
1732
|
+
"card.ariaIndexed": "批注 {index}",
|
|
1733
|
+
"card.ariaPlain": "划词批注",
|
|
1734
|
+
"card.editComment": "编辑评论",
|
|
1735
|
+
"card.remove": "移除批注",
|
|
1736
|
+
"summary.hint": "悬停查看批注",
|
|
1737
|
+
"summary.dialogAria": "{count} 条划词批注详情",
|
|
1738
|
+
"summary.edit": "编辑批注 {index}",
|
|
1739
|
+
"summary.remove": "移除批注 {index}",
|
|
1740
|
+
"summary.userComment": "用户评论",
|
|
1741
|
+
"summary.noComment": "未添加评论",
|
|
1742
|
+
"summary.aria.other": "{count} 条划词批注。悬停查看详情。",
|
|
1743
|
+
"summary.title.other": "{count} 条批注",
|
|
1744
|
+
"summary.removeAll": "移除全部划词批注",
|
|
1745
|
+
"chip.annotation": "批注 {index}"
|
|
1876
1746
|
};
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1747
|
+
const en = {
|
|
1748
|
+
"common.selectedText": "Selected text",
|
|
1749
|
+
"common.commentPlaceholder": "Add an optional comment…",
|
|
1750
|
+
"common.cancel": "Cancel",
|
|
1751
|
+
"common.save": "Save",
|
|
1752
|
+
"toolbar.aria": "Selection actions",
|
|
1753
|
+
"toolbar.commentHint": "Add annotation…",
|
|
1754
|
+
"toolbar.copy": "Copy",
|
|
1755
|
+
"toolbar.copyFailed": "Copy failed",
|
|
1756
|
+
"toolbar.addToTask": "Add to task",
|
|
1757
|
+
"toolbar.commentDialogAria": "Add an optional comment",
|
|
1758
|
+
"toolbar.skipComment": "Skip comment",
|
|
1759
|
+
"toolbar.addComment": "Add comment",
|
|
1760
|
+
"card.commentSubtitle": "Comment · {comment}",
|
|
1761
|
+
"card.ariaIndexed": "Annotation {index}",
|
|
1762
|
+
"card.ariaPlain": "Selection annotation",
|
|
1763
|
+
"card.editComment": "Edit comment",
|
|
1764
|
+
"card.remove": "Remove annotation",
|
|
1765
|
+
"summary.hint": "Hover to view annotations",
|
|
1766
|
+
"summary.dialogAria": "Annotation details: {count}",
|
|
1767
|
+
"summary.edit": "Edit annotation {index}",
|
|
1768
|
+
"summary.remove": "Remove annotation {index}",
|
|
1769
|
+
"summary.userComment": "User comment",
|
|
1770
|
+
"summary.noComment": "No comment added",
|
|
1771
|
+
"summary.aria.one": "{count} annotation. Hover to view details.",
|
|
1772
|
+
"summary.aria.other": "{count} annotations. Hover to view details.",
|
|
1773
|
+
"summary.title.one": "{count} annotation",
|
|
1774
|
+
"summary.title.other": "{count} annotations",
|
|
1775
|
+
"summary.removeAll": "Remove all annotations",
|
|
1776
|
+
"chip.annotation": "Annotation {index}"
|
|
1777
|
+
};
|
|
1778
|
+
/**
|
|
1779
|
+
* Russian is not a shipped host locale: it is added through
|
|
1780
|
+
* `ctx.locale.addLanguage()` and its dictionary through the three-argument
|
|
1781
|
+
* `register()`. Its fallback chain ends at `en`, as the host requires.
|
|
1782
|
+
*/
|
|
1783
|
+
const ru = {
|
|
1784
|
+
"common.selectedText": "Выделенный текст",
|
|
1785
|
+
"common.commentPlaceholder": "Добавить необязательный комментарий…",
|
|
1786
|
+
"common.cancel": "Отмена",
|
|
1787
|
+
"common.save": "Сохранить",
|
|
1788
|
+
"toolbar.aria": "Действия с выделенным текстом",
|
|
1789
|
+
"toolbar.commentHint": "Добавить аннотацию…",
|
|
1790
|
+
"toolbar.copy": "Копировать",
|
|
1791
|
+
"toolbar.copyFailed": "Не удалось скопировать",
|
|
1792
|
+
"toolbar.addToTask": "Добавить к задаче",
|
|
1793
|
+
"toolbar.commentDialogAria": "Добавить необязательный комментарий",
|
|
1794
|
+
"toolbar.skipComment": "Без комментария",
|
|
1795
|
+
"toolbar.addComment": "Добавить комментарий",
|
|
1796
|
+
"card.commentSubtitle": "Комментарий · {comment}",
|
|
1797
|
+
"card.ariaIndexed": "Аннотация {index}",
|
|
1798
|
+
"card.ariaPlain": "Аннотация к выделенному тексту",
|
|
1799
|
+
"card.editComment": "Изменить комментарий",
|
|
1800
|
+
"card.remove": "Удалить аннотацию",
|
|
1801
|
+
"summary.hint": "Наведите, чтобы увидеть аннотации",
|
|
1802
|
+
"summary.dialogAria": "Детали аннотаций: {count}",
|
|
1803
|
+
"summary.edit": "Изменить аннотацию {index}",
|
|
1804
|
+
"summary.remove": "Удалить аннотацию {index}",
|
|
1805
|
+
"summary.userComment": "Комментарий пользователя",
|
|
1806
|
+
"summary.noComment": "Комментарий не добавлен",
|
|
1807
|
+
"summary.aria.one": "{count} аннотация. Наведите, чтобы увидеть детали.",
|
|
1808
|
+
"summary.aria.few": "{count} аннотации. Наведите, чтобы увидеть детали.",
|
|
1809
|
+
"summary.aria.many": "{count} аннотаций. Наведите, чтобы увидеть детали.",
|
|
1810
|
+
"summary.aria.other": "{count} аннотаций. Наведите, чтобы увидеть детали.",
|
|
1811
|
+
"summary.title.one": "{count} аннотация",
|
|
1812
|
+
"summary.title.few": "{count} аннотации",
|
|
1813
|
+
"summary.title.many": "{count} аннотаций",
|
|
1814
|
+
"summary.title.other": "{count} аннотаций",
|
|
1815
|
+
"summary.removeAll": "Удалить все аннотации",
|
|
1816
|
+
"chip.annotation": "Аннотация {index}"
|
|
1817
|
+
};
|
|
1818
|
+
/** Both shipped host locales, required together by the typed `register()`. */
|
|
1819
|
+
const SHIPPED_DICTS = Object.freeze({
|
|
1820
|
+
zh,
|
|
1821
|
+
en
|
|
1822
|
+
});
|
|
1823
|
+
/** Dictionary for the language registered through {@link LANGUAGE_RU}. */
|
|
1824
|
+
const DICTIONARIES = Object.freeze({
|
|
1825
|
+
zh,
|
|
1826
|
+
en,
|
|
1827
|
+
ru
|
|
1828
|
+
});
|
|
1829
|
+
const LANGUAGE_RU = Object.freeze({
|
|
1830
|
+
id: "ru",
|
|
1831
|
+
label: "Русский",
|
|
1832
|
+
fallback: "en"
|
|
1833
|
+
});
|
|
1834
|
+
let api;
|
|
1835
|
+
/**
|
|
1836
|
+
* Install the host-bound translation entry point. Called once from `apply()`
|
|
1837
|
+
* with `ctx.locale.bind(LOCALE_NS)` and the live snapshot accessors.
|
|
1838
|
+
*/
|
|
1839
|
+
function bindLocale(next) {
|
|
1840
|
+
api = next;
|
|
1904
1841
|
}
|
|
1905
|
-
/** Active
|
|
1906
|
-
function
|
|
1907
|
-
|
|
1908
|
-
return cached;
|
|
1842
|
+
/** Active language id, or `en` before the host service is bound. */
|
|
1843
|
+
function activeLocale() {
|
|
1844
|
+
return api?.getActive() ?? "en";
|
|
1909
1845
|
}
|
|
1910
|
-
function
|
|
1846
|
+
function pluralForm(count) {
|
|
1847
|
+
const locale = activeLocale();
|
|
1911
1848
|
if (locale === "ru") {
|
|
1912
1849
|
const mod10 = count % 10;
|
|
1913
1850
|
const mod100 = count % 100;
|
|
1914
|
-
if (mod10 === 1 && mod100 !== 11) return
|
|
1915
|
-
if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) return
|
|
1916
|
-
return
|
|
1851
|
+
if (mod10 === 1 && mod100 !== 11) return "one";
|
|
1852
|
+
if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) return "few";
|
|
1853
|
+
return "many";
|
|
1917
1854
|
}
|
|
1918
|
-
if (locale === "en") return
|
|
1919
|
-
return
|
|
1920
|
-
}
|
|
1921
|
-
function interpolate(template, params) {
|
|
1922
|
-
return template.replace(/\{(\w+)\}/g, (whole, name) => name in params ? String(params[name]) : whole);
|
|
1855
|
+
if (locale === "en") return count === 1 ? "one" : "other";
|
|
1856
|
+
return "other";
|
|
1923
1857
|
}
|
|
1924
1858
|
/**
|
|
1925
|
-
* Translate a key
|
|
1926
|
-
* `
|
|
1927
|
-
* selects the right plural form when the message is a `PluralForms` object.
|
|
1859
|
+
* Translate a `select-quote` key. A numeric `count` selects a plural form and
|
|
1860
|
+
* the matching `key.form` entry is used, falling back to `key.other`.
|
|
1928
1861
|
*/
|
|
1929
1862
|
function t(key, params = {}) {
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1863
|
+
if (!api) return key;
|
|
1864
|
+
if (typeof params.count === "number") {
|
|
1865
|
+
const form = pluralForm(params.count);
|
|
1866
|
+
const exact = api.translate(`${key}.${form}`, params);
|
|
1867
|
+
if (exact !== `${key}.${form}`) return exact;
|
|
1868
|
+
const other = api.translate(`${key}.other`, params);
|
|
1869
|
+
if (other !== `${key}.other`) return other;
|
|
1870
|
+
}
|
|
1871
|
+
return api.translate(key, params);
|
|
1872
|
+
}
|
|
1873
|
+
/**
|
|
1874
|
+
* Subscribe to language or dictionary changes. Components go through
|
|
1875
|
+
* `useLocale()` instead; this is the non-React entry point (DOM decorations).
|
|
1876
|
+
* Subscribing before the host service is bound is a no-op, so callers subscribe
|
|
1877
|
+
* from `apply()`, after `bindLocale()`.
|
|
1878
|
+
*/
|
|
1879
|
+
function subscribeLocale(listener) {
|
|
1880
|
+
return api ? api.subscribe(listener) : () => {};
|
|
1881
|
+
}
|
|
1882
|
+
/** Monotonic host revision counter, bumped on locale and dictionary changes. */
|
|
1883
|
+
function localeRevision() {
|
|
1884
|
+
return api?.getRevision() ?? 0;
|
|
1885
|
+
}
|
|
1886
|
+
//#endregion
|
|
1887
|
+
//#region src/client/useLocale.ts
|
|
1888
|
+
/**
|
|
1889
|
+
* Re-render the calling component when the host language changes, so slot copy
|
|
1890
|
+
* switches without a page reload. Subscriptions happen after mount, by which
|
|
1891
|
+
* time `apply()` has bound the locale service.
|
|
1892
|
+
*/
|
|
1893
|
+
function useLocale() {
|
|
1894
|
+
(0, react.useSyncExternalStore)(subscribeLocale, localeRevision, localeRevision);
|
|
1933
1895
|
}
|
|
1934
1896
|
//#endregion
|
|
1935
1897
|
//#region src/client/ui/AnnotationCardView.tsx
|
|
1936
1898
|
function AnnotationCardView({ title, text, comment, variant, index, onRemove, onEditComment }) {
|
|
1899
|
+
useLocale();
|
|
1937
1900
|
const composer = variant === "composer";
|
|
1938
1901
|
const subtitle = comment ? composer ? t("card.commentSubtitle", { comment }) : comment : composer ? t("common.selectedText") : t("summary.noComment");
|
|
1939
1902
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1940
|
-
className:
|
|
1903
|
+
className: cx(A.box, A.row, styles.card, !composer && styles.tCard),
|
|
1941
1904
|
role: "group",
|
|
1942
1905
|
"aria-label": index ? t("card.ariaIndexed", { index }) : t("card.ariaPlain"),
|
|
1943
1906
|
children: [
|
|
1944
1907
|
(0, react_jsx_runtime.jsx)("div", {
|
|
1945
|
-
className:
|
|
1908
|
+
className: cx(A.keep, A.gridMid, styles.cardIcon, !composer && styles.tCardIcon),
|
|
1946
1909
|
"aria-hidden": true,
|
|
1947
1910
|
children: index ? String(index) : "AI"
|
|
1948
1911
|
}),
|
|
1949
1912
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
1950
|
-
className:
|
|
1913
|
+
className: cx(A.grow, A.col, styles.cardBody),
|
|
1951
1914
|
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
1952
|
-
className:
|
|
1915
|
+
className: cx(A.ell, styles.cardTitle),
|
|
1953
1916
|
title: text,
|
|
1954
1917
|
children: title
|
|
1955
1918
|
}), (0, react_jsx_runtime.jsx)("div", {
|
|
1956
|
-
className:
|
|
1919
|
+
className: cx(A.ell, styles.cardSubtitle),
|
|
1957
1920
|
...comment ? {
|
|
1958
1921
|
title: comment,
|
|
1959
1922
|
"data-dsq-has-comment": "true"
|
|
@@ -1963,14 +1926,14 @@ function AnnotationCardView({ title, text, comment, variant, index, onRemove, on
|
|
|
1963
1926
|
}),
|
|
1964
1927
|
composer && onEditComment ? (0, react_jsx_runtime.jsx)("button", {
|
|
1965
1928
|
type: "button",
|
|
1966
|
-
className: styles.cardAction,
|
|
1929
|
+
className: cx(A.plain, A.gridMid, styles.cardAction),
|
|
1967
1930
|
"aria-label": t("card.editComment"),
|
|
1968
1931
|
onClick: onEditComment,
|
|
1969
1932
|
children: "✎"
|
|
1970
1933
|
}) : null,
|
|
1971
1934
|
composer && onRemove ? (0, react_jsx_runtime.jsx)("button", {
|
|
1972
1935
|
type: "button",
|
|
1973
|
-
className: styles.cardClose,
|
|
1936
|
+
className: cx(A.plain, A.gridMid, styles.cardClose),
|
|
1974
1937
|
"aria-label": t("card.remove"),
|
|
1975
1938
|
onClick: onRemove,
|
|
1976
1939
|
children: "×"
|
|
@@ -1993,7 +1956,8 @@ const SETTLE_DELAY = 600;
|
|
|
1993
1956
|
* other cancels. Comment editing happens inline in the list — the panel stays
|
|
1994
1957
|
* put and auto-close is suppressed while an edit is open.
|
|
1995
1958
|
*/
|
|
1996
|
-
function AnnotationSummary({ count, items, onRemoveAll, onItemSelect, onItemRemove, onItemEdit
|
|
1959
|
+
function AnnotationSummary({ count, items, onRemoveAll, onItemSelect, onItemRemove, onItemEdit }) {
|
|
1960
|
+
useLocale();
|
|
1997
1961
|
const rootRef = (0, react.useRef)(null);
|
|
1998
1962
|
const popRef = (0, react.useRef)(null);
|
|
1999
1963
|
const closeTimerRef = (0, react.useRef)(null);
|
|
@@ -2153,16 +2117,16 @@ function AnnotationSummary({ count, items, onRemoveAll, onItemSelect, onItemRemo
|
|
|
2153
2117
|
onPointerEnter: openPop,
|
|
2154
2118
|
onPointerLeave,
|
|
2155
2119
|
children: (0, react_jsx_runtime.jsx)("div", {
|
|
2156
|
-
className: styles.summaryHoverInner,
|
|
2120
|
+
className: cx(A.col, styles.summaryHoverInner),
|
|
2157
2121
|
children: items.map((item, i) => {
|
|
2158
2122
|
const isEditing = editing === i;
|
|
2159
2123
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
2160
2124
|
className: styles.summaryItem,
|
|
2161
2125
|
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
2162
|
-
className: styles.summaryItemHead,
|
|
2126
|
+
className: cx(A.row, styles.summaryItemHead),
|
|
2163
2127
|
children: [
|
|
2164
2128
|
(0, react_jsx_runtime.jsx)("div", {
|
|
2165
|
-
className: styles.summaryItemBadge,
|
|
2129
|
+
className: cx(A.keep, A.gridMid, styles.summaryItemBadge),
|
|
2166
2130
|
"aria-hidden": true,
|
|
2167
2131
|
children: String(i + 1)
|
|
2168
2132
|
}),
|
|
@@ -2171,16 +2135,16 @@ function AnnotationSummary({ count, items, onRemoveAll, onItemSelect, onItemRemo
|
|
|
2171
2135
|
children: t("common.selectedText")
|
|
2172
2136
|
}),
|
|
2173
2137
|
!isEditing && (onItemRemove || onItemEdit) ? (0, react_jsx_runtime.jsxs)("div", {
|
|
2174
|
-
className: styles.summaryItemActions,
|
|
2138
|
+
className: cx(A.row, styles.summaryItemActions),
|
|
2175
2139
|
children: [onItemEdit ? (0, react_jsx_runtime.jsx)("button", {
|
|
2176
2140
|
type: "button",
|
|
2177
|
-
className: styles.summaryItemAction,
|
|
2141
|
+
className: cx(A.plain, A.gridMid, styles.summaryItemAction),
|
|
2178
2142
|
"aria-label": t("summary.edit", { index: i + 1 }),
|
|
2179
2143
|
onClick: () => startEdit(i),
|
|
2180
2144
|
children: (0, react_jsx_runtime.jsx)(LucideIcon, { name: "pencil" })
|
|
2181
2145
|
}) : null, onItemRemove ? (0, react_jsx_runtime.jsx)("button", {
|
|
2182
2146
|
type: "button",
|
|
2183
|
-
className: styles.summaryItemAction,
|
|
2147
|
+
className: cx(A.plain, A.gridMid, styles.summaryItemAction),
|
|
2184
2148
|
"aria-label": t("summary.remove", { index: i + 1 }),
|
|
2185
2149
|
onClick: () => onItemRemove(i, item),
|
|
2186
2150
|
children: (0, react_jsx_runtime.jsx)(LucideIcon, { name: "trash-2" })
|
|
@@ -2200,7 +2164,7 @@ function AnnotationSummary({ count, items, onRemoveAll, onItemSelect, onItemRemo
|
|
|
2200
2164
|
}),
|
|
2201
2165
|
(0, react_jsx_runtime.jsx)("textarea", {
|
|
2202
2166
|
ref: editInputRef,
|
|
2203
|
-
className: styles.annEditInput,
|
|
2167
|
+
className: cx(A.box, styles.annEditInput),
|
|
2204
2168
|
placeholder: t("common.commentPlaceholder"),
|
|
2205
2169
|
value: editDraft,
|
|
2206
2170
|
onChange: (e) => setEditDraft(e.target.value),
|
|
@@ -2217,10 +2181,10 @@ function AnnotationSummary({ count, items, onRemoveAll, onItemSelect, onItemRemo
|
|
|
2217
2181
|
}
|
|
2218
2182
|
}),
|
|
2219
2183
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
2220
|
-
className: styles.annEditActions,
|
|
2184
|
+
className: cx(A.row, styles.annEditActions),
|
|
2221
2185
|
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
2222
2186
|
type: "button",
|
|
2223
|
-
className: styles.annEditCancel,
|
|
2187
|
+
className: cx(A.plain, styles.annEditCancel),
|
|
2224
2188
|
onClick: () => setEditing(null),
|
|
2225
2189
|
children: t("common.cancel")
|
|
2226
2190
|
}), (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -2235,7 +2199,7 @@ function AnnotationSummary({ count, items, onRemoveAll, onItemSelect, onItemRemo
|
|
|
2235
2199
|
className: styles.summaryItemMain,
|
|
2236
2200
|
children: (0, react_jsx_runtime.jsxs)("button", {
|
|
2237
2201
|
type: "button",
|
|
2238
|
-
className: styles.summaryItemButton,
|
|
2202
|
+
className: cx(A.plain, styles.summaryItemButton),
|
|
2239
2203
|
onClick: () => {
|
|
2240
2204
|
onItemSelect?.(i, item);
|
|
2241
2205
|
closePop();
|
|
@@ -2262,7 +2226,7 @@ function AnnotationSummary({ count, items, onRemoveAll, onItemSelect, onItemRemo
|
|
|
2262
2226
|
}) : null;
|
|
2263
2227
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
2264
2228
|
ref: rootRef,
|
|
2265
|
-
className: styles.summary,
|
|
2229
|
+
className: cx(A.row, styles.summary),
|
|
2266
2230
|
tabIndex: 0,
|
|
2267
2231
|
"data-selection-annotation-summary": "true",
|
|
2268
2232
|
"aria-label": t("summary.aria", { count }),
|
|
@@ -2274,23 +2238,23 @@ function AnnotationSummary({ count, items, onRemoveAll, onItemSelect, onItemRemo
|
|
|
2274
2238
|
onKeyDown,
|
|
2275
2239
|
children: [
|
|
2276
2240
|
(0, react_jsx_runtime.jsx)("div", {
|
|
2277
|
-
className: styles.summaryIcon,
|
|
2241
|
+
className: cx(A.keep, A.gridMid, styles.summaryIcon),
|
|
2278
2242
|
"aria-hidden": true,
|
|
2279
2243
|
children: String(count)
|
|
2280
2244
|
}),
|
|
2281
2245
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
2282
|
-
className: styles.summaryBody,
|
|
2246
|
+
className: cx(A.grow, styles.summaryBody),
|
|
2283
2247
|
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
2284
2248
|
className: styles.summaryTitle,
|
|
2285
2249
|
children: t("summary.title", { count })
|
|
2286
2250
|
}), (0, react_jsx_runtime.jsx)("div", {
|
|
2287
2251
|
className: styles.summaryHint,
|
|
2288
|
-
children: hint
|
|
2252
|
+
children: t("summary.hint")
|
|
2289
2253
|
})]
|
|
2290
2254
|
}),
|
|
2291
2255
|
onRemoveAll ? (0, react_jsx_runtime.jsx)("button", {
|
|
2292
2256
|
type: "button",
|
|
2293
|
-
className: styles.cardClose,
|
|
2257
|
+
className: cx(A.plain, A.gridMid, styles.cardClose),
|
|
2294
2258
|
"aria-label": t("summary.removeAll"),
|
|
2295
2259
|
onClick: onRemoveAll,
|
|
2296
2260
|
children: (0, react_jsx_runtime.jsx)(LucideIcon, {
|
|
@@ -2471,7 +2435,7 @@ function AnnotationPanel({ useInput, inputActions, sessionId }) {
|
|
|
2471
2435
|
if (!sessionId || items.length === 0) return null;
|
|
2472
2436
|
return (0, react_jsx_runtime.jsx)("div", {
|
|
2473
2437
|
ref: stackRef,
|
|
2474
|
-
className: styles.cardStack,
|
|
2438
|
+
className: cx(A.col, styles.cardStack),
|
|
2475
2439
|
children: (0, react_jsx_runtime.jsx)(AnnotationSummary, {
|
|
2476
2440
|
count: items.length,
|
|
2477
2441
|
items: items.map((item) => ({
|
|
@@ -2586,6 +2550,7 @@ async function copyText(text) {
|
|
|
2586
2550
|
/** Cap for the auto-growing comment box; beyond this it scrolls internally. */
|
|
2587
2551
|
const COMMENT_MAX_HEIGHT = 220;
|
|
2588
2552
|
function SelectionToolbar({ sessionId }) {
|
|
2553
|
+
useLocale();
|
|
2589
2554
|
const toolbarRef = (0, react.useRef)(null);
|
|
2590
2555
|
const commentWrapRef = (0, react.useRef)(null);
|
|
2591
2556
|
const commentRef = (0, react.useRef)(null);
|
|
@@ -2760,7 +2725,7 @@ function SelectionToolbar({ sessionId }) {
|
|
|
2760
2725
|
return (0, react_dom.createPortal)((0, react_jsx_runtime.jsxs)(react.Fragment, { children: [
|
|
2761
2726
|
(0, react_jsx_runtime.jsx)("div", {
|
|
2762
2727
|
ref: toolbarRef,
|
|
2763
|
-
className: styles.toolbar,
|
|
2728
|
+
className: cx(A.row, styles.toolbar),
|
|
2764
2729
|
style: toolbarStyle,
|
|
2765
2730
|
role: "toolbar",
|
|
2766
2731
|
"aria-label": t("toolbar.aria"),
|
|
@@ -2789,7 +2754,7 @@ function SelectionToolbar({ sessionId }) {
|
|
|
2789
2754
|
children: [
|
|
2790
2755
|
(0, react_jsx_runtime.jsx)("button", {
|
|
2791
2756
|
type: "button",
|
|
2792
|
-
className: styles.button,
|
|
2757
|
+
className: cx(A.plain, A.inlineRow, styles.button),
|
|
2793
2758
|
onClick: () => void handleCopy(),
|
|
2794
2759
|
children: [(0, react_jsx_runtime.jsx)(LucideIcon, {
|
|
2795
2760
|
name: "copy",
|
|
@@ -2802,7 +2767,7 @@ function SelectionToolbar({ sessionId }) {
|
|
|
2802
2767
|
}),
|
|
2803
2768
|
(0, react_jsx_runtime.jsx)("button", {
|
|
2804
2769
|
type: "button",
|
|
2805
|
-
className: styles.button,
|
|
2770
|
+
className: cx(A.plain, A.inlineRow, styles.button),
|
|
2806
2771
|
onClick: openComment,
|
|
2807
2772
|
children: [(0, react_jsx_runtime.jsx)(LucideIcon, {
|
|
2808
2773
|
name: "plus",
|
|
@@ -2814,14 +2779,14 @@ function SelectionToolbar({ sessionId }) {
|
|
|
2814
2779
|
}),
|
|
2815
2780
|
phase === "comment" ? (0, react_jsx_runtime.jsxs)("div", {
|
|
2816
2781
|
ref: commentWrapRef,
|
|
2817
|
-
className: styles.commentPop,
|
|
2782
|
+
className: cx(A.col, styles.commentPop),
|
|
2818
2783
|
style: void 0,
|
|
2819
2784
|
role: "dialog",
|
|
2820
2785
|
"aria-label": t("toolbar.commentDialogAria"),
|
|
2821
2786
|
onPointerDown: handleToolbarPointerDown,
|
|
2822
2787
|
children: [(0, react_jsx_runtime.jsx)("textarea", {
|
|
2823
2788
|
ref: commentRef,
|
|
2824
|
-
className: styles.commentInput,
|
|
2789
|
+
className: cx(A.box, styles.commentInput),
|
|
2825
2790
|
placeholder: t("common.commentPlaceholder"),
|
|
2826
2791
|
value: comment,
|
|
2827
2792
|
onChange: (e) => setComment(e.target.value),
|
|
@@ -2840,12 +2805,12 @@ function SelectionToolbar({ sessionId }) {
|
|
|
2840
2805
|
className: styles.commentActions,
|
|
2841
2806
|
children: [(0, react_jsx_runtime.jsx)("button", {
|
|
2842
2807
|
type: "button",
|
|
2843
|
-
className: styles.button,
|
|
2808
|
+
className: cx(A.plain, A.inlineRow, styles.button),
|
|
2844
2809
|
onClick: () => commitAnnotation(void 0),
|
|
2845
2810
|
children: t("toolbar.skipComment")
|
|
2846
2811
|
}), (0, react_jsx_runtime.jsx)("button", {
|
|
2847
2812
|
type: "button",
|
|
2848
|
-
className: styles.button,
|
|
2813
|
+
className: cx(A.plain, A.inlineRow, styles.button, styles.buttonAddComment),
|
|
2849
2814
|
disabled: !comment.trim(),
|
|
2850
2815
|
onClick: () => commitAnnotation(comment),
|
|
2851
2816
|
children: t("toolbar.addComment")
|
|
@@ -2867,6 +2832,7 @@ function SelectionToolbar({ sessionId }) {
|
|
|
2867
2832
|
* Display-only decoration — durable message text is never rewritten.
|
|
2868
2833
|
*/
|
|
2869
2834
|
const DECO = "dsq-ann-directive";
|
|
2835
|
+
const DECO_KEY = "dsqAnnDirective";
|
|
2870
2836
|
function decorateTextNode(node) {
|
|
2871
2837
|
const value = node.nodeValue;
|
|
2872
2838
|
if (!value || !value.includes(":dsh-annotation{")) return;
|
|
@@ -2885,9 +2851,9 @@ function decorateTextNode(node) {
|
|
|
2885
2851
|
const index = Number(m[1]);
|
|
2886
2852
|
const chip = document.createElement("button");
|
|
2887
2853
|
chip.type = "button";
|
|
2888
|
-
chip.className =
|
|
2889
|
-
chip.dataset[DECO] = String(index);
|
|
2854
|
+
chip.className = cx(styles.annDirective, A.inlineRow);
|
|
2890
2855
|
chip.setAttribute("data-dsq-deco", "true");
|
|
2856
|
+
chip.dataset[DECO_KEY] = String(index);
|
|
2891
2857
|
chip.textContent = t("chip.annotation", { index });
|
|
2892
2858
|
chip.addEventListener("click", () => {
|
|
2893
2859
|
const mark = findAnnotationMarkBefore(index, chip);
|
|
@@ -2913,6 +2879,22 @@ function decorateAnnotationDirectives(root) {
|
|
|
2913
2879
|
}
|
|
2914
2880
|
for (const node of hits) decorateTextNode(node);
|
|
2915
2881
|
}
|
|
2882
|
+
/**
|
|
2883
|
+
* Re-label chips that are already on screen after the language changed.
|
|
2884
|
+
*
|
|
2885
|
+
* Chip text is plain DOM, not React state, so a switch would otherwise leave
|
|
2886
|
+
* old-language chips next to new-language popovers. Writing `textContent` here
|
|
2887
|
+
* is safe for the observer: the new text node never contains the directive
|
|
2888
|
+
* literal, so `decorateTextNode()` returns before it can touch the DOM.
|
|
2889
|
+
*/
|
|
2890
|
+
function refreshDirectiveChips(root) {
|
|
2891
|
+
const el = root ?? (typeof document === "undefined" ? void 0 : document.body);
|
|
2892
|
+
if (!el) return;
|
|
2893
|
+
for (const chip of el.querySelectorAll(`[data-${DECO}]`)) {
|
|
2894
|
+
const index = Number(chip.dataset[DECO_KEY]);
|
|
2895
|
+
if (Number.isFinite(index)) chip.textContent = t("chip.annotation", { index });
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2916
2898
|
/** Observe chat DOM and decorate new directive text. Returns disposer. */
|
|
2917
2899
|
function watchAnnotationDirectives(root) {
|
|
2918
2900
|
const el = root ?? (typeof document === "undefined" ? void 0 : document.body);
|
|
@@ -2970,7 +2952,7 @@ function TranscriptQuoteCard({ node }) {
|
|
|
2970
2952
|
className: styles.tCardStack,
|
|
2971
2953
|
"data-dsq-deco": "true",
|
|
2972
2954
|
children: quotes.map((quote, index) => (0, react_jsx_runtime.jsx)("div", {
|
|
2973
|
-
className: styles.tCardButton,
|
|
2955
|
+
className: cx(A.plain, styles.tCardButton),
|
|
2974
2956
|
role: "button",
|
|
2975
2957
|
tabIndex: 0,
|
|
2976
2958
|
"aria-label": quote.title,
|
|
@@ -3007,7 +2989,8 @@ function contentImages(content) {
|
|
|
3007
2989
|
* Replacement for the built-in `user` Chat node view.
|
|
3008
2990
|
*
|
|
3009
2991
|
* Strips the `<response-annotations>` protocol block from the bubble **and**
|
|
3010
|
-
* renders one card per annotation right
|
|
2992
|
+
* renders one card per annotation right above it — the same order the composer
|
|
2993
|
+
* shows them in, so the sent message reads like the draft did.
|
|
3011
2994
|
*
|
|
3012
2995
|
* Why the cards live here instead of on a `select-quote` Chat node:
|
|
3013
2996
|
* `conversation.chat.node` is gated by the shipped `TURN_PROCESS_INDEPENDENT_KINDS`
|
|
@@ -3027,12 +3010,12 @@ function UserMessageDisplay({ node, renderMessageImages }) {
|
|
|
3027
3010
|
const images = contentImages(node.data?.content);
|
|
3028
3011
|
if (!text && images.length === 0 && quotes.length === 0) return null;
|
|
3029
3012
|
return (0, react_jsx_runtime.jsx)("div", {
|
|
3030
|
-
className: styles.userRow,
|
|
3013
|
+
className: cx(A.colEnd, styles.userRow),
|
|
3031
3014
|
children: (0, react_jsx_runtime.jsxs)("div", {
|
|
3032
|
-
className: styles.userStack,
|
|
3015
|
+
className: cx(A.colEnd, styles.userStack),
|
|
3033
3016
|
children: [
|
|
3034
3017
|
images.length > 0 && renderMessageImages !== void 0 ? (0, react_jsx_runtime.jsx)("div", {
|
|
3035
|
-
className: styles.userImages,
|
|
3018
|
+
className: cx(A.colEnd, styles.userImages),
|
|
3036
3019
|
"data-message-attachments": true,
|
|
3037
3020
|
children: renderMessageImages({
|
|
3038
3021
|
images,
|
|
@@ -3040,11 +3023,11 @@ function UserMessageDisplay({ node, renderMessageImages }) {
|
|
|
3040
3023
|
compact: images.length > 1
|
|
3041
3024
|
})
|
|
3042
3025
|
}, "images") : null,
|
|
3026
|
+
quotes.length > 0 ? (0, react_jsx_runtime.jsx)(TranscriptQuoteCard, { node: { data: { quotes } } }, "quotes") : null,
|
|
3043
3027
|
text ? (0, react_jsx_runtime.jsx)("div", {
|
|
3044
3028
|
className: styles.userBubble,
|
|
3045
3029
|
children: text
|
|
3046
|
-
}, "bubble") : null
|
|
3047
|
-
quotes.length > 0 ? (0, react_jsx_runtime.jsx)(TranscriptQuoteCard, { node: { data: { quotes } } }, "quotes") : null
|
|
3030
|
+
}, "bubble") : null
|
|
3048
3031
|
]
|
|
3049
3032
|
})
|
|
3050
3033
|
});
|
|
@@ -3064,10 +3047,30 @@ function UserMessageDisplay({ node, renderMessageImages }) {
|
|
|
3064
3047
|
* kind is classified as a turn-process member and `hidden` when the completed
|
|
3065
3048
|
* turn's process disclosure is folded — mounted, but invisible. `user` is on
|
|
3066
3049
|
* that allowlist, so the bubble is the only placement that is reliably visible.
|
|
3050
|
+
*
|
|
3051
|
+
* Language is the host's decision, never this plugin's: dictionaries go to
|
|
3052
|
+
* `ctx.locale`, and `t()` resolves against whatever language the user picked in
|
|
3053
|
+
* Settings. Self-detecting from `navigator` would disagree with the host on the
|
|
3054
|
+
* desktop shell, where Electron pins the document language (`--lang`) to its own
|
|
3055
|
+
* value while the host preference comes through `__DSH_LOCALE__`.
|
|
3067
3056
|
*/
|
|
3068
|
-
const inject = [
|
|
3057
|
+
const inject = [
|
|
3058
|
+
"slots",
|
|
3059
|
+
"sessions",
|
|
3060
|
+
"locale"
|
|
3061
|
+
];
|
|
3069
3062
|
function apply(ctx) {
|
|
3070
3063
|
ensureToolbarStyles();
|
|
3064
|
+
ctx.effect(() => ctx.locale.addLanguage(LANGUAGE_RU), "dsh-select-quote: ru language");
|
|
3065
|
+
ctx.effect(() => ctx.locale.register(LOCALE_NS, SHIPPED_DICTS), "dsh-select-quote: dictionaries (zh, en)");
|
|
3066
|
+
ctx.effect(() => ctx.locale.register(LOCALE_NS, LANGUAGE_RU.id, DICTIONARIES.ru), "dsh-select-quote: dictionary (ru)");
|
|
3067
|
+
bindLocale({
|
|
3068
|
+
translate: ctx.locale.bind(LOCALE_NS),
|
|
3069
|
+
getActive: () => ctx.locale.getLocale().active,
|
|
3070
|
+
getRevision: () => ctx.locale.getSnapshot().revision,
|
|
3071
|
+
subscribe: (listener) => ctx.locale.subscribe(listener)
|
|
3072
|
+
});
|
|
3073
|
+
ctx.effect(() => subscribeLocale(() => refreshDirectiveChips()), "dsh-select-quote: chip language");
|
|
3071
3074
|
ctx.effect(() => watchAnnotationDirectives(), "dsh-select-quote: annotation directives");
|
|
3072
3075
|
ctx.effect(() => watchAnnotationMarks(), "dsh-select-quote: annotation marks");
|
|
3073
3076
|
ctx.effect(() => {
|