Skip to content

[WIKI-471] refactor: custom image extension#7247

Merged
sriramveeraghanta merged 4 commits intopreviewfrom
refactor/custom-image-extension
Jun 24, 2025
Merged

[WIKI-471] refactor: custom image extension#7247
sriramveeraghanta merged 4 commits intopreviewfrom
refactor/custom-image-extension

Conversation

@aaryan610
Copy link
Member

@aaryan610 aaryan610 commented Jun 20, 2025

Description

This PR refactors the existing image extensions, by-

  1. Creating types and enums for attributes.
  2. Renaming components.
  3. Removing read only extensions.
  4. Replacing file handling commands with options.

Type of Change

  • Code refactoring

Summary by CodeRabbit

  • New Features

    • Introduced a new custom image extension for the rich text editor, supporting advanced image attributes, file uploads, and enhanced image handling.
    • Added utilities and types for managing custom image attributes, file validation, and image insertion.
  • Refactor

    • Consolidated and restructured image extension modules for improved maintainability and type safety.
    • Updated component and prop type definitions for better clarity and consistency.
    • Simplified image source resolution and upload handling through extension options.
    • Streamlined prop passing and state management in image-related components.
    • Improved type safety and null handling in image toolbar and full-screen components.
    • Updated editor extensions setup to use new configuration patterns and options objects.
  • Removals

    • Removed legacy and read-only image extension modules and related exports.
    • Eliminated centralized re-export files for image components and extensions.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 20, 2025

Walkthrough

The changes introduce a comprehensive refactor of the custom image extension for a rich text editor. They modularize types, utilities, and extension logic, replace previous read-only and editable image extensions with a unified approach, update component props and imports, and streamline extension configuration and registration throughout the codebase.

Changes

Files / Groups Change Summary
.../custom-image/types.ts
.../custom-image/utils.ts
Added new modules defining enums, types, and utilities for custom image attributes, upload entities, extension options, storage, and pixel normalization.
.../custom-image/extension-config.ts
.../custom-image/extension.ts
Added new modules implementing and exporting the main custom image extension logic, configuration, commands, storage, keyboard shortcuts, and node view integration.
.../custom-image/custom-image.ts
.../custom-image/read-only-custom-image.ts
Deleted legacy custom image extension files (editable and read-only variants), consolidating logic into new unified extension modules.
.../custom-image/components/block.tsx
.../custom-image/components/node-view.tsx
.../custom-image/components/uploader.tsx
Refactored components to use new types and utilities, updated prop types and destructuring, simplified attribute handling, and adjusted upload/restore logic to use extension options.
.../custom-image/components/toolbar/full-screen.tsx
.../custom-image/components/toolbar/root.tsx
Updated prop types to use new named types, improved type safety, and added nullish coalescing for attributes; reordered properties for consistency.
.../custom-image/components/index.ts
.../custom-image/index.ts
Deleted central re-export files for custom image components and extension modules, removing aggregated import interfaces.
.../storage.ts Changed import path for CustomImageExtensionStorage type from "@/extensions/custom-image" to "@/extensions/custom-image/types".
.../image/extension.tsx Refactored image extension to accept a props object, improved option typing, removed redundant commands, updated node view renderer, and handled optional max file size safely.
.../image/read-only-image.tsx Deleted legacy read-only image extension.
.../image/index.ts Removed exports of read-only image extension and image-extension-without-props; added export of new extension-config.
.../extensions.ts
.../read-only-extensions.ts
Updated registration of image and custom image extensions to use new unified modules and initialization signatures; removed legacy extension usage and configuration.
.../index.ts Removed export of custom image extension from extensions index.
.../core-without-props.ts Replaced imports and usage of image extensions without props with new extension configurations without explicit configuration.
.../image/extension-config.tsx Renamed and updated image extension configuration export with explicit generic typing for options and storage.
.../image/image-component-without-props.tsx Deleted legacy custom image extension without props.
.../helpers/editor-commands.ts Changed import of InsertImageComponentProps type to import from "@/extensions/custom-image/types" as a type-only import.

