Skip to content

Commit 4873db5

Browse files
committed
fix: let a shortcode be typed as inline code
Converting :name: right after an unclosed backtick deleted the opening backtick, so `:name:` could never be typed as a code span. The swap now skips a shortcode preceded by an unclosed backtick, leaving the text for the code input rule to close; code text is already exempt everywhere. An emoji picked from the popup still drops the dangling backtick, since inline code cannot close across the emoji.
1 parent ec69181 commit 4873db5

3 files changed

Lines changed: 15 additions & 7 deletions

File tree

‎apps/web/src/components/features/editor/customEmoji.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ export const CustomEmoji = Node.create({
3737
renderText: ({ node }) => node.attrs.alt ?? "",
3838

3939
// A known `:name:` is always the emoji: typed, pasted, or already in a draft or a
40-
// message being edited.
40+
// message being edited. Exception: after an unclosed backtick it stays text, so
41+
// `:name:` can be typed as inline code.
4142
addProseMirrorPlugins() {
4243
return [
4344
new Plugin({

‎apps/web/src/components/features/editor/customEmojiInput.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ getDefaultStore().set(customEmojiCategoriesAtom, [
4646
])
4747

4848
describe("customEmojiInputTransaction", () => {
49-
it("swaps a typed shortcode and drops the unclosed backtick before it", () => {
50-
expect(show(run(stateOf(text("run `:party")), (tr) => tr.insertText(":", 12)))).toBe("run [party]")
49+
it("leaves a shortcode after an unclosed backtick, so inline code can close", () => {
50+
expect(show(run(stateOf(text("run `:party")), (tr) => tr.insertText(":", 12)))).toBe("run `:party:")
5151
})
5252

5353
it("keeps a closed pair of backticks", () => {

‎apps/web/src/components/features/editor/customEmojiInput.ts‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,11 @@ const changedRanges = (doc: PMNode, transactions?: readonly Transaction[]): [num
4747
}
4848

4949
/**
50-
* Swaps each known `:name:` in the changed paragraphs for its emoji (code keeps it), and
51-
* drops the unclosed backtick before each emoji added, as that inline code could never close.
50+
* Swaps each known `:name:` in the changed paragraphs for its emoji — except in code,
51+
* and except after an unclosed backtick: the writer may be typing the shortcode as
52+
* inline code, so the text stays for the code input rule to close. An emoji the picker
53+
* or popup inserts still drops the unclosed backtick before it, as that inline code
54+
* can no longer close across the emoji.
5255
* Without `transactions`, checks the whole document. Null when nothing changes.
5356
*/
5457
export const customEmojiInputTransaction = (
@@ -73,8 +76,12 @@ export const customEmojiInputTransaction = (
7376
for (const part of splitCustomEmojiShortcodes(child.text ?? "", shortcodes) ?? []) {
7477
if (typeof part === "string") at += part.length
7578
else {
76-
swaps.push({ from: at, to: at + part.shortcode.length, marks: child.marks, ...part })
77-
added.add(at)
79+
// After an unclosed backtick, keep the text: the writer may be
80+
// closing `:name:` into inline code.
81+
if (unclosedBacktick(state.doc, at) === null) {
82+
swaps.push({ from: at, to: at + part.shortcode.length, marks: child.marks, ...part })
83+
added.add(at)
84+
}
7885
at += part.shortcode.length
7986
}
8087
}

0 commit comments

Comments
 (0)