Skip to content

Commit

Permalink
feat(core): add info modal to doc title bar
Browse files Browse the repository at this point in the history
  • Loading branch information
JimmFly committed Jul 4, 2024
1 parent a0ce75c commit 983904c
Show file tree
Hide file tree
Showing 24 changed files with 826 additions and 58 deletions.
1 change: 1 addition & 0 deletions packages/frontend/core/src/atoms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const openQuotaModalAtom = atom(false);
export const openStarAFFiNEModalAtom = atom(false);
export const openIssueFeedbackModalAtom = atom(false);
export const openHistoryTipsModalAtom = atom(false);
export const openInfoModalAtom = atom(false);

export const rightSidebarWidthAtom = atom(320);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './icons-mapping';
export * from './info-modal/info-modal';
export * from './page-properties-manager';
export * from './table';
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { cssVar } from '@toeverything/theme';
import { globalStyle, style } from '@vanilla-extract/css';

export const title = style({
fontSize: cssVar('fontSm'),
fontWeight: '500',
color: cssVar('textSecondaryColor'),
padding: '6px',
});

export const wrapper = style({
width: '100%',
borderRadius: 4,
color: cssVar('textPrimaryColor'),
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: 2,
padding: '6px',
':hover': {
backgroundColor: cssVar('hoverColor'),
},
});

