Skip to content

[WEB-4325] chore: additional changes for mobile editor support #7212

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: preview
Choose a base branch
from

Conversation

1akhanBaheti
Copy link
Collaborator

@1akhanBaheti 1akhanBaheti commented Jun 13, 2025

Description

This PR includes updates to support the mobile editor.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring

Summary by CodeRabbit

  • New Features

    • Enhanced mobile support with new mobile-specific props and UI adjustments across editor components.
    • Added configurable drag-and-drop functionality toggle.
    • Introduced new editor commands including undo, redo, advanced focus, selection, and attribute retrieval.
    • Added a link menu item for streamlined link management.
    • Provided callbacks for initial content load and editor click events.
  • Style

    • Added mobile-specific font sizes and line spacing for improved readability.
    • Updated placeholder styling to display on every empty line in the editor.
  • Bug Fixes

    • Improved image handling on mobile devices, including selection behavior, upload flow, and error source resolution.
  • Chores

    • Enabled cleaning of the output directory before each build for more reliable builds.

@1akhanBaheti 1akhanBaheti self-assigned this Jun 13, 2025
Copy link
Contributor

coderabbitai bot commented Jun 13, 2025

Walkthrough

This update introduces comprehensive support for mobile-specific features in the editor, including new props, types, and conditional UI logic for mobile rendering. It enhances editor APIs with new commands, selection, and attribute retrieval methods, and adds a link menu item. CSS changes introduce mobile font and spacing variants, and placeholder rules are adjusted for consistency across platforms.

Changes

Files/Groups Change Summary
.../editors/document/collaborative-editor.tsx, .../editors/document/page-renderer.tsx,
.../editors/document/read-only-editor.tsx, .../editors/editor-container.tsx,
.../editors/editor-content.tsx, .../editors/editor-wrapper.tsx,
.../editors/read-only-editor-wrapper.tsx
Added/propagated isMobile prop and related options for mobile-aware rendering and event handling; new props for editor configuration and event callbacks; updated component signatures accordingly.
.../menus/menu-items.ts Added a new link menu item with active state and command logic; updated divider item typing.
.../extensions/custom-image/components/image-block.tsx, .../extensions/custom-image/components/image-node.tsx,
.../extensions/custom-image/components/image-uploader.tsx, .../extensions/custom-image/components/toolbar/full-screen.tsx,
.../extensions/custom-image/components/toolbar/root.tsx, .../extensions/custom-image/custom-image.tsx
Introduced isMobile prop for image components; added mobile-specific logic for image interaction, error handling, and toolbar display.
.../helpers/editor-commands.ts Updated setLinkEditor to support optional text insertion and selection logic.
.../hooks/use-collaborative-editor.ts Made drag-and-drop enablement configurable via isDragDropEnabled prop.
.../hooks/use-editor.ts Added initial content load detection, new editor API methods (undo, redo, focus, selection, attribute retrieval), and improved selection/attribute logic.
.../types/collaboration.ts, .../types/config.ts,
.../types/editor.ts
Extended types/interfaces for mobile support, drag-and-drop, new editor commands, and richer editor API.
.../styles/editor.css, web/styles/editor.css,
space/styles/editor.css
Updated placeholder rules for empty lines; added or adjusted placeholder display for empty paragraphs.
.../styles/variables.css Added mobile font size and line spacing CSS variables.
web/styles/globals.css, space/styles/globals.css Added import for external editor.css stylesheet.
.../tsup.config.ts Enabled output directory cleaning before build.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant EditorComponent
    participant PageRenderer
    participant EditorContainer
    participant CustomImageNode
    participant ImageBlock
    participant Toolbar

    User->>EditorComponent: Open editor (mobile or desktop)
    EditorComponent->>PageRenderer: Pass isMobile, onEditorClick, etc.
    PageRenderer->>EditorContainer: Pass isMobile
    PageRenderer->>CustomImageNode: Pass isMobile for image rendering
    CustomImageNode->>ImageBlock: Pass isMobile for image interaction
    ImageBlock->>Toolbar: Pass showExternalLink = !isMobile
    User->>EditorComponent: Interact (drag-drop, click, etc.)
    EditorComponent->>EditorContainer: Enable/disable features based on isMobile and isDragDropEnabled
Loading

Suggested labels

🌐frontend, 🛠️refactor, ✍️editor, ready_to_merge

