feat(core): support block or element reference links (#7946)
Upstreams: https://github.com/toeverything/blocksuite/pull/8021 * open doc with mode * monitor mode changes in query string * scroll anchoring https://github.com/user-attachments/assets/681abff8-e51b-47ea-bb71-447e8b312142 https://github.com/user-attachments/assets/e73ed4c0-4e33-45f8-9db4-d8eed3525d05
This commit is contained in:
@@ -29,23 +29,23 @@ import * as styles from './styles.css';
|
|||||||
import { scrollAnchoring } from './utils';
|
import { scrollAnchoring } from './utils';
|
||||||
|
|
||||||
export interface PageReferenceRendererOptions {
|
export interface PageReferenceRendererOptions {
|
||||||
docMode: DocMode | null;
|
|
||||||
pageId: string;
|
pageId: string;
|
||||||
docCollection: DocCollection;
|
docCollection: DocCollection;
|
||||||
pageMetaHelper: ReturnType<typeof useDocMetaHelper>;
|
pageMetaHelper: ReturnType<typeof useDocMetaHelper>;
|
||||||
journalHelper: ReturnType<typeof useJournalHelper>;
|
journalHelper: ReturnType<typeof useJournalHelper>;
|
||||||
t: ReturnType<typeof useI18n>;
|
t: ReturnType<typeof useI18n>;
|
||||||
|
docMode?: DocMode;
|
||||||
// linking doc with block or element
|
// linking doc with block or element
|
||||||
blockIds?: string[];
|
blockIds?: string[];
|
||||||
elementIds?: string[];
|
elementIds?: string[];
|
||||||
}
|
}
|
||||||
// use a function to be rendered in the lit renderer
|
// use a function to be rendered in the lit renderer
|
||||||
export function pageReferenceRenderer({
|
export function pageReferenceRenderer({
|
||||||
docMode,
|
|
||||||
pageId,
|
pageId,
|
||||||
pageMetaHelper,
|
pageMetaHelper,
|
||||||
journalHelper,
|
journalHelper,
|
||||||
t,
|
t,
|
||||||
|
docMode,
|
||||||
blockIds,
|
blockIds,
|
||||||
elementIds,
|
elementIds,
|
||||||
}: PageReferenceRendererOptions) {
|
}: PageReferenceRendererOptions) {
|
||||||
@@ -93,8 +93,8 @@ export function AffinePageReference({
|
|||||||
isSameDoc = false,
|
isSameDoc = false,
|
||||||
std,
|
std,
|
||||||
}: {
|
}: {
|
||||||
docCollection: DocCollection;
|
|
||||||
pageId: string;
|
pageId: string;
|
||||||
|
docCollection: DocCollection;
|
||||||
wrapper?: React.ComponentType<PropsWithChildren>;
|
wrapper?: React.ComponentType<PropsWithChildren>;
|
||||||
mode?: DocMode;
|
mode?: DocMode;
|
||||||
params?: {
|
params?: {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { EditorService } from '@affine/core/modules/editor';
|
|||||||
import { I18n } from '@affine/i18n';
|
import { I18n } from '@affine/i18n';
|
||||||
import type { DatabaseBlockModel, MenuOptions } from '@blocksuite/blocks';
|
import type { DatabaseBlockModel, MenuOptions } from '@blocksuite/blocks';
|
||||||
import { LinkIcon } from '@blocksuite/icons/lit';
|
import { LinkIcon } 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';
|
||||||
|
|
||||||
export function createDatabaseOptionsConfig(framework: FrameworkProvider) {
|
export function createDatabaseOptionsConfig(framework: FrameworkProvider) {
|
||||||
@@ -54,12 +54,12 @@ function createCopyLinkToBlockMenuItem(
|
|||||||
const baseUrl = getAffineCloudBaseUrl();
|
const baseUrl = getAffineCloudBaseUrl();
|
||||||
if (!baseUrl) return;
|
if (!baseUrl) return;
|
||||||
|
|
||||||
|
const pageId = model.doc.id;
|
||||||
const { editor } = framework.get(EditorService);
|
const { editor } = framework.get(EditorService);
|
||||||
const mode = editor.mode$.value;
|
const mode = editor.mode$.value;
|
||||||
|
|
||||||
if (mode === 'edgeless') return;
|
if (mode === 'edgeless') return;
|
||||||
|
|
||||||
const pageId = editor.doc.id;
|
|
||||||
const workspaceId = editor.doc.workspace.id;
|
const workspaceId = editor.doc.workspace.id;
|
||||||
const options: UseSharingUrl = {
|
const options: UseSharingUrl = {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ export const resolveRouteLinkMeta = (href: string) => {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
moduleName: 'doc' as const,
|
moduleName: 'doc' as const,
|
||||||
docId: moduleName,
|
docId: moduleName,
|
||||||
blockId: hash.slice(1),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { BlockComponent, EditorHost } from '@blocksuite/block-std';
|
import type { BlockComponent, EditorHost } from '@blocksuite/block-std';
|
||||||
import {
|
import {
|
||||||
AffineReference,
|
AffineReference,
|
||||||
type DocMode,
|
DocMode,
|
||||||
type EmbedLinkedDocModel,
|
type EmbedLinkedDocModel,
|
||||||
type EmbedSyncedDocModel,
|
type EmbedSyncedDocModel,
|
||||||
type ImageBlockModel,
|
type ImageBlockModel,
|
||||||
@@ -135,7 +135,7 @@ function resolvePeekInfoFromPeekTarget(
|
|||||||
return {
|
return {
|
||||||
type: 'doc',
|
type: 'doc',
|
||||||
docId,
|
docId,
|
||||||
mode: 'edgeless' as DocMode,
|
mode: DocMode.Edgeless,
|
||||||
xywh: refModel.xywh,
|
xywh: refModel.xywh,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,8 +112,7 @@ export const DocPeekViewControls = ({
|
|||||||
nameKey: 'open',
|
nameKey: 'open',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
// TODO(@Peng): for frame blocks, we should mimic "view in edgeless" button behavior
|
// TODO(@Peng): for frame blocks, we should mimic "view in edgeless" button behavior
|
||||||
workbench.openDoc({ docId, blockId, mode });
|
workbench.openDoc({ docId, mode, blockId });
|
||||||
|
|
||||||
peekView.close('none');
|
peekView.close('none');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -122,7 +121,7 @@ export const DocPeekViewControls = ({
|
|||||||
nameKey: 'new-tab',
|
nameKey: 'new-tab',
|
||||||
name: t['com.affine.peek-view-controls.open-doc-in-new-tab'](),
|
name: t['com.affine.peek-view-controls.open-doc-in-new-tab'](),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
workbench.openDoc(docId, { at: 'new-tab' });
|
workbench.openDoc({ docId, mode }, { at: 'new-tab' });
|
||||||
peekView.close('none');
|
peekView.close('none');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -131,7 +130,7 @@ export const DocPeekViewControls = ({
|
|||||||
nameKey: 'split-view',
|
nameKey: 'split-view',
|
||||||
name: t['com.affine.peek-view-controls.open-doc-in-split-view'](),
|
name: t['com.affine.peek-view-controls.open-doc-in-split-view'](),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
workbench.openDoc(docId, { at: 'beside' });
|
workbench.openDoc({ docId, mode }, { at: 'beside' });
|
||||||
peekView.close('none');
|
peekView.close('none');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,8 +28,7 @@ const useLoadDoc = (pageId: string) => {
|
|||||||
const docRecord = useLiveData(docRecordList.doc$(pageId));
|
const docRecord = useLiveData(docRecordList.doc$(pageId));
|
||||||
const viewService = useService(ViewService);
|
const viewService = useService(ViewService);
|
||||||
|
|
||||||
const queryString = useLiveData(
|
const queryString$ = viewService.view.queryString$<{
|
||||||
viewService.view.queryString$<{
|
|
||||||
mode?: string;
|
mode?: string;
|
||||||
blockIds?: string[];
|
blockIds?: string[];
|
||||||
elementIds?: string[];
|
elementIds?: string[];
|
||||||
@@ -41,20 +40,22 @@ const useLoadDoc = (pageId: string) => {
|
|||||||
blockIds: value => (value.length ? value.split(',') : []),
|
blockIds: value => (value.length ? value.split(',') : []),
|
||||||
elementIds: value => (value.length ? value.split(',') : []),
|
elementIds: value => (value.length ? value.split(',') : []),
|
||||||
},
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const modeInQuery = useLiveData(
|
||||||
|
queryString$.map(q => {
|
||||||
|
if (q.mode && DocModes.includes(q.mode)) {
|
||||||
|
return q.mode as DocMode;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const queryStringMode =
|
|
||||||
queryString.mode && DocModes.includes(queryString.mode)
|
|
||||||
? (queryString.mode as DocMode)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// We only read the querystring mode when entering, so use useState here.
|
// We only read the querystring mode when entering, so use useState here.
|
||||||
const [initialQueryStringMode] = useState(() => queryStringMode);
|
const [initialQueryStringSelector] = useState(() => {
|
||||||
const [initialQueryStringSelector] = useState(() => ({
|
const { blockIds, elementIds } = queryString$.value;
|
||||||
blockIds: queryString.blockIds,
|
return { blockIds, elementIds };
|
||||||
elementIds: queryString.elementIds,
|
});
|
||||||
}));
|
|
||||||
|
|
||||||
const [doc, setDoc] = useState<Doc | null>(null);
|
const [doc, setDoc] = useState<Doc | null>(null);
|
||||||
const [editor, setEditor] = useState<Editor | null>(null);
|
const [editor, setEditor] = useState<Editor | null>(null);
|
||||||
@@ -75,17 +76,18 @@ const useLoadDoc = (pageId: string) => {
|
|||||||
if (!doc) {
|
if (!doc) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const editor = doc.scope
|
const editor = doc.scope
|
||||||
.get(EditorsService)
|
.get(EditorsService)
|
||||||
.createEditor(
|
.createEditor(
|
||||||
initialQueryStringMode || doc.getPrimaryMode() || ('page' as DocMode),
|
modeInQuery || doc.getPrimaryMode() || ('page' as DocMode),
|
||||||
initialQueryStringSelector
|
initialQueryStringSelector
|
||||||
);
|
);
|
||||||
setEditor(editor);
|
setEditor(editor);
|
||||||
return () => {
|
return () => {
|
||||||
editor.dispose();
|
editor.dispose();
|
||||||
};
|
};
|
||||||
}, [doc, initialQueryStringMode, initialQueryStringSelector]);
|
}, [doc, modeInQuery, initialQueryStringSelector]);
|
||||||
|
|
||||||
// update editor mode to queryString
|
// update editor mode to queryString
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user