mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-24 18:02:47 +08:00
@@ -45,5 +45,5 @@ export const prefix = style({
|
||||
export const suffix = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 18,
|
||||
gap: 6,
|
||||
});
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { PageHeader } from '../../components/page-header';
|
||||
|
||||
export const Component = () => {
|
||||
return (
|
||||
<>
|
||||
<PageHeader back />
|
||||
<div>/workspace/:workspaceId/:pageId</div>;
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import { IconButton, MobileMenu } from '@affine/component';
|
||||
import { useJournalInfoHelper } from '@affine/core/hooks/use-journal';
|
||||
import { EditorJournalPanel } from '@affine/core/pages/workspace/detail-page/tabs/journal';
|
||||
import { TodayIcon, TomorrowIcon, YesterdayIcon } from '@blocksuite/icons/rc';
|
||||
import { useService, WorkspaceService } from '@toeverything/infra';
|
||||
|
||||
export const JournalIconButton = ({
|
||||
docId,
|
||||
className,
|
||||
}: {
|
||||
docId: string;
|
||||
className?: string;
|
||||
}) => {
|
||||
const workspace = useService(WorkspaceService).workspace;
|
||||
const { journalDate, isJournal } = useJournalInfoHelper(
|
||||
workspace.docCollection,
|
||||
docId
|
||||
);
|
||||
const Icon = journalDate
|
||||
? journalDate.isBefore(new Date(), 'day')
|
||||
? YesterdayIcon
|
||||
: journalDate.isAfter(new Date(), 'day')
|
||||
? TomorrowIcon
|
||||
: TodayIcon
|
||||
: TodayIcon;
|
||||
|
||||
if (!isJournal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<MobileMenu
|
||||
items={<EditorJournalPanel />}
|
||||
contentOptions={{
|
||||
align: 'center',
|
||||
}}
|
||||
>
|
||||
<IconButton className={className} size={24}>
|
||||
<Icon />
|
||||
</IconButton>
|
||||
</MobileMenu>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { globalStyle, style } from '@vanilla-extract/css';
|
||||
|
||||
export const root = style({
|
||||
background: cssVarV2('layer/background/primary'),
|
||||
minHeight: '100vh',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
});
|
||||
|
||||
export const header = style({
|
||||
background: cssVarV2('layer/background/primary'),
|
||||
position: 'sticky',
|
||||
top: 0,
|
||||
zIndex: 1,
|
||||
});
|
||||
|
||||
export const mainContainer = style({
|
||||
containerType: 'inline-size',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
overflow: 'hidden',
|
||||
borderTop: `0.5px solid transparent`,
|
||||
transition: 'border-color 0.2s',
|
||||
selectors: {
|
||||
'&[data-dynamic-top-border="false"]': {
|
||||
borderColor: cssVar('borderColor'),
|
||||
},
|
||||
'&[data-has-scroll-top="true"]': {
|
||||
borderColor: cssVar('borderColor'),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const editorContainer = style({
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex: 1,
|
||||
zIndex: 0,
|
||||
});
|
||||
// brings styles of .affine-page-viewport from blocksuite
|
||||
export const affineDocViewport = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
containerName: 'viewport',
|
||||
containerType: 'inline-size',
|
||||
background: cssVarV2('layer/background/primary'),
|
||||
selectors: {
|
||||
'&[data-mode="edgeless"]': {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const scrollbar = style({
|
||||
marginRight: '4px',
|
||||
});
|
||||
|
||||
globalStyle('.doc-title-container', {
|
||||
fontSize: cssVar('fontH1'),
|
||||
'@container': {
|
||||
[`viewport (width <= 640px)`]: {
|
||||
padding: '10px 16px',
|
||||
lineHeight: '38px',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
globalStyle('.affine-page-root-block-container', {
|
||||
'@container': {
|
||||
[`viewport (width <= 640px)`]: {
|
||||
paddingLeft: 16,
|
||||
paddingRight: 16,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const journalIconButton = style({
|
||||
position: 'absolute',
|
||||
zIndex: 1,
|
||||
top: 16,
|
||||
right: 12,
|
||||
display: 'flex',
|
||||
});
|
||||
@@ -0,0 +1,225 @@
|
||||
import { IconButton } from '@affine/component';
|
||||
import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary';
|
||||
import { PageDetailEditor } from '@affine/core/components/page-detail-editor';
|
||||
import { useRegisterBlocksuiteEditorCommands } from '@affine/core/hooks/affine/use-register-blocksuite-editor-commands';
|
||||
import { useActiveBlocksuiteEditor } from '@affine/core/hooks/use-block-suite-editor';
|
||||
import { useDocMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import { usePageDocumentTitle } from '@affine/core/hooks/use-global-state';
|
||||
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import { ViewService } from '@affine/core/modules/workbench/services/view';
|
||||
import { DetailPageWrapper } from '@affine/core/pages/workspace/detail-page/detail-page-wrapper';
|
||||
import type { PageRootService } from '@blocksuite/blocks';
|
||||
import {
|
||||
BookmarkBlockService,
|
||||
customImageProxyMiddleware,
|
||||
EmbedGithubBlockService,
|
||||
EmbedLoomBlockService,
|
||||
EmbedYoutubeBlockService,
|
||||
ImageBlockService,
|
||||
} from '@blocksuite/blocks';
|
||||
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||
import { ShareIcon } from '@blocksuite/icons/rc';
|
||||
import { type AffineEditorContainer } from '@blocksuite/presets';
|
||||
import type { Doc as BlockSuiteDoc } from '@blocksuite/store';
|
||||
import {
|
||||
DocService,
|
||||
FrameworkScope,
|
||||
GlobalContextService,
|
||||
useLiveData,
|
||||
useServices,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { PageHeader } from '../../../components';
|
||||
import { JournalIconButton } from './journal-icon-button';
|
||||
import * as styles from './mobile-detail-page.css';
|
||||
import { PageHeaderMenuButton } from './page-header-more-button';
|
||||
|
||||
const DetailPageImpl = () => {
|
||||
const { editorService, docService, workspaceService, globalContextService } =
|
||||
useServices({
|
||||
WorkbenchService,
|
||||
ViewService,
|
||||
EditorService,
|
||||
DocService,
|
||||
WorkspaceService,
|
||||
GlobalContextService,
|
||||
});
|
||||
const editor = editorService.editor;
|
||||
const workspace = workspaceService.workspace;
|
||||
const docCollection = workspace.docCollection;
|
||||
const globalContext = globalContextService.globalContext;
|
||||
const doc = docService.doc;
|
||||
|
||||
const mode = useLiveData(editor.mode$);
|
||||
|
||||
const isInTrash = useLiveData(doc.meta$.map(meta => meta.trash));
|
||||
const { openPage, jumpToPageBlock, jumpToTag } = useNavigateHelper();
|
||||
const editorContainer = useLiveData(editor.editorContainer$);
|
||||
|
||||
const { setDocReadonly } = useDocMetaHelper(workspace.docCollection);
|
||||
|
||||
// TODO(@eyhn): remove jotai here
|
||||
const [_, setActiveBlockSuiteEditor] = useActiveBlocksuiteEditor();
|
||||
|
||||
useEffect(() => {
|
||||
setActiveBlockSuiteEditor(editorContainer);
|
||||
}, [editorContainer, setActiveBlockSuiteEditor]);
|
||||
|
||||
useEffect(() => {
|
||||
globalContext.docId.set(doc.id);
|
||||
globalContext.isDoc.set(true);
|
||||
|
||||
return () => {
|
||||
globalContext.docId.set(null);
|
||||
globalContext.isDoc.set(false);
|
||||
};
|
||||
}, [doc, globalContext]);
|
||||
|
||||
useEffect(() => {
|
||||
globalContext.docMode.set(mode);
|
||||
|
||||
return () => {
|
||||
globalContext.docMode.set(null);
|
||||
};
|
||||
}, [doc, globalContext, mode]);
|
||||
|
||||
useEffect(() => {
|
||||
setDocReadonly(doc.id, true);
|
||||
}, [doc.id, setDocReadonly]);
|
||||
|
||||
useEffect(() => {
|
||||
globalContext.isTrashDoc.set(!!isInTrash);
|
||||
|
||||
return () => {
|
||||
globalContext.isTrashDoc.set(null);
|
||||
};
|
||||
}, [globalContext, isInTrash]);
|
||||
|
||||
useRegisterBlocksuiteEditorCommands(editor);
|
||||
const title = useLiveData(doc.title$);
|
||||
usePageDocumentTitle(title);
|
||||
|
||||
const onLoad = useCallback(
|
||||
(_bsPage: BlockSuiteDoc, editorContainer: AffineEditorContainer) => {
|
||||
// blocksuite editor host
|
||||
const editorHost = editorContainer.host;
|
||||
|
||||
// provide image proxy endpoint to blocksuite
|
||||
editorHost?.std.clipboard.use(
|
||||
customImageProxyMiddleware(runtimeConfig.imageProxyUrl)
|
||||
);
|
||||
ImageBlockService.setImageProxyURL(runtimeConfig.imageProxyUrl);
|
||||
|
||||
// provide link preview endpoint to blocksuite
|
||||
BookmarkBlockService.setLinkPreviewEndpoint(runtimeConfig.linkPreviewUrl);
|
||||
EmbedGithubBlockService.setLinkPreviewEndpoint(
|
||||
runtimeConfig.linkPreviewUrl
|
||||
);
|
||||
EmbedYoutubeBlockService.setLinkPreviewEndpoint(
|
||||
runtimeConfig.linkPreviewUrl
|
||||
);
|
||||
EmbedLoomBlockService.setLinkPreviewEndpoint(
|
||||
runtimeConfig.linkPreviewUrl
|
||||
);
|
||||
|
||||
// provide page mode and updated date to blocksuite
|
||||
const pageService =
|
||||
editorHost?.std.spec.getService<PageRootService>('affine:page');
|
||||
const disposable = new DisposableGroup();
|
||||
if (pageService) {
|
||||
disposable.add(
|
||||
pageService.slots.docLinkClicked.on(({ docId, blockId }) => {
|
||||
return blockId
|
||||
? jumpToPageBlock(docCollection.id, docId, blockId)
|
||||
: openPage(docCollection.id, docId);
|
||||
})
|
||||
);
|
||||
disposable.add(
|
||||
pageService.slots.tagClicked.on(({ tagId }) => {
|
||||
jumpToTag(workspace.id, tagId);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
editor.setEditorContainer(editorContainer);
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
},
|
||||
[
|
||||
editor,
|
||||
jumpToPageBlock,
|
||||
docCollection.id,
|
||||
openPage,
|
||||
jumpToTag,
|
||||
workspace.id,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<FrameworkScope scope={editor.scope}>
|
||||
<div className={styles.mainContainer}>
|
||||
<div
|
||||
data-mode={mode}
|
||||
className={clsx(
|
||||
'affine-page-viewport',
|
||||
styles.affineDocViewport,
|
||||
styles.editorContainer
|
||||
)}
|
||||
>
|
||||
{/* Add a key to force rerender when page changed, to avoid error boundary persisting. */}
|
||||
<AffineErrorBoundary key={doc.id}>
|
||||
<JournalIconButton
|
||||
docId={doc.id}
|
||||
className={styles.journalIconButton}
|
||||
/>
|
||||
<PageDetailEditor
|
||||
pageId={doc.id}
|
||||
onLoad={onLoad}
|
||||
docCollection={docCollection}
|
||||
/>
|
||||
</AffineErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
</FrameworkScope>
|
||||
);
|
||||
};
|
||||
|
||||
export const Component = () => {
|
||||
const params = useParams();
|
||||
const pageId = params.pageId;
|
||||
|
||||
if (!pageId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<DetailPageWrapper pageId={pageId}>
|
||||
<PageHeader
|
||||
back
|
||||
className={styles.header}
|
||||
suffix={
|
||||
<>
|
||||
<IconButton
|
||||
size={24}
|
||||
style={{ padding: 10 }}
|
||||
onClick={console.log}
|
||||
icon={<ShareIcon />}
|
||||
/>
|
||||
<PageHeaderMenuButton docId={pageId} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<DetailPageImpl />
|
||||
</DetailPageWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const iconButton = style({
|
||||
selectors: {
|
||||
'&[data-state=open]': {
|
||||
backgroundColor: cssVar('hoverColor'),
|
||||
},
|
||||
},
|
||||
padding: '10px',
|
||||
});
|
||||
|
||||
export const outlinePanel = style({
|
||||
maxHeight: '60vh',
|
||||
overflow: 'auto',
|
||||
});
|
||||
@@ -0,0 +1,132 @@
|
||||
import { IconButton, toast } from '@affine/component';
|
||||
import {
|
||||
MenuSeparator,
|
||||
MobileMenu,
|
||||
MobileMenuItem,
|
||||
} from '@affine/component/ui/menu';
|
||||
import { useFavorite } from '@affine/core/components/blocksuite/block-suite-header/favorite';
|
||||
import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
|
||||
import { track } from '@affine/core/mixpanel';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { EditorOutlinePanel } from '@affine/core/pages/workspace/detail-page/tabs/outline';
|
||||
import { preventDefault } from '@affine/core/utils';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import {
|
||||
EdgelessIcon,
|
||||
InformationIcon,
|
||||
MoreHorizontalIcon,
|
||||
PageIcon,
|
||||
TocIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import * as styles from './page-header-more-button.css';
|
||||
import { DocInfoSheet } from './sheets/doc-info';
|
||||
|
||||
type PageMenuProps = {
|
||||
docId: string;
|
||||
};
|
||||
|
||||
export const PageHeaderMenuButton = ({ docId }: PageMenuProps) => {
|
||||
const t = useI18n();
|
||||
|
||||
const editorService = useService(EditorService);
|
||||
const editorContainer = useLiveData(editorService.editor.editorContainer$);
|
||||
|
||||
const isInTrash = useLiveData(
|
||||
editorService.editor.doc.meta$.map(meta => meta.trash)
|
||||
);
|
||||
const currentMode = useLiveData(editorService.editor.mode$);
|
||||
|
||||
const { favorite, toggleFavorite } = useFavorite(docId);
|
||||
|
||||
const handleSwitchMode = useCallback(() => {
|
||||
editorService.editor.toggleMode();
|
||||
track.$.header.docOptions.switchPageMode({
|
||||
mode: currentMode === 'page' ? 'edgeless' : 'page',
|
||||
});
|
||||
toast(
|
||||
currentMode === 'page'
|
||||
? t['com.affine.toastMessage.edgelessMode']()
|
||||
: t['com.affine.toastMessage.pageMode']()
|
||||
);
|
||||
}, [currentMode, editorService, t]);
|
||||
|
||||
const handleMenuOpenChange = useCallback((open: boolean) => {
|
||||
if (open) {
|
||||
track.$.header.docOptions.open();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleToggleFavorite = useCallback(() => {
|
||||
track.$.header.docOptions.toggleFavorite();
|
||||
toggleFavorite();
|
||||
}, [toggleFavorite]);
|
||||
|
||||
const EditMenu = (
|
||||
<>
|
||||
<MobileMenuItem
|
||||
prefixIcon={currentMode === 'page' ? <EdgelessIcon /> : <PageIcon />}
|
||||
data-testid="editor-option-menu-edgeless"
|
||||
onSelect={handleSwitchMode}
|
||||
>
|
||||
{t['Convert to ']()}
|
||||
{currentMode === 'page'
|
||||
? t['com.affine.pageMode.edgeless']()
|
||||
: t['com.affine.pageMode.page']()}
|
||||
</MobileMenuItem>
|
||||
<MobileMenuItem
|
||||
data-testid="editor-option-menu-favorite"
|
||||
onSelect={handleToggleFavorite}
|
||||
prefixIcon={<IsFavoriteIcon favorite={favorite} />}
|
||||
>
|
||||
{favorite
|
||||
? t['com.affine.favoritePageOperation.remove']()
|
||||
: t['com.affine.favoritePageOperation.add']()}
|
||||
</MobileMenuItem>
|
||||
<MenuSeparator />
|
||||
<MobileMenu items={<DocInfoSheet docId={docId} />}>
|
||||
<MobileMenuItem
|
||||
prefixIcon={<InformationIcon />}
|
||||
onClick={preventDefault}
|
||||
>
|
||||
<span>{t['com.affine.page-properties.page-info.view']()}</span>
|
||||
</MobileMenuItem>
|
||||
</MobileMenu>
|
||||
<MobileMenu
|
||||
items={
|
||||
<div className={styles.outlinePanel}>
|
||||
<EditorOutlinePanel editor={editorContainer} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<MobileMenuItem prefixIcon={<TocIcon />} onClick={preventDefault}>
|
||||
<span>{t['com.affine.header.option.view-toc']()}</span>
|
||||
</MobileMenuItem>
|
||||
</MobileMenu>
|
||||
</>
|
||||
);
|
||||
if (isInTrash) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<MobileMenu
|
||||
items={EditMenu}
|
||||
contentOptions={{
|
||||
align: 'center',
|
||||
}}
|
||||
rootOptions={{
|
||||
onOpenChange: handleMenuOpenChange,
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
size={24}
|
||||
data-testid="header-dropDownButton"
|
||||
className={styles.iconButton}
|
||||
>
|
||||
<MoreHorizontalIcon />
|
||||
</IconButton>
|
||||
</MobileMenu>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import { rowHPadding } from '@affine/core/components/affine/page-properties/styles.css';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const viewport = style({
|
||||
vars: {
|
||||
[rowHPadding]: '0px',
|
||||
},
|
||||
});
|
||||
|
||||
export const item = style({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
height: 34,
|
||||
padding: '0 20px',
|
||||
|
||||
fontSize: 17,
|
||||
lineHeight: '22px',
|
||||
fontWeight: 400,
|
||||
letterSpacing: -0.43,
|
||||
});
|
||||
|
||||
export const linksRow = style({
|
||||
padding: '0 16px',
|
||||
});
|
||||
|
||||
export const timeRow = style({
|
||||
padding: '0 16px',
|
||||
});
|
||||
|
||||
export const tagsRow = style({
|
||||
padding: '0 16px',
|
||||
});
|
||||
|
||||
export const properties = style({
|
||||
padding: '0 16px',
|
||||
});
|
||||
|
||||
export const scrollBar = style({
|
||||
width: 6,
|
||||
transform: 'translateX(-4px)',
|
||||
});
|
||||
|
||||
export const rowNameContainer = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: 6,
|
||||
padding: 6,
|
||||
width: '160px',
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
import { Divider, Scrollable } from '@affine/component';
|
||||
import {
|
||||
PagePropertyRow,
|
||||
SortableProperties,
|
||||
usePagePropertiesManager,
|
||||
} from '@affine/core/components/affine/page-properties';
|
||||
import { managerContext } from '@affine/core/components/affine/page-properties/common';
|
||||
import { LinksRow } from '@affine/core/components/affine/page-properties/info-modal/links-row';
|
||||
import { TagsRow } from '@affine/core/components/affine/page-properties/info-modal/tags-row';
|
||||
import { TimeRow } from '@affine/core/components/affine/page-properties/info-modal/time-row';
|
||||
import { DocsSearchService } from '@affine/core/modules/docs-search';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { LiveData, useLiveData, useService } from '@toeverything/infra';
|
||||
import { Suspense, useMemo } from 'react';
|
||||
|
||||
import * as styles from './doc-info.css';
|
||||
|
||||
export const DocInfoSheet = ({ docId }: { docId: string }) => {
|
||||
const manager = usePagePropertiesManager(docId);
|
||||
const docsSearchService = useService(DocsSearchService);
|
||||
const t = useI18n();
|
||||
|
||||
const links = useLiveData(
|
||||
useMemo(
|
||||
() => LiveData.from(docsSearchService.watchRefsFrom(docId), null),
|
||||
[docId, docsSearchService]
|
||||
)
|
||||
);
|
||||
const backlinks = useLiveData(
|
||||
useMemo(
|
||||
() => LiveData.from(docsSearchService.watchRefsTo(docId), null),
|
||||
[docId, docsSearchService]
|
||||
)
|
||||
);
|
||||
|
||||
return (
|
||||
<Scrollable.Root>
|
||||
<Scrollable.Viewport
|
||||
className={styles.viewport}
|
||||
data-testid="doc-info-menu"
|
||||
>
|
||||
<managerContext.Provider value={manager}>
|
||||
<Suspense>
|
||||
<TimeRow docId={docId} className={styles.timeRow} />
|
||||
<Divider size="thinner" />
|
||||
{backlinks && backlinks.length > 0 ? (
|
||||
<>
|
||||
<LinksRow
|
||||
className={styles.linksRow}
|
||||
references={backlinks}
|
||||
label={t['com.affine.page-properties.backlinks']()}
|
||||
/>
|
||||
<Divider size="thinner" />
|
||||
</>
|
||||
) : null}
|
||||
{links && links.length > 0 ? (
|
||||
<>
|
||||
<LinksRow
|
||||
className={styles.linksRow}
|
||||
references={links}
|
||||
label={t['com.affine.page-properties.outgoing-links']()}
|
||||
/>
|
||||
<Divider size="thinner" />
|
||||
</>
|
||||
) : null}
|
||||
<div className={styles.properties}>
|
||||
<TagsRow docId={docId} readonly={manager.readonly} />
|
||||
<SortableProperties>
|
||||
{properties =>
|
||||
properties.length ? (
|
||||
<>
|
||||
{properties
|
||||
.filter(
|
||||
property =>
|
||||
manager.isPropertyRequired(property.id) ||
|
||||
(property.visibility !== 'hide' &&
|
||||
!(
|
||||
property.visibility === 'hide-if-empty' &&
|
||||
!property.value
|
||||
))
|
||||
)
|
||||
.map(property => (
|
||||
<PagePropertyRow
|
||||
key={property.id}
|
||||
property={property}
|
||||
rowNameClassName={styles.rowNameContainer}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
</SortableProperties>
|
||||
</div>
|
||||
</Suspense>
|
||||
</managerContext.Provider>
|
||||
</Scrollable.Viewport>
|
||||
<Scrollable.Scrollbar className={styles.scrollBar} />
|
||||
</Scrollable.Root>
|
||||
);
|
||||
};
|
||||
@@ -95,7 +95,7 @@ export const viewRoutes = [
|
||||
},
|
||||
{
|
||||
path: '/:pageId',
|
||||
lazy: () => import('./pages/workspace/detail'),
|
||||
lazy: () => import('./pages/workspace/detail/mobile-detail-page'),
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
|
||||
Reference in New Issue
Block a user