diff --git a/simple-mind-map/index.js b/simple-mind-map/index.js index af812ed2..d7c4a32d 100644 --- a/simple-mind-map/index.js +++ b/simple-mind-map/index.js @@ -78,6 +78,10 @@ class MindMap { */ this.nodeInnerPrefixList = [] + // 编辑节点的类名列表,快捷键响应会检查事件目标是否是body或该列表中的元素,是的话才会响应 + // 该检查可以通过customCheckEnableShortcut选项来覆盖 + this.editNodeClassList = [] + // 画布 this.initContainer() @@ -241,6 +245,29 @@ class MindMap { if (this.cssEl) document.head.removeChild(this.cssEl) } + // 检查某个编辑节点类名是否存在,返回索引 + checkEditNodeClassIndex(className) { + return this.editNodeClassList.findIndex(item => { + return item === className + }) + } + + // 添加一个编辑节点类名 + addEditNodeClass(className) { + const index = this.checkEditNodeClassIndex(className) + if (index === -1) { + this.editNodeClassList.push(className) + } + } + + // 删除一个编辑节点类名 + deleteEditNodeClass(className) { + const index = this.checkEditNodeClassIndex(className) + if (index !== -1) { + this.editNodeClassList.splice(index, 1) + } + } + // 渲染,部分渲染 render(callback, source = '') { this.initTheme() diff --git a/simple-mind-map/src/constants/constant.js b/simple-mind-map/src/constants/constant.js index f6cc7bf2..5d864c4a 100644 --- a/simple-mind-map/src/constants/constant.js +++ b/simple-mind-map/src/constants/constant.js @@ -79,11 +79,6 @@ export const CONSTANTS = { TOP: 'top', RIGHT: 'right', BOTTOM: 'bottom' - }, - EDIT_NODE_CLASS: { - SMM_NODE_EDIT_WRAP: 'smm-node-edit-wrap', - RICH_TEXT_EDIT_WRAP: 'ql-editor', - ASSOCIATIVE_LINE_TEXT_EDIT_WRAP: 'associative-line-text-edit-warp' } } diff --git a/simple-mind-map/src/core/command/KeyCommand.js b/simple-mind-map/src/core/command/KeyCommand.js index 9997bd77..a51bdcc6 100644 --- a/simple-mind-map/src/core/command/KeyCommand.js +++ b/simple-mind-map/src/core/command/KeyCommand.js @@ -1,5 +1,4 @@ import { keyMap } from './keyMap' -import { CONSTANTS } from '../../constants/constant' // 快捷按键、命令处理类 export default class KeyCommand { @@ -13,6 +12,8 @@ export default class KeyCommand { this.shortcutMapCache = {} this.isPause = false this.isInSvg = false + this.isStopCheckInSvg = false + this.defaultEnableCheck = this.defaultEnableCheck.bind(this) this.bindEvent() } @@ -58,6 +59,22 @@ export default class KeyCommand { this.shortcutMapCache = {} } + // 停止对鼠标是否在画布内的检查,前提是开启了enableShortcutOnlyWhenMouseInSvg选项 + // 库内部节点文本编辑、关联线文本编辑、外框文本编辑前都会暂停检查,否则无法响应回车快捷键用于结束编辑 + // 如果你新增了额外的文本编辑,也可以在编辑前调用此方法 + stopCheckInSvg() { + const { enableShortcutOnlyWhenMouseInSvg } = this.mindMap.opt + if (!enableShortcutOnlyWhenMouseInSvg) return + this.isStopCheckInSvg = true + } + + // 恢复对鼠标是否在画布内的检查 + recoveryCheckInSvg() { + const { enableShortcutOnlyWhenMouseInSvg } = this.mindMap.opt + if (!enableShortcutOnlyWhenMouseInSvg) return + this.isStopCheckInSvg = true + } + // 绑定事件 bindEvent() { this.onKeydown = this.onKeydown.bind(this) @@ -66,13 +83,6 @@ export default class KeyCommand { this.isInSvg = true }) this.mindMap.on('svg_mouseleave', () => { - if (this.mindMap.renderer.textEdit.isShowTextEdit()) return - if ( - this.mindMap.associativeLine && - this.mindMap.associativeLine.showTextEdit - ) { - return - } this.isInSvg = false }) window.addEventListener('keydown', this.onKeydown) @@ -89,12 +99,14 @@ export default class KeyCommand { // 根据事件目标判断是否响应快捷键事件 defaultEnableCheck(e) { const target = e.target - return ( - target === document.body || - target.classList.contains(CONSTANTS.EDIT_NODE_CLASS.SMM_NODE_EDIT_WRAP) || - target.classList.contains(CONSTANTS.EDIT_NODE_CLASS.RICH_TEXT_EDIT_WRAP) || - target.classList.contains(CONSTANTS.EDIT_NODE_CLASS.ASSOCIATIVE_LINE_TEXT_EDIT_WRAP) - ) + if (target === document.body) return true + for (let i = 0; i < this.mindMap.editNodeClassList.length; i++) { + const cur = this.mindMap.editNodeClassList[i] + if (target.classList.contains(cur)) { + return true + } + } + return false } // 按键事件 @@ -109,7 +121,12 @@ export default class KeyCommand { ? customCheckEnableShortcut : this.defaultEnableCheck if (!checkFn(e)) return - if (this.isPause || (enableShortcutOnlyWhenMouseInSvg && !this.isInSvg)) { + if ( + this.isPause || + (enableShortcutOnlyWhenMouseInSvg && + !this.isStopCheckInSvg && + !this.isInSvg) + ) { return } Object.keys(this.shortcutMap).forEach(key => { diff --git a/simple-mind-map/src/core/render/TextEdit.js b/simple-mind-map/src/core/render/TextEdit.js index 1d5f2e25..e5e6caa2 100644 --- a/simple-mind-map/src/core/render/TextEdit.js +++ b/simple-mind-map/src/core/render/TextEdit.js @@ -16,6 +16,8 @@ import { noneRichTextNodeLineHeight } from '../../constants/constant' +const SMM_NODE_EDIT_WRAP = 'smm-node-edit-wrap' + // 节点文字编辑类 export default class TextEdit { // 构造函数 @@ -34,6 +36,7 @@ export default class TextEdit { this.textNodePaddingX = 5 this.textNodePaddingY = 3 this.isNeedUpdateTextEditNode = false + this.mindMap.addEditNodeClass(SMM_NODE_EDIT_WRAP) this.bindEvent() } @@ -193,6 +196,16 @@ export default class TextEdit { return this.showTextEdit } + // 设置文本编辑框是否处于显示状态 + setIsShowTextEdit(val) { + this.showTextEdit = val + if (val) { + this.mindMap.keyCommand.stopCheckInSvg() + } else { + this.mindMap.keyCommand.recoveryCheckInSvg() + } + } + // 显示文本编辑框 // isInserting:是否是刚创建的节点 // isFromKeyDown:是否是在按键事件进入的编辑 @@ -275,7 +288,7 @@ export default class TextEdit { this.mindMap.richText.showTextEdit = false } else { this.cacheEditingText = this.getEditText() - this.showTextEdit = false + this.setIsShowTextEdit(false) } this.show({ node, @@ -299,9 +312,7 @@ export default class TextEdit { this.registerTmpShortcut() if (!this.textEditNode) { this.textEditNode = document.createElement('div') - this.textEditNode.classList.add( - CONSTANTS.EDIT_NODE_CLASS.SMM_NODE_EDIT_WRAP - ) + this.textEditNode.classList.add(SMM_NODE_EDIT_WRAP) this.textEditNode.style.cssText = ` position: fixed; box-sizing: border-box; @@ -383,7 +394,7 @@ export default class TextEdit { } else { this.textEditNode.style.lineHeight = 'normal' } - this.showTextEdit = true + this.setIsShowTextEdit(true) // 选中文本 // if (!this.cacheEditingText) { // selectAllInput(this.textEditNode) @@ -477,7 +488,7 @@ export default class TextEdit { this.textEditNode.style.fontSize = 'inherit' this.textEditNode.style.fontWeight = 'normal' this.textEditNode.style.transform = 'translateY(0)' - this.showTextEdit = false + this.setIsShowTextEdit(false) this.mindMap.execCommand('SET_NODE_TEXT', currentNode, text) // if (currentNode.isGeneralization) { // // 概要节点 diff --git a/simple-mind-map/src/plugins/AssociativeLine.js b/simple-mind-map/src/plugins/AssociativeLine.js index a17ffcc5..8e24c0e6 100644 --- a/simple-mind-map/src/plugins/AssociativeLine.js +++ b/simple-mind-map/src/plugins/AssociativeLine.js @@ -23,6 +23,8 @@ const styleProps = [ 'associativeLineTextFontFamily' ] +const ASSOCIATIVE_LINE_TEXT_EDIT_WRAP = 'associative-line-text-edit-warp' + // 关联线插件 class AssociativeLine { constructor(opt = {}) { @@ -62,9 +64,11 @@ class AssociativeLine { this[item] = associativeLineControlsMethods[item].bind(this) }) // 关联线文字相关方法 + this.showTextEdit = false Object.keys(associativeLineTextMethods).forEach(item => { this[item] = associativeLineTextMethods[item].bind(this) }) + this.mindMap.addEditNodeClass(ASSOCIATIVE_LINE_TEXT_EDIT_WRAP) this.bindEvent() } @@ -744,11 +748,13 @@ class AssociativeLine { // 插件被移除前做的事情 beforePluginRemove() { + this.mindMap.deleteEditNodeClass(ASSOCIATIVE_LINE_TEXT_EDIT_WRAP) this.unBindEvent() } // 插件被卸载前做的事情 beforePluginDestroy() { + this.mindMap.deleteEditNodeClass(ASSOCIATIVE_LINE_TEXT_EDIT_WRAP) this.unBindEvent() } } diff --git a/simple-mind-map/src/plugins/RichText.js b/simple-mind-map/src/plugins/RichText.js index 4f9c690a..5da0d5d9 100644 --- a/simple-mind-map/src/plugins/RichText.js +++ b/simple-mind-map/src/plugins/RichText.js @@ -40,6 +40,8 @@ let fontSizeList = new Array(100).fill(0).map((_, index) => { return index + 'px' }) +const RICH_TEXT_EDIT_WRAP = 'ql-editor' + // 富文本编辑插件 class RichText { constructor({ mindMap, pluginOpt }) { @@ -58,6 +60,7 @@ class RichText { this.isCompositing = false this.textNodePaddingX = 6 this.textNodePaddingY = 4 + this.mindMap.addEditNodeClass(RICH_TEXT_EDIT_WRAP) this.initOpt() this.extendQuill() this.appendCss() @@ -113,7 +116,7 @@ class RichText { ` ) let cssText = ` - .${CONSTANTS.EDIT_NODE_CLASS.RICH_TEXT_EDIT_WRAP} { + .${RICH_TEXT_EDIT_WRAP} { overflow: hidden; padding: 0; height: auto; @@ -297,7 +300,7 @@ class RichText { } this.initQuillEditor() this.setQuillContainerMinHeight(originHeight) - this.showTextEdit = true + this.setIsShowTextEdit(true) // 如果是刚创建的节点,那么默认全选,否则普通激活不全选,除非selectTextOnEnterEditText配置为true // 在selectTextOnEnterEditText时,如果是在keydown事件进入的节点编辑,也不需要全选 this.focus( @@ -331,9 +334,8 @@ class RichText { // 设置quill编辑器容器的最小高度 setQuillContainerMinHeight(minHeight) { - document.querySelector( - '.' + CONSTANTS.EDIT_NODE_CLASS.RICH_TEXT_EDIT_WRAP - ).style.minHeight = minHeight + 'px' + document.querySelector('.' + RICH_TEXT_EDIT_WRAP).style.minHeight = + minHeight + 'px' } // 更新文本编辑框的大小和位置 @@ -385,7 +387,7 @@ class RichText { const list = nodes && nodes.length > 0 ? nodes : [this.node] const node = this.node this.textEditNode.style.display = 'none' - this.showTextEdit = false + this.setIsShowTextEdit(false) this.mindMap.emit('rich_text_selection_change', false) this.node = null this.isInserting = false @@ -614,6 +616,16 @@ class RichText { this.isCompositing = false } + // 设置文本编辑框是否处于显示状态 + setIsShowTextEdit(val) { + this.showTextEdit = val + if (val) { + this.mindMap.keyCommand.stopCheckInSvg() + } else { + this.mindMap.keyCommand.recoveryCheckInSvg() + } + } + // 选中全部 selectAll() { this.quill.setSelection(0, this.quill.getLength()) @@ -853,12 +865,14 @@ class RichText { document.head.removeChild(this.styleEl) this.unbindEvent() this.mindMap.removeAppendCss('richText') + this.mindMap.deleteEditNodeClass(RICH_TEXT_EDIT_WRAP) } // 插件被卸载前做的事情 beforePluginDestroy() { document.head.removeChild(this.styleEl) this.unbindEvent() + this.mindMap.deleteEditNodeClass(RICH_TEXT_EDIT_WRAP) } } diff --git a/simple-mind-map/src/plugins/associativeLine/associativeLineText.js b/simple-mind-map/src/plugins/associativeLine/associativeLineText.js index a2147d16..079aad08 100644 --- a/simple-mind-map/src/plugins/associativeLine/associativeLineText.js +++ b/simple-mind-map/src/plugins/associativeLine/associativeLineText.js @@ -5,6 +5,8 @@ import { selectAllInput } from '../../utils/index' +const ASSOCIATIVE_LINE_TEXT_EDIT_WRAP = 'associative-line-text-edit-warp' + // 创建文字节点 function createText(data) { let g = this.associativeLineDraw.group() @@ -43,7 +45,7 @@ function showEditTextBox(g) { // 输入框元素没有创建过,则先创建 if (!this.textEditNode) { this.textEditNode = document.createElement('div') - this.textEditNode.className = 'associative-line-text-edit-warp' + this.textEditNode.className = ASSOCIATIVE_LINE_TEXT_EDIT_WRAP this.textEditNode.style.cssText = `position:fixed;box-sizing: border-box;background-color:#fff;box-shadow: 0 0 20px rgba(0,0,0,.5);padding: 3px 5px;margin-left: -5px;margin-top: -3px;outline: none; word-break: break-all;` this.textEditNode.setAttribute('contenteditable', true) this.textEditNode.addEventListener('keyup', e => { @@ -73,7 +75,7 @@ function showEditTextBox(g) { this.textEditNode.innerHTML = textLines.join('
') this.textEditNode.style.display = 'block' this.updateTextEditBoxPos(g) - this.showTextEdit = true + this.setIsShowTextEdit(true) // 如果是默认文本要全选输入框 if (text === '' || text === defaultAssociativeLineText) { selectAllInput(this.textEditNode) @@ -83,6 +85,16 @@ function showEditTextBox(g) { } } +// 设置文本编辑框是否处于显示状态 +function setIsShowTextEdit(val) { + this.showTextEdit = val + if (val) { + this.mindMap.keyCommand.stopCheckInSvg() + } else { + this.mindMap.keyCommand.recoveryCheckInSvg() + } +} + // 删除文本编辑框元素 function removeTextEditEl() { if (!this.textEditNode) return @@ -124,7 +136,7 @@ function hideEditTextBox() { }) this.textEditNode.style.display = 'none' this.textEditNode.innerHTML = '' - this.showTextEdit = false + this.setIsShowTextEdit(false) this.renderText(str, path, text, node, toNode) this.mindMap.emit('hide_text_edit') } @@ -192,6 +204,7 @@ export default { styleText, onScale, showEditTextBox, + setIsShowTextEdit, removeTextEditEl, hideEditTextBox, updateTextEditBoxPos,