From 1a8f8496939d741f8bc95621f4fb15798883c0de Mon Sep 17 00:00:00 2001 From: JimmFly Date: Fri, 11 Aug 2023 21:58:59 +0800 Subject: [PATCH] feat: add duplicate feature (#3675) Co-authored-by: Alex Yang --- .../header-right-items/editor-option-menu.tsx | 44 ++++++++++++++++--- .../components/block-suite-editor/index.tsx | 2 +- tests/affine-local/e2e/duplicate-page.spec.ts | 22 ++++++++++ 3 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 tests/affine-local/e2e/duplicate-page.spec.ts diff --git a/apps/core/src/components/blocksuite/workspace-header/header-right-items/editor-option-menu.tsx b/apps/core/src/components/blocksuite/workspace-header/header-right-items/editor-option-menu.tsx index 04d0e7640..9e95f1d86 100644 --- a/apps/core/src/components/blocksuite/workspace-header/header-right-items/editor-option-menu.tsx +++ b/apps/core/src/components/blocksuite/workspace-header/header-right-items/editor-option-menu.tsx @@ -4,6 +4,7 @@ import { Export, MoveToTrash } from '@affine/component/page-list'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { assertExists } from '@blocksuite/global/utils'; import { + DuplicateIcon, EdgelessIcon, EditIcon, FavoritedIcon, @@ -18,14 +19,17 @@ import { useBlockSuitePageMeta, usePageMetaHelper, } from '@toeverything/hooks/use-block-suite-page-meta'; +import { useBlockSuiteWorkspaceHelper } from '@toeverything/hooks/use-block-suite-workspace-helper'; import { currentPageIdAtom } from '@toeverything/infra/atom'; -import { useAtom, useAtomValue } from 'jotai'; +import { useAtom, useAtomValue, useSetAtom } from 'jotai'; import { useCallback, useState } from 'react'; import { useParams } from 'react-router-dom'; +import * as Y from 'yjs'; -import { pageSettingFamily } from '../../../../atoms'; +import { pageSettingFamily, setPageModeAtom } from '../../../../atoms'; import { useBlockSuiteMetaHelper } from '../../../../hooks/affine/use-block-suite-meta-helper'; import { useCurrentWorkspace } from '../../../../hooks/current/use-current-workspace'; +import { useNavigateHelper } from '../../../../hooks/use-navigate-helper'; import { toast } from '../../../../utils'; import { HeaderDropDownButton } from '../../../pure/header-drop-down-button'; import { usePageHelper } from '../../block-suite-page-list/utils'; @@ -78,7 +82,7 @@ export const PageMenu = ({ rename }: PageMenuProps) => { const mode = setting?.mode ?? 'page'; const favorite = pageMeta.favorite ?? false; - const { setPageMeta } = usePageMetaHelper(blockSuiteWorkspace); + const { setPageMeta, setPageTitle } = usePageMetaHelper(blockSuiteWorkspace); const [openConfirm, setOpenConfirm] = useState(false); const { removeToTrash } = useBlockSuiteMetaHelper(blockSuiteWorkspace); const { importFile } = usePageHelper(blockSuiteWorkspace); @@ -104,6 +108,34 @@ export const PageMenu = ({ rename }: PageMenuProps) => { const menuItemStyle = { padding: '4px 12px', }; + const { openPage } = useNavigateHelper(); + const { createPage } = useBlockSuiteWorkspaceHelper(blockSuiteWorkspace); + const setPageMode = useSetAtom(setPageModeAtom); + const duplicate = useCallback(async () => { + const currentPage = blockSuiteWorkspace.getPage(pageId); + assertExists(currentPage); + const currentPageMeta = currentPage.meta; + const newPage = createPage(); + await newPage.waitForLoaded(); + const update = Y.encodeStateAsUpdate(currentPage.spaceDoc); + Y.applyUpdate(newPage.spaceDoc, update); + setPageMeta(newPage.id, { + tags: currentPageMeta.tags, + favorite: currentPageMeta.favorite, + }); + setPageMode(newPage.id, mode); + setPageTitle(newPage.id, `${currentPageMeta.title}(1)`); + openPage(blockSuiteWorkspace.id, newPage.id); + }, [ + blockSuiteWorkspace, + createPage, + mode, + openPage, + pageId, + setPageMeta, + setPageMode, + setPageTitle, + ]); const EditMenu = ( <> { {t['com.affine.header.option.add-tag']()} */} - {/* } data-testid="editor-option-menu-duplicate" - onClick={() => {}} + onClick={duplicate} style={menuItemStyle} > {t['com.affine.header.option.duplicate']()} - */} + } data-testid="editor-option-menu-import" diff --git a/packages/component/src/components/block-suite-editor/index.tsx b/packages/component/src/components/block-suite-editor/index.tsx index b30be9edf..40f3597e1 100644 --- a/packages/component/src/components/block-suite-editor/index.tsx +++ b/packages/component/src/components/block-suite-editor/index.tsx @@ -184,7 +184,7 @@ export const BlockSuiteEditor = memo(function BlockSuiteEditor( )} > }> - + ); diff --git a/tests/affine-local/e2e/duplicate-page.spec.ts b/tests/affine-local/e2e/duplicate-page.spec.ts new file mode 100644 index 000000000..cf592cd18 --- /dev/null +++ b/tests/affine-local/e2e/duplicate-page.spec.ts @@ -0,0 +1,22 @@ +import { test } from '@affine-test/kit/playwright'; +import { openHomePage } from '@affine-test/kit/utils/load-page'; +import { + clickPageMoreActions, + getBlockSuiteEditorTitle, + newPage, + waitEditorLoad, +} from '@affine-test/kit/utils/page-logic'; +import { expect } from '@playwright/test'; + +test('Duplicate page should work', async ({ page }) => { + await openHomePage(page); + await waitEditorLoad(page); + await newPage(page); + const title = getBlockSuiteEditorTitle(page); + await title.type('test'); + await clickPageMoreActions(page); + const duplicateButton = page.getByTestId('editor-option-menu-duplicate'); + await duplicateButton.click({ delay: 100 }); + const title2 = getBlockSuiteEditorTitle(page); + expect(await title2.innerText()).toBe('test(1)'); +});