Skip to content
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

[PE-45] feat: page export as PDF & Markdown #5705

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

Conversation

aaryan610
Copy link
Collaborator

@aaryan610 aaryan610 commented Sep 26, 2024

What's new?

A page can now be exported as a PDF and Markdown file.

Media:

Screen.Recording.2024-09-26.at.14.33.16.mov

Other improvements:

  1. Updated the default font-family of all the editors.

Plane issue: PE-45

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new ExportPageModal for exporting content in PDF and Markdown formats.
    • Added a PDFDocument component for rendering PDF documents.
    • Expanded modal width options with new constants for SM, MD, and LG.
  • Improvements

    • Updated font styling for a more specific font stack in the editor.
    • Enhanced content processing for HTML and Markdown to ensure compatibility during exports.
  • Bug Fixes

    • Streamlined rendering logic by removing unnecessary style properties in the title component.

Copy link
Contributor

coderabbitai bot commented Sep 26, 2024

Walkthrough

The changes introduce several enhancements, including updates to styling variables, new constants for modal widths, the addition of a PDF document rendering component, and the implementation of an export functionality for pages. New helper functions for processing content and converting units are also included. Additionally, the project now includes new dependencies for PDF rendering, expanding its capabilities.

Changes

File Path Change Summary
packages/editor/src/styles/editor.css Updated --font-style variable for .sans-serif class from sans-serif to "Inter", sans-serif.
packages/ui/src/modals/constants.ts Added new constants SM, MD, and LG to EModalWidth enum with corresponding values for modal widths.
web/core/components/editor/index.ts Added export statement for pdf module alongside existing exports.
web/core/components/editor/pdf/document.tsx Introduced PDFDocument component to render PDF documents using @react-pdf/renderer, accepting content and pageFormat props.
web/core/components/editor/pdf/index.ts Created index.ts to re-export all entities from the document module.
web/core/components/pages/editor/header/options-dropdown.tsx Added isExportModalOpen state variable and integrated ExportPageModal for exporting pages.
web/core/components/pages/editor/title.tsx Removed fontStyle property and simplified title rendering logic.
web/core/components/pages/modals/export-page-modal.tsx Introduced ExportPageModal component for exporting content in PDF and Markdown formats, utilizing react-hook-form for managing form state.
web/core/components/pages/modals/index.ts Added export statement for export-page-modal module.
web/core/constants/editor.ts Added several new style definitions for PDF editing, including font family and typography styles, and exported EDITOR_PDF_DOCUMENT_STYLESHEET.
web/helpers/common.helper.ts Added convertRemToPixel function to convert rem units to pixels.
web/helpers/editor.helper.ts Introduced functions to replace custom components in HTML and Markdown content for compatibility with PDF generation.
web/helpers/file.helper.ts Added getBase64Image function to fetch images and convert them to base64 format.
web/package.json Added dependencies: @react-pdf/renderer and react-pdf-html.

Suggested labels

🌟improvement

Suggested reviewers

  • SatishGandham
  • rahulramesha

Poem

🐰 In the meadow, changes bloom,
New styles and exports make room.
PDF dreams take flight today,
With helpers guiding the way.
Hooray for updates, bright and new,
A hop, a skip, a joyful view! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

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 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.

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

🧹 Outside diff range and nitpick comments (19)
web/core/components/editor/index.ts (1)

1-3: LGTM! Consider alphabetical ordering for consistency.

The addition of the PDF module export aligns well with the PR objectives, introducing the ability to export pages as PDF. This change effectively makes the PDF-related functionality available for import alongside the existing editor components.

For improved readability and easier maintenance, consider ordering the exports alphabetically:

 export * from "./lite-text-editor";
-export * from "./pdf";
 export * from "./rich-text-editor";
+export * from "./pdf";
packages/ui/src/modals/constants.ts (1)

7-9: LGTM! Consider reordering for consistency.

The addition of SM, MD, and LG constants to the EModalWidth enum is a good improvement. It provides more granular control over modal widths and follows Tailwind CSS conventions.

For better readability and consistency, consider reordering the enum values from smallest to largest. Here's a suggested order:

export enum EModalWidth {
  SM = "sm:max-w-sm",
  MD = "sm:max-w-md",
  LG = "sm:max-w-lg",
  XL = "sm:max-w-xl",
  XXL = "sm:max-w-2xl",
  XXXL = "sm:max-w-3xl",
  XXXXL = "sm:max-w-4xl",
  VXL = "sm:max-w-5xl",
  VIXL = "sm:max-w-6xl",
}

This ordering aligns with the natural progression of sizes and makes it easier to locate specific widths at a glance.

web/helpers/file.helper.ts (2)

1-6: Enhance function documentation for clarity.

The current documentation is good, but it could be more detailed to improve clarity and maintainability.

Consider expanding the documentation as follows:

/**
 * Fetches an image from the provided URL and converts it to a base64-encoded string.
 * 
 * @param {string} url - The URL of the image to be converted.
 * @returns {Promise<string>} A promise that resolves with the base64-encoded string representation of the image.
 * @throws {Error} If the image fetch fails or if the conversion process encounters an error.
 */

7-13: Approve image fetching with a suggestion for improved error handling.

The image fetching logic is well-implemented. The use of the Fetch API and the conversion to a blob are appropriate for this task.

Consider enhancing the error handling by including the HTTP status code in the error message. This can be achieved by modifying line 10 as follows:

throw new Error(`Failed to fetch image: ${response.status} ${response.statusText}`);

