refactor(editor): move menu context to components (#9302)
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ export {
|
||||
focusTextModel,
|
||||
getInlineEditorByModel,
|
||||
getRichTextByModel,
|
||||
onModelTextUpdated,
|
||||
selectTextModel,
|
||||
} from './dom.js';
|
||||
export * from './effects.js';
|
||||
|
||||
@@ -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,
|
||||
|
||||
30
blocksuite/affine/components/src/toolbar/menu-context.ts
Normal file
30
blocksuite/affine/components/src/toolbar/menu-context.ts
Normal 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;
|
||||
}
|
||||
@@ -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>[];
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
31
blocksuite/affine/shared/src/utils/model/transform-model.ts
Normal file
31
blocksuite/affine/shared/src/utils/model/transform-model.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user