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,
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: {
category: 'affine',
displayName: 'Editor Settings',
@@ -52,7 +52,9 @@ export function AffinePageReference({
referenceToNode: linkToNode,
})
);
const title = useLiveData(docDisplayMetaService.title$(pageId));
const title = useLiveData(
docDisplayMetaService.title$(pageId, { reference: true })
);
const el = (
<>
@@ -34,7 +34,9 @@ export function createLinkedWidgetConfig(
return !meta.trash;
})
.map(meta => {
const title = docDisplayMetaService.title$(meta.id).value;
const title = docDisplayMetaService.title$(meta.id, {
reference: true,
}).value;
return {
...meta,
title: typeof title === 'string' ? title : I18n[title.key](),
@@ -1,5 +1,6 @@
import {
DocsService,
FeatureFlagService,
type Framework,
WorkspaceScope,
} from '@toeverything/infra';
@@ -12,5 +13,9 @@ export { DocDisplayMetaService };
export function configureDocDisplayMetaModule(framework: Framework) {
framework
.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 {
BlockLinkIcon as LitBlockLinkIcon,
@@ -19,7 +20,11 @@ import {
TomorrowIcon,
YesterdayIcon,
} 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 type { Dayjs } from 'dayjs';
import dayjs from 'dayjs';
@@ -37,6 +42,18 @@ interface DocDisplayIconOptions<T extends IconType> {
mode?: 'edgeless' | 'page';
reference?: boolean;
referenceToNode?: boolean;
/**
* @default true
*/
enableEmojiIcon?: boolean;
}
interface DocDisplayTitleOptions {
originalTitle?: string;
reference?: boolean;
/**
* @default true
*/
enableEmojiIcon?: boolean;
}
const rcIcons = {
@@ -67,7 +84,8 @@ const icons = { rc: rcIcons, lit: litIcons } as {
export class DocDisplayMetaService extends Service {
constructor(
private readonly propertiesAdapter: WorkspacePropertiesAdapter,
private readonly docsService: DocsService
private readonly docsService: DocsService,
private readonly featureFlagService: FeatureFlagService
) {
super();
}
@@ -80,6 +98,7 @@ export class DocDisplayMetaService extends Service {
return LiveData.computed(get => {
const doc = get(this.docsService.list.doc$(docId));
const title = doc ? get(doc.title$) : '';
const mode = doc ? get(doc.primaryMode$) : undefined;
const finalMode = options?.mode ?? mode ?? 'page';
const referenceToNode = !!(options?.reference && options.referenceToNode);
@@ -89,10 +108,10 @@ export class DocDisplayMetaService extends Service {
return iconSet.BlockLinkIcon;
}
// journal icon
const journalDate = this._toDayjs(
this.propertiesAdapter.getJournalPageDateString(docId)
);
if (journalDate) {
if (!options?.compareDate) return iconSet.TodayIcon;
const compareDate = dayjs(options?.compareDate);
@@ -103,36 +122,65 @@ export class DocDisplayMetaService extends Service {
: iconSet.TodayIcon;
}
return options?.reference
? finalMode === 'edgeless'
// reference icon
if (options?.reference) {
return finalMode === 'edgeless'
? iconSet.LinkedEdgelessIcon
: iconSet.LinkedPageIcon
: finalMode === 'edgeless'
? iconSet.EdgelessIcon
: iconSet.PageIcon;
: 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;
});
}
title$(docId: string, originalTitle?: string) {
title$(docId: string, options?: DocDisplayTitleOptions) {
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);
// journal
if (journalDateString) {
return i18nTime(journalDateString, { absolute: { accuracy: 'day' } });
}
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) {
return {
title: this.title$(docRecord.id, originalTitle).value,
title: this.title$(docRecord.id, { originalTitle }).value,
icon: this.icon$(docRecord.id).value,
updatedDate: docRecord.meta$.value.updatedDate,
};
@@ -14,6 +14,7 @@ import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import {
DocsService,
FeatureFlagService,
GlobalContextService,
LiveData,
useLiveData,
@@ -47,11 +48,13 @@ export const ExplorerDocNode = ({
docsService,
globalContextService,
docDisplayMetaService,
featureFlagService,
} = useServices({
DocsSearchService,
DocsService,
GlobalContextService,
DocDisplayMetaService,
FeatureFlagService,
});
// const pageInfoAdapter = useCurrentWorkspacePropertiesAdapter();
@@ -67,6 +70,9 @@ export const ExplorerDocNode = ({
);
const docTitle = useLiveData(docDisplayMetaService.title$(docId));
const isInTrash = useLiveData(docRecord?.trash$);
const enableEmojiIcon = useLiveData(
featureFlagService.flags.enable_emoji_doc_icon.$
);
const Icon = useCallback(
({ className }: { className?: string }) => {
@@ -206,6 +212,7 @@ export const ExplorerDocNode = ({
dndData={dndData}
onDrop={handleDropOnDoc}
renameable
extractEmojiAsIcon={enableEmojiIcon}
collapsed={collapsed}
setCollapsed={setCollapsed}
canDrop={handleCanDrop}