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:
- 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. - Click the
+button — appears in the gutter next to empty paragraphs. Same menu as/. - 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.
Right sidebar
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-gridhas 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}.contentMarkdownfield is human-readable - Plugin authors can transform the markdown via the
post.markdown.beforefilter - You can in principle import / export Markdown files (
flexweg-importplugin 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
- Core blocks — paragraph, heading, image, list, etc.
- Theme blocks — blocks that depend on the active theme
- Embeds — YouTube, Vimeo, Twitter, Spotify
- Custom HTML — paste arbitrary HTML
- Columns — multi-column layouts
- Shortcuts and tips — power-user efficiency