dsh-paste-code-block 0.1.2 → 0.2.5
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 +17 -11
- package/README.zh-CN.md +17 -11
- package/docs/design.md +144 -3
- package/docs/images/hero.png +0 -0
- package/docs/images/hero.svg +1 -1
- package/docs/images/languages.png +0 -0
- package/docs/images/languages.svg +1 -1
- package/docs/images/render.cjs +20 -0
- package/docs/images/workflow.png +0 -0
- package/package.json +20 -9
- package/src/client.js +677 -18
- package/src/i18n.js +42 -13
- package/src/parse.js +64 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Cherry Studio‑style text/code block pasting for DeepSeek Harness Web**
|
|
6
6
|
|
|
7
|
-
Paste long code or text into the DSH Web composer → it lands as a **bordered, collapsible, language‑tagged card** instead of a wall of text. Names follow the DSH interface language (中文 / English). On send, the original content is restored as a fenced code block.
|
|
7
|
+
Paste long code or text into the DSH Web composer → it lands as a **bordered, collapsible, language‑tagged card** instead of a wall of text. Names follow the DSH interface language (中文 / English). On send, the original content is restored as a fenced code block — and in the conversation it **stays folded** into click‑to‑expand cards, so your bubbles never become text walls again.
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/dsh-paste-code-block)
|
|
10
10
|
[](./LICENSE)
|
|
@@ -25,9 +25,11 @@ Copying a 200‑line stack trace, an API response, or a long log into a chat inp
|
|
|
25
25
|
- 🧩 **Auto block detection** — pasted text with ``` fences, newlines, heavy indentation, or length ≥ 96 becomes a card; ordinary short prose pastes through untouched.
|
|
26
26
|
- 🏷️ **Language tags** — auto-detects Python / JS / JSON / YAML / SQL / bash / HTML·XML / C++ / Go / Ruby (incl. shebangs); plain-text blocks are tagged `text`.
|
|
27
27
|
- ✏️ **In-card editing** — edit the content right in the detail card; the edited text is exactly what gets sent. Plus copy, collapse/expand, and hide.
|
|
28
|
-
- 🈯 **Localized names** — chips are named in the current DSH language:
|
|
28
|
+
- 🈯 **Localized names** — chips are named in the current DSH language: `代码块 1` / `Code #1`, `文本块 N` / `Text #N`. A leading glyph and tint tells the two apart at a glance — `</>` blue for code, `Aa` gray for text. Switching **Settings → Language** live-retitles every chip.
|
|
29
29
|
- × **One-click remove** — every chip carries its own `×` delete button (the detail card has one too); a removed number is recycled immediately.
|
|
30
30
|
- 📤 **Fenced restore on send** — each card expands back into a ```` ```lang … ``` ```` block when you send; a failed send automatically refills the input.
|
|
31
|
+
- 📦 **Folded after send** — sent blocks keep folding in the transcript: every fenced block inside your own messages renders as a collapsed card (`python · 128 行` / `python · 128 lines`). Click the header to expand, again to collapse; the card's copy button grabs the original fenced text verbatim. Bare ```` ``` ```` fences fold as code (only an explicit ```` ```text ```` folds as a text block).
|
|
32
|
+
- 💾 **Mid-draft safe** — DSH persists a composer draft as plain text, so pasted blocks are automatically re-attached when the composer is rebuilt (workspace switch on the new-session page, page reload); hand-deleted blocks are never "resurrected".
|
|
31
33
|
- 🔢 **Smart numbering** — code and text count apart; the smallest free number is always reused.
|
|
32
34
|
- 📱 **Every surface** — pure client plugin; works on desktop & mobile web, follows light/dark themes.
|
|
33
35
|
|
|
@@ -43,7 +45,7 @@ Chip labels, tooltips, line counts, and the send-time error notice all render th
|
|
|
43
45
|
|
|
44
46
|
- **New pastes** are named in whatever language is active at paste time.
|
|
45
47
|
- **Existing chips** are re-titled live when you flip 设置 → 语言 / Settings → Language — no reload needed.
|
|
46
|
-
- Clicks and deletes match chips **across languages**: a
|
|
48
|
+
- Clicks and deletes match chips **across languages**: a `代码块 2` chip still resolves to its block after switching to English (where it now reads `Code #2`), and vice versa.
|
|
47
49
|
|
|
48
50
|
## Install
|
|
49
51
|
|
|
@@ -65,9 +67,10 @@ dsh plugin --profile web add file:/path/to/dsh-paste-code-block # from a local
|
|
|
65
67
|
## Usage
|
|
66
68
|
|
|
67
69
|
1. Copy any code or multi-line text from anywhere.
|
|
68
|
-
2. Paste into the DSH input → it collapses into a chip such as
|
|
70
|
+
2. Paste into the DSH input → it collapses into a chip such as `代码块 1` / `Code #1`.
|
|
69
71
|
3. Click the chip to open the card: read, edit inline, copy, collapse. Click the chip's (or the card's) `×` to delete that block.
|
|
70
72
|
4. Send — the full original text goes out as a proper fenced code block.
|
|
73
|
+
5. In the conversation it stays folded: each block sits in the bubble as a collapsed card — click the header to expand and read, click again to collapse.
|
|
71
74
|
|
|
72
75
|
Detection threshold: **contains a newline / ≥ 2 lines / length ≥ 96 / indented & brace-dense / fenced** — any one turns the paste into a block.
|
|
73
76
|
|
|
@@ -78,21 +81,24 @@ Detection threshold: **contains a newline / ≥ 2 lines / length ≥ 96 / indent
|
|
|
78
81
|
- Removing a chip any other way (e.g. `Backspace`) also frees its number — plugin state is reconciled against the editor continuously.
|
|
79
82
|
- Deleting one block never touches the others, and removal is an ordinary editor edit — `Ctrl+Z` brings it back.
|
|
80
83
|
- Creating a block leaves **no stray space**: DSH appends a separating space after a freshly inserted chip, and the plugin removes exactly that one character, so text typed after a block does not start with a space and pasting several blocks never piles spaces into the message. Spaces you typed yourself are never touched by pasting or deleting.
|
|
84
|
+
- Post-send folding is **display-only**: it restyles your own message bubbles (and steering rows / the send echo) in the DOM. Stored messages are never modified, the bubble's own copy / edit / delete bar keeps working on the original text, and assistant messages are left alone.
|
|
85
|
+
- Draft-rebuild recovery keeps a small localStorage mirror (block payloads plus a draft fingerprint; written while you edit, cleared when you send, expired after two weeks). If the browser blocks local storage (private mode), blocks still survive a same-tab workspace switch from memory — just not a full page reload.
|
|
81
86
|
|
|
82
87
|
## Project layout
|
|
83
88
|
|
|
84
89
|
```
|
|
85
90
|
dsh-paste-code-block/
|
|
86
91
|
├── src/
|
|
87
|
-
│ ├── client.js # Web (browser) half — paste capture, chips, detail card, × delete
|
|
92
|
+
│ ├── client.js # Web (browser) half — paste capture, chips, detail card, × delete, sent-message fold, draft-rebuild recovery
|
|
88
93
|
│ ├── index.js # Host (node) half — intentionally empty (pure UI plugin)
|
|
89
|
-
│ ├── parse.js # Pure block-detection logic (canonical, unit-tested)
|
|
90
|
-
│ └── i18n.js # Locale dictionaries
|
|
94
|
+
│ ├── parse.js # Pure block-detection + fence-splitting logic (canonical, unit-tested)
|
|
95
|
+
│ └── i18n.js # Locale dictionaries, label & fold-title builders (canonical, unit-tested)
|
|
91
96
|
├── tests/
|
|
92
|
-
│ ├── parse.test.js
|
|
93
|
-
│
|
|
97
|
+
│ ├── parse.test.js # node:test unit tests for src/parse.js
|
|
98
|
+
│ ├── i18n.test.js # node:test unit tests for src/i18n.js
|
|
99
|
+
│ └── fold-smoke.test.js # end-to-end fold scan against a DOM shim (runs the real client bundle)
|
|
94
100
|
├── docs/
|
|
95
|
-
│ ├── design.md # Design rationale (numbering, anchors, codec, i18n, chip
|
|
101
|
+
│ ├── design.md # Design rationale (numbering, anchors, codec, i18n, chip ×, sent-message fold, draft-rebuild recovery)
|
|
96
102
|
│ └── images/ # README mockups (PNG + editable SVG sources)
|
|
97
103
|
├── cordis.patch.yml # DSH bundle patch declaring the host plugin
|
|
98
104
|
├── package.json # Package + DSH plugin manifest
|
|
@@ -104,7 +110,7 @@ dsh-paste-code-block/
|
|
|
104
110
|
|
|
105
111
|
```sh
|
|
106
112
|
npm run check # syntax-check the JS halves
|
|
107
|
-
npm test # run unit tests (node:test,
|
|
113
|
+
npm test # run unit tests (node:test, 61 tests)
|
|
108
114
|
```
|
|
109
115
|
|
|
110
116
|
## License
|
package/README.zh-CN.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Cherry Studio 式的文本块/代码块粘贴效果(DeepSeek Harness Web)**
|
|
6
6
|
|
|
7
|
-
把复制的**长代码 / 长文本**粘到 DSH Web 输入框时,不再糊成一长串普通文字,而是收成一张**带边框、可折叠、带语言标签的卡片**;块名称自动跟随 DSH 界面语言(中文 / English);发送时自动还原成真正的 ```` ```lang … ``` ````
|
|
7
|
+
把复制的**长代码 / 长文本**粘到 DSH Web 输入框时,不再糊成一长串普通文字,而是收成一张**带边框、可折叠、带语言标签的卡片**;块名称自动跟随 DSH 界面语言(中文 / English);发送时自动还原成真正的 ```` ```lang … ``` ```` 围栏代码块——**发送后在对话里也保持折叠**,点击即可展开阅读。
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/dsh-paste-code-block)
|
|
10
10
|
[](./LICENSE)
|
|
@@ -25,9 +25,11 @@
|
|
|
25
25
|
- 🧩 **智能识别**:粘贴内容带 ``` 围栏、含换行、明显缩进、或长度 ≥ 96 → 收成卡片;普通短文本照常粘贴,绝不打扰。
|
|
26
26
|
- 🏷️ **语言标签**:自动识别 Python / JS / JSON / YAML / SQL / bash / HTML·XML / C++ / Go / Ruby 等(可识别 shebang);纯文本块标为 `text`。
|
|
27
27
|
- ✏️ **卡片内编辑**:详情卡里直接修改内容,发送的正是编辑后的文本;另有复制、折叠/展开、收起。
|
|
28
|
-
- 🈯 **名称本地化**:芯片按当前 DSH
|
|
28
|
+
- 🈯 **名称本地化**:芯片按当前 DSH 界面语言命名——`代码块 1` / `Code #1`、`文本块 N` / `Text #N`;前缀图标与配色一眼区分两类块(代码 `</>` 蓝色、文本 `Aa` 灰色);在「设置 → 语言」切换后所有芯片即时改名。
|
|
29
29
|
- × **一键删除**:每个芯片自带 `×` 删除按钮(详情卡上也有);删掉的块编号立刻回收复用。
|
|
30
30
|
- 📤 **发送还原**:发送时每个卡片展开还原为 ```` ```lang … ``` ```` 围栏代码块;发送失败自动回填输入框。
|
|
31
|
+
- 📦 **发送后仍折叠**:发出去的消息在对话里继续折叠成卡片(如 `python · 128 行`),气泡不再变成文字墙;点标题展开、再点收起,卡片上的复制按钮原样复制带围栏的完整代码块。裸 ```` ``` ```` 围栏按代码块折叠(只有显式 ```` ```text ```` 才折叠为文本块)。
|
|
32
|
+
- 💾 **草稿中途不丢**:DSH 只把输入框草稿按纯文本持久化,输入框被重建时(新会话页切换工作区、刷新页面)已粘贴的块会自动重新挂回草稿;手动删掉的块绝不会被“复活”。
|
|
31
33
|
- 🔢 **智能编号**:代码与文本分开计数,永远分配最小可用号。
|
|
32
34
|
- 📱 **全端适配**:纯客户端插件,PC 与手机 Web 通用,实时跟随深浅主题。
|
|
33
35
|
|
|
@@ -43,7 +45,7 @@
|
|
|
43
45
|
|
|
44
46
|
- **新粘贴**的块按粘贴时的界面语言命名。
|
|
45
47
|
- **已插入的芯片**在 设置 → 语言 切换后原地改名,无需刷新页面。
|
|
46
|
-
- 点击与删除**跨语言匹配**:切到英文后,原来叫
|
|
48
|
+
- 点击与删除**跨语言匹配**:切到英文后,原来叫 `代码块 2` 的芯片(此时显示 `Code #2`)依然指向同一个块,反之亦然。
|
|
47
49
|
|
|
48
50
|
## 安装
|
|
49
51
|
|
|
@@ -65,9 +67,10 @@ dsh plugin --profile web add file:/path/to/dsh-paste-code-block # 从本地目
|
|
|
65
67
|
## 使用
|
|
66
68
|
|
|
67
69
|
1. 在任意地方复制一段代码 / 一段多行文本。
|
|
68
|
-
2. 在 DSH 输入框粘贴 → 折叠成
|
|
70
|
+
2. 在 DSH 输入框粘贴 → 折叠成 `代码块 1` 这样的芯片。
|
|
69
71
|
3. 点芯片打开详情卡:阅读、就地编辑、复制、折叠;点芯片(或卡片)上的 `×` 删除该块。
|
|
70
72
|
4. 发送——完整原文以围栏代码块形式发出。
|
|
73
|
+
5. 发送后对话里仍保持折叠:每个块在气泡中显示为一张卡片,点标题展开阅读、再点收起。
|
|
71
74
|
|
|
72
75
|
识别阈值:**含换行 / 行数 ≥ 2 / 长度 ≥ 96 / 缩进且符号密集 / 带围栏**,任一命中即成块。
|
|
73
76
|
|
|
@@ -78,21 +81,24 @@ dsh plugin --profile web add file:/path/to/dsh-paste-code-block # 从本地目
|
|
|
78
81
|
- 用其他方式删除芯片(如 `Backspace`)同样会释放编号——插件状态与编辑器持续对账。
|
|
79
82
|
- 删除单个块绝不影响其它块;删除是一次普通的编辑器编辑,可用 `Ctrl+Z` 撤销找回。
|
|
80
83
|
- 创建块**不会残留空格**:DSH 会在新插入的芯片后自动补一个分隔空格,插件只把这一个字符删掉——因此块后面接着输入的文字不会顶着一个前导空格,连续粘贴多个块也不会在正文里堆出多余空格;你自己敲的空格在粘贴/删除时都不会被动到。
|
|
84
|
+
- 发送后折叠**只是显示层**的改动:它只重排你自己消息(含插话与发送回显)气泡的 DOM,**不改任何存储的消息数据**;气泡自带的复制/编辑/删除操作照常作用于原文;助手消息完全不受影响。
|
|
85
|
+
- 草稿恢复依赖一个小型 localStorage 镜像(只存块内容与草稿指纹;编辑时写入、发送即清、两周过期)。浏览器禁用本地存储(如隐身模式)时,同一页面内的工作区切换仍可从内存恢复,但整页刷新不能。
|
|
81
86
|
|
|
82
87
|
## 目录结构
|
|
83
88
|
|
|
84
89
|
```
|
|
85
90
|
dsh-paste-code-block/
|
|
86
91
|
├── src/
|
|
87
|
-
│ ├── client.js # Web(浏览器侧)—— 粘贴拦截、芯片、详情卡片、×
|
|
92
|
+
│ ├── client.js # Web(浏览器侧)—— 粘贴拦截、芯片、详情卡片、× 删除、发送后折叠、草稿恢复
|
|
88
93
|
│ ├── index.js # Host(Node 侧)—— 有意为空(纯 UI 插件)
|
|
89
|
-
│ ├── parse.js #
|
|
90
|
-
│ └── i18n.js #
|
|
94
|
+
│ ├── parse.js # 纯块识别 + 围栏切分逻辑(规范版,含单元测试)
|
|
95
|
+
│ └── i18n.js # 词典、块名称与折叠卡标题(规范版,含单元测试)
|
|
91
96
|
├── tests/
|
|
92
|
-
│ ├── parse.test.js
|
|
93
|
-
│
|
|
97
|
+
│ ├── parse.test.js # node:test 单元测试(src/parse.js)
|
|
98
|
+
│ ├── i18n.test.js # node:test 单元测试(src/i18n.js)
|
|
99
|
+
│ └── fold-smoke.test.js # 用 DOM 垫片端到端跑真实 client.js:折叠扫描 + 草稿恢复
|
|
94
100
|
├── docs/
|
|
95
|
-
│ ├── design.md # 设计说明(编号复用、锚点稳定、codec、i18n、芯片
|
|
101
|
+
│ ├── design.md # 设计说明(编号复用、锚点稳定、codec、i18n、芯片 ×、发送后折叠)
|
|
96
102
|
│ └── images/ # README 示意图(PNG + 可编辑 SVG 源)
|
|
97
103
|
├── cordis.patch.yml # DSH bundle patch,声明宿主插件
|
|
98
104
|
├── package.json # 包与 DSH 插件清单
|
|
@@ -104,7 +110,7 @@ dsh-paste-code-block/
|
|
|
104
110
|
|
|
105
111
|
```sh
|
|
106
112
|
npm run check # 语法检查 JS 半端
|
|
107
|
-
npm test # 运行单元测试(node:test,
|
|
113
|
+
npm test # 运行单元测试(node:test,61 项)
|
|
108
114
|
```
|
|
109
115
|
|
|
110
116
|
## License
|
package/docs/design.md
CHANGED
|
@@ -48,7 +48,7 @@ mechanism that renders file/upload references inline. The plugin registers a sou
|
|
|
48
48
|
- `codec.clipboardText` → `''` (the chip leaves no visible text on copy)
|
|
49
49
|
- `codec.serialize(ref)` → the block's fenced ```` ```lang … ``` ```` text
|
|
50
50
|
|
|
51
|
-
The chip label is the localized human-readable badge (
|
|
51
|
+
The chip label is the localized human-readable badge (`代码块 N` / `Code #N` — see §9);
|
|
52
52
|
clicking it opens the detail card, and each chip carries a `×` button that deletes the block
|
|
53
53
|
(see §10).
|
|
54
54
|
|
|
@@ -70,7 +70,7 @@ Numbers are tracked **per session, split by type** (`code` vs `text`):
|
|
|
70
70
|
- `nextLabel` always allocates the **smallest free integer** (`while (used.has(n)) n++`).
|
|
71
71
|
- Deleting a chip frees its number; the next paste reuses it.
|
|
72
72
|
|
|
73
|
-
So
|
|
73
|
+
So `代码块 1` … `代码块 N` and `文本块 1` … `文本块 M` each count independently and
|
|
74
74
|
stay dense after removals.
|
|
75
75
|
|
|
76
76
|
## 6. Markdown vs. code detection
|
|
@@ -112,6 +112,15 @@ Block names are produced through **DSH's own locale service**, the same one firs
|
|
|
112
112
|
- The dock slot entry declares `locale: NS`, so the renderer hands it a fresh `t` seat **and
|
|
113
113
|
re-renders it on every locale revision**.
|
|
114
114
|
|
|
115
|
+
**Naming and visual identity (0.1.3).** A chip is a *reference badge*, not an action button, so
|
|
116
|
+
the pre-0.1.3 names carried a verb nobody invokes on it — `复制代码块N` / `Code block #N` read
|
|
117
|
+
like a command and burned width inside the composer. Current names are noun + number
|
|
118
|
+
(`代码块 1` / `Code #1`). The code/text distinction moved from wording to visuals: `scanChips`
|
|
119
|
+
tags every chip host with `data-pcb-type`, and CSS renders a leading glyph — `</>` for code
|
|
120
|
+
(mono, business-primary) and `Aa` for text (neutral gray) — plus a soft blue tint on code
|
|
121
|
+
chips. Pre-0.1.3 labels stay parseable through the `legacyLabels` table, so chips inserted by
|
|
122
|
+
an older bundle keep their click/✕/retitle identity until the next locale-synced retitle.
|
|
123
|
+
|
|
115
124
|
**Chip retitling across switches.** The chip label is *insert-time* content: Lexical caches the
|
|
116
125
|
label inside the decorator node, and React never re-renders those node bodies while the node
|
|
117
126
|
state is unchanged. So on a locale switch the plugin: (1) recomputes `block.label` for every
|
|
@@ -193,4 +202,136 @@ for us) while the scoped edit verbs address detect coordinates (a chip collapses
|
|
|
193
202
|
placeholder character). `detectOffsetOf()` converts with
|
|
194
203
|
`detect = clipboard - Σ(len_i - 1)` over preceding occurrences, so blocks stay correctly
|
|
195
204
|
addressed even when other plugins' chips (file references and the like) sit in the same draft.
|
|
196
|
-
Both helpers are pure and unit-tested (8 cases in tests/projection.test.js).
|
|
205
|
+
Both helpers are pure and unit-tested (8 cases in tests/projection.test.js).
|
|
206
|
+
|
|
207
|
+
## 12. Sent-message folding (0.2.0)
|
|
208
|
+
|
|
209
|
+
The composer fold was only half the story: on send the chips expand back into fenced text, and
|
|
210
|
+
DSH renders user bubbles as **plain pre-wrap text** (`projectUserText` → `span.plainRun` runs
|
|
211
|
+
plus `data-ref-chip` badges), so the sent message reappeared in the transcript as the very wall
|
|
212
|
+
of text the plugin exists to prevent. §12 folds *sent* blocks back down.
|
|
213
|
+
|
|
214
|
+
**Where the DOM hooks are.** Every conversation row carries `data-chat-flow-kind`
|
|
215
|
+
(`user` / `steering` / `agent` / …) and `data-chat-flow-key` on its wrapper; the right-aligned
|
|
216
|
+
user bubble lives under the same row and contains the `plainRun` spans. The send echo
|
|
217
|
+
(`data-submission-echo`) and pending steering (`data-pending-steering`) render the same bubble
|
|
218
|
+
markup one frame before the durable node exists, so scanning all four gives an instant
|
|
219
|
+
collapsed view from the moment of send. Assistant output is deliberately not touched: its
|
|
220
|
+
markdown renderer already styles code blocks its own way.
|
|
221
|
+
|
|
222
|
+
**Never fight React.** The bubble's children belong to React; removing them would make the
|
|
223
|
+
next reconciliation throw. So the fold *hides* a qualifying `plainRun` span (`display:none` —
|
|
224
|
+
the node stays a child, React keeps patching it harmlessly) and inserts a
|
|
225
|
+
`div.dsh-pcb-fold-host` **after** it. The host is ours: prose segments re-render as
|
|
226
|
+
`span.dsh-pcb-fold-prose`, each fence as a collapsed card (glyph `</>`/`Aa` + localized title
|
|
227
|
+
`python · 128 行` / `Text · 128 lines`, copy button copying the **raw fenced text** exactly as
|
|
228
|
+
sent, click-to-expand `<pre>`). Since hiding a child does not affect `textContent`, the
|
|
229
|
+
message action bar (React-side copy/edit/delete) is entirely unaffected.
|
|
230
|
+
|
|
231
|
+
**What folds.** `splitFencedSegments()` (canonical in `src/parse.js`, embedded copy in
|
|
232
|
+
`client.js`) walks the run line by line with GFM fence rules (``` or ~~~, opener indented ≤ 3
|
|
233
|
+
spaces, closer ≥ opener length alone on its line; backticks in a ``` info string kill that
|
|
234
|
+
opener; an unterminated opener stays prose). Any fence segment with a non-empty body folds;
|
|
235
|
+
everything else — prose, whitespace between blocks, empty fences — renders verbatim around
|
|
236
|
+
the cards. Fence-only (not "any long text") is deliberate: composer-created blocks always
|
|
237
|
+
travel as fences, while unfenced long messages may have been *typed*, and silently folding
|
|
238
|
+
typed prose would be a nasty surprise.
|
|
239
|
+
|
|
240
|
+
**Idempotence and refresh.** Each scanned run gets
|
|
241
|
+
`data-dsh-pcb-run = "1|<stamp>"` (folded) or `"0|<stamp>"` (checked, nothing to fold), where
|
|
242
|
+
the stamp folds `foldRev`, text length, and an FNV-1a hash. The shared chip-sync
|
|
243
|
+
MutationObserver coalesces to one rAF scan; a run whose stamp matches is skipped, so steady
|
|
244
|
+
state costs attribute reads. A locale switch bumps `foldRev`, which invalidates every stamp —
|
|
245
|
+
the next scan rebuilds each host and re-titles cards from the live translator. Rebuilds
|
|
246
|
+
preserve who was expanded via a stable `flowKey#runIndex:segIndex` open set, and an orphan
|
|
247
|
+
sweep drops any host whose predecessor is no longer its hidden, stamp-matching run (React can
|
|
248
|
+
remount the run underneath us). Disposal (HMR / plugin removal) removes all hosts and unhides
|
|
249
|
+
every run — the transcript hands back clean.
|
|
250
|
+
|
|
251
|
+
`tests/fold-smoke.test.js` exercises the real bundle against a dependency-free DOM shim:
|
|
252
|
+
fold → marker/card/prose structure, steering rows, non-fenced messages untouched, retitle
|
|
253
|
+
across locale switches with open-state kept, text edits re-fold and unfold, orphan sweep,
|
|
254
|
+
and dispose restoration.
|
|
255
|
+
|
|
256
|
+
**Bare fences are code (0.2.1).** Card type follows the composer round-trip convention: a
|
|
257
|
+
plain-text block always serializes as an explicit ` ```text ` fence while a code block whose
|
|
258
|
+
language was not detected serializes as a **bare** ` ``` ` fence — so only an explicit `text`
|
|
259
|
+
info string folds as a 文本块, and a bare fence folds as 代码块, re-guessing a language from
|
|
260
|
+
the body for the title (`python · 2 行`, else `代码块 · N 行`). Manually typed bare fences
|
|
261
|
+
follow the same rule; that is also the CommonMark reading. (0.2.0 shipped the inverse and a
|
|
262
|
+
sent 代码块 chip could reappear in the transcript as 文本块.)
|
|
263
|
+
|
|
264
|
+
## 13. Draft rebuild recovery (0.2.1)
|
|
265
|
+
|
|
266
|
+
**Typing-window guard and retired hygiene (0.2.2).** The recovery rewrite is a whole-
|
|
267
|
+
document `setDraft`: the caret drops at the end and an in-flight IME composition is
|
|
268
|
+
destroyed. Run in the middle of typing, that ate the user's first character and bounced
|
|
269
|
+
the view on every pass (reported). Two keystroke-time paths existed: sweeping a stray
|
|
270
|
+
U+200B that belonged to no restorable block (external invisible copy from WeChat / web
|
|
271
|
+
pages — the signature is NOT exclusive to rebuilds after all), and restoring blocks while
|
|
272
|
+
the draft had already moved past the re-seeded text. The hygiene path is retired outright:
|
|
273
|
+
external residue is left exactly where the user put it. Both paths now share one gate —
|
|
274
|
+
`recover()` only acts when the draft is IDENTICAL to the last draft observed for that
|
|
275
|
+
session in this page view (or was never observed: fresh boot, or the composer only ever
|
|
276
|
+
seen hydrating empty). A draft that MOVED between passes is live typing, never a rebuild.
|
|
277
|
+
`attach()` records the shell's post-insertion draft as that fingerprint immediately, and
|
|
278
|
+
the reconcile prune drops the mirror synchronously, so a hand-deleted block cannot be
|
|
279
|
+
resurrected from stale mirror data through the empty-prior window.
|
|
280
|
+
|
|
281
|
+
**Paste-storm guard (0.2.3).** Even with the typing-window guard deployed (load log
|
|
282
|
+
`client loaded v0.2.2` verified live), the console showed the SAME clipboard text (571
|
|
283
|
+
chars) intercepted dozens of times in seconds — an external automation (a Tampermonkey
|
|
284
|
+
AutoClicker was running on the page) re-dispatching paste. Each accepted paste inserts a
|
|
285
|
+
chip and re-renders the dock, so a storm reproduces the exact bounce-and-eat-the-first-
|
|
286
|
+
character experience through a different door. Identical clipboard text arriving
|
|
287
|
+
faster than a human can act collapses into one paste (`pasteStorm.seen`, 1200 ms
|
|
288
|
+
sliding window refreshed on every matching event, so a held repeat stays
|
|
289
|
+
suppressed while it continues); suppressed events are still `preventDefault`ed so
|
|
290
|
+
raw text never falls through to the composer. The plugin cannot suppress the
|
|
291
|
+
automation itself — users hitting this should disable AutoClicker-style userscripts
|
|
292
|
+
on the DSH page.
|
|
293
|
+
|
|
294
|
+
**Burst-only collapse (0.2.5).** 0.2.4 tried a content-liveness rule: while a block
|
|
295
|
+
with the same text was still attached, re-pasting identical text was consumed as
|
|
296
|
+
"not a new intent." That over-suppressed — pasting the same snippet twice (a
|
|
297
|
+
completely normal action) yielded a single block and confused users, and in the
|
|
298
|
+
end the reported bounces were traced to a *different* plugin entirely, not to
|
|
299
|
+
paste storms. So 0.2.5 reverts to a pure burst window: identical text pasted after
|
|
300
|
+
the 1200 ms window is a fresh intent and gets its own block (`sourceText` is
|
|
301
|
+
recorded per block and carried through the recovery mirror); only machine-paced
|
|
302
|
+
repeats *within* the window collapse. The `intercept` load log still carries
|
|
303
|
+
`trusted=` — `false` names a page-script dispatch (userscript glue, or the
|
|
304
|
+
`dsh-vision-router` image-paste replay, which carries the original text and lands
|
|
305
|
+
seconds after the trusted paste) rather than a real Ctrl+V, so any future storm
|
|
306
|
+
report identifies its own source.
|
|
307
|
+
|
|
308
|
+
Two DSH paths strand pasted blocks mid-draft, and both share one signature. (a) A page
|
|
309
|
+
reload re-seeds the composer from DSH's persisted per-session draft
|
|
310
|
+
(`dsh.conversation.<sid>`), which is **plain text** — Lexical chips cannot round-trip
|
|
311
|
+
through it (§7). (b) A workspace switch on the new-session page *carries the draft text
|
|
312
|
+
over* to the target workspace's blank session (`inputHub`: `next.setDraft(from.snapshot
|
|
313
|
+
.draft)`, then `from.setDraft("")`) — prose follows the composer, chips and controller
|
|
314
|
+
state do not, so blocks were silently orphaned under the old session id (user-reported).
|
|
315
|
+
Typed prose comes back either way; the blocks did not.
|
|
316
|
+
|
|
317
|
+
The signature — as 0.2.1 read it: every chip contributes exactly one U+200B to the clipboard
|
|
318
|
+
projection and nothing scrubs it from persisted/carried text (DSH's placeholder-stripping
|
|
319
|
+
regex covers private-use chars and U+FFFC, not ZWSPs). Stray U+200Bs with **no** live
|
|
320
|
+
occurrence of ours can therefore only mean the draft was rebuilt from text; conversely
|
|
321
|
+
deleting a chip — ✕, Backspace, or prune — removes its ZWSP together with the node, so
|
|
322
|
+
deliberate deletion never looks like a rebuild and cannot be "resurrected".
|
|
323
|
+
|
|
324
|
+
`BlockController.recover()` runs in the dock's reconcile effect, ahead of the reconcile,
|
|
325
|
+
and resolves blocks in this order: own-session memory → the session's own localStorage
|
|
326
|
+
mirror → **carry-over donor**: the unmounted session whose last published draft equals the
|
|
327
|
+
re-seeded text character for character (the mirror stores that same fingerprint, so the
|
|
328
|
+
donor is even found after a reload wiped memory) → nothing. An ambiguous fingerprint aborts
|
|
329
|
+
the donor step — no guessing. On a hit the mirror moves to the new sid. (0.2.1 also stripped
|
|
330
|
+
strays when nothing could be restored; the typing-window guard above retired that — see the
|
|
331
|
+
0.2.2 note.) The mirror holds just the block payloads plus that draft fingerprint, is written
|
|
332
|
+
synchronously on attach (and debounced on detail-card edits), drops on a send attempt, and
|
|
333
|
+
expires after two weeks. The recovery pass then *skips* its reconcile — the snapshot it
|
|
334
|
+
would reconcile against is stale; the draft mutation bumps `draftRev` and the next pass
|
|
335
|
+
reconciles real occurrences. Restored chips get re-derived labels and numbers, so a
|
|
336
|
+
restored draft is indistinguishable from one that never left. Mirror writes are best
|
|
337
|
+
effort: private mode / quota only lose reload durability, never the feature itself.
|
package/docs/images/hero.png
CHANGED
|
Binary file
|
package/docs/images/hero.svg
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="700" viewBox="0 0 1200 700">
|
|
2
2
|
<rect width="1200" height="700" fill="#0d1220"/>
|
|
3
|
-
<text x="40" y="56" font-size="26" fill="#e6ecf8" font-family="Noto Sans SC" font-weight="700">粘贴长代码 / 长文本 → 输入框里只留一张整洁卡片</text><text x="40" y="86" font-size="14.5" fill="#93a3c0" font-family="Noto Sans SC">Paste long code or text into the DSH composer — it lands as a tidy, collapsible card, never a wall of text</text><rect x="40" y="112" width="720" height="516" rx="16" fill="#151c2f"/><rect x="40" y="112" width="720" height="516" rx="16" fill="none" stroke="#2a3450" stroke-width="1"/><text x="64" y="140" font-size="12" fill="#5b6a8a" font-family="Noto Sans SC">DSH Web · 输入框 / composer</text><rect x="64" y="152" width="672" height="96" rx="10" fill="#10162a"/><rect x="64" y="152" width="672" height="96" rx="10" fill="none" stroke="#232c45" stroke-width="1"/><rect x="80" y="168" width="
|
|
3
|
+
<text x="40" y="56" font-size="26" fill="#e6ecf8" font-family="Noto Sans SC" font-weight="700">粘贴长代码 / 长文本 → 输入框里只留一张整洁卡片</text><text x="40" y="86" font-size="14.5" fill="#93a3c0" font-family="Noto Sans SC">Paste long code or text into the DSH composer — it lands as a tidy, collapsible card, never a wall of text</text><rect x="40" y="112" width="720" height="516" rx="16" fill="#151c2f"/><rect x="40" y="112" width="720" height="516" rx="16" fill="none" stroke="#2a3450" stroke-width="1"/><text x="64" y="140" font-size="12" fill="#5b6a8a" font-family="Noto Sans SC">DSH Web · 输入框 / composer</text><rect x="64" y="152" width="672" height="96" rx="10" fill="#10162a"/><rect x="64" y="152" width="672" height="96" rx="10" fill="none" stroke="#232c45" stroke-width="1"/><rect x="80" y="168" width="126" height="27" rx="7" fill="#151e3c"/><rect x="80" y="168" width="126" height="27" rx="7" fill="none" stroke="#263777" stroke-width="1"/><text x="89" y="185.5" font-size="10.5" fill="#7aa2ff" font-family="JetBrains Mono" font-weight="700"></></text><text x="112" y="186" font-size="12.5" fill="#8fb0ff" font-family="Noto Sans SC">代码块 1</text><text x="196" y="187" font-size="15" fill="#ff7b7b" font-family="Noto Sans SC" text-anchor="middle">×</text><rect x="214" y="168" width="126" height="27" rx="7" fill="#1d2742"/><rect x="214" y="168" width="126" height="27" rx="7" fill="none" stroke="#3a4a75" stroke-width="1"/><text x="223" y="185.5" font-size="10.5" fill="#93a3c0" font-family="Noto Sans SC" font-weight="700">Aa</text><text x="243" y="186" font-size="12.5" fill="#cdd9f2" font-family="Noto Sans SC">文本块 1</text><text x="330" y="187" font-size="15" fill="#ff7b7b" font-family="Noto Sans SC" text-anchor="middle">×</text><rect x="348" y="168" width="126" height="27" rx="7" fill="#151e3c"/><rect x="348" y="168" width="126" height="27" rx="7" fill="none" stroke="#263777" stroke-width="1"/><text x="357" y="185.5" font-size="10.5" fill="#7aa2ff" font-family="JetBrains Mono" font-weight="700"></></text><text x="380" y="186" font-size="12.5" fill="#8fb0ff" font-family="Noto Sans SC">代码块 2</text><text x="464" y="187" font-size="15" fill="#ff7b7b" font-family="Noto Sans SC" text-anchor="middle">×</text><circle cx="464" cy="181.5" r="11" fill="none" stroke="#ff7b7b" stroke-width="1.6" stroke-dasharray="4 3"/><text x="80" y="234" font-size="13" fill="#5b6a8a" font-family="Noto Sans SC">帮我分析这段代码,重点是并发问题…</text><rect x="64" y="268" width="672" height="312" rx="12" fill="#131a2c"/><rect x="64" y="268" width="672" height="312" rx="12" fill="none" stroke="#232c45" stroke-width="1"/><rect x="80" y="282" width="60" height="22" rx="8" fill="#1c2b4d"/><text x="90" y="297" font-size="11.5" fill="#7aa2ff" font-family="JetBrains Mono">python</text><text x="154" y="297" font-size="11.5" fill="#5b6a8a" font-family="Noto Sans SC">42 行</text><path d="M 610 291 L 617 284.5 L 624 291" stroke="#93a3c0" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round"/><rect x="642" y="284" width="11" height="13" rx="2" fill="none"/><rect x="642" y="284" width="11" height="13" rx="2" fill="none" stroke="#93a3c0" stroke-width="1"/><rect x="647" y="288" width="11" height="13" rx="2" fill="#131a2c"/><rect x="647" y="288" width="11" height="13" rx="2" fill="none" stroke="#93a3c0" stroke-width="1"/><path d="M 680 284.5 L 673.5 290.5 L 680 297" stroke="#93a3c0" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round"/><text x="708" y="296.5" font-size="16" fill="#ff7b7b" font-family="Noto Sans SC" text-anchor="middle">×</text><circle cx="708" cy="291" r="12" fill="none" stroke="#ff7b7b" stroke-width="1.6" stroke-dasharray="4 3"/><rect x="80" y="318" width="640" height="240" rx="8" fill="#0b1120"/><text x="98" y="348" font-size="13" fill="#c792ea" font-family="JetBrains Mono">def </text><text x="132" y="348" font-size="13" fill="#7aa2ff" font-family="JetBrains Mono">analyze</text><text x="180" y="348" font-size="13" fill="#d6deeb" font-family="JetBrains Mono">(chunks):</text><text x="98" y="376" font-size="13" fill="#d6deeb" font-family="JetBrains Mono"> total = 0</text><text x="98" y="404" font-size="13" fill="#d6deeb" font-family="JetBrains Mono"> results = []</text><text x="98" y="432" font-size="13" fill="#c792ea" font-family="JetBrains Mono"> for </text><text x="132" y="432" font-size="13" fill="#d6deeb" font-family="JetBrains Mono">c</text><text x="143" y="432" font-size="13" fill="#c792ea" font-family="JetBrains Mono"> in </text><text x="164" y="432" font-size="13" fill="#d6deeb" font-family="JetBrains Mono">chunks</text><text x="213" y="432" font-size="13" fill="#d6deeb" font-family="JetBrains Mono">:</text><text x="224" y="432" font-size="13" fill="#5b6a8a" font-family="JetBrains Mono"># 42 rows total — full, editable</text><text x="98" y="460" font-size="13" fill="#d6deeb" font-family="JetBrains Mono"> total += len(c.text)</text><text x="98" y="488" font-size="13" fill="#c792ea" font-family="JetBrains Mono"> return </text><text x="148" y="488" font-size="13" fill="#7aa2ff" font-family="JetBrains Mono">sorted</text><text x="198" y="488" font-size="13" fill="#d6deeb" font-family="JetBrains Mono">(results)[-5:]</text><rect x="98" y="504" width="520" height="1.6" fill="#232c45"/><text x="98" y="528" font-size="11.5" fill="#5b6a8a" font-family="Noto Sans SC">卡片内直接编辑 · 编辑后的文本即发送内容</text><rect x="664" y="596" width="72" height="28" rx="8" fill="#3b5bdb"/><text x="700" y="615" font-size="13.5" fill="#fff" font-family="Noto Sans SC" text-anchor="middle">发送</text><path d="M 806 168 C 675.5 168, 675.5 182, 545 182" stroke="#5b6a8a" stroke-width="1.4" fill="none" stroke-linecap="round" stroke-linejoin="round"/><circle cx="820" cy="168" r="13" fill="#3b5bdb"/><text x="820" y="172.5" font-size="13.5" fill="#fff" font-family="Noto Sans SC" text-anchor="middle">1</text><text x="842" y="169" font-size="15" fill="#e6ecf8" font-family="Noto Sans SC">点击芯片 → 展开 / 收起详情卡</text><text x="842" y="189" font-size="12" fill="#5b6a8a" font-family="Noto Sans SC">click a chip to open its card</text><path d="M 806 262 C 753 262, 753 291, 700 291" stroke="#5b6a8a" stroke-width="1.4" fill="none" stroke-linecap="round" stroke-linejoin="round"/><circle cx="820" cy="268" r="13" fill="#3b5bdb"/><text x="820" y="272.5" font-size="13.5" fill="#fff" font-family="Noto Sans SC" text-anchor="middle">2</text><text x="842" y="269" font-size="15" fill="#e6ecf8" font-family="Noto Sans SC">× 一键删除对应块,编号自动回收复用</text><text x="842" y="289" font-size="12" fill="#5b6a8a" font-family="Noto Sans SC">one-click remove · the number is recycled</text><path d="M 806 372 C 764 372, 764 430, 722 430" stroke="#5b6a8a" stroke-width="1.4" fill="none" stroke-linecap="round" stroke-linejoin="round"/><circle cx="820" cy="372" r="13" fill="#3b5bdb"/><text x="820" y="376.5" font-size="13.5" fill="#fff" font-family="Noto Sans SC" text-anchor="middle">3</text><text x="842" y="373" font-size="15" fill="#e6ecf8" font-family="Noto Sans SC">详情卡:编辑 / 复制 / 折叠</text><text x="842" y="393" font-size="12" fill="#5b6a8a" font-family="Noto Sans SC">edit, copy, collapse — all inline</text><line x1="756" y1="612" x2="806" y2="612" stroke="#5b6a8a" stroke-width="1.4"/><path d="M 806 612 l -8 -4 v 8 z" stroke="#5b6a8a" stroke-width="1" fill="#5b6a8a" stroke-linecap="round" stroke-linejoin="round"/><rect x="812" y="470" width="348" height="150" rx="12" fill="#10162a"/><rect x="812" y="470" width="348" height="150" rx="12" fill="none" stroke="#232c45" stroke-width="1"/><text x="832" y="496" font-size="12" fill="#5b6a8a" font-family="Noto Sans SC">发送后 · on send</text><text x="832" y="524" font-size="12.5" fill="#93a3c0" font-family="JetBrains Mono">```python</text><text x="832" y="548" font-size="12.5" fill="#d6deeb" font-family="JetBrains Mono">def analyze(chunks):</text><text x="832" y="572" font-size="12.5" fill="#d6deeb" font-family="JetBrains Mono"> ...完整原始文本...</text><text x="832" y="596" font-size="12.5" fill="#93a3c0" font-family="JetBrains Mono">```</text><circle cx="820" cy="662" r="13" fill="#3b5bdb"/><text x="820" y="666.5" font-size="13.5" fill="#fff" font-family="Noto Sans SC" text-anchor="middle">4</text><text x="842" y="663" font-size="15" fill="#e6ecf8" font-family="Noto Sans SC">发送时自动还原为围栏代码块</text><text x="842" y="683" font-size="12" fill="#5b6a8a" font-family="Noto Sans SC">restored as a fenced code block on send</text><text x="40" y="662" font-size="12.5" fill="#5b6a8a" font-family="Noto Sans SC">PC 与移动端 Web 通用 · 深浅主题自适应 · 标签随 DSH 界面语言自动切换(中文 / English)</text>
|
|
4
4
|
</svg>
|
|
Binary file
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="464" viewBox="0 0 1200 464">
|
|
2
2
|
<rect width="1200" height="464" fill="#0d1220"/>
|
|
3
|
-
<text x="40" y="50" font-size="22" fill="#e6ecf8" font-family="Noto Sans SC" font-weight="700">块名称跟随 DSH 界面语言 · Block names follow the UI language</text><rect x="36" y="84" width="548" height="296" rx="14" fill="#151c2f"/><rect x="36" y="84" width="548" height="296" rx="14" fill="none" stroke="#2a3450" stroke-width="1"/><rect x="60" y="108" width="226" height="28" rx="14" fill="#0b1120"/><rect x="60" y="108" width="226" height="28" rx="14" fill="none" stroke="#232c45" stroke-width="1"/><text x="76" y="126" font-size="12.5" fill="#93a3c0" font-family="Noto Sans SC">设置 → 语言:中文</text><rect x="60" y="158" width="
|
|
3
|
+
<text x="40" y="50" font-size="22" fill="#e6ecf8" font-family="Noto Sans SC" font-weight="700">块名称跟随 DSH 界面语言 · Block names follow the UI language</text><rect x="36" y="84" width="548" height="296" rx="14" fill="#151c2f"/><rect x="36" y="84" width="548" height="296" rx="14" fill="none" stroke="#2a3450" stroke-width="1"/><rect x="60" y="108" width="226" height="28" rx="14" fill="#0b1120"/><rect x="60" y="108" width="226" height="28" rx="14" fill="none" stroke="#232c45" stroke-width="1"/><text x="76" y="126" font-size="12.5" fill="#93a3c0" font-family="Noto Sans SC">设置 → 语言:中文</text><rect x="60" y="158" width="126" height="27" rx="7" fill="#1a2341"/><rect x="60" y="158" width="126" height="27" rx="7" fill="none" stroke="#2a3b7a" stroke-width="1"/><text x="69" y="175.5" font-size="10.5" fill="#7aa2ff" font-family="JetBrains Mono" font-weight="700"></></text><text x="92" y="176" font-size="12.5" fill="#8fb0ff" font-family="Noto Sans SC">代码块 1</text><text x="176" y="177" font-size="15" fill="#ff7b7b" font-family="Noto Sans SC" text-anchor="middle">×</text><rect x="194" y="158" width="126" height="27" rx="7" fill="#1d2742"/><rect x="194" y="158" width="126" height="27" rx="7" fill="none" stroke="#3a4a75" stroke-width="1"/><text x="203" y="175.5" font-size="10.5" fill="#93a3c0" font-family="Noto Sans SC" font-weight="700">Aa</text><text x="223" y="176" font-size="12.5" fill="#cdd9f2" font-family="Noto Sans SC">文本块 1</text><text x="310" y="177" font-size="15" fill="#ff7b7b" font-family="Noto Sans SC" text-anchor="middle">×</text><rect x="60" y="206" width="500" height="148" rx="10" fill="#131a2c"/><rect x="60" y="206" width="500" height="148" rx="10" fill="none" stroke="#232c45" stroke-width="1"/><rect x="76" y="220" width="62" height="20" rx="7" fill="#1c2b4d"/><text x="86" y="234" font-size="11" fill="#7aa2ff" font-family="JetBrains Mono">python</text><text x="152" y="234" font-size="11" fill="#5b6a8a" font-family="Noto Sans SC">18 行</text><path d="M 436 229 L 443 222.5 L 450 229" stroke="#93a3c0" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round"/><rect x="468" y="222" width="11" height="13" rx="2" fill="none"/><rect x="468" y="222" width="11" height="13" rx="2" fill="none" stroke="#93a3c0" stroke-width="1"/><rect x="473" y="226" width="11" height="13" rx="2" fill="#131a2c"/><rect x="473" y="226" width="11" height="13" rx="2" fill="none" stroke="#93a3c0" stroke-width="1"/><path d="M 506 222.5 L 499.5 228.5 L 506 235" stroke="#93a3c0" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round"/><text x="534" y="234.5" font-size="16" fill="#ff7b7b" font-family="Noto Sans SC" text-anchor="middle">×</text><text x="76" y="266" font-size="12.5" fill="#d6deeb" font-family="JetBrains Mono">def analyze(chunks):</text><text x="76" y="290" font-size="12.5" fill="#d6deeb" font-family="JetBrains Mono"> total = 0</text><text x="76" y="314" font-size="12.5" fill="#d6deeb" font-family="JetBrains Mono"> return sorted(results)</text><rect x="616" y="84" width="548" height="296" rx="14" fill="#151c2f"/><rect x="616" y="84" width="548" height="296" rx="14" fill="none" stroke="#2a3450" stroke-width="1"/><rect x="640" y="108" width="226" height="28" rx="14" fill="#0b1120"/><rect x="640" y="108" width="226" height="28" rx="14" fill="none" stroke="#232c45" stroke-width="1"/><text x="656" y="126" font-size="12.5" fill="#93a3c0" font-family="Noto Sans SC">Settings → Language: English</text><rect x="640" y="158" width="122" height="27" rx="7" fill="#1a2341"/><rect x="640" y="158" width="122" height="27" rx="7" fill="none" stroke="#2a3b7a" stroke-width="1"/><text x="649" y="175.5" font-size="10.5" fill="#7aa2ff" font-family="JetBrains Mono" font-weight="700"></></text><text x="672" y="176" font-size="12.5" fill="#8fb0ff" font-family="Noto Sans SC">Code #1</text><text x="751" y="177" font-size="15" fill="#ff7b7b" font-family="Noto Sans SC" text-anchor="middle">×</text><rect x="770" y="158" width="122" height="27" rx="7" fill="#1d2742"/><rect x="770" y="158" width="122" height="27" rx="7" fill="none" stroke="#3a4a75" stroke-width="1"/><text x="779" y="175.5" font-size="10.5" fill="#93a3c0" font-family="Noto Sans SC" font-weight="700">Aa</text><text x="799" y="176" font-size="12.5" fill="#cdd9f2" font-family="Noto Sans SC">Text #1</text><text x="881" y="177" font-size="15" fill="#ff7b7b" font-family="Noto Sans SC" text-anchor="middle">×</text><rect x="640" y="206" width="500" height="148" rx="10" fill="#131a2c"/><rect x="640" y="206" width="500" height="148" rx="10" fill="none" stroke="#232c45" stroke-width="1"/><rect x="656" y="220" width="62" height="20" rx="7" fill="#1c2b4d"/><text x="666" y="234" font-size="11" fill="#7aa2ff" font-family="JetBrains Mono">python</text><text x="732" y="234" font-size="11" fill="#5b6a8a" font-family="Noto Sans SC">18 lines</text><path d="M 1016 229 L 1023 222.5 L 1030 229" stroke="#93a3c0" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round"/><rect x="1048" y="222" width="11" height="13" rx="2" fill="none"/><rect x="1048" y="222" width="11" height="13" rx="2" fill="none" stroke="#93a3c0" stroke-width="1"/><rect x="1053" y="226" width="11" height="13" rx="2" fill="#131a2c"/><rect x="1053" y="226" width="11" height="13" rx="2" fill="none" stroke="#93a3c0" stroke-width="1"/><path d="M 1086 222.5 L 1079.5 228.5 L 1086 235" stroke="#93a3c0" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round"/><text x="1114" y="234.5" font-size="16" fill="#ff7b7b" font-family="Noto Sans SC" text-anchor="middle">×</text><text x="656" y="266" font-size="12.5" fill="#d6deeb" font-family="JetBrains Mono">def analyze(chunks):</text><text x="656" y="290" font-size="12.5" fill="#d6deeb" font-family="JetBrains Mono"> total = 0</text><text x="656" y="314" font-size="12.5" fill="#d6deeb" font-family="JetBrains Mono"> return sorted(results)</text><path d="M 590 226 h 20 m -7 -6 l 7 6 l -7 6 M 610 246 h -20 m 7 6 l -7 -6 l 7 -6" stroke="#7aa2ff" stroke-width="2.2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><text x="600" y="284" font-size="11" fill="#5b6a8a" font-family="Noto Sans SC" text-anchor="middle">切换</text><text x="600" y="300" font-size="10.5" fill="#5b6a8a" font-family="Noto Sans SC" text-anchor="middle">live</text><text x="40" y="416" font-size="13.5" fill="#93a3c0" font-family="Noto Sans SC">语言切换即时生效:新粘贴的块用新语言命名,已插入的芯片也会同步改名</text><text x="40" y="438" font-size="12" fill="#5b6a8a" font-family="Noto Sans SC">Switch anytime — new pastes land in the new language, existing chips retitle live</text>
|
|
4
4
|
</svg>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Regenerate the README mockup PNGs from the SVG sources in this folder.
|
|
2
|
+
// Dev-only helper (not loaded by the plugin). Requires: npm i @resvg/resvg-js
|
|
3
|
+
// and the fonts referenced below (apt: fonts-noto-cjk fonts-jetbrains-mono).
|
|
4
|
+
const fs = require('fs')
|
|
5
|
+
const path = require('path')
|
|
6
|
+
const { Resvg } = require('@resvg/resvg-js')
|
|
7
|
+
|
|
8
|
+
const FONTS = [
|
|
9
|
+
'/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc',
|
|
10
|
+
'/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc',
|
|
11
|
+
'/usr/share/fonts/truetype/jetbrains-mono/JetBrainsMono-Regular.ttf',
|
|
12
|
+
'/usr/share/fonts/truetype/jetbrains-mono/JetBrainsMono-Bold.ttf',
|
|
13
|
+
]
|
|
14
|
+
for (const name of ['hero', 'languages', 'workflow']) {
|
|
15
|
+
let src = fs.readFileSync(path.join(__dirname, name + '.svg'), 'utf8')
|
|
16
|
+
src = src.split('font-family="Noto Sans SC"').join('font-family="Noto Sans CJK SC"')
|
|
17
|
+
const r = new Resvg(src, { fitTo: { mode: 'zoom', value: 2 }, font: { fontFiles: FONTS, loadSystemFonts: false, defaultFontFamily: 'Noto Sans CJK SC', sansSerifFamily: 'Noto Sans CJK SC', monospaceFamily: 'JetBrains Mono' } })
|
|
18
|
+
fs.writeFileSync(path.join(__dirname, name + '.png'), r.render().asPng())
|
|
19
|
+
console.log('rendered', name + '.png')
|
|
20
|
+
}
|
package/docs/images/workflow.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-paste-code-block",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Cherry Studio-style paste for DSH Web: pasted text/code becomes a bordered, collapsible, language-tagged card with localized names (中文/English) and a one-click remove button; restored as a fenced code block on send.",
|
|
3
|
+
"version": "0.2.5",
|
|
4
|
+
"description": "Cherry Studio-style paste for DSH Web: pasted text/code becomes a bordered, collapsible, language-tagged card with localized names (中文/English) and a one-click remove button; restored as a fenced code block on send — and folded back into collapsed, expandable cards in the conversation.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "DSH paste-code-block contributors",
|
|
7
7
|
"repository": {
|
|
@@ -65,17 +65,28 @@
|
|
|
65
65
|
"react": "^18.2.0"
|
|
66
66
|
},
|
|
67
67
|
"peerDependenciesMeta": {
|
|
68
|
-
"@deepseek-ai/dsh-client-ui-conversation": {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"@deepseek-ai/dsh-client-
|
|
72
|
-
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-conversation": {
|
|
69
|
+
"optional": true
|
|
70
|
+
},
|
|
71
|
+
"@deepseek-ai/dsh-client-ui-input-trigger": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"@deepseek-ai/dsh-client-ui-slots": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"@deepseek-ai/dsh-client-locale": {
|
|
78
|
+
"optional": true
|
|
79
|
+
},
|
|
80
|
+
"react": {
|
|
81
|
+
"optional": true
|
|
82
|
+
}
|
|
73
83
|
},
|
|
74
84
|
"engines": {
|
|
75
85
|
"node": ">=20"
|
|
76
86
|
},
|
|
77
87
|
"scripts": {
|
|
78
|
-
"check": "node --check src/index.js && node --check src/i18n.js && node --check src/chip-nodes.js && node --check src/projection.js && node --check src/client.js",
|
|
79
|
-
"test": "node --test 'tests/**/*.test.js'"
|
|
88
|
+
"check": "node --check src/index.js && node --check src/parse.js && node --check src/i18n.js && node --check src/chip-nodes.js && node --check src/projection.js && node --check src/client.js",
|
|
89
|
+
"test": "node --test 'tests/**/*.test.js'",
|
|
90
|
+
"render:images": "node docs/images/render.cjs"
|
|
80
91
|
}
|
|
81
92
|
}
|