This change will provide more detailed information for debugging purposes.

web/helpers/common.helper.ts (1)

40-41: LGTM with suggestions for improvement

The convertRemToPixel function is well-implemented and typed correctly. However, I have a few suggestions to enhance its clarity and flexibility:

  1. Consider adding a comment to explain the purpose of the 0.9 scaling factor. This will help other developers understand the rationale behind this specific conversion.

  2. To make the function more flexible, you might want to consider making the scaling factor configurable. This could be achieved by adding an optional parameter with a default value of 0.9.

Here's an example of how you could refactor the function:

/**
 * Converts rem units to pixels.
 * @param rem The value in rem units to convert.
 * @param scaleFactor Optional scaling factor (default: 0.9) to adjust the conversion.
 * @returns The equivalent value in pixels.
 */
export const convertRemToPixel = (rem: number, scaleFactor: number = 0.9): number => 
  rem * scaleFactor * 16;

This refactoring maintains the current behavior while allowing for future flexibility if needed.

web/core/components/editor/pdf/document.tsx (3)

8-30: LGTM: Comprehensive font registration. Consider lazy loading for optimization.

The Inter font family is well-registered with a wide range of weights and styles, which will enhance the visual consistency of the PDF output.

For optimization, consider lazy loading the fonts or only registering the weights and styles that are actually used in the PDF content. This could potentially improve the initial load time of the component.


32-35: LGTM: Props type is well-defined. Consider adding content validation.

The Props type is correctly defined with appropriate types for content and pageFormat.

For improved type safety and to prevent potential runtime errors, consider adding a runtime check or using a library like zod to validate the content string. This could help ensure that the content is valid HTML before attempting to render it in the PDF.


37-53: LGTM: PDFDocument component is well-structured. Consider adding error handling.

The PDFDocument component is correctly implemented using @react-pdf/renderer and react-pdf-html. The structure is clean and follows React best practices.

Consider the following improvements:

  1. Add error handling for cases where the HTML content might be invalid or cause rendering issues.
  2. Implement a loading state or fallback UI while the PDF is being generated, especially for large documents.
  3. Consider making the padding and background color customizable through props if this component might be reused in different contexts.

Example implementation for error handling:

export const PDFDocument: React.FC<Props> = (props) => {
  const { content, pageFormat } = props;

  return (
    <ErrorBoundary fallback={<ErrorFallback />}>
      <Document>
        <Page
          size={pageFormat}
          style={{
            backgroundColor: "#ffffff",
            padding: 64,
          }}
        >
          <Html stylesheet={EDITOR_PDF_DOCUMENT_STYLESHEET}>{content}</Html>
        </Page>
      </Document>
    </ErrorBoundary>
  );
};

Don't forget to implement the ErrorBoundary and ErrorFallback components.

web/core/components/pages/editor/title.tsx (1)

40-41: Improved styling approach for TextArea component

The changes to the TextArea component are good improvements:

  1. Removing the inline style in favor of utility classes is a better practice for maintainability and consistency.
  2. The added utility classes provide more specific styling control.

These changes align well with modern CSS practices and improve the component's flexibility.

Consider extracting the long className string into a separate constant for better readability:

+ const textAreaClassName = cn(
+   titleClassName,
+   "w-full outline-none p-0 border-none resize-none rounded-none"
+ );

  <TextArea
-   className={cn(titleClassName, "w-full outline-none p-0 border-none resize-none rounded-none")}
+   className={textAreaClassName}
    placeholder="Untitled"
    // ... other props
  />

This change would make the JSX more readable and the className easier to maintain.

web/core/components/pages/editor/header/options-dropdown.tsx (2)

166-172: LGTM: New export menu item added.

The new export menu item is well-structured and consistent with other items in the MENU_ITEMS array. The icon choice and action are appropriate for the export functionality.

Consider adding a condition to shouldRender based on whether exporting is allowed for the current user or page state. This would make the menu item's visibility consistent with other conditional items like "lock-unlock-page".


176-183: LGTM: Export modal integration.

The ExportPageModal component is well-integrated into the existing structure. Its placement and props are appropriate, and the use of a fragment to wrap multiple elements is correct.

Consider memoizing the onClose callback using useCallback to optimize performance, especially if this component re-renders frequently:

const onCloseExportModal = useCallback(() => setIsExportModalOpen(false), []);

Then use onCloseExportModal in the onClose prop of ExportPageModal.

web/core/constants/editor.ts (4)

138-148: LGTM: Well-defined font family styles.

The EDITOR_PDF_FONT_FAMILY_STYLES constant provides a consistent typography across the PDF document. However, consider using a fallback font stack for better cross-platform compatibility.

Consider updating the font family definitions to include fallback fonts:

 const EDITOR_PDF_FONT_FAMILY_STYLES: Styles = {
   "*:not(.courier, .courier-bold)": {
-    fontFamily: "Inter",
+    fontFamily: "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
   },
   ".courier": {
-    fontFamily: "Courier",
+    fontFamily: "Courier, monospace",
   },
   ".courier-bold": {
-    fontFamily: "Courier-Bold",
+    fontFamily: "Courier-Bold, Courier, monospace",
   },
 };

150-203: LGTM: Well-structured typography styles.

The EDITOR_PDF_TYPOGRAPHY_STYLES constant provides a clear hierarchy for headings and paragraphs. The use of convertRemToPixel ensures consistent sizing.

Consider adding lineHeight properties to improve readability and accessibility:

 const EDITOR_PDF_TYPOGRAPHY_STYLES: Styles = {
   // page title
   "h1.page-title": {
     fontSize: convertRemToPixel(1.6),
     fontWeight: "bold",
     marginTop: 0,
     marginBottom: convertRemToPixel(2),
+    lineHeight: 1.2,
   },
   // headings
   "h1:not(.page-title)": {
     fontSize: convertRemToPixel(1.4),
     fontWeight: "semibold",
     marginTop: convertRemToPixel(2),
     marginBottom: convertRemToPixel(0.25),
+    lineHeight: 1.2,
   },
   // ... (apply similar changes to other headings)
   // paragraph
   "p:not(table p)": {
     fontSize: convertRemToPixel(0.8),
+    lineHeight: 1.5,
   },
   // ... (rest of the styles)
 };

205-243: LGTM: Comprehensive list styles with task list support.

The EDITOR_PDF_LIST_STYLES constant provides well-defined styles for various list types, including task lists with checkboxes.

For consistency with other styles, consider using convertRemToPixel for pixel values:

 const EDITOR_PDF_LIST_STYLES: Styles = {
   "ul, ol": {
     fontSize: convertRemToPixel(0.8),
-    marginHorizontal: -20,
+    marginHorizontal: convertRemToPixel(-1.25), // Assuming 1rem = 16px
   },
   // ... (rest of the styles)
   "div.input-checkbox": {
     // ... (other properties)
-    borderWidth: "1.5px",
+    borderWidth: convertRemToPixel(0.09375), // 1.5px
   },
   // ... (rest of the styles)
 };

268-313: LGTM: Comprehensive PDF document stylesheet.

The EDITOR_PDF_DOCUMENT_STYLESHEET effectively combines all previously defined styles and adds necessary styles for additional elements.

Consider the following improvements for consistency and accessibility:

  1. Use convertRemToPixel for all pixel values:
 export const EDITOR_PDF_DOCUMENT_STYLESHEET = StyleSheet.create({
   // ... (other styles)
   blockquote: {
-    borderLeft: "3px solid gray",
+    borderLeft: `${convertRemToPixel(0.1875)} solid gray`,
     // ... (other properties)
   },
   // ... (other styles)
   "div[data-type='horizontalRule']": {
     // ... (other properties)
-    height: 1,
+    height: convertRemToPixel(0.0625),
     // ... (other properties)
   },
   // ... (other styles)
 });
  1. Improve color contrast for better accessibility:
 export const EDITOR_PDF_DOCUMENT_STYLESHEET = StyleSheet.create({
   // ... (other styles)
   "[data-node-type='mention-block']": {
     margin: 0,
-    color: "#3f76ff",
-    backgroundColor: "#3f76ff33",
+    color: "#0056b3", // Darker blue for better contrast
+    backgroundColor: "#e6f2ff", // Lighter background
     paddingHorizontal: convertRemToPixel(0.375),
   },
   // ... (other styles)
 });

These changes will improve consistency in using the convertRemToPixel helper and enhance accessibility through better color contrast.

packages/editor/src/styles/editor.css (1)

47-47: LGTM! Consider adding system fonts to the font stack.

The change to use "Inter" as the primary font for the sans-serif style is a good improvement. It aligns with the PR objective of updating the default font-family across all editors and provides a modern, readable font designed for screens.

Consider expanding the font stack to include system fonts for better performance and fallback options:

--- --font-style: "Inter", sans-serif;
+++ --font-style: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

This change ensures that if "Inter" is not available, the browser will use an appropriate system font before falling back to a generic sans-serif font. This can improve performance by potentially avoiding an additional font download and provides a more consistent look across different operating systems.

web/core/components/pages/modals/export-page-modal.tsx (3)

166-171: Use else if to prevent unnecessary checks

Since selectedExportFormat can only be either "pdf" or "markdown", using else if after the first condition avoids an unnecessary check when the first condition is true.

Update the conditional statements:

if (selectedExportFormat === "pdf") {
  await handleExportAsPDF();
- }
- if (selectedExportFormat === "markdown") {
+ } else if (selectedExportFormat === "markdown") {
  await handleExportAsMarkdown();
}

257-257: Simplify key prop in CustomSelect.Option

In the PAGE_FORMATS options, the key prop uses format.key.toString(). Since format.key is already a string, calling toString() is unnecessary.

Simplify the key prop:

- <CustomSelect.Option key={format.key.toString()} value={format.key}>
+ <CustomSelect.Option key={format.key} value={format.key}>

116-119: Clarify the use of setTimeout when resetting the form

The reset function is called inside a setTimeout with a delay of 300ms. This may be related to the modal's closing animation, but it's not immediately clear. Adding a comment can improve code readability.

Add a comment to explain the delay:

setTimeout(() => {
  reset();
+ // Reset the form after the modal close animation completes
}, 300);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 150af98 and 0e17d3d.

⛔ Files ignored due to path filters (19)
  • web/public/fonts/inter/bold-italic.ttf is excluded by !**/*.ttf
  • web/public/fonts/inter/bold.ttf is excluded by !**/*.ttf
  • web/public/fonts/inter/heavy-italic.ttf is excluded by !**/*.ttf
  • web/public/fonts/inter/heavy.ttf is excluded by !**/*.ttf
  • web/public/fonts/inter/light-italic.ttf is excluded by !**/*.ttf
  • web/public/fonts/inter/light.ttf is excluded by !**/*.ttf
  • web/public/fonts/inter/medium-italic.ttf is excluded by !**/*.ttf
  • web/public/fonts/inter/medium.ttf is excluded by !**/*.ttf
  • web/public/fonts/inter/regular-italic.ttf is excluded by !**/*.ttf
  • web/public/fonts/inter/regular.ttf is excluded by !**/*.ttf
  • web/public/fonts/inter/semibold-italic.ttf is excluded by !**/*.ttf
  • web/public/fonts/inter/semibold.ttf is excluded by !**/*.ttf
  • web/public/fonts/inter/thin-italic.ttf is excluded by !**/*.ttf
  • web/public/fonts/inter/thin.ttf is excluded by !**/*.ttf
  • web/public/fonts/inter/ultrabold-italic.ttf is excluded by !**/*.ttf
  • web/public/fonts/inter/ultrabold.ttf is excluded by !**/*.ttf
  • web/public/fonts/inter/ultralight-italic.ttf is excluded by !**/*.ttf
  • web/public/fonts/inter/ultralight.ttf is excluded by !**/*.ttf
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (14)
  • packages/editor/src/styles/editor.css (1 hunks)
  • packages/ui/src/modals/constants.ts (1 hunks)
  • web/core/components/editor/index.ts (1 hunks)
  • web/core/components/editor/pdf/document.tsx (1 hunks)
  • web/core/components/editor/pdf/index.ts (1 hunks)
  • web/core/components/pages/editor/header/options-dropdown.tsx (4 hunks)
  • web/core/components/pages/editor/title.tsx (2 hunks)
  • web/core/components/pages/modals/export-page-modal.tsx (1 hunks)
  • web/core/components/pages/modals/index.ts (1 hunks)
  • web/core/constants/editor.ts (3 hunks)
  • web/helpers/common.helper.ts (1 hunks)
  • web/helpers/editor.helper.ts (1 hunks)
  • web/helpers/file.helper.ts (1 hunks)
  • web/package.json (2 hunks)
🧰 Additional context used
🪛 Biome
web/helpers/editor.helper.ts

[error] 143-143: The regular expression includes this negated empty character class.

Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.

(lint/correctness/noEmptyCharacterClassInRegex)


[error] 153-153: The regular expression includes this negated empty character class.

Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.

(lint/correctness/noEmptyCharacterClassInRegex)

🔇 Additional comments (14)
web/core/components/pages/modals/index.ts (1)

3-3: LGTM! New export aligns with PR objectives.

The addition of export * from "./export-page-modal"; is consistent with the PR's goal of introducing page export functionality. The naming convention and placement within the file are appropriate.

To ensure the completeness of this feature, let's verify the implementation of the exported module:

This script will help confirm that the exported module is properly implemented and contains the necessary components for the export functionality.

✅ Verification successful

Verified the addition of export-page-modal.tsx.

The exported module exists and is properly implemented with the necessary components for the export functionality.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the existence and basic structure of the export-page-modal file

# Check if the file exists
if [ -f "web/core/components/pages/modals/export-page-modal.ts" ] || [ -f "web/core/components/pages/modals/export-page-modal.tsx" ]; then
    echo "export-page-modal file exists."
    
    # Check the content of the file
    file_path=$(fd -t f "export-page-modal" web/core/components/pages/modals)
    if [ -n "$file_path" ]; then
        echo "File contents:"
        cat "$file_path"
    else
        echo "Couldn't find the exact file path."
    fi
else
    echo "export-page-modal file does not exist in the expected location."
fi

Length of output: 9255

web/helpers/file.helper.ts (1)

14-30: Well-structured FileReader usage and Promise handling.

The implementation of the FileReader and Promise handling is well-done. It correctly manages the asynchronous nature of the file reading process and includes appropriate error handling.

Key points:

  • Proper use of FileReader to convert blob to base64.
  • Clear Promise structure with resolve and reject handlers.
  • Comprehensive error handling for both reading failures and null results.
web/core/components/editor/pdf/document.tsx (1)

1-6: LGTM: Imports and directives are correctly structured.

The "use client" directive and import statements are well-organized and appropriate for the component's functionality.

web/core/components/pages/editor/title.tsx (3)

36-36: Improved styling approach for read-only title

The changes to the read-only title rendering are good improvements:

  1. Removing the inline style in favor of className is a better practice for maintainability.
  2. Adding the "break-words" class improves text wrapping, preventing overflow issues.

These changes align well with React best practices and improve the component's flexibility.


Line range hint 1-78: Summary of changes and recommendations

The changes in this file primarily focus on improving the styling approach:

  1. Removal of inline styles in favor of utility classes and classNames.
  2. Better handling of text wrapping for the read-only title.
  3. More specific styling control for the TextArea component.

These changes align well with React and CSS best practices, improving maintainability and consistency. However, there are a couple of points to consider:

  1. Verify the impact of removing fontStyle from usePageFilters as it might affect other components.
  2. Consider extracting the long className string in the TextArea component into a separate constant for better readability.

Overall, these changes represent a positive step towards more maintainable and consistent styling in the component.


3-3: Verify the impact of removing fontStyle from usePageFilters

The fontStyle property has been removed from the usePageFilters hook. This change suggests a shift in how text styling is handled in the component.

To ensure this change doesn't negatively impact the component's functionality:

Also applies to: 26-26

web/package.json (1)

35-35: New dependencies added for PDF functionality.

The addition of @react-pdf/renderer (v^3.4.5) and react-pdf-html (v^2.1.2) aligns with the PR objective of implementing PDF export functionality. These libraries are well-known for handling PDF generation in React applications.