Sequence Diagram(s)

sequenceDiagram
    participant Editor
    participant CustomImageExtension
    participant FileHandler
    participant ReactComponent

    Editor->>CustomImageExtension: Initialize with { fileHandler, isEditable }
    CustomImageExtension->>FileHandler: Validate file, getImageSource, uploadImage (if supported)
    CustomImageExtension->>CustomImageExtension: Manage storage (fileMap, deletedImageSet)
    CustomImageExtension->>ReactComponent: Render node view with attributes, handlers
    ReactComponent->>CustomImageExtension: Request image source, restore image, upload image
Loading

Suggested labels

ready to merge

Suggested reviewers

  • Palanikannan1437
  • sriramveeraghanta

Poem

Oh, what a hop and a leap in code,
Where images now have a single abode!
Types and utilities, clean and neat,
Extensions unified—a tidy feat.
With pixel strings and storage maps,
This bunny cheers your refactor laps! 🐇✨


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6d9fc4f and 41717d4.

📒 Files selected for processing (2)
  • packages/editor/src/core/extensions/custom-image/components/block.tsx (8 hunks)
  • packages/editor/src/core/extensions/custom-image/components/node-view.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/editor/src/core/extensions/custom-image/components/block.tsx
  • packages/editor/src/core/extensions/custom-image/components/node-view.tsx
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Analyze (python)
✨ 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.

@makeplane
Copy link

makeplane bot commented Jun 20, 2025

Pull Request Linked with Plane Work Items

Comment Automatically Generated by Plane

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: 4

🧹 Nitpick comments (3)
packages/editor/src/core/extensions/custom-image/extension-config.ts (1)

22-34: Consider performance optimization for attribute merging.

The reduce operation to merge attributes is correct but could be optimized for better performance, especially if ECustomImageAttributeNames contains many values.

Consider this more efficient approach:

  addAttributes() {
-   const attributes = {
-     ...this.parent?.(),
-     ...Object.values(ECustomImageAttributeNames).reduce((acc, value) => {
-       acc[value] = {
-         default: DEFAULT_CUSTOM_IMAGE_ATTRIBUTES[value],
-       };
-       return acc;
-     }, {}),
-   };
+   const customAttributes = Object.fromEntries(
+     Object.values(ECustomImageAttributeNames).map(value => [
+       value,
+       { default: DEFAULT_CUSTOM_IMAGE_ATTRIBUTES[value] }
+     ])
+   );
+   
+   const attributes = {
+     ...this.parent?.(),
+     ...customAttributes,
+   };

    return attributes;
  },
packages/editor/src/core/extensions/custom-image/utils.ts (1)

20-33: Improve type safety for pixel string normalization.

The function handles various input types well, but the type assertion could be more robust.

Consider making the return type more explicit:

export const ensurePixelString = <TDefault>(
  value: Pixel | TDefault | number | undefined | null,
  defaultValue?: TDefault
-) => {
+): Pixel | TDefault => {
  if (!value || value === defaultValue) {
    return defaultValue;
  }

  if (typeof value === "number") {
-   return `${value}px` satisfies Pixel;
+   return `${value}px` as Pixel;
  }

- return value;
+ return value as Pixel;
};
packages/editor/src/core/extensions/custom-image/types.ts (1)

31-31: Union type design could be more explicit.

The UploadEntity type combines event-specific properties with optional properties. Consider using discriminated unions for better type safety.

-export type UploadEntity = ({ event: "insert" } | { event: "drop"; file: File }) & { hasOpenedFileInputOnce?: boolean };
+export type UploadEntity = 
+  | { event: "insert"; hasOpenedFileInputOnce?: boolean }
+  | { event: "drop"; file: File; hasOpenedFileInputOnce?: boolean };
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f26b4d3 and 9b09737.

