mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-30 00:29:46 +08:00
feat(mobile): add journal conflict block to the top of detail page (#9042)
This commit is contained in:
+74
@@ -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';
|
import * as styles from './journal-conflicts.css';
|
||||||
|
|
||||||
const ResolveConflictOperations = ({ docRecord }: { docRecord: DocRecord }) => {
|
export const ResolveConflictOperations = ({
|
||||||
|
docRecord,
|
||||||
|
}: {
|
||||||
|
docRecord: DocRecord;
|
||||||
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const journalService = useService(JournalService);
|
const journalService = useService(JournalService);
|
||||||
const { openConfirmModal } = useConfirmModal();
|
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 => (
|
return docRecords.map(docRecord => (
|
||||||
<DocItem key={docRecord.id} docRecord={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 { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { AppTabs } from '../../../components';
|
import { AppTabs } from '../../../components';
|
||||||
|
import { JournalConflictBlock } from './journal-conflict-block';
|
||||||
import { JournalDatePicker } from './journal-date-picker';
|
import { JournalDatePicker } from './journal-date-picker';
|
||||||
import * as styles from './mobile-detail-page.css';
|
import * as styles from './mobile-detail-page.css';
|
||||||
import { PageHeaderMenuButton } from './page-header-more-button';
|
import { PageHeaderMenuButton } from './page-header-more-button';
|
||||||
@@ -288,6 +289,7 @@ const MobileDetailPage = ({
|
|||||||
: title}
|
: title}
|
||||||
</span>
|
</span>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
<JournalConflictBlock date={date} />
|
||||||
<DetailPageImpl />
|
<DetailPageImpl />
|
||||||
<AppTabs background={cssVarV2('layer/background/primary')} />
|
<AppTabs background={cssVarV2('layer/background/primary')} />
|
||||||
</DetailPageWrapper>
|
</DetailPageWrapper>
|
||||||
|
|||||||
@@ -1491,6 +1491,7 @@
|
|||||||
"com.affine.attachment.preview.error.title": "Unable to preview this file",
|
"com.affine.attachment.preview.error.title": "Unable to preview this file",
|
||||||
"com.affine.attachment.preview.error.subtitle": "file type not supported.",
|
"com.affine.attachment.preview.error.subtitle": "file type not supported.",
|
||||||
"com.affine.pdf.page.render.error": "Failed to render page.",
|
"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.link-to-doc": "Link to Doc",
|
||||||
"com.affine.editor.at-menu.new-doc": "New Doc",
|
"com.affine.editor.at-menu.new-doc": "New Doc",
|
||||||
"com.affine.editor.at-menu.create-doc": "Create \"{{name}}\" Doc",
|
"com.affine.editor.at-menu.create-doc": "Create \"{{name}}\" Doc",
|
||||||
|
|||||||
Reference in New Issue
Block a user