Files
hudozka/docs/specs/20260408-textblock-container-redesign.md
T
tmshv ed3f93de53 PB + Writer (#287)
* add pb models

* api rules has updated to view public

* pb setup is completed

* add migrate script

* add pb migrations

* ignore storage

* add pocketbase SDK dependency

* change Tag.id and PageCardDto.id to string for PocketBase

* add DocV1 content block types

* add PB client singleton and PB record types

* rewrite factory for PocketBase record types

* rewrite api.ts for PocketBase SDK

* remove unused Strapi files, add PB to config

* mark Task 7 complete in plan

* feat: verify build, tests, and lint pass with PocketBase API layer

* move completed plan: 2026-04-06-pocketbase-api-layer.md

* scaffold hudozka-writer with Vite 8, React 19, TS 5.9, Tiptap

* add PocketBase client and DocV1 types to writer

* add Login component for PocketBase superuser auth

* add App shell with auth gate and page loader

* add DocV1 <-> Tiptap serialization layer

* mark Task 5 complete in writer redesign plan

* add custom Tiptap nodes for DocV1 block types

* add Tiptap editor with toolbar and save to PocketBase

* add image picker with browse and upload

* feat: verify writer build and complete integration task

* move completed plan: 2026-04-06-hudozka-writer-redesign.md

* add local env

* rm textblock from writer

* add writer editor improvements design spec

* add writer editor improvements implementation plan

* feat: use @tiptap/markdown for text block serialization

* feat: add global markdown/rendered toggle

* feat: add BlockMenu component for block type selection

* feat: add hover + button for block insertion

* feat: add slash commands for block insertion

* feat: add interactive file block editor with FilePicker

* feat: show resolved page titles in card grid block

* move completed plan: 2026-04-07-writer-editor-improvements.md

* writer ui improved

* pages migration

* spec: draft/publish workflow for hudozka-writer

* spec: rename draft_doc to draft in spec

* plan: writer draft/publish implementation

* plan has moved

* gitignores

* plans upd

* feat(writer): update PbPage type for draft/publish workflow

* feat(writer): add save/publish/discard handlers with dirty tracking

* feat(writer): add DraftBanner component

* feat(writer): add Publish button and update toolbar layout

* feat(writer): add published toggle and update duplicate for new schema

* docs: mark manual verification steps as skipped in writer draft/publish plan

* move completed plan: 20260407-writer-draft-publish.md

* feat(writer): add BlockActions component for inline block menus

* feat(writer): add draggable NodeViews for text blocks

* feat(writer): use BlockActions in custom block NodeViews

* feat(writer): replace floating BlockInsert with inline BlockActions

* fix(writer): ensure all block wrappers have position: relative for BlockActions

* chore: mark manual verification tasks as skipped in writer block actions plan

* move completed plan: 20260408-writer-block-actions.md

* writer update

* refactor(writer): simplify BlockActions to always-visible delete + drag handle

* feat(writer): add BlockInsert component for inserting blocks between nodes

* feat(writer): redesign block sidebar with always-visible actions and insert button

* move completed plan: 20260408-writer-block-actions-redesign.md

* add spec: textblock container redesign for writer editor

* feat(writer): add TextBlock container node definition

* feat(writer): replace DraggableXxx wrappers with TextBlock container

* feat(writer): update Editor extensions for TextBlock container

* feat(writer): add split button to BlockActions for text blocks

* feat(writer): update serialization for TextBlock container

* feat(writer): update BlockMenu and SlashCommands for TextBlock

* fix(writer): use Fragment.from() for replaceWith calls in BlockActions and SlashCommands

* move completed plan: 20260408-textblock-container.md

* add air
2026-04-12 19:02:03 +03:00

2.8 KiB
Raw Blame History

TextBlock Container Redesign

Problem

The writer editor has a flat document schema where every paragraph, heading, list, etc. is a top-level block with its own sidebar actions (delete, drag) and insert button. This causes two issues:

  1. List items inside bullet/ordered lists each get their own sidebar actions (because paragraphs inside list items use custom DraggableXxx node views)
  2. Conceptually related text content (several paragraphs in sequence) appears as many independent blocks instead of one cohesive unit

Solution

Introduce a textBlock container node that groups text-level content. Each textBlock gets one set of sidebar actions. Media blocks (image, document, embed, card-grid) remain standalone top-level blocks as they are today.

Document Schema

Current:

doc → (paragraph | heading | bulletList | ... | imageBlock | documentBlock | ...)+

New:

doc → (textBlock | imageBlock | documentBlock | embedBlock | cardGridBlock)+
textBlock → (paragraph | heading | bulletList | orderedList | blockquote | codeBlock | horizontalRule)+

TextBlock Node

  • Tiptap Node.create() with name: "textBlock", group: "block", draggable: true
  • Content spec allows standard text-level nodes inside it
  • Node view renders the same pattern as other blocks:
    • BlockActions (delete, split, drag handle) inside NodeViewWrapper
    • NodeViewContent for the editable area
    • BlockInsert after the wrapper

BlockActions Changes

BlockActions gets a new showSplit prop, only passed by textBlock. The sidebar shows three buttons for text blocks:

  1. Delete (×)
  2. Split (✂) — splits the text block at cursor position
  3. Drag handle (⠇)

Custom blocks (image, document, etc.) keep delete + drag only.

Split Operation

When the user clicks Split:

  1. Find cursor position within the textBlock
  2. Determine which child node the cursor is in
  3. Split at the boundary between child nodes — the block divides into two textBlock nodes
  4. If either half would be empty, don't create it
  5. Split is disabled when: cursor at the very start/end, or the text block contains only one child node

Split happens at child-node boundaries, not mid-paragraph.

Serialization

The DocV1 format does not change. The serialization/deserialization logic in serialize.ts is updated to work with textBlock container nodes instead of the current buffer-accumulation pattern.

What Gets Removed

  • All DraggableXxx wrappers: DraggableParagraph, DraggableHeading, DraggableBulletList, DraggableOrderedList, DraggableBlockquote, DraggableCodeBlock, DraggableHorizontalRule
  • Standard StarterKit extensions used instead (no longer disabled in StarterKit.configure())
  • Text-level nodes lose their individual BlockActions and BlockInsert
  • The textBlocks.tsx file is replaced with the new TextBlock node definition