Skip to content

feat(editor): add block button for hovering blocks#14879

Open
001-mak wants to merge 11 commits into
toeverything:canaryfrom
001-mak:001-mak/feat/14845/add-block-widget
Open

feat(editor): add block button for hovering blocks#14879
001-mak wants to merge 11 commits into
toeverything:canaryfrom
001-mak:001-mak/feat/14845/add-block-widget

Conversation

@001-mak
Copy link
Copy Markdown
Contributor

@001-mak 001-mak commented Apr 27, 2026

This PR implements [feature request] #14845

Summary by CodeRabbit

  • New Features

    • Added an "add block" control that appears when hovering blocks in page mode to insert a new paragraph; the new paragraph is automatically focused and the add control hides after insertion.
  • Style

    • Adjusted heading icon positioning for improved visual alignment.

@001-mak 001-mak requested a review from a team as a code owner April 27, 2026 13:17
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b229b98c-ff22-4a2b-98df-53f190f17033

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Adds an "add block" widget: a new Lit component and constants, registers the element, shows/positions the widget from drag-handle pointer logic, inserts and focuses a new affine:paragraph on activation, and includes a small unrelated CSS tweak for paragraph heading icon positioning. (33 words)

Changes

Add Block Widget and Drag-handle Integration

Layer / File(s) Summary
Component
blocksuite/affine/widgets/drag-handle/src/components/add-block-widget.ts
New AffineAddBlockWidget Lit component with visible property; renders a plus button and dispatches an add-block CustomEvent on click.
Data / Constants
blocksuite/affine/widgets/drag-handle/src/config.ts, blocksuite/affine/widgets/drag-handle/src/consts.ts
Adds ADD_BLOCK_WIDGET_WIDTH = 16 and AFFINE_ADD_BLOCK_WIDGET = 'affine-add-block-widget'.
Registration / Build
blocksuite/affine/widgets/drag-handle/src/effects.ts, blocksuite/affine/widgets/drag-handle/package.json, blocksuite/affine/widgets/drag-handle/tsconfig.json
Registers the new element in effects; adds @blocksuite/affine-rich-text dependency and a ../../rich-text tsconfig reference.
Core Logic
blocksuite/affine/widgets/drag-handle/src/drag-handle.ts
Adds showAddBlockWidget state, addBlockWidgetContainer query, _handleAddBlock() to insert a new affine:paragraph via store.captureSync() and focus it via focusTextModel, extends hide() to reset/hide widget, and renders the add-block widget container wired to add-block events.
Pointer Wiring / Positioning
blocksuite/affine/widgets/drag-handle/src/watchers/pointer-event-watcher.ts
Enables showAddBlockWidget on hover and updates addBlockWidgetContainer inline styles to vertically align and position it left by ADD_BLOCK_WIDGET_WIDTH, setting display: flex.
Styles
blocksuite/affine/widgets/drag-handle/src/styles.ts
Imports ADD_BLOCK_WIDGET_WIDTH and adds .affine-add-block-widget-container CSS to size/position the widget container.
Tests / Docs
(none)
No tests or docs added in this diff.

Minor UI Tweak — Paragraph Heading Icon

Layer / File(s) Summary
Style Change
blocksuite/affine/blocks/paragraph/src/heading-icon.ts
Adjusts .heading-icon CSS transform: translateX from -64px to -80px.

Sequence Diagram

sequenceDiagram
    participant User
    participant PointerWatcher as Pointer Event\nWatcher
    participant DragHandle as Drag Handle
    participant AddBlockWidget as Add Block\nWidget
    participant BlockEditor as Block\nEditor

    User->>PointerWatcher: hover over block
    PointerWatcher->>DragHandle: reveal drag handle
    PointerWatcher->>AddBlockWidget: set visible = true
    PointerWatcher->>DragHandle: position add-block container
    AddBlockWidget-->>User: render plus button

    User->>AddBlockWidget: click plus button
    AddBlockWidget->>DragHandle: dispatch "add-block" event
    DragHandle->>DragHandle: _handleAddBlock() — insert paragraph via store
    DragHandle->>BlockEditor: focus text model in new paragraph
    DragHandle->>AddBlockWidget: hide widget (visible = false)
    BlockEditor-->>User: new block focused and ready
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately describes the main change: introducing an 'add block' button widget for hovering blocks, which aligns with the core functionality added across multiple files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
blocksuite/affine/widgets/drag-handle/src/drag-handle.ts (1)

