Skip to content

feat: allow block type transformation via palette on non-empty blocks #186

Description

@oobagi

Problem

There's no way to change a block's type once it has content. The / palette only opens on empty blocks (ta.Value() == ""), so users who want to turn a paragraph into a heading — or a bullet into a quote — must clear the block, change its type, then re-type the content.

This is a common editing operation (realize a paragraph should be a heading, promote/demote headings, convert to a list after drafting). The infrastructure already supports it — applyPaletteSelection preserves content for all non-Divider types — but the opening gate blocks it.

Context

  • Palette open condition (internal/editor/editor.go:1184-1188): requires ta.Value() == "", ta.Line() == 0, ta.LineInfo().ColumnOffset == 0
  • applyPaletteSelection (internal/editor/editor.go:729-743): already changes type and preserves content; only clears content for Divider
  • Backspace at pos 0 already converts lists → paragraph, showing the pattern exists for non-empty type changes
  • Palette filtering, navigation, and selection all work independently of content state

Possible approaches

Approach A: Extend / trigger to position-0 on non-empty blocks

  • How: Remove the ta.Value() == "" gate. Keep the position-0 requirement so / mid-sentence still types a slash.
  • Pros: Single mental model ("go to start of block, type /, pick a type"). Mirrors Notion. Minimal code change.
  • Cons: Need to ensure / isn't inserted into the block text when triggering the palette.
  • Files: internal/editor/editor.go (open condition), internal/editor/palette.go (maybe hide Divider when content exists)

Approach B: Separate shortcut (e.g., Ctrl+/) for non-empty blocks

  • How: Add a dedicated keybind that opens the palette regardless of content or cursor position.
  • Pros: No ambiguity about when / is a character vs. a command.
  • Cons: Two ways to do the same thing. More to document. Conflicts with macOS terminal remapping concerns.
  • Files: internal/editor/editor.go

Recommended: Approach A — simpler, consistent, less to learn.

Tasks

  • Remove ta.Value() == "" from palette open condition (keep pos-0 check)
  • Ensure the / character is not inserted into block content when palette opens
  • Hide Divider from palette options when the current block has content (avoids silent data loss)
  • Visually indicate the current block type in the palette (dim or checkmark)
  • Verify undo reverts a type transformation as a single operation
  • Add tests: palette opens on non-empty block at pos 0, content preserved after type change, / still types normally mid-text

Test plan

  • Place cursor at position 0 of a block with text, press / — palette opens
  • Select a different type — block type changes, all content preserved
  • Place cursor mid-text, press / — literal slash inserted, no palette
  • Open palette on non-empty block — Divider option is hidden
  • Select the block's current type — no-op, palette closes
  • Undo after type change — type and content revert
  • Multi-line block converted to heading — content preserved

Scope

Type: enhancement
Size: small–medium

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions