Skip to content

Commit

Permalink
feat(core): new "is journal" page property (#8525)
Browse files Browse the repository at this point in the history
  • Loading branch information
CatsJuice committed Oct 18, 2024
1 parent 714a87c commit 8f92be9
Show file tree
Hide file tree
Showing 19 changed files with 494 additions and 49 deletions.
5 changes: 5 additions & 0 deletions packages/common/infra/src/modules/doc/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ export const BUILT_IN_CUSTOM_PROPERTY_TYPE = [
type: 'docPrimaryMode',
show: 'always-hide',
},
{
id: 'journal',
type: 'journal',
show: 'always-hide',
},
] as DocCustomPropertyInfo[];
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useJournalInfoHelper } from '@affine/core/components/hooks/use-journal';
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
import { JournalService } from '@affine/core/modules/journal';
import { PeekViewService } from '@affine/core/modules/peek-view/services/peek-view';
import { useInsidePeekView } from '@affine/core/modules/peek-view/view/modal-container';
import { WorkbenchLink } from '@affine/core/modules/workbench';
Expand Down Expand Up @@ -30,7 +30,8 @@ export function AffinePageReference({
params?: URLSearchParams;
}) {
const docDisplayMetaService = useService(DocDisplayMetaService);
const journalHelper = useJournalInfoHelper();
const journalService = useService(JournalService);
const isJournal = !!useLiveData(journalService.journalDate$(pageId));
const i18n = useI18n();

let linkWithMode: DocMode | null = null;
Expand Down Expand Up @@ -67,7 +68,6 @@ export function AffinePageReference({

const peekView = useService(PeekViewService).peekView;
const isInPeekView = useInsidePeekView();
const isJournal = journalHelper.isPageJournal(pageId);

const onClick = useCallback(
(e: React.MouseEvent) => {
Expand Down Expand Up @@ -127,7 +127,8 @@ export function AffineSharedPageReference({
params?: URLSearchParams;
}) {
const docDisplayMetaService = useService(DocDisplayMetaService);
const journalHelper = useJournalInfoHelper();
const journalService = useService(JournalService);
const isJournal = !!useLiveData(journalService.journalDate$(pageId));
const i18n = useI18n();

let linkWithMode: DocMode | null = null;
Expand Down Expand Up @@ -159,8 +160,6 @@ export function AffineSharedPageReference({

const [refreshKey, setRefreshKey] = useState<string>(() => nanoid());

const isJournal = journalHelper.isPageJournal(pageId);

const onClick = useCallback(
(e: React.MouseEvent) => {
if (isJournal) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import { useJournalInfoHelper } from '@affine/core/components/hooks/use-journal';
import { useI18n } from '@affine/i18n';
import { JournalService } from '@affine/core/modules/journal';
import { i18nTime, useI18n } from '@affine/i18n';
import type { Doc } from '@blocksuite/affine/store';
import { useLiveData, useService } from '@toeverything/infra';
import dayjs from 'dayjs';

import * as styles from './styles.css';

export const BlocksuiteEditorJournalDocTitle = ({ page }: { page: Doc }) => {
const { localizedJournalDate, isTodayJournal, journalDate } =
useJournalInfoHelper(page.id);
const journalService = useService(JournalService);
const journalDateStr = useLiveData(journalService.journalDate$(page.id));
const journalDate = journalDateStr ? dayjs(journalDateStr) : null;
const isTodayJournal = useLiveData(journalService.journalToday$(page.id));
const localizedJournalDate = i18nTime(journalDateStr, {
absolute: { accuracy: 'day' },
});
const t = useI18n();

// TODO(catsjuice): i18n
const day = journalDate?.format('dddd') ?? null;

return (
<span className="doc-title-container">
<span>{localizedJournalDate}</span>
<div className="doc-title-container" data-testid="journal-title">
<span data-testid="date">{localizedJournalDate}</span>
{isTodayJournal ? (
<span className={styles.titleTodayTag}>{t['com.affine.today']()}</span>
) : (
<span className={styles.titleDayTag}>{day}</span>
)}
</span>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import {
useConfirmModal,
useLitPortalFactory,
} from '@affine/component';
import { useJournalInfoHelper } from '@affine/core/components/hooks/use-journal';
import { ServerConfigService } from '@affine/core/modules/cloud';
import { EditorService } from '@affine/core/modules/editor';
import { EditorSettingService } from '@affine/core/modules/editor-settting';
import { JournalService } from '@affine/core/modules/journal';
import { toURLSearchParams } from '@affine/core/modules/navigation';
import { PeekViewService } from '@affine/core/modules/peek-view/services/peek-view';
import type { DocMode } from '@blocksuite/affine/blocks';
Expand Down Expand Up @@ -196,7 +196,8 @@ export const BlocksuiteDocEditor = forwardRef<
) {
const titleRef = useRef<DocTitle | null>(null);
const docRef = useRef<PageEditor | null>(null);
const { isJournal } = useJournalInfoHelper(page.id);
const journalService = useService(JournalService);
const isJournal = !!useLiveData(journalService.journalDate$(page.id));

const editorSettingService = useService(EditorSettingService);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const titleTagBasic = style({
padding: '0 4px',
borderRadius: '4px',
marginLeft: '4px',
lineHeight: '0px',
});
export const titleDayTag = style([
titleTagBasic,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import type { WeekDatePickerHandle } from '@affine/component';
import { WeekDatePicker } from '@affine/component';
import {
useJournalInfoHelper,
useJournalRouteHelper,
} from '@affine/core/components/hooks/use-journal';
import { useJournalRouteHelper } from '@affine/core/components/hooks/use-journal';
import { JournalService } from '@affine/core/modules/journal';
import type { Doc, DocCollection } from '@blocksuite/affine/store';
import { useLiveData, useService } from '@toeverything/infra';
import dayjs from 'dayjs';
import { useEffect, useRef, useState } from 'react';

Expand All @@ -19,7 +18,9 @@ export const JournalWeekDatePicker = ({
page,
}: JournalWeekDatePickerProps) => {
const handleRef = useRef<WeekDatePickerHandle>(null);
const { journalDate } = useJournalInfoHelper(page.id);
const journalService = useService(JournalService);
const journalDateStr = useLiveData(journalService.journalDate$(page.id));
const journalDate = journalDateStr ? dayjs(journalDateStr) : null;
const { openJournal } = useJournalRouteHelper(docCollection);
const [date, setDate] = useState(
(journalDate ?? dayjs()).format('YYYY-MM-DD')
Expand All @@ -33,6 +34,7 @@ export const JournalWeekDatePicker = ({

return (
<WeekDatePicker
data-testid="journal-week-picker"
handleRef={handleRef}
style={weekStyle}
value={date}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ export const DocPropertyRow = ({
hideEmpty={hideEmpty}
hide={hide}
data-testid="doc-property-row"
data-info-id={propertyInfo.id}
>
<PropertyName
defaultOpenMenu={defaultOpenEditMenu}
Expand Down Expand Up @@ -414,6 +415,7 @@ export const DocPropertiesTableBody = forwardRef<
variant="plain"
prefix={<PlusIcon />}
className={styles.propertyActionButton}
data-testid="add-property-button"
>
{t['com.affine.page-properties.add-property']()}
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import {
NumberIcon,
TagIcon,
TextIcon,
TodayIcon,
} from '@blocksuite/icons/rc';

import { CheckboxValue } from './checkbox';
import { CreatedByValue, UpdatedByValue } from './created-updated-by';
import { DateValue } from './date';
import { DocPrimaryModeValue } from './doc-primary-mode';
import { JournalValue } from './journal';
import { NumberValue } from './number';
import { TagsValue } from './tags';
import { TextValue } from './text';
Expand Down Expand Up @@ -61,6 +63,13 @@ export const DocPropertyTypes = {
value: DocPrimaryModeValue,
name: 'com.affine.page-properties.property.docPrimaryMode',
},
journal: {
icon: TodayIcon,
value: JournalValue,
name: 'com.affine.page-properties.property.journal',
uniqueId: 'journal',
renameable: false,
},
} as Record<
string,
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { cssVar } from '@toeverything/theme';
import { cssVarV2 } from '@toeverything/theme/v2';
import { style } from '@vanilla-extract/css';

export const property = style({
padding: 4,
});

export const root = style({
height: '100%',
display: 'flex',
gap: 2,
alignItems: 'center',
});

export const checkbox = style({
fontSize: 24,
color: cssVarV2('icon/primary'),
});

export const date = style({
fontSize: cssVar('fontSm'),
color: cssVarV2('text/primary'),
lineHeight: '22px',
padding: '0 4px',
borderRadius: 4,
':hover': {
background: cssVarV2('layer/background/hoverOverlay'),
},
});

export const duplicateTag = style({
padding: '0 8px',
border: `1px solid ${cssVarV2('database/border')}`,
background: cssVarV2('layer/background/error'),
color: cssVarV2('toast/iconState/error'),
borderRadius: 4,
});
134 changes: 134 additions & 0 deletions packages/frontend/core/src/components/doc-properties/types/journal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { Checkbox, DatePicker, Menu, PropertyValue } from '@affine/component';
import { JournalService } from '@affine/core/modules/journal';
import { WorkbenchService } from '@affine/core/modules/workbench';
import { ViewService } from '@affine/core/modules/workbench/services/view';
import { i18nTime, useI18n } from '@affine/i18n';
import {
DocService,
useLiveData,
useService,
useServiceOptional,
} from '@toeverything/infra';
import dayjs from 'dayjs';
import { useCallback, useEffect, useMemo, useState } from 'react';

import * as styles from './journal.css';

export const JournalValue = () => {
const t = useI18n();

const journalService = useService(JournalService);
const doc = useService(DocService).doc;
const journalDate = useLiveData(journalService.journalDate$(doc.id));
const checked = !!journalDate;

const [selectedDate, setSelectedDate] = useState(
dayjs().format('YYYY-MM-DD')
);
const [showDatePicker, setShowDatePicker] = useState(false);
const displayDate = useMemo(
() =>
i18nTime(selectedDate, {
absolute: { accuracy: 'day' },
}),
[selectedDate]
);
const docs = useLiveData(
useMemo(
() => journalService.journalsByDate$(selectedDate),
[journalService, selectedDate]
)
);
const conflict = docs.length > 1;

useEffect(() => {
if (journalDate) setSelectedDate(journalDate);
}, [journalDate]);

const handleDateSelect = useCallback(
(day: string) => {
const date = dayjs(day).format('YYYY-MM-DD');
setSelectedDate(date);
journalService.setJournalDate(doc.id, date);
},
[journalService, doc.id]
);

const handleCheck = useCallback(
(_: unknown, v: boolean) => {
if (!v) {
journalService.removeJournalDate(doc.id);
} else {
handleDateSelect(selectedDate);
}
},
[handleDateSelect, journalService, doc.id, selectedDate]
);

const workbench = useService(WorkbenchService).workbench;
const activeView = useLiveData(workbench.activeView$);
const view = useServiceOptional(ViewService)?.view ?? activeView;

const handleOpenDuplicate = useCallback(
(e: React.MouseEvent) => {
e.stopPropagation();
workbench.openSidebar();
view.activeSidebarTab('journal');
},
[view, workbench]
);

const toggle = useCallback(() => {
handleCheck(null, !checked);
}, [checked, handleCheck]);

return (
<PropertyValue className={styles.property} onClick={toggle}>
<div className={styles.root}>
<Checkbox
className={styles.checkbox}
checked={checked}
onChange={handleCheck}
/>
{checked ? (
<Menu
contentOptions={{
onClick: e => e.stopPropagation(),
sideOffset: 10,
alignOffset: -30,
style: { padding: 20 },
}}
rootOptions={{
modal: true,
open: showDatePicker,
onOpenChange: setShowDatePicker,
}}
items={
<DatePicker
weekDays={t['com.affine.calendar-date-picker.week-days']()}
monthNames={t['com.affine.calendar-date-picker.month-names']()}
todayLabel={t['com.affine.calendar-date-picker.today']()}
value={selectedDate}
onChange={handleDateSelect}
/>
}
>
<div data-testid="date-selector" className={styles.date}>
{displayDate}
</div>
</Menu>
) : null}

{checked && conflict ? (
<div
data-testid="conflict-tag"
className={styles.duplicateTag}
onClick={handleOpenDuplicate}
>
{t['com.affine.page-properties.property.journal-duplicated']()}
</div>
) : null}
</div>
</PropertyValue>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import { BlocksuiteHeaderTitle } from '@affine/core/components/blocksuite/block-
import { EditorModeSwitch } from '@affine/core/components/blocksuite/block-suite-mode-switch';
import { useRegisterCopyLinkCommands } from '@affine/core/components/hooks/affine/use-register-copy-link-commands';
import { useDocCollectionPageTitle } from '@affine/core/components/hooks/use-block-suite-workspace-page-title';
import { useJournalInfoHelper } from '@affine/core/components/hooks/use-journal';
import { HeaderDivider } from '@affine/core/components/pure/header';
import { EditorService } from '@affine/core/modules/editor';
import { JournalService } from '@affine/core/modules/journal';
import { ViewIcon, ViewTitle } from '@affine/core/modules/workbench';
import type { Doc } from '@blocksuite/affine/store';
import { useLiveData, useService, type Workspace } from '@toeverything/infra';
Expand Down Expand Up @@ -155,7 +155,8 @@ export function NormalPageHeader({ page, workspace }: PageHeaderProps) {

export function DetailPageHeader(props: PageHeaderProps) {
const { page, workspace } = props;
const { isJournal } = useJournalInfoHelper(page.id);
const journalService = useService(JournalService);
const isJournal = !!useLiveData(journalService.journalDate$(page.id));
const isInTrash = page.meta?.trash;

useRegisterCopyLinkCommands({
Expand Down
Loading

0 comments on commit 8f92be9

Please sign in to comment.