To ensure these dependencies are being utilized correctly, let's verify their usage:

This script will help us confirm that the new dependencies are being used in the codebase and that PDF-related functionality has been implemented.

Also applies to: 61-61

web/core/components/pages/editor/header/options-dropdown.tsx (2)

3-3: LGTM: New imports for export functionality.

The added imports (useState, ArrowUpToLine, and ExportPageModal) are consistent with the new export feature being implemented. They provide the necessary hooks, icons, and components for the new functionality.

Also applies to: 6-6, 12-12


46-46: LGTM: New state for export modal visibility.

The addition of isExportModalOpen state using the useState hook is well-implemented. It's appropriately named and initialized to false, which is correct for managing the visibility of the export modal.

web/core/constants/editor.ts (2)

1-1: LGTM: New imports for PDF styling.

The added imports from "@react-pdf/renderer" and the helper function convertRemToPixel are appropriate for the new PDF styling functionality.

Also applies to: 27-28


Line range hint 1-313: Summary: PDF styling constants align well with PR objectives.

The introduced PDF styling constants and exported stylesheet in web/core/constants/editor.ts provide a comprehensive set of styles for PDF document generation. This aligns perfectly with the PR objective of adding PDF export functionality.

Key points:

  1. Font family styles ensure consistent typography.
  2. Well-structured typography styles provide a clear hierarchy.
  3. Comprehensive list styles, including support for task lists.
  4. Code styles offer good visual distinction for code elements.
  5. The exported stylesheet combines all styles effectively.

The suggested improvements focus on consistency, accessibility, and addressing TODOs, which will further enhance the quality of the PDF export feature.

web/helpers/editor.helper.ts (2)

9-122: The function replaceCustomComponentsFromHTMLContent is well-implemented

The function effectively transforms custom HTML components into standard HTML elements suitable for PDF generation. Asynchronous operations and error handling are appropriately managed.


129-156: The function replaceCustomComponentsFromMarkdownContent is well-implemented

The function correctly processes Markdown content, replacing custom components with standard Markdown syntax suitable for exporting.

🧰 Tools
🪛 Biome

[error] 143-143: The regular expression includes this negated empty character class.

Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.

(lint/correctness/noEmptyCharacterClassInRegex)


[error] 153-153: The regular expression includes this negated empty character class.

Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.

(lint/correctness/noEmptyCharacterClassInRegex)

web/core/components/pages/modals/export-page-modal.tsx (1)

143-149: Handle asynchronous operations consistently

The replaceCustomComponentsFromMarkdownContent function is called without await, assuming it returns synchronously. If it returns a promise, this could lead to unexpected behavior.

Confirm whether replaceCustomComponentsFromMarkdownContent is synchronous. If it's asynchronous, update the code accordingly:

