PhoenixKitWeb.Components.Core.MarkdownEditor (phoenix_kit v1.7.165)

Copy Markdown View Source

Reusable markdown editor LiveComponent with cursor tracking, markdown formatting toolbar, component insertion, and unsaved changes protection.

Features

  • Monospace textarea optimized for markdown/code editing
  • Markdown formatting toolbar with selection-aware text manipulation
  • Cursor position tracking for inserting components at cursor
  • Optional toolbar for inserting images, videos, etc.
  • Save status indicator (saving/unsaved/saved)
  • Browser navigation protection for unsaved changes
  • Debounced content updates

Usage

<.live_component
  module={PhoenixKitWeb.Components.Core.MarkdownEditor}
  id="content-editor"
  content={@content}
  save_status={@save_status}
  show_formatting_toolbar={true}
  toolbar={[:image, :video]}
  on_change="content_changed"
  on_save="save_content"
  on_insert_component="insert_component"
/>

Formatting Toolbar

The formatting toolbar includes:

  • Headings: H1-H6 (adds # prefix to current line)
  • Inline styles: Bold, Italic, Strikethrough, Inline Code (wraps selection)
  • Links: Prompts for URL and wraps selection as link text
  • Lists: Bullet and numbered lists (adds prefix to current line)

When text is selected, formatting wraps the selection. When no text is selected, a placeholder is inserted and auto-selected for easy replacement.

Events Sent to Parent — required host wiring (silent failure otherwise)

This is a LiveComponent, so it has no handle_info of its own: it reports edits by sending process messages to the host LiveView via send/2. The host MUST handle these, or the edits are silently lost (the editor looks live but the parent never sees the typed content — no crash, no warning):

  • {:editor_content_changed, %{content: content, editor_id: id}}required. Content updated; the host folds content into its own changeset/assign. Forget this and the form never sees what's typed.
  • {:editor_insert_component, %{type: :image | :video, editor_id: id}} — toolbar insert button clicked (handle if you support media inserts).

  • {:editor_save_requested, %{editor_id: id}} — save button clicked.

Each host folds the content into its own form differently, so there is intentionally no use ...Embed macro — the handling is yours to write.

Commands from Parent

Use send_update/2 to send commands to the component:

# Insert text at cursor position
send_update(PhoenixKitWeb.Components.Core.MarkdownEditor,
  id: "content-editor",
  action: :insert_at_cursor,
  text: "<Image file_id=\"abc123\" />"
)

# Prompt the user (window.prompt) on the client, then insert the value by
# substituting `%{value}` in `template` (e.g. a video URL):
send_update(PhoenixKitWeb.Components.Core.MarkdownEditor,
  id: "content-editor",
  action: :prompt_insert,
  prompt: "Enter YouTube URL:",
  template: "\n![Video](%{value})\n"
)

JavaScript / CSP

All behavior is driven by the MarkdownEditor LiveView hook (shipped in priv/static/assets/phoenix_kit.js as window.PhoenixKitHooks.MarkdownEditor), not by inline <script> or onclick= handlers. That means it works under a strict Content-Security-Policy with no nonce and survives LiveView navigation. The host only needs the vendored phoenix_kit.js loaded (the standard install; mix phoenix_kit.update refreshes it) and window.PhoenixKitHooks spread into its LiveSocket — exactly like every other PhoenixKit hook.