Skip to content

[WIKI-400] feat: page navigation pane #7206

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 21 commits into
base: preview
Choose a base branch
from

Conversation

aaryan610
Copy link
Member

@aaryan610 aaryan610 commented Jun 12, 2025

Description

This PR includes the implementation of a new navigation pane for pages consisting of a wide range of information of the current page, including-

  1. Outline- Table of content
  2. Info- Document info and version history
  3. Assets- List of all the assets(Images, file attachments, work item embeds, sub-pages and more to come)
  4. New download feature for images.

Type of Change

  • Feature (non-breaking change which adds functionality)

Media

Screen.Recording.2025-06-16.at.19.54.46.mov

Fixes

#7169

Summary by CodeRabbit

  • New Features

    • Introduced a page navigation pane with tabs for Outline, Info, and Assets, accessible via the editor interface.
    • Added document statistics and version history display in the Info tab.
    • Enabled browsing and downloading of page assets in the Assets tab.
    • Provided localized UI labels and messages for the navigation pane in multiple languages.
    • Added smooth scrolling across all interfaces for improved navigation experience.
  • Enhancements

    • Improved asset management in the editor, including real-time asset list updates and synchronization with the UI.
    • Refined document outline and asset empty states with user-friendly messages and visuals.
    • Streamlined editor state and asset tracking for more reliable collaborative editing.
  • Bug Fixes

    • Fixed asset list updates on file upload, deletion, and restoration to ensure accurate asset tracking.
  • Refactor

    • Simplified and modularized editor and page state management.
    • Replaced the previous version sidebar with a timeline-based version history in the navigation pane.
  • Documentation

    • Updated and extended internationalization files for new navigation pane features and messages.
  • Style

    • Centralized and optimized CSS for consistent smooth scrolling and improved UI component styling.

Copy link
Contributor

coderabbitai bot commented Jun 12, 2025

Walkthrough

This update introduces a comprehensive "Page Navigation Pane" feature to the editor UI, including outline, info, and assets tabs, with localized support in multiple languages. It refactors editor asset management, centralizes editor references in a MobX sub-store, and streamlines version history handling. The change also improves asset tracking, updates extension storage, and removes deprecated sidebar components.

Changes

File(s) / Path(s) Change Summary
web/core/components/pages/navigation-pane/..., web/ce/components/pages/navigation-pane/... Introduced the Page Navigation Pane: new sidebar UI, tab panels (outline, info, assets), tab list, and supporting types/components.
web/core/components/pages/editor/toolbar/root.tsx, editor-body.tsx, summary/content-browser.tsx Updated editor toolbar and body to support navigation pane controls and improved outline/empty state rendering.
web/core/components/pages/editor/page-root.tsx Integrated navigation pane open/close logic with URL query parameters; removed versions overlay state.
web/core/components/pages/version/... Refactored version overlay to use URL state, removed sidebar components, simplified version editor props.
web/core/store/pages/base-page.ts, page-editor-info.ts Refactored editor reference management: removed direct editorRef, added MobX sub-store for editor state/assets.
packages/editor/src/core/extensions/utility.ts, core/helpers/assets.ts, core/types/asset.ts Enhanced asset tracking: added assets list to extension storage, asset metadata helpers, and new asset types.
packages/editor/src/core/hooks/use-editor.ts, use-collaborative-editor.ts, use-read-only-editor.ts Refactored hooks to use new editor helpers, asset change subscriptions, and consolidated imperative API.
packages/editor/src/core/types/editor.ts, core/types/hook.ts Extended editor API and hook types with asset change handlers and new document info methods.
packages/editor/src/core/plugins/file/delete.ts, file/restore.ts Updated plugins to maintain assets list on file deletion/restoration.
packages/editor/src/ce/constants/assets.ts, extensions.ts, types/asset.ts Added support for additional asset types/extensions (currently placeholders).
packages/editor/src/index.ts Updated exports to include new extension and asset constants.
packages/utils/src/editor.ts Added helper for generating asset download URLs.
packages/i18n/src/locales/*/translations.json Added translations for the page navigation pane and its tabs in all supported languages.
web/core/components/pages/editor/toolbar/options-dropdown.tsx, header/actions.tsx Adjusted editorRef access and removed deprecated info popover.
web/core/components/pages/editor/toolbar/info-popover.tsx Deleted deprecated info popover component.
web/core/components/pages/version/sidebar-*.tsx Deleted deprecated version sidebar components.
web/styles/globals.css, admin/styles/globals.css, space/styles/globals.css Added scroll-behavior: smooth; to global styles for smooth scrolling.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant PageEditor
    participant NavigationPane
    participant EditorStore
    participant EditorExtension

    User->>PageEditor: Opens page
    PageEditor->>EditorStore: Initialize editor sub-store
    PageEditor->>NavigationPane: Renders sidebar (outline/info/assets)
    NavigationPane->>EditorStore: Reads editorRef, assetsList
    EditorExtension-->>EditorStore: Updates assetsList on asset add/remove
    User->>NavigationPane: Switches tab (outline/info/assets)
    NavigationPane->>EditorStore: Fetches corresponding data
    User->>NavigationPane: Closes pane
    NavigationPane->>PageEditor: Triggers close handler
Loading

Possibly related PRs

Suggested labels

pages, ready to merge

Suggested reviewers

  • anmolsinghbhatia
  • prateekshourya29

Poem

In the warren of code, a pane did appear,
With outline, info, and assets so clear.
Tabs now in order, translations in tow,
Assets tracked smartly, in MobX they grow.
A hop and a skip, scrolls smooth as can be—
This rabbit’s quite proud of our new UI spree!
🐇✨


📜 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 71b8b30 and 2b251b6.

📒 Files selected for processing (1)
  • web/core/components/pages/editor/toolbar/options-dropdown.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/core/components/pages/editor/toolbar/options-dropdown.tsx
✨ 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.

Copy link

makeplane bot commented Jun 12, 2025

Pull Request Linked with Plane Work Items

Comment Automatically Generated by Plane

@aaryan610 aaryan610 marked this pull request as ready for review June 13, 2025 10:56
@Palanikannan1437 Palanikannan1437 marked this pull request as draft June 13, 2025 11:01
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: 15

🔭 Outside diff range comments (1)
web/core/components/pages/version/main-content.tsx (1)

31-38: ⚠️ Potential issue

SWR cache key is not unique per page – risk of cross-page data bleed

Using only "PAGE_VERSION_${activeVersion}" ignores pageId, so visiting two different pages that happen to share a version UUID will share the same cache entry.

-  pageId && activeVersion ? `PAGE_VERSION_${activeVersion}` : null,
-  pageId && activeVersion ? () => fetchVersionDetails(pageId, activeVersion) : null
+  pageId && activeVersion ? ["PAGE_VERSION", pageId, activeVersion] : null,
+  pageId && activeVersion ? () => fetchVersionDetails(pageId, activeVersion) : null

SWR stringifies array keys, guaranteeing uniqueness and preserving the existing cache-invalidation behavior.

🧹 Nitpick comments (24)
packages/editor/src/ce/constants/extensions.ts (1)

1-1: Add documentation to the placeholder enum

The exported ADDITIONAL_EXTENSIONS enum is currently empty—consider adding a comment describing its intended use or deferring its introduction until it contains members to avoid confusion.

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

25-28: Doc-string copy-paste

The JSDoc still says “generate the file source using assetId”. Clarify that this variant returns a download URL to avoid confusion when scanning the file.

packages/editor/src/ce/constants/assets.ts (1)

6-6: Empty placeholder constant

ADDITIONAL_ASSETS_META_DATA_RECORD is currently an empty object. If this is intentional scaffolding, add a TODO comment describing how/when it will be populated to avoid it being mistaken for dead code later.

web/core/components/pages/navigation-pane/tabs-list.tsx (2)

25-31: Animation math ties to list length

left and width are derived from ORDERED_PAGE_NAVIGATION_TABS_LIST.length.
If tabs are ever conditionally hidden or added at runtime, the indicator will be mis-aligned.

Consider computing positions from the actual rendered tab count (React.Children.count) or from the element’s offset width for robustness.


15-23: Minor: provide index in map for clarity

Tab supplies selectedIndex but the individual button doesn’t know its own index – using the map’s index can be helpful for debugging or aria attributes.

-{ORDERED_PAGE_NAVIGATION_TABS_LIST.map((tab) => (
+{ORDERED_PAGE_NAVIGATION_TABS_LIST.map((tab, idx) => (
   <Tab
     key={tab.key}
     type="button"
     className="relative z-[1] flex-1 py-1.5 text-sm font-semibold outline-none"
   >
     {t(tab.i18n_label)}
   </Tab>
 ))}
web/core/components/pages/navigation-pane/tab-panels/outline.tsx (1)

24-36: Memoise the Empty-State element & localise the alt text.

  1. EmptyState is recreated on every render; wrap it in useMemo to avoid useless re-allocations.
  2. The hard-coded alt string bypasses i18n. Expose it via t(...) so screen-reader users get a translated description.
+  const EmptyState = useMemo(
+    () => (
       ...
-        <Image src={resolvedPath} width={160} height={160} alt="An image depicting the outline of a page" />
+        <Image
+          src={resolvedPath}
+          width={160}
+          height={160}
+          alt={t("page_navigation_pane.tabs.outline.empty_state.image_alt")}
+        />
       ...
-  );
+    ),
+    [resolvedPath, t]
+  );
web/core/components/pages/editor/summary/heading-components.tsx (1)

13-29: Expose full heading text via title to improve accessibility.

Because the label is truncated with truncate, users can’t see the full heading unless they click it. Adding title={marking.text} keeps the UI unchanged while providing the full text on hover & for assistive tech.

-  <button type="button" onClick={onClick} className={cn(COMMON_CLASSNAME, "text-sm pl-1")}>
+  <button
+    type="button"
+    onClick={onClick}
+    className={cn(COMMON_CLASSNAME, "text-sm pl-1")}
+    title={marking.text}
+  >

Apply the same to OutlineHeading2 and OutlineHeading3.

web/core/components/pages/navigation-pane/tab-panels/info/document-info.tsx (2)

29-36: Guard against missing editorRef.

editorRef is optional (?. is used), but if it’s undefined, the component still subscribes & shows zeroed stats forever.
Consider an early-return fallback to avoid misleading information:

if (!editorRef) {
  return null; // or a dedicated “unavailable” state
}

38-42: secondsToReadableTime ignores i18n & plural rules.

Formatting "60s" vs "1m" is language-dependent and may need pluralisation.
Expose it through the translation layer or at least allow overriding the units.

web/core/components/pages/navigation-pane/tab-panels/root.tsx (1)

24-35: All tab panels stay mounted – could waste resources.

@headlessui/react will keep every <Tab.Panel> in the DOM unless you add unmount.
Panels like “info” subscribe to editor updates and can run expensive observers. Mounting only the active tab avoids unnecessary work.

-        <Tab.Panel
+        <Tab.Panel
+          unmount={true}
web/core/components/pages/editor/summary/content-browser.tsx (1)

39-44: Micro-perf: move HeadingComponent mapping out of render

HeadingComponent is recreated on every render. Memoising (or defining it outside the component) avoids needless allocations and keeps referential equality stable.

-const HeadingComponent: {
-  [key: number]: React.FC<THeadingComponentProps>;
-} = {
+// move this *above* the component definition
+export const HEADING_COMPONENT: {
+  [key: number]: React.FC<THeadingComponentProps>;
+} = {
 };
 
 // inside the component
-const HeadingComponent = HEADING_COMPONENT;
packages/editor/src/core/hooks/use-read-only-editor.ts (1)

75-75: Stabilise imperative handle with dependencies

useImperativeHandle is called without a deps array, so forwardedRef.current is reassigned on every render even when editor is unchanged.
Providing [editor, provider] keeps the same object reference unless something relevant really changed.

-useImperativeHandle(forwardedRef, () => getEditorRefHelpers({ editor, provider }));
+useImperativeHandle(
+  forwardedRef,
+  () => getEditorRefHelpers({ editor, provider }),
+  [editor, provider]
+);
web/core/components/pages/navigation-pane/tab-panels/info/actors-info.tsx (1)

30-66: Duplication between “edited by” and “created by” blocks – extract a small presentational component

The two sections share identical structure. Extracting something like <UserMeta label="Edited by" userId={...} date={...} /> will cut the duplication, simplify maintenance, and reduce future divergence in styling.

web/ce/components/pages/navigation-pane/index.ts (1)

1-22: Use as const to retain literal types & avoid widening

Adding as const to PAGE_NAVIGATION_PANE_TABS_LIST preserves literal string unions for callers and eliminates the need for repetitive key properties.

> = {
   outline: {
     key: "outline",
     i18n_label: "page_navigation_pane.tabs.outline.label",
   },
   ...
-};
+} as const;

This also allows ORDERED_PAGE_NAVIGATION_TABS_LIST to derive types without additional assertions.

web/core/components/pages/editor/toolbar/root.tsx (2)

54-60: Add aria-label & translation to the visible navigation-pane toggle button

The floating button (lines 69-76) is accessible, but the primary toolbar button is missing an aria-label, making it invisible to screen-reader users and inconsistent with the hidden-toolbar variant.

-<button
+<button
   type="button"
   className="flex-shrink-0 size-6 grid place-items-center rounded text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-80 transition-colors"
   onClick={handleOpenNavigationPane}
+  aria-label={t("page_navigation_pane.open_button")}
 >

53-77: DRY: duplicate toggle-button markup appears twice

The two identical button blocks (inside toolbar & floating) could be abstracted into a small <NavigationPaneToggle /> component to prevent drift and simplify future style updates.

web/core/components/pages/navigation-pane/tab-panels/info/version-history.tsx (1)

85-89: Consider deterministic ordering of versions

fetchAllVersions may return unsorted data. Sorting (e.g. by last_saved_at descending) guarantees a stable UI and prevents accidental re-ordering when the backend changes its default.

const { data: versionsList } = useSWR(
  id ? `PAGE_VERSIONS_LIST_${id}` : null,
  id ? () => versionHistory.fetchAllVersions(id).then(v => v?.sort((a,b) => Date.parse(b.last_saved_at) - Date.parse(a.last_saved_at))) : null,
);
web/core/components/pages/navigation-pane/root.tsx (1)

48-56: Use router.replace + shallow routing to avoid full page navigations on tab switch

Tab changes only mutate query parameters. router.push triggers a full navigation, which is heavier and resets scroll position. Replacing in place keeps history lean and improves UX.

-      const updatedRoute = updateQueryParams({
-        paramsToAdd: { [PAGE_NAVIGATION_PANE_TABS_QUERY_PARAM]: updatedTab },
-      });
-      router.push(updatedRoute);
+      router.replace(
+        updateQueryParams({ paramsToAdd: { [PAGE_NAVIGATION_PANE_TABS_QUERY_PARAM]: updatedTab } }),
+        { scroll: false }
+      );
web/core/components/pages/navigation-pane/tab-panels/assets.tsx (1)

101-110: Effect dependency should include page or editorRef stable reference

If page.editorRef changes, the cleanup of the previous subscription is skipped because the dependency array only tracks editorRef reference identity. If page is re-created elsewhere, explicitly include it to avoid leaks.

-}, [editorRef]);
+}, [page, editorRef]);
web/core/components/pages/version/root.tsx (1)

50-51: Avoid layout coupling with a magic width

The overlay always reserves 100% - 294px, even when the navigation pane is closed, creating dead space and complicating responsive layouts.
Compute the width conditionally or derive it from a CSS variable controlled by the nav-pane state.

web/core/components/pages/editor/page-root.tsx (2)

103-108: Memoise derived flag

isValidNavigationPaneTab recalculates on every render.
Wrap it in useMemo with navigationPaneQueryParam as dep to avoid useless renders once the editor grows heavier.


110-114: Use router.replace to prevent history spam

Opening the navigation pane just toggles a UI element; adding a new entry to browser history on every toggle (router.push) clutters the back-stack.
router.replace (or { scroll:false }) keeps the URL in sync without polluting history.

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

108-109: Consistent casing for markdown helper

The method is getMarkDown, but the underlying storage helper is getMarkdown(). Align the casing (getMarkdown) to avoid confusion.

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

220-227: Throttle expensive document-info computations

onDocumentInfoChange recomputes counts on every transaction, traversing the doc each time.
Wrap the handler in a throttle/debounce (e.g. 250 ms) to avoid UI jank on rapid typing.

📜 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 553aa62.

📒 Files selected for processing (40)
  • packages/editor/src/ce/constants/assets.ts (1 hunks)
  • packages/editor/src/ce/constants/extensions.ts (1 hunks)
  • packages/editor/src/ce/types/storage.ts (2 hunks)
  • packages/editor/src/core/extensions/custom-image/components/image-block.tsx (2 hunks)
  • packages/editor/src/core/helpers/assets.ts (1 hunks)
  • packages/editor/src/core/helpers/editor-ref.ts (1 hunks)
  • packages/editor/src/core/hooks/use-editor.ts (4 hunks)
  • packages/editor/src/core/hooks/use-read-only-editor.ts (3 hunks)
  • packages/editor/src/core/plugins/drag-handle.ts (3 hunks)
  • packages/editor/src/core/types/editor.ts (2 hunks)
  • web/ce/components/pages/navigation-pane/index.ts (1 hunks)
  • web/ce/components/pages/navigation-pane/tab-panels/root.tsx (1 hunks)
  • web/core/components/pages/editor/editor-body.tsx (5 hunks)
  • web/core/components/pages/editor/page-root.tsx (4 hunks)
  • web/core/components/pages/editor/summary/content-browser.tsx (3 hunks)
  • web/core/components/pages/editor/summary/heading-components.tsx (1 hunks)
  • web/core/components/pages/editor/toolbar/index.ts (0 hunks)
  • web/core/components/pages/editor/toolbar/info-popover.tsx (0 hunks)
  • web/core/components/pages/editor/toolbar/root.tsx (3 hunks)
  • web/core/components/pages/header/actions.tsx (1 hunks)
  • web/core/components/pages/navigation-pane/index.ts (1 hunks)
  • web/core/components/pages/navigation-pane/root.tsx (1 hunks)
  • web/core/components/pages/navigation-pane/tab-panels/assets.tsx (1 hunks)
  • web/core/components/pages/navigation-pane/tab-panels/info/actors-info.tsx (1 hunks)
  • web/core/components/pages/navigation-pane/tab-panels/info/document-info.tsx (1 hunks)
  • web/core/components/pages/navigation-pane/tab-panels/info/root.tsx (1 hunks)
  • web/core/components/pages/navigation-pane/tab-panels/info/version-history.tsx (1 hunks)
  • web/core/components/pages/navigation-pane/tab-panels/outline.tsx (1 hunks)
  • web/core/components/pages/navigation-pane/tab-panels/root.tsx (1 hunks)
  • web/core/components/pages/navigation-pane/tabs-list.tsx (1 hunks)
  • web/core/components/pages/version/editor.tsx (3 hunks)
  • web/core/components/pages/version/index.ts (0 hunks)
  • web/core/components/pages/version/main-content.tsx (5 hunks)
  • web/core/components/pages/version/root.tsx (1 hunks)
  • web/core/components/pages/version/sidebar-list-item.tsx (0 hunks)
  • web/core/components/pages/version/sidebar-list.tsx (0 hunks)
  • web/core/components/pages/version/sidebar-root.tsx (0 hunks)
  • web/core/hooks/use-query-params.ts (2 hunks)
  • web/helpers/editor.helper.ts (1 hunks)
  • web/styles/globals.css (1 hunks)
💤 Files with no reviewable changes (6)
  • web/core/components/pages/editor/toolbar/index.ts
  • web/core/components/pages/version/index.ts
  • web/core/components/pages/version/sidebar-list.tsx
  • web/core/components/pages/version/sidebar-list-item.tsx
  • web/core/components/pages/version/sidebar-root.tsx
  • web/core/components/pages/editor/toolbar/info-popover.tsx
🧰 Additional context used
🧬 Code Graph Analysis (17)
web/ce/components/pages/navigation-pane/tab-panels/root.tsx (2)
web/ce/components/pages/navigation-pane/index.ts (1)
  • TPageNavigationPaneTab (1-1)
web/core/store/pages/base-page.ts (1)
  • TPageInstance (67-70)
web/helpers/editor.helper.ts (2)
apiserver/plane/settings/storage.py (1)
  • url (15-16)
web/core/store/router.store.ts (2)
  • projectId (85-87)
  • workspaceSlug (69-71)
web/core/components/pages/navigation-pane/tabs-list.tsx (3)
packages/i18n/src/hooks/use-translation.ts (1)
  • useTranslation (23-35)
web/ce/components/pages/navigation-pane/index.ts (1)
  • ORDERED_PAGE_NAVIGATION_TABS_LIST (24-31)
packages/i18n/src/store/index.ts (1)
  • t (211-232)
packages/editor/src/ce/constants/assets.ts (1)
packages/editor/src/core/helpers/assets.ts (1)
  • TAssetMetaDataRecord (12-12)
packages/editor/src/core/hooks/use-read-only-editor.ts (1)
packages/editor/src/core/helpers/editor-ref.ts (1)
  • getEditorRefHelpers (20-57)
web/core/components/pages/navigation-pane/tab-panels/outline.tsx (4)
web/core/store/pages/base-page.ts (1)
  • TPageInstance (67-70)
web/core/hooks/use-resolved-asset-path.tsx (1)
  • useResolvedAssetPath (10-26)
packages/i18n/src/hooks/use-translation.ts (1)
  • useTranslation (23-35)
web/core/components/pages/editor/summary/content-browser.tsx (1)
  • PageContentBrowser (16-81)
web/core/components/pages/editor/editor-body.tsx (2)
packages/i18n/src/hooks/use-translation.ts (1)
  • useTranslation (23-35)
web/core/components/pages/editor/summary/content-browser.tsx (1)
  • PageContentBrowser (16-81)
web/core/components/pages/navigation-pane/index.ts (1)
web/ce/components/pages/navigation-pane/index.ts (1)
  • ORDERED_PAGE_NAVIGATION_TABS_LIST (24-31)
web/core/components/pages/version/main-content.tsx (1)
web/helpers/date-time.helper.ts (1)
  • renderFormattedTime (79-94)
web/core/components/pages/editor/summary/heading-components.tsx (1)
packages/editor/src/core/extensions/headings-list.ts (1)
  • IMarking (6-11)
web/core/components/pages/version/root.tsx (3)
web/core/components/pages/version/editor.tsx (1)
  • TVersionEditorProps (17-20)
web/core/hooks/use-query-params.ts (1)
  • useQueryParams (8-37)
web/core/components/pages/navigation-pane/index.ts (2)
  • PAGE_NAVIGATION_PANE_VERSION_QUERY_PARAM (9-9)
  • PAGE_NAVIGATION_PANE_WIDTH (6-6)
web/core/components/pages/navigation-pane/tab-panels/info/version-history.tsx (7)
web/core/store/pages/base-page.ts (1)
  • TPageInstance (67-70)
web/core/components/pages/editor/page-root.tsx (1)
  • TPageRootHandlers (32-39)
packages/i18n/src/hooks/use-translation.ts (1)
  • useTranslation (23-35)
web/helpers/date-time.helper.ts (1)
  • renderFormattedTime (79-94)
packages/ui/src/avatar/avatar.tsx (1)
  • Avatar (116-169)
web/core/components/pages/navigation-pane/index.ts (1)
  • PAGE_NAVIGATION_PANE_VERSION_QUERY_PARAM (9-9)
web/core/hooks/use-query-params.ts (1)
  • useQueryParams (8-37)
web/core/components/pages/navigation-pane/tab-panels/assets.tsx (7)
web/core/store/pages/base-page.ts (1)
  • TPageInstance (67-70)
packages/editor/src/core/types/editor.ts (1)
  • TEditorAsset (82-89)
packages/i18n/src/hooks/use-translation.ts (1)
  • useTranslation (23-35)
web/core/store/router.store.ts (1)
  • workspaceSlug (69-71)
web/helpers/editor.helper.ts (2)
  • getEditorAssetSrc (14-23)
  • getEditorAssetDownloadSrc (29-38)
packages/utils/src/attachment.ts (1)
  • convertBytesToSize (22-32)
web/core/hooks/use-resolved-asset-path.tsx (1)
  • useResolvedAssetPath (10-26)
packages/editor/src/core/helpers/assets.ts (3)
packages/editor/src/core/types/editor.ts (1)
  • TEditorAsset (82-89)
packages/editor/src/core/extensions/custom-image/components/image-block.tsx (1)
  • getImageBlockId (41-41)
packages/editor/src/ce/constants/assets.ts (1)
  • ADDITIONAL_ASSETS_META_DATA_RECORD (6-6)
web/core/components/pages/editor/toolbar/root.tsx (4)
web/core/store/pages/base-page.ts (1)
  • TPageInstance (67-70)
packages/i18n/src/hooks/use-translation.ts (1)
  • useTranslation (23-35)
web/core/components/pages/editor/toolbar/toolbar.tsx (1)
  • PageToolbar (66-164)
web/ce/components/pages/header/collaborators-list.tsx (1)
  • PageCollaboratorsList (10-10)
packages/editor/src/core/types/editor.ts (1)
packages/editor/src/core/extensions/headings-list.ts (1)
  • IMarking (6-11)
packages/editor/src/core/hooks/use-editor.ts (5)
packages/editor/src/core/helpers/editor-ref.ts (1)
  • getEditorRefHelpers (20-57)
packages/editor/src/core/helpers/get-extension-storage.ts (1)
  • getExtensionStorage (5-8)
packages/editor/src/core/helpers/assets.ts (1)
  • getAllEditorAssets (39-54)
packages/editor/src/core/helpers/common.ts (1)
  • getParagraphCount (92-99)
packages/editor/src/core/helpers/insert-content-at-cursor-position.ts (1)
  • insertContentAtSavedSelection (3-22)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (19)
web/styles/globals.css (1)

500-500: Enable smooth scrolling globally

Adding scroll-behavior: smooth at the global selector aligns with the new navigation pane's scroll-to-section feature.

web/core/components/pages/header/actions.tsx (1)

5-5: Imported PageOptionsDropdown for new options menu

The PageOptionsDropdown replaces the removed PageInfoPopover to surface page actions—import path and usage look correct.

packages/editor/src/ce/types/storage.ts (1)

1-1: Integrate character count into extension storage

The CharacterCountStorage is now mapped under CORE_EXTENSIONS.CHARACTER_COUNT, matching the new document info API. Imports and type mapping appear correct.

Also applies to: 19-19

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

25-38: Avoid duplicating logic & tighten return type

getEditorAssetDownloadSrc repeats almost the exact branch logic of getEditorAssetSrc. Consider extracting the common URL prefix builder or passing a boolean flag indicating “download” to a single helper – this prevents two functions from silently diverging.

In addition, the function is typed to return string | undefined but always returns a string ("" at worst). Either change the initial value to undefined or narrow the return type to string for consistency with actual behavior.

-export const getEditorAssetDownloadSrc = (args: TEditorSrcArgs): string | undefined => {
+export const getEditorAssetDownloadSrc = (args: TEditorSrcArgs): string => {
   const { assetId, projectId, workspaceSlug } = args;
-  let url: string | undefined = "";
+  let url = "";

[ suggest_essential_refactor ]

packages/editor/src/ce/constants/assets.ts (1)

1-7: Potential incorrect import alias

import { TAssetMetaDataRecord } from "@/helpers/assets";

Inside packages/editor, the alias @/ usually resolves to packages/editor/src.
The actual file lives at core/helpers/assets.ts, so the correct path should likely be:

import { TAssetMetaDataRecord } from "@/core/helpers/assets";

Please verify the ts-config path mapping; otherwise this will break type-checking and builds.

web/core/components/pages/navigation-pane/tab-panels/outline.tsx (1)

18-18: editorRef might not exist on TPageInstance.

editorRef is destructured directly from page, yet the snippet that defines TPageInstance doesn’t list this property.
If the type wasn’t extended elsewhere, this will raise a TypeScript error.

Please confirm that TPageInstance has been augmented (e.g., via declaration merging) to include editorRef, or update the prop typing accordingly.

web/core/components/pages/editor/summary/heading-components.tsx (1)

10-12: Good consolidation of duplicated class names.

Extracting COMMON_CLASSNAME removes repetition and eases future tweaks.

packages/editor/src/core/plugins/drag-handle.ts (1)

419-420: Switched to public serializeForClipboard – nice!

Using the public API removes the reliance on an internal symbol and future-proofs the plugin.

web/core/components/pages/navigation-pane/tab-panels/root.tsx (1)

7-8: Potential circular / duplicate import path.

This file is itself .../navigation-pane/tab-panels/root.tsx yet imports
@/plane-web/components/pages/navigation-pane/tab-panels/root.
Double-check that it refers to a different module and not to itself through another alias, which would cause unexpected bundler behaviour.

web/core/components/pages/editor/summary/content-browser.tsx (1)

46-46: Fail-safe early-return looks good

Gracefully returning the provided emptyState (or null) when there are no headings prevents unnecessary DOM nodes.
No action required.

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

43-46: Switching immediatelyRender to false can delay the first paint

With read-only editors this is usually fine, but if you rely on the initial HTML being present for layout calculations (e.g. scroll height) you may see a one-frame flash.
Please verify in the page/version screens that the document no longer flickers on mount.

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

92-98: Rendering nothing when description is empty might be confusing

If a version exists but its description_html is empty, the component returns null, so users see a blank pane instead of a placeholder or “No content” message. Consider falling back to the empty paragraph you already pass to the editor, or showing an explicit notice.

-if (description === undefined || description?.trim() === "") return null;
+if (description === undefined || description.trim() === "") {
+  return (
+    <p className="text-sm text-custom-text-400 px-5 py-4">
+      This version contains no content.
+    </p>
+  );
+}
web/core/components/pages/navigation-pane/tab-panels/info/root.tsx (1)

16-26: Component structure looks solid

Clear separation of document info, actors info and version history keeps the tab panel cohesive. No issues spotted.

web/core/components/pages/editor/editor-body.tsx (1)

92-94: Translation hook addition LGTM

Good to see i18n support for the floating outline button.

web/core/components/pages/navigation-pane/index.ts (1)

1-11: Import path looks CE-specific – confirm alias correctness

"@/plane-web/components/pages/navigation-pane" resolves to the CE layer. If this file is compiled in OSS/EE builds that don’t include that alias, the import will fail. Please double-check that tsconfig.paths maps this alias in every build target.

web/core/components/pages/version/main-content.tsx (1)

111-113: Render guard for VersionEditor during loading

<VersionEditor> receives versionDetails={undefined} on initial render. If the implementation assumes a non-null object this can cause runtime errors. Consider short-circuiting until versionDetails is available or ensure the editor handles undefined.

packages/editor/src/core/helpers/assets.ts (1)

8-9: Path alias likely wrong

"@/plane-editor/constants/assets" looks out of place—the file lives in packages/editor/src/ce/constants/assets.ts.
Double-check the tsconfig path alias; a wrong import will silently yield an empty object, compounding the issue above.

web/core/components/pages/version/root.tsx (1)

31-33: Validate query-param before use

activeVersion comes straight from searchParams. If a malformed value is injected it will propagate to PageVersionsMainContent, potentially triggering an unnecessary network call or even a crash.
Add a simple format check (e.g. UUID/number regex) before passing it downstream.

web/core/components/pages/editor/page-root.tsx (1)

152-160: Double-check prop drilling of version API

PageNavigationPaneRoot receives both isNavigationPaneOpen and versionHistory while the overlay independently fetches version details via the same handlers. Ensure concurrent fetches don’t race or duplicate network traffic.

Comment on lines +13 to +32
const updateQueryParams = useCallback(
({ paramsToAdd = {}, paramsToRemove = [] }: { paramsToAdd?: TParamsToAdd; paramsToRemove?: string[] }) => {
const currentParams = new URLSearchParams(searchParams.toString());

// add or update query parameters
Object.keys(paramsToAdd).forEach((key) => {
currentParams.set(key, paramsToAdd[key]);
});
// add or update query parameters
Object.keys(paramsToAdd).forEach((key) => {
currentParams.set(key, paramsToAdd[key]);
});

// remove specified query parameters
paramsToRemove.forEach((key) => {
currentParams.delete(key);
});
// remove specified query parameters
paramsToRemove.forEach((key) => {
currentParams.delete(key);
});

// construct the new route with the updated query parameters
const newRoute = `${pathname}?${currentParams.toString()}`;
return newRoute;
};
// construct the new route with the updated query parameters
const newRoute = `${pathname}?${currentParams.toString()}`;
return newRoute;
},
[pathname, searchParams]
);
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

Trailing “?” when no params

new URLSearchParams().toString() returns an empty string, so routes with no query parameters become ${pathname}? (e.g. /docs?). This yields visually odd URLs and can confuse caching layers.

Add a conditional:

-const newRoute = `${pathname}?${currentParams.toString()}`;
+const query = currentParams.toString();
+const newRoute = query ? `${pathname}?${query}` : pathname;
📝 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 updateQueryParams = useCallback(
({ paramsToAdd = {}, paramsToRemove = [] }: { paramsToAdd?: TParamsToAdd; paramsToRemove?: string[] }) => {
const currentParams = new URLSearchParams(searchParams.toString());
// add or update query parameters
Object.keys(paramsToAdd).forEach((key) => {
currentParams.set(key, paramsToAdd[key]);
});
// add or update query parameters
Object.keys(paramsToAdd).forEach((key) => {
currentParams.set(key, paramsToAdd[key]);
});
// remove specified query parameters
paramsToRemove.forEach((key) => {
currentParams.delete(key);
});
// remove specified query parameters
paramsToRemove.forEach((key) => {
currentParams.delete(key);
});
// construct the new route with the updated query parameters
const newRoute = `${pathname}?${currentParams.toString()}`;
return newRoute;
};
// construct the new route with the updated query parameters
const newRoute = `${pathname}?${currentParams.toString()}`;
return newRoute;
},
[pathname, searchParams]
);
const updateQueryParams = useCallback(
({ paramsToAdd = {}, paramsToRemove = [] }: { paramsToAdd?: TParamsToAdd; paramsToRemove?: string[] }) => {
const currentParams = new URLSearchParams(searchParams.toString());
// add or update query parameters
Object.keys(paramsToAdd).forEach((key) => {
currentParams.set(key, paramsToAdd[key]);
});
// remove specified query parameters
paramsToRemove.forEach((key) => {
currentParams.delete(key);
});
// construct the new route with the updated query parameters
const query = currentParams.toString();
const newRoute = query ? `${pathname}?${query}` : pathname;
return newRoute;
},
[pathname, searchParams]
);
🤖 Prompt for AI Agents
In web/core/hooks/use-query-params.ts around lines 13 to 32, the function
updateQueryParams appends a trailing "?" to the pathname even when there are no
query parameters, resulting in URLs like "/docs?". To fix this, add a
conditional check before constructing newRoute: if currentParams.toString() is
empty, return pathname without the "?", otherwise append "?" followed by the
query string. This prevents trailing question marks on URLs with no parameters.

Comment on lines +173 to 175
const scrollableParent = getScrollParent(dragHandleElement!);
if (!scrollableParent) return;

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

Null-safety over non-null assertion.

dragHandleElement! will crash if, for any reason, the element hasn’t been initialised. A defensive early return is cheaper than a runtime exception.

-    const scrollableParent = getScrollParent(dragHandleElement!);
+    if (!dragHandleElement) return;
+    const scrollableParent = getScrollParent(dragHandleElement);
📝 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 scrollableParent = getScrollParent(dragHandleElement!);
if (!scrollableParent) return;
if (!dragHandleElement) return;
const scrollableParent = getScrollParent(dragHandleElement);
if (!scrollableParent) return;
🤖 Prompt for AI Agents
In packages/editor/src/core/plugins/drag-handle.ts around lines 173 to 175,
replace the non-null assertion on dragHandleElement with a null check before
calling getScrollParent. Add a defensive early return if dragHandleElement is
null or undefined to prevent runtime exceptions and ensure null-safety.

Comment on lines 65 to 89
return (
<a
href={asset.scrollId}
className="relative group/asset-item h-12 flex items-center gap-2 rounded border border-custom-border-200 hover:bg-custom-background-80 transition-colors"
>
<div
className="flex-shrink-0 w-11 h-12 rounded-l bg-cover bg-no-repeat bg-center"
style={{
backgroundImage: `url('${getAssetSrc(asset.src)}')`,
}}
/>
<div className="flex-1 space-y-0.5">
<p className="text-sm font-medium">{asset.name}</p>
<p className="text-xs text-custom-text-200">{convertBytesToSize(Number(asset.size || 0))}</p>
</div>
<a
href={getAssetDownloadSrc(asset.src)}
target="_blank"
rel="noreferrer noopener"
className="shrink-0 self-end mb-1 mr-1 py-0.5 px-1 flex items-center gap-1 rounded text-custom-text-200 hover:text-custom-text-100 opacity-0 pointer-events-none group-hover/asset-item:opacity-100 group-hover/asset-item:pointer-events-auto transition-all"
>
<Download className="shrink-0 size-3" />
<span className="text-xs font-medium">{t("page_navigation_pane.tabs.assets.download_button")}</span>
</a>
</a>
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

Nested anchors are invalid HTML – convert the outer wrapper to a <div>

An <a> element cannot contain another interactive <a>. This breaks accessibility and can lead to unpredictable click behaviour.

-<a
-  href={asset.scrollId}
-  className="relative group/asset-item h-12 flex items-center gap-2 ..."
->
+<div
+  role="link"
+  tabIndex={0}
+  onClick={() => document.querySelector(asset.scrollId)?.scrollIntoView({ behavior: "smooth" })}
+  className="relative group/asset-item h-12 flex items-center gap-2 ..."
+>
...
-</a>
+</div>

Keep the inner download link as the only anchor.

📝 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
return (
<a
href={asset.scrollId}
className="relative group/asset-item h-12 flex items-center gap-2 rounded border border-custom-border-200 hover:bg-custom-background-80 transition-colors"
>
<div
className="flex-shrink-0 w-11 h-12 rounded-l bg-cover bg-no-repeat bg-center"
style={{
backgroundImage: `url('${getAssetSrc(asset.src)}')`,
}}
/>
<div className="flex-1 space-y-0.5">
<p className="text-sm font-medium">{asset.name}</p>
<p className="text-xs text-custom-text-200">{convertBytesToSize(Number(asset.size || 0))}</p>
</div>
<a
href={getAssetDownloadSrc(asset.src)}
target="_blank"
rel="noreferrer noopener"
className="shrink-0 self-end mb-1 mr-1 py-0.5 px-1 flex items-center gap-1 rounded text-custom-text-200 hover:text-custom-text-100 opacity-0 pointer-events-none group-hover/asset-item:opacity-100 group-hover/asset-item:pointer-events-auto transition-all"
>
<Download className="shrink-0 size-3" />
<span className="text-xs font-medium">{t("page_navigation_pane.tabs.assets.download_button")}</span>
</a>
</a>
return (
- <a
- href={asset.scrollId}
- className="relative group/asset-item h-12 flex items-center gap-2 rounded border border-custom-border-200 hover:bg-custom-background-80 transition-colors"
- >
+ <div
+ role="link"
+ tabIndex={0}
+ onClick={() =>
+ document
+ .querySelector(asset.scrollId)
+ ?.scrollIntoView({ behavior: "smooth" })
+ }
+ className="relative group/asset-item h-12 flex items-center gap-2 rounded border border-custom-border-200 hover:bg-custom-background-80 transition-colors"
+ >
<div
className="flex-shrink-0 w-11 h-12 rounded-l bg-cover bg-no-repeat bg-center"
style={{
backgroundImage: `url('${getAssetSrc(asset.src)}')`,
}}
/>
<div className="flex-1 space-y-0.5">
<p className="text-sm font-medium">{asset.name}</p>
<p className="text-xs text-custom-text-200">
{convertBytesToSize(Number(asset.size || 0))}
</p>
</div>
<a
href={getAssetDownloadSrc(asset.src)}
target="_blank"
rel="noreferrer noopener"
className="shrink-0 self-end mb-1 mr-1 py-0.5 px-1 flex items-center gap-1 rounded text-custom-text-200 hover:text-custom-text-100 opacity-0 pointer-events-none group-hover/asset-item:opacity-100 group-hover/asset-item:pointer-events-auto transition-all"
>
<Download className="shrink-0 size-3" />
<span className="text-xs font-medium">
{t("page_navigation_pane.tabs.assets.download_button")}
</span>
</a>
- </a>
+ </div>
)
🤖 Prompt for AI Agents
In web/core/components/pages/navigation-pane/tab-panels/assets.tsx around lines
65 to 89, the outer <a> tag wraps an inner <a> tag, which is invalid HTML and
breaks accessibility. Change the outer <a> element to a <div> to remove nested
anchors, keeping the inner download link as the only anchor. Adjust styling and
event handling if necessary to preserve the original behavior.

Comment on lines 82 to 89
export type TEditorAsset = {
id: string;
name: string;
scrollId: string;
size: number;
src: string;
type: keyof typeof CORE_EXTENSIONS | keyof typeof ADDITIONAL_EXTENSIONS;
};
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

Narrow the type union to asset-extensions only

type: keyof typeof CORE_EXTENSIONS | keyof typeof ADDITIONAL_EXTENSIONS admits non-asset extensions (e.g. PARAGRAPH).
Create a dedicated ASSET_EXTENSION_KEYS union so tooling & autocomplete don’t accept invalid values.

🤖 Prompt for AI Agents
In packages/editor/src/core/types/editor.ts around lines 82 to 89, the type
property currently allows keys from both CORE_EXTENSIONS and
ADDITIONAL_EXTENSIONS, which includes non-asset extensions like PARAGRAPH. To
fix this, define a new union type ASSET_EXTENSION_KEYS that includes only
asset-related extension keys, and update the type property to use this new
union. This will restrict the allowed values to asset extensions only and
improve tooling and autocomplete accuracy.

Comment on lines +238 to +246
onHeadingChange: (callback) => {
const handleHeadingChange = () => {
if (!editor) return;
const headings = getExtensionStorage(editor, CORE_EXTENSIONS.HEADINGS_LIST)?.headings;
if (headings) {
callback(headings);
}
});
};

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

Notify when heading list becomes empty

Callback fires only when headings is truthy. Consumers never learn that the document now has zero headings.
Invoke the callback unconditionally.

🤖 Prompt for AI Agents
In packages/editor/src/core/hooks/use-editor.ts around lines 238 to 246, the
callback for heading changes is only invoked when the headings list is truthy,
so consumers are not notified when the headings list becomes empty. Modify the
code to call the callback unconditionally, passing the headings list even if it
is empty or undefined, so consumers are always informed of the current heading
state.

@aaryan610 aaryan610 marked this pull request as ready for review June 16, 2025 14:32
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

🔭 Outside diff range comments (1)
web/core/components/pages/editor/toolbar/options-dropdown.tsx (1)

70-79: Handle copy-to-clipboard failures

copyTextToClipboard returns a promise but any rejection is silently ignored. Surfacing the error helps debugging and gives the user feedback:

-  copyTextToClipboard(editorRef.getMarkDown()).then(() =>
-    setToast({
-      type: TOAST_TYPE.SUCCESS,
-      title: "Success!",
-      message: "Markdown copied to clipboard.",
-    })
-  );
+  copyTextToClipboard(editorRef.getMarkDown())
+    .then(() =>
+      setToast({
+        type: TOAST_TYPE.SUCCESS,
+        title: "Success!",
+        message: "Markdown copied to clipboard.",
+      }),
+    )
+    .catch(() =>
+      setToast({
+        type: TOAST_TYPE.ERROR,
+        title: "Failed!",
+        message: "Unable to copy markdown to clipboard.",
+      }),
+    );
♻️ Duplicate comments (4)
packages/editor/src/core/helpers/assets.ts (1)

13-36: Key-name mismatch with node.type.name still unfixed
(previously reported)

The record is keyed by enum values (IMAGE, CUSTOM_IMAGE) but elsewhere look-ups are done with node.type.name ("image", "customImage").
Unless every caller maps the case manually (they currently don’t, see file-restore/delete plugins), asset discovery will silently fail.

Fix options:

  1. Normalise on access:
const key = node.type.name.toUpperCase() as keyof typeof CORE_ASSETS_META_DATA_RECORD;
CORE_ASSETS_META_DATA_RECORD[key]?.(attrs)
  1. Store the record with the actual node names.

Please address globally.

web/core/components/pages/navigation-pane/tab-panels/assets.tsx (1)

65-90: Nested anchors are still present – invalid HTML & accessibility issue
The outer <a> (href={asset.scrollId}) wraps another <a> used for downloads. This was flagged in the previous review and has not been addressed.

Convert the wrapper to a non-anchor element (<div> or <button> with role="link") and keep the download link as the only anchor.

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

194-204: insertText still bails when the selection is empty

Users expect pasted/inserted content to appear at the caret even when nothing is selected.
Re-apply the previously suggested fallback:

-        if (empty) return;
+        if (empty) {
+          editor.chain().focus().insertContent(contentHTML).run();
+          return;
+        }

238-245: Heading-change callback not fired for empty list

The callback is only invoked when headings is truthy, so consumers never learn that the document now has zero headings.

Invoke the callback unconditionally:

-          if (headings) {
-            callback(headings);
-          }
+          callback(headings ?? []);
🧹 Nitpick comments (5)
packages/editor/src/core/hooks/use-collaborative-editor.ts (1)

16-17: Dependency awareness

onAssetChange is forwarded to useEditor, but it’s not memo-ised. If callers pass a new lambda on every render, it could force unnecessary re-subscriptions inside useEditor. Recommend wrapping in useCallback at call-site or documenting this expectation.

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

177-178: Missing editor in dependency array

useCallback depends on editor, but only editor.isEditable is listed.
While the instance is usually stable, eslint will still warn and future refactors might break the assumption.

-}, [draggedInside, editor.isEditable, failedToLoadImage, isImageBeingUploaded]);
+}, [draggedInside, editor, failedToLoadImage, isImageBeingUploaded]);
web/core/store/pages/page-editor-info.ts (1)

30-40: Redundant runInAction – action decorators already batch

setEditorRef / updateAssetsList are declared as MobX actions via the decorator, so wrapping the body in an extra runInAction is superfluous.

-  setEditorRef: ... = (editorRef) => {
-    runInAction(() => {
-      this.editorRef = editorRef;
-    });
-  };
+  setEditorRef: ... = (editorRef) => {
+    this.editorRef = editorRef;
+  };

Same for updateAssetsList.

web/core/store/pages/base-page.ts (1)

104-107: Provide a cleanup hook for the new editor sub-store

PageEditorInstance is created but never torn down.
If the class later adds RxJS/MobX reactions or event listeners, the parent BasePage.cleanup() won’t dispose them, leading to memory leaks when pages are unmounted or swapped.

+  cleanup = () => {
+    this.disposers.forEach((disposer) => disposer());
+
+    // optional — call a disposer on the sub-store once it exists
+    if ("cleanup" in this.editor && typeof this.editor.cleanup === "function") {
+      this.editor.cleanup();
+    }
+  };

Adding a pass-through now prevents future surprises and keeps ownership boundaries clear.

web/core/components/pages/navigation-pane/tab-panels/assets.tsx (1)

34-62: Duplicate URL-builder logic – consolidate to avoid drift

getAssetSrc and getAssetDownloadSrc contain identical branching logic differing only in the helper they call. Reduce duplication:

-const buildSrc = (
-  path: string,
-  builder: typeof getEditorAssetSrc | typeof getEditorAssetDownloadSrc
-) => {
+const buildSrc = (
+  path: string,
+  builder: typeof getEditorAssetSrc | typeof getEditorAssetDownloadSrc
+) => {
   if (!path || !workspaceSlug) return "";
   if (path.startsWith("http")) return path;
   return (
     builder({
       assetId: path,
       projectId: project_ids?.[0],
       workspaceSlug: workspaceSlug.toString(),
     }) ?? ""
   );
 };
 
-const getAssetSrc = (path: string) => buildSrc(path, getEditorAssetSrc);
-const getAssetDownloadSrc = (path: string) =>
-  buildSrc(path, getEditorAssetDownloadSrc);
+const getAssetSrc = (path: string) => buildSrc(path, getEditorAssetSrc);
+const getAssetDownloadSrc = (path: string) =>
+  buildSrc(path, getEditorAssetDownloadSrc);

This keeps the two paths in sync and makes future changes less error-prone.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 553aa62 and 37bd7d4.

📒 Files selected for processing (21)
  • packages/editor/src/core/components/editors/document/collaborative-editor.tsx (2 hunks)
  • packages/editor/src/core/extensions/custom-image/components/image-uploader.tsx (3 hunks)
  • packages/editor/src/core/extensions/utility.ts (3 hunks)
  • packages/editor/src/core/helpers/assets.ts (1 hunks)
  • packages/editor/src/core/helpers/editor-ref.ts (1 hunks)
  • packages/editor/src/core/hooks/use-collaborative-editor.ts (2 hunks)
  • packages/editor/src/core/hooks/use-editor.ts (7 hunks)
  • packages/editor/src/core/plugins/file/delete.ts (1 hunks)
  • packages/editor/src/core/plugins/file/restore.ts (2 hunks)
  • packages/editor/src/core/types/collaboration.ts (2 hunks)
  • packages/editor/src/core/types/editor.ts (3 hunks)
  • packages/editor/src/index.ts (0 hunks)
  • web/core/components/pages/editor/editor-body.tsx (7 hunks)
  • web/core/components/pages/editor/page-root.tsx (6 hunks)
  • web/core/components/pages/editor/toolbar/options-dropdown.tsx (1 hunks)
  • web/core/components/pages/editor/toolbar/root.tsx (2 hunks)
  • web/core/components/pages/navigation-pane/tab-panels/assets.tsx (1 hunks)
  • web/core/components/pages/navigation-pane/tab-panels/info/document-info.tsx (1 hunks)
  • web/core/components/pages/navigation-pane/tab-panels/outline.tsx (1 hunks)
  • web/core/store/pages/base-page.ts (4 hunks)
  • web/core/store/pages/page-editor-info.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/editor/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • web/core/components/pages/navigation-pane/tab-panels/info/document-info.tsx
  • web/core/components/pages/navigation-pane/tab-panels/outline.tsx
  • packages/editor/src/core/helpers/editor-ref.ts
  • web/core/components/pages/editor/page-root.tsx
  • web/core/components/pages/editor/editor-body.tsx
  • packages/editor/src/core/types/editor.ts
  • web/core/components/pages/editor/toolbar/root.tsx
🧰 Additional context used
🧬 Code Graph Analysis (8)
packages/editor/src/core/types/collaboration.ts (1)
packages/editor/src/core/types/editor.ts (1)
  • TEditorAsset (82-89)
packages/editor/src/core/plugins/file/restore.ts (1)
packages/editor/src/core/helpers/assets.ts (1)
  • CORE_ASSETS_META_DATA_RECORD (13-37)
packages/editor/src/core/extensions/custom-image/components/image-uploader.tsx (1)
packages/editor/src/core/helpers/assets.ts (1)
  • CORE_ASSETS_META_DATA_RECORD (13-37)
packages/editor/src/core/helpers/assets.ts (3)
packages/editor/src/core/types/editor.ts (1)
  • TEditorAsset (82-89)
packages/editor/src/core/extensions/custom-image/components/image-block.tsx (1)
  • getImageBlockId (41-41)
packages/editor/src/ce/constants/assets.ts (1)
  • ADDITIONAL_ASSETS_META_DATA_RECORD (6-6)
packages/editor/src/core/extensions/utility.ts (2)
packages/editor/src/core/types/config.ts (1)
  • TFileHandler (7-19)
packages/editor/src/core/types/editor.ts (1)
  • TEditorAsset (82-89)
web/core/store/pages/page-editor-info.ts (1)
packages/editor/src/core/types/editor.ts (2)
  • TEditorAsset (82-89)
  • EditorRefApi (112-129)
web/core/components/pages/navigation-pane/tab-panels/assets.tsx (7)
web/core/store/pages/base-page.ts (1)
  • TPageInstance (68-71)
packages/editor/src/core/types/editor.ts (1)
  • TEditorAsset (82-89)
packages/i18n/src/hooks/use-translation.ts (1)
  • useTranslation (23-35)
web/core/store/router.store.ts (1)
  • workspaceSlug (69-71)
web/helpers/editor.helper.ts (2)
  • getEditorAssetSrc (14-23)
  • getEditorAssetDownloadSrc (29-38)
packages/utils/src/attachment.ts (1)
  • convertBytesToSize (22-32)
web/core/hooks/use-resolved-asset-path.tsx (1)
  • useResolvedAssetPath (10-26)
web/core/store/pages/base-page.ts (1)
web/core/store/pages/page-editor-info.ts (1)
  • PageEditorInstance (14-41)
🔇 Additional comments (6)
packages/editor/src/core/types/collaboration.ts (1)

38-38: 👍 New onAssetChange prop looks good

The optional callback is clearly typed and keeps backward compatibility.

packages/editor/src/core/plugins/file/delete.ts (1)

60-63: Risk of recursive transactions inside appendTransaction

Invoking editor.commands.updateAssetsList from within appendTransaction dispatches a new transaction, which ProseMirror will feed back through all plugins, including this one. If updateAssetsList sets docChanged false, you’re probably safe, but a future change could create an infinite loop.

Safer pattern: build and return a transaction instead of dispatching, or add a guard meta flag:

editor.commands.updateAssetsList?.({
   idToRemove: node.attrs.id,
+  meta: { skipAssetPlugin: true },
});

and skip when that meta is present.

Consider revisiting the command implementation to eliminate dispatch from plugin callbacks.

packages/editor/src/core/plugins/file/restore.ts (1)

48-53: Same recursion caveat as delete plugin

editor.commands.updateAssetsList is dispatched from appendTransaction, carrying the same recursion / performance risk highlighted for the delete plugin. Consider the same mitigation (returning a transaction or flagging with meta).

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

33-35: Prop drilling looks correct – double-check interface update

onAssetChange and the (re-added) onTransaction are successfully threaded through to useCollaborativeEditor.
Please make sure ICollaborativeDocumentEditor has been updated so both callbacks are declared as optional and properly typed – otherwise TS consumers will wobble.

Also applies to: 64-66

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

139-151: Missed empty-state notification & unnecessary re-renders in asset change effect

  1. if (!assets || !onAssetChange) return; skips the callback when assets is an empty array, hiding “no assets” transitions.
  2. useEditorState returns a new object each time; comparing assetsList?.assets by reference may trigger the useEffect even when contents are unchanged.
-  const assets = assetsList?.assets;
-  if (!assets || !onAssetChange) return;
-  onAssetChange(assets);
+  const assets = assetsList?.assets ?? [];
+  if (!onAssetChange) return;
+  onAssetChange(assets);

Consider using a stable comparison (e.g., hashing or lodash/isEqual) inside selector to avoid spurious updates.


217-218: 👍 Guard clause implemented

listenToRealTimeUpdate now safely returns undefined when provider is absent, preventing runtime errors flagged earlier.

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

🧹 Nitpick comments (1)
space/styles/globals.css (1)

370-370: Global smooth scrolling may conflict with user preferences
Applying scroll-behavior: smooth; on * overrides prefers-reduced-motion and can cause unexpected behavior in nested scroll containers. Consider scoping it to html and respecting motion-reduction preferences:

/* remove from universal selector */
* {
  /* … */
  /* scroll-behavior: smooth; */
}

/* add scoped, accessible smooth scrolling */
@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6640b57 and ab2e5ff.

📒 Files selected for processing (6)
  • admin/styles/globals.css (1 hunks)
  • space/styles/globals.css (2 hunks)
  • web/core/components/pages/editor/editor-body.tsx (7 hunks)
  • web/core/components/pages/navigation-pane/root.tsx (1 hunks)
  • web/core/components/pages/navigation-pane/tab-panels/assets.tsx (1 hunks)
  • web/core/components/pages/navigation-pane/tab-panels/info/version-history.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • admin/styles/globals.css
🚧 Files skipped from review as they are similar to previous changes (4)
  • web/core/components/pages/editor/editor-body.tsx
  • web/core/components/pages/navigation-pane/root.tsx
  • web/core/components/pages/navigation-pane/tab-panels/assets.tsx
  • web/core/components/pages/navigation-pane/tab-panels/info/version-history.tsx
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
space/styles/globals.css (1)

50-75: Reformatted shadow variable declarations
The move from single‐line to multi‐line for --color-shadow-* enhances readability without altering values. Indentation and trailing commas appear consistent and valid. No functional impact detected.

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

♻️ Duplicate comments (1)
web/core/components/pages/navigation-pane/tab-panels/assets.tsx (1)

66-91: Nested <a> elements – invalid & inaccessible HTML

The outer anchor (<a href={asset.href}>) wraps another anchor (<a href={getAssetDownloadSrc(...)}), re-introducing the exact issue called out in the previous review: nested anchors are not allowed and break keyboard / screen-reader semantics.
Convert the outer anchor into a non-anchor element (<div> or <button> with role="link" + click handler) so that only one interactive anchor remains.

-    return (
-      <a
-        href={asset.href}
-        className="relative group/asset-item h-12 flex items-center gap-2 pr-2 rounded border border-custom-border-200 hover:bg-custom-background-80 transition-colors"
-      >
+    return (
+      <div
+        role="link"
+        tabIndex={0}
+        onClick={() => document.querySelector(asset.href)?.scrollIntoView({ behavior: "smooth" })}
+        className="relative group/asset-item h-12 flex items-center gap-2 pr-2 rounded border border-custom-border-200 hover:bg-custom-background-80 transition-colors"
+      >
        ...
-      </a>
+      </div>
🧹 Nitpick comments (2)
web/core/components/pages/navigation-pane/tab-panels/assets.tsx (2)

34-61: Duplicate URL-resolution logic – extract helper to reduce noise

getAssetSrc and getAssetDownloadSrc are identical except for the wrapped util. Consider a single factory/helper to keep the component lean:

-  const getAssetSrc = (path: string) => { ... }
-  const getAssetDownloadSrc = (path: string) => { ... }
+  const resolveSrc = (
+    path: string,
+    resolver: typeof getEditorAssetSrc | typeof getEditorAssetDownloadSrc,
+  ) => {
+    if (!path || !workspaceSlug) return "";
+    return path.startsWith("http")
+      ? path
+      : resolver({
+          assetId: path,
+          projectId: project_ids?.[0],
+          workspaceSlug: workspaceSlug.toString(),
+        }) ?? "";
+  };

This removes duplication and prevents future drift between the two implementations.


81-83: Add download attribute for explicit download behaviour

Browsers will attempt to navigate if the Content-Disposition header is missing. Adding download hints the intent and improves UX:

-      <a
+      <a
+        download
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 68f902c and 28b867c.

📒 Files selected for processing (7)
  • packages/editor/src/ce/types/asset.ts (1 hunks)
  • packages/editor/src/core/helpers/assets.ts (1 hunks)
  • packages/editor/src/core/types/asset.ts (1 hunks)
  • packages/editor/src/core/types/editor.ts (4 hunks)
  • packages/editor/src/core/types/index.ts (1 hunks)
  • packages/editor/src/index.ts (1 hunks)
  • web/core/components/pages/navigation-pane/tab-panels/assets.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • packages/editor/src/ce/types/asset.ts
  • packages/editor/src/core/types/index.ts
  • packages/editor/src/core/types/asset.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/editor/src/index.ts
  • packages/editor/src/core/helpers/assets.ts
  • packages/editor/src/core/types/editor.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
web/core/components/pages/navigation-pane/tab-panels/assets.tsx (6)
web/core/store/pages/base-page.ts (1)
  • TPageInstance (68-71)
packages/editor/src/core/types/asset.ts (1)
  • TEditorAsset (14-14)
packages/i18n/src/hooks/use-translation.ts (1)
  • useTranslation (23-35)
packages/utils/src/editor.ts (1)
  • getEditorAssetDownloadSrc (29-38)
web/ce/components/pages/navigation-pane/tab-panels/assets.tsx (1)
  • AdditionalPageNavigationPaneAssetItem (11-11)
web/core/hooks/use-resolved-asset-path.tsx (1)
  • useResolvedAssetPath (10-26)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (javascript)

@@ -140,27 +135,26 @@ export const useEditor = (props: CustomEditorProps) => {
editor.commands.updateAssetsUploadStatus?.(assetsUploadStatus);
}, [editor, fileHandler.assetsUploadStatus]);

// subscribe to assets list changes
const assetsList = useEditorState({
Copy link
Member

Choose a reason for hiding this comment

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

let's render this in a different component since it'll rerender the entire editor

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