Doc: update

This commit is contained in:
wanglin2
2023-08-08 09:51:05 +08:00
parent 2b6263acb4
commit 86b184d5c1
14 changed files with 583 additions and 9 deletions

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"><link rel="icon" href="dist/logo.ico"><title>思绪思维导图</title><script>// 自定义静态资源的路径
window.externalPublicPath = './dist/'
// 接管应用
window.takeOverApp = false</script><link href="dist/css/chunk-vendors.css?f6cb6d932d2f3720fc06" rel="stylesheet"><link href="dist/css/app.css?f6cb6d932d2f3720fc06" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
window.takeOverApp = false</script><link href="dist/css/chunk-vendors.css?82cd2198e53e186a377d" rel="stylesheet"><link href="dist/css/app.css?82cd2198e53e186a377d" rel="stylesheet"></head><body><noscript><strong>We're sorry but thoughts doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script>const getDataFromBackend = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
@@ -66,4 +66,4 @@
// 可以通过window.$bus.$on()来监听应用的一些事件
// 实例化页面
window.initApp()
}</script><script src="dist/js/chunk-vendors.js?f6cb6d932d2f3720fc06"></script><script src="dist/js/app.js?f6cb6d932d2f3720fc06"></script></body></html>
}</script><script src="dist/js/chunk-vendors.js?82cd2198e53e186a377d"></script><script src="dist/js/app.js?82cd2198e53e186a377d"></script></body></html>

View File

