diff --git a/packages/frontend/core/src/components/affine/page-properties/info-modal/links-row.css.ts b/packages/frontend/core/src/components/affine/page-properties/info-modal/links-row.css.ts index e7ae8f64e741..4f093464f629 100644 --- a/packages/frontend/core/src/components/affine/page-properties/info-modal/links-row.css.ts +++ b/packages/frontend/core/src/components/affine/page-properties/info-modal/links-row.css.ts @@ -32,5 +32,6 @@ globalStyle(`${wrapper} span`, { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', - borderBottom: 'none', + // don't modify border width to avoid layout shift + borderBottomColor: 'transparent', }); diff --git a/packages/frontend/core/src/components/affine/page-properties/info-modal/links-row.tsx b/packages/frontend/core/src/components/affine/page-properties/info-modal/links-row.tsx index 6e46f0e34f94..adf796ea9918 100644 --- a/packages/frontend/core/src/components/affine/page-properties/info-modal/links-row.tsx +++ b/packages/frontend/core/src/components/affine/page-properties/info-modal/links-row.tsx @@ -1,8 +1,6 @@ import type { Backlink, Link } from '@affine/core/modules/doc-link'; -import { useContext } from 'react'; import { AffinePageReference } from '../../reference-link'; -import { managerContext } from '../common'; import * as styles from './links-row.css'; export const LinksRow = ({ @@ -16,20 +14,18 @@ export const LinksRow = ({ className?: string; onClick?: () => void; }) => { - const manager = useContext(managerContext); return (
{label} ยท {references.length}
- {references.map(link => ( + {references.map((link, index) => ( (
)} - docCollection={manager.workspace.docCollection} /> ))}
diff --git a/packages/frontend/core/src/components/affine/page-properties/table.tsx b/packages/frontend/core/src/components/affine/page-properties/table.tsx index d7e987940d9f..1d138a891cc9 100644 --- a/packages/frontend/core/src/components/affine/page-properties/table.tsx +++ b/packages/frontend/core/src/components/affine/page-properties/table.tsx @@ -379,8 +379,6 @@ export const PageBacklinksPopup = ({ backlinks, children, }: PageBacklinksPopupProps) => { - const manager = useContext(managerContext); - return ( ))}
diff --git a/packages/frontend/core/src/components/affine/reference-link/index.tsx b/packages/frontend/core/src/components/affine/reference-link/index.tsx index 37714fd05bbf..3094c0ebab42 100644 --- a/packages/frontend/core/src/components/affine/reference-link/index.tsx +++ b/packages/frontend/core/src/components/affine/reference-link/index.tsx @@ -1,5 +1,5 @@ -import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-page-meta'; -import { useJournalHelper } from '@affine/core/components/hooks/use-journal'; +import { useJournalInfoHelper } from '@affine/core/components/hooks/use-journal'; +import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { PeekViewService, useInsidePeekView, @@ -8,15 +8,8 @@ import { WorkbenchLink } from '@affine/core/modules/workbench'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import type { DocMode } from '@blocksuite/blocks'; -import { - BlockLinkIcon, - DeleteIcon, - LinkedEdgelessIcon, - LinkedPageIcon, - TodayIcon, -} from '@blocksuite/icons/rc'; import type { DocCollection } from '@blocksuite/store'; -import { DocsService, useLiveData, useService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import { nanoid } from 'nanoid'; import { type PropsWithChildren, @@ -29,77 +22,18 @@ import { Link } from 'react-router-dom'; import * as styles from './styles.css'; -export interface PageReferenceRendererOptions { - pageId: string; - docCollection: DocCollection; - pageMetaHelper: ReturnType; - journalHelper: ReturnType; - t: ReturnType; - docMode?: DocMode; - // Link to block or element - linkToNode?: boolean; -} - -// use a function to be rendered in the lit renderer -export function pageReferenceRenderer({ - pageId, - pageMetaHelper, - journalHelper, - t, - docMode, - linkToNode = false, -}: PageReferenceRendererOptions) { - const { isPageJournal, getLocalizedJournalDateString } = journalHelper; - const referencedPage = pageMetaHelper.getDocMeta(pageId); - let title = - referencedPage?.title ?? t['com.affine.editor.reference-not-found'](); - - let Icon = DeleteIcon; - - if (referencedPage) { - if (docMode === 'edgeless') { - Icon = LinkedEdgelessIcon; - } else { - Icon = LinkedPageIcon; - } - if (linkToNode) { - Icon = BlockLinkIcon; - } - } - - const isJournal = isPageJournal(pageId); - const localizedJournalDate = getLocalizedJournalDateString(pageId); - if (isJournal && localizedJournalDate) { - title = localizedJournalDate; - Icon = TodayIcon; - } - - return ( - <> - - - {title ? title : t['Untitled']()} - - - ); -} - export function AffinePageReference({ pageId, - docCollection, wrapper: Wrapper, params, }: { pageId: string; - docCollection: DocCollection; wrapper?: React.ComponentType; params?: URLSearchParams; }) { + const docDisplayMetaService = useService(DocDisplayMetaService); + const journalHelper = useJournalInfoHelper(); const t = useI18n(); - const pageMetaHelper = useDocMetaHelper(); - const journalHelper = useJournalHelper(docCollection); - const docsService = useService(DocsService); - const mode = useLiveData(docsService.list.primaryMode$(pageId)); let linkWithMode: DocMode | null = null; let linkToNode = false; @@ -111,15 +45,23 @@ export function AffinePageReference({ linkToNode = params.has('blockIds') || params.has('elementIds'); } - const el = pageReferenceRenderer({ - docMode: linkWithMode ?? mode ?? 'page', - pageId, - pageMetaHelper, - journalHelper, - docCollection, - t, - linkToNode, - }); + const Icon = useLiveData( + docDisplayMetaService.icon$(pageId, { + mode: linkWithMode ?? undefined, + reference: true, + referenceToNode: linkToNode, + }) + ); + const title = useLiveData(docDisplayMetaService.title$(pageId)); + + const el = ( + <> + + + {typeof title === 'string' ? title : t[title.key]()} + + + ); const ref = useRef(null); @@ -186,11 +128,9 @@ export function AffineSharedPageReference({ wrapper?: React.ComponentType; params?: URLSearchParams; }) { + const docDisplayMetaService = useService(DocDisplayMetaService); + const journalHelper = useJournalInfoHelper(); const t = useI18n(); - const pageMetaHelper = useDocMetaHelper(); - const journalHelper = useJournalHelper(docCollection); - const docsService = useService(DocsService); - const mode = useLiveData(docsService.list.primaryMode$(pageId)); let linkWithMode: DocMode | null = null; let linkToNode = false; @@ -202,15 +142,22 @@ export function AffineSharedPageReference({ linkToNode = params.has('blockIds') || params.has('elementIds'); } - const el = pageReferenceRenderer({ - docMode: linkWithMode ?? mode ?? 'page', - pageId, - pageMetaHelper, - journalHelper, - docCollection, - t, - linkToNode, - }); + const Icon = useLiveData( + docDisplayMetaService.icon$(pageId, { + mode: linkWithMode ?? undefined, + reference: true, + referenceToNode: linkToNode, + }) + ); + const title = useLiveData(docDisplayMetaService.title$(pageId)); + const el = ( + <> + + + {typeof title === 'string' ? title : t[title.key]()} + + + ); const ref = useRef(null); diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.tsx index 7adf3527fc92..9c201c988a21 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/bi-directional-link-panel.tsx @@ -1,10 +1,6 @@ import { DocLinksService } from '@affine/core/modules/doc-link'; import { useI18n } from '@affine/i18n'; -import { - useLiveData, - useServices, - WorkspaceService, -} from '@toeverything/infra'; +import { useLiveData, useServices } from '@toeverything/infra'; import { useCallback, useState } from 'react'; import { AffinePageReference } from '../../affine/reference-link'; @@ -12,9 +8,8 @@ import * as styles from './bi-directional-link-panel.css'; export const BiDirectionalLinkPanel = () => { const [show, setShow] = useState(false); - const { docLinksService, workspaceService } = useServices({ + const { docLinksService } = useServices({ DocLinksService, - WorkspaceService, }); const t = useI18n(); @@ -50,11 +45,7 @@ export const BiDirectionalLinkPanel = () => { {backlinks.map(link => (
- +
))} @@ -68,11 +59,7 @@ export const BiDirectionalLinkPanel = () => { key={`${link.docId}-${link.params?.toString()}-${i}`} className={styles.link} > - + ))} diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/journal-doc-title.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/journal-doc-title.tsx index 76e29a285d23..d42a8496c15e 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/journal-doc-title.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/journal-doc-title.tsx @@ -6,7 +6,7 @@ import * as styles from './styles.css'; export const BlocksuiteEditorJournalDocTitle = ({ page }: { page: Doc }) => { const { localizedJournalDate, isTodayJournal, journalDate } = - useJournalInfoHelper(page.collection, page.id); + useJournalInfoHelper(page.id); const t = useI18n(); // TODO(catsjuice): i18n diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx index 30c10ab0ea88..70b653b30ba5 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx @@ -72,7 +72,7 @@ interface BlocksuiteEditorProps { shared?: boolean; } -const usePatchSpecs = (page: Doc, shared: boolean, mode: DocMode) => { +const usePatchSpecs = (shared: boolean, mode: DocMode) => { const [reactToLit, portals] = useLitPortalFactory(); const { peekViewService, @@ -110,15 +110,9 @@ const usePatchSpecs = (page: Doc, shared: boolean, mode: DocMode) => { ); } - return ( - - ); + return ; }; - }, [page.collection, workspaceService]); + }, [workspaceService]); const specs = useMemo(() => { const enableAI = featureFlagService.flags.enable_ai.value; @@ -184,7 +178,7 @@ export const BlocksuiteDocEditor = forwardRef< ) { const titleRef = useRef(null); const docRef = useRef(null); - const { isJournal } = useJournalInfoHelper(page.collection, page.id); + const { isJournal } = useJournalInfoHelper(page.id); const editorSettingService = useService(EditorSettingService); @@ -216,7 +210,7 @@ export const BlocksuiteDocEditor = forwardRef< [externalTitleRef] ); - const [specs, portals] = usePatchSpecs(page, !!shared, 'page'); + const [specs, portals] = usePatchSpecs(!!shared, 'page'); const displayBiDirectionalLink = useLiveData( editorSettingService.editorSetting.settings$.selector( @@ -257,7 +251,7 @@ export const BlocksuiteEdgelessEditor = forwardRef< EdgelessEditor, BlocksuiteEditorProps >(function BlocksuiteEdgelessEditor({ page, shared }, ref) { - const [specs, portals] = usePatchSpecs(page, !!shared, 'edgeless'); + const [specs, portals] = usePatchSpecs(!!shared, 'edgeless'); const editorRef = useRef(null); const onDocRef = useCallback( diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/widgets/linked.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/widgets/linked.ts index fd4c8ea163f4..22fa16841553 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/widgets/linked.ts +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/widgets/linked.ts @@ -1,20 +1,12 @@ +import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { WorkspacePropertiesAdapter } from '@affine/core/modules/properties'; -import { I18n, i18nTime } from '@affine/i18n'; +import { I18n } from '@affine/i18n'; import { track } from '@affine/track'; import type { EditorHost } from '@blocksuite/block-std'; import type { AffineInlineEditor } from '@blocksuite/blocks'; import { LinkedWidgetUtils } from '@blocksuite/blocks'; -import { - LinkedEdgelessIcon, - LinkedPageIcon, - TodayIcon, -} from '@blocksuite/icons/lit'; import type { DocMeta } from '@blocksuite/store'; -import { - DocsService, - type FrameworkProvider, - WorkspaceService, -} from '@toeverything/infra'; +import { type FrameworkProvider, WorkspaceService } from '@toeverything/infra'; // TODO: fix the type export function createLinkedWidgetConfig( @@ -33,6 +25,7 @@ export function createLinkedWidgetConfig( const isJournal = (d: DocMeta) => !!adapter.getJournalPageDateString(d.id); + const docDisplayMetaService = framework.get(DocDisplayMetaService); const docMetas = rawMetas .filter(meta => { if (isJournal(meta) && !meta.updatedDate) { @@ -41,37 +34,28 @@ export function createLinkedWidgetConfig( return !meta.trash; }) .map(meta => { - if (isJournal(meta)) { - const date = adapter.getJournalPageDateString(meta.id); - if (date) { - const title = i18nTime(date, { absolute: { accuracy: 'day' } }); - return { ...meta, title }; - } - } - if (!meta.title) { - const title = I18n['Untitled'](); - return { ...meta, title }; - } - return meta; + const title = docDisplayMetaService.title$(meta.id).value; + return { + ...meta, + title: typeof title === 'string' ? title : I18n[title.key](), + }; }) .filter(({ title }) => isFuzzyMatch(title, query)); // TODO need i18n if BlockSuite supported const MAX_DOCS = 6; - const docsService = framework.get(DocsService); - const isEdgeless = (d: DocMeta) => - docsService.list.getPrimaryMode(d.id) === 'edgeless'; return Promise.resolve([ { name: 'Link to Doc', items: docMetas.map(doc => ({ key: doc.id, name: doc.title, - icon: isJournal(doc) - ? TodayIcon() - : isEdgeless(doc) - ? LinkedEdgelessIcon() - : LinkedPageIcon(), + icon: docDisplayMetaService + .icon$(doc.id, { + type: 'lit', + reference: true, + }) + .value(), action: () => { abort(); LinkedWidgetUtils.insertLinkedNode({ 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 db65f06a22ac..ac76a12a1904 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(docCollection, page.id); + const { journalDate } = useJournalInfoHelper(page.id); const { openJournal } = useJournalRouteHelper(docCollection); const [date, setDate] = useState( (journalDate ?? dayjs()).format('YYYY-MM-DD') diff --git a/packages/frontend/core/src/components/hooks/use-block-suite-page-meta.ts b/packages/frontend/core/src/components/hooks/use-block-suite-page-meta.ts index 77ef800a0221..98604a5d2fe7 100644 --- a/packages/frontend/core/src/components/hooks/use-block-suite-page-meta.ts +++ b/packages/frontend/core/src/components/hooks/use-block-suite-page-meta.ts @@ -4,7 +4,7 @@ import { useCallback, useMemo } from 'react'; import { useAsyncCallback } from './affine-async-hooks'; import { useAllBlockSuiteDocMeta } from './use-all-block-suite-page-meta'; -import { useJournalHelper } from './use-journal'; +import { useJournalInfoHelper } from './use-journal'; /** * Get pageMetas excluding journal pages without updatedDate @@ -13,7 +13,7 @@ import { useJournalHelper } from './use-journal'; */ export function useBlockSuiteDocMeta(docCollection: DocCollection) { const pageMetas = useAllBlockSuiteDocMeta(docCollection); - const { isPageJournal } = useJournalHelper(docCollection); + const { isPageJournal } = useJournalInfoHelper(); return useMemo( () => pageMetas.filter( diff --git a/packages/frontend/core/src/components/hooks/use-block-suite-workspace-page-title.ts b/packages/frontend/core/src/components/hooks/use-block-suite-workspace-page-title.ts index d0ba731ecb14..c5339174957e 100644 --- a/packages/frontend/core/src/components/hooks/use-block-suite-workspace-page-title.ts +++ b/packages/frontend/core/src/components/hooks/use-block-suite-workspace-page-title.ts @@ -4,7 +4,7 @@ import type { Atom } from 'jotai'; import { atom, useAtomValue } from 'jotai'; import { useCallback } from 'react'; -import { useJournalHelper, useJournalInfoHelper } from './use-journal'; +import { useJournalInfoHelper } from './use-journal'; const weakMap = new WeakMap>>(); @@ -32,6 +32,9 @@ function getAtom(w: DocCollection, pageId: string): Atom { } } +/** + * @deprecated use `useDocTitle(docId: string)` instead + */ export function useDocCollectionPageTitle( docCollection: DocCollection, pageId: string @@ -39,13 +42,13 @@ export function useDocCollectionPageTitle( const titleAtom = getAtom(docCollection, pageId); assertExists(titleAtom); const title = useAtomValue(titleAtom); - const { localizedJournalDate } = useJournalInfoHelper(docCollection, pageId); + const { localizedJournalDate } = useJournalInfoHelper(pageId); return localizedJournalDate || title; } // This hook is NOT reactive to the page title change export function useGetDocCollectionPageTitle(docCollection: DocCollection) { - const { getLocalizedJournalDateString } = useJournalHelper(docCollection); + const { getLocalizedJournalDateString } = useJournalInfoHelper(); return useCallback( (pageId: string) => { return ( diff --git a/packages/frontend/core/src/components/hooks/use-journal.ts b/packages/frontend/core/src/components/hooks/use-journal.ts index 674c8decdb03..6a972ccdc676 100644 --- a/packages/frontend/core/src/components/hooks/use-journal.ts +++ b/packages/frontend/core/src/components/hooks/use-journal.ts @@ -37,6 +37,7 @@ export const useJournalHelper = (docCollection: DocCollection) => { EditorSettingService, }); const adapter = useCurrentWorkspacePropertiesAdapter(); + const { isPageJournal } = useJournalInfoHelper(); /** * @internal @@ -67,13 +68,6 @@ export const useJournalHelper = (docCollection: DocCollection) => { [adapter, bsWorkspaceHelper, docsService.list, editorSettingService] ); - const isPageJournal = useCallback( - (pageId: string) => { - return !!adapter.getJournalPageDateString(pageId); - }, - [adapter] - ); - /** * query all journals by date */ @@ -104,31 +98,6 @@ export const useJournalHelper = (docCollection: DocCollection) => { [_createJournal, getJournalsByDate] ); - const isPageTodayJournal = useCallback( - (pageId: string) => { - const date = dayjs().format(JOURNAL_DATE_FORMAT); - const d = adapter.getJournalPageDateString(pageId); - return isPageJournal(pageId) && d === date; - }, - [adapter, isPageJournal] - ); - - const getJournalDateString = useCallback( - (pageId: string) => { - return adapter.getJournalPageDateString(pageId); - }, - [adapter] - ); - - const getLocalizedJournalDateString = useCallback( - (pageId: string) => { - const journalDateString = getJournalDateString(pageId); - if (!journalDateString) return null; - return i18nTime(journalDateString, { absolute: { accuracy: 'day' } }); - }, - [getJournalDateString] - ); - const appendContentToToday = useCallback( async (content: string) => { if (!content) return; @@ -148,21 +117,9 @@ export const useJournalHelper = (docCollection: DocCollection) => { () => ({ getJournalsByDate, getJournalByDate, - getJournalDateString, - getLocalizedJournalDateString, - isPageJournal, - isPageTodayJournal, appendContentToToday, }), - [ - getJournalsByDate, - getJournalByDate, - getJournalDateString, - getLocalizedJournalDateString, - isPageJournal, - isPageTodayJournal, - appendContentToToday, - ] + [getJournalsByDate, getJournalByDate, appendContentToToday] ); }; @@ -207,16 +164,41 @@ export const useJournalRouteHelper = (docCollection: DocCollection) => { ); }; -export const useJournalInfoHelper = ( - docCollection: DocCollection, - pageId?: string | null -) => { - const { - isPageJournal, - getJournalDateString, - getLocalizedJournalDateString, - isPageTodayJournal, - } = useJournalHelper(docCollection); +// get journal info that don't rely on `docCollection` +export const useJournalInfoHelper = (pageId?: string | null) => { + const adapter = useCurrentWorkspacePropertiesAdapter(); + + const isPageJournal = useCallback( + (pageId: string) => { + return !!adapter.getJournalPageDateString(pageId); + }, + [adapter] + ); + + const isPageTodayJournal = useCallback( + (pageId: string) => { + const date = dayjs().format(JOURNAL_DATE_FORMAT); + const d = adapter.getJournalPageDateString(pageId); + return isPageJournal(pageId) && d === date; + }, + [adapter, isPageJournal] + ); + + const getJournalDateString = useCallback( + (pageId: string) => { + return adapter.getJournalPageDateString(pageId); + }, + [adapter] + ); + + const getLocalizedJournalDateString = useCallback( + (pageId: string) => { + const journalDateString = getJournalDateString(pageId); + if (!journalDateString) return null; + return i18nTime(journalDateString, { absolute: { accuracy: 'day' } }); + }, + [getJournalDateString] + ); return useMemo( () => ({ @@ -226,6 +208,10 @@ export const useJournalInfoHelper = ( ? getLocalizedJournalDateString(pageId) : null, isTodayJournal: pageId ? isPageTodayJournal(pageId) : false, + isPageJournal, + isPageTodayJournal, + getJournalDateString, + getLocalizedJournalDateString, }), [ getJournalDateString, diff --git a/packages/frontend/core/src/components/page-list/page-group.tsx b/packages/frontend/core/src/components/page-list/page-group.tsx index 092aeb0c4671..3532cdc3ff43 100644 --- a/packages/frontend/core/src/components/page-list/page-group.tsx +++ b/packages/frontend/core/src/components/page-list/page-group.tsx @@ -1,17 +1,11 @@ -import { useJournalInfoHelper } from '@affine/core/components/hooks/use-journal'; +import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import type { Tag } from '@affine/env/filter'; import { useI18n } from '@affine/i18n'; import { assertExists } from '@blocksuite/global/utils'; -import { - EdgelessIcon, - PageIcon, - TodayIcon, - ToggleCollapseIcon, - ViewLayersIcon, -} from '@blocksuite/icons/rc'; +import { ToggleCollapseIcon, ViewLayersIcon } from '@blocksuite/icons/rc'; import type { DocCollection, DocMeta } from '@blocksuite/store'; import * as Collapsible from '@radix-ui/react-collapsible'; -import { DocsService, useLiveData, useService } from '@toeverything/infra'; +import { useLiveData, useService } from '@toeverything/infra'; import clsx from 'clsx'; import { selectAtom } from 'jotai/utils'; import type { MouseEventHandler } from 'react'; @@ -284,26 +278,16 @@ function tagIdToTagOption( } const PageTitle = ({ id }: { id: string }) => { - const doc = useLiveData(useService(DocsService).list.doc$(id)); - const title = useLiveData(doc?.title$); const t = useI18n(); - return title || t['Untitled'](); + const docDisplayMetaService = useService(DocDisplayMetaService); + const title = useLiveData(docDisplayMetaService.title$(id)); + return typeof title === 'string' ? title : t[title.key](); }; -const UnifiedPageIcon = ({ - id, - docCollection, -}: { - id: string; - docCollection: DocCollection; -}) => { - const list = useService(DocsService).list; - const isEdgeless = useLiveData(list.primaryMode$(id)) === 'edgeless'; - const { isJournal } = useJournalInfoHelper(docCollection, id); - if (isJournal) { - return ; - } - return isEdgeless ? : ; +const UnifiedPageIcon = ({ id }: { id: string }) => { + const docDisplayMetaService = useService(DocDisplayMetaService); + const Icon = useLiveData(docDisplayMetaService.icon$(id)); + return ; }; function pageMetaToListItemProp( @@ -338,7 +322,7 @@ function pageMetaToListItemProp( updatedDate: item.updatedDate ? new Date(item.updatedDate) : undefined, to: props.rowAsLink && !props.selectable ? `/${item.id}` : undefined, onClick: toggleSelection, - icon: , + icon: , tags: item.tags ?.map(id => tagIdToTagOption(id, props.docCollection)) 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 74bb18c51cc4..cb80f23f25b4 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 @@ -3,10 +3,11 @@ import { useJournalInfoHelper, useJournalRouteHelper, } from '@affine/core/components/hooks/use-journal'; +import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { WorkbenchService } from '@affine/core/modules/workbench'; import { isNewTabTrigger } from '@affine/core/utils'; import { useI18n } from '@affine/i18n'; -import { TodayIcon, TomorrowIcon, YesterdayIcon } from '@blocksuite/icons/rc'; +import { TodayIcon } from '@blocksuite/icons/rc'; import type { DocCollection } from '@blocksuite/store'; import { useLiveData, useService } from '@toeverything/infra'; import { type MouseEvent } from 'react'; @@ -21,13 +22,11 @@ export const AppSidebarJournalButton = ({ docCollection, }: AppSidebarJournalButtonProps) => { const t = useI18n(); + const docDisplayMetaService = useService(DocDisplayMetaService); const workbench = useService(WorkbenchService).workbench; const location = useLiveData(workbench.location$); const { openToday } = useJournalRouteHelper(docCollection); - const { journalDate, isJournal } = useJournalInfoHelper( - docCollection, - location.pathname.split('/')[1] - ); + const { isJournal } = useJournalInfoHelper(location.pathname.split('/')[1]); const handleOpenToday = useCatchEventCallback( (e: MouseEvent) => { @@ -36,14 +35,12 @@ export const AppSidebarJournalButton = ({ [openToday] ); - const Icon = - isJournal && journalDate - ? journalDate.isBefore(new Date(), 'day') - ? YesterdayIcon - : journalDate.isAfter(new Date(), 'day') - ? TomorrowIcon - : TodayIcon - : TodayIcon; + const JournalIcon = useLiveData( + docDisplayMetaService.icon$(docCollection.id, { + compareDate: new Date(), + }) + ); + const Icon = isJournal ? JournalIcon : TodayIcon; return ( , 'onClick'> { - docRecord: DocRecord; + docId: string; right?: ReactNode; } -const PageItem = ({ docRecord, right, className, ...attrs }: PageItemProps) => { - const mode = useLiveData(docRecord.primaryMode$); - const workspace = useService(WorkspaceService).workspace; - const title = useDocCollectionPageTitle( - workspace.docCollection, - docRecord.id - ); - const { isJournal } = useJournalInfoHelper( - workspace.docCollection, - docRecord.id +const PageItem = ({ docId, right, className, ...attrs }: PageItemProps) => { + const t = useI18n(); + const docDisplayMetaService = useService(DocDisplayMetaService); + const Icon = useLiveData( + docDisplayMetaService.icon$(docId, { compareDate: new Date() }) ); + const titleMeta = useLiveData(docDisplayMetaService.title$(docId)); + const title = typeof titleMeta === 'string' ? titleMeta : t[titleMeta.key](); - const Icon = isJournal - ? TodayIcon - : mode === 'edgeless' - ? EdgelessIcon - : PageIcon; return ( @@ -95,16 +82,8 @@ export const EditorJournalPanel = () => { const t = useI18n(); const doc = useService(DocService).doc; const workspace = useService(WorkspaceService).workspace; - const { journalDate, isJournal } = useJournalInfoHelper( - workspace.docCollection, - doc.id - ); + const { journalDate, isJournal } = useJournalInfoHelper(doc.id); const { openJournal } = useJournalRouteHelper(workspace.docCollection); - const [date, setDate] = useState(dayjs().format('YYYY-MM-DD')); - - useEffect(() => { - journalDate && setDate(journalDate.format('YYYY-MM-DD')); - }, [journalDate]); const onDateSelect = useCallback( (date: string) => { @@ -150,14 +129,18 @@ export const EditorJournalPanel = () => { monthNames={t['com.affine.calendar-date-picker.month-names']()} todayLabel={t['com.affine.calendar-date-picker.today']()} customDayRenderer={customDayRenderer} - value={date} + value={journalDate?.format('YYYY-MM-DD')} onChange={onDateSelect} monthHeaderCellClassName={styles.journalDateCellWrapper} monthBodyCellClassName={styles.journalDateCellWrapper} /> - - + {journalDate ? ( + <> + + + + ) : null} ); }; @@ -276,7 +259,7 @@ const JournalDailyCountBlock = ({ date }: JournalBlockProps) => { ))} @@ -322,7 +305,7 @@ const ConflictList = ({ return ( { - const workspace = useService(WorkspaceService).workspace; - const { journalDate, isJournal } = useJournalInfoHelper( - workspace.docCollection, - docId + const { isJournal } = useJournalInfoHelper(docId); + + const docDisplayMetaService = useService(DocDisplayMetaService); + const Icon = useLiveData( + docDisplayMetaService.icon$(docId, { + compareDate: new Date(), + }) ); - const Icon = journalDate - ? journalDate.isBefore(new Date(), 'day') - ? YesterdayIcon - : journalDate.isAfter(new Date(), 'day') - ? TomorrowIcon - : TodayIcon - : TodayIcon; if (!isJournal) { return null; diff --git a/packages/frontend/core/src/modules/doc-display-meta/index.ts b/packages/frontend/core/src/modules/doc-display-meta/index.ts new file mode 100644 index 000000000000..179fedf2235e --- /dev/null +++ b/packages/frontend/core/src/modules/doc-display-meta/index.ts @@ -0,0 +1,16 @@ +import { + DocsService, + type Framework, + WorkspaceScope, +} from '@toeverything/infra'; + +import { WorkspacePropertiesAdapter } from '../properties'; +import { DocDisplayMetaService } from './services/doc-display-meta'; + +export { DocDisplayMetaService }; + +export function configureDocDisplayMetaModule(framework: Framework) { + framework + .scope(WorkspaceScope) + .service(DocDisplayMetaService, [WorkspacePropertiesAdapter, DocsService]); +} diff --git a/packages/frontend/core/src/modules/doc-display-meta/services/doc-display-meta.ts b/packages/frontend/core/src/modules/doc-display-meta/services/doc-display-meta.ts new file mode 100644 index 000000000000..bc9cf097412e --- /dev/null +++ b/packages/frontend/core/src/modules/doc-display-meta/services/doc-display-meta.ts @@ -0,0 +1,147 @@ +import { i18nTime } from '@affine/i18n'; +import { + BlockLinkIcon as LitBlockLinkIcon, + EdgelessIcon as LitEdgelessIcon, + LinkedEdgelessIcon as LitLinkedEdgelessIcon, + LinkedPageIcon as LitLinkedPageIcon, + PageIcon as LitPageIcon, + TodayIcon as LitTodayIcon, + TomorrowIcon as LitTomorrowIcon, + YesterdayIcon as LitYesterdayIcon, +} from '@blocksuite/icons/lit'; +import { + BlockLinkIcon, + EdgelessIcon, + LinkedEdgelessIcon, + LinkedPageIcon, + PageIcon, + TodayIcon, + TomorrowIcon, + YesterdayIcon, +} from '@blocksuite/icons/rc'; +import type { DocRecord, DocsService } from '@toeverything/infra'; +import { LiveData, Service } from '@toeverything/infra'; +import type { Dayjs } from 'dayjs'; +import dayjs from 'dayjs'; + +import type { WorkspacePropertiesAdapter } from '../../properties'; + +type IconType = 'rc' | 'lit'; +interface DocDisplayIconOptions { + type?: T; + compareDate?: Date | Dayjs; + /** + * Override the mode detected inside the hook: + * by default, it will use the `primaryMode$` of the doc. + */ + mode?: 'edgeless' | 'page'; + reference?: boolean; + referenceToNode?: boolean; +} + +const rcIcons = { + BlockLinkIcon, + EdgelessIcon, + LinkedEdgelessIcon, + LinkedPageIcon, + PageIcon, + TodayIcon, + TomorrowIcon, + YesterdayIcon, +}; +const litIcons = { + BlockLinkIcon: LitBlockLinkIcon, + EdgelessIcon: LitEdgelessIcon, + LinkedEdgelessIcon: LitLinkedEdgelessIcon, + LinkedPageIcon: LitLinkedPageIcon, + PageIcon: LitPageIcon, + TodayIcon: LitTodayIcon, + TomorrowIcon: LitTomorrowIcon, + YesterdayIcon: LitYesterdayIcon, +}; +const icons = { rc: rcIcons, lit: litIcons } as { + rc: Record; + lit: Record; +}; + +export class DocDisplayMetaService extends Service { + constructor( + private readonly propertiesAdapter: WorkspacePropertiesAdapter, + private readonly docsService: DocsService + ) { + super(); + } + + icon$( + docId: string, + options?: DocDisplayIconOptions + ): LiveData { + const iconSet = icons[options?.type ?? 'rc']; + + return LiveData.computed(get => { + const doc = get(this.docsService.list.doc$(docId)); + const mode = doc ? get(doc.primaryMode$) : undefined; + const finalMode = options?.mode ?? mode ?? 'page'; + + const journalDate = this._toDayjs( + this.propertiesAdapter.getJournalPageDateString(docId) + ); + + if (journalDate) { + if (!options?.compareDate) return iconSet.TodayIcon; + const compareDate = dayjs(options?.compareDate); + return journalDate.isBefore(compareDate, 'day') + ? iconSet.YesterdayIcon + : journalDate.isAfter(compareDate, 'day') + ? iconSet.TomorrowIcon + : iconSet.TodayIcon; + } + + return options?.reference + ? options?.referenceToNode + ? iconSet.BlockLinkIcon + : finalMode === 'edgeless' + ? iconSet.LinkedEdgelessIcon + : iconSet.LinkedPageIcon + : finalMode === 'edgeless' + ? iconSet.EdgelessIcon + : iconSet.PageIcon; + }); + } + + title$(docId: string, originalTitle?: string) { + return LiveData.computed(get => { + const doc = get(this.docsService.list.doc$(docId)); + const docTitle = doc ? get(doc.title$) : undefined; + + const journalDateString = + this.propertiesAdapter.getJournalPageDateString(docId); + return journalDateString + ? i18nTime(journalDateString, { absolute: { accuracy: 'day' } }) + : originalTitle || + docTitle || + ({ + key: 'Untitled', + } as const); + }); + } + + getDocDisplayMeta(docRecord: DocRecord, originalTitle?: string) { + return { + title: this.title$(docRecord.id, originalTitle).value, + icon: this.icon$(docRecord.id).value, + updatedDate: docRecord.meta$.value.updatedDate, + }; + } + + private _isJournalString(j?: string | false) { + return j ? !!j?.match(/^\d{4}-\d{2}-\d{2}$/) : false; + } + + private _toDayjs(j?: string | false) { + if (!j || !this._isJournalString(j)) return null; + const day = dayjs(j); + if (!day.isValid()) return null; + return day; + } +} diff --git a/packages/frontend/core/src/modules/explorer/views/nodes/doc/index.tsx b/packages/frontend/core/src/modules/explorer/views/nodes/doc/index.tsx index f21de9e3216e..2774f009dadd 100644 --- a/packages/frontend/core/src/modules/explorer/views/nodes/doc/index.tsx +++ b/packages/frontend/core/src/modules/explorer/views/nodes/doc/index.tsx @@ -7,16 +7,11 @@ import { } from '@affine/component'; import { InfoModal } from '@affine/core/components/affine/page-properties'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; +import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { DocsSearchService } from '@affine/core/modules/docs-search'; import type { AffineDNDData } from '@affine/core/types/dnd'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; -import { - EdgelessIcon, - LinkedEdgelessIcon, - LinkedPageIcon, - PageIcon, -} from '@blocksuite/icons/rc'; import { DocsService, GlobalContextService, @@ -46,35 +41,37 @@ export const ExplorerDocNode = ({ isLinked?: boolean; } & GenericExplorerNode) => { const t = useI18n(); - const { docsSearchService, docsService, globalContextService } = useServices({ + const { + docsSearchService, + docsService, + globalContextService, + docDisplayMetaService, + } = useServices({ DocsSearchService, DocsService, GlobalContextService, + DocDisplayMetaService, }); + // const pageInfoAdapter = useCurrentWorkspacePropertiesAdapter(); + const active = useLiveData(globalContextService.globalContext.docId.$) === docId; const [collapsed, setCollapsed] = useState(true); const docRecord = useLiveData(docsService.list.doc$(docId)); - const docPrimaryMode = useLiveData(docRecord?.primaryMode$); - const docTitle = useLiveData(docRecord?.title$); + const DocIcon = useLiveData( + docDisplayMetaService.icon$(docId, { + reference: isLinked, + }) + ); + const docTitle = useLiveData(docDisplayMetaService.title$(docId)); const isInTrash = useLiveData(docRecord?.trash$); const Icon = useCallback( ({ className }: { className?: string }) => { - return isLinked ? ( - docPrimaryMode === 'edgeless' ? ( - - ) : ( - - ) - ) : docPrimaryMode === 'edgeless' ? ( - - ) : ( - - ); + return ; }, - [docPrimaryMode, isLinked] + [DocIcon] ); const children = useLiveData( @@ -205,7 +202,7 @@ export const ExplorerDocNode = ({ <>