Feat:1.支持暂停检查鼠标是否在画布内;2.快捷键响应目标由硬编码改为由插件控制

This commit is contained in:
街角小林
2025-03-28 09:47:56 +08:00
parent 92d01e510b
commit 37eab5f084
7 changed files with 118 additions and 35 deletions

View File

@@ -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()

View File

@@ -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'
}
}

View File

@@ -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 => {

View File

@@ -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) {
// // 概要节点

View File

@@ -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()
}
}

View File

@@ -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)
}
}

View File

@@ -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('<br>')
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,