Custom code blocks
Customize toolbars, language loading and styles while retaining native code editing.
Complete toolbar example
The example adds a language selector to a fence. Changing language edits only its header field, preserving the body and fence spelling. Locked mode disables the selector; undo restores the previous language.
Choose the customization layer
| Goal | Entry point |
|---|---|
| Fonts, surfaces and spacing | EditorView.theme() and syntax highlighting |
| Language, copy or menu controls | CodeBlock.extend({ addNodeView() {} }) |
| TypeScript, JSON and other grammars | codeLanguages creation option |
| Mermaid, ECharts or flow DSL output | Semantic extension plus block projection; see Diagrams |
The CodeBlock NodeView sits before the fence with placement: 'before'. It does not replace the complete code body and provides no editable contentDOM.
Replace one preset feature
import { StarterKit } from '@quirekit/msfe-starter-kit'
import { CodeBlock } from '@quirekit/msfe-code-block'
import { VueNodeViewRenderer } from '@quirekit/msfe-vue'
import Toolbar from './toolbar.vue'
const CustomCodeBlock = CodeBlock.extend({
addNodeView() { return VueNodeViewRenderer(Toolbar) },
})
const extensions = [StarterKit.configure({ codeBlock: CustomCodeBlock })]
Do not install both StarterKit's original owner and a derived owner with the same name. Alternatively disable the preset entry and install exactly one replacement.
Available fields and actions
The component receives nodeView: MsfeNodeViewProps<CodeBlockAttributes>. The persisted field is currently language. Call nodeView.updateAttributes({ language: 'json' }). Admitted language tokens contain at most 40 letters, digits, underscores or + # . -; newlines and arbitrary header injection are rejected.
With an applicable selection, editor.commands.setCodeLanguage('typescript') updates a confirmed fence. Check failure results. Field validity does not imply that a matching highlighter was installed.
Copying is a read action: the host should read confirmed CodeText ranges while preserving indentation and line breaks. NodeView getRange() supplies coordinates, not an already-extracted code body. Use anchors for actions spanning a dialog.
Lazy language loading
code-block/languages.ts supplies TypeScript, JavaScript and JSON through @quirekit/cm-lang-javascript and @quirekit/cm-lang-json. Pass the registry as codeLanguages when creating the editor.
Names and aliases match exactly, case-insensitively. load(signal) returns LanguageSupport. Unknown languages remain plain text; no package path is guessed or downloaded. Dynamic imports cannot truly abort, so host resources must respect cancellation and discard obsolete results.
Closed fences without a body line
CodeBlock shows an input placeholder when the opening and closing fence are on adjacent lines.
Clicking it or activating it with Enter / Space inserts one real line through a normal editor command,
preserves quote/list prefixes, and moves the caret into the body. Rendering, scrolling and mode changes
do not modify Markdown. Existing empty body lines need no placeholder. Read-only mode disables activation;
materializing the line supports undo and redo.
Set CodeBlock.configure({ emptyLine: false }) to disable it; emptyLineLabel supplies the accessible hint.
MD Editor accepts features.codeBlock: ProseCodeBlock.configure({ emptyLine: false }) and localizes the default hint.
Source mode shows the actual fences and newlines without an input placeholder.
MD Editor provides the lazy mdCodeLanguages catalog by default: TypeScript, JavaScript / JSX, TSX, JSON,
CSS, HTML, Python, Bash and Markdown. Supplying codeLanguages replaces the catalog; [] disables it.
Custom themes and grammar packages must share the same @lezer/highlight version: different tag identities
can leave parsed tokens without colors.
Styling and active source
Style semantic classes such as .msfe-code-line, .msfe-code-start, .msfe-code-body-start, .msfe-code-end and .msfe-code-indented; use HighlightStyle for code tokens. Let the runtime handle source-token reveal and line layout. Do not hide arbitrary .cm-line elements or move the body into another editable region.
Selecting a fence reveals active source. Composition, cross-block drag selection, copying and history remain native. Report geometry changes through the component measurement entry; see Projection layers.
Verification
Cover language changes and undo, empty/unknown languages, long code, unclosed fences, both fence characters, readonly mode, Tab indentation and theme changes. See Rendering API for exact language and component contracts.