refactor(editor): move menu context to components (#9302)

This commit is contained in:
Saul-Mirone
2024-12-25 07:34:04 +00:00
parent 62b930f336
commit 16c59d96d9
52 changed files with 235 additions and 586 deletions

View File

@@ -1,12 +1,12 @@
import type { ExtensionType } from '@blocksuite/block-std';
import { EmbedFigmaBlockSpec } from './embed-figma-block/index.js';
import { EmbedGithubBlockSpec } from './embed-github-block/index.js';
import { EmbedHtmlBlockSpec } from './embed-html-block/index.js';
import { EmbedLinkedDocBlockSpec } from './embed-linked-doc-block/index.js';
import { EmbedLoomBlockSpec } from './embed-loom-block/index.js';
import { EmbedSyncedDocBlockSpec } from './embed-synced-doc-block/index.js';
import { EmbedYoutubeBlockSpec } from './embed-youtube-block/index.js';
import { EmbedFigmaBlockSpec } from './embed-figma-block';
import { EmbedGithubBlockSpec } from './embed-github-block';
import { EmbedHtmlBlockSpec } from './embed-html-block';
import { EmbedLinkedDocBlockSpec } from './embed-linked-doc-block';
import { EmbedLoomBlockSpec } from './embed-loom-block';
import { EmbedSyncedDocBlockSpec } from './embed-synced-doc-block';
import { EmbedYoutubeBlockSpec } from './embed-youtube-block';
export const EmbedExtensions: ExtensionType[] = [
EmbedFigmaBlockSpec,
@@ -18,22 +18,23 @@ export const EmbedExtensions: ExtensionType[] = [
EmbedSyncedDocBlockSpec,
].flat();
export { createEmbedBlockHtmlAdapterMatcher } from './common/adapters/html.js';
export { createEmbedBlockMarkdownAdapterMatcher } from './common/adapters/markdown.js';
export { createEmbedBlockPlainTextAdapterMatcher } from './common/adapters/plain-text.js';
export { generateDocUrl } from './common/adapters/utils.js';
export { EmbedBlockComponent } from './common/embed-block-element.js';
export { createEmbedBlockHtmlAdapterMatcher } from './common/adapters/html';
export { createEmbedBlockMarkdownAdapterMatcher } from './common/adapters/markdown';
export { createEmbedBlockPlainTextAdapterMatcher } from './common/adapters/plain-text';
export { generateDocUrl } from './common/adapters/utils';
export { EmbedBlockComponent } from './common/embed-block-element';
export { insertEmbedCard } from './common/insert-embed-card.js';
export {
LinkPreviewer,
type LinkPreviewResponseData,
} from './common/link-previewer.js';
export { getDocContentWithMaxLength } from './common/render-linked-doc.js';
export { toEdgelessEmbedBlock } from './common/to-edgeless-embed-block.js';
export * from './embed-figma-block/index.js';
export * from './embed-github-block/index.js';
export * from './embed-html-block/index.js';
export * from './embed-linked-doc-block/index.js';
export * from './embed-loom-block/index.js';
export * from './embed-synced-doc-block/index.js';
export * from './embed-youtube-block/index.js';
export { getDocContentWithMaxLength } from './common/render-linked-doc';
export { toEdgelessEmbedBlock } from './common/to-edgeless-embed-block';
export * from './common/utils';
export * from './embed-figma-block';
export * from './embed-github-block';
export * from './embed-html-block';
export * from './embed-linked-doc-block';
export * from './embed-loom-block';
export * from './embed-synced-doc-block';
export * from './embed-youtube-block';

View File

@@ -86,3 +86,26 @@ export function selectTextModel(
}),
]);
}
export async function onModelTextUpdated(
editorHost: EditorHost,
model: BlockModel,
callback?: (text: RichText) => void
) {
const richText = await asyncGetRichText(editorHost, model.id);
if (!richText) {
console.error('RichText is not ready yet.');
return;
}
await richText.updateComplete;
const inlineEditor = richText.inlineEditor;
if (!inlineEditor) {
console.error('Inline editor is not ready yet.');
return;
}
inlineEditor.slots.renderComplete.once(() => {
if (callback) {
callback(richText);
}
});
}

View File

@@ -5,6 +5,7 @@ export {
focusTextModel,
getInlineEditorByModel,
getRichTextByModel,
onModelTextUpdated,
selectTextModel,
} from './dom.js';
export * from './effects.js';

View File