74-91: Avoid leaving an empty undo step when addBlock returns falsy.

store.captureSync() is called unconditionally before store.addBlock(...). If addBlock fails or returns falsy (unlikely but handled by the early return), an empty history entry has already been captured and hide() is also skipped, so the drag-handle stays visible after a no-op. Either gate captureSync() on success or always call hide() to keep UI state consistent.

♻️ Suggested adjustment
-    store.captureSync();
     const newBlockId = store.addBlock(
       'affine:paragraph',
       {},
       parent,
       index + 1
     );

-    if (!newBlockId) return;
+    if (!newBlockId) {
+      this.hide();
+      return;
+    }
+    store.captureSync();

     this.host.updateComplete
       .then(() => {
         focusTextModel(this.std, newBlockId);
       })
       .catch(console.error);

     this.hide();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@blocksuite/affine/widgets/drag-handle/src/drag-handle.ts` around lines 74 -
91, The current code calls store.captureSync() before store.addBlock(...),
creating an empty undo step if addBlock returns falsy and leaving the drag
handle visible; fix by calling store.addBlock first and if it returns falsy call
this.hide() and return, otherwise call store.captureSync() and then proceed with
this.host.updateComplete.then(()=> focusTextModel(this.std,
newBlockId)).catch(console.error) so undo history is only captured for
successful adds and UI is always hidden on failure.
blocksuite/affine/widgets/drag-handle/src/components/add-block-widget.ts (1)

66-75: Use the shared icon from @blocksuite/icons/lit instead of an inline SVG.

@blocksuite/icons is already a dependency of this package. Reusing PlusIcon (imported from @blocksuite/icons/lit) keeps icon styling/sizing consistent with the rest of the editor and avoids drift if the design system changes the glyph.

♻️ Example refactor
-import { css, html, LitElement } from 'lit';
+import { PlusIcon } from '@blocksuite/icons/lit';
+import { css, html, LitElement } from 'lit';
@@
-        <svg
-          xmlns="http://www.w3.org/2000/svg"
-          viewBox="0 0 12 12"
-          fill="currentColor"
-          aria-hidden="true"
-        >
-          <path
-            d="M6 1a.75.75 0 0 1 .75.75v3.5h3.5a.75.75 0 0 1 0 1.5h-3.5v3.5a.75.75 0 0 1-1.5 0v-3.5h-3.5a.75.75 0 0 1 0-1.5h3.5v-3.5A.75.75 0 0 1 6 1Z"
-          />
-        </svg>
+        ${PlusIcon({ width: '12', height: '12' })}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@blocksuite/affine/widgets/drag-handle/src/components/add-block-widget.ts`
around lines 66 - 75, Replace the inline SVG in add-block-widget's render with
the shared PlusIcon from `@blocksuite/icons/lit`: remove the <svg> block and
import PlusIcon from "@blocksuite/icons/lit", then render <PlusIcon /> where the
SVG was, preserving accessibility attributes (aria-hidden or aria-label) and any
sizing/className/styles used by the surrounding component (e.g., in
AddBlockWidget or the component that contains the SVG). Ensure the import
statement is added at the top and that existing CSS/props that control
color/size still apply to PlusIcon so visual parity is maintained.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@blocksuite/affine/widgets/drag-handle/src/watchers/pointer-event-watcher.ts`:
- Around line 343-349: The add-block container
(.affine-add-block-widget-container / addBlockWidgetContainer) is sized to
draggingAreaRect.height and left displayed, so it continues to capture pointer
events even when the button isn't rendered; update the logic that
positions/updates addBlockWidgetContainer and AffineDragHandleWidget.hide() to
either set addBlockWidgetContainer.style.display = 'none' (and clear its inline
left/top/height) when showAddBlockWidget is false or when hide() is called (or
mode !== 'page'), or alternatively size the container to the button's intrinsic
height instead of draggingAreaRect.height (use the actual button element's
offsetHeight) so the container no longer spans the full block height; ensure any
inline styles left from previous shows are cleared when hiding to prevent a
lingering, pointer-capturing element.

---

Nitpick comments:
In `@blocksuite/affine/widgets/drag-handle/src/components/add-block-widget.ts`:
- Around line 66-75: Replace the inline SVG in add-block-widget's render with
the shared PlusIcon from `@blocksuite/icons/lit`: remove the <svg> block and
import PlusIcon from "@blocksuite/icons/lit", then render <PlusIcon /> where the
SVG was, preserving accessibility attributes (aria-hidden or aria-label) and any
sizing/className/styles used by the surrounding component (e.g., in
AddBlockWidget or the component that contains the SVG). Ensure the import
statement is added at the top and that existing CSS/props that control
color/size still apply to PlusIcon so visual parity is maintained.

In `@blocksuite/affine/widgets/drag-handle/src/drag-handle.ts`:
- Around line 74-91: The current code calls store.captureSync() before
store.addBlock(...), creating an empty undo step if addBlock returns falsy and
leaving the drag handle visible; fix by calling store.addBlock first and if it
returns falsy call this.hide() and return, otherwise call store.captureSync()
and then proceed with this.host.updateComplete.then(()=>
focusTextModel(this.std, newBlockId)).catch(console.error) so undo history is
only captured for successful adds and UI is always hidden on failure.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3d76dae8-274e-4be3-94b0-a74143155583

📥 Commits

Reviewing files that changed from the base of the PR and between bf6fc66 and ed4b64c.

📒 Files selected for processing (9)
  • blocksuite/affine/blocks/paragraph/src/heading-icon.ts
  • blocksuite/affine/widgets/drag-handle/package.json
  • blocksuite/affine/widgets/drag-handle/src/components/add-block-widget.ts
  • blocksuite/affine/widgets/drag-handle/src/config.ts
  • blocksuite/affine/widgets/drag-handle/src/consts.ts
  • blocksuite/affine/widgets/drag-handle/src/drag-handle.ts
  • blocksuite/affine/widgets/drag-handle/src/effects.ts
  • blocksuite/affine/widgets/drag-handle/src/styles.ts
  • blocksuite/affine/widgets/drag-handle/src/watchers/pointer-event-watcher.ts

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 27, 2026

Codecov Report

❌ Patch coverage is 32.55814% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.20%. Comparing base (5813e7d) to head (b47a8e8).

Files with missing lines Patch % Lines
...uite/affine/widgets/drag-handle/src/drag-handle.ts 26.08% 15 Missing and 2 partials ⚠️
.../drag-handle/src/watchers/pointer-event-watcher.ts 0.00% 7 Missing ⚠️
...ets/drag-handle/src/components/add-block-widget.ts 50.00% 4 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           canary   #14879      +/-   ##
==========================================
- Coverage   58.61%   58.20%   -0.42%     
==========================================
  Files        3190     3191       +1     
  Lines      175351   175394      +43     
  Branches    26020    25953      -67     
==========================================
- Hits       102787   102091     -696     
- Misses      69150    69860     +710     
- Partials     3414     3443      +29     
Flag Coverage Δ
server-test 78.82% <ø> (-0.62%) ⬇️
unittest 34.56% <32.55%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@blocksuite/affine/widgets/drag-handle/src/drag-handle.ts`:
- Around line 72-80: The code assumes parent.children.indexOf(block.model)
yields a valid position but that can be -1 if the hovered block moved or was
deleted; before calling store.addBlock (and after store.captureSync()), compute
index = parent.children.indexOf(block.model) and if index === -1 set a safe
fallback (e.g. insertIndex = parent.children.length to append after the current
children) or bail out; then call store.addBlock with insertIndex (or index + 1)
so you don't accidentally insert at the start when the anchor is stale.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c9f258d8-39fe-439d-8217-d3ff3e25aeba

📥 Commits

Reviewing files that changed from the base of the PR and between e7ea5d5 and 56ec718.

📒 Files selected for processing (1)
  • blocksuite/affine/widgets/drag-handle/src/drag-handle.ts

Comment thread blocksuite/affine/widgets/drag-handle/src/drag-handle.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
blocksuite/affine/widgets/drag-handle/src/drag-handle.ts (1)

261-266: Minor: Dual visibility mechanism for the add-block widget.

The widget's visibility is controlled both via the .visible property (reactive) and by toggling the container's display style in hide() (imperative). This layered approach is defensive but ensure they stay synchronized—if showAddBlockWidget becomes true while the container has display: none from a previous hide() call, the widget won't appear.

The current flow looks correct since hide() resets both, but worth noting for future maintenance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@blocksuite/affine/widgets/drag-handle/src/drag-handle.ts` around lines 261 -
266, The add-block widget visibility is controlled both by the .visible property
(showAddBlockWidget) and by imperatively toggling the container's display in
hide(); ensure these stay synchronized by updating the code path that shows the
widget to also clear the container's display style (or remove the imperative
toggle entirely). Concretely, when setting showAddBlockWidget = true (or in the
method that makes the widget visible), also set the container element's
style.display = '' (or appropriate visible value) so affine-add-block-widget's
.visible and the container DOM style cannot diverge; alternatively consolidate
to a single mechanism by removing the display toggle in hide() and relying
solely on showAddBlockWidget/.visible.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@blocksuite/affine/widgets/drag-handle/src/drag-handle.ts`:
- Line 20: The file drag-handle.ts currently imports focusTextModel via a
relative internal path; update the import to use the package public API by
replacing the relative import with an import from '@blocksuite/affine-rich-text'
so that focusTextModel is imported from the public package export instead of
'../../../rich-text/src/dom.js'.

---

Nitpick comments:
In `@blocksuite/affine/widgets/drag-handle/src/drag-handle.ts`:
- Around line 261-266: The add-block widget visibility is controlled both by the
.visible property (showAddBlockWidget) and by imperatively toggling the
container's display in hide(); ensure these stay synchronized by updating the
code path that shows the widget to also clear the container's display style (or
remove the imperative toggle entirely). Concretely, when setting
showAddBlockWidget = true (or in the method that makes the widget visible), also
set the container element's style.display = '' (or appropriate visible value) so
affine-add-block-widget's .visible and the container DOM style cannot diverge;
alternatively consolidate to a single mechanism by removing the display toggle
in hide() and relying solely on showAddBlockWidget/.visible.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 517111c6-3eef-4853-9c12-6281cfafbd27

📥 Commits

Reviewing files that changed from the base of the PR and between 56ec718 and 5f29bf6.

📒 Files selected for processing (1)
  • blocksuite/affine/widgets/drag-handle/src/drag-handle.ts

Comment thread blocksuite/affine/widgets/drag-handle/src/drag-handle.ts Outdated
@001-mak
Copy link
Copy Markdown
Contributor Author

001-mak commented Apr 28, 2026

@darkskygit please review.

@darkskygit darkskygit force-pushed the canary branch 5 times, most recently from 3403237 to 78a9942 Compare April 29, 2026 11:31
@001-mak 001-mak requested a review from darkskygit May 3, 2026 20:02
@darkskygit darkskygit changed the title feat: add AffineAddBlockWidget feat(editor): add block button for hovering blocks May 3, 2026
@darkskygit
Copy link
Copy Markdown
Member

need to yarn install

@001-mak
Copy link
Copy Markdown
Contributor Author

001-mak commented May 8, 2026

@darkskygit do i need to change something here or the PR is ready to merge?

@darkskygit
Copy link
Copy Markdown
Member

@darkskygit do i need to change something here or the PR is ready to merge?

image image

this pr break some test, please fix them

@001-mak
Copy link
Copy Markdown
Contributor Author

001-mak commented May 8, 2026

@darkskygit do i need to change something here or the PR is ready to merge?

image image
this pr break some test, please fix them

Ok, will do.

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

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants