Skip to content

Add image insertion and editing to docs editor (Phase 1)#121

Merged
hackerwins merged 4 commits into
mainfrom
docs-image-editing-phase1
Apr 12, 2026
Merged

Add image insertion and editing to docs editor (Phase 1)#121
hackerwins merged 4 commits into
mainfrom
docs-image-editing-phase1

Conversation

@hackerwins

@hackerwins hackerwins commented Apr 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Toolbar insert — new Insert image dropdown with Upload from computer and By URL entries, plus drag-and-drop and clipboard paste
  • Click-to-select — 8-handle selection overlay for body and simple table-cell images, with text caret suppression
  • Resize — aspect-ratio-locked corner drag (Shift releases), free side-handle drag, keyboard nudge (arrows ±1 / Shift ±8), drag-lift shadow, and live dimension HUD showing ratio/free state
  • Delete / Esc — standard Google Docs keyboard shortcuts via TextEditor pre-handler

Milestones delivered

# Scope Tests added
M1 ImageData extended with rotation/crop/original* fields, EditorAPI image methods 8
M2 Selection overlay, hit-test, capture-phase click routing, keyboard routing 18
M3 Resize drag state machine, computeResizeDelta, preview rect, cursor hints, drag-lift shadow 14
M4 Toolbar dropdown, image-insert.ts helpers, DnD/paste wiring, clampImageToWidth 7

546 tests pass (pnpm verify:fast EXIT=0).

Bug fixes included

  • collectImageRects double-added margins.left (pl.x already includes it)
  • TextEditor handleMouseMove always reset cursor to text, overriding handle hover hints
  • loadImageDimensions forced CORS crossOrigin="anonymous" which broke non-CORS image hosts
  • InsertImageDropdown didn't reset urlMode on programmatic close
  • Table cell images (non-merged, top-aligned) are now selectable

Out of scope (Phase 2+)

  • Floating context bar (Replace / Alt text / Image options / Delete)
  • Image Options side panel (Size / Rotation / Alt / Reset)
  • Rotation & crop rendering
  • Crop mode
  • Text wrap modes
  • Row-spanning / vertically-aligned table cell images

Test plan

  • Insert image via toolbar Upload from computer → renders at caret
  • Insert image via toolbar By URL → renders inline
  • Drag-and-drop image file onto editor → inserts at drop position
  • Paste screenshot → inserts at caret
  • Click image → 8-handle overlay appears, text caret hidden
  • Corner handle drag → aspect-ratio locked resize with shadow + HUD
  • Shift + corner drag → free resize, HUD shows "free"
  • Side handle drag → single-axis resize
  • Arrow keys on selected image → proportional nudge
  • Delete/Backspace → removes image
  • Esc → deselects, returns to text mode
  • Click outside image → deselects
  • Table cell image click → handles appear correctly
  • Large image paste → auto-clamped to page content width

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Image insertion via upload, URL, drag-and-drop, and clipboard paste
    • Inline image selection with eight-handle resize, keyboard nudging, delete/escape behavior, and single-undo resize
    • Floating context bar and side Image Options (size, aspect lock, free rotation, alt text, Reset)
  • Documentation

    • New design spec and task entries detailing Phase 1 image insertion & editing scope and rollout
  • Tests

    • Extensive tests for image utilities, selection overlay, resize math, and insertion flows

Delivers the first phase of Google Docs–style image support:
toolbar insert (upload / URL), drag-and-drop, clipboard paste,
click-to-select with 8-handle overlay, aspect-ratio-locked resize
with drag shadow + dimension HUD, keyboard nudge, and Delete/Esc.

Key changes across four milestones:

Milestone 1 — Data model & editor API
- ImageData gains rotation, crop*, originalWidth/Height fields
- EditorAPI: insertImage, selectImageAt, clearImageSelection,
  getSelectedImage, updateSelectedImage
- findImageAtOffset / clampImageToWidth helpers

Milestone 2 — Selection state & overlay rendering
- image-selection-overlay.ts: drawImageSelection, collectImageRects,
  hitTestImageHandle/Rect, findImageAtPoint, cursorForHandle