Suggested reviewers

  • vamsikrishnamathala
  • prateekshourya29

Poem

In fields of code, the rabbits hop,
Now editors gleam on every stop.
Mobile or desktop, features abound,
Images, links, and menus found.
With styles that flex and APIs anew,
This patch brings freshness—cheers to the crew!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@1akhanBaheti 1akhanBaheti changed the title chore: additional changes for mobile editor support [WEB-4325] chore: additional changes for mobile editor support Jun 16, 2025
Copy link

makeplane bot commented Jun 16, 2025

Pull Request Linked with Plane Work Items

Comment Automatically Generated by Plane

@1akhanBaheti 1akhanBaheti marked this pull request as ready for review June 16, 2025 08:11
Copy link
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: 5

🔭 Outside diff range comments (1)
packages/editor/src/core/components/editors/editor-content.tsx (1)

4-10: 🛠️ Refactor suggestion

Incorrect onClick type constrains legitimate handlers

React’s click handler receives a MouseEvent, but the type here omits that parameter, preventing callers from accessing the event object.

-  onClick?: () => void;
+  onClick?: React.MouseEventHandler<HTMLDivElement>;
🧹 Nitpick comments (10)
packages/editor/src/core/extensions/custom-image/components/toolbar/root.tsx (1)

15-20: Provide default for showExternalLink to avoid undefined downstream

ImageFullScreenAction expects a boolean; forwarding undefined could produce unintended UI states. Supply a default:

-type Props = {
+type Props = {
   ...
-  showExternalLink: boolean;
+  showExternalLink?: boolean;
 };

And inside the component:

-const { containerClassName, image, showExternalLink } = props;
+const { containerClassName, image, showExternalLink = false } = props;

Also applies to: 30-34

packages/editor/src/core/hooks/use-collaborative-editor.ts (1)

85-89: Guard against undefined when passing flag to SideMenuExtension.

After adding the default suggested above this becomes safe, but at the moment dragDropEnabled may receive undefined.

-        dragDropEnabled: isDragDropEnabled,
+        dragDropEnabled: !!isDragDropEnabled,

A coercion makes the intent explicit and prevents accidental “truthy / falsy” surprises.

packages/editor/src/core/components/editors/editor-container.tsx (2)

19-24: Mark isMobile optional or give it a default.

isMobile is required by the EditorContainerProps interface, but almost every existing desktop call-site will now need to be updated.
If the majority of usages are desktop-only, consider:

-  isMobile: boolean;
+  isMobile?: boolean; // defaults to false

and within the component

-  const { …, isMobile } = props;
+  const { …, isMobile = false } = props;

This avoids a breaking change across the codebase.


98-101: Early-return instead of fragment for cleaner DOM.

You already gate LinkViewContainer on !isMobile. Doing the same for the wrapper could avoid an extra React fragment:

-        {children}
-        {!isMobile && <LinkViewContainer … />}
+        {children}
+        {!isMobile && <LinkViewContainer … />}

No functional change, purely a readability/DOM-tidiness tweak.

packages/editor/src/core/extensions/custom-image/custom-image.tsx (2)

45-50: Minor: isMobile leaks into every extension instance even when unused.

Passing the flag is fine, but consider early-returning the mobile-specific props from addNodeView() only when isMobile is true to avoid unnecessary prop bloat in desktop renders.


178-179: Type safety – tighten prop typing for node view factory.

CustomImageNodeProps already includes isMobile, so the cast is redundant:

-      return ReactNodeViewRenderer((props: CustomImageNodeProps) => <CustomImageNode {...props} isMobile={isMobile} />);
+      return ReactNodeViewRenderer((props) => <CustomImageNode {...props} isMobile={isMobile} />);

Keeps the generic closer to the actual value and avoids an unnecessary import.

packages/editor/src/core/types/config.ts (1)

19-20: isMobile belongs in a dedicated mobile-config structure.

Long-term, adding mobile-specific flags to generic handler types risks clutter.
Consider grouping all mobile-only knobs into a single mobile?: { … } object for clearer intent.

packages/editor/src/core/extensions/custom-image/components/image-node.tsx (1)

53-60: Missing editor in effect dependency list

The async getImageSource effect dereferences editor, but editor isn’t listed in the dependency array.
If the editor instance is ever replaced (e.g. during collaborative re-initialisation) the effect will not re-run, leaving resolvedSrc stale.

-  }, [imgNodeSrc]);
+  }, [editor, imgNodeSrc]);
packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx (2)

