mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-18 02:21:51 +08:00
feat: custom the items of linked menu by configuration (#7554)
Close issue [BS-719](https://linear.app/affine-design/issue/BS-719) and [BS-791](https://linear.app/affine-design/issue/BS-791). Related PR in [BlockSuite](https://github.com/toeverything/blocksuite/pull/7693). ### What Changed? - Support config in BlockSpec - AFFiNE’s Custom @Menu - ignore docs in trash - ignore unedited journal - customized journal icon  ### What's next? - getMenus returns an observable array - Add commands field to BlockSpec and encapsulated insertLinkedNode into a command
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
|||||||
import { useJournalInfoHelper } from '@affine/core/hooks/use-journal';
|
import { useJournalInfoHelper } from '@affine/core/hooks/use-journal';
|
||||||
import { PeekViewService } from '@affine/core/modules/peek-view';
|
import { PeekViewService } from '@affine/core/modules/peek-view';
|
||||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||||
|
import type { DocMode } from '@blocksuite/blocks';
|
||||||
import {
|
import {
|
||||||
DocMetaTags,
|
DocMetaTags,
|
||||||
DocTitle,
|
DocTitle,
|
||||||
@@ -14,7 +15,6 @@ import {
|
|||||||
} from '@blocksuite/presets';
|
} from '@blocksuite/presets';
|
||||||
import type { Doc } from '@blocksuite/store';
|
import type { Doc } from '@blocksuite/store';
|
||||||
import {
|
import {
|
||||||
type DocMode,
|
|
||||||
DocService,
|
DocService,
|
||||||
DocsService,
|
DocsService,
|
||||||
useFramework,
|
useFramework,
|
||||||
@@ -44,8 +44,8 @@ import {
|
|||||||
patchReferenceRenderer,
|
patchReferenceRenderer,
|
||||||
type ReferenceReactRenderer,
|
type ReferenceReactRenderer,
|
||||||
} from './specs/custom/spec-patchers';
|
} from './specs/custom/spec-patchers';
|
||||||
import { EdgelessModeSpecs } from './specs/edgeless';
|
import { createEdgelessModeSpecs } from './specs/edgeless';
|
||||||
import { PageModeSpecs } from './specs/page';
|
import { createPageModeSpecs } from './specs/page';
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
const adapted = {
|
const adapted = {
|
||||||
@@ -88,7 +88,11 @@ const usePatchSpecs = (page: Doc, shared: boolean, mode: DocMode) => {
|
|||||||
};
|
};
|
||||||
}, [page.collection]);
|
}, [page.collection]);
|
||||||
|
|
||||||
const specs = mode === 'page' ? PageModeSpecs : EdgelessModeSpecs;
|
const specs = useMemo(() => {
|
||||||
|
return mode === 'edgeless'
|
||||||
|
? createEdgelessModeSpecs(framework)
|
||||||
|
: createPageModeSpecs(framework);
|
||||||
|
}, [mode, framework]);
|
||||||
|
|
||||||
const confirmModal = useConfirmModal();
|
const confirmModal = useConfirmModal();
|
||||||
const patchedSpecs = useMemo(() => {
|
const patchedSpecs = useMemo(() => {
|
||||||
|
|||||||
+119
@@ -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, '\\$&');
|
||||||
|
}
|
||||||
+31
-10
@@ -3,13 +3,20 @@ import {
|
|||||||
AIPageRootBlockSpec,
|
AIPageRootBlockSpec,
|
||||||
} from '@affine/core/blocksuite/presets/ai';
|
} from '@affine/core/blocksuite/presets/ai';
|
||||||
import { mixpanel } from '@affine/core/utils';
|
import { mixpanel } from '@affine/core/utils';
|
||||||
import type { BlockSpec } from '@blocksuite/block-std';
|
import type {
|
||||||
import type { RootService, TelemetryEventMap } from '@blocksuite/blocks';
|
EdgelessRootBlockSpecType,
|
||||||
|
PageRootBlockSpecType,
|
||||||
|
RootService,
|
||||||
|
TelemetryEventMap,
|
||||||
|
} from '@blocksuite/blocks';
|
||||||
import {
|
import {
|
||||||
AffineCanvasTextFonts,
|
AffineCanvasTextFonts,
|
||||||
EdgelessRootService,
|
EdgelessRootService,
|
||||||
PageRootService,
|
PageRootService,
|
||||||
} from '@blocksuite/blocks';
|
} from '@blocksuite/blocks';
|
||||||
|
import { type FrameworkProvider } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { createLinkedWidgetConfig } from './linked-widget';
|
||||||
|
|
||||||
function customLoadFonts(service: RootService): void {
|
function customLoadFonts(service: RootService): void {
|
||||||
if (runtimeConfig.isSelfHosted) {
|
if (runtimeConfig.isSelfHosted) {
|
||||||
@@ -41,12 +48,26 @@ function withAffineRootService(Service: typeof RootService) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CustomPageRootBlockSpec: BlockSpec = {
|
export function createPageRootBlockSpec(
|
||||||
...AIPageRootBlockSpec,
|
framework: FrameworkProvider
|
||||||
service: withAffineRootService(PageRootService),
|
): PageRootBlockSpecType {
|
||||||
};
|
return {
|
||||||
|
...AIPageRootBlockSpec,
|
||||||
|
service: withAffineRootService(PageRootService),
|
||||||
|
config: {
|
||||||
|
linkedWidget: createLinkedWidgetConfig(framework),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const CustomEdgelessRootBlockSpec: BlockSpec = {
|
export function createEdgelessRootBlockSpec(
|
||||||
...AIEdgelessRootBlockSpec,
|
framework: FrameworkProvider
|
||||||
service: withAffineRootService(EdgelessRootService),
|
): EdgelessRootBlockSpecType {
|
||||||
};
|
return {
|
||||||
|
...AIEdgelessRootBlockSpec,
|
||||||
|
service: withAffineRootService(EdgelessRootService),
|
||||||
|
config: {
|
||||||
|
linkedWidget: createLinkedWidgetConfig(framework),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
+16
-11
@@ -6,17 +6,22 @@ import {
|
|||||||
EdgelessTextBlockSpec,
|
EdgelessTextBlockSpec,
|
||||||
FrameBlockSpec,
|
FrameBlockSpec,
|
||||||
} from '@blocksuite/blocks';
|
} from '@blocksuite/blocks';
|
||||||
|
import type { FrameworkProvider } from '@toeverything/infra';
|
||||||
|
|
||||||
import { CommonBlockSpecs } from './common';
|
import { CommonBlockSpecs } from './common';
|
||||||
import { CustomEdgelessRootBlockSpec } from './custom/root-block';
|
import { createEdgelessRootBlockSpec } from './custom/root-block';
|
||||||
|
|
||||||
export const EdgelessModeSpecs: BlockSpec[] = [
|
export function createEdgelessModeSpecs(
|
||||||
...CommonBlockSpecs,
|
framework: FrameworkProvider
|
||||||
EdgelessSurfaceBlockSpec,
|
): BlockSpec[] {
|
||||||
EdgelessSurfaceRefBlockSpec,
|
return [
|
||||||
FrameBlockSpec,
|
...CommonBlockSpecs,
|
||||||
EdgelessTextBlockSpec,
|
EdgelessSurfaceBlockSpec,
|
||||||
EdgelessNoteBlockSpec,
|
EdgelessSurfaceRefBlockSpec,
|
||||||
// special
|
FrameBlockSpec,
|
||||||
CustomEdgelessRootBlockSpec,
|
EdgelessTextBlockSpec,
|
||||||
];
|
EdgelessNoteBlockSpec,
|
||||||
|
// special
|
||||||
|
createEdgelessRootBlockSpec(framework),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,14 +3,17 @@ import {
|
|||||||
PageSurfaceBlockSpec,
|
PageSurfaceBlockSpec,
|
||||||
PageSurfaceRefBlockSpec,
|
PageSurfaceRefBlockSpec,
|
||||||
} from '@blocksuite/blocks';
|
} from '@blocksuite/blocks';
|
||||||
|
import { type FrameworkProvider } from '@toeverything/infra';
|
||||||
|
|
||||||
import { CommonBlockSpecs } from './common';
|
import { CommonBlockSpecs } from './common';
|
||||||
import { CustomPageRootBlockSpec } from './custom/root-block';
|
import { createPageRootBlockSpec } from './custom/root-block';
|
||||||
|
|
||||||
export const PageModeSpecs: BlockSpec[] = [
|
export function createPageModeSpecs(framework: FrameworkProvider): BlockSpec[] {
|
||||||
...CommonBlockSpecs,
|
return [
|
||||||
PageSurfaceBlockSpec,
|
...CommonBlockSpecs,
|
||||||
PageSurfaceRefBlockSpec,
|
PageSurfaceBlockSpec,
|
||||||
// special
|
PageSurfaceRefBlockSpec,
|
||||||
CustomPageRootBlockSpec,
|
// special
|
||||||
];
|
createPageRootBlockSpec(framework),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user