📒 Files selected for processing (20)
  • packages/editor/src/ce/types/storage.ts (1 hunks)
  • packages/editor/src/core/extensions/custom-image/components/block.tsx (7 hunks)
  • packages/editor/src/core/extensions/custom-image/components/index.ts (0 hunks)
  • packages/editor/src/core/extensions/custom-image/components/node-view.tsx (2 hunks)
  • packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx (5 hunks)
  • packages/editor/src/core/extensions/custom-image/components/toolbar/root.tsx (1 hunks)
  • packages/editor/src/core/extensions/custom-image/components/uploader.tsx (4 hunks)
  • packages/editor/src/core/extensions/custom-image/custom-image.ts (0 hunks)
  • packages/editor/src/core/extensions/custom-image/extension-config.ts (1 hunks)
  • packages/editor/src/core/extensions/custom-image/extension.ts (1 hunks)
  • packages/editor/src/core/extensions/custom-image/index.ts (0 hunks)
  • packages/editor/src/core/extensions/custom-image/read-only-custom-image.ts (0 hunks)
  • packages/editor/src/core/extensions/custom-image/types.ts (1 hunks)
  • packages/editor/src/core/extensions/custom-image/utils.ts (1 hunks)
  • packages/editor/src/core/extensions/extensions.ts (2 hunks)
  • packages/editor/src/core/extensions/image/extension.tsx (3 hunks)
  • packages/editor/src/core/extensions/image/index.ts (0 hunks)
  • packages/editor/src/core/extensions/image/read-only-image.tsx (0 hunks)
  • packages/editor/src/core/extensions/index.ts (0 hunks)
  • packages/editor/src/core/extensions/read-only-extensions.ts (2 hunks)
💤 Files with no reviewable changes (7)
  • packages/editor/src/core/extensions/image/index.ts
  • packages/editor/src/core/extensions/custom-image/index.ts
  • packages/editor/src/core/extensions/custom-image/components/index.ts
  • packages/editor/src/core/extensions/index.ts
  • packages/editor/src/core/extensions/custom-image/read-only-custom-image.ts
  • packages/editor/src/core/extensions/image/read-only-image.tsx
  • packages/editor/src/core/extensions/custom-image/custom-image.ts
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Analyze (python)
🔇 Additional comments (39)
packages/editor/src/ce/types/storage.ts (1)

5-5: LGTM! Improved type organization.

The import path change from a general extension import to a dedicated types module improves code organization and separation of concerns. This modularization makes the codebase more maintainable and the imports more explicit.

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

2-5: LGTM! Improved type safety and organization.

The changes enhance the codebase by:

  • Using a centralized TCustomImageAttributes type instead of inline object types
  • Improving import categorization with clearer comments
  • Centralizing type definitions for better maintainability

This refactoring provides better type safety and a single source of truth for image attributes.


10-10: Good type consistency improvement.

Replacing the inline object type with TCustomImageAttributes ensures type consistency across components and makes the code more maintainable when image attribute structure changes.

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

40-41: LGTM! Better import organization.

The addition of local imports section and clearer categorization improves code readability and maintainability.


195-201: Verify the new extension instantiation pattern is consistent.

The changes to use object parameters for both ImageExtension and CustomImageExtension improve API consistency and extensibility. The isEditable parameter in CustomImageExtension suggests a unified approach for handling editable/read-only modes.

Please verify that this new instantiation pattern is consistently implemented across all extension usage:

#!/bin/bash
# Description: Verify consistent extension instantiation patterns

# Search for ImageExtension and CustomImageExtension usage patterns
echo "=== ImageExtension usage patterns ==="
rg -A 3 "ImageExtension\(" 

echo -e "\n=== CustomImageExtension usage patterns ==="
rg -A 3 "CustomImageExtension\("
packages/editor/src/core/extensions/read-only-extensions.ts (2)

26-26: LGTM! Consistent import organization.

The import additions align with the architectural refactoring to use unified extensions for both editable and read-only modes, which is a good consolidation approach.

Also applies to: 34-35


139-145: Excellent architectural improvement.

