From c1faa1cfa896e1d240f5a2a100e1fd9b89dbef0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E7=A6=8F=E6=9C=8B?= Date: Wed, 9 Jun 2021 11:47:50 +0800 Subject: [PATCH] feat: video menu --- examples/index.html | 4 + packages/basic/src/assets/index.less | 2 + packages/basic/src/assets/video.less | 5 + packages/basic/src/index.ts | 2 + .../basic/src/modules/_helpers/icon-svg.ts | 4 + .../basic/src/modules/divider/render-elem.tsx | 2 +- .../src/modules/image/menu/InsertImage.ts | 10 +- .../basic/src/modules/image/render-elem.tsx | 1 + packages/basic/src/modules/video/index.ts | 17 +++ .../src/modules/video/menu/DeleteVideoMenu.ts | 51 +++++++ .../src/modules/video/menu/InsertVideoMenu.ts | 127 ++++++++++++++++++ .../basic/src/modules/video/menu/index.ts | 21 +++ packages/basic/src/modules/video/plugin.ts | 54 ++++++++ .../basic/src/modules/video/render-elem.tsx | 55 ++++++++ packages/editor/src/index.ts | 45 +++++-- 15 files changed, 381 insertions(+), 19 deletions(-) create mode 100644 packages/basic/src/assets/video.less create mode 100644 packages/basic/src/modules/video/index.ts create mode 100644 packages/basic/src/modules/video/menu/DeleteVideoMenu.ts create mode 100644 packages/basic/src/modules/video/menu/InsertVideoMenu.ts create mode 100644 packages/basic/src/modules/video/menu/index.ts create mode 100644 packages/basic/src/modules/video/plugin.ts create mode 100644 packages/basic/src/modules/video/render-elem.tsx diff --git a/examples/index.html b/examples/index.html index 24d2b0fd3..39b1180f5 100644 --- a/examples/index.html +++ b/examples/index.html @@ -83,6 +83,10 @@ type: 'paragraph', children: [{ text: '一行文字' }] }, + { + type: 'divider', + children: [{text: ''}] + }, { type: 'paragraph', children: [{ text: '一行文字' }] diff --git a/packages/basic/src/assets/index.less b/packages/basic/src/assets/index.less index 0800f7285..0384d0f9b 100644 --- a/packages/basic/src/assets/index.less +++ b/packages/basic/src/assets/index.less @@ -2,3 +2,5 @@ @import "blockquote.less"; @import "emotion.less"; @import "divider.less"; +@import "blockquote.less"; +@import "video.less"; \ No newline at end of file diff --git a/packages/basic/src/assets/video.less b/packages/basic/src/assets/video.less new file mode 100644 index 000000000..d1be05a9e --- /dev/null +++ b/packages/basic/src/assets/video.less @@ -0,0 +1,5 @@ +.w-e-textarea-video-container { + text-align: center; + border: 1px solid #d9d9d9; + padding: 5px 0; +} \ No newline at end of file diff --git a/packages/basic/src/index.ts b/packages/basic/src/index.ts index e58d3963e..ca9da5ffe 100644 --- a/packages/basic/src/index.ts +++ b/packages/basic/src/index.ts @@ -20,6 +20,7 @@ import lineHeight from './modules/line-height' import undoRedo from './modules/undo-redo' import list from './modules/list' import divider from './modules/divider' +import video from './modules/video' export { simpleStyle, @@ -37,4 +38,5 @@ export { undoRedo, list, divider, + video, } diff --git a/packages/basic/src/modules/_helpers/icon-svg.ts b/packages/basic/src/modules/_helpers/icon-svg.ts index d07b0c784..ebc0d374d 100644 --- a/packages/basic/src/modules/_helpers/icon-svg.ts +++ b/packages/basic/src/modules/_helpers/icon-svg.ts @@ -122,3 +122,7 @@ export const NUMBERED_LIST_SVG = // 分割线 export const DIVIDER_SVG = '' + +// 视频 +export const VIDEO_SVG = + '' diff --git a/packages/basic/src/modules/divider/render-elem.tsx b/packages/basic/src/modules/divider/render-elem.tsx index 0d0144d50..4b4c669e8 100644 --- a/packages/basic/src/modules/divider/render-elem.tsx +++ b/packages/basic/src/modules/divider/render-elem.tsx @@ -18,7 +18,7 @@ function renderDivider( // 是否选中 const selected = isNodeSelected(editor, elemNode, 'divider') renderStyle.boxShadow = selected ? '0 0 0 3px #B4D5FF' : 'none' - // TODO 选中时,显示拖拽框(不要在这里渲染,考虑一个独立的插件) + // TODO 抽离选中样式 const vnode =
// 【注意】void node 中,renderElem 不用处理 children 。core 会统一处理。 diff --git a/packages/basic/src/modules/image/menu/InsertImage.ts b/packages/basic/src/modules/image/menu/InsertImage.ts index 5af649360..4c8663ec1 100644 --- a/packages/basic/src/modules/image/menu/InsertImage.ts +++ b/packages/basic/src/modules/image/menu/InsertImage.ts @@ -58,10 +58,10 @@ class InsertImage implements IModalMenu { if (type === 'code') return true // 行内代码 if (type === 'pre') return true // 代码块 if (type === 'link') return true // 链接 + if (type === 'list-item') return true // list if (type.startsWith('header')) return true // 标题 - if (['ul', 'ol', 'li'].includes(type)) return true // 列表 + if (type === 'blockquote') return true // 引用 if (Editor.isVoid(editor, n)) return true // void - // TODO 引用块,禁用 return false }, @@ -92,9 +92,9 @@ class InsertImage implements IModalMenu { // 绑定事件(第一次渲染时绑定,不要重复绑定) $content.on('click', `#${buttonId}`, e => { e.preventDefault() - const src = $(`#${srcInputId}`).val() - const alt = $(`#${altInputId}`).val() - const url = $(`#${urlInputId}`).val() + const src = $(`#${srcInputId}`).val().trim() + const alt = $(`#${altInputId}`).val().trim() + const url = $(`#${urlInputId}`).val().trim() this.insertImage(editor, src, alt, url) }) diff --git a/packages/basic/src/modules/image/render-elem.tsx b/packages/basic/src/modules/image/render-elem.tsx index 95d7aecc3..40a0eb1c5 100644 --- a/packages/basic/src/modules/image/render-elem.tsx +++ b/packages/basic/src/modules/image/render-elem.tsx @@ -26,6 +26,7 @@ function renderImage(elemNode: SlateElement, children: VNode[] | null, editor: I const selected = isNodeSelected(editor, elemNode, 'image') renderStyle.boxShadow = selected ? '0 0 0 3px #B4D5FF' : 'none' // TODO 选中时,显示拖拽框(不要在这里渲染,考虑一个独立的插件) + // TODO 抽离选中样式 // 尺寸 if (width) renderStyle.width = width diff --git a/packages/basic/src/modules/video/index.ts b/packages/basic/src/modules/video/index.ts new file mode 100644 index 000000000..824013b26 --- /dev/null +++ b/packages/basic/src/modules/video/index.ts @@ -0,0 +1,17 @@ +/** + * @description video module + * @author wangfupeng + */ + +import { IModuleConf } from '@wangeditor/core' +import withVideo from './plugin' +import { renderVideoConf } from './render-elem' +import { insertVideoMenuConf, deleteVideoMenuConf } from './menu/index' + +const video: IModuleConf = { + renderElems: [renderVideoConf], + menus: [insertVideoMenuConf, deleteVideoMenuConf], + editorPlugin: withVideo, +} + +export default video diff --git a/packages/basic/src/modules/video/menu/DeleteVideoMenu.ts b/packages/basic/src/modules/video/menu/DeleteVideoMenu.ts new file mode 100644 index 000000000..2192c1c35 --- /dev/null +++ b/packages/basic/src/modules/video/menu/DeleteVideoMenu.ts @@ -0,0 +1,51 @@ +/** + * @description delete video menu + * @author wangfupeng + */ + +import { Transforms } from 'slate' +import { IButtonMenu, IDomEditor } from '@wangeditor/core' +import { checkNodeType, getSelectedNodeByType } from '../../_helpers/node' +import { TRASH_SVG } from '../../_helpers/icon-svg' + +class DeleteVideoMenu implements IButtonMenu { + title = '删除视频' + iconSvg = TRASH_SVG + tag = 'button' + + getValue(editor: IDomEditor): string | boolean { + // 无需获取 val + return '' + } + + isActive(editor: IDomEditor): boolean { + const videoNode = getSelectedNodeByType(editor, 'video') + if (videoNode) { + // 选区处于 video node + return true + } + return false + } + + isDisabled(editor: IDomEditor): boolean { + if (editor.selection == null) return true + + const videoNode = getSelectedNodeByType(editor, 'video') + if (videoNode == null) { + // 选区未处于 video node ,则禁用 + return true + } + return false + } + + exec(editor: IDomEditor, value: string | boolean) { + if (this.isDisabled(editor)) return + + // 删除视频 + Transforms.removeNodes(editor, { + match: n => checkNodeType(n, 'video'), + }) + } +} + +export default DeleteVideoMenu diff --git a/packages/basic/src/modules/video/menu/InsertVideoMenu.ts b/packages/basic/src/modules/video/menu/InsertVideoMenu.ts new file mode 100644 index 000000000..904850133 --- /dev/null +++ b/packages/basic/src/modules/video/menu/InsertVideoMenu.ts @@ -0,0 +1,127 @@ +/** + * @description insert video menu + * @author wangfupeng + */ + +import { Editor, Transforms, Range, Node } from 'slate' +import { IModalMenu, IDomEditor, DomEditor, hideAllPanelsAndModals } from '@wangeditor/core' +import $, { Dom7Array } from '../../../utils/dom' +import { genRandomStr } from '../../../utils/util' +import { genModalInputElems, genModalButtonElems } from '../../_helpers/menu' +import { VIDEO_SVG } from '../../_helpers/icon-svg' + +/** + * 生成唯一的 DOM ID + */ +function genDomID(): string { + return genRandomStr('w-e-insert-video') +} + +class InsertVideoMenu implements IModalMenu { + title = '插入视频' + iconSvg = VIDEO_SVG + tag = 'button' + showModal = true // 点击 button 时显示 modal + modalWidth = 300 + private $content: Dom7Array | null = null + private srcInputId = genDomID() + private buttonId = genDomID() + + getValue(editor: IDomEditor): string | boolean { + // 插入菜单,不需要 value + return '' + } + + isActive(editor: IDomEditor): boolean { + // 任何时候,都不用激活 menu + return false + } + + exec(editor: IDomEditor, value: string | boolean) { + // 点击菜单时,弹出 modal 之前,不需要执行其他代码 + // 此处空着即可 + } + + isDisabled(editor: IDomEditor): boolean { + const { selection } = editor + if (selection == null) return true + if (!Range.isCollapsed(selection)) return true // 选区非折叠,禁用 + + return false + } + + getModalPositionNode(editor: IDomEditor): Node | null { + return null // modal 依据选区定位 + } + + getModalContentElem(editor: IDomEditor): Dom7Array { + const { srcInputId, buttonId } = this + + // 获取 input button elem + const [$srcContainer, $inputSrc] = genModalInputElems( + '视频地址', + srcInputId, + 'mp4 网址,或第三方