mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
feat(mobile): add journal conflict block to the top of detail page (#9042)
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
bodyRegular,
|
||||
subHeadlineEmphasized,
|
||||
} from '@toeverything/theme/typography';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const body = style({
|
||||
paddingTop: 8,
|
||||
});
|
||||
export const header = style([
|
||||
subHeadlineEmphasized,
|
||||
{
|
||||
height: 42,
|
||||
padding: '11px 20px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
color: cssVarV2.button.error,
|
||||
},
|
||||
]);
|
||||
export const separator = style({
|
||||
width: '100%',
|
||||
height: 16,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
':before': {
|
||||
content: '',
|
||||
width: '100%',
|
||||
height: 0,
|
||||
borderTop: '0.5px solid ' + cssVarV2.layer.insideBorder.border,
|
||||
},
|
||||
});
|
||||
|
||||
export const docItem = style({
|
||||
padding: '11px 20px',
|
||||
height: 44,
|
||||
display: 'flex',
|
||||
gap: 8,
|
||||
alignItems: 'center',
|
||||
});
|
||||
export const icon = style({
|
||||
fontSize: 20,
|
||||
color: cssVarV2.icon.primary,
|
||||
});
|
||||
export const content = style({
|
||||
width: 0,
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
});
|
||||
export const title = style([
|
||||
bodyRegular,
|
||||
{
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
},
|
||||
]);
|
||||
export const duplicatedTag = style({
|
||||
fontSize: 12,
|
||||
lineHeight: '20px',
|
||||
height: 20,
|
||||
padding: '0 8px',
|
||||
borderRadius: 4,
|
||||
color: cssVarV2.toast.iconState.error,
|
||||
backgroundColor: cssVarV2.layer.background.error,
|
||||
border: `1px solid ${cssVarV2.database.border}`,
|
||||
});
|
||||
export const edit = style({
|
||||
fontSize: 20,
|
||||
color: cssVarV2.icon.primary,
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
import { IconButton, Menu } from '@affine/component';
|
||||
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
|
||||
import { JournalService } from '@affine/core/modules/journal';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { EditIcon, TodayIcon } from '@blocksuite/icons/rc';
|
||||
import type { DocRecord } from '@toeverything/infra';
|
||||
import { DocsService, useLiveData, useService } from '@toeverything/infra';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import * as styles from './journal-conflict-block.css';
|
||||
import { ResolveConflictOperations } from './menu/journal-conflicts';
|
||||
|
||||
export const JournalConflictBlock = ({ date }: { date?: string }) => {
|
||||
return date ? <JournalConflictChecker date={date} /> : null;
|
||||
};
|
||||
|
||||
const JournalConflictChecker = ({ date }: { date: string }) => {
|
||||
const docRecordList = useService(DocsService).list;
|
||||
const journalService = useService(JournalService);
|
||||
const docs = useLiveData(
|
||||
useMemo(() => journalService.journalsByDate$(date), [journalService, date])
|
||||
);
|
||||
const docRecords = useLiveData(
|
||||
docRecordList.docs$.map(records =>
|
||||
records.filter(v => {
|
||||
return docs.some(doc => doc.id === v.id);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
if (docRecords.length <= 1) return null;
|
||||
|
||||
return <JournalConflictList docRecords={docRecords} />;
|
||||
};
|
||||
|
||||
const JournalConflictList = ({ docRecords }: { docRecords: DocRecord[] }) => {
|
||||
const t = useI18n();
|
||||
return (
|
||||
<>
|
||||
<div className={styles.body}>
|
||||
<div className={styles.header}>
|
||||
{t['com.affine.editor.journal-conflict.title']()}
|
||||
</div>
|
||||
{docRecords.map(docRecord => (
|
||||
<ConflictItem docRecord={docRecord} key={docRecord.id} />
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.separator} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ConflictItem = ({ docRecord }: { docRecord: DocRecord }) => {
|
||||
const docId = docRecord.id;
|
||||
const i18n = useI18n();
|
||||
const docDisplayMetaService = useService(DocDisplayMetaService);
|
||||
const titleMeta = useLiveData(docDisplayMetaService.title$(docId));
|
||||
const title = i18n.t(titleMeta);
|
||||
|
||||
return (
|
||||
<div className={styles.docItem}>
|
||||
<TodayIcon className={styles.icon} />
|
||||
<div className={styles.content}>
|
||||
<div className={styles.title}>{title}</div>
|
||||
<div className={styles.duplicatedTag}>
|
||||
{i18n['com.affine.page-properties.property.journal-duplicated']()}
|
||||
</div>
|
||||
</div>
|
||||
<Menu items={<ResolveConflictOperations docRecord={docRecord} />}>
|
||||
<IconButton className={styles.edit} icon={<EditIcon />} />
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -22,7 +22,11 @@ import { type MouseEvent, useCallback, useMemo } from 'react';
|
||||
|
||||
import * as styles from './journal-conflicts.css';
|
||||
|
||||
const ResolveConflictOperations = ({ docRecord }: { docRecord: DocRecord }) => {
|
||||
export const ResolveConflictOperations = ({
|
||||
docRecord,
|
||||
}: {
|
||||
docRecord: DocRecord;
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const journalService = useService(JournalService);
|
||||
const { openConfirmModal } = useConfirmModal();
|
||||
@@ -106,7 +110,7 @@ const DocItem = ({ docRecord }: { docRecord: DocRecord }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const ConflictList = ({ docRecords }: { docRecords: DocRecord[] }) => {
|
||||
export const ConflictList = ({ docRecords }: { docRecords: DocRecord[] }) => {
|
||||
return docRecords.map(docRecord => (
|
||||
<DocItem key={docRecord.id} docRecord={docRecord} />
|
||||
));
|
||||
|
||||
@@ -45,6 +45,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { AppTabs } from '../../../components';
|
||||
import { JournalConflictBlock } from './journal-conflict-block';
|
||||
import { JournalDatePicker } from './journal-date-picker';
|
||||
import * as styles from './mobile-detail-page.css';
|
||||
import { PageHeaderMenuButton } from './page-header-more-button';
|
||||
@@ -288,6 +289,7 @@ const MobileDetailPage = ({
|
||||
: title}
|
||||
</span>
|
||||
</PageHeader>
|
||||
<JournalConflictBlock date={date} />
|
||||
<DetailPageImpl />
|
||||
<AppTabs background={cssVarV2('layer/background/primary')} />
|
||||
</DetailPageWrapper>
|
||||
|
||||
@@ -1491,6 +1491,7 @@
|
||||
"com.affine.attachment.preview.error.title": "Unable to preview this file",
|
||||
"com.affine.attachment.preview.error.subtitle": "file type not supported.",
|
||||
"com.affine.pdf.page.render.error": "Failed to render page.",
|
||||
"com.affine.editor.journal-conflict.title": "Duplicate Entries in Today's Journal",
|
||||
"com.affine.editor.at-menu.link-to-doc": "Link to Doc",
|
||||
"com.affine.editor.at-menu.new-doc": "New Doc",
|
||||
"com.affine.editor.at-menu.create-doc": "Create \"{{name}}\" Doc",
|
||||
|
||||
Reference in New Issue
Block a user