Skip to content

feat(editor): add "Copy as Markdown" option in context & export menus#14705

Open
asaurabhprsas wants to merge 9 commits intotoeverything:canaryfrom
asaurabhprsas:feature/copy-as-markdown
Open

feat(editor): add "Copy as Markdown" option in context & export menus#14705
asaurabhprsas wants to merge 9 commits intotoeverything:canaryfrom
asaurabhprsas:feature/copy-as-markdown

Conversation

@asaurabhprsas
Copy link
Copy Markdown

@asaurabhprsas asaurabhprsas commented Mar 22, 2026

  • Allow users to select text and copy it as Markdown via the context menu
  • Add "Copy as Markdown" under Export menu to copy entire document to clipboard

Fixes #12983

Summary by CodeRabbit

  • New Features

    • Added "Copy as Markdown" to the toolbar clipboard More menu for selected content.
    • Added "Copy as Markdown" to the page export menu to copy entire pages as Markdown.
  • Behavior

    • Export flow now returns success/failure so the UI shows a dedicated success or error notification for clipboard exports.
  • Localization

    • Added strings for "Copy as Markdown" and "Copied as Markdown".

- Allow users to select text and copy it as Markdown via the context menu
- Add "Copy as Markdown" under Export menu to copy entire document to clipboard

Fixes toeverything#12983
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Mar 22, 2026

CLA assistant check
All committers have signed the CLA.

@github-actions github-actions bot added mod:i18n Related to i18n app:core labels Mar 22, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 22, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Adds "Copy as Markdown" actions to the editor toolbar and export menu that draft selected models or the full document, convert to Markdown, write the Markdown to the clipboard, and show a localized toast notification.

Changes

Cohort / File(s) Summary
Toolbar Integration
packages/frontend/core/src/blocksuite/view-extensions/editor-config/toolbar/index.ts
Inserted a "copy-as-markdown" action into the clipboard "More" menu and createToolbarMoreMenuConfigV2; implemented helper that drafts selected models, builds a Slice, renders Markdown via getContentFromSlice(..., 'markdown'), writes to navigator.clipboard, shows a localized toast, and closes the menu.
Export Logic & Hook
packages/frontend/core/src/components/hooks/affine/use-export-page.ts
Added 'copy-markdown' export type, refactored transformer creation into createTransformer(doc), implemented copyAsMarkdown(doc, std?) to convert doc to Markdown and write to clipboard, and changed exportHandler to return Promise<boolean> with a 'copy-markdown' branch.
Export Menu UI
packages/frontend/core/src/components/page-list/operation-menu-items/export.tsx
Added "Copy as Markdown" export menu item that calls exportHandler('copy-markdown'); updated ExportProps.exportHandler type to include 'copy-markdown'.
Internationalization
packages/frontend/i18n/src/resources/en.json
Added i18n keys com.affine.export.copy-markdown ("Copy as Markdown") and com.affine.export.copied-as-markdown ("Copied as Markdown").

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as rgba(54,162,235,0.5) Toolbar / Export UI
    participant Editor as rgba(75,192,192,0.5) Editor Core
    participant Draft as rgba(255,99,132,0.5) Drafting Command
    participant Transformer as rgba(153,102,255,0.5) Markdown Transformer
    participant Clipboard as rgba(255,159,64,0.5) Clipboard API
    participant Toast as rgba(255,205,86,0.5) Toast Notification

    User->>UI: Trigger "Copy as Markdown"
    UI->>Editor: getSelectedModelsCommand()
    Editor-->>UI: selected models
    UI->>Draft: draftSelectedModelsCommand(selected models)
    Draft-->>UI: drafted models (Slice)
    UI->>Transformer: getContentFromSlice(slice, "markdown")
    Transformer-->>UI: markdown text
    UI->>Clipboard: navigator.clipboard.writeText(markdown)
    Clipboard-->>UI: success / failure
    UI->>Toast: show localized toast (copied or error)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I'm a rabbit with a tiny quill,
I draft and slice with gentle skill.
Markdown hops onto your clipboard bright,
Paste and find your notes just right.
🐇📋✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The implementation addresses all primary objectives from #12983: provides 'Copy as Markdown' capability for selected content and whole documents, surfaces it in context menu and export panel, and enables quick clipboard copying in standard Markdown format.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the 'Copy as Markdown' feature as specified in #12983; no unrelated modifications detected.
Title check ✅ Passed The title accurately and concisely describes the main change: adding a 'Copy as Markdown' feature to context and export menus.

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


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/frontend/core/src/blocksuite/view-extensions/editor-config/toolbar/index.ts (1)

123-133: ⚠️ Potential issue | 🟡 Minor

splice(copyIndex + 1, ...) is inserting new items in reverse order.

Because all three inserts target the same index, the last inserted item appears first. If intended order is copy-link → copy-as-image → copy-as-markdown, this currently won’t hold.

Suggested fix
-        clipboardGroup.items.splice(
-          copyIndex + 1,
-          0,
-          createCopyLinkToBlockMenuItem(framework)
-        );
-
-        clipboardGroup.items.splice(
-          copyIndex + 1,
-          0,
-          createCopyAsPngMenuItem(framework)
-        );
-
-        clipboardGroup.items.splice(
-          copyIndex + 1,
-          0,
-          createCopyAsMarkdownMenuItem(framework)
-        );
+        clipboardGroup.items.splice(
+          copyIndex + 1,
+          0,
+          createCopyLinkToBlockMenuItem(framework),
+          createCopyAsPngMenuItem(framework),
+          createCopyAsMarkdownMenuItem(framework)
+        );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/frontend/core/src/blocksuite/view-extensions/editor-config/toolbar/index.ts`
around lines 123 - 133, Multiple splice calls at the same index cause the items
to land in reverse; replace the successive splices with a single splice that
inserts all menu items in the desired order (e.g., copy-link, copy-as-image,
copy-as-markdown) so use clipboardGroup.items.splice(copyIndex + 1, 0,
createCopyLinkMenuItem(framework), createCopyAsPngMenuItem(framework),
createCopyAsMarkdownMenuItem(framework)) or otherwise insert them in increasing
index order; update references to createCopyAsPngMenuItem,
createCopyAsMarkdownMenuItem (and createCopyLinkMenuItem) accordingly.
🧹 Nitpick comments (2)
packages/frontend/core/src/blocksuite/view-extensions/editor-config/toolbar/index.ts (1)

223-270: Copy-as-markdown logic is duplicated in two menu systems.

Both V1 and V2 paths implement nearly the same command → draft → slice → markdown → clipboard pipeline. Extracting a shared helper will prevent behavior drift and simplify future fixes.

Refactor sketch
+async function copySelectionAsMarkdown(std: MenuContext['std']) {
+  const [ok, ctx] = std.command
+    .chain()
+    .pipe(getSelectedModelsCommand)
+    .pipe(draftSelectedModelsCommand)
+    .run();
+  const draftedModels = ctx.draftedModels;
+  if (!ok || !draftedModels) return false;
+  const models = await draftedModels;
+  if (!models.length) return false;
+  const slice = Slice.fromModels(std.store, models);
+  const markdown = await getContentFromSlice(std.host, slice, 'markdown');
+  if (!markdown) return false;
+  await navigator.clipboard.writeText(markdown);
+  toast(std.host, I18n['com.affine.export.copied-as-markdown']());
+  return true;
+}

Also applies to: 291-330

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

In
`@packages/frontend/core/src/blocksuite/view-extensions/editor-config/toolbar/index.ts`
around lines 223 - 270, The copy-as-markdown flow is duplicated between V1 and
V2 menu items; extract the shared pipeline from createCopyAsMarkdownMenuItem
into a single helper (e.g., exportAsMarkdownAndCopy) that accepts the command
std/context and selected models, uses getSelectedModelsCommand →
draftSelectedModelsCommand flow, awaits draftedModels, builds
Slice.fromModels(std.store, models), calls getContentFromSlice(std.host, slice,
'markdown'), writes to navigator.clipboard and calls toast; update
createCopyAsMarkdownMenuItem and the other duplicated menu factory to call this
helper, preserve error handling (.catch(console.error)) and ctx.close()
behavior, and keep existing identifiers (createCopyAsMarkdownMenuItem,
getSelectedModelsCommand, draftSelectedModelsCommand, draftedModels,
Slice.fromModels, getContentFromSlice) so behavior remains identical.
packages/frontend/core/src/components/hooks/affine/use-export-page.ts (1)

149-162: Extract shared transformer construction to avoid drift.

The transformer setup in Line 149-162 duplicates the one in exportDoc (Line 72-85). A shared factory would reduce maintenance risk.

Refactor sketch
+function createDocTransformer(doc: Store) {
+  return new Transformer({
+    schema: getAFFiNEWorkspaceSchema(),
+    blobCRUD: doc.workspace.blobSync,
+    docCRUD: {
+      create: (id: string) => doc.workspace.createDoc(id).getStore({ id }),
+      get: (id: string) => doc.workspace.getDoc(id)?.getStore({ id }) ?? null,
+      delete: (id: string) => doc.workspace.removeDoc(id),
+    },
+    middlewares: [
+      docLinkBaseURLMiddleware(doc.workspace.id),
+      titleMiddleware(doc.workspace.meta.docMetas),
+      embedSyncedDocMiddleware('content'),
+    ],
+  });
+}
@@
-  const transformer = new Transformer({ ... });
+  const transformer = createDocTransformer(doc);

Also applies to: 72-85

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

In `@packages/frontend/core/src/components/hooks/affine/use-export-page.ts` around
lines 149 - 162, The Transformer construction is duplicated (one at the top: the
const transformer = new Transformer({...}) using getAFFiNEWorkspaceSchema(),
doc.workspace.blobSync and middlewares including docLinkBaseURLMiddleware,
titleMiddleware, embedSyncedDocMiddleware, and another similar block in
exportDoc); extract this into a shared factory function (e.g.,
createWorkspaceTransformer) that accepts the workspace or doc and returns a new
Transformer configured the same way, then replace both direct new
Transformer(...) usages with calls to that factory (update places referencing
transformer and exportDoc to use createWorkspaceTransformer(doc.workspace) or
similar). Ensure the factory uses the same schema function
getAFFiNEWorkspaceSchema(), the same blobCRUD/docCRUD closures
(create/get/delete using workspace.createDoc/getDoc/removeDoc), and the same
middlewares so behavior is identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/frontend/core/src/components/hooks/affine/use-export-page.ts`:
- Around line 145-148: The copyAsMarkdown function currently returns early when
std is missing, causing callers to always show a "Copied as Markdown" toast;
change copyAsMarkdown (in use-export-page.ts) to return a boolean indicating
success (false when std is falsy or when copy fails, true on success) and update
any callers that show the toast (the code path that displays "Copied as
Markdown") to check the returned boolean and only show the success toast when
true (also handle false by optionally showing an error toast or no toast).
Ensure you reference the copyAsMarkdown function and the toast/display logic so
the early-return case no longer produces a false-success message.

---

Outside diff comments:
In
`@packages/frontend/core/src/blocksuite/view-extensions/editor-config/toolbar/index.ts`:
- Around line 123-133: Multiple splice calls at the same index cause the items
to land in reverse; replace the successive splices with a single splice that
inserts all menu items in the desired order (e.g., copy-link, copy-as-image,
copy-as-markdown) so use clipboardGroup.items.splice(copyIndex + 1, 0,
createCopyLinkMenuItem(framework), createCopyAsPngMenuItem(framework),
createCopyAsMarkdownMenuItem(framework)) or otherwise insert them in increasing
index order; update references to createCopyAsPngMenuItem,
createCopyAsMarkdownMenuItem (and createCopyLinkMenuItem) accordingly.

---

Nitpick comments:
In
`@packages/frontend/core/src/blocksuite/view-extensions/editor-config/toolbar/index.ts`:
- Around line 223-270: The copy-as-markdown flow is duplicated between V1 and V2
menu items; extract the shared pipeline from createCopyAsMarkdownMenuItem into a
single helper (e.g., exportAsMarkdownAndCopy) that accepts the command
std/context and selected models, uses getSelectedModelsCommand →
draftSelectedModelsCommand flow, awaits draftedModels, builds
Slice.fromModels(std.store, models), calls getContentFromSlice(std.host, slice,
'markdown'), writes to navigator.clipboard and calls toast; update
createCopyAsMarkdownMenuItem and the other duplicated menu factory to call this
helper, preserve error handling (.catch(console.error)) and ctx.close()
behavior, and keep existing identifiers (createCopyAsMarkdownMenuItem,
getSelectedModelsCommand, draftSelectedModelsCommand, draftedModels,
Slice.fromModels, getContentFromSlice) so behavior remains identical.

In `@packages/frontend/core/src/components/hooks/affine/use-export-page.ts`:
- Around line 149-162: The Transformer construction is duplicated (one at the
top: the const transformer = new Transformer({...}) using
getAFFiNEWorkspaceSchema(), doc.workspace.blobSync and middlewares including
docLinkBaseURLMiddleware, titleMiddleware, embedSyncedDocMiddleware, and another
similar block in exportDoc); extract this into a shared factory function (e.g.,
createWorkspaceTransformer) that accepts the workspace or doc and returns a new
Transformer configured the same way, then replace both direct new
Transformer(...) usages with calls to that factory (update places referencing
transformer and exportDoc to use createWorkspaceTransformer(doc.workspace) or
similar). Ensure the factory uses the same schema function
getAFFiNEWorkspaceSchema(), the same blobCRUD/docCRUD closures
(create/get/delete using workspace.createDoc/getDoc/removeDoc), and the same
middlewares so behavior is identical.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ed0d4aba-1b6d-418f-85af-30c6d2cedb73

📥 Commits

Reviewing files that changed from the base of the PR and between 8ba02ed and e88ebe8.

📒 Files selected for processing (4)
  • packages/frontend/core/src/blocksuite/view-extensions/editor-config/toolbar/index.ts
  • packages/frontend/core/src/components/hooks/affine/use-export-page.ts
  • packages/frontend/core/src/components/page-list/operation-menu-items/export.tsx
  • packages/frontend/i18n/src/resources/en.json

Copy link
Copy Markdown
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.

🧹 Nitpick comments (2)
packages/frontend/core/src/components/hooks/affine/use-export-page.ts (2)

260-265: Unused success variable for non-copy-markdown exports.

The success variable is captured at Line 244 but ignored for non-'copy-markdown' export types (Lines 260-265). The success notification is shown unconditionally. This is currently safe since other export functions throw on failure (caught by the outer catch), but it's inconsistent with the 'copy-markdown' handling pattern.

Consider either using the success value consistently for all types, or document why this asymmetry is intentional.

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

In `@packages/frontend/core/src/components/hooks/affine/use-export-page.ts` around
lines 260 - 265, The code captures a local variable named success (around Line
244) but then ignores it for non-'copy-markdown' export flows, while showing
notify.success unconditionally; update the export branches inside the export
handler in use-export-page (the functions that perform exports for types other
than 'copy-markdown') to return/assign a boolean result into success and then
only call notify.success when success is truthy, or explicitly document/annotate
the asymmetry by adding a clear comment and ESLint-disable if you intentionally
ignore success; reference the existing success variable and the 'copy-markdown'
branch to mirror its handling so the notify.success call at the end uses that
same success value consistently.

145-182: Consider extracting shared Transformer creation logic.

Lines 151-164 duplicate the Transformer configuration from exportDoc (lines 72-85). Extracting a helper function would reduce duplication and ensure consistency.

♻️ Proposed refactor
+function createDocTransformer(doc: Store) {
+  return new Transformer({
+    schema: getAFFiNEWorkspaceSchema(),
+    blobCRUD: doc.workspace.blobSync,
+    docCRUD: {
+      create: (id: string) => doc.workspace.createDoc(id).getStore({ id }),
+      get: (id: string) => doc.workspace.getDoc(id)?.getStore({ id }) ?? null,
+      delete: (id: string) => doc.workspace.removeDoc(id),
+    },
+    middlewares: [
+      docLinkBaseURLMiddleware(doc.workspace.id),
+      titleMiddleware(doc.workspace.meta.docMetas),
+      embedSyncedDocMiddleware('content'),
+    ],
+  });
+}

 async function exportDoc(
   doc: Store,
   std: BlockStdScope,
   config: AdapterConfig
 ) {
-  const transformer = new Transformer({
-    schema: getAFFiNEWorkspaceSchema(),
-    blobCRUD: doc.workspace.blobSync,
-    docCRUD: {
-      create: (id: string) => doc.workspace.createDoc(id).getStore({ id }),
-      get: (id: string) => doc.workspace.getDoc(id)?.getStore({ id }) ?? null,
-      delete: (id: string) => doc.workspace.removeDoc(id),
-    },
-    middlewares: [
-      docLinkBaseURLMiddleware(doc.workspace.id),
-      titleMiddleware(doc.workspace.meta.docMetas),
-      embedSyncedDocMiddleware('content'),
-    ],
-  });
+  const transformer = createDocTransformer(doc);

   // ... rest of function
 }

 async function copyAsMarkdown(doc: Store, std?: BlockStdScope) {
   // ...
   try {
-    const transformer = new Transformer({
-      schema: getAFFiNEWorkspaceSchema(),
-      // ... same config
-    });
+    const transformer = createDocTransformer(doc);

     // ... rest of function
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/frontend/core/src/components/hooks/affine/use-export-page.ts` around
lines 145 - 182, The Transformer configuration in copyAsMarkdown duplicates the
one used in exportDoc; extract that shared logic into a helper (e.g.,
createTransformer or getTransformer) that accepts the Store (doc) and optional
std/BlockStdScope and returns a configured Transformer with schema, blobCRUD,
docCRUD and the middlewares (docLinkBaseURLMiddleware, titleMiddleware,
embedSyncedDocMiddleware). Replace the inline new Transformer({...}) in both
copyAsMarkdown and exportDoc to call this helper so both use the same
construction and avoid duplication.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/frontend/core/src/components/hooks/affine/use-export-page.ts`:
- Around line 260-265: The code captures a local variable named success (around
Line 244) but then ignores it for non-'copy-markdown' export flows, while
showing notify.success unconditionally; update the export branches inside the
export handler in use-export-page (the functions that perform exports for types
other than 'copy-markdown') to return/assign a boolean result into success and
then only call notify.success when success is truthy, or explicitly
document/annotate the asymmetry by adding a clear comment and ESLint-disable if
you intentionally ignore success; reference the existing success variable and
the 'copy-markdown' branch to mirror its handling so the notify.success call at
the end uses that same success value consistently.
- Around line 145-182: The Transformer configuration in copyAsMarkdown
duplicates the one used in exportDoc; extract that shared logic into a helper
(e.g., createTransformer or getTransformer) that accepts the Store (doc) and
optional std/BlockStdScope and returns a configured Transformer with schema,
blobCRUD, docCRUD and the middlewares (docLinkBaseURLMiddleware,
titleMiddleware, embedSyncedDocMiddleware). Replace the inline new
Transformer({...}) in both copyAsMarkdown and exportDoc to call this helper so
both use the same construction and avoid duplication.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7dcb40fc-fda4-4d56-917d-d5dac22370e2

📥 Commits

Reviewing files that changed from the base of the PR and between e88ebe8 and f22a10a.

📒 Files selected for processing (1)
  • packages/frontend/core/src/components/hooks/affine/use-export-page.ts

Copy link
Copy Markdown
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/frontend/core/src/components/hooks/affine/use-export-page.ts (1)

95-97: ⚠️ Potential issue | 🟠 Major

Propagate adapter no-op as failure to avoid false success toasts.

exportDoc can exit without exporting anything, but HTML/Markdown paths still return true, so the UI can still show success when no file was produced.

Suggested fix
-async function exportDoc(
+async function exportDoc(
   doc: Store,
   std: BlockStdScope,
   config: AdapterConfig
-) {
+): Promise<boolean> {
@@
   if (!result || (!result.file && !result.assetsIds.length)) {
-    return;
+    return false;
   }
@@
   download(downloadBlob, name);
+  return true;
 }
 
-async function exportToHtml(doc: Store, std?: BlockStdScope) {
+async function exportToHtml(doc: Store, std?: BlockStdScope): Promise<boolean> {
   if (!std) {
     // If std is not provided, we use the default export method
     await HtmlTransformer.exportDoc(doc);
+    return true;
   } else {
-    await exportDoc(doc, std, {
+    return await exportDoc(doc, std, {
       identifier: HtmlAdapterFactoryIdentifier,
@@
-async function exportToMarkdown(doc: Store, std?: BlockStdScope) {
+async function exportToMarkdown(
+  doc: Store,
+  std?: BlockStdScope
+): Promise<boolean> {
   if (!std) {
     // If std is not provided, we use the default export method
     await MarkdownTransformer.exportDoc(doc);
+    return true;
   } else {
-    await exportDoc(doc, std, {
+    return await exportDoc(doc, std, {
       identifier: MarkdownAdapterFactoryIdentifier,
@@
     case 'html':
-      await exportToHtml(page, editorRoot?.std);
-      return true;
+      return await exportToHtml(page, editorRoot?.std);
     case 'markdown':
-      await exportToMarkdown(page, editorRoot?.std);
-      return true;
+      return await exportToMarkdown(page, editorRoot?.std);

Also applies to: 186-189

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

In `@packages/frontend/core/src/components/hooks/affine/use-export-page.ts` around
lines 95 - 97, The export flow currently treats adapter no-ops as success
because exportDoc (in use-export-page.ts) can return undefined/true for
HTML/Markdown even when nothing was produced; change the early-exit checks that
read "if (!result || (!result.file && !result.assetsIds.length)) { return; }" to
explicitly return false so the caller receives a failure signal, and apply the
same fix to the analogous check around the later block (lines handling
result.file and result.assetsIds). Ensure the function signature and callers
accept/propagate the boolean return so UI success toasts only show when a real
file or assets were produced.
🧹 Nitpick comments (1)
packages/frontend/core/src/components/hooks/affine/use-export-page.ts (1)

166-167: Prefer the shared clipboard utility instead of direct navigator.clipboard.

Using the existing clipboard helper keeps feature detection/fallback behavior consistent with the rest of the app.

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

In `@packages/frontend/core/src/components/hooks/affine/use-export-page.ts` around
lines 166 - 167, Replace the direct call to
navigator.clipboard.writeText(result.file) in the useExportPage hook with the
app's shared clipboard utility (e.g., copyToClipboard or clipboard.writeText
helper); import the shared helper at the top of use-export-page.ts, call it with
result.file, and keep the same return true flow so feature detection/fallback
behavior is consistent with the rest of the app.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/frontend/core/src/components/hooks/affine/use-export-page.ts`:
- Around line 203-205: The current export path returns true even when editorRoot
is null because the optional chaining makes the call a no-op; update the export
logic in use-export-page so it explicitly checks editorRoot (and/or
editorRoot.std) before calling ExportManager.exportPng(): if editorRoot is
missing, return false (or throw/handle error) and only await
editorRoot.std.get(ExportManager).exportPng() when the context exists, ensuring
the function does not report success when export could not run.

---

Outside diff comments:
In `@packages/frontend/core/src/components/hooks/affine/use-export-page.ts`:
- Around line 95-97: The export flow currently treats adapter no-ops as success
because exportDoc (in use-export-page.ts) can return undefined/true for
HTML/Markdown even when nothing was produced; change the early-exit checks that
read "if (!result || (!result.file && !result.assetsIds.length)) { return; }" to
explicitly return false so the caller receives a failure signal, and apply the
same fix to the analogous check around the later block (lines handling
result.file and result.assetsIds). Ensure the function signature and callers
accept/propagate the boolean return so UI success toasts only show when a real
file or assets were produced.

---

Nitpick comments:
In `@packages/frontend/core/src/components/hooks/affine/use-export-page.ts`:
- Around line 166-167: Replace the direct call to
navigator.clipboard.writeText(result.file) in the useExportPage hook with the
app's shared clipboard utility (e.g., copyToClipboard or clipboard.writeText
helper); import the shared helper at the top of use-export-page.ts, call it with
result.file, and keep the same return true flow so feature detection/fallback
behavior is consistent with the rest of the app.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2787fc45-6745-4971-bad3-7616696af7b9

📥 Commits

Reviewing files that changed from the base of the PR and between f22a10a and f0ce4cd.

📒 Files selected for processing (1)
  • packages/frontend/core/src/components/hooks/affine/use-export-page.ts

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 57.34%. Comparing base (91ad783) to head (a0ad3d4).
⚠️ Report is 21 commits behind head on canary.

Additional details and impacted files
@@            Coverage Diff             @@
##           canary   #14705      +/-   ##
==========================================
- Coverage   57.82%   57.34%   -0.49%     
==========================================
  Files        2960     2960              
  Lines      165735   165735              
  Branches    24428    24290     -138     
==========================================
- Hits        95841    95044     -797     
- Misses      66848    67560     +712     
- Partials     3046     3131      +85     
Flag Coverage Δ
server-test 76.25% <ø> (-0.89%) ⬇️
unittest 34.62% <ø> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@asaurabhprsas
Copy link
Copy Markdown
Author

@darkskygit is there any issue in the PR. Do I have follow some set of rules?? I will fix let me know.

@darkskygit
Copy link
Copy Markdown
Member

@darkskygit is there any issue in the PR. Do I have follow some set of rules?? I will fix let me know.

please be patient; I haven’t had time to review the pr yet
before submitting, you can run yarn typecheck && yarn lin && yarn test locally to make sure the code has no issues and passes the tests, so it can be merged quickly once the review is ok

@darkskygit darkskygit changed the title feat(export): add "Copy as Markdown" option in context & export menus feat(editor): add "Copy as Markdown" option in context & export menus Mar 25, 2026
@darkskygit
Copy link
Copy Markdown
Member

need run yarn affine bs-docs build / yarn typecheck / yarn lint before commit

@asaurabhprsas
Copy link
Copy Markdown
Author

need run yarn affine bs-docs build / yarn typecheck / yarn lint before commit

I tried to run but getting some errors related to prisma while yarn install process.
I even tried completely removing node_modules folder but still same error.
And I think these issues are coming when I updated/synced up to date branches.
What should I do . And also errors getting with yarn test command related to files which I even haven't touched.

@darkskygit
Copy link
Copy Markdown
Member

need run / / before commityarn affine bs-docs build``yarn typecheck``yarn lint

I tried to run but getting some errors related to prisma while yarn install process. I even tried completely removing node_modules folder but still same error. And I think these issues are coming when I updated/synced up to date branches. What should I do . And also errors getting with yarn test command related to files which I even haven't touched.

if you are run in mac or linux, you can run rm -rf ./**/node_modules to remove all deps in sub folder, for windows please manually search for and delete it

@lawvs lawvs self-assigned this Apr 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app:core mod:i18n Related to i18n

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[Feature Request]: Copy content as markdown

4 participants