chendefine/dsh-composer-markdown ↗★ 0
dsh-composer-markdown
DSH Web 插件,用于增强对话输入框的 Markdown 编辑体验。提供列表自动续行、层级缩进、行内代码样式及代码块围栏自动闭合等功能,保持发送文本的字面保真度。
AI 分析
核心用于提升 DSH 对话框中 Markdown 文本的输入效率。适合频繁编写格式化文本的用户,需配合 DSH 0.1.2-alpha.x 及 Node.js 环境使用。
安装
$
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:chendefine/dsh-composer-markdown说明文档
阅读完整 README ↗Usage
Every editing gesture rides on Shift+Enter; Enter = native submit, never intercepted; a Shift+Enter that matches no scenario = the native soft break. Tab / Shift+Tab are claimed only when the caret's line is a list item (the level ladder) — everywhere else they stay native (focus traversal and the like). Plain Backspace / Delete / arrow keys are claimed only when they hit exactly the boundaries below — everything else keeps the native per-character behavior.
Lists
| Operation | Trigger | Behavior |
|---|---|---|
| Bullet/ordered continuation | Type content after a line-head - / * / N. (any indent depth), press Shift+Enter | The new line below is auto-seeded with the {indent}- / {n+1}. prefix (nesting level preserved), caret right after it; works on any visual line — paragraph heads, soft-broken lines, and multi-line-pasted lines alike |
| Split at caret | Shift+Enter with the caret mid-content of a list item | The line is cut at the caret: everything after it moves down and becomes the new item's content, spliced after the new prefix (byte-faithful); a caret at the line start / inside the marker / at the line end keeps the append-below behavior |
| Mid-insert shift-down | Shift+Enter inserting a new item mid-group | The new line takes n+1 and every member below in the same group shifts +1 (1. 2. 3. inserting between 1 and 2 → 1. 2. 3. 4.); the group stays continuous and duplicate-free; insert and shift share one undo step |
| Renumbering (the invariant) | After any change (delete/insert a line, empty-item exit, paste, undo/redo) | Every ordered group (consecutive ordered lines, one indent, outside fences) is always numbered 1. … n. from its first member: a deleted line closes the gap below; a group split by a plain/blank/bullet line restarts its tail at 1.; deleting the splitting line merges the two runs into one continuous count; deeper indents merely suspend the outer group (a nested sublist never interrupts the outer run — its count resumes when the outer indent returns), nested groups normalize independently. Deliberate trade: a group cannot hold a non-1 start or a manual gap — hand-typed digits snap back (continuity IS the contract) |
| Empty-item exit | Shift+Enter on an item that is only a prefix | The prefix is removed and the empty paragraph kept (an empty line on soft lines) — press twice to cleanly leave the list |
| Level ladder — sink | Tab with the collapsed caret anywhere on a list item's line (mid-content included) | The item and every deeper line below it (nested sublists, deeper-continuation text — the whole subtree, recursively) each gain 2 spaces of indent; same-level siblings and everything after them are untouched; the caret rides its in-line position; the whole subtree moves in one keystroke, one undo step; the reshuffled ordered groups normalize on the invariant right after (a 2. nested under a parent renumbers to that nested run's 1.). Sinking is capped (v2.9): an item may sit at most ONE level below its parent — the nearest item line above (plain lines skipped, blanks/fences ending the search). An item already one level below its parent, and a list's FIRST item (nothing above to nest under), get a claimed-but-inert no-op on Tab (focus never jumps away mid-list-editing) — the ladder cannot descend without bound |
| Level ladder — lift / unlist | Same positions, Shift+Tab — or Backspace with the collapsed caret right after the list atom (at the content head) | A nested item (indent ≥ 1 space) → the item and its whole subtree each lose min(2, indent) spaces (odd hand-typed indents floor at 0); a top-level item → unlisted: the whole atom dies, the content stays as a plain-text line, the subtree still rises one level. The plain line an unlist leaves behind splits an ordered group — the same-indent members below restart at 1. per the invariant. Holding Tab sinks once and then holds at the cap; holding Shift+Tab climbs one rung per repeat — focus never escapes an item line on either key |
| Atomic atom delete | Delete with the collapsed caret right at a list item's line head (before the whole atom) | The indent + marker atom goes in one stroke (␣␣1. — all of it), the line becomes top-level plain text, its own undo step; the indent can never be eaten character by character |
| Atomic arrow hop over atoms | Plain ← / → with the collapsed caret at any list-atom boundary | → at the line head jumps clear over the whole ␣␣1. / ␣␣- (indent included) to the content's first character; ← right after the atom jumps back to the line head; a caret already inside the atom (including inside the indent spaces — a click, a vertical move) exits to the far edge — the keyboard never walks the atom interior. Shift+arrows stay native (a selection can still cover exactly the digit characters for manual renumbering) |
| Differentiated marker rendering | Any visual line headed by - / * / N. (outside fences, empty prefixes included) | Ordered digits switch to the code font; the bullet dash/star renders as • (dot + space as one marker unit); the marker bytes are kept verbatim in the draft/copy/send text; a selection genuinely covering a marker character reveals the raw character |
| No resting inside an atom | A collapsed caret landing inside a list atom (indent spaces included) via any path (↑/↓, mouse click) | The caret is homed to the nearest atom edge (ties snap to the line head); the edges themselves (line head / content head) are legal rest points; selections are unaffected |
Inline code
- When a line holds a paired
`non-empty`whose content stays on one line and whose inner-edge characters are non-blank (full-width space / NBSP included): the inner text gets the inline-code look (code font, light background, rounded corners — reusing the DSH theme tokens); - The paired backticks hide the instant the pair closes (invisible, zero width, still kept byte-for-byte in the draft and the sent text); they stay hidden while the caret travels inside the pair or brushes either side;
- Only a selection genuinely covering one of the backtick characters (Shift+arrows, select-all) temporarily reveals the pair (select-what-you-see); it hides again once the selection collapses; a cross-paragraph selection (Ctrl+A) projects per paragraph — no misses at paragraph boundaries;
- Non-matching pairs are fully inert (their backticks stay visible and never affect later pairing);
``-style multi-backtick delimiters are not recognized (naive left-to-right pairing).
Code blocks
| Operation | Trigger | Behavior |
|---|---|---|
| Skeleton closing | The caret's line is exactly ``` (optionally with a language id ```ts) and the caret sits inside/after the marker run, Shift+Enter | Inserts the three-line skeleton ``` / empty / ```, caret at the head of the empty line; works when typed at the head of any line (soft-broken and pasted lines included); a caret before the marker run (bare ``` line, offset 0) is plain text and keeps the native soft break |
| Content-preserving flow | Type ``` at the head of a line that already has content (caret still right after it), Shift+Enter | Splits after the ```: the marker line keeps ```, an empty line + closing skeleton is inserted, and everything after ``` on the original line moves below the closing marker (outside the block, byte-faithful); the language-id flow is unaffected (```ts at line end still yields the badged skeleton) |
| Paired rendering | A paired ```…``` region in the draft | The whole region renders as one code block: code background + code font inside (the same theme tokens and geometry as the message-side CodeBlock); the first marker line is zero-height, the language id shows as a floating badge at the block's top-right; the ``` markers are fully imperceptible (invisible, never host the caret); an unclosed ``` is plain text, always — typing ``` mid-draft never swallows the content below into a block |
| One-keystroke unwrap | Backspace / Delete at a block boundary (← at the first body line's head / the paragraph after the block; → at the paragraph before the block / the last body line's end) | One keystroke removes the pair of ``` marker lines: the block un-renders, the body survives verbatim as plain paragraphs, the caret stays put — its own undo step |
| Leaving the block | ↓ at the last body line's end (an empty paragraph grows below when the block ends the draft) | The caret drops to the new paragraph below the block to continue typing plain text; ↑/↓ skip the marker lines at block boundaries |
| New paragraph inside a fence | Shift+Enter inside a fence (the closing line included) | Inserts a new paragraph (not a soft break), split at the caret; plain Enter inside a fence still submits |
Coexistence with native behavior
- Enter: keeps DSH's native "submit" and is never intercepted (on list lines and inside fences alike);
- Ctrl/Cmd+Enter: keeps the native "accelerated submit";
- Shift+Enter (no scenario matched): the native soft break (
); - Tab / Shift+Tab (not a list-item line, inside a fence, a range selection, or a modifier chord): fully native (browser focus traversal etc.), never intercepted; likewise yielded while a trigger menu is open; held repeats on a list-item line re-plan like any press (they never fall through to focus traversal);
- While a
/or@trigger menu is open: the Enter family is fully yielded; - IME (e.g. Chinese input): keys during a composition trigger nothing (three-signal guard:
isComposing/keyCode 229/ a 10 ms window aftercompositionend— the same guard the DSH input machine itself uses); - Slash-command claims,
@reference chips, image drag-drop/paste, busy/locked states, and Q&A/approval cards are unaffected; - Coexisting with other composer plugins: capture-phase first-come-first-served — a key already
preventDefault-ed is yielded to; keys this plugin consumes getstopImmediatePropagation.