- const parsedMarkdownContent = replaceCustomComponentsFromMarkdownContent({
+ const parsedMarkdownContent = await replaceCustomComponentsFromMarkdownContent({

@@ -0,0 +1 @@
export * from "./document";
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Issue Detected: Missing document.ts Module

The index.ts file is attempting to export from ./document, but no such document.ts file exists in the web/core/components/editor/pdf/ directory. Please verify the existence of the document module or update the export path accordingly to ensure proper functionality.

🔗 Analysis chain

LGTM! Consider verifying exports from "./document".

The use of a barrel file (index.ts) is a good practice for simplifying imports and improving module organization. This change aligns well with the PR objectives of introducing PDF export functionality.

To ensure that only necessary entities are being re-exported, please verify the contents of the "./document" module. You can use the following script to check the exports:

This will help confirm that we're not inadvertently exposing any internal implementation details or creating potential naming conflicts.


Further Verification Needed: Export Module Missing

It appears that web/core/components/editor/pdf/document.ts does not exist. Please verify the existence and correct naming of the document module. You can use the following scripts to investigate:


Further Verification Needed: Corrected Export Module Checks

The previous scripts encountered issues due to incorrect command syntax. Please use the following updated scripts to verify the exports:

This will help identify the actual TypeScript files present and their export statements, ensuring that the index.ts file references the correct module.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the exports from the "./document" module

# Test: Display the contents of the document.ts file
cat web/core/components/editor/pdf/document.ts

# Test: List all exported entities from the document.ts file
ast-grep --lang typescript --pattern 'export $_' web/core/components/editor/pdf/document.ts

Length of output: 649


Script:

#!/bin/bash
# Description: List all TypeScript files in the pdf directory

fd --extension ts --type f web/core/components/editor/pdf/

Length of output: 490


Script:

#!/bin/bash
# Description: Search for export statements in the pdf directory

ast-grep --lang typescript --pattern 'export $_' web/core/components/editor/pdf/

Length of output: 514


Script:

#!/bin/bash
# Description: List all TypeScript files in the pdf directory

fd --extension ts --type f . web/core/components/editor/pdf/

Length of output: 102


Script:

#!/bin/bash
# Description: Search for export statements in TypeScript files within the pdf directory

# List all TypeScript files in the pdf directory
TS_FILES=$(fd --extension ts --type f . web/core/components/editor/pdf/)

# Iterate over each TypeScript file and list export statements
for file in $TS_FILES; do
  echo "Exports in $file:"
  ast-grep --lang typescript --pattern 'export $_' "$file" || echo "No exports found in $file."
  echo ""
done

Length of output: 916

Comment on lines +6 to +31
export const getBase64Image = async (url: string): Promise<string> => {
const response = await fetch(url);
// check if the response is OK
if (!response.ok) {
throw new Error(`Failed to fetch image: ${response.statusText}`);
}

const blob = await response.blob();
return new Promise((resolve, reject) => {
const reader = new FileReader();

reader.onloadend = () => {
if (reader.result) {
resolve(reader.result as string);
} else {
reject(new Error("Failed to convert image to base64."));
}
};

reader.onerror = () => {
reject(new Error("Failed to read the image file."));
};

reader.readAsDataURL(blob);
});
};
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

Consider adding input validation for the URL parameter.

The function is well-structured and focused. However, to improve robustness, consider adding input validation for the URL parameter.

Here's a suggested improvement:

export const getBase64Image = async (url: string): Promise<string> => {
  if (!url || typeof url !== 'string') {
    throw new Error('Invalid URL provided');
  }

  // Try to create a URL object to validate the URL
  try {
    new URL(url);
  } catch (error) {
    throw new Error('Invalid URL format');
  }

  // Rest of the function remains the same
  ...
};

This addition will ensure that the function receives a valid URL string before attempting to fetch the image, potentially preventing errors further down the line.

Comment on lines +245 to +266
const EDITOR_PDF_CODE_STYLES: Styles = {
// code block
"[data-node-type='code-block']": {
marginVertical: convertRemToPixel(0.5),
padding: convertRemToPixel(1),
borderRadius: convertRemToPixel(0.5),
backgroundColor: "#f7f7f7",
fontSize: convertRemToPixel(0.7),
},
// inline code block
// TODO: update width
"[data-node-type='inline-code-block']": {
margin: 0,
paddingVertical: convertRemToPixel(0.25 / 4 + 0.25 / 8),
paddingHorizontal: convertRemToPixel(0.375),
border: "0.5px solid #e5e5e5",
borderRadius: convertRemToPixel(0.25),
backgroundColor: "#e8e8e8",
color: "#f97316",
fontSize: convertRemToPixel(0.7),
},
};
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

LGTM: Well-defined code styles with room for improvement.

The EDITOR_PDF_CODE_STYLES constant provides good visual distinction for code elements. However, there's a TODO comment that needs to be addressed.

Address the TODO comment for updating the width of inline code blocks. Consider using a flexible width approach:

 const EDITOR_PDF_CODE_STYLES: Styles = {
   // ... (code block styles)
   // inline code block
-  // TODO: update width
   "[data-node-type='inline-code-block']": {
     margin: 0,
     paddingVertical: convertRemToPixel(0.25 / 4 + 0.25 / 8),
     paddingHorizontal: convertRemToPixel(0.375),
     border: "0.5px solid #e5e5e5",
     borderRadius: convertRemToPixel(0.25),
     backgroundColor: "#e8e8e8",
     color: "#f97316",
     fontSize: convertRemToPixel(0.7),
+    display: "inline-block",
+    maxWidth: "100%",
+    wordWrap: "break-word",
   },
 };

This change allows inline code blocks to adjust their width based on content while preventing overflow.

📝 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
const EDITOR_PDF_CODE_STYLES: Styles = {
// code block
"[data-node-type='code-block']": {
marginVertical: convertRemToPixel(0.5),
padding: convertRemToPixel(1),
borderRadius: convertRemToPixel(0.5),
backgroundColor: "#f7f7f7",
fontSize: convertRemToPixel(0.7),
},
// inline code block
// TODO: update width
"[data-node-type='inline-code-block']": {
margin: 0,
paddingVertical: convertRemToPixel(0.25 / 4 + 0.25 / 8),
paddingHorizontal: convertRemToPixel(0.375),
border: "0.5px solid #e5e5e5",
borderRadius: convertRemToPixel(0.25),
backgroundColor: "#e8e8e8",
color: "#f97316",
fontSize: convertRemToPixel(0.7),
},
};
const EDITOR_PDF_CODE_STYLES: Styles = {
// code block
"[data-node-type='code-block']": {
marginVertical: convertRemToPixel(0.5),
padding: convertRemToPixel(1),
borderRadius: convertRemToPixel(0.5),
backgroundColor: "#f7f7f7",
fontSize: convertRemToPixel(0.7),
},
// inline code block
"[data-node-type='inline-code-block']": {
margin: 0,
paddingVertical: convertRemToPixel(0.25 / 4 + 0.25 / 8),
paddingHorizontal: convertRemToPixel(0.375),
border: "0.5px solid #e5e5e5",
borderRadius: convertRemToPixel(0.25),
backgroundColor: "#e8e8e8",
color: "#f97316",
fontSize: convertRemToPixel(0.7),
display: "inline-block",
maxWidth: "100%",
wordWrap: "break-word",
},
};

let parsedMarkdownContent = markdownContent;
// replace the matched mention components with [label](redirect_uri)
const mentionRegex = /<mention-component[^>]*label="([^"]+)"[^>]*redirect_uri="([^"]+)"[^>]*><\/mention-component>/g;
const originUrl = window?.location?.origin ?? "";
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

Ensure window is defined when accessing window.location.origin

At line 137, accessing window.location.origin can result in a ReferenceError if window is undefined, such as in server-side environments.

Consider modifying the code to safely check for window:

-const originUrl = window?.location?.origin ?? "";
+const originUrl = (typeof window !== 'undefined' && window.location.origin) ? window.location.origin : "";

This change ensures that the code does not throw an error when window is not available.

📝 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
const originUrl = window?.location?.origin ?? "";
const originUrl = (typeof window !== 'undefined' && window.location.origin) ? window.location.origin : "";

(_match, label, redirectUri) => `[${label}](${originUrl}/${redirectUri})`
);
// replace the matched image components with <img src={src} >
const imageComponentRegex = /<image-component[^>]*src="([^"]+)"[^>]*>[^]*<\/image-component>/g;
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

Fix the incorrect regular expression in imageComponentRegex

The regular expression at line 143 includes an empty negated character class [^]*, which matches any character, including line breaks, and may not behave as intended. This can lead to unexpected matches or performance issues.

Apply this diff to correct the regular expression:

-const imageComponentRegex = /<image-component[^>]*src="([^"]+)"[^>]*>[^]*<\/image-component>/g;
+const imageComponentRegex = /<image-component[^>]*src="([^"]+)"[^>]*>.*?<\/image-component>/gs;

