Repository navigation
feat: add list, quote, code, preformatted block edit components - #305
ViewFromTheBox wants to merge 1 commit into
Conversation
Port the four remaining static text blocks from the Alpine editor to React edit.tsx modules with Tiptap integration. Extends the rich-text toolbar and inserter registry for the new block types. - List: BulletList/OrderedList with toolbar toggle, Enter on empty item exits to new paragraph block - Quote: Blockquote with citation input, Tiptap rich text body - Code: Plain textarea with language dropdown and Tab-indent support - Preformatted: Custom Tiptap node with minimal inline formatting (bold/italic/link), whitespace preserved via code: true - RichTextToolbar: Extended to show for list/quote/preformatted blocks, added ListTypeSwitcher with faListUl/faListOl icons - Inserter: All 4 blocks registered with metadata and factories Note: This branch will be superseded by the editor overhaul (#302, #303, #304) which refactors blocks to use block.json manifests and <RichText> primitives. Kept as reference for the block edit implementations. Closes #292
📝 WalkthroughWalkthroughIntroduces four new text block types (list, quote, code, preformatted) for the visual editor with React components, TipTap/textarea editors, comprehensive test suites, and integration into the core block registry and inserter system. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
resources/js/visual-editor/editor/blocks/__tests__/list.test.tsx (1)
64-111: Add a test for “Enter on empty item exits list”.This acceptance behavior is central to list UX, but the current suite doesn’t assert it yet. Adding it would protect against regressions in key handling.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@resources/js/visual-editor/editor/blocks/__tests__/list.test.tsx` around lines 64 - 111, Add a new test "Enter on empty item exits list" that creates a list block via makeList (single empty <li>), mounts it with createEditorStore and renderBlock, grabs the editor via getBlockEditor, focuses it (editor.commands.focus), simulates pressing Enter (either dispatch a keydown "Enter" on editor.view.dom or call an available editor.commands.enter) inside the empty list item, and then assert the list was exited by verifying the top-level node is a paragraph (editor!.state.doc.firstChild?.type.name === 'paragraph') and/or the stored block content now starts with a paragraph tag (store.getState().blocks[0].attributes.content contains '<p>'). Ensure the test wraps editor interactions in act where needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@resources/js/visual-editor/editor/blocks/quote/edit.tsx`:
- Around line 46-64: Auto-focus is stealing focus from the citation input when
the block is selected; update the selection useEffect to skip calling
editor.commands.focus if the user's current activeElement is the citation input
(or any input/textarea inside this block). Add a wrapper ref (e.g.,
containerRef) for the block DOM, then in the effect (before calling
editor.commands.focus) check if document.activeElement is an input/textarea or
is contained within containerRef.current (or matches your citation input
selector) and return early if so; keep the existing pending cursor
(takePendingCursor) and selectionEdge logic otherwise.
---
Nitpick comments:
In `@resources/js/visual-editor/editor/blocks/__tests__/list.test.tsx`:
- Around line 64-111: Add a new test "Enter on empty item exits list" that
creates a list block via makeList (single empty <li>), mounts it with
createEditorStore and renderBlock, grabs the editor via getBlockEditor, focuses
it (editor.commands.focus), simulates pressing Enter (either dispatch a keydown
"Enter" on editor.view.dom or call an available editor.commands.enter) inside
the empty list item, and then assert the list was exited by verifying the
top-level node is a paragraph (editor!.state.doc.firstChild?.type.name ===
'paragraph') and/or the stored block content now starts with a paragraph tag
(store.getState().blocks[0].attributes.content contains '<p>'). Ensure the test
wraps editor interactions in act where needed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: b44efd67-c160-4750-95b0-c0a250573e42
📒 Files selected for processing (15)
resources/js/visual-editor/editor/blocks/__tests__/code.test.tsxresources/js/visual-editor/editor/blocks/__tests__/list.test.tsxresources/js/visual-editor/editor/blocks/__tests__/preformatted.test.tsxresources/js/visual-editor/editor/blocks/__tests__/quote.test.tsxresources/js/visual-editor/editor/blocks/code/edit.tsxresources/js/visual-editor/editor/blocks/code/index.tsresources/js/visual-editor/editor/blocks/index.tsresources/js/visual-editor/editor/blocks/list/edit.tsxresources/js/visual-editor/editor/blocks/list/index.tsresources/js/visual-editor/editor/blocks/preformatted/edit.tsxresources/js/visual-editor/editor/blocks/preformatted/index.tsresources/js/visual-editor/editor/blocks/quote/edit.tsxresources/js/visual-editor/editor/blocks/quote/index.tsresources/js/visual-editor/editor/components/RichTextToolbar.tsxresources/js/visual-editor/editor/inserter/builtinInserterBlocks.ts
| useEffect(() => { | ||
| if (!editor || !isSelected) { | ||
| return; | ||
| } | ||
|
|
||
| const pending = takePendingCursor(clientId); | ||
|
|
||
| if (pending !== undefined) { | ||
| editor.commands.focus(pending); | ||
| return; | ||
| } | ||
|
|
||
| if (editor.isFocused) { | ||
| return; | ||
| } | ||
|
|
||
| const position = selectionEdge === 'start' ? 'start' : 'end'; | ||
| editor.commands.focus(position); | ||
| }, [editor, isSelected, selectionEdge, clientId]); |
There was a problem hiding this comment.
Auto-focus can steal focus from the citation input.
On Line 46, the selection effect always focuses the TipTap editor when selected and not already focused. That can interrupt typing when users click into the citation field.
💡 Suggested fix
-import { type ChangeEvent, useCallback, useEffect } from 'react';
+import { type ChangeEvent, useCallback, useEffect, useRef } from 'react';
...
export default function QuoteEdit({ clientId, attributes }: BlockEditProps) {
+ const citationInputRef = useRef<HTMLInputElement>(null);
...
useEffect(() => {
if (!editor || !isSelected) {
return;
}
+
+ if (document.activeElement === citationInputRef.current) {
+ return;
+ }
...
}, [editor, isSelected, selectionEdge, clientId]);
...
<input
+ ref={citationInputRef}
type="text"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@resources/js/visual-editor/editor/blocks/quote/edit.tsx` around lines 46 -
64, Auto-focus is stealing focus from the citation input when the block is
selected; update the selection useEffect to skip calling editor.commands.focus
if the user's current activeElement is the citation input (or any input/textarea
inside this block). Add a wrapper ref (e.g., containerRef) for the block DOM,
then in the effect (before calling editor.commands.focus) check if
document.activeElement is an input/textarea or is contained within
containerRef.current (or matches your citation input selector) and return early
if so; keep the existing pending cursor (takePendingCursor) and selectionEdge
logic otherwise.
Description
Port the four remaining static text blocks from the Alpine editor to React edit.tsx modules with Tiptap integration. Extends the rich-text toolbar and inserter registry for the new block types.
Closes: #292
Type of Change
Related Issue
Issue: #292
Motivation and Context
Phase 2.1 requires porting list, quote, code, and preformatted blocks to the React editor. This PR implements the edit-side JS components and wiring.
Note: This PR will be superseded by the editor overhaul (#302, #303, #304) which refactors blocks to use
block.jsonmanifests and<RichText>primitives. Kept as a draft reference for the block edit implementations.Changes Made
blocks/list/): BulletList/OrderedList/ListItem with Tiptap, toolbar toggle between ordered/unordered, Enter on empty item exits to new paragraph blockblocks/quote/): Tiptap Blockquote with separate citation text inputblocks/code/): Plain textarea (no Tiptap), language dropdown, Tab inserts 4 spacesblocks/preformatted/): Custom Tiptap node withcode: truefor whitespace preservation, minimal inline formatting (bold/italic/link)ListTypeSwitcherwithfaListUl/faListOliconsregisterCoreBlocks()registers all 6 blocksHow Has This Been Tested?
Tests Performed:
Accessibility Tests Run
Details: Code block textarea and language select have aria-labels. Quote citation input has aria-label. List type toolbar buttons have aria-labels.
Tests Added
Test details:
list.test.tsx(5 tests): registration, normalizeOrdered, mount, content update, ordered togglequote.test.tsx(4 tests): registration, render content, content update, citation updatecode.test.tsx(5 tests): registration, normalizeLanguage, render textarea, content update, language changepreformatted.test.tsx(4 tests): registration, render pre, content update, bold formattingDocumentation
Documentation details: Documentation deferred — these blocks will be refactored during the editor overhaul (#302).
Pre-Submission Checklist
Summary by CodeRabbit
New Features
Tests