dsh-client-ui-quote 0.11.1 → 0.11.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 +5 -5
- package/lib/client.js +31 -7
- package/package.json +1 -1
- package/test/capsule.mjs +20 -0
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ DSH 会把包记进 profile 的依赖并加入组合包列表,装完在侧边
|
|
|
55
55
|
|
|
56
56
|
### 方式三:npm 包(已上架)
|
|
57
57
|
|
|
58
|
-
已发布到 npm:`dsh-client-ui-quote
|
|
58
|
+
已发布到 npm:`dsh-client-ui-quote`(0.10.0 起上架;每个 `v*` 标签由下面的 workflow 自动发布,本仓库当前 0.11.2)。发布由 GitHub Actions 完成(`.github/workflows/publish.yml`):推 `v*` 标签或手动 Run `publish` workflow,`prepublishOnly` 的 293 项断言会先在 runner 上跑完,任何一项失败都不会发出去。`npm pack --dry-run` 可核对内容(0.10.0 = 14 个文件 / 97.6 kB)。
|
|
59
59
|
|
|
60
60
|
装:
|
|
61
61
|
|
|
@@ -227,7 +227,7 @@ transcript 侧的 `.dshq-cap` 胶囊:一颗胶囊同时装引用与评论,`
|
|
|
227
227
|
1. **语义而不是机制**:引用与评论是同一颗节点(对齐 Kimi 的 `quote` 节点 `{text, comment}`),一颗芯片 → 一个块 → 一颗胶囊 → 一张卡片 → 一次复制;机制(reference chip、DOM 增强)谁都能抄,这条链子不好抄。
|
|
228
228
|
2. **引用是带坐标的活对象**:`跳回原文` 在自己的会话面板里找行、按宿主的方式对齐到 24px,行被虚拟列表藏起来或没挂载都能找回来;坐标不随引用文本发给模型(不污染 prompt)。
|
|
229
229
|
3. **引用有下落**:清单页(搜索 / 跳转 / 导出 Markdown)+ 侧边线程回流(结论写回主会话草稿 / 导出线程),这两件事在 4471 条里没有对应物。
|
|
230
|
-
4. **工程化**:
|
|
230
|
+
4. **工程化**:293 项离线断言(含真跑 `sessions.retain` / `setDraft` / 下载链路)、每个宿主契约都在 README 里写明出处、每个版本都有可回读的 release。同类里多数是一两百行、没有测试的插件。
|
|
231
231
|
|
|
232
232
|
反过来也要说清楚:**本插件不做的**——不做翻译/解释类选区动作(81 条同类在做)、不做多选合并引用(7 条有)、不做选区折叠/行内 diff 这类渲染花样。它只把「引用」这一件事做穿。
|
|
233
233
|
|
|
@@ -260,7 +260,7 @@ locale/zh.json 插件页卡片文案(中文)
|
|
|
260
260
|
locale/en.json 插件页卡片文案(英文)
|
|
261
261
|
test/check.mjs 离线自检(清单 + 组件契约)
|
|
262
262
|
test/dom.mjs 给离线自检用的最小 DOM / MutationObserver 垫片
|
|
263
|
-
test/capsule.mjs 离线自检(发送后胶囊 + 悬停卡片 + 跳回原文 + 键位 + 改这条引用 + 代码引用 + 文件路径 + 逐条回应的回复徽章,
|
|
263
|
+
test/capsule.mjs 离线自检(发送后胶囊 + 悬停卡片 + 跳回原文 + 键位 + 改这条引用 + 代码引用 + 文件路径 + 逐条回应的回复徽章,197 项断言)
|
|
264
264
|
test/panel.mjs 离线自检(引用清单:解析 / 搜索 / Markdown / 读取 / 导出 / 页面注册 + 侧边线程回流,96 项断言)
|
|
265
265
|
test/capsule.html 真浏览器里跑同一套胶囊断言(可选)
|
|
266
266
|
```
|
|
@@ -308,7 +308,7 @@ React 重渲染不会出现第二颗、引用变了就地重建、消息被删
|
|
|
308
308
|
卡片给出 `跳回原文` 与 `改这条引用` 两个动作、点后者把栏切到评论模式并**预填旧评论与旧正文**、
|
|
309
309
|
按确认后 `insertReference` 恰好一次(新芯片的 `ref` 带改后的评论)、旧引用被记为「已修订」并落进 `localStorage`、
|
|
310
310
|
旧胶囊挂上 `已修订` 徽标、它的卡片显示改后的评论(连正文改过的连改后原文一起显示)、空修订不算修订、
|
|
311
|
-
修订不改写引用原文本身、清空引用正文时确认按钮禁用、改后的正文随新芯片发出(共
|
|
311
|
+
修订不改写引用原文本身、清空引用正文时确认按钮禁用、改后的正文随新芯片发出(共 197 项,含文件路径与逐条回应的线上形态、回复徽章的建立/重建/卸载与悬停卡片复用,以及评论框两个输入区的自适应高度——引用正文那格曾经在被挂进栏之前就量高,读到 0 后被写成 `height:0`,首行因此被裁掉)。
|
|
312
312
|
`test/capsule.html` 是同一套断言的真浏览器版,用 `file://` 打开即可(页面会把结果写进标题与 `#probe-out`)。
|
|
313
313
|
|
|
314
314
|
`test/panel.mjs` 单独跑引用清单页与侧边线程回流(96 项):只认**持久**的 `user/message`(`transient`、助手消息、`data: null` 都跳过)、
|
|
@@ -327,7 +327,7 @@ Markdown 的标题/编号/块引用/评论行/时间戳与空列表兜底、超
|
|
|
327
327
|
`bringThreadBack` 把结论写成**草稿**(原有草稿保留、空行隔开)、外壳没有 `setDraft` 时如实失败、没有线程时不乱写;
|
|
328
328
|
`exportThread` 的文件名与整条线程内容、以及 MIME 是 Markdown。
|
|
329
329
|
|
|
330
|
-
合计 **
|
|
330
|
+
合计 **293 项**(check.mjs 的契约断言 + capsule 197 + panel 96),`npm test` 全绿。
|
|
331
331
|
|
|
332
332
|
## 已知限制
|
|
333
333
|
|
package/lib/client.js
CHANGED
|
@@ -2160,6 +2160,29 @@ code's own spacing — collapsed keeps one clipped line, expanded scrolls. */
|
|
|
2160
2160
|
* its wording, not just its comment. Both halves map onto the wire form's
|
|
2161
2161
|
* two marks, top to bottom.
|
|
2162
2162
|
*/
|
|
2163
|
+
/**
|
|
2164
|
+
* Grow one field to its content. A textarea that is not laid out yet
|
|
2165
|
+
* reports a scrollHeight of 0 — writing that back as `0px` collapsed the
|
|
2166
|
+
* quote half of the box and clipped its first line, so an unmeasurable
|
|
2167
|
+
* field is left on `auto` until the bar is on screen.
|
|
2168
|
+
*/
|
|
2169
|
+
function growField(node) {
|
|
2170
|
+
node.style.height = "auto";
|
|
2171
|
+
const next = node.scrollHeight;
|
|
2172
|
+
if (!Number.isFinite(next) || next <= 0) return;
|
|
2173
|
+
node.style.height = Math.min(next, COMMENT_MAX_HEIGHT) + "px";
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
/** Every field the bar is currently showing, re-measured. */
|
|
2177
|
+
function growInputs() {
|
|
2178
|
+
const bar = state.bar;
|
|
2179
|
+
if (bar === null || bar === undefined || typeof bar.querySelectorAll !== "function") return;
|
|
2180
|
+
for (const field of Array.from(bar.querySelectorAll(".dshq-input"))) {
|
|
2181
|
+
if (String(field.tagName).toLowerCase() !== "textarea") continue;
|
|
2182
|
+
growField(field);
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2163
2186
|
function renderComment(bar) {
|
|
2164
2187
|
const doc = bar.ownerDocument;
|
|
2165
2188
|
bar.setAttribute("role", "dialog");
|
|
@@ -2186,10 +2209,6 @@ code's own spacing — collapsed keeps one clipped line, expanded scrolls. */
|
|
|
2186
2209
|
"data-action": "confirm"
|
|
2187
2210
|
}, [el(doc, "span", { text: translate("addToChat") }), el(doc, "span", { class: "dshq-enter", text: "⏎" })]);
|
|
2188
2211
|
|
|
2189
|
-
const grow = (node) => {
|
|
2190
|
-
node.style.height = "auto";
|
|
2191
|
-
node.style.height = Math.min(node.scrollHeight, COMMENT_MAX_HEIGHT) + "px";
|
|
2192
|
-
};
|
|
2193
2212
|
// The quote half is mandatory now (an empty quote is nothing to send);
|
|
2194
2213
|
// the comment keeps Kimi's rule of being required in this box.
|
|
2195
2214
|
const syncConfirm = () => {
|
|
@@ -2197,12 +2216,12 @@ code's own spacing — collapsed keeps one clipped line, expanded scrolls. */
|
|
|
2197
2216
|
};
|
|
2198
2217
|
quoteInput.addEventListener("input", () => {
|
|
2199
2218
|
state.quote = quoteInput.value;
|
|
2200
|
-
|
|
2219
|
+
growField(quoteInput);
|
|
2201
2220
|
syncConfirm();
|
|
2202
2221
|
});
|
|
2203
2222
|
input.addEventListener("input", () => {
|
|
2204
2223
|
state.comment = input.value;
|
|
2205
|
-
|
|
2224
|
+
growField(input);
|
|
2206
2225
|
syncConfirm();
|
|
2207
2226
|
});
|
|
2208
2227
|
const onKey = (event) => {
|
|
@@ -2234,7 +2253,8 @@ code's own spacing — collapsed keeps one clipped line, expanded scrolls. */
|
|
|
2234
2253
|
el(doc, "span", { text: translate("replyToggle") })
|
|
2235
2254
|
]);
|
|
2236
2255
|
|
|
2237
|
-
grow(
|
|
2256
|
+
// No grow here: the fields are measured in `position()`, once the bar
|
|
2257
|
+
// takes part in layout. Reading them while detached returned 0.
|
|
2238
2258
|
syncConfirm();
|
|
2239
2259
|
|
|
2240
2260
|
bar.replaceChildren(
|
|
@@ -2258,6 +2278,10 @@ code's own spacing — collapsed keeps one clipped line, expanded scrolls. */
|
|
|
2258
2278
|
|
|
2259
2279
|
bar.style.visibility = "hidden";
|
|
2260
2280
|
bar.dataset.visible = "true";
|
|
2281
|
+
// The bar is in layout now (merely invisible), which is the first
|
|
2282
|
+
// moment its fields can be measured — and it has to happen before the
|
|
2283
|
+
// height below is read, or the box would be placed at the wrong place.
|
|
2284
|
+
growInputs();
|
|
2261
2285
|
|
|
2262
2286
|
const viewport = window.visualViewport;
|
|
2263
2287
|
const width = viewport === undefined || viewport === null ? window.innerWidth : viewport.width;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-client-ui-quote",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "Select text in the DSH transcript, a file preview or the terminal to quote it into the composer or into a side thread's composer, Kimi-Code style (comment / add to conversation / side chat), with Alt+Q/C/B, a jump back to the quoted source, a searchable quote list page that exports as Markdown, one-click back-flow of a side thread's conclusion, and revisions of a sent quote that mark the old capsule as superseded. The quoted text itself is editable in the comment box (trim a selection, correct a revision's wording), a selection from the file preview or the terminal travels to the model as a fenced code block carrying its file path and line range, and a per-quote reply toggle has the model answer quote by quote, each 「引用N」 label in the reply becoming a badge linked back to the quote.",
|
|
5
5
|
"icon": "icon.svg",
|
|
6
6
|
"license": "MIT",
|
package/test/capsule.mjs
CHANGED
|
@@ -914,6 +914,21 @@ try {
|
|
|
914
914
|
// this block is about what a mounted enhancer does with a revision.
|
|
915
915
|
capsuleEffect.fn();
|
|
916
916
|
|
|
917
|
+
// A textarea measures itself from its own text — but only while it is in the
|
|
918
|
+
// document, the way the real one reads 0 before the bar is laid out. That is
|
|
919
|
+
// what made the auto-grow collapse the quote field.
|
|
920
|
+
const makeTextarea = dom.document.createElement.bind(dom.document);
|
|
921
|
+
dom.document.createElement = (tag) => {
|
|
922
|
+
const node = makeTextarea(tag);
|
|
923
|
+
if (String(tag).toLowerCase() === "textarea") {
|
|
924
|
+
Object.defineProperty(node, "scrollHeight", {
|
|
925
|
+
configurable: true,
|
|
926
|
+
get: () => (node.isConnected ? 12 + 12 * String(node.value ?? "").split("\n").length : 0)
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
return node;
|
|
930
|
+
};
|
|
931
|
+
|
|
917
932
|
// A transcript of its own, with a capsule that has a comment.
|
|
918
933
|
const host2 = dom.document.createElement("div");
|
|
919
934
|
host2.setAttribute("data-conversation-content", "");
|
|
@@ -948,11 +963,16 @@ try {
|
|
|
948
963
|
const input = bar?.querySelector(".dshq-comment-input");
|
|
949
964
|
check("the box is prefilled with the old quote for editing", quoteBox?.value === original, JSON.stringify(quoteBox?.value));
|
|
950
965
|
check("the box is prefilled with the old comment", input?.value === oldComment, JSON.stringify(input?.value));
|
|
966
|
+
// Regression: the quote half used to be measured before it was attached, so
|
|
967
|
+
// it was written `height: 0px` and its first line showed up clipped.
|
|
968
|
+
check("the quote field is grown to its text, not collapsed", quoteBox?.style?.height === "24px", JSON.stringify(quoteBox?.style?.height));
|
|
969
|
+
check("the comment field is grown too", input?.style?.height === "24px", JSON.stringify(input?.style?.height));
|
|
951
970
|
|
|
952
971
|
// Correcting it writes a NEW quote (nothing is edited in place) and the old
|
|
953
972
|
// capsule learns it was superseded.
|
|
954
973
|
input.value = newComment;
|
|
955
974
|
input.dispatch("input", {});
|
|
975
|
+
check("typing in a field re-measures it", input?.style?.height === "24px", JSON.stringify(input?.style?.height));
|
|
956
976
|
// The bar handles clicks by delegation, so the click is dispatched on the bar
|
|
957
977
|
// with the button as its target — the shim has no bubbling of its own.
|
|
958
978
|
const confirm = bar.querySelector('[data-action="confirm"]');
|