Explanation:

  • Replaced [^]* with .*? to match any character (non-greedy).
  • Added the s flag to allow . to match newline characters, ensuring multiline content is correctly handled.
📝 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
const imageComponentRegex = /<image-component[^>]*src="([^"]+)"[^>]*>[^]*<\/image-component>/g;
const imageComponentRegex = /<image-component[^>]*src="([^"]+)"[^>]*>.*?<\/image-component>/gs;
🧰 Tools
🪛 Biome

[error] 143-143: The regular expression includes this negated empty character class.

Negated empty character classes match anything.
If you want to match against [, escape it [.
Otherwise, remove the character class or fill it.

(lint/correctness/noEmptyCharacterClassInRegex)

Comment on lines +179 to +184
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Page could not be exported. Please try again later.",
});
} finally {
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

Log errors for improved debugging

In the handleExport function, errors are caught and a generic error message is displayed to the user. However, the actual error details are not logged, which may hinder debugging efforts. Logging the error can provide valuable insights during development and troubleshooting.

Apply the following change to log the error details:

} catch (error) {
+ console.error('Export error:', error);
  setToast({
    type: TOAST_TYPE.ERROR,
    title: "Error!",
    message: "Page could not be exported. Please try again later.",
  });
📝 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
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Page could not be exported. Please try again later.",
});
} finally {
} catch (error) {
console.error('Export error:', error);
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: "Page could not be exported. Please try again later.",
});
} finally {

const selectedPageFormat = watch("page_format");
const selectedContentVariety = watch("content_variety");
const isPDFSelected = selectedExportFormat === "pdf";
const fileName = pageTitle?.toLowerCase()?.replace(/ /g, "-");
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

Ensure fileName is safe for file system usage

The fileName derived from pageTitle may contain characters that are invalid or unsafe for file names (e.g., /, \, ?, *). This could cause issues when saving the file. Sanitizing fileName will prevent potential errors.

Sanitize fileName to remove or replace unsafe characters:

- const fileName = pageTitle?.toLowerCase()?.replace(/ /g, "-");
+ const fileName = pageTitle
+   ?.toLowerCase()
+   ?.replace(/[^a-z0-9-_]/g, "-")
+   .replace(/-+/g, "-");

This will replace any character that is not a lowercase letter, number, hyphen, or underscore with a hyphen and collapse multiple hyphens into one.

📝 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
const fileName = pageTitle?.toLowerCase()?.replace(/ /g, "-");
const fileName = pageTitle
?.toLowerCase()
?.replace(/[^a-z0-9-_]/g, "-")
.replace(/-+/g, "-");

Comment on lines +121 to +140
const handleExportAsPDF = async () => {
try {
const pageContent = `<h1 class="page-title">${pageTitle}</h1>${editorRef?.getDocument().html ?? "<p></p>"}`;
const parsedPageContent = await replaceCustomComponentsFromHTMLContent({
htmlContent: pageContent,
noAssets: selectedContentVariety === "no-assets",
});

const blob = await pdf(<PDFDocument content={parsedPageContent} pageFormat={selectedPageFormat} />).toBlob();
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `${fileName}-${selectedPageFormat.toString().toLowerCase()}.pdf`;
link.click();

URL.revokeObjectURL(url);
} catch (error) {
throw new Error(`Error in exporting as a PDF: ${error}`);
}
};
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

Ensure URL.revokeObjectURL is called after download

In the handleExportAsPDF function, URL.revokeObjectURL(url); is called immediately after link.click();. In some browsers, the download may not start before the URL is revoked.

Consider moving URL.revokeObjectURL(url); into a setTimeout to ensure the download begins:

link.click();

- URL.revokeObjectURL(url);
+ setTimeout(() => {
+   URL.revokeObjectURL(url);
+ }, 1000);
📝 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
const handleExportAsPDF = async () => {
try {
const pageContent = `<h1 class="page-title">${pageTitle}</h1>${editorRef?.getDocument().html ?? "<p></p>"}`;
const parsedPageContent = await replaceCustomComponentsFromHTMLContent({
htmlContent: pageContent,
noAssets: selectedContentVariety === "no-assets",
});
const blob = await pdf(<PDFDocument content={parsedPageContent} pageFormat={selectedPageFormat} />).toBlob();
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `${fileName}-${selectedPageFormat.toString().toLowerCase()}.pdf`;
link.click();
URL.revokeObjectURL(url);
} catch (error) {
throw new Error(`Error in exporting as a PDF: ${error}`);
}
};
const handleExportAsPDF = async () => {
try {
const pageContent = `<h1 class="page-title">${pageTitle}</h1>${editorRef?.getDocument().html ?? "<p></p>"}`;
const parsedPageContent = await replaceCustomComponentsFromHTMLContent({
htmlContent: pageContent,
noAssets: selectedContentVariety === "no-assets",
});
const blob = await pdf(<PDFDocument content={parsedPageContent} pageFormat={selectedPageFormat} />).toBlob();
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = `${fileName}-${selectedPageFormat.toString().toLowerCase()}.pdf`;
link.click();
setTimeout(() => {
URL.revokeObjectURL(url);
}, 1000);
} catch (error) {
throw new Error(`Error in exporting as a PDF: ${error}`);
}
};

🛠️ Refactor suggestion

Refactor duplicated code in export functions

The handleExportAsPDF and handleExportAsMarkdown functions share similar logic for creating blobs, generating URLs, creating links, and initiating downloads. Refactoring the shared code into a helper function will enhance maintainability and reduce redundancy.

Consider extracting the common logic into a helper function:

+const initiateDownload = (blob: Blob, filename: string) => {
+  const url = URL.createObjectURL(blob);
+  const link = document.createElement("a");
+  link.href = url;
+  link.download = filename;
+  link.click();
+  URL.revokeObjectURL(url);
+};

Then update the export functions:

// Inside handleExportAsPDF
- const url = URL.createObjectURL(blob);
- const link = document.createElement("a");
- link.href = url;
- link.download = `${fileName}-${selectedPageFormat.toString().toLowerCase()}.pdf`;
- link.click();

- URL.revokeObjectURL(url);
+ initiateDownload(
+   blob,
+   `${fileName}-${selectedPageFormat.toString().toLowerCase()}.pdf`
+ );

Committable suggestion was skipped due to low confidence.

// handle export as a PDF
const handleExportAsPDF = async () => {
try {
const pageContent = `<h1 class="page-title">${pageTitle}</h1>${editorRef?.getDocument().html ?? "<p></p>"}`;
Copy link
Contributor

@coderabbitai coderabbitai bot Sep 26, 2024

Choose a reason for hiding this comment

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

⚠️ Potential issue

Sanitize pageTitle to prevent XSS vulnerabilities

The pageTitle is directly inserted into HTML without sanitization, which could lead to Cross-Site Scripting (XSS) vulnerabilities if it contains malicious content. It's important to sanitize or encode pageTitle before embedding it in the HTML.

Consider sanitizing pageTitle:

+ import DOMPurify from 'dompurify';

- const pageContent = `<h1 class="page-title">${pageTitle}</h1>${editorRef?.getDocument().html ?? "<p></p>"}`;
+ const safePageTitle = DOMPurify.sanitize(pageTitle);
+ const pageContent = `<h1 class="page-title">${safePageTitle}</h1>${editorRef?.getDocument().html ?? "<p></p>"}`;

Ensure that you have a sanitization library like dompurify installed and imported.

Committable suggestion was skipped due to low confidence.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Check this.

Copy link
Contributor

Choose a reason for hiding this comment

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

@SatishGandham, I've checked the codebase and found that pageTitle is not currently being sanitized before being embedded into HTML content. This could lead to XSS vulnerabilities if pageTitle contains malicious input.

I recommend sanitizing pageTitle using a library like DOMPurify to prevent potential security risks. Here's how you can modify your code:

import DOMPurify from 'isomorphic-dompurify';

const safePageTitle = DOMPurify.sanitize(pageTitle);
const pageContent = `<h1 class="page-title">${safePageTitle}</h1>${editorRef?.getDocument().html ?? "<p></p>"}`;

Ensure that isomorphic-dompurify is installed and properly imported in your project. This will sanitize the pageTitle and help prevent potential XSS attacks.

Let me know if you have any questions or need further assistance.

🔗 Analysis chain
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any sanitization applied to 'pageTitle'
rg 'sanitize.*pageTitle'

# Search for any usage of 'pageTitle' where it's sanitized with DOMPurify
rg 'DOMPurify\.sanitize\(\s*pageTitle\s*\)'

# Search for imports of DOMPurify
rg 'import.*DOMPurify'

# Search for all uses of 'pageTitle' to check its usage context
rg 'pageTitle'

Length of output: 12798

Comment on lines +197 to +216
<Controller
control={control}
name="export_format"
render={({ field: { onChange, value } }) => (
<CustomSelect
label={EXPORT_FORMATS.find((format) => format.key === value)?.label}
buttonClassName="border-none"
value={value}
onChange={(val: TExportFormats) => onChange(val)}
className="flex-shrink-0"
placement="bottom-end"
>
{EXPORT_FORMATS.map((format) => (
<CustomSelect.Option key={format.key} value={format.key}>
{format.label}
</CustomSelect.Option>
))}
</CustomSelect>
)}
/>
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 accessibility by adding labels to form controls

The form controls lack explicit labels associated with their respective inputs, which may affect accessibility for screen readers.

Wrap each CustomSelect with a <label> element or use aria-label:

- <h6 className="flex-shrink-0 text-sm text-custom-text-200">Export format</h6>
+ <label className="flex-shrink-0 text-sm text-custom-text-200" htmlFor="export_format">Export format</label>
...
<CustomSelect
+ id="export_format"

Repeat this pattern for other form controls.

Committable suggestion was skipped due to low confidence.

// handle export as a PDF
const handleExportAsPDF = async () => {
try {
const pageContent = `<h1 class="page-title">${pageTitle}</h1>${editorRef?.getDocument().html ?? "<p></p>"}`;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Check this.

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.

2 participants