mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-10-03 22:57:25 +03:00
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:
parent
61870c04d0
commit
cc7740d8d3
@ -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,
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -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>[]) => {
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
]);
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
@ -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);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user