dsh-paste-code-block 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/docs/design.md +11 -2
- 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 +18 -7
- package/src/client.js +42 -11
- package/src/i18n.js +23 -13
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ 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
31
|
- 🔢 **Smart numbering** — code and text count apart; the smallest free number is always reused.
|
|
@@ -43,7 +43,7 @@ Chip labels, tooltips, line counts, and the send-time error notice all render th
|
|
|
43
43
|
|
|
44
44
|
- **New pastes** are named in whatever language is active at paste time.
|
|
45
45
|
- **Existing chips** are re-titled live when you flip 设置 → 语言 / Settings → Language — no reload needed.
|
|
46
|
-
- Clicks and deletes match chips **across languages**: a
|
|
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 #2`), and vice versa.
|
|
47
47
|
|
|
48
48
|
## Install
|
|
49
49
|
|
|
@@ -65,7 +65,7 @@ dsh plugin --profile web add file:/path/to/dsh-paste-code-block # from a local
|
|
|
65
65
|
## Usage
|
|
66
66
|
|
|
67
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
|
|
68
|
+
2. Paste into the DSH input → it collapses into a chip such as `代码块 1` / `Code #1`.
|
|
69
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
70
|
4. Send — the full original text goes out as a proper fenced code block.
|
|
71
71
|
|
package/README.zh-CN.md
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
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
31
|
- 🔢 **智能编号**:代码与文本分开计数,永远分配最小可用号。
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
|
|
44
44
|
- **新粘贴**的块按粘贴时的界面语言命名。
|
|
45
45
|
- **已插入的芯片**在 设置 → 语言 切换后原地改名,无需刷新页面。
|
|
46
|
-
- 点击与删除**跨语言匹配**:切到英文后,原来叫
|
|
46
|
+
- 点击与删除**跨语言匹配**:切到英文后,原来叫 `代码块 2` 的芯片(此时显示 `Code #2`)依然指向同一个块,反之亦然。
|
|
47
47
|
|
|
48
48
|
## 安装
|
|
49
49
|
|
|
@@ -65,7 +65,7 @@ dsh plugin --profile web add file:/path/to/dsh-paste-code-block # 从本地目
|
|
|
65
65
|
## 使用
|
|
66
66
|
|
|
67
67
|
1. 在任意地方复制一段代码 / 一段多行文本。
|
|
68
|
-
2. 在 DSH 输入框粘贴 → 折叠成
|
|
68
|
+
2. 在 DSH 输入框粘贴 → 折叠成 `代码块 1` 这样的芯片。
|
|
69
69
|
3. 点芯片打开详情卡:阅读、就地编辑、复制、折叠;点芯片(或卡片)上的 `×` 删除该块。
|
|
70
70
|
4. 发送——完整原文以围栏代码块形式发出。
|
|
71
71
|
|
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
|
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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-paste-code-block",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
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",
|
|
@@ -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
88
|
"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'"
|
|
89
|
+
"test": "node --test 'tests/**/*.test.js'",
|
|
90
|
+
"render:images": "node docs/images/render.cjs"
|
|
80
91
|
}
|
|
81
92
|
}
|
package/src/client.js
CHANGED
|
@@ -94,8 +94,8 @@ window.__ModuleLoader__.load({
|
|
|
94
94
|
const NS = 'paste-code-block'
|
|
95
95
|
const L10N = {
|
|
96
96
|
zh: {
|
|
97
|
-
'block.code': '
|
|
98
|
-
'block.text': '
|
|
97
|
+
'block.code': '代码块 {n}',
|
|
98
|
+
'block.text': '文本块 {n}',
|
|
99
99
|
'detail.aria': '代码块详情',
|
|
100
100
|
'slot.label': '粘贴代码块详情',
|
|
101
101
|
'lines.one': '{count} 行',
|
|
@@ -111,8 +111,8 @@ window.__ModuleLoader__.load({
|
|
|
111
111
|
'error.remove': '无法删除该块,请重试',
|
|
112
112
|
},
|
|
113
113
|
en: {
|
|
114
|
-
'block.code': 'Code
|
|
115
|
-
'block.text': 'Text
|
|
114
|
+
'block.code': 'Code #{n}',
|
|
115
|
+
'block.text': 'Text #{n}',
|
|
116
116
|
'detail.aria': 'Block details',
|
|
117
117
|
'slot.label': 'Pasted block details',
|
|
118
118
|
'lines.one': '{count} line',
|
|
@@ -147,15 +147,22 @@ window.__ModuleLoader__.load({
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
// Pre-0.1.3 chip labels: no longer rendered, still recognized (see below).
|
|
151
|
+
const LEGACY_L10N = {
|
|
152
|
+
zh: { 'block.code': '复制代码块{n}', 'block.text': '复制文本块{n}' },
|
|
153
|
+
en: { 'block.code': 'Code block #{n}', 'block.text': 'Text block #{n}' },
|
|
154
|
+
}
|
|
155
|
+
|
|
150
156
|
/**
|
|
151
|
-
* Recognize a rendered block label in ANY shipped locale
|
|
152
|
-
* `{ type, n }` identity — this keeps chip
|
|
153
|
-
* locale-switch retitling correct even for
|
|
154
|
-
* different language than the one currently
|
|
157
|
+
* Recognize a rendered block label in ANY shipped locale (or a pre-0.1.3
|
|
158
|
+
* legacy locale) and recover its `{ type, n }` identity — this keeps chip
|
|
159
|
+
* clicks, ✕ deletes, and the locale-switch retitling correct even for
|
|
160
|
+
* chips inserted under a different language than the one currently
|
|
161
|
+
* active. Null otherwise.
|
|
155
162
|
*/
|
|
156
163
|
function parseBlockLabel(text) {
|
|
157
164
|
if (!text) return null
|
|
158
|
-
for (const dict of [L10N.zh, L10N.en]) {
|
|
165
|
+
for (const dict of [L10N.zh, L10N.en, LEGACY_L10N.zh, LEGACY_L10N.en]) {
|
|
159
166
|
for (const key of ['block.code', 'block.text']) {
|
|
160
167
|
const pattern = new RegExp(
|
|
161
168
|
'^' +
|
|
@@ -201,8 +208,8 @@ window.__ModuleLoader__.load({
|
|
|
201
208
|
/**
|
|
202
209
|
* Parse pasted text into a block, or null when it is ordinary short prose
|
|
203
210
|
* that should paste plainly. `isCode` drives both the chip label
|
|
204
|
-
* (i18n key `block.code` vs `block.text`, e.g. "
|
|
205
|
-
*
|
|
211
|
+
* (i18n key `block.code` vs `block.text`, e.g. "代码块 N" / "Code #N")
|
|
212
|
+
* and the fenced language on send.
|
|
206
213
|
*/
|
|
207
214
|
function parseBlock(raw) {
|
|
208
215
|
const text = String(raw || '').replace(/^\uFEFF/, '')
|
|
@@ -849,6 +856,25 @@ window.__ModuleLoader__.load({
|
|
|
849
856
|
/* the label text (the trailing ✕ element is ours — never ellipsis it) */
|
|
850
857
|
[data-composer-chip="${SOURCE}"] > span > span:not(.${CHIP_X_CLASS}){font-weight:500!important;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
851
858
|
|
|
859
|
+
/* --- type identity: leading glyph + tint (data-pcb-type set by scanChips) --- */
|
|
860
|
+
[data-composer-chip="${SOURCE}"][data-pcb-type="code"] > span{
|
|
861
|
+
border-color:var(--dsh-pcb-code-border,rgba(76,110,245,.38))!important;
|
|
862
|
+
background:var(--dsh-pcb-code-bg,rgba(76,110,245,.09))!important;
|
|
863
|
+
color:var(--dsw-alias-state-business-primary,#4c6ef5)!important
|
|
864
|
+
}
|
|
865
|
+
[data-composer-chip="${SOURCE}"][data-pcb-type="code"]:hover > span{
|
|
866
|
+
border-color:var(--dsw-alias-state-business-primary,#4c6ef5)!important;
|
|
867
|
+
background:var(--dsh-pcb-code-bg-hover,rgba(76,110,245,.16))!important
|
|
868
|
+
}
|
|
869
|
+
[data-composer-chip="${SOURCE}"][data-pcb-type="code"] > span::before{
|
|
870
|
+
content:'</>';flex:none;font-family:var(--dsw-font-mono,ui-monospace,Menlo,Consolas,monospace);
|
|
871
|
+
font-size:10px;font-weight:700;line-height:16px;opacity:.9
|
|
872
|
+
}
|
|
873
|
+
[data-composer-chip="${SOURCE}"][data-pcb-type="text"] > span::before{
|
|
874
|
+
content:'Aa';flex:none;font-size:10px;font-weight:700;line-height:16px;
|
|
875
|
+
letter-spacing:.2px;color:var(--dsw-alias-label-tertiary)
|
|
876
|
+
}
|
|
877
|
+
|
|
852
878
|
/* --- per-chip ✕ delete button (element we own, appended inside the chip) --- */
|
|
853
879
|
.${CHIP_X_CLASS}{
|
|
854
880
|
box-sizing:border-box;flex:none;width:16px;height:16px;display:inline-grid;place-items:center;
|
|
@@ -923,6 +949,11 @@ window.__ModuleLoader__.load({
|
|
|
923
949
|
if (!sid) continue
|
|
924
950
|
const block = controller.listFor(sid).find((b) => b.type === parsed.type && b.labelNumber === parsed.n)
|
|
925
951
|
if (!block) continue
|
|
952
|
+
// Tag the chip host with its type so CSS can render the leading
|
|
953
|
+
// glyph and tint (code = blue `</>`, text = neutral `Aa`).
|
|
954
|
+
if (host.getAttribute('data-pcb-type') !== parsed.type) {
|
|
955
|
+
host.setAttribute('data-pcb-type', parsed.type)
|
|
956
|
+
}
|
|
926
957
|
// Retitle the chip to the current language. Lexical caches the
|
|
927
958
|
// insert-time label inside the node, so on a Settings → Language
|
|
928
959
|
// switch (or a chip that remounted with the old label) the DOM is
|
package/src/i18n.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-paste-code-block — locale dictionaries and label logic (pure, no DOM).
|
|
3
3
|
*
|
|
4
|
-
* Canonical source for the user-visible names of pasted blocks ("
|
|
5
|
-
* vs "Code
|
|
4
|
+
* Canonical source for the user-visible names of pasted blocks ("代码块 1"
|
|
5
|
+
* vs "Code #1", etc.). The browser half (`client.js`) embeds its own
|
|
6
6
|
* copy of this logic because DSH's client module loader resolves *package
|
|
7
7
|
* names*, not relative ESM files; this module is the single place where that
|
|
8
8
|
* logic is unit-tested. **Keep `src/i18n.js` and the embedded copy in
|
|
@@ -19,8 +19,8 @@ export const NS = 'paste-code-block'
|
|
|
19
19
|
|
|
20
20
|
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
21
21
|
export const zh = {
|
|
22
|
-
'block.code': '
|
|
23
|
-
'block.text': '
|
|
22
|
+
'block.code': '代码块 {n}',
|
|
23
|
+
'block.text': '文本块 {n}',
|
|
24
24
|
'detail.aria': '代码块详情',
|
|
25
25
|
'slot.label': '粘贴代码块详情',
|
|
26
26
|
'lines.one': '{count} 行',
|
|
@@ -38,8 +38,8 @@ export const zh = {
|
|
|
38
38
|
|
|
39
39
|
/** English dictionary, key-identical to the Chinese source of truth. */
|
|
40
40
|
export const en = {
|
|
41
|
-
'block.code': 'Code
|
|
42
|
-
'block.text': 'Text
|
|
41
|
+
'block.code': 'Code #{n}',
|
|
42
|
+
'block.text': 'Text #{n}',
|
|
43
43
|
'detail.aria': 'Block details',
|
|
44
44
|
'slot.label': 'Pasted block details',
|
|
45
45
|
'lines.one': '{count} line',
|
|
@@ -58,6 +58,16 @@ export const en = {
|
|
|
58
58
|
/** Shipped dictionaries keyed by locale id (mirrors DSH's built-in zh/en set). */
|
|
59
59
|
export const dictionaries = { zh, en }
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Pre-0.1.3 chip labels ("复制代码块N" / "Code block #N" …). No longer
|
|
63
|
+
* rendered, but still recognized by `parseBlockLabel` so chips inserted by an
|
|
64
|
+
* older bundle keep resolving to their block after an in-place page refresh.
|
|
65
|
+
*/
|
|
66
|
+
export const legacyLabels = {
|
|
67
|
+
zh: { 'block.code': '复制代码块{n}', 'block.text': '复制文本块{n}' },
|
|
68
|
+
en: { 'block.code': 'Code block #{n}', 'block.text': 'Text block #{n}' },
|
|
69
|
+
}
|
|
70
|
+
|
|
61
71
|
/**
|
|
62
72
|
* Standalone translator used only when no DSH locale service is present
|
|
63
73
|
* (defensive fallback outside the standard web composition). English is the
|
|
@@ -82,21 +92,21 @@ export function detectBrowserLocale() {
|
|
|
82
92
|
}
|
|
83
93
|
}
|
|
84
94
|
|
|
85
|
-
/** Build a block chip label from a translator:
|
|
95
|
+
/** Build a block chip label from a translator: 代码块 2 / Code #2. */
|
|
86
96
|
export function blockLabel(t, type, n) {
|
|
87
97
|
return t(type === 'code' ? 'block.code' : 'block.text', { n })
|
|
88
98
|
}
|
|
89
99
|
|
|
90
100
|
/**
|
|
91
|
-
* Recognize a rendered block label in ANY shipped locale
|
|
92
|
-
* `{ type, n }` identity. This lets a chip
|
|
93
|
-
* locale-switch retitling find the right block
|
|
94
|
-
* inserted under a different language than the one
|
|
95
|
-
* Returns null for anything that is not a block label.
|
|
101
|
+
* Recognize a rendered block label in ANY shipped locale (or a pre-0.1.3
|
|
102
|
+
* legacy locale) and recover its `{ type, n }` identity. This lets a chip
|
|
103
|
+
* click, an ✕ delete, and the locale-switch retitling find the right block
|
|
104
|
+
* even when the chip was inserted under a different language than the one
|
|
105
|
+
* currently active. Returns null for anything that is not a block label.
|
|
96
106
|
*/
|
|
97
107
|
export function parseBlockLabel(text) {
|
|
98
108
|
if (!text) return null
|
|
99
|
-
for (const dict of [dictionaries.zh, dictionaries.en]) {
|
|
109
|
+
for (const dict of [dictionaries.zh, dictionaries.en, legacyLabels.zh, legacyLabels.en]) {
|
|
100
110
|
for (const key of ['block.code', 'block.text']) {
|
|
101
111
|
const pattern = new RegExp(
|
|
102
112
|
'^' +
|