feat(core): emoji as doc icon support with feature flag (#8348)

close AF-1412

Adjusted the priority of the public doc icon:

1. block-reference
2. journal
3. page reference
4. **emoji** (*new)
5. default
This commit is contained in:
CatsJuice
2024-10-10 04:14:31 +00:00
parent 6e9db761a4
commit 17fec8928f
6 changed files with 96 additions and 21 deletions
@@ -104,6 +104,17 @@ export const AFFINE_FLAGS = {
configurable: true, configurable: true,
defaultState: true, defaultState: true,
}, },
enable_emoji_doc_icon: {
category: 'affine',
displayName: 'Emoji Doc Icon',
description:
'Once enabled, you can use an emoji as the page icon. When the first character of the folder name is an emoji, it will be extracted and used as its icon.',
feedbackType: 'discord',
feedbackLink:
'https://discord.com/channels/959027316334407691/1280014319865696351',
configurable: true,
defaultState: true,
},
enable_editor_settings: { enable_editor_settings: {
category: 'affine', category: 'affine',
displayName: 'Editor Settings', displayName: 'Editor Settings',
@@ -52,7 +52,9 @@ export function AffinePageReference({
referenceToNode: linkToNode, referenceToNode: linkToNode,
}) })
); );
const title = useLiveData(docDisplayMetaService.title$(pageId)); const title = useLiveData(
docDisplayMetaService.title$(pageId, { reference: true })
);
const el = ( const el = (
<> <>
@@ -34,7 +34,9 @@ export function createLinkedWidgetConfig(
return !meta.trash; return !meta.trash;
}) })
.map(meta => { .map(meta => {
const title = docDisplayMetaService.title$(meta.id).value; const title = docDisplayMetaService.title$(meta.id, {
reference: true,
}).value;
return { return {
...meta, ...meta,
title: typeof title === 'string' ? title : I18n[title.key](), title: typeof title === 'string' ? title : I18n[title.key](),
@@ -1,5 +1,6 @@
import { import {
DocsService, DocsService,
FeatureFlagService,
type Framework, type Framework,
WorkspaceScope, WorkspaceScope,
} from '@toeverything/infra'; } from '@toeverything/infra';
@@ -12,5 +13,9 @@ export { DocDisplayMetaService };
export function configureDocDisplayMetaModule(framework: Framework) { export function configureDocDisplayMetaModule(framework: Framework) {
framework framework
.scope(WorkspaceScope) .scope(WorkspaceScope)
.service(DocDisplayMetaService, [WorkspacePropertiesAdapter, DocsService]); .service(DocDisplayMetaService, [
WorkspacePropertiesAdapter,
DocsService,
FeatureFlagService,
]);
} }
@@ -1,3 +1,4 @@
import { extractEmojiIcon } from '@affine/core/utils';
import { i18nTime } from '@affine/i18n'; import { i18nTime } from '@affine/i18n';
import { import {
BlockLinkIcon as LitBlockLinkIcon, BlockLinkIcon as LitBlockLinkIcon,
@@ -19,7 +20,11 @@ import {
TomorrowIcon, TomorrowIcon,
YesterdayIcon, YesterdayIcon,
} from '@blocksuite/icons/rc'; } from '@blocksuite/icons/rc';
import type { DocRecord, DocsService } from '@toeverything/infra'; import type {
DocRecord,
DocsService,
FeatureFlagService,
} from '@toeverything/infra';
import { LiveData, Service } from '@toeverything/infra'; import { LiveData, Service } from '@toeverything/infra';
import type { Dayjs } from 'dayjs'; import type { Dayjs } from 'dayjs';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
@@ -37,6 +42,18 @@ interface DocDisplayIconOptions<T extends IconType> {
mode?: 'edgeless' | 'page'; mode?: 'edgeless' | 'page';
reference?: boolean; reference?: boolean;
referenceToNode?: boolean; referenceToNode?: boolean;
/**
* @default true
*/
enableEmojiIcon?: boolean;
}
interface DocDisplayTitleOptions {
originalTitle?: string;
reference?: boolean;
/**
* @default true
*/
enableEmojiIcon?: boolean;
} }
const rcIcons = { const rcIcons = {
@@ -67,7 +84,8 @@ const icons = { rc: rcIcons, lit: litIcons } as {
export class DocDisplayMetaService extends Service { export class DocDisplayMetaService extends Service {
constructor( constructor(
private readonly propertiesAdapter: WorkspacePropertiesAdapter, private readonly propertiesAdapter: WorkspacePropertiesAdapter,
private readonly docsService: DocsService private readonly docsService: DocsService,
private readonly featureFlagService: FeatureFlagService
) { ) {
super(); super();
} }
@@ -80,6 +98,7 @@ export class DocDisplayMetaService extends Service {
return LiveData.computed(get => { return LiveData.computed(get => {
const doc = get(this.docsService.list.doc$(docId)); const doc = get(this.docsService.list.doc$(docId));
const title = doc ? get(doc.title$) : '';
const mode = doc ? get(doc.primaryMode$) : undefined; const mode = doc ? get(doc.primaryMode$) : undefined;
const finalMode = options?.mode ?? mode ?? 'page'; const finalMode = options?.mode ?? mode ?? 'page';
const referenceToNode = !!(options?.reference && options.referenceToNode); const referenceToNode = !!(options?.reference && options.referenceToNode);
@@ -89,10 +108,10 @@ export class DocDisplayMetaService extends Service {
return iconSet.BlockLinkIcon; return iconSet.BlockLinkIcon;
} }
// journal icon
const journalDate = this._toDayjs( const journalDate = this._toDayjs(
this.propertiesAdapter.getJournalPageDateString(docId) this.propertiesAdapter.getJournalPageDateString(docId)
); );
if (journalDate) { if (journalDate) {
if (!options?.compareDate) return iconSet.TodayIcon; if (!options?.compareDate) return iconSet.TodayIcon;
const compareDate = dayjs(options?.compareDate); const compareDate = dayjs(options?.compareDate);
@@ -103,36 +122,65 @@ export class DocDisplayMetaService extends Service {
: iconSet.TodayIcon; : iconSet.TodayIcon;
} }
return options?.reference // reference icon
? finalMode === 'edgeless' if (options?.reference) {
return finalMode === 'edgeless'
? iconSet.LinkedEdgelessIcon ? iconSet.LinkedEdgelessIcon
: iconSet.LinkedPageIcon : iconSet.LinkedPageIcon;
: finalMode === 'edgeless' }
? iconSet.EdgelessIcon
: iconSet.PageIcon; // 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;
}); });
} }
title$(docId: string, originalTitle?: string) { title$(docId: string, options?: DocDisplayTitleOptions) {
return LiveData.computed(get => { return LiveData.computed(get => {
const doc = get(this.docsService.list.doc$(docId)); const doc = get(this.docsService.list.doc$(docId));
const docTitle = doc ? get(doc.title$) : undefined; const docTitle = doc ? get(doc.title$) : undefined;
const journalDateString = const journalDateString =
this.propertiesAdapter.getJournalPageDateString(docId); this.propertiesAdapter.getJournalPageDateString(docId);
return journalDateString
? i18nTime(journalDateString, { absolute: { accuracy: 'day' } }) // journal
: originalTitle || if (journalDateString) {
docTitle || return i18nTime(journalDateString, { absolute: { accuracy: 'day' } });
({ }
key: 'Untitled',
} as const); if (options?.originalTitle) return options.originalTitle;
// empty title
if (!docTitle) return { key: 'Untitled' } as const;
// reference
if (options?.reference) return docTitle;
// check emoji
const enableEmojiIcon =
get(this.featureFlagService.flags.enable_emoji_doc_icon.$) &&
options?.enableEmojiIcon !== false;
if (enableEmojiIcon) {
const { rest } = extractEmojiIcon(docTitle);
return rest;
}
// default
return docTitle;
}); });
} }
getDocDisplayMeta(docRecord: DocRecord, originalTitle?: string) { getDocDisplayMeta(docRecord: DocRecord, originalTitle?: string) {
return { return {
title: this.title$(docRecord.id, originalTitle).value, title: this.title$(docRecord.id, { originalTitle }).value,
icon: this.icon$(docRecord.id).value, icon: this.icon$(docRecord.id).value,
updatedDate: docRecord.meta$.value.updatedDate, updatedDate: docRecord.meta$.value.updatedDate,
}; };
@@ -14,6 +14,7 @@ import { useI18n } from '@affine/i18n';
import { track } from '@affine/track'; import { track } from '@affine/track';
import { import {
DocsService, DocsService,
FeatureFlagService,
GlobalContextService, GlobalContextService,
LiveData, LiveData,
useLiveData, useLiveData,
@@ -47,11 +48,13 @@ export const ExplorerDocNode = ({
docsService, docsService,
globalContextService, globalContextService,
docDisplayMetaService, docDisplayMetaService,
featureFlagService,
} = useServices({ } = useServices({
DocsSearchService, DocsSearchService,
DocsService, DocsService,
GlobalContextService, GlobalContextService,
DocDisplayMetaService, DocDisplayMetaService,
FeatureFlagService,
}); });
// const pageInfoAdapter = useCurrentWorkspacePropertiesAdapter(); // const pageInfoAdapter = useCurrentWorkspacePropertiesAdapter();
@@ -67,6 +70,9 @@ export const ExplorerDocNode = ({
); );
const docTitle = useLiveData(docDisplayMetaService.title$(docId)); const docTitle = useLiveData(docDisplayMetaService.title$(docId));
const isInTrash = useLiveData(docRecord?.trash$); const isInTrash = useLiveData(docRecord?.trash$);
const enableEmojiIcon = useLiveData(
featureFlagService.flags.enable_emoji_doc_icon.$
);
const Icon = useCallback( const Icon = useCallback(
({ className }: { className?: string }) => { ({ className }: { className?: string }) => {
@@ -206,6 +212,7 @@ export const ExplorerDocNode = ({
dndData={dndData} dndData={dndData}
onDrop={handleDropOnDoc} onDrop={handleDropOnDoc}
renameable renameable
extractEmojiAsIcon={enableEmojiIcon}
collapsed={collapsed} collapsed={collapsed}
setCollapsed={setCollapsed} setCollapsed={setCollapsed}
canDrop={handleCanDrop} canDrop={handleCanDrop}