diff --git a/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx b/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx
index 1dd334ce4..8a6b8dd9e 100644
--- a/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx
+++ b/packages/frontend/core/src/modules/peek-view/view/peek-view-controls.tsx
@@ -1,4 +1,5 @@
-import { IconButton } from '@affine/component';
+import { IconButton, notify } from '@affine/component';
+import { copyTextToClipboard } from '@affine/core/utils/clipboard';
import { useI18n } from '@affine/i18n';
import track from '@affine/track';
import type { DocMode } from '@blocksuite/affine/model';
@@ -6,6 +7,7 @@ import {
CloseIcon,
ExpandFullIcon,
InformationIcon,
+ LinkIcon,
OpenInNewIcon,
SplitViewIcon,
} from '@blocksuite/icons/rc';
@@ -21,7 +23,10 @@ import {
useMemo,
} from 'react';
+import { ServerService } from '../../cloud';
import { WorkspaceDialogService } from '../../dialogs';
+import { DocsService } from '../../doc/services/docs';
+import { toURLSearchParams } from '../../navigation';
import { WorkbenchService } from '../../workbench';
import type {
AttachmentPeekViewInfo,
@@ -105,6 +110,8 @@ export const DocPeekViewControls = ({
const workbench = useService(WorkbenchService).workbench;
const t = useI18n();
const workspaceDialogService = useService(WorkspaceDialogService);
+ const serverService = useService(ServerService);
+ const docsService = useService(DocsService);
const controls = useMemo(() => {
return [
{
@@ -140,6 +147,27 @@ export const DocPeekViewControls = ({
peekView.close(false);
},
},
+ {
+ icon: ,
+ nameKey: 'copy-link',
+ name: t['com.affine.peek-view-controls.copy-link'](),
+ onClick: async () => {
+ const preferredMode = docsService.list.getPrimaryMode(docRef.docId);
+ const search = toURLSearchParams({
+ mode: docRef.mode || preferredMode,
+ blockIds: docRef.blockIds,
+ elementIds: docRef.elementIds,
+ xywh: docRef.xywh,
+ });
+ const url = new URL(
+ workbench.basename$.value + '/' + docRef.docId,
+ serverService.server.baseUrl
+ );
+ if (search?.size) url.search = search.toString();
+ await copyTextToClipboard(url.toString());
+ notify.success({ title: t['Copied link to clipboard']() });
+ },
+ },
{
icon: ,
nameKey: 'info',
@@ -149,7 +177,15 @@ export const DocPeekViewControls = ({
},
},
].filter((opt): opt is ControlButtonProps => Boolean(opt));
- }, [t, peekView, workbench, docRef, workspaceDialogService]);
+ }, [
+ t,
+ peekView,
+ workbench,
+ docRef,
+ docsService.list,
+ serverService.server.baseUrl,
+ workspaceDialogService,
+ ]);
return (
{controls.map(option => (
diff --git a/packages/frontend/i18n/src/i18n.gen.ts b/packages/frontend/i18n/src/i18n.gen.ts
index e6bd98b27..8749010cc 100644
--- a/packages/frontend/i18n/src/i18n.gen.ts
+++ b/packages/frontend/i18n/src/i18n.gen.ts
@@ -4379,6 +4379,10 @@ export function useAFFiNEI18N(): {
* `Open in center peek`
*/
["com.affine.peek-view-controls.open-doc-in-center-peek"](): string;
+ /**
+ * `Copy link`
+ */
+ ["com.affine.peek-view-controls.copy-link"](): string;
/**
* `Click or drag`
*/
diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json
index 8573a2ea3..983cda218 100644
--- a/packages/frontend/i18n/src/resources/en.json
+++ b/packages/frontend/i18n/src/resources/en.json
@@ -1086,6 +1086,7 @@
"com.affine.peek-view-controls.open-attachment-in-new-tab": "Open in new tab",
"com.affine.peek-view-controls.open-attachment-in-split-view": "Open in split view",
"com.affine.peek-view-controls.open-doc-in-center-peek": "Open in center peek",
+ "com.affine.peek-view-controls.copy-link": "Copy link",
"com.affine.split-view-drag-handle.tooltip": "Click or drag",
"com.affine.split-view-folder-warning.description": "Split view does not support folders.",
"do-not-show-this-again": "Do not show this again",