提交 dc071b55 authored 作者: 王鹏飞's avatar 王鹏飞

refactor(admin): colocate editor menu events

上级 6712281f
...@@ -15,15 +15,18 @@ export const createFeatureRegistry = features => { ...@@ -15,15 +15,18 @@ export const createFeatureRegistry = features => {
const menus = features.flatMap(feature => feature.menus).sort(byOrder) const menus = features.flatMap(feature => feature.menus).sort(byOrder)
const nodeModules = features.flatMap(feature => feature.nodeModules).sort(byOrder) const nodeModules = features.flatMap(feature => feature.nodeModules).sort(byOrder)
const dialogEntries = features.flatMap(feature => feature.dialogs) const dialogEntries = features.flatMap(feature => feature.dialogs)
const events = features.flatMap(feature => feature.events)
assertUnique(menus.map(entry => entry.config.key), 'menu key') assertUnique(menus.map(entry => entry.config.key), 'menu key')
assertUnique(nodeModules.flatMap(entry => entry.types), 'node type') assertUnique(nodeModules.flatMap(entry => entry.types), 'node type')
assertUnique(dialogEntries.map(entry => entry.id), 'dialog id') assertUnique(dialogEntries.map(entry => entry.id), 'dialog id')
assertUnique(events.map(entry => entry.name), 'editor event')
return { return {
features: [...features], features: [...features],
menus, menus,
nodeModules, nodeModules,
events,
dialogs: new Map(dialogEntries.map(entry => [entry.id, entry.component])), dialogs: new Map(dialogEntries.map(entry => [entry.id, entry.component])),
} }
} }
...@@ -3,4 +3,5 @@ export const defineEditorFeature = ({ ...@@ -3,4 +3,5 @@ export const defineEditorFeature = ({
menus = [], menus = [],
nodeModules = [], nodeModules = [],
dialogs = [], dialogs = [],
}) => ({ id, menus, nodeModules, dialogs }) events = [],
}) => ({ id, menus, nodeModules, dialogs, events })
import { useEffect, useRef } from 'react'
export const useEditorFeatureEvents = ({ editor, registry, runtime }) => {
const runtimeRef = useRef(runtime)
runtimeRef.current = runtime
useEffect(() => {
if (!editor) return
const listeners = registry.events.map(({ name, handler }) => ({
name,
listener: payload => handler({ editor, payload, runtime: runtimeRef.current }),
}))
listeners.forEach(({ name, listener }) => editor.on(name, listener))
return () => listeners.forEach(({ name, listener }) => editor.off(name, listener))
}, [editor, registry])
}
...@@ -6,4 +6,5 @@ export default defineEditorFeature({ ...@@ -6,4 +6,5 @@ export default defineEditorFeature({
id: 'audio', id: 'audio',
menus: [{ order: 50, config: menu }], menus: [{ order: 50, config: menu }],
nodeModules: [{ order: 50, types: ['chapterAudio'], module: nodeModule }], nodeModules: [{ order: 50, types: ['chapterAudio'], module: nodeModule }],
events: [{ name: 'AudioMenuClick', handler: ({ runtime }) => runtime.openAudio() }],
}) })
...@@ -11,4 +11,8 @@ export default defineEditorFeature({ ...@@ -11,4 +11,8 @@ export default defineEditorFeature({
{ order: 10, types: ['chapterSection'], module: sectionNodeModule }, { order: 10, types: ['chapterSection'], module: sectionNodeModule },
{ order: 20, types: ['chapterHeader'], module: headerNodeModule }, { order: 20, types: ['chapterHeader'], module: headerNodeModule },
], ],
events: [
{ name: 'ChapterItemMenuClick', handler: ({ runtime }) => runtime.openSection() },
{ name: 'ChapterTitleClick', handler: ({ runtime }) => runtime.openTitle() },
],
}) })
...@@ -10,4 +10,5 @@ export default defineEditorFeature({ ...@@ -10,4 +10,5 @@ export default defineEditorFeature({
{ order: 120, types: ['chapterExpandRead'], module: nodeModule }, { order: 120, types: ['chapterExpandRead'], module: nodeModule },
{ order: 130, types: ['chapterExpandReadSimple'], module: simpleNodeModule }, { order: 130, types: ['chapterExpandReadSimple'], module: simpleNodeModule },
], ],
events: [{ name: 'ExpandReadClick', handler: ({ runtime }) => { runtime.captureSelectionFontSize(); runtime.openExpandRead() } }],
}) })
...@@ -6,4 +6,5 @@ export default defineEditorFeature({ ...@@ -6,4 +6,5 @@ export default defineEditorFeature({
id: 'formula', id: 'formula',
menus: [{ order: 60, config: menu }], menus: [{ order: 60, config: menu }],
nodeModules: [{ order: 80, types: ['chapterFormula'], module: nodeModule }], nodeModules: [{ order: 80, types: ['chapterFormula'], module: nodeModule }],
events: [{ name: 'FormulaMenuClick', handler: ({ runtime }) => runtime.openFormula() }],
}) })
...@@ -11,4 +11,8 @@ export default defineEditorFeature({ ...@@ -11,4 +11,8 @@ export default defineEditorFeature({
{ order: 60, types: ['chapterGallery'], module: nodeModule }, { order: 60, types: ['chapterGallery'], module: nodeModule },
{ order: 70, types: ['chapterGalleryInline'], module: inlineNodeModule }, { order: 70, types: ['chapterGalleryInline'], module: inlineNodeModule },
], ],
events: [
{ name: 'GalleryMenuClick', handler: ({ runtime }) => { runtime.captureSelectionFontSize(); runtime.openGallery({ isOnline: false }) } },
{ name: 'GalleryOnlineMenuClick', handler: ({ runtime }) => { runtime.captureSelectionFontSize(); runtime.openGallery({ isOnline: true }) } },
],
}) })
...@@ -2,9 +2,31 @@ import { defineEditorFeature } from '../../core/define-editor-feature' ...@@ -2,9 +2,31 @@ import { defineEditorFeature } from '../../core/define-editor-feature'
import editMenu from './edit-menu' import editMenu from './edit-menu'
import onlineMenu from './online-menu' import onlineMenu from './online-menu'
import nodeModule from './node' import nodeModule from './node'
import { SlateEditor, SlateElement } from '@wangeditor/editor'
const getSelectedImageInfo = editor => {
const entries = SlateEditor.nodes(editor, {
match: node => SlateElement.isElement(node) && node.type === 'paragraph',
universal: true,
})
const info = {}
for (const [node, path] of entries) {
node.children.forEach(item => {
if (item.type === 'image') info.image = item
})
info.node = node
info.path = path
}
return info
}
export default defineEditorFeature({ export default defineEditorFeature({
id: 'image', id: 'image',
menus: [{ order: 10, config: onlineMenu }, { order: 110, config: editMenu }], menus: [{ order: 10, config: onlineMenu }, { order: 110, config: editMenu }],
nodeModules: [{ order: 30, types: ['chapterImage'], module: nodeModule }], nodeModules: [{ order: 30, types: ['chapterImage'], module: nodeModule }],
events: [
{ name: 'ImageMenuClick', handler: ({ runtime }) => runtime.openImage({ isOnline: false }) },
{ name: 'ImageOnlineMenuClick', handler: ({ runtime }) => runtime.openImage({ isOnline: true }) },
{ name: 'ImageEditorClick', handler: ({ editor, runtime }) => runtime.openImage({ isOnline: false, imageInfo: getSelectedImageInfo(editor) }) },
],
}) })
...@@ -6,4 +6,5 @@ export default defineEditorFeature({ ...@@ -6,4 +6,5 @@ export default defineEditorFeature({
id: 'link', id: 'link',
menus: [{ order: 120, config: menu }], menus: [{ order: 120, config: menu }],
nodeModules: [{ order: 110, types: ['chapterLink'], module: nodeModule }], nodeModules: [{ order: 110, types: ['chapterLink'], module: nodeModule }],
events: [{ name: 'CustomerLinkClick', handler: ({ runtime }) => { runtime.captureSelectionFontSize(); runtime.openLink() } }],
}) })
...@@ -6,4 +6,5 @@ export default defineEditorFeature({ ...@@ -6,4 +6,5 @@ export default defineEditorFeature({
id: 'practice', id: 'practice',
menus: [{ order: 100, config: menu }], menus: [{ order: 100, config: menu }],
nodeModules: [{ order: 90, types: ['chapterPractice'], module: nodeModule }], nodeModules: [{ order: 90, types: ['chapterPractice'], module: nodeModule }],
events: [{ name: 'PracticeMenuClick', handler: ({ runtime }) => runtime.openPractice() }],
}) })
...@@ -6,4 +6,5 @@ export default defineEditorFeature({ ...@@ -6,4 +6,5 @@ export default defineEditorFeature({
id: 'tooltip', id: 'tooltip',
menus: [{ order: 70, config: menu }], menus: [{ order: 70, config: menu }],
nodeModules: [{ order: 100, types: ['chapterTooltip'], module: nodeModule }], nodeModules: [{ order: 100, types: ['chapterTooltip'], module: nodeModule }],
events: [{ name: 'TooltipMenuClick', handler: ({ runtime }) => { runtime.captureSelectionFontSize(); runtime.openTooltip() } }],
}) })
...@@ -6,4 +6,5 @@ export default defineEditorFeature({ ...@@ -6,4 +6,5 @@ export default defineEditorFeature({
id: 'video', id: 'video',
menus: [{ order: 40, config: menu }], menus: [{ order: 40, config: menu }],
nodeModules: [{ order: 40, types: ['chapterVideo'], module: nodeModule }], nodeModules: [{ order: 40, types: ['chapterVideo'], module: nodeModule }],
events: [{ name: 'VideoMenuClick', handler: ({ runtime }) => runtime.openVideo() }],
}) })
...@@ -13,8 +13,8 @@ import { registerEditorFeatures } from "../core/register-editor-features"; ...@@ -13,8 +13,8 @@ import { registerEditorFeatures } from "../core/register-editor-features";
import { EditorDialogHost } from "../core/EditorDialogHost"; import { EditorDialogHost } from "../core/EditorDialogHost";
import { editorFeatureRegistry } from "../features"; import { editorFeatureRegistry } from "../features";
import { SlateEditor, DomEditor, SlateElement, SlateTransforms } from "@wangeditor/editor"; import { DomEditor, SlateTransforms } from "@wangeditor/editor";
import { Editor, Toolbar } from "@wangeditor/editor-for-react"; import { Editor } from "@wangeditor/editor-for-react";
import { fontFamilyList, lineHeightList } from "../utils/setting"; import { fontFamilyList, lineHeightList } from "../utils/setting";
import "@wangeditor/editor/dist/css/style.css"; // 引入 css import "@wangeditor/editor/dist/css/style.css"; // 引入 css
import timesave from "@/assets/images/timesave.png"; import timesave from "@/assets/images/timesave.png";
...@@ -43,6 +43,7 @@ import { EditorHeader } from './EditorHeader' ...@@ -43,6 +43,7 @@ import { EditorHeader } from './EditorHeader'
import { EditorSidebar } from './EditorSidebar' import { EditorSidebar } from './EditorSidebar'
import { useEditorLifecycle } from './useEditorLifecycle' import { useEditorLifecycle } from './useEditorLifecycle'
import { useToolbarPresentation } from './useToolbarPresentation' import { useToolbarPresentation } from './useToolbarPresentation'
import { useEditorFeatureEvents } from '../core/useEditorFeatureEvents'
import "../index.less"; import "../index.less";
...@@ -341,143 +342,44 @@ const WangEditorCustomer = (props, ref) => { ...@@ -341,143 +342,44 @@ const WangEditorCustomer = (props, ref) => {
setContent(editor.getHtml()); setContent(editor.getHtml());
}; };
const featureRuntime = {
openImage: ({ isOnline, imageInfo }) => {
if (imageInfo) setImageInfo(imageInfo)
setIsOnline(isOnline)
setImageVisible(true)
},
openGallery: ({ isOnline }) => {
setIsOnline(isOnline)
setGalleryVisible(true)
},
openVideo: () => videoRef.current?.setVisible(true),
openAudio: () => audioRef.current?.setVisible(true),
openTitle: () => setTitleVisible(true),
openSection: () => setSectionVisible(true),
openPractice: () => setPracticeOpenVisible(true),
openFormula: () => formulaRef.current?.setVisible(true),
openTooltip: () => setTooltipVisible(true),
openLink: () => setLinkVisible(true),
openExpandRead: () => setExpandVisible(true),
captureSelectionFontSize: () => {
if (editor?.selection) listenNodeStyle(editor.selection.anchor.path)
},
}
useEditorFeatureEvents({ editor, registry: editorFeatureRegistry, runtime: featureRuntime })
// 及时销毁 editor ,重要! // 及时销毁 editor ,重要!
useEffect(() => { useEffect(() => {
if (editor) { if (!editor) return
// 图片上传 editor.addMark("fontSize", "18px")
editor.on("ImageMenuClick", () => { editor.addMark("fontFamily", "黑体")
console.log("ImageMenuClick", "----"); editor.addMark("lineHeight", 1.5)
setIsOnline(false); setContent(html)
setImageVisible(true);
});
// 在线图片
editor.on("ImageOnlineMenuClick", () => {
console.log("ImageOnlineMenuClick", "----");
setIsOnline(true);
setImageVisible(true);
});
// 画廊上传
editor.on("GalleryMenuClick", () => {
if (editor.selection) {
listenNodeStyle(editor.selection.anchor.path);
}
console.log("GalleryMenuClick", "----");
// galleryRef.current.setVisible(true);
setIsOnline(false);
setGalleryVisible(true);
});
// 在线画廊
editor.on("GalleryOnlineMenuClick", () => {
if (editor.selection) {
listenNodeStyle(editor.selection.anchor.path);
}
console.log("GalleryOnlineMenuClick", "----");
// galleryRef.current.setVisible(true);
setIsOnline(true);
setGalleryVisible(true);
});
// 视频上传
editor.on("VideoMenuClick", () => {
console.log("VideoMenuClick", "----");
videoRef.current.setVisible(true);
});
// 音频上传
editor.on("AudioMenuClick", () => {
console.log("AudioMenuClick", "----");
audioRef.current.setVisible(true);
});
// 章节
editor.on("ChapterItemMenuClick", () => {
console.log("ChapterItemMenuClick", "----");
setSectionVisible(true);
});
// 章头
editor.on("ChapterTitleClick", () => {
console.log("ChapterTitleClick", "----");
setTitleVisible(true);
});
// 交互练习
editor.on("PracticeMenuClick", () => {
console.log("PracticeMenuClick", "----");
setPracticeOpenVisible(true);
});
// 公式
editor.on("FormulaMenuClick", () => {
console.log("FormulaMenuClick", "----");
formulaRef.current.setVisible(true);
});
// 插入图片
editor.on("ImageEditorClick", () => {
console.log("ImageEditorClick", "----");
const nodeEntries = SlateEditor.nodes(editor, {
match: (node) => {
// JS syntax
if (SlateElement.isElement(node)) {
if (node.type === "paragraph") {
return true; // 匹配 paragraph
}
}
return false;
},
universal: true,
});
let info = {};
for (let nodeEntry of nodeEntries) {
const [node, path] = nodeEntry;
node.children.forEach((item) => {
if (item.type === "image") {
info.image = item;
}
});
info.node = node;
info.path = path;
}
setImageInfo(info);
setIsOnline(false);
setImageVisible(true);
});
// 气泡
editor.on("TooltipMenuClick", () => {
if (editor.selection) {
listenNodeStyle(editor.selection.anchor.path);
}
setTooltipVisible(true);
});
// 链接
editor.on("CustomerLinkClick", () => {
if (editor.selection) {
listenNodeStyle(editor.selection.anchor.path);
}
setLinkVisible(true);
});
// 扩展
editor.on("ExpandReadClick", () => {
if (editor.selection) {
listenNodeStyle(editor.selection.anchor.path);
}
setExpandVisible(true);
});
editor.addMark("fontSize", "18px");
editor.addMark("fontFamily", "黑体");
editor.addMark("lineHeight", 1.5);
setContent(html);
if (gData.length > 0) {
editor.enable();
editor.focus();
} else {
editor.disable();
}
}
return () => { return () => {
if (editor === null) return; editor.destroy()
editor.destroy(); setEditor(null)
setEditor(null); }
}; }, [editor])
}, [editor]);
useEditorLifecycle({ editor, gData, disabled }); useEditorLifecycle({ editor, gData, disabled });
useToolbarPresentation({ toolbarRef, active: !isView && tabKey === "text" }); useToolbarPresentation({ toolbarRef, active: !isView && tabKey === "text" });
......
...@@ -48,7 +48,8 @@ describe('createFeatureRegistry', () => { ...@@ -48,7 +48,8 @@ describe('createFeatureRegistry', () => {
id: 'feature-a', id: 'feature-a',
menus: [{ order: 10, config: menu('menu-a') }], menus: [{ order: 10, config: menu('menu-a') }],
nodeModules: [{ order: 10, types: ['node-a'], module: {} }], nodeModules: [{ order: 10, types: ['node-a'], module: {} }],
dialogs: [{ id: 'dialog-a', component: dialog }], dialogs: [{ id: 'dialog-a', component: dialog }],
events: [{ name: 'event-a', handler: () => {} }],
...overrides, ...overrides,
}) })
...@@ -56,6 +57,7 @@ describe('createFeatureRegistry', () => { ...@@ -56,6 +57,7 @@ describe('createFeatureRegistry', () => {
expect(() => createFeatureRegistry([make(), make({ id: 'feature-b' })])).toThrow('Duplicate menu key: menu-a') expect(() => createFeatureRegistry([make(), make({ id: 'feature-b' })])).toThrow('Duplicate menu key: menu-a')
expect(() => createFeatureRegistry([make(), make({ id: 'feature-b', menus: [] })])).toThrow('Duplicate node type: node-a') expect(() => createFeatureRegistry([make(), make({ id: 'feature-b', menus: [] })])).toThrow('Duplicate node type: node-a')
expect(() => createFeatureRegistry([make(), make({ id: 'feature-b', menus: [], nodeModules: [] })])).toThrow('Duplicate dialog id: dialog-a') expect(() => createFeatureRegistry([make(), make({ id: 'feature-b', menus: [], nodeModules: [] })])).toThrow('Duplicate dialog id: dialog-a')
expect(() => createFeatureRegistry([make(), make({ id: 'feature-b', menus: [], nodeModules: [], dialogs: [] })])).toThrow('Duplicate editor event: event-a')
}) })
it('keeps the legacy menu and node registration orders', () => { it('keeps the legacy menu and node registration orders', () => {
......
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论