Replacing separate read-only extensions with unified extensions configured via isEditable: false is a significant improvement that:

  • Reduces code duplication
  • Ensures consistency between editable and read-only behaviors
  • Simplifies maintenance by having a single extension implementation
  • Follows the DRY principle effectively

The instantiation pattern is consistent with the changes in extensions.ts.

packages/editor/src/core/extensions/custom-image/extension-config.ts (3)

1-7: LGTM! Well-organized imports.

The import structure clearly separates TipTap dependencies, constants, and local imports, following good organizational practices.


9-15: Good TypeScript module augmentation.

The command declaration properly extends the TipTap Commands interface, enabling type-safe usage of the insertImageComponent command throughout the codebase.


36-46: Here’s the replacement logic so we can confirm how <image-component> is transformed and whether alt (or other a11y attributes) are applied:

#!/bin/bash
echo "=== Snippet around image-component replacement in web/core/hooks/use-parse-editor-content.ts ==="
rg -n "image-component" -A5 -B5 web/core/hooks/use-parse-editor-content.ts
packages/editor/src/core/extensions/custom-image/extension.ts (3)

15-18: LGTM! Clean props interface design.

The props interface clearly defines the required dependencies and supports both read-only and editable modes effectively.


110-115: LGTM! Clean keyboard shortcut implementation.

The keyboard shortcuts for inserting empty paragraphs around images provide good UX for navigation.


29-38: ```shell
#!/bin/bash

Display the custom-image types file

sed -n '1,200p' packages/editor/src/core/extensions/custom-image/types.ts

Search specifically for uploadImage in the types file

grep -n "uploadImage" -n packages/editor/src/core/extensions/custom-image/types.ts

Locate TFileHandler definition

grep -R "export type TFileHandler" -n packages/editor/src

```shell
#!/bin/bash
# As a fallback, search for TFileHandler in absolute imports
grep -R "interface TFileHandler" -n .
packages/editor/src/core/extensions/custom-image/utils.ts (1)

9-15: LGTM! Well-structured default attributes.

The use of enums for attribute names provides good type safety, and the default values are reasonable for image components.

packages/editor/src/core/extensions/custom-image/components/toolbar/full-screen.tsx (2)

6-12: LGTM! Improved type safety with centralized types.

The switch to TCustomImageAttributes improves consistency across the codebase and provides better type safety.


31-31: Good defensive programming with nullish coalescing.

The explicit null handling with fallback values (aspect ratio fallback to 1, src fallback to undefined) prevents runtime errors and improves robustness.

Also applies to: 42-42, 208-208, 214-214, 244-244

packages/editor/src/core/extensions/image/extension.tsx (3)

15-23: LGTM! Clean props interface and extraction.

The simplified props interface and clear extraction of getAssetSrc improves readability and consistency with the custom image extension pattern.


41-47: Good defensive programming for optional properties.

The safe access to fileHandler.validation?.maxFileSize with fallback to 0 prevents runtime errors when validation is not available.


66-66: ```shell
#!/bin/bash

Inspect ImageExtension definition and default options

sed -n '1,200p' packages/editor/src/core/extensions/image/extension.tsx


</details>
<details>
<summary>packages/editor/src/core/extensions/custom-image/components/uploader.tsx (4)</summary>

`13-15`: **LGTM! Cleaner import organization.**

The switch to local relative imports aligns with the removal of the centralized exports and improves module organization.

---

`17-22`: **Good type interface evolution.**

The addition of `failedToLoadImage` property and reordering improves the interface structure for better error handling.

---

`80-83`: **LGTM! Proper extension method usage.**

The switch from `editor.commands.uploadImage` to `extension.options.uploadImage` aligns with the new extension architecture where upload functionality is provided through options.

---

`76-78`: **Good improvement to dependency arrays.**

The addition of missing dependencies like `imageEntityId` and `editor.isEditable` improves hook correctness and prevents stale closure issues.



Also applies to: 133-133, 168-168

</details>
<details>
<summary>packages/editor/src/core/extensions/custom-image/components/node-view.tsx (5)</summary>

`7-10`: **LGTM! Well-organized imports with clear separation.**

The local imports are properly separated and the new type imports align with the refactoring objectives.

---

`12-21`: **Strong type safety improvements with explicit extension typing.**

The new `CustomImageNodeViewProps` type provides better type safety by explicitly requiring the `CustomImageExtension` type for the extension prop, replacing the generic `NodeViewProps["extension"]`.

---

`23-24`: **Component rename aligns with PR objectives.**

The rename from `CustomImageNode` to `CustomImageNodeView` better reflects the component's purpose as a node view implementation.

---

`67-82`: **Simplified prop passing reduces boilerplate but verify component contracts.**

The spread operator approach (`{...props}`) simplifies prop passing, but ensure that `CustomImageBlock` and `CustomImageUploader` components can handle all props from `CustomImageNodeViewProps`.



The change from individual prop passing to spreading all props could introduce unintended prop conflicts. Please verify that the child components properly type their expected props.

```shell
#!/bin/bash
# Description: Check CustomImageBlock and CustomImageUploader prop definitions
# Expected: Components should properly type their expected props