@@ -14,6 +14,7 @@ export {
EditorMenuButton,
EditorMenuContent,
} from './menu-button.js';
export { MenuContext } from './menu-context.js';
export { EditorToolbarSeparator } from './separator.js';
export { darkToolbarStyles, lightToolbarStyles } from './styles.js';
export { EditorToolbar } from './toolbar.js';
@@ -23,9 +24,11 @@ export type {
FatMenuItems,
MenuItem,
MenuItemGroup,
ToolbarMoreMenuConfig,
} from './types.js';
export {
cloneGroups,
getMoreMenuConfig,
groupsToActions,
renderActions,
renderGroups,

View File

@@ -0,0 +1,30 @@
import type { BlockStdScope, EditorHost } from '@blocksuite/block-std';
import type { GfxModel } from '@blocksuite/block-std/gfx';
import type { BlockModel, Doc } from '@blocksuite/store';
export abstract class MenuContext {
abstract get doc(): Doc;
get firstElement(): GfxModel | null {
return null;
}
abstract get host(): EditorHost;
abstract get selectedBlockModels(): BlockModel[];
abstract get std(): BlockStdScope;
// Sometimes we need to close the menu.
close() {}
isElement() {
return false;
}
abstract isEmpty(): boolean;
abstract isMultiple(): boolean;
abstract isSingle(): boolean;
}

View File

@@ -1,5 +1,7 @@
import type { nothing, TemplateResult } from 'lit';
import type { MenuContext } from './menu-context.js';
export type MenuItemPart = {
action: () => void;
disabled?: boolean;
@@ -29,3 +31,9 @@ export type MenuItemGroup<T> = {
// Group Actions
export type FatMenuItems = (MenuItem | typeof nothing)[][];
export interface ToolbarMoreMenuConfig {
configure: <T extends MenuContext>(
groups: MenuItemGroup<T>[]
) => MenuItemGroup<T>[];
}

View File

@@ -1,9 +1,16 @@
import type { BlockStdScope } from '@blocksuite/block-std';
import { html, nothing } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
import { join } from 'lit/directives/join.js';
import { repeat } from 'lit/directives/repeat.js';
import type { FatMenuItems, MenuItem, MenuItemGroup } from './types.js';
import type { MenuContext } from './menu-context.js';
import type {
FatMenuItems,
MenuItem,
MenuItemGroup,
ToolbarMoreMenuConfig,
} from './types.js';
export function groupsToActions<T>(
groups: MenuItemGroup<T>[],
@@ -101,3 +108,14 @@ export function renderGroups<T>(groups: MenuItemGroup<T>[], context: T) {
export function renderToolbarSeparator() {
return html`<editor-toolbar-separator></editor-toolbar-separator>`;
}
export function getMoreMenuConfig(std: BlockStdScope): ToolbarMoreMenuConfig {
return {
configure: <T extends MenuContext>(groups: MenuItemGroup<T>[]) => groups,
...(
std.getConfig('affine:page' as BlockSuite.ConfigKeys) as null | {
toolbarMoreMenu: Partial<ToolbarMoreMenuConfig>;
}
)?.toolbarMoreMenu,
};
}

View File

@@ -4,3 +4,5 @@ export * from './doc.js';
export * from './get-content-block.js';
export * from './getter.js';
export * from './list.js';
export * from './merge-to-code-model.js';
export * from './transform-model.js';

View File

@@ -0,0 +1,33 @@
import type { BlockModel } from '@blocksuite/store';
import { Text } from '@blocksuite/store';
export function mergeToCodeModel(models: BlockModel[]) {
if (models.length === 0) {
return null;
}
const doc = models[0].doc;
const parent = doc.getParent(models[0]);
if (!parent) {
return null;
}
const index = parent.children.indexOf(models[0]);
const text = models
.map(model => {
if (model.text instanceof Text) {
return model.text.toString();
}
return null;
})
.filter(Boolean)
.join('\n');
models.forEach(model => doc.deleteBlock(model));
const id = doc.addBlock(
'affine:code',
{ text: new Text(text) },
parent,
index
);
return id;
}

View File

@@ -0,0 +1,31 @@
import type { BlockModel, Doc, Text } from '@blocksuite/store';
export function transformModel(
model: BlockModel,
flavour: BlockSuite.Flavour,
props?: Parameters<Doc['addBlock']>[1]
) {
const doc = model.doc;
const parent = doc.getParent(model);
if (!parent) {
return null;
}
const blockProps: {
type?: string;
text?: Text;
children?: BlockModel[];
} = {
text: model?.text?.clone(), // should clone before `deleteBlock`
children: model.children,
...props,
};
const index = parent.children.indexOf(model);
// Sometimes the new block can not be added due to some reason, e.g. invalid schema check.
// So we need to try to add the new block first, and if it fails, we will not delete the old block.
const id = doc.addBlock(flavour, blockProps, parent, index);
doc.deleteBlock(model, {
deleteChildren: false,
});
return id;
}