feat: linked doc supports aliases (#9009)
Closes: [AF-1882](https://linear.app/affine-design/issue/AF-1882/实现-generatedocurlservice) Upstreams: https://github.com/toeverything/blocksuite/pull/8806
This commit is contained in:
@@ -30,6 +30,7 @@ import * as styles from './styles.css';
|
|||||||
interface AffinePageReferenceProps {
|
interface AffinePageReferenceProps {
|
||||||
pageId: string;
|
pageId: string;
|
||||||
params?: URLSearchParams;
|
params?: URLSearchParams;
|
||||||
|
title?: string | null; // title alias
|
||||||
className?: string;
|
className?: string;
|
||||||
Icon?: ComponentType;
|
Icon?: ComponentType;
|
||||||
onClick?: (e: MouseEvent) => void;
|
onClick?: (e: MouseEvent) => void;
|
||||||
@@ -38,20 +39,21 @@ interface AffinePageReferenceProps {
|
|||||||
function AffinePageReferenceInner({
|
function AffinePageReferenceInner({
|
||||||
pageId,
|
pageId,
|
||||||
params,
|
params,
|
||||||
|
title,
|
||||||
Icon: UserIcon,
|
Icon: UserIcon,
|
||||||
}: AffinePageReferenceProps) {
|
}: AffinePageReferenceProps) {
|
||||||
const docDisplayMetaService = useService(DocDisplayMetaService);
|
const docDisplayMetaService = useService(DocDisplayMetaService);
|
||||||
const docsService = useService(DocsService);
|
const docsService = useService(DocsService);
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
|
|
||||||
let linkWithMode: DocMode | null = null;
|
let referenceWithMode: DocMode | null = null;
|
||||||
let linkToNode = false;
|
let referenceToNode = false;
|
||||||
if (params) {
|
if (params) {
|
||||||
const m = params.get('mode');
|
const m = params.get('mode');
|
||||||
if (m && (m === 'page' || m === 'edgeless')) {
|
if (m && (m === 'page' || m === 'edgeless')) {
|
||||||
linkWithMode = m as DocMode;
|
referenceWithMode = m as DocMode;
|
||||||
}
|
}
|
||||||
linkToNode = params.has('blockIds') || params.has('elementIds');
|
referenceToNode = params.has('blockIds') || params.has('elementIds');
|
||||||
}
|
}
|
||||||
|
|
||||||
const Icon = useLiveData(
|
const Icon = useLiveData(
|
||||||
@@ -61,25 +63,33 @@ function AffinePageReferenceInner({
|
|||||||
}
|
}
|
||||||
return get(
|
return get(
|
||||||
docDisplayMetaService.icon$(pageId, {
|
docDisplayMetaService.icon$(pageId, {
|
||||||
mode: linkWithMode ?? undefined,
|
mode: referenceWithMode ?? undefined,
|
||||||
reference: true,
|
reference: true,
|
||||||
referenceToNode: linkToNode,
|
referenceToNode,
|
||||||
|
hasTitleAlias: Boolean(title),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const notFound = !useLiveData(docsService.list.doc$(pageId));
|
const notFound = !useLiveData(docsService.list.doc$(pageId));
|
||||||
|
|
||||||
let title = useLiveData(
|
const docTitle = useLiveData(
|
||||||
docDisplayMetaService.title$(pageId, { reference: true })
|
docDisplayMetaService.title$(pageId, { reference: true })
|
||||||
);
|
);
|
||||||
|
|
||||||
title = notFound ? i18n.t('com.affine.notFoundPage.title') : title;
|
if (notFound) {
|
||||||
|
title = i18n.t('com.affine.notFoundPage.title');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!title) {
|
||||||
|
title = i18n.t(docTitle);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={notFound ? styles.notFound : ''}>
|
<span className={notFound ? styles.notFound : ''}>
|
||||||
<Icon className={styles.pageReferenceIcon} />
|
<Icon className={styles.pageReferenceIcon} />
|
||||||
<span className="affine-reference-title">{i18n.t(title)}</span>
|
<span className="affine-reference-title">{title}</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -87,6 +97,7 @@ function AffinePageReferenceInner({
|
|||||||
export function AffinePageReference({
|
export function AffinePageReference({
|
||||||
pageId,
|
pageId,
|
||||||
params,
|
params,
|
||||||
|
title,
|
||||||
className,
|
className,
|
||||||
Icon,
|
Icon,
|
||||||
onClick: userOnClick,
|
onClick: userOnClick,
|
||||||
@@ -152,7 +163,12 @@ export function AffinePageReference({
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={clsx(styles.pageReferenceLink, className)}
|
className={clsx(styles.pageReferenceLink, className)}
|
||||||
>
|
>
|
||||||
<AffinePageReferenceInner pageId={pageId} params={params} Icon={Icon} />
|
<AffinePageReferenceInner
|
||||||
|
pageId={pageId}
|
||||||
|
params={params}
|
||||||
|
title={title}
|
||||||
|
Icon={Icon}
|
||||||
|
/>
|
||||||
</WorkbenchLink>
|
</WorkbenchLink>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -161,6 +177,7 @@ export function AffineSharedPageReference({
|
|||||||
pageId,
|
pageId,
|
||||||
docCollection,
|
docCollection,
|
||||||
params,
|
params,
|
||||||
|
title,
|
||||||
Icon,
|
Icon,
|
||||||
onClick: userOnClick,
|
onClick: userOnClick,
|
||||||
}: AffinePageReferenceProps & {
|
}: AffinePageReferenceProps & {
|
||||||
@@ -213,7 +230,12 @@ export function AffineSharedPageReference({
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={styles.pageReferenceLink}
|
className={styles.pageReferenceLink}
|
||||||
>
|
>
|
||||||
<AffinePageReferenceInner pageId={pageId} params={params} Icon={Icon} />
|
<AffinePageReferenceInner
|
||||||
|
pageId={pageId}
|
||||||
|
params={params}
|
||||||
|
title={title}
|
||||||
|
Icon={Icon}
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import {
|
|||||||
patchEmbedLinkedDocBlockConfig,
|
patchEmbedLinkedDocBlockConfig,
|
||||||
patchForMobile,
|
patchForMobile,
|
||||||
patchForSharedPage,
|
patchForSharedPage,
|
||||||
|
patchGenerateDocUrlExtension,
|
||||||
patchNotificationService,
|
patchNotificationService,
|
||||||
patchParseDocUrlExtension,
|
patchParseDocUrlExtension,
|
||||||
patchPeekViewService,
|
patchPeekViewService,
|
||||||
@@ -112,6 +113,8 @@ const usePatchSpecs = (shared: boolean, mode: DocMode) => {
|
|||||||
const pageId = data.pageId;
|
const pageId = data.pageId;
|
||||||
if (!pageId) return <span />;
|
if (!pageId) return <span />;
|
||||||
|
|
||||||
|
// title alias
|
||||||
|
const title = data.title;
|
||||||
const params = toURLSearchParams(data.params);
|
const params = toURLSearchParams(data.params);
|
||||||
|
|
||||||
if (workspaceService.workspace.openOptions.isSharedMode) {
|
if (workspaceService.workspace.openOptions.isSharedMode) {
|
||||||
@@ -120,11 +123,14 @@ const usePatchSpecs = (shared: boolean, mode: DocMode) => {
|
|||||||
docCollection={workspaceService.workspace.docCollection}
|
docCollection={workspaceService.workspace.docCollection}
|
||||||
pageId={pageId}
|
pageId={pageId}
|
||||||
params={params}
|
params={params}
|
||||||
|
title={title}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <AffinePageReference pageId={pageId} params={params} />;
|
return (
|
||||||
|
<AffinePageReference pageId={pageId} params={params} title={title} />
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}, [workspaceService]);
|
}, [workspaceService]);
|
||||||
|
|
||||||
@@ -147,6 +153,7 @@ const usePatchSpecs = (shared: boolean, mode: DocMode) => {
|
|||||||
patched = patched.concat(patchPeekViewService(peekViewService));
|
patched = patched.concat(patchPeekViewService(peekViewService));
|
||||||
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(patchQuickSearchService(framework));
|
patched = patched.concat(patchQuickSearchService(framework));
|
||||||
patched = patched.concat(patchEmbedLinkedDocBlockConfig(framework));
|
patched = patched.concat(patchEmbedLinkedDocBlockConfig(framework));
|
||||||
if (shared) {
|
if (shared) {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ function createCopyLinkToBlockMenuItem(
|
|||||||
const options: UseSharingUrl = {
|
const options: UseSharingUrl = {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
pageId,
|
pageId,
|
||||||
shareMode: mode,
|
mode,
|
||||||
blockIds: [model.id],
|
blockIds: [model.id],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
toReactNode,
|
toReactNode,
|
||||||
type useConfirmModal,
|
type useConfirmModal,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
|
import { WorkspaceServerService } from '@affine/core/modules/cloud';
|
||||||
import type { EditorService } from '@affine/core/modules/editor';
|
import type { EditorService } from '@affine/core/modules/editor';
|
||||||
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||||
import { resolveLinkToDoc } from '@affine/core/modules/navigation';
|
import { resolveLinkToDoc } from '@affine/core/modules/navigation';
|
||||||
@@ -47,6 +48,7 @@ import {
|
|||||||
EdgelessRootBlockComponent,
|
EdgelessRootBlockComponent,
|
||||||
EmbedLinkedDocBlockComponent,
|
EmbedLinkedDocBlockComponent,
|
||||||
EmbedLinkedDocBlockConfigExtension,
|
EmbedLinkedDocBlockConfigExtension,
|
||||||
|
GenerateDocUrlExtension,
|
||||||
MobileSpecsPatches,
|
MobileSpecsPatches,
|
||||||
NotificationExtension,
|
NotificationExtension,
|
||||||
ParseDocUrlExtension,
|
ParseDocUrlExtension,
|
||||||
@@ -55,6 +57,7 @@ import {
|
|||||||
ReferenceNodeConfigExtension,
|
ReferenceNodeConfigExtension,
|
||||||
} from '@blocksuite/affine/blocks';
|
} from '@blocksuite/affine/blocks';
|
||||||
import { type BlockSnapshot, Text } from '@blocksuite/affine/store';
|
import { type BlockSnapshot, Text } from '@blocksuite/affine/store';
|
||||||
|
import type { ReferenceParams } from '@blocksuite/affine-model';
|
||||||
import {
|
import {
|
||||||
AIChatBlockSchema,
|
AIChatBlockSchema,
|
||||||
type DocProps,
|
type DocProps,
|
||||||
@@ -68,6 +71,7 @@ import { customElement } from 'lit/decorators.js';
|
|||||||
import { literal } from 'lit/static-html.js';
|
import { literal } from 'lit/static-html.js';
|
||||||
import { pick } from 'lodash-es';
|
import { pick } from 'lodash-es';
|
||||||
|
|
||||||
|
import { generateUrl } from '../../../../hooks/affine/use-share-url';
|
||||||
import { createKeyboardToolbarConfig } from './widgets/keyboard-toolbar';
|
import { createKeyboardToolbarConfig } from './widgets/keyboard-toolbar';
|
||||||
|
|
||||||
export type ReferenceReactRenderer = (
|
export type ReferenceReactRenderer = (
|
||||||
@@ -110,13 +114,13 @@ export function patchReferenceRenderer(
|
|||||||
reactToLit: (element: ElementOrFactory) => TemplateResult,
|
reactToLit: (element: ElementOrFactory) => TemplateResult,
|
||||||
reactRenderer: ReferenceReactRenderer
|
reactRenderer: ReferenceReactRenderer
|
||||||
): ExtensionType {
|
): ExtensionType {
|
||||||
const litRenderer = (reference: AffineReference) => {
|
const customContent = (reference: AffineReference) => {
|
||||||
const node = reactRenderer(reference);
|
const node = reactRenderer(reference);
|
||||||
return reactToLit(node);
|
return reactToLit(node);
|
||||||
};
|
};
|
||||||
|
|
||||||
return ReferenceNodeConfigExtension({
|
return ReferenceNodeConfigExtension({
|
||||||
customContent: litRenderer,
|
customContent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,18 +467,32 @@ export function patchParseDocUrlExtension(framework: FrameworkProvider) {
|
|||||||
const info = resolveLinkToDoc(url);
|
const info = resolveLinkToDoc(url);
|
||||||
if (!info || info.workspaceId !== workspaceService.workspace.id) return;
|
if (!info || info.workspaceId !== workspaceService.workspace.id) return;
|
||||||
|
|
||||||
return {
|
delete info.refreshKey;
|
||||||
docId: info.docId,
|
|
||||||
blockIds: info.blockIds,
|
return info;
|
||||||
elementIds: info.elementIds,
|
|
||||||
mode: info.mode,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return [ParseDocUrl];
|
return [ParseDocUrl];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function patchGenerateDocUrlExtension(framework: FrameworkProvider) {
|
||||||
|
const workspaceService = framework.get(WorkspaceService);
|
||||||
|
const workspaceServerService = framework.get(WorkspaceServerService);
|
||||||
|
const GenerateDocUrl = GenerateDocUrlExtension({
|
||||||
|
generateDocUrl(pageId: string, params?: ReferenceParams) {
|
||||||
|
return generateUrl({
|
||||||
|
...params,
|
||||||
|
pageId,
|
||||||
|
workspaceId: workspaceService.workspace.id,
|
||||||
|
baseUrl: workspaceServerService.server?.baseUrl ?? location.origin,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return [GenerateDocUrl];
|
||||||
|
}
|
||||||
|
|
||||||
export function patchEdgelessClipboard() {
|
export function patchEdgelessClipboard() {
|
||||||
class EdgelessClipboardWatcher extends BlockServiceWatcher {
|
class EdgelessClipboardWatcher extends BlockServiceWatcher {
|
||||||
static override readonly flavour = 'affine:page';
|
static override readonly flavour = 'affine:page';
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ function createCopyLinkToBlockMenuItem(
|
|||||||
const mode = editor.mode$.value;
|
const mode = editor.mode$.value;
|
||||||
const pageId = editor.doc.id;
|
const pageId = editor.doc.id;
|
||||||
const workspaceId = editor.doc.workspace.id;
|
const workspaceId = editor.doc.workspace.id;
|
||||||
const options: UseSharingUrl = { workspaceId, pageId, shareMode: mode };
|
const options: UseSharingUrl = { workspaceId, pageId, mode };
|
||||||
let type = '';
|
let type = '';
|
||||||
|
|
||||||
if (mode === 'page') {
|
if (mode === 'page') {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { useCallback } from 'react';
|
|||||||
export type UseSharingUrl = {
|
export type UseSharingUrl = {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
pageId: string;
|
pageId: string;
|
||||||
shareMode?: DocMode;
|
mode?: DocMode;
|
||||||
blockIds?: string[];
|
blockIds?: string[];
|
||||||
elementIds?: string[];
|
elementIds?: string[];
|
||||||
xywh?: string; // not needed currently
|
xywh?: string; // not needed currently
|
||||||
@@ -30,7 +30,7 @@ export const generateUrl = ({
|
|||||||
pageId,
|
pageId,
|
||||||
blockIds,
|
blockIds,
|
||||||
elementIds,
|
elementIds,
|
||||||
shareMode: mode,
|
mode,
|
||||||
xywh, // not needed currently
|
xywh, // not needed currently
|
||||||
}: UseSharingUrl & { baseUrl: string }) => {
|
}: UseSharingUrl & { baseUrl: string }) => {
|
||||||
try {
|
try {
|
||||||
@@ -38,23 +38,24 @@ export const generateUrl = ({
|
|||||||
const search = toURLSearchParams({ mode, blockIds, elementIds, xywh });
|
const search = toURLSearchParams({ mode, blockIds, elementIds, xywh });
|
||||||
if (search?.size) url.search = search.toString();
|
if (search?.size) url.search = search.toString();
|
||||||
return url.toString();
|
return url.toString();
|
||||||
} catch {
|
} catch (err) {
|
||||||
return null;
|
console.error(err);
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getShareLinkType = ({
|
const getShareLinkType = ({
|
||||||
shareMode,
|
mode,
|
||||||
blockIds,
|
blockIds,
|
||||||
elementIds,
|
elementIds,
|
||||||
}: {
|
}: {
|
||||||
shareMode?: DocMode;
|
mode?: DocMode;
|
||||||
blockIds?: string[];
|
blockIds?: string[];
|
||||||
elementIds?: string[];
|
elementIds?: string[];
|
||||||
}) => {
|
}) => {
|
||||||
if (shareMode === 'page') {
|
if (mode === 'page') {
|
||||||
return 'doc';
|
return 'doc';
|
||||||
} else if (shareMode === 'edgeless') {
|
} else if (mode === 'edgeless') {
|
||||||
return 'whiteboard';
|
return 'whiteboard';
|
||||||
} else if (blockIds && blockIds.length > 0) {
|
} else if (blockIds && blockIds.length > 0) {
|
||||||
return 'block';
|
return 'block';
|
||||||
@@ -131,17 +132,17 @@ export const useSharingUrl = ({ workspaceId, pageId }: UseSharingUrl) => {
|
|||||||
const serverService = useService(ServerService);
|
const serverService = useService(ServerService);
|
||||||
|
|
||||||
const onClickCopyLink = useCallback(
|
const onClickCopyLink = useCallback(
|
||||||
(shareMode?: DocMode, blockIds?: string[], elementIds?: string[]) => {
|
(mode?: DocMode, blockIds?: string[], elementIds?: string[]) => {
|
||||||
const sharingUrl = generateUrl({
|
const sharingUrl = generateUrl({
|
||||||
baseUrl: serverService.server.baseUrl,
|
baseUrl: serverService.server.baseUrl,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
pageId,
|
pageId,
|
||||||
blockIds,
|
blockIds,
|
||||||
elementIds,
|
elementIds,
|
||||||
shareMode, // if view is not provided, use the current view
|
mode, // if view is not provided, use the current view
|
||||||
});
|
});
|
||||||
const type = getShareLinkType({
|
const type = getShareLinkType({
|
||||||
shareMode,
|
mode,
|
||||||
blockIds,
|
blockIds,
|
||||||
elementIds,
|
elementIds,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { extractEmojiIcon } from '@affine/core/utils';
|
import { extractEmojiIcon } from '@affine/core/utils';
|
||||||
import { i18nTime } from '@affine/i18n';
|
import { i18nTime } from '@affine/i18n';
|
||||||
import {
|
import {
|
||||||
|
AliasIcon as LitAliasIcon,
|
||||||
BlockLinkIcon as LitBlockLinkIcon,
|
BlockLinkIcon as LitBlockLinkIcon,
|
||||||
EdgelessIcon as LitEdgelessIcon,
|
EdgelessIcon as LitEdgelessIcon,
|
||||||
LinkedEdgelessIcon as LitLinkedEdgelessIcon,
|
LinkedEdgelessIcon as LitLinkedEdgelessIcon,
|
||||||
@@ -11,6 +12,7 @@ import {
|
|||||||
YesterdayIcon as LitYesterdayIcon,
|
YesterdayIcon as LitYesterdayIcon,
|
||||||
} from '@blocksuite/icons/lit';
|
} from '@blocksuite/icons/lit';
|
||||||
import {
|
import {
|
||||||
|
AliasIcon,
|
||||||
BlockLinkIcon,
|
BlockLinkIcon,
|
||||||
EdgelessIcon,
|
EdgelessIcon,
|
||||||
LinkedEdgelessIcon,
|
LinkedEdgelessIcon,
|
||||||
@@ -42,6 +44,7 @@ interface DocDisplayIconOptions<T extends IconType> {
|
|||||||
mode?: 'edgeless' | 'page';
|
mode?: 'edgeless' | 'page';
|
||||||
reference?: boolean;
|
reference?: boolean;
|
||||||
referenceToNode?: boolean;
|
referenceToNode?: boolean;
|
||||||
|
hasTitleAlias?: boolean;
|
||||||
/**
|
/**
|
||||||
* @default true
|
* @default true
|
||||||
*/
|
*/
|
||||||
@@ -57,6 +60,7 @@ interface DocDisplayTitleOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rcIcons = {
|
const rcIcons = {
|
||||||
|
AliasIcon,
|
||||||
BlockLinkIcon,
|
BlockLinkIcon,
|
||||||
EdgelessIcon,
|
EdgelessIcon,
|
||||||
LinkedEdgelessIcon,
|
LinkedEdgelessIcon,
|
||||||
@@ -67,6 +71,7 @@ const rcIcons = {
|
|||||||
YesterdayIcon,
|
YesterdayIcon,
|
||||||
};
|
};
|
||||||
const litIcons = {
|
const litIcons = {
|
||||||
|
AliasIcon: LitAliasIcon,
|
||||||
BlockLinkIcon: LitBlockLinkIcon,
|
BlockLinkIcon: LitBlockLinkIcon,
|
||||||
EdgelessIcon: LitEdgelessIcon,
|
EdgelessIcon: LitEdgelessIcon,
|
||||||
LinkedEdgelessIcon: LitLinkedEdgelessIcon,
|
LinkedEdgelessIcon: LitLinkedEdgelessIcon,
|
||||||
@@ -130,6 +135,12 @@ export class DocDisplayMetaService extends Service {
|
|||||||
const mode = doc ? get(doc.primaryMode$) : undefined;
|
const mode = doc ? get(doc.primaryMode$) : undefined;
|
||||||
const finalMode = options?.mode ?? mode ?? 'page';
|
const finalMode = options?.mode ?? mode ?? 'page';
|
||||||
const referenceToNode = !!(options?.reference && options.referenceToNode);
|
const referenceToNode = !!(options?.reference && options.referenceToNode);
|
||||||
|
const hasTitleAlias = !!(options?.reference && options?.hasTitleAlias);
|
||||||
|
|
||||||
|
// increases block link priority with title alias
|
||||||
|
if (hasTitleAlias) {
|
||||||
|
return iconSet.AliasIcon;
|
||||||
|
}
|
||||||
|
|
||||||
// increases block link priority
|
// increases block link priority
|
||||||
if (referenceToNode) {
|
if (referenceToNode) {
|
||||||
|
|||||||
@@ -580,3 +580,305 @@ test('should show edgeless content when switching card view of linked mode doc i
|
|||||||
edgelessText.y + edgelessText.height
|
edgelessText.y + edgelessText.height
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Aliases & Copy link
|
||||||
|
test.describe('Customize linked doc title and description', () => {
|
||||||
|
// Inline View
|
||||||
|
test('should set a custom title for inline link', async ({ page }) => {
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
await createLinkedPage(page, 'Test Page');
|
||||||
|
|
||||||
|
const link = page.locator('affine-reference');
|
||||||
|
const title = link.locator('.affine-reference-title');
|
||||||
|
await link.hover();
|
||||||
|
|
||||||
|
const toolbar = page.locator('reference-popup');
|
||||||
|
|
||||||
|
// Copies link
|
||||||
|
await toolbar.getByRole('button', { name: 'Copy link' }).click();
|
||||||
|
const url0 = await link.locator('a').getAttribute('href');
|
||||||
|
const url1 = await (
|
||||||
|
await page.evaluateHandle(() => navigator.clipboard.readText())
|
||||||
|
).jsonValue();
|
||||||
|
expect(url0).not.toBeNull();
|
||||||
|
expect(new URL(url0!, coreUrl).pathname).toBe(new URL(url1).pathname);
|
||||||
|
|
||||||
|
// Edits title
|
||||||
|
await toolbar.getByRole('button', { name: 'Edit' }).click();
|
||||||
|
|
||||||
|
// Title alias
|
||||||
|
await page.keyboard.type('Test Page Alias');
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
|
||||||
|
await expect(title).toHaveText('Test Page Alias');
|
||||||
|
|
||||||
|
// Original title
|
||||||
|
await link.hover();
|
||||||
|
const docTitle = toolbar.getByRole('button', { name: 'Doc title' });
|
||||||
|
await expect(docTitle).toHaveText('Test Page', { useInnerText: true });
|
||||||
|
|
||||||
|
// Edits title
|
||||||
|
await toolbar.getByRole('button', { name: 'Edit' }).click();
|
||||||
|
|
||||||
|
const aliasPopup = page.locator('reference-alias-popup');
|
||||||
|
|
||||||
|
// Input
|
||||||
|
await expect(aliasPopup.locator('input')).toHaveValue('Test Page Alias');
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
await aliasPopup.getByRole('button', { name: 'Reset' }).click();
|
||||||
|
|
||||||
|
await expect(title).toHaveText('Test Page');
|
||||||
|
|
||||||
|
await link.hover();
|
||||||
|
await expect(docTitle).toBeHidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Card View
|
||||||
|
test('should set a custom title and description for card link', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
await createLinkedPage(page, 'Test Page');
|
||||||
|
|
||||||
|
const inlineLink = page.locator('affine-reference');
|
||||||
|
const inlineToolbar = page.locator('reference-popup');
|
||||||
|
|
||||||
|
await inlineLink.hover();
|
||||||
|
|
||||||
|
// Copies link
|
||||||
|
await inlineToolbar.getByRole('button', { name: 'Copy link' }).click();
|
||||||
|
const url0 = await (
|
||||||
|
await page.evaluateHandle(() => navigator.clipboard.readText())
|
||||||
|
).jsonValue();
|
||||||
|
|
||||||
|
// Edits title
|
||||||
|
await inlineToolbar.getByRole('button', { name: 'Edit' }).click();
|
||||||
|
|
||||||
|
// Title alias
|
||||||
|
await page.keyboard.type('Test Page Alias');
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
|
||||||
|
await inlineLink.hover();
|
||||||
|
|
||||||
|
// Switches to card view
|
||||||
|
await inlineToolbar.getByRole('button', { name: 'Switch view' }).click();
|
||||||
|
await inlineToolbar.getByRole('button', { name: 'Card view' }).click();
|
||||||
|
|
||||||
|
const cardLink = page.locator('affine-embed-linked-doc-block');
|
||||||
|
const cardTitle = cardLink.locator(
|
||||||
|
'.affine-embed-linked-doc-content-title-text'
|
||||||
|
);
|
||||||
|
const cardDescription = cardLink.locator(
|
||||||
|
'.affine-embed-linked-doc-content-note.alias'
|
||||||
|
);
|
||||||
|
const cardToolbar = page.locator('affine-embed-card-toolbar');
|
||||||
|
|
||||||
|
await cardLink.click();
|
||||||
|
|
||||||
|
// Copies link
|
||||||
|
await cardToolbar.getByRole('button', { name: 'Copy link' }).click();
|
||||||
|
const url1 = await (
|
||||||
|
await page.evaluateHandle(() => navigator.clipboard.readText())
|
||||||
|
).jsonValue();
|
||||||
|
|
||||||
|
expect(url0).not.toBeNull();
|
||||||
|
expect(url1).not.toBeNull();
|
||||||
|
expect(url0).toBe(url1);
|
||||||
|
|
||||||
|
const docTitle = cardToolbar.getByRole('button', { name: 'Doc title' });
|
||||||
|
await expect(docTitle).toHaveText('Test Page', { useInnerText: true });
|
||||||
|
await expect(cardTitle).toHaveText('Test Page Alias');
|
||||||
|
|
||||||
|
// Edits title & description
|
||||||
|
await cardToolbar.getByRole('button', { name: 'Edit' }).click();
|
||||||
|
|
||||||
|
const cardEditPopup = page.locator('embed-card-edit-modal');
|
||||||
|
|
||||||
|
// Title alias
|
||||||
|
await page.keyboard.type('Test Page Alias Again');
|
||||||
|
await page.keyboard.press('Tab');
|
||||||
|
// Description alias
|
||||||
|
await page.keyboard.type('This is a new description');
|
||||||
|
|
||||||
|
// Saves aliases
|
||||||
|
await cardEditPopup.getByRole('button', { name: 'Save' }).click();
|
||||||
|
await expect(cardTitle).toHaveText('Test Page Alias Again');
|
||||||
|
await expect(cardDescription).toHaveText('This is a new description');
|
||||||
|
|
||||||
|
await cardLink.click();
|
||||||
|
|
||||||
|
// Switches to inline view
|
||||||
|
{
|
||||||
|
await cardToolbar.getByRole('button', { name: 'Switch view' }).click();
|
||||||
|
await cardToolbar.getByRole('button', { name: 'Inline view' }).click();
|
||||||
|
|
||||||
|
// Focuses inline editor
|
||||||
|
const bounds = (await inlineLink.boundingBox())!;
|
||||||
|
await page.mouse.click(
|
||||||
|
bounds.x + bounds.width + 30,
|
||||||
|
bounds.y + bounds.height / 2
|
||||||
|
);
|
||||||
|
|
||||||
|
await inlineLink.hover();
|
||||||
|
|
||||||
|
const title = inlineLink.locator('.affine-reference-title');
|
||||||
|
await expect(title).toHaveText('Test Page Alias Again');
|
||||||
|
|
||||||
|
// Switches to card view
|
||||||
|
await inlineToolbar.getByRole('button', { name: 'Switch view' }).click();
|
||||||
|
await inlineToolbar.getByRole('button', { name: 'Card view' }).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
await cardLink.click();
|
||||||
|
|
||||||
|
await cardToolbar.getByRole('button', { name: 'Edit' }).click();
|
||||||
|
|
||||||
|
// Resets
|
||||||
|
await cardEditPopup.getByRole('button', { name: 'Reset' }).click();
|
||||||
|
|
||||||
|
await expect(cardTitle).toHaveText('Test Page');
|
||||||
|
await expect(cardDescription).toBeHidden();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Embed View
|
||||||
|
test('should automatically switch to card view and set a custom title and description', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
await createLinkedPage(page, 'Test Page');
|
||||||
|
|
||||||
|
const inlineLink = page.locator('affine-reference');
|
||||||
|
const inlineToolbar = page.locator('reference-popup');
|
||||||
|
|
||||||
|
await inlineLink.hover();
|
||||||
|
|
||||||
|
// Copies link
|
||||||
|
await inlineToolbar.getByRole('button', { name: 'Copy link' }).click();
|
||||||
|
const url0 = await (
|
||||||
|
await page.evaluateHandle(() => navigator.clipboard.readText())
|
||||||
|
).jsonValue();
|
||||||
|
|
||||||
|
// Switches to card view
|
||||||
|
await inlineToolbar.getByRole('button', { name: 'Switch view' }).click();
|
||||||
|
await inlineToolbar.getByRole('button', { name: 'Card view' }).click();
|
||||||
|
|
||||||
|
const cardLink = page.locator('affine-embed-linked-doc-block');
|
||||||
|
const cardTitle = cardLink.locator(
|
||||||
|
'.affine-embed-linked-doc-content-title-text'
|
||||||
|
);
|
||||||
|
const cardDescription = cardLink.locator(
|
||||||
|
'.affine-embed-linked-doc-content-note.alias'
|
||||||
|
);
|
||||||
|
const cardToolbar = page.locator('affine-embed-card-toolbar');
|
||||||
|
|
||||||
|
await cardLink.click();
|
||||||
|
|
||||||
|
// Copies link
|
||||||
|
await cardToolbar.getByRole('button', { name: 'Copy link' }).click();
|
||||||
|
const url1 = await (
|
||||||
|
await page.evaluateHandle(() => navigator.clipboard.readText())
|
||||||
|
).jsonValue();
|
||||||
|
|
||||||
|
// Switches to embed view
|
||||||
|
await cardToolbar.getByRole('button', { name: 'Switch view' }).click();
|
||||||
|
await cardToolbar.getByRole('button', { name: 'Embed view' }).click();
|
||||||
|
|
||||||
|
const embedLink = page.locator('affine-embed-synced-doc-block');
|
||||||
|
const embedTitle = embedLink.locator('.affine-embed-synced-doc-title');
|
||||||
|
const embedToolbar = page.locator('affine-embed-card-toolbar');
|
||||||
|
|
||||||
|
await embedLink.click();
|
||||||
|
|
||||||
|
// Copies link
|
||||||
|
await embedToolbar.getByRole('button', { name: 'Copy link' }).click();
|
||||||
|
const url2 = await (
|
||||||
|
await page.evaluateHandle(() => navigator.clipboard.readText())
|
||||||
|
).jsonValue();
|
||||||
|
|
||||||
|
expect(url0).not.toBeNull();
|
||||||
|
expect(url1).not.toBeNull();
|
||||||
|
expect(url2).not.toBeNull();
|
||||||
|
expect(url0).toBe(url1);
|
||||||
|
expect(url1).toBe(url2);
|
||||||
|
|
||||||
|
// Edits title & description
|
||||||
|
await embedToolbar.getByRole('button', { name: 'Edit' }).click();
|
||||||
|
|
||||||
|
const embedEditPopup = page.locator('embed-card-edit-modal');
|
||||||
|
|
||||||
|
// Title alias
|
||||||
|
await page.keyboard.type('Test Page Alias Again');
|
||||||
|
await page.keyboard.press('Tab');
|
||||||
|
// Description alias
|
||||||
|
await page.keyboard.type('This is a new description');
|
||||||
|
|
||||||
|
// Cancels
|
||||||
|
await embedEditPopup.getByRole('button', { name: 'Cancel' }).click();
|
||||||
|
await expect(embedEditPopup).toBeHidden();
|
||||||
|
|
||||||
|
await embedLink.click();
|
||||||
|
|
||||||
|
// Edits title & description
|
||||||
|
await embedToolbar.getByRole('button', { name: 'Edit' }).click();
|
||||||
|
|
||||||
|
// Title alias
|
||||||
|
await page.keyboard.type('Test Page Alias');
|
||||||
|
await page.keyboard.press('Tab');
|
||||||
|
// Description alias
|
||||||
|
await page.keyboard.type('This is a new description');
|
||||||
|
|
||||||
|
// Saves aliases
|
||||||
|
await embedEditPopup.getByRole('button', { name: 'Save' }).click();
|
||||||
|
|
||||||
|
// Automatically switch to card view
|
||||||
|
await expect(embedLink).toBeHidden();
|
||||||
|
|
||||||
|
await expect(cardTitle).toHaveText('Test Page Alias');
|
||||||
|
await expect(cardDescription).toHaveText('This is a new description');
|
||||||
|
|
||||||
|
await cardLink.click();
|
||||||
|
|
||||||
|
const docTitle = cardToolbar.getByRole('button', { name: 'Doc title' });
|
||||||
|
await expect(docTitle).toHaveText('Test Page', { useInnerText: true });
|
||||||
|
await expect(cardTitle).toHaveText('Test Page Alias');
|
||||||
|
|
||||||
|
// Switches to embed view
|
||||||
|
await cardToolbar.getByRole('button', { name: 'Switch view' }).click();
|
||||||
|
await cardToolbar.getByRole('button', { name: 'Embed view' }).click();
|
||||||
|
|
||||||
|
await expect(embedTitle).toHaveText('Test Page');
|
||||||
|
|
||||||
|
await embedLink.click();
|
||||||
|
|
||||||
|
// Switches to inline view
|
||||||
|
{
|
||||||
|
await embedToolbar.getByRole('button', { name: 'Switch view' }).click();
|
||||||
|
await embedToolbar.getByRole('button', { name: 'Inline view' }).click();
|
||||||
|
|
||||||
|
// Focuses inline editor
|
||||||
|
const bounds = (await inlineLink.boundingBox())!;
|
||||||
|
await page.mouse.click(
|
||||||
|
bounds.x + bounds.width + 30,
|
||||||
|
bounds.y + bounds.height / 2
|
||||||
|
);
|
||||||
|
|
||||||
|
await inlineLink.hover();
|
||||||
|
|
||||||
|
// Upstreams: https://github.com/toeverything/blocksuite/pull/8884
|
||||||
|
// const title = inlineLink.locator('.affine-reference-title');
|
||||||
|
// await expect(title).toHaveText('Test Page');
|
||||||
|
|
||||||
|
// Switches to embed view
|
||||||
|
await inlineToolbar.getByRole('button', { name: 'Switch view' }).click();
|
||||||
|
await inlineToolbar.getByRole('button', { name: 'Embed view' }).click();
|
||||||
|
}
|
||||||
|
|
||||||
|
await embedLink.click();
|
||||||
|
|
||||||
|
await expect(embedTitle).toHaveText('Test Page');
|
||||||
|
await expect(
|
||||||
|
embedToolbar.getByRole('button', { name: 'Doc title' })
|
||||||
|
).toBeHidden();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user