Skip to content

Commit

Permalink
fix(core): at menu performance on large doc list
Browse files Browse the repository at this point in the history
  • Loading branch information
pengx17 committed Nov 23, 2024
1 parent 83d2527 commit 652b7e1
Show file tree
Hide file tree
Showing 20 changed files with 1,095 additions and 261 deletions.
18 changes: 11 additions & 7 deletions packages/common/infra/src/modules/doc/entities/record-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,7 @@ export class DocRecordList extends Entity {
this.store.watchDocIds().pipe(
map(ids =>
ids.map(id => {
const exists = this.pool.get(id);
if (exists) {
return exists;
}
const record = this.framework.createEntity(DocRecord, { id });
this.pool.set(id, record);
return record;
return this.doc(id);
})
)
),
Expand Down Expand Up @@ -52,6 +46,16 @@ export class DocRecordList extends Entity {
false
);

public doc(id: string) {
const exists = this.pool.get(id);
if (exists) {
return exists;
}
const record = this.framework.createEntity(DocRecord, { id });
this.pool.set(id, record);
return record;
}

public doc$(id: string) {
return this.docs$.map(record => record.find(record => record.id === id));
}
Expand Down
8 changes: 8 additions & 0 deletions packages/common/infra/src/modules/doc/entities/record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export class DocRecord extends Entity<{ id: string }> {
{ id: this.id }
);

get properties() {
return this.docPropertiesStore.getDocProperties(this.id) as DocProperties;
}

customProperty$(propertyId: string) {
return this.properties$.selector(
p => p['custom:' + propertyId]
Expand Down Expand Up @@ -74,6 +78,10 @@ export class DocRecord extends Entity<{ id: string }> {
return this.setMeta({ trash: false, trashDate: undefined });
}

get title() {
return this.docsStore.meta.getDocMeta(this.id)?.title ?? '';
}

title$ = this.meta$.map(meta => meta.title ?? '');

trash$ = this.meta$.map(meta => meta.trash ?? false);
Expand Down
4 changes: 4 additions & 0 deletions packages/common/infra/src/modules/doc/stores/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ export class DocsStore extends Store {
return this.workspaceService.workspace.docCollection.createDoc();
}

get meta() {
return this.workspaceService.workspace.docCollection.meta;
}

watchDocIds() {
return yjsObserveByPath(
this.workspaceService.workspace.rootYDoc.getMap('meta'),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,196 +1,9 @@
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
import { JournalService } from '@affine/core/modules/journal';
import { I18n } from '@affine/i18n';
import { track } from '@affine/track';
import type { EditorHost } from '@blocksuite/affine/block-std';
import type {
AffineInlineEditor,
LinkedWidgetConfig,
} from '@blocksuite/affine/blocks';
import { LinkedWidgetUtils } from '@blocksuite/affine/blocks';
import type { DocMeta } from '@blocksuite/affine/store';
import { type FrameworkProvider, WorkspaceService } from '@toeverything/infra';
import { AtMenuConfigService } from '@affine/core/modules/at-menu-config/services';
import type { LinkedWidgetConfig } from '@blocksuite/affine/blocks';
import { type FrameworkProvider } from '@toeverything/infra';

function createNewDocMenuGroup(
framework: FrameworkProvider,
query: string,
abort: () => void,
editorHost: EditorHost,
inlineEditor: AffineInlineEditor
) {
const originalNewDocMenuGroup = LinkedWidgetUtils.createNewDocMenuGroup(
query,
abort,
editorHost,
inlineEditor
);

const items = Array.isArray(originalNewDocMenuGroup.items)
? originalNewDocMenuGroup.items
: originalNewDocMenuGroup.items.value;

// Patch the import item, to use the custom import dialog.
const importItemIndex = items.findIndex(item => item.key === 'import');
if (importItemIndex === -1) {
return originalNewDocMenuGroup;
}

const originalImportItem = items[importItemIndex];
const customImportItem = {
...originalImportItem,
action: () => {
abort();
track.doc.editor.atMenu.import();
framework
.get(WorkspaceDialogService)
.open('import', undefined, payload => {
if (!payload) {
return;
}

// If the imported file is a workspace file, insert the entry page node.
const { docIds, entryId, isWorkspaceFile } = payload;
if (isWorkspaceFile && entryId) {
LinkedWidgetUtils.insertLinkedNode({
inlineEditor,
docId: entryId,
});
return;
}

// Otherwise, insert all the doc nodes.
for (const docId of docIds) {
LinkedWidgetUtils.insertLinkedNode({
inlineEditor,
docId,
});
}
});
},
};

// only replace the original import item
items.splice(importItemIndex, 1, customImportItem);
return originalNewDocMenuGroup;
}

// TODO: fix the type
export function createLinkedWidgetConfig(
framework: FrameworkProvider
): Partial<LinkedWidgetConfig> {
return {
getMenus: (
query: string,
abort: () => void,
editorHost: EditorHost,
inlineEditor: AffineInlineEditor
) => {
const currentWorkspace = framework.get(WorkspaceService).workspace;
const rawMetas = currentWorkspace.docCollection.meta.docMetas;
const journalService = framework.get(JournalService);
const isJournal = (d: DocMeta) =>
!!journalService.journalDate$(d.id).value;

const docDisplayMetaService = framework.get(DocDisplayMetaService);
const docMetas = rawMetas
.filter(meta => {
if (isJournal(meta) && !meta.updatedDate) {
return false;
}
return !meta.trash;
})
.map(meta => {
const title = docDisplayMetaService.title$(meta.id, {
reference: true,
}).value;
return {
...meta,
title: I18n.t(title),
};
})
.filter(({ title }) => isFuzzyMatch(title, query));

// TODO need i18n if BlockSuite supported
const MAX_DOCS = 6;
return Promise.resolve([
{
name: 'Link to Doc',
items: docMetas.map(doc => ({
key: doc.id,
name: doc.title,
icon: docDisplayMetaService
.icon$(doc.id, {
type: 'lit',
reference: true,
})
.value(),
action: () => {
abort();
LinkedWidgetUtils.insertLinkedNode({
inlineEditor,
docId: doc.id,
});
track.doc.editor.atMenu.linkDoc();
},
})),
maxDisplay: MAX_DOCS,
overflowText: `${docMetas.length - MAX_DOCS} more docs`,
},
createNewDocMenuGroup(
framework,
query,
abort,
editorHost,
inlineEditor
),
]);
},
mobile: {
useScreenHeight: BUILD_CONFIG.isIOS,
scrollContainer: window,
scrollTopOffset: () => {
const header = document.querySelector('header');
if (!header) return 0;

const { y, height } = header.getBoundingClientRect();
return y + height;
},
},
};
}

/**
* Checks if the name is a fuzzy match of the query.
*
* @example
* ```ts
* const name = 'John Smith';
* const query = 'js';
* const isMatch = isFuzzyMatch(name, query);
* // isMatch: true
* ```
*/
function isFuzzyMatch(name: string, query: string) {
const pureName = name
.trim()
.toLowerCase()
.split('')
.filter(char => char !== ' ')
.join('');

const regex = new RegExp(
query
.split('')
.filter(char => char !== ' ')
.map(item => `${escapeRegExp(item)}.*`)
.join(''),
'i'
);
return regex.test(pureName);
}

function escapeRegExp(input: string) {
// escape regex characters in the input string to prevent regex format errors
return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return framework.get(AtMenuConfigService).getConfig();
}
57 changes: 9 additions & 48 deletions packages/frontend/core/src/components/hooks/use-journal.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import { EditorSettingService } from '@affine/core/modules/editor-setting';
import { JournalService } from '@affine/core/modules/journal';
import {
JOURNAL_DATE_FORMAT,
JournalService,
type MaybeDate,
} from '@affine/core/modules/journal';
import { i18nTime } from '@affine/i18n';
import { track } from '@affine/track';
import { Text } from '@blocksuite/affine/store';
import {
type DocProps,
DocsService,
initDocFromProps,
useService,
useServices,
} from '@toeverything/infra';
import { DocsService, useService, useServices } from '@toeverything/infra';
import dayjs from 'dayjs';
import { useCallback, useMemo } from 'react';

import { WorkbenchService } from '../../modules/workbench';

type MaybeDate = Date | string | number;
export const JOURNAL_DATE_FORMAT = 'YYYY-MM-DD';

function isJournalString(j?: string | false) {
return j ? !!j?.match(/^\d{4}-\d{2}-\d{2}$/) : false;
}
Expand All @@ -33,43 +27,12 @@ function toDayjs(j?: string | false) {
* @deprecated use `JournalService` directly
*/
export const useJournalHelper = () => {
const { docsService, editorSettingService, journalService } = useServices({
const { journalService } = useServices({
DocsService,
EditorSettingService,
JournalService,
});

/**
* @internal
*/
const _createJournal = useCallback(
(maybeDate: MaybeDate) => {
const day = dayjs(maybeDate);
const title = day.format(JOURNAL_DATE_FORMAT);
const docRecord = docsService.createDoc();
const { doc, release } = docsService.open(docRecord.id);
docsService.list.setPrimaryMode(docRecord.id, 'page');
// set created date to match the journal date
docRecord.setMeta({
createDate: dayjs()
.set('year', day.year())
.set('month', day.month())
.set('date', day.date())
.toDate()
.getTime(),
});
const docProps: DocProps = {
page: { title: new Text(title) },
note: editorSettingService.editorSetting.get('affine:note'),
};
initDocFromProps(doc.blockSuiteDoc, docProps);
release();
journalService.setJournalDate(docRecord.id, title);
return docRecord;
},
[docsService, editorSettingService.editorSetting, journalService]
);

/**
* query all journals by date
*/
Expand All @@ -87,11 +50,9 @@ export const useJournalHelper = () => {
*/
const getJournalByDate = useCallback(
(maybeDate: MaybeDate) => {
const pages = getJournalsByDate(maybeDate);
if (pages.length) return pages[0];
return _createJournal(maybeDate);
return journalService.ensureJournalByDate(maybeDate);
},
[_createJournal, getJournalsByDate]
[journalService]
);

return useMemo(
Expand Down
2 changes: 2 additions & 0 deletions packages/frontend/core/src/desktop/dialogs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ImportDialog } from './import';
import { ImportTemplateDialog } from './import-template';
import { ImportWorkspaceDialog } from './import-workspace';
import { CollectionSelectorDialog } from './selectors/collection';
import { DateSelectorDialog } from './selectors/date';
import { DocSelectorDialog } from './selectors/doc';
import { TagSelectorDialog } from './selectors/tag';
import { SettingDialog } from './setting';
Expand All @@ -36,6 +37,7 @@ const WORKSPACE_DIALOGS = {
'tag-selector': TagSelectorDialog,
'doc-selector': DocSelectorDialog,
'collection-selector': CollectionSelectorDialog,
'date-selector': DateSelectorDialog,
import: ImportDialog,
} satisfies {
[key in keyof WORKSPACE_DIALOG_SCHEMA]?: React.FC<
Expand Down
Loading

0 comments on commit 652b7e1

Please sign in to comment.