fix: some regression issues on quick search refactor (#7410)

- fix PD-1370. doc link resolve issue on pasting
- fix AF-1029
- implement doc creation in doc search

added two test cases to cover the above two issues.
This commit is contained in:
pengx17
2024-07-03 03:24:50 +00:00
parent 61870c04d0
commit cc7740d8d3
6 changed files with 185 additions and 89 deletions

View File

@@ -29,11 +29,11 @@ import {
type RootService, type RootService,
} from '@blocksuite/blocks'; } from '@blocksuite/blocks';
import { LinkIcon } from '@blocksuite/icons/rc'; import { LinkIcon } from '@blocksuite/icons/rc';
import type { import {
DocMode, type DocMode,
DocService, type DocService,
DocsService, DocsService,
FrameworkProvider, type FrameworkProvider,
} from '@toeverything/infra'; } from '@toeverything/infra';
import { type TemplateResult } from 'lit'; import { type TemplateResult } from 'lit';
import { customElement } from 'lit/decorators.js'; import { customElement } from 'lit/decorators.js';
@@ -337,13 +337,27 @@ export function patchQuickSearchService(
if (!query) { if (!query) {
logger.error('No user input provided'); logger.error('No user input provided');
} else { } else {
const searchedDoc = ( const resolvedDoc = resolveLinkToDoc(query);
await framework.get(DocsSearchService).search(query) if (resolvedDoc) {
).at(0);
if (searchedDoc) {
searchResult = { searchResult = {
docId: searchedDoc.docId, docId: resolvedDoc.docId,
}; };
} else if (
query.startsWith('http://') ||
query.startsWith('https://')
) {
searchResult = {
userInput: query,
};
} else {
const searchedDoc = (
await framework.get(DocsSearchService).search(query)
).at(0);
if (searchedDoc) {
searchResult = {
docId: searchedDoc.docId,
};
}
} }
} }
} else { } else {
@@ -394,11 +408,15 @@ export function patchQuickSearchService(
result.source === 'creation' && result.source === 'creation' &&
result.id === 'creation:create-page' result.id === 'creation:create-page'
) { ) {
throw new Error('Not implemented'); const docsService = framework.get(DocsService);
// resolve({ const newDoc = docsService.createDoc({
// docId: 'new-doc', mode: 'page',
// isNewDoc: true, title: result.payload.title,
// }); });
resolve({
docId: newDoc.id,
isNewDoc: true,
});
} }
}, },
{ {

View File

@@ -1,5 +1,4 @@
import { EdgelessIcon, PageIcon, TodayIcon } from '@blocksuite/icons/rc'; import type { DocRecord, DocsService } from '@toeverything/infra';
import type { DocsService } from '@toeverything/infra';
import { import {
effect, effect,
Entity, Entity,
@@ -12,8 +11,8 @@ import { EMPTY, map, mergeMap, of, switchMap } from 'rxjs';
import type { DocsSearchService } from '../../docs-search'; import type { DocsSearchService } from '../../docs-search';
import { resolveLinkToDoc } from '../../navigation'; import { resolveLinkToDoc } from '../../navigation';
import type { WorkspacePropertiesAdapter } from '../../properties';
import type { QuickSearchSession } from '../providers/quick-search-provider'; import type { QuickSearchSession } from '../providers/quick-search-provider';
import type { DocDisplayMetaService } from '../services/doc-display-meta';
import type { QuickSearchItem } from '../types/item'; import type { QuickSearchItem } from '../types/item';
interface DocsPayload { interface DocsPayload {
@@ -30,7 +29,7 @@ export class DocsQuickSearchSession
constructor( constructor(
private readonly docsSearchService: DocsSearchService, private readonly docsSearchService: DocsSearchService,
private readonly docsService: DocsService, private readonly docsService: DocsService,
private readonly propertiesAdapter: WorkspacePropertiesAdapter private readonly docDisplayMetaService: DocDisplayMetaService
) { ) {
super(); super();
} }
@@ -56,62 +55,40 @@ export class DocsQuickSearchSession
if (!query) { if (!query) {
out = of([] as QuickSearchItem<'docs', DocsPayload>[]); out = of([] as QuickSearchItem<'docs', DocsPayload>[]);
} else { } else {
const maybeLink = resolveLinkToDoc(query); out = this.docsSearchService.search$(query).pipe(
const docRecord = maybeLink map(docs => {
? this.docsService.list.doc$(maybeLink.docId).value const resolvedDoc = resolveLinkToDoc(query);
: null; if (
resolvedDoc &&
if (docRecord) { !docs.some(doc => doc.docId === resolvedDoc.docId)
const docMode = docRecord?.mode$.value; ) {
const icon = this.propertiesAdapter.getJournalPageDateString( return [
docRecord.id {
) /* is journal */ docId: resolvedDoc.docId,
? TodayIcon score: 100,
: docMode === 'edgeless' blockId: resolvedDoc.blockId,
? EdgelessIcon blockContent: '',
: PageIcon;
out = of([
{
id: 'doc:' + docRecord.id,
source: 'docs',
group: {
id: 'docs',
label: {
key: 'com.affine.quicksearch.group.searchfor',
options: { query: truncate(query) },
}, },
score: 5, ...docs,
}, ];
label: { } else {
title: docRecord.title$.value || { key: 'Untitled' }, return docs;
}, }
score: 100, }),
icon, map(docs =>
timestamp: docRecord.meta$.value.updatedDate, docs
payload: { .map(doc => {
docId: docRecord.id,
},
},
] as QuickSearchItem<'docs', DocsPayload>[]);
} else {
out = this.docsSearchService.search$(query).pipe(
map(docs =>
docs.map(doc => {
const docRecord = this.docsService.list.doc$(doc.docId).value; const docRecord = this.docsService.list.doc$(doc.docId).value;
const docMode = docRecord?.mode$.value; return [doc, docRecord] as const;
const updatedTime = docRecord?.meta$.value.updatedDate; })
.filter(
const icon = this.propertiesAdapter.getJournalPageDateString( (props): props is [(typeof props)[0], DocRecord] => !!props[1]
doc.docId )
) /* is journal */ .map(([doc, docRecord]) => {
? TodayIcon const { title, icon, updatedDate } =
: docMode === 'edgeless' this.docDisplayMetaService.getDocDisplayMeta(docRecord);
? EdgelessIcon
: PageIcon;
return { return {
id: 'doc:' + doc.docId, id: 'doc:' + docRecord.id,
source: 'docs', source: 'docs',
group: { group: {
id: 'docs', id: 'docs',
@@ -122,18 +99,17 @@ export class DocsQuickSearchSession
score: 5, score: 5,
}, },
label: { label: {
title: doc.title || { key: 'Untitled' }, title: title,
subTitle: doc.blockContent, subTitle: doc.blockContent,
}, },
score: doc.score, score: doc.score,
icon, icon,
timestamp: updatedTime, timestamp: updatedDate,
payload: doc, payload: doc,
} as QuickSearchItem<'docs', DocsPayload>; } as QuickSearchItem<'docs', DocsPayload>;
}) })
) )
); );
}
} }
return out.pipe( return out.pipe(
mergeMap((items: QuickSearchItem<'docs', DocsPayload>[]) => { mergeMap((items: QuickSearchItem<'docs', DocsPayload>[]) => {

View File

@@ -1,8 +1,7 @@
import { EdgelessIcon, PageIcon, TodayIcon } from '@blocksuite/icons/rc';
import { Entity, LiveData } from '@toeverything/infra'; import { Entity, LiveData } from '@toeverything/infra';
import type { WorkspacePropertiesAdapter } from '../../properties';
import type { QuickSearchSession } from '../providers/quick-search-provider'; import type { QuickSearchSession } from '../providers/quick-search-provider';
import type { DocDisplayMetaService } from '../services/doc-display-meta';
import type { RecentDocsService } from '../services/recent-pages'; import type { RecentDocsService } from '../services/recent-pages';
import type { QuickSearchGroup } from '../types/group'; import type { QuickSearchGroup } from '../types/group';
import type { QuickSearchItem } from '../types/item'; import type { QuickSearchItem } from '../types/item';
@@ -21,7 +20,7 @@ export class RecentDocsQuickSearchSession
{ {
constructor( constructor(
private readonly recentDocsService: RecentDocsService, private readonly recentDocsService: RecentDocsService,
private readonly propertiesAdapter: WorkspacePropertiesAdapter private readonly docDisplayMetaService: DocDisplayMetaService
) { ) {
super(); super();
} }
@@ -40,20 +39,15 @@ export class RecentDocsQuickSearchSession
return docRecords.map<QuickSearchItem<'recent-doc', { docId: string }>>( return docRecords.map<QuickSearchItem<'recent-doc', { docId: string }>>(
docRecord => { docRecord => {
const icon = this.propertiesAdapter.getJournalPageDateString( const { title, icon } =
docRecord.id this.docDisplayMetaService.getDocDisplayMeta(docRecord);
) /* is journal */
? TodayIcon
: docRecord.mode$.value === 'edgeless'
? EdgelessIcon
: PageIcon;
return { return {
id: 'recent-doc:' + docRecord.id, id: 'recent-doc:' + docRecord.id,
source: 'recent-doc', source: 'recent-doc',
group: group, group: group,
label: { label: {
title: docRecord.meta$.value.title || { key: 'Untitled' }, title: title,
}, },
score: 0, score: 0,
icon, icon,

View File

@@ -17,6 +17,7 @@ import { CreationQuickSearchSession } from './impls/creation';
import { DocsQuickSearchSession } from './impls/docs'; import { DocsQuickSearchSession } from './impls/docs';
import { RecentDocsQuickSearchSession } from './impls/recent-docs'; import { RecentDocsQuickSearchSession } from './impls/recent-docs';
import { CMDKQuickSearchService } from './services/cmdk'; import { CMDKQuickSearchService } from './services/cmdk';
import { DocDisplayMetaService } from './services/doc-display-meta';
import { QuickSearchService } from './services/quick-search'; import { QuickSearchService } from './services/quick-search';
import { RecentDocsService } from './services/recent-pages'; import { RecentDocsService } from './services/recent-pages';
@@ -40,17 +41,18 @@ export function configureQuickSearchModule(framework: Framework) {
DocsService, DocsService,
]) ])
.service(RecentDocsService, [WorkspaceLocalState, DocsService]) .service(RecentDocsService, [WorkspaceLocalState, DocsService])
.service(DocDisplayMetaService, [WorkspacePropertiesAdapter])
.entity(QuickSearch) .entity(QuickSearch)
.entity(CommandsQuickSearchSession, [GlobalContextService]) .entity(CommandsQuickSearchSession, [GlobalContextService])
.entity(DocsQuickSearchSession, [ .entity(DocsQuickSearchSession, [
DocsSearchService, DocsSearchService,
DocsService, DocsService,
WorkspacePropertiesAdapter, DocDisplayMetaService,
]) ])
.entity(CreationQuickSearchSession) .entity(CreationQuickSearchSession)
.entity(CollectionsQuickSearchSession, [CollectionService]) .entity(CollectionsQuickSearchSession, [CollectionService])
.entity(RecentDocsQuickSearchSession, [ .entity(RecentDocsQuickSearchSession, [
RecentDocsService, RecentDocsService,
WorkspacePropertiesAdapter, DocDisplayMetaService,
]); ]);
} }

View File

@@ -0,0 +1,36 @@
import { i18nTime } from '@affine/i18n';
import { EdgelessIcon, PageIcon, TodayIcon } from '@blocksuite/icons/rc';
import type { DocRecord } from '@toeverything/infra';
import { Service } from '@toeverything/infra';
import type { WorkspacePropertiesAdapter } from '../../properties';
export class DocDisplayMetaService extends Service {
constructor(private readonly propertiesAdapter: WorkspacePropertiesAdapter) {
super();
}
getDocDisplayMeta(docRecord: DocRecord) {
const journalDateString = this.propertiesAdapter.getJournalPageDateString(
docRecord.id
);
const icon = journalDateString
? TodayIcon
: docRecord.mode$.value === 'edgeless'
? EdgelessIcon
: PageIcon;
const title = journalDateString
? i18nTime(journalDateString, { absolute: { accuracy: 'day' } })
: docRecord.meta$.value.title ||
({
key: 'Untitled',
} as const);
return {
title: title,
icon: icon,
updatedDate: docRecord.meta$.value.updatedDate,
};
}
}

View File

@@ -507,3 +507,73 @@ test('can use @ to open quick search to search for doc and insert into canvas',
await page.locator('affine-embed-linked-doc-block').dblclick({ force: true }); await page.locator('affine-embed-linked-doc-block').dblclick({ force: true });
await expect(page.getByTestId('peek-view-modal')).toBeVisible(); await expect(page.getByTestId('peek-view-modal')).toBeVisible();
}); });
test('can paste a doc link to create link reference', async ({ page }) => {
await openHomePage(page);
await waitForEditorLoad(page);
const url = page.url();
await clickNewPageButton(page);
// goto main content
await page.keyboard.press('Enter');
// paste the url
await page.evaluate(
async ([url]) => {
const clipData = {
'text/plain': url,
};
const e = new ClipboardEvent('paste', {
clipboardData: new DataTransfer(),
});
Object.defineProperty(e, 'target', {
writable: false,
value: document,
});
Object.entries(clipData).forEach(([key, value]) => {
e.clipboardData?.setData(key, value);
});
document.dispatchEvent(e);
},
[url]
);
// check the link reference
await page.waitForTimeout(500);
await expect(
page.locator('affine-reference:has-text("Write, Draw, Plan all at Once.")')
).toBeVisible();
// can ctrl-z to revert to normal link
await page.keyboard.press('ControlOrMeta+z');
// check the normal link
await page.waitForTimeout(500);
await expect(page.locator(`affine-link:has-text("${url}")`)).toBeVisible();
});
test('can use slash menu to insert a newly created doc card', async ({
page,
}) => {
await openHomePage(page);
await clickNewPageButton(page);
// goto main content
await page.keyboard.press('Enter');
// open slash menu
await page.keyboard.type('/linkedoc', {
delay: 50,
});
await page.keyboard.press('Enter');
await expect(page.getByTestId('cmdk-quick-search')).toBeVisible();
const testTitle = 'test title';
await page.locator('[cmdk-input]').fill(testTitle);
await page.keyboard.press('Enter');
await expect(page.locator('affine-embed-linked-doc-block')).toBeVisible();
await expect(
page.locator('.affine-embed-linked-doc-content-title')
).toContainText(testTitle);
});