mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-04 08:38:34 +00:00
feat(core): page info adapter for journal (#5561)
Page info adapter + schema. Adapted for journal features. 
This commit is contained in:
@@ -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<string, DocHistory[]>();
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -19,7 +19,7 @@ export const JournalWeekDatePicker = ({
|
||||
page,
|
||||
}: JournalWeekDatePickerProps) => {
|
||||
const handleRef = useRef<WeekDatePickerHandle>(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')
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
39
packages/frontend/core/src/hooks/use-affine-adapter.ts
Normal file
39
packages/frontend/core/src/hooks/use-affine-adapter.ts
Normal file
@@ -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);
|
||||
}
|
||||
@@ -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]
|
||||
);
|
||||
};
|
||||
@@ -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]
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from './atoms';
|
||||
export * from './properties';
|
||||
|
||||
@@ -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<any>;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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<WorkspacePropertiesAdapter>(get => {
|
||||
return of(get(currentWorkspaceAtom)).pipe(
|
||||
filter((workspace): workspace is Workspace => !!workspace),
|
||||
map(workspace => {
|
||||
return new WorkspacePropertiesAdapter(workspace.blockSuiteWorkspace);
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './adapter';
|
||||
export * from './atom';
|
||||
@@ -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<typeof TagOptionSchema>;
|
||||
|
||||
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<typeof WorkspaceFavoriteItemSchema>;
|
||||
|
||||
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
|
||||
>;
|
||||
@@ -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 ? (
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -43,7 +43,7 @@ interface PageItemProps extends HTMLAttributes<HTMLDivElement> {
|
||||
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'));
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ export const Component = (): ReactElement => {
|
||||
setCurrentWorkspace(null);
|
||||
return undefined;
|
||||
}
|
||||
setCurrentWorkspace(workspace ?? null);
|
||||
setCurrentWorkspace(workspace);
|
||||
|
||||
// for debug purpose
|
||||
window.currentWorkspace = workspace;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './create-emotion-cache';
|
||||
export * from './intl-formatter';
|
||||
export * from './string2color';
|
||||
export * from './toast';
|
||||
|
||||
17
packages/frontend/core/src/utils/intl-formatter.ts
Normal file
17
packages/frontend/core/src/utils/intl-formatter.ts
Normal file
@@ -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));
|
||||
};
|
||||
Reference in New Issue
Block a user