From 735e1cb1176db0fc2de212e11abdd71bbfb3947e Mon Sep 17 00:00:00 2001 From: Peng Xiao Date: Mon, 22 Jan 2024 08:25:27 +0000 Subject: [PATCH] feat(core): page info adapter for journal (#5561) Page info adapter + schema. Adapted for journal features. ![image](https://github.com/toeverything/AFFiNE/assets/584378/2731ed2b-a125-4d62-b658-f2aff49d0e17) --- .../affine/page-history-modal/data.ts | 7 +- .../page-history-modal/history-modal.tsx | 9 +- .../journal/date-picker.tsx | 2 +- .../root-app-sidebar/journal-button.tsx | 6 +- .../core/src/hooks/use-affine-adapter.ts | 39 +++++ .../core/src/hooks/use-get-page-info.ts | 29 ---- .../frontend/core/src/hooks/use-journal.ts | 81 +++++++-- .../core/src/modules/workspace/index.ts | 1 + .../modules/workspace/properties/adapter.ts | 156 ++++++++++++++++++ .../src/modules/workspace/properties/atom.ts | 16 ++ .../src/modules/workspace/properties/index.ts | 2 + .../modules/workspace/properties/schema.ts | 101 ++++++++++++ .../detail-page/detail-page-header.tsx | 2 +- .../editor-sidebar/extensions/extensions.tsx | 2 +- .../editor-sidebar/extensions/journal.tsx | 7 +- .../core/src/pages/workspace/index.tsx | 2 +- packages/frontend/core/src/utils/index.ts | 1 + .../frontend/core/src/utils/intl-formatter.ts | 17 ++ 18 files changed, 412 insertions(+), 68 deletions(-) create mode 100644 packages/frontend/core/src/hooks/use-affine-adapter.ts delete mode 100644 packages/frontend/core/src/hooks/use-get-page-info.ts create mode 100644 packages/frontend/core/src/modules/workspace/properties/adapter.ts create mode 100644 packages/frontend/core/src/modules/workspace/properties/atom.ts create mode 100644 packages/frontend/core/src/modules/workspace/properties/index.ts create mode 100644 packages/frontend/core/src/modules/workspace/properties/schema.ts create mode 100644 packages/frontend/core/src/utils/intl-formatter.ts diff --git a/packages/frontend/core/src/components/affine/page-history-modal/data.ts b/packages/frontend/core/src/components/affine/page-history-modal/data.ts index ba716dbdc..3fbde19f9 100644 --- a/packages/frontend/core/src/components/affine/page-history-modal/data.ts +++ b/packages/frontend/core/src/components/affine/page-history-modal/data.ts @@ -1,5 +1,6 @@ import { usePageMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta'; import { useBlockSuiteWorkspacePage } from '@affine/core/hooks/use-block-suite-workspace-page'; +import { timestampToLocalDate } from '@affine/core/utils'; import { DebugLogger } from '@affine/debug'; import { fetchWithTraceReport, @@ -176,11 +177,7 @@ export const useSnapshotPage = ( export const historyListGroupByDay = (histories: DocHistory[]) => { const map = new Map(); for (const history of histories) { - const day = new Date(history.timestamp).toLocaleDateString(undefined, { - year: 'numeric', - month: 'short', - day: 'numeric', - }); + const day = timestampToLocalDate(history.timestamp); const list = map.get(day) ?? []; list.push(history); map.set(day, list); diff --git a/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx b/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx index 7776838d2..70d046e16 100644 --- a/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx +++ b/packages/frontend/core/src/components/affine/page-history-modal/history-modal.tsx @@ -10,6 +10,7 @@ import { useIsWorkspaceOwner } from '@affine/core/hooks/affine/use-is-workspace- import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; import { useUserSubscription } from '@affine/core/hooks/use-subscription'; import { waitForCurrentWorkspaceAtom } from '@affine/core/modules/workspace'; +import { timestampToLocalTime } from '@affine/core/utils'; import { SubscriptionPlan } from '@affine/graphql'; import { Trans } from '@affine/i18n'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; @@ -88,14 +89,6 @@ const ModalContainer = ({ ); }; -const localTimeFormatter = new Intl.DateTimeFormat('en', { - timeStyle: 'short', -}); - -const timestampToLocalTime = (ts: string) => { - return localTimeFormatter.format(new Date(ts)); -}; - interface HistoryEditorPreviewProps { ts?: string; snapshotPage?: Page; diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-header/journal/date-picker.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-header/journal/date-picker.tsx index c33f546ed..a85022814 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-header/journal/date-picker.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-header/journal/date-picker.tsx @@ -19,7 +19,7 @@ export const JournalWeekDatePicker = ({ page, }: JournalWeekDatePickerProps) => { const handleRef = useRef(null); - const { journalDate } = useJournalInfoHelper(page.meta); + const { journalDate } = useJournalInfoHelper(workspace, page.id); const { openJournal } = useJournalHelper(workspace); const [date, setDate] = useState( (journalDate ?? dayjs()).format('YYYY-MM-DD') diff --git a/packages/frontend/core/src/components/root-app-sidebar/journal-button.tsx b/packages/frontend/core/src/components/root-app-sidebar/journal-button.tsx index 49a0fb861..cf37a082b 100644 --- a/packages/frontend/core/src/components/root-app-sidebar/journal-button.tsx +++ b/packages/frontend/core/src/components/root-app-sidebar/journal-button.tsx @@ -19,9 +19,11 @@ export const AppSidebarJournalButton = ({ }: AppSidebarJournalButtonProps) => { const t = useAFFiNEI18N(); const currentPageId = useAtomValue(currentPageIdAtom); - const currentPage = currentPageId ? workspace.getPage(currentPageId) : null; const { openToday } = useJournalHelper(workspace); - const { journalDate, isJournal } = useJournalInfoHelper(currentPage?.meta); + const { journalDate, isJournal } = useJournalInfoHelper( + workspace, + currentPageId + ); const params = useParams(); const isJournalActive = isJournal && !!params.pageId; diff --git a/packages/frontend/core/src/hooks/use-affine-adapter.ts b/packages/frontend/core/src/hooks/use-affine-adapter.ts new file mode 100644 index 000000000..8e0be39bd --- /dev/null +++ b/packages/frontend/core/src/hooks/use-affine-adapter.ts @@ -0,0 +1,39 @@ +import type { Workspace } from '@blocksuite/store'; +import { useAtomValue } from 'jotai'; +import { useEffect, useMemo, useReducer } from 'react'; + +import { + currentWorkspacePropertiesAdapterAtom, + WorkspacePropertiesAdapter, +} from '../modules/workspace'; + +const useReactiveAdapter = (adapter: WorkspacePropertiesAdapter) => { + const [, forceUpdate] = useReducer(c => c + 1, 0); + + useEffect(() => { + // todo: track which properties are used and then filter by property path change + // using Y.YEvent.path + function observe() { + forceUpdate(); + } + adapter.properties.observeDeep(observe); + return () => { + adapter.properties.unobserveDeep(observe); + }; + }, [adapter]); + + return adapter; +}; + +export function useCurrentWorkspacePropertiesAdapter() { + const adapter = useAtomValue(currentWorkspacePropertiesAdapterAtom); + return useReactiveAdapter(adapter); +} + +export function useWorkspacePropertiesAdapter(workspace: Workspace) { + const adapter = useMemo( + () => new WorkspacePropertiesAdapter(workspace), + [workspace] + ); + return useReactiveAdapter(adapter); +} diff --git a/packages/frontend/core/src/hooks/use-get-page-info.ts b/packages/frontend/core/src/hooks/use-get-page-info.ts deleted file mode 100644 index 024957ad7..000000000 --- a/packages/frontend/core/src/hooks/use-get-page-info.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta'; -import type { GetPageInfoById } from '@affine/env/page-info'; -import type { Workspace } from '@blocksuite/store'; -import { useAtomValue } from 'jotai'; -import { useCallback, useMemo } from 'react'; - -import { pageSettingsAtom } from '../atoms'; - -export const useGetPageInfoById = (workspace: Workspace): GetPageInfoById => { - const pageMetas = useBlockSuitePageMeta(workspace); - const pageMap = useMemo( - () => Object.fromEntries(pageMetas.map(page => [page.id, page])), - [pageMetas] - ); - const pageSettings = useAtomValue(pageSettingsAtom); - return useCallback( - (id: string) => { - const page = pageMap[id]; - if (!page) { - return; - } - return { - ...page, - isEdgeless: pageSettings[id]?.mode === 'edgeless', - }; - }, - [pageMap, pageSettings] - ); -}; diff --git a/packages/frontend/core/src/hooks/use-journal.ts b/packages/frontend/core/src/hooks/use-journal.ts index 949f8c5dc..4fe46c6ba 100644 --- a/packages/frontend/core/src/hooks/use-journal.ts +++ b/packages/frontend/core/src/hooks/use-journal.ts @@ -1,31 +1,34 @@ -import type { PageMeta } from '@blocksuite/store'; import { initEmptyPage } from '@toeverything/infra/blocksuite'; import dayjs from 'dayjs'; import { useCallback, useMemo } from 'react'; import type { BlockSuiteWorkspace } from '../shared'; +import { timestampToLocalDate } from '../utils'; +import { useWorkspacePropertiesAdapter } from './use-affine-adapter'; import { useBlockSuiteWorkspaceHelper } from './use-block-suite-workspace-helper'; import { useNavigateHelper } from './use-navigate-helper'; type MaybeDate = Date | string | number; export const JOURNAL_DATE_FORMAT = 'YYYY-MM-DD'; -function isPageJournal(pageMeta?: PageMeta) { +function isJournalString(j?: string | false) { if (!runtimeConfig.enableJournal) return false; - return !!(pageMeta && pageMeta.title.match(/^\d{4}-\d{2}-\d{2}$/)); + return j ? !!j?.match(/^\d{4}-\d{2}-\d{2}$/) : false; } -function getJournalDate(pageMeta?: PageMeta) { - if (!isPageJournal(pageMeta)) return null; - if (!pageMeta?.title) return null; - if (!dayjs(pageMeta.title).isValid()) return null; - return dayjs(pageMeta.title); +function toDayjs(j?: string | false) { + if (!j || !isJournalString(j)) return null; + const day = dayjs(j); + if (!day.isValid()) return null; + return day; } export const useJournalHelper = (workspace: BlockSuiteWorkspace) => { const bsWorkspaceHelper = useBlockSuiteWorkspaceHelper(workspace); const navigateHelper = useNavigateHelper(); + const adapter = useWorkspacePropertiesAdapter(workspace); + /** * @internal */ @@ -36,9 +39,17 @@ export const useJournalHelper = (workspace: BlockSuiteWorkspace) => { initEmptyPage(page, title).catch(err => console.error('Failed to load journal page', err) ); + adapter.setJournalPageDateString(page.id, title); return page; }, - [bsWorkspaceHelper] + [adapter, bsWorkspaceHelper] + ); + + const isPageJournal = useCallback( + (pageId: string) => { + return !!adapter.getJournalPageDateString(pageId); + }, + [adapter] ); /** @@ -48,14 +59,15 @@ export const useJournalHelper = (workspace: BlockSuiteWorkspace) => { (maybeDate: MaybeDate) => { const day = dayjs(maybeDate); return Array.from(workspace.pages.values()).filter(page => { - if (!isPageJournal(page.meta)) return false; + const pageId = page.id; + if (!isPageJournal(pageId)) return false; if (page.meta.trash) return false; - const journalDate = getJournalDate(page.meta); + const journalDate = adapter.getJournalPageDateString(page.id); if (!journalDate) return false; return day.isSame(journalDate, 'day'); }); }, - [workspace.pages] + [adapter, isPageJournal, workspace.pages] ); /** @@ -89,26 +101,59 @@ export const useJournalHelper = (workspace: BlockSuiteWorkspace) => { openJournal(date); }, [openJournal]); + const getJournalDateString = useCallback( + (pageId: string) => { + return adapter.getJournalPageDateString(pageId); + }, + [adapter] + ); + + const getLocalizedJournalDateString = useCallback( + (pageId: string) => { + const journalDateString = getJournalDateString(pageId); + if (!journalDateString) return null; + return timestampToLocalDate(journalDateString); + }, + [getJournalDateString] + ); + return useMemo( () => ({ getJournalsByDate, getJournalByDate, + getJournalDateString, + getLocalizedJournalDateString, openJournal, openToday, + isPageJournal, }), - [getJournalByDate, getJournalsByDate, openJournal, openToday] + [ + getJournalByDate, + getJournalDateString, + getJournalsByDate, + getLocalizedJournalDateString, + isPageJournal, + openJournal, + openToday, + ] ); }; -export const useJournalInfoHelper = (pageMeta?: PageMeta) => { - const isJournal = isPageJournal(pageMeta); - const journalDate = useMemo(() => getJournalDate(pageMeta), [pageMeta]); +export const useJournalInfoHelper = ( + workspace: BlockSuiteWorkspace, + pageId?: string | null +) => { + const { isPageJournal, getJournalDateString } = useJournalHelper(workspace); + const journalDate = useMemo( + () => (pageId ? toDayjs(getJournalDateString(pageId)) : null), + [getJournalDateString, pageId] + ); return useMemo( () => ({ - isJournal, + isJournal: pageId ? isPageJournal(pageId) : false, journalDate, }), - [isJournal, journalDate] + [isPageJournal, journalDate, pageId] ); }; diff --git a/packages/frontend/core/src/modules/workspace/index.ts b/packages/frontend/core/src/modules/workspace/index.ts index 2b93289c3..645c9ae88 100644 --- a/packages/frontend/core/src/modules/workspace/index.ts +++ b/packages/frontend/core/src/modules/workspace/index.ts @@ -1 +1,2 @@ export * from './atoms'; +export * from './properties'; diff --git a/packages/frontend/core/src/modules/workspace/properties/adapter.ts b/packages/frontend/core/src/modules/workspace/properties/adapter.ts new file mode 100644 index 000000000..dad50bdb8 --- /dev/null +++ b/packages/frontend/core/src/modules/workspace/properties/adapter.ts @@ -0,0 +1,156 @@ +// the adapter is to bridge the workspace rootdoc & native js bindings + +import { createYProxy, type Workspace, type Y } from '@blocksuite/store'; +import { defaultsDeep } from 'lodash-es'; + +import { + PagePropertyType, + PageSystemPropertyId, + type TagOption, + type WorkspaceAffineProperties, + type WorkspaceFavoriteItem, +} from './schema'; + +const AFFINE_PROPERTIES_ID = 'affine:workspace-properties'; + +/** + * WorkspacePropertiesAdapter is a wrapper for workspace properties. + * Users should not directly access the workspace properties via yjs, but use this adapter instead. + * + * Question for enhancement in the future: + * May abstract the adapter for each property type, e.g. PagePropertiesAdapter, SchemaAdapter, etc. + * So that the adapter could be more focused and easier to maintain (like assigning default values) + * However the properties for an abstraction may not be limited to a single yjs map. + */ +export class WorkspacePropertiesAdapter { + // provides a easy-to-use interface for workspace properties + private readonly proxy: WorkspaceAffineProperties; + public readonly properties: Y.Map; + + constructor(private readonly workspace: Workspace) { + // check if properties exists, if not, create one + const rootDoc = workspace.doc; + this.properties = rootDoc.getMap(AFFINE_PROPERTIES_ID); + this.proxy = createYProxy(this.properties); + + // fixme: deal with migration issue? + this.ensureRootProperties(); + } + + private ensureRootProperties() { + // todo: deal with schema change issue + // fixme: may not to be called every time + defaultsDeep(this.proxy, { + schema: { + pageProperties: { + custom: {}, + system: { + journal: { + id: PageSystemPropertyId.Journal, + name: 'Journal', + source: 'system', + type: PagePropertyType.Date, + }, + tags: { + id: PageSystemPropertyId.Tags, + name: 'Tags', + source: 'system', + type: PagePropertyType.Tags, + options: this.workspace.meta.properties.tags?.options ?? [], // better use a one time migration + }, + }, + }, + }, + favorites: {}, + pageProperties: {}, + }); + } + + private ensurePageProperties(pageId: string) { + // fixme: may not to be called every time + defaultsDeep(this.proxy.pageProperties, { + [pageId]: { + custom: {}, + system: { + [PageSystemPropertyId.Journal]: { + id: PageSystemPropertyId.Journal, + value: false, + }, + [PageSystemPropertyId.Tags]: { + id: PageSystemPropertyId.Tags, + value: [], + }, + }, + }, + }); + } + + get schema() { + return this.proxy.schema; + } + + get favorites() { + return this.proxy.favorites; + } + + get pageProperties() { + return this.proxy.pageProperties; + } + + // ====== utilities ====== + + getPageProperties(pageId: string) { + return this.pageProperties[pageId]; + } + + isFavorite(id: string, type: WorkspaceFavoriteItem['type']) { + return this.favorites[id]?.type === type; + } + + getJournalPageDateString(id: string) { + this.ensurePageProperties(id); + return this.pageProperties[id].system[PageSystemPropertyId.Journal].value; + } + + setJournalPageDateString(id: string, date: string) { + this.ensurePageProperties(id); + const pageProperties = this.pageProperties[id]; + pageProperties.system[PageSystemPropertyId.Journal].value = date; + } + + get tagOptions() { + return this.schema.pageProperties.system[PageSystemPropertyId.Tags].options; + } + + // page tags could be reactive + getPageTags(pageId: string) { + this.ensurePageProperties(pageId); + const tags = + this.pageProperties[pageId].system[PageSystemPropertyId.Tags].value; + const optionsMap = Object.fromEntries(this.tagOptions.map(o => [o.id, o])); + return tags.map(tag => optionsMap[tag]).filter((t): t is TagOption => !!t); + } + + addPageTag(pageId: string, tag: TagOption | string) { + this.ensurePageProperties(pageId); + const tags = this.getPageTags(pageId); + const tagId = typeof tag === 'string' ? tag : tag.id; + if (tags.some(t => t.id === tagId)) { + return; + } + const pageProperties = this.pageProperties[pageId]; + pageProperties.system[PageSystemPropertyId.Tags].value.push(tagId); + } + + removePageTag(pageId: string, tag: TagOption | string) { + this.ensurePageProperties(pageId); + const tags = this.getPageTags(pageId); + const tagId = typeof tag === 'string' ? tag : tag.id; + const index = tags.findIndex(t => t.id === tagId); + if (index === -1) { + return; + } + const pageProperties = this.pageProperties[pageId]; + pageProperties.system[PageSystemPropertyId.Tags].value.splice(index, 1); + } +} diff --git a/packages/frontend/core/src/modules/workspace/properties/atom.ts b/packages/frontend/core/src/modules/workspace/properties/atom.ts new file mode 100644 index 000000000..30db075d7 --- /dev/null +++ b/packages/frontend/core/src/modules/workspace/properties/atom.ts @@ -0,0 +1,16 @@ +import type { Workspace } from '@affine/workspace/workspace'; +import { atomWithObservable } from 'jotai/utils'; +import { filter, map, of } from 'rxjs'; + +import { currentWorkspaceAtom } from '../atoms'; +import { WorkspacePropertiesAdapter } from './adapter'; + +export const currentWorkspacePropertiesAdapterAtom = + atomWithObservable(get => { + return of(get(currentWorkspaceAtom)).pipe( + filter((workspace): workspace is Workspace => !!workspace), + map(workspace => { + return new WorkspacePropertiesAdapter(workspace.blockSuiteWorkspace); + }) + ); + }); diff --git a/packages/frontend/core/src/modules/workspace/properties/index.ts b/packages/frontend/core/src/modules/workspace/properties/index.ts new file mode 100644 index 000000000..ed27f8989 --- /dev/null +++ b/packages/frontend/core/src/modules/workspace/properties/index.ts @@ -0,0 +1,2 @@ +export * from './adapter'; +export * from './atom'; diff --git a/packages/frontend/core/src/modules/workspace/properties/schema.ts b/packages/frontend/core/src/modules/workspace/properties/schema.ts new file mode 100644 index 000000000..0b01637a5 --- /dev/null +++ b/packages/frontend/core/src/modules/workspace/properties/schema.ts @@ -0,0 +1,101 @@ +import { z } from 'zod'; + +// ===== workspace-wide page property schema ===== +export const TagOptionSchema = z.object({ + id: z.string(), + name: z.string(), + color: z.string(), +}); + +export type TagOption = z.infer; + +export enum PageSystemPropertyId { + Tags = 'tags', + Journal = 'journal', +} + +export enum PagePropertyType { + String = 'string', + Number = 'number', + Boolean = 'boolean', + Date = 'date', + Tags = 'tags', +} + +export const PagePropertyMetaBaseSchema = z.object({ + id: z.string(), + name: z.string(), + source: z.string(), + type: z.string(), +}); + +export const PageSystemPropertyMetaBaseSchema = + PagePropertyMetaBaseSchema.extend({ + source: z.literal('system'), + }); + +export const PageCustomPropertyMetaSchema = PagePropertyMetaBaseSchema.extend({ + source: z.literal('custom'), + type: z.nativeEnum(PagePropertyType), +}); + +// ====== page info schema ====== +export const PageInfoItemSchema = z.object({ + id: z.string(), // property id. Maps to PagePropertyMetaSchema.id + hidden: z.boolean().optional(), + value: z.any(), // corresponds to PagePropertyMetaSchema.type +}); + +export const PageInfoJournalItemSchema = PageInfoItemSchema.extend({ + id: z.literal(PageSystemPropertyId.Journal), + value: z.union([z.string(), z.literal(false)]), +}); + +export const PageInfoTagsItemSchema = PageInfoItemSchema.extend({ + id: z.literal(PageSystemPropertyId.Tags), + value: z.array(z.string()), +}); + +// ====== workspace properties schema ====== +export const WorkspaceFavoriteItemSchema = z.object({ + id: z.string(), + order: z.number(), + type: z.enum(['page', 'collection']), +}); + +export type WorkspaceFavoriteItem = z.infer; + +const WorkspaceAffinePropertiesSchemaSchema = z.object({ + pageProperties: z.object({ + custom: z.record(PageCustomPropertyMetaSchema), + system: z.object({ + [PageSystemPropertyId.Journal]: PageSystemPropertyMetaBaseSchema.extend({ + id: z.literal(PageSystemPropertyId.Journal), + type: z.literal(PagePropertyType.Date), + }), + [PageSystemPropertyId.Tags]: PagePropertyMetaBaseSchema.extend({ + id: z.literal(PageSystemPropertyId.Tags), + type: z.literal(PagePropertyType.Tags), + options: z.array(TagOptionSchema), + }), + }), + }), +}); + +const WorkspacePagePropertiesSchema = z.object({ + custom: z.record(PageInfoItemSchema.extend({ order: z.number() })), + system: z.object({ + [PageSystemPropertyId.Journal]: PageInfoJournalItemSchema, + [PageSystemPropertyId.Tags]: PageInfoTagsItemSchema, + }), +}); + +export const WorkspaceAffinePropertiesSchema = z.object({ + schema: WorkspaceAffinePropertiesSchemaSchema, + favorites: z.record(WorkspaceFavoriteItemSchema), + pageProperties: z.record(WorkspacePagePropertiesSchema), +}); + +export type WorkspaceAffineProperties = z.infer< + typeof WorkspaceAffinePropertiesSchema +>; diff --git a/packages/frontend/core/src/pages/workspace/detail-page/detail-page-header.tsx b/packages/frontend/core/src/pages/workspace/detail-page/detail-page-header.tsx index e2e432e53..e6f3c1e86 100644 --- a/packages/frontend/core/src/pages/workspace/detail-page/detail-page-header.tsx +++ b/packages/frontend/core/src/pages/workspace/detail-page/detail-page-header.tsx @@ -178,7 +178,7 @@ export function NormalPageHeader({ export function DetailPageHeader(props: PageHeaderProps) { const { page } = props; - const { isJournal } = useJournalInfoHelper(page.meta); + const { isJournal } = useJournalInfoHelper(page.workspace, page.id); const isInTrash = page.meta.trash; return isJournal && !isInTrash ? ( diff --git a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/extensions.tsx b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/extensions.tsx index 4803c34dd..b3a1b8ed0 100644 --- a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/extensions.tsx +++ b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/extensions.tsx @@ -27,7 +27,7 @@ export const ExtensionTabs = ({ page }: ExtensionTabsProps) => { FeatureType.Copilot ); - const { isJournal } = useJournalInfoHelper(page.meta); + const { isJournal } = useJournalInfoHelper(page.workspace, page.id); const exts = useAtomValue(editorExtensionsAtom).filter(ext => { if (ext.name === 'copilot' && !copilotEnabled) return false; diff --git a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/journal.tsx b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/journal.tsx index 05d34ff8e..f97008b0e 100644 --- a/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/journal.tsx +++ b/packages/frontend/core/src/pages/workspace/detail-page/editor-sidebar/extensions/journal.tsx @@ -43,7 +43,7 @@ interface PageItemProps extends HTMLAttributes { right?: ReactNode; } const PageItem = ({ page, right, className, ...attrs }: PageItemProps) => { - const { isJournal } = useJournalInfoHelper(page.meta); + const { isJournal } = useJournalInfoHelper(page.workspace, page.id); const Icon = isJournal ? TodayIcon @@ -77,7 +77,10 @@ interface JournalBlockProps extends EditorExtensionProps { const EditorJournalPanel = (props: EditorExtensionProps) => { const { workspace, page } = props; - const { journalDate, isJournal } = useJournalInfoHelper(page?.meta); + const { journalDate, isJournal } = useJournalInfoHelper( + page.workspace, + page.id + ); const { openJournal } = useJournalHelper(workspace); const [date, setDate] = useState(dayjs().format('YYYY-MM-DD')); diff --git a/packages/frontend/core/src/pages/workspace/index.tsx b/packages/frontend/core/src/pages/workspace/index.tsx index a21b87daa..d87c31fc9 100644 --- a/packages/frontend/core/src/pages/workspace/index.tsx +++ b/packages/frontend/core/src/pages/workspace/index.tsx @@ -52,7 +52,7 @@ export const Component = (): ReactElement => { setCurrentWorkspace(null); return undefined; } - setCurrentWorkspace(workspace ?? null); + setCurrentWorkspace(workspace); // for debug purpose window.currentWorkspace = workspace; diff --git a/packages/frontend/core/src/utils/index.ts b/packages/frontend/core/src/utils/index.ts index 196e77843..d410ad668 100644 --- a/packages/frontend/core/src/utils/index.ts +++ b/packages/frontend/core/src/utils/index.ts @@ -1,3 +1,4 @@ export * from './create-emotion-cache'; +export * from './intl-formatter'; export * from './string2color'; export * from './toast'; diff --git a/packages/frontend/core/src/utils/intl-formatter.ts b/packages/frontend/core/src/utils/intl-formatter.ts new file mode 100644 index 000000000..650401344 --- /dev/null +++ b/packages/frontend/core/src/utils/intl-formatter.ts @@ -0,0 +1,17 @@ +const timeFormatter = new Intl.DateTimeFormat(undefined, { + timeStyle: 'short', +}); + +const dateFormatter = new Intl.DateTimeFormat(undefined, { + year: 'numeric', + month: 'short', + day: 'numeric', +}); + +export const timestampToLocalTime = (ts: string) => { + return timeFormatter.format(new Date(ts)); +}; + +export const timestampToLocalDate = (ts: string) => { + return dateFormatter.format(new Date(ts)); +};