mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-11 22:18:54 +08:00
chore: merge blocksuite source code (#9213)
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
import {
|
||||
ImportIcon,
|
||||
LinkedDocIcon,
|
||||
LinkedEdgelessIcon,
|
||||
NewDocIcon,
|
||||
} from '@blocksuite/affine-components/icons';
|
||||
import {
|
||||
type AffineInlineEditor,
|
||||
insertLinkedNode,
|
||||
} from '@blocksuite/affine-components/rich-text';
|
||||
import { toast } from '@blocksuite/affine-components/toast';
|
||||
import {
|
||||
DocModeProvider,
|
||||
TelemetryProvider,
|
||||
} from '@blocksuite/affine-shared/services';
|
||||
import {
|
||||
createDefaultDoc,
|
||||
isFuzzyMatch,
|
||||
type Signal,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import type { BlockStdScope, EditorHost } from '@blocksuite/block-std';
|
||||
import type { InlineRange } from '@blocksuite/inline';
|
||||
import type { TemplateResult } from 'lit';
|
||||
|
||||
import { showImportModal } from './import-doc/index.js';
|
||||
|
||||
export interface LinkedWidgetConfig {
|
||||
/**
|
||||
* The first item of the trigger keys will be the primary key
|
||||
* e.g. @, [[
|
||||
*/
|
||||
triggerKeys: [string, ...string[]];
|
||||
/**
|
||||
* Convert trigger key to primary key (the first item of the trigger keys)
|
||||
* [[ -> @
|
||||
*/
|
||||
convertTriggerKey: boolean;
|
||||
ignoreBlockTypes: (keyof BlockSuite.BlockModels)[];
|
||||
getMenus: (
|
||||
query: string,
|
||||
abort: () => void,
|
||||
editorHost: EditorHost,
|
||||
inlineEditor: AffineInlineEditor,
|
||||
abortSignal: AbortSignal
|
||||
) => Promise<LinkedMenuGroup[]> | LinkedMenuGroup[];
|
||||
|
||||
mobile: {
|
||||
useScreenHeight?: boolean;
|
||||
/**
|
||||
* The linked doc menu widget will scroll the container to make sure the input cursor is visible in viewport.
|
||||
* It accepts a selector string, HTMLElement or Window
|
||||
*
|
||||
* @default getViewportElement(editorHost) this is the scrollable container in playground
|
||||
*/
|
||||
scrollContainer?: string | HTMLElement | Window;
|
||||
/**
|
||||
* The offset between the top of viewport and the input cursor
|
||||
*
|
||||
* @default 46 The height of header in playground
|
||||
*/
|
||||
scrollTopOffset?: number | (() => number);
|
||||
};
|
||||
}
|
||||
|
||||
export type LinkedMenuItem = {
|
||||
key: string;
|
||||
name: string | TemplateResult<1>;
|
||||
icon: TemplateResult<1>;
|
||||
suffix?: string | TemplateResult<1>;
|
||||
// disabled?: boolean;
|
||||
action: () => Promise<void> | void;
|
||||
};
|
||||
|
||||
export type LinkedMenuGroup = {
|
||||
name: string;
|
||||
items: LinkedMenuItem[] | Signal<LinkedMenuItem[]>;
|
||||
styles?: string;
|
||||
// maximum quantity displayed by default
|
||||
maxDisplay?: number;
|
||||
// copywriting when display quantity exceeds
|
||||
overflowText?: string;
|
||||
};
|
||||
|
||||
export type LinkedDocContext = {
|
||||
std: BlockStdScope;
|
||||
inlineEditor: AffineInlineEditor;
|
||||
startRange: InlineRange;
|
||||
triggerKey: string;
|
||||
config: LinkedWidgetConfig;
|
||||
close: () => void;
|
||||
};
|
||||
|
||||
const DEFAULT_DOC_NAME = 'Untitled';
|
||||
const DISPLAY_NAME_LENGTH = 8;
|
||||
|
||||
export function createLinkedDocMenuGroup(
|
||||
query: string,
|
||||
abort: () => void,
|
||||
editorHost: EditorHost,
|
||||
inlineEditor: AffineInlineEditor
|
||||
) {
|
||||
const doc = editorHost.doc;
|
||||
const { docMetas } = doc.collection.meta;
|
||||
const filteredDocList = docMetas
|
||||
.filter(({ id }) => id !== doc.id)
|
||||
.filter(({ title }) => isFuzzyMatch(title, query));
|
||||
const MAX_DOCS = 6;
|
||||
|
||||
return {
|
||||
name: 'Link to Doc',
|
||||
items: filteredDocList.map(doc => ({
|
||||
key: doc.id,
|
||||
name: doc.title || DEFAULT_DOC_NAME,
|
||||
icon:
|
||||
editorHost.std.get(DocModeProvider).getPrimaryMode(doc.id) ===
|
||||
'edgeless'
|
||||
? LinkedEdgelessIcon
|
||||
: LinkedDocIcon,
|
||||
action: () => {
|
||||
abort();
|
||||
insertLinkedNode({
|
||||
inlineEditor,
|
||||
docId: doc.id,
|
||||
});
|
||||
editorHost.std
|
||||
.getOptional(TelemetryProvider)
|
||||
?.track('LinkedDocCreated', {
|
||||
control: 'linked doc',
|
||||
module: 'inline @',
|
||||
type: 'doc',
|
||||
other: 'existing doc',
|
||||
});
|
||||
},
|
||||
})),
|
||||
maxDisplay: MAX_DOCS,
|
||||
overflowText: `${filteredDocList.length - MAX_DOCS} more docs`,
|
||||
};
|
||||
}
|
||||
|
||||
export function createNewDocMenuGroup(
|
||||
query: string,
|
||||
abort: () => void,
|
||||
editorHost: EditorHost,
|
||||
inlineEditor: AffineInlineEditor
|
||||
): LinkedMenuGroup {
|
||||
const doc = editorHost.doc;
|
||||
const docName = query || DEFAULT_DOC_NAME;
|
||||
const displayDocName =
|
||||
docName.slice(0, DISPLAY_NAME_LENGTH) +
|
||||
(docName.length > DISPLAY_NAME_LENGTH ? '..' : '');
|
||||
|
||||
return {
|
||||
name: 'New Doc',
|
||||
items: [
|
||||
{
|
||||
key: 'create',
|
||||
name: `Create "${displayDocName}" doc`,
|
||||
icon: NewDocIcon,
|
||||
action: () => {
|
||||
abort();
|
||||
const docName = query;
|
||||
const newDoc = createDefaultDoc(doc.collection, {
|
||||
title: docName,
|
||||
});
|
||||
insertLinkedNode({
|
||||
inlineEditor,
|
||||
docId: newDoc.id,
|
||||
});
|
||||
const telemetryService =
|
||||
editorHost.std.getOptional(TelemetryProvider);
|
||||
telemetryService?.track('LinkedDocCreated', {
|
||||
control: 'new doc',
|
||||
module: 'inline @',
|
||||
type: 'doc',
|
||||
other: 'new doc',
|
||||
});
|
||||
telemetryService?.track('DocCreated', {
|
||||
control: 'new doc',
|
||||
module: 'inline @',
|
||||
type: 'doc',
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'import',
|
||||
name: 'Import',
|
||||
icon: ImportIcon,
|
||||
action: () => {
|
||||
abort();
|
||||
const onSuccess = (
|
||||
docIds: string[],
|
||||
options: {
|
||||
importedCount: number;
|
||||
}
|
||||
) => {
|
||||
toast(
|
||||
editorHost,
|
||||
`Successfully imported ${options.importedCount} Doc${options.importedCount > 1 ? 's' : ''}.`
|
||||
);
|
||||
for (const docId of docIds) {
|
||||
insertLinkedNode({
|
||||
inlineEditor,
|
||||
docId,
|
||||
});
|
||||
}
|
||||
};
|
||||
const onFail = (message: string) => {
|
||||
toast(editorHost, message);
|
||||
};
|
||||
showImportModal({
|
||||
collection: doc.collection,
|
||||
onSuccess,
|
||||
onFail,
|
||||
});
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function getMenus(
|
||||
query: string,
|
||||
abort: () => void,
|
||||
editorHost: EditorHost,
|
||||
inlineEditor: AffineInlineEditor
|
||||
): Promise<LinkedMenuGroup[]> {
|
||||
return Promise.resolve([
|
||||
createLinkedDocMenuGroup(query, abort, editorHost, inlineEditor),
|
||||
createNewDocMenuGroup(query, abort, editorHost, inlineEditor),
|
||||
]);
|
||||
}
|
||||
|
||||
export const LinkedWidgetUtils = {
|
||||
createLinkedDocMenuGroup,
|
||||
createNewDocMenuGroup,
|
||||
insertLinkedNode,
|
||||
};
|
||||
Reference in New Issue
Block a user