diff --git a/blocksuite/affine/components/src/toolbar/icon-button.ts b/blocksuite/affine/components/src/toolbar/icon-button.ts index de8432fde0..720ab7f3d7 100644 --- a/blocksuite/affine/components/src/toolbar/icon-button.ts +++ b/blocksuite/affine/components/src/toolbar/icon-button.ts @@ -57,7 +57,7 @@ export class EditorIconButton extends LitElement { padding: 0 4px; overflow: hidden; white-space: nowrap; - line-height: var(--label-height, inherit); + line-height: var(--label-height, var(--icon-size, 20px)); } ::slotted(.label.padding0) { padding: 0; diff --git a/blocksuite/affine/widgets/widget-toolbar/src/utils.ts b/blocksuite/affine/widgets/widget-toolbar/src/utils.ts index 07f85a0638..1abdbbb033 100644 --- a/blocksuite/affine/widgets/widget-toolbar/src/utils.ts +++ b/blocksuite/affine/widgets/widget-toolbar/src/utils.ts @@ -232,7 +232,7 @@ export function renderToolbar( `${flavour}:${key}`, html` diff --git a/packages/frontend/core/src/blocksuite/extensions/editor-config/index.ts b/packages/frontend/core/src/blocksuite/extensions/editor-config/index.ts index 7945e973bf..4fbeb0ad93 100644 --- a/packages/frontend/core/src/blocksuite/extensions/editor-config/index.ts +++ b/packages/frontend/core/src/blocksuite/extensions/editor-config/index.ts @@ -29,6 +29,6 @@ export function getEditorConfigExtension( }), ToolbarMoreMenuConfigExtension(createToolbarMoreMenuConfig(framework)), - createCustomToolbarExtension(baseUrl), + createCustomToolbarExtension(editorSettingService.editorSetting, baseUrl), ].flat(); } diff --git a/packages/frontend/core/src/blocksuite/extensions/editor-config/toolbar/index.ts b/packages/frontend/core/src/blocksuite/extensions/editor-config/toolbar/index.ts index 409134e583..8ce143c943 100644 --- a/packages/frontend/core/src/blocksuite/extensions/editor-config/toolbar/index.ts +++ b/packages/frontend/core/src/blocksuite/extensions/editor-config/toolbar/index.ts @@ -5,6 +5,7 @@ import { } from '@affine/core/components/hooks/affine/use-share-url'; import { WorkspaceServerService } from '@affine/core/modules/cloud'; import { EditorService } from '@affine/core/modules/editor'; +import type { EditorSettingExt } from '@affine/core/modules/editor-setting/entities/editor-setting'; import { copyLinkToBlockStdScopeClipboard } from '@affine/core/utils/clipboard'; import { I18n, i18nTime } from '@affine/i18n'; import { track } from '@affine/track'; @@ -57,7 +58,6 @@ import { GenerateDocUrlProvider, isRemovedUserInfo, OpenDocExtensionIdentifier, - type OpenDocMode, type ToolbarAction, type ToolbarActionGenerator, type ToolbarActionGroupGenerator, @@ -69,14 +69,11 @@ import { import { matchModels } from '@blocksuite/affine/shared/utils'; import type { ExtensionType } from '@blocksuite/affine/store'; import { - CenterPeekIcon, CopyAsImgaeIcon, CopyIcon, EditIcon, - ExpandFullIcon, LinkIcon, OpenInNewIcon, - SplitViewIcon, } from '@blocksuite/icons/lit'; import { computed } from '@preact/signals-core'; import type { FrameworkProvider } from '@toeverything/infra'; @@ -86,6 +83,7 @@ import { keyed } from 'lit/directives/keyed.js'; import { repeat } from 'lit/directives/repeat.js'; import { styleMap } from 'lit/directives/style-map.js'; +import { openDocActions } from '../../open-doc'; import { createCopyAsPngMenuItem } from './copy-as-image'; export function createToolbarMoreMenuConfig(framework: FrameworkProvider) { @@ -477,39 +475,6 @@ function createExternalLinkableToolbarConfig( } as const satisfies ToolbarModuleConfig; } -const openDocActions = [ - { - mode: 'open-in-active-view', - id: 'a.open-in-active-view', - label: I18n['com.affine.peek-view-controls.open-doc'](), - icon: ExpandFullIcon(), - when: true, - }, - { - mode: 'open-in-new-view', - id: 'b.open-in-new-view', - label: I18n['com.affine.peek-view-controls.open-doc-in-split-view'](), - icon: SplitViewIcon(), - when: BUILD_CONFIG.isElectron, - }, - { - mode: 'open-in-new-tab', - id: 'c.open-in-new-tab', - label: I18n['com.affine.peek-view-controls.open-doc-in-new-tab'](), - icon: OpenInNewIcon(), - when: true, - }, - { - mode: 'open-in-center-peek', - id: 'd.open-in-center-peek', - label: I18n['com.affine.peek-view-controls.open-doc-in-center-peek'](), - icon: CenterPeekIcon(), - when: true, - }, -] as const satisfies (Pick & { - mode: OpenDocMode; -})[]; - function createOpenDocActions( ctx: ToolbarContext, target: @@ -517,7 +482,15 @@ function createOpenDocActions( | EmbedSyncedDocBlockComponent | AffineReference, isSameDoc: boolean, - actions = openDocActions + actions = openDocActions.map( + ({ type: mode, label, icon, enabled: when }, i) => ({ + mode, + id: `${i}.${mode}`, + label, + icon, + when, + }) + ) ) { return actions .filter(action => action.when) @@ -552,7 +525,8 @@ function createOpenDocActions( function createOpenDocActionGroup( klass: | typeof EmbedLinkedDocBlockComponent - | typeof EmbedSyncedDocBlockComponent + | typeof EmbedSyncedDocBlockComponent, + settings: EditorSettingExt ): ToolbarAction { return { placement: ActionPlacement.Start, @@ -562,6 +536,7 @@ function createOpenDocActionGroup( if (!block) return null; return renderOpenDocMenu( + settings, ctx, block, block.model.props.pageId === ctx.store.id @@ -594,6 +569,7 @@ function createEdgelessOpenDocActionGroup( } function renderOpenDocMenu( + settings: EditorSettingExt, ctx: ToolbarContext, target: | EmbedLinkedDocBlockComponent @@ -607,42 +583,64 @@ function renderOpenDocMenu( })); if (!actions.length) return null; - return html` - ${keyed( - target, - html` - - ${OpenInNewIcon()} ${EditorChevronDown} - - `} - > -
- ${repeat( - actions, - action => action.id, - ({ label, icon, run, disabled }) => html` - run?.(ctx)} - > - ${icon}${label} - - ` - )} -
-
- ` - )} - `; + const currentOpenMode = + settings.settingSignal.value.openDocMode ?? 'open-in-active-view'; + const currentIcon = + openDocActions.find(a => a.type === currentOpenMode)?.icon ?? + OpenInNewIcon(); + const currentAction = actions.find(a => a.icon === currentIcon) ?? actions[0]; + + return html`${keyed( + target, + html` + currentAction.run?.(ctx)} + > + ${currentAction.icon} Open + + + ${EditorChevronDown} + + `} + > +
+ ${repeat( + actions, + action => action.id, + ({ label, icon, run, disabled }) => html` + { + run?.(ctx); + settings.openDocMode.set( + openDocActions.find(a => a.icon === icon)?.type ?? + 'open-in-active-view' + ); + }} + > + ${icon}${label} + + ` + )} +
+
+ ` + )}`; } const embedLinkedDocToolbarConfig = { actions: [ - createOpenDocActionGroup(EmbedLinkedDocBlockComponent), { id: 'a.doc-title.after.copy-link-and-edit', actions: [ @@ -724,7 +722,6 @@ const embedLinkedDocToolbarConfig = { const embedSyncedDocToolbarConfig = { actions: [ - createOpenDocActionGroup(EmbedSyncedDocBlockComponent), { placement: ActionPlacement.Start, id: 'B.copy-link-and-edit', @@ -800,20 +797,6 @@ const embedSyncedDocToolbarConfig = { const inlineReferenceToolbarConfig = { actions: [ - { - placement: ActionPlacement.Start, - id: 'A.open-doc', - content(ctx) { - const target = ctx.message$.peek()?.element; - if (!(target instanceof AffineReference)) return null; - - return renderOpenDocMenu( - ctx, - target, - target.referenceInfo.pageId === ctx.store.id - ); - }, - }, { id: 'b.copy-link-and-edit', actions: [ @@ -957,6 +940,7 @@ const embedIframeToolbarConfig = { } as const satisfies ToolbarModuleConfig; export const createCustomToolbarExtension = ( + settings: EditorSettingExt, baseUrl: string ): ExtensionType[] => { return [ @@ -1017,7 +1001,12 @@ export const createCustomToolbarExtension = ( ToolbarModuleExtension({ id: BlockFlavourIdentifier('custom:affine:embed-linked-doc'), - config: embedLinkedDocToolbarConfig, + config: { + actions: [ + embedLinkedDocToolbarConfig.actions, + createOpenDocActionGroup(EmbedLinkedDocBlockComponent, settings), + ].flat(), + }, }), ToolbarModuleExtension({ @@ -1025,6 +1014,7 @@ export const createCustomToolbarExtension = ( config: { actions: [ embedLinkedDocToolbarConfig.actions, + createOpenDocActionGroup(EmbedLinkedDocBlockComponent, settings), createEdgelessOpenDocActionGroup(EmbedLinkedDocBlockComponent), ].flat(), @@ -1034,7 +1024,13 @@ export const createCustomToolbarExtension = ( ToolbarModuleExtension({ id: BlockFlavourIdentifier('custom:affine:embed-synced-doc'), - config: embedSyncedDocToolbarConfig, + config: { + actions: [ + embedSyncedDocToolbarConfig.actions, + createOpenDocActionGroup(EmbedSyncedDocBlockComponent, settings), + createEdgelessOpenDocActionGroup(EmbedSyncedDocBlockComponent), + ].flat(), + }, }), ToolbarModuleExtension({ @@ -1042,6 +1038,7 @@ export const createCustomToolbarExtension = ( config: { actions: [ embedSyncedDocToolbarConfig.actions, + createOpenDocActionGroup(EmbedSyncedDocBlockComponent, settings), createEdgelessOpenDocActionGroup(EmbedSyncedDocBlockComponent), ].flat(), @@ -1051,7 +1048,26 @@ export const createCustomToolbarExtension = ( ToolbarModuleExtension({ id: BlockFlavourIdentifier('custom:affine:reference'), - config: inlineReferenceToolbarConfig, + config: { + actions: [ + { + placement: ActionPlacement.Start, + id: 'A.open-doc', + content(ctx) { + const target = ctx.message$.peek()?.element; + if (!(target instanceof AffineReference)) return null; + + return renderOpenDocMenu( + settings, + ctx, + target, + target.referenceInfo.pageId === ctx.store.id + ); + }, + } as const satisfies ToolbarAction, + inlineReferenceToolbarConfig.actions, + ].flat(), + }, }), ToolbarModuleExtension({ diff --git a/packages/frontend/core/src/blocksuite/extensions/open-doc.ts b/packages/frontend/core/src/blocksuite/extensions/open-doc.ts index 5eb4798397..4db7bd9623 100644 --- a/packages/frontend/core/src/blocksuite/extensions/open-doc.ts +++ b/packages/frontend/core/src/blocksuite/extensions/open-doc.ts @@ -1,3 +1,4 @@ +import { EditorSettingSchema } from '@affine/core/modules/editor-setting'; import { I18n } from '@affine/i18n'; import { type OpenDocConfig, @@ -11,33 +12,45 @@ import { SplitViewIcon, } from '@blocksuite/icons/lit'; +type OpenDocAction = OpenDocConfigItem & { enabled: boolean }; + +export const openDocActions: Array = [ + { + type: 'open-in-active-view', + label: I18n['com.affine.peek-view-controls.open-doc'](), + icon: ExpandFullIcon(), + enabled: true, + }, + { + type: 'open-in-new-view', + label: I18n['com.affine.peek-view-controls.open-doc-in-split-view'](), + icon: SplitViewIcon(), + enabled: BUILD_CONFIG.isElectron, + }, + { + type: 'open-in-new-tab', + label: I18n['com.affine.peek-view-controls.open-doc-in-new-tab'](), + icon: OpenInNewIcon(), + enabled: true, + }, + { + type: 'open-in-center-peek', + label: I18n['com.affine.peek-view-controls.open-doc-in-center-peek'](), + icon: CenterPeekIcon(), + enabled: true, + }, +].filter( + (a): a is OpenDocAction => + a.enabled && EditorSettingSchema.shape.openDocMode.safeParse(a.type).success +); + export function patchOpenDocExtension() { const openDocConfig: OpenDocConfig = { - items: [ - { - type: 'open-in-active-view', - label: I18n['com.affine.peek-view-controls.open-doc'](), - icon: ExpandFullIcon(), - }, - BUILD_CONFIG.isElectron - ? { - type: 'open-in-new-view', - label: - I18n['com.affine.peek-view-controls.open-doc-in-split-view'](), - icon: SplitViewIcon(), - } - : null, - { - type: 'open-in-new-tab', - label: I18n['com.affine.peek-view-controls.open-doc-in-new-tab'](), - icon: OpenInNewIcon(), - }, - { - type: 'open-in-center-peek', - label: I18n['com.affine.peek-view-controls.open-doc-in-center-peek'](), - icon: CenterPeekIcon(), - }, - ].filter((item): item is OpenDocConfigItem => item !== null), + items: openDocActions.map(({ type, label, icon }) => ({ + type, + label, + icon, + })), }; return OpenDocExtension(openDocConfig); } diff --git a/packages/frontend/core/src/modules/editor-setting/schema.ts b/packages/frontend/core/src/modules/editor-setting/schema.ts index b95135194f..4ff375c53a 100644 --- a/packages/frontend/core/src/modules/editor-setting/schema.ts +++ b/packages/frontend/core/src/modules/editor-setting/schema.ts @@ -26,6 +26,14 @@ const AffineEditorSettingSchema = z.object({ edgelessDefaultTheme: z .enum(['specified', 'dark', 'light', 'auto']) .default('specified'), + openDocMode: z + .enum([ + 'open-in-active-view', + 'open-in-new-view', + 'open-in-new-tab', + 'open-in-center-peek', + ]) + .default('open-in-active-view'), }); export const EditorSettingSchema = BSEditorSettingSchema.merge( diff --git a/tests/affine-local/e2e/blocksuite/edgeless/frame.spec.ts b/tests/affine-local/e2e/blocksuite/edgeless/frame.spec.ts index 464ec75bb1..4841a6bb1e 100644 --- a/tests/affine-local/e2e/blocksuite/edgeless/frame.spec.ts +++ b/tests/affine-local/e2e/blocksuite/edgeless/frame.spec.ts @@ -47,7 +47,7 @@ test('should update zindex of element when moving it into frame', async ({ await createEdgelessNoteBlock(page, [500, 500]); await clickView(page, [0, 100]); await clickView(page, [500, 500]); - await toolbar.getByLabel('more-menu').click(); + await toolbar.getByLabel('More menu').click(); await toolbar.getByLabel('Send to Back').click(); await pressEscape(page); diff --git a/tests/affine-local/e2e/blocksuite/outline/outline-viewer.spec.ts b/tests/affine-local/e2e/blocksuite/outline/outline-viewer.spec.ts index be1a869f6c..328c8725c4 100644 --- a/tests/affine-local/e2e/blocksuite/outline/outline-viewer.spec.ts +++ b/tests/affine-local/e2e/blocksuite/outline/outline-viewer.spec.ts @@ -190,8 +190,11 @@ test('outline viewer should be useable in doc peek preview', async ({ const toolbar = page.locator('affine-toolbar-widget editor-toolbar'); await expect(toolbar).toBeVisible(); - await toolbar.getByLabel('Open doc').click(); - await toolbar.getByLabel('Open in center peek').click(); + await toolbar.getByLabel(/^Open doc$/).click(); + await toolbar + .getByLabel('Open doc menu') + .getByLabel('Open in center peek') + .click(); const peekView = page.getByTestId('peek-view-modal'); await expect(peekView).toBeVisible(); diff --git a/tests/affine-local/e2e/links.spec.ts b/tests/affine-local/e2e/links.spec.ts index 0613b11bf8..9f64ed62d7 100644 --- a/tests/affine-local/e2e/links.spec.ts +++ b/tests/affine-local/e2e/links.spec.ts @@ -156,7 +156,7 @@ test('not allowed to switch to embed view when linking to block', async ({ await cardLink.click(); - await toolbar.getByLabel('more-menu').click(); + await toolbar.getByLabel('More menu').click(); await toolbar.getByLabel('Copy link to block').click(); await page.keyboard.press('Enter'); @@ -1035,3 +1035,89 @@ test.describe('Customize linked doc title and description', () => { await expect(a.locator('.affine-reference-title')).toContainText(year); }); }); + +test('should save open doc mode of internal links', async ({ page }) => { + await waitForEditorLoad(page); + await enableEmojiDocIcon(page); + + await clickNewPageButton(page); + const title = getBlockSuiteEditorTitle(page); + await title.click(); + + await page.keyboard.press('Enter'); + await createLinkedPage(page, 'Test Page'); + + const { toolbar, switchViewBtn, inlineViewBtn, cardViewBtn, embedViewBtn } = + toolbarButtons(page); + + const inlineLink = page.locator('affine-reference'); + await inlineLink.hover(); + + const recentOpenModeBtn = toolbar.getByLabel(/^Open/).nth(0); + await expect(recentOpenModeBtn).toHaveAttribute( + 'aria-label', + 'Open this doc' + ); + await expect( + recentOpenModeBtn.locator('span.label:has-text("Open")') + ).toBeVisible(); + + const openDocBtn = toolbar.getByLabel(/^Open doc$/); + await openDocBtn.click(); + + const openDocMenu = toolbar.getByLabel('Open doc menu'); + + const openThisDocBtn = openDocMenu.getByLabel('Open this doc'); + // In Desktop + const openInSplitViewBtn = openDocMenu.getByLabel('Open in split view'); + const openInNewTabBtn = openDocMenu.getByLabel('Open in new tab'); + const openInCenterPeekBtn = openDocMenu.getByLabel('Open in center peek'); + + await expect(openThisDocBtn).toBeVisible(); + await expect(openInSplitViewBtn).toBeHidden(); + await expect(openInNewTabBtn).toBeVisible(); + await expect(openInCenterPeekBtn).toBeVisible(); + + await openInCenterPeekBtn.click(); + + await page.keyboard.press('Escape'); + + await inlineLink.hover(); + + await expect(toolbar).toBeVisible(); + await expect(recentOpenModeBtn).toHaveAttribute( + 'aria-label', + 'Open in center peek' + ); + + await switchViewBtn.click(); + await cardViewBtn.click(); + + await expect(toolbar).toBeVisible(); + await expect(recentOpenModeBtn).toHaveAttribute( + 'aria-label', + 'Open in center peek' + ); + + await switchViewBtn.click(); + await embedViewBtn.click(); + + await expect(toolbar).toBeVisible(); + await expect(recentOpenModeBtn).toHaveAttribute( + 'aria-label', + 'Open in center peek' + ); + + await switchViewBtn.click(); + await inlineViewBtn.click(); + + await inlineLink.hover(); + + await page.waitForTimeout(250); + + await expect(toolbar).toBeVisible(); + await expect(recentOpenModeBtn).toHaveAttribute( + 'aria-label', + 'Open in center peek' + ); +}); diff --git a/tests/affine-local/e2e/peek-view.spec.ts b/tests/affine-local/e2e/peek-view.spec.ts index f2774dc2ab..683216f1a6 100644 --- a/tests/affine-local/e2e/peek-view.spec.ts +++ b/tests/affine-local/e2e/peek-view.spec.ts @@ -26,9 +26,11 @@ test('can open peek view via link popover', async ({ page }) => { const toolbar = page.locator('affine-toolbar-widget editor-toolbar'); await expect(toolbar).toBeVisible(); - // click more button - await toolbar.getByLabel('Open doc').click(); - await toolbar.getByLabel('Open in center peek').click(); + await toolbar.getByLabel(/^Open doc$/).click(); + await toolbar + .getByLabel('Open doc menu') + .getByLabel('Open in center peek') + .click(); // verify peek view is opened await expect(page.getByTestId('peek-view-modal')).toBeVisible(); diff --git a/tests/blocksuite/e2e/edgeless/frame/clipboard.spec.ts b/tests/blocksuite/e2e/edgeless/frame/clipboard.spec.ts index 67a088e420..e6dc0e64de 100644 --- a/tests/blocksuite/e2e/edgeless/frame/clipboard.spec.ts +++ b/tests/blocksuite/e2e/edgeless/frame/clipboard.spec.ts @@ -108,7 +108,7 @@ test.describe('frame copy and paste', () => { await frameTitles.nth(0).click(); - const moreMenu = page.getByLabel('more-menu'); + const moreMenu = page.getByLabel('More menu'); await moreMenu.click(); await moreMenu diff --git a/tests/blocksuite/e2e/edgeless/note/note.spec.ts b/tests/blocksuite/e2e/edgeless/note/note.spec.ts index a53da1fa9f..aa91278864 100644 --- a/tests/blocksuite/e2e/edgeless/note/note.spec.ts +++ b/tests/blocksuite/e2e/edgeless/note/note.spec.ts @@ -262,7 +262,7 @@ test('duplicate note should work correctly', async ({ page }) => { await triggerComponentToolbarAction(page, 'duplicate'); await waitNextFrame(page, 200); // wait viewport fit animation - const moreActionsContainer = page.getByLabel('more-menu').getByRole('menu'); + const moreActionsContainer = page.getByLabel('More menu').getByRole('menu'); await expect(moreActionsContainer).toBeHidden(); const noteLocator = page.locator('affine-edgeless-note'); diff --git a/tests/blocksuite/e2e/utils/actions/edgeless.ts b/tests/blocksuite/e2e/utils/actions/edgeless.ts index aa2ee2b1bf..3e699f053a 100644 --- a/tests/blocksuite/e2e/utils/actions/edgeless.ts +++ b/tests/blocksuite/e2e/utils/actions/edgeless.ts @@ -797,7 +797,7 @@ export async function updateExistedBrushElementSize( export async function openComponentToolbarMoreMenu(page: Page) { const btn = page .locator('affine-toolbar-widget editor-toolbar') - .getByLabel('more-menu'); + .getByLabel('More menu'); await btn.click(); } @@ -1023,7 +1023,7 @@ export function locatorComponentToolbar(page: Page) { } export function locatorComponentToolbarMoreButton(page: Page) { - const moreButton = locatorComponentToolbar(page).getByLabel('more-menu'); + const moreButton = locatorComponentToolbar(page).getByLabel('More menu'); return moreButton; } type Action =