From 7436c139abaa1d2f0752333c2e2aaca6c71821d5 Mon Sep 17 00:00:00 2001 From: fundon Date: Fri, 17 Jan 2025 11:30:10 +0000 Subject: [PATCH] fix(core): improve doc title and icon display (#9755) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes: [AF-2132](https://linear.app/affine-design/issue/AF-2132/优化-emoji-title-和-icon-显示) --- .../embed-linked-doc-block.ts | 3 +- .../nodes/reference-node/reference-node.ts | 3 +- .../affine/reference-link/index.tsx | 2 +- .../specs/custom/root-block.ts | 6 +- .../detail-page/tabs/journal/index.tsx | 3 +- .../detail/journal-conflict-block.tsx | 3 +- .../detail/menu/journal-conflicts.tsx | 3 +- .../detail/menu/journal-today-activity.tsx | 4 +- .../modules/at-menu-config/services/index.ts | 6 +- .../services/doc-display-meta.ts | 90 +++++++++---------- .../src/modules/quicksearch/impls/docs.ts | 5 +- tests/affine-local/e2e/links.spec.ts | 90 ++++++++++++++++++- tests/kit/src/utils/page-logic.ts | 15 ++++ 13 files changed, 159 insertions(+), 74 deletions(-) diff --git a/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts b/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts index 1b5b6d650..49f058fc0 100644 --- a/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts +++ b/blocksuite/affine/block-embed/src/embed-linked-doc-block/embed-linked-doc-block.ts @@ -229,10 +229,9 @@ export class EmbedLinkedDocBlockComponent extends EmbedBlockComponent { const { pageId, params, title } = this.referenceInfo$.value; return ( - title || this.std .get(DocDisplayMetaProvider) - .title(pageId, { params, title, referenced: true }) + .title(pageId, { params, title, referenced: true }) || title ); }); diff --git a/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-node.ts b/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-node.ts index b9b3f3ecc..391a8e951 100644 --- a/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-node.ts +++ b/blocksuite/affine/components/src/rich-text/inline/presets/nodes/reference-node/reference-node.ts @@ -145,10 +145,9 @@ export class AffineReference extends WithDisposable(ShadowlessElement) { get _title() { const { pageId, params, title } = this.referenceInfo; return ( - title || this.block?.std ?.get(DocDisplayMetaProvider) - .title(pageId, { params, title, referenced: true }).value + .title(pageId, { params, title, referenced: true }).value || title ); } 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 206bc367a..e7b1fd111 100644 --- a/packages/frontend/core/src/components/affine/reference-link/index.tsx +++ b/packages/frontend/core/src/components/affine/reference-link/index.tsx @@ -60,7 +60,7 @@ function AffinePageReferenceInner({ mode: referenceWithMode ?? undefined, reference: true, referenceToNode, - hasTitleAlias: Boolean(title), + title, }) ); }) 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 d7edc88cb..29b9c4c92 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 @@ -186,8 +186,8 @@ export function buildDocDisplayMetaExtension(framework: FrameworkProvider) { const icon$ = docDisplayMetaService .icon$(docId, { type: 'lit', + title, reference: referenced, - hasTitleAlias: Boolean(title), referenceToNode: referenceToNode({ pageId: docId, params }), }) .map(iconBuilder); @@ -204,7 +204,7 @@ export function buildDocDisplayMetaExtension(framework: FrameworkProvider) { title( docId: string, - { title = '', referenced }: DocDisplayMetaParams = {} + { title, referenced }: DocDisplayMetaParams = {} ): Signal { const title$ = docDisplayMetaService.title$(docId, { title, @@ -212,7 +212,7 @@ export function buildDocDisplayMetaExtension(framework: FrameworkProvider) { }); const { signal: titleSignal, cleanup } = - createSignalFromObservable(title$, title); + createSignalFromObservable(title$, title ?? ''); this.disposables.push(cleanup); diff --git a/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/journal/index.tsx b/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/journal/index.tsx index fb00d3b8b..641c1fb57 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/journal/index.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/detail-page/tabs/journal/index.tsx @@ -59,8 +59,7 @@ const PageItem = ({ const Icon = useLiveData( docDisplayMetaService.icon$(docId, { compareDate: new Date() }) ); - const titleMeta = useLiveData(docDisplayMetaService.title$(docId)); - const title = i18n.t(titleMeta); + const title = useLiveData(docDisplayMetaService.title$(docId)); return ( { const docId = docRecord.id; const i18n = useI18n(); const docDisplayMetaService = useService(DocDisplayMetaService); - const titleMeta = useLiveData(docDisplayMetaService.title$(docId)); - const title = i18n.t(titleMeta); + const title = useLiveData(docDisplayMetaService.title$(docId)); return ( diff --git a/packages/frontend/core/src/mobile/pages/workspace/detail/menu/journal-conflicts.tsx b/packages/frontend/core/src/mobile/pages/workspace/detail/menu/journal-conflicts.tsx index ec4d8b134..3243e75e3 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/detail/menu/journal-conflicts.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/detail/menu/journal-conflicts.tsx @@ -84,8 +84,7 @@ const DocItem = ({ docRecord }: { docRecord: DocRecord }) => { const Icon = useLiveData( docDisplayMetaService.icon$(docId, { compareDate: new Date() }) ); - const titleMeta = useLiveData(docDisplayMetaService.title$(docId)); - const title = i18n.t(titleMeta); + const title = useLiveData(docDisplayMetaService.title$(docId)); return ( { - const i18n = useI18n(); const docDisplayMetaService = useService(DocDisplayMetaService); const Icon = useLiveData( docDisplayMetaService.icon$(docId, { compareDate: new Date() }) ); - const titleMeta = useLiveData(docDisplayMetaService.title$(docId)); - const title = i18n.t(titleMeta); + const title = useLiveData(docDisplayMetaService.title$(docId)); return ( }>{title} diff --git a/packages/frontend/core/src/modules/at-menu-config/services/index.ts b/packages/frontend/core/src/modules/at-menu-config/services/index.ts index cdeaaa8cc..e4c914705 100644 --- a/packages/frontend/core/src/modules/at-menu-config/services/index.ts +++ b/packages/frontend/core/src/modules/at-menu-config/services/index.ts @@ -91,14 +91,10 @@ export class AtMenuConfigService extends Service { return null; } - let title = docDisplayMetaService.title$(meta.id, { + const title = docDisplayMetaService.title$(meta.id, { reference: true, }).value; - if (typeof title === 'object' && 'i18nKey' in title) { - title = I18n.t(title); - } - if (!fuzzyMatch(title, query)) { return null; } 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 257218723..c29a1302a 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 @@ -40,16 +40,15 @@ interface DocDisplayIconOptions { * by default, it will use the `primaryMode$` of the doc. */ mode?: 'edgeless' | 'page'; + title?: string; // title alias reference?: boolean; referenceToNode?: boolean; - hasTitleAlias?: boolean; /** * @default true */ enableEmojiIcon?: boolean; } interface DocDisplayTitleOptions { - originalTitle?: string; title?: string; // title alias reference?: boolean; /** @@ -130,24 +129,31 @@ export class DocDisplayMetaService extends Service { const iconSet = icons[options?.type ?? 'rc']; return LiveData.computed(get => { + const enableEmojiIcon = + get(this.featureFlagService.flags.enable_emoji_doc_icon.$) && + options?.enableEmojiIcon !== false; const doc = get(this.docsService.list.doc$(docId)); - const title = doc ? get(doc.title$) : ''; + const referenced = !!options?.reference; + const titleAlias = referenced ? options?.title : undefined; + const originalTitle = doc ? get(doc.title$) : ''; + const title = titleAlias ?? originalTitle; const mode = doc ? get(doc.primaryMode$) : undefined; const finalMode = options?.mode ?? mode ?? 'page'; - const referenceToNode = !!(options?.reference && options.referenceToNode); - const hasTitleAlias = !!(options?.reference && options?.hasTitleAlias); + const referenceToNode = !!(referenced && options.referenceToNode); - // increases block link priority with title alias - if (hasTitleAlias) { - return iconSet.AliasIcon; + // emoji title + if (enableEmojiIcon && title) { + const { emoji } = extractEmojiIcon(title); + if (emoji) return () => emoji; } - // increases block link priority - if (referenceToNode) { - return iconSet.BlockLinkIcon; - } + // title alias + if (titleAlias) return iconSet.AliasIcon; - // journal icon + // link to specified block + if (referenceToNode) return iconSet.BlockLinkIcon; + + // link to journal doc const journalDate = this._toDayjs( get(this.journalService.journalDate$(docId)) ); @@ -155,22 +161,13 @@ export class DocDisplayMetaService extends Service { return this.getJournalIcon(journalDate, options); } - // reference icon + // link to regular doc (reference) if (options?.reference) { return finalMode === 'edgeless' ? iconSet.LinkedEdgelessIcon : iconSet.LinkedPageIcon; } - // emoji icon - const enableEmojiIcon = - get(this.featureFlagService.flags.enable_emoji_doc_icon.$) && - options?.enableEmojiIcon !== false; - if (enableEmojiIcon) { - const { emoji } = extractEmojiIcon(title); - if (emoji) return () => emoji; - } - // default icon return finalMode === 'edgeless' ? iconSet.EdgelessIcon : iconSet.PageIcon; }); @@ -183,14 +180,28 @@ export class DocDisplayMetaService extends Service { options?.enableEmojiIcon !== false; const lng = get(this.i18nService.i18n.currentLanguageKey$); const doc = get(this.docsService.list.doc$(docId)); + const referenced = !!options?.reference; + const titleAlias = referenced ? options?.title : undefined; + const originalTitle = doc ? get(doc.title$) : ''; + const title = titleAlias ?? originalTitle; - // title alias - if (options?.title) { - return enableEmojiIcon - ? extractEmojiIcon(options.title).rest - : options.title; + // emoji title + if (enableEmojiIcon && title) { + const { rest } = extractEmojiIcon(title); + if (rest) return rest; + + // When the title has only one emoji character, + // if it's a journal document, the date should be displayed. + const journalDateString = get(this.journalService.journalDate$(docId)); + if (journalDateString) { + return i18nTime(journalDateString, { absolute: { accuracy: 'day' } }); + } } + // title alias + if (titleAlias) return titleAlias; + + // doc not found if (!doc) { return this.i18nService.i18n.i18next.t( 'com.affine.notFoundPage.title', @@ -205,31 +216,16 @@ export class DocDisplayMetaService extends Service { } // original title - if (options?.originalTitle) return options.originalTitle; - - const docTitle = get(doc.title$); + if (originalTitle) return originalTitle; // empty title - if (!docTitle) { - return this.i18nService.i18n.i18next.t('Untitled', { lng }); - } - - // reference - if (options?.reference) return docTitle; - - // emoji icon - if (enableEmojiIcon) { - return extractEmojiIcon(docTitle).rest; - } - - // default - return docTitle; + return this.i18nService.i18n.i18next.t('Untitled', { lng }); }); } - getDocDisplayMeta(docRecord: DocRecord, originalTitle?: string) { + getDocDisplayMeta(docRecord: DocRecord) { return { - title: this.title$(docRecord.id, { originalTitle }).value, + title: this.title$(docRecord.id).value, icon: this.icon$(docRecord.id).value, updatedDate: docRecord.meta$.value.updatedDate, }; diff --git a/packages/frontend/core/src/modules/quicksearch/impls/docs.ts b/packages/frontend/core/src/modules/quicksearch/impls/docs.ts index a2edce49a..d2ebacdb0 100644 --- a/packages/frontend/core/src/modules/quicksearch/impls/docs.ts +++ b/packages/frontend/core/src/modules/quicksearch/impls/docs.ts @@ -66,10 +66,7 @@ export class DocsQuickSearchSession ) .map(([doc, docRecord]) => { const { title, icon, updatedDate } = - this.docDisplayMetaService.getDocDisplayMeta( - docRecord, - 'title' in doc ? doc.title : undefined - ); + this.docDisplayMetaService.getDocDisplayMeta(docRecord); return { id: 'doc:' + docRecord.id, source: 'docs', diff --git a/tests/affine-local/e2e/links.spec.ts b/tests/affine-local/e2e/links.spec.ts index 949f1e5a7..6627a875e 100644 --- a/tests/affine-local/e2e/links.spec.ts +++ b/tests/affine-local/e2e/links.spec.ts @@ -8,10 +8,17 @@ import { coreUrl, openHomePage } from '@affine-test/kit/utils/load-page'; import { clickNewPageButton, createLinkedPage, + createTodayPage, getBlockSuiteEditorTitle, + waitForEditorLoad, waitForEmptyEditor, } from '@affine-test/kit/utils/page-logic'; -import { expect, type Locator } from '@playwright/test'; +import { + confirmExperimentalPrompt, + openEditorSetting, + openExperimentalFeaturesPanel, +} from '@affine-test/kit/utils/setting'; +import { expect, type Locator, type Page } from '@playwright/test'; test.beforeEach(async ({ page }) => { await openHomePage(page); @@ -19,6 +26,26 @@ test.beforeEach(async ({ page }) => { await waitForEmptyEditor(page); }); +async function enableEmojiDocIcon(page: Page) { + // Opens settings panel + await openEditorSetting(page); + await openExperimentalFeaturesPanel(page); + await confirmExperimentalPrompt(page); + + const settingModal = page.locator('[data-testid=setting-modal-content]'); + const item = settingModal.locator('div').getByText('Emoji Doc Icon'); + await item.waitFor({ state: 'attached' }); + await expect(item).toBeVisible(); + const button = item.locator('label'); + const isChecked = await button.locator('input').isChecked(); + if (!isChecked) { + await button.click(); + } + + // Closes settings panel + await page.keyboard.press('Escape'); +} + async function notClickable(locator: Locator) { await expect(locator).toHaveAttribute('disabled', ''); } @@ -908,4 +935,65 @@ test.describe('Customize linked doc title and description', () => { embedToolbar.getByRole('button', { name: 'Doc title' }) ).toBeHidden(); }); + + test('should show emoji doc icon in normal document', async ({ page }) => { + await waitForEditorLoad(page); + await enableEmojiDocIcon(page); + + await clickNewPageButton(page); + const title = getBlockSuiteEditorTitle(page); + await title.click(); + + await page.keyboard.press('Enter'); + await createLinkedPage(page, 'Test Page'); + + const inlineLink = page.locator('affine-reference'); + const inlineToolbar = page.locator('reference-popup'); + + await inlineLink.hover(); + + // Edits title + await inlineToolbar.getByRole('button', { name: 'Edit' }).click(); + + // Title alias + await page.keyboard.type('🦀hello'); + await page.keyboard.press('Enter'); + + const a = inlineLink.locator('a'); + + await expect(a).toHaveText('🦀hello'); + await expect(a.locator('svg')).toBeHidden(); + await expect(a.locator('.affine-reference-title')).toHaveText('hello'); + }); + + test('should show emoji doc icon in journal document', async ({ page }) => { + await waitForEditorLoad(page); + await enableEmojiDocIcon(page); + + await clickNewPageButton(page); + const title = getBlockSuiteEditorTitle(page); + await title.click(); + + await page.keyboard.press('Enter'); + await createTodayPage(page); + + const inlineLink = page.locator('affine-reference'); + const inlineToolbar = page.locator('reference-popup'); + + await inlineLink.hover(); + + // Edits title + await inlineToolbar.getByRole('button', { name: 'Edit' }).click(); + + // Title alias + await page.keyboard.type('🦀'); + await page.keyboard.press('Enter'); + + const a = inlineLink.locator('a'); + + const year = String(new Date().getFullYear()); + await expect(a).toContainText('🦀'); + await expect(a.locator('svg')).toBeHidden(); + await expect(a.locator('.affine-reference-title')).toContainText(year); + }); }); diff --git a/tests/kit/src/utils/page-logic.ts b/tests/kit/src/utils/page-logic.ts index 2c0515ae6..8837b956c 100644 --- a/tests/kit/src/utils/page-logic.ts +++ b/tests/kit/src/utils/page-logic.ts @@ -76,6 +76,21 @@ export const createLinkedPage = async (page: Page, pageName?: string) => { .click(); }; +export const createTodayPage = async (page: Page) => { + // fixme: workaround for @ popover not showing up when editor is not ready + await page.waitForTimeout(500); + await page.keyboard.type('@', { delay: 50 }); + const linkedPagePopover = page.locator('.linked-doc-popover'); + await expect(linkedPagePopover).toBeVisible(); + await type(page, 'Today'); + + await linkedPagePopover + .locator(`icon-button`) + .filter({ hasText: 'Today' }) + .nth(0) + .click(); +}; + export async function clickPageMoreActions(page: Page) { return page .getByTestId('header')