@@ -541,7 +541,7 @@ export const getVisibleColorFromTheme = (themeConfig) => {
}
}
// 将<p><span></span><p>形式的节点富文本内容转换成<br>换行的文本
// 将<p><span></span><p>形式的节点富文本内容转换成\n换行的文本
let nodeRichTextToTextWithWrapEl = null
export const nodeRichTextToTextWithWrap = (html) => {
if (!nodeRichTextToTextWithWrapEl) {

View File

@@ -11,7 +11,7 @@ let langList = [
}
]
let StartList = ['introduction', 'start', 'deploy', 'client', 'translate', 'changelog']
let CourseList = new Array(21).fill(0).map((_, index) => {
let CourseList = new Array(22).fill(0).map((_, index) => {
return 'course' + (index + 1)
})
let APIList = [

View File

@@ -236,7 +236,7 @@ Determine whether a color is transparent.
> v0.6.12+
Convert the rich text content of nodes in the form of `<p><span></span><p>` into text wrapped in `<br>`.
Convert the rich text content of nodes in the form of `<p><span></span><p>` into text wrapped in `\n`.
#### textToNodeRichTextWithWrap(html)

View File

@@ -168,7 +168,7 @@ and copying the <code>data</code> of the data object, example:</p>
<blockquote>
<p>v0.6.12+</p>
</blockquote>
<p>Convert the rich text content of nodes in the form of <code>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;p&gt;</code> into text wrapped in <code>&lt;br&gt;</code>.</p>
<p>Convert the rich text content of nodes in the form of <code>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;p&gt;</code> into text wrapped in <code>\n</code>.</p>
<h4>textToNodeRichTextWithWrap(html)</h4>
<blockquote>
<p>v0.6.12+</p>

View File

@@ -28,6 +28,7 @@ export default [
{ path: 'course19', title: '插入和扩展节点图标' },
{ path: 'course20', title: '如何自定义节点内容' },
{ path: 'course21', title: '如何复制、剪切、粘贴' },
{ path: 'course22', title: '如何实现搜索、替换' },
{ path: 'doExport', title: 'Export 插件' },
{ path: 'drag', title: 'Drag插件' },
{ path: 'introduction', title: '简介' },

View File

@@ -34,4 +34,269 @@ data._node.setText('xxx')
```js
mindMap.execCommand('INSERT_NODE', false)
mindMap.execCommand('INSERT_CHILD_NODE', false)
```
```
## 进阶
要实现一个功能完善的大纲并不容易,下面介绍一下包含定位、编辑、拖拽、删除、单独编辑功能的大纲实现。
以[ElementUI Tree组件](https://element.eleme.cn/#/zh-CN/component/tree)为例。
实现监听`data_change`事件来刷新树数据:
```js
import { nodeRichTextToTextWithWrap } from 'simple-mind-map/src/utils'
this.mindMap.on('data_change', () => {
this.refresh()
})
{
refresh() {
let data = mindMap.getData()// 获取思维导图树数据
data.root = true // 标记根节点
// 遍历树,添加一些属性
let walk = root => {
// 如果是富文本节点那么调用nodeRichTextToTextWithWrap方法将<p><span></span><p>形式的节点富文本内容转换成\n换行的文本
const text = (root.data.richText
? nodeRichTextToTextWithWrap(root.data.text)
: root.data.text
).replaceAll(/\n/g, '<br>')
root.textCache = text // 保存一份修改前的数据,用于对比是否修改了
root.label = text// 用于树组件渲染
root.uid = root.data.uid// 用于树组件渲染
if (root.children && root.children.length > 0) {
root.children.forEach(item => {
walk(item)
})
}
}
walk(data)
this.data = [data]// 赋值给树组件
}
}
```
模板如下:
```html
<el-tree
ref="tree"
node-key="uid"
draggable
default-expand-all
:data="data"
:highlight-current="true"
:expand-on-click-node="false"
:allow-drag="checkAllowDrag"
@node-drop="onNodeDrop"
@current-change="onCurrentChange"
@mouseenter.native="isInTreArea = true"
@mouseleave.native="isInTreArea = false"
>
<span
class="customNode"
slot-scope="{ node, data }"
:data-id="data.uid"
@click="onClick(data)"
>
<span
class="nodeEdit"
contenteditable="true"
:key="getKey()"
@keydown.stop="onNodeInputKeydown($event, node)"
@keyup.stop
@blur="onBlur($event, node)"
@paste="onPaste($event, node)"
v-html="node.label"
></span>
</span>
</el-tree>
```
### 定位节点
给节点绑定了一个`click`事件用于在画布内定位点击的节点,可以调用思维导图的相关方法实现:
```js
// 激活当前节点且移动当前节点到画布中间
onClick(data) {
// 根据uid知道思维导图节点对象
const targetNode = this.mindMap.renderer.findNodeByUid(data.uid)
// 如果当前已经是激活状态,那么上面都不做
if (targetNode && targetNode.nodeData.data.isActive) return
// 思维导图节点激活时默认会聚焦到内部创建的一个隐藏输入框中,`stopFocusOnNodeActive`方法是用于关闭这个特性,因为我们想把焦点留在大纲的输入框中
this.mindMap.renderer.textEdit.stopFocusOnNodeActive()
// 定位到目标节点
this.mindMap.execCommand('GO_TARGET_NODE', data.uid, () => {
// 定位完成后再开启前面关闭的特性
this.mindMap.renderer.textEdit.openFocusOnNodeActive()
})
}
```
### 编辑
我们通过自定义树节点内容渲染了一个`contenteditable=true`的标签用于输入文本,然后在`blur`事件中修改节点文本:
```js
import { textToNodeRichTextWithWrap } from 'simple-mind-map/src/utils'
// 失去焦点更新节点文本
onBlur(e, node) {
// 节点数据没有修改那么什么也不用做
if (node.data.textCache === e.target.innerHTML) {
return
}
// 根据是否是富文本模式获取不同的文本数据
const richText = node.data.data.richText
const text = richText ? e.target.innerHTML : e.target.innerText
const targetNode = this.mindMap.renderer.findNodeByUid(node.data.uid)
if (!targetNode) return
if (richText) {
// 如果是富文本节点那么需要先调用textToNodeRichTextWithWrap方法将<br>换行的文本转换成<p><span></span><p>形式的节点富文本内容
// 第二个参数代表设置的是富文本内容
// 第三个参数指定要重置富文本节点的样式
targetNode.setText(textToNodeRichTextWithWrap(text), true, true)
} else {
targetNode.setText(text)
}
}
```
### 拖拽
设置了`draggable`属性即可开启拖拽,首先根节点是不允许拖拽的,所以通过`allow-drag`属性传入一个判断方法:
```js
// 根节点不允许拖拽
checkAllowDrag(node) {
return !node.data.root
}
```
然后监听拖拽完成事件`node-drop`来实现画布内节点的调整:
```js
// 拖拽结束事件
onNodeDrop(data, target, position) {
// 被拖拽的节点
const node = this.mindMap.renderer.findNodeByUid(data.data.uid)
// 拖拽到的目标节点
const targetNode = this.mindMap.renderer.findNodeByUid(target.data.uid)
if (!node || !targetNode) {
return
}
// 根据不同拖拽的情况调用不同的方法
switch (position) {
case 'before':
this.mindMap.execCommand('INSERT_BEFORE', node, targetNode)
break
case 'after':
this.mindMap.execCommand('INSERT_AFTER', node, targetNode)
break
case 'inner':
this.mindMap.execCommand('MOVE_NODE_TO', node, targetNode)
break
default:
break
}
}
```
### 删除节点
首先通过树组件的`current-change`事件来保存当前高亮的树节点:
```js
// 当前选中的树节点变化事件
onCurrentChange(data) {
this.currentData = data
}
```
然后通过监听`keydown`事件来完成删除节点的操作:
```js
window.addEventListener('keydown', this.onKeyDown)
// 删除节点
onKeyDown(e) {
if ([46, 8].includes(e.keyCode) && this.currentData) {
e.stopPropagation()
// 处理当前正在编辑节点内容时删除的情况
this.mindMap.renderer.textEdit.hideEditTextBox()
const node = this.mindMap.renderer.findNodeByUid(this.currentData.uid)
if (node && !node.isRoot) {
// 首先从树里删除
this.$refs.tree.remove(this.currentData)
// 然后从画布里删除
this.mindMap.execCommand('REMOVE_NODE', [node])
}
}
}
```
### 创建新节点
通过监听节点内容编辑框的`keydown`事件来完成添加新节点的操作:
```js
import { createUid } from 'simple-mind-map/src/utils'
// 节点输入区域按键事件
onNodeInputKeydown(e) {
// 回车键添加同级节点
if (e.keyCode === 13 && !e.shiftKey) {
e.preventDefault()
this.insertNode()
}
// tab键添加子节点
if (e.keyCode === 9) {
e.preventDefault()
this.insertChildNode()
}
}
// 插入兄弟节点
insertNode() {
this.mindMap.execCommand('INSERT_NODE', false, [], {
uid: createUid()
})
}
// 插入下级节点
insertChildNode() {
this.mindMap.execCommand('INSERT_CHILD_NODE', false, [], {
uid: createUid()
})
}
```
### 拦截输入框的粘贴操作
为什么要拦截输入框的粘贴操作因为用户可能粘贴的是富文本内容也就是带html标签的但是一般我们都不希望用户粘贴这种内容只允许粘贴纯文本所以我们要拦截粘贴事件处理一下用户粘贴的内容
```js
import { getTextFromHtml } from 'simple-mind-map/src/utils'
// 拦截粘贴事件
onPaste(e) {
e.preventDefault()
const selection = window.getSelection()
if (!selection.rangeCount) return
selection.deleteFromDocument()// 删除当前选区,也就是如果当前用户在输入框中选择了一些文本,会被删除
// 从剪贴板里取出文本数据
let text = (e.clipboardData || window.clipboardData).getData('text')
// 调用库提供的getTextFromHtml方法去除格式
text = getTextFromHtml(text)
// 去除换行
text = text.replaceAll(/\n/g, '')
// 创建文本节点添加到当前选区
const node = document.createTextNode(text)
selection.getRangeAt(0).insertNode(node)
selection.collapseToEnd()
}
```
到这里基本功能就都完成了,是不是觉得挺简单的?核心原理和操作确实很简单,麻烦的是各种情况和冲突的处理,比如焦点的冲突、快捷键的冲突、操作的时间顺序等等,所以务必先阅读一下完整的源码[Outline.vue](https://github.com/wanglin2/mind-map/blob/main/web/src/pages/Edit/components/Outline.vue)。

View File

@@ -24,6 +24,231 @@ mindMap.execCommand(<span class="hljs-string">&#x27;GO_TARGET_NODE&#x27;</span>,
<pre class="hljs"><code>mindMap.execCommand(<span class="hljs-string">&#x27;INSERT_NODE&#x27;</span>, <span class="hljs-literal">false</span>)
mindMap.execCommand(<span class="hljs-string">&#x27;INSERT_CHILD_NODE&#x27;</span>, <span class="hljs-literal">false</span>)
</code></pre>
<h2>进阶</h2>
<p>要实现一个功能完善的大纲并不容易下面介绍一下包含定位编辑拖拽删除单独编辑功能的大纲实现</p>
<p><a href="https://element.eleme.cn/#/zh-CN/component/tree">ElementUI Tree组件</a>为例</p>
<p>实现监听<code>data_change</code>事件来刷新树数据</p>
<pre class="hljs"><code><span class="hljs-keyword">import</span> { nodeRichTextToTextWithWrap } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/utils&#x27;</span>
<span class="hljs-built_in">this</span>.mindMap.on(<span class="hljs-string">&#x27;data_change&#x27;</span>, <span class="hljs-function">() =&gt;</span> {
<span class="hljs-built_in">this</span>.refresh()
})
{
<span class="hljs-function"><span class="hljs-title">refresh</span>(<span class="hljs-params"></span>)</span> {
<span class="hljs-keyword">let</span> data = mindMap.getData()<span class="hljs-comment">// 获取思维导图树数据</span>
data.root = <span class="hljs-literal">true</span> <span class="hljs-comment">// 标记根节点</span>
<span class="hljs-comment">// 遍历树,添加一些属性</span>
<span class="hljs-keyword">let</span> walk = <span class="hljs-function"><span class="hljs-params">root</span> =&gt;</span> {
<span class="hljs-comment">// 如果是富文本节点那么调用nodeRichTextToTextWithWrap方法将&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;p&gt;形式的节点富文本内容转换成\n换行的文本</span>
<span class="hljs-keyword">const</span> text = (root.data.richText
? nodeRichTextToTextWithWrap(root.data.text)
: root.data.text
).replaceAll(<span class="hljs-regexp">/\n/g</span>, <span class="hljs-string">&#x27;&lt;br&gt;&#x27;</span>)
root.textCache = text <span class="hljs-comment">// 保存一份修改前的数据,用于对比是否修改了</span>
root.label = text<span class="hljs-comment">// 用于树组件渲染</span>
root.uid = root.data.uid<span class="hljs-comment">// 用于树组件渲染</span>
<span class="hljs-keyword">if</span> (root.children &amp;&amp; root.children.length &gt; <span class="hljs-number">0</span>) {
root.children.forEach(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> {
walk(item)
})
}
}
walk(data)
<span class="hljs-built_in">this</span>.data = [data]<span class="hljs-comment">// 赋值给树组件</span>
}
}
</code></pre>
<p>模板如下</p>
<pre class="hljs"><code><span class="hljs-tag">&lt;<span class="hljs-name">el-tree</span>
<span class="hljs-attr">ref</span>=<span class="hljs-string">&quot;tree&quot;</span>
<span class="hljs-attr">node-key</span>=<span class="hljs-string">&quot;uid&quot;</span>
<span class="hljs-attr">draggable</span>
<span class="hljs-attr">default-expand-all</span>
<span class="hljs-attr">:data</span>=<span class="hljs-string">&quot;data&quot;</span>
<span class="hljs-attr">:highlight-current</span>=<span class="hljs-string">&quot;true&quot;</span>
<span class="hljs-attr">:expand-on-click-node</span>=<span class="hljs-string">&quot;false&quot;</span>
<span class="hljs-attr">:allow-drag</span>=<span class="hljs-string">&quot;checkAllowDrag&quot;</span>
@<span class="hljs-attr">node-drop</span>=<span class="hljs-string">&quot;onNodeDrop&quot;</span>
@<span class="hljs-attr">current-change</span>=<span class="hljs-string">&quot;onCurrentChange&quot;</span>
@<span class="hljs-attr">mouseenter.native</span>=<span class="hljs-string">&quot;isInTreArea = true&quot;</span>
@<span class="hljs-attr">mouseleave.native</span>=<span class="hljs-string">&quot;isInTreArea = false&quot;</span>
&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">span</span>
<span class="hljs-attr">class</span>=<span class="hljs-string">&quot;customNode&quot;</span>
<span class="hljs-attr">slot-scope</span>=<span class="hljs-string">&quot;{ node, data }&quot;</span>
<span class="hljs-attr">:data-id</span>=<span class="hljs-string">&quot;data.uid&quot;</span>
@<span class="hljs-attr">click</span>=<span class="hljs-string">&quot;onClick(data)&quot;</span>
&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">span</span>
<span class="hljs-attr">class</span>=<span class="hljs-string">&quot;nodeEdit&quot;</span>
<span class="hljs-attr">contenteditable</span>=<span class="hljs-string">&quot;true&quot;</span>
<span class="hljs-attr">:key</span>=<span class="hljs-string">&quot;getKey()&quot;</span>
@<span class="hljs-attr">keydown.stop</span>=<span class="hljs-string">&quot;onNodeInputKeydown($event, node)&quot;</span>
@<span class="hljs-attr">keyup.stop</span>
@<span class="hljs-attr">blur</span>=<span class="hljs-string">&quot;onBlur($event, node)&quot;</span>
@<span class="hljs-attr">paste</span>=<span class="hljs-string">&quot;onPaste($event, node)&quot;</span>
<span class="hljs-attr">v-html</span>=<span class="hljs-string">&quot;node.label&quot;</span>
&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">el-tree</span>&gt;</span>
</code></pre>
<h3>定位节点</h3>
<p>给节点绑定了一个<code>click</code>事件用于在画布内定位点击的节点可以调用思维导图的相关方法实现</p>
<pre class="hljs"><code><span class="hljs-comment">// 激活当前节点且移动当前节点到画布中间</span>
<span class="hljs-function"><span class="hljs-title">onClick</span>(<span class="hljs-params">data</span>)</span> {
<span class="hljs-comment">// 根据uid知道思维导图节点对象</span>
<span class="hljs-keyword">const</span> targetNode = <span class="hljs-built_in">this</span>.mindMap.renderer.findNodeByUid(data.uid)
<span class="hljs-comment">// 如果当前已经是激活状态,那么上面都不做</span>
<span class="hljs-keyword">if</span> (targetNode &amp;&amp; targetNode.nodeData.data.isActive) <span class="hljs-keyword">return</span>
<span class="hljs-comment">// 思维导图节点激活时默认会聚焦到内部创建的一个隐藏输入框中,`stopFocusOnNodeActive`方法是用于关闭这个特性,因为我们想把焦点留在大纲的输入框中</span>
<span class="hljs-built_in">this</span>.mindMap.renderer.textEdit.stopFocusOnNodeActive()
<span class="hljs-comment">// 定位到目标节点</span>
<span class="hljs-built_in">this</span>.mindMap.execCommand(<span class="hljs-string">&#x27;GO_TARGET_NODE&#x27;</span>, data.uid, <span class="hljs-function">() =&gt;</span> {
<span class="hljs-comment">// 定位完成后再开启前面关闭的特性</span>
<span class="hljs-built_in">this</span>.mindMap.renderer.textEdit.openFocusOnNodeActive()
})
}
</code></pre>
<h3>编辑</h3>
<p>我们通过自定义树节点内容渲染了一个<code>contenteditable=true</code>的标签用于输入文本然后在<code>blur</code>事件中修改节点文本</p>
<pre class="hljs"><code><span class="hljs-keyword">import</span> { textToNodeRichTextWithWrap } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/utils&#x27;</span>
<span class="hljs-comment">// 失去焦点更新节点文本</span>
<span class="hljs-function"><span class="hljs-title">onBlur</span>(<span class="hljs-params">e, node</span>)</span> {
<span class="hljs-comment">// 节点数据没有修改那么什么也不用做</span>
<span class="hljs-keyword">if</span> (node.data.textCache === e.target.innerHTML) {
<span class="hljs-keyword">return</span>
}
<span class="hljs-comment">// 根据是否是富文本模式获取不同的文本数据</span>
<span class="hljs-keyword">const</span> richText = node.data.data.richText
<span class="hljs-keyword">const</span> text = richText ? e.target.innerHTML : e.target.innerText
<span class="hljs-keyword">const</span> targetNode = <span class="hljs-built_in">this</span>.mindMap.renderer.findNodeByUid(node.data.uid)
<span class="hljs-keyword">if</span> (!targetNode) <span class="hljs-keyword">return</span>
<span class="hljs-keyword">if</span> (richText) {
<span class="hljs-comment">// 如果是富文本节点那么需要先调用textToNodeRichTextWithWrap方法将&lt;br&gt;换行的文本转换成&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;p&gt;形式的节点富文本内容</span>
<span class="hljs-comment">// 第二个参数代表设置的是富文本内容</span>
<span class="hljs-comment">// 第三个参数指定要重置富文本节点的样式</span>
targetNode.setText(textToNodeRichTextWithWrap(text), <span class="hljs-literal">true</span>, <span class="hljs-literal">true</span>)
} <span class="hljs-keyword">else</span> {
targetNode.setText(text)
}
}
</code></pre>
<h3>拖拽</h3>
<p>设置了<code>draggable</code>属性即可开启拖拽首先根节点是不允许拖拽的所以通过<code>allow-drag</code>属性传入一个判断方法</p>
<pre class="hljs"><code><span class="hljs-comment">// 根节点不允许拖拽</span>
<span class="hljs-function"><span class="hljs-title">checkAllowDrag</span>(<span class="hljs-params">node</span>)</span> {
<span class="hljs-keyword">return</span> !node.data.root
}
</code></pre>
<p>然后监听拖拽完成事件<code>node-drop</code>来实现画布内节点的调整</p>
<pre class="hljs"><code><span class="hljs-comment">// 拖拽结束事件</span>
<span class="hljs-function"><span class="hljs-title">onNodeDrop</span>(<span class="hljs-params">data, target, position</span>)</span> {
<span class="hljs-comment">// 被拖拽的节点</span>
<span class="hljs-keyword">const</span> node = <span class="hljs-built_in">this</span>.mindMap.renderer.findNodeByUid(data.data.uid)
<span class="hljs-comment">// 拖拽到的目标节点</span>
<span class="hljs-keyword">const</span> targetNode = <span class="hljs-built_in">this</span>.mindMap.renderer.findNodeByUid(target.data.uid)
<span class="hljs-keyword">if</span> (!node || !targetNode) {
<span class="hljs-keyword">return</span>
}
<span class="hljs-comment">// 根据不同拖拽的情况调用不同的方法</span>
<span class="hljs-keyword">switch</span> (position) {
<span class="hljs-keyword">case</span> <span class="hljs-string">&#x27;before&#x27;</span>:
<span class="hljs-built_in">this</span>.mindMap.execCommand(<span class="hljs-string">&#x27;INSERT_BEFORE&#x27;</span>, node, targetNode)
<span class="hljs-keyword">break</span>
<span class="hljs-keyword">case</span> <span class="hljs-string">&#x27;after&#x27;</span>:
<span class="hljs-built_in">this</span>.mindMap.execCommand(<span class="hljs-string">&#x27;INSERT_AFTER&#x27;</span>, node, targetNode)
<span class="hljs-keyword">break</span>
<span class="hljs-keyword">case</span> <span class="hljs-string">&#x27;inner&#x27;</span>:
<span class="hljs-built_in">this</span>.mindMap.execCommand(<span class="hljs-string">&#x27;MOVE_NODE_TO&#x27;</span>, node, targetNode)
<span class="hljs-keyword">break</span>
<span class="hljs-attr">default</span>:
<span class="hljs-keyword">break</span>
}
}
</code></pre>
<h3>删除节点</h3>
<p>首先通过树组件的<code>current-change</code>事件来保存当前高亮的树节点</p>
<pre class="hljs"><code><span class="hljs-comment">// 当前选中的树节点变化事件</span>
<span class="hljs-function"><span class="hljs-title">onCurrentChange</span>(<span class="hljs-params">data</span>)</span> {
<span class="hljs-built_in">this</span>.currentData = data
}
</code></pre>
<p>然后通过监听<code>keydown</code>事件来完成删除节点的操作</p>
<pre class="hljs"><code><span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">&#x27;keydown&#x27;</span>, <span class="hljs-built_in">this</span>.onKeyDown)
<span class="hljs-comment">// 删除节点</span>
<span class="hljs-function"><span class="hljs-title">onKeyDown</span>(<span class="hljs-params">e</span>)</span> {
<span class="hljs-keyword">if</span> ([<span class="hljs-number">46</span>, <span class="hljs-number">8</span>].includes(e.keyCode) &amp;&amp; <span class="hljs-built_in">this</span>.currentData) {
e.stopPropagation()
<span class="hljs-comment">// 处理当前正在编辑节点内容时删除的情况</span>
<span class="hljs-built_in">this</span>.mindMap.renderer.textEdit.hideEditTextBox()
<span class="hljs-keyword">const</span> node = <span class="hljs-built_in">this</span>.mindMap.renderer.findNodeByUid(<span class="hljs-built_in">this</span>.currentData.uid)
<span class="hljs-keyword">if</span> (node &amp;&amp; !node.isRoot) {
<span class="hljs-comment">// 首先从树里删除</span>
<span class="hljs-built_in">this</span>.$refs.tree.remove(<span class="hljs-built_in">this</span>.currentData)
<span class="hljs-comment">// 然后从画布里删除</span>
<span class="hljs-built_in">this</span>.mindMap.execCommand(<span class="hljs-string">&#x27;REMOVE_NODE&#x27;</span>, [node])
}
}
}
</code></pre>
<h3>创建新节点</h3>
<p>通过监听节点内容编辑框的<code>keydown</code>事件来完成添加新节点的操作</p>
<pre class="hljs"><code><span class="hljs-keyword">import</span> { createUid } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/utils&#x27;</span>
<span class="hljs-comment">// 节点输入区域按键事件</span>
<span class="hljs-function"><span class="hljs-title">onNodeInputKeydown</span>(<span class="hljs-params">e</span>)</span> {
<span class="hljs-comment">// 回车键添加同级节点</span>
<span class="hljs-keyword">if</span> (e.keyCode === <span class="hljs-number">13</span> &amp;&amp; !e.shiftKey) {
e.preventDefault()
<span class="hljs-built_in">this</span>.insertNode()
}
<span class="hljs-comment">// tab键添加子节点</span>
<span class="hljs-keyword">if</span> (e.keyCode === <span class="hljs-number">9</span>) {
e.preventDefault()
<span class="hljs-built_in">this</span>.insertChildNode()
}
}
<span class="hljs-comment">// 插入兄弟节点</span>
<span class="hljs-function"><span class="hljs-title">insertNode</span>(<span class="hljs-params"></span>)</span> {
<span class="hljs-built_in">this</span>.mindMap.execCommand(<span class="hljs-string">&#x27;INSERT_NODE&#x27;</span>, <span class="hljs-literal">false</span>, [], {
<span class="hljs-attr">uid</span>: createUid()
})
}
<span class="hljs-comment">// 插入下级节点</span>
<span class="hljs-function"><span class="hljs-title">insertChildNode</span>(<span class="hljs-params"></span>)</span> {
<span class="hljs-built_in">this</span>.mindMap.execCommand(<span class="hljs-string">&#x27;INSERT_CHILD_NODE&#x27;</span>, <span class="hljs-literal">false</span>, [], {
<span class="hljs-attr">uid</span>: createUid()
})
}
</code></pre>
<h3>拦截输入框的粘贴操作</h3>
<p>为什么要拦截输入框的粘贴操作因为用户可能粘贴的是富文本内容也就是带html标签的但是一般我们都不希望用户粘贴这种内容只允许粘贴纯文本所以我们要拦截粘贴事件处理一下用户粘贴的内容</p>
<pre class="hljs"><code><span class="hljs-keyword">import</span> { getTextFromHtml } <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;simple-mind-map/src/utils&#x27;</span>
<span class="hljs-comment">// 拦截粘贴事件</span>
<span class="hljs-function"><span class="hljs-title">onPaste</span>(<span class="hljs-params">e</span>)</span> {
e.preventDefault()
<span class="hljs-keyword">const</span> selection = <span class="hljs-built_in">window</span>.getSelection()
<span class="hljs-keyword">if</span> (!selection.rangeCount) <span class="hljs-keyword">return</span>
selection.deleteFromDocument()<span class="hljs-comment">// 删除当前选区,也就是如果当前用户在输入框中选择了一些文本,会被删除</span>
<span class="hljs-comment">// 从剪贴板里取出文本数据</span>
<span class="hljs-keyword">let</span> text = (e.clipboardData || <span class="hljs-built_in">window</span>.clipboardData).getData(<span class="hljs-string">&#x27;text&#x27;</span>)
<span class="hljs-comment">// 调用库提供的getTextFromHtml方法去除格式</span>
text = getTextFromHtml(text)
<span class="hljs-comment">// 去除换行</span>
text = text.replaceAll(<span class="hljs-regexp">/\n/g</span>, <span class="hljs-string">&#x27;&#x27;</span>)
<span class="hljs-comment">// 创建文本节点添加到当前选区</span>
<span class="hljs-keyword">const</span> node = <span class="hljs-built_in">document</span>.createTextNode(text)
selection.getRangeAt(<span class="hljs-number">0</span>).insertNode(node)
selection.collapseToEnd()
}
</code></pre>
<p>到这里基本功能就都完成了是不是觉得挺简单的核心原理和操作确实很简单麻烦的是各种情况和冲突的处理比如焦点的冲突快捷键的冲突操作的时间顺序等等所以务必先阅读一下完整的源码<a href="https://github.com/wanglin2/mind-map/blob/main/web/src/pages/Edit/components/Outline.vue">Outline.vue</a></p>
</div>
</template>

View File

@@ -1,5 +1,7 @@
# 如何持久化数据
> 目前提供了一种新方式,可以参考[对接自己的存储服务](https://wanglin2.github.io/mind-map/#/doc/zh/deploy/%E5%AF%B9%E6%8E%A5%E8%87%AA%E5%B7%B1%E7%9A%84%E5%AD%98%E5%82%A8%E6%9C%8D%E5%8A%A1)。
在线`demo`的数据是存储在电脑本地的,也就是`localStorage`里,当然,你也可以存储到数据库中。
## 保存数据

View File

@@ -1,6 +1,9 @@
<template>
<div>
<h1>如何持久化数据</h1>
<blockquote>
<p>目前提供了一种新方式可以参考<a href="https://wanglin2.github.io/mind-map/#/doc/zh/deploy/%E5%AF%B9%E6%8E%A5%E8%87%AA%E5%B7%B1%E7%9A%84%E5%AD%98%E5%82%A8%E6%9C%8D%E5%8A%A1">对接自己的存储服务</a></p>
</blockquote>
<p>在线<code>demo</code>的数据是存储在电脑本地的也就是<code>localStorage</code>当然你也可以存储到数据库中</p>
<h2>保存数据</h2>
<p>保存数据一般有两种做法一是让用户手动保存二是当画布上的数据改变后自动保存显然第二中体验更好一点</p>

View File

@@ -0,0 +1,39 @@
# 如何实现搜索替换
> 需要先注册 Search 插件
要实现搜索替换很简单,你只要先创建两个输入框,两个按钮,然后调用相关方法即可。
第一个输入框用于搜索,可以绑定一个回车事件,然后调用如下方法:
```js
mindMap.search.search(this.searchText, () => {
this.$refs.searchInputRef.focus()
})
```
`search`方法调用一次就会跳转到下一个匹配的节点,当搜索文本改变后再调用,默认会重新搜索从头开始。
`search`方法第二个参数是一个回调函数,当本次搜索完成,即在跳转到节点后调用,一般需要在这个回调函数里重新让你的输入框聚焦,因为激活节点会拿走焦点,所以你需要把焦点拿回来。
第二个输入框用于替换,替换支持单个替换和全部替换,需要注意的是要先在调用了`search`方法后才能调用这两个方法,单个替换只需要调用如下方法:
```js
mindMap.search.replace(this.replaceText, true)
```
第二个参数传`true`会在替换完成后自动跳转到下一个匹配的节点,这样可以进行连续替换。
要进行全部替换可以调用如下方法:
```js
mindMap.search.replaceAll(this.replaceText)
```
最后你可以通过监听`search_info_change`方法来获取匹配的节点数量和当前定位到的索引:
```js
mindMap.on('search_info_change', data => {
console.log('当前所在:'+ (data.currentIndex + 1), '匹配总数:' + data.total)
})
```

View File

@@ -0,0 +1,39 @@
<template>
<div>
<h1>如何实现搜索替换</h1>
<blockquote>
<p>需要先注册 Search 插件</p>
</blockquote>
<p>要实现搜索替换很简单你只要先创建两个输入框两个按钮然后调用相关方法即可</p>
<p>第一个输入框用于搜索可以绑定一个回车事件然后调用如下方法</p>
<pre class="hljs"><code>mindMap.search.search(<span class="hljs-built_in">this</span>.searchText, <span class="hljs-function">() =&gt;</span> {
<span class="hljs-built_in">this</span>.$refs.searchInputRef.focus()
})
</code></pre>
<p><code>search</code>方法调用一次就会跳转到下一个匹配的节点当搜索文本改变后再调用默认会重新搜索从头开始</p>
<p><code>search</code>方法第二个参数是一个回调函数当本次搜索完成即在跳转到节点后调用一般需要在这个回调函数里重新让你的输入框聚焦因为激活节点会拿走焦点所以你需要把焦点拿回来</p>
<p>第二个输入框用于替换替换支持单个替换和全部替换需要注意的是要先在调用了<code>search</code>方法后才能调用这两个方法单个替换只需要调用如下方法</p>
<pre class="hljs"><code>mindMap.search.replace(<span class="hljs-built_in">this</span>.replaceText, <span class="hljs-literal">true</span>)
</code></pre>
<p>第二个参数传<code>true</code>会在替换完成后自动跳转到下一个匹配的节点这样可以进行连续替换</p>
<p>要进行全部替换可以调用如下方法</p>
<pre class="hljs"><code>mindMap.search.replaceAll(<span class="hljs-built_in">this</span>.replaceText)
</code></pre>
<p>最后你可以通过监听<code>search_info_change</code>方法来获取匹配的节点数量和当前定位到的索引</p>
<pre class="hljs"><code>mindMap.on(<span class="hljs-string">&#x27;search_info_change&#x27;</span>, <span class="hljs-function"><span class="hljs-params">data</span> =&gt;</span> {
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">&#x27;当前所在&#x27;</span>+ (data.currentIndex + <span class="hljs-number">1</span>), <span class="hljs-string">&#x27;匹配总数&#x27;</span> + data.total)
})
</code></pre>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>

View File

@@ -231,7 +231,7 @@ copyNodeTree({}, node)
> v0.6.12+
`<p><span></span><p>`形式的节点富文本内容转换成`<br>`换行的文本。
`<p><span></span><p>`形式的节点富文本内容转换成`\n`换行的文本。
#### textToNodeRichTextWithWrap(html)

View File

@@ -163,7 +163,7 @@
<blockquote>
<p>v0.6.12+</p>
</blockquote>
<p><code>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;p&gt;</code><code>&lt;br&gt;</code></p>
<p><code>&lt;p&gt;&lt;span&gt;&lt;/span&gt;&lt;p&gt;</code><code>\n</code></p>
<h4>textToNodeRichTextWithWrap(html)</h4>
<blockquote>
<p>v0.6.12+</p>