mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 02:13:00 +08:00
feat(core): info modal should render backlinks with preview (#9387)
fix AF-2033
This commit is contained in:
@@ -89,6 +89,14 @@ globalStyle(`${link} .affine-reference-title`, {
|
||||
borderBottom: 'none',
|
||||
});
|
||||
|
||||
globalStyle(`${link} svg`, {
|
||||
color: cssVarV2('icon/secondary'),
|
||||
});
|
||||
|
||||
globalStyle(`${link}:hover svg`, {
|
||||
color: cssVarV2('icon/primary'),
|
||||
});
|
||||
|
||||
export const linkPreviewContainer = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
||||
@@ -160,29 +160,13 @@ const usePreviewExtensions = () => {
|
||||
return [extensions, portals] as const;
|
||||
};
|
||||
|
||||
export const BiDirectionalLinkPanel = () => {
|
||||
const { docLinksService, workspaceService, docService } = useServices({
|
||||
const useBacklinkGroups = () => {
|
||||
const { docLinksService } = useServices({
|
||||
DocLinksService,
|
||||
WorkspaceService,
|
||||
DocService,
|
||||
});
|
||||
|
||||
const [extensions, portals] = usePreviewExtensions();
|
||||
const t = useI18n();
|
||||
|
||||
const [show, setShow] = useBiDirectionalLinkPanelCollapseState(
|
||||
docService.doc.id
|
||||
);
|
||||
|
||||
const links = useLiveData(
|
||||
show ? docLinksService.links.links$ : new LiveData([] as Link[])
|
||||
);
|
||||
const backlinkGroups = useLiveData(
|
||||
LiveData.computed(get => {
|
||||
if (!show) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const links = get(docLinksService.backlinks.backlinks$);
|
||||
|
||||
// group by docId
|
||||
@@ -202,17 +186,17 @@ export const BiDirectionalLinkPanel = () => {
|
||||
})
|
||||
);
|
||||
|
||||
const backlinkCount = useMemo(() => {
|
||||
return backlinkGroups.reduce((acc, link) => acc + link.links.length, 0);
|
||||
}, [backlinkGroups]);
|
||||
return backlinkGroups;
|
||||
};
|
||||
|
||||
const handleClickShow = useCallback(() => {
|
||||
setShow(!show);
|
||||
track.doc.biDirectionalLinksPanel.$.toggle({
|
||||
type: show ? 'collapse' : 'expand',
|
||||
});
|
||||
}, [show, setShow]);
|
||||
export const BacklinkGroups = () => {
|
||||
const [extensions, portals] = usePreviewExtensions();
|
||||
const { workspaceService, docService } = useServices({
|
||||
WorkspaceService,
|
||||
DocService,
|
||||
});
|
||||
|
||||
const backlinkGroups = useBacklinkGroups();
|
||||
const textRendererOptions = useMemo(() => {
|
||||
const docLinkBaseURLMiddleware: JobMiddleware = ({ adapterConfigs }) => {
|
||||
adapterConfigs.set(
|
||||
@@ -228,6 +212,128 @@ export const BiDirectionalLinkPanel = () => {
|
||||
};
|
||||
}, [extensions, workspaceService.workspace.id]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{backlinkGroups.map(linkGroup => (
|
||||
<CollapsibleSection
|
||||
key={linkGroup.docId}
|
||||
title={
|
||||
<AffinePageReference
|
||||
pageId={linkGroup.docId}
|
||||
onClick={() => {
|
||||
track.doc.biDirectionalLinksPanel.backlinkTitle.navigate();
|
||||
}}
|
||||
/>
|
||||
}
|
||||
length={linkGroup.links.length}
|
||||
docId={docService.doc.id}
|
||||
linkDocId={linkGroup.docId}
|
||||
>
|
||||
<div className={styles.linkPreviewContainer}>
|
||||
{linkGroup.links.map(link => {
|
||||
if (!link.markdownPreview) {
|
||||
return null;
|
||||
}
|
||||
const searchParams = new URLSearchParams();
|
||||
const displayMode = link.displayMode || 'page';
|
||||
searchParams.set('mode', displayMode);
|
||||
|
||||
let blockId = link.blockId;
|
||||
if (
|
||||
link.parentFlavour === 'affine:database' &&
|
||||
link.parentBlockId
|
||||
) {
|
||||
// if parentBlockFlavour is 'affine:database',
|
||||
// we will fallback to the database block instead
|
||||
blockId = link.parentBlockId;
|
||||
} else if (displayMode === 'edgeless' && link.noteBlockId) {
|
||||
// if note has displayMode === 'edgeless' && has noteBlockId,
|
||||
// set noteBlockId as blockId
|
||||
blockId = link.noteBlockId;
|
||||
}
|
||||
|
||||
searchParams.set('blockIds', blockId);
|
||||
|
||||
const to = {
|
||||
pathname: '/' + linkGroup.docId,
|
||||
search: '?' + searchParams.toString(),
|
||||
hash: '',
|
||||
};
|
||||
|
||||
// if this backlink has no noteBlock && displayMode is edgeless, we will render
|
||||
// the link as a page link
|
||||
const edgelessLink =
|
||||
displayMode === 'edgeless' && !link.noteBlockId;
|
||||
|
||||
return (
|
||||
<WorkbenchLink
|
||||
to={to}
|
||||
key={link.blockId}
|
||||
className={styles.linkPreview}
|
||||
onClick={() => {
|
||||
track.doc.biDirectionalLinksPanel.backlinkPreview.navigate();
|
||||
}}
|
||||
>
|
||||
{edgelessLink ? (
|
||||
<>
|
||||
[Edgeless]
|
||||
<AffinePageReference
|
||||
key={link.blockId}
|
||||
pageId={linkGroup.docId}
|
||||
params={searchParams}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<BlocksuiteTextRenderer
|
||||
className={styles.linkPreviewRenderer}
|
||||
answer={link.markdownPreview}
|
||||
schema={getAFFiNEWorkspaceSchema()}
|
||||
options={textRendererOptions}
|
||||
/>
|
||||
)}
|
||||
</WorkbenchLink>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
))}
|
||||
<>
|
||||
{portals.map(p => (
|
||||
<Fragment key={p.id}>{p.portal}</Fragment>
|
||||
))}
|
||||
</>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const BiDirectionalLinkPanel = () => {
|
||||
const { docLinksService, docService } = useServices({
|
||||
DocLinksService,
|
||||
DocService,
|
||||
});
|
||||
|
||||
const t = useI18n();
|
||||
|
||||
const [show, setShow] = useBiDirectionalLinkPanelCollapseState(
|
||||
docService.doc.id
|
||||
);
|
||||
|
||||
const links = useLiveData(
|
||||
show ? docLinksService.links.links$ : new LiveData([] as Link[])
|
||||
);
|
||||
|
||||
const backlinkGroups = useBacklinkGroups();
|
||||
|
||||
const backlinkCount = useMemo(() => {
|
||||
return backlinkGroups.reduce((acc, link) => acc + link.links.length, 0);
|
||||
}, [backlinkGroups]);
|
||||
|
||||
const handleClickShow = useCallback(() => {
|
||||
setShow(!show);
|
||||
track.doc.biDirectionalLinksPanel.$.toggle({
|
||||
type: show ? 'collapse' : 'expand',
|
||||
});
|
||||
}, [show, setShow]);
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{!show && (
|
||||
@@ -254,89 +360,7 @@ export const BiDirectionalLinkPanel = () => {
|
||||
<div className={styles.linksTitles}>
|
||||
{t['com.affine.page-properties.backlinks']()} · {backlinkCount}
|
||||
</div>
|
||||
{backlinkGroups.map(linkGroup => (
|
||||
<CollapsibleSection
|
||||
key={linkGroup.docId}
|
||||
title={
|
||||
<AffinePageReference
|
||||
pageId={linkGroup.docId}
|
||||
onClick={() => {
|
||||
track.doc.biDirectionalLinksPanel.backlinkTitle.navigate();
|
||||
}}
|
||||
/>
|
||||
}
|
||||
length={linkGroup.links.length}
|
||||
docId={docService.doc.id}
|
||||
linkDocId={linkGroup.docId}
|
||||
>
|
||||
<div className={styles.linkPreviewContainer}>
|
||||
{linkGroup.links.map(link => {
|
||||
if (!link.markdownPreview) {
|
||||
return null;
|
||||
}
|
||||
const searchParams = new URLSearchParams();
|
||||
const displayMode = link.displayMode || 'page';
|
||||
searchParams.set('mode', displayMode);
|
||||
|
||||
let blockId = link.blockId;
|
||||
if (
|
||||
link.parentFlavour === 'affine:database' &&
|
||||
link.parentBlockId
|
||||
) {
|
||||
// if parentBlockFlavour is 'affine:database',
|
||||
// we will fallback to the database block instead
|
||||
blockId = link.parentBlockId;
|
||||
} else if (displayMode === 'edgeless' && link.noteBlockId) {
|
||||
// if note has displayMode === 'edgeless' && has noteBlockId,
|
||||
// set noteBlockId as blockId
|
||||
blockId = link.noteBlockId;
|
||||
}
|
||||
|
||||
searchParams.set('blockIds', blockId);
|
||||
|
||||
const to = {
|
||||
pathname: '/' + linkGroup.docId,
|
||||
search: '?' + searchParams.toString(),
|
||||
hash: '',
|
||||
};
|
||||
|
||||
// if this backlink has no noteBlock && displayMode is edgeless, we will render
|
||||
// the link as a page link
|
||||
const edgelessLink =
|
||||
displayMode === 'edgeless' && !link.noteBlockId;
|
||||
|
||||
return (
|
||||
<WorkbenchLink
|
||||
to={to}
|
||||
key={link.blockId}
|
||||
className={styles.linkPreview}
|
||||
onClick={() => {
|
||||
track.doc.biDirectionalLinksPanel.backlinkPreview.navigate();
|
||||
}}
|
||||
>
|
||||
{edgelessLink ? (
|
||||
<>
|
||||
[Edgeless]
|
||||
<AffinePageReference
|
||||
key={link.blockId}
|
||||
pageId={linkGroup.docId}
|
||||
params={searchParams}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<BlocksuiteTextRenderer
|
||||
className={styles.linkPreviewRenderer}
|
||||
answer={link.markdownPreview}
|
||||
schema={getAFFiNEWorkspaceSchema()}
|
||||
options={textRendererOptions}
|
||||
/>
|
||||
)}
|
||||
</WorkbenchLink>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CollapsibleSection>
|
||||
))}
|
||||
<BacklinkGroups />
|
||||
</div>
|
||||
<div className={styles.linksContainer}>
|
||||
<div className={styles.linksTitles}>
|
||||
@@ -354,13 +378,6 @@ export const BiDirectionalLinkPanel = () => {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{
|
||||
<>
|
||||
{portals.map(p => (
|
||||
<Fragment key={p.id}>{p.portal}</Fragment>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
PropertyCollapsibleContent,
|
||||
PropertyCollapsibleSection,
|
||||
} from '@affine/component';
|
||||
import { BacklinkGroups } from '@affine/core/components/blocksuite/block-suite-editor/bi-directional-link-panel';
|
||||
import { CreatePropertyMenuItems } from '@affine/core/components/doc-properties/menu/create-doc-property';
|
||||
import { DocPropertyRow } from '@affine/core/components/doc-properties/table';
|
||||
import type { DocCustomPropertyInfo } from '@affine/core/modules/db';
|
||||
@@ -162,7 +163,8 @@ export const InfoTable = ({
|
||||
{backlinks && backlinks.length > 0 ? (
|
||||
<>
|
||||
<LinksRow
|
||||
references={backlinks}
|
||||
count={backlinks.length}
|
||||
references={<BacklinkGroups />}
|
||||
onClick={onClose}
|
||||
label={t['com.affine.page-properties.backlinks']()}
|
||||
/>
|
||||
@@ -172,6 +174,7 @@ export const InfoTable = ({
|
||||
{links && links.length > 0 ? (
|
||||
<>
|
||||
<LinksRow
|
||||
count={links.length}
|
||||
references={links}
|
||||
onClick={onClose}
|
||||
label={t['com.affine.page-properties.outgoing-links']()}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { globalStyle, style } from '@vanilla-extract/css';
|
||||
export const wrapper = style({
|
||||
width: '100%',
|
||||
borderRadius: 4,
|
||||
color: cssVar('textPrimaryColor'),
|
||||
color: cssVarV2('text/primary'),
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@@ -16,10 +16,15 @@ export const wrapper = style({
|
||||
});
|
||||
|
||||
globalStyle(`${wrapper} svg`, {
|
||||
color: cssVar('iconSecondary'),
|
||||
color: cssVarV2('icon/secondary'),
|
||||
fontSize: 16,
|
||||
transform: 'none',
|
||||
});
|
||||
|
||||
globalStyle(`${wrapper}:hover svg`, {
|
||||
color: cssVarV2('icon/primary'),
|
||||
});
|
||||
|
||||
globalStyle(`${wrapper} span`, {
|
||||
fontSize: cssVar('fontSm'),
|
||||
whiteSpace: 'nowrap',
|
||||
|
||||
@@ -1,35 +1,39 @@
|
||||
import { PropertyCollapsibleSection } from '@affine/component';
|
||||
import { AffinePageReference } from '@affine/core/components/affine/reference-link';
|
||||
import type { Backlink, Link } from '@affine/core/modules/doc-link';
|
||||
import type { MouseEvent } from 'react';
|
||||
import type { MouseEvent, ReactNode } from 'react';
|
||||
|
||||
import * as styles from './links-row.css';
|
||||
|
||||
export const LinksRow = ({
|
||||
references,
|
||||
count,
|
||||
label,
|
||||
className,
|
||||
onClick,
|
||||
}: {
|
||||
references: Backlink[] | Link[];
|
||||
references: Backlink[] | Link[] | ReactNode;
|
||||
count: number;
|
||||
label: string;
|
||||
className?: string;
|
||||
onClick?: (e: MouseEvent) => void;
|
||||
}) => {
|
||||
return (
|
||||
<PropertyCollapsibleSection
|
||||
title={`${label} · ${references.length}`}
|
||||
title={`${label} · ${count}`}
|
||||
className={className}
|
||||
>
|
||||
{references.map(link => (
|
||||
<AffinePageReference
|
||||
key={link.docId}
|
||||
pageId={link.docId}
|
||||
params={'params' in link ? link.params : undefined}
|
||||
className={styles.wrapper}
|
||||
onClick={onClick}
|
||||
/>
|
||||
))}
|
||||
{Array.isArray(references)
|
||||
? references.map(link => (
|
||||
<AffinePageReference
|
||||
key={link.docId}
|
||||
pageId={link.docId}
|
||||
params={'params' in link ? link.params : undefined}
|
||||
className={styles.wrapper}
|
||||
onClick={onClick}
|
||||
/>
|
||||
))
|
||||
: references}
|
||||
</PropertyCollapsibleSection>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -131,6 +131,7 @@ export const DocInfoSheet = ({
|
||||
<LinksRow
|
||||
className={styles.linksRow}
|
||||
references={backlinks}
|
||||
count={backlinks.length}
|
||||
label={t['com.affine.page-properties.backlinks']()}
|
||||
/>
|
||||
<Divider size="thinner" />
|
||||
@@ -141,6 +142,7 @@ export const DocInfoSheet = ({
|
||||
<LinksRow
|
||||
className={styles.linksRow}
|
||||
references={links}
|
||||
count={links.length}
|
||||
label={t['com.affine.page-properties.outgoing-links']()}
|
||||
/>
|
||||
<Divider size="thinner" />
|
||||
|
||||
Reference in New Issue
Block a user