前段时间,去试用了下processon 上的脑图功能,发现人家这块确实已经做的好强大了。而且他的节点竟然还可以支持单独某个文本的颜色字体的设置,这个可是连xmind,本身都没有实现的功能的。所以想着学习下人家的实现看看是否能够借鉴到我们的平台上来。
现实很残酷
看了下process on的实现, 发现它跟百度的kityminder差别还是很大的,因为他的每个节点其实都是div的方式实现的,但是百度脑图的节点其实一个个都是svg。 我们可以看下。
ProcessOn
百度脑图
所以这块的参考意义就真的不大了,目前是没有能力去改动到脑图的底层内容的,所以还是只能顺着现有的框架改是最方便快捷的了。
具体变更点
要让原本的antd组件的 TextArea
支持富文本内容是不太可能的,所以我们这里更多还是需要引入一个富文本的组件才可以,这里我们直接就考虑用 braft-editor , 主要是这个组件在我们其他项目中用过,所以用的其实会比较多一些了。
现在我们要考虑一个问题,新的富文本组件需要满足哪些我们特殊的需求,然后我们需要考虑如何进行改造。
这里关键的因素其实就是我们需要做如下的内容 富文本转换后的html的内容,如何让 kityminder-core
识别到并且针对这个文本做相应的颜色标识出来。我们发现富文本转换后的html样式如果带有颜色的情况下基本上这样子的内容: <span style="color:#cccccc">太阳</span>
但是我们需要将这样子的文本内容转换成类似于 <text fill="#cccccc">太阳</text>
这样子的内容。那直接用一些正则替换的方式不就可以了吗?
问题其实那么简单,最关键的一个问题先出来了,我们举个栗子来说明下。
svg的一个文本标签,是默认就进行换行的我们可以看下:
但是如果我们通过富文本进行颜色以后如 下图所示
这样子的一个富文本内容其实应该要放到同一行, 但是对于svg来说由于需要将这两个文本变成两个svg, 同时还要在同一行,这个就是我们需要去解决的问题了。
另外: 替换为富文本的情况还需要满足一下的内容:
- 仅支持文本颜色修改,暂时不支持其他的富文本内容:字体大小,图片,音频等内容。
- 需要响应一些特殊按键操作
- 回车不是换行,而是完成文本输出
- 通过shift+enter进行换行
- 选中文本的时候才显示出工具栏的颜色修改按钮
解决
最重点是要解决富文本内容转换到svg节点的逻辑,这里我们需要改动到的是 text 这块的代码
var textArrTemp = nodeText ? nodeText.split(/\r\n|\n/) : [" "];
// 这里存储包含有富文本的内容
var textArr = [];
for (var index in textArrTemp) {
if (textArrTemp[index].indexOf("<span style") !== -1 || textArrTemp[index].indexOf("</span>") !== -1) {
var newArray = textArrTemp[index].split(/<span\s|<\/span>/)
if (newArray.length > 1) {
newArray = newArray.filter(function (item) {
return item != "";
});
}
for (var i in newArray) {
textArr.push(newArray[i]);
}
} else {
textArr.push(textArrTemp[index]);
}
}
var originTextArr = nodeText ? nodeText.split(/\r\n|\n/) : [" "];
for (var index in originTextArr) {
// 这个是富文本的标签切割了,需要提取出真正的内容出来
if (originTextArr[index].indexOf("<span style") !== -1 || originTextArr[index].indexOf("</span>") !== -1) {
originTextArr[index] = originTextArr[index].replace(/<span style(.*?)>/g, "").replace(/<\/span>/g, "");
}
}
// 这里将带有<span>格式的做过滤将颜色跟实际的文本做一个map存储起来。
for (var i = 0; i < textArr.length; i++) {
if (textArr[i].indexOf("style=") !== -1) {
var m = textArr[i].match(/style="color:(.*?)">/);
var n = textArr[i].match(/>(.*)/);
colorMap[i] = {
color: m[1],
text: n[1]
};
}
}
以上这块就是针对文本的预处理的情况。下来还要到渲染处
return function () {
var isContainOriginText = function (originTextArr, text) {
for (var i in originTextArr) {
if (originTextArr[i].indexOf(text) === 0) {
return true;
}
}
return false
}
var line = 0;
var keys = Object.keys(colorMap);
textGroup.eachItem(function (i, textShape) {
// 这里要判断自己在不在里面 同时要判断我的上一个节点在不在里面
var y;
// 如果这个节点是在colorMap中,说明他是一个颜色标签的, 另外 还需要把颜色标签后面的文本也放到同一行去。
if (i in colorMap && i === 0 || i !== 0 && i - 1 in colorMap && !isContainOriginText(originTextArr, textShape.getContent()) || i !== 0 && i in colorMap && !isContainOriginText(originTextArr, textShape.getContent())) {
y = yStart + i * fontSize * lineHeight;
var lastTextShape = textGroup.getItem(i == 0 ? 0 : i - 1);
var lastBbox = textGroup.getItem(i == 0 ? 0 : i - 1).getBoundaryBox();
textShape.setX(i === 0 ? 0 : lastBbox.width == 0 ? (parseFloat(lastTextShape.getAttr("x")) + 10) : lastBbox.x + lastBbox.width);
textShape.setY(i === 0 ? y : lastBbox.y == 0 ? lastTextShape.getAttr('y') : lastBbox.y);
if (i in colorMap) {
textShape.setAttr("fill", colorMap[i].color);
} else {
textShape.setAttr("fill", "");
}
if (i === 0) {
line++;
}
rBox = rBox.merge(new kity.Box(0, textShape.getY(), textShape.getX() + textShape.getBoundaryBox().width || 1, fontSize));
} else {
y = yStart + line * fontSize * lineHeight;
textShape.setY(y);
textShape.setX(0);
textShape.setAttr("fill", i in colorMap ? colorMap[i].color : "");
var bbox = textShape.getBoundaryBox();
line++;
rBox = rBox.merge(new kity.Box(0, y, bbox.height && bbox.width || 1, fontSize));
}
});
var nBox = new kity.Box(r(rBox.x), r(rBox.y), r(rBox.width), r(rBox.height));
node._currentTextGroupBox = nBox;
return nBox;
};
逻辑有点小复杂了,但是其实就是针对svg做设置颜色,同时需要处理好,如果是富文本且为换行的话,需要通过x, y的属性去设置svg的位置。这样子就可以做到svg在同一行的情况了这里主要 通过上一个box
x坐标以及宽度,然后算出当前的svg的box
应该放置的位置的位置
至于富文本其他功能就比较简单了,很多都是通过监听document
的事件来满足一些特殊的需求即可了。