Skip to content

Markdown editor

UIMarkdownEditor edits Markdown with Crepe and a code mode, and keeps the bytes of what you do not touch.

UIMarkdownEditor lives in its own module, @meekojs/ui/editor, because Crepe weighs 347 KB of gzipped JS: inside the main module it would be a chunk of every site. The JavaScript of Crepe loads in the browser, when the component mounts, and never enters the server bundle. Until then, the HTML of the server paints a placeholder with the final height.

Setup

Crepe and CodeMirror are optional dependencies of @meekojs/ui: an application without the module does not install them.

meeko.config.ts ts
// pnpm add @milkdown/crepe @milkdown/kit @codemirror/language @codemirror/view \
//   @codemirror/lang-javascript @codemirror/lang-html @codemirror/lang-css @codemirror/lang-json @lezer/highlight
// meeko.config.ts
export default defineMeekoConfig({
  modules: ['@meekojs/ui', '@meekojs/ui/editor'],
})

Usage

Content
Loading the editor…

Visual mode: 144 characters.

meeko
<script lang="ts">
  const markdown = signal('# Title')
  const mode = signal<'visual' | 'code'>('visual')
</script>

<template>
  <UIMarkdownEditor label="Content" model="markdown" model:mode="mode" />
</template>

Block-level saving

Crepe serializes a document in its own format, so saving its output would rewrite tables and blank lines nobody touched. That is why the model only changes with a real edit, and then it keeps the bytes of the original outside the edited blocks.

  • Without editing, model stays the received text, byte by byte. Typing and undoing too.
  • An edited block comes out of Crepe. A list is one block.
  • The frontmatter never reaches Crepe: it is kept as it is.
  • The event waits for a pause while typing, and comes early when leaving the editor, before a click on «save» reads the model.
  • If a document does not match its serialization block by block, it opens in code mode with a notice.

API

UIMarkdownEditor props
Name Type / default Description
model Signal<string> The text. If the parent writes another one, the editor starts over with it.
model:mode Signal<visual | code> visual The mode.
label string Required. Visible label and accessible name.
id string id of the text area of the code mode.
readonly boolean false It can be read and selected, but not changed.
disabled boolean false Like readonly, and it also looks disabled.
describedBy string id of the text that describes it.
placeholder string Text of the empty document.
class string Class of the root.

The tools snippet goes in the bar, between the label and the switcher. The height is set with --ui-markdown-editor-height and the content scrolls inside. Its texts, also those of the menus of Crepe, are in ui.messages.<language>.markdownEditor. splitMarkdownSource and createMarkdownSplicer are exported from @meekojs/ui/editor, without Crepe.

Was this page helpful?

Add a comment

It is sent when you choose “Yes” or “No”. Only the page, your answer and the comment are stored: no cookies and no personal data.

0 of 2000 characters