Repository navigation
Add opt-in Markdown shortcuts to the Lexical plugin - #21
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
registerZettelLexicalPlugin(editor, { markdownShortcuts: true }) enables
input rules for bold, italic, inline code and strike-through markers, for
bullet/numbered lists and quotes at the start of a paragraph, and autolinks
bare http(s) URLs on space, Return and paste (a URL pasted over selected
text links it). Conversions run in their own update tagged history-push, so
Undo restores the typed Markdown in one step. onLinkShortcut handles Mod+K
with an application-provided link UI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9b34ba71f2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const url = data?.getData("text/plain").trim() ?? ""; | ||
| if (!BARE_URL.test(url)) return false; |
There was a problem hiding this comment.
Preserve rich payloads when linkifying a pasted URL
At a collapsed caret, copying a formatted URL supplies text/zettel, HTML, and plain-text representations, but this handler decides solely from the plain-text value and runs before the normal clipboard handler. Consequently, pasting a bold URL or other rich URL-only content bypasses the established text/zettel/HTML priority and reconstructs it as a plain linked span, losing formatting and extension data. Only apply this shortcut when no richer clipboard representation is available.
Useful? React with 👍 / 👎.
| if (saved.getNodes().every((node) => node.isAttached())) $setSelection(saved.clone()); | ||
| $setZettelLink(result); |
There was a problem hiding this comment.
Abort async link updates after the saved selection detaches
When onLinkShortcut returns a promise and the original selected nodes are deleted or the document is reloaded before it resolves, the attachment check skips restoring the saved selection but the next line still calls $setZettelLink against whatever selection is currently active. The returned URL can therefore link unrelated text, potentially in a newly loaded document; abort the update when the saved selection is no longer valid.
Useful? React with 👍 / 👎.
From Lexical 0.50, core dispatches keys from a KEY_DOWN_COMMAND listener at COMMAND_PRIORITY_EDITOR that handles every key, so a Mod+K listener registered after it at the same priority never runs. Register it at COMMAND_PRIORITY_LOW, which also works with Lexical 0.30. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What
Opt-in Markdown-style input rules for
@opral/zettel-lexical:**x**/__x__→strong,*x*/_x_→em,`x`→code,~~x~~→strike-through. The rule applies when the closing marker is typed, and typing continues without the new mark. A marker opens only after a non-word character and the content cannot start or end with a space, sosnake_case_name,2*3*4anda * bstay as typed. Nothing is converted inside inline code or code blocks.-/*→ bullet list,1./1)→ numbered list (the typed number is thestart),>→ quote. These apply at the start of a normal paragraph at the root or in a quote. A new list joins a list of the same kind directly before it, because Markdown would read the two as one list anyway.http(s)://URL becomes a link when it is followed by a space or Return, or when it is pasted. Trailing punctuation is left out of the link, as in GFM. Pasting a URL over selected text links the text.onLinkShortcut({ text, href })on selected text or inside a link. The callback returns an href,nullto unlink, orundefinedto cancel, and may return a promise. It applies through the existingSET_ZETTEL_LINK_COMMANDlogic. The editor has no link UI, so without the callback Cmd+K is left to the browser.history-push, after the update that typed the triggering character. One Undo brings back the literal Markdown, such as**b**or-.The rules only create what the Zettel profile stores: the
strong/em/code/strike-throughdecorators,zettel_linkmark definitions,zettel_list(bullet/number) andzettel_quote. There are no heading, code-block or task-list rules, since those were not needed for the comment composer. They can be added the same way.Why opt-in
This goes beyond "bindings for the Zettel format": it changes what typing does. Some apps want literal
*and_, for example code-heavy comments. It is therefore behindmarkdownShortcuts: trueand off by default, and the tests check that nothing changes without it. The playground turns it on.Implementation
src/markdown-shortcuts.tsregisters an update listener that detects a single typed character: the caret moved one step in the text node that grew by one character. It is skipped for historic, collaboration and composition updates. The listener then runs the matching rule in a follow-up update. Return is detected as a new text block with the caret at its start, and a URL at the end of the previous block is linked. Pasted URLs are handled by aPASTE_COMMANDlistener with a higher priority than the plugin's clipboard handler.Tests
markdown-shortcuts.test.ts(11 tests, committed before the implementation): every inline rule, and typing continuing unformatted after it; markers that must stay literal; no rules inside inline code; undo of an inline rule and of a list rule; every block rule including3)→start: 3; list continuation and joining; no list mid-paragraph; autolinking on space and Return with trailing punctuation and undo; URL paste and URL-over-selection paste; Cmd/Ctrl+K; nothing changing without the option.**bold** after *it*codehttps://example.com/x. doneand checks the spans and the link. Cmd+Z undoes the typing and then only the autolink, and a separate case checks that Cmd+Z reverts**b**in one step.- one, Return,twogives a bullet list. This covers the caret format after a conversion at the end of a block. In jsdom that cannot be checked because a mounted editor re-reads the format from the DOM, so the unit tests use an unmounted editor.Local run:
pnpm build, package tests (ast 11, html 17, markdown 19, lexical 46) andexamples/playgroundpnpm test(27 checks) all pass.🤖 Generated with Claude Code