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
No known key found for this signature in database
GPG Key ID: 23F23D9E8B3971ED
6 changed files with 185 additions and 89 deletions

View File

@ -29,11 +29,11 @@ import {
type RootService,
} from '@blocksuite/blocks';
import { LinkIcon } from '@blocksuite/icons/rc';
import type {
DocMode,
DocService,
import {
type DocMode,
type DocService,
DocsService,
FrameworkProvider,
type FrameworkProvider,
} from '@toeverything/infra';
import { type TemplateResult } from 'lit';
import { customElement } from 'lit/decorators.js';
@ -337,13 +337,27 @@ export function patchQuickSearchService(
if (!query) {
logger.error('No user input provided');
} else {
const searchedDoc = (
await framework.get(DocsSearchService).search(query)
).at(0);
if (searchedDoc) {
const resolvedDoc = resolveLinkToDoc(query);
if (resolvedDoc) {
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 {
@ -394,11 +408,15 @@ export function patchQuickSearchService(
result.source === 'creation' &&
result.id === 'creation:create-page'
) {
throw new Error('Not implemented');
// resolve({
// docId: 'new-doc',
// isNewDoc: true,
// });
const docsService = framework.get(DocsService);
const newDoc = docsService.createDoc({
mode: 'page',
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 { DocsService } from '@toeverything/infra';
import type { DocRecord, DocsService } from '@toeverything/infra';
import {
effect,
Entity,
@ -12,8 +11,8 @@ import { EMPTY, map, mergeMap, of, switchMap } from 'rxjs';
import type { DocsSearchService } from '../../docs-search';
import { resolveLinkToDoc } from '../../navigation';
import type { WorkspacePropertiesAdapter } from '../../properties';
import type { QuickSearchSession } from '../providers/quick-search-provider';
import type { DocDisplayMetaService } from '../services/doc-display-meta';
import type { QuickSearchItem } from '../types/item';
interface DocsPayload {
@ -30,7 +29,7 @@ export class DocsQuickSearchSession
constructor(
private readonly docsSearchService: DocsSearchService,
private readonly docsService: DocsService,
private readonly propertiesAdapter: WorkspacePropertiesAdapter
private readonly docDisplayMetaService: DocDisplayMetaService
) {
super();
}
@ -56,62 +55,40 @@ export class DocsQuickSearchSession
if (!query) {
out = of([] as QuickSearchItem<'docs', DocsPayload>[]);
} else {
const maybeLink = resolveLinkToDoc(query);
const docRecord = maybeLink
? this.docsService.list.doc$(maybeLink.docId).value
: null;
if (docRecord) {
const docMode = docRecord?.mode$.value;
const icon = this.propertiesAdapter.getJournalPageDateString(
docRecord.id
) /* is journal */
? TodayIcon
: docMode === 'edgeless'
? EdgelessIcon
: PageIcon;
out = of([
{
id: 'doc:' + docRecord.id,
source: 'docs',
group: {
id: 'docs',
label: {
key: 'com.affine.quicksearch.group.searchfor',
options: { query: truncate(query) },
out = this.docsSearchService.search$(query).pipe(
map(docs => {
const resolvedDoc = resolveLinkToDoc(query);
if (
resolvedDoc &&
!docs.some(doc => doc.docId === resolvedDoc.docId)
) {
return [
{
docId: resolvedDoc.docId,
score: 100,
blockId: resolvedDoc.blockId,
blockContent: '',
},
score: 5,
},
label: {
title: docRecord.title$.value || { key: 'Untitled' },
},
score: 100,
icon,
timestamp: docRecord.meta$.value.updatedDate,
payload: {
docId: docRecord.id,
},
},
] as QuickSearchItem<'docs', DocsPayload>[]);
} else {
out = this.docsSearchService.search$(query).pipe(
map(docs =>
docs.map(doc => {
...docs,
];
} else {
return docs;
}
}),
map(docs =>
docs
.map(doc => {
const docRecord = this.docsService.list.doc$(doc.docId).value;
const docMode = docRecord?.mode$.value;
const updatedTime = docRecord?.meta$.value.updatedDate;
const icon = this.propertiesAdapter.getJournalPageDateString(
doc.docId
) /* is journal */
? TodayIcon
: docMode === 'edgeless'
? EdgelessIcon
: PageIcon;
return [doc, docRecord] as const;
})
.filter(
(props): props is [(typeof props)[0], DocRecord] => !!props[1]
)
.map(([doc, docRecord]) => {
const { title, icon, updatedDate } =
this.docDisplayMetaService.getDocDisplayMeta(docRecord);
return {
id: 'doc:' + doc.docId,
id: 'doc:' + docRecord.id,
source: 'docs',
group: {
id: 'docs',
@ -122,18 +99,17 @@ export class DocsQuickSearchSession
score: 5,
},
label: {
title: doc.title || { key: 'Untitled' },
title: title,
subTitle: doc.blockContent,
},
score: doc.score,
icon,
timestamp: updatedTime,
timestamp: updatedDate,
payload: doc,
} as QuickSearchItem<'docs', DocsPayload>;
})
)
);
}
)
);
}
return out.pipe(
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 type { WorkspacePropertiesAdapter } from '../../properties';
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 { QuickSearchGroup } from '../types/group';
import type { QuickSearchItem } from '../types/item';
@ -21,7 +20,7 @@ export class RecentDocsQuickSearchSession
{
constructor(
private readonly recentDocsService: RecentDocsService,
private readonly propertiesAdapter: WorkspacePropertiesAdapter
private readonly docDisplayMetaService: DocDisplayMetaService
) {
super();
}
@ -40,20 +39,15 @@ export class RecentDocsQuickSearchSession
return docRecords.map<QuickSearchItem<'recent-doc', { docId: string }>>(
docRecord => {
const icon = this.propertiesAdapter.getJournalPageDateString(
docRecord.id
) /* is journal */
? TodayIcon
: docRecord.mode$.value === 'edgeless'
? EdgelessIcon
: PageIcon;
const { title, icon } =
this.docDisplayMetaService.getDocDisplayMeta(docRecord);
return {
id: 'recent-doc:' + docRecord.id,
source: 'recent-doc',
group: group,
label: {
title: docRecord.meta$.value.title || { key: 'Untitled' },
title: title,
},
score: 0,
icon,

View File

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