dsh-quote-followup 0.2.6 → 0.2.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 +3 -0
- package/README.zh.md +3 -0
- package/lib/client.js +81 -20
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -4,6 +4,7 @@ English | [中文](README.zh.md)
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-quote-followup)
|
|
6
6
|
[](LICENSE)
|
|
7
|
+
[](https://dsh.market/)
|
|
7
8
|
|
|
8
9
|
A Web-only [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin for quoting selected conversation text into the composer and asking a focused follow-up.
|
|
9
10
|
|
|
@@ -58,6 +59,8 @@ npm test
|
|
|
58
59
|
|
|
59
60
|
The regression harness covers compact native quote chips, locale switching, turn-number provenance with graceful degradation, repeated quoting, spacing after an existing draft, codec serialization, the Firefox text fallback, and current-client takeover of stale singleton/button state after a hot swap.
|
|
60
61
|
|
|
62
|
+
An opt-in [assembled DSH Web regression](https://github.com/tr1v3r/dsh-quote-followup/blob/master/test/integration/README.md) runs the plugin through a pinned, built DSH checkout with Chromium. It covers existing drafts, repeated quoting, undo/redo, session isolation, prompt serialization, and Host-driven unavailable input.
|
|
63
|
+
|
|
61
64
|
## License
|
|
62
65
|
|
|
63
66
|
MIT
|
package/README.zh.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-quote-followup)
|
|
6
6
|
[](LICENSE)
|
|
7
|
+
[](https://dsh.market/)
|
|
7
8
|
|
|
8
9
|
一个仅支持 Web 的 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 插件:选中对话文本,将其追加到输入框,再进行针对性追问。
|
|
9
10
|
|
|
@@ -58,6 +59,8 @@ npm test
|
|
|
58
59
|
|
|
59
60
|
回归测试覆盖精简的原生引用 chip、中英文 locale 切换、轮次溯源与降级、连续引用、已有草稿后的间距、codec 序列化、Firefox 文本降级路径,以及热替换后新版 client 接管旧按钮/单例状态。
|
|
60
61
|
|
|
62
|
+
可选的[真实 DSH Web 集成回归](https://github.com/tr1v3r/dsh-quote-followup/blob/master/test/integration/README.md)通过固定版本、已构建的 DSH 和 Chromium 加载插件,验证已有草稿、连续引用、撤销重做、会话隔离、发送序列化及 Host 驱动的不可写输入状态。
|
|
63
|
+
|
|
61
64
|
## 许可证
|
|
62
65
|
|
|
63
66
|
MIT
|
package/lib/client.js
CHANGED
|
@@ -35,7 +35,11 @@ window.__ModuleLoader__.load({
|
|
|
35
35
|
];
|
|
36
36
|
const BUTTON_ID = "dsh-quote-followup-btn";
|
|
37
37
|
const BUTTON_VERSION_ATTR = "data-dsh-quote-followup-version";
|
|
38
|
-
const CLIENT_VERSION = "0.2.
|
|
38
|
+
const CLIENT_VERSION = "0.2.7";
|
|
39
|
+
/** Shared <style> element; versioned like the button so hot-swaps replace it. */
|
|
40
|
+
const STYLE_ID = "dsh-quote-followup-style";
|
|
41
|
+
const STYLE_VERSION_ATTR = "data-dsh-quote-followup-style-version";
|
|
42
|
+
const BUTTON_CLASS = "dsh-qf-btn";
|
|
39
43
|
/** Codec owner for native DSH reference chips. */
|
|
40
44
|
const QUOTE_SOURCE = "quote-followup";
|
|
41
45
|
const LOCALE_NS = "quote-followup";
|
|
@@ -358,7 +362,67 @@ window.__ModuleLoader__.load({
|
|
|
358
362
|
let pendingQuote = null;
|
|
359
363
|
let quoteSourceReady = false;
|
|
360
364
|
let currentT = fallbackT;
|
|
365
|
+
/**
|
|
366
|
+
* Inject the button stylesheet once per client version. Visuals follow
|
|
367
|
+
* the dsh-annotation convention: a single idempotent <style> element
|
|
368
|
+
* built on DSH Web design tokens (--dsw-alias-* / --dsw-specific-*),
|
|
369
|
+
* so the pill tracks the active theme instead of hard-coded colors.
|
|
370
|
+
* The trailing fallbacks only engage when a token is renamed upstream.
|
|
371
|
+
* Without a real document.head (test fakes) the button still works,
|
|
372
|
+
* just unstyled.
|
|
373
|
+
*/
|
|
374
|
+
const ensureStyles = () => {
|
|
375
|
+
const head = document.head;
|
|
376
|
+
if (head === null || typeof head.appendChild !== "function")
|
|
377
|
+
return;
|
|
378
|
+
const existing = document.getElementById(STYLE_ID);
|
|
379
|
+
if (existing !== null) {
|
|
380
|
+
if (existing.getAttribute(STYLE_VERSION_ATTR) === CLIENT_VERSION)
|
|
381
|
+
return;
|
|
382
|
+
existing.remove();
|
|
383
|
+
}
|
|
384
|
+
const style = document.createElement("style");
|
|
385
|
+
style.id = STYLE_ID;
|
|
386
|
+
style.setAttribute(STYLE_VERSION_ATTR, CLIENT_VERSION);
|
|
387
|
+
style.textContent = [
|
|
388
|
+
`.${BUTTON_CLASS} {`,
|
|
389
|
+
" position: fixed;",
|
|
390
|
+
" z-index: 2147483600;",
|
|
391
|
+
" display: inline-flex;",
|
|
392
|
+
" align-items: center;",
|
|
393
|
+
" gap: 6px;",
|
|
394
|
+
" height: 28px;",
|
|
395
|
+
" padding: 0 12px;",
|
|
396
|
+
" border: none;",
|
|
397
|
+
" border-radius: 14px;",
|
|
398
|
+
" background: var(--dsw-alias-button-primary-fill, rgba(56, 56, 62, .94));",
|
|
399
|
+
" color: var(--dsw-alias-label-primary-foreground, #f5f5f7);",
|
|
400
|
+
" font-family: var(--dsw-font-family, system-ui);",
|
|
401
|
+
" font-size: 12px;",
|
|
402
|
+
" line-height: 18px;",
|
|
403
|
+
" white-space: nowrap;",
|
|
404
|
+
" cursor: pointer;",
|
|
405
|
+
" box-shadow: var(--dsw-shadow-lv3, 0 2px 10px rgba(0, 0, 0, .28));",
|
|
406
|
+
" user-select: none;",
|
|
407
|
+
" animation: dsh-qf-pop .12s var(--ds-ease-in-out, ease);",
|
|
408
|
+
"}",
|
|
409
|
+
`.${BUTTON_CLASS}:hover:not(:disabled) {`,
|
|
410
|
+
" background: var(--dsw-alias-button-primary-hover, rgba(72, 72, 80, .96));",
|
|
411
|
+
"}",
|
|
412
|
+
`.${BUTTON_CLASS}:active { filter: brightness(.92); }`,
|
|
413
|
+
`.${BUTTON_CLASS}:focus-visible {`,
|
|
414
|
+
" outline: 2px solid var(--dsw-alias-text-accent, #4c9aff);",
|
|
415
|
+
" outline-offset: 1px;",
|
|
416
|
+
"}",
|
|
417
|
+
"@keyframes dsh-qf-pop {",
|
|
418
|
+
" from { opacity: 0; transform: translateY(3px); }",
|
|
419
|
+
" to { opacity: 1; transform: none; }",
|
|
420
|
+
"}"
|
|
421
|
+
].join("\n");
|
|
422
|
+
head.appendChild(style);
|
|
423
|
+
};
|
|
361
424
|
const ensureButton = () => {
|
|
425
|
+
ensureStyles();
|
|
362
426
|
const existing = document.getElementById(BUTTON_ID);
|
|
363
427
|
if (existing?.getAttribute(BUTTON_VERSION_ATTR) === CLIENT_VERSION)
|
|
364
428
|
return existing;
|
|
@@ -367,23 +431,10 @@ window.__ModuleLoader__.load({
|
|
|
367
431
|
button.id = BUTTON_ID;
|
|
368
432
|
button.setAttribute(BUTTON_VERSION_ATTR, CLIENT_VERSION);
|
|
369
433
|
button.type = "button";
|
|
434
|
+
button.className = BUTTON_CLASS;
|
|
370
435
|
button.textContent = currentT("button.label");
|
|
371
436
|
button.title = currentT("button.title");
|
|
372
|
-
|
|
373
|
-
position: "fixed",
|
|
374
|
-
zIndex: "2147483600",
|
|
375
|
-
padding: "3px 10px",
|
|
376
|
-
fontSize: "12px",
|
|
377
|
-
lineHeight: "20px",
|
|
378
|
-
borderRadius: "6px",
|
|
379
|
-
border: "1px solid rgba(140,140,150,.45)",
|
|
380
|
-
background: "rgba(56,56,62,.94)",
|
|
381
|
-
color: "#f5f5f7",
|
|
382
|
-
cursor: "pointer",
|
|
383
|
-
boxShadow: "0 2px 10px rgba(0,0,0,.28)",
|
|
384
|
-
userSelect: "none",
|
|
385
|
-
display: "none"
|
|
386
|
-
});
|
|
437
|
+
button.style.display = "none";
|
|
387
438
|
button.addEventListener("mousedown", (event) => {
|
|
388
439
|
event.preventDefault();
|
|
389
440
|
event.stopPropagation();
|
|
@@ -397,14 +448,21 @@ window.__ModuleLoader__.load({
|
|
|
397
448
|
return button;
|
|
398
449
|
};
|
|
399
450
|
const placeButton = (button, rect) => {
|
|
400
|
-
|
|
401
|
-
|
|
451
|
+
// Reveal before measuring: offsetWidth of a display:none element is 0,
|
|
452
|
+
// and the fixed-width guard below keeps fake DOMs (no layout) working.
|
|
453
|
+
button.style.display = "inline-flex";
|
|
454
|
+
const width = Number.isFinite(button.offsetWidth) && button.offsetWidth > 0
|
|
455
|
+
? button.offsetWidth
|
|
456
|
+
: 64;
|
|
457
|
+
// Center on the selection; measurement guards keep fake/hidden
|
|
458
|
+
// elements (offsetWidth 0 / undefined) on the old fixed-width path.
|
|
459
|
+
let x = rect.left + rect.width / 2 - width / 2;
|
|
460
|
+
let y = rect.top - 34; // pill height (28px) + 6px gap
|
|
402
461
|
if (y < 4)
|
|
403
462
|
y = rect.bottom + 6;
|
|
404
|
-
x = Math.max(4, Math.min(x, window.innerWidth -
|
|
463
|
+
x = Math.max(4, Math.min(x, window.innerWidth - width - 4));
|
|
405
464
|
button.style.left = `${Math.round(x)}px`;
|
|
406
465
|
button.style.top = `${Math.round(y)}px`;
|
|
407
|
-
button.style.display = "block";
|
|
408
466
|
};
|
|
409
467
|
const hideButton = () => {
|
|
410
468
|
const button = document.getElementById(BUTTON_ID);
|
|
@@ -528,6 +586,9 @@ window.__ModuleLoader__.load({
|
|
|
528
586
|
const button = document.getElementById(BUTTON_ID);
|
|
529
587
|
if (button?.getAttribute(BUTTON_VERSION_ATTR) === CLIENT_VERSION)
|
|
530
588
|
button.remove();
|
|
589
|
+
const style = document.getElementById(STYLE_ID);
|
|
590
|
+
if (style?.getAttribute(STYLE_VERSION_ATTR) === CLIENT_VERSION)
|
|
591
|
+
style.remove();
|
|
531
592
|
if (globalThis[STATE] === state)
|
|
532
593
|
delete globalThis[STATE];
|
|
533
594
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-quote-followup",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Quote selected Web conversation text into the DeepSeek Harness composer for targeted follow-up turns.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"follow-up"
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
|
-
"test": "node --check lib/index.js && node --check lib/client.js && node test/web-repeat-harness.mjs"
|
|
14
|
+
"test": "node --check lib/index.js && node --check lib/client.js && node test/web-repeat-harness.mjs",
|
|
15
|
+
"test:assembled-web": "node test/run-assembled-web.mjs"
|
|
15
16
|
},
|
|
16
17
|
"type": "module",
|
|
17
18
|
"main": "lib/index.js",
|