213-228: img element missing alt text

For accessibility the fullscreen image should carry an alt attribute (even if empty) to avoid screen-reader confusion.

-          <img
+          <img
+            alt=""

268-279: showExternalLink flag name vs. behaviour

The conditional block renders a fullscreen toggle, not an external-link opener; the naming is mildly confusing. Consider renaming the prop to showFullScreenToggle (or similar) to match intent.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6fe0415 and b07351c.

📒 Files selected for processing (27)
  • packages/editor/src/core/components/editors/document/collaborative-editor.tsx (3 hunks)
  • packages/editor/src/core/components/editors/document/page-renderer.tsx (2 hunks)
  • packages/editor/src/core/components/editors/document/read-only-editor.tsx (3 hunks)
  • packages/editor/src/core/components/editors/editor-container.tsx (2 hunks)
  • packages/editor/src/core/components/editors/editor-content.tsx (1 hunks)
  • packages/editor/src/core/components/editors/editor-wrapper.tsx (4 hunks)
  • packages/editor/src/core/components/editors/read-only-editor-wrapper.tsx (2 hunks)
  • packages/editor/src/core/components/menus/menu-items.ts (5 hunks)
  • packages/editor/src/core/extensions/custom-image/components/image-block.tsx (4 hunks)
  • packages/editor/src/core/extensions/custom-image/components/image-node.tsx (4 hunks)
  • packages/editor/src/core/extensions/custom-image/components/image-uploader.tsx (4 hunks)
  • packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx (6 hunks)
  • packages/editor/src/core/extensions/custom-image/components/toolbar/root.tsx (2 hunks)
  • packages/editor/src/core/extensions/custom-image/custom-image.tsx (3 hunks)
  • packages/editor/src/core/helpers/editor-commands.ts (1 hunks)
  • packages/editor/src/core/hooks/use-collaborative-editor.ts (2 hunks)
  • packages/editor/src/core/hooks/use-editor.ts (9 hunks)
  • packages/editor/src/core/types/collaboration.ts (1 hunks)
  • packages/editor/src/core/types/config.ts (1 hunks)
  • packages/editor/src/core/types/editor.ts (8 hunks)
  • packages/editor/src/styles/editor.css (0 hunks)
  • packages/editor/src/styles/variables.css (3 hunks)
  • packages/editor/tsup.config.ts (1 hunks)
  • space/styles/editor.css (1 hunks)
  • space/styles/globals.css (1 hunks)
  • web/styles/editor.css (1 hunks)
  • web/styles/globals.css (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/editor/src/styles/editor.css
🧰 Additional context used
🧬 Code Graph Analysis (4)
packages/editor/src/core/components/menus/menu-items.ts (1)
packages/editor/src/core/helpers/editor-commands.ts (2)
  • setLinkEditor (142-157)
  • unsetLinkEditor (138-140)
packages/editor/src/core/components/editors/document/page-renderer.tsx (1)
packages/editor/src/core/components/editors/editor-content.tsx (1)
  • EditorContentWrapper (12-26)
packages/editor/src/core/extensions/custom-image/custom-image.tsx (1)
packages/editor/src/core/extensions/custom-image/components/image-node.tsx (2)
  • CustomImageNodeProps (21-21)
  • CustomImageNode (23-96)
packages/editor/src/core/types/editor.ts (3)
packages/editor/src/core/types/config.ts (2)
  • TDisplayConfig (28-33)
  • TFileHandler (7-20)
packages/editor/src/core/types/extensions.ts (1)
  • TExtensions (1-1)
packages/editor/src/core/types/mention.ts (1)
  • TMentionHandler (26-28)
🔇 Additional comments (25)
packages/editor/tsup.config.ts (1)

7-7: Approve adding clean: true for fresh build outputs.

Enabling clean ensures stale artifacts are removed before each build, which helps avoid inconsistent bundles in the mobile editor package.

space/styles/globals.css (1)

2-2: Confirm editor.css import precedence and path
The URL import for editor.css is placed before Tailwind directives to ensure editor-specific styles load globally. Verify that the path resolves correctly in the production CSS build.

web/styles/globals.css (1)

2-2: Confirm editor.css import for web global styles
This URL import brings in the editor-specific stylesheet; ensure the build pipeline includes and resolves this import for the web environment.

space/styles/editor.css (1)

1-8: Placeholder styling for empty paragraphs
The .ProseMirror p.is-empty::before rule correctly uses content: attr(data-placeholder) and --color-placeholder. This aligns with the new placeholder behavior—looks good.

web/styles/editor.css (1)

1-8: Placeholder styling for .ProseMirror p.is-empty
This new rule mirrors the implementation in space/styles/editor.css and properly displays placeholders using the data-placeholder attribute and --color-placeholder.

packages/editor/src/styles/variables.css (2)

91-111: Introduce .mobile-font variant for mobile editing
The new .mobile-font class defines mobile-optimized font sizes and line heights. Confirm that the editor components apply this class when isMobile is true.


170-190: Add .line-spacing-mobile-regular spacing scheme
Mobile-specific line spacing is introduced here. Validate that this class is applied in mobile contexts and that its padding values match the design guidelines.

packages/editor/src/core/types/collaboration.ts (1)

41-46:

✅ Verification successful

isDragDropEnabled / isMobile should be optional or have sane defaults

Making both flags mandatory forces every caller to update immediately and may break downstream build-time contracts. Unless the intent is to require explicit opt-in on every usage, declare them as optional with a default value in the consuming component/hook.

-  isDragDropEnabled: boolean;
-  isMobile: boolean;
+  /**
+   * Enables drag-drop in the editor. Defaults to `true` on desktop.
+   */
+  isDragDropEnabled?: boolean;
+  /**
+   * Marks the editor as running in a mobile context. Defaults to `false`.
+   */
+  isMobile?: boolean;

Ensure all hooks/components that consume these props provide fallback values so behaviour stays unchanged for existing callers.


🏁 Script executed:

#!/bin/bash
set -e

echo "🔍 Searching for usages of isDragDropEnabled:"
rg --color=never -n "isDragDropEnabled" -A3

echo -e "\n🔍 Searching for usages of isMobile:"
rg --color=never -n "isMobile" -A3

Length of output: 83449


🏁 Script executed:

#!/bin/bash
# Display the beginning of the hook file to inspect its signature
sed -n '1,60p' packages/editor/src/core/hooks/use-collaborative-editor.ts

Length of output: 1998


Here’s the head of docs/packages/editor/src/core/components/editors/document/collaborative-editor.tsx showing prop defaults and hook invocation:

export const CollaborativeEditor: React.FC<TCollaborativeEditorProps> = ({
  id,
  isDragDropEnabled = true,
  isMobile = false,
  mentionHandler,
  placeholder,
  onTransaction,
  // …other props
}) => {
  const editor = useCollaborativeEditor({
    id,
    isDragDropEnabled,
    isMobile,
    mentionHandler,
    placeholder,
    onTransaction,
    // …forward remaining props
  });

  // …
};

And the hook signature:

export const useCollaborativeEditor = (props: TCollaborativeEditorProps) => {
  const {
    // …,
    isDragDropEnabled,
    isMobile,
    // …
  } = props;
  // …
  Collaboration.configure({ dragDropEnabled: isDragDropEnabled /*bool*/ });
};

Since every consuming component (right now only CollaborativeEditor) already provides defaults (true / false), making these flags optional in the type is safe: callers that omit them will get the intended defaults.

Proposed change:

 in packages/editor/src/core/types/collaboration.ts
-  isDragDropEnabled: boolean;
-  isMobile: boolean;
+  /**
+   * Enables drag-drop in the editor. Defaults to `true` when not provided.
+   */
+  isDragDropEnabled?: boolean;
+  /**
+   * Marks the editor as running in a mobile context. Defaults to `false` when not provided.
+   */
+  isMobile?: boolean;

No further updates needed—defaults live in CollaborativeEditor.

packages/editor/src/core/components/editors/read-only-editor-wrapper.tsx (1)

23-26: Prop type drift – confirm IReadOnlyEditorProps & EditorContainer accept isMobile

isMobile is now threaded through but the source type comes from @/types. If that interface wasn’t updated, TS will error. Likewise, EditorContainer must declare isMobile?: boolean. Double-check both points to avoid compilation failures.

Also applies to: 45-50

packages/editor/src/core/components/editors/document/read-only-editor.tsx (1)

33-34: Mobile flag plumbing looks good – mind the optional chain

Passing isMobile={isMobile} through PageRenderer is consistent with other components – nice.

Just verify PageRenderer makes the prop optional (isMobile?: boolean) and sets a default to maintain backwards compatibility.

Also applies to: 49-50, 82-89

packages/editor/src/core/components/editors/editor-content.tsx (1)

16-22: Good focus handling & new CSS hook

The extra "editor-content" class and onFocus chain keep UX intact. LGTM.

packages/editor/src/core/hooks/use-collaborative-editor.ts (1)

14-34:

❌ Incorrect review comment

Provide a safe default for isDragDropEnabled.

isDragDropEnabled is taken straight from props without a default.
If the caller forgets to pass it, undefined will reach SideMenuExtension, which most likely expects a boolean and could break its internal checks.

-    isDragDropEnabled,
+    isDragDropEnabled = true,

This keeps the previous behaviour intact while still allowing the flag to be explicitly switched off.

Also applies to: 27-28


🏁 Script executed:

#!/bin/bash
set -e

# Locate the prop interface to see if isDragDropEnabled is optional or required
rg "interface TCollaborativeEditorProps" -A5 -n packages/editor/src

# Locate SideMenuExtension and inspect its constructor/options for drag-and-drop defaults
rg "class SideMenuExtension" -A15 -n packages/editor/src

# Find all references to isDragDropEnabled in the codebase
rg "isDragDropEnabled" -n packages/editor/src

Length of output: 78


🏁 Script executed:

#!/bin/bash
set -e

# Search for the props type to see if isDragDropEnabled is optional or has a default
rg "TCollaborativeEditorProps" -n .

# Find any occurrences of isDragDropEnabled and its usage
rg "isDragDropEnabled" -n .

# Locate SideMenuExtension declaration to inspect its constructor or default options
rg "class SideMenuExtension" -n .

Length of output: 1069


No default needed for isDragDropEnabled in the hook

isDragDropEnabled is declared as a required boolean in TCollaborativeEditorProps and is already defaulted to true in the CollaborativeEditor component, so it will never be undefined when passed into useCollaborativeEditor.

packages/editor/src/core/types/collaboration.ts:41isDragDropEnabled: boolean;
packages/editor/src/core/components/editors/document/collaborative-editor.tsx:35 – destructured as isDragDropEnabled = true

Likely an incorrect or invalid review comment.

packages/editor/src/core/types/config.ts (1)

24-27: Confirm CSS tokens for new enum values exist.

"mobile-font" and "mobile-regular" are now valid class tokens.
Make sure corresponding CSS variables/classes were added, otherwise consumers will silently fall back to default styles.

packages/editor/src/core/components/editors/document/page-renderer.tsx (1)

20-31: Mobile gating & click-through wiring look solid

isMobile defaults to false, is forwarded to EditorContainer, and correctly hides menus on mobile.
onEditorClick is bubbled through to EditorContentWrapper. No issues spotted.

packages/editor/src/core/extensions/custom-image/components/image-uploader.tsx (1)

121-132: Good call deferring automatic file-picker on mobile

The extra !isMobile guard prevents the instant picker pop-up that mobile Safari/Chrome would trigger, yet still flags hasOpenedFileInputOnce so the component won’t spam subsequent mounts. Behaviour looks correct.

packages/editor/src/core/components/editors/editor-wrapper.tsx (1)

25-34: Good addition of configurable props with sensible defaults.

The new props (editable, isMobile, onInitialContentLoad) enhance the component's flexibility and enable mobile-specific behavior. The default values ensure backward compatibility.

Also applies to: 45-55, 78-78

packages/editor/src/core/extensions/custom-image/components/image-block.tsx (3)

203-212: Appropriate mobile-specific behavior for image interactions.

The addition of preventDefault and conditional blur on mobile devices helps prevent unwanted default behaviors and improves the user experience when interacting with images on touch devices.


303-303: Appropriate conditional rendering for mobile UI.

Hiding the external link option on mobile devices is a good UX decision given the limited screen space.


263-269:

✅ Verification successful

Mobile-specific image source resolution addresses cross-origin limitations.

The implementation correctly handles the mobile platform limitation where authentication cookies cannot be passed in cross-origin requests. The async resolution with proper error handling is well implemented.

Verify that the getImageSource command is available in all editor configurations where this component is used:


🏁 Script executed:

#!/bin/bash
# Description: Verify the getImageSource command implementation and usage

# Search for getImageSource command definition
ast-grep --pattern $'commands.getImageSource'

# Search for image extension configurations that might need this command
rg -A 5 "CustomImageExtension|ImageExtension" --type ts

Length of output: 16377


🏁 Script executed:

#!/bin/bash
# Search for any references to getImageSource across the codebase
rg --color=never -n "getImageSource" -C 3 packages/editor/src

# Inspect CustomImageExtension to see if it registers a getImageSource command
rg --color=never -n "addCommands" -C 5 packages/editor/src/core/extensions/custom-image/custom-image.tsx

Length of output: 6200


getImageSource is correctly registered and your mobile logic is solid

The getImageSource command is implemented in both the core ImageExtension (and its read-only variant) as well as in CustomImageExtension. Your async resolution and error handling for mobile-only image loading are appropriate and cover the cross-origin cookie limitation.

No further changes needed—this is good to merge.

packages/editor/src/core/components/editors/document/collaborative-editor.tsx (2)

46-58: Well-implemented extensions configuration with proper memoization.

The use of useMemo to memoize the extensions array is appropriate, and the conditional inclusion of WorkItemEmbedExtension based on embedHandler.issue is correctly implemented with the right dependencies.


24-44: Proper prop handling and forwarding to child components.

The new props are correctly destructured and forwarded to the appropriate child components, maintaining a clean component interface and enabling mobile-specific behaviors throughout the editor stack.

Also applies to: 65-66, 72-73, 105-106

packages/editor/src/core/hooks/use-editor.ts (2)

163-172: Good additions to the editor API.

The new undo/redo methods and enhanced focus/scroll functionality provide useful editor control capabilities. The implementations properly handle edge cases with null checks.


353-357: Well-implemented attribute retrieval method.

The getSelectedNodeAttributes method correctly extends the mark range for links before retrieving attributes, ensuring complete link selection.

packages/editor/src/core/types/editor.ts (2)

46-46: Proper typing for the new link command.

The link command is correctly added to TEditorCommands with appropriate props including required url and optional text.

Also applies to: 70-73


105-133: Comprehensive type definitions for enhanced editor capabilities.

The extended EditorRefApi interface and updated editor prop interfaces properly type all the new functionality including:

  • Selection and focus control methods
  • Mobile support flags
  • Drag-drop configuration
  • Initial content load callbacks

The types align well with the implementations across the codebase.

Also applies to: 140-179

Comment on lines +173 to +205
createSelectionAtCursorPosition: () => {
if (!editor) return;
const { empty } = editor.state.selection;

// if (empty) return null;
if (empty) {
// Get the text content and position info
const { $from } = editor.state.selection;
const textContent = $from.parent.textContent;
const posInNode = $from.parentOffset;

// Find word boundaries
let start = posInNode;
let end = posInNode;

// Move start position backwards until we hit a word boundary
while (start > 0 && /\w/.test(textContent[start - 1])) {
start--;
}

// Move end position forwards until we hit a word boundary
while (end < textContent.length && /\w/.test(textContent[end])) {
end++;
}

// If we found a word, select it using editor commands
if (start !== end) {
const from = $from.start() + start;
const to = $from.start() + end;
editor.commands.setTextSelection({ from, to });
}
}
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve word boundary detection for international text support.

The current regex pattern /\w/ only matches ASCII word characters (a-zA-Z0-9_) and won't properly handle international characters, emojis, or special characters.

Consider using a more inclusive pattern or Unicode-aware word boundary detection:

-while (start > 0 && /\w/.test(textContent[start - 1])) {
+while (start > 0 && /[\p{L}\p{N}_]/u.test(textContent[start - 1])) {
   start--;
}

-while (end < textContent.length && /\w/.test(textContent[end])) {
+while (end < textContent.length && /[\p{L}\p{N}_]/u.test(textContent[end])) {
   end++;
}

This uses Unicode property escapes to match any letter (\p{L}) or number (\p{N}) character across all languages.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
createSelectionAtCursorPosition: () => {
if (!editor) return;
const { empty } = editor.state.selection;
// if (empty) return null;
if (empty) {
// Get the text content and position info
const { $from } = editor.state.selection;
const textContent = $from.parent.textContent;
const posInNode = $from.parentOffset;
// Find word boundaries
let start = posInNode;
let end = posInNode;
// Move start position backwards until we hit a word boundary
while (start > 0 && /\w/.test(textContent[start - 1])) {
start--;
}
// Move end position forwards until we hit a word boundary
while (end < textContent.length && /\w/.test(textContent[end])) {
end++;
}
// If we found a word, select it using editor commands
if (start !== end) {
const from = $from.start() + start;
const to = $from.start() + end;
editor.commands.setTextSelection({ from, to });
}
}
},
createSelectionAtCursorPosition: () => {
if (!editor) return;
const { empty } = editor.state.selection;
// if (empty) return null;
if (empty) {
// Get the text content and position info
const { $from } = editor.state.selection;
const textContent = $from.parent.textContent;
const posInNode = $from.parentOffset;
// Find word boundaries
let start = posInNode;
let end = posInNode;
// Move start position backwards until we hit a word boundary
- while (start > 0 && /\w/.test(textContent[start - 1])) {
+ while (start > 0 && /[\p{L}\p{N}_]/u.test(textContent[start - 1])) {
start--;
}
// Move end position forwards until we hit a word boundary
- while (end < textContent.length && /\w/.test(textContent[end])) {
+ while (end < textContent.length && /[\p{L}\p{N}_]/u.test(textContent[end])) {
end++;
}
// If we found a word, select it using editor commands
if (start !== end) {
const from = $from.start() + start;
const to = $from.start() + end;
editor.commands.setTextSelection({ from, to });
}
}
},
🤖 Prompt for AI Agents
In packages/editor/src/core/hooks/use-editor.ts between lines 173 and 205, the
current word boundary detection uses the regex /\w/, which only matches ASCII
characters and fails for international characters and emojis. Replace the regex
with a Unicode-aware pattern using Unicode property escapes, such as
/\p{L}|\p{N}/u, to correctly identify letters and numbers across all languages.
Update both the backward and forward boundary checks to use this new regex for
improved international text support.

Comment on lines +83 to +94
const onEditorContentLoad = useCallback(() => {
const editorContainer = document.querySelector(".tiptap");
if (
editorContainer &&
'<p class="editor-paragraph-block"></p>' !== editorContainer.innerHTML &&
!isInitialContentLoaded &&
onInitialContentLoad
) {
setIsInitialContentLoaded(true);
onInitialContentLoad?.(editorContainer.clientHeight);
}
}, [onInitialContentLoad, isInitialContentLoaded]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Consider improving the initial content load detection logic.

The current implementation has potential issues:

  1. The hardcoded .tiptap class selector is fragile and could break if the editor's internal structure changes
  2. The innerHTML comparison with a specific paragraph format is brittle

Consider using a more robust approach:

-const editorContainer = document.querySelector(".tiptap");
-if (
-  editorContainer &&
-  '<p class="editor-paragraph-block"></p>' !== editorContainer.innerHTML &&
-  !isInitialContentLoaded &&
-  onInitialContentLoad
-) {
+const editorElement = editor?.view?.dom;
+if (
+  editorElement &&
+  editor?.state.doc.textContent.trim() !== "" &&
+  !isInitialContentLoaded &&
+  onInitialContentLoad
+) {
   setIsInitialContentLoaded(true);
-  onInitialContentLoad?.(editorContainer.clientHeight);
+  onInitialContentLoad?.(editorElement.clientHeight);
}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/editor/src/core/hooks/use-editor.ts around lines 83 to 94, the
current logic for detecting initial content load relies on a hardcoded '.tiptap'
selector and a brittle innerHTML string comparison. To fix this, replace the
selector with a more stable reference to the editor container, such as a ref or
a prop if available, and instead of comparing innerHTML, use a more reliable
method like checking if the editor's content length or text content is
non-empty. This will make the detection more robust against structural changes.

Copy link
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 (3)
packages/editor/src/core/extensions/custom-image/read-only-custom-image.tsx (1)

75-77: Shadowed props identifier – rename for clarity
The inner arrow function parameter shadows the outer props. While not technically wrong, it’s an easy readability foot-gun.

-return ReactNodeViewRenderer((props: CustomImageNodeProps) => <CustomImageNode {...props} isMobile={isMobile} />);
+return ReactNodeViewRenderer(
+  (nodeViewProps: CustomImageNodeProps) => (
+    <CustomImageNode {...nodeViewProps} isMobile={isMobile} />
+  ),
+);
packages/editor/src/core/extensions/image/read-only-image.tsx (1)

33-35: Avoid shadowing outer props
Replicate the renaming shown in the Custom-Image counterpart to keep consistency and avoid confusion.

-return ReactNodeViewRenderer((props: CustomImageNodeProps) => <CustomImageNode {...props} isMobile={isMobile} />);
+return ReactNodeViewRenderer(
+  (nodeViewProps: CustomImageNodeProps) => (
+    <CustomImageNode {...nodeViewProps} isMobile={isMobile} />
+  ),
+);
packages/editor/src/core/extensions/image/extension.tsx (1)

59-61: Consistent param naming
Same param-shadowing nitpick; rename the inner props.

-return ReactNodeViewRenderer((props: CustomImageNodeProps) => <CustomImageNode {...props} isMobile={isMobile} />);
+return ReactNodeViewRenderer(
+  (nodeViewProps: CustomImageNodeProps) => (
+    <CustomImageNode {...nodeViewProps} isMobile={isMobile} />
+  ),
+);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b07351c and e3fd8a5.

📒 Files selected for processing (6)
  • packages/editor/src/core/components/menus/menu-items.ts (6 hunks)
  • packages/editor/src/core/extensions/custom-image/read-only-custom-image.tsx (2 hunks)
  • packages/editor/src/core/extensions/image/extension.tsx (3 hunks)
  • packages/editor/src/core/extensions/image/read-only-image.tsx (2 hunks)
  • packages/editor/src/core/helpers/editor-commands.ts (1 hunks)
  • packages/editor/src/core/types/config.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/editor/src/core/helpers/editor-commands.ts
  • packages/editor/src/core/types/config.ts
  • packages/editor/src/core/components/menus/menu-items.ts
🧰 Additional context used
🧬 Code Graph Analysis (3)
packages/editor/src/core/extensions/custom-image/read-only-custom-image.tsx (2)
packages/editor/src/core/types/config.ts (1)
  • TReadOnlyFileHandler (1-6)
packages/editor/src/core/extensions/custom-image/components/image-node.tsx (2)
  • CustomImageNodeProps (21-21)
  • CustomImageNode (23-96)
packages/editor/src/core/extensions/image/read-only-image.tsx (2)
packages/editor/src/core/types/config.ts (1)
  • TReadOnlyFileHandler (1-6)
packages/editor/src/core/extensions/custom-image/components/image-node.tsx (2)
  • CustomImageNodeProps (21-21)
  • CustomImageNode (23-96)
packages/editor/src/core/extensions/image/extension.tsx (1)
packages/editor/src/core/extensions/custom-image/components/image-node.tsx (2)
  • CustomImageNodeProps (21-21)
  • CustomImageNode (23-96)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
packages/editor/src/core/extensions/custom-image/read-only-custom-image.tsx (1)

12-13: isMobile addition looks correct – confirm all call-sites
isMobile is plumbed through cleanly here, but double-check every place where CustomReadOnlyImageExtension is instantiated so that new call-sites don’t silently default to false when mobile behaviour is actually required.

packages/editor/src/core/extensions/image/read-only-image.tsx (1)

9-10: Prop drilling check
Same remark as above: ensure every creator of ReadOnlyImageExtension now feeds the correct isMobile flag – missing it will quietly fall back to desktop rendering.

Comment on lines +18 to 20
isMobile = false,
} = fileHandler;

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential type-safety gap – does TFileHandler include isMobile?
isMobile is destructured from fileHandler, but the older TFileHandler definition historically didn’t expose that field. If the type hasn’t been updated, this will raise a compile-time error or force an any escape.

Make sure TFileHandler mirrors the new shape, e.g.:

export type TFileHandler = {
  getAssetSrc: (path: string) => Promise<string>;
  validation: { maxFileSize: number };
  // NEW
  isMobile?: boolean;
};
🤖 Prompt for AI Agents
In packages/editor/src/core/extensions/image/extension.tsx around lines 18 to
20, the isMobile property is destructured from fileHandler but may not be
defined in the TFileHandler type, causing type-safety issues. Update the
TFileHandler type definition to include an optional isMobile boolean property to
match the new object shape and prevent compile-time errors.

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