Skip to content

feat(editor): theme the code block and give it a copy + language toolbar - #2169

Merged
h4yfans merged 4 commits into
mainfrom
code-block-powershell-kql
Sep 11, 2026
Merged

feat(editor): theme the code block and give it a copy + language toolbar#2169
h4yfans merged 4 commits into
mainfrom
code-block-powershell-kql

Conversation

@h4yfans

@h4yfans h4yfans commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

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's codeBlockOptions — 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: the supportedLanguages entry and the grammar itself, loaded up front inside createHighlighter. 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 calls highlighter.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-block subpath, not the package root: it pulls shiki, which mobile must not (#2032). The mobile editor-web asset is rebuilt only because the freshness hash covers all of packages/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 language prop — a ```kql fence round-trips as ```kql and resolves to the kusto grammar at render time, the same way bash reaches shellscript.

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 is var(--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 bare font-family: monospace fell into the browser's fixed ~13px default — unrelated to Appearance → Font Size. It is now 0.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, including code-block.test.ts: the two entries and their aliases, no regression against BlockNote's 48, alphabetical order with Plain Text first, defaultLanguage still javascript (it is the only field reaching the prop schema and must match across surfaces), both grammars in getLoadedLanguages(), both tokenizing to more than one colour, and the seeded parser emitting --shiki-light / --shiki-dark per 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 through updateBlock, torn down when the pointer moves to another block.
  • pnpm --filter @memry/desktop test:main -- blocknote-converter — 464 pass, including ```powershell and ```kql fences 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

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
h4yfans marked this pull request as ready for review September 11, 2026 20:41
@github-actions github-actions Bot added dependencies documentation Improvements or additions to documentation enhancement New feature or request test labels Sep 11, 2026
@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown

React Doctor found 1 new issue in 1 file · 1 warning · score 84 / 100 (Needs work) · 1 fixed · vs main

1 warning

src/renderer/src/components/note/content-area/ContentArea.tsx

  • ⚠️ L299 React function has high control-flow complexity no-high-complexity-react-function

Reviewed by React Doctor for commit 77f3800. See inline comments for fixes.

@codecov

codecov Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.52174% with 17 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...omponents/note/content-area/code-block-toolbar.tsx 81.52% 17 Missing ⚠️

📢 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.
@h4yfans h4yfans changed the title feat(editor): add PowerShell and KQL to the code block language picker feat(editor): theme the code block and give it a copy + language toolbar Sep 11, 2026
@h4yfans
h4yfans merged commit f04bab6 into main Sep 11, 2026
19 checks passed
@h4yfans
h4yfans deleted the code-block-powershell-kql branch September 11, 2026 21:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies documentation Improvements or additions to documentation enhancement New feature or request test

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant