diff --git a/blocksuite/affine/block-embed/src/effects.ts b/blocksuite/affine/block-embed/src/effects.ts index c654f0fe45..c419b313af 100644 --- a/blocksuite/affine/block-embed/src/effects.ts +++ b/blocksuite/affine/block-embed/src/effects.ts @@ -17,7 +17,6 @@ import type { insertEmbedLinkedDocCommand, } from './embed-linked-doc-block/commands/insert-embed-linked-doc'; import { EmbedEdgelessLinkedDocBlockComponent } from './embed-linked-doc-block/embed-edgeless-linked-doc-block'; -import type { EmbedLinkedDocBlockConfig } from './embed-linked-doc-block/embed-linked-doc-config'; import { EmbedLoomBlockComponent, type EmbedLoomBlockService, @@ -123,9 +122,7 @@ declare global { interface CommandContext { insertedLinkType?: Promise; } - interface BlockConfigs { - 'affine:embed-linked-doc': EmbedLinkedDocBlockConfig; - } + interface Commands { insertEmbedLinkedDoc: typeof insertEmbedLinkedDocCommand; } diff --git a/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-edgeless-linked-doc-block.ts b/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-edgeless-linked-doc-block.ts index 154b17fd27..d564088d9d 100644 --- a/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-edgeless-linked-doc-block.ts +++ b/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-edgeless-linked-doc-block.ts @@ -4,7 +4,11 @@ import { EMBED_CARD_WIDTH, } from '@blocksuite/affine-shared/consts'; import { FeatureFlagService } from '@blocksuite/affine-shared/services'; -import { cloneReferenceInfoWithoutAliases } from '@blocksuite/affine-shared/utils'; +import { + cloneReferenceInfoWithoutAliases, + isNewTabTrigger, + isNewViewTrigger, +} from '@blocksuite/affine-shared/utils'; import { Bound } from '@blocksuite/global/utils'; import { toEdgelessEmbedBlock } from '../common/to-edgeless-embed-block.js'; @@ -65,9 +69,10 @@ export class EmbedEdgelessLinkedDocBlockComponent extends toEdgelessEmbedBlock( } protected override _handleClick(evt: MouseEvent): void { - if (this.config.handleClick) { - this.config.handleClick(evt, this.host, this.referenceInfo$.peek()); - return; + if (isNewTabTrigger(evt)) { + this.open({ openMode: 'open-in-new-tab', event: evt }); + } else if (isNewViewTrigger(evt)) { + this.open({ openMode: 'open-in-new-view', event: evt }); } } } diff --git a/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts b/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts index 9c4a178671..1b5b6d6508 100644 --- a/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts +++ b/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts @@ -14,11 +14,15 @@ import { DocDisplayMetaProvider, DocModeProvider, FeatureFlagService, + OpenDocExtensionIdentifier, + type OpenDocMode, ThemeProvider, } from '@blocksuite/affine-shared/services'; import { cloneReferenceInfo, cloneReferenceInfoWithoutAliases, + isNewTabTrigger, + isNewViewTrigger, matchFlavours, referenceToNode, } from '@blocksuite/affine-shared/utils'; @@ -39,10 +43,6 @@ import { renderLinkedDocInCard, } from '../common/render-linked-doc.js'; import { SyncedDocErrorIcon } from '../embed-synced-doc-block/styles.js'; -import { - type EmbedLinkedDocBlockConfig, - EmbedLinkedDocBlockConfigIdentifier, -} from './embed-linked-doc-config.js'; import { styles } from './styles.js'; import { getEmbedLinkedDocIcons } from './utils.js'; @@ -205,10 +205,18 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent { - this.std - .getOptional(RefNodeSlotsProvider) - ?.docLinkClicked.emit(this.referenceInfo$.peek()); + open = ({ + openMode, + event, + }: { + openMode?: OpenDocMode; + event?: MouseEvent; + } = {}) => { + this.std.getOptional(RefNodeSlotsProvider)?.docLinkClicked.emit({ + ...this.referenceInfo$.peek(), + openMode, + event, + }); }; refreshData = () => { @@ -228,12 +236,6 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent void; - handleDoubleClick?: ( - e: MouseEvent, - host: EditorHost, - referenceInfo: ReferenceInfo - ) => void; -} - -export const EmbedLinkedDocBlockConfigIdentifier = - createIdentifier('EmbedLinkedDocBlockConfig'); - -export function EmbedLinkedDocBlockConfigExtension( - config: EmbedLinkedDocBlockConfig -): ExtensionType { - return { - setup: di => { - di.addImpl(EmbedLinkedDocBlockConfigIdentifier, () => config); - }, - }; -} diff --git a/blocksuite/affine/block-embed/src/embed-linked-doc-block/index.ts b/blocksuite/affine/block-embed/src/embed-linked-doc-block/index.ts index 5216305109..70e4756bd3 100644 --- a/blocksuite/affine/block-embed/src/embed-linked-doc-block/index.ts +++ b/blocksuite/affine/block-embed/src/embed-linked-doc-block/index.ts @@ -1,5 +1,4 @@ export * from './adapters'; export type { InsertedLinkType } from './commands'; export * from './embed-linked-doc-block'; -export * from './embed-linked-doc-config'; export * from './embed-linked-doc-spec'; diff --git a/blocksuite/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-block.ts b/blocksuite/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-block.ts index 636a2f5f07..7ab4bf9db8 100644 --- a/blocksuite/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-block.ts +++ b/blocksuite/affine/block-embed/src/embed-synced-doc-block/embed-synced-doc-block.ts @@ -1,5 +1,8 @@ import { Peekable } from '@blocksuite/affine-components/peek'; -import { RefNodeSlotsProvider } from '@blocksuite/affine-components/rich-text'; +import { + type DocLinkClickedEvent, + RefNodeSlotsProvider, +} from '@blocksuite/affine-components/rich-text'; import { type AliasInfo, type DocMode, @@ -305,11 +308,13 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent { + open = (event?: Partial) => { const pageId = this.model.pageId; if (pageId === this.doc.id) return; - this.std.getOptional(RefNodeSlotsProvider)?.docLinkClicked.emit({ pageId }); + this.std + .getOptional(RefNodeSlotsProvider) + ?.docLinkClicked.emit({ ...event, pageId }); }; refreshData = () => { diff --git a/blocksuite/affine/components/src/rich-text/inline/presets/nodes/index.ts b/blocksuite/affine/components/src/rich-text/inline/presets/nodes/index.ts index 131689ad5e..ece36cc15c 100644 --- a/blocksuite/affine/components/src/rich-text/inline/presets/nodes/index.ts +++ b/blocksuite/affine/components/src/rich-text/inline/presets/nodes/index.ts @@ -1,4 +1,7 @@ export { AffineLink, toggleLinkPopup } from './link-node/index.js'; export * from './reference-node/reference-config.js'; export { AffineReference } from './reference-node/reference-node.js'; -export type { RefNodeSlots } from './reference-node/types.js'; +export type { + DocLinkClickedEvent, + RefNodeSlots, +} from './reference-node/types.js'; diff --git a/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-popup.ts b/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-popup.ts index 8c8a6dbdc9..17743be8d0 100644 --- a/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-popup.ts +++ b/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-popup.ts @@ -3,6 +3,8 @@ import { FeatureFlagService, GenerateDocUrlProvider, type LinkEventType, + OpenDocExtensionIdentifier, + type OpenDocMode, type TelemetryEvent, TelemetryProvider, } from '@blocksuite/affine-shared/services'; @@ -26,11 +28,9 @@ import { join } from 'lit/directives/join.js'; import { repeat } from 'lit/directives/repeat.js'; import { - CenterPeekIcon, CopyIcon, DeleteIcon, EditIcon, - ExpandFullSmallIcon, MoreVerticalIcon, OpenIcon, SmallArrowDownIcon, @@ -47,6 +47,7 @@ import { RefNodeSlotsProvider } from '../../../../extension/index.js'; import type { AffineInlineEditor } from '../../affine-inline-specs.js'; import { ReferenceAliasPopup } from './reference-alias-popup.js'; import { styles } from './styles.js'; +import type { DocLinkClickedEvent } from './types.js'; export class ReferencePopup extends WithDisposable(LitElement) { static override styles = styles; @@ -66,10 +67,11 @@ export class ReferencePopup extends WithDisposable(LitElement) { track(this.std, 'CopiedLink', { control: 'copy link' }); }; - private readonly _openDoc = () => { - this.std - .getOptional(RefNodeSlotsProvider) - ?.docLinkClicked.emit(this.referenceInfo); + private readonly _openDoc = (event?: Partial) => { + this.std.getOptional(RefNodeSlotsProvider)?.docLinkClicked.emit({ + ...this.referenceInfo, + ...event, + }); }; private readonly _openEditPopup = (e: MouseEvent) => { @@ -134,8 +136,11 @@ export class ReferencePopup extends WithDisposable(LitElement) { ); } - get _openButtonDisabled() { - return this.referenceDocId === this.doc.id; + _openButtonDisabled(openMode?: OpenDocMode) { + if (openMode === 'open-in-active-view') { + return this.referenceDocId === this.doc.id; + } + return false; } get block() { @@ -246,28 +251,37 @@ export class ReferencePopup extends WithDisposable(LitElement) { } private _openMenuButton() { - const buttons: MenuItem[] = [ - { - label: 'Open this doc', - type: 'open-this-doc', - icon: ExpandFullSmallIcon, - action: this._openDoc, - disabled: this._openButtonDisabled, - }, - ]; + const openDocConfig = this.std.get(OpenDocExtensionIdentifier); - // open in new tab - - if (isPeekable(this.target)) { - buttons.push({ - label: 'Open in center peek', - type: 'open-in-center-peek', - icon: CenterPeekIcon, - action: () => peek(this.target), - }); - } - - // open in split view + const buttons: MenuItem[] = openDocConfig.items + .map(item => { + if ( + (item.type === 'open-in-center-peek' && !isPeekable(this.target)) || + !openDocConfig?.isAllowed(item.type) + ) { + return null; + } + return { + label: item.label, + type: item.type, + icon: item.icon, + action: () => { + if (item.type === 'open-in-center-peek') { + peek(this.target); + } else { + this._openDoc({ openMode: item.type }); + } + }, + disabled: this._openButtonDisabled(item.type), + when: () => { + if (item.type === 'open-in-center-peek') { + return isPeekable(this.target); + } + return openDocConfig?.isAllowed(item.type) ?? true; + }, + }; + }) + .filter(item => item !== null); if (buttons.length === 0) { return nothing; diff --git a/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/types.ts b/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/types.ts index d5e4501d9d..15f3beb022 100644 --- a/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/types.ts +++ b/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/types.ts @@ -1,6 +1,13 @@ import type { ReferenceInfo } from '@blocksuite/affine-model'; +import type { OpenDocMode } from '@blocksuite/affine-shared/services'; import type { Slot } from '@blocksuite/global/utils'; -export type RefNodeSlots = { - docLinkClicked: Slot; +export type DocLinkClickedEvent = ReferenceInfo & { + // default is active view + openMode?: OpenDocMode; + event?: MouseEvent; +}; + +export type RefNodeSlots = { + docLinkClicked: Slot; }; diff --git a/blocksuite/affine/components/src/toolbar/menu-button.ts b/blocksuite/affine/components/src/toolbar/menu-button.ts index 2d70f92978..4edb3c369f 100644 --- a/blocksuite/affine/components/src/toolbar/menu-button.ts +++ b/blocksuite/affine/components/src/toolbar/menu-button.ts @@ -204,6 +204,7 @@ export class EditorMenuAction extends LitElement { ::slotted(svg) { color: var(--affine-icon-color); + font-size: 20px; } `; diff --git a/blocksuite/affine/shared/src/services/index.ts b/blocksuite/affine/shared/src/services/index.ts index 84e69b2e3f..340679bad7 100644 --- a/blocksuite/affine/shared/src/services/index.ts +++ b/blocksuite/affine/shared/src/services/index.ts @@ -9,6 +9,7 @@ export * from './font-loader'; export * from './generate-url-service'; export * from './native-clipboard-service'; export * from './notification-service'; +export * from './open-doc-config'; export * from './page-viewport-service'; export * from './parse-url-service'; export * from './quick-search-service'; diff --git a/blocksuite/affine/shared/src/services/open-doc-config.ts b/blocksuite/affine/shared/src/services/open-doc-config.ts new file mode 100644 index 0000000000..d34bc7b6ac --- /dev/null +++ b/blocksuite/affine/shared/src/services/open-doc-config.ts @@ -0,0 +1,59 @@ +import { createIdentifier } from '@blocksuite/global/di'; +import { CenterPeekIcon, ExpandFullIcon } from '@blocksuite/icons/lit'; +import { type ExtensionType } from '@blocksuite/store'; +import type { TemplateResult } from 'lit'; + +export type OpenDocMode = + | 'open-in-active-view' + | 'open-in-new-view' + | 'open-in-new-tab' + | 'open-in-center-peek'; + +// todo: later this will be used to generate the menu items. +// for now we only use it as a hint for whether or not to show the open doc buttons. +export interface OpenDocConfigItem { + type: OpenDocMode; + label: string; + icon: TemplateResult<1>; +} +export interface OpenDocConfig { + items: OpenDocConfigItem[]; +} + +export interface OpenDocService { + isAllowed: (mode: OpenDocMode) => boolean; + items: OpenDocConfig['items']; +} + +export const OpenDocExtensionIdentifier = createIdentifier( + 'AffineOpenDocExtension' +); + +const defaultConfig: OpenDocConfig = { + items: [ + { + type: 'open-in-active-view', + label: 'Open this doc', + icon: ExpandFullIcon(), + }, + { + type: 'open-in-center-peek', + label: 'Open in center peek', + icon: CenterPeekIcon(), + }, + ], +}; + +export const OpenDocExtension = (config: OpenDocConfig): ExtensionType => ({ + setup: di => { + di.override(OpenDocExtensionIdentifier, () => { + const allowedOpenDocModes = new Set(config.items.map(item => item.type)); + return { + isAllowed: (mode: OpenDocMode) => allowedOpenDocModes.has(mode), + items: config.items, + }; + }); + }, +}); + +export const DefaultOpenDocExtension = OpenDocExtension(defaultConfig); diff --git a/blocksuite/affine/shared/src/utils/event.ts b/blocksuite/affine/shared/src/utils/event.ts index daa5a6e395..ce18c5f289 100644 --- a/blocksuite/affine/shared/src/utils/event.ts +++ b/blocksuite/affine/shared/src/utils/event.ts @@ -44,6 +44,16 @@ export function isControlledKeyboardEvent(e: KeyboardEvent) { return e.ctrlKey || e.metaKey || e.altKey; } +export function isNewTabTrigger(event?: MouseEvent) { + return event + ? (event.ctrlKey || event.metaKey || event.button === 1) && !event.altKey + : false; +} + +export function isNewViewTrigger(event?: MouseEvent) { + return event ? (event.ctrlKey || event.metaKey) && event.altKey : false; +} + export function on< T extends HTMLElement, K extends keyof M, diff --git a/blocksuite/blocks/src/_specs/common.ts b/blocksuite/blocks/src/_specs/common.ts index c278de58d8..a814060a4a 100644 --- a/blocksuite/blocks/src/_specs/common.ts +++ b/blocksuite/blocks/src/_specs/common.ts @@ -28,6 +28,7 @@ import { RichTextExtensions, } from '@blocksuite/affine-components/rich-text'; import { + DefaultOpenDocExtension, DocDisplayMetaService, EditPropsStore, FeatureFlagService, @@ -54,6 +55,7 @@ export const CommonBlockSpecs: ExtensionType[] = [ CodeBlockSpec, ImageBlockSpec, ParagraphBlockSpec, + DefaultOpenDocExtension, ].flat(); export const PageFirstPartyBlockSpecs: ExtensionType[] = [ diff --git a/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-embed-card-button.ts b/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-embed-card-button.ts index a6d42f4a45..7f280c27b8 100644 --- a/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-embed-card-button.ts +++ b/blocksuite/blocks/src/root-block/widgets/element-toolbar/change-embed-card-button.ts @@ -13,7 +13,6 @@ import { import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface'; import { CaptionIcon, - CenterPeekIcon, CopyIcon, EditIcon, ExpandFullSmallIcon, @@ -44,6 +43,8 @@ import { GenerateDocUrlProvider, type GenerateDocUrlService, type LinkEventType, + OpenDocExtensionIdentifier, + type OpenDocMode, type TelemetryEvent, TelemetryProvider, ThemeProvider, @@ -266,8 +267,8 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { return bound.h / EMBED_CARD_HEIGHT[this.model.style]; }; - private readonly _open = () => { - this._blockComponent?.open(); + private readonly _open = ({ openMode }: { openMode?: OpenDocMode } = {}) => { + this._blockComponent?.open({ openMode }); }; private readonly _openEditPopup = (e: MouseEvent) => { @@ -493,12 +494,6 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { ); } - get _openButtonDisabled() { - return ( - isEmbedLinkedDocBlock(this.model) && this.model.pageId === this._doc.id - ); - } - get _originalDocInfo(): AliasInfo | undefined { const model = this.model; const doc = isInternalEmbedModel(model) @@ -543,20 +538,46 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { } private _openMenuButton() { - const buttons: MenuItem[] = []; + const openDocConfig = this.std.get(OpenDocExtensionIdentifier); + const buttons: MenuItem[] = openDocConfig.items + .map(item => { + if ( + item.type === 'open-in-center-peek' && + this._blockComponent && + !isPeekable(this._blockComponent) + ) { + return null; + } - if ( - isEmbedLinkedDocBlock(this.model) || - isEmbedSyncedDocBlock(this.model) - ) { - buttons.push({ - type: 'open-this-doc', - label: 'Open this doc', - icon: ExpandFullSmallIcon, - action: this._open, - disabled: this._openButtonDisabled, - }); - } else if (this._canShowFullScreenButton) { + if ( + !( + isEmbedLinkedDocBlock(this.model) || + isEmbedSyncedDocBlock(this.model) + ) + ) { + return null; + } + + return { + label: item.label, + type: item.type, + icon: item.icon, + disabled: + this.model.pageId === this._doc.id && + item.type === 'open-in-active-view', + action: () => { + if (item.type === 'open-in-center-peek') { + this._peek(); + } else { + this._open({ openMode: item.type }); + } + }, + }; + }) + .filter(item => item !== null); + + // todo: abstract this? + if (this._canShowFullScreenButton) { buttons.push({ type: 'open-this-doc', label: 'Open this doc', @@ -565,19 +586,6 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) { }); } - // open in new tab - - if (this._blockComponent && isPeekable(this._blockComponent)) { - buttons.push({ - type: 'open-in-center-peek', - label: 'Open in center peek', - icon: CenterPeekIcon, - action: () => this._peek(), - }); - } - - // open in split view - if (buttons.length === 0) { return nothing; } diff --git a/blocksuite/blocks/src/root-block/widgets/element-toolbar/more-menu/config.ts b/blocksuite/blocks/src/root-block/widgets/element-toolbar/more-menu/config.ts index 065994e317..cd41553480 100644 --- a/blocksuite/blocks/src/root-block/widgets/element-toolbar/more-menu/config.ts +++ b/blocksuite/blocks/src/root-block/widgets/element-toolbar/more-menu/config.ts @@ -12,7 +12,10 @@ import type { ImageBlockComponent } from '@blocksuite/affine-block-image'; import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface'; import { isPeekable, peek } from '@blocksuite/affine-components/peek'; import type { MenuItemGroup } from '@blocksuite/affine-components/toolbar'; -import { TelemetryProvider } from '@blocksuite/affine-shared/services'; +import { + OpenDocExtensionIdentifier, + TelemetryProvider, +} from '@blocksuite/affine-shared/services'; import { Bound, getCommonBoundWithRotation } from '@blocksuite/global/utils'; import { ArrowDownBigBottomIcon, @@ -23,11 +26,13 @@ import { CopyIcon, DeleteIcon, DuplicateIcon, + ExpandFullIcon, FrameIcon, GroupIcon, LinkedPageIcon, OpenInNewIcon, ResetIcon, + SplitViewIcon, } from '@blocksuite/icons/lit'; import { duplicate } from '../../../edgeless/utils/clipboard-utils.js'; @@ -135,11 +140,12 @@ export const reorderGroup: MenuItemGroup = { }; // Open Group +// TODO: construct this group dynamically export const openGroup: MenuItemGroup = { type: 'open', items: [ { - icon: OpenInNewIcon({ width: '20', height: '20' }), + icon: ExpandFullIcon({ width: '20', height: '20' }), label: 'Open this doc', type: 'open', generate: ctx => { @@ -163,6 +169,62 @@ export const openGroup: MenuItemGroup = { disabled, }; }, + when: ctx => { + const openDocService = ctx.std.get(OpenDocExtensionIdentifier); + return openDocService.isAllowed('open-in-active-view'); + }, + }, + { + icon: SplitViewIcon({ width: '20', height: '20' }), + label: 'Open in split view', + type: 'open-in-split-view', + generate: ctx => { + const linkedDocBlock = ctx.getLinkedDocBlock(); + + if (!linkedDocBlock) return; + + return { + action: () => { + const blockComponent = ctx.firstBlockComponent; + + if (!blockComponent) return; + if (!('open' in blockComponent)) return; + if (typeof blockComponent.open !== 'function') return; + + blockComponent.open({ openMode: 'open-in-new-view' }); + }, + }; + }, + when: ctx => { + const openDocService = ctx.std.get(OpenDocExtensionIdentifier); + return openDocService.isAllowed('open-in-new-view'); + }, + }, + { + icon: OpenInNewIcon({ width: '20', height: '20' }), + label: 'Open in new tab', + type: 'open-in-new-tab', + generate: ctx => { + const linkedDocBlock = ctx.getLinkedDocBlock(); + + if (!linkedDocBlock) return; + + return { + action: () => { + const blockComponent = ctx.firstBlockComponent; + + if (!blockComponent) return; + if (!('open' in blockComponent)) return; + if (typeof blockComponent.open !== 'function') return; + + blockComponent.open({ openMode: 'open-in-new-tab' }); + }, + }; + }, + when: ctx => { + const openDocService = ctx.std.get(OpenDocExtensionIdentifier); + return openDocService.isAllowed('open-in-new-tab'); + }, }, { icon: CenterPeekIcon({ width: '20', height: '20' }), @@ -184,6 +246,10 @@ export const openGroup: MenuItemGroup = { }, }; }, + when: ctx => { + const openDocService = ctx.std.get(OpenDocExtensionIdentifier); + return openDocService.isAllowed('open-in-center-peek'); + }, }, ], }; diff --git a/blocksuite/blocks/src/root-block/widgets/embed-card-toolbar/embed-card-toolbar.ts b/blocksuite/blocks/src/root-block/widgets/embed-card-toolbar/embed-card-toolbar.ts index b4c8c5dc95..d11fff94fe 100644 --- a/blocksuite/blocks/src/root-block/widgets/embed-card-toolbar/embed-card-toolbar.ts +++ b/blocksuite/blocks/src/root-block/widgets/embed-card-toolbar/embed-card-toolbar.ts @@ -12,10 +12,8 @@ import { } from '@blocksuite/affine-block-embed'; import { CaptionIcon, - CenterPeekIcon, CopyIcon, EditIcon, - ExpandFullSmallIcon, MoreVerticalIcon, OpenIcon, PaletteIcon, @@ -48,6 +46,7 @@ import { GenerateDocUrlProvider, type GenerateDocUrlService, type LinkEventType, + OpenDocExtensionIdentifier, type TelemetryEvent, TelemetryProvider, ThemeProvider, @@ -503,34 +502,42 @@ export class EmbedCardToolbar extends WidgetComponent< } private _openMenuButton() { - const buttons: MenuItem[] = []; - - if ( - this.focusModel && - (isEmbedLinkedDocBlock(this.focusModel) || - isEmbedSyncedDocBlock(this.focusModel)) - ) { - buttons.push({ - type: 'open-this-doc', - label: 'Open this doc', - icon: ExpandFullSmallIcon, - action: () => this.focusBlock?.open(), - }); - } - - // open in new tab - + const openDocConfig = this.std.get(OpenDocExtensionIdentifier); const element = this.focusBlock; - if (element && isPeekable(element)) { - buttons.push({ - type: 'open-in-center-peek', - label: 'Open in center peek', - icon: CenterPeekIcon, - action: () => peek(element), - }); - } + const buttons: MenuItem[] = openDocConfig.items + .map(item => { + if ( + item.type === 'open-in-center-peek' && + element && + !isPeekable(element) + ) { + return null; + } - // open in split view + if ( + !( + this.focusModel && + (isEmbedLinkedDocBlock(this.focusModel) || + isEmbedSyncedDocBlock(this.focusModel)) + ) + ) { + return null; + } + + return { + label: item.label, + type: item.type, + icon: item.icon, + action: () => { + if (item.type === 'open-in-center-peek') { + element && peek(element); + } else { + this.focusBlock?.open({ openMode: item.type }); + } + }, + }; + }) + .filter(item => item !== null); if (buttons.length === 0) { return nothing; diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/.gitignore b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/.gitignore new file mode 100644 index 0000000000..24e5b0a1ae --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/.gitignore @@ -0,0 +1 @@ +.build diff --git a/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Package.resolved b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Package.resolved new file mode 100644 index 0000000000..092041f0fb --- /dev/null +++ b/packages/frontend/apps/ios/App/Packages/AffineGraphQL/Package.resolved @@ -0,0 +1,23 @@ +{ + "pins" : [ + { + "identity" : "apollo-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apollographql/apollo-ios.git", + "state" : { + "revision" : "c3f48d45ec1300bc95243bf19f67284f9dc0d14a", + "version" : "1.15.3" + } + }, + { + "identity" : "sqlite.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/stephencelis/SQLite.swift.git", + "state" : { + "revision" : "a95fc6df17d108bd99210db5e8a9bac90fe984b8", + "version" : "0.15.3" + } + } + ], + "version" : 2 +} diff --git a/packages/frontend/apps/ios/App/Packages/Intelligents/Package.resolved b/packages/frontend/apps/ios/App/Packages/Intelligents/Package.resolved index bf00e11417..e61877399d 100644 --- a/packages/frontend/apps/ios/App/Packages/Intelligents/Package.resolved +++ b/packages/frontend/apps/ios/App/Packages/Intelligents/Package.resolved @@ -1,5 +1,23 @@ { "pins" : [ + { + "identity" : "apollo-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apollographql/apollo-ios.git", + "state" : { + "revision" : "c3f48d45ec1300bc95243bf19f67284f9dc0d14a", + "version" : "1.15.3" + } + }, + { + "identity" : "msdisplaylink", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Lakr233/MSDisplayLink", + "state" : { + "revision" : "c2fcd28cb99300d83acc30860ce252ef97c20b61", + "version" : "1.1.1" + } + }, { "identity" : "networkimage", "kind" : "remoteSourceControl", @@ -9,6 +27,24 @@ "version" : "6.0.1" } }, + { + "identity" : "springinterpolation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Lakr233/SpringInterpolation", + "state" : { + "revision" : "f9d1ee3d2466bdb00fd0ade7f256ed20229c8413", + "version" : "1.3.0" + } + }, + { + "identity" : "sqlite.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/stephencelis/SQLite.swift.git", + "state" : { + "revision" : "a95fc6df17d108bd99210db5e8a9bac90fe984b8", + "version" : "0.15.3" + } + }, { "identity" : "swift-cmark", "kind" : "remoteSourceControl", @@ -18,6 +54,15 @@ "version" : "0.5.0" } }, + { + "identity" : "swift-eventsource", + "kind" : "remoteSourceControl", + "location" : "https://github.com/LaunchDarkly/swift-eventsource.git", + "state" : { + "revision" : "57051701c58a93603ffa2051f8e9cf0c8cff7814", + "version" : "3.3.0" + } + }, { "identity" : "swift-markdown-ui", "kind" : "remoteSourceControl", diff --git a/packages/frontend/core/src/components/affine/empty/docs.tsx b/packages/frontend/core/src/components/affine/empty/docs.tsx index fe09a9f210..1e3d3ebb35 100644 --- a/packages/frontend/core/src/components/affine/empty/docs.tsx +++ b/packages/frontend/core/src/components/affine/empty/docs.tsx @@ -1,6 +1,6 @@ import { TagService } from '@affine/core/modules/tag'; import { WorkspaceService } from '@affine/core/modules/workspace'; -import { isNewTabTrigger } from '@affine/core/utils'; +import { inferOpenMode } from '@affine/core/utils'; import { useI18n } from '@affine/i18n'; import { AllDocsIcon } from '@blocksuite/icons/rc'; import { useLiveData, useService } from '@toeverything/infra'; @@ -36,10 +36,9 @@ export const EmptyDocs = ({ const onCreate = useCallback( (e: MouseEvent) => { - const doc = pageHelper.createPage( - undefined, - isNewTabTrigger(e) ? 'new-tab' : true - ); + const doc = pageHelper.createPage(undefined, { + at: inferOpenMode(e), + }); if (tag) tag.tag(doc.id); }, diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx index 7ea6173db6..879e28d15d 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx @@ -53,13 +53,13 @@ import { extendEdgelessPreviewSpec } from './specs/custom/root-block'; import { patchDocModeService, patchEdgelessClipboard, - patchEmbedLinkedDocBlockConfig, patchForAttachmentEmbedViews, patchForClipboardInElectron, patchForMobile, patchForSharedPage, patchGenerateDocUrlExtension, patchNotificationService, + patchOpenDocExtension, patchParseDocUrlExtension, patchPeekViewService, patchQuickSearchService, @@ -160,11 +160,11 @@ const usePatchSpecs = (shared: boolean, mode: DocMode) => { patched = patched.concat(patchNotificationService(confirmModal)); patched = patched.concat(patchPeekViewService(peekViewService)); + patched = patched.concat(patchOpenDocExtension()); patched = patched.concat(patchEdgelessClipboard()); patched = patched.concat(patchParseDocUrlExtension(framework)); patched = patched.concat(patchGenerateDocUrlExtension(framework)); patched = patched.concat(patchQuickSearchService(framework)); - patched = patched.concat(patchEmbedLinkedDocBlockConfig(framework)); if (shared) { patched = patched.concat(patchForSharedPage()); } diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/common.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/common.ts index 75bce26ab9..b3a7af2c30 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/common.ts +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/common.ts @@ -11,6 +11,7 @@ import { CodeBlockSpec, DatabaseBlockSpec, DataViewBlockSpec, + DefaultOpenDocExtension, DividerBlockSpec, EditPropsStore, EmbedExtensions, @@ -38,6 +39,7 @@ const CommonBlockSpecs: ExtensionType[] = [ AttachmentBlockSpec, AdapterFactoryExtensions, FontLoaderService, + DefaultOpenDocExtension, ].flat(); export const DefaultBlockSpecs: ExtensionType[] = [ diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx index cfca27fc4f..7387ca0f43 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx @@ -24,10 +24,9 @@ import { } from '@affine/core/modules/quicksearch'; import { ExternalLinksQuickSearchSession } from '@affine/core/modules/quicksearch/impls/external-links'; import { JournalsQuickSearchSession } from '@affine/core/modules/quicksearch/impls/journals'; -import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkspaceService } from '@affine/core/modules/workspace'; -import { isNewTabTrigger } from '@affine/core/utils'; import { DebugLogger } from '@affine/debug'; +import { I18n } from '@affine/i18n'; import { track } from '@affine/track'; import { BlockServiceWatcher, @@ -39,6 +38,8 @@ import type { AffineReference, DocMode, DocModeProvider, + OpenDocConfig, + OpenDocConfigItem, PeekOptions, PeekViewService as BSPeekViewService, QuickSearchResult, @@ -50,11 +51,11 @@ import { DocModeExtension, EdgelessRootBlockComponent, EmbedLinkedDocBlockComponent, - EmbedLinkedDocBlockConfigExtension, GenerateDocUrlExtension, MobileSpecsPatches, NativeClipboardExtension, NotificationExtension, + OpenDocExtension, ParseDocUrlExtension, PeekViewExtension, QuickSearchExtension, @@ -67,6 +68,12 @@ import { Text, } from '@blocksuite/affine/store'; import type { ReferenceParams } from '@blocksuite/affine-model'; +import { + CenterPeekIcon, + ExpandFullIcon, + OpenInNewIcon, + SplitViewIcon, +} from '@blocksuite/icons/lit'; import { type FrameworkProvider } from '@toeverything/infra'; import { type TemplateResult } from 'lit'; import { customElement } from 'lit/decorators.js'; @@ -236,18 +243,35 @@ export function patchNotificationService({ }); } -export function patchEmbedLinkedDocBlockConfig(framework: FrameworkProvider) { - const getWorkbench = () => framework.get(WorkbenchService).workbench; - - return EmbedLinkedDocBlockConfigExtension({ - handleClick(e, _, refInfo) { - if (isNewTabTrigger(e)) { - const workbench = getWorkbench(); - workbench.openDoc(refInfo.pageId, { at: 'new-tab' }); - e.preventDefault(); - } - }, - }); +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), + }; + return OpenDocExtension(openDocConfig); } export function patchPeekViewService(service: PeekViewService) { diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx index 44559f9b34..5033e6aeab 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-page-list/utils.tsx @@ -26,7 +26,16 @@ export const usePageHelper = (docCollection: Workspace) => { const appSidebar = appSidebarService.sidebar; const createPageAndOpen = useCallback( - (mode?: DocMode, open?: boolean | 'new-tab') => { + ( + mode?: DocMode, + options: { + at?: 'new-tab' | 'tail' | 'active'; + show?: boolean; + } = { + at: 'active', + show: true, + } + ) => { appSidebar.setHovering(false); const docProps: DocProps = { note: editorSettingService.editorSetting.get('affine:note'), @@ -37,10 +46,12 @@ export const usePageHelper = (docCollection: Workspace) => { docRecordList.doc$(page.id).value?.setPrimaryMode(mode); } - if (open !== false) + if (options.show !== false) { workbench.openDoc(page.id, { - at: open === 'new-tab' ? 'new-tab' : 'active', + at: options.at, + show: options.show, }); + } return page; }, [ @@ -53,8 +64,16 @@ export const usePageHelper = (docCollection: Workspace) => { ); const createEdgelessAndOpen = useCallback( - (open?: boolean | 'new-tab') => { - return createPageAndOpen('edgeless', open); + ( + options: { + at?: 'new-tab' | 'tail' | 'active'; + show?: boolean; + } = { + at: 'active', + show: true, + } + ) => { + return createPageAndOpen('edgeless', options); }, [createPageAndOpen] ); @@ -103,8 +122,13 @@ export const usePageHelper = (docCollection: Workspace) => { return useMemo(() => { return { - createPage: (mode?: DocMode, open?: boolean | 'new-tab') => - createPageAndOpen(mode, open), + createPage: ( + mode?: DocMode, + options?: { + at?: 'new-tab' | 'tail' | 'active'; + show?: boolean; + } + ) => createPageAndOpen(mode, options), createEdgeless: createEdgelessAndOpen, importFile: importFileAndOpen, }; diff --git a/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx b/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx index 3da0ad6b05..d77b225307 100644 --- a/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx +++ b/packages/frontend/core/src/components/page-list/docs/page-list-header.tsx @@ -13,7 +13,7 @@ import type { Tag } from '@affine/core/modules/tag'; import { TagService } from '@affine/core/modules/tag'; import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkspaceService } from '@affine/core/modules/workspace'; -import { isNewTabTrigger } from '@affine/core/utils'; +import { inferOpenMode } from '@affine/core/utils'; import type { Collection } from '@affine/env/filter'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; @@ -92,15 +92,11 @@ export const PageListHeader = () => { - createEdgeless(isNewTabTrigger(e) ? 'new-tab' : true) - } + onCreateEdgeless={e => createEdgeless({ at: inferOpenMode(e) })} onCreatePage={e => - createPage('page' as DocMode, isNewTabTrigger(e) ? 'new-tab' : true) - } - onCreateDoc={e => - createPage(undefined, isNewTabTrigger(e) ? 'new-tab' : true) + createPage('page' as DocMode, { at: inferOpenMode(e) }) } + onCreateDoc={e => createPage(undefined, { at: inferOpenMode(e) })} onImportFile={onImportFile} >
{t['New Page']()}
diff --git a/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-header.tsx b/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-header.tsx index 9bccaad0f1..f18e1fb8ed 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-header.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/all-page/all-page-header.tsx @@ -9,7 +9,7 @@ import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-m import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkspaceService } from '@affine/core/modules/workspace'; -import { isNewTabTrigger } from '@affine/core/utils'; +import { inferOpenMode } from '@affine/core/utils'; import type { Filter } from '@affine/env/filter'; import { track } from '@affine/track'; import { PlusIcon } from '@blocksuite/icons/rc'; @@ -89,15 +89,9 @@ export const AllPageHeader = ({ styles.headerCreateNewButton, !showCreateNew && styles.headerCreateNewButtonHidden )} - onCreateEdgeless={e => - createEdgeless(isNewTabTrigger(e) ? 'new-tab' : true) - } - onCreatePage={e => - createPage('page', isNewTabTrigger(e) ? 'new-tab' : true) - } - onCreateDoc={e => - createPage(undefined, isNewTabTrigger(e) ? 'new-tab' : true) - } + onCreateEdgeless={e => createEdgeless({ at: inferOpenMode(e) })} + onCreatePage={e => createPage('page', { at: inferOpenMode(e) })} + onCreateDoc={e => createPage(undefined, { at: inferOpenMode(e) })} onImportFile={onImportFile} > diff --git a/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx b/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx index 716ee695a5..7934f82ceb 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page.tsx @@ -11,9 +11,11 @@ import { DocService } from '@affine/core/modules/doc'; import { EditorService } from '@affine/core/modules/editor'; import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { GlobalContextService } from '@affine/core/modules/global-context'; +import { PeekViewService } from '@affine/core/modules/peek-view'; import { RecentDocsService } from '@affine/core/modules/quicksearch'; import { ViewService } from '@affine/core/modules/workbench'; import { WorkspaceService } from '@affine/core/modules/workspace'; +import { isNewTabTrigger } from '@affine/core/utils'; import { RefNodeSlotsProvider } from '@blocksuite/affine/blocks'; import { DisposableGroup } from '@blocksuite/affine/global/utils'; import { type AffineEditorContainer } from '@blocksuite/affine/presets'; @@ -39,7 +41,6 @@ import { GlobalPageHistoryModal } from '../../../../components/affine/page-histo import { useRegisterBlocksuiteEditorCommands } from '../../../../components/hooks/affine/use-register-blocksuite-editor-commands'; import { useActiveBlocksuiteEditor } from '../../../../components/hooks/use-block-suite-editor'; import { usePageDocumentTitle } from '../../../../components/hooks/use-global-state'; -import { useNavigateHelper } from '../../../../components/hooks/use-navigate-helper'; import { PageDetailEditor } from '../../../../components/page-detail-editor'; import { TrashPageFooter } from '../../../../components/pure/trash-page-footer'; import { TopTip } from '../../../../components/top-tip'; @@ -81,7 +82,6 @@ const DetailPageImpl = memo(function DetailPageImpl() { const editor = editorService.editor; const view = viewService.view; const workspace = workspaceService.workspace; - const docCollection = workspace.docCollection; const globalContext = globalContextService.globalContext; const doc = docService.doc; @@ -89,7 +89,6 @@ const DetailPageImpl = memo(function DetailPageImpl() { const activeSidebarTab = useLiveData(view.activeSidebarTab$); const isInTrash = useLiveData(doc.meta$.map(meta => meta.trash)); - const { openPage, jumpToPageBlock } = useNavigateHelper(); const editorContainer = useLiveData(editor.editorContainer$); const isSideBarOpen = useLiveData(workbench.sidebarOpen$); @@ -97,6 +96,8 @@ const DetailPageImpl = memo(function DetailPageImpl() { const chatPanelRef = useRef(null); const { setDocReadonly } = useDocMetaHelper(); + const peekView = useService(PeekViewService).peekView; + const isActiveView = useIsActiveView(); // TODO(@eyhn): remove jotai here const [_, setActiveBlockSuiteEditor] = useActiveBlocksuiteEditor(); @@ -178,25 +179,50 @@ const DetailPageImpl = memo(function DetailPageImpl() { const refNodeSlots = std.getOptional(RefNodeSlotsProvider); if (refNodeSlots) { disposable.add( - refNodeSlots.docLinkClicked.on(({ pageId, params }) => { - if (params) { - const { mode, blockIds, elementIds } = params; - jumpToPageBlock( - docCollection.id, - pageId, - mode, - blockIds, - elementIds - ); - return; + // the event should not be emitted by AffineReference + refNodeSlots.docLinkClicked.on( + ({ pageId, params, openMode, event }) => { + openMode ??= + event && isNewTabTrigger(event) + ? 'open-in-new-tab' + : 'open-in-active-view'; + if (openMode !== 'open-in-center-peek') { + const at = (() => { + if (openMode === 'open-in-active-view') { + return 'active'; + } + // split view is only supported on electron + if (openMode === 'open-in-new-view') { + return BUILD_CONFIG.isElectron ? 'tail' : 'new-tab'; + } + if (openMode === 'open-in-new-tab') { + return 'new-tab'; + } + return 'active'; + })(); + workbench.openDoc( + { + docId: pageId, + blockIds: params?.blockIds, + elementIds: params?.elementIds, + }, + { + at: at, + show: true, + } + ); + } else { + peekView + .open({ + docRef: { + docId: pageId, + }, + ...params, + }) + .catch(console.error); + } } - - if (editor.doc.id === pageId) { - return; - } - - openPage(docCollection.id, pageId); - }) + ) ); } } @@ -212,7 +238,7 @@ const DetailPageImpl = memo(function DetailPageImpl() { disposable.dispose(); }; }, - [editor, openPage, docCollection.id, jumpToPageBlock] + [editor, workbench, peekView] ); const [hasScrollTop, setHasScrollTop] = useState(false); diff --git a/packages/frontend/core/src/mobile/components/app-tabs/create.tsx b/packages/frontend/core/src/mobile/components/app-tabs/create.tsx index 1c638b6202..94b2af05b4 100644 --- a/packages/frontend/core/src/mobile/components/app-tabs/create.tsx +++ b/packages/frontend/core/src/mobile/components/app-tabs/create.tsx @@ -18,7 +18,7 @@ export const AppTabCreate = ({ tab }: AppTabCustomFCProps) => { const createPage = useCallback( (isActive: boolean) => { if (isActive) return; - const doc = pageHelper.createPage(undefined, false); + const doc = pageHelper.createPage(undefined, { show: false }); workbench.openDoc({ docId: doc.id, fromTab: 'true' }); track.$.navigationPanel.$.createDoc(); }, diff --git a/packages/frontend/core/src/modules/app-sidebar/views/add-page-button/index.tsx b/packages/frontend/core/src/modules/app-sidebar/views/add-page-button/index.tsx index f9f21d6805..5af5e7aaea 100644 --- a/packages/frontend/core/src/modules/app-sidebar/views/add-page-button/index.tsx +++ b/packages/frontend/core/src/modules/app-sidebar/views/add-page-button/index.tsx @@ -1,7 +1,7 @@ import { IconButton } from '@affine/component'; import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils'; import { WorkspaceService } from '@affine/core/modules/workspace'; -import { isNewTabTrigger } from '@affine/core/utils'; +import { inferOpenMode } from '@affine/core/utils'; import { useI18n } from '@affine/i18n'; import track from '@affine/track'; import { PlusIcon } from '@blocksuite/icons/rc'; @@ -25,7 +25,7 @@ export function AddPageButton({ className, style }: AddPageButtonProps) { const onClickNewPage = useCallback( (e?: MouseEvent) => { - pageHelper.createPage(undefined, isNewTabTrigger(e) ? 'new-tab' : true); + pageHelper.createPage(undefined, { at: inferOpenMode(e) }); track.$.navigationPanel.$.createDoc(); }, [pageHelper] diff --git a/packages/frontend/core/src/modules/explorer/views/sections/favorites/index.tsx b/packages/frontend/core/src/modules/explorer/views/sections/favorites/index.tsx index 57923bdf5d..b20d4663c9 100644 --- a/packages/frontend/core/src/modules/explorer/views/sections/favorites/index.tsx +++ b/packages/frontend/core/src/modules/explorer/views/sections/favorites/index.tsx @@ -15,7 +15,7 @@ import { } from '@affine/core/modules/favorite'; import { WorkspaceService } from '@affine/core/modules/workspace'; import type { AffineDNDData } from '@affine/core/types/dnd'; -import { isNewTabTrigger } from '@affine/core/utils'; +import { inferOpenMode } from '@affine/core/utils'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { PlusIcon } from '@blocksuite/icons/rc'; @@ -81,10 +81,7 @@ export const ExplorerFavorites = () => { const handleCreateNewFavoriteDoc: MouseEventHandler = useCallback( e => { - const newDoc = createPage( - undefined, - isNewTabTrigger(e) ? 'new-tab' : true - ); + const newDoc = createPage(undefined, { at: inferOpenMode(e) }); favoriteService.favoriteList.add( 'doc', newDoc.id, diff --git a/packages/frontend/core/src/modules/workbench/services/workbench-new-tab-handler.ts b/packages/frontend/core/src/modules/workbench/services/workbench-new-tab-handler.ts index 58e352ad4d..3e03c6b000 100644 --- a/packages/frontend/core/src/modules/workbench/services/workbench-new-tab-handler.ts +++ b/packages/frontend/core/src/modules/workbench/services/workbench-new-tab-handler.ts @@ -27,13 +27,13 @@ export class DesktopWorkbenchNewTabHandler constructor(private readonly electronApi: DesktopApiService) { super(); } - handle({ basename, to }: { basename: string; to: To }) { + handle({ basename, to, show }: { basename: string; to: To; show: boolean }) { const path = typeof to === 'string' ? parsePath(to) : to; this.electronApi.api.handler.ui .addTab({ basename, view: { path }, - show: false, + show: show, }) .catch(console.error); } diff --git a/packages/frontend/core/src/modules/workbench/view/split-view/panel.tsx b/packages/frontend/core/src/modules/workbench/view/split-view/panel.tsx index b27cb0ed49..2ef6cbc9df 100644 --- a/packages/frontend/core/src/modules/workbench/view/split-view/panel.tsx +++ b/packages/frontend/core/src/modules/workbench/view/split-view/panel.tsx @@ -240,6 +240,7 @@ export const SplitViewPanel = memo(function SplitViewPanel({ dropTargetRef.current = node; dragRef.current = node; }} + data-is-active={isActive && views.length > 1 && !draggingEntity} className={styles.splitViewPanelDrag} >
diff --git a/packages/frontend/core/src/modules/workbench/view/split-view/split-view.css.ts b/packages/frontend/core/src/modules/workbench/view/split-view/split-view.css.ts index 6034b31e51..ccfdc1e0b3 100644 --- a/packages/frontend/core/src/modules/workbench/view/split-view/split-view.css.ts +++ b/packages/frontend/core/src/modules/workbench/view/split-view/split-view.css.ts @@ -74,7 +74,7 @@ export const splitViewPanelDrag = style({ transition: 'box-shadow 0.5s cubic-bezier(0.16, 1, 0.3, 1)', }, - '[data-is-active="true"] &::after': { + '[data-is-active="true"]&::after': { boxShadow: `inset 0 0 0 1px ${cssVarV2('button/primary')}`, }, diff --git a/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx b/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx index 94b35b3d19..65ba3e639f 100644 --- a/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx +++ b/packages/frontend/core/src/modules/workbench/view/workbench-link.tsx @@ -1,7 +1,7 @@ import { useDraggable } from '@affine/component'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import type { AffineDNDData, AffineDNDEntity } from '@affine/core/types/dnd'; -import { isNewTabTrigger } from '@affine/core/utils'; +import { inferOpenMode as inferOpenAt } from '@affine/core/utils'; import { useLiveData, useServices } from '@toeverything/infra'; import { type To } from 'history'; import { forwardRef, type MouseEvent } from 'react'; @@ -62,13 +62,8 @@ export const WorkbenchLink = forwardRef( if (event.defaultPrevented) { return; } - const at = (() => { - if (isNewTabTrigger(event)) { - return BUILD_CONFIG.isElectron && event.altKey ? 'tail' : 'new-tab'; - } - return 'active'; - })(); - workbench.open(to, { at, replaceHistory }); + const at = inferOpenAt(event); + workbench.open(to, { at, replaceHistory, show: false }); event.preventDefault(); event.stopPropagation(); }, diff --git a/packages/frontend/core/src/utils/event.ts b/packages/frontend/core/src/utils/event.ts index c2a15b3027..f90f142fb1 100644 --- a/packages/frontend/core/src/utils/event.ts +++ b/packages/frontend/core/src/utils/event.ts @@ -8,11 +8,21 @@ export function preventDefault(event: BaseSyntheticEvent) { event.preventDefault(); } -export function stopEvent(event: BaseSyntheticEvent) { - event.stopPropagation(); - event.preventDefault(); +export function isNewTabTrigger(event?: React.MouseEvent | MouseEvent) { + return event + ? (event.ctrlKey || event.metaKey || event.button === 1) && !event.altKey + : false; } -export function isNewTabTrigger(event?: React.MouseEvent | MouseEvent) { - return event ? event.ctrlKey || event.metaKey || event.button === 1 : false; +export function isNewViewTrigger(event?: React.MouseEvent | MouseEvent) { + return event ? (event.ctrlKey || event.metaKey) && event.altKey : false; +} + +export function inferOpenMode(event?: React.MouseEvent | MouseEvent) { + if (isNewTabTrigger(event)) { + return 'new-tab'; + } else if (isNewViewTrigger(event)) { + return BUILD_CONFIG.isElectron ? 'tail' : 'new-tab'; + } + return 'active'; } diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 9bd749542e..765177018c 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -1042,6 +1042,7 @@ "com.affine.peek-view-controls.open-attachment": "Open this attachment", "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.quicksearch.group.creation": "New", "com.affine.quicksearch.group.searchfor": "Search for \"{{query}}\"", "com.affine.resetSyncStatus.button": "Reset sync", diff --git a/tests/affine-desktop/e2e/split-view.spec.ts b/tests/affine-desktop/e2e/split-view.spec.ts index cd09417628..942a17e6d3 100644 --- a/tests/affine-desktop/e2e/split-view.spec.ts +++ b/tests/affine-desktop/e2e/split-view.spec.ts @@ -28,8 +28,8 @@ test('open split view', async ({ page }) => { await expect(page.getByTestId('split-view-label')).toHaveCount(2); await expectTabTitle(page, 0, ['Untitled', 'hi from another page']); - // the second split view should be active - await expectActiveTab(page, 0, 1); + // the first split view should be active + await expectActiveTab(page, 0, 0); // by clicking the first split view label, the first split view should be active await page.getByTestId('split-view-label').nth(0).click(); diff --git a/tests/affine-desktop/tsconfig.json b/tests/affine-desktop/tsconfig.json index 5a058bda33..d33eee85a4 100644 --- a/tests/affine-desktop/tsconfig.json +++ b/tests/affine-desktop/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.web.json", "compilerOptions": { + "composite": true, "rootDir": "./e2e", "outDir": "./dist", "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo" diff --git a/tools/utils/src/logger.ts b/tools/utils/src/logger.ts index 5d45ff6983..a429cbea9d 100644 --- a/tools/utils/src/logger.ts +++ b/tools/utils/src/logger.ts @@ -1,5 +1,5 @@ import chalk from 'chalk'; -import identity from 'lodash-es/identity'; +import { identity } from 'lodash-es'; export const newLineSeparator = /\r\n|[\n\r\x85\u2028\u2029]/g; diff --git a/tools/utils/src/workspace.ts b/tools/utils/src/workspace.ts index a8410a9579..0bbade1e8c 100644 --- a/tools/utils/src/workspace.ts +++ b/tools/utils/src/workspace.ts @@ -1,4 +1,4 @@ -import once from 'lodash-es/once'; +import { once } from 'lodash-es'; import { Logger } from './logger'; import { Package, readPackageJson } from './package';