feat(core): add a two-step confirm page to create new journal (#13240)

close AF-2750;

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Introduced a new workspace journals page with date-based navigation,
placeholder UI, and the ability to create daily journals directly from
the page.
* Added a "Today" button for quick navigation to the current day's
journal when viewing other dates.

* **Improvements**
* Enhanced the journal document title display with improved date
formatting and flexible styling.
* Expanded the active state for the journal sidebar button to cover all
journal-related routes.
* Updated journal navigation to open existing entries directly or
navigate to filtered journal listings.

* **Bug Fixes**
* Improved date handling and navigation logic for journal entries to
ensure accurate redirection and creation flows.

* **Style**
* Added new styles for the workspace journals page, including headers,
placeholders, and buttons.

* **Localization**
* Added English translations for journal placeholder text and create
journal prompts.

* **Tests**
* Added confirmation steps in journal creation flows to improve test
reliability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Cats Juice
2025-07-17 10:32:01 +08:00
committed by GitHub
parent 3949714618
commit 46a2ad750f
14 changed files with 304 additions and 30 deletions

View File

@@ -9,20 +9,40 @@ import * as styles from './styles.css';
export const BlocksuiteEditorJournalDocTitle = ({ page }: { page: Store }) => {
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, {
return <BlocksuiteEditorJournalDocTitleUI date={journalDateStr} />;
};
export const BlocksuiteEditorJournalDocTitleUI = ({
date: dateStr,
overrideClassName,
}: {
date?: string;
/**
* The `doc-title-container` class style is defined in editor,
* which means if we use this component outside editor, the style will not work,
* so we provide a className to override
*/
overrideClassName?: string;
}) => {
const localizedJournalDate = i18nTime(dateStr, {
absolute: { accuracy: 'day' },
});
const t = useI18n();
// TODO(catsjuice): i18n
const day = journalDate?.format('dddd') ?? null;
const today = dayjs();
const date = dayjs(dateStr);
const day = dayjs(date).format('dddd') ?? null;
const isToday = date.isSame(today, 'day');
return (
<div className="doc-title-container" data-testid="journal-title">
<div
className={overrideClassName ?? 'doc-title-container'}
data-testid="journal-title"
>
<span data-testid="date">{localizedJournalDate}</span>
{isTodayJournal ? (
{isToday ? (
<span className={styles.titleTodayTag} data-testid="date-today-label">
{t['com.affine.today']()}
</span>

View File

@@ -1,11 +1,14 @@
import type { WeekDatePickerHandle } from '@affine/component';
import { WeekDatePicker } from '@affine/component';
import { useJournalRouteHelper } from '@affine/core/components/hooks/use-journal';
import { JournalService } from '@affine/core/modules/journal';
import {
JOURNAL_DATE_FORMAT,
JournalService,
} from '@affine/core/modules/journal';
import { WorkbenchService } from '@affine/core/modules/workbench';
import type { Store } from '@blocksuite/affine/store';
import { useLiveData, useService } from '@toeverything/infra';
import dayjs from 'dayjs';
import { useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
export interface JournalWeekDatePickerProps {
page: Store;
@@ -17,17 +20,29 @@ export const JournalWeekDatePicker = ({ page }: JournalWeekDatePickerProps) => {
const journalService = useService(JournalService);
const journalDateStr = useLiveData(journalService.journalDate$(page.id));
const journalDate = journalDateStr ? dayjs(journalDateStr) : null;
const { openJournal } = useJournalRouteHelper();
const [date, setDate] = useState(
(journalDate ?? dayjs()).format('YYYY-MM-DD')
(journalDate ?? dayjs()).format(JOURNAL_DATE_FORMAT)
);
const workbench = useService(WorkbenchService).workbench;
useEffect(() => {
if (!journalDate) return;
setDate(journalDate.format('YYYY-MM-DD'));
setDate(journalDate.format(JOURNAL_DATE_FORMAT));
handleRef.current?.setCursor?.(journalDate);
}, [journalDate]);
const openJournal = useCallback(
(date: string) => {
const docs = journalService.journalsByDate$(date).value;
if (docs.length > 0) {
workbench.openDoc(docs[0].id, { at: 'active' });
} else {
workbench.open(`/journals?date=${date}`, { at: 'active' });
}
},
[journalService, workbench]
);
return (
<WeekDatePicker
data-testid="journal-week-picker"