feat(editor): theme the code block and give it a copy + language toolbar - #2169
Merged
Conversation
BlockNote ships a closed map of 48 languages and a highlighter built from a matching grammar bundle, so neither PowerShell nor KQL could be picked. Both grammars already exist in the installed @shikijs/langs-precompiled; they were just missing from BlockNote's map. memryCodeBlockOptions spreads BlockNote's options, adds the two entries, and loads the two grammars inside createHighlighter — the map entry alone would put a dropdown item there that throws when picked, because BlockNote asks its own bundle for any key it has not loaded. Also sorts the whole map by display name (Plain Text pinned first). The dropdown renders insertion order and BlockNote's own list was unsorted, with its later additions in a tail after TSX. Renderer and main pass the same object, so a fence language one side accepts is one the other can serialize. Kept off the package root: it pulls shiki, which mobile must not (#2032). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
h4yfans
marked this pull request as ready for review
September 11, 2026 20:41
|
React Doctor found 1 new issue in 1 file · 1 warning · score 84 / 100 (Needs work) · 1 fixed · vs 1 warning
Reviewed by React Doctor for commit |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…olbar Code blocks were a hardcoded github-dark slab in every theme, their text sat at the browser's fixed monospace default instead of the editor's font size, and the language picker was a bare half-opacity <select> in the top-left corner that did not read as a control. - Highlight with both github themes at once, as CSS variables, so light and dark each pick their own token colours with no re-highlight on toggle. BlockNote builds its shiki parser as `globalThis[symbol] || createParser(h)`, so seeding that symbol is the supported way to hand it configured options. - Surface is the sidebar colour rather than shiki's own background, so the block reads as part of the app instead of a white or black slab. - Code text is 0.875em: tied to the S/M/L setting, a notch below body text. - One pill in the block's top-right with the language picker and a copy button, shown on hover and while the caret is in the block.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three rounds on the code block, all user-reported.
Languages. The picker had no PowerShell and no KQL. It came verbatim from
@blocknote/code-block'scodeBlockOptions— a closed map of 48 languages plus a highlighter built from a matching precompiled-grammar bundle. Both grammars already ship in the installed@shikijs/langs-precompiled@3.23.0; they were just missing from BlockNote's map.memryCodeBlockOptions(packages/editor-schema/src/code-block.ts) spreads BlockNote's options rather than forking them, so future BlockNote language additions keep flowing through. Two things had to move together: thesupportedLanguagesentry and the grammar itself, loaded up front insidecreateHighlighter. A map entry alone would put a dropdown item there that breaks the block when picked — BlockNote resolves a fence tag or dropdown value to a grammar key, then callshighlighter.loadLanguage(key)for anything not already loaded, which throws for a key outside its own bundle.It also sorts the whole map by display name, with Plain Text pinned first (it is the "no language" choice, not a language). The dropdown renders insertion order, and BlockNote's own list was not sorted — its later additions (Haskell, C#, LaTeX, Kotlin, Objective C, ...) sat in a tail after TSX. Appending ours would have extended that tail.
Both desktop call sites pass the same object, so a fence language one side accepts is one the other can serialize. The module is exported from a
./code-blocksubpath, not the package root: it pulls shiki, which mobile must not (#2032). The mobileeditor-webasset is rebuilt only because the freshness hash covers all ofpackages/editor-schema/src; the packed payload is the same size, so nothing new entered that bundle.Note that an alias stays verbatim as the block's
languageprop — a```kqlfence round-trips as```kqland resolves to thekustogrammar at render time, the same waybashreachesshellscript.Theme. A code block was a hardcoded github-dark slab in every theme. Shiki can emit both themes at once as per-token CSS variables, so light and dark each pick their own colours with no re-highlight on toggle — but BlockNote builds its parser as
globalThis[Symbol.for('blocknote.shikiParser')] || createParser(h), with no options, so seeding that symbol is the way to hand it configured ones. The surface isvar(--sidebar)rather than shiki's own background: the block reads as part of the app instead of a white or black slab.Size and controls. Code text had no explicit
font-size, so a barefont-family: monospacefell into the browser's fixed ~13px default — unrelated to Appearance → Font Size. It is now0.875em: tied to that setting, one notch below body text. The language picker was a half-opacity<select>in the block's top-LEFT corner that read as plain text; it is replaced by one pill in the top-right holding the picker and a new Copy code button.That toolbar is portalled and fixed-positioned rather than appended into the block: a code block is a ProseMirror node view whose content DOM is the
<pre>, and DOM ProseMirror did not write is either ignored or wiped on its next redraw. It shows on hover and while the caret is in the block — hover is not a gesture every user has, and without the second the picker is unreachable by keyboard.Release note
Code blocks now follow your theme instead of always being dark, carry a copy button and a proper language dropdown in their top-right corner, and size their text with the app's font-size setting. The language picker offers PowerShell and KQL (Kusto), and lists all languages alphabetically.
Test plan
pnpm --filter @memry/editor-schema test— 202 pass, includingcode-block.test.ts: the two entries and their aliases, no regression against BlockNote's 48, alphabetical order with Plain Text first,defaultLanguagestilljavascript(it is the only field reaching the prop schema and must match across surfaces), both grammars ingetLoadedLanguages(), both tokenizing to more than one colour, and the seeded parser emitting--shiki-light/--shiki-darkper token.pnpm --filter @memry/desktop test:renderer -- code-block-toolbar— 5 pass: hidden until hovered, copies the block's text then confirms, corner placement, language written back throughupdateBlock, torn down when the pointer moves to another block.pnpm --filter @memry/desktop test:main -- blocknote-converter— 464 pass, including```powershelland```kqlfences surviving md → blocks → Yjs → md.pnpm --filter @memry/desktop test:renderer -- editor-schema— 42 pass; the renderer/main config parity gate stays green.pnpm typecheck,pnpm lint,pnpm --filter @memry/desktop i18n:check,pnpm docs:impact --strict.Verified by hand in the running app: theme switching, the copy button, and the language dropdown.
🤖 Generated with Claude Code