# Search for CustomImageBlock and CustomImageUploader prop type definitions
ast-grep --pattern 'type $_ = $_ & {
  $$$
}'

# Also check component function signatures
ast-grep --pattern 'export const CustomImageBlock: React.FC<$_> = ($_) => {'
ast-grep --pattern 'export const CustomImageUploader: React.FC<$_> = ($_) => {'

54-61: Let’s inspect the TFileHandler and its base TReadOnlyFileHandler in config.ts to see what getAssetSrc actually returns:

#!/bin/bash
# Show TFileHandler and its parent type to verify getAssetSrc signature
rg -n -A5 'export type TFileHandler' packages/editor/src/core/types/config.ts
rg -n -A5 'TReadOnlyFileHandler' packages/editor/src/core/types/config.ts
packages/editor/src/core/extensions/custom-image/types.ts (4)

5-11: Well-structured enum with descriptive naming.

The ECustomImageAttributeNames enum provides type-safe attribute name constants, improving maintainability and reducing magic strings.


13-16: Clever pixel type definition with template literal types.

The Pixel type using template literal types ensures type safety for CSS pixel values, and PixelAttribute allows for flexible default values.


39-43: Extension options provide good separation of concerns.

The CustomImageExtensionOptions type clearly defines the required handlers with appropriate optional properties, following good architectural patterns.


45-49: Storage type structure supports the extension lifecycle well.

The CustomImageExtensionStorage type appropriately uses Map for tracking files and deleted images, with a clear maxFileSize property.

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

6-9: Good modularization with centralized imports.

The refactor to import types and utilities from centralized modules improves maintainability and reduces code duplication.


14-20: Props interface properly extends the node view props.

The CustomImageBlockProps extension of CustomImageNodeViewProps maintains type consistency across the component hierarchy.


27-35: Destructured props alignment with new interface.

The destructuring properly extracts the new props structure, maintaining clean separation of concerns.


39-43: State initialization uses centralized utility function.

The ensurePixelString utility usage provides consistent pixel value handling across the component.


145-145: Callback dependencies correctly updated.

The handleResizeEnd callback properly includes the updateAttributesSafely dependency, maintaining correct closure behavior.


219-230: Proper refactoring to extension options pattern.

The change from editor.commands.restoreImage to extension.options.restoreImage aligns with the new architecture, centralizing image handling through extension options.


265-265: Simplified toolbar prop passing with full attributes.

Passing the entire node.attrs object to ImageToolbarRoot as the image prop simplifies the interface and ensures all attributes are available.

@sriramveeraghanta sriramveeraghanta merged commit c1fa372 into preview Jun 24, 2025
5 of 6 checks passed
@sriramveeraghanta sriramveeraghanta deleted the refactor/custom-image-extension branch June 24, 2025 08:35
lifeiscontent pushed a commit that referenced this pull request Aug 18, 2025
* refactor: custom image extension

* refactor: extension config

* revert: image full screen component

* fix: undo operation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants