dsh-paste-code-block 0.1.0 → 0.1.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 CHANGED
@@ -4,64 +4,96 @@
4
4
 
5
5
  **Cherry Studio‑style text/code block pasting for DeepSeek Harness Web**
6
6
 
7
- Paste text/code into the DSH Web composer → it collapses into a **bordered, collapsible, language-tagged card** instead of a long flat string; on send it restores the original content 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.
8
8
 
9
- `type: module` · `runtime: host` · `client: web` · MIT
9
+ [![npm version](https://img.shields.io/npm/v/dsh-paste-code-block?logo=npm)](https://www.npmjs.com/package/dsh-paste-code-block)
10
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](./LICENSE)
11
+ [![platform: web](https://img.shields.io/badge/platform-DSH%20Web-blueviolet)](https://github.com/deepseek-ai/deepseek-harness)
10
12
 
11
- **[中文](./README.zh-CN.md) | English**
13
+ **English | [简体中文](./README.zh-CN.md)**
12
14
 
13
15
  </div>
14
16
 
15
- ---
17
+ ![Preview — pasted blocks become tidy cards](./docs/images/hero.png)
18
+
19
+ ## Why
20
+
21
+ Copying a 200‑line stack trace, an API response, or a long log into a chat input turns the composer into an unreadable blob. This plugin folds any qualifying paste into a **chip** (built on DSH's own hidden‑reference mechanism, the same one file uploads use) plus an **editing detail card** — the draft stays clean, and the content stays fully editable and restorable.
16
22
 
17
23
  ## Features
18
24
 
19
- - 📋 **Smart detection** — pasted content with ``` fences, newlines, or long indented code becomes a card; ordinary short prose pastes as-is.
20
- - 🏷️ **Language tags** — auto-detects Python / JS / JSON / YAML / SQL / bash / HTML·XML / C++ / Go / Ruby (incl. shebang). Plain text blocks are tagged `text`.
21
- - 📂 **Collapsible card** — collapsed by default showing a one-line preview; click to expand full code (scrollable, up to 260px).
22
- - ✏️ **In‑card editing** — edit the block content right in the detail card; the send path uses your edited text.
23
- - 📤 **Restore on send** — each card is restored to a ```` ```lang … ``` ```` fenced block; a failed send auto-fills it back into the input.
24
- - 🔢 **Smart numbering** — code and text are counted apart; deleting a block frees its number for reuse (smallest-free allocation).
25
- - 📱 **All surfaces** — pure client plugin; works on PC & mobile Web, adapts to light/dark themes.
25
+ - 🧩 **Auto block detection** — pasted text with ``` fences, newlines, heavy indentation, or length ≥ 96 becomes a card; ordinary short prose pastes through untouched.
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
+ - ✏️ **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: `复制代码块1` / `Code block #1`, `复制文本块N` / `Text block #N`. Switching **Settings → Language** live-retitles every chip.
29
+ - × **One-click remove** — every chip carries its own `×` delete button (the detail card has one too); a removed number is recycled immediately.
30
+ - 📤 **Fenced restore on send** — each card expands back into a ```` ```lang … ``` ```` block when you send; a failed send automatically refills the input.
31
+ - 🔢 **Smart numbering** — code and text count apart; the smallest free number is always reused.
32
+ - 📱 **Every surface** — pure client plugin; works on desktop & mobile web, follows light/dark themes.
33
+
34
+ ## How it works
35
+
36
+ ![Four steps: copy · paste · tidy · send](./docs/images/workflow.png)
37
+
38
+ ## Localization
39
+
40
+ Chip labels, tooltips, line counts, and the send-time error notice all render through the official DSH locale service: the plugin registers a `paste-code-block` dictionary namespace with complete **zh / en** key sets, and its composer dock declares that namespace, so everything re-renders on every language switch.
41
+
42
+ ![Block names follow the UI language](./docs/images/languages.png)
43
+
44
+ - **New pastes** are named in whatever language is active at paste time.
45
+ - **Existing chips** are re-titled live when you flip 设置 → 语言 / Settings → Language — no reload needed.
46
+ - Clicks and deletes match chips **across languages**: a `复制代码块2` chip still resolves to its block after switching to English (where it now reads `Code block #2`), and vice versa.
26
47
 
27
48
  ## Install
28
49
 
29
- Requires DSH `0.1.x` (current developer preview — APIs may change).
50
+ Requires DSH `0.1.x` (developer preview — APIs may change). Published on **npm** — install by name:
30
51
 
31
52
  ```sh
32
- # Local install from this directory
33
- dsh plugin --profile web add file:/path/to/dsh-paste-code-block
53
+ dsh plugin --profile web add dsh-paste-code-block
54
+ ```
55
+
56
+ Or from this repository / a local checkout:
34
57
 
35
- # Or after publishing
36
- # dsh plugin --profile web add dsh-paste-code-block
58
+ ```sh
59
+ dsh plugin --profile web add github:cjm-m/dsh-paste-code-block # from GitHub
60
+ dsh plugin --profile web add file:/path/to/dsh-paste-code-block # from a local folder
37
61
  ```
38
62
 
39
63
  **Restart `dsh web`** after installing.
40
64
 
41
65
  ## Usage
42
66
 
43
- 1. Copy a block of code or a multi-line block of text anywhere.
44
- 2. Paste it into the DSH input → it becomes a code-block card.
45
- 3. Expand / copy / edit / remove as you like; press Enter or send and it's restored as a fenced code block.
67
+ 1. Copy any code or multi-line text from anywhere.
68
+ 2. Paste into the DSH input → it collapses into a chip such as `复制代码块1` / `Code block #1`.
69
+ 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
+ 4. Send — the full original text goes out as a proper fenced code block.
71
+
72
+ Detection threshold: **contains a newline / ≥ 2 lines / length ≥ 96 / indented & brace-dense / fenced** — any one turns the paste into a block.
46
73
 
47
74
  ## Notes & boundaries
48
75
 
49
- - Blocks are **appended at the end of the draft** (reusing DSH's hidden-reference semantics), keeping cursor/undo stable; ordinary short text is not intercepted.
50
- - Plain-text blocks are wrapped in ```` ```text ``` ```` on send so they don't smear into one line.
51
- - Detection threshold: **contains newline / ≥2 lines / length ≥96 / indented or fenced** — any one triggers the card.
76
+ - Blocks are **appended at the end of the draft** (reusing DSH's hidden-reference semantics), which keeps cursor/undo stable.
77
+ - Plain-text blocks are sent wrapped in ```` ```text ``` ```` so they never smear into one line.
78
+ - Removing a chip any other way (e.g. `Backspace`) also frees its number — plugin state is reconciled against the editor continuously.
79
+ - Deleting one block never touches the others, and removal is an ordinary editor edit — `Ctrl+Z` brings it back.
80
+ - 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.
52
81
 
53
82
  ## Project layout
54
83
 
55
84
  ```
56
85
  dsh-paste-code-block/
57
86
  ├── src/
58
- │ ├── client.js # Web (browser) half — paste capture, chip styling, detail card
87
+ │ ├── client.js # Web (browser) half — paste capture, chips, detail card, × delete
59
88
  │ ├── index.js # Host (node) half — intentionally empty (pure UI plugin)
60
- │ └── parse.js # Pure block-parsing logic (canonical, unit-tested)
89
+ │ ├── parse.js # Pure block-detection logic (canonical, unit-tested)
90
+ │ └── i18n.js # Locale dictionaries + label parsing (canonical, unit-tested)
61
91
  ├── tests/
62
- │ └── parse.test.js # node:test unit tests for src/parse.js
92
+ │ ├── parse.test.js # node:test unit tests for src/parse.js
93
+ │ └── i18n.test.js # node:test unit tests for src/i18n.js
63
94
  ├── docs/
64
- │ └── design.md # Design rationale (numbering, anchor stability, codec)
95
+ │ ├── design.md # Design rationale (numbering, anchors, codec, i18n, chip ×)
96
+ │ └── images/ # README mockups (PNG + editable SVG sources)
65
97
  ├── cordis.patch.yml # DSH bundle patch declaring the host plugin
66
98
  ├── package.json # Package + DSH plugin manifest
67
99
  ├── README.md # English
@@ -71,8 +103,10 @@ dsh-paste-code-block/
71
103
  ## Development
72
104
 
73
105
  ```sh
74
- npm run check # syntax-check both JS halves
75
- npm test # run the unit tests (node:test)
106
+ npm run check # syntax-check the JS halves
107
+ npm test # run unit tests (node:test, 38 tests)
76
108
  ```
77
109
 
78
- MIT — see [LICENSE](./LICENSE).
110
+ ## License
111
+
112
+ MIT — see [LICENSE](./LICENSE).
package/README.zh-CN.md CHANGED
@@ -4,66 +4,96 @@
4
4
 
5
5
  **Cherry Studio 式的文本块/代码块粘贴效果(DeepSeek Harness Web)**
6
6
 
7
- 把复制的**文本块 / 代码块**粘到 DSH Web 输入框时,不再是一长串普通文字,而是收成一张**带边框、可折叠、带语言标签的代码块卡片**;发送时自动还原成真正的 ```` ```lang … ``` ```` 围栏代码块发送。
7
+ 把复制的**长代码 / 长文本**粘到 DSH Web 输入框时,不再糊成一长串普通文字,而是收成一张**带边框、可折叠、带语言标签的卡片**;块名称自动跟随 DSH 界面语言(中文 / English);发送时自动还原成真正的 ```` ```lang … ``` ```` 围栏代码块。
8
8
 
9
- 纯客户端插件(浏览器侧),PC 与手机 Web 通用,实时适配深浅主题。
10
-
11
- `type: module` · `runtime: host` · `client: web` · MIT
9
+ [![npm version](https://img.shields.io/npm/v/dsh-paste-code-block?logo=npm)](https://www.npmjs.com/package/dsh-paste-code-block)
10
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](./LICENSE)
11
+ [![platform: web](https://img.shields.io/badge/platform-DSH%20Web-blueviolet)](https://github.com/deepseek-ai/deepseek-harness)
12
12
 
13
13
  **[English](./README.md) | 中文**
14
14
 
15
15
  </div>
16
16
 
17
- ---
17
+ ![预览:粘贴的块自动收成整洁卡片](./docs/images/hero.png)
18
+
19
+ ## 为什么需要它
20
+
21
+ 往聊天输入框里粘贴 200 行报错栈、API 返回、长日志,输入框立刻变成一坨无法阅读的文字墙。本插件把符合特征的粘贴折叠成 **芯片(chip)**(复用 DSH 自带的隐藏引用机制,与文件上传同源)外加一张**可编辑详情卡**——草稿保持整洁,内容随时可读、可改、可还原。
18
22
 
19
23
  ## 功能
20
24
 
21
- - 📋 **智能识别**:粘贴内容已带 ``` 围栏、含换行、或是较长的缩进代码 → 收成卡片;普通短文本照常粘贴。
22
- - 🏷️ **语言标签**:自动识别 Python / JS / JSON / YAML / SQL / bash / HTML·XML / C++ / Go / Ruby 等常见语言(可识别 shebang)。纯文本块标为 `text`。
23
- - 📂 **可折叠卡片**:默认收起显示首行预览,点击展开完整代码(最多 260px 高可滚动)。
24
- - ✏️ **卡片内编辑**:在详情卡片里直接编辑代码块内容,发送时使用编辑后的文本。
25
- - 📤 **发送还原**:发出时把每个卡片还原成 ```` ```lang ``` ```` 围栏代码块;发送失败自动回填回输入框。
26
- - 🔢 **智能编号**:代码与文本分开计数,从最小可用号分配;删除某块即释放其编号供复用。
27
- - 📱 **全端适配**:纯客户端插件,PC 与手机 Web 通用,实时适配深浅主题。
25
+ - 🧩 **智能识别**:粘贴内容带 ``` 围栏、含换行、明显缩进、或长度 ≥ 96 → 收成卡片;普通短文本照常粘贴,绝不打扰。
26
+ - 🏷️ **语言标签**:自动识别 Python / JS / JSON / YAML / SQL / bash / HTML·XML / C++ / Go / Ruby 等(可识别 shebang);纯文本块标为 `text`。
27
+ - ✏️ **卡片内编辑**:详情卡里直接修改内容,发送的正是编辑后的文本;另有复制、折叠/展开、收起。
28
+ - 🈯 **名称本地化**:芯片按当前 DSH 界面语言命名——`复制代码块1` / `Code block #1`、`复制文本块N` / `Text block #N`;在「设置 → 语言」切换后所有芯片即时改名。
29
+ - × **一键删除**:每个芯片自带 `×` 删除按钮(详情卡上也有);删掉的块编号立刻回收复用。
30
+ - 📤 **发送还原**:发送时每个卡片展开还原为 ```` ```lang … ``` ```` 围栏代码块;发送失败自动回填输入框。
31
+ - 🔢 **智能编号**:代码与文本分开计数,永远分配最小可用号。
32
+ - 📱 **全端适配**:纯客户端插件,PC 与手机 Web 通用,实时跟随深浅主题。
33
+
34
+ ## 工作流
35
+
36
+ ![四步:复制 · 粘贴 · 整理 · 发送](./docs/images/workflow.png)
37
+
38
+ ## 名称本地化
39
+
40
+ 芯片名称、按钮提示、行数、发送失效提示全部走 DSH 官方 locale 服务:插件注册自己的 `paste-code-block` 词典命名空间(zh / en 键集完全一致),输入坞声明该命名空间,因此切换语言时所有文案自动重渲染。
41
+
42
+ ![块名称跟随界面语言](./docs/images/languages.png)
43
+
44
+ - **新粘贴**的块按粘贴时的界面语言命名。
45
+ - **已插入的芯片**在 设置 → 语言 切换后原地改名,无需刷新页面。
46
+ - 点击与删除**跨语言匹配**:切到英文后,原来叫 `复制代码块2` 的芯片(此时显示 `Code block #2`)依然指向同一个块,反之亦然。
28
47
 
29
48
  ## 安装
30
49
 
31
- 需要 DSH `0.1.x`(当前为开发者预览版,接口可能变化)。
50
+ 需要 DSH `0.1.x`(开发者预览版,接口可能变化)。已发布到 **npm**,直接按包名安装:
32
51
 
33
52
  ```sh
34
- # 从本目录本地安装
35
- dsh plugin --profile web add file:/path/to/dsh-paste-code-block
53
+ dsh plugin --profile web add dsh-paste-code-block
54
+ ```
36
55
 
37
- # 或打包发布后
38
- # dsh plugin --profile web add dsh-paste-code-block
56
+ 也可以从本仓库或本地目录安装:
57
+
58
+ ```sh
59
+ dsh plugin --profile web add github:cjm-m/dsh-paste-code-block # 从 GitHub
60
+ dsh plugin --profile web add file:/path/to/dsh-paste-code-block # 从本地目录
39
61
  ```
40
62
 
41
63
  安装后**重启 `dsh web`** 生效。
42
64
 
43
65
  ## 使用
44
66
 
45
- 1. 在任意地方复制一段代码 / 一段带换行的文本。
46
- 2. 在 DSH 输入框粘贴 → 变成一张代码块卡片。
47
- 3. 可展开 / 复制 / 编辑 / 移除;回车或发送即还原为围栏代码块发出。
67
+ 1. 在任意地方复制一段代码 / 一段多行文本。
68
+ 2. 在 DSH 输入框粘贴 → 折叠成 `复制代码块1` 这样的芯片。
69
+ 3. 点芯片打开详情卡:阅读、就地编辑、复制、折叠;点芯片(或卡片)上的 `×` 删除该块。
70
+ 4. 发送——完整原文以围栏代码块形式发出。
71
+
72
+ 识别阈值:**含换行 / 行数 ≥ 2 / 长度 ≥ 96 / 缩进且符号密集 / 带围栏**,任一命中即成块。
48
73
 
49
74
  ## 说明与边界
50
75
 
51
- - 默认**追加在草稿末尾**(复用 DSH 隐藏引用按文件/上传的既有语义),保证光标/撤销稳定;短普通文本不拦截。
52
- - 纯文本块发送时用 ```` ```text ``` ```` 围栏包裹,避免在对话里糊成一行。
53
- - 识别阈值:**含换行 / 行数 ≥2 / 长度 ≥96 / 有缩进或围栏**其一即视为块。
76
+ - 块一律**追加在草稿末尾**(复用 DSH 隐藏引用语义),光标与撤销保持稳定。
77
+ - 纯文本块发送时用 ```` ```text ``` ```` 围栏包裹,不会在对话里糊成一行。
78
+ - 用其他方式删除芯片(如 `Backspace`)同样会释放编号——插件状态与编辑器持续对账。
79
+ - 删除单个块绝不影响其它块;删除是一次普通的编辑器编辑,可用 `Ctrl+Z` 撤销找回。
80
+ - 创建块**不会残留空格**:DSH 会在新插入的芯片后自动补一个分隔空格,插件只把这一个字符删掉——因此块后面接着输入的文字不会顶着一个前导空格,连续粘贴多个块也不会在正文里堆出多余空格;你自己敲的空格在粘贴/删除时都不会被动到。
54
81
 
55
82
  ## 目录结构
56
83
 
57
84
  ```
58
85
  dsh-paste-code-block/
59
86
  ├── src/
60
- │ ├── client.js # Web(浏览器侧)半端 —— 粘贴拦截、徽章样式、详情卡片
61
- │ ├── index.js # Host(Node 侧)半端 —— 有意为空(纯 UI 插件)
62
- │ └── parse.js # 纯文本块解析逻辑(规范版,含单元测试)
87
+ │ ├── client.js # Web(浏览器侧)—— 粘贴拦截、芯片、详情卡片、× 删除
88
+ │ ├── index.js # Host(Node 侧)—— 有意为空(纯 UI 插件)
89
+ │ ├── parse.js # 纯块识别逻辑(规范版,含单元测试)
90
+ │ └── i18n.js # 词典与块名称解析(规范版,含单元测试)
63
91
  ├── tests/
64
- │ └── parse.test.js # node:test 单元测试(针对 src/parse.js)
92
+ │ ├── parse.test.js # node:test 单元测试(src/parse.js)
93
+ │ └── i18n.test.js # node:test 单元测试(src/i18n.js)
65
94
  ├── docs/
66
- │ └── design.md # 设计说明(编号复用、锚点稳定、codec)
95
+ │ ├── design.md # 设计说明(编号复用、锚点稳定、codec、i18n、芯片 ×)
96
+ │ └── images/ # README 示意图(PNG + 可编辑 SVG 源)
67
97
  ├── cordis.patch.yml # DSH bundle patch,声明宿主插件
68
98
  ├── package.json # 包与 DSH 插件清单
69
99
  ├── README.md # English
@@ -73,10 +103,10 @@ dsh-paste-code-block/
73
103
  ## 开发
74
104
 
75
105
  ```sh
76
- npm run check # 语法检查两个 JS 半端
77
- npm test # 运行单元测试(node:test)
106
+ npm run check # 语法检查 JS 半端
107
+ npm test # 运行单元测试(node:test,38 项)
78
108
  ```
79
109
 
80
110
  ## License
81
111
 
82
- MIT — 见 [LICENSE](./LICENSE)。
112
+ MIT — 见 [LICENSE](./LICENSE)。
package/docs/design.md CHANGED
@@ -16,10 +16,14 @@ A DSH plugin can ship a **host** (node) half and a **client** (browser) half. Th
16
16
  reference chips into boxed badges, and renders an editing detail card above the composer.
17
17
 
18
18
  Because the client is loaded by DSH's module loader — which resolves *package names*, not
19
- relative ESM files — `client.js` **cannot** `import` a sibling module. Its parsing logic is
19
+ relative ESM files — `client.js` **cannot** `import` a sibling module. Its runtime logic is
20
20
  therefore **embedded** in the bundle. To keep that logic unit-testable, the same functions are
21
- extracted into `src/parse.js` (a plain, dependency-free ESM module) which the test suite
22
- exercises. **Keep `src/parse.js` and the embedded copy in `src/client.js` in sync.**
21
+ extracted into plain, dependency-free ESM modules which the test suite exercises:
22
+
23
+ - `src/parse.js` — block detection / language detection / serialization.
24
+ - `src/i18n.js` — locale dictionaries, label building, and label parsing.
25
+
26
+ **Keep `src/parse.js` / `src/i18n.js` and their embedded copies in `src/client.js` in sync.**
23
27
 
24
28
  ## 2. How a paste becomes a card (the pipeline)
25
29
 
@@ -44,8 +48,9 @@ mechanism that renders file/upload references inline. The plugin registers a sou
44
48
  - `codec.clipboardText` → `''` (the chip leaves no visible text on copy)
45
49
  - `codec.serialize(ref)` → the block's fenced ```` ```lang … ``` ```` text
46
50
 
47
- The chip label is the human-readable badge (`复制代码块N` / `复制文本块N`); clicking it opens the
48
- detail card.
51
+ The chip label is the localized human-readable badge (`复制代码块N` / `Code block #N` — see §9);
52
+ clicking it opens the detail card, and each chip carries a `×` button that deletes the block
53
+ (see §10).
49
54
 
50
55
  ## 4. Why `clipboardText` is U+200B (zero-width space)
51
56
 
@@ -84,10 +89,108 @@ send fails — so a failed send auto-refills the input.
84
89
 
85
90
  ## 8. Testing strategy
86
91
 
87
- `tests/parse.test.js` exercises the pure parsing module (`src/parse.js`) with the built-in
88
- `node:test` runner (no test framework dependency). The DOM/controller glue is not unit-tested
89
- because it depends on the live DSH client runtime; it is verified manually in the browser.
92
+ `tests/parse.test.js` and `tests/i18n.test.js` exercise the two pure modules (`src/parse.js`,
93
+ `src/i18n.js`) with the built-in `node:test` runner (no test framework dependency). The
94
+ DOM/controller glue is not unit-tested because it depends on the live DSH client runtime; it is
95
+ verified manually in the browser.
90
96
 
91
97
  ```sh
92
98
  npm test
93
- ```
99
+ ```
100
+
101
+ ## 9. Localization (i18n)
102
+
103
+ Block names are produced through **DSH's own locale service**, the same one first-party plugins
104
+ (schedule, session-log-export, …) use:
105
+
106
+ - `apply()` registers the `paste-code-block` namespace via `ctx.locale.register(NS, {zh, en})`
107
+ with *identical key sets* in both dictionaries (the built-in locale runtime assumes that).
108
+ - All user-visible strings — chip labels (`block.code` / `block.text` with `{n}`), button
109
+ tooltips, the `{count}` line counter (with a `lines.one` / `lines.other` plural split),
110
+ aria labels, slot label, and the stale-block send error — go through the bound translator
111
+ `t(key, params)`.
112
+ - The dock slot entry declares `locale: NS`, so the renderer hands it a fresh `t` seat **and
113
+ re-renders it on every locale revision**.
114
+
115
+ **Chip retitling across switches.** The chip label is *insert-time* content: Lexical caches the
116
+ label inside the decorator node, and React never re-renders those node bodies while the node
117
+ state is unchanged. So on a locale switch the plugin: (1) recomputes `block.label` for every
118
+ tracked block and rebuilds `labelIndex` (`retitleAll`), then (2) patches the visible DOM —
119
+ `title` attribute and label span `.textContent` — inside `scanChips`. React's virtual DOM
120
+ record still says the old label, so it never fights the patch; a full chip remount (the only
121
+ revert vector) is caught by the same MutationObserver that manages the ✕ button.
122
+
123
+ **Cross-language identity.** `parseBlockLabel` recognizes a rendered label in **any** shipped
124
+ locale and recovers `{ type, n }`; lookups (chip click, ✕ delete, retitle matching) resolve the
125
+ block by that identity within the chip's own session. A chip inserted under one language keeps
126
+ working after switching to the other.
127
+
128
+ ## 10. The per-chip ✕ delete button
129
+
130
+ DSH renders chips from Lexical decorator nodes; third parties cannot add React children to
131
+ them. Instead the plugin **appends a `<span class="dsh-pcb-chip-x">×</span>` into each chip's
132
+ inner element** and keeps them present with a `MutationObserver` on `document.body`
133
+ (rAF-coalesced; the scan exits immediately when no block is tracked). A capture-phase
134
+ `pointerdown` handler checks `.dsh-pcb-chip-x` **before** the toggle logic and calls
135
+ `controller.remove`, which removes **just that chip's Lexical node** and frees its number. If
136
+ the chip is not backed by a tracked block (e.g. a restored draft after redraw), no ✕ is added —
137
+ no dead affordances. Touch targets grow on coarse pointers (`@media (pointer:coarse)`).
138
+
139
+ **Why node-level removal matters — the 0.1.1 incident.** The first `remove()` implementation
140
+ spliced the chip's placeholder char out of the draft string and wrote the result through
141
+ `shell.setDraft()`. But `setDraft` **clears the editor root and rebuilds plain-text
142
+ paragraphs** — chips are decorator nodes that cannot round-trip through text, so deleting one
143
+ block destroyed *every* chip in the draft. `removeChipNodes()` instead scans the editor's node
144
+ map for `ReferenceChipNode`s whose public `getSource()`/`getReference()` match this plugin and
145
+ the block's ref, and calls the node's own `remove()` inside one discrete update — a surgical,
146
+ undoable (`Ctrl+Z`) deletion that leaves sibling chips untouched.
147
+
148
+ **The 0.1.2 silent no-op.** The node map (`editor.getEditorState()._nodeMap`) is keyed by
149
+ NodeKey and **its values are the nodes themselves**; the older `NodeState` wrapper with a
150
+ `.node` field no longer exists. Reading `value.node` therefore matched *nothing* for every
151
+ entry, `removeChipNodes()` removed zero chips, yet the call looked successful — the block's
152
+ state was pruned behind a chip that was still in the draft, so clicking ✕ appeared to do
153
+ nothing at all. `src/chip-nodes.js` now normalizes both shapes (`chipNodeOf`) and is unit
154
+ tested for exactly this rule; `tests/lexical-chip-probe.mjs` reproduces the original defect
155
+ against real `lexical@0.49.0`:
156
+
157
+ ```
158
+ _nodeMap value shape: valueIsNode=true, hasNodeProp=false
159
+ OLD code (value.node): removed = 0 | chips after = r1,r2,r3 <- silent no-op
160
+ NEW code (value is node): removed = 1 | chips after = r1,r3 <- only the target
161
+ ```
162
+
163
+ **Failure is never silent and never destructive.** `remove()` gates every state change on
164
+ `chipPresent()` — the editor's node map first, the published occurrence projection as a
165
+ cross-check — so a block is pruned only once its chip is verifiably gone. If the chip survives
166
+ the attempt the block stays tracked and the composer surfaces `error.remove`; the old
167
+ `setDraft` fallback is gone for good. State that diverges from the editor still self-heals via
168
+ `reconcile`.
169
+
170
+ ## 11. Insertion leaves no stray space
171
+
172
+ `SessionInputShell.insertReference` inserts `[chip, ' ']` — a separating space — whenever the
173
+ character at the pick-time span is not itself a space. Because a block is always appended at
174
+ the **end** of the draft, that condition is always true, so every paste used to leave a space
175
+ behind: text typed next started with a space (the visible "prefix space"), and pasting several
176
+ blocks piled stray spaces into the message body.
177
+
178
+ Two rules keep the draft clean without ever touching user whitespace:
179
+
180
+ 1. **Insert, then drop exactly the appended character.** `dropSeparatorSpace()` asks
181
+ `separatorSpaceSpan()` (src/projection.js) for the detect span of the character directly
182
+ after *our* chip and deletes it with the same public `slash/input-insert-text` verb DSH uses
183
+ for its own span edits. Since the chip is inserted at the end, that character can only have
184
+ come from the insertion itself — a trailing space the user had already typed still sits
185
+ before the chip, untouched. The span is only used when the projection shows a space right
186
+ after this block's chip, and a failure to apply just logs (the draft keeps DSH's behaviour).
187
+ 2. **Delete removes the chip node only.** `remove()` drops the chip's Lexical node
188
+ (§10) and never rewrites text, so spaces around a deleted block survive exactly as they were.
189
+
190
+ `src/projection.js` owns the coordinate conversion this needs: the published `draft` is the
191
+ clipboard projection (a chip occupies `occurrence.length` characters, i.e. one zero-width space
192
+ for us) while the scoped edit verbs address detect coordinates (a chip collapses to exactly one
193
+ placeholder character). `detectOffsetOf()` converts with
194
+ `detect = clipboard - Σ(len_i - 1)` over preceding occurrences, so blocks stay correctly
195
+ 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).
Binary file
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="700" viewBox="0 0 1200 700">
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="142" height="27" rx="7" fill="#1d2742"/><rect x="80" y="168" width="142" height="27" rx="7" fill="none" stroke="#3a4a75" stroke-width="1"/><text x="91" y="186" font-size="12.5" fill="#cdd9f2" font-family="Noto Sans SC">复制代码块1</text><text x="206" y="187" font-size="15" fill="#ff7b7b" font-family="Noto Sans SC" text-anchor="middle">×</text><rect x="236" y="168" width="142" height="27" rx="7" fill="#1d2742"/><rect x="236" y="168" width="142" height="27" rx="7" fill="none" stroke="#3a4a75" stroke-width="1"/><text x="247" y="186" font-size="12.5" fill="#cdd9f2" font-family="Noto Sans SC">复制文本块1</text><text x="362" y="187" font-size="15" fill="#ff7b7b" font-family="Noto Sans SC" text-anchor="middle">×</text><rect x="392" y="168" width="142" height="27" rx="7" fill="#1d2742"/><rect x="392" y="168" width="142" height="27" rx="7" fill="none" stroke="#3a4a75" stroke-width="1"/><text x="403" y="186" font-size="12.5" fill="#cdd9f2" font-family="Noto Sans SC">复制代码块2</text><text x="518" y="187" font-size="15" fill="#ff7b7b" font-family="Noto Sans SC" text-anchor="middle">×</text><circle cx="518" 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
+ </svg>
Binary file
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="464" viewBox="0 0 1200 464">
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="142" height="27" rx="7" fill="#1d2742"/><rect x="60" y="158" width="142" height="27" rx="7" fill="none" stroke="#3a4a75" stroke-width="1"/><text x="71" y="176" font-size="12.5" fill="#cdd9f2" font-family="Noto Sans SC">复制代码块1</text><text x="186" y="177" font-size="15" fill="#ff7b7b" font-family="Noto Sans SC" text-anchor="middle">×</text><rect x="210" y="158" width="142" height="27" rx="7" fill="#1d2742"/><rect x="210" y="158" width="142" height="27" rx="7" fill="none" stroke="#3a4a75" stroke-width="1"/><text x="221" y="176" font-size="12.5" fill="#cdd9f2" font-family="Noto Sans SC">复制文本块1</text><text x="336" 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="150" height="27" rx="7" fill="#1d2742"/><rect x="640" y="158" width="150" height="27" rx="7" fill="none" stroke="#3a4a75" stroke-width="1"/><text x="651" y="176" font-size="12.5" fill="#cdd9f2" font-family="Noto Sans SC">Code block #1</text><text x="774" y="177" font-size="15" fill="#ff7b7b" font-family="Noto Sans SC" text-anchor="middle">×</text><rect x="798" y="158" width="150" height="27" rx="7" fill="#1d2742"/><rect x="798" y="158" width="150" height="27" rx="7" fill="none" stroke="#3a4a75" stroke-width="1"/><text x="809" y="176" font-size="12.5" fill="#cdd9f2" font-family="Noto Sans SC">Text block #1</text><text x="932" 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
+ </svg>
Binary file
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="360" viewBox="0 0 1200 360">
2
+ <rect width="1200" height="360" fill="#0d1220"/>
3
+ <text x="40" y="52" font-size="22" fill="#e6ecf8" font-family="Noto Sans SC" font-weight="700">使用只要四步 · Four steps, zero noise</text><rect x="40" y="92" width="247" height="190" rx="14" fill="#151c2f"/><rect x="40" y="92" width="247" height="190" rx="14" fill="none" stroke="#2a3450" stroke-width="1"/><circle cx="74" cy="132" r="17" fill="#3b5bdb"/><text x="74" y="138" font-size="17" fill="#fff" font-family="Noto Sans SC" text-anchor="middle">1</text><text x="102" y="139" font-size="18" fill="#e6ecf8" font-family="Noto Sans SC" font-weight="700">复制</text><rect x="196" y="118" width="26" height="30" rx="4" fill="none"/><rect x="196" y="118" width="26" height="30" rx="4" fill="none" stroke="#7aa2ff" stroke-width="1"/><rect x="202" y="113" width="14" height="10" rx="3" fill="#151c2f"/><rect x="202" y="113" width="14" height="10" rx="3" fill="none" stroke="#7aa2ff" stroke-width="1"/><line x1="202" y1="127" x2="216" y2="127" stroke="#93a3c0" stroke-width="1.5"/><line x1="202" y1="134" x2="216" y2="134" stroke="#93a3c0" stroke-width="1.5"/><line x1="202" y1="141" x2="212" y2="141" stroke="#93a3c0" stroke-width="1.5"/><text x="62" y="186" font-size="13" fill="#93a3c0" font-family="Noto Sans SC">在任意应用里复制一段代码或长文本</text><text x="62" y="226" font-size="11.5" fill="#5b6a8a" font-family="Noto Sans SC">Copy code or long text anywhere</text><path d="M 295 187 h 60 m -10 -7 l 10 7 l -10 7" stroke="#5b6a8a" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><rect x="331" y="92" width="247" height="190" rx="14" fill="#151c2f"/><rect x="331" y="92" width="247" height="190" rx="14" fill="none" stroke="#2a3450" stroke-width="1"/><circle cx="365" cy="132" r="17" fill="#3b5bdb"/><text x="365" y="138" font-size="17" fill="#fff" font-family="Noto Sans SC" text-anchor="middle">2</text><text x="393" y="139" font-size="18" fill="#e6ecf8" font-family="Noto Sans SC" font-weight="700">粘贴</text><rect x="481" y="122" width="44" height="24" rx="6" fill="#0b1120"/><rect x="481" y="122" width="44" height="24" rx="6" fill="none" stroke="#232c45" stroke-width="1"/><line x1="489" y1="129" x2="489" y2="139" stroke="#7aa2ff" stroke-width="2"/><text x="353" y="186" font-size="13" fill="#93a3c0" font-family="Noto Sans SC">粘到 DSH 输入框(Ctrl+V</text><text x="353" y="206" font-size="13" fill="#93a3c0" font-family="Noto Sans SC"> / 长按)</text><text x="353" y="246" font-size="11.5" fill="#5b6a8a" font-family="Noto Sans SC">Paste into the DSH composer</text><path d="M 586 187 h 60 m -10 -7 l 10 7 l -10 7" stroke="#5b6a8a" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><rect x="622" y="92" width="247" height="190" rx="14" fill="#151c2f"/><rect x="622" y="92" width="247" height="190" rx="14" fill="none" stroke="#2a3450" stroke-width="1"/><circle cx="656" cy="132" r="17" fill="#3b5bdb"/><text x="656" y="138" font-size="17" fill="#fff" font-family="Noto Sans SC" text-anchor="middle">3</text><text x="684" y="139" font-size="18" fill="#e6ecf8" font-family="Noto Sans SC" font-weight="700">整理</text><rect x="772" y="122" width="44" height="24" rx="12" fill="#1d2742"/><rect x="772" y="122" width="44" height="24" rx="12" fill="none" stroke="#3a4a75" stroke-width="1"/><text x="780" y="139" font-size="11" fill="#d6deeb" font-family="Noto Sans SC">Aa</text><text x="806" y="140" font-size="13" fill="#ff7b7b" font-family="Noto Sans SC">×</text><text x="644" y="186" font-size="13" fill="#93a3c0" font-family="Noto Sans SC">自动收成卡片:点芯片看详情,× 删</text><text x="644" y="206" font-size="13" fill="#93a3c0" font-family="Noto Sans SC">除</text><text x="644" y="246" font-size="11.5" fill="#5b6a8a" font-family="Noto Sans SC">Auto card: open · edit · remove ×</text><path d="M 877 187 h 60 m -10 -7 l 10 7 l -10 7" stroke="#5b6a8a" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><rect x="913" y="92" width="247" height="190" rx="14" fill="#151c2f"/><rect x="913" y="92" width="247" height="190" rx="14" fill="none" stroke="#2a3450" stroke-width="1"/><circle cx="947" cy="132" r="17" fill="#3b5bdb"/><text x="947" y="138" font-size="17" fill="#fff" font-family="Noto Sans SC" text-anchor="middle">4</text><text x="975" y="139" font-size="18" fill="#e6ecf8" font-family="Noto Sans SC" font-weight="700">发送</text><path d="M 1063 134 L 1103 120 L 1089 142 L 1083 134 Z" stroke="#7aa2ff" stroke-width="1.6" fill="none" stroke-linecap="round" stroke-linejoin="round"/><text x="935" y="186" font-size="13" fill="#93a3c0" font-family="Noto Sans SC">还原为 ```lang 围栏代码块</text><text x="935" y="206" font-size="13" fill="#93a3c0" font-family="Noto Sans SC">发出</text><text x="935" y="246" font-size="11.5" fill="#5b6a8a" font-family="Noto Sans SC">Sent back as fenced code</text><text x="40" y="326" font-size="12.5" fill="#5b6a8a" font-family="Noto Sans SC">代码 / Markdown / 普通长文本自动识别;短普通文本照常粘贴,不打扰</text>
4
+ </svg>
package/package.json CHANGED
@@ -1,9 +1,17 @@
1
1
  {
2
2
  "name": "dsh-paste-code-block",
3
- "version": "0.1.0",
4
- "description": "Cherry Studio-style paste: pasted text/code blocks into the DSH Web composer render as a bordered, collapsible, language-tagged card instead of a long flat string; the original text is restored as a fenced block on send.",
3
+ "version": "0.1.2",
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.",
5
5
  "license": "MIT",
6
6
  "author": "DSH paste-code-block contributors",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/cjm-m/dsh-paste-code-block.git"
10
+ },
11
+ "homepage": "https://github.com/cjm-m/dsh-paste-code-block#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/cjm-m/dsh-paste-code-block/issues"
14
+ },
7
15
  "keywords": [
8
16
  "dsh",
9
17
  "deepseek-harness",
@@ -11,6 +19,7 @@
11
19
  "paste",
12
20
  "code-block",
13
21
  "cherry-studio",
22
+ "i18n",
14
23
  "ui"
15
24
  ],
16
25
  "type": "module",
@@ -19,6 +28,9 @@
19
28
  ".": "./src/index.js",
20
29
  "./client": "./src/client.js",
21
30
  "./parse": "./src/parse.js",
31
+ "./i18n": "./src/i18n.js",
32
+ "./chip-nodes": "./src/chip-nodes.js",
33
+ "./projection": "./src/projection.js",
22
34
  "./package.json": "./package.json"
23
35
  },
24
36
  "files": [
@@ -39,7 +51,8 @@
39
51
  "inject": [
40
52
  "@deepseek-ai/dsh-client-ui-conversation",
41
53
  "@deepseek-ai/dsh-client-ui-input-trigger",
42
- "@deepseek-ai/dsh-client-ui-slots"
54
+ "@deepseek-ai/dsh-client-ui-slots",
55
+ "@deepseek-ai/dsh-client-locale"
43
56
  ]
44
57
  }
45
58
  },
@@ -48,19 +61,21 @@
48
61
  "@deepseek-ai/dsh-client-ui-conversation": "*",
49
62
  "@deepseek-ai/dsh-client-ui-input-trigger": "*",
50
63
  "@deepseek-ai/dsh-client-ui-slots": "*",
64
+ "@deepseek-ai/dsh-client-locale": "*",
51
65
  "react": "^18.2.0"
52
66
  },
53
67
  "peerDependenciesMeta": {
54
68
  "@deepseek-ai/dsh-client-ui-conversation": { "optional": true },
55
69
  "@deepseek-ai/dsh-client-ui-input-trigger": { "optional": true },
56
70
  "@deepseek-ai/dsh-client-ui-slots": { "optional": true },
71
+ "@deepseek-ai/dsh-client-locale": { "optional": true },
57
72
  "react": { "optional": true }
58
73
  },
59
74
  "engines": {
60
75
  "node": ">=20"
61
76
  },
62
77
  "scripts": {
63
- "check": "node --check src/index.js && node --check src/client.js",
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",
64
79
  "test": "node --test 'tests/**/*.test.js'"
65
80
  }
66
81
  }