React components for Meowdown, a hybrid (live-preview) Markdown editor.
import '@meowdown/core/style.css'
import '@meowdown/react/style.css'
import { MeowdownEditor, type EditorHandle } from '@meowdown/react'
import { useRef, useCallback } from 'react'
export function App() {
const ref = useRef<EditorHandle>(null)
const handleDocChange = useCallback(() => {
console.log(ref.current?.getMarkdown())
}, [])
return (
<MeowdownEditor
handleRef={ref}
mode="focus"
initialMarkdown="# Hello"
onDocChange={handleDocChange}
/>
)
}The Markdown editor component. Renders inside a div.meowdown wrapper that fills a flex parent. In rich modes, typing / opens a slash menu for inserting blocks (headings, blockquote, lists, code block, table). Hovering a block shows a handle to its left: the plus button inserts an empty paragraph below the block, and the grip selects the block and can be dragged to move it, with a drop indicator line marking the target.
mode?: 'focus' | 'show' | 'hide' | 'source': defaults to'focus'.'focus': Markdown syntax is hidden, revealed around the cursor.'show': Markdown syntax is always visible.'hide': Markdown syntax is always hidden.'source': raw Markdown source with syntax highlighting.
initialMarkdown?: string: first render only.onDocChange?: VoidFunction: called on every user-driven document change. ProgrammaticsetMarkdown/setStateon the handle do not fire it.onTagSearch?: (query: string) => TagItem[] | Promise<TagItem[]>: enables the tag menu, which opens when typing#followed by text in a rich mode. Returns ranked rows{ tag, label?, detail?, onSelect? }(the menu does not re-sort). Selecting a row inserts#tagthen runs itsonSelect. Omit to disable.onWikilinkSearch?: (query: string) => WikilinkItem[] | Promise<WikilinkItem[]>: enables the wikilink menu, which opens as soon as[[is typed in a rich mode. Returns ranked rows{ target, label?, detail?, onSelect? }(the menu does not re-sort). Selecting a row inserts[[target]]then runs itsonSelect. Omit to disable.onWikilinkClick?: (payload: { target: string; event: MouseEvent }) => void: called when a rendered wiki link is clicked. A plain click inside a link the caret already sits in just places the caret;Mod-click always fires. Pass a stable function (e.g. fromuseCallback). Ignored in source mode.resolveImageUrl?: (src: string) => string | undefined: maps an imagesrcto a displayable URL (orundefinedto skip). Enables inline image rendering:stays literal text and the image renders beneath its line. Pass a stable function. Ignored in source mode.onImagePaste?: (file: File) => string | undefined | Promise<string | undefined>: persists a pasted or dropped image file and returns its markdownsrc(orundefinedto decline), synchronously or as a promise. Pass a stable function. Ignored in source mode.onImageSaveError?: (error: unknown, file: File) => void: called whenonImagePastethrows. Defaults toconsole.error. Ignored in source mode.embedPaste?: boolean: auto-embeds a pasted tweet or YouTube link as a rich embed; one undo turns the embed back into the raw link. On by default; setfalseto disable. Only takes effect whenresolveImageUrlis set, since embeds render through the image pipeline. Ignored in source mode.bulletAfterHeading?: boolean: pressing Enter at the end of the document's first heading (the title line) starts a fresh empty bullet on the next line instead of a plain paragraph. Off by default. Ignored in source mode.blockHandle?: boolean: shows the per-block gutter handle in the rich modes (a drag grip for reordering blocks and a+add button, plus the drop indicator). On by default; setfalseto hide the gutter affordance entirely, e.g. when the host does not want block reordering. Ignored in source mode and whenreadOnlyis set.placeholder?: string | ((state) => string): placeholder text shown when the whole document is empty. Pass a stable function. Ignored in source mode.readOnly?: boolean: makes the editor read-only, in both the rich and source modes.spellCheck?: boolean: toggles the browser's native spell checking in the rich modes. Defaults to the browser's behavior. Ignored in source mode.editorClassName?: string: class on the editable root (the contenteditable). Rich modes only.wrapperClassName?: string: class on the outerdiv.meowdownwrapper.handleRef?: Ref<EditorHandle>children?: ReactNode: rendered inside the editor's ProseKit context, so children can calluseEditor(). Only rendered in the rich modes; source mode ignores them.
Re-exported from @prosekit/react. Call it from a component passed as children to read the live editor instance.
Re-exported from @prosekit/react. Registers a keymap on the editor from a children component; set its priority with Priority from @meowdown/core.
Re-exported from @prosekit/react. Applies an extension to the editor from a children component.
Imperative handle for the editor, attached via handleRef.
getMarkdown(): string: serializes the current document to Markdown. Can be expensive on large documents; call it on demand (e.g. throttled) instead of on every change.setMarkdown(markdown: string): void: replaces the whole document as a single undoable edit. Does not fireonDocChange.getState(): EditorStateSnapshot: returns[markdown, selection], whereselectionis aSelectionJSON({ anchor: number, head: number, type: string }).setState(markdown?: string, selection?: SelectionJSON | 'start' | 'end'): void: replaces the document (ifmarkdownis given) and restoresselection: exactly when valid, otherwise clamped to the nearest text selection; out-of-range positions never throw.'start'and'end'jump to the document edges. Without a selection, the current one is mapped through the change. Restore a snapshot withhandle.setState(...handle.getState()).getSelection(): SelectionJSON: returns the current selection.setSelection(selection: SelectionJSON | 'start' | 'end'): void: restores a selection with the same hint semantics assetState.focus(): void: focuses the editor.scrollIntoView(): void: scrolls the selection into view.editor: TypedEditor | undefined: escape hatch for the underlying ProseKit editor,undefinedin source mode. No stability guarantees beyond what@meowdown/coreexports.
Selection positions are in the mounted editor's coordinate space: ProseMirror document positions in the rich modes, character offsets in source mode. They round-trip within one mode but are not portable across a mode switch.
Import both stylesheets: @meowdown/core/style.css (the editor theme and variables) and @meowdown/react/style.css (the component layout). The core theme is documented in @meowdown/core.
MIT