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:
fundon
2024-12-09 03:12:09 +00:00
parent efa4a80019
commit 0bafc35aad
8 changed files with 394 additions and 33 deletions

View File

@@ -30,6 +30,7 @@ import * as styles from './styles.css';
interface AffinePageReferenceProps {
pageId: string;
params?: URLSearchParams;
title?: string | null; // title alias
className?: string;
Icon?: ComponentType;
onClick?: (e: MouseEvent) => void;
@@ -38,20 +39,21 @@ interface AffinePageReferenceProps {
function AffinePageReferenceInner({
pageId,
params,
title,
Icon: UserIcon,
}: AffinePageReferenceProps) {
const docDisplayMetaService = useService(DocDisplayMetaService);
const docsService = useService(DocsService);
const i18n = useI18n();
let linkWithMode: DocMode | null = null;
let linkToNode = false;
let referenceWithMode: DocMode | null = null;
let referenceToNode = false;
if (params) {
const m = params.get('mode');
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(
@@ -61,25 +63,33 @@ function AffinePageReferenceInner({
}
return get(
docDisplayMetaService.icon$(pageId, {
mode: linkWithMode ?? undefined,
mode: referenceWithMode ?? undefined,
reference: true,
referenceToNode: linkToNode,
referenceToNode,
hasTitleAlias: Boolean(title),
})
);
})
);
const notFound = !useLiveData(docsService.list.doc$(pageId));
let title = useLiveData(
const docTitle = useLiveData(
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 (
<span className={notFound ? styles.notFound : ''}>
<Icon className={styles.pageReferenceIcon} />
<span className="affine-reference-title">{i18n.t(title)}</span>
<span className="affine-reference-title">{title}</span>
</span>
);
}
@@ -87,6 +97,7 @@ function AffinePageReferenceInner({
export function AffinePageReference({
pageId,
params,
title,
className,
Icon,
onClick: userOnClick,
@@ -152,7 +163,12 @@ export function AffinePageReference({
onClick={onClick}
className={clsx(styles.pageReferenceLink, className)}
>
<AffinePageReferenceInner pageId={pageId} params={params} Icon={Icon} />
<AffinePageReferenceInner
pageId={pageId}
params={params}
title={title}
Icon={Icon}
/>
</WorkbenchLink>
);
}
@@ -161,6 +177,7 @@ export function AffineSharedPageReference({
pageId,
docCollection,
params,
title,
Icon,
onClick: userOnClick,
}: AffinePageReferenceProps & {
@@ -213,7 +230,12 @@ export function AffineSharedPageReference({
onClick={onClick}
className={styles.pageReferenceLink}
>
<AffinePageReferenceInner pageId={pageId} params={params} Icon={Icon} />
<AffinePageReferenceInner
pageId={pageId}
params={params}
title={title}
Icon={Icon}
/>
</Link>
);
}

View File

@@ -56,6 +56,7 @@ import {
patchEmbedLinkedDocBlockConfig,
patchForMobile,
patchForSharedPage,
patchGenerateDocUrlExtension,
patchNotificationService,
patchParseDocUrlExtension,
patchPeekViewService,
@@ -112,6 +113,8 @@ const usePatchSpecs = (shared: boolean, mode: DocMode) => {
const pageId = data.pageId;
if (!pageId) return <span />;
// title alias
const title = data.title;
const params = toURLSearchParams(data.params);
if (workspaceService.workspace.openOptions.isSharedMode) {
@@ -120,11 +123,14 @@ const usePatchSpecs = (shared: boolean, mode: DocMode) => {
docCollection={workspaceService.workspace.docCollection}
pageId={pageId}
params={params}
title={title}
/>
);
}
return <AffinePageReference pageId={pageId} params={params} />;
return (
<AffinePageReference pageId={pageId} params={params} title={title} />
);
};
}, [workspaceService]);
@@ -147,6 +153,7 @@ const usePatchSpecs = (shared: boolean, mode: DocMode) => {
patched = patched.concat(patchPeekViewService(peekViewService));
patched = patched.concat(patchEdgelessClipboard());
patched = patched.concat(patchParseDocUrlExtension(framework));
patched = patched.concat(patchGenerateDocUrlExtension(framework));
patched = patched.concat(patchQuickSearchService(framework));
patched = patched.concat(patchEmbedLinkedDocBlockConfig(framework));
if (shared) {

View File

@@ -52,7 +52,7 @@ function createCopyLinkToBlockMenuItem(
const options: UseSharingUrl = {
workspaceId,
pageId,
shareMode: mode,
mode,
blockIds: [model.id],
};

View File

@@ -7,6 +7,7 @@ import {
toReactNode,
type useConfirmModal,
} from '@affine/component';
import { WorkspaceServerService } from '@affine/core/modules/cloud';
import type { EditorService } from '@affine/core/modules/editor';
import { EditorSettingService } from '@affine/core/modules/editor-setting';
import { resolveLinkToDoc } from '@affine/core/modules/navigation';
@@ -47,6 +48,7 @@ import {
EdgelessRootBlockComponent,
EmbedLinkedDocBlockComponent,
EmbedLinkedDocBlockConfigExtension,
GenerateDocUrlExtension,
MobileSpecsPatches,
NotificationExtension,
ParseDocUrlExtension,
@@ -55,6 +57,7 @@ import {
ReferenceNodeConfigExtension,
} from '@blocksuite/affine/blocks';
import { type BlockSnapshot, Text } from '@blocksuite/affine/store';
import type { ReferenceParams } from '@blocksuite/affine-model';
import {
AIChatBlockSchema,
type DocProps,
@@ -68,6 +71,7 @@ import { customElement } from 'lit/decorators.js';
import { literal } from 'lit/static-html.js';
import { pick } from 'lodash-es';
import { generateUrl } from '../../../../hooks/affine/use-share-url';
import { createKeyboardToolbarConfig } from './widgets/keyboard-toolbar';
export type ReferenceReactRenderer = (
@@ -110,13 +114,13 @@ export function patchReferenceRenderer(
reactToLit: (element: ElementOrFactory) => TemplateResult,
reactRenderer: ReferenceReactRenderer
): ExtensionType {
const litRenderer = (reference: AffineReference) => {
const customContent = (reference: AffineReference) => {
const node = reactRenderer(reference);
return reactToLit(node);
};
return ReferenceNodeConfigExtension({
customContent: litRenderer,
customContent,
});
}
@@ -463,18 +467,32 @@ export function patchParseDocUrlExtension(framework: FrameworkProvider) {
const info = resolveLinkToDoc(url);
if (!info || info.workspaceId !== workspaceService.workspace.id) return;
return {
docId: info.docId,
blockIds: info.blockIds,
elementIds: info.elementIds,
mode: info.mode,
};
delete info.refreshKey;
return info;
},
});
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() {
class EdgelessClipboardWatcher extends BlockServiceWatcher {
static override readonly flavour = 'affine:page';

View File

@@ -93,7 +93,7 @@ function createCopyLinkToBlockMenuItem(
const mode = editor.mode$.value;
const pageId = editor.doc.id;
const workspaceId = editor.doc.workspace.id;
const options: UseSharingUrl = { workspaceId, pageId, shareMode: mode };
const options: UseSharingUrl = { workspaceId, pageId, mode };
let type = '';
if (mode === 'page') {

View File

@@ -13,7 +13,7 @@ import { useCallback } from 'react';
export type UseSharingUrl = {
workspaceId: string;
pageId: string;
shareMode?: DocMode;
mode?: DocMode;
blockIds?: string[];
elementIds?: string[];
xywh?: string; // not needed currently
@@ -30,7 +30,7 @@ export const generateUrl = ({
pageId,
blockIds,
elementIds,
shareMode: mode,
mode,
xywh, // not needed currently
}: UseSharingUrl & { baseUrl: string }) => {
try {
@@ -38,23 +38,24 @@ export const generateUrl = ({
const search = toURLSearchParams({ mode, blockIds, elementIds, xywh });
if (search?.size) url.search = search.toString();
return url.toString();
} catch {
return null;
} catch (err) {
console.error(err);
return undefined;
}
};
const getShareLinkType = ({
shareMode,
mode,
blockIds,
elementIds,
}: {
shareMode?: DocMode;
mode?: DocMode;
blockIds?: string[];
elementIds?: string[];
}) => {
if (shareMode === 'page') {
if (mode === 'page') {
return 'doc';
} else if (shareMode === 'edgeless') {
} else if (mode === 'edgeless') {
return 'whiteboard';
} else if (blockIds && blockIds.length > 0) {
return 'block';
@@ -131,17 +132,17 @@ export const useSharingUrl = ({ workspaceId, pageId }: UseSharingUrl) => {
const serverService = useService(ServerService);
const onClickCopyLink = useCallback(
(shareMode?: DocMode, blockIds?: string[], elementIds?: string[]) => {
(mode?: DocMode, blockIds?: string[], elementIds?: string[]) => {
const sharingUrl = generateUrl({
baseUrl: serverService.server.baseUrl,
workspaceId,
pageId,
blockIds,
elementIds,
shareMode, // if view is not provided, use the current view
mode, // if view is not provided, use the current view
});
const type = getShareLinkType({
shareMode,
mode,
blockIds,
elementIds,
});

View File

@@ -1,6 +1,7 @@
import { extractEmojiIcon } from '@affine/core/utils';
import { i18nTime } from '@affine/i18n';
import {
AliasIcon as LitAliasIcon,
BlockLinkIcon as LitBlockLinkIcon,
EdgelessIcon as LitEdgelessIcon,
LinkedEdgelessIcon as LitLinkedEdgelessIcon,
@@ -11,6 +12,7 @@ import {
YesterdayIcon as LitYesterdayIcon,
} from '@blocksuite/icons/lit';
import {
AliasIcon,
BlockLinkIcon,
EdgelessIcon,
LinkedEdgelessIcon,
@@ -42,6 +44,7 @@ interface DocDisplayIconOptions<T extends IconType> {
mode?: 'edgeless' | 'page';
reference?: boolean;
referenceToNode?: boolean;
hasTitleAlias?: boolean;
/**
* @default true
*/
@@ -57,6 +60,7 @@ interface DocDisplayTitleOptions {
}
const rcIcons = {
AliasIcon,
BlockLinkIcon,
EdgelessIcon,
LinkedEdgelessIcon,
@@ -67,6 +71,7 @@ const rcIcons = {
YesterdayIcon,
};
const litIcons = {
AliasIcon: LitAliasIcon,
BlockLinkIcon: LitBlockLinkIcon,
EdgelessIcon: LitEdgelessIcon,
LinkedEdgelessIcon: LitLinkedEdgelessIcon,
@@ -130,6 +135,12 @@ export class DocDisplayMetaService extends Service {
const mode = doc ? get(doc.primaryMode$) : undefined;
const finalMode = options?.mode ?? mode ?? 'page';
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
if (referenceToNode) {