Skip to main content

The block editor

Flexweg CMS uses a block-based editor — every paragraph, heading, image, list, embed or layout primitive is its own block that you can insert, reorder, edit and delete independently. The editor is built on Tiptap (a ProseMirror wrapper) but the UX is heavily inspired by WordPress's Gutenberg.

The same editor is used for both posts and pages.

Anatomy

┌────────────────────────────────────────────────┬──────────────────┐
│ │ │
│ Editor area (the document) │ Right sidebar │
│ │ │
│ ┌──────────────────────────────────┐ │ ┌─────────────┐ │
│ │ Block 1 │ │ │ Document │ │
│ └──────────────────────────────────┘ │ │ Block │ │
│ │ │ │ │
│ ┌──────────────────────────────────┐ │ │ (active │ │
│ │ Block 2 ↑ ↓ ⎘ 🗑 │ ← block │ │ tab fills │ │
│ │ ─────────────────────────────── │ toolbar │ │ this area) │ │
│ │ │ │ │ │ │
│ └──────────────────────────────────┘ │ │ │ │
│ │ │ │ │
│ ┌──────────────────────────────────┐ │ │ │ │
│ │ / │ ← / │ │ │ │
│ └──────────────────────────────────┘ insert │ └─────────────┘ │
│ │ │
└────────────────────────────────────────────────┴──────────────────┘

Inserting blocks

Three ways to add a block:

  1. Type / on an empty line — opens the floating menu (a popover over the cursor) with a list of every available block. Type to filter (e.g. /img → Image block highlighted). Enter to insert.
  2. Click the + button — appears in the gutter next to empty paragraphs. Same menu as /.
  3. Paste rich content — copying from a Google Doc / Word doc / web page pastes as appropriate blocks (paragraphs, headings, lists, images via URL).

The block library includes:

  • Text: paragraph, heading 1-6, bullet/ordered list, blockquote, code block, horizontal rule
  • Media: image, embed
  • Layout: columns, custom HTML
  • Embed: YouTube, Vimeo, Twitter/X, Spotify
  • Theme blocks: depends on the active theme — see Theme blocks

See Core blocks for details on each text/media/layout block.

Block toolbar

When your cursor is in a block (or you select it), the block toolbar floats at the top-right of the active top-level block with:

  • Move up ⬆ / Move down ⬇ — reorder
  • Duplicate ⎘ — copy + paste below
  • Delete 🗑

The toolbar acts on the depth-1 ancestor of the cursor. If you're inside a list item, the toolbar reorders the entire list (per-item reordering would require nested toolbars; not in v1).

Bubble menu (inline formatting)

When you highlight text within any block, a small bubble menu appears above the selection with inline formatting:

  • Bold (Cmd+B)
  • Italic (Cmd+I)
  • Underline (Cmd+U)
  • Strike-through
  • Inline code (Cmd+E)
  • Link (Cmd+K) — opens an inline link editor
  • Clear formatting

These are standard Tiptap marks. They survive copy / paste and round-trip through Markdown.

The right sidebar has two tabs:

Document

Post-level / page-level metadata:

  • Title, slug, excerpt, status
  • Categories, tags (posts only)
  • Author, publication date (posts only)
  • Hero image
  • SEO fields (title, description, OG image)
  • More menu actions (delete, unpublish, etc.)

See Posts for the full list.

Block

Properties of the currently-selected block. Each block manifest can declare its own inspector component. Examples:

  • Heading: level (H1–H6)
  • Image: alt text, caption, link target, format picker (small / medium / large)
  • Custom HTML: a CodeMirror editor with syntax highlighting and a fullscreen modal for long blobs
  • Columns: column count, individual column widths
  • YouTube embed: video URL
  • Theme blocks: their own custom inspectors (e.g. corporate/services-grid has a list editor for service cards)

When the cursor is on a paragraph (which has no inspector), the Block tab is hidden.

Markdown round-trip

Internally the editor stores content as Markdown (plus HTML for blocks that don't fit Markdown's syntax — embeds, custom HTML, columns, theme blocks).

This means:

  • Saving + reopening a post produces identical content (no quirky transformations)
  • The Firestore posts/{id}.contentMarkdown field is human-readable
  • Plugin authors can transform the markdown via the post.markdown.before filter
  • You can in principle import / export Markdown files (flexweg-import plugin does the import side)

Atom blocks (embeds, custom HTML, etc.) round-trip as <div data-cms-...> markers — invisible in your editor view but preserved across saves.

Saving and publishing

Two buttons in the editor's top bar:

  • Save — saves the current state to Firestore as status: "draft". Doesn't generate any HTML on Flexweg. Useful for long-form work where you want to come back later.
  • Publish / Update — saves AND triggers the publish flow (renders the post via the theme, uploads HTML to Flexweg, cascades regenerations).

If you've made changes since the last save, both buttons are enabled. If you've made changes since the last publish but already saved, only Update is highlighted.

A publish log appears at the bottom of the editor when publishing. It shows each step: rendering, hashing, uploading, cascading. If anything fails, you see the error inline.

Auto-save

There's no auto-save while typing in v1. Edits live only in your browser until you click Save or Publish. Closing the tab without saving loses your changes.

The browser warns you with a "Leave site?" prompt if you have unsaved changes — but it's not a guarantee. Save often.

Concurrent editing

If two editors open the same post:

  • They both see the current Firestore state when they open
  • They edit independently in their browser
  • The last to click Save wins — their version overwrites the other's

There's no draft locking, no operational transformation, no live cursors. For collaborative editing of a single post, coordinate offline.

Continue