mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-24 04:27:27 +08:00
feat(editor): add more open doc options to editor toolbar (#9588)
fix AF-2036, AF-2092
This commit is contained in:
@@ -17,7 +17,6 @@ import type {
|
|||||||
insertEmbedLinkedDocCommand,
|
insertEmbedLinkedDocCommand,
|
||||||
} from './embed-linked-doc-block/commands/insert-embed-linked-doc';
|
} from './embed-linked-doc-block/commands/insert-embed-linked-doc';
|
||||||
import { EmbedEdgelessLinkedDocBlockComponent } from './embed-linked-doc-block/embed-edgeless-linked-doc-block';
|
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 {
|
import {
|
||||||
EmbedLoomBlockComponent,
|
EmbedLoomBlockComponent,
|
||||||
type EmbedLoomBlockService,
|
type EmbedLoomBlockService,
|
||||||
@@ -123,9 +122,7 @@ declare global {
|
|||||||
interface CommandContext {
|
interface CommandContext {
|
||||||
insertedLinkType?: Promise<InsertedLinkType>;
|
insertedLinkType?: Promise<InsertedLinkType>;
|
||||||
}
|
}
|
||||||
interface BlockConfigs {
|
|
||||||
'affine:embed-linked-doc': EmbedLinkedDocBlockConfig;
|
|
||||||
}
|
|
||||||
interface Commands {
|
interface Commands {
|
||||||
insertEmbedLinkedDoc: typeof insertEmbedLinkedDocCommand;
|
insertEmbedLinkedDoc: typeof insertEmbedLinkedDocCommand;
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-4
@@ -4,7 +4,11 @@ import {
|
|||||||
EMBED_CARD_WIDTH,
|
EMBED_CARD_WIDTH,
|
||||||
} from '@blocksuite/affine-shared/consts';
|
} from '@blocksuite/affine-shared/consts';
|
||||||
import { FeatureFlagService } from '@blocksuite/affine-shared/services';
|
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 { Bound } from '@blocksuite/global/utils';
|
||||||
|
|
||||||
import { toEdgelessEmbedBlock } from '../common/to-edgeless-embed-block.js';
|
import { toEdgelessEmbedBlock } from '../common/to-edgeless-embed-block.js';
|
||||||
@@ -65,9 +69,10 @@ export class EmbedEdgelessLinkedDocBlockComponent extends toEdgelessEmbedBlock(
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override _handleClick(evt: MouseEvent): void {
|
protected override _handleClick(evt: MouseEvent): void {
|
||||||
if (this.config.handleClick) {
|
if (isNewTabTrigger(evt)) {
|
||||||
this.config.handleClick(evt, this.host, this.referenceInfo$.peek());
|
this.open({ openMode: 'open-in-new-tab', event: evt });
|
||||||
return;
|
} else if (isNewViewTrigger(evt)) {
|
||||||
|
this.open({ openMode: 'open-in-new-view', event: evt });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-35
@@ -14,11 +14,15 @@ import {
|
|||||||
DocDisplayMetaProvider,
|
DocDisplayMetaProvider,
|
||||||
DocModeProvider,
|
DocModeProvider,
|
||||||
FeatureFlagService,
|
FeatureFlagService,
|
||||||
|
OpenDocExtensionIdentifier,
|
||||||
|
type OpenDocMode,
|
||||||
ThemeProvider,
|
ThemeProvider,
|
||||||
} from '@blocksuite/affine-shared/services';
|
} from '@blocksuite/affine-shared/services';
|
||||||
import {
|
import {
|
||||||
cloneReferenceInfo,
|
cloneReferenceInfo,
|
||||||
cloneReferenceInfoWithoutAliases,
|
cloneReferenceInfoWithoutAliases,
|
||||||
|
isNewTabTrigger,
|
||||||
|
isNewViewTrigger,
|
||||||
matchFlavours,
|
matchFlavours,
|
||||||
referenceToNode,
|
referenceToNode,
|
||||||
} from '@blocksuite/affine-shared/utils';
|
} from '@blocksuite/affine-shared/utils';
|
||||||
@@ -39,10 +43,6 @@ import {
|
|||||||
renderLinkedDocInCard,
|
renderLinkedDocInCard,
|
||||||
} from '../common/render-linked-doc.js';
|
} from '../common/render-linked-doc.js';
|
||||||
import { SyncedDocErrorIcon } from '../embed-synced-doc-block/styles.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 { styles } from './styles.js';
|
||||||
import { getEmbedLinkedDocIcons } from './utils.js';
|
import { getEmbedLinkedDocIcons } from './utils.js';
|
||||||
|
|
||||||
@@ -205,10 +205,18 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
|
|||||||
.icon(pageId, { params, title, referenced: true }).value;
|
.icon(pageId, { params, title, referenced: true }).value;
|
||||||
});
|
});
|
||||||
|
|
||||||
open = () => {
|
open = ({
|
||||||
this.std
|
openMode,
|
||||||
.getOptional(RefNodeSlotsProvider)
|
event,
|
||||||
?.docLinkClicked.emit(this.referenceInfo$.peek());
|
}: {
|
||||||
|
openMode?: OpenDocMode;
|
||||||
|
event?: MouseEvent;
|
||||||
|
} = {}) => {
|
||||||
|
this.std.getOptional(RefNodeSlotsProvider)?.docLinkClicked.emit({
|
||||||
|
...this.referenceInfo$.peek(),
|
||||||
|
openMode,
|
||||||
|
event,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
refreshData = () => {
|
refreshData = () => {
|
||||||
@@ -228,12 +236,6 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
get config(): EmbedLinkedDocBlockConfig {
|
|
||||||
return (
|
|
||||||
this.std.provider.getOptional(EmbedLinkedDocBlockConfigIdentifier) || {}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get docTitle() {
|
get docTitle() {
|
||||||
return this.model.title || this.linkedDoc?.meta?.title || 'Untitled';
|
return this.model.title || this.linkedDoc?.meta?.title || 'Untitled';
|
||||||
}
|
}
|
||||||
@@ -247,22 +249,16 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _handleDoubleClick(event: MouseEvent) {
|
private _handleDoubleClick(event: MouseEvent) {
|
||||||
if (this.config.handleDoubleClick) {
|
|
||||||
this.config.handleDoubleClick(
|
|
||||||
event,
|
|
||||||
this.host,
|
|
||||||
this.referenceInfo$.peek()
|
|
||||||
);
|
|
||||||
if (event.defaultPrevented) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPeekable(this)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.open();
|
const openDocService = this.std.get(OpenDocExtensionIdentifier);
|
||||||
|
const shouldOpenInPeek =
|
||||||
|
openDocService.isAllowed('open-in-center-peek') && isPeekable(this);
|
||||||
|
this.open({
|
||||||
|
openMode: shouldOpenInPeek
|
||||||
|
? 'open-in-center-peek'
|
||||||
|
: 'open-in-active-view',
|
||||||
|
event,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _isDocEmpty() {
|
private _isDocEmpty() {
|
||||||
@@ -274,13 +270,11 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent<EmbedLinke
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected _handleClick(event: MouseEvent) {
|
protected _handleClick(event: MouseEvent) {
|
||||||
if (this.config.handleClick) {
|
if (isNewTabTrigger(event)) {
|
||||||
this.config.handleClick(event, this.host, this.referenceInfo$.peek());
|
this.open({ openMode: 'open-in-new-tab', event });
|
||||||
if (event.defaultPrevented) {
|
} else if (isNewViewTrigger(event)) {
|
||||||
return;
|
this.open({ openMode: 'open-in-new-view', event });
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._selectBlock();
|
this._selectBlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import type { ReferenceInfo } from '@blocksuite/affine-model';
|
|
||||||
import type { EditorHost } from '@blocksuite/block-std';
|
|
||||||
import { createIdentifier } from '@blocksuite/global/di';
|
|
||||||
import type { ExtensionType } from '@blocksuite/store';
|
|
||||||
|
|
||||||
export interface EmbedLinkedDocBlockConfig {
|
|
||||||
handleClick?: (
|
|
||||||
e: MouseEvent,
|
|
||||||
host: EditorHost,
|
|
||||||
referenceInfo: ReferenceInfo
|
|
||||||
) => void;
|
|
||||||
handleDoubleClick?: (
|
|
||||||
e: MouseEvent,
|
|
||||||
host: EditorHost,
|
|
||||||
referenceInfo: ReferenceInfo
|
|
||||||
) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const EmbedLinkedDocBlockConfigIdentifier =
|
|
||||||
createIdentifier<EmbedLinkedDocBlockConfig>('EmbedLinkedDocBlockConfig');
|
|
||||||
|
|
||||||
export function EmbedLinkedDocBlockConfigExtension(
|
|
||||||
config: EmbedLinkedDocBlockConfig
|
|
||||||
): ExtensionType {
|
|
||||||
return {
|
|
||||||
setup: di => {
|
|
||||||
di.addImpl(EmbedLinkedDocBlockConfigIdentifier, () => config);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
export * from './adapters';
|
export * from './adapters';
|
||||||
export type { InsertedLinkType } from './commands';
|
export type { InsertedLinkType } from './commands';
|
||||||
export * from './embed-linked-doc-block';
|
export * from './embed-linked-doc-block';
|
||||||
export * from './embed-linked-doc-config';
|
|
||||||
export * from './embed-linked-doc-spec';
|
export * from './embed-linked-doc-spec';
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { Peekable } from '@blocksuite/affine-components/peek';
|
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 {
|
import {
|
||||||
type AliasInfo,
|
type AliasInfo,
|
||||||
type DocMode,
|
type DocMode,
|
||||||
@@ -305,11 +308,13 @@ export class EmbedSyncedDocBlockComponent extends EmbedBlockComponent<EmbedSynce
|
|||||||
.icon(pageId, { params, referenced: true }).value;
|
.icon(pageId, { params, referenced: true }).value;
|
||||||
});
|
});
|
||||||
|
|
||||||
open = () => {
|
open = (event?: Partial<DocLinkClickedEvent>) => {
|
||||||
const pageId = this.model.pageId;
|
const pageId = this.model.pageId;
|
||||||
if (pageId === this.doc.id) return;
|
if (pageId === this.doc.id) return;
|
||||||
|
|
||||||
this.std.getOptional(RefNodeSlotsProvider)?.docLinkClicked.emit({ pageId });
|
this.std
|
||||||
|
.getOptional(RefNodeSlotsProvider)
|
||||||
|
?.docLinkClicked.emit({ ...event, pageId });
|
||||||
};
|
};
|
||||||
|
|
||||||
refreshData = () => {
|
refreshData = () => {
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
export { AffineLink, toggleLinkPopup } from './link-node/index.js';
|
export { AffineLink, toggleLinkPopup } from './link-node/index.js';
|
||||||
export * from './reference-node/reference-config.js';
|
export * from './reference-node/reference-config.js';
|
||||||
export { AffineReference } from './reference-node/reference-node.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';
|
||||||
|
|||||||
+43
-29
@@ -3,6 +3,8 @@ import {
|
|||||||
FeatureFlagService,
|
FeatureFlagService,
|
||||||
GenerateDocUrlProvider,
|
GenerateDocUrlProvider,
|
||||||
type LinkEventType,
|
type LinkEventType,
|
||||||
|
OpenDocExtensionIdentifier,
|
||||||
|
type OpenDocMode,
|
||||||
type TelemetryEvent,
|
type TelemetryEvent,
|
||||||
TelemetryProvider,
|
TelemetryProvider,
|
||||||
} from '@blocksuite/affine-shared/services';
|
} from '@blocksuite/affine-shared/services';
|
||||||
@@ -26,11 +28,9 @@ import { join } from 'lit/directives/join.js';
|
|||||||
import { repeat } from 'lit/directives/repeat.js';
|
import { repeat } from 'lit/directives/repeat.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CenterPeekIcon,
|
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
DeleteIcon,
|
DeleteIcon,
|
||||||
EditIcon,
|
EditIcon,
|
||||||
ExpandFullSmallIcon,
|
|
||||||
MoreVerticalIcon,
|
MoreVerticalIcon,
|
||||||
OpenIcon,
|
OpenIcon,
|
||||||
SmallArrowDownIcon,
|
SmallArrowDownIcon,
|
||||||
@@ -47,6 +47,7 @@ import { RefNodeSlotsProvider } from '../../../../extension/index.js';
|
|||||||
import type { AffineInlineEditor } from '../../affine-inline-specs.js';
|
import type { AffineInlineEditor } from '../../affine-inline-specs.js';
|
||||||
import { ReferenceAliasPopup } from './reference-alias-popup.js';
|
import { ReferenceAliasPopup } from './reference-alias-popup.js';
|
||||||
import { styles } from './styles.js';
|
import { styles } from './styles.js';
|
||||||
|
import type { DocLinkClickedEvent } from './types.js';
|
||||||
|
|
||||||
export class ReferencePopup extends WithDisposable(LitElement) {
|
export class ReferencePopup extends WithDisposable(LitElement) {
|
||||||
static override styles = styles;
|
static override styles = styles;
|
||||||
@@ -66,10 +67,11 @@ export class ReferencePopup extends WithDisposable(LitElement) {
|
|||||||
track(this.std, 'CopiedLink', { control: 'copy link' });
|
track(this.std, 'CopiedLink', { control: 'copy link' });
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly _openDoc = () => {
|
private readonly _openDoc = (event?: Partial<DocLinkClickedEvent>) => {
|
||||||
this.std
|
this.std.getOptional(RefNodeSlotsProvider)?.docLinkClicked.emit({
|
||||||
.getOptional(RefNodeSlotsProvider)
|
...this.referenceInfo,
|
||||||
?.docLinkClicked.emit(this.referenceInfo);
|
...event,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly _openEditPopup = (e: MouseEvent) => {
|
private readonly _openEditPopup = (e: MouseEvent) => {
|
||||||
@@ -134,8 +136,11 @@ export class ReferencePopup extends WithDisposable(LitElement) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get _openButtonDisabled() {
|
_openButtonDisabled(openMode?: OpenDocMode) {
|
||||||
return this.referenceDocId === this.doc.id;
|
if (openMode === 'open-in-active-view') {
|
||||||
|
return this.referenceDocId === this.doc.id;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
get block() {
|
get block() {
|
||||||
@@ -246,28 +251,37 @@ export class ReferencePopup extends WithDisposable(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _openMenuButton() {
|
private _openMenuButton() {
|
||||||
const buttons: MenuItem[] = [
|
const openDocConfig = this.std.get(OpenDocExtensionIdentifier);
|
||||||
{
|
|
||||||
label: 'Open this doc',
|
|
||||||
type: 'open-this-doc',
|
|
||||||
icon: ExpandFullSmallIcon,
|
|
||||||
action: this._openDoc,
|
|
||||||
disabled: this._openButtonDisabled,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// open in new tab
|
const buttons: MenuItem[] = openDocConfig.items
|
||||||
|
.map(item => {
|
||||||
if (isPeekable(this.target)) {
|
if (
|
||||||
buttons.push({
|
(item.type === 'open-in-center-peek' && !isPeekable(this.target)) ||
|
||||||
label: 'Open in center peek',
|
!openDocConfig?.isAllowed(item.type)
|
||||||
type: 'open-in-center-peek',
|
) {
|
||||||
icon: CenterPeekIcon,
|
return null;
|
||||||
action: () => peek(this.target),
|
}
|
||||||
});
|
return {
|
||||||
}
|
label: item.label,
|
||||||
|
type: item.type,
|
||||||
// open in split view
|
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) {
|
if (buttons.length === 0) {
|
||||||
return nothing;
|
return nothing;
|
||||||
|
|||||||
+8
-1
@@ -1,6 +1,13 @@
|
|||||||
import type { ReferenceInfo } from '@blocksuite/affine-model';
|
import type { ReferenceInfo } from '@blocksuite/affine-model';
|
||||||
|
import type { OpenDocMode } from '@blocksuite/affine-shared/services';
|
||||||
import type { Slot } from '@blocksuite/global/utils';
|
import type { Slot } from '@blocksuite/global/utils';
|
||||||
|
|
||||||
|
export type DocLinkClickedEvent = ReferenceInfo & {
|
||||||
|
// default is active view
|
||||||
|
openMode?: OpenDocMode;
|
||||||
|
event?: MouseEvent;
|
||||||
|
};
|
||||||
|
|
||||||
export type RefNodeSlots = {
|
export type RefNodeSlots = {
|
||||||
docLinkClicked: Slot<ReferenceInfo>;
|
docLinkClicked: Slot<DocLinkClickedEvent>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -204,6 +204,7 @@ export class EditorMenuAction extends LitElement {
|
|||||||
|
|
||||||
::slotted(svg) {
|
::slotted(svg) {
|
||||||
color: var(--affine-icon-color);
|
color: var(--affine-icon-color);
|
||||||
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export * from './font-loader';
|
|||||||
export * from './generate-url-service';
|
export * from './generate-url-service';
|
||||||
export * from './native-clipboard-service';
|
export * from './native-clipboard-service';
|
||||||
export * from './notification-service';
|
export * from './notification-service';
|
||||||
|
export * from './open-doc-config';
|
||||||
export * from './page-viewport-service';
|
export * from './page-viewport-service';
|
||||||
export * from './parse-url-service';
|
export * from './parse-url-service';
|
||||||
export * from './quick-search-service';
|
export * from './quick-search-service';
|
||||||
|
|||||||
@@ -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<OpenDocService>(
|
||||||
|
'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);
|
||||||
@@ -44,6 +44,16 @@ export function isControlledKeyboardEvent(e: KeyboardEvent) {
|
|||||||
return e.ctrlKey || e.metaKey || e.altKey;
|
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<
|
export function on<
|
||||||
T extends HTMLElement,
|
T extends HTMLElement,
|
||||||
K extends keyof M,
|
K extends keyof M,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
RichTextExtensions,
|
RichTextExtensions,
|
||||||
} from '@blocksuite/affine-components/rich-text';
|
} from '@blocksuite/affine-components/rich-text';
|
||||||
import {
|
import {
|
||||||
|
DefaultOpenDocExtension,
|
||||||
DocDisplayMetaService,
|
DocDisplayMetaService,
|
||||||
EditPropsStore,
|
EditPropsStore,
|
||||||
FeatureFlagService,
|
FeatureFlagService,
|
||||||
@@ -54,6 +55,7 @@ export const CommonBlockSpecs: ExtensionType[] = [
|
|||||||
CodeBlockSpec,
|
CodeBlockSpec,
|
||||||
ImageBlockSpec,
|
ImageBlockSpec,
|
||||||
ParagraphBlockSpec,
|
ParagraphBlockSpec,
|
||||||
|
DefaultOpenDocExtension,
|
||||||
].flat();
|
].flat();
|
||||||
|
|
||||||
export const PageFirstPartyBlockSpecs: ExtensionType[] = [
|
export const PageFirstPartyBlockSpecs: ExtensionType[] = [
|
||||||
|
|||||||
+43
-35
@@ -13,7 +13,6 @@ import {
|
|||||||
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
|
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
|
||||||
import {
|
import {
|
||||||
CaptionIcon,
|
CaptionIcon,
|
||||||
CenterPeekIcon,
|
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
EditIcon,
|
EditIcon,
|
||||||
ExpandFullSmallIcon,
|
ExpandFullSmallIcon,
|
||||||
@@ -44,6 +43,8 @@ import {
|
|||||||
GenerateDocUrlProvider,
|
GenerateDocUrlProvider,
|
||||||
type GenerateDocUrlService,
|
type GenerateDocUrlService,
|
||||||
type LinkEventType,
|
type LinkEventType,
|
||||||
|
OpenDocExtensionIdentifier,
|
||||||
|
type OpenDocMode,
|
||||||
type TelemetryEvent,
|
type TelemetryEvent,
|
||||||
TelemetryProvider,
|
TelemetryProvider,
|
||||||
ThemeProvider,
|
ThemeProvider,
|
||||||
@@ -266,8 +267,8 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
|
|||||||
return bound.h / EMBED_CARD_HEIGHT[this.model.style];
|
return bound.h / EMBED_CARD_HEIGHT[this.model.style];
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly _open = () => {
|
private readonly _open = ({ openMode }: { openMode?: OpenDocMode } = {}) => {
|
||||||
this._blockComponent?.open();
|
this._blockComponent?.open({ openMode });
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly _openEditPopup = (e: MouseEvent) => {
|
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 {
|
get _originalDocInfo(): AliasInfo | undefined {
|
||||||
const model = this.model;
|
const model = this.model;
|
||||||
const doc = isInternalEmbedModel(model)
|
const doc = isInternalEmbedModel(model)
|
||||||
@@ -543,20 +538,46 @@ export class EdgelessChangeEmbedCardButton extends WithDisposable(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _openMenuButton() {
|
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 (
|
if (
|
||||||
isEmbedLinkedDocBlock(this.model) ||
|
!(
|
||||||
isEmbedSyncedDocBlock(this.model)
|
isEmbedLinkedDocBlock(this.model) ||
|
||||||
) {
|
isEmbedSyncedDocBlock(this.model)
|
||||||
buttons.push({
|
)
|
||||||
type: 'open-this-doc',
|
) {
|
||||||
label: 'Open this doc',
|
return null;
|
||||||
icon: ExpandFullSmallIcon,
|
}
|
||||||
action: this._open,
|
|
||||||
disabled: this._openButtonDisabled,
|
return {
|
||||||
});
|
label: item.label,
|
||||||
} else if (this._canShowFullScreenButton) {
|
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({
|
buttons.push({
|
||||||
type: 'open-this-doc',
|
type: 'open-this-doc',
|
||||||
label: '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) {
|
if (buttons.length === 0) {
|
||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ import type { ImageBlockComponent } from '@blocksuite/affine-block-image';
|
|||||||
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
|
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
|
||||||
import { isPeekable, peek } from '@blocksuite/affine-components/peek';
|
import { isPeekable, peek } from '@blocksuite/affine-components/peek';
|
||||||
import type { MenuItemGroup } from '@blocksuite/affine-components/toolbar';
|
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 { Bound, getCommonBoundWithRotation } from '@blocksuite/global/utils';
|
||||||
import {
|
import {
|
||||||
ArrowDownBigBottomIcon,
|
ArrowDownBigBottomIcon,
|
||||||
@@ -23,11 +26,13 @@ import {
|
|||||||
CopyIcon,
|
CopyIcon,
|
||||||
DeleteIcon,
|
DeleteIcon,
|
||||||
DuplicateIcon,
|
DuplicateIcon,
|
||||||
|
ExpandFullIcon,
|
||||||
FrameIcon,
|
FrameIcon,
|
||||||
GroupIcon,
|
GroupIcon,
|
||||||
LinkedPageIcon,
|
LinkedPageIcon,
|
||||||
OpenInNewIcon,
|
OpenInNewIcon,
|
||||||
ResetIcon,
|
ResetIcon,
|
||||||
|
SplitViewIcon,
|
||||||
} from '@blocksuite/icons/lit';
|
} from '@blocksuite/icons/lit';
|
||||||
|
|
||||||
import { duplicate } from '../../../edgeless/utils/clipboard-utils.js';
|
import { duplicate } from '../../../edgeless/utils/clipboard-utils.js';
|
||||||
@@ -135,11 +140,12 @@ export const reorderGroup: MenuItemGroup<ElementToolbarMoreMenuContext> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Open Group
|
// Open Group
|
||||||
|
// TODO: construct this group dynamically
|
||||||
export const openGroup: MenuItemGroup<ElementToolbarMoreMenuContext> = {
|
export const openGroup: MenuItemGroup<ElementToolbarMoreMenuContext> = {
|
||||||
type: 'open',
|
type: 'open',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
icon: OpenInNewIcon({ width: '20', height: '20' }),
|
icon: ExpandFullIcon({ width: '20', height: '20' }),
|
||||||
label: 'Open this doc',
|
label: 'Open this doc',
|
||||||
type: 'open',
|
type: 'open',
|
||||||
generate: ctx => {
|
generate: ctx => {
|
||||||
@@ -163,6 +169,62 @@ export const openGroup: MenuItemGroup<ElementToolbarMoreMenuContext> = {
|
|||||||
disabled,
|
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' }),
|
icon: CenterPeekIcon({ width: '20', height: '20' }),
|
||||||
@@ -184,6 +246,10 @@ export const openGroup: MenuItemGroup<ElementToolbarMoreMenuContext> = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
when: ctx => {
|
||||||
|
const openDocService = ctx.std.get(OpenDocExtensionIdentifier);
|
||||||
|
return openDocService.isAllowed('open-in-center-peek');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,10 +12,8 @@ import {
|
|||||||
} from '@blocksuite/affine-block-embed';
|
} from '@blocksuite/affine-block-embed';
|
||||||
import {
|
import {
|
||||||
CaptionIcon,
|
CaptionIcon,
|
||||||
CenterPeekIcon,
|
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
EditIcon,
|
EditIcon,
|
||||||
ExpandFullSmallIcon,
|
|
||||||
MoreVerticalIcon,
|
MoreVerticalIcon,
|
||||||
OpenIcon,
|
OpenIcon,
|
||||||
PaletteIcon,
|
PaletteIcon,
|
||||||
@@ -48,6 +46,7 @@ import {
|
|||||||
GenerateDocUrlProvider,
|
GenerateDocUrlProvider,
|
||||||
type GenerateDocUrlService,
|
type GenerateDocUrlService,
|
||||||
type LinkEventType,
|
type LinkEventType,
|
||||||
|
OpenDocExtensionIdentifier,
|
||||||
type TelemetryEvent,
|
type TelemetryEvent,
|
||||||
TelemetryProvider,
|
TelemetryProvider,
|
||||||
ThemeProvider,
|
ThemeProvider,
|
||||||
@@ -503,34 +502,42 @@ export class EmbedCardToolbar extends WidgetComponent<
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _openMenuButton() {
|
private _openMenuButton() {
|
||||||
const buttons: MenuItem[] = [];
|
const openDocConfig = this.std.get(OpenDocExtensionIdentifier);
|
||||||
|
|
||||||
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 element = this.focusBlock;
|
const element = this.focusBlock;
|
||||||
if (element && isPeekable(element)) {
|
const buttons: MenuItem[] = openDocConfig.items
|
||||||
buttons.push({
|
.map(item => {
|
||||||
type: 'open-in-center-peek',
|
if (
|
||||||
label: 'Open in center peek',
|
item.type === 'open-in-center-peek' &&
|
||||||
icon: CenterPeekIcon,
|
element &&
|
||||||
action: () => 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) {
|
if (buttons.length === 0) {
|
||||||
return nothing;
|
return nothing;
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
.build
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -1,5 +1,23 @@
|
|||||||
{
|
{
|
||||||
"pins" : [
|
"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",
|
"identity" : "networkimage",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
@@ -9,6 +27,24 @@
|
|||||||
"version" : "6.0.1"
|
"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",
|
"identity" : "swift-cmark",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
@@ -18,6 +54,15 @@
|
|||||||
"version" : "0.5.0"
|
"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",
|
"identity" : "swift-markdown-ui",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { TagService } from '@affine/core/modules/tag';
|
import { TagService } from '@affine/core/modules/tag';
|
||||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
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 { useI18n } from '@affine/i18n';
|
||||||
import { AllDocsIcon } from '@blocksuite/icons/rc';
|
import { AllDocsIcon } from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
@@ -36,10 +36,9 @@ export const EmptyDocs = ({
|
|||||||
|
|
||||||
const onCreate = useCallback(
|
const onCreate = useCallback(
|
||||||
(e: MouseEvent) => {
|
(e: MouseEvent) => {
|
||||||
const doc = pageHelper.createPage(
|
const doc = pageHelper.createPage(undefined, {
|
||||||
undefined,
|
at: inferOpenMode(e),
|
||||||
isNewTabTrigger(e) ? 'new-tab' : true
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if (tag) tag.tag(doc.id);
|
if (tag) tag.tag(doc.id);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -53,13 +53,13 @@ import { extendEdgelessPreviewSpec } from './specs/custom/root-block';
|
|||||||
import {
|
import {
|
||||||
patchDocModeService,
|
patchDocModeService,
|
||||||
patchEdgelessClipboard,
|
patchEdgelessClipboard,
|
||||||
patchEmbedLinkedDocBlockConfig,
|
|
||||||
patchForAttachmentEmbedViews,
|
patchForAttachmentEmbedViews,
|
||||||
patchForClipboardInElectron,
|
patchForClipboardInElectron,
|
||||||
patchForMobile,
|
patchForMobile,
|
||||||
patchForSharedPage,
|
patchForSharedPage,
|
||||||
patchGenerateDocUrlExtension,
|
patchGenerateDocUrlExtension,
|
||||||
patchNotificationService,
|
patchNotificationService,
|
||||||
|
patchOpenDocExtension,
|
||||||
patchParseDocUrlExtension,
|
patchParseDocUrlExtension,
|
||||||
patchPeekViewService,
|
patchPeekViewService,
|
||||||
patchQuickSearchService,
|
patchQuickSearchService,
|
||||||
@@ -160,11 +160,11 @@ const usePatchSpecs = (shared: boolean, mode: DocMode) => {
|
|||||||
|
|
||||||
patched = patched.concat(patchNotificationService(confirmModal));
|
patched = patched.concat(patchNotificationService(confirmModal));
|
||||||
patched = patched.concat(patchPeekViewService(peekViewService));
|
patched = patched.concat(patchPeekViewService(peekViewService));
|
||||||
|
patched = patched.concat(patchOpenDocExtension());
|
||||||
patched = patched.concat(patchEdgelessClipboard());
|
patched = patched.concat(patchEdgelessClipboard());
|
||||||
patched = patched.concat(patchParseDocUrlExtension(framework));
|
patched = patched.concat(patchParseDocUrlExtension(framework));
|
||||||
patched = patched.concat(patchGenerateDocUrlExtension(framework));
|
patched = patched.concat(patchGenerateDocUrlExtension(framework));
|
||||||
patched = patched.concat(patchQuickSearchService(framework));
|
patched = patched.concat(patchQuickSearchService(framework));
|
||||||
patched = patched.concat(patchEmbedLinkedDocBlockConfig(framework));
|
|
||||||
if (shared) {
|
if (shared) {
|
||||||
patched = patched.concat(patchForSharedPage());
|
patched = patched.concat(patchForSharedPage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
CodeBlockSpec,
|
CodeBlockSpec,
|
||||||
DatabaseBlockSpec,
|
DatabaseBlockSpec,
|
||||||
DataViewBlockSpec,
|
DataViewBlockSpec,
|
||||||
|
DefaultOpenDocExtension,
|
||||||
DividerBlockSpec,
|
DividerBlockSpec,
|
||||||
EditPropsStore,
|
EditPropsStore,
|
||||||
EmbedExtensions,
|
EmbedExtensions,
|
||||||
@@ -38,6 +39,7 @@ const CommonBlockSpecs: ExtensionType[] = [
|
|||||||
AttachmentBlockSpec,
|
AttachmentBlockSpec,
|
||||||
AdapterFactoryExtensions,
|
AdapterFactoryExtensions,
|
||||||
FontLoaderService,
|
FontLoaderService,
|
||||||
|
DefaultOpenDocExtension,
|
||||||
].flat();
|
].flat();
|
||||||
|
|
||||||
export const DefaultBlockSpecs: ExtensionType[] = [
|
export const DefaultBlockSpecs: ExtensionType[] = [
|
||||||
|
|||||||
+39
-15
@@ -24,10 +24,9 @@ import {
|
|||||||
} from '@affine/core/modules/quicksearch';
|
} from '@affine/core/modules/quicksearch';
|
||||||
import { ExternalLinksQuickSearchSession } from '@affine/core/modules/quicksearch/impls/external-links';
|
import { ExternalLinksQuickSearchSession } from '@affine/core/modules/quicksearch/impls/external-links';
|
||||||
import { JournalsQuickSearchSession } from '@affine/core/modules/quicksearch/impls/journals';
|
import { JournalsQuickSearchSession } from '@affine/core/modules/quicksearch/impls/journals';
|
||||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
|
||||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||||
import { isNewTabTrigger } from '@affine/core/utils';
|
|
||||||
import { DebugLogger } from '@affine/debug';
|
import { DebugLogger } from '@affine/debug';
|
||||||
|
import { I18n } from '@affine/i18n';
|
||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
import {
|
import {
|
||||||
BlockServiceWatcher,
|
BlockServiceWatcher,
|
||||||
@@ -39,6 +38,8 @@ import type {
|
|||||||
AffineReference,
|
AffineReference,
|
||||||
DocMode,
|
DocMode,
|
||||||
DocModeProvider,
|
DocModeProvider,
|
||||||
|
OpenDocConfig,
|
||||||
|
OpenDocConfigItem,
|
||||||
PeekOptions,
|
PeekOptions,
|
||||||
PeekViewService as BSPeekViewService,
|
PeekViewService as BSPeekViewService,
|
||||||
QuickSearchResult,
|
QuickSearchResult,
|
||||||
@@ -50,11 +51,11 @@ import {
|
|||||||
DocModeExtension,
|
DocModeExtension,
|
||||||
EdgelessRootBlockComponent,
|
EdgelessRootBlockComponent,
|
||||||
EmbedLinkedDocBlockComponent,
|
EmbedLinkedDocBlockComponent,
|
||||||
EmbedLinkedDocBlockConfigExtension,
|
|
||||||
GenerateDocUrlExtension,
|
GenerateDocUrlExtension,
|
||||||
MobileSpecsPatches,
|
MobileSpecsPatches,
|
||||||
NativeClipboardExtension,
|
NativeClipboardExtension,
|
||||||
NotificationExtension,
|
NotificationExtension,
|
||||||
|
OpenDocExtension,
|
||||||
ParseDocUrlExtension,
|
ParseDocUrlExtension,
|
||||||
PeekViewExtension,
|
PeekViewExtension,
|
||||||
QuickSearchExtension,
|
QuickSearchExtension,
|
||||||
@@ -67,6 +68,12 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
} from '@blocksuite/affine/store';
|
} from '@blocksuite/affine/store';
|
||||||
import type { ReferenceParams } from '@blocksuite/affine-model';
|
import type { ReferenceParams } from '@blocksuite/affine-model';
|
||||||
|
import {
|
||||||
|
CenterPeekIcon,
|
||||||
|
ExpandFullIcon,
|
||||||
|
OpenInNewIcon,
|
||||||
|
SplitViewIcon,
|
||||||
|
} from '@blocksuite/icons/lit';
|
||||||
import { type FrameworkProvider } from '@toeverything/infra';
|
import { type FrameworkProvider } from '@toeverything/infra';
|
||||||
import { type TemplateResult } from 'lit';
|
import { type TemplateResult } from 'lit';
|
||||||
import { customElement } from 'lit/decorators.js';
|
import { customElement } from 'lit/decorators.js';
|
||||||
@@ -236,18 +243,35 @@ export function patchNotificationService({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function patchEmbedLinkedDocBlockConfig(framework: FrameworkProvider) {
|
export function patchOpenDocExtension() {
|
||||||
const getWorkbench = () => framework.get(WorkbenchService).workbench;
|
const openDocConfig: OpenDocConfig = {
|
||||||
|
items: [
|
||||||
return EmbedLinkedDocBlockConfigExtension({
|
{
|
||||||
handleClick(e, _, refInfo) {
|
type: 'open-in-active-view',
|
||||||
if (isNewTabTrigger(e)) {
|
label: I18n['com.affine.peek-view-controls.open-doc'](),
|
||||||
const workbench = getWorkbench();
|
icon: ExpandFullIcon(),
|
||||||
workbench.openDoc(refInfo.pageId, { at: 'new-tab' });
|
},
|
||||||
e.preventDefault();
|
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) {
|
export function patchPeekViewService(service: PeekViewService) {
|
||||||
|
|||||||
@@ -26,7 +26,16 @@ export const usePageHelper = (docCollection: Workspace) => {
|
|||||||
const appSidebar = appSidebarService.sidebar;
|
const appSidebar = appSidebarService.sidebar;
|
||||||
|
|
||||||
const createPageAndOpen = useCallback(
|
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);
|
appSidebar.setHovering(false);
|
||||||
const docProps: DocProps = {
|
const docProps: DocProps = {
|
||||||
note: editorSettingService.editorSetting.get('affine:note'),
|
note: editorSettingService.editorSetting.get('affine:note'),
|
||||||
@@ -37,10 +46,12 @@ export const usePageHelper = (docCollection: Workspace) => {
|
|||||||
docRecordList.doc$(page.id).value?.setPrimaryMode(mode);
|
docRecordList.doc$(page.id).value?.setPrimaryMode(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (open !== false)
|
if (options.show !== false) {
|
||||||
workbench.openDoc(page.id, {
|
workbench.openDoc(page.id, {
|
||||||
at: open === 'new-tab' ? 'new-tab' : 'active',
|
at: options.at,
|
||||||
|
show: options.show,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
return page;
|
return page;
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
@@ -53,8 +64,16 @@ export const usePageHelper = (docCollection: Workspace) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const createEdgelessAndOpen = useCallback(
|
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]
|
[createPageAndOpen]
|
||||||
);
|
);
|
||||||
@@ -103,8 +122,13 @@ export const usePageHelper = (docCollection: Workspace) => {
|
|||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return {
|
return {
|
||||||
createPage: (mode?: DocMode, open?: boolean | 'new-tab') =>
|
createPage: (
|
||||||
createPageAndOpen(mode, open),
|
mode?: DocMode,
|
||||||
|
options?: {
|
||||||
|
at?: 'new-tab' | 'tail' | 'active';
|
||||||
|
show?: boolean;
|
||||||
|
}
|
||||||
|
) => createPageAndOpen(mode, options),
|
||||||
createEdgeless: createEdgelessAndOpen,
|
createEdgeless: createEdgelessAndOpen,
|
||||||
importFile: importFileAndOpen,
|
importFile: importFileAndOpen,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import type { Tag } from '@affine/core/modules/tag';
|
|||||||
import { TagService } from '@affine/core/modules/tag';
|
import { TagService } from '@affine/core/modules/tag';
|
||||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
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 type { Collection } from '@affine/env/filter';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
@@ -92,15 +92,11 @@ export const PageListHeader = () => {
|
|||||||
<PageListNewPageButton
|
<PageListNewPageButton
|
||||||
size="small"
|
size="small"
|
||||||
testId="new-page-button-trigger"
|
testId="new-page-button-trigger"
|
||||||
onCreateEdgeless={e =>
|
onCreateEdgeless={e => createEdgeless({ at: inferOpenMode(e) })}
|
||||||
createEdgeless(isNewTabTrigger(e) ? 'new-tab' : true)
|
|
||||||
}
|
|
||||||
onCreatePage={e =>
|
onCreatePage={e =>
|
||||||
createPage('page' as DocMode, isNewTabTrigger(e) ? 'new-tab' : true)
|
createPage('page' as DocMode, { at: inferOpenMode(e) })
|
||||||
}
|
|
||||||
onCreateDoc={e =>
|
|
||||||
createPage(undefined, isNewTabTrigger(e) ? 'new-tab' : true)
|
|
||||||
}
|
}
|
||||||
|
onCreateDoc={e => createPage(undefined, { at: inferOpenMode(e) })}
|
||||||
onImportFile={onImportFile}
|
onImportFile={onImportFile}
|
||||||
>
|
>
|
||||||
<div className={styles.buttonText}>{t['New Page']()}</div>
|
<div className={styles.buttonText}>{t['New Page']()}</div>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-m
|
|||||||
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
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 type { Filter } from '@affine/env/filter';
|
||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||||
@@ -89,15 +89,9 @@ export const AllPageHeader = ({
|
|||||||
styles.headerCreateNewButton,
|
styles.headerCreateNewButton,
|
||||||
!showCreateNew && styles.headerCreateNewButtonHidden
|
!showCreateNew && styles.headerCreateNewButtonHidden
|
||||||
)}
|
)}
|
||||||
onCreateEdgeless={e =>
|
onCreateEdgeless={e => createEdgeless({ at: inferOpenMode(e) })}
|
||||||
createEdgeless(isNewTabTrigger(e) ? 'new-tab' : true)
|
onCreatePage={e => createPage('page', { at: inferOpenMode(e) })}
|
||||||
}
|
onCreateDoc={e => createPage(undefined, { at: inferOpenMode(e) })}
|
||||||
onCreatePage={e =>
|
|
||||||
createPage('page', isNewTabTrigger(e) ? 'new-tab' : true)
|
|
||||||
}
|
|
||||||
onCreateDoc={e =>
|
|
||||||
createPage(undefined, isNewTabTrigger(e) ? 'new-tab' : true)
|
|
||||||
}
|
|
||||||
onImportFile={onImportFile}
|
onImportFile={onImportFile}
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import { DocService } from '@affine/core/modules/doc';
|
|||||||
import { EditorService } from '@affine/core/modules/editor';
|
import { EditorService } from '@affine/core/modules/editor';
|
||||||
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
||||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||||
|
import { PeekViewService } from '@affine/core/modules/peek-view';
|
||||||
import { RecentDocsService } from '@affine/core/modules/quicksearch';
|
import { RecentDocsService } from '@affine/core/modules/quicksearch';
|
||||||
import { ViewService } from '@affine/core/modules/workbench';
|
import { ViewService } from '@affine/core/modules/workbench';
|
||||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||||
|
import { isNewTabTrigger } from '@affine/core/utils';
|
||||||
import { RefNodeSlotsProvider } from '@blocksuite/affine/blocks';
|
import { RefNodeSlotsProvider } from '@blocksuite/affine/blocks';
|
||||||
import { DisposableGroup } from '@blocksuite/affine/global/utils';
|
import { DisposableGroup } from '@blocksuite/affine/global/utils';
|
||||||
import { type AffineEditorContainer } from '@blocksuite/affine/presets';
|
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 { useRegisterBlocksuiteEditorCommands } from '../../../../components/hooks/affine/use-register-blocksuite-editor-commands';
|
||||||
import { useActiveBlocksuiteEditor } from '../../../../components/hooks/use-block-suite-editor';
|
import { useActiveBlocksuiteEditor } from '../../../../components/hooks/use-block-suite-editor';
|
||||||
import { usePageDocumentTitle } from '../../../../components/hooks/use-global-state';
|
import { usePageDocumentTitle } from '../../../../components/hooks/use-global-state';
|
||||||
import { useNavigateHelper } from '../../../../components/hooks/use-navigate-helper';
|
|
||||||
import { PageDetailEditor } from '../../../../components/page-detail-editor';
|
import { PageDetailEditor } from '../../../../components/page-detail-editor';
|
||||||
import { TrashPageFooter } from '../../../../components/pure/trash-page-footer';
|
import { TrashPageFooter } from '../../../../components/pure/trash-page-footer';
|
||||||
import { TopTip } from '../../../../components/top-tip';
|
import { TopTip } from '../../../../components/top-tip';
|
||||||
@@ -81,7 +82,6 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
const editor = editorService.editor;
|
const editor = editorService.editor;
|
||||||
const view = viewService.view;
|
const view = viewService.view;
|
||||||
const workspace = workspaceService.workspace;
|
const workspace = workspaceService.workspace;
|
||||||
const docCollection = workspace.docCollection;
|
|
||||||
const globalContext = globalContextService.globalContext;
|
const globalContext = globalContextService.globalContext;
|
||||||
const doc = docService.doc;
|
const doc = docService.doc;
|
||||||
|
|
||||||
@@ -89,7 +89,6 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
const activeSidebarTab = useLiveData(view.activeSidebarTab$);
|
const activeSidebarTab = useLiveData(view.activeSidebarTab$);
|
||||||
|
|
||||||
const isInTrash = useLiveData(doc.meta$.map(meta => meta.trash));
|
const isInTrash = useLiveData(doc.meta$.map(meta => meta.trash));
|
||||||
const { openPage, jumpToPageBlock } = useNavigateHelper();
|
|
||||||
const editorContainer = useLiveData(editor.editorContainer$);
|
const editorContainer = useLiveData(editor.editorContainer$);
|
||||||
|
|
||||||
const isSideBarOpen = useLiveData(workbench.sidebarOpen$);
|
const isSideBarOpen = useLiveData(workbench.sidebarOpen$);
|
||||||
@@ -97,6 +96,8 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
const chatPanelRef = useRef<ChatPanel | null>(null);
|
const chatPanelRef = useRef<ChatPanel | null>(null);
|
||||||
const { setDocReadonly } = useDocMetaHelper();
|
const { setDocReadonly } = useDocMetaHelper();
|
||||||
|
|
||||||
|
const peekView = useService(PeekViewService).peekView;
|
||||||
|
|
||||||
const isActiveView = useIsActiveView();
|
const isActiveView = useIsActiveView();
|
||||||
// TODO(@eyhn): remove jotai here
|
// TODO(@eyhn): remove jotai here
|
||||||
const [_, setActiveBlockSuiteEditor] = useActiveBlocksuiteEditor();
|
const [_, setActiveBlockSuiteEditor] = useActiveBlocksuiteEditor();
|
||||||
@@ -178,25 +179,50 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
const refNodeSlots = std.getOptional(RefNodeSlotsProvider);
|
const refNodeSlots = std.getOptional(RefNodeSlotsProvider);
|
||||||
if (refNodeSlots) {
|
if (refNodeSlots) {
|
||||||
disposable.add(
|
disposable.add(
|
||||||
refNodeSlots.docLinkClicked.on(({ pageId, params }) => {
|
// the event should not be emitted by AffineReference
|
||||||
if (params) {
|
refNodeSlots.docLinkClicked.on(
|
||||||
const { mode, blockIds, elementIds } = params;
|
({ pageId, params, openMode, event }) => {
|
||||||
jumpToPageBlock(
|
openMode ??=
|
||||||
docCollection.id,
|
event && isNewTabTrigger(event)
|
||||||
pageId,
|
? 'open-in-new-tab'
|
||||||
mode,
|
: 'open-in-active-view';
|
||||||
blockIds,
|
if (openMode !== 'open-in-center-peek') {
|
||||||
elementIds
|
const at = (() => {
|
||||||
);
|
if (openMode === 'open-in-active-view') {
|
||||||
return;
|
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();
|
disposable.dispose();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[editor, openPage, docCollection.id, jumpToPageBlock]
|
[editor, workbench, peekView]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [hasScrollTop, setHasScrollTop] = useState(false);
|
const [hasScrollTop, setHasScrollTop] = useState(false);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export const AppTabCreate = ({ tab }: AppTabCustomFCProps) => {
|
|||||||
const createPage = useCallback(
|
const createPage = useCallback(
|
||||||
(isActive: boolean) => {
|
(isActive: boolean) => {
|
||||||
if (isActive) return;
|
if (isActive) return;
|
||||||
const doc = pageHelper.createPage(undefined, false);
|
const doc = pageHelper.createPage(undefined, { show: false });
|
||||||
workbench.openDoc({ docId: doc.id, fromTab: 'true' });
|
workbench.openDoc({ docId: doc.id, fromTab: 'true' });
|
||||||
track.$.navigationPanel.$.createDoc();
|
track.$.navigationPanel.$.createDoc();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { IconButton } from '@affine/component';
|
import { IconButton } from '@affine/component';
|
||||||
import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
|
import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
|
||||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
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 { useI18n } from '@affine/i18n';
|
||||||
import track from '@affine/track';
|
import track from '@affine/track';
|
||||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||||
@@ -25,7 +25,7 @@ export function AddPageButton({ className, style }: AddPageButtonProps) {
|
|||||||
|
|
||||||
const onClickNewPage = useCallback(
|
const onClickNewPage = useCallback(
|
||||||
(e?: MouseEvent) => {
|
(e?: MouseEvent) => {
|
||||||
pageHelper.createPage(undefined, isNewTabTrigger(e) ? 'new-tab' : true);
|
pageHelper.createPage(undefined, { at: inferOpenMode(e) });
|
||||||
track.$.navigationPanel.$.createDoc();
|
track.$.navigationPanel.$.createDoc();
|
||||||
},
|
},
|
||||||
[pageHelper]
|
[pageHelper]
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
} from '@affine/core/modules/favorite';
|
} from '@affine/core/modules/favorite';
|
||||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
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 { useI18n } from '@affine/i18n';
|
||||||
import { track } from '@affine/track';
|
import { track } from '@affine/track';
|
||||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||||
@@ -81,10 +81,7 @@ export const ExplorerFavorites = () => {
|
|||||||
|
|
||||||
const handleCreateNewFavoriteDoc: MouseEventHandler = useCallback(
|
const handleCreateNewFavoriteDoc: MouseEventHandler = useCallback(
|
||||||
e => {
|
e => {
|
||||||
const newDoc = createPage(
|
const newDoc = createPage(undefined, { at: inferOpenMode(e) });
|
||||||
undefined,
|
|
||||||
isNewTabTrigger(e) ? 'new-tab' : true
|
|
||||||
);
|
|
||||||
favoriteService.favoriteList.add(
|
favoriteService.favoriteList.add(
|
||||||
'doc',
|
'doc',
|
||||||
newDoc.id,
|
newDoc.id,
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ export class DesktopWorkbenchNewTabHandler
|
|||||||
constructor(private readonly electronApi: DesktopApiService) {
|
constructor(private readonly electronApi: DesktopApiService) {
|
||||||
super();
|
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;
|
const path = typeof to === 'string' ? parsePath(to) : to;
|
||||||
this.electronApi.api.handler.ui
|
this.electronApi.api.handler.ui
|
||||||
.addTab({
|
.addTab({
|
||||||
basename,
|
basename,
|
||||||
view: { path },
|
view: { path },
|
||||||
show: false,
|
show: show,
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch(console.error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,6 +240,7 @@ export const SplitViewPanel = memo(function SplitViewPanel({
|
|||||||
dropTargetRef.current = node;
|
dropTargetRef.current = node;
|
||||||
dragRef.current = node;
|
dragRef.current = node;
|
||||||
}}
|
}}
|
||||||
|
data-is-active={isActive && views.length > 1 && !draggingEntity}
|
||||||
className={styles.splitViewPanelDrag}
|
className={styles.splitViewPanelDrag}
|
||||||
>
|
>
|
||||||
<div draggable={false} className={styles.splitViewPanelContent}>
|
<div draggable={false} className={styles.splitViewPanelContent}>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ export const splitViewPanelDrag = style({
|
|||||||
transition: 'box-shadow 0.5s cubic-bezier(0.16, 1, 0.3, 1)',
|
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')}`,
|
boxShadow: `inset 0 0 0 1px ${cssVarV2('button/primary')}`,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useDraggable } from '@affine/component';
|
import { useDraggable } from '@affine/component';
|
||||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||||
import type { AffineDNDData, AffineDNDEntity } from '@affine/core/types/dnd';
|
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 { useLiveData, useServices } from '@toeverything/infra';
|
||||||
import { type To } from 'history';
|
import { type To } from 'history';
|
||||||
import { forwardRef, type MouseEvent } from 'react';
|
import { forwardRef, type MouseEvent } from 'react';
|
||||||
@@ -62,13 +62,8 @@ export const WorkbenchLink = forwardRef<HTMLAnchorElement, WorkbenchLinkProps>(
|
|||||||
if (event.defaultPrevented) {
|
if (event.defaultPrevented) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const at = (() => {
|
const at = inferOpenAt(event);
|
||||||
if (isNewTabTrigger(event)) {
|
workbench.open(to, { at, replaceHistory, show: false });
|
||||||
return BUILD_CONFIG.isElectron && event.altKey ? 'tail' : 'new-tab';
|
|
||||||
}
|
|
||||||
return 'active';
|
|
||||||
})();
|
|
||||||
workbench.open(to, { at, replaceHistory });
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,11 +8,21 @@ export function preventDefault(event: BaseSyntheticEvent) {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stopEvent(event: BaseSyntheticEvent) {
|
export function isNewTabTrigger(event?: React.MouseEvent | MouseEvent) {
|
||||||
event.stopPropagation();
|
return event
|
||||||
event.preventDefault();
|
? (event.ctrlKey || event.metaKey || event.button === 1) && !event.altKey
|
||||||
|
: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNewTabTrigger(event?: React.MouseEvent | MouseEvent) {
|
export function isNewViewTrigger(event?: React.MouseEvent | MouseEvent) {
|
||||||
return event ? event.ctrlKey || event.metaKey || event.button === 1 : false;
|
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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1042,6 +1042,7 @@
|
|||||||
"com.affine.peek-view-controls.open-attachment": "Open this attachment",
|
"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-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-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.creation": "New",
|
||||||
"com.affine.quicksearch.group.searchfor": "Search for \"{{query}}\"",
|
"com.affine.quicksearch.group.searchfor": "Search for \"{{query}}\"",
|
||||||
"com.affine.resetSyncStatus.button": "Reset sync",
|
"com.affine.resetSyncStatus.button": "Reset sync",
|
||||||
|
|||||||
@@ -28,8 +28,8 @@ test('open split view', async ({ page }) => {
|
|||||||
await expect(page.getByTestId('split-view-label')).toHaveCount(2);
|
await expect(page.getByTestId('split-view-label')).toHaveCount(2);
|
||||||
await expectTabTitle(page, 0, ['Untitled', 'hi from another page']);
|
await expectTabTitle(page, 0, ['Untitled', 'hi from another page']);
|
||||||
|
|
||||||
// the second split view should be active
|
// the first split view should be active
|
||||||
await expectActiveTab(page, 0, 1);
|
await expectActiveTab(page, 0, 0);
|
||||||
|
|
||||||
// by clicking the first split view label, the first split view should be active
|
// by clicking the first split view label, the first split view should be active
|
||||||
await page.getByTestId('split-view-label').nth(0).click();
|
await page.getByTestId('split-view-label').nth(0).click();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.web.json",
|
"extends": "../../tsconfig.web.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
"rootDir": "./e2e",
|
"rootDir": "./e2e",
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
|
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import chalk from 'chalk';
|
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;
|
export const newLineSeparator = /\r\n|[\n\r\x85\u2028\u2029]/g;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import once from 'lodash-es/once';
|
import { once } from 'lodash-es';
|
||||||
|
|
||||||
import { Logger } from './logger';
|
import { Logger } from './logger';
|
||||||
import { Package, readPackageJson } from './package';
|
import { Package, readPackageJson } from './package';
|
||||||
|
|||||||
Reference in New Issue
Block a user