globalStyle(`${wrapper} svg`, {
color: cssVar('iconSecondary'),
fontSize: 16,
transform: 'none',
});
globalStyle(`${wrapper} span`, {
fontSize: cssVar('fontSm'),
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
borderBottom: 'none',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useI18n } from '@affine/i18n';
import { useContext } from 'react';

import { AffinePageReference } from '../../reference-link';
import { managerContext } from '../common';
import * as styles from './back-links-row.css';
export const BackLinksRow = ({
references,
onClick,
}: {
references: { docId: string; title: string }[];
onClick?: () => void;
}) => {
const manager = useContext(managerContext);
const t = useI18n();
return (
<div>
<div className={styles.title}>
{t['com.affine.page-properties.backlinks']()} · {references.length}
</div>
{references.map(link => (
<AffinePageReference
key={link.docId}
pageId={link.docId}
wrapper={props => (
<div className={styles.wrapper} onClick={onClick} {...props} />
)}
docCollection={manager.workspace.docCollection}
/>
))}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css';

export const container = style({
maxWidth: 480,
minWidth: 360,
padding: '20px 0',
alignSelf: 'start',
marginTop: '120px',
});

export const titleContainer = style({
display: 'flex',
width: '100%',
flexDirection: 'column',
});

export const titleStyle = style({
fontSize: cssVar('fontH6'),
fontWeight: '600',
});

export const rowNameContainer = style({
display: 'flex',
flexDirection: 'row',
gap: 6,
padding: 6,
width: '160px',
});

export const viewport = style({
maxHeight: 'calc(100vh - 220px)',
padding: '0 24px',
});

export const scrollBar = style({
width: 6,
transform: 'translateX(-4px)',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import {
Divider,
type InlineEditHandle,
Modal,
Scrollable,
} from '@affine/component';
import { DocsSearchService } from '@affine/core/modules/docs-search';
import type { Doc } from '@blocksuite/store';
import {
LiveData,
useLiveData,
useService,
type Workspace,
} from '@toeverything/infra';
import { Suspense, useCallback, useContext, useMemo, useRef } from 'react';

import { BlocksuiteHeaderTitle } from '../../../blocksuite/block-suite-header/title';
import { managerContext } from '../common';
import {
PagePropertiesAddProperty,
PagePropertyRow,
SortableProperties,
usePagePropertiesManager,
} from '../table';
import { BackLinksRow } from './back-links-row';
import * as styles from './info-modal.css';
import { TagsRow } from './tags-row';
import { TimeRow } from './time-row';

export const InfoModal = ({
open,
onOpenChange,
page,
workspace,
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
page: Doc;
workspace: Workspace;
}) => {
const titleInputHandleRef = useRef<InlineEditHandle>(null);
const manager = usePagePropertiesManager(page);
const handleClose = useCallback(() => {
onOpenChange(false);
}, [onOpenChange]);

const docsSearchService = useService(DocsSearchService);
const references = useLiveData(
useMemo(
() => LiveData.from(docsSearchService.watchRefsFrom(page.id), null),
[docsSearchService, page.id]
)
);

if (!manager.page || manager.readonly) {
return null;
}

return (
<Modal
contentOptions={{
className: styles.container,
'aria-describedby': undefined,
}}
open={open}
onOpenChange={onOpenChange}
withoutCloseButton
>
<Scrollable.Root>
<Scrollable.Viewport className={styles.viewport}>
<div className={styles.titleContainer}>
<BlocksuiteHeaderTitle
className={styles.titleStyle}
inputHandleRef={titleInputHandleRef}
pageId={page.id}
docCollection={workspace.docCollection}
/>
</div>
<managerContext.Provider value={manager}>
<Suspense>
<InfoTable
docId={page.id}
onClose={handleClose}
references={references}
readonly={manager.readonly}
/>
</Suspense>
</managerContext.Provider>
</Scrollable.Viewport>
<Scrollable.Scrollbar className={styles.scrollBar} />
</Scrollable.Root>
</Modal>
);
};

const InfoTable = ({
onClose,
references,
docId,
readonly,
}: {
docId: string;
onClose: () => void;
readonly: boolean;
references:
| {
docId: string;
title: string;
}[]
| null;
}) => {
const manager = useContext(managerContext);

return (
<div>
<TimeRow docId={docId} />
<Divider size="thinner" />
{references && references.length > 0 ? (
<>
<BackLinksRow references={references} onClick={onClose} />
<Divider size="thinner" />
</>
) : null}
<TagsRow docId={docId} readonly={readonly} />
<SortableProperties>
{properties =>
properties.length ? (
<div>
{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}
/>
))}
</div>
) : null
}
</SortableProperties>
{manager.readonly ? null : <PagePropertiesAddProperty />}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css';

export const icon = style({
fontSize: 16,
color: cssVar('iconSecondary'),
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
});

export const rowNameContainer = style({
display: 'flex',
flexDirection: 'row',
alignItems: 'center',

gap: 6,
padding: 6,
width: '160px',
});

export const rowName = style({
flexGrow: 1,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
fontSize: cssVar('fontSm'),
color: cssVar('textSecondaryColor'),
});

export const time = style({
display: 'flex',
alignItems: 'center',
padding: '6px 8px',
flexGrow: 1,
fontSize: cssVar('fontSm'),
});

export const rowCell = style({
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: 4,
});

export const container = style({
display: 'flex',
flexDirection: 'column',
marginTop: 20,
marginBottom: 4,
});

export const rowValueCell = style({
display: 'flex',
flexDirection: 'row',
alignItems: 'flex-start',
position: 'relative',
borderRadius: 4,
fontSize: cssVar('fontSm'),
lineHeight: '22px',
userSelect: 'none',
':focus-visible': {
outline: 'none',
},
cursor: 'pointer',
':hover': {
backgroundColor: cssVar('hoverColor'),
},
padding: '6px 8px',
border: `1px solid transparent`,
color: cssVar('textPrimaryColor'),
':focus': {
backgroundColor: cssVar('hoverColor'),
},
'::placeholder': {
color: cssVar('placeholderColor'),
},
selectors: {
'&[data-empty="true"]': {
color: cssVar('placeholderColor'),
},
'&[data-readonly=true]': {
pointerEvents: 'none',
},
},
flex: 1,
});

export const tagsMenu = style({
padding: 0,
transform:
'translate(-3.5px, calc(-3.5px + var(--radix-popper-anchor-height) * -1))',
width: 'calc(var(--radix-popper-anchor-width) + 16px)',
overflow: 'hidden',
});

export const tagsInlineEditor = style({
selectors: {
'&[data-empty=true]': {
color: cssVar('placeholderColor'),
},
},
});
Loading

0 comments on commit 983904c

Please sign in to comment.