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',
|
borderBottom: 'none',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
globalStyle(`${link} svg`, {
|
||||||
|
color: cssVarV2('icon/secondary'),
|
||||||
|
});
|
||||||
|
|
||||||
|
globalStyle(`${link}:hover svg`, {
|
||||||
|
color: cssVarV2('icon/primary'),
|
||||||
|
});
|
||||||
|
|
||||||
export const linkPreviewContainer = style({
|
export const linkPreviewContainer = style({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
|
|||||||
@@ -160,29 +160,13 @@ const usePreviewExtensions = () => {
|
|||||||
return [extensions, portals] as const;
|
return [extensions, portals] as const;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BiDirectionalLinkPanel = () => {
|
const useBacklinkGroups = () => {
|
||||||
const { docLinksService, workspaceService, docService } = useServices({
|
const { docLinksService } = useServices({
|
||||||
DocLinksService,
|
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(
|
const backlinkGroups = useLiveData(
|
||||||
LiveData.computed(get => {
|
LiveData.computed(get => {
|
||||||
if (!show) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const links = get(docLinksService.backlinks.backlinks$);
|
const links = get(docLinksService.backlinks.backlinks$);
|
||||||
|
|
||||||
// group by docId
|
// group by docId
|
||||||
@@ -202,17 +186,17 @@ export const BiDirectionalLinkPanel = () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const backlinkCount = useMemo(() => {
|
return backlinkGroups;
|
||||||
return backlinkGroups.reduce((acc, link) => acc + link.links.length, 0);
|
};
|
||||||
}, [backlinkGroups]);
|
|
||||||
|
|
||||||
const handleClickShow = useCallback(() => {
|
export const BacklinkGroups = () => {
|
||||||
setShow(!show);
|
const [extensions, portals] = usePreviewExtensions();
|
||||||
track.doc.biDirectionalLinksPanel.$.toggle({
|
const { workspaceService, docService } = useServices({
|
||||||
type: show ? 'collapse' : 'expand',
|
WorkspaceService,
|
||||||
});
|
DocService,
|
||||||
}, [show, setShow]);
|
});
|
||||||
|
|
||||||
|
const backlinkGroups = useBacklinkGroups();
|
||||||
const textRendererOptions = useMemo(() => {
|
const textRendererOptions = useMemo(() => {
|
||||||
const docLinkBaseURLMiddleware: JobMiddleware = ({ adapterConfigs }) => {
|
const docLinkBaseURLMiddleware: JobMiddleware = ({ adapterConfigs }) => {
|
||||||
adapterConfigs.set(
|
adapterConfigs.set(
|
||||||
@@ -228,6 +212,128 @@ export const BiDirectionalLinkPanel = () => {
|
|||||||
};
|
};
|
||||||
}, [extensions, workspaceService.workspace.id]);
|
}, [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 (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
{!show && (
|
{!show && (
|
||||||
@@ -254,89 +360,7 @@ export const BiDirectionalLinkPanel = () => {
|
|||||||
<div className={styles.linksTitles}>
|
<div className={styles.linksTitles}>
|
||||||
{t['com.affine.page-properties.backlinks']()} · {backlinkCount}
|
{t['com.affine.page-properties.backlinks']()} · {backlinkCount}
|
||||||
</div>
|
</div>
|
||||||
{backlinkGroups.map(linkGroup => (
|
<BacklinkGroups />
|
||||||
<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>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.linksContainer}>
|
<div className={styles.linksContainer}>
|
||||||
<div className={styles.linksTitles}>
|
<div className={styles.linksTitles}>
|
||||||
@@ -354,13 +378,6 @@ export const BiDirectionalLinkPanel = () => {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{
|
|
||||||
<>
|
|
||||||
{portals.map(p => (
|
|
||||||
<Fragment key={p.id}>{p.portal}</Fragment>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
PropertyCollapsibleContent,
|
PropertyCollapsibleContent,
|
||||||
PropertyCollapsibleSection,
|
PropertyCollapsibleSection,
|
||||||
} from '@affine/component';
|
} 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 { CreatePropertyMenuItems } from '@affine/core/components/doc-properties/menu/create-doc-property';
|
||||||
import { DocPropertyRow } from '@affine/core/components/doc-properties/table';
|
import { DocPropertyRow } from '@affine/core/components/doc-properties/table';
|
||||||
import type { DocCustomPropertyInfo } from '@affine/core/modules/db';
|
import type { DocCustomPropertyInfo } from '@affine/core/modules/db';
|
||||||
@@ -162,7 +163,8 @@ export const InfoTable = ({
|
|||||||
{backlinks && backlinks.length > 0 ? (
|
{backlinks && backlinks.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<LinksRow
|
<LinksRow
|
||||||
references={backlinks}
|
count={backlinks.length}
|
||||||
|
references={<BacklinkGroups />}
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
label={t['com.affine.page-properties.backlinks']()}
|
label={t['com.affine.page-properties.backlinks']()}
|
||||||
/>
|
/>
|
||||||
@@ -172,6 +174,7 @@ export const InfoTable = ({
|
|||||||
{links && links.length > 0 ? (
|
{links && links.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<LinksRow
|
<LinksRow
|
||||||
|
count={links.length}
|
||||||
references={links}
|
references={links}
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
label={t['com.affine.page-properties.outgoing-links']()}
|
label={t['com.affine.page-properties.outgoing-links']()}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { globalStyle, style } from '@vanilla-extract/css';
|
|||||||
export const wrapper = style({
|
export const wrapper = style({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
color: cssVar('textPrimaryColor'),
|
color: cssVarV2('text/primary'),
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
@@ -16,10 +16,15 @@ export const wrapper = style({
|
|||||||
});
|
});
|
||||||
|
|
||||||
globalStyle(`${wrapper} svg`, {
|
globalStyle(`${wrapper} svg`, {
|
||||||
color: cssVar('iconSecondary'),
|
color: cssVarV2('icon/secondary'),
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
transform: 'none',
|
transform: 'none',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
globalStyle(`${wrapper}:hover svg`, {
|
||||||
|
color: cssVarV2('icon/primary'),
|
||||||
|
});
|
||||||
|
|
||||||
globalStyle(`${wrapper} span`, {
|
globalStyle(`${wrapper} span`, {
|
||||||
fontSize: cssVar('fontSm'),
|
fontSize: cssVar('fontSm'),
|
||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
|
|||||||
@@ -1,35 +1,39 @@
|
|||||||
import { PropertyCollapsibleSection } from '@affine/component';
|
import { PropertyCollapsibleSection } from '@affine/component';
|
||||||
import { AffinePageReference } from '@affine/core/components/affine/reference-link';
|
import { AffinePageReference } from '@affine/core/components/affine/reference-link';
|
||||||
import type { Backlink, Link } from '@affine/core/modules/doc-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';
|
import * as styles from './links-row.css';
|
||||||
|
|
||||||
export const LinksRow = ({
|
export const LinksRow = ({
|
||||||
references,
|
references,
|
||||||
|
count,
|
||||||
label,
|
label,
|
||||||
className,
|
className,
|
||||||
onClick,
|
onClick,
|
||||||
}: {
|
}: {
|
||||||
references: Backlink[] | Link[];
|
references: Backlink[] | Link[] | ReactNode;
|
||||||
|
count: number;
|
||||||
label: string;
|
label: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
onClick?: (e: MouseEvent) => void;
|
onClick?: (e: MouseEvent) => void;
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<PropertyCollapsibleSection
|
<PropertyCollapsibleSection
|
||||||
title={`${label} · ${references.length}`}
|
title={`${label} · ${count}`}
|
||||||
className={className}
|
className={className}
|
||||||
>
|
>
|
||||||
{references.map(link => (
|
{Array.isArray(references)
|
||||||
<AffinePageReference
|
? references.map(link => (
|
||||||
key={link.docId}
|
<AffinePageReference
|
||||||
pageId={link.docId}
|
key={link.docId}
|
||||||
params={'params' in link ? link.params : undefined}
|
pageId={link.docId}
|
||||||
className={styles.wrapper}
|
params={'params' in link ? link.params : undefined}
|
||||||
onClick={onClick}
|
className={styles.wrapper}
|
||||||
/>
|
onClick={onClick}
|
||||||
))}
|
/>
|
||||||
|
))
|
||||||
|
: references}
|
||||||
</PropertyCollapsibleSection>
|
</PropertyCollapsibleSection>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ export const DocInfoSheet = ({
|
|||||||
<LinksRow
|
<LinksRow
|
||||||
className={styles.linksRow}
|
className={styles.linksRow}
|
||||||
references={backlinks}
|
references={backlinks}
|
||||||
|
count={backlinks.length}
|
||||||
label={t['com.affine.page-properties.backlinks']()}
|
label={t['com.affine.page-properties.backlinks']()}
|
||||||
/>
|
/>
|
||||||
<Divider size="thinner" />
|
<Divider size="thinner" />
|
||||||
@@ -141,6 +142,7 @@ export const DocInfoSheet = ({
|
|||||||
<LinksRow
|
<LinksRow
|
||||||
className={styles.linksRow}
|
className={styles.linksRow}
|
||||||
references={links}
|
references={links}
|
||||||
|
count={links.length}
|
||||||
label={t['com.affine.page-properties.outgoing-links']()}
|
label={t['com.affine.page-properties.outgoing-links']()}
|
||||||
/>
|
/>
|
||||||
<Divider size="thinner" />
|
<Divider size="thinner" />
|
||||||
|
|||||||
Reference in New Issue
Block a user