feat(editor): improve dated doc creation (#14791)

#### PR Dependency Tree


* **PR #14791** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)
This commit is contained in:
DarkSky
2026-04-05 17:16:31 +08:00
committed by GitHub
parent 558400b7db
commit b0cc2fd389
10 changed files with 129 additions and 331 deletions
@@ -2,22 +2,51 @@ import { toast } from '@affine/component';
import { getStoreManager } from '@affine/core/blocksuite/manager/store'; import { getStoreManager } from '@affine/core/blocksuite/manager/store';
import { AppSidebarService } from '@affine/core/modules/app-sidebar'; import { AppSidebarService } from '@affine/core/modules/app-sidebar';
import { DocsService } from '@affine/core/modules/doc'; import { DocsService } from '@affine/core/modules/doc';
import {
EditorSettingService,
resolveNewDocTitle,
} from '@affine/core/modules/editor-setting';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
import { getAFFiNEWorkspaceSchema } from '@affine/core/modules/workspace'; import { getAFFiNEWorkspaceSchema } from '@affine/core/modules/workspace';
import { type DocMode } from '@blocksuite/affine/model'; import { type DocMode } from '@blocksuite/affine/model';
import type { Workspace } from '@blocksuite/affine/store'; import type { Workspace } from '@blocksuite/affine/store';
import { useServices } from '@toeverything/infra'; import { LiveData, useLiveData, useServices } from '@toeverything/infra';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
export const usePageHelper = (docCollection: Workspace) => { export const usePageHelper = (docCollection: Workspace) => {
const { docsService, workbenchService, appSidebarService } = useServices({ const {
docsService,
workbenchService,
appSidebarService,
editorSettingService,
} = useServices({
DocsService, DocsService,
WorkbenchService, WorkbenchService,
AppSidebarService, AppSidebarService,
EditorSettingService,
}); });
const workbench = workbenchService.workbench; const workbench = workbenchService.workbench;
const docRecordList = docsService.list; const docRecordList = docsService.list;
const appSidebar = appSidebarService.sidebar; const appSidebar = appSidebarService.sidebar;
const settings = useLiveData(editorSettingService.editorSetting.settings$);
const allDocTitles = useLiveData(
useMemo(() => LiveData.from(docsService.allDocTitle$(), []), [docsService])
);
const createBlankDoc = useCallback(() => {
const title = resolveNewDocTitle({
autoTitleEnabled: settings.autoTitleNewDocWithCurrentDate,
existingTitles: allDocTitles.map(doc => doc.title).filter(Boolean),
format: settings.newDocDateTitleFormat,
});
return docsService.createDoc(title ? { title } : undefined);
}, [
allDocTitles,
docsService,
settings.autoTitleNewDocWithCurrentDate,
settings.newDocDateTitleFormat,
]);
const createPageAndOpen = useCallback( const createPageAndOpen = useCallback(
( (
@@ -31,7 +60,7 @@ export const usePageHelper = (docCollection: Workspace) => {
} }
) => { ) => {
appSidebar.setHovering(false); appSidebar.setHovering(false);
const page = docsService.createDoc(); const page = createBlankDoc();
if (mode) { if (mode) {
docRecordList.doc$(page.id).value?.setPrimaryMode(mode); docRecordList.doc$(page.id).value?.setPrimaryMode(mode);
@@ -45,7 +74,7 @@ export const usePageHelper = (docCollection: Workspace) => {
} }
return page; return page;
}, },
[appSidebar, docRecordList, docsService, workbench] [appSidebar, createBlankDoc, docRecordList, workbench]
); );
const createEdgelessAndOpen = useCallback( const createEdgelessAndOpen = useCallback(
@@ -37,6 +37,7 @@ import { useLiveData, useService, useServices } from '@toeverything/infra';
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { usePageHelper } from '../../blocksuite/block-suite-page-list/utils';
import { import {
type CollectionMeta, type CollectionMeta,
CollectionService, CollectionService,
@@ -340,13 +341,16 @@ export const CollectionOperationCell = ({
compatibleFavoriteItemsAdapter: favAdapter, compatibleFavoriteItemsAdapter: favAdapter,
workspaceDialogService, workspaceDialogService,
collectionService, collectionService,
docsService, workspaceService,
} = useServices({ } = useServices({
CompatibleFavoriteItemsAdapter, CompatibleFavoriteItemsAdapter,
WorkspaceDialogService, WorkspaceDialogService,
CollectionService, CollectionService,
DocsService, WorkspaceService,
}); });
const { createPage } = usePageHelper(
workspaceService.workspace.docCollection
);
const collectionId = collectionMeta.id; const collectionId = collectionMeta.id;
const { openConfirmModal } = useConfirmModal(); const { openConfirmModal } = useConfirmModal();
const favourite = useLiveData( const favourite = useLiveData(
@@ -409,9 +413,9 @@ export const CollectionOperationCell = ({
}, [favAdapter, collectionId, t]); }, [favAdapter, collectionId, t]);
const createAndAddDocument = useCallback(() => { const createAndAddDocument = useCallback(() => {
const newDoc = docsService.createDoc(); const newDoc = createPage(undefined, { show: false });
collectionService.addDocToCollection(collectionId, newDoc.id); collectionService.addDocToCollection(collectionId, newDoc.id);
}, [docsService, collectionService, collectionId]); }, [collectionService, collectionId, createPage]);
const onConfirmAddDocToCollection = useCallback(() => { const onConfirmAddDocToCollection = useCallback(() => {
openConfirmModal({ openConfirmModal({
@@ -430,26 +430,22 @@ const NewDocDefaultModeSettings = () => {
); );
}; };
export const getNewDocDateTitleFormatItems = ( const getDateTitleFormatLabel = (format: NewDocDateTitleFormat) => {
t: ReturnType<typeof useI18n> return `com.affine.settings.editorSettings.general.auto-date-title.format.${format.toLowerCase()}` as const;
): Array<{
value: NewDocDateTitleFormat;
label: string;
}> => {
return newDocDateTitleFormatOptions.map(value => ({
value,
label: t.t(
`com.affine.settings.editorSettings.general.auto-date-title.format.${value.toLowerCase()}`
),
}));
}; };
export const NewDocDateTitleSettings = () => { export const NewDocDateTitleSettings = () => {
const t = useI18n(); const t = useI18n();
const { editorSettingService } = useServices({ EditorSettingService }); const { editorSettingService } = useServices({ EditorSettingService });
const settings = useLiveData(editorSettingService.editorSetting.settings$); const settings = useLiveData(editorSettingService.editorSetting.settings$);
const formatItems = useMemo(
const formatItems = useMemo(() => getNewDocDateTitleFormatItems(t), [t]); () =>
newDocDateTitleFormatOptions.map(value => ({
value,
label: t.t(getDateTitleFormatLabel(value)),
})),
[t]
);
const onToggleAutoDateTitle = useCallback( const onToggleAutoDateTitle = useCallback(
(checked: boolean) => { (checked: boolean) => {
@@ -494,17 +490,15 @@ export const NewDocDateTitleSettings = () => {
> >
<Menu <Menu
contentOptions={menuContentOptions} contentOptions={menuContentOptions}
items={formatItems.map(item => { items={formatItems.map(item => (
return ( <MenuItem
<MenuItem key={item.value}
key={item.value} selected={item.value === settings.newDocDateTitleFormat}
selected={item.value === settings.newDocDateTitleFormat} onSelect={() => onDateTitleFormatChange(item.value)}
onSelect={() => onDateTitleFormatChange(item.value)} >
> {item.label}
{item.label} </MenuItem>
</MenuItem> ))}
);
})}
> >
<MenuTrigger <MenuTrigger
className={styles.menuTrigger} className={styles.menuTrigger}
@@ -4,7 +4,7 @@ export { DocRecordList } from './entities/record-list';
export { DocCreated } from './events'; export { DocCreated } from './events';
export { DocScope } from './scopes/doc'; export { DocScope } from './scopes/doc';
export { DocService } from './services/doc'; export { DocService } from './services/doc';
export { DocsQueryService, DocsService } from './services/docs'; export { DocsService } from './services/docs';
import type { Framework } from '@toeverything/infra'; import type { Framework } from '@toeverything/infra';
@@ -17,7 +17,7 @@ import { DocRecordList } from './entities/record-list';
import { DocCreateMiddleware } from './providers/doc-create-middleware'; import { DocCreateMiddleware } from './providers/doc-create-middleware';
import { DocScope } from './scopes/doc'; import { DocScope } from './scopes/doc';
import { DocService } from './services/doc'; import { DocService } from './services/doc';
import { DocsQueryService, DocsService } from './services/docs'; import { DocsService } from './services/docs';
import { DocPropertiesStore } from './stores/doc-properties'; import { DocPropertiesStore } from './stores/doc-properties';
import { DocsStore } from './stores/docs'; import { DocsStore } from './stores/docs';
@@ -26,11 +26,10 @@ export { DocCreateMiddleware } from './providers/doc-create-middleware';
export function configureDocModule(framework: Framework) { export function configureDocModule(framework: Framework) {
framework framework
.scope(WorkspaceScope) .scope(WorkspaceScope)
.service(DocsQueryService, [DocsStore, DocPropertiesStore])
.service(DocsService, [ .service(DocsService, [
DocsStore, DocsStore,
DocPropertiesStore,
[DocCreateMiddleware], [DocCreateMiddleware],
DocsQueryService,
]) ])
.store(DocPropertiesStore, [WorkspaceService, WorkspaceDBService]) .store(DocPropertiesStore, [WorkspaceService, WorkspaceDBService])
.store(DocsStore, [WorkspaceService, DocPropertiesStore]) .store(DocsStore, [WorkspaceService, DocPropertiesStore])
@@ -22,7 +22,15 @@ import { getDuplicatedDocTitle } from './duplicate-title';
const logger = new DebugLogger('DocsService'); const logger = new DebugLogger('DocsService');
export class DocsQueryService extends Service { export class DocsService extends Service {
list = this.framework.createEntity(DocRecordList);
pool = new ObjectPool<string, Doc>({
onDelete(obj) {
obj.scope.dispose();
},
});
/** /**
* Get all property values of a property, used for search * Get all property values of a property, used for search
* *
@@ -80,60 +88,11 @@ export class DocsQueryService extends Service {
constructor( constructor(
private readonly store: DocsStore, private readonly store: DocsStore,
private readonly docPropertiesStore: DocPropertiesStore private readonly docPropertiesStore: DocPropertiesStore,
private readonly docCreateMiddlewares: DocCreateMiddleware[]
) { ) {
super(); super();
} }
}
export class DocsService extends Service {
list = this.framework.createEntity(DocRecordList);
pool = new ObjectPool<string, Doc>({
onDelete(obj) {
obj.scope.dispose();
},
});
constructor(
private readonly store: DocsStore,
private readonly docCreateMiddlewares: DocCreateMiddleware[],
private readonly docsQueryService: DocsQueryService
) {
super();
}
propertyValues$(propertyKey: string) {
return this.docsQueryService.propertyValues$(propertyKey);
}
allDocsCreatedDate$() {
return this.docsQueryService.allDocsCreatedDate$();
}
allDocsUpdatedDate$() {
return this.docsQueryService.allDocsUpdatedDate$();
}
allDocsTagIds$() {
return this.docsQueryService.allDocsTagIds$();
}
allDocIds$() {
return this.docsQueryService.allDocIds$();
}
allNonTrashDocIds$() {
return this.docsQueryService.allNonTrashDocIds$();
}
allTrashDocIds$() {
return this.docsQueryService.allTrashDocIds$();
}
allDocTitle$() {
return this.docsQueryService.allDocTitle$();
}
loaded(docId: string) { loaded(docId: string) {
const exists = this.pool.get(docId); const exists = this.pool.get(docId);
@@ -206,9 +165,6 @@ export class DocsService extends Service {
if (options.isTemplate) { if (options.isTemplate) {
docRecord.setProperty('isTemplate', true); docRecord.setProperty('isTemplate', true);
} }
if (options.title?.trim()) {
docRecord.setMeta({ title: options.title });
}
for (const middleware of this.docCreateMiddlewares) { for (const middleware of this.docCreateMiddlewares) {
middleware.afterCreate?.(docRecord, options); middleware.afterCreate?.(docRecord, options);
} }
@@ -4,18 +4,13 @@ import { describe, expect, test } from 'vitest';
import { import {
buildNewDocDateTitle, buildNewDocDateTitle,
getUniqueNewDocDateTitle, getUniqueNewDocDateTitle,
resolveNewDocTitle,
} from '../utils/date-title'; } from '../utils/date-title';
describe('date-title', () => { describe('date-title', () => {
test('formats dates using DD-MM-YYYY', () => { test('formats dates using supported numeric formats', () => {
expect(buildNewDocDateTitle('2026-03-23', 'DD-MM-YYYY')).toBe('23-03-2026'); expect(buildNewDocDateTitle('2026-03-23', 'DD-MM-YYYY')).toBe('23-03-2026');
});
test('formats dates using MM-DD-YYYY', () => {
expect(buildNewDocDateTitle('2026-03-23', 'MM-DD-YYYY')).toBe('03-23-2026'); expect(buildNewDocDateTitle('2026-03-23', 'MM-DD-YYYY')).toBe('03-23-2026');
});
test('formats dates using YYYY-MM-DD', () => {
expect(buildNewDocDateTitle('2026-03-23', 'YYYY-MM-DD')).toBe('2026-03-23'); expect(buildNewDocDateTitle('2026-03-23', 'YYYY-MM-DD')).toBe('2026-03-23');
}); });
@@ -24,26 +19,6 @@ describe('date-title', () => {
expect(buildNewDocDateTitle('2026-03-23', 'journal')).toBe('Mar 23, 2026'); expect(buildNewDocDateTitle('2026-03-23', 'journal')).toBe('Mar 23, 2026');
}); });
test('returns the base title when there is no collision', () => {
expect(
getUniqueNewDocDateTitle({
existingTitles: ['Some title'],
format: 'DD-MM-YYYY',
date: '2026-03-23',
})
).toBe('23-03-2026');
});
test('suffixes duplicate titles starting at (2)', () => {
expect(
getUniqueNewDocDateTitle({
existingTitles: ['23-03-2026'],
format: 'DD-MM-YYYY',
date: '2026-03-23',
})
).toBe('23-03-2026(2)');
});
test('increments to the next available duplicate suffix', () => { test('increments to the next available duplicate suffix', () => {
expect( expect(
getUniqueNewDocDateTitle({ getUniqueNewDocDateTitle({
@@ -59,13 +34,37 @@ describe('date-title', () => {
).toBe('23-03-2026(4)'); ).toBe('23-03-2026(4)');
}); });
test('does not suffix when only duplicate-style titles exist', () => { test('keeps provided titles unchanged', () => {
expect( expect(
getUniqueNewDocDateTitle({ resolveNewDocTitle({
existingTitles: ['23-03-2026(2)'], title: 'Typed by user',
autoTitleEnabled: true,
existingTitles: ['23-03-2026'],
format: 'DD-MM-YYYY', format: 'DD-MM-YYYY',
date: '2026-03-23', date: '2026-03-23',
}) })
).toBe('23-03-2026'); ).toBe('Typed by user');
});
test('returns undefined for blank titles when the feature is disabled', () => {
expect(
resolveNewDocTitle({
autoTitleEnabled: false,
existingTitles: ['23-03-2026'],
format: 'DD-MM-YYYY',
date: '2026-03-23',
})
).toBeUndefined();
});
test('generates a unique title for blank docs when enabled', () => {
expect(
resolveNewDocTitle({
autoTitleEnabled: true,
existingTitles: ['23-03-2026', '23-03-2026(2)'],
format: 'DD-MM-YYYY',
date: '2026-03-23',
})
).toBe('23-03-2026(3)');
}); });
}); });
@@ -1,177 +0,0 @@
import { getOrCreateI18n } from '@affine/i18n';
import { Framework, Service } from '@toeverything/infra';
import { of } from 'rxjs';
import { afterEach, describe, expect, test, vi } from 'vitest';
import { EditorSettingDocCreateMiddleware } from '../impls/doc-create-middleware';
const createDocsQueryService = (titles: string[]) => {
return {
['allDocTitle$']: () =>
of(
titles.map((title, index) => ({
id: `doc-${index}`,
title,
}))
),
};
};
const createEditorSettingService = (overrides?: Record<string, unknown>) => {
return {
editorSetting: {
['settings$']: {
value: {
newDocDefaultMode: 'page',
autoTitleNewDocWithCurrentDate: false,
newDocDateTitleFormat: 'DD-MM-YYYY',
...overrides,
},
},
get: vi.fn((key: string) => {
if (key === 'affine:note') {
return undefined;
}
if (key === 'edgelessDefaultTheme') {
return 'specified';
}
return undefined;
}),
},
};
};
const appThemeService = {
appTheme: {
['theme$']: {
value: 'light',
},
},
};
const createMiddleware = ({
settings,
titles,
}: {
settings?: Record<string, unknown>;
titles?: string[];
}) => {
class MockEditorSettingService extends Service {
editorSetting = createEditorSettingService(settings).editorSetting;
}
class MockAppThemeService extends Service {
appTheme = appThemeService.appTheme;
}
class MockDocsQueryService extends Service {
['allDocTitle$'] =
createDocsQueryService(titles ?? [])['allDocTitle$'];
}
const framework = new Framework();
framework
.service(MockEditorSettingService)
.service(MockAppThemeService)
.service(MockDocsQueryService)
.service(EditorSettingDocCreateMiddleware, [
MockEditorSettingService as never,
MockAppThemeService as never,
MockDocsQueryService as never,
]);
return framework.provider().get(EditorSettingDocCreateMiddleware);
};
describe('EditorSettingDocCreateMiddleware', () => {
afterEach(() => {
vi.useRealTimers();
});
test('adds an auto date title for blank docs when enabled', () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-03-23T09:00:00.000Z'));
const middleware = createMiddleware({
settings: {
autoTitleNewDocWithCurrentDate: true,
newDocDateTitleFormat: 'DD-MM-YYYY',
},
});
expect(middleware.beforeCreate({})).toMatchObject({
title: '23-03-2026',
primaryMode: 'page',
});
});
test('keeps blank docs untitled when the feature is disabled', () => {
const middleware = createMiddleware({});
expect(middleware.beforeCreate({})).toMatchObject({
primaryMode: 'page',
});
expect(middleware.beforeCreate({}).title).toBeUndefined();
});
test('does not override explicitly provided titles', () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-03-23T09:00:00.000Z'));
const middleware = createMiddleware({
settings: {
autoTitleNewDocWithCurrentDate: true,
},
titles: ['23-03-2026'],
});
expect(
middleware.beforeCreate({
title: 'Typed by user',
}).title
).toBe('Typed by user');
});
test('uses the next duplicate suffix when the date title already exists', () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-03-23T09:00:00.000Z'));
const middleware = createMiddleware({
settings: {
autoTitleNewDocWithCurrentDate: true,
},
titles: ['23-03-2026', '23-03-2026(2)'],
});
expect(middleware.beforeCreate({}).title).toBe('23-03-2026(3)');
});
test('uses the selected format for the generated title', () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-03-23T09:00:00.000Z'));
const middleware = createMiddleware({
settings: {
autoTitleNewDocWithCurrentDate: true,
newDocDateTitleFormat: 'YYYY-MM-DD',
},
});
expect(middleware.beforeCreate({}).title).toBe('2026-03-23');
});
test('supports month-name formats for generated titles', () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-03-23T09:00:00.000Z'));
getOrCreateI18n();
const middleware = createMiddleware({
settings: {
autoTitleNewDocWithCurrentDate: true,
newDocDateTitleFormat: 'journal',
},
});
expect(middleware.beforeCreate({}).title).toBe('Mar 23, 2026');
});
});
@@ -1,15 +1,10 @@
import { LiveData, Service } from '@toeverything/infra'; import { Service } from '@toeverything/infra';
import type { import type { DocCreateMiddleware, DocRecord } from '../../doc';
DocCreateMiddleware,
DocRecord,
DocsQueryService,
} from '../../doc';
import type { DocCreateOptions } from '../../doc/types'; import type { DocCreateOptions } from '../../doc/types';
import type { AppThemeService } from '../../theme'; import type { AppThemeService } from '../../theme';
import type { EdgelessDefaultTheme } from '../schema'; import type { EdgelessDefaultTheme } from '../schema';
import type { EditorSettingService } from '../services/editor-setting'; import type { EditorSettingService } from '../services/editor-setting';
import { getUniqueNewDocDateTitle } from '../utils/date-title';
const getValueByDefaultTheme = ( const getValueByDefaultTheme = (
defaultTheme: EdgelessDefaultTheme, defaultTheme: EdgelessDefaultTheme,
@@ -33,42 +28,23 @@ export class EditorSettingDocCreateMiddleware
extends Service extends Service
implements DocCreateMiddleware implements DocCreateMiddleware
{ {
private readonly allDocTitles$: LiveData<{ id: string; title: string }[]>;
constructor( constructor(
private readonly editorSettingService: EditorSettingService, private readonly editorSettingService: EditorSettingService,
private readonly appThemeService: AppThemeService, private readonly appThemeService: AppThemeService
private readonly docsQueryService: DocsQueryService
) { ) {
super(); super();
this.allDocTitles$ = LiveData.from(this.docsQueryService.allDocTitle$(), []);
} }
private getCurrentDocTitles() {
return this.allDocTitles$.value.map(doc => doc.title).filter(Boolean);
}
beforeCreate(docCreateOptions: DocCreateOptions): DocCreateOptions { beforeCreate(docCreateOptions: DocCreateOptions): DocCreateOptions {
// clone the docCreateOptions to avoid mutating the original object // clone the docCreateOptions to avoid mutating the original object
docCreateOptions = { docCreateOptions = {
...docCreateOptions, ...docCreateOptions,
}; };
const settings = this.editorSettingService.editorSetting.settings$.value; const preferMode =
const preferMode = settings.newDocDefaultMode; this.editorSettingService.editorSetting.settings$.value.newDocDefaultMode;
const mode = preferMode === 'ask' ? 'page' : preferMode; const mode = preferMode === 'ask' ? 'page' : preferMode;
docCreateOptions.primaryMode ??= mode; docCreateOptions.primaryMode ??= mode;
if (
!docCreateOptions.title?.trim() &&
settings.autoTitleNewDocWithCurrentDate
) {
docCreateOptions.title = getUniqueNewDocDateTitle({
existingTitles: this.getCurrentDocTitles(),
format: settings.newDocDateTitleFormat,
});
}
docCreateOptions.docProps = { docCreateOptions.docProps = {
...docCreateOptions.docProps, ...docCreateOptions.docProps,
note: this.editorSettingService.editorSetting.get('affine:note'), note: this.editorSettingService.editorSetting.get('affine:note'),
@@ -2,7 +2,7 @@ import { type Framework } from '@toeverything/infra';
import { ServersService } from '../cloud'; import { ServersService } from '../cloud';
import { DesktopApiService } from '../desktop-api'; import { DesktopApiService } from '../desktop-api';
import { DocCreateMiddleware, DocsQueryService } from '../doc'; import { DocCreateMiddleware } from '../doc';
import { I18n } from '../i18n'; import { I18n } from '../i18n';
import { GlobalState, GlobalStateService } from '../storage'; import { GlobalState, GlobalStateService } from '../storage';
import { AppThemeService } from '../theme'; import { AppThemeService } from '../theme';
@@ -21,6 +21,7 @@ export {
newDocDateTitleFormatOptions, newDocDateTitleFormatOptions,
} from './schema'; } from './schema';
export { EditorSettingService } from './services/editor-setting'; export { EditorSettingService } from './services/editor-setting';
export { resolveNewDocTitle } from './utils/date-title';
export function configureEditorSettingModule(framework: Framework) { export function configureEditorSettingModule(framework: Framework) {
framework framework
@@ -34,7 +35,6 @@ export function configureEditorSettingModule(framework: Framework) {
.impl(DocCreateMiddleware, EditorSettingDocCreateMiddleware, [ .impl(DocCreateMiddleware, EditorSettingDocCreateMiddleware, [
EditorSettingService, EditorSettingService,
AppThemeService, AppThemeService,
DocsQueryService,
]); ]);
} }
@@ -39,3 +39,21 @@ export const getUniqueNewDocDateTitle = ({
return `${baseTitle}(${duplicateIndex})`; return `${baseTitle}(${duplicateIndex})`;
}; };
export const resolveNewDocTitle = ({
title,
autoTitleEnabled,
existingTitles,
format,
date = new Date(),
}: {
title?: string;
autoTitleEnabled: boolean;
existingTitles: Iterable<string>;
format: NewDocDateTitleFormat;
date?: dayjs.ConfigType;
}) => {
if (title?.trim()) return title;
if (!autoTitleEnabled) return undefined;
return getUniqueNewDocDateTitle({ existingTitles, format, date });
};