- DocCanvas: imageSelectionRect param, caret suppression when active
- Capture-phase mousedown for image click-to-select
- TextEditor: imageKeyHandler for Delete/Backspace/Esc routing

Milestone 3 — Resize interaction
- computeResizeDelta (dominant-axis aspect lock) + computePreviewRect
- Drag state machine: mousedown → document mousemove/up → single
  undo-step commit on mouseup
- Keyboard nudge (arrows ±1, Shift ±8), proportional scaling
- Cursor hints via TextEditor.imageHoverHandler
- Drag-lift shadow: committed run skipped, preview drawn with
  ctx.shadowBlur=16 drop shadow

Milestone 4 — Insert flows
- Toolbar InsertImageDropdown: Upload from computer + By URL
- image-insert.ts: uploadImageFile, loadImageDimensions,
  insertImageFromFile, insertImageFromUrl
- EditorAPI.onImageFileDrop callback for DnD + clipboard paste
- TextEditor.imageFilePasteHandler intercepts image/* files
- insertImage auto-clamps to page content width

Bug fixes applied during development:
- Double-margin offset in collectImageRects (pl.x already includes
  margins.left)
- TextEditor mousemove always reset cursor to 'text', overriding
  handle hover hints
- loadImageDimensions crossOrigin="anonymous" broke non-CORS hosts
- InsertImageDropdown urlMode not reset on programmatic close
- Table cell images now selectable (non-merged, top-aligned cells)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@coderabbitai

coderabbitai Bot commented Apr 12, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@hackerwins has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 38 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 1 minutes and 38 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0167d234-395f-4467-b6a8-d0d50b3d8aa6

📥 Commits

Reviewing files that changed from the base of the PR and between c1ae234 and 5a13af0.

📒 Files selected for processing (8)
  • docs/design/docs/docs-image-editing.md
  • docs/tasks/active/20260412-docs-image-editing-todo.md
  • packages/docs/src/model/types.ts
  • packages/docs/src/view/editor.ts
  • packages/docs/src/view/text-editor.ts
  • packages/frontend/src/app/docs/docs-formatting-toolbar.tsx
  • packages/frontend/src/app/docs/docs-view.tsx
  • packages/frontend/src/app/docs/image-insert.ts
📝 Walkthrough

Walkthrough

Adds Phase 1 inline image insertion and editing: new ImageData fields (rotation/crop/original dims), EditorAPI image methods, image selection/resize overlay with eight handles, rendering/hit-testing/resize math, toolbar/drag‑drop/paste insertion flows, and accompanying tests and docs.

Changes

Cohort / File(s) Summary
Docs & Tasks
docs/design/README.md, docs/design/docs/docs-image-editing.md, docs/tasks/README.md, docs/tasks/active/...
Added design spec and task entries for Phase 1 image editing and lessons/todo files.
Model Types & Tests
packages/docs/src/model/types.ts, packages/docs/test/model/types.test.ts
Extended ImageData with rotation, crop, original dims; added clampImageToWidth() and findImageAtOffset(); updated equality logic and tests.
Canvas & Table Rendering
packages/docs/src/view/doc-canvas.ts, packages/docs/src/view/table-renderer.ts
DocCanvas.render signature extended to accept image overlay/drag inputs; suppresses caret and draws drag preview/shadow; table renderer can skip the dragging run.
Editor Core & Interaction
packages/docs/src/view/editor.ts, packages/docs/src/view/text-editor.ts
Extended EditorAPI with image insert/select/update hooks, image selection state and resize-drag lifecycle, keyboard/hover/file-drop handlers, and text-editor integration hooks for image events.
Image Selection Overlay
packages/docs/src/view/image-selection-overlay.ts, packages/docs/test/view/image-selection-overlay.test.ts
New overlay module: ImageRect model, handle enums, drawing helpers, hit-testing, layout traversal, resize math, preview rect computation; comprehensive view tests added.
Frontend Insert UI & Utilities
packages/frontend/src/app/docs/docs-formatting-toolbar.tsx, packages/frontend/src/app/docs/image-insert.ts, packages/frontend/src/app/docs/docs-view.tsx
Added InsertImageDropdown (upload/URL), image upload/dimension utilities, insert flows, and registered editor file-drop handler in DocsView.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend as Frontend UI<br/>(Toolbar / Upload)
    participant Editor as Editor<br/>(EditorAPI)
    participant Model as Data Model<br/>(types.ts)
    participant Renderer as Renderer<br/>(DocCanvas)

    rect rgba(100, 150, 255, 0.5)
    Note over User,Frontend: Image insertion path
    User->>Frontend: Upload / Paste / Enter URL / Drag-drop
    Frontend->>Frontend: uploadImageFile() / loadImageDimensions()
    Frontend->>Editor: insertImage(url, w, h, opts)
    Editor->>Model: create & insert ImageData
    Editor->>Renderer: requestRender()
    Renderer-->>User: render inline image
    end

    rect rgba(150, 200, 100, 0.5)
    Note over User,Editor: Selection & resize flow
    User->>Renderer: Click image / Pointer down on handle
    Renderer->>Editor: hitTest → selectImageAt()
    Editor->>Renderer: compute imageSelectionRect & requestRender()
    Renderer-->>User: draw selection overlay with 8 handles
    User->>Editor: drag handle (mousemove)
    Editor->>Editor: computeResizeDelta() / computePreviewRect()
    Editor->>Renderer: requestRender(previewRect, dragRun)
    Renderer-->>User: show drag preview + shadow
    User->>Editor: mouseup
    Editor->>Model: commit updateSelectedImage(patch) (single undo)
    Editor->>Renderer: requestRender()
    Renderer-->>User: render updated image
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

Poem

🐰 Hop-hop—pixels snug in a line,

I tuck an image, rotate, and align.
Eight tiny handles, a gentle tug—resize!
From paste to toolbar, the canvas replies.
A rabbit’s cheer for images, crisp and fine.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 79.31% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title accurately summarizes the primary changes: adding image insertion and editing functionality to the docs editor in Phase 1, which matches the comprehensive changeset across UI, data model, selection/overlay rendering, and resize interactions.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs-image-editing-phase1

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.

@github-actions

github-actions Bot commented Apr 12, 2026

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 114.7s

Lane Status Duration
sheets:build ✅ pass 13.1s
docs:build ✅ pass 7.7s
verify:fast ✅ pass 58.3s
frontend:build ✅ pass 15.0s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 4.7s
cli:build ✅ pass 1.8s
verify:entropy ✅ pass 13.9s

Verification: verify:integration

Result: ✅ PASS

@codecov

codecov Bot commented Apr 12, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (2)
packages/docs/src/model/types.ts (1)

274-287: Consider guarding against non-positive maxWidth.

The function handles width <= 0 defensively, but if maxWidth is zero or negative (e.g., miscalculated content area), the scale computation would produce invalid dimensions. This is an edge case, but adding a similar guard for maxWidth would make the function fully defensive.

🛡️ Optional defensive fix
 export function clampImageToWidth(
   width: number,
   height: number,
   maxWidth: number,
 ): { width: number; height: number } {
-  if (width <= maxWidth || width <= 0) {
+  if (width <= maxWidth || width <= 0 || maxWidth <= 0) {
     return { width, height };
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/src/model/types.ts` around lines 274 - 287, In
clampImageToWidth, add a guard for non-positive maxWidth (e.g., if maxWidth <=
0) to avoid dividing by zero or producing invalid scales; return the original {
width, height } (same behavior as the existing width <= 0 case) or normalize
maxWidth to a safe minimum (Math.max(1, maxWidth)) before computing scale so the
function never computes an invalid scale.
packages/docs/src/view/editor.ts (1)

2077-2093: Consider clearing image-related state in dispose() to prevent stale references.

When dispose() is called during an active resize drag, imageResizeDrag retains references to DOM measurement data and the callback imageFileDropCallback may still hold a closure. While not a functional bug (the listeners are removed), explicitly clearing this state would be more defensive.

🧹 Proposed cleanup additions
     dispose: () => {
       peerCursors = [];
       cursorMoveCallback = null;
       lastPeerPixels = [];
+      selectedImage = null;
+      imageResizeDrag = null;
+      imageFileDropCallback = null;
       ruler.dispose();
       cursor.dispose();
       textEditor?.dispose();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/src/view/editor.ts` around lines 2077 - 2093, In dispose() add
explicit cleanup for image-related state: clear/reset the imageResizeDrag object
(or set imageResizeDrag = null) to drop any DOM measurement refs and any drag
state, and set imageFileDropCallback = null (and any other image-related
callback vars) to remove closure references; keep the rest of the existing
listener removals and resource disposals (ruler, cursor, textEditor,
resizeObserver, canvas) intact. Ensure you reference the same symbols used in
this file (dispose, imageResizeDrag, imageFileDropCallback) so the garbage
collector can reclaim DOM-related memory when the editor is torn down.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/design/docs/docs-image-editing.md`:
- Around line 83-90: The docs for insertImage are out of sync: update the
documented signature and behavior to match the actual implementation's API
(insertImage(src, width, height, opts?) in packages/docs/src/view/editor.ts) and
note that it inserts at the caret (does not replace non-collapsed selections)
rather than replacing selections; adjust parameter names (naturalWidth→width,
naturalHeight→height, alt→opts) and describe the opts structure if applicable so
tests and callers aren't misled.
- Around line 148-179: Update the docs to reflect the actual rollout phases:
change any language in the "Floating Context Bar" and "Image Options Side Panel"
sections (and the other occurrence around lines noted) that claims these
features "ship in Phase 1" to indicate they are planned for later milestones
(Milestones 5 and 6) or mark them as Phase 2+ work; ensure the doc explicitly
references that these features are out-of-scope for Phase 1, mention that they
are pending in Milestones 5/6, and keep the feature descriptions but add a short
"Planned in later milestones" note under each heading.

In `@docs/tasks/active/20260412-docs-image-editing-todo.md`:
- Around line 129-132: Update the By-URL path note for
insertImageFromUrl(editor, url) to reflect that the preflight no longer forces
crossOrigin="anonymous"; instead validate the http[s]:// prefix, perform a
lightweight preflight/load without setting crossOrigin so we don't force CORS,
and document that natural dimensions are used and that callers should handle any
CORS requirements if they need cross-origin tainting protection.

In `@packages/frontend/src/app/docs/docs-formatting-toolbar.tsx`:
- Around line 147-152: The current handleUrlSubmit closes and clears the URL
form (closeAndReset) before insertImageFromUrl completes, losing user input on
failure; change handleUrlSubmit to await insertImageFromUrl(editor, url) and
only call closeAndReset() if that call returns a success true (or does not
throw), otherwise leave the form open and surface the error; update the
insertImageFromUrl helper to return a boolean success flag (or throw on fatal
errors) instead of swallowing failures so handleUrlSubmit can decide whether to
close the form; ensure you reference handleUrlSubmit, insertImageFromUrl,
closeAndReset, urlInput and editor when making these changes.

In `@packages/frontend/src/app/docs/image-insert.ts`:
- Around line 78-111: insertImageFromUrl currently persists third‑party
hotlinked URLs; change it to fetch/proxy and store the image on our trusted
storage before calling editor.insertImage so viewers won't hotlink external
hosts. Keep the existing validation and loadImageDimensions call to get
width/height, then call a new or existing helper (e.g. uploadImageFromUrl or
proxyFetchImage) to download and upload the remote image to first‑party storage
or return a proxied URL, and pass that returned hosted URL (not the original
trimmed URL) into EditorAPI.insertImage while preserving
originalWidth/originalHeight metadata and error handling.

---

Nitpick comments:
In `@packages/docs/src/model/types.ts`:
- Around line 274-287: In clampImageToWidth, add a guard for non-positive
maxWidth (e.g., if maxWidth <= 0) to avoid dividing by zero or producing invalid
scales; return the original { width, height } (same behavior as the existing
width <= 0 case) or normalize maxWidth to a safe minimum (Math.max(1, maxWidth))
before computing scale so the function never computes an invalid scale.

In `@packages/docs/src/view/editor.ts`:
- Around line 2077-2093: In dispose() add explicit cleanup for image-related
state: clear/reset the imageResizeDrag object (or set imageResizeDrag = null) to
drop any DOM measurement refs and any drag state, and set imageFileDropCallback
= null (and any other image-related callback vars) to remove closure references;
keep the rest of the existing listener removals and resource disposals (ruler,
cursor, textEditor, resizeObserver, canvas) intact. Ensure you reference the
same symbols used in this file (dispose, imageResizeDrag, imageFileDropCallback)
so the garbage collector can reclaim DOM-related memory when the editor is torn
down.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2508f4ae-8288-43f8-b0b3-ac67914b0095

📥 Commits

Reviewing files that changed from the base of the PR and between 83705ac and a378582.

📒 Files selected for processing (16)
  • docs/design/README.md
  • docs/design/docs/docs-image-editing.md
  • docs/tasks/README.md
  • docs/tasks/active/20260412-docs-image-editing-lessons.md
  • docs/tasks/active/20260412-docs-image-editing-todo.md
  • packages/docs/src/model/types.ts
  • packages/docs/src/view/doc-canvas.ts
  • packages/docs/src/view/editor.ts
  • packages/docs/src/view/image-selection-overlay.ts
  • packages/docs/src/view/table-renderer.ts
  • packages/docs/src/view/text-editor.ts
  • packages/docs/test/model/types.test.ts
  • packages/docs/test/view/image-selection-overlay.test.ts
  • packages/frontend/src/app/docs/docs-formatting-toolbar.tsx
  • packages/frontend/src/app/docs/docs-view.tsx
  • packages/frontend/src/app/docs/image-insert.ts

Comment thread docs/design/docs/docs-image-editing.md Outdated
Comment thread docs/design/docs/docs-image-editing.md Outdated
Comment thread docs/tasks/active/20260412-docs-image-editing-todo.md Outdated
Comment thread packages/frontend/src/app/docs/docs-formatting-toolbar.tsx Outdated
Comment on lines +78 to +111
/**
* Validate + insert an image URL typed by the user in the toolbar.
* Unlike `insertImageFromFile`, this skips the upload step — the URL
* is inserted as-is so external images (e.g. `https://...` referenced
* from another host) do not get re-uploaded. The preflight load still
* happens so we capture the natural dimensions and fail early on 404 /
* CORS errors.
*/
export async function insertImageFromUrl(
editor: EditorAPI,
url: string,
): Promise<void> {
const trimmed = url.trim();
if (!trimmed) return;
if (!/^https?:\/\//i.test(trimmed)) {
toast.error("Image URL must start with http:// or https://");
return;
}
try {
const { width, height } = await loadImageDimensions(trimmed);
editor.insertImage(trimmed, width, height, {
originalWidth: width,
originalHeight: height,
});
editor.focus();
} catch (err) {
console.error("Image URL insert failed", err);
toast.error(
err instanceof Error
? `Couldn't load image: ${err.message}`
: "Couldn't load image",
);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid persisting third-party image hotlinks.

insertImageFromUrl() stores the remote URL as-is. That means every future viewer of the doc will fetch arbitrary third-party resources from their browser, which is a privacy/tracking leak and makes rendering depend on an external host staying reachable. Please import URL-based images into first-party storage (or proxy them through a trusted backend path) before persisting style.image.src.

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

In `@packages/frontend/src/app/docs/image-insert.ts` around lines 78 - 111,
insertImageFromUrl currently persists third‑party hotlinked URLs; change it to
fetch/proxy and store the image on our trusted storage before calling
editor.insertImage so viewers won't hotlink external hosts. Keep the existing
validation and loadImageDimensions call to get width/height, then call a new or
existing helper (e.g. uploadImageFromUrl or proxyFetchImage) to download and
upload the remote image to first‑party storage or return a proxied URL, and pass
that returned hosted URL (not the original trimmed URL) into
EditorAPI.insertImage while preserving originalWidth/originalHeight metadata and
error handling.

hackerwins and others added 2 commits April 12, 2026 11:31
dragover handler checked dataTransfer.files which browsers leave
empty during dragover for security. Without preventDefault() the
browser falls through to its default: navigate to the dropped image
URL. Switch to dataTransfer.items (which exposes MIME types during
drag) for the dragover check, and dataTransfer.files for the actual
drop where the File object is needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Sync design doc insertImage signature with actual API (src, width,
  height, opts?) and note that caret-insert (not selection-replace)
  is the current behavior
- Mark Floating Context Bar and Image Options Side Panel as
  "Planned — Milestone 5/6" in design doc
- Update task doc: remove stale crossOrigin="anonymous" reference,
  note dataTransfer.items fix for dragover
- insertImageFromUrl returns boolean so toolbar keeps URL form open
  on failure instead of discarding user input
- Guard clampImageToWidth against non-positive maxWidth
- Clear selectedImage/imageResizeDrag/imageFileDropCallback in
  dispose() to prevent stale closure references

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

Inline comments:
In `@packages/docs/src/view/editor.ts`:
- Around line 1414-1435: The drop/paste flow (handleImageDrop) currently moves
the global cursor with cursor.moveTo then calls imageFileDropCallback(file),
which lets async uploads later insert at the current cursor instead of the
original drop position; capture the resolved DocPosition (use the hit from
paginatedPixelToPosition or the current cursor position if no hit) immediately
and pass that position (or an insertion closure bound to it) into
imageFileDropCallback instead of only the File so downstream code (e.g.,
insertImage) can insert at the original drop/paste location; apply the same
pattern to the other paste handler around the 2049-2058 area.
- Around line 1263-1299: handleImageResizeMouseUp and the related image-editing
paths (imageResizeDrag, handleImageResizeMouseMove, selection logic at
findImageAtOffset, and the mutation call doc.applyInlineStyle) are allowed to
run even when readOnly is true; guard these paths and the public mutating APIs
(insertImage, updateSelectedImage) with a check for !readOnly. Specifically,
before wiring event handlers or starting a drag/selection (where imageResizeDrag
is set and handleImageResizeMouseMove/handleImageResizeMouseUp are attached) and
before committing mutations (the docStore.snapshot()/doc.applyInlineStyle
sequence in handleImageResizeMouseUp), return early if readOnly is true;
likewise add the same !readOnly guard to insertImage and updateSelectedImage so
they no-op or throw when readOnly is set. Ensure rendering/cleanup still happens
(remove listeners, reset cursor) but prevent any state changes or
document.applyInlineStyle calls when readOnly is true.
- Around line 1352-1360: When an image is selected via findImageAtPoint (the
code path that sets selectedImage, calls cursor.moveTo and render), clear any
active text selection by calling selection.setRange(null) so printable keys
don't first clear selectedImage and then apply normal replace-selection
behavior; add the same selection.setRange(null) in the other image-selection
branch (the block around the second occurrence of selectedImage/cursor.moveTo at
the 2004-2010 area) to ensure consistent image-selection mode.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 60dddcc2-af0c-40b0-8c46-f3dcfa58eaa7

📥 Commits

Reviewing files that changed from the base of the PR and between a378582 and c1ae234.

📒 Files selected for processing (1)
  • packages/docs/src/view/editor.ts

Comment thread packages/docs/src/view/editor.ts
Comment thread packages/docs/src/view/editor.ts
Comment thread packages/docs/src/view/editor.ts
A) Gate image editing paths behind !readOnly so a read-only editor
   cannot select, resize, drag-drop, or mutate images. TextEditor is
   already null when readOnly; this guards the container-level
   listeners and the insertImage/updateSelectedImage API methods.

B) Clear selection.range when entering image-selection mode so a
   stale text selection doesn't cause unintended text deletion when
   the user types while an image is selected.

C) Capture the resolved insertion position at drop/paste time and
   thread it through the callback → insertImageFromFile → insertImage
   so async uploads land at the original drop location even if the
   cursor moves during the upload.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@hackerwins
hackerwins merged commit 3a4c4e8 into main Apr 12, 2026
1 check passed
@hackerwins
hackerwins deleted the docs-image-editing-phase1 branch April 12, 2026 02:49
hackerwins added a commit that referenced this pull request Apr 19, 2026
- random-axis-id (#127)
- axis-id-selection (#130)
- docs-image-editing Phase 1 (#121, #123)
- nested-tables (#128, #129)
- docs-mobile-toolbar (#132)
- release-v0.3.3 (#131)
- docs-image-editing lessons
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant