Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 42 additions & 6 deletions src/features/editor/ui/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import {
type BlockNoteEditor,
BlockNoteSchema,
createCodeBlockSpec,
createStyleSpecFromTipTapMark,
defaultBlockSpecs,
defaultStyleSpecs,
} from '@blocknote/core'
import {
FormattingToolbar,
Expand All @@ -12,6 +14,7 @@ import {
useCreateBlockNote,
} from '@blocknote/react'
import { BlockNoteView } from '@blocknote/shadcn'
import { Code } from '@tiptap/extension-code'
import type { Transaction } from 'prosemirror-state'
import {
forwardRef,
Expand Down Expand Up @@ -104,20 +107,53 @@ function withSuppressedHistory(
}

/**
* Custom BlockNote schema with Shiki-powered syntax highlighting for code blocks.
* Custom BlockNote schema with Shiki-powered syntax highlighting for code blocks
* and inline code + highlight mark coexistence.
*
* Replaces the default `codeBlock` spec with one configured via
* {@link codeBlockOptions}, which provides a Shiki highlighter and the full
* set of supported programming languages.
* **Block specs** – Replaces the default `codeBlock` spec with one configured
* via {@link codeBlockOptions}, which provides a Shiki highlighter and the full
* set of supported programming languages. All other block specs (paragraph,
* heading, bulletList, image, etc.) are inherited from
* {@link defaultBlockSpecs} unchanged.
*
* All other block specs (paragraph, heading, bulletList, image, etc.) are
* inherited from {@link defaultBlockSpecs} unchanged.
* **Style specs** – Re-declares every default style spec in an explicit order
* so that the `code` mark is registered **last**. ProseMirror renders marks in
* registration order (outermost first), so placing `code` last ensures the
* `<code>` element nests *inside* all other mark spans (bold, italic,
* textColor, backgroundColor, etc.). This makes the `backgroundColor`
* `<span>` wrap the `<code>` element — not the other way around — so the
* highlight box matches normal text height.
*
* The `code` mark itself is overridden via {@link Code}.extend to set
* `excludes: ''` (exclude no marks), replacing TipTap's default
* `excludes: '_'` (exclude all). This allows inline code to coexist with
* highlight, textColor, bold, italic, underline, and strike — matching
* Notion's behaviour.
*/
const schema = BlockNoteSchema.create({
blockSpecs: {
...defaultBlockSpecs,
codeBlock: createCodeBlockSpec(codeBlockOptions),
},
styleSpecs: {
// Explicit ordering: code is placed LAST so it nests inside all other
// marks (bold, italic, textColor, backgroundColor, etc.). This ensures
// the backgroundColor <span> wraps the <code> element — not the other
// way around — so the highlight box matches normal text height.
bold: defaultStyleSpecs.bold,
italic: defaultStyleSpecs.italic,
underline: defaultStyleSpecs.underline,
strike: defaultStyleSpecs.strike,
textColor: defaultStyleSpecs.textColor,
backgroundColor: defaultStyleSpecs.backgroundColor,
// Override TipTap Code mark's `excludes: '_'` (exclude all marks) with
// `excludes: ''` (exclude none) so inline code can coexist with highlight,
// textColor, bold, italic, underline, and strike — matching Notion behavior.
code: createStyleSpecFromTipTapMark(
Code.extend({ excludes: '' }),
'boolean'
),
},
})

/**
Expand Down
18 changes: 18 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,24 @@ body {
color: oklch(0.828 0.111 54);
}

/*
* Inline code inside a highlighted span.
*
* When both `code` and `backgroundColor` marks are applied to the same
* text, the `<code>` element sits inside the highlight `<span>`. The code
* retains its own opaque background — the surrounding highlighted text
* conveys highlight membership. A subtle ring distinguishes code from
* a bare highlight.
*/
[data-style-type="backgroundColor"] code {
background-color: oklch(0.964 0.021 56 / 88%);
box-shadow: 0 0 0 1px oklch(0 0 0 / 10%);
}
.dark [data-style-type="backgroundColor"] code {
background-color: oklch(0.18 0.01 56 / 88%);
box-shadow: 0 0 0 1px oklch(1 0 0 / 10%);
}

/*
* Highlighter markers.
*
Expand Down
Loading