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 94f1a90df3..76f1ca891d 100644 --- a/packages/frontend/core/src/components/affine/reference-link/index.tsx +++ b/packages/frontend/core/src/components/affine/reference-link/index.tsx @@ -3,7 +3,6 @@ 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'; -import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import type { DocMode } from '@blocksuite/affine/blocks'; import type { DocCollection } from '@blocksuite/affine/store'; @@ -30,7 +29,7 @@ import * as styles from './styles.css'; interface AffinePageReferenceProps { pageId: string; params?: URLSearchParams; - title?: string | null; // title alias + title?: string; // title alias className?: string; Icon?: ComponentType; onClick?: (e: MouseEvent) => void; @@ -44,7 +43,6 @@ function AffinePageReferenceInner({ }: AffinePageReferenceProps) { const docDisplayMetaService = useService(DocDisplayMetaService); const docsService = useService(DocsService); - const i18n = useI18n(); let referenceWithMode: DocMode | null = null; let referenceToNode = false; @@ -74,18 +72,10 @@ function AffinePageReferenceInner({ const notFound = !useLiveData(docsService.list.doc$(pageId)); - const docTitle = useLiveData( - docDisplayMetaService.title$(pageId, { reference: true }) + title = useLiveData( + docDisplayMetaService.title$(pageId, { title, reference: true }) ); - if (notFound) { - title = i18n.t('com.affine.notFoundPage.title'); - } - - if (!title) { - title = i18n.t(docTitle); - } - return ( diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/root-block.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/root-block.ts index d89f519809..d4e1bb7f00 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/root-block.ts +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/root-block.ts @@ -2,6 +2,7 @@ import { AIEdgelessRootBlockSpec, AIPageRootBlockSpec, } from '@affine/core/blocksuite/presets/ai'; +import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { EditorSettingService } from '@affine/core/modules/editor-setting'; import { AppThemeService } from '@affine/core/modules/theme'; import { mixpanel } from '@affine/track'; @@ -12,12 +13,15 @@ import { StdIdentifier, } from '@blocksuite/affine/block-std'; import type { + DocDisplayMetaExtension, + DocDisplayMetaParams, RootBlockConfig, TelemetryEventMap, ThemeExtension, } from '@blocksuite/affine/blocks'; import { ColorScheme, + DocDisplayMetaProvider, EdgelessBuiltInManager, EdgelessRootBlockSpec, EdgelessToolExtension, @@ -29,16 +33,19 @@ import { } from '@blocksuite/affine/blocks'; import { createSignalFromObservable, + referenceToNode, type Signal, SpecProvider, } from '@blocksuite/affine-shared/utils'; import type { Container } from '@blocksuite/global/di'; +import { LinkedPageIcon, PageIcon } from '@blocksuite/icons/lit'; import { DocService, DocsService, FeatureFlagService, type FrameworkProvider, } from '@toeverything/infra'; +import type { TemplateResult } from 'lit'; import type { Observable } from 'rxjs'; import { combineLatest, map } from 'rxjs'; @@ -141,6 +148,88 @@ function getThemeExtension(framework: FrameworkProvider) { return AffineThemeExtension; } +export function buildDocDisplayMetaExtension(framework: FrameworkProvider) { + const docDisplayMetaService = framework.get(DocDisplayMetaService); + + function iconBuilder( + icon: typeof PageIcon, + size = '1.25em', + style = 'user-select:none;flex-shrink:0;vertical-align:middle;font-size:inherit;margin-bottom:0.1em;' + ) { + return icon({ + width: size, + height: size, + style, + }); + } + + class AffineDocDisplayMetaService + extends LifeCycleWatcher + implements DocDisplayMetaExtension + { + static override key = 'doc-display-meta'; + + readonly disposables: (() => void)[] = []; + + static override setup(di: Container) { + super.setup(di); + di.override(DocDisplayMetaProvider, this, [StdIdentifier]); + } + + dispose() { + while (this.disposables.length > 0) { + this.disposables.pop()?.(); + } + } + + icon( + docId: string, + { params, title, referenced }: DocDisplayMetaParams = {} + ): Signal { + const icon$ = docDisplayMetaService + .icon$(docId, { + type: 'lit', + reference: referenced, + hasTitleAlias: Boolean(title), + referenceToNode: referenceToNode({ pageId: docId, params }), + }) + .map(iconBuilder); + + const { signal: iconSignal, cleanup } = createSignalFromObservable( + icon$, + iconBuilder(referenced ? LinkedPageIcon : PageIcon) + ); + + this.disposables.push(cleanup); + + return iconSignal; + } + + title( + docId: string, + { title = '', referenced }: DocDisplayMetaParams = {} + ): Signal { + const title$ = docDisplayMetaService.title$(docId, { + title, + reference: referenced, + }); + + const { signal: titleSignal, cleanup } = + createSignalFromObservable(title$, title); + + this.disposables.push(cleanup); + + return titleSignal; + } + + override unmounted() { + this.dispose(); + } + } + + return AffineDocDisplayMetaService; +} + function getEditorConfigExtension( framework: FrameworkProvider ): ExtensionType[] { @@ -184,6 +273,7 @@ export function createPageRootBlockSpec( getFontConfigExtension(), getTelemetryExtension(), getEditorConfigExtension(framework), + buildDocDisplayMetaExtension(framework), ].flat(); } @@ -201,5 +291,6 @@ export function createEdgelessRootBlockSpec( getFontConfigExtension(), getTelemetryExtension(), getEditorConfigExtension(framework), + buildDocDisplayMetaExtension(framework), ].flat(); } diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx index dec5a64bc7..2d69923dc3 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/spec-patchers.tsx @@ -312,7 +312,6 @@ export function patchDocModeService( return (mode || DEFAULT_MODE) as DocMode; }; onPrimaryModeChange = (handler: (mode: DocMode) => void, id?: string) => { - // eslint-disable-next-line rxjs/finnish const mode$ = id ? docsService.list.primaryMode$(id) : docService.doc.primaryMode$; diff --git a/packages/frontend/core/src/mobile/components/doc-card/index.tsx b/packages/frontend/core/src/mobile/components/doc-card/index.tsx index 333f6a0cc7..f7cf3abd9a 100644 --- a/packages/frontend/core/src/mobile/components/doc-card/index.tsx +++ b/packages/frontend/core/src/mobile/components/doc-card/index.tsx @@ -8,7 +8,6 @@ import { WorkbenchLink, type WorkbenchLinkProps, } from '@affine/core/modules/workbench'; -import { useI18n } from '@affine/i18n'; import type { DocMeta } from '@blocksuite/affine/store'; import { useLiveData, useService } from '@toeverything/infra'; import clsx from 'clsx'; @@ -48,13 +47,9 @@ export const DocCard = forwardRef( outerRef ) { const containerRef = useRef(null); - const t = useI18n(); const favAdapter = useService(CompatibleFavoriteItemsAdapter); const docDisplayService = useService(DocDisplayMetaService); - const titleInfo = useLiveData(docDisplayService.title$(meta.id)); - const title = - typeof titleInfo === 'string' ? titleInfo : t[titleInfo.i18nKey](); - + const title = useLiveData(docDisplayService.title$(meta.id)); const favorited = useLiveData(favAdapter.isFavorite$(meta.id, 'doc')); const toggleFavorite = useCatchEventCallback( diff --git a/packages/frontend/core/src/mobile/dialogs/selectors/doc-selector.tsx b/packages/frontend/core/src/mobile/dialogs/selectors/doc-selector.tsx index c294111297..652707b584 100644 --- a/packages/frontend/core/src/mobile/dialogs/selectors/doc-selector.tsx +++ b/packages/frontend/core/src/mobile/dialogs/selectors/doc-selector.tsx @@ -19,11 +19,9 @@ const DocIcon = ({ docId }: { docId: string }) => { }; const DocLabel = ({ docId }: { docId: string }) => { - const t = useI18n(); const docDisplayMetaService = useService(DocDisplayMetaService); const label = useLiveData(docDisplayMetaService.title$(docId)); - - return typeof label === 'string' ? label : t[label.i18nKey](); + return label; }; export const DocSelectorDialog = ({ diff --git a/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx b/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx index fbe5d113f3..055e5aace7 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/detail/mobile-detail-page.tsx @@ -15,7 +15,7 @@ import { EditorService } from '@affine/core/modules/editor'; 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 { i18nTime } from '@affine/i18n'; import { BookmarkBlockService, customImageProxyMiddleware, @@ -240,14 +240,11 @@ const MobileDetailPage = ({ pageId: string; date?: string; }) => { - const t = useI18n(); const docDisplayMetaService = useService(DocDisplayMetaService); const journalService = useService(JournalService); const workbench = useService(WorkbenchService).workbench; const [showTitle, setShowTitle] = useState(checkShowTitle); - const titleInfo = useLiveData(docDisplayMetaService.title$(pageId)); - const title = - typeof titleInfo === 'string' ? titleInfo : t[titleInfo.i18nKey](); + const title = useLiveData(docDisplayMetaService.title$(pageId)); const allJournalDates = useLiveData(journalService.allJournalDates$); diff --git a/packages/frontend/core/src/modules/doc-display-meta/index.ts b/packages/frontend/core/src/modules/doc-display-meta/index.ts index 1c612be44e..1618b2bbba 100644 --- a/packages/frontend/core/src/modules/doc-display-meta/index.ts +++ b/packages/frontend/core/src/modules/doc-display-meta/index.ts @@ -5,6 +5,7 @@ import { WorkspaceScope, } from '@toeverything/infra'; +import { I18nService } from '../i18n'; import { JournalService } from '../journal'; import { DocDisplayMetaService } from './services/doc-display-meta'; @@ -17,5 +18,6 @@ export function configureDocDisplayMetaModule(framework: Framework) { JournalService, DocsService, FeatureFlagService, + I18nService, ]); } 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 index 8cd617a1e4..830d726c48 100644 --- 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 @@ -31,6 +31,7 @@ import { LiveData, Service } from '@toeverything/infra'; import type { Dayjs } from 'dayjs'; import dayjs from 'dayjs'; +import type { I18nService } from '../../i18n'; import type { JournalService } from '../../journal'; type IconType = 'rc' | 'lit'; @@ -52,6 +53,7 @@ interface DocDisplayIconOptions { } interface DocDisplayTitleOptions { originalTitle?: string; + title?: string; // title alias reference?: boolean; /** * @default true @@ -90,7 +92,8 @@ export class DocDisplayMetaService extends Service { constructor( private readonly journalService: JournalService, private readonly docsService: DocsService, - private readonly featureFlagService: FeatureFlagService + private readonly featureFlagService: FeatureFlagService, + private readonly i18nService: I18nService ) { super(); } @@ -149,7 +152,7 @@ export class DocDisplayMetaService extends Service { // journal icon const journalDate = this._toDayjs( - this.journalService.journalDate$(docId).value + get(this.journalService.journalDate$(docId)) ); if (journalDate) { return this.getJournalIcon(journalDate, options); @@ -178,31 +181,48 @@ export class DocDisplayMetaService extends Service { title$(docId: string, options?: DocDisplayTitleOptions) { return LiveData.computed(get => { + const enableEmojiIcon = + get(this.featureFlagService.flags.enable_emoji_doc_icon.$) && + options?.enableEmojiIcon !== false; + const lng = get(this.i18nService.i18n.currentLanguageKey$); const doc = get(this.docsService.list.doc$(docId)); - const docTitle = doc ? get(doc.title$) : undefined; + // title alias + if (options?.title) { + return enableEmojiIcon + ? extractEmojiIcon(options.title).rest + : options.title; + } + + if (!doc) { + return this.i18nService.i18n.i18next.t( + 'com.affine.notFoundPage.title', + { lng } + ); + } + + // journal title const journalDateString = get(this.journalService.journalDate$(docId)); - - // journal if (journalDateString) { return i18nTime(journalDateString, { absolute: { accuracy: 'day' } }); } + // original title if (options?.originalTitle) return options.originalTitle; + const docTitle = get(doc.title$); + // empty title - if (!docTitle) return { i18nKey: 'Untitled' } as const; + if (!docTitle) { + return this.i18nService.i18n.i18next.t('Untitled', { lng }); + } // reference if (options?.reference) return docTitle; - // check emoji - const enableEmojiIcon = - get(this.featureFlagService.flags.enable_emoji_doc_icon.$) && - options?.enableEmojiIcon !== false; + // emoji icon if (enableEmojiIcon) { - const { rest } = extractEmojiIcon(docTitle); - return rest; + return extractEmojiIcon(docTitle).rest; } // default