diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx b/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx index 43c027fc8a..690d1ec293 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/lit-adaper.tsx @@ -6,6 +6,7 @@ import { import { useJournalInfoHelper } from '@affine/core/hooks/use-journal'; import { PeekViewService } from '@affine/core/modules/peek-view'; import { WorkbenchService } from '@affine/core/modules/workbench'; +import type { DocMode } from '@blocksuite/blocks'; import { DocMetaTags, DocTitle, @@ -14,7 +15,6 @@ import { } from '@blocksuite/presets'; import type { Doc } from '@blocksuite/store'; import { - type DocMode, DocService, DocsService, useFramework, @@ -44,8 +44,8 @@ import { patchReferenceRenderer, type ReferenceReactRenderer, } from './specs/custom/spec-patchers'; -import { EdgelessModeSpecs } from './specs/edgeless'; -import { PageModeSpecs } from './specs/page'; +import { createEdgelessModeSpecs } from './specs/edgeless'; +import { createPageModeSpecs } from './specs/page'; import * as styles from './styles.css'; const adapted = { @@ -88,7 +88,11 @@ const usePatchSpecs = (page: Doc, shared: boolean, mode: DocMode) => { }; }, [page.collection]); - const specs = mode === 'page' ? PageModeSpecs : EdgelessModeSpecs; + const specs = useMemo(() => { + return mode === 'edgeless' + ? createEdgelessModeSpecs(framework) + : createPageModeSpecs(framework); + }, [mode, framework]); const confirmModal = useConfirmModal(); const patchedSpecs = useMemo(() => { diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/linked-widget.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/linked-widget.ts new file mode 100644 index 0000000000..764386b730 --- /dev/null +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/linked-widget.ts @@ -0,0 +1,119 @@ +import { WorkspacePropertiesAdapter } from '@affine/core/modules/properties'; +import { mixpanel } from '@affine/core/utils'; +import type { EditorHost } from '@blocksuite/block-std'; +import type { AffineInlineEditor } from '@blocksuite/blocks'; +import { LinkedWidgetUtils } from '@blocksuite/blocks'; +import { + LinkedEdgelessIcon, + LinkedPageIcon, + TodayIcon, +} from '@blocksuite/icons/lit'; +import type { DocMeta } from '@blocksuite/store'; +import { + DocsService, + type FrameworkProvider, + WorkspaceService, +} from '@toeverything/infra'; + +export function createLinkedWidgetConfig(framework: FrameworkProvider) { + return { + getMenus: ( + query: string, + abort: () => void, + editorHost: EditorHost, + inlineEditor: AffineInlineEditor + ) => { + const currentWorkspace = framework.get(WorkspaceService).workspace; + const rawMetas = currentWorkspace.docCollection.meta.docMetas; + const adapter = framework.get(WorkspacePropertiesAdapter); + const isJournal = (d: DocMeta) => + !!adapter.getJournalPageDateString(d.id); + + const docMetas = rawMetas + .filter(meta => { + if (isJournal(meta) && !meta.updatedDate) { + return false; + } + return !meta.trash; + }) + .filter(({ title }) => isFuzzyMatch(title, query)); + + // TODO need i18n if BlockSuite supported + const MAX_DOCS = 6; + const DEFAULT_DOC_NAME = 'Untitled'; + const docsService = framework.get(DocsService); + const isEdgeless = (d: DocMeta) => + docsService.list.getMode(d.id) === 'edgeless'; + return Promise.resolve([ + { + name: 'Link to Doc', + items: docMetas.map(doc => ({ + key: doc.id, + name: doc.title || DEFAULT_DOC_NAME, + icon: isJournal(doc) + ? TodayIcon + : isEdgeless(doc) + ? LinkedEdgelessIcon + : LinkedPageIcon, + action: () => { + abort(); + LinkedWidgetUtils.insertLinkedNode({ + inlineEditor, + docId: doc.id, + }); + mixpanel.track('LinkedDocCreated', { + control: 'linked doc', + module: 'inline @', + type: 'doc', + other: 'existing doc', + }); + }, + })), + maxDisplay: MAX_DOCS, + overflowText: `${docMetas.length - MAX_DOCS} more docs`, + }, + LinkedWidgetUtils.createNewDocMenuGroup( + query, + abort, + editorHost, + inlineEditor + ), + ]); + }, + }; +} + +/** + * 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, '\\$&'); +} diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/root-block.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/root-block.ts index 92b92e8437..db6c068ae9 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/root-block.ts +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/custom/root-block.ts @@ -3,13 +3,20 @@ import { AIPageRootBlockSpec, } from '@affine/core/blocksuite/presets/ai'; import { mixpanel } from '@affine/core/utils'; -import type { BlockSpec } from '@blocksuite/block-std'; -import type { RootService, TelemetryEventMap } from '@blocksuite/blocks'; +import type { + EdgelessRootBlockSpecType, + PageRootBlockSpecType, + RootService, + TelemetryEventMap, +} from '@blocksuite/blocks'; import { AffineCanvasTextFonts, EdgelessRootService, PageRootService, } from '@blocksuite/blocks'; +import { type FrameworkProvider } from '@toeverything/infra'; + +import { createLinkedWidgetConfig } from './linked-widget'; function customLoadFonts(service: RootService): void { if (runtimeConfig.isSelfHosted) { @@ -41,12 +48,26 @@ function withAffineRootService(Service: typeof RootService) { }; } -export const CustomPageRootBlockSpec: BlockSpec = { - ...AIPageRootBlockSpec, - service: withAffineRootService(PageRootService), -}; +export function createPageRootBlockSpec( + framework: FrameworkProvider +): PageRootBlockSpecType { + return { + ...AIPageRootBlockSpec, + service: withAffineRootService(PageRootService), + config: { + linkedWidget: createLinkedWidgetConfig(framework), + }, + }; +} -export const CustomEdgelessRootBlockSpec: BlockSpec = { - ...AIEdgelessRootBlockSpec, - service: withAffineRootService(EdgelessRootService), -}; +export function createEdgelessRootBlockSpec( + framework: FrameworkProvider +): EdgelessRootBlockSpecType { + return { + ...AIEdgelessRootBlockSpec, + service: withAffineRootService(EdgelessRootService), + config: { + linkedWidget: createLinkedWidgetConfig(framework), + }, + }; +} diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/edgeless.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/edgeless.ts index f8b160717d..3e1d52f757 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/edgeless.ts +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/edgeless.ts @@ -6,17 +6,22 @@ import { EdgelessTextBlockSpec, FrameBlockSpec, } from '@blocksuite/blocks'; +import type { FrameworkProvider } from '@toeverything/infra'; import { CommonBlockSpecs } from './common'; -import { CustomEdgelessRootBlockSpec } from './custom/root-block'; +import { createEdgelessRootBlockSpec } from './custom/root-block'; -export const EdgelessModeSpecs: BlockSpec[] = [ - ...CommonBlockSpecs, - EdgelessSurfaceBlockSpec, - EdgelessSurfaceRefBlockSpec, - FrameBlockSpec, - EdgelessTextBlockSpec, - EdgelessNoteBlockSpec, - // special - CustomEdgelessRootBlockSpec, -]; +export function createEdgelessModeSpecs( + framework: FrameworkProvider +): BlockSpec[] { + return [ + ...CommonBlockSpecs, + EdgelessSurfaceBlockSpec, + EdgelessSurfaceRefBlockSpec, + FrameBlockSpec, + EdgelessTextBlockSpec, + EdgelessNoteBlockSpec, + // special + createEdgelessRootBlockSpec(framework), + ]; +} diff --git a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/page.ts b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/page.ts index 9e5b3b8808..696cc1b3ee 100644 --- a/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/page.ts +++ b/packages/frontend/core/src/components/blocksuite/block-suite-editor/specs/page.ts @@ -3,14 +3,17 @@ import { PageSurfaceBlockSpec, PageSurfaceRefBlockSpec, } from '@blocksuite/blocks'; +import { type FrameworkProvider } from '@toeverything/infra'; import { CommonBlockSpecs } from './common'; -import { CustomPageRootBlockSpec } from './custom/root-block'; +import { createPageRootBlockSpec } from './custom/root-block'; -export const PageModeSpecs: BlockSpec[] = [ - ...CommonBlockSpecs, - PageSurfaceBlockSpec, - PageSurfaceRefBlockSpec, - // special - CustomPageRootBlockSpec, -]; +export function createPageModeSpecs(framework: FrameworkProvider): BlockSpec[] { + return [ + ...CommonBlockSpecs, + PageSurfaceBlockSpec, + PageSurfaceRefBlockSpec, + // special + createPageRootBlockSpec(framework), + ]; +}