vue-kityminder-ggg
Version:
百度脑图
397 lines (353 loc) • 16.2 kB
JavaScript
/**
* @fileOverview
*
* 文本输入支持
*
* @author: techird
* @copyright: Baidu FEX, 2014
*/
define(function(require, exports, module) {
require('../tool/innertext');
var Debug = require('../tool/debug');
var debug = new Debug('input');
// debugger
function InputRuntime() {
var fsm = this.fsm;
var minder = this.minder;
var hotbox = this.hotbox;
var receiver = this.receiver;
var receiverElement = receiver.element;
var isGecko = window.kity.Browser.gecko;
// setup everything to go
setupReciverElement();
setupFsm();
setupHotbox();
// expose editText()
this.editText = editText;
// listen the fsm changes, make action.
function setupFsm() {
// when jumped to input mode, enter
fsm.when('* -> input', enterInputMode);
// when exited, commit or exit depends on the exit reason
fsm.when('input -> *', function(exit, enter, reason) {
switch (reason) {
case 'input-cancel':
return exitInputMode();
case 'input-commit':
default:
return commitInputResult();
}
});
// lost focus to commit
receiver.onblur(function (e) {
if (fsm.state() == 'input') {
fsm.jump('normal', 'input-commit');
}
});
minder.on('beforemousedown', function () {
if (fsm.state() == 'input') {
fsm.jump('normal', 'input-commit');
}
});
minder.on('dblclick', function() {
// debugger
if (minder.getSelectedNode() && minder._status !== 'readonly') {
editText();
}
});
}
// let the receiver follow the current selected node position
function setupReciverElement() {
if (debug.flaged) {
receiverElement.classList.add('debug');
}
receiverElement.onmousedown = function(e) {
e.stopPropagation();
};
minder.on('layoutallfinish viewchange viewchanged selectionchange', function(e) {
// viewchange event is too frequenced, lazy it
if (e.type == 'viewchange' && fsm.state() != 'input') return;
updatePosition();
});
updatePosition();
}
// edit entrance in hotbox
function setupHotbox() {
hotbox.state('main').button({
position: 'center',
label: '编辑',
key: 'F2',
enable: function() {
return minder.queryCommandState('text') != -1;
},
action: editText
});
}
/**
* 增加对字体的鉴别,以保证用户在编辑状态ctrl/cmd + b/i所触发的加粗斜体与显示一致
* @editor Naixor
* @Date 2015-12-2
*/
// edit for the selected node
function editText() {
var node = minder.getSelectedNode();
if (!node) {
return;
}
var textContainer = receiverElement;
receiverElement.innerText = "";
if (node.getData('font-weight') === 'bold') {
var b = document.createElement('b');
textContainer.appendChild(b);
textContainer = b;
}
if (node.getData('font-style') === 'italic') {
var i = document.createElement('i');
textContainer.appendChild(i);
textContainer = i;
}
textContainer.innerText = minder.queryCommandValue('text');
if (isGecko) {
receiver.fixFFCaretDisappeared();
};
fsm.jump('input', 'input-request');
receiver.selectAll();
}
/**
* 增加对字体的鉴别,以保证用户在编辑状态ctrl/cmd + b/i所触发的加粗斜体与显示一致
* @editor Naixor
* @Date 2015-12-2
*/
function enterInputMode() {
var node = minder.getSelectedNode();
if (node) {
var fontSize = node.getData('font-size') || node.getStyle('font-size');
receiverElement.style.fontSize = fontSize + 'px';
receiverElement.style.minWidth = 0;
receiverElement.style.minWidth = receiverElement.clientWidth + 'px';
receiverElement.style.fontWeight = node.getData('font-weight') || '';
receiverElement.style.fontStyle = node.getData('font-style') || '';
receiverElement.classList.add('input');
receiverElement.focus();
}
}
/**
* 按照文本提交操作处理
* @Desc: 从其他节点复制文字到另一个节点时部分浏览器(chrome)会自动包裹一个span标签,这样试用一下逻辑出来的就不是text节点二是span节点因此导致undefined的情况发生
* @Warning: 下方代码使用[].slice.call来将HTMLDomCollection处理成为Array,ie8及以下会有问题
* @Editor: Naixor
* @Date: 2015.9.16
*/
function commitInputText (textNodes) {
var text = '';
var TAB_CHAR = '\t',
ENTER_CHAR = '\n',
STR_CHECK = /\S/,
SPACE_CHAR = '\u0020',
// 针对FF,SG,BD,LB,IE等浏览器下SPACE的charCode存在为32和160的情况做处理
SPACE_CHAR_REGEXP = new RegExp('(\u0020|' + String.fromCharCode(160) + ')'),
BR = document.createElement('br');
var isBold = false,
isItalic = false;
for (var str,
_divChildNodes,
space_l, space_num, tab_num,
i = 0, l = textNodes.length; i < l; i++) {
str = textNodes[i];
switch (Object.prototype.toString.call(str)) {
// 正常情况处理
case '[object HTMLBRElement]': {
text += ENTER_CHAR;
break;
}
case '[object Text]': {
// SG下会莫名其妙的加上 影响后续判断,干掉!
/**
* FF下的wholeText会导致如下问题:
* |123| -> 在一个节点中输入一段字符,此时TextNode为[#Text 123]
* 提交并重新编辑,在后面追加几个字符
* |123abc| -> 此时123为一个TextNode为[#Text 123, #Text abc],但是对这两个任意取值wholeText均为全部内容123abc
* 上述BUG仅存在在FF中,故将wholeText更改为textContent
*/
str = str.textContent.replace(" ", " ");
if (!STR_CHECK.test(str)) {
space_l = str.length;
while (space_l--) {
if (SPACE_CHAR_REGEXP.test(str[space_l])) {
text += SPACE_CHAR;
} else if (str[space_l] === TAB_CHAR) {
text += TAB_CHAR;
}
}
} else {
text += str;
}
break;
}
// ctrl + b/i 会给字体加上<b>/<i>标签来实现黑体和斜体
case '[object HTMLElement]': {
switch (str.nodeName) {
case "B": {
isBold = true;
break;
}
case "I": {
isItalic = true;
break;
}
default: {}
}
[].splice.apply(textNodes, [i, 1].concat([].slice.call(str.childNodes)));
l = textNodes.length;
i--;
break;
}
// 被增加span标签的情况会被处理成正常情况并会推交给上面处理
case '[object HTMLSpanElement]': {
[].splice.apply(textNodes, [i, 1].concat([].slice.call(str.childNodes)));
l = textNodes.length;
i--;
break;
}
// 若标签为image标签,则判断是否为合法url,是将其加载进来
case '[object HTMLImageElement]': {
if (str.src) {
if (/http(|s):\/\//.test(str.src)) {
minder.execCommand("Image", str.src, str.alt);
} else {
// data:image协议情况
}
};
break;
}
// 被增加div标签的情况会被处理成正常情况并会推交给上面处理
case '[object HTMLDivElement]': {
_divChildNodes = [];
for (var di = 0, l = str.childNodes.length; di < l; di++) {
_divChildNodes.push(str.childNodes[di]);
}
_divChildNodes.push(BR);
[].splice.apply(textNodes, [i, 1].concat(_divChildNodes));
l = textNodes.length;
i--;
break;
}
default: {
if (str && str.childNodes.length) {
_divChildNodes = [];
for (var di = 0, l = str.childNodes.length; di < l; di++) {
_divChildNodes.push(str.childNodes[di]);
}
_divChildNodes.push(BR);
[].splice.apply(textNodes, [i, 1].concat(_divChildNodes));
l = textNodes.length;
i--;
} else {
if (str && str.textContent !== undefined) {
text += str.textContent;
} else {
text += "";
}
}
// // 其他带有样式的节点被粘贴进来,则直接取textContent,若取不出来则置空
}
}
};
text = text.replace(/^\n*|\n*$/g, '');
text = text.replace(new RegExp('(\n|\r|\n\r)(\u0020|' + String.fromCharCode(160) + '){4}', 'g'), '$1\t');
minder.getSelectedNode().setText(text);
if (isBold) {
minder.queryCommandState('bold') || minder.execCommand('bold');
} else {
minder.queryCommandState('bold') && minder.execCommand('bold');
}
if (isItalic) {
minder.queryCommandState('italic') || minder.execCommand('italic');
} else {
minder.queryCommandState('italic') && minder.execCommand('italic');
}
exitInputMode();
return text;
}
/**
* 判断节点的文本信息是否是
* @Desc: 从其他节点复制文字到另一个节点时部分浏览器(chrome)会自动包裹一个span标签,这样使用以下逻辑出来的就不是text节点二是span节点因此导致undefined的情况发生
* @Notice: 此处逻辑应该拆分到 kityminder-core/core/data中去,单独增加一个对某个节点importJson的事件
* @Editor: Naixor
* @Date: 2015.9.16
*/
function commitInputNode(node, text) {
try {
minder.decodeData('text', text).then(function(json) {
function importText(node, json, minder) {
var data = json.data;
node.setText(data.text || '');
var childrenTreeData = json.children || [];
for (var i = 0; i < childrenTreeData.length; i++) {
var childNode = minder.createNode(null, node);
importText(childNode, childrenTreeData[i], minder);
}
return node;
}
importText(node, json, minder);
minder.fire("contentchange");
minder.getRoot().renderTree();
minder.layout(300);
});
} catch (e) {
minder.fire("contentchange");
minder.getRoot().renderTree();
// 无法被转换成脑图节点则不处理
if (e.toString() !== 'Error: Invalid local format') {
throw e;
}
}
}
function commitInputResult() {
/**
* @Desc: 进行如下处理:
* 根据用户的输入判断是否生成新的节点
* fix #83 https://github.com/fex-team/kityminder-editor/issues/83
* @Editor: Naixor
* @Date: 2015.9.16
*/
var textNodes = [].slice.call(receiverElement.childNodes);
/**
* @Desc: 增加setTimeout的原因:ie下receiverElement.innerHTML=""会导致后
* 面commitInputText中使用textContent报错,不要问我什么原因!
* @Editor: Naixor
* @Date: 2015.12.14
*/
setTimeout(function () {
// 解决过大内容导致SVG窜位问题
receiverElement.innerHTML = "";
}, 0);
var node = minder.getSelectedNode();
textNodes = commitInputText(textNodes);
commitInputNode(node, textNodes);
if (node.type == 'root') {
var rootText = minder.getRoot().getText();
minder.fire('initChangeRoot', {text: rootText});
}
}
function exitInputMode() {
receiverElement.classList.remove('input');
receiver.selectAll();
}
function updatePosition() {
var planed = updatePosition;
var focusNode = minder.getSelectedNode();
if (!focusNode) return;
if (!planed.timer) {
planed.timer = setTimeout(function() {
var box = focusNode.getRenderBox('TextRenderer');
receiverElement.style.left = Math.round(box.x) + 'px';
receiverElement.style.top = (debug.flaged ? Math.round(box.bottom + 30) : Math.round(box.y)) + 'px';
//receiverElement.focus();
planed.timer = 0;
});
}
}
}
return module.exports = InputRuntime;
});