mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-11 23:26:30 +08:00
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:
@@ -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 = (
|
||||
<>
|
||||
|
||||
+3
-1
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user