diff --git a/packages/client/global.d.ts b/packages/client/global.d.ts index dfafdb38..c449ed46 100644 --- a/packages/client/global.d.ts +++ b/packages/client/global.d.ts @@ -1,6 +1,6 @@ interface Window { - // 思维导图 - MindElixir: any; // drawio 绘图 GraphViewer: any; + // 百度脑图 + kityminder: any; } diff --git a/packages/client/package.json b/packages/client/package.json index 7d7514ed..208c035e 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -62,6 +62,7 @@ "dompurify": "^2.3.5", "interactjs": "^1.10.11", "katex": "^0.15.2", + "kity": "^2.0.4", "lib0": "^0.2.47", "lowlight": "^2.5.0", "markdown-it": "^12.3.2", @@ -88,7 +89,6 @@ "react-split-pane": "^0.1.92", "scroll-into-view-if-needed": "^2.2.29", "swr": "^1.2.0", - "tilg": "^0.1.1", "timeago.js": "^4.0.2", "tippy.js": "^6.3.7", "toggle-selection": "^1.0.6", diff --git a/packages/client/src/thirtypart/kityminder/hotbox/expose.js b/packages/client/src/thirtypart/kityminder/hotbox/expose.js new file mode 100644 index 00000000..337a3b3a --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/hotbox/expose.js @@ -0,0 +1,4 @@ +/* eslint-disable */ +define('expose', function (require, exports, module) { + module.exports = window.HotBox = require('./hotbox'); +}); diff --git a/packages/client/src/thirtypart/kityminder/hotbox/hotbox.js b/packages/client/src/thirtypart/kityminder/hotbox/hotbox.js new file mode 100644 index 00000000..b9c04207 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/hotbox/hotbox.js @@ -0,0 +1,647 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var key = require('./key'); + var KeyControl = require('./keycontrol'); + + /**** Dom Utils ****/ + function createElement(name) { + return document.createElement(name); + } + + function setElementAttribute(element, name, value) { + element.setAttribute(name, value); + } + + function getElementAttribute(element, name) { + return element.getAttribute(name); + } + + function addElementClass(element, name) { + element.classList.add(name); + } + + function removeElementClass(element, name) { + element.classList.remove(name); + } + + function appendChild(parent, child) { + parent.appendChild(child); + } + /*******************/ + + var IDLE = (HotBox.STATE_IDLE = 'idle'); + var div = 'div'; + + /** + * Simple Formatter + */ + function format(template, args) { + if (typeof args != 'object') { + args = [].slice.apply(arguments, 1); + } + return String(template).replace(/\{(\w+)\}/g, function (match, name) { + return args[name] || match; + }); + } + + /** + * Hot Box Class + */ + function HotBox($container) { + if (typeof $container == 'string') { + $container = document.querySelector($container); + } + if (!$container || !($container instanceof HTMLElement)) { + throw new Error('No container or not invalid container for hot box'); + } + + // 创建 HotBox Dom 解构 + var $hotBox = createElement(div); + addElementClass($hotBox, 'hotbox'); + appendChild($container, $hotBox); + + // 保存 Dom 解构和父容器 + this.$element = $hotBox; + this.$container = $container; + + // 标示是否是输入法状态 + this.isIME = false; + + /** + * @Desc: 增加一个browser用于判断浏览器类型,方便解决兼容性问题 + * @Editor: Naixor + * @Date: 2015.09.14 + */ + this.browser = { + sg: /se[\s\S]+metasr/.test(navigator.userAgent.toLowerCase()), + }; + + /* + * added by zhangbobell + * 2015.09.22 + * 增加父状态机,以解决在父 FSM 下状态控制的问题,最好的解决办法是增加一个函数队列 + * 将其中的函数一起执行。//TODO + * */ + this._parentFSM = {}; + + // 记录位置 + this.position = {}; + + // 已定义的状态(string => HotBoxState) + var _states = {}; + + // 主状态(HotBoxState) + var _mainState = null; + + // 当前状态(HotBoxState) + var _currentState = IDLE; + + // 当前状态堆栈 + var _stateStack = []; + + // 实例引用 + var _this = this; + var _controler; + + /** + * Controller: { + * constructor(hotbox: HotBox), + * active: () => void + * } + */ + function _control(Controller) { + if (_controler) { + _controler.active(); + return; + } + + Controller = Controller || KeyControl; + + _controler = new Controller(_this); + _controler.active(); + + $hotBox.onmousedown = function (e) { + e.stopPropagation(); + e.preventDefault(); + }; + + return _this; + } + + function _dispatchKey(e) { + var type = e.type.toLowerCase(); + e.keyHash = key.hash(e); + e.isKey = function (keyExpression) { + if (!keyExpression) return false; + var expressions = keyExpression.split(/\s*\|\s*/); + while (expressions.length) { + if (e.keyHash == key.hash(expressions.shift())) return true; + } + return false; + }; + e[type] = true; + // Boot: keyup and activeKey pressed on IDLE, active main state. + if (e.keyup && _this.activeKey && e.isKey(_this.activeKey) && _currentState == IDLE && _mainState) { + _activeState('main', { + x: $container.clientWidth / 2, + y: $container.clientHeight / 2, + }); + return; + } + var handleState = _currentState == IDLE ? _mainState : _currentState; + if (handleState) { + var handleResult = handleState.handleKeyEvent(e); + if (typeof _this.onkeyevent == 'function') { + e.handleResult = handleResult; + _this.onkeyevent(e, handleResult); + } + return handleResult; + } + return null; + } + + function _addState(name) { + if (!name) return _currentState; + if (name == IDLE) { + throw new Error('Can not define or use the `idle` state.'); + } + _states[name] = _states[name] || new HotBoxState(this, name); + if (name == 'main') { + _mainState = _states[name]; + } + return _states[name]; + } + + function _activeState(name, position) { + _this.position = position; + + // 回到 IDLE + if (name == IDLE) { + if (_currentState != IDLE) { + _stateStack.shift().deactive(); + _stateStack = []; + } + _currentState = IDLE; + } + // 回退一个状态 + else if (name == 'back') { + if (_currentState != IDLE) { + _currentState.deactive(); + _stateStack.shift(); + _currentState = _stateStack[0]; + if (_currentState) { + _currentState.active(); + } else { + _currentState = 'idle'; + } + } + } + // 切换到具体状态 + else { + if (_currentState != IDLE) { + _currentState.deactive(); + } + var newState = _states[name]; + _stateStack.unshift(newState); + if (typeof _this.position == 'function') { + position = _this.position(position); + } + newState.active(position); + _currentState = newState; + } + } + + function setParentFSM(fsm) { + _this._parentFSM = fsm; + } + + function getParentFSM() { + return _this._parentFSM; + } + + this.control = _control; + this.state = _addState; + this.active = _activeState; + this.dispatch = _dispatchKey; + this.setParentFSM = setParentFSM; + this.getParentFSM = getParentFSM; + this.activeKey = 'space'; + this.actionKey = 'space'; + } + + /** + * 表示热盒某个状态,包含这些状态需要的 Dom 对象 + */ + function HotBoxState(hotBox, stateName) { + var BUTTON_SELECTED_CLASS = 'selected'; + var BUTTON_PRESSED_CLASS = 'pressed'; + var STATE_ACTIVE_CLASS = 'active'; + + // 状态容器 + var $state = createElement(div); + + // 四种可见的按钮容器 + var $center = createElement(div); + var $ring = createElement(div); + var $ringShape = createElement('div'); + var $top = createElement(div); + var $bottom = createElement(div); + + // 添加 CSS 类 + addElementClass($state, 'state'); + addElementClass($state, stateName); + addElementClass($center, 'center'); + addElementClass($ring, 'ring'); + addElementClass($ringShape, 'ring-shape'); + addElementClass($top, 'top'); + addElementClass($bottom, 'bottom'); + + // 摆放容器 + appendChild(hotBox.$element, $state); + appendChild($state, $ringShape); + appendChild($state, $center); + appendChild($state, $ring); + appendChild($state, $top); + appendChild($state, $bottom); + + // 记住状态名称 + this.name = stateName; + + // 五种按钮:中心,圆环,上栏,下栏,幕后 + var buttons = { + center: null, + ring: [], + top: [], + bottom: [], + behind: [], + }; + var allButtons = []; + var selectedButton = null; + var pressedButton = null; + + var stateActived = false; + // 布局,添加按钮后,标记需要布局 + var needLayout = true; + + function layout() { + var radius = buttons.ring.length * 15; + layoutRing(radius); + layoutTop(radius); + layoutBottom(radius); + indexPosition(); + needLayout = false; + + function layoutRing(radius) { + var ring = buttons.ring; + var step = (2 * Math.PI) / ring.length; + + if (buttons.center) { + buttons.center.indexedPosition = [0, 0]; + } + + $ringShape.style.marginLeft = $ringShape.style.marginTop = -radius + 'px'; + $ringShape.style.width = $ringShape.style.height = radius + radius + 'px'; + + var $button, angle, x, y; + for (var i = 0; i < ring.length; i++) { + $button = ring[i].$button; + angle = step * i - Math.PI / 2; + x = radius * Math.cos(angle); + y = radius * Math.sin(angle); + ring[i].indexedPosition = [x, y]; + $button.style.left = x + 'px'; + $button.style.top = y + 'px'; + } + } + + function layoutTop(radius) { + var xOffset = -$top.clientWidth / 2; + var yOffset = -radius * 2 - $top.clientHeight / 2; + $top.style.marginLeft = xOffset + 'px'; + $top.style.marginTop = yOffset + 'px'; + buttons.top.forEach(function (topButton) { + var $button = topButton.$button; + topButton.indexedPosition = [xOffset + $button.offsetLeft + $button.clientWidth / 2, yOffset]; + }); + } + function layoutBottom(radius) { + var xOffset = -$bottom.clientWidth / 2; + var yOffset = radius * 2 - $bottom.clientHeight / 2; + $bottom.style.marginLeft = xOffset + 'px'; + $bottom.style.marginTop = yOffset + 'px'; + buttons.bottom.forEach(function (bottomButton) { + var $button = bottomButton.$button; + bottomButton.indexedPosition = [xOffset + $button.offsetLeft + $button.clientWidth / 2, yOffset]; + }); + } + function indexPosition() { + var positionedButtons = allButtons.filter(function (button) { + return button.indexedPosition; + }); + + positionedButtons.forEach(findNeightbour); + + function findNeightbour(button) { + var neighbor = {}; + var coef = 0; + var minCoef = {}; + var homePosition = button.indexedPosition; + var candidatePosition, dx, dy, ds; + var possible, dir; + var abs = Math.abs; + + positionedButtons.forEach(function (candidate) { + if (button == candidate) return; + + candidatePosition = candidate.indexedPosition; + + possible = []; + + dx = candidatePosition[0] - homePosition[0]; + dy = candidatePosition[1] - homePosition[1]; + ds = Math.sqrt(dx * dx + dy * dy); + + if (abs(dx) > 2) { + possible.push(dx > 0 ? 'right' : 'left'); + possible.push(ds + abs(dy)); // coef for right/left neighbor + } + if (abs(dy) > 2) { + possible.push(dy > 0 ? 'down' : 'up'); + possible.push(ds + abs(dx)); // coef for up/down neighbor + } + + while (possible.length) { + dir = possible.shift(); + coef = possible.shift(); + if (!neighbor[dir] || coef < minCoef[dir]) { + neighbor[dir] = candidate; + minCoef[dir] = coef; + } + } + }); + + button.neighbor = neighbor; + } + } + } + + function alwaysEnable() { + return true; + } + + // 为状态创建按钮 + function createButton(option) { + var $button = createElement(div); + addElementClass($button, 'button'); + var render = option.render || defaultButtonRender; + $button.innerHTML = render(format, option); + + switch (option.position) { + case 'center': + appendChild($center, $button); + break; + case 'ring': + appendChild($ring, $button); + break; + case 'top': + appendChild($top, $button); + break; + case 'bottom': + appendChild($bottom, $button); + break; + } + + return { + action: option.action, + enable: option.enable || alwaysEnable, + beforeShow: option.beforeShow, + key: option.key, + next: option.next, + label: option.label, + data: option.data || null, + $button: $button, + }; + } + + // 默认按钮渲染 + function defaultButtonRender(format, option) { + return format('{label}{key}', { + label: option.label, + key: option.key && option.key.split('|')[0], + }); + } + + // 为当前状态添加按钮 + this.button = function (option) { + var button = createButton(option); + if (option.position == 'center') { + buttons.center = button; + } else if (buttons[option.position]) { + buttons[option.position].push(button); + } + allButtons.push(button); + needLayout = true; + }; + + function activeState(position) { + position = position || { + x: hotBox.$container.clientWidth / 2, + y: hotBox.$container.clientHeight / 2, + }; + if (position) { + $state.style.left = position.x + 'px'; + $state.style.top = position.y + 'px'; + } + allButtons.forEach(function (button) { + var $button = button.$button; + if ($button) { + $button.classList[button.enable() ? 'add' : 'remove']('enabled'); + } + + if (button.beforeShow) { + button.beforeShow(); + } + }); + addElementClass($state, STATE_ACTIVE_CLASS); + if (needLayout) { + layout(); + } + if (!selectedButton) { + select(buttons.center || buttons.ring[0] || buttons.top[0] || buttons.bottom[0]); + } + stateActived = true; + } + + function deactiveState() { + removeElementClass($state, STATE_ACTIVE_CLASS); + select(null); + stateActived = false; + } + + // 激活当前状态 + this.active = activeState; + + // 反激活当前状态 + this.deactive = deactiveState; + + function press(button) { + if (pressedButton && pressedButton.$button) { + removeElementClass(pressedButton.$button, BUTTON_PRESSED_CLASS); + } + pressedButton = button; + if (pressedButton && pressedButton.$button) { + addElementClass(pressedButton.$button, BUTTON_PRESSED_CLASS); + } + } + + function select(button) { + if (selectedButton && selectedButton.$button) { + if (selectedButton.$button) { + removeElementClass(selectedButton.$button, BUTTON_SELECTED_CLASS); + } + } + selectedButton = button; + if (selectedButton && selectedButton.$button) { + addElementClass(selectedButton.$button, BUTTON_SELECTED_CLASS); + } + } + + $state.onmouseup = function (e) { + if (e.button) return; + var target = e.target; + while (target && target != $state) { + if (target.classList.contains('button')) { + allButtons.forEach(function (button) { + if (button.$button == target) { + execute(button); + } + }); + } + target = target.parentNode; + } + }; + + this.handleKeyEvent = function (e) { + var handleResult = null; + /** + * @Desc: 搜狗浏览器下esc只触发keyup,因此做兼容性处理 + * @Editor: Naixor + * @Date: 2015.09.14 + */ + if (hotBox.browser.sg) { + if (e.isKey('esc')) { + if (pressedButton) { + // 若存在已经按下的按钮,则取消操作 + if (!e.isKey(pressedButton.key)) { + // the button is not esc + press(null); + } + } else { + hotBox.active('back', hotBox.position); + } + return 'back'; + } + } + if (e.keydown || (hotBox.isIME && e.keyup)) { + allButtons.forEach(function (button) { + if (button.enable() && e.isKey(button.key)) { + if (stateActived || hotBox.hintDeactiveMainState) { + select(button); + press(button); + handleResult = 'buttonpress'; + + // 如果是 keyup 事件触发的,因为没有后续的按键事件,所以就直接执行 + if (e.keyup) { + execute(button); + handleResult = 'execute'; + return handleResult; + } + } else { + execute(button); + handleResult = 'execute'; + } + e.preventDefault(); + e.stopPropagation(); + if (!stateActived && hotBox.hintDeactiveMainState) { + hotBox.active(stateName, hotBox.position); + } + } + }); + if (stateActived) { + if (e.isKey('esc')) { + if (pressedButton) { + // 若存在已经按下的按钮,则取消操作 + if (!e.isKey(pressedButton.key)) { + // the button is not esc + press(null); + } + } else { + hotBox.active('back', hotBox.position); + } + return 'back'; + } + ['up', 'down', 'left', 'right'].forEach(function (dir) { + if (!e.isKey(dir)) return; + if (!selectedButton) { + select(buttons.center || buttons.ring[0] || buttons.top[0] || buttons.bottom[0]); + return; + } + var neighbor = selectedButton.neighbor[dir]; + while (neighbor && !neighbor.enable()) { + neighbor = neighbor.neighbor[dir]; + } + if (neighbor) { + select(neighbor); + } + handleResult = 'navigate'; + }); + + // 若是由 keyup 触发的,则直接执行选中的按钮 + if (e.isKey('space') && e.keyup) { + execute(selectedButton); + e.preventDefault(); + e.stopPropagation(); + handleResult = 'execute'; + } else if (e.isKey('space') && selectedButton) { + press(selectedButton); + handleResult = 'buttonpress'; + } else if (pressedButton && pressedButton != selectedButton) { + press(null); + handleResult = 'selectcancel'; + } + } + } else if (e.keyup && (stateActived || !hotBox.hintDeactiveMainState)) { + if (pressedButton) { + if ((e.isKey('space') && selectedButton == pressedButton) || e.isKey(pressedButton.key)) { + execute(pressedButton); + e.preventDefault(); + e.stopPropagation(); + handleResult = 'execute'; + } + } + } + + /* + * Add by zhangbobell 2015.09.06 + * 增加了下面这一个判断因为 safari 下开启输入法后,所有的 keydown 的 keycode 都为 229, + * 只能以 keyup 的 keycode 进行判断 + * */ + hotBox.isIME = e.keyCode == 229 && e.keydown; + + return handleResult; + }; + + function execute(button) { + if (button) { + if (!button.enable || button.enable()) { + if (button.action) button.action(button); + hotBox.active(button.next || IDLE, hotBox.position); + } + press(null); + select(null); + } + } + } + + module.exports = HotBox; +}); diff --git a/packages/client/src/thirtypart/kityminder/hotbox/key.js b/packages/client/src/thirtypart/kityminder/hotbox/key.js new file mode 100644 index 00000000..8fa15f27 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/hotbox/key.js @@ -0,0 +1,62 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var keymap = require('./keymap'); + + var CTRL_MASK = 0x1000; + var ALT_MASK = 0x2000; + var SHIFT_MASK = 0x4000; + + function hash(unknown) { + if (typeof unknown == 'string') { + return hashKeyExpression(unknown); + } + return hashKeyEvent(unknown); + } + function is(a, b) { + return a && b && hash(a) == hash(b); + } + exports.hash = hash; + exports.is = is; + + function hashKeyEvent(keyEvent) { + var hashCode = 0; + if (keyEvent.ctrlKey || keyEvent.metaKey) { + hashCode |= CTRL_MASK; + } + if (keyEvent.altKey) { + hashCode |= ALT_MASK; + } + if (keyEvent.shiftKey) { + hashCode |= SHIFT_MASK; + } + // Shift, Control, Alt KeyCode ignored. + if ([16, 17, 18, 91].indexOf(keyEvent.keyCode) == -1) { + hashCode |= keyEvent.keyCode; + } + return hashCode; + } + + function hashKeyExpression(keyExpression) { + var hashCode = 0; + keyExpression + .toLowerCase() + .split(/\s*\+\s*/) + .forEach(function (name) { + switch (name) { + case 'ctrl': + case 'cmd': + hashCode |= CTRL_MASK; + break; + case 'alt': + hashCode |= ALT_MASK; + break; + case 'shift': + hashCode |= SHIFT_MASK; + break; + default: + hashCode |= keymap[name]; + } + }); + return hashCode; + } +}); diff --git a/packages/client/src/thirtypart/kityminder/hotbox/keycontrol.js b/packages/client/src/thirtypart/kityminder/hotbox/keycontrol.js new file mode 100644 index 00000000..e16aff13 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/hotbox/keycontrol.js @@ -0,0 +1,70 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var key = require('./key'); + var FOCUS_CLASS = 'hotbox-focus'; + var RECEIVER_CLASS = 'hotbox-key-receiver'; + + function KeyControl(hotbox) { + var _this = this; + var _receiver; + var _actived = true; + var _receiverIsSelfCreated = false; + var $container = hotbox.$container; + + _createReceiver(); + _bindReceiver(); + _bindContainer(); + _active(); + + function _createReceiver() { + _receiver = document.createElement('input'); + _receiver.classList.add(RECEIVER_CLASS); + $container.appendChild(_receiver); + _receiverIsSelfCreated = true; + } + + function _bindReceiver() { + _receiver.onkeyup = _handle; + _receiver.onkeypress = _handle; + _receiver.onkeydown = _handle; + _receiver.onfocus = _active; + _receiver.onblur = _deactive; + if (_receiverIsSelfCreated) { + _receiver.oninput = function (e) { + _receiver.value = null; + }; + } + } + + function _bindContainer() { + $container.onmousedown = function (e) { + _active(); + e.preventDefault(); + }; + } + + function _handle(keyEvent) { + if (!_actived) return; + hotbox.dispatch(keyEvent); + } + + function _active() { + _receiver.select(); + _receiver.focus(); + _actived = true; + $container.classList.add(FOCUS_CLASS); + } + + function _deactive() { + _receiver.blur(); + _actived = false; + $container.classList.remove(FOCUS_CLASS); + } + + this.handle = _handle; + this.active = _active; + this.deactive = _deactive; + } + + module.exports = KeyControl; +}); diff --git a/packages/client/src/thirtypart/kityminder/hotbox/keymap.js b/packages/client/src/thirtypart/kityminder/hotbox/keymap.js new file mode 100644 index 00000000..fd2720da --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/hotbox/keymap.js @@ -0,0 +1,82 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var keymap = { + 'Shift': 16, + 'Control': 17, + 'Alt': 18, + 'CapsLock': 20, + + 'BackSpace': 8, + 'Tab': 9, + 'Enter': 13, + 'Esc': 27, + 'Space': 32, + + 'PageUp': 33, + 'PageDown': 34, + 'End': 35, + 'Home': 36, + + 'Insert': 45, + + 'Left': 37, + 'Up': 38, + 'Right': 39, + 'Down': 40, + + 'Direction': { + 37: 1, + 38: 1, + 39: 1, + 40: 1, + }, + + 'Delete': 46, + + 'NumLock': 144, + + 'Cmd': 91, + 'CmdFF': 224, + 'F1': 112, + 'F2': 113, + 'F3': 114, + 'F4': 115, + 'F5': 116, + 'F6': 117, + 'F7': 118, + 'F8': 119, + 'F9': 120, + 'F10': 121, + 'F11': 122, + 'F12': 123, + + '`': 192, + '=': 187, + '-': 189, + + '/': 191, + '.': 190, + }; + + // 小写适配 + for (var key in keymap) { + if (keymap.hasOwnProperty(key)) { + keymap[key.toLowerCase()] = keymap[key]; + } + } + var aKeyCode = 65; + var aCharCode = 'a'.charCodeAt(0); + + // letters + 'abcdefghijklmnopqrstuvwxyz'.split('').forEach(function (letter) { + keymap[letter] = aKeyCode + (letter.charCodeAt(0) - aCharCode); + }); + + // numbers + var n = 9; + do { + keymap[n.toString()] = n + 48; + } while (n--); + + module.exports = keymap; +}); diff --git a/packages/client/src/thirtypart/kityminder/index.ts b/packages/client/src/thirtypart/kityminder/index.ts new file mode 100644 index 00000000..07a6c01a --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/index.ts @@ -0,0 +1,39 @@ +export const load = async (): Promise => { + try { + if (typeof window !== 'undefined') { + if (window.kityminder) { + if (window.kityminder.Editor) return; + } + } + + await import('kity'); + await import('./kity-core/kityminder'); + await import('./kity-editor/expose-editor'); + } catch (e) { + throw new Error(e); + } +}; + +type Options = { + container: HTMLElement; + isEditable: boolean; + data?: Record; +}; + +export function renderMind(options: Options) { + const Editor = window.kityminder.Editor; + const editor = new Editor(options.container); + const mind = editor.minder; + + options.data && mind.importJson(options.data); + + if (!options.isEditable) { + mind.disable(); + } + + setTimeout(() => { + mind.execCommand('camera'); + }, 0); + + return mind; +} diff --git a/packages/client/src/thirtypart/kityminder/kity-core/connect/arc.js b/packages/client/src/thirtypart/kityminder/kity-core/connect/arc.js new file mode 100644 index 00000000..ec09a105 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/connect/arc.js @@ -0,0 +1,50 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 圆弧连线 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ + +define(function (require, exports, module) { + var kity = require('../core/kity'); + var connect = require('../core/connect'); + + var connectMarker = new kity.Marker().pipe(function () { + var r = 7; + var dot = new kity.Circle(r - 1); + this.addShape(dot); + this.setRef(r - 1, 0) + .setViewBox(-r, -r, r + r, r + r) + .setWidth(r) + .setHeight(r); + this.dot = dot; + this.node.setAttribute('markerUnits', 'userSpaceOnUse'); + }); + + connect.register('arc', function (node, parent, connection, width, color) { + var box = node.getLayoutBox(), + pBox = parent.getLayoutBox(); + + var start, end, vector; + var abs = Math.abs; + var pathData = []; + var side = box.x > pBox.x ? 'right' : 'left'; + + node.getMinder().getPaper().addResource(connectMarker); + + start = new kity.Point(pBox.cx, pBox.cy); + end = side == 'left' ? new kity.Point(box.right + 2, box.cy) : new kity.Point(box.left - 2, box.cy); + + vector = kity.Vector.fromPoints(start, end); + pathData.push('M', start); + pathData.push('A', abs(vector.x), abs(vector.y), 0, 0, vector.x * vector.y > 0 ? 0 : 1, end); + + connection.setMarker(connectMarker); + connectMarker.dot.fill(color); + + connection.setPathData(pathData); + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/connect/arc_tp.js b/packages/client/src/thirtypart/kityminder/kity-core/connect/arc_tp.js new file mode 100644 index 00000000..0cc87d43 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/connect/arc_tp.js @@ -0,0 +1,81 @@ +/* eslint-disable */ +/** + * + * 圆弧连线 + * + * @author: along + * @copyright: bpd729@163.com , 2015 + */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var connect = require('../core/connect'); + + var connectMarker = new kity.Marker().pipe(function () { + var r = 7; + var dot = new kity.Circle(r - 1); + this.addShape(dot); + this.setRef(r - 1, 0) + .setViewBox(-r, -r, r + r, r + r) + .setWidth(r) + .setHeight(r); + this.dot = dot; + this.node.setAttribute('markerUnits', 'userSpaceOnUse'); + }); + + /** + * 天盘图连线除了连接当前节点和前一个节点外, 还需要渲染当前节点和后一个节点的连接, 防止样式上的断线 + * 这是天盘图与其余的模板不同的地方 + */ + connect.register('arc_tp', function (node, parent, connection, width, color) { + var end_box = node.getLayoutBox(), + start_box = parent.getLayoutBox(); + + var index = node.getIndex(); + var nextNode = parent.getChildren()[index + 1]; + + if (node.getIndex() > 0) { + start_box = parent.getChildren()[index - 1].getLayoutBox(); + } + + var start, end, vector; + var abs = Math.abs; + var pathData = []; + var side = end_box.x > start_box.x ? 'right' : 'left'; + + node.getMinder().getPaper().addResource(connectMarker); + + start = new kity.Point(start_box.cx, start_box.cy); + end = new kity.Point(end_box.cx, end_box.cy); + + var jl = Math.sqrt(Math.pow(start.x - end.x, 2) + Math.pow(start.y - end.y, 2)); //两圆中心点距离 + + jl = node.getIndex() == 0 ? jl * 0.4 : jl; + + vector = kity.Vector.fromPoints(start, end); + pathData.push('M', start); + pathData.push('A', jl, jl, 0, 0, 1, end); + + connection.setMarker(connectMarker); + connectMarker.dot.fill(color); + connection.setPathData(pathData); + + // 设置下一个的节点的连接线 + if (nextNode && nextNode.getConnection()) { + var nextConnection = nextNode.getConnection(); + var next_end_box = nextNode.getLayoutBox(); + var next_end = new kity.Point(next_end_box.cx, next_end_box.cy); + + var jl2 = Math.sqrt(Math.pow(end.x - next_end.x, 2) + Math.pow(end.y - next_end.y, 2)); //两圆中心点距离 + + pathData = []; + + pathData.push('M', end); + pathData.push('A', jl2, jl2, 0, 0, 1, next_end); + + nextConnection.setMarker(connectMarker); + connectMarker.dot.fill(color); + + nextConnection.setPathData(pathData); + } + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/connect/bezier.js b/packages/client/src/thirtypart/kityminder/kity-core/connect/bezier.js new file mode 100644 index 00000000..522ae302 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/connect/bezier.js @@ -0,0 +1,42 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 提供折线相连的方法 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ + +define(function (require, exports, module) { + var kity = require('../core/kity'); + var connect = require('../core/connect'); + + connect.register('bezier', function (node, parent, connection) { + // 连线起点和终点 + var po = parent.getLayoutVertexOut(), + pi = node.getLayoutVertexIn(); + + // 连线矢量和方向 + var v = parent.getLayoutVectorOut().normalize(); + + var r = Math.round; + var abs = Math.abs; + + var pathData = []; + pathData.push('M', r(po.x), r(po.y)); + + if (abs(v.x) > abs(v.y)) { + // x - direction + var hx = (pi.x + po.x) / 2; + pathData.push('C', hx, po.y, hx, pi.y, pi.x, pi.y); + } else { + // y - direction + var hy = (pi.y + po.y) / 2; + pathData.push('C', po.x, hy, pi.x, hy, pi.x, pi.y); + } + + connection.setMarker(null); + connection.setPathData(pathData); + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/connect/fish-bone-master.js b/packages/client/src/thirtypart/kityminder/kity-core/connect/fish-bone-master.js new file mode 100644 index 00000000..379fb191 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/connect/fish-bone-master.js @@ -0,0 +1,33 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 鱼骨头主干连线 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ + +define(function (require, exports, module) { + var kity = require('../core/kity'); + var connect = require('../core/connect'); + + connect.register('fish-bone-master', function (node, parent, connection) { + var pout = parent.getLayoutVertexOut(), + pin = node.getLayoutVertexIn(); + + var abs = Math.abs; + + var dy = abs(pout.y - pin.y), + dx = abs(pout.x - pin.x); + + var pathData = []; + + pathData.push('M', pout.x, pout.y); + pathData.push('h', dx - dy); + pathData.push('L', pin.x, pin.y); + + connection.setMarker(null); + connection.setPathData(pathData); + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/connect/l.js b/packages/client/src/thirtypart/kityminder/kity-core/connect/l.js new file mode 100644 index 00000000..4ccc66e1 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/connect/l.js @@ -0,0 +1,34 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * "L" 连线 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ + +define(function (require, exports, module) { + var kity = require('../core/kity'); + var connect = require('../core/connect'); + + connect.register('l', function (node, parent, connection) { + var po = parent.getLayoutVertexOut(); + var pi = node.getLayoutVertexIn(); + var vo = parent.getLayoutVectorOut(); + + var pathData = []; + var r = Math.round, + abs = Math.abs; + + pathData.push('M', po.round()); + if (abs(vo.x) > abs(vo.y)) { + pathData.push('H', r(pi.x)); + } else { + pathData.push('V', pi.y); + } + pathData.push('L', pi); + + connection.setPathData(pathData); + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/connect/poly.js b/packages/client/src/thirtypart/kityminder/kity-core/connect/poly.js new file mode 100644 index 00000000..7e22b20a --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/connect/poly.js @@ -0,0 +1,62 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 提供折线相连的方法 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ + +define(function (require, exports, module) { + var kity = require('../core/kity'); + var connect = require('../core/connect'); + + connect.register('poly', function (node, parent, connection, width) { + // 连线起点和终点 + var po = parent.getLayoutVertexOut(), + pi = node.getLayoutVertexIn(); + + // 连线矢量和方向 + var v = parent.getLayoutVectorOut().normalize(); + + var r = Math.round; + var abs = Math.abs; + + var pathData = []; + pathData.push('M', r(po.x), r(po.y)); + + switch (true) { + case abs(v.x) > abs(v.y) && v.x < 0: + // left + pathData.push('h', -parent.getStyle('margin-left')); + pathData.push('v', pi.y - po.y); + pathData.push('H', pi.x); + break; + + case abs(v.x) > abs(v.y) && v.x >= 0: + // right + pathData.push('h', parent.getStyle('margin-right')); + pathData.push('v', pi.y - po.y); + pathData.push('H', pi.x); + break; + + case abs(v.x) <= abs(v.y) && v.y < 0: + // top + pathData.push('v', -parent.getStyle('margin-top')); + pathData.push('h', pi.x - po.x); + pathData.push('V', pi.y); + break; + + case abs(v.x) <= abs(v.y) && v.y >= 0: + // bottom + pathData.push('v', parent.getStyle('margin-bottom')); + pathData.push('h', pi.x - po.x); + pathData.push('V', pi.y); + break; + } + + connection.setMarker(null); + connection.setPathData(pathData); + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/connect/under.js b/packages/client/src/thirtypart/kityminder/kity-core/connect/under.js new file mode 100644 index 00000000..86e7538b --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/connect/under.js @@ -0,0 +1,49 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 下划线连线 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ + +define(function (require, exports, module) { + var kity = require('../core/kity'); + var connect = require('../core/connect'); + + connect.register('under', function (node, parent, connection, width, color) { + var box = node.getLayoutBox(), + pBox = parent.getLayoutBox(); + + var start, end, vector; + var abs = Math.abs; + var pathData = []; + var side = box.x > pBox.x ? 'right' : 'left'; + + var radius = node.getStyle('connect-radius'); + var underY = box.bottom + 3; + var startY = parent.getType() == 'sub' ? pBox.bottom + 3 : pBox.cy; + var p1, p2, p3, mx; + + if (side == 'right') { + p1 = new kity.Point(pBox.right, startY); + p2 = new kity.Point(box.left - 10, underY); + p3 = new kity.Point(box.right, underY); + } else { + p1 = new kity.Point(pBox.left, startY); + p2 = new kity.Point(box.right + 10, underY); + p3 = new kity.Point(box.left, underY); + } + + mx = (p1.x + p2.x) / 2; + + pathData.push('M', p1); + pathData.push('C', mx, p1.y, mx, p2.y, p2); + pathData.push('L', p3); + + connection.setMarker(null); + + connection.setPathData(pathData); + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/_boxv.js b/packages/client/src/thirtypart/kityminder/kity-core/core/_boxv.js new file mode 100644 index 00000000..9272f7e4 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/_boxv.js @@ -0,0 +1,34 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 调试工具:为 kity.Box 提供一个可视化的渲染 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ + +define(function (require, exports, module) { + var kity = require('./kity'); + var Minder = require('./minder'); + + if (location.href.indexOf('boxv') != -1) { + var vrect; + + Object.defineProperty(kity.Box.prototype, 'visualization', { + get: function () { + if (!vrect) return null; + return vrect.setBox(this); + }, + }); + + Minder.registerInitHook(function () { + this.on('paperrender', function () { + vrect = new kity.Rect(); + vrect.fill('rgba(200, 200, 200, .5)'); + vrect.stroke('orange'); + this.getRenderContainer().addShape(vrect); + }); + }); + } +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/animate.js b/packages/client/src/thirtypart/kityminder/kity-core/core/animate.js new file mode 100644 index 00000000..73cb20af --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/animate.js @@ -0,0 +1,44 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 动画控制 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + var Minder = require('./minder'); + + var animateDefaultOptions = { + enableAnimation: true, + layoutAnimationDuration: 300, + viewAnimationDuration: 100, + zoomAnimationDuration: 300, + }; + var resoredAnimationOptions = {}; + + Minder.registerInitHook(function () { + this.setDefaultOptions(animateDefaultOptions); + if (!this.getOption('enableAnimation')) { + this.disableAnimation(); + } + }); + + Minder.prototype.enableAnimation = function () { + for (var name in animateDefaultOptions) { + if (animateDefaultOptions.hasOwnProperty(name)) { + this.setOption(resoredAnimationOptions[name]); + } + } + }; + + Minder.prototype.disableAnimation = function () { + for (var name in animateDefaultOptions) { + if (animateDefaultOptions.hasOwnProperty(name)) { + resoredAnimationOptions[name] = this.getOption(name); + this.setOption(name, 0); + } + } + }; +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/command.js b/packages/client/src/thirtypart/kityminder/kity-core/core/command.js new file mode 100644 index 00000000..197c7289 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/command.js @@ -0,0 +1,176 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('./kity'); + var utils = require('./utils'); + var Minder = require('./minder'); + var MinderNode = require('./node'); + var MinderEvent = require('./event'); + + var COMMAND_STATE_NORMAL = 0; + var COMMAND_STATE_DISABLED = -1; + var COMMAND_STATE_ACTIVED = 1; + + /** + * 表示一个命令,包含命令的查询及执行 + */ + var Command = kity.createClass('Command', { + constructor: function () { + this._isContentChange = true; + this._isSelectionChange = false; + }, + + execute: function (minder, args) { + throw new Error('Not Implement: Command.execute()'); + }, + + setContentChanged: function (val) { + this._isContentChange = !!val; + }, + + isContentChanged: function () { + return this._isContentChange; + }, + + setSelectionChanged: function (val) { + this._isSelectionChange = !!val; + }, + + isSelectionChanged: function () { + return this._isContentChange; + }, + + queryState: function (km) { + return COMMAND_STATE_NORMAL; + }, + + queryValue: function (km) { + return 0; + }, + + isNeedUndo: function () { + return true; + }, + }); + + Command.STATE_NORMAL = COMMAND_STATE_NORMAL; + Command.STATE_ACTIVE = COMMAND_STATE_ACTIVED; + Command.STATE_DISABLED = COMMAND_STATE_DISABLED; + + kity.extendClass(Minder, { + _getCommand: function (name) { + return this._commands[name.toLowerCase()]; + }, + + _queryCommand: function (name, type, args) { + var cmd = this._getCommand(name); + if (cmd) { + var queryCmd = cmd['query' + type]; + if (queryCmd) return queryCmd.apply(cmd, [this].concat(args)); + } + return 0; + }, + + /** + * @method queryCommandState() + * @for Minder + * @description 查询指定命令的状态 + * + * @grammar queryCommandName(name) => {number} + * + * @param {string} name 要查询的命令名称 + * + * @return {number} + * -1: 命令不存在或命令当前不可用 + * 0: 命令可用 + * 1: 命令当前可用并且已经执行过 + */ + queryCommandState: function (name) { + return this._queryCommand(name, 'State', [].slice.call(arguments, 1)); + }, + + /** + * @method queryCommandValue() + * @for Minder + * @description 查询指定命令当前的执行值 + * + * @grammar queryCommandValue(name) => {any} + * + * @param {string} name 要查询的命令名称 + * + * @return {any} + * 如果命令不存在,返回 undefined + * 不同命令具有不同返回值,具体请查看 [Command](command) 章节 + */ + queryCommandValue: function (name) { + return this._queryCommand(name, 'Value', [].slice.call(arguments, 1)); + }, + + /** + * @method execCommand() + * @for Minder + * @description 执行指定的命令。 + * + * @grammar execCommand(name, args...) + * + * @param {string} name 要执行的命令名称 + * @param {argument} args 要传递给命令的其它参数 + */ + execCommand: function (name) { + if (!name) return null; + + name = name.toLowerCase(); + + var cmdArgs = [].slice.call(arguments, 1), + cmd, + stoped, + result, + eventParams; + var me = this; + cmd = this._getCommand(name); + + eventParams = { + command: cmd, + commandName: name.toLowerCase(), + commandArgs: cmdArgs, + }; + + // 放行 zoom + if (name !== 'zoom') { + if (!cmd || !~this.queryCommandState(name)) { + return false; + } + } + + if (!this._hasEnterExecCommand) { + this._hasEnterExecCommand = true; + + stoped = this._fire(new MinderEvent('beforeExecCommand', eventParams, true)); + + if (!stoped) { + this._fire(new MinderEvent('preExecCommand', eventParams, false)); + + result = cmd.execute.apply(cmd, [me].concat(cmdArgs)); + + this._fire(new MinderEvent('execCommand', eventParams, false)); + + if (cmd.isContentChanged()) { + this._firePharse(new MinderEvent('contentchange')); + } + + this._interactChange(); + } + this._hasEnterExecCommand = false; + } else { + result = cmd.execute.apply(cmd, [me].concat(cmdArgs)); + + if (!this._hasEnterExecCommand) { + this._interactChange(); + } + } + + return result === undefined ? null : result; + }, + }); + + module.exports = Command; +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/compatibility.js b/packages/client/src/thirtypart/kityminder/kity-core/core/compatibility.js new file mode 100644 index 00000000..801a217f --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/compatibility.js @@ -0,0 +1,93 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var utils = require('./utils'); + + function compatibility(json) { + var version = json.version || (json.root ? '1.4.0' : '1.1.3'); + + switch (version) { + case '1.1.3': + c_113_120(json); + /* falls through */ + case '1.2.0': + case '1.2.1': + c_120_130(json); + /* falls through */ + case '1.3.0': + case '1.3.1': + case '1.3.2': + case '1.3.3': + case '1.3.4': + case '1.3.5': + /* falls through */ + c_130_140(json); + } + return json; + } + + function traverse(node, fn) { + fn(node); + if (node.children) + node.children.forEach(function (child) { + traverse(child, fn); + }); + } + + /* 脑图数据升级 */ + function c_120_130(json) { + traverse(json, function (node) { + var data = node.data; + delete data.layout_bottom_offset; + delete data.layout_default_offset; + delete data.layout_filetree_offset; + }); + } + + /** + * 脑图数据升级 + * v1.1.3 => v1.2.0 + * */ + function c_113_120(json) { + // 原本的布局风格 + var ocs = json.data.currentstyle; + delete json.data.currentstyle; + + // 为 1.2 选择模板,同时保留老版本文件的皮肤 + if (ocs == 'bottom') { + json.template = 'structure'; + json.theme = 'snow'; + } else if (ocs == 'default') { + json.template = 'default'; + json.theme = 'classic'; + } + + traverse(json, function (node) { + var data = node.data; + + // 升级优先级、进度图标 + if ('PriorityIcon' in data) { + data.priority = data.PriorityIcon; + delete data.PriorityIcon; + } + if ('ProgressIcon' in data) { + data.progress = 1 + ((data.ProgressIcon - 1) << 1); + delete data.ProgressIcon; + } + + // 删除过时属性 + delete data.point; + delete data.layout; + }); + } + + function c_130_140(json) { + json.root = { + data: json.data, + children: json.children, + }; + delete json.data; + delete json.children; + } + + return compatibility; +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/connect.js b/packages/client/src/thirtypart/kityminder/kity-core/core/connect.js new file mode 100644 index 00000000..cc5f6a70 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/connect.js @@ -0,0 +1,122 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('./kity'); + var utils = require('./utils'); + var Module = require('./module'); + var Minder = require('./minder'); + var MinderNode = require('./node'); + + // 连线提供方 + var _connectProviders = {}; + + function register(name, provider) { + _connectProviders[name] = provider; + } + + register('default', function (node, parent, connection) { + connection.setPathData(['M', parent.getLayoutVertexOut(), 'L', node.getLayoutVertexIn()]); + }); + + kity.extendClass(MinderNode, { + /** + * @private + * @method getConnect() + * @for MinderNode + * @description 获取当前节点的连线类型 + * + * @grammar getConnect() => {string} + */ + getConnect: function () { + return this.data.connect || 'default'; + }, + + getConnectProvider: function () { + return _connectProviders[this.getConnect()] || _connectProviders['default']; + }, + + /** + * @private + * @method getConnection() + * @for MinderNode + * @description 获取当前节点的连线对象 + * + * @grammar getConnection() => {kity.Path} + */ + getConnection: function () { + return this._connection || null; + }, + }); + + kity.extendClass(Minder, { + getConnectContainer: function () { + return this._connectContainer; + }, + + createConnect: function (node) { + if (node.isRoot()) return; + + var connection = new kity.Path(); + + node._connection = connection; + + this._connectContainer.addShape(connection); + this.updateConnect(node); + }, + + removeConnect: function (node) { + var me = this; + node.traverse(function (node) { + me._connectContainer.removeShape(node._connection); + node._connection = null; + }); + }, + + updateConnect: function (node) { + var connection = node._connection; + var parent = node.parent; + + if (!parent || !connection) return; + + if (parent.isCollapsed()) { + connection.setVisible(false); + return; + } + connection.setVisible(true); + + var provider = node.getConnectProvider(); + + var strokeColor = node.getStyle('connect-color') || 'white', + strokeWidth = node.getStyle('connect-width') || 2; + + connection.stroke(strokeColor, strokeWidth); + + provider(node, parent, connection, strokeWidth, strokeColor); + + if (strokeWidth % 2 === 0) { + connection.setTranslate(0.5, 0.5); + } else { + connection.setTranslate(0, 0); + } + }, + }); + + Module.register('Connect', { + init: function () { + this._connectContainer = new kity.Group().setId(utils.uuid('minder_connect_group')); + this.getRenderContainer().prependShape(this._connectContainer); + }, + events: { + 'nodeattach': function (e) { + this.createConnect(e.node); + }, + 'nodedetach': function (e) { + this.removeConnect(e.node); + }, + 'layoutapply layoutfinish noderender': function (e) { + this.updateConnect(e.node); + }, + }, + }); + + exports.register = register; +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/data.js b/packages/client/src/thirtypart/kityminder/kity-core/core/data.js new file mode 100644 index 00000000..12c260a8 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/data.js @@ -0,0 +1,366 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('./kity'); + var utils = require('./utils'); + var Minder = require('./minder'); + var MinderNode = require('./node'); + var MinderEvent = require('./event'); + var compatibility = require('./compatibility'); + var Promise = require('./promise'); + + var protocols = {}; + + function registerProtocol(name, protocol) { + protocols[name] = protocol; + + for (var pname in protocols) { + if (protocols.hasOwnProperty(pname)) { + protocols[pname] = protocols[pname]; + protocols[pname].name = pname; + } + } + } + + function getRegisterProtocol(name) { + return name === undefined ? protocols : protocols[name] || null; + } + + exports.registerProtocol = registerProtocol; + exports.getRegisterProtocol = getRegisterProtocol; + + // 导入导出 + kity.extendClass(Minder, { + // 自动导入 + setup: function (target) { + if (typeof target == 'string') { + target = document.querySelector(target); + } + if (!target) return; + var protocol = target.getAttribute('minder-data-type'); + if (protocol in protocols) { + var data = target.textContent; + target.textContent = null; + this.renderTo(target); + this.importData(protocol, data); + } + return this; + }, + + /** + * @method exportJson() + * @for Minder + * @description + * 导出当前脑图数据为 JSON 对象,导出的数据格式请参考 [Data](data) 章节。 + * @grammar exportJson() => {plain} + */ + exportJson: function () { + /* 导出 node 上整棵树的数据为 JSON */ + function exportNode(node) { + var exported = {}; + exported.data = node.getData(); + var childNodes = node.getChildren(); + exported.children = []; + for (var i = 0; i < childNodes.length; i++) { + exported.children.push(exportNode(childNodes[i])); + } + return exported; + } + + var json = { + root: exportNode(this.getRoot()), + }; + + json.template = this.getTemplate(); + json.theme = this.getTheme(); + json.version = Minder.version; + + return JSON.parse(JSON.stringify(json)); + }, + + /** + * function Text2Children(MinderNode, String) + * @param {MinderNode} node 要导入数据的节点 + * @param {String} text 导入的text数据 + * @Desc: 用于批量插入子节点,并不会修改被插入的父节点 + * @Editor: Naixor + * @Date: 2015.9.21 + * @example: 用于批量导入如下类型的节点 + * 234 + * 3456346 asadf + * 12312414 + * wereww + * 12314 + * 1231412 + * 13123 + */ + Text2Children: function (node, text) { + if (!(node instanceof kityminder.Node)) { + return; + // throw new Error('Json2Children::node is not a kityminder.Node type!'); + } + var children = [], + jsonMap = {}, + level = 0; + + var LINE_SPLITTER = /\r|\n|\r\n/, + TAB_REGEXP = /^(\t|\x20{4})/; + + var lines = text.split(LINE_SPLITTER), + line = '', + jsonNode, + i = 0; + var minder = this; + + function isEmpty(line) { + return line === '' && !/\S/.test(line); + } + + function getNode(line) { + return { + data: { + text: line.replace(/^(\t|\x20{4})+/, '').replace(/(\t|\x20{4})+$/, ''), + }, + children: [], + }; + } + + function getLevel(text) { + var level = 0; + while (TAB_REGEXP.test(text)) { + text = text.replace(TAB_REGEXP, ''); + level++; + } + return level; + } + + function addChild(parent, node) { + parent.children.push(node); + } + + function importChildren(node, children) { + for (var i = 0, l = children.length; i < l; i++) { + var childNode = minder.createNode(null, node); + childNode.setData('text', children[i].data.text || ''); + importChildren(childNode, children[i].children); + } + } + + while ((line = lines[i++]) !== undefined) { + line = line.replace(/ /g, ''); + if (isEmpty(line)) continue; + + level = getLevel(line); + jsonNode = getNode(line); + if (level === 0) { + jsonMap = {}; + children.push(jsonNode); + jsonMap[0] = children[children.length - 1]; + } else { + if (!jsonMap[level - 1]) { + throw new Error('Invalid local format'); + } + addChild(jsonMap[level - 1], jsonNode); + jsonMap[level] = jsonNode; + } + } + + importChildren(node, children); + minder.refresh(); + }, + + /** + * @method exportNode(MinderNode) + * @param {MinderNode} node 当前要被导出的节点 + * @return {Object} 返回只含有data和children的Object + * @Editor: Naixor + * @Date: 2015.9.22 + */ + exportNode: function (node) { + var exported = {}; + exported.data = node.getData(); + var childNodes = node.getChildren(); + exported.children = []; + for (var i = 0; i < childNodes.length; i++) { + exported.children.push(this.exportNode(childNodes[i])); + } + return exported; + }, + /** + * @method importNode() + * @description 根据纯json {data, children}数据转换成为脑图节点 + * @Editor: Naixor + * @Date: 2015.9.20 + */ + importNode: function (node, json) { + var data = json.data; + node.data = {}; + + for (var field in data) { + node.setData(field, data[field]); + } + + var childrenTreeData = json.children || []; + for (var i = 0; i < childrenTreeData.length; i++) { + var childNode = this.createNode(null, node); + this.importNode(childNode, childrenTreeData[i]); + } + return node; + }, + + /** + * @method importJson() + * @for Minder + * @description 导入脑图数据,数据为 JSON 对象,具体的数据字段形式请参考 [Data](data) 章节。 + * + * @grammar importJson(json) => {this} + * + * @param {plain} json 要导入的数据 + */ + importJson: function (json) { + if (!json) return; + + /** + * @event preimport + * @for Minder + * @when 导入数据之前 + */ + this._fire(new MinderEvent('preimport', null, false)); + + // 删除当前所有节点 + while (this._root.getChildren().length) { + this.removeNode(this._root.getChildren()[0]); + } + + json = compatibility(json); + + this.importNode(this._root, json.root); + + this.setTemplate(json.template || 'default'); + this.setTheme(json.theme || null); + this.refresh(); + + /** + * @event import,contentchange,interactchange + * @for Minder + * @when 导入数据之后 + */ + this.fire('import'); + + this._firePharse({ + type: 'contentchange', + }); + + this._interactChange(); + + return this; + }, + + /** + * @method exportData() + * @for Minder + * @description 使用指定使用的数据协议,导入脑图数据 + * + * @grammar exportData(protocol) => Promise + * + * @param {string} protocol 指定的数据协议(默认内置五种数据协议 `json`、`text`、`markdown`、`svg` 和 `png`) + */ + exportData: function (protocolName, option) { + var json, protocol; + + json = this.exportJson(); + + // 指定了协议进行导出,需要检测协议是否支持 + if (protocolName) { + protocol = protocols[protocolName]; + + if (!protocol || !protocol.encode) { + return Promise.reject(new Error('Not supported protocol:' + protocolName)); + } + } + + // 导出前抛个事件 + this._fire( + new MinderEvent('beforeexport', { + json: json, + protocolName: protocolName, + protocol: protocol, + }) + ); + + return Promise.resolve(protocol.encode(json, this, option)); + }, + + /** + * @method importData() + * @for Minder + * @description 使用指定的数据协议,导入脑图数据,覆盖当前实例的脑图 + * + * @grammar importData(protocol, callback) => Promise + * + * @param {string} protocol 指定的用于解析数据的数据协议(默认内置三种数据协议 `json`、`text` 和 `markdown` 的支持) + * @param {any} data 要导入的数据 + */ + importData: function (protocolName, data, option) { + var json, protocol; + var minder = this; + + // 指定了协议进行导入,需要检测协议是否支持 + if (protocolName) { + protocol = protocols[protocolName]; + + if (!protocol || !protocol.decode) { + return Promise.reject(new Error('Not supported protocol:' + protocolName)); + } + } + + var params = { + local: data, + protocolName: protocolName, + protocol: protocol, + }; + + // 导入前抛事件 + this._fire(new MinderEvent('beforeimport', params)); + + return Promise.resolve(protocol.decode(data, this, option)).then(function (json) { + minder.importJson(json); + return json; + }); + }, + + /** + * @method decodeData() + * @for Minder + * @description 使用指定的数据协议,解析为脑图数据,与 importData 的区别在于:不覆盖当前实例的脑图 + * + * @grammar decodeData(protocol, callback) => Promise + * + * @param {string} protocol 指定的用于解析数据的数据协议(默认内置三种数据协议 `json`、`text` 和 `markdown` 的支持) + * @param {any} data 要导入的数据 + */ + decodeData: function (protocolName, data, option) { + var json, protocol; + var minder = this; + + // 指定了协议进行导入,需要检测协议是否支持 + if (protocolName) { + protocol = protocols[protocolName]; + + if (!protocol || !protocol.decode) { + return Promise.reject(new Error('Not supported protocol:' + protocolName)); + } + } + + var params = { + local: data, + protocolName: protocolName, + protocol: protocol, + }; + + // 导入前抛事件 + this._fire(new MinderEvent('beforeimport', params)); + + return Promise.resolve(protocol.decode(data, this, option)); + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/event.js b/packages/client/src/thirtypart/kityminder/kity-core/core/event.js new file mode 100644 index 00000000..043bcf57 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/event.js @@ -0,0 +1,264 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('./kity'); + var utils = require('./utils'); + var Minder = require('./minder'); + + /** + * @class MinderEvent + * @description 表示一个脑图中发生的事件 + */ + var MinderEvent = kity.createClass('MindEvent', { + constructor: function (type, params, canstop) { + params = params || {}; + if (params.getType && params.getType() == 'ShapeEvent') { + /** + * @property kityEvent + * @for MinderEvent + * @description 如果事件是从一个 kity 的事件派生的,会有 kityEvent 属性指向原来的 kity 事件 + * @type {KityEvent} + */ + this.kityEvent = params; + + /** + * @property originEvent + * @for MinderEvent + * @description 如果事件是从原声 Dom 事件派生的(如 click、mousemove 等),会有 originEvent 指向原来的 Dom 事件 + * @type {DomEvent} + */ + this.originEvent = params.originEvent; + } else if (params.target && params.preventDefault) { + this.originEvent = params; + } else { + kity.Utils.extend(this, params); + } + + /** + * @property type + * @for MinderEvent + * @description 事件的类型,如 `click`、`contentchange` 等 + * @type {string} + */ + this.type = type; + this._canstop = canstop || false; + }, + + /** + * @method getPosition() + * @for MinderEvent + * @description 如果事件是从一个 kity 事件派生的,会有 `getPosition()` 获取事件发生的坐标 + * + * @grammar getPosition(refer) => {kity.Point} + * + * @param {string|kity.Shape} refer + * 参照的坐标系, + * `"screen"` - 以浏览器屏幕为参照坐标系 + * `"minder"` - (默认)以脑图画布为参照坐标系 + * `{kity.Shape}` - 指定以某个 kity 图形为参照坐标系 + */ + getPosition: function (refer) { + if (!this.kityEvent) return; + if (!refer || refer == 'minder') { + return this.kityEvent.getPosition(this.minder.getRenderContainer()); + } + return this.kityEvent.getPosition.call(this.kityEvent, refer); + }, + + /** + * @method getTargetNode() + * @for MinderEvent + * @description 当发生的事件是鼠标事件时,获取事件位置命中的脑图节点 + * + * @grammar getTargetNode() => {MinderNode} + */ + getTargetNode: function () { + var findShape = this.kityEvent && this.kityEvent.targetShape; + if (!findShape) return null; + while (!findShape.minderNode && findShape.container) { + findShape = findShape.container; + } + var node = findShape.minderNode; + if (node && findShape.getOpacity() < 1) return null; + return node || null; + }, + + /** + * @method stopPropagation() + * @for MinderEvent + * @description 当发生的事件是鼠标事件时,获取事件位置命中的脑图节点 + * + * @grammar getTargetNode() => {MinderNode} + */ + stopPropagation: function () { + this._stoped = true; + }, + + stopPropagationImmediately: function () { + this._immediatelyStoped = true; + this._stoped = true; + }, + + shouldStopPropagation: function () { + return this._canstop && this._stoped; + }, + + shouldStopPropagationImmediately: function () { + return this._canstop && this._immediatelyStoped; + }, + preventDefault: function () { + this.originEvent.preventDefault(); + }, + isRightMB: function () { + var isRightMB = false; + if (!this.originEvent) { + return false; + } + if ('which' in this.originEvent) isRightMB = this.originEvent.which == 3; + else if ('button' in this.originEvent) isRightMB = this.originEvent.button == 2; + return isRightMB; + }, + getKeyCode: function () { + var evt = this.originEvent; + return evt.keyCode || evt.which; + }, + }); + + Minder.registerInitHook(function (option) { + this._initEvents(); + }); + + kity.extendClass(Minder, { + _initEvents: function () { + this._eventCallbacks = {}; + }, + + _resetEvents: function () { + this._initEvents(); + this._bindEvents(); + }, + + _bindEvents: function () { + /* jscs:disable maximumLineLength */ + this._paper.on( + 'click dblclick mousedown contextmenu mouseup mousemove mouseover mousewheel DOMMouseScroll touchstart touchmove touchend dragenter dragleave drop', + this._firePharse.bind(this) + ); + if (window) { + window.addEventListener('resize', this._firePharse.bind(this)); + } + }, + + /** + * @method dispatchKeyEvent + * @description 派发键盘(相关)事件到脑图实例上,让实例的模块处理 + * @grammar dispatchKeyEvent(e) => {this} + * @param {Event} e 原生的 Dom 事件对象 + */ + dispatchKeyEvent: function (e) { + this._firePharse(e); + }, + + _firePharse: function (e) { + var beforeEvent, preEvent, executeEvent; + + if (e.type == 'DOMMouseScroll') { + e.type = 'mousewheel'; + e.wheelDelta = e.originEvent.wheelDelta = e.originEvent.detail * -10; + e.wheelDeltaX = e.originEvent.mozMovementX; + e.wheelDeltaY = e.originEvent.mozMovementY; + } + + beforeEvent = new MinderEvent('before' + e.type, e, true); + if (this._fire(beforeEvent)) { + return; + } + preEvent = new MinderEvent('pre' + e.type, e, true); + executeEvent = new MinderEvent(e.type, e, true); + + if (this._fire(preEvent) || this._fire(executeEvent)) this._fire(new MinderEvent('after' + e.type, e, false)); + }, + + _interactChange: function (e) { + var me = this; + if (me._interactScheduled) return; + setTimeout(function () { + me._fire(new MinderEvent('interactchange')); + me._interactScheduled = false; + }, 100); + me._interactScheduled = true; + }, + + _listen: function (type, callback) { + var callbacks = this._eventCallbacks[type] || (this._eventCallbacks[type] = []); + callbacks.push(callback); + }, + + _fire: function (e) { + /** + * @property minder + * @description 产生事件的 Minder 对象 + * @for MinderShape + * @type {Minder} + */ + e.minder = this; + + var status = this.getStatus(); + var callbacks = this._eventCallbacks[e.type.toLowerCase()] || []; + + if (status) { + callbacks = callbacks.concat(this._eventCallbacks[status + '.' + e.type.toLowerCase()] || []); + } + + if (callbacks.length === 0) { + return; + } + + var lastStatus = this.getStatus(); + for (var i = 0; i < callbacks.length; i++) { + callbacks[i].call(this, e); + + /* this.getStatus() != lastStatus ||*/ + if (e.shouldStopPropagationImmediately()) { + break; + } + } + + return e.shouldStopPropagation(); + }, + + on: function (name, callback) { + var km = this; + name.split(/\s+/).forEach(function (n) { + km._listen(n.toLowerCase(), callback); + }); + return this; + }, + + off: function (name, callback) { + var types = name.split(/\s+/); + var i, j, callbacks, removeIndex; + for (i = 0; i < types.length; i++) { + callbacks = this._eventCallbacks[types[i].toLowerCase()]; + if (callbacks) { + removeIndex = null; + for (j = 0; j < callbacks.length; j++) { + if (callbacks[j] == callback) { + removeIndex = j; + } + } + if (removeIndex !== null) { + callbacks.splice(removeIndex, 1); + } + } + } + }, + + fire: function (type, params) { + var e = new MinderEvent(type, params); + this._fire(e); + return this; + }, + }); + + module.exports = MinderEvent; +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/focus.js b/packages/client/src/thirtypart/kityminder/kity-core/core/focus.js new file mode 100644 index 00000000..e9785b1b --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/focus.js @@ -0,0 +1,44 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('./kity'); + var Minder = require('./minder'); + + Minder.registerInitHook(function () { + this.on('beforemousedown', function (e) { + this.focus(); + // FIXME:如果遇到事件触发问题,需要检查这里 + if (e.kityEvent.targetShape.__KityClassName === 'Paper') return; + e.preventDefault(); + }); + this.on('paperrender', function () { + this.focus(); + }); + }); + + kity.extendClass(Minder, { + focus: function () { + if (!this.isFocused()) { + var renderTarget = this._renderTarget; + renderTarget.classList.add('focus'); + this.renderNodeBatch(this.getSelectedNodes()); + } + this.fire('focus'); + return this; + }, + + blur: function () { + if (this.isFocused()) { + var renderTarget = this._renderTarget; + renderTarget.classList.remove('focus'); + this.renderNodeBatch(this.getSelectedNodes()); + } + this.fire('blur'); + return this; + }, + + isFocused: function () { + var renderTarget = this._renderTarget; + return renderTarget && renderTarget.classList.contains('focus'); + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/keymap.js b/packages/client/src/thirtypart/kityminder/kity-core/core/keymap.js new file mode 100644 index 00000000..d9d7aa9d --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/keymap.js @@ -0,0 +1,129 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var keymap = { + 'Backspace': 8, + 'Tab': 9, + 'Enter': 13, + + 'Shift': 16, + 'Control': 17, + 'Alt': 18, + 'CapsLock': 20, + + 'Esc': 27, + + 'Spacebar': 32, + + 'PageUp': 33, + 'PageDown': 34, + 'End': 35, + 'Home': 36, + + 'Insert': 45, + + 'Left': 37, + 'Up': 38, + 'Right': 39, + 'Down': 40, + + 'direction': { + 37: 1, + 38: 1, + 39: 1, + 40: 1, + }, + + 'Del': 46, + + 'NumLock': 144, + + 'Cmd': 91, + 'CmdFF': 224, + 'F1': 112, + 'F2': 113, + 'F3': 114, + 'F4': 115, + 'F5': 116, + 'F6': 117, + 'F7': 118, + 'F8': 119, + 'F9': 120, + 'F10': 121, + 'F11': 122, + 'F12': 123, + + '`': 192, + '=': 187, + '-': 189, + + '/': 191, + '.': 190, + 'controlKeys': { + 16: 1, + 17: 1, + 18: 1, + 20: 1, + 91: 1, + 224: 1, + }, + 'notContentChange': { + 13: 1, + 9: 1, + + 33: 1, + 34: 1, + 35: 1, + 36: 1, + + 16: 1, + 17: 1, + 18: 1, + 20: 1, + 91: 1, + + //上下左右 + 37: 1, + 38: 1, + 39: 1, + 40: 1, + + 113: 1, + 114: 1, + 115: 1, + 144: 1, + 27: 1, + }, + + 'isSelectedNodeKey': { + //上下左右 + 37: 1, + 38: 1, + 39: 1, + 40: 1, + 13: 1, + 9: 1, + }, + }; + + // 小写适配 + for (var key in keymap) { + if (keymap.hasOwnProperty(key)) { + keymap[key.toLowerCase()] = keymap[key]; + } + } + var aKeyCode = 65; + var aCharCode = 'a'.charCodeAt(0); + + // letters + 'abcdefghijklmnopqrstuvwxyz'.split('').forEach(function (letter) { + keymap[letter] = aKeyCode + (letter.charCodeAt(0) - aCharCode); + }); + + // numbers + var n = 9; + do { + keymap[n.toString()] = n + 48; + } while (--n); + + module.exports = keymap; +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/keyreceiver.js b/packages/client/src/thirtypart/kityminder/kity-core/core/keyreceiver.js new file mode 100644 index 00000000..53b0663e --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/keyreceiver.js @@ -0,0 +1,66 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('./kity'); + var utils = require('./utils'); + var Minder = require('./minder'); + + function listen(element, type, handler) { + type.split(' ').forEach(function (name) { + element.addEventListener(name, handler, false); + }); + } + + Minder.registerInitHook(function (option) { + this.setDefaultOptions({ + enableKeyReceiver: true, + }); + if (this.getOption('enableKeyReceiver')) { + this.on('paperrender', function () { + this._initKeyReceiver(); + }); + } + }); + + kity.extendClass(Minder, { + _initKeyReceiver: function () { + if (this._keyReceiver) return; + + var receiver = (this._keyReceiver = document.createElement('input')); + receiver.classList.add('km-receiver'); + + var renderTarget = this._renderTarget; + renderTarget.appendChild(receiver); + + var minder = this; + + listen(receiver, 'keydown keyup keypress copy paste blur focus input', function (e) { + switch (e.type) { + case 'blur': + minder.blur(); + break; + case 'focus': + minder.focus(); + break; + case 'input': + receiver.value = null; + break; + } + minder._firePharse(e); + e.preventDefault(); + }); + + this.on('focus', function () { + receiver.select(); + receiver.focus(); + }); + this.on('blur', function () { + receiver.blur(); + }); + + if (this.isFocused()) { + receiver.select(); + receiver.focus(); + } + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/kity.js b/packages/client/src/thirtypart/kityminder/kity-core/core/kity.js new file mode 100644 index 00000000..1b254b92 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/kity.js @@ -0,0 +1,12 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * Kity 引入 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + module.exports = window.kity; +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/layout.js b/packages/client/src/thirtypart/kityminder/kity-core/core/layout.js new file mode 100644 index 00000000..be1f0d5a --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/layout.js @@ -0,0 +1,529 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('./kity'); + var utils = require('./utils'); + var Minder = require('./minder'); + var MinderNode = require('./node'); + var MinderEvent = require('./event'); + var Command = require('./command'); + + var _layouts = {}; + var _defaultLayout; + + function register(name, layout) { + _layouts[name] = layout; + _defaultLayout = _defaultLayout || name; + } + + /** + * @class Layout 布局基类,具体布局需要从该类派生 + */ + var Layout = kity.createClass('Layout', { + /** + * @abstract + * + * 子类需要实现的布局算法,该算法输入一个节点,排布该节点的子节点(相对父节点的变换) + * + * @param {MinderNode} node 需要布局的节点 + * + * @example + * + * doLayout: function(node) { + * var children = node.getChildren(); + * // layout calculation + * children[i].setLayoutTransform(new kity.Matrix().translate(x, y)); + * } + */ + doLayout: function (parent, children) { + throw new Error('Not Implement: Layout.doLayout()'); + }, + + /** + * 对齐指定的节点 + * + * @param {Array} nodes 要对齐的节点 + * @param {string} border 对齐边界,允许取值 left, right, top, bottom + * + */ + align: function (nodes, border, offset) { + var me = this; + offset = offset || 0; + nodes.forEach(function (node) { + var tbox = me.getTreeBox([node]); + var matrix = node.getLayoutTransform(); + switch (border) { + case 'left': + return matrix.translate(offset - tbox.left, 0); + case 'right': + return matrix.translate(offset - tbox.right, 0); + case 'top': + return matrix.translate(0, offset - tbox.top); + case 'bottom': + return matrix.translate(0, offset - tbox.bottom); + } + }); + }, + + stack: function (nodes, axis, distance) { + var me = this; + + var position = 0; + + distance = + distance || + function (node, next, axis) { + return ( + node.getStyle( + { + x: 'margin-right', + y: 'margin-bottom', + }[axis] + ) + + next.getStyle( + { + x: 'margin-left', + y: 'margin-top', + }[axis] + ) + ); + }; + + nodes.forEach(function (node, index, nodes) { + var tbox = me.getTreeBox([node]); + + var size = { + x: tbox.width, + y: tbox.height, + }[axis]; + var offset = { + x: tbox.left, + y: tbox.top, + }[axis]; + + var matrix = node.getLayoutTransform(); + + if (axis == 'x') { + matrix.translate(position - offset, 0); + } else { + matrix.translate(0, position - offset); + } + position += size; + if (nodes[index + 1]) position += distance(node, nodes[index + 1], axis); + }); + return position; + }, + + move: function (nodes, dx, dy) { + nodes.forEach(function (node) { + node.getLayoutTransform().translate(dx, dy); + }); + }, + + /** + * 工具方法:获取给点的节点所占的布局区域 + * + * @param {MinderNode[]} nodes 需要计算的节点 + * + * @return {Box} 计算结果 + */ + getBranchBox: function (nodes) { + var box = new kity.Box(); + var i, node, matrix, contentBox; + for (i = 0; i < nodes.length; i++) { + node = nodes[i]; + matrix = node.getLayoutTransform(); + contentBox = node.getContentBox(); + box = box.merge(matrix.transformBox(contentBox)); + } + + return box; + }, + + /** + * 工具方法:计算给定的节点的子树所占的布局区域 + * + * @param {MinderNode} nodes 需要计算的节点 + * + * @return {Box} 计算的结果 + */ + getTreeBox: function (nodes) { + var i, node, matrix, treeBox; + + var box = new kity.Box(); + + if (!(nodes instanceof Array)) nodes = [nodes]; + + for (i = 0; i < nodes.length; i++) { + node = nodes[i]; + matrix = node.getLayoutTransform(); + + treeBox = node.getContentBox(); + + if (node.isExpanded() && node.children.length) { + treeBox = treeBox.merge(this.getTreeBox(node.children)); + } + + box = box.merge(matrix.transformBox(treeBox)); + } + + return box; + }, + + getOrderHint: function (node) { + return []; + }, + }); + + Layout.register = register; + + Minder.registerInitHook(function (options) { + this.refresh(); + }); + + /** + * 布局支持池子管理 + */ + utils.extend(Minder, { + getLayoutList: function () { + return _layouts; + }, + + getLayoutInstance: function (name) { + var LayoutClass = _layouts[name]; + if (!LayoutClass) throw new Error('Missing Layout: ' + name); + var layout = new LayoutClass(); + return layout; + }, + }); + + /** + * MinderNode 上的布局支持 + */ + kity.extendClass(MinderNode, { + /** + * 获得当前节点的布局名称 + * + * @return {String} + */ + getLayout: function () { + var layout = this.getData('layout'); + + layout = layout || (this.isRoot() ? _defaultLayout : this.parent.getLayout()); + + return layout; + }, + + setLayout: function (name) { + if (name) { + if (name == 'inherit') { + this.setData('layout'); + } else { + this.setData('layout', name); + } + } + return this; + }, + + layout: function (name) { + this.setLayout(name).getMinder().layout(); + + return this; + }, + + getLayoutInstance: function () { + return Minder.getLayoutInstance(this.getLayout()); + }, + + getOrderHint: function (refer) { + return this.parent.getLayoutInstance().getOrderHint(this); + }, + + /** + * 获取当前节点相对于父节点的布局变换 + */ + getLayoutTransform: function () { + return this._layoutTransform || new kity.Matrix(); + }, + + /** + * 第一轮布局计算后,获得的全局布局位置 + * + * @return {[type]} [description] + */ + getGlobalLayoutTransformPreview: function () { + var pMatrix = this.parent ? this.parent.getLayoutTransform() : new kity.Matrix(); + var matrix = this.getLayoutTransform(); + var offset = this.getLayoutOffset(); + if (offset) { + matrix = matrix.clone().translate(offset.x, offset.y); + } + return pMatrix.merge(matrix); + }, + + getLayoutPointPreview: function () { + return this.getGlobalLayoutTransformPreview().transformPoint(new kity.Point()); + }, + + /** + * 获取节点相对于全局的布局变换 + */ + getGlobalLayoutTransform: function () { + if (this._globalLayoutTransform) { + return this._globalLayoutTransform; + } else if (this.parent) { + return this.parent.getGlobalLayoutTransform(); + } else { + return new kity.Matrix(); + } + }, + + /** + * 设置当前节点相对于父节点的布局变换 + */ + setLayoutTransform: function (matrix) { + this._layoutTransform = matrix; + return this; + }, + + /** + * 设置当前节点相对于全局的布局变换(冗余优化) + */ + setGlobalLayoutTransform: function (matrix) { + this.getRenderContainer().setMatrix((this._globalLayoutTransform = matrix)); + return this; + }, + + setVertexIn: function (p) { + this._vertexIn = p; + }, + + setVertexOut: function (p) { + this._vertexOut = p; + }, + + getVertexIn: function () { + return this._vertexIn || new kity.Point(); + }, + + getVertexOut: function () { + return this._vertexOut || new kity.Point(); + }, + + getLayoutVertexIn: function () { + return this.getGlobalLayoutTransform().transformPoint(this.getVertexIn()); + }, + + getLayoutVertexOut: function () { + return this.getGlobalLayoutTransform().transformPoint(this.getVertexOut()); + }, + + setLayoutVectorIn: function (v) { + this._layoutVectorIn = v; + return this; + }, + + setLayoutVectorOut: function (v) { + this._layoutVectorOut = v; + return this; + }, + + getLayoutVectorIn: function () { + return this._layoutVectorIn || new kity.Vector(); + }, + + getLayoutVectorOut: function () { + return this._layoutVectorOut || new kity.Vector(); + }, + + getLayoutBox: function () { + var matrix = this.getGlobalLayoutTransform(); + return matrix.transformBox(this.getContentBox()); + }, + + getLayoutPoint: function () { + var matrix = this.getGlobalLayoutTransform(); + return matrix.transformPoint(new kity.Point()); + }, + + getLayoutOffset: function () { + if (!this.parent) return new kity.Point(); + + // 影响当前节点位置的是父节点的布局 + var data = this.getData('layout_' + this.parent.getLayout() + '_offset'); + + if (data) return new kity.Point(data.x, data.y); + + return new kity.Point(); + }, + + setLayoutOffset: function (p) { + if (!this.parent) return this; + + this.setData( + 'layout_' + this.parent.getLayout() + '_offset', + p + ? { + x: p.x, + y: p.y, + } + : undefined + ); + + return this; + }, + + hasLayoutOffset: function () { + return !!this.getData('layout_' + this.parent.getLayout() + '_offset'); + }, + + resetLayoutOffset: function () { + return this.setLayoutOffset(null); + }, + + getLayoutRoot: function () { + if (this.isLayoutRoot()) { + return this; + } + return this.parent.getLayoutRoot(); + }, + + isLayoutRoot: function () { + return this.getData('layout') || this.isRoot(); + }, + }); + + /** + * Minder 上的布局支持 + */ + kity.extendClass(Minder, { + layout: function () { + var duration = this.getOption('layoutAnimationDuration'); + + this.getRoot().traverse(function (node) { + // clear last results + node.setLayoutTransform(null); + }); + + function layoutNode(node, round) { + // layout all children first + // 剪枝:收起的节点无需计算 + if (node.isExpanded() || true) { + node.children.forEach(function (child) { + layoutNode(child, round); + }); + } + + var layout = node.getLayoutInstance(); + // var childrenInFlow = node.getChildren().filter(function(child) { + // return !child.hasLayoutOffset(); + // }); + layout.doLayout(node, node.getChildren(), round); + } + + // 第一轮布局 + layoutNode(this.getRoot(), 1); + + // 第二轮布局 + layoutNode(this.getRoot(), 2); + + var minder = this; + this.applyLayoutResult(this.getRoot(), duration, function () { + /** + * 当节点>200, 不使用动画时, 此处逻辑变为同步逻辑, 外部minder.on事件无法 + * 被提前录入, 因此增加setTimeout + * @author Naixor + */ + setTimeout(function () { + minder.fire('layoutallfinish'); + }, 0); + }); + + return this.fire('layout'); + }, + + refresh: function () { + this.getRoot().renderTree(); + this.layout().fire('contentchange')._interactChange(); + return this; + }, + + applyLayoutResult: function (root, duration, callback) { + root = root || this.getRoot(); + var me = this; + + var complex = root.getComplex(); + + function consume() { + if (!--complex) { + if (callback) { + callback(); + } + } + } + + // 节点复杂度大于 100,关闭动画 + if (complex > 200) duration = 0; + + function applyMatrix(node, matrix) { + node.setGlobalLayoutTransform(matrix); + + me.fire('layoutapply', { + node: node, + matrix: matrix, + }); + } + + function apply(node, pMatrix) { + var matrix = node.getLayoutTransform().merge(pMatrix.clone()); + var lastMatrix = node.getGlobalLayoutTransform() || new kity.Matrix(); + + var offset = node.getLayoutOffset(); + matrix.translate(offset.x, offset.y); + + matrix.m.e = Math.round(matrix.m.e); + matrix.m.f = Math.round(matrix.m.f); + + // 如果当前有动画,停止动画 + if (node._layoutTimeline) { + node._layoutTimeline.stop(); + node._layoutTimeline = null; + } + + // 如果要求以动画形式来更新,创建动画 + if (duration) { + node._layoutTimeline = new kity.Animator(lastMatrix, matrix, applyMatrix) + .start(node, duration, 'ease') + .on('finish', function () { + //可能性能低的时候会丢帧,手动添加一帧 + setTimeout(function () { + applyMatrix(node, matrix); + me.fire('layoutfinish', { + node: node, + matrix: matrix, + }); + consume(); + }, 150); + }); + } + + // 否则直接更新 + else { + applyMatrix(node, matrix); + me.fire('layoutfinish', { + node: node, + matrix: matrix, + }); + consume(); + } + + for (var i = 0; i < node.children.length; i++) { + apply(node.children[i], matrix); + } + } + apply(root, root.parent ? root.parent.getGlobalLayoutTransform() : new kity.Matrix()); + return this; + }, + }); + + module.exports = Layout; +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/minder.js b/packages/client/src/thirtypart/kityminder/kity-core/core/minder.js new file mode 100644 index 00000000..9e5ca176 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/minder.js @@ -0,0 +1,41 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * KityMinder 类,暴露在 window 上的唯一变量 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + var kity = require('./kity'); + var utils = require('./utils'); + + var _initHooks = []; + + var Minder = kity.createClass('Minder', { + constructor: function (options) { + this._options = utils.extend({}, options); + + var initHooks = _initHooks.slice(); + + var initHook; + while (initHooks.length) { + initHook = initHooks.shift(); + if (typeof initHook == 'function') { + initHook.call(this, this._options); + } + } + + this.fire('finishInitHook'); + }, + }); + + Minder.version = '1.4.43'; + + Minder.registerInitHook = function (hook) { + _initHooks.push(hook); + }; + + module.exports = Minder; +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/module.js b/packages/client/src/thirtypart/kityminder/kity-core/core/module.js new file mode 100644 index 00000000..c58c0acd --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/module.js @@ -0,0 +1,150 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('./kity'); + var utils = require('./utils'); + var Minder = require('./minder'); + + /* 已注册的模块 */ + var _modules = {}; + + exports.register = function (name, module) { + _modules[name] = module; + }; + + /* 模块初始化 */ + Minder.registerInitHook(function () { + this._initModules(); + }); + + // 模块声明周期维护 + kity.extendClass(Minder, { + _initModules: function () { + var modulesPool = _modules; + var modulesToLoad = this._options.modules || utils.keys(modulesPool); + + this._commands = {}; + this._query = {}; + this._modules = {}; + this._rendererClasses = {}; + + var i, name, type, module, moduleDeals, dealCommands, dealEvents, dealRenderers; + + var me = this; + for (i = 0; i < modulesToLoad.length; i++) { + name = modulesToLoad[i]; + + if (!modulesPool[name]) continue; + + // 执行模块初始化,抛出后续处理对象 + + if (typeof modulesPool[name] == 'function') { + moduleDeals = modulesPool[name].call(me); + } else { + moduleDeals = modulesPool[name]; + } + this._modules[name] = moduleDeals; + + if (!moduleDeals) continue; + + if (moduleDeals.defaultOptions) { + me.setDefaultOptions(moduleDeals.defaultOptions); + } + + if (moduleDeals.init) { + moduleDeals.init.call(me, this._options); + } + + /** + * @Desc: 判断是否支持原生clipboard事件,如果支持,则对pager添加其监听 + * @Editor: Naixor + * @Date: 2015.9.20 + */ + /** + * 由于当前脑图解构问题,clipboard暂时全权交由玩不托管 + * @Editor: Naixor + * @Date: 2015.9.24 + */ + // if (name === 'ClipboardModule' && this.supportClipboardEvent && !kity.Browser.gecko) { + // var on = function () { + // var clipBoardReceiver = this.clipBoardReceiver || document; + + // if (document.addEventListener) { + // clipBoardReceiver.addEventListener.apply(this, arguments); + // } else { + // arguments[0] = 'on' + arguments[0]; + // clipBoardReceiver.attachEvent.apply(this, arguments); + // } + // } + // for (var command in moduleDeals.clipBoardEvents) { + // on(command, moduleDeals.clipBoardEvents[command]); + // } + // }; + + // command加入命令池子 + dealCommands = moduleDeals.commands; + for (name in dealCommands) { + this._commands[name.toLowerCase()] = new dealCommands[name](); + } + + // 绑定事件 + dealEvents = moduleDeals.events; + if (dealEvents) { + for (type in dealEvents) { + me.on(type, dealEvents[type]); + } + } + + // 渲染器 + dealRenderers = moduleDeals.renderers; + + if (dealRenderers) { + for (type in dealRenderers) { + this._rendererClasses[type] = this._rendererClasses[type] || []; + + if (utils.isArray(dealRenderers[type])) { + this._rendererClasses[type] = this._rendererClasses[type].concat(dealRenderers[type]); + } else { + this._rendererClasses[type].push(dealRenderers[type]); + } + } + } + + //添加模块的快捷键 + if (moduleDeals.commandShortcutKeys) { + this.addCommandShortcutKeys(moduleDeals.commandShortcutKeys); + } + } + }, + + _garbage: function () { + // this.clearSelect(); + + while (this._root.getChildren().length) { + this._root.removeChild(0); + } + }, + + destroy: function () { + var modules = this._modules; + + this._resetEvents(); + this._garbage(); + + for (var key in modules) { + if (!modules[key].destroy) continue; + modules[key].destroy.call(this); + } + }, + + reset: function () { + var modules = this._modules; + + this._garbage(); + + for (var key in modules) { + if (!modules[key].reset) continue; + modules[key].reset.call(this); + } + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/node.js b/packages/client/src/thirtypart/kityminder/kity-core/core/node.js new file mode 100644 index 00000000..e81fafa3 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/node.js @@ -0,0 +1,404 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('./kity'); + var utils = require('./utils'); + var Minder = require('./minder'); + + /** + * @class MinderNode + * + * 表示一个脑图节点 + */ + var MinderNode = kity.createClass('MinderNode', { + /** + * 创建一个游离的脑图节点 + * + * @param {String|Object} textOrData + * 节点的初始数据或文本 + */ + constructor: function (textOrData) { + // 指针 + this.parent = null; + this.root = this; + this.children = []; + + // 数据 + this.data = { + id: utils.guid(), + created: +new Date(), + }; + + // 绘图容器 + this.initContainers(); + + if (utils.isString(textOrData)) { + this.setText(textOrData); + } else if (utils.isObject(textOrData)) { + utils.extend(this.data, textOrData); + } + }, + + initContainers: function () { + this.rc = new kity.Group().setId(utils.uuid('minder_node')); + this.rc.minderNode = this; + }, + + /** + * 判断节点是否根节点 + */ + isRoot: function () { + return this.root === this; + }, + + /** + * 判断节点是否叶子 + */ + isLeaf: function () { + return this.children.length === 0; + }, + + /** + * 获取节点的根节点 + */ + getRoot: function () { + return this.root || this; + }, + + /** + * 获得节点的父节点 + */ + getParent: function () { + return this.parent; + }, + + getSiblings: function () { + var children = this.parent.children; + var siblings = []; + var self = this; + children.forEach(function (child) { + if (child != self) siblings.push(child); + }); + return siblings; + }, + + /** + * 获得节点的深度 + */ + getLevel: function () { + var level = 0, + ancestor = this.parent; + while (ancestor) { + level++; + ancestor = ancestor.parent; + } + return level; + }, + + /** + * 获得节点的复杂度(即子树中节点的数量) + */ + getComplex: function () { + var complex = 0; + this.traverse(function () { + complex++; + }); + return complex; + }, + + /** + * 获得节点的类型(root|main|sub) + */ + getType: function (type) { + this.type = ['root', 'main', 'sub'][Math.min(this.getLevel(), 2)]; + return this.type; + }, + + /** + * 判断当前节点是否被测试节点的祖先 + * @param {MinderNode} test 被测试的节点 + */ + isAncestorOf: function (test) { + var ancestor = test.parent; + while (ancestor) { + if (ancestor == this) return true; + ancestor = ancestor.parent; + } + return false; + }, + + getData: function (key) { + return key ? this.data[key] : this.data; + }, + + setData: function (key, value) { + if (typeof key == 'object') { + var data = key; + for (key in data) + if (data.hasOwnProperty(key)) { + this.data[key] = data[key]; + } + } else { + this.data[key] = value; + } + return this; + }, + + /** + * 设置节点的文本数据 + * @param {String} text 文本数据 + */ + setText: function (text) { + return (this.data.text = text); + }, + + /** + * 获取节点的文本数据 + * @return {String} + */ + getText: function () { + return this.data.text || null; + }, + + /** + * 先序遍历当前节点树 + * @param {Function} fn 遍历函数 + */ + preTraverse: function (fn, excludeThis) { + var children = this.getChildren(); + if (!excludeThis) fn(this); + for (var i = 0; i < children.length; i++) { + children[i].preTraverse(fn); + } + }, + + /** + * 后序遍历当前节点树 + * @param {Function} fn 遍历函数 + */ + postTraverse: function (fn, excludeThis) { + var children = this.getChildren(); + for (var i = 0; i < children.length; i++) { + children[i].postTraverse(fn); + } + if (!excludeThis) fn(this); + }, + + traverse: function (fn, excludeThis) { + return this.postTraverse(fn, excludeThis); + }, + + getChildren: function () { + return this.children; + }, + + getIndex: function () { + return this.parent ? this.parent.children.indexOf(this) : -1; + }, + + insertChild: function (node, index) { + if (index === undefined) { + index = this.children.length; + } + if (node.parent) { + node.parent.removeChild(node); + } + node.parent = this; + node.root = this.root; + + this.children.splice(index, 0, node); + }, + + appendChild: function (node) { + return this.insertChild(node); + }, + + prependChild: function (node) { + return this.insertChild(node, 0); + }, + + removeChild: function (elem) { + var index = elem, + removed; + if (elem instanceof MinderNode) { + index = this.children.indexOf(elem); + } + if (index >= 0) { + removed = this.children.splice(index, 1)[0]; + removed.parent = null; + removed.root = removed; + } + }, + + clearChildren: function () { + this.children = []; + }, + + getChild: function (index) { + return this.children[index]; + }, + + getRenderContainer: function () { + return this.rc; + }, + + getCommonAncestor: function (node) { + return MinderNode.getCommonAncestor(this, node); + }, + + contains: function (node) { + return this == node || this.isAncestorOf(node); + }, + + clone: function () { + var cloned = new MinderNode(); + + cloned.data = utils.clone(this.data); + + this.children.forEach(function (child) { + cloned.appendChild(child.clone()); + }); + + return cloned; + }, + + compareTo: function (node) { + if (!utils.comparePlainObject(this.data, node.data)) return false; + if (!utils.comparePlainObject(this.temp, node.temp)) return false; + if (this.children.length != node.children.length) return false; + + var i = 0; + while (this.children[i]) { + if (!this.children[i].compareTo(node.children[i])) return false; + i++; + } + + return true; + }, + + getMinder: function () { + return this.getRoot().minder; + }, + }); + + MinderNode.getCommonAncestor = function (nodeA, nodeB) { + if (nodeA instanceof Array) { + return MinderNode.getCommonAncestor.apply(this, nodeA); + } + switch (arguments.length) { + case 1: + return nodeA.parent || nodeA; + + case 2: + if (nodeA.isAncestorOf(nodeB)) { + return nodeA; + } + if (nodeB.isAncestorOf(nodeA)) { + return nodeB; + } + var ancestor = nodeA.parent; + while (ancestor && !ancestor.isAncestorOf(nodeB)) { + ancestor = ancestor.parent; + } + return ancestor; + + default: + return Array.prototype.reduce.call( + arguments, + function (prev, current) { + return MinderNode.getCommonAncestor(prev, current); + }, + nodeA + ); + } + }; + + kity.extendClass(Minder, { + getRoot: function () { + return this._root; + }, + + setRoot: function (root) { + this._root = root; + root.minder = this; + }, + + getAllNode: function () { + var nodes = []; + this.getRoot().traverse(function (node) { + nodes.push(node); + }); + return nodes; + }, + + getNodeById: function (id) { + return this.getNodesById([id])[0]; + }, + + getNodesById: function (ids) { + var nodes = this.getAllNode(); + var result = []; + nodes.forEach(function (node) { + if (ids.indexOf(node.getData('id')) != -1) { + result.push(node); + } + }); + return result; + }, + + createNode: function (textOrData, parent, index) { + var node = new MinderNode(textOrData); + this.fire('nodecreate', { + node: node, + parent: parent, + index: index, + }); + this.appendNode(node, parent, index); + return node; + }, + + appendNode: function (node, parent, index) { + if (parent) parent.insertChild(node, index); + this.attachNode(node); + return this; + }, + + removeNode: function (node) { + if (node.parent) { + node.parent.removeChild(node); + this.detachNode(node); + this.fire('noderemove', { + node: node, + }); + } + }, + + attachNode: function (node) { + var rc = this.getRenderContainer(); + node.traverse(function (current) { + current.attached = true; + rc.addShape(current.getRenderContainer()); + }); + rc.addShape(node.getRenderContainer()); + this.fire('nodeattach', { + node: node, + }); + }, + + detachNode: function (node) { + var rc = this.getRenderContainer(); + node.traverse(function (current) { + current.attached = false; + rc.removeShape(current.getRenderContainer()); + }); + this.fire('nodedetach', { + node: node, + }); + }, + + getMinderTitle: function () { + return this.getRoot().getText(); + }, + }); + + module.exports = MinderNode; +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/option.js b/packages/client/src/thirtypart/kityminder/kity-core/core/option.js new file mode 100644 index 00000000..6ef91213 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/option.js @@ -0,0 +1,35 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 提供脑图选项支持 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + var kity = require('./kity'); + var utils = require('./utils'); + var Minder = require('./minder'); + + Minder.registerInitHook(function (options) { + this._defaultOptions = {}; + }); + + kity.extendClass(Minder, { + setDefaultOptions: function (options) { + utils.extend(this._defaultOptions, options); + return this; + }, + getOption: function (key) { + if (key) { + return key in this._options ? this._options[key] : this._defaultOptions[key]; + } else { + return utils.extend({}, this._defaultOptions, this._options); + } + }, + setOption: function (key, value) { + this._options[key] = value; + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/paper.js b/packages/client/src/thirtypart/kityminder/kity-core/core/paper.js new file mode 100644 index 00000000..19922392 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/paper.js @@ -0,0 +1,75 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 初始化渲染容器 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + var kity = require('./kity'); + var utils = require('./utils'); + var Minder = require('./minder'); + + Minder.registerInitHook(function () { + this._initPaper(); + }); + + kity.extendClass(Minder, { + _initPaper: function () { + this._paper = new kity.Paper(); + this._paper._minder = this; + this._paper.getNode().ondragstart = function (e) { + e.preventDefault(); + }; + this._paper.shapeNode.setAttribute('transform', 'translate(0.5, 0.5)'); + + this._addRenderContainer(); + + this.setRoot(this.createNode()); + + if (this._options.renderTo) { + this.renderTo(this._options.renderTo); + } + }, + + _addRenderContainer: function () { + this._rc = new kity.Group().setId(utils.uuid('minder')); + this._paper.addShape(this._rc); + }, + + renderTo: function (target) { + if (typeof target == 'string') { + target = document.querySelector(target); + } + if (target) { + if (target.tagName.toLowerCase() == 'script') { + var newTarget = document.createElement('div'); + newTarget.id = target.id; + newTarget.class = target.class; + target.parentNode.insertBefore(newTarget, target); + target.parentNode.removeChild(target); + target = newTarget; + } + target.classList.add('km-view'); + this._paper.renderTo((this._renderTarget = target)); + this._bindEvents(); + this.fire('paperrender'); + } + return this; + }, + + getRenderContainer: function () { + return this._rc; + }, + + getPaper: function () { + return this._paper; + }, + + getRenderTarget: function () { + return this._renderTarget; + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/patch.js b/packages/client/src/thirtypart/kityminder/kity-core/core/patch.js new file mode 100644 index 00000000..c76447c7 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/patch.js @@ -0,0 +1,109 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 打补丁 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + var kity = require('./kity'); + var Minder = require('./minder'); + + function insertNode(minder, info, parent, index) { + parent = minder.createNode(info.data, parent, index); + info.children.forEach(function (childInfo, index) { + insertNode(minder, childInfo, parent, index); + }); + return parent; + } + + function applyPatch(minder, patch) { + // patch.op - 操作,包括 remove, add, replace + // patch.path - 路径,如 '/root/children/1/data' + // patch.value - 数据,如 { text: "思路" } + var path = patch.path.split('/'); + path.shift(); + + var changed = path.shift(); + var node; + + if (changed == 'root') { + var dataIndex = path.indexOf('data'); + if (dataIndex > -1) { + changed = 'data'; + var dataPath = path.splice(dataIndex + 1); + patch.dataPath = dataPath; + } else { + changed = 'node'; + } + + node = minder.getRoot(); + var segment, index; + while ((segment = path.shift())) { + if (segment == 'children') continue; + if (typeof index != 'undefined') node = node.getChild(index); + index = +segment; + } + patch.index = index; + patch.node = node; + } + + var express = (patch.express = [changed, patch.op].join('.')); + + switch (express) { + case 'theme.replace': + minder.useTheme(patch.value); + break; + case 'template.replace': + minder.useTemplate(patch.value); + break; + case 'node.add': + insertNode(minder, patch.value, patch.node, patch.index).renderTree(); + minder.layout(); + break; + case 'node.remove': + minder.removeNode(patch.node.getChild(patch.index)); + minder.layout(); + break; + case 'data.add': + case 'data.replace': + case 'data.remove': + var data = patch.node.data; + var field; + path = patch.dataPath.slice(); + while (data && path.length > 1) { + field = path.shift(); + if (field in data) { + data = data[field]; + } else if (patch.op != 'remove') { + data = data[field] = {}; + } + } + if (data) { + field = path.shift(); + data[field] = patch.value; + } + if (field == 'expandState') { + node.renderTree(); + } else { + node.render(); + } + minder.layout(); + } + + minder.fire('patch', { patch: patch }); + } + + kity.extendClass(Minder, { + applyPatches: function (patches) { + for (var i = 0; i < patches.length; i++) { + applyPatch(this, patches[i]); + } + + this.fire('contentchange'); + return this; + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/promise.js b/packages/client/src/thirtypart/kityminder/kity-core/core/promise.js new file mode 100644 index 00000000..3dd36343 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/promise.js @@ -0,0 +1,207 @@ +/* eslint-disable */ +define(function (require, exports, module) { + /*! + ** Thenable -- Embeddable Minimum Strictly-Compliant Promises/A+ 1.1.1 Thenable + ** Copyright (c) 2013-2014 Ralf S. Engelschall + ** Licensed under The MIT License + ** Source-Code distributed on + */ + + /* promise states [Promises/A+ 2.1] */ + var STATE_PENDING = 0; /* [Promises/A+ 2.1.1] */ + var STATE_FULFILLED = 1; /* [Promises/A+ 2.1.2] */ + var STATE_REJECTED = 2; /* [Promises/A+ 2.1.3] */ + + /* promise object constructor */ + var Promise = function (executor) { + /* optionally support non-constructor/plain-function call */ + if (!(this instanceof Promise)) return new Promise(executor); + + /* initialize object */ + this.id = 'Thenable/1.0.7'; + this.state = STATE_PENDING; /* initial state */ + this.fulfillValue = undefined; /* initial value */ /* [Promises/A+ 1.3, 2.1.2.2] */ + this.rejectReason = undefined; /* initial reason */ /* [Promises/A+ 1.5, 2.1.3.2] */ + this.onFulfilled = []; /* initial handlers */ + this.onRejected = []; /* initial handlers */ + + /* support optional executor function */ + if (typeof executor === 'function') executor.call(this, this.fulfill.bind(this), this.reject.bind(this)); + }; + + /* Promise API methods */ + Promise.prototype = { + /* promise resolving methods */ + fulfill: function (value) { + return deliver(this, STATE_FULFILLED, 'fulfillValue', value); + }, + reject: function (value) { + return deliver(this, STATE_REJECTED, 'rejectReason', value); + }, + + /* 'The then Method' [Promises/A+ 1.1, 1.2, 2.2] */ + then: function (onFulfilled, onRejected) { + var curr = this; + var next = new Promise(); /* [Promises/A+ 2.2.7] */ + curr.onFulfilled.push(resolver(onFulfilled, next, 'fulfill')); /* [Promises/A+ 2.2.2/2.2.6] */ + curr.onRejected.push(resolver(onRejected, next, 'reject')); /* [Promises/A+ 2.2.3/2.2.6] */ + execute(curr); + return next; /* [Promises/A+ 2.2.7, 3.3] */ + }, + }; + + Promise.all = function (arr) { + return new Promise(function (resolve, reject) { + var len = arr.length, + i = 0, + res = 0, + results = []; + + if (len === 0) { + resolve(results); + } + + while (i < len) { + arr[i].then( + function (result) { + results.push(result); + if (++res === len) { + resolve(results); + } + }, + function (val) { + reject(val); + } + ); + i++; + } + }); + }; + + /* deliver an action */ + var deliver = function (curr, state, name, value) { + if (curr.state === STATE_PENDING) { + curr.state = state; /* [Promises/A+ 2.1.2.1, 2.1.3.1] */ + curr[name] = value; /* [Promises/A+ 2.1.2.2, 2.1.3.2] */ + execute(curr); + } + return curr; + }; + + /* execute all handlers */ + var execute = function (curr) { + if (curr.state === STATE_FULFILLED) execute_handlers(curr, 'onFulfilled', curr.fulfillValue); + else if (curr.state === STATE_REJECTED) execute_handlers(curr, 'onRejected', curr.rejectReason); + }; + + /* execute particular set of handlers */ + var execute_handlers = function (curr, name, value) { + /* global process: true */ + /* global setImmediate: true */ + /* global setTimeout: true */ + + /* short-circuit processing */ + if (curr[name].length === 0) return; + + /* iterate over all handlers, exactly once */ + var handlers = curr[name]; + curr[name] = []; /* [Promises/A+ 2.2.2.3, 2.2.3.3] */ + var func = function () { + for (var i = 0; i < handlers.length; i++) handlers[i](value); /* [Promises/A+ 2.2.5] */ + }; + + /* execute procedure asynchronously */ /* [Promises/A+ 2.2.4, 3.1] */ + if (typeof process === 'object' && typeof process.nextTick === 'function') process.nextTick(func); + else if (typeof setImmediate === 'function') setImmediate(func); + else setTimeout(func, 0); + }; + + /* generate a resolver function */ + var resolver = function (cb, next, method) { + return function (value) { + if (typeof cb !== 'function') /* [Promises/A+ 2.2.1, 2.2.7.3, 2.2.7.4] */ next[method].call(next, value); + /* [Promises/A+ 2.2.7.3, 2.2.7.4] */ else { + var result; + try { + if (value instanceof Promise) { + result = value.then(cb); + } else result = cb(value); + } catch (e) { + /* [Promises/A+ 2.2.2.1, 2.2.3.1, 2.2.5, 3.2] */ + next.reject(e); /* [Promises/A+ 2.2.7.2] */ + return; + } + resolve(next, result); /* [Promises/A+ 2.2.7.1] */ + } + }; + }; + + /* 'Promise Resolution Procedure' */ /* [Promises/A+ 2.3] */ + var resolve = function (promise, x) { + /* sanity check arguments */ /* [Promises/A+ 2.3.1] */ + if (promise === x) { + promise.reject(new TypeError('cannot resolve promise with itself')); + return; + } + + /* surgically check for a 'then' method + (mainly to just call the 'getter' of 'then' only once) */ + var then; + if ((typeof x === 'object' && x !== null) || typeof x === 'function') { + try { + then = x.then; + } catch (e) { + /* [Promises/A+ 2.3.3.1, 3.5] */ + promise.reject(e); /* [Promises/A+ 2.3.3.2] */ + return; + } + } + + /* handle own Thenables [Promises/A+ 2.3.2] + and similar 'thenables' [Promises/A+ 2.3.3] */ + if (typeof then === 'function') { + var resolved = false; + try { + /* call retrieved 'then' method */ /* [Promises/A+ 2.3.3.3] */ + then.call( + x, + /* resolvePromise */ /* [Promises/A+ 2.3.3.3.1] */ + function (y) { + if (resolved) return; + resolved = true; /* [Promises/A+ 2.3.3.3.3] */ + if (y === x) /* [Promises/A+ 3.6] */ promise.reject(new TypeError('circular thenable chain')); + else resolve(promise, y); + }, + + /* rejectPromise */ /* [Promises/A+ 2.3.3.3.2] */ + function (r) { + if (resolved) return; + resolved = true; /* [Promises/A+ 2.3.3.3.3] */ + promise.reject(r); + } + ); + } catch (e) { + if (!resolved) /* [Promises/A+ 2.3.3.3.3] */ promise.reject(e); /* [Promises/A+ 2.3.3.3.4] */ + } + return; + } + + /* handle other values */ + promise.fulfill(x); /* [Promises/A+ 2.3.4, 2.3.3.4] */ + }; + + Promise.resolve = function (value) { + return new Promise(function (resolve) { + resolve(value); + }); + }; + + Promise.reject = function (reason) { + return new Promise(function (resolve, reject) { + reject(reason); + }); + }; + + /* export API */ + module.exports = Promise; +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/readonly.js b/packages/client/src/thirtypart/kityminder/kity-core/core/readonly.js new file mode 100644 index 00000000..812d2a2e --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/readonly.js @@ -0,0 +1,63 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 只读模式支持 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ + +define(function (require, exports, module) { + var kity = require('./kity'); + var Minder = require('./minder'); + var MinderEvent = require('./event'); + + Minder.registerInitHook(function (options) { + if (options.readOnly) { + this.setDisabled(); + } + }); + + kity.extendClass(Minder, { + disable: function () { + var me = this; + //禁用命令 + me.bkqueryCommandState = me.queryCommandState; + me.bkqueryCommandValue = me.queryCommandValue; + me.queryCommandState = function (type) { + var cmd = this._getCommand(type); + if (cmd && cmd.enableReadOnly) { + return me.bkqueryCommandState.apply(me, arguments); + } + return -1; + }; + me.queryCommandValue = function (type) { + var cmd = this._getCommand(type); + if (cmd && cmd.enableReadOnly) { + return me.bkqueryCommandValue.apply(me, arguments); + } + return null; + }; + this.setStatus('readonly'); + me._interactChange(); + }, + + enable: function () { + var me = this; + + if (me.bkqueryCommandState) { + me.queryCommandState = me.bkqueryCommandState; + delete me.bkqueryCommandState; + } + if (me.bkqueryCommandValue) { + me.queryCommandValue = me.bkqueryCommandValue; + delete me.bkqueryCommandValue; + } + + this.setStatus('normal'); + + me._interactChange(); + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/render.js b/packages/client/src/thirtypart/kityminder/kity-core/core/render.js new file mode 100644 index 00000000..d14d83ab --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/render.js @@ -0,0 +1,255 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('./kity'); + var Minder = require('./minder'); + var MinderNode = require('./node'); + + var Renderer = kity.createClass('Renderer', { + constructor: function (node) { + this.node = node; + }, + + create: function (node) { + throw new Error('Not implement: Renderer.create()'); + }, + + shouldRender: function (node) { + return true; + }, + + watchChange: function (data) { + var changed; + + if (this.watchingData === undefined) { + changed = true; + } else if (this.watchingData != data) { + changed = true; + } else { + changed = false; + } + + this.watchingData = data; + }, + + shouldDraw: function (node) { + return true; + }, + + update: function (shape, node, box) { + if (this.shouldDraw()) this.draw(shape, node); + return this.place(shape, node, box); + }, + + draw: function (shape, node) { + throw new Error('Not implement: Renderer.draw()'); + }, + + place: function (shape, node, box) { + throw new Error('Not implement: Renderer.place()'); + }, + + getRenderShape: function () { + return this._renderShape || null; + }, + + setRenderShape: function (shape) { + this._renderShape = shape; + }, + }); + + function createMinderExtension() { + function createRendererForNode(node, registered) { + var renderers = []; + + ['center', 'left', 'right', 'top', 'bottom', 'outline', 'outside'].forEach(function (section) { + var before = 'before' + section; + var after = 'after' + section; + + if (registered[before]) { + renderers = renderers.concat(registered[before]); + } + if (registered[section]) { + renderers = renderers.concat(registered[section]); + } + if (registered[after]) { + renderers = renderers.concat(registered[after]); + } + }); + + node._renderers = renderers.map(function (Renderer) { + return new Renderer(node); + }); + } + + return { + renderNodeBatch: function (nodes) { + var rendererClasses = this._rendererClasses; + var lastBoxes = []; + var rendererCount = 0; + var i, j, renderer, node; + + if (!nodes.length) return; + + for (j = 0; j < nodes.length; j++) { + node = nodes[j]; + if (!node._renderers) { + createRendererForNode(node, rendererClasses); + } + node._contentBox = new kity.Box(); + this.fire('beforerender', { + node: node, + }); + } + + // 所有节点渲染器数量是一致的 + rendererCount = nodes[0]._renderers.length; + + for (i = 0; i < rendererCount; i++) { + // 获取延迟盒子数据 + for (j = 0; j < nodes.length; j++) { + if (typeof lastBoxes[j] == 'function') { + lastBoxes[j] = lastBoxes[j](); + } + if (!(lastBoxes[j] instanceof kity.Box)) { + lastBoxes[j] = new kity.Box(lastBoxes[j]); + } + } + + for (j = 0; j < nodes.length; j++) { + node = nodes[j]; + renderer = node._renderers[i]; + + // 合并盒子 + if (lastBoxes[j]) { + node._contentBox = node._contentBox.merge(lastBoxes[j]); + renderer.contentBox = lastBoxes[j]; + } + + // 判断当前上下文是否应该渲染 + if (renderer.shouldRender(node)) { + // 应该渲染,但是渲染图形没创建过,需要创建 + if (!renderer.getRenderShape()) { + renderer.setRenderShape(renderer.create(node)); + if (renderer.bringToBack) { + node.getRenderContainer().prependShape(renderer.getRenderShape()); + } else { + node.getRenderContainer().appendShape(renderer.getRenderShape()); + } + } + + // 强制让渲染图形显示 + renderer.getRenderShape().setVisible(true); + + // 更新渲染图形 + lastBoxes[j] = renderer.update(renderer.getRenderShape(), node, node._contentBox); + } + + // 如果不应该渲染,但是渲染图形创建过了,需要隐藏起来 + else if (renderer.getRenderShape()) { + renderer.getRenderShape().setVisible(false); + lastBoxes[j] = null; + } + } + } + + for (j = 0; j < nodes.length; j++) { + this.fire('noderender', { + node: nodes[j], + }); + } + }, + + renderNode: function (node) { + var rendererClasses = this._rendererClasses; + var i, latestBox, renderer; + + if (!node._renderers) { + createRendererForNode(node, rendererClasses); + } + + this.fire('beforerender', { + node: node, + }); + + node._contentBox = new kity.Box(); + + node._renderers.forEach(function (renderer) { + // 判断当前上下文是否应该渲染 + if (renderer.shouldRender(node)) { + // 应该渲染,但是渲染图形没创建过,需要创建 + if (!renderer.getRenderShape()) { + renderer.setRenderShape(renderer.create(node)); + if (renderer.bringToBack) { + node.getRenderContainer().prependShape(renderer.getRenderShape()); + } else { + node.getRenderContainer().appendShape(renderer.getRenderShape()); + } + } + + // 强制让渲染图形显示 + renderer.getRenderShape().setVisible(true); + + // 更新渲染图形 + latestBox = renderer.update(renderer.getRenderShape(), node, node._contentBox); + + if (typeof latestBox == 'function') latestBox = latestBox(); + + // 合并渲染区域 + if (latestBox) { + node._contentBox = node._contentBox.merge(latestBox); + renderer.contentBox = latestBox; + } + } + + // 如果不应该渲染,但是渲染图形创建过了,需要隐藏起来 + else if (renderer.getRenderShape()) { + renderer.getRenderShape().setVisible(false); + } + }); + + this.fire('noderender', { + node: node, + }); + }, + }; + } + + kity.extendClass(Minder, createMinderExtension()); + + kity.extendClass(MinderNode, { + render: function () { + if (!this.attached) return; + this.getMinder().renderNode(this); + return this; + }, + renderTree: function () { + if (!this.attached) return; + var list = []; + this.traverse(function (node) { + list.push(node); + }); + this.getMinder().renderNodeBatch(list); + return this; + }, + getRenderer: function (type) { + var rs = this._renderers; + if (!rs) return null; + for (var i = 0; i < rs.length; i++) { + if (rs[i].getType() == type) return rs[i]; + } + return null; + }, + getContentBox: function () { + //if (!this._contentBox) this.render(); + return this.parent && this.parent.isCollapsed() ? new kity.Box() : this._contentBox || new kity.Box(); + }, + getRenderBox: function (rendererType, refer) { + var renderer = rendererType && this.getRenderer(rendererType); + var contentBox = renderer ? renderer.contentBox : this.getContentBox(); + var ctm = kity.Matrix.getCTM(this.getRenderContainer(), refer || 'paper'); + return ctm.transformBox(contentBox); + }, + }); + + module.exports = Renderer; +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/select.js b/packages/client/src/thirtypart/kityminder/kity-core/core/select.js new file mode 100644 index 00000000..dd12cadc --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/select.js @@ -0,0 +1,146 @@ +/* eslint-disable */ + +define(function (require, exports, module) { + var kity = require('./kity'); + var utils = require('./utils'); + var Minder = require('./minder'); + var MinderNode = require('./node'); + + Minder.registerInitHook(function () { + this._initSelection(); + }); + + // 选区管理 + kity.extendClass(Minder, { + _initSelection: function () { + this._selectedNodes = []; + }, + renderChangedSelection: function (last) { + var current = this.getSelectedNodes(); + var changed = []; + + current.forEach(function (node) { + if (last.indexOf(node) == -1) { + changed.push(node); + } + }); + + last.forEach(function (node) { + if (current.indexOf(node) == -1) { + changed.push(node); + } + }); + + if (changed.length) { + this._interactChange(); + this.fire('selectionchange'); + } + while (changed.length) { + changed.shift().render(); + } + }, + getSelectedNodes: function () { + //不能克隆返回,会对当前选区操作,从而影响querycommand + return this._selectedNodes; + }, + getSelectedNode: function () { + return this.getSelectedNodes()[0] || null; + }, + removeAllSelectedNodes: function () { + var me = this; + var last = this._selectedNodes.splice(0); + this._selectedNodes = []; + this.renderChangedSelection(last); + return this.fire('selectionclear'); + }, + removeSelectedNodes: function (nodes) { + var me = this; + var last = this._selectedNodes.slice(0); + nodes = utils.isArray(nodes) ? nodes : [nodes]; + + nodes.forEach(function (node) { + var index; + if ((index = me._selectedNodes.indexOf(node)) === -1) return; + me._selectedNodes.splice(index, 1); + }); + + this.renderChangedSelection(last); + return this; + }, + select: function (nodes, isSingleSelect) { + var lastSelect = this.getSelectedNodes().slice(0); + if (isSingleSelect) { + this._selectedNodes = []; + } + var me = this; + nodes = utils.isArray(nodes) ? nodes : [nodes]; + nodes.forEach(function (node) { + if (me._selectedNodes.indexOf(node) !== -1) return; + me._selectedNodes.unshift(node); + }); + this.renderChangedSelection(lastSelect); + return this; + }, + selectById: function (ids, isSingleSelect) { + ids = utils.isArray(ids) ? ids : [ids]; + var nodes = this.getNodesById(ids); + return this.select(nodes, isSingleSelect); + }, + //当前选区中的节点在给定的节点范围内的保留选中状态, + //没在给定范围的取消选中,给定范围中的但没在当前选中范围的也做选中效果 + toggleSelect: function (node) { + if (utils.isArray(node)) { + node.forEach(this.toggleSelect.bind(this)); + } else { + if (node.isSelected()) this.removeSelectedNodes(node); + else this.select(node); + } + return this; + }, + + isSingleSelect: function () { + return this._selectedNodes.length == 1; + }, + + getSelectedAncestors: function (includeRoot) { + var nodes = this.getSelectedNodes().slice(0), + ancestors = [], + judge; + + // 根节点不参与计算 + var rootIndex = nodes.indexOf(this.getRoot()); + if (~rootIndex && !includeRoot) { + nodes.splice(rootIndex, 1); + } + + // 判断 nodes 列表中是否存在 judge 的祖先 + function hasAncestor(nodes, judge) { + for (var i = nodes.length - 1; i >= 0; --i) { + if (nodes[i].isAncestorOf(judge)) return true; + } + return false; + } + + // 按照拓扑排序 + nodes.sort(function (node1, node2) { + return node1.getLevel() - node2.getLevel(); + }); + + // 因为是拓扑有序的,所以只需往上查找 + while ((judge = nodes.pop())) { + if (!hasAncestor(nodes, judge)) { + ancestors.push(judge); + } + } + + return ancestors; + }, + }); + + kity.extendClass(MinderNode, { + isSelected: function () { + var minder = this.getMinder(); + return minder && minder.getSelectedNodes().indexOf(this) != -1; + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/shortcut.js b/packages/client/src/thirtypart/kityminder/kity-core/core/shortcut.js new file mode 100644 index 00000000..d00c86f4 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/shortcut.js @@ -0,0 +1,156 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 添加快捷键支持 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ + +define(function (require, exports, module) { + var kity = require('./kity'); + var utils = require('./utils'); + var keymap = require('./keymap'); + var Minder = require('./minder'); + var MinderEvent = require('./event'); + + /** + * 计算包含 meta 键的 keycode + * + * @param {String|KeyEvent} unknown + */ + function getMetaKeyCode(unknown) { + var CTRL_MASK = 0x1000; + var ALT_MASK = 0x2000; + var SHIFT_MASK = 0x4000; + var metaKeyCode = 0; + + if (typeof unknown == 'string') { + // unknown as string + unknown + .toLowerCase() + .split(/\+\s*/) + .forEach(function (name) { + switch (name) { + case 'ctrl': + case 'cmd': + metaKeyCode |= CTRL_MASK; + break; + case 'alt': + metaKeyCode |= ALT_MASK; + break; + case 'shift': + metaKeyCode |= SHIFT_MASK; + break; + default: + metaKeyCode |= keymap[name]; + } + }); + } else { + // unknown as key event + if (unknown.ctrlKey || unknown.metaKey) { + metaKeyCode |= CTRL_MASK; + } + if (unknown.altKey) { + metaKeyCode |= ALT_MASK; + } + if (unknown.shiftKey) { + metaKeyCode |= SHIFT_MASK; + } + metaKeyCode |= unknown.keyCode; + } + + return metaKeyCode; + } + kity.extendClass(MinderEvent, { + isShortcutKey: function (keyCombine) { + var keyEvent = this.originEvent; + if (!keyEvent) return false; + + return getMetaKeyCode(keyCombine) == getMetaKeyCode(keyEvent); + }, + }); + + Minder.registerInitHook(function () { + this._initShortcutKey(); + }); + + kity.extendClass(Minder, { + _initShortcutKey: function () { + this._bindShortcutKeys(); + }, + + _bindShortcutKeys: function () { + var map = (this._shortcutKeys = {}); + var has = 'hasOwnProperty'; + this.on('keydown', function (e) { + for (var keys in map) { + if (!map[has](keys)) continue; + if (e.isShortcutKey(keys)) { + var fn = map[keys]; + if (fn.__statusCondition && fn.__statusCondition != this.getStatus()) return; + fn(); + e.preventDefault(); + } + } + }); + }, + + addShortcut: function (keys, fn) { + var binds = this._shortcutKeys; + keys.split(/\|\s*/).forEach(function (combine) { + var parts = combine.split('::'); + var status; + if (parts.length > 1) { + combine = parts[1]; + status = parts[0]; + fn.__statusCondition = status; + } + binds[combine] = fn; + }); + }, + + addCommandShortcutKeys: function (cmd, keys) { + var binds = this._commandShortcutKeys || (this._commandShortcutKeys = {}); + var obj = {}, + km = this; + if (keys) { + obj[cmd] = keys; + } else { + obj = cmd; + } + + var minder = this; + + utils.each(obj, function (keys, command) { + binds[command] = keys; + + minder.addShortcut(keys, function execCommandByShortcut() { + /** + * 之前判断有问题,由 === 0 改为 !== -1 + * @editor Naixor + * @Date 2015-12-2 + */ + if (minder.queryCommandState(command) !== -1) { + minder.execCommand(command); + } + }); + }); + }, + + getCommandShortcutKey: function (cmd) { + var binds = this._commandShortcutKeys; + return (binds && binds[cmd]) || null; + }, + + /** + * @Desc: 添加一个判断是否支持原生Clipboard的变量,用于对ctrl + v和ctrl + c的处理 + * @Editor: Naixor + * @Date: 2015.9.20 + */ + supportClipboardEvent: (function (window) { + return !!window.ClipboardEvent; + })(window), + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/status.js b/packages/client/src/thirtypart/kityminder/kity-core/core/status.js new file mode 100644 index 00000000..bed89c80 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/status.js @@ -0,0 +1,59 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 状态切换控制 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ + +define(function (require, exports, module) { + var kity = require('./kity'); + var Minder = require('./minder'); + + var sf = ~window.location.href.indexOf('status'); + var tf = ~window.location.href.indexOf('trace'); + + Minder.registerInitHook(function () { + this._initStatus(); + }); + + kity.extendClass(Minder, { + _initStatus: function () { + this._status = 'normal'; + this._rollbackStatus = 'normal'; + }, + + setStatus: function (status, force) { + // 在 readonly 模式下,只有 force 为 true 才能切换回来 + if (this._status == 'readonly' && !force) return this; + if (status != this._status) { + this._rollbackStatus = this._status; + this._status = status; + this.fire('statuschange', { + lastStatus: this._rollbackStatus, + currentStatus: this._status, + }); + if (sf) { + /* global console: true */ + console.log(window.event.type, this._rollbackStatus, '->', this._status); + if (tf) { + console.trace(); + } + } + } + return this; + }, + + rollbackStatus: function () { + this.setStatus(this._rollbackStatus); + }, + getRollbackStatus: function () { + return this._rollbackStatus; + }, + getStatus: function () { + return this._status; + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/template.js b/packages/client/src/thirtypart/kityminder/kity-core/core/template.js new file mode 100644 index 00000000..4eb3550c --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/template.js @@ -0,0 +1,102 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('./kity'); + var utils = require('./utils'); + var Minder = require('./minder'); + var Command = require('./command'); + var MinderNode = require('./node'); + var Module = require('./module'); + + var _templates = {}; + + function register(name, supports) { + _templates[name] = supports; + } + exports.register = register; + + utils.extend(Minder, { + getTemplateList: function () { + return _templates; + }, + }); + + kity.extendClass( + Minder, + (function () { + var originGetTheme = Minder.prototype.getTheme; + return { + useTemplate: function (name, duration) { + this.setTemplate(name); + this.refresh(duration || 500); + }, + + getTemplate: function () { + return this._template || 'default'; + }, + + setTemplate: function (name) { + this._template = name || null; + }, + + getTemplateSupport: function (method) { + var supports = _templates[this.getTemplate()]; + return supports && supports[method]; + }, + + getTheme: function (node) { + var support = this.getTemplateSupport('getTheme') || originGetTheme; + return support.call(this, node); + }, + }; + })() + ); + + kity.extendClass( + MinderNode, + (function () { + var originGetLayout = MinderNode.prototype.getLayout; + var originGetConnect = MinderNode.prototype.getConnect; + return { + getLayout: function () { + var support = this.getMinder().getTemplateSupport('getLayout') || originGetLayout; + return support.call(this, this); + }, + + getConnect: function () { + var support = this.getMinder().getTemplateSupport('getConnect') || originGetConnect; + return support.call(this, this); + }, + }; + })() + ); + let timer = null; + + Module.register('TemplateModule', { + /** + * @command Template + * @description 设置当前脑图的模板 + * @param {string} name 模板名称 + * 允许使用的模板可以使用 `kityminder.Minder.getTemplateList()` 查询 + * @state + * 0: 始终可用 + * @return 返回当前的模板名称 + */ + commands: { + template: kity.createClass('TemplateCommand', { + base: Command, + + execute: function (minder, name) { + minder.useTemplate(name); + clearTimeout(timer); + timer = setTimeout(() => { + minder.execCommand('camera'); + }, 550); + }, + + queryValue: function (minder) { + return minder.getTemplate() || 'default'; + }, + }), + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/theme.js b/packages/client/src/thirtypart/kityminder/kity-core/core/theme.js new file mode 100644 index 00000000..459493d3 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/theme.js @@ -0,0 +1,171 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('./kity'); + var utils = require('./utils'); + var Minder = require('./minder'); + var MinderNode = require('./node'); + var Module = require('./module'); + var Command = require('./command'); + + var cssLikeValueMatcher = { + left: function (value) { + return (3 in value && value[3]) || (1 in value && value[1]) || value[0]; + }, + right: function (value) { + return (1 in value && value[1]) || value[0]; + }, + top: function (value) { + return value[0]; + }, + bottom: function (value) { + return (2 in value && value[2]) || value[0]; + }, + }; + + var _themes = {}; + + /** + * 注册一个主题 + * + * @param {String} name 主题的名称 + * @param {Plain} theme 主题的样式描述 + * + * @example + * Minder.registerTheme('default', { + * 'root-color': 'red', + * 'root-stroke': 'none', + * 'root-padding': [10, 20] + * }); + */ + function register(name, theme) { + _themes[name] = theme; + } + exports.register = register; + + utils.extend(Minder, { + getThemeList: function () { + return _themes; + }, + }); + + kity.extendClass(Minder, { + /** + * 切换脑图实例上的主题 + * @param {String} name 要使用的主题的名称 + */ + useTheme: function (name) { + this.setTheme(name); + this.refresh(800); + + return true; + }, + + setTheme: function (name) { + if (name && !_themes[name]) throw new Error('Theme ' + name + ' not exists!'); + var lastTheme = this._theme; + this._theme = name || null; + var container = this.getRenderTarget(); + if (container) { + container.classList.remove('km-theme-' + lastTheme); + if (name) { + container.classList.add('km-theme-' + name); + } + container.style.background = this.getStyle('background'); + } + this.fire('themechange', { + theme: name, + }); + return this; + }, + + /** + * 获取脑图实例上的当前主题 + * @return {[type]} [description] + */ + getTheme: function (node) { + return this._theme || this.getOption('defaultTheme') || 'fresh-blue'; + }, + + getThemeItems: function (node) { + var theme = this.getTheme(node); + return _themes[this.getTheme(node)]; + }, + + /** + * 获得脑图实例上的样式 + * @param {String} item 样式名称 + */ + getStyle: function (item, node) { + var items = this.getThemeItems(node); + var segment, dir, selector, value, matcher; + + if (item in items) return items[item]; + + // 尝试匹配 CSS 数组形式的值 + // 比如 item 为 'pading-left' + // theme 里有 {'padding': [10, 20]} 的定义,则可以返回 20 + segment = item.split('-'); + if (segment.length < 2) return null; + + dir = segment.pop(); + item = segment.join('-'); + + if (item in items) { + value = items[item]; + if (utils.isArray(value) && (matcher = cssLikeValueMatcher[dir])) { + return matcher(value); + } + if (!isNaN(value)) return value; + } + + return null; + }, + + /** + * 获取指定节点的样式 + * @param {String} name 样式名称,可以不加节点类型的前缀 + */ + getNodeStyle: function (node, name) { + var value = this.getStyle(node.getType() + '-' + name, node); + return value !== null ? value : this.getStyle(name, node); + }, + }); + + kity.extendClass(MinderNode, { + getStyle: function (name) { + return this.getMinder().getNodeStyle(this, name); + }, + }); + + Module.register('Theme', { + defaultOptions: { + defaultTheme: 'fresh-blue', + }, + commands: { + /** + * @command Theme + * @description 设置当前脑图的主题 + * @param {string} name 主题名称 + * 允许使用的主题可以使用 `kityminder.Minder.getThemeList()` 查询 + * @state + * 0: 始终可用 + * @return 返回当前的主题名称 + */ + theme: kity.createClass('ThemeCommand', { + base: Command, + + execute: function (km, name) { + return km.useTheme(name); + }, + + queryValue: function (km) { + return km.getTheme() || 'default'; + }, + }), + }, + }); + + Minder.registerInitHook(function () { + this.setTheme(); + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/core/utils.js b/packages/client/src/thirtypart/kityminder/kity-core/core/utils.js new file mode 100644 index 00000000..f963ea1a --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/core/utils.js @@ -0,0 +1,68 @@ +/* eslint-disable */ +define(function (require, exports) { + var kity = require('./kity'); + var uuidMap = {}; + + exports.extend = kity.Utils.extend.bind(kity.Utils); + exports.each = kity.Utils.each.bind(kity.Utils); + + exports.uuid = function (group) { + uuidMap[group] = uuidMap[group] ? uuidMap[group] + 1 : 1; + return group + uuidMap[group]; + }; + + exports.guid = function () { + return (+new Date() * 1e6 + Math.floor(Math.random() * 1e6)).toString(36); + }; + + exports.trim = function (str) { + return str.replace(/(^[ \t\n\r]+)|([ \t\n\r]+$)/g, ''); + }; + + exports.keys = function (plain) { + var keys = []; + for (var key in plain) { + if (plain.hasOwnProperty(key)) { + keys.push(key); + } + } + return keys; + }; + + exports.clone = function (source) { + return JSON.parse(JSON.stringify(source)); + }; + + exports.comparePlainObject = function (a, b) { + return JSON.stringify(a) == JSON.stringify(b); + }; + + exports.encodeHtml = function (str, reg) { + return str + ? str.replace(reg || /[&<">'](?:(amp|lt|quot|gt|#39|nbsp);)?/g, function (a, b) { + if (b) { + return a; + } else { + return { + '<': '<', + '&': '&', + '"': '"', + '>': '>', + "'": ''', + }[a]; + } + }) + : ''; + }; + + exports.clearWhiteSpace = function (str) { + return str.replace(/[\u200b\t\r\n]/g, ''); + }; + + exports.each(['String', 'Function', 'Array', 'Number', 'RegExp', 'Object'], function (v) { + var toString = Object.prototype.toString; + exports['is' + v] = function (obj) { + return toString.apply(obj) == '[object ' + v + ']'; + }; + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/expose-kityminder.js b/packages/client/src/thirtypart/kityminder/kity-core/expose-kityminder.js new file mode 100644 index 00000000..e282886b --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/expose-kityminder.js @@ -0,0 +1,4 @@ +/* eslint-disable */ +define('expose-kityminder', function (require, exports, module) { + module.exports = window.kityminder = require('./kityminder'); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/kityminder.js b/packages/client/src/thirtypart/kityminder/kity-core/kityminder.js new file mode 100644 index 00000000..bb127dee --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/kityminder.js @@ -0,0 +1,107 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 默认导出(全部模块) + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ + +define(function (require, exports, module) { + var kityminder = { + version: require('./core/minder').version, + }; + + // 核心导出,大写的部分导出类,小写的部分简单 require 一下 + // 这里顺序是有讲究的,调整前先弄清楚依赖关系。 + require('./core/utils'); + kityminder.Minder = require('./core/minder'); + kityminder.Command = require('./core/command'); + kityminder.Node = require('./core/node'); + require('./core/option'); + require('./core/animate'); + kityminder.Event = require('./core/event'); + kityminder.data = require('./core/data'); + require('./core/compatibility'); + kityminder.KeyMap = require('./core/keymap'); + require('./core/shortcut'); + require('./core/status'); + require('./core/paper'); + require('./core/select'); + require('./core/focus'); + require('./core/keyreceiver'); + kityminder.Module = require('./core/module'); + require('./core/readonly'); + kityminder.Render = require('./core/render'); + kityminder.Connect = require('./core/connect'); + kityminder.Layout = require('./core/layout'); + kityminder.Theme = require('./core/theme'); + kityminder.Template = require('./core/template'); + kityminder.Promise = require('./core/promise'); + require('./core/_boxv'); + require('./core/patch'); + + // 模块依赖 + require('./module/arrange'); + require('./module/basestyle'); + require('./module/clipboard'); + require('./module/dragtree'); + require('./module/expand'); + require('./module/font'); + require('./module/hyperlink'); + require('./module/image'); + require('./module/image-viewer'); + require('./module/keynav'); + require('./module/layout'); + require('./module/node'); + require('./module/note'); + require('./module/outline'); + require('./module/priority'); + require('./module/progress'); + require('./module/resource'); + require('./module/select'); + require('./module/style'); + require('./module/text'); + require('./module/view'); + require('./module/zoom'); + + require('./protocol/json'); + require('./protocol/text'); + require('./protocol/markdown'); + require('./protocol/svg'); + require('./protocol/png'); + + require('./layout/mind'); + require('./layout/btree'); + require('./layout/filetree'); + require('./layout/fish-bone-master'); + require('./layout/fish-bone-slave'); + require('./layout/tianpan'); + + require('./theme/default'); + require('./theme/snow'); + require('./theme/fresh'); + require('./theme/fish'); + require('./theme/snow'); + require('./theme/wire'); + require('./theme/tianpan'); + + require('./connect/arc'); + require('./connect/arc_tp'); + require('./connect/bezier'); + require('./connect/fish-bone-master'); + require('./connect/l'); + require('./connect/poly'); + require('./connect/under'); + + require('./template/default'); + require('./template/structure'); + require('./template/filetree'); + require('./template/right'); + require('./template/fish-bone'); + require('./template/tianpan'); + + window.kityminder = kityminder; + module.exports = kityminder; +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/layout/btree.js b/packages/client/src/thirtypart/kityminder/kity-core/layout/btree.js new file mode 100644 index 00000000..038ebddf --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/layout/btree.js @@ -0,0 +1,145 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var Layout = require('../core/layout'); + + ['left', 'right', 'top', 'bottom'].forEach(registerLayoutForDirection); + + function registerLayoutForDirection(name) { + var axis = name == 'left' || name == 'right' ? 'x' : 'y'; + var dir = name == 'left' || name == 'top' ? -1 : 1; + + var oppsite = { + left: 'right', + right: 'left', + top: 'bottom', + bottom: 'top', + x: 'y', + y: 'x', + }; + + function getOrderHint(node) { + var hint = []; + var box = node.getLayoutBox(); + var offset = 5; + + if (axis == 'x') { + hint.push({ + type: 'up', + node: node, + area: new kity.Box({ + x: box.x, + y: box.top - node.getStyle('margin-top') - offset, + width: box.width, + height: node.getStyle('margin-top'), + }), + path: ['M', box.x, box.top - offset, 'L', box.right, box.top - offset], + }); + + hint.push({ + type: 'down', + node: node, + area: new kity.Box({ + x: box.x, + y: box.bottom + offset, + width: box.width, + height: node.getStyle('margin-bottom'), + }), + path: ['M', box.x, box.bottom + offset, 'L', box.right, box.bottom + offset], + }); + } else { + hint.push({ + type: 'up', + node: node, + area: new kity.Box({ + x: box.left - node.getStyle('margin-left') - offset, + y: box.top, + width: node.getStyle('margin-left'), + height: box.height, + }), + path: ['M', box.left - offset, box.top, 'L', box.left - offset, box.bottom], + }); + + hint.push({ + type: 'down', + node: node, + area: new kity.Box({ + x: box.right + offset, + y: box.top, + width: node.getStyle('margin-right'), + height: box.height, + }), + path: ['M', box.right + offset, box.top, 'L', box.right + offset, box.bottom], + }); + } + return hint; + } + + Layout.register( + name, + kity.createClass({ + base: Layout, + + doLayout: function (parent, children) { + var pbox = parent.getContentBox(); + + if (axis == 'x') { + parent.setVertexOut(new kity.Point(pbox[name], pbox.cy)); + parent.setLayoutVectorOut(new kity.Vector(dir, 0)); + } else { + parent.setVertexOut(new kity.Point(pbox.cx, pbox[name])); + parent.setLayoutVectorOut(new kity.Vector(0, dir)); + } + + if (!children.length) { + return false; + } + + children.forEach(function (child) { + var cbox = child.getContentBox(); + child.setLayoutTransform(new kity.Matrix()); + + if (axis == 'x') { + child.setVertexIn(new kity.Point(cbox[oppsite[name]], cbox.cy)); + child.setLayoutVectorIn(new kity.Vector(dir, 0)); + } else { + child.setVertexIn(new kity.Point(cbox.cx, cbox[oppsite[name]])); + child.setLayoutVectorIn(new kity.Vector(0, dir)); + } + }); + + this.align(children, oppsite[name]); + this.stack(children, oppsite[axis]); + + var bbox = this.getBranchBox(children); + var xAdjust = 0, + yAdjust = 0; + + if (axis == 'x') { + xAdjust = pbox[name]; + xAdjust += dir * parent.getStyle('margin-' + name); + xAdjust += dir * children[0].getStyle('margin-' + oppsite[name]); + + yAdjust = pbox.bottom; + yAdjust -= pbox.height / 2; + yAdjust -= bbox.height / 2; + yAdjust -= bbox.y; + } else { + xAdjust = pbox.right; + xAdjust -= pbox.width / 2; + xAdjust -= bbox.width / 2; + xAdjust -= bbox.x; + + yAdjust = pbox[name]; + yAdjust += dir * parent.getStyle('margin-' + name); + yAdjust += dir * children[0].getStyle('margin-' + oppsite[name]); + } + + this.move(children, xAdjust, yAdjust); + }, + + getOrderHint: getOrderHint, + }) + ); + } +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/layout/filetree.js b/packages/client/src/thirtypart/kityminder/kity-core/layout/filetree.js new file mode 100644 index 00000000..e38c7686 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/layout/filetree.js @@ -0,0 +1,90 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var Layout = require('../core/layout'); + + [-1, 1].forEach(registerLayoutForDir); + + function registerLayoutForDir(dir) { + var name = 'filetree-' + (dir > 0 ? 'down' : 'up'); + + Layout.register( + name, + kity.createClass({ + base: Layout, + + doLayout: function (parent, children, round) { + var pBox = parent.getContentBox(); + var indent = 20; + + parent.setVertexOut(new kity.Point(pBox.left + indent, dir > 0 ? pBox.bottom : pBox.top)); + parent.setLayoutVectorOut(new kity.Vector(0, dir)); + + if (!children.length) return; + + children.forEach(function (child) { + var cbox = child.getContentBox(); + child.setLayoutTransform(new kity.Matrix()); + + child.setVertexIn(new kity.Point(cbox.left, cbox.cy)); + child.setLayoutVectorIn(new kity.Vector(1, 0)); + }); + + this.align(children, 'left'); + this.stack(children, 'y'); + + var xAdjust = 0; + xAdjust += pBox.left; + xAdjust += indent; + xAdjust += children[0].getStyle('margin-left'); + + var yAdjust = 0; + + if (dir > 0) { + yAdjust += pBox.bottom; + yAdjust += parent.getStyle('margin-bottom'); + yAdjust += children[0].getStyle('margin-top'); + } else { + yAdjust -= this.getTreeBox(children).bottom; + yAdjust += pBox.top; + yAdjust -= parent.getStyle('margin-top'); + yAdjust -= children[0].getStyle('margin-bottom'); + } + + this.move(children, xAdjust, yAdjust); + }, + + getOrderHint: function (node) { + var hint = []; + var box = node.getLayoutBox(); + var offset = node.getLevel() > 1 ? 3 : 5; + + hint.push({ + type: 'up', + node: node, + area: new kity.Box({ + x: box.x, + y: box.top - node.getStyle('margin-top') - offset, + width: box.width, + height: node.getStyle('margin-top'), + }), + path: ['M', box.x, box.top - offset, 'L', box.right, box.top - offset], + }); + + hint.push({ + type: 'down', + node: node, + area: new kity.Box({ + x: box.x, + y: box.bottom + offset, + width: box.width, + height: node.getStyle('margin-bottom'), + }), + path: ['M', box.x, box.bottom + offset, 'L', box.right, box.bottom + offset], + }); + return hint; + }, + }) + ); + } +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/layout/fish-bone-master.js b/packages/client/src/thirtypart/kityminder/kity-core/layout/fish-bone-master.js new file mode 100644 index 00000000..6ba1fd1a --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/layout/fish-bone-master.js @@ -0,0 +1,68 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 鱼骨图主骨架布局 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ + +define(function (require, exports, module) { + var kity = require('../core/kity'); + var Layout = require('../core/layout'); + + Layout.register( + 'fish-bone-master', + kity.createClass('FishBoneMasterLayout', { + base: Layout, + + doLayout: function (parent, children, round) { + var upPart = [], + downPart = []; + + var child = children[0]; + var pBox = parent.getContentBox(); + + parent.setVertexOut(new kity.Point(pBox.right, pBox.cy)); + parent.setLayoutVectorOut(new kity.Vector(1, 0)); + + if (!child) return; + + var cBox = child.getContentBox(); + var pMarginRight = parent.getStyle('margin-right'); + var cMarginLeft = child.getStyle('margin-left'); + var cMarginTop = child.getStyle('margin-top'); + var cMarginBottom = child.getStyle('margin-bottom'); + + children.forEach(function (child, index) { + child.setLayoutTransform(new kity.Matrix()); + var cBox = child.getContentBox(); + + if (index % 2) { + downPart.push(child); + child.setVertexIn(new kity.Point(cBox.left, cBox.top)); + child.setLayoutVectorIn(new kity.Vector(1, 1)); + } else { + upPart.push(child); + child.setVertexIn(new kity.Point(cBox.left, cBox.bottom)); + child.setLayoutVectorIn(new kity.Vector(1, -1)); + } + }); + + this.stack(upPart, 'x'); + this.stack(downPart, 'x'); + + this.align(upPart, 'bottom'); + this.align(downPart, 'top'); + + var xAdjust = pBox.right + pMarginRight + cMarginLeft; + var yAdjustUp = pBox.cy - cMarginBottom - parent.getStyle('margin-top'); + var yAdjustDown = pBox.cy + cMarginTop + parent.getStyle('margin-bottom'); + + this.move(upPart, xAdjust, yAdjustUp); + this.move(downPart, xAdjust + cMarginLeft, yAdjustDown); + }, + }) + ); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/layout/fish-bone-slave.js b/packages/client/src/thirtypart/kityminder/kity-core/layout/fish-bone-slave.js new file mode 100644 index 00000000..4ee9a409 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/layout/fish-bone-slave.js @@ -0,0 +1,76 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ + +define(function (require, exports, module) { + var kity = require('../core/kity'); + var Layout = require('../core/layout'); + + Layout.register( + 'fish-bone-slave', + kity.createClass('FishBoneSlaveLayout', { + base: Layout, + + doLayout: function (parent, children, round) { + var layout = this; + var abs = Math.abs; + var GOLD_CUT = 1 - 0.618; + + var pBox = parent.getContentBox(); + var vi = parent.getLayoutVectorIn(); + + parent.setLayoutVectorOut(vi); + + var goldX = pBox.left + pBox.width * GOLD_CUT; + var pout = new kity.Point(goldX, vi.y > 0 ? pBox.bottom : pBox.top); + parent.setVertexOut(pout); + + var child = children[0]; + if (!child) return; + + var cBox = child.getContentBox(); + + children.forEach(function (child, index) { + child.setLayoutTransform(new kity.Matrix()); + child.setLayoutVectorIn(new kity.Vector(1, 0)); + child.setVertexIn(new kity.Point(cBox.left, cBox.cy)); + }); + + this.stack(children, 'y'); + this.align(children, 'left'); + + var xAdjust = 0, + yAdjust = 0; + xAdjust += pout.x; + + if (parent.getLayoutVectorOut().y < 0) { + yAdjust -= this.getTreeBox(children).bottom; + yAdjust += parent.getContentBox().top; + yAdjust -= parent.getStyle('margin-top'); + yAdjust -= child.getStyle('margin-bottom'); + } else { + yAdjust += parent.getContentBox().bottom; + yAdjust += parent.getStyle('margin-bottom'); + yAdjust += child.getStyle('margin-top'); + } + + this.move(children, xAdjust, yAdjust); + + if (round == 2) { + children.forEach(function (child) { + var m = child.getLayoutTransform(); + var cbox = child.getContentBox(); + var pin = m.transformPoint(new kity.Point(cbox.left, 0)); + layout.move([child], abs(pin.y - pout.y), 0); + }); + } + }, + }) + ); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/layout/mind.js b/packages/client/src/thirtypart/kityminder/kity-core/layout/mind.js new file mode 100644 index 00000000..57f0cbed --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/layout/mind.js @@ -0,0 +1,66 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var Layout = require('../core/layout'); + var Minder = require('../core/minder'); + + Layout.register( + 'mind', + kity.createClass({ + base: Layout, + + doLayout: function (node, children) { + var layout = this; + var half = Math.ceil(node.children.length / 2); + var right = []; + var left = []; + + children.forEach(function (child) { + if (child.getIndex() < half) right.push(child); + else left.push(child); + }); + + var leftLayout = Minder.getLayoutInstance('left'); + var rightLayout = Minder.getLayoutInstance('right'); + + leftLayout.doLayout(node, left); + rightLayout.doLayout(node, right); + + var box = node.getContentBox(); + node.setVertexOut(new kity.Point(box.cx, box.cy)); + node.setLayoutVectorOut(new kity.Vector(0, 0)); + }, + + getOrderHint: function (node) { + var hint = []; + var box = node.getLayoutBox(); + var offset = 5; + + hint.push({ + type: 'up', + node: node, + area: new kity.Box({ + x: box.x, + y: box.top - node.getStyle('margin-top') - offset, + width: box.width, + height: node.getStyle('margin-top'), + }), + path: ['M', box.x, box.top - offset, 'L', box.right, box.top - offset], + }); + + hint.push({ + type: 'down', + node: node, + area: new kity.Box({ + x: box.x, + y: box.bottom + offset, + width: box.width, + height: node.getStyle('margin-bottom'), + }), + path: ['M', box.x, box.bottom + offset, 'L', box.right, box.bottom + offset], + }); + return hint; + }, + }) + ); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/layout/tianpan.js b/packages/client/src/thirtypart/kityminder/kity-core/layout/tianpan.js new file mode 100644 index 00000000..5c666eda --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/layout/tianpan.js @@ -0,0 +1,80 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 天盘模板 + * + * @author: along + * @copyright: bpd729@163.com, 2015 + */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var Layout = require('../core/layout'); + var Minder = require('../core/minder'); + + Layout.register( + 'tianpan', + kity.createClass({ + base: Layout, + + doLayout: function (parent, children) { + if (children.length == 0) return; + + var layout = this; + var pbox = parent.getContentBox(); + + var x, y, box; + var _theta = 5; + var _r = Math.max(pbox.width, 50); + children.forEach(function (child, index) { + child.setLayoutTransform(new kity.Matrix()); + box = layout.getTreeBox(child); + _r = Math.max(Math.max(box.width, box.height), _r); + }); + _r = _r / 1.5 / Math.PI; + + children.forEach(function (child, index) { + x = _r * (Math.cos(_theta) + Math.sin(_theta) * _theta); + y = _r * (Math.sin(_theta) - Math.cos(_theta) * _theta); + + _theta += 0.9 - index * 0.02; + child.setLayoutVectorIn(new kity.Vector(1, 0)); + child.setVertexIn(new kity.Point(pbox.cx, pbox.cy)); + child.setLayoutTransform(new kity.Matrix()); + layout.move([child], x, y); + }); + }, + + getOrderHint: function (node) { + var hint = []; + var box = node.getLayoutBox(); + var offset = 5; + + hint.push({ + type: 'up', + node: node, + area: { + x: box.x, + y: box.top - node.getStyle('margin-top') - offset, + width: box.width, + height: node.getStyle('margin-top'), + }, + path: ['M', box.x, box.top - offset, 'L', box.right, box.top - offset], + }); + + hint.push({ + type: 'down', + node: node, + area: { + x: box.x, + y: box.bottom + offset, + width: box.width, + height: node.getStyle('margin-bottom'), + }, + path: ['M', box.x, box.bottom + offset, 'L', box.right, box.bottom + offset], + }); + return hint; + }, + }) + ); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/arrange.js b/packages/client/src/thirtypart/kityminder/kity-core/module/arrange.js new file mode 100644 index 00000000..3fe5fcd9 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/arrange.js @@ -0,0 +1,166 @@ +/* eslint-disable */ + +define(function (require, exports, module) { + var kity = require('../core/kity'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + + kity.extendClass(MinderNode, { + arrange: function (index) { + var parent = this.parent; + if (!parent) return; + var sibling = parent.children; + + if (index < 0 || index >= sibling.length) return; + sibling.splice(this.getIndex(), 1); + sibling.splice(index, 0, this); + return this; + }, + }); + + function asc(nodeA, nodeB) { + return nodeA.getIndex() - nodeB.getIndex(); + } + function desc(nodeA, nodeB) { + return -asc(nodeA, nodeB); + } + + function canArrange(km) { + var selected = km.getSelectedNode(); + return selected && selected.parent && selected.parent.children.length > 1; + } + + /** + * @command ArrangeUp + * @description 向上调整选中节点的位置 + * @shortcut Alt + Up + * @state + * 0: 当前选中了具有相同父亲的节点 + * -1: 其它情况 + */ + var ArrangeUpCommand = kity.createClass('ArrangeUpCommand', { + base: Command, + + execute: function (km) { + var nodes = km.getSelectedNodes(); + nodes.sort(asc); + var lastIndexes = nodes.map(function (node) { + return node.getIndex(); + }); + nodes.forEach(function (node, index) { + node.arrange(lastIndexes[index] - 1); + }); + km.layout(300); + }, + + queryState: function (km) { + var selected = km.getSelectedNode(); + return selected ? 0 : -1; + }, + }); + + /** + * @command ArrangeDown + * @description 向下调整选中节点的位置 + * @shortcut Alt + Down + * @state + * 0: 当前选中了具有相同父亲的节点 + * -1: 其它情况 + */ + var ArrangeDownCommand = kity.createClass('ArrangeUpCommand', { + base: Command, + + execute: function (km) { + var nodes = km.getSelectedNodes(); + nodes.sort(desc); + var lastIndexes = nodes.map(function (node) { + return node.getIndex(); + }); + nodes.forEach(function (node, index) { + node.arrange(lastIndexes[index] + 1); + }); + km.layout(300); + }, + + queryState: function (km) { + var selected = km.getSelectedNode(); + return selected ? 0 : -1; + }, + }); + + /** + * @command Arrange + * @description 调整选中节点的位置 + * @param {number} index 调整后节点的新位置 + * @state + * 0: 当前选中了具有相同父亲的节点 + * -1: 其它情况 + */ + var ArrangeCommand = kity.createClass('ArrangeCommand', { + base: Command, + + execute: function (km, index) { + var nodes = km.getSelectedNodes().slice(); + + if (!nodes.length) return; + + var ancestor = MinderNode.getCommonAncestor(nodes); + + if (ancestor != nodes[0].parent) return; + + var indexed = nodes.map(function (node) { + return { + index: node.getIndex(), + node: node, + }; + }); + + var asc = + Math.min.apply( + Math, + indexed.map(function (one) { + return one.index; + }) + ) >= index; + + indexed.sort(function (a, b) { + return asc ? b.index - a.index : a.index - b.index; + }); + + indexed.forEach(function (one) { + one.node.arrange(index); + }); + + km.layout(300); + }, + + queryState: function (km) { + var selected = km.getSelectedNode(); + return selected ? 0 : -1; + }, + }); + + Module.register('ArrangeModule', { + commands: { + arrangeup: ArrangeUpCommand, + arrangedown: ArrangeDownCommand, + arrange: ArrangeCommand, + }, + contextmenu: [ + { + command: 'arrangeup', + }, + { + command: 'arrangedown', + }, + { + divider: true, + }, + ], + commandShortcutKeys: { + arrangeup: 'normal::alt+Up', + arrangedown: 'normal::alt+Down', + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/basestyle.js b/packages/client/src/thirtypart/kityminder/kity-core/module/basestyle.js new file mode 100644 index 00000000..3cd6c42e --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/basestyle.js @@ -0,0 +1,123 @@ +/* eslint-disable */ + +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + + var Minder = require('../core/minder'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + + var TextRenderer = require('./text'); + + Module.register('basestylemodule', function () { + var km = this; + + function getNodeDataOrStyle(node, name) { + return node.getData(name) || node.getStyle(name); + } + + TextRenderer.registerStyleHook(function (node, textGroup) { + var fontWeight = getNodeDataOrStyle(node, 'font-weight'); + var fontStyle = getNodeDataOrStyle(node, 'font-style'); + var styleHash = [fontWeight, fontStyle].join('/'); + + textGroup.eachItem(function (index, item) { + item.setFont({ + weight: fontWeight, + style: fontStyle, + }); + }); + }); + return { + commands: { + /** + * @command Bold + * @description 加粗选中的节点 + * @shortcut Ctrl + B + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + * 1: 当前已选中的节点已加粗 + */ + bold: kity.createClass('boldCommand', { + base: Command, + + execute: function (km) { + var nodes = km.getSelectedNodes(); + if (this.queryState('bold') == 1) { + nodes.forEach(function (n) { + n.setData('font-weight').render(); + }); + } else { + nodes.forEach(function (n) { + n.setData('font-weight', 'bold').render(); + }); + } + km.layout(); + }, + queryState: function () { + var nodes = km.getSelectedNodes(), + result = 0; + if (nodes.length === 0) { + return -1; + } + nodes.forEach(function (n) { + if (n && n.getData('font-weight')) { + result = 1; + return false; + } + }); + return result; + }, + }), + /** + * @command Italic + * @description 加斜选中的节点 + * @shortcut Ctrl + I + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + * 1: 当前已选中的节点已加斜 + */ + italic: kity.createClass('italicCommand', { + base: Command, + + execute: function (km) { + var nodes = km.getSelectedNodes(); + if (this.queryState('italic') == 1) { + nodes.forEach(function (n) { + n.setData('font-style').render(); + }); + } else { + nodes.forEach(function (n) { + n.setData('font-style', 'italic').render(); + }); + } + + km.layout(); + }, + queryState: function () { + var nodes = km.getSelectedNodes(), + result = 0; + if (nodes.length === 0) { + return -1; + } + nodes.forEach(function (n) { + if (n && n.getData('font-style')) { + result = 1; + return false; + } + }); + return result; + }, + }), + }, + commandShortcutKeys: { + bold: 'ctrl+b', //bold + italic: 'ctrl+i', //italic + }, + }; + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/clipboard.js b/packages/client/src/thirtypart/kityminder/kity-core/module/clipboard.js new file mode 100644 index 00000000..636768b1 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/clipboard.js @@ -0,0 +1,172 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + + Module.register('ClipboardModule', function () { + var km = this, + _clipboardNodes = [], + _selectedNodes = []; + + function appendChildNode(parent, child) { + _selectedNodes.push(child); + km.appendNode(child, parent); + child.render(); + child.setLayoutOffset(null); + var children = child.children.map(function (node) { + return node.clone(); + }); + + /* + * fixed bug: Modified on 2015.08.05 + * 原因:粘贴递归 append 时没有清空原来父节点的子节点,而父节点被复制的时候,是连同子节点一起复制过来的 + * 解决办法:增加了下面这一行代码 + * by: @zhangbobell zhangbobell@163.com + */ + child.clearChildren(); + + for (var i = 0, ci; (ci = children[i]); i++) { + appendChildNode(child, ci); + } + } + + function sendToClipboard(nodes) { + if (!nodes.length) return; + nodes.sort(function (a, b) { + return a.getIndex() - b.getIndex(); + }); + _clipboardNodes = nodes.map(function (node) { + return node.clone(); + }); + } + + /** + * @command Copy + * @description 复制当前选中的节点 + * @shortcut Ctrl + C + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + */ + var CopyCommand = kity.createClass('CopyCommand', { + base: Command, + + execute: function (km) { + sendToClipboard(km.getSelectedAncestors(true)); + this.setContentChanged(false); + }, + }); + + /** + * @command Cut + * @description 剪切当前选中的节点 + * @shortcut Ctrl + X + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + */ + var CutCommand = kity.createClass('CutCommand', { + base: Command, + + execute: function (km) { + var ancestors = km.getSelectedAncestors(); + + if (ancestors.length === 0) return; + + sendToClipboard(ancestors); + + km.select(MinderNode.getCommonAncestor(ancestors), true); + + ancestors.slice().forEach(function (node) { + km.removeNode(node); + }); + + km.layout(300); + }, + }); + + /** + * @command Paste + * @description 粘贴已复制的节点到每一个当前选中的节点上 + * @shortcut Ctrl + V + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + */ + var PasteCommand = kity.createClass('PasteCommand', { + base: Command, + + execute: function (km) { + if (_clipboardNodes.length) { + var nodes = km.getSelectedNodes(); + if (!nodes.length) return; + + for (var i = 0, ni; (ni = _clipboardNodes[i]); i++) { + for (var j = 0, node; (node = nodes[j]); j++) { + appendChildNode(node, ni.clone()); + } + } + + km.select(_selectedNodes, true); + _selectedNodes = []; + + km.layout(300); + } + }, + + queryState: function (km) { + return km.getSelectedNode() ? 0 : -1; + }, + }); + + /** + * @Desc: 若支持原生clipboadr事件则基于原生扩展,否则使用km的基础事件只处理节点的粘贴复制 + * @Editor: Naixor + * @Date: 2015.9.20 + */ + if (km.supportClipboardEvent && !kity.Browser.gecko) { + var Copy = function (e) { + this.fire('beforeCopy', e); + }; + + var Cut = function (e) { + this.fire('beforeCut', e); + }; + + var Paste = function (e) { + this.fire('beforePaste', e); + }; + + return { + commands: { + copy: CopyCommand, + cut: CutCommand, + paste: PasteCommand, + }, + clipBoardEvents: { + copy: Copy.bind(km), + cut: Cut.bind(km), + paste: Paste.bind(km), + }, + sendToClipboard: sendToClipboard, + }; + } else { + return { + commands: { + copy: CopyCommand, + cut: CutCommand, + paste: PasteCommand, + }, + commandShortcutKeys: { + copy: 'normal::ctrl+c|', + cut: 'normal::ctrl+x', + paste: 'normal::ctrl+v', + }, + sendToClipboard: sendToClipboard, + }; + } + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/dragtree.js b/packages/client/src/thirtypart/kityminder/kity-core/module/dragtree.js new file mode 100644 index 00000000..98edf541 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/dragtree.js @@ -0,0 +1,403 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + + // 矩形的变形动画定义 + var MoveToParentCommand = kity.createClass('MoveToParentCommand', { + base: Command, + execute: function (minder, nodes, parent) { + var node; + for (var i = 0; i < nodes.length; i++) { + node = nodes[i]; + if (node.parent) { + node.parent.removeChild(node); + parent.appendChild(node); + node.render(); + } + } + parent.expand(); + minder.select(nodes, true); + }, + }); + + var DropHinter = kity.createClass('DropHinter', { + base: kity.Group, + + constructor: function () { + this.callBase(); + this.rect = new kity.Rect(); + this.addShape(this.rect); + }, + + render: function (target) { + this.setVisible(!!target); + if (target) { + this.rect + .setBox(target.getLayoutBox()) + .setRadius(target.getStyle('radius') || 0) + .stroke(target.getStyle('drop-hint-color') || 'yellow', target.getStyle('drop-hint-width') || 2); + this.bringTop(); + } + }, + }); + + var OrderHinter = kity.createClass('OrderHinter', { + base: kity.Group, + + constructor: function () { + this.callBase(); + this.area = new kity.Rect(); + this.path = new kity.Path(); + this.addShapes([this.area, this.path]); + }, + + render: function (hint) { + this.setVisible(!!hint); + if (hint) { + this.area.setBox(hint.area); + this.area.fill(hint.node.getStyle('order-hint-area-color') || 'rgba(0, 255, 0, .5)'); + this.path.setPathData(hint.path); + this.path.stroke( + hint.node.getStyle('order-hint-path-color') || '#0f0', + hint.node.getStyle('order-hint-path-width') || 1 + ); + } + }, + }); + + // 对拖动对象的一个替代盒子,控制整个拖放的逻辑,包括: + // 1. 从节点列表计算出拖动部分 + // 2. 计算可以 drop 的节点,产生 drop 交互提示 + var TreeDragger = kity.createClass('TreeDragger', { + constructor: function (minder) { + this._minder = minder; + this._dropHinter = new DropHinter(); + this._orderHinter = new OrderHinter(); + minder.getRenderContainer().addShapes([this._dropHinter, this._orderHinter]); + }, + + dragStart: function (position) { + // 只记录开始位置,不马上开启拖放模式 + // 这个位置同时是拖放范围收缩时的焦点位置(中心) + this._startPosition = position; + }, + + dragMove: function (position) { + // 启动拖放模式需要最小的移动距离 + var DRAG_MOVE_THRESHOLD = 10; + + if (!this._startPosition) return; + + var movement = kity.Vector.fromPoints(this._dragPosition || this._startPosition, position); + var minder = this._minder; + + this._dragPosition = position; + + if (!this._dragMode) { + // 判断拖放模式是否该启动 + if (kity.Vector.fromPoints(this._dragPosition, this._startPosition).length() < DRAG_MOVE_THRESHOLD) { + return; + } + if (!this._enterDragMode()) { + return; + } + } + + for (var i = 0; i < this._dragSources.length; i++) { + this._dragSources[i].setLayoutOffset(this._dragSources[i].getLayoutOffset().offset(movement)); + minder.applyLayoutResult(this._dragSources[i]); + } + + if (!this._dropTest()) { + this._orderTest(); + } else { + this._renderOrderHint((this._orderSucceedHint = null)); + } + }, + + dragEnd: function () { + this._startPosition = null; + this._dragPosition = null; + + if (!this._dragMode) { + return; + } + + this._fadeDragSources(1); + + if (this._dropSucceedTarget) { + this._dragSources.forEach(function (source) { + source.setLayoutOffset(null); + }); + + this._minder.layout(-1); + + this._minder.execCommand('movetoparent', this._dragSources, this._dropSucceedTarget); + } else if (this._orderSucceedHint) { + var hint = this._orderSucceedHint; + var index = hint.node.getIndex(); + + var sourceIndexes = this._dragSources.map(function (source) { + // 顺便干掉布局偏移 + source.setLayoutOffset(null); + return source.getIndex(); + }); + + var maxIndex = Math.max.apply(Math, sourceIndexes); + var minIndex = Math.min.apply(Math, sourceIndexes); + + if (index < minIndex && hint.type == 'down') index++; + if (index > maxIndex && hint.type == 'up') index--; + + hint.node.setLayoutOffset(null); + + this._minder.execCommand('arrange', index); + this._renderOrderHint(null); + } else { + this._minder.fire('savescene'); + } + this._minder.layout(300); + this._leaveDragMode(); + this._minder.fire('contentchange'); + }, + + // 进入拖放模式: + // 1. 计算拖放源和允许的拖放目标 + // 2. 标记已启动 + _enterDragMode: function () { + this._calcDragSources(); + if (!this._dragSources.length) { + this._startPosition = null; + return false; + } + this._fadeDragSources(0.5); + this._calcDropTargets(); + this._calcOrderHints(); + this._dragMode = true; + this._minder.setStatus('dragtree'); + return true; + }, + + // 从选中的节点计算拖放源 + // 并不是所有选中的节点都作为拖放源,如果选中节点中存在 A 和 B, + // 并且 A 是 B 的祖先,则 B 不作为拖放源 + // + // 计算过程: + // 1. 将节点按照树高排序,排序后只可能是前面节点是后面节点的祖先 + // 2. 从后往前枚举排序的结果,如果发现枚举目标之前存在其祖先, + // 则排除枚举目标作为拖放源,否则加入拖放源 + _calcDragSources: function () { + this._dragSources = this._minder.getSelectedAncestors(); + }, + + _fadeDragSources: function (opacity) { + var minder = this._minder; + this._dragSources.forEach(function (source) { + source.getRenderContainer().setOpacity(opacity, 200); + source.traverse(function (node) { + if (opacity < 1) { + minder.detachNode(node); + } else { + minder.attachNode(node); + } + }, true); + }); + }, + + // 计算拖放目标可以释放的节点列表(释放意味着成为其子树),存在这条限制规则: + // - 不能拖放到拖放目标的子树上(允许拖放到自身,因为多选的情况下可以把其它节点加入) + // + // 1. 加入当前节点(初始为根节点)到允许列表 + // 2. 对于当前节点的每一个子节点: + // (1) 如果是拖放目标的其中一个节点,忽略(整棵子树被剪枝) + // (2) 如果不是拖放目标之一,以当前子节点为当前节点,回到 1 计算 + // 3. 返回允许列表 + // + _calcDropTargets: function () { + function findAvailableParents(nodes, root) { + var availables = [], + i; + availables.push(root); + root.getChildren().forEach(function (test) { + for (i = 0; i < nodes.length; i++) { + if (nodes[i] == test) return; + } + availables = availables.concat(findAvailableParents(nodes, test)); + }); + return availables; + } + + this._dropTargets = findAvailableParents(this._dragSources, this._minder.getRoot()); + this._dropTargetBoxes = this._dropTargets.map(function (source) { + return source.getLayoutBox(); + }); + }, + + _calcOrderHints: function () { + var sources = this._dragSources; + var ancestor = MinderNode.getCommonAncestor(sources); + + // 只有一个元素选中,公共祖先是其父 + if (ancestor == sources[0]) ancestor = sources[0].parent; + + if (sources.length === 0 || ancestor != sources[0].parent) { + this._orderHints = []; + return; + } + + var siblings = ancestor.children; + + this._orderHints = siblings.reduce(function (hint, sibling) { + if (sources.indexOf(sibling) == -1) { + hint = hint.concat(sibling.getOrderHint()); + } + return hint; + }, []); + }, + + _leaveDragMode: function () { + this._dragMode = false; + this._dropSucceedTarget = null; + this._orderSucceedHint = null; + this._renderDropHint(null); + this._renderOrderHint(null); + this._minder.rollbackStatus(); + }, + + _drawForDragMode: function () { + this._text.setContent(this._dragSources.length + ' items'); + this._text.setPosition(this._startPosition.x, this._startPosition.y + 5); + this._minder.getRenderContainer().addShape(this); + }, + + /** + * 通过 judge 函数判断 targetBox 和 sourceBox 的位置交叉关系 + * @param targets -- 目标节点 + * @param targetBoxMapper -- 目标节点与对应 Box 的映射关系 + * @param judge -- 判断函数 + * @returns {*} + * @private + */ + _boxTest: function (targets, targetBoxMapper, judge) { + var sourceBoxes = this._dragSources.map(function (source) { + return source.getLayoutBox(); + }); + + var i, j, target, sourceBox, targetBox; + + judge = + judge || + function (intersectBox, sourceBox, targetBox) { + return intersectBox && !intersectBox.isEmpty(); + }; + + for (i = 0; i < targets.length; i++) { + target = targets[i]; + targetBox = targetBoxMapper.call(this, target, i); + + for (j = 0; j < sourceBoxes.length; j++) { + sourceBox = sourceBoxes[j]; + + var intersectBox = sourceBox.intersect(targetBox); + if (judge(intersectBox, sourceBox, targetBox)) { + return target; + } + } + } + + return null; + }, + + _dropTest: function () { + this._dropSucceedTarget = this._boxTest( + this._dropTargets, + function (target, i) { + return this._dropTargetBoxes[i]; + }, + function (intersectBox, sourceBox, targetBox) { + function area(box) { + return box.width * box.height; + } + if (!intersectBox) return false; + /* + * Added by zhangbobell, 2015.9.8 + * + * 增加了下面一行判断,修复了循环比较中 targetBox 为折叠节点时,intersetBox 面积为 0, + * 而 targetBox 的 width 和 height 均为 0 + * 此时造成了满足以下的第二个条件而返回 true + * */ + if (!area(intersectBox)) return false; + // 面积判断,交叉面积大于其中的一半 + if (area(intersectBox) > 0.5 * Math.min(area(sourceBox), area(targetBox))) return true; + // 有一个边完全重合的情况,也认为两个是交叉的 + if (intersectBox.width + 1 >= Math.min(sourceBox.width, targetBox.width)) return true; + if (intersectBox.height + 1 >= Math.min(sourceBox.height, targetBox.height)) return true; + return false; + } + ); + this._renderDropHint(this._dropSucceedTarget); + return !!this._dropSucceedTarget; + }, + + _orderTest: function () { + this._orderSucceedHint = this._boxTest(this._orderHints, function (hint) { + return hint.area; + }); + this._renderOrderHint(this._orderSucceedHint); + return !!this._orderSucceedHint; + }, + + _renderDropHint: function (target) { + this._dropHinter.render(target); + }, + + _renderOrderHint: function (hint) { + this._orderHinter.render(hint); + }, + preventDragMove: function () { + this._startPosition = null; + }, + }); + + Module.register('DragTree', function () { + var dragger; + + return { + init: function () { + dragger = new TreeDragger(this); + window.addEventListener('mouseup', function () { + dragger.dragEnd(); + }); + }, + events: { + 'normal.mousedown inputready.mousedown': function (e) { + // 单选中根节点也不触发拖拽 + if (e.originEvent.button) return; + if (e.getTargetNode() && e.getTargetNode() != this.getRoot()) { + dragger.dragStart(e.getPosition()); + } + }, + 'normal.mousemove dragtree.mousemove': function (e) { + dragger.dragMove(e.getPosition()); + }, + 'normal.mouseup dragtree.beforemouseup': function (e) { + dragger.dragEnd(); + //e.stopPropagation(); + e.preventDefault(); + }, + 'statuschange': function (e) { + if (e.lastStatus == 'textedit' && e.currentStatus == 'normal') { + dragger.preventDragMove(); + } + }, + }, + commands: { + movetoparent: MoveToParentCommand, + }, + }; + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/expand.js b/packages/client/src/thirtypart/kityminder/kity-core/module/expand.js new file mode 100644 index 00000000..e8fc84f1 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/expand.js @@ -0,0 +1,299 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + var keymap = require('../core/keymap'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + var Renderer = require('../core/render'); + + Module.register('Expand', function () { + var minder = this; + var EXPAND_STATE_DATA = 'expandState', + STATE_EXPAND = 'expand', + STATE_COLLAPSE = 'collapse'; + + // 将展开的操作和状态读取接口拓展到 MinderNode 上 + kity.extendClass(MinderNode, { + /** + * 展开节点 + * @param {Policy} policy 展开的策略,默认为 KEEP_STATE + */ + expand: function () { + this.setData(EXPAND_STATE_DATA, STATE_EXPAND); + return this; + }, + + /** + * 收起节点 + */ + collapse: function () { + this.setData(EXPAND_STATE_DATA, STATE_COLLAPSE); + return this; + }, + + /** + * 判断节点当前的状态是否为展开 + */ + isExpanded: function () { + var expanded = this.getData(EXPAND_STATE_DATA) !== STATE_COLLAPSE; + return expanded && (this.isRoot() || this.parent.isExpanded()); + }, + + /** + * 判断节点当前的状态是否为收起 + */ + isCollapsed: function () { + return !this.isExpanded(); + }, + }); + + /** + * @command Expand + * @description 展开当前选中的节点,保证其可见 + * @param {bool} justParents 是否只展开到父亲 + * * `false` - (默认)保证选中的节点以及其子树可见 + * * `true` - 只保证选中的节点可见,不展开其子树 + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + */ + var ExpandCommand = kity.createClass('ExpandCommand', { + base: Command, + + execute: function (km, justParents) { + var node = km.getSelectedNode(); + if (!node) return; + if (justParents) { + node = node.parent; + } + while (node.parent) { + node.expand(); + node = node.parent; + } + node.renderTree(); + km.layout(100); + }, + + queryState: function (km) { + var node = km.getSelectedNode(); + return node && !node.isRoot() && !node.isExpanded() ? 0 : -1; + }, + }); + + /** + * @command ExpandToLevel + * @description 展开脑图到指定的层级 + * @param {number} level 指定展开到的层级,最少值为 1。 + * @state + * 0: 一直可用 + */ + var ExpandToLevelCommand = kity.createClass('ExpandToLevelCommand', { + base: Command, + execute: function (km, level) { + km.getRoot().traverse(function (node) { + if (node.getLevel() < level) node.expand(); + if (node.getLevel() == level && !node.isLeaf()) node.collapse(); + }); + km.refresh(100); + }, + enableReadOnly: true, + }); + + /** + * @command Collapse + * @description 收起当前节点的子树 + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + */ + var CollapseCommand = kity.createClass('CollapseCommand', { + base: Command, + + execute: function (km) { + var node = km.getSelectedNode(); + if (!node) return; + + node.collapse(); + node.renderTree(); + km.layout(); + }, + + queryState: function (km) { + var node = km.getSelectedNode(); + return node && !node.isRoot() && node.isExpanded() ? 0 : -1; + }, + }); + + var Expander = kity.createClass('Expander', { + base: kity.Group, + + constructor: function (node) { + this.callBase(); + this.radius = 6; + this.outline = new kity.Circle(this.radius).stroke('gray').fill('white'); + this.sign = new kity.Path().stroke('gray'); + this.addShapes([this.outline, this.sign]); + this.initEvent(node); + this.setId(utils.uuid('node_expander')); + this.setStyle('cursor', 'pointer'); + }, + + initEvent: function (node) { + this.on('mousedown', function (e) { + minder.select([node], true); + if (node.isExpanded()) { + node.collapse(); + } else { + node.expand(); + } + node.renderTree().getMinder().layout(100); + node.getMinder().fire('contentchange'); + e.stopPropagation(); + e.preventDefault(); + }); + this.on('dblclick click mouseup', function (e) { + e.stopPropagation(); + e.preventDefault(); + }); + }, + + setState: function (state) { + if (state == 'hide') { + this.setVisible(false); + return; + } + this.setVisible(true); + var pathData = ['M', 1.5 - this.radius, 0, 'L', this.radius - 1.5, 0]; + if (state == STATE_COLLAPSE) { + pathData.push(['M', 0, 1.5 - this.radius, 'L', 0, this.radius - 1.5]); + } + this.sign.setPathData(pathData); + }, + }); + + var ExpanderRenderer = kity.createClass('ExpanderRenderer', { + base: Renderer, + + create: function (node) { + if (node.isRoot()) return; + this.expander = new Expander(node); + node.getRenderContainer().prependShape(this.expander); + node.expanderRenderer = this; + this.node = node; + return this.expander; + }, + + shouldRender: function (node) { + return !node.isRoot(); + }, + + update: function (expander, node, box) { + if (!node.parent) return; + + var visible = node.parent.isExpanded(); + + expander.setState(visible && node.children.length ? node.getData(EXPAND_STATE_DATA) : 'hide'); + + var vector = node.getLayoutVectorIn().normalize(expander.radius + node.getStyle('stroke-width')); + var position = node.getVertexIn().offset(vector.reverse()); + + this.expander.setTranslate(position); + }, + }); + + return { + commands: { + expand: ExpandCommand, + expandtolevel: ExpandToLevelCommand, + collapse: CollapseCommand, + }, + events: { + 'layoutapply': function (e) { + var r = e.node.getRenderer('ExpanderRenderer'); + if (r.getRenderShape()) { + r.update(r.getRenderShape(), e.node); + } + }, + 'beforerender': function (e) { + var node = e.node; + var visible = !node.parent || node.parent.isExpanded(); + var minder = this; + + node.getRenderContainer().setVisible(visible); + if (!visible) e.stopPropagation(); + }, + 'normal.keydown': function (e) { + if (this.getStatus() == 'textedit') return; + if (e.originEvent.keyCode == keymap['/']) { + var node = this.getSelectedNode(); + if (!node || node == this.getRoot()) return; + var expanded = node.isExpanded(); + this.getSelectedNodes().forEach(function (node) { + if (expanded) node.collapse(); + else node.expand(); + node.renderTree(); + }); + this.layout(100); + this.fire('contentchange'); + e.preventDefault(); + e.stopPropagationImmediately(); + } + if (e.isShortcutKey('Alt+`')) { + this.execCommand('expandtolevel', 9999); + } + for (var i = 1; i < 6; i++) { + if (e.isShortcutKey('Alt+' + i)) { + this.execCommand('expandtolevel', i); + } + } + }, + }, + renderers: { + outside: ExpanderRenderer, + }, + contextmenu: [ + { + command: 'expandtoleaf', + query: function () { + return !minder.getSelectedNode(); + }, + fn: function (minder) { + minder.execCommand('expandtolevel', 9999); + }, + }, + { + command: 'expandtolevel1', + query: function () { + return !minder.getSelectedNode(); + }, + fn: function (minder) { + minder.execCommand('expandtolevel', 1); + }, + }, + { + command: 'expandtolevel2', + query: function () { + return !minder.getSelectedNode(); + }, + fn: function (minder) { + minder.execCommand('expandtolevel', 2); + }, + }, + { + command: 'expandtolevel3', + query: function () { + return !minder.getSelectedNode(); + }, + fn: function (minder) { + minder.execCommand('expandtolevel', 3); + }, + }, + { + divider: true, + }, + ], + }; + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/font.js b/packages/client/src/thirtypart/kityminder/kity-core/module/font.js new file mode 100644 index 00000000..b25b0ced --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/font.js @@ -0,0 +1,158 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + + var Minder = require('../core/minder'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + + var TextRenderer = require('./text'); + + function getNodeDataOrStyle(node, name) { + return node.getData(name) || node.getStyle(name); + } + + TextRenderer.registerStyleHook(function (node, textGroup) { + var dataColor = node.getData('color'); + var selectedColor = node.getStyle('selected-color'); + var styleColor = node.getStyle('color'); + + var foreColor = dataColor || (node.isSelected() && selectedColor ? selectedColor : styleColor); + var fontFamily = getNodeDataOrStyle(node, 'font-family'); + var fontSize = getNodeDataOrStyle(node, 'font-size'); + + textGroup.fill(foreColor); + + textGroup.eachItem(function (index, item) { + item.setFont({ + family: fontFamily, + size: fontSize, + }); + }); + }); + + Module.register('fontmodule', { + commands: { + /** + * @command ForeColor + * @description 设置选中节点的字体颜色 + * @param {string} color 表示颜色的字符串 + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + * @return 如果只有一个节点选中,返回已选中节点的字体颜色;否则返回 'mixed'。 + */ + forecolor: kity.createClass('fontcolorCommand', { + base: Command, + execute: function (km, color) { + var nodes = km.getSelectedNodes(); + nodes.forEach(function (n) { + n.setData('color', color); + n.render(); + }); + }, + queryState: function (km) { + return km.getSelectedNodes().length === 0 ? -1 : 0; + }, + queryValue: function (km) { + if (km.getSelectedNodes().length == 1) { + return km.getSelectedNodes()[0].getData('color'); + } + return 'mixed'; + }, + }), + + /** + * @command Background + * @description 设置选中节点的背景颜色 + * @param {string} color 表示颜色的字符串 + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + * @return 如果只有一个节点选中,返回已选中节点的背景颜色;否则返回 'mixed'。 + */ + background: kity.createClass('backgroudCommand', { + base: Command, + + execute: function (km, color) { + var nodes = km.getSelectedNodes(); + nodes.forEach(function (n) { + n.setData('background', color); + n.render(); + }); + }, + queryState: function (km) { + return km.getSelectedNodes().length === 0 ? -1 : 0; + }, + queryValue: function (km) { + if (km.getSelectedNodes().length == 1) { + return km.getSelectedNodes()[0].getData('background'); + } + return 'mixed'; + }, + }), + + /** + * @command FontFamily + * @description 设置选中节点的字体 + * @param {string} family 表示字体的字符串 + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + * @return 返回首个选中节点的字体 + */ + fontfamily: kity.createClass('fontfamilyCommand', { + base: Command, + + execute: function (km, family) { + var nodes = km.getSelectedNodes(); + nodes.forEach(function (n) { + n.setData('font-family', family); + n.render(); + km.layout(); + }); + }, + queryState: function (km) { + return km.getSelectedNodes().length === 0 ? -1 : 0; + }, + queryValue: function (km) { + var node = km.getSelectedNode(); + if (node) return node.getData('font-family'); + return null; + }, + }), + + /** + * @command FontSize + * @description 设置选中节点的字体大小 + * @param {number} size 字体大小(px) + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + * @return 返回首个选中节点的字体大小 + */ + fontsize: kity.createClass('fontsizeCommand', { + base: Command, + + execute: function (km, size) { + var nodes = km.getSelectedNodes(); + nodes.forEach(function (n) { + n.setData('font-size', size); + n.render(); + km.layout(300); + }); + }, + queryState: function (km) { + return km.getSelectedNodes().length === 0 ? -1 : 0; + }, + queryValue: function (km) { + var node = km.getSelectedNode(); + if (node) return node.getData('font-size'); + return null; + }, + }), + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/hyperlink.js b/packages/client/src/thirtypart/kityminder/kity-core/module/hyperlink.js new file mode 100644 index 00000000..f79c380d --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/hyperlink.js @@ -0,0 +1,127 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + + var Minder = require('../core/minder'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + var Renderer = require('../core/render'); + + // jscs:disable maximumLineLength + var linkShapePath = + 'M16.614,10.224h-1.278c-1.668,0-3.07-1.07-3.599-2.556h4.877c0.707,0,1.278-0.571,1.278-1.278V3.834 c0-0.707-0.571-1.278-1.278-1.278h-4.877C12.266,1.071,13.668,0,15.336,0h1.278c2.116,0,3.834,1.716,3.834,3.834V6.39 C20.448,8.508,18.73,10.224,16.614,10.224z M5.112,5.112c0-0.707,0.573-1.278,1.278-1.278h7.668c0.707,0,1.278,0.571,1.278,1.278 S14.765,6.39,14.058,6.39H6.39C5.685,6.39,5.112,5.819,5.112,5.112z M2.556,3.834V6.39c0,0.707,0.573,1.278,1.278,1.278h4.877 c-0.528,1.486-1.932,2.556-3.599,2.556H3.834C1.716,10.224,0,8.508,0,6.39V3.834C0,1.716,1.716,0,3.834,0h1.278 c1.667,0,3.071,1.071,3.599,2.556H3.834C3.129,2.556,2.556,3.127,2.556,3.834z'; + + Module.register('hyperlink', { + commands: { + /** + * @command HyperLink + * @description 为选中的节点添加超链接 + * @param {string} url 超链接的 URL,设置为 null 移除 + * @param {string} title 超链接的说明 + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + * @return 返回首个选中节点的超链接信息,JSON 对象: `{url: url, title: title}` + */ + hyperlink: kity.createClass('hyperlink', { + base: Command, + + execute: function (km, url, title) { + var nodes = km.getSelectedNodes(); + nodes.forEach(function (n) { + n.setData('hyperlink', url); + n.setData('hyperlinkTitle', url && title); + n.render(); + }); + km.layout(); + }, + queryState: function (km) { + var nodes = km.getSelectedNodes(), + result = 0; + if (nodes.length === 0) { + return -1; + } + nodes.forEach(function (n) { + if (n && n.getData('hyperlink')) { + result = 0; + return false; + } + }); + return result; + }, + queryValue: function (km) { + var node = km.getSelectedNode(); + return { + url: node.getData('hyperlink'), + title: node.getData('hyperlinkTitle'), + }; + }, + }), + }, + renderers: { + right: kity.createClass('hyperlinkrender', { + base: Renderer, + + create: function () { + var link = new kity.HyperLink(); + var linkshape = new kity.Path(); + var outline = new kity.Rect(24, 22, -2, -6, 4).fill('rgba(255, 255, 255, 0)'); + + linkshape.setPathData(linkShapePath).fill('#666'); + link.addShape(outline); + link.addShape(linkshape); + link.setTarget('_blank'); + link.setStyle('cursor', 'pointer'); + + link + .on('mouseover', function () { + outline.fill('rgba(255, 255, 200, .8)'); + }) + .on('mouseout', function () { + outline.fill('rgba(255, 255, 255, 0)'); + }); + return link; + }, + + shouldRender: function (node) { + return node.getData('hyperlink'); + }, + + update: function (link, node, box) { + var href = node.getData('hyperlink'); + link.setHref('#'); + + var allowed = ['^http:', '^https:', '^ftp:', '^mailto:']; + for (var i = 0; i < allowed.length; i++) { + var regex = new RegExp(allowed[i]); + if (regex.test(href)) { + link.setHref(href); + break; + } + } + var title = node.getData('hyperlinkTitle'); + + if (title) { + title = [title, '(', href, ')'].join(''); + } else { + title = href; + } + + link.node.setAttributeNS('http://www.w3.org/1999/xlink', 'title', title); + + var spaceRight = node.getStyle('space-right'); + + link.setTranslate(box.right + spaceRight + 2, -5); + return new kity.Box({ + x: box.right + spaceRight, + y: -11, + width: 24, + height: 22, + }); + }, + }), + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/image-viewer.js b/packages/client/src/thirtypart/kityminder/kity-core/module/image-viewer.js new file mode 100644 index 00000000..67f7621c --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/image-viewer.js @@ -0,0 +1,113 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var keymap = require('../core/keymap'); + + var Module = require('../core/module'); + var Command = require('../core/command'); + + Module.register('ImageViewer', function () { + function createEl(name, classNames, children) { + var el = document.createElement(name); + addClass(el, classNames); + children && + children.length && + children.forEach(function (child) { + el.appendChild(child); + }); + return el; + } + + function on(el, event, handler) { + el.addEventListener(event, handler); + } + + function addClass(el, classNames) { + classNames && + classNames.split(' ').forEach(function (className) { + el.classList.add(className); + }); + } + + function removeClass(el, classNames) { + classNames && + classNames.split(' ').forEach(function (className) { + el.classList.remove(className); + }); + } + + var ImageViewer = kity.createClass('ImageViewer', { + constructor: function () { + var btnClose = createEl('button', 'km-image-viewer-btn km-image-viewer-close'); + var btnSource = createEl('button', 'km-image-viewer-btn km-image-viewer-source'); + var image = (this.image = createEl('img')); + var toolbar = (this.toolbar = createEl('div', 'km-image-viewer-toolbar', [btnSource, btnClose])); + var container = createEl('div', 'km-image-viewer-container', [image]); + var viewer = (this.viewer = createEl('div', 'km-image-viewer', [toolbar, container])); + this.hotkeyHandler = this.hotkeyHandler.bind(this); + on(btnClose, 'click', this.close.bind(this)); + on(btnSource, 'click', this.viewSource.bind(this)); + on(image, 'click', this.zoomImage.bind(this)); + on(viewer, 'contextmenu', this.toggleToolbar.bind(this)); + on(document, 'keydown', this.hotkeyHandler); + }, + dispose: function () { + this.close(); + document.removeEventListener('remove', this.hotkeyHandler); + }, + hotkeyHandler: function (e) { + if (!this.actived) { + return; + } + if (e.keyCode === keymap['esc']) { + this.close(); + } + }, + toggleToolbar: function (e) { + e && e.preventDefault(); + this.toolbar.classList.toggle('hidden'); + }, + zoomImage: function (restore) { + var image = this.image; + if (typeof restore === 'boolean') { + restore && addClass(image, 'limited'); + } else { + image.classList.toggle('limited'); + } + }, + viewSource: function (src) { + window.open(this.image.src); + }, + open: function (src) { + var input = document.querySelector('input'); + if (input) { + input.focus(); + input.blur(); + } + this.image.src = src; + this.zoomImage(true); + document.body.appendChild(this.viewer); + this.actived = true; + }, + close: function () { + this.image.src = ''; + document.body.removeChild(this.viewer); + this.actived = false; + }, + }); + + return { + init: function () { + this.viewer = new ImageViewer(); + }, + events: { + 'normal.dblclick': function (e) { + var shape = e.kityEvent.targetShape; + if (shape.__KityClassName === 'Image' && shape.url) { + this.viewer.open(shape.url); + } + }, + }, + }; + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/image.js b/packages/client/src/thirtypart/kityminder/kity-core/module/image.js new file mode 100644 index 00000000..e4042614 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/image.js @@ -0,0 +1,144 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + + var Minder = require('../core/minder'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + var Renderer = require('../core/render'); + + Module.register('image', function () { + function loadImageSize(url, callback) { + var img = document.createElement('img'); + img.onload = function () { + callback(img.width, img.height); + }; + img.onerror = function () { + callback(null); + }; + img.src = url; + } + + function fitImageSize(width, height, maxWidth, maxHeight) { + var ratio = width / height, + fitRatio = maxWidth / maxHeight; + + // 宽高比大于最大尺寸的宽高比,以宽度为标准适应 + if (width > maxWidth && ratio > fitRatio) { + width = maxWidth; + height = width / ratio; + } else if (height > maxHeight) { + height = maxHeight; + width = height * ratio; + } + + return { + width: width | 0, + height: height | 0, + }; + } + + /** + * @command Image + * @description 为选中的节点添加图片 + * @param {string} url 图片的 URL,设置为 null 移除 + * @param {string} title 图片的说明 + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + * @return 返回首个选中节点的图片信息,JSON 对象: `{url: url, title: title}` + */ + var ImageCommand = kity.createClass('ImageCommand', { + base: Command, + + execute: function (km, url, title) { + var nodes = km.getSelectedNodes(); + + loadImageSize(url, function (width, height) { + nodes.forEach(function (n) { + var size = fitImageSize(width, height, km.getOption('maxImageWidth'), km.getOption('maxImageHeight')); + n.setData('image', url); + n.setData('imageTitle', url && title); + n.setData('imageSize', url && size); + n.render(); + }); + km.fire('saveScene'); + km.layout(300); + }); + }, + queryState: function (km) { + var nodes = km.getSelectedNodes(), + result = 0; + if (nodes.length === 0) { + return -1; + } + nodes.forEach(function (n) { + if (n && n.getData('image')) { + result = 0; + return false; + } + }); + return result; + }, + queryValue: function (km) { + var node = km.getSelectedNode(); + return { + url: node.getData('image'), + title: node.getData('imageTitle'), + }; + }, + }); + + var ImageRenderer = kity.createClass('ImageRenderer', { + base: Renderer, + + create: function (node) { + return new kity.Image(node.getData('image')); + }, + + shouldRender: function (node) { + return node.getData('image'); + }, + + update: function (image, node, box) { + var url = node.getData('image'); + var title = node.getData('imageTitle'); + var size = node.getData('imageSize'); + var spaceTop = node.getStyle('space-top'); + + if (!size) return; + + if (title) { + image.node.setAttributeNS('http://www.w3.org/1999/xlink', 'title', title); + } + + var x = box.cx - size.width / 2; + var y = box.y - size.height - spaceTop; + + image + .setUrl(url) + .setX(x | 0) + .setY(y | 0) + .setWidth(size.width | 0) + .setHeight(size.height | 0); + + return new kity.Box(x | 0, y | 0, size.width | 0, size.height | 0); + }, + }); + + return { + defaultOptions: { + maxImageWidth: 200, + maxImageHeight: 200, + }, + commands: { + image: ImageCommand, + }, + renderers: { + top: ImageRenderer, + }, + }; + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/keynav.js b/packages/client/src/thirtypart/kityminder/kity-core/module/keynav.js new file mode 100644 index 00000000..bb1300d8 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/keynav.js @@ -0,0 +1,171 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + var keymap = require('../core/keymap'); + + var Minder = require('../core/minder'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + var Renderer = require('../core/render'); + + Module.register('KeyboardModule', function () { + var min = Math.min, + max = Math.max, + abs = Math.abs, + sqrt = Math.sqrt, + exp = Math.exp; + + function buildPositionNetwork(root) { + var pointIndexes = [], + p; + root.traverse(function (node) { + p = node.getLayoutBox(); + + // bugfix: 不应导航到收起的节点(判断其尺寸是否存在) + if (p.width && p.height) { + pointIndexes.push({ + left: p.x, + top: p.y, + right: p.x + p.width, + bottom: p.y + p.height, + width: p.width, + height: p.height, + node: node, + }); + } + }); + for (var i = 0; i < pointIndexes.length; i++) { + findClosestPointsFor(pointIndexes, i); + } + } + + // 这是金泉的点子,赞! + // 求两个不相交矩形的最近距离 + function getCoefedDistance(box1, box2) { + var xMin, xMax, yMin, yMax, xDist, yDist, dist, cx, cy; + xMin = min(box1.left, box2.left); + xMax = max(box1.right, box2.right); + yMin = min(box1.top, box2.top); + yMax = max(box1.bottom, box2.bottom); + + xDist = xMax - xMin - box1.width - box2.width; + yDist = yMax - yMin - box1.height - box2.height; + + if (xDist < 0) dist = yDist; + else if (yDist < 0) dist = xDist; + else dist = sqrt(xDist * xDist + yDist * yDist); + + var node1 = box1.node; + var node2 = box2.node; + + // sibling + if (node1.parent == node2.parent) { + dist /= 10; + } + // parent + if (node2.parent == node1) { + dist /= 5; + } + + return dist; + } + + function findClosestPointsFor(pointIndexes, iFind) { + var find = pointIndexes[iFind]; + var most = {}, + quad; + var current, dist; + + for (var i = 0; i < pointIndexes.length; i++) { + if (i == iFind) continue; + current = pointIndexes[i]; + + dist = getCoefedDistance(current, find); + + // left check + if (current.right < find.left) { + if (!most.left || dist < most.left.dist) { + most.left = { + dist: dist, + node: current.node, + }; + } + } + + // right check + if (current.left > find.right) { + if (!most.right || dist < most.right.dist) { + most.right = { + dist: dist, + node: current.node, + }; + } + } + + // top check + if (current.bottom < find.top) { + if (!most.top || dist < most.top.dist) { + most.top = { + dist: dist, + node: current.node, + }; + } + } + + // bottom check + if (current.top > find.bottom) { + if (!most.down || dist < most.down.dist) { + most.down = { + dist: dist, + node: current.node, + }; + } + } + } + find.node._nearestNodes = { + right: (most.right && most.right.node) || null, + top: (most.top && most.top.node) || null, + left: (most.left && most.left.node) || null, + down: (most.down && most.down.node) || null, + }; + } + + function navigateTo(km, direction) { + var referNode = km.getSelectedNode(); + if (!referNode) { + km.select(km.getRoot()); + buildPositionNetwork(km.getRoot()); + return; + } + if (!referNode._nearestNodes) { + buildPositionNetwork(km.getRoot()); + } + var nextNode = referNode._nearestNodes[direction]; + if (nextNode) { + km.select(nextNode, true); + } + } + + // 稀释用 + var lastFrame; + return { + events: { + 'layoutallfinish': function () { + var root = this.getRoot(); + buildPositionNetwork(root); + }, + 'normal.keydown readonly.keydown': function (e) { + var minder = this; + ['left', 'right', 'up', 'down'].forEach(function (key) { + if (e.isShortcutKey(key)) { + navigateTo(minder, key == 'up' ? 'top' : key); + e.preventDefault(); + } + }); + }, + }, + }; + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/layout.js b/packages/client/src/thirtypart/kityminder/kity-core/module/layout.js new file mode 100644 index 00000000..6807c16b --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/layout.js @@ -0,0 +1,95 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 布局模块 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ + +define(function (require, exports, module) { + var kity = require('../core/kity'); + var Command = require('../core/command'); + var Module = require('../core/module'); + + /** + * @command Layout + * @description 设置选中节点的布局 + * 允许使用的布局可以使用 `kityminder.Minder.getLayoutList()` 查询 + * @param {string} name 布局的名称,设置为 null 则使用继承或默认的布局 + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + * @return 返回首个选中节点的布局名称 + */ + var LayoutCommand = kity.createClass('LayoutCommand', { + base: Command, + + execute: function (minder, name) { + var nodes = minder.getSelectedNodes(); + nodes.forEach(function (node) { + node.layout(name); + }); + }, + + queryValue: function (minder) { + var node = minder.getSelectedNode(); + if (node) { + return node.getData('layout'); + } + }, + + queryState: function (minder) { + return minder.getSelectedNode() ? 0 : -1; + }, + }); + + /** + * @command ResetLayout + * @description 重设选中节点的布局,如果当前没有选中的节点,重设整个脑图的布局 + * @state + * 0: 始终可用 + * @return 返回首个选中节点的布局名称 + */ + var ResetLayoutCommand = kity.createClass('ResetLayoutCommand', { + base: Command, + + execute: function (minder) { + var nodes = minder.getSelectedNodes(); + + if (!nodes.length) nodes = [minder.getRoot()]; + + nodes.forEach(function (node) { + node.traverse(function (child) { + child.resetLayoutOffset(); + if (!child.isRoot()) { + child.setData('layout', null); + } + }); + }); + minder.layout(300); + }, + + enableReadOnly: true, + }); + + Module.register('LayoutModule', { + commands: { + layout: LayoutCommand, + resetlayout: ResetLayoutCommand, + }, + contextmenu: [ + { + command: 'resetlayout', + }, + { + divider: true, + }, + ], + + commandShortcutKeys: { + resetlayout: 'Ctrl+Shift+L', + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/node.js b/packages/client/src/thirtypart/kityminder/kity-core/module/node.js new file mode 100644 index 00000000..0df02ada --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/node.js @@ -0,0 +1,150 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + + var Minder = require('../core/minder'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + var Renderer = require('../core/render'); + + /** + * @command AppendChildNode + * @description 添加子节点到选中的节点中 + * @param {string|object} textOrData 要插入的节点的文本或数据 + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + */ + var AppendChildCommand = kity.createClass('AppendChildCommand', { + base: Command, + execute: function (km, text) { + var parent = km.getSelectedNode(); + if (!parent) { + return null; + } + var node = km.createNode(text, parent); + km.select(node, true); + if (parent.isExpanded()) { + node.render(); + } else { + parent.expand(); + parent.renderTree(); + } + km.layout(600); + }, + queryState: function (km) { + var selectedNode = km.getSelectedNode(); + return selectedNode ? 0 : -1; + }, + }); + + /** + * @command AppendSiblingNode + * @description 添加选中的节点的兄弟节点 + * @param {string|object} textOrData 要添加的节点的文本或数据 + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + */ + var AppendSiblingCommand = kity.createClass('AppendSiblingCommand', { + base: Command, + execute: function (km, text) { + var sibling = km.getSelectedNode(); + var parent = sibling.parent; + if (!parent) { + return km.execCommand('AppendChildNode', text); + } + var node = km.createNode(text, parent, sibling.getIndex() + 1); + node.setGlobalLayoutTransform(sibling.getGlobalLayoutTransform()); + km.select(node, true); + node.render(); + km.layout(600); + }, + queryState: function (km) { + var selectedNode = km.getSelectedNode(); + return selectedNode ? 0 : -1; + }, + }); + + /** + * @command RemoveNode + * @description 移除选中的节点 + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + */ + var RemoveNodeCommand = kity.createClass('RemoverNodeCommand', { + base: Command, + execute: function (km) { + var nodes = km.getSelectedNodes(); + var ancestor = MinderNode.getCommonAncestor.apply(null, nodes); + var index = nodes[0].getIndex(); + + nodes.forEach(function (node) { + if (!node.isRoot()) km.removeNode(node); + }); + if (nodes.length == 1) { + var selectBack = ancestor.children[index - 1] || ancestor.children[index]; + km.select(selectBack || ancestor || km.getRoot(), true); + } else { + km.select(ancestor || km.getRoot(), true); + } + km.layout(600); + }, + queryState: function (km) { + var selectedNode = km.getSelectedNode(); + return selectedNode && !selectedNode.isRoot() ? 0 : -1; + }, + }); + + var AppendParentCommand = kity.createClass('AppendParentCommand', { + base: Command, + execute: function (km, text) { + var nodes = km.getSelectedNodes(); + + nodes.sort(function (a, b) { + return a.getIndex() - b.getIndex(); + }); + var parent = nodes[0].parent; + + var newParent = km.createNode(text, parent, nodes[0].getIndex()); + nodes.forEach(function (node) { + newParent.appendChild(node); + }); + newParent.setGlobalLayoutTransform(nodes[nodes.length >> 1].getGlobalLayoutTransform()); + + km.select(newParent, true); + km.layout(600); + }, + queryState: function (km) { + var nodes = km.getSelectedNodes(); + if (!nodes.length) return -1; + var parent = nodes[0].parent; + if (!parent) return -1; + for (var i = 1; i < nodes.length; i++) { + if (nodes[i].parent != parent) return -1; + } + return 0; + }, + }); + + Module.register('NodeModule', function () { + return { + commands: { + AppendChildNode: AppendChildCommand, + AppendSiblingNode: AppendSiblingCommand, + RemoveNode: RemoveNodeCommand, + AppendParentNode: AppendParentCommand, + }, + + commandShortcutKeys: { + appendsiblingnode: 'normal::Enter', + appendchildnode: 'normal::Insert|Tab', + appendparentnode: 'normal::Shift+Tab|normal::Shift+Insert', + removenode: 'normal::Del|Backspace', + }, + }; + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/note.js b/packages/client/src/thirtypart/kityminder/kity-core/module/note.js new file mode 100644 index 00000000..e9209f94 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/note.js @@ -0,0 +1,114 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 支持节点详细信息(HTML)格式 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + + var Minder = require('../core/minder'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + var Renderer = require('../core/render'); + + Module.register('NoteModule', function () { + var NOTE_PATH = 'M9,9H3V8h6L9,9L9,9z M9,7H3V6h6V7z M9,5H3V4h6V5z M8.5,11H2V2h8v7.5 M9,12l2-2V1H1v11'; + + /** + * @command Note + * @description 设置节点的备注信息 + * @param {string} note 要设置的备注信息,设置为 null 则移除备注信息 + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + */ + var NoteCommand = kity.createClass('NoteCommand', { + base: Command, + + execute: function (minder, note) { + var node = minder.getSelectedNode(); + node.setData('note', note); + node.render(); + node.getMinder().layout(300); + }, + + queryState: function (minder) { + return minder.getSelectedNodes().length === 1 ? 0 : -1; + }, + + queryValue: function (minder) { + var node = minder.getSelectedNode(); + return node && node.getData('note'); + }, + }); + + var NoteIcon = kity.createClass('NoteIcon', { + base: kity.Group, + + constructor: function () { + this.callBase(); + this.width = 16; + this.height = 17; + this.rect = new kity.Rect(16, 17, 0.5, -8.5, 2).fill('transparent'); + this.path = new kity.Path().setPathData(NOTE_PATH).setTranslate(2.5, -6.5); + this.addShapes([this.rect, this.path]); + + this.on('mouseover', function () { + this.rect.fill('rgba(255, 255, 200, .8)'); + }).on('mouseout', function () { + this.rect.fill('transparent'); + }); + + this.setStyle('cursor', 'pointer'); + }, + }); + + var NoteIconRenderer = kity.createClass('NoteIconRenderer', { + base: Renderer, + + create: function (node) { + var icon = new NoteIcon(); + icon.on('mousedown', function (e) { + e.preventDefault(); + node.getMinder().fire('editnoterequest'); + }); + icon.on('mouseover', function () { + node.getMinder().fire('shownoterequest', { node: node, icon: icon }); + }); + icon.on('mouseout', function () { + node.getMinder().fire('hidenoterequest', { node: node, icon: icon }); + }); + return icon; + }, + + shouldRender: function (node) { + return node.getData('note'); + }, + + update: function (icon, node, box) { + var x = box.right + node.getStyle('space-left'); + var y = box.cy; + + icon.path.fill(node.getStyle('color')); + icon.setTranslate(x, y); + + return new kity.Box(x, Math.round(y - icon.height / 2), icon.width, icon.height); + }, + }); + + return { + renderers: { + right: NoteIconRenderer, + }, + commands: { + note: NoteCommand, + }, + }; + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/outline.js b/packages/client/src/thirtypart/kityminder/kity-core/module/outline.js new file mode 100644 index 00000000..a9a53e3e --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/outline.js @@ -0,0 +1,154 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + + var Minder = require('../core/minder'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + var Renderer = require('../core/render'); + + var OutlineRenderer = kity.createClass('OutlineRenderer', { + base: Renderer, + + create: function (node) { + var outline = new kity.Rect().setId(utils.uuid('node_outline')); + + this.bringToBack = true; + + return outline; + }, + + update: function (outline, node, box) { + var shape = node.getStyle('shape'); + + var paddingLeft = node.getStyle('padding-left'), + paddingRight = node.getStyle('padding-right'), + paddingTop = node.getStyle('padding-top'), + paddingBottom = node.getStyle('padding-bottom'); + + var outlineBox = { + x: box.x - paddingLeft, + y: box.y - paddingTop, + width: box.width + paddingLeft + paddingRight, + height: box.height + paddingTop + paddingBottom, + }; + + var radius = node.getStyle('radius'); + // 天盘图圆形的情况 + if (shape && shape == 'circle') { + var p = Math.pow; + var r = Math.round; + + radius = r(Math.sqrt(p(outlineBox.width, 2) + p(outlineBox.height, 2)) / 2); + + outlineBox.x = box.cx - radius; + outlineBox.y = box.cy - radius; + outlineBox.width = 2 * radius; + outlineBox.height = 2 * radius; + } + + var prefix = node.isSelected() ? (node.getMinder().isFocused() ? 'selected-' : 'blur-selected-') : ''; + outline + .setPosition(outlineBox.x, outlineBox.y) + .setSize(outlineBox.width, outlineBox.height) + .setRadius(radius) + .fill(node.getData('background') || node.getStyle(prefix + 'background') || node.getStyle('background')) + .stroke(node.getStyle(prefix + 'stroke' || node.getStyle('stroke')), node.getStyle(prefix + 'stroke-width')); + + return new kity.Box(outlineBox); + }, + }); + + var ShadowRenderer = kity.createClass('ShadowRenderer', { + base: Renderer, + + create: function (node) { + this.bringToBack = true; + return new kity.Rect(); + }, + + shouldRender: function (node) { + return node.getStyle('shadow'); + }, + + update: function (shadow, node, box) { + shadow.setPosition(box.x + 4, box.y + 5).fill(node.getStyle('shadow')); + + var shape = node.getStyle('shape'); + if (!shape) { + shadow.setSize(box.width, box.height); + shadow.setRadius(node.getStyle('radius')); + } else if (shape == 'circle') { + var width = Math.max(box.width, box.height); + shadow.setSize(width, width); + shadow.setRadius(width / 2); + } + }, + }); + + var marker = new kity.Marker(); + + marker.setWidth(10); + marker.setHeight(12); + marker.setRef(0, 0); + marker.setViewBox(-6, -4, 8, 10); + + marker.addShape(new kity.Path().setPathData('M-5-3l5,3,-5,3').stroke('#33ffff')); + + var wireframeOption = /wire/.test(window.location.href); + var WireframeRenderer = kity.createClass('WireframeRenderer', { + base: Renderer, + + create: function () { + var wireframe = new kity.Group(); + var oxy = (this.oxy = new kity.Path().stroke('#f6f').setPathData('M0,-50L0,50M-50,0L50,0')); + + var box = (this.wireframe = new kity.Rect().stroke('lightgreen')); + + var vectorIn = (this.vectorIn = new kity.Path().stroke('#66ffff')); + var vectorOut = (this.vectorOut = new kity.Path().stroke('#66ffff')); + + vectorIn.setMarker(marker, 'end'); + vectorOut.setMarker(marker, 'end'); + + return wireframe.addShapes([oxy, box, vectorIn, vectorOut]); + }, + + shouldRender: function () { + return wireframeOption; + }, + + update: function (created, node, box) { + this.wireframe.setPosition(box.x, box.y).setSize(box.width, box.height); + var pin = node.getVertexIn(); + var pout = node.getVertexOut(); + var vin = node.getLayoutVectorIn().normalize(30); + var vout = node.getLayoutVectorOut().normalize(30); + this.vectorIn.setPathData(['M', pin.offset(vin.reverse()), 'L', pin]); + this.vectorOut.setPathData(['M', pout, 'l', vout]); + }, + }); + + Module.register('OutlineModule', function () { + return { + events: !wireframeOption + ? null + : { + ready: function () { + this.getPaper().addResource(marker); + }, + layoutallfinish: function () { + this.getRoot().traverse(function (node) { + node.getRenderer('WireframeRenderer').update(null, node, node.getContentBox()); + }); + }, + }, + renderers: { + outline: OutlineRenderer, + outside: [ShadowRenderer, WireframeRenderer], + }, + }; + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/priority.js b/packages/client/src/thirtypart/kityminder/kity-core/module/priority.js new file mode 100644 index 00000000..7facd628 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/priority.js @@ -0,0 +1,161 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + + var Minder = require('../core/minder'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + var Renderer = require('../core/render'); + + Module.register('PriorityModule', function () { + var minder = this; + + // Designed by Akikonata + // [MASK, BACK] + var PRIORITY_COLORS = [ + null, + ['#FF1200', '#840023'], // 1 - red + ['#0074FF', '#01467F'], // 2 - blue + ['#00AF00', '#006300'], // 3 - green + ['#FF962E', '#B25000'], // 4 - orange + ['#A464FF', '#4720C4'], // 5 - purple + ['#A3A3A3', '#515151'], // 6,7,8,9 - gray + ['#A3A3A3', '#515151'], + ['#A3A3A3', '#515151'], + ['#A3A3A3', '#515151'], + ]; // hue from 1 to 5 + + // jscs:disable maximumLineLength + var BACK_PATH = 'M0,13c0,3.866,3.134,7,7,7h6c3.866,0,7-3.134,7-7V7H0V13z'; + var MASK_PATH = 'M20,10c0,3.866-3.134,7-7,7H7c-3.866,0-7-3.134-7-7V7c0-3.866,3.134-7,7-7h6c3.866,0,7,3.134,7,7V10z'; + + var PRIORITY_DATA = 'priority'; + + // 优先级图标的图形 + var PriorityIcon = kity.createClass('PriorityIcon', { + base: kity.Group, + + constructor: function () { + this.callBase(); + this.setSize(20); + this.create(); + this.setId(utils.uuid('node_priority')); + }, + + setSize: function (size) { + this.width = this.height = size; + }, + + create: function () { + var white, back, mask, number; // 4 layer + + white = new kity.Path().setPathData(MASK_PATH).fill('white'); + back = new kity.Path().setPathData(BACK_PATH).setTranslate(0.5, 0.5); + mask = new kity.Path().setPathData(MASK_PATH).setOpacity(0.8).setTranslate(0.5, 0.5); + + number = new kity.Text() + .setX(this.width / 2 - 0.5) + .setY(this.height / 2) + .setTextAnchor('middle') + .setVerticalAlign('middle') + .setFontItalic(true) + .setFontSize(12) + .fill('white'); + + this.addShapes([back, mask, number]); + this.mask = mask; + this.back = back; + this.number = number; + }, + + setValue: function (value) { + var back = this.back, + mask = this.mask, + number = this.number; + + var color = PRIORITY_COLORS[value]; + + if (color) { + back.fill(color[1]); + mask.fill(color[0]); + } + + number.setContent(value); + }, + }); + + /** + * @command Priority + * @description 设置节点的优先级信息 + * @param {number} value 要设置的优先级(添加一个优先级小图标) + * 取值为 0 移除优先级信息; + * 取值为 1 - 9 设置优先级,超过 9 的优先级不渲染 + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + */ + var PriorityCommand = kity.createClass('SetPriorityCommand', { + base: Command, + execute: function (km, value) { + var nodes = km.getSelectedNodes(); + for (var i = 0; i < nodes.length; i++) { + nodes[i].setData(PRIORITY_DATA, value || null).render(); + } + km.layout(); + }, + queryValue: function (km) { + var nodes = km.getSelectedNodes(); + var val; + for (var i = 0; i < nodes.length; i++) { + val = nodes[i].getData(PRIORITY_DATA); + if (val) break; + } + return val || null; + }, + + queryState: function (km) { + return km.getSelectedNodes().length ? 0 : -1; + }, + }); + return { + commands: { + priority: PriorityCommand, + }, + renderers: { + left: kity.createClass('PriorityRenderer', { + base: Renderer, + + create: function (node) { + return new PriorityIcon(); + }, + + shouldRender: function (node) { + return node.getData(PRIORITY_DATA); + }, + + update: function (icon, node, box) { + var data = node.getData(PRIORITY_DATA); + var spaceLeft = node.getStyle('space-left'), + x, + y; + + icon.setValue(data); + x = box.left - icon.width - spaceLeft; + y = -icon.height / 2; + + icon.setTranslate(x, y); + + return new kity.Box({ + x: x, + y: y, + width: icon.width, + height: icon.height, + }); + }, + }), + }, + }; + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/progress.js b/packages/client/src/thirtypart/kityminder/kity-core/module/progress.js new file mode 100644 index 00000000..84f7991e --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/progress.js @@ -0,0 +1,150 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + + var Minder = require('../core/minder'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + var Renderer = require('../core/render'); + + Module.register('ProgressModule', function () { + var minder = this; + + var PROGRESS_DATA = 'progress'; + + // Designed by Akikonata + var BG_COLOR = '#FFED83'; + var PIE_COLOR = '#43BC00'; + var SHADOW_PATH = 'M10,3c4.418,0,8,3.582,8,8h1c0-5.523-3.477-10-9-10S1,5.477,1,11h1C2,6.582,5.582,3,10,3z'; + var SHADOW_COLOR = '#8E8E8E'; + + // jscs:disable maximumLineLength + var FRAME_PATH = + 'M10,0C4.477,0,0,4.477,0,10c0,5.523,4.477,10,10,10s10-4.477,10-10C20,4.477,15.523,0,10,0zM10,18c-4.418,0-8-3.582-8-8s3.582-8,8-8s8,3.582,8,8S14.418,18,10,18z'; + + var FRAME_GRAD = new kity.LinearGradient().pipe(function (g) { + g.setStartPosition(0, 0); + g.setEndPosition(0, 1); + g.addStop(0, '#fff'); + g.addStop(1, '#ccc'); + }); + var CHECK_PATH = 'M15.812,7.896l-6.75,6.75l-4.5-4.5L6.25,8.459l2.812,2.803l5.062-5.053L15.812,7.896z'; + var CHECK_COLOR = '#EEE'; + + minder.getPaper().addResource(FRAME_GRAD); + + // 进度图标的图形 + var ProgressIcon = kity.createClass('ProgressIcon', { + base: kity.Group, + + constructor: function (value) { + this.callBase(); + this.setSize(20); + this.create(); + this.setValue(value); + this.setId(utils.uuid('node_progress')); + this.translate(0.5, 0.5); + }, + + setSize: function (size) { + this.width = this.height = size; + }, + + create: function () { + var bg, pie, shadow, frame, check; + + bg = new kity.Circle(9).fill(BG_COLOR); + + pie = new kity.Pie(9, 0).fill(PIE_COLOR); + + shadow = new kity.Path().setPathData(SHADOW_PATH).setTranslate(-10, -10).fill(SHADOW_COLOR); + + frame = new kity.Path().setTranslate(-10, -10).setPathData(FRAME_PATH).fill(FRAME_GRAD); + + check = new kity.Path().setTranslate(-10, -10).setPathData(CHECK_PATH).fill(CHECK_COLOR); + + this.addShapes([bg, pie, shadow, check, frame]); + this.pie = pie; + this.check = check; + }, + + setValue: function (value) { + this.pie.setAngle((-360 * (value - 1)) / 8); + this.check.setVisible(value == 9); + }, + }); + + /** + * @command Progress + * @description 设置节点的进度信息(添加一个进度小图标) + * @param {number} value 要设置的进度 + * 取值为 0 移除进度信息; + * 取值为 1 表示未开始; + * 取值为 2 表示完成 1/8; + * 取值为 3 表示完成 2/8; + * 取值为 4 表示完成 3/8; + * 其余类推,取值为 9 表示全部完成 + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + */ + var ProgressCommand = kity.createClass('ProgressCommand', { + base: Command, + execute: function (km, value) { + var nodes = km.getSelectedNodes(); + for (var i = 0; i < nodes.length; i++) { + nodes[i].setData(PROGRESS_DATA, value || null).render(); + } + km.layout(); + }, + queryValue: function (km) { + var nodes = km.getSelectedNodes(); + var val; + for (var i = 0; i < nodes.length; i++) { + val = nodes[i].getData(PROGRESS_DATA); + if (val) break; + } + return val || null; + }, + + queryState: function (km) { + return km.getSelectedNodes().length ? 0 : -1; + }, + }); + + return { + commands: { + progress: ProgressCommand, + }, + renderers: { + left: kity.createClass('ProgressRenderer', { + base: Renderer, + + create: function (node) { + return new ProgressIcon(); + }, + + shouldRender: function (node) { + return node.getData(PROGRESS_DATA); + }, + + update: function (icon, node, box) { + var data = node.getData(PROGRESS_DATA); + var spaceLeft = node.getStyle('space-left'); + var x, y; + + icon.setValue(data); + + x = box.left - icon.width - spaceLeft; + y = -icon.height / 2; + icon.setTranslate(x + icon.width / 2, y + icon.height / 2); + + return new kity.Box(x, y, icon.width, icon.height); + }, + }), + }, + }; + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/resource.js b/packages/client/src/thirtypart/kityminder/kity-core/module/resource.js new file mode 100644 index 00000000..2aa19696 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/resource.js @@ -0,0 +1,404 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + + var Minder = require('../core/minder'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + var Renderer = require('../core/render'); + + Module.register('Resource', function () { + // String Hash + // https://github.com/drostie/sha3-js/edit/master/blake32.min.js + var blake32 = (function () { + var k, g, r, l, m, o, p, q, t, w, x; + x = 4 * (1 << 30); + k = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]; + m = [ + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, 0x452821e6, + 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + ]; + w = function (i) { + if (i < 0) { + i += x; + } + return ('00000000' + i.toString(16)).slice(-8); + }; + o = [ + [16, 50, 84, 118, 152, 186, 220, 254], + [174, 132, 249, 109, 193, 32, 123, 53], + [139, 12, 37, 223, 234, 99, 23, 73], + [151, 19, 205, 235, 98, 165, 4, 143], + [9, 117, 66, 250, 30, 203, 134, 211], + [194, 166, 176, 56, 212, 87, 239, 145], + [92, 241, 222, 164, 112, 54, 41, 184], + [189, 231, 28, 147, 5, 79, 104, 162], + [246, 158, 59, 128, 44, 125, 65, 90], + [42, 72, 103, 81, 191, 233, 195, 13], + ]; + p = function (a, b, n) { + var s = q[a] ^ q[b]; + q[a] = (s >>> n) | (s << (32 - n)); + }; + g = function (i, a, b, c, d) { + var u = l + (o[r][i] % 16), + v = l + (o[r][i] >> 4); + a %= 4; + b = 4 + (b % 4); + c = 8 + (c % 4); + d = 12 + (d % 4); + q[a] += q[b] + (t[u] ^ m[v % 16]); + p(d, a, 16); + q[c] += q[d]; + p(b, c, 12); + q[a] += q[b] + (t[v] ^ m[u % 16]); + p(d, a, 8); + q[c] += q[d]; + p(b, c, 7); + }; + return function (a, b) { + if (!(b instanceof Array && b.length === 4)) { + b = [0, 0, 0, 0]; + } + var c, d, e, L, f, h, j, i; + d = k.slice(0); + c = m.slice(0, 8); + for (r = 0; r < 4; r += 1) { + c[r] ^= b[r]; + } + e = a.length * 16; + f = e % 512 > 446 || e % 512 === 0 ? 0 : e; + if (e % 512 === 432) { + a += '\u8001'; + } else { + a += '\u8000'; + while (a.length % 32 !== 27) { + a += '\u0000'; + } + a += '\u0001'; + } + t = []; + for (i = 0; i < a.length; i += 2) { + t.push(a.charCodeAt(i) * 65536 + a.charCodeAt(i + 1)); + } + t.push(0); + t.push(e); + h = t.length - 16; + j = 0; + for (l = 0; l < t.length; l += 16) { + j += 512; + L = l === h ? f : Math.min(e, j); + q = d.concat(c); + q[12] ^= L; + q[13] ^= L; + for (r = 0; r < 10; r += 1) { + for (i = 0; i < 8; i += 1) { + if (i < 4) { + g(i, i, i, i, i); + } else { + g(i, i, i + 1, i + 2, i + 3); + } + } + } + for (i = 0; i < 8; i += 1) { + d[i] ^= b[i % 4] ^ q[i] ^ q[i + 8]; + } + } + return d.map(w).join(''); + }; + })(); + + /** + * 自动使用的颜色序列 + */ + var RESOURCE_COLOR_SERIES = [51, 303, 75, 200, 157, 0, 26, 254].map(function (h) { + return kity.Color.createHSL(h, 100, 85); + }); + + /** + * 在 Minder 上拓展一些关于资源的支持接口 + */ + kity.extendClass(Minder, { + /** + * 获取字符串的哈希值 + * + * @param {String} str + * @return {Number} hashCode + */ + getHashCode: function (str) { + str = blake32(str); + var hash = 1315423911, + i, + ch; + for (i = str.length - 1; i >= 0; i--) { + ch = str.charCodeAt(i); + hash ^= (hash << 5) + ch + (hash >> 2); + } + return hash & 0x7fffffff; + }, + + /** + * 获取脑图中某个资源对应的颜色 + * + * 如果存在同名资源,则返回已经分配给该资源的颜色,否则分配给该资源一个颜色,并且返回 + * + * 如果资源数超过颜色序列数量,返回哈希颜色 + * + * @param {String} resource 资源名称 + * @return {Color} + */ + getResourceColor: function (resource) { + var colorMapping = this._getResourceColorIndexMapping(); + var nextIndex; + + if (!Object.prototype.hasOwnProperty.call(colorMapping, resource)) { + // 找不到找下个可用索引 + nextIndex = this._getNextResourceColorIndex(); + colorMapping[resource] = nextIndex; + } + + // 资源过多,找不到可用索引颜色,统一返回哈希函数得到的颜色 + return ( + RESOURCE_COLOR_SERIES[colorMapping[resource]] || + kity.Color.createHSL(Math.floor((this.getHashCode(resource) / 0x7fffffff) * 359), 100, 85) + ); + }, + + /** + * 获得已使用的资源的列表 + * + * @return {Array} + */ + getUsedResource: function () { + var mapping = this._getResourceColorIndexMapping(); + var used = [], + resource; + + for (resource in mapping) { + if (Object.prototype.hasOwnProperty.call(mapping, resource)) { + used.push(resource); + } + } + + return used; + }, + + /** + * 获取脑图下一个可用的资源颜色索引 + * + * @return {int} + */ + _getNextResourceColorIndex: function () { + // 获取现有颜色映射 + // resource => color_index + var colorMapping = this._getResourceColorIndexMapping(); + + var resource, used, i; + + used = []; + + // 抽取已经使用的值到 used 数组 + for (resource in colorMapping) { + if (Object.prototype.hasOwnProperty.call(colorMapping, resource)) { + used.push(colorMapping[resource]); + } + } + + // 枚举所有的可用值,如果还没被使用,返回 + for (i = 0; i < RESOURCE_COLOR_SERIES.length; i++) { + if (!~used.indexOf(i)) return i; + } + + // 没有可用的颜色了 + return -1; + }, + + // 获取现有颜色映射 + // resource => color_index + _getResourceColorIndexMapping: function () { + return this._resourceColorMapping || (this._resourceColorMapping = {}); + }, + }); + + /** + * @class 设置资源的命令 + * + * @example + * + * // 设置选中节点资源为 "张三" + * minder.execCommand('resource', ['张三']); + * + * // 添加资源 "李四" 到选中节点 + * var resource = minder.queryCommandValue(); + * resource.push('李四'); + * minder.execCommand('resource', resource); + * + * // 清除选中节点的资源 + * minder.execCommand('resource', null); + */ + var ResourceCommand = kity.createClass('ResourceCommand', { + base: Command, + + execute: function (minder, resource) { + var nodes = minder.getSelectedNodes(); + + if (typeof resource == 'string') { + resource = [resource]; + } + + nodes.forEach(function (node) { + node.setData('resource', resource).render(); + }); + + minder.layout(200); + }, + + queryValue: function (minder) { + var nodes = minder.getSelectedNodes(); + var resource = []; + + nodes.forEach(function (node) { + var nodeResource = node.getData('resource'); + + if (!nodeResource) return; + + nodeResource.forEach(function (name) { + if (!~resource.indexOf(name)) { + resource.push(name); + } + }); + }); + + return resource; + }, + + queryState: function (km) { + return km.getSelectedNode() ? 0 : -1; + }, + }); + + /** + * @class 资源的覆盖图形 + * + * 该类为一个资源以指定的颜色渲染一个动态的覆盖图形 + */ + var ResourceOverlay = kity.createClass('ResourceOverlay', { + base: kity.Group, + + constructor: function () { + this.callBase(); + + var text, rect; + + rect = this.rect = new kity.Rect().setRadius(4); + + text = this.text = new kity.Text().setFontSize(12).setVerticalAlign('middle'); + + this.addShapes([rect, text]); + }, + + setValue: function (resourceName, color) { + var paddingX = 8, + paddingY = 4, + borderRadius = 4; + var text, box, rect; + + text = this.text; + + if (resourceName == this.lastResourceName) { + box = this.lastBox; + } else { + text.setContent(resourceName); + + box = text.getBoundaryBox(); + this.lastResourceName = resourceName; + this.lastBox = box; + } + + text.setX(paddingX).fill(color.dec('l', 70)); + + rect = this.rect; + rect.setPosition(0, box.y - paddingY); + this.width = Math.round(box.width + paddingX * 2); + this.height = Math.round(box.height + paddingY * 2); + rect.setSize(this.width, this.height); + rect.fill(color); + }, + }); + + /** + * @class 资源渲染器 + */ + var ResourceRenderer = kity.createClass('ResourceRenderer', { + base: Renderer, + + create: function (node) { + this.overlays = []; + return new kity.Group(); + }, + + shouldRender: function (node) { + return node.getData('resource') && node.getData('resource').length; + }, + + update: function (container, node, box) { + var spaceRight = node.getStyle('space-right'); + + var overlays = this.overlays; + + /* 修复 resource 数组中出现 null 的 bug + * @Author zhangbobell + * @date 2016-01-15 + */ + var resource = node.getData('resource').filter(function (ele) { + return ele !== null; + }); + if (resource.length === 0) { + return; + } + var minder = node.getMinder(); + var i, overlay, x; + + x = 0; + for (i = 0; i < resource.length; i++) { + x += spaceRight; + + overlay = overlays[i]; + if (!overlay) { + overlay = new ResourceOverlay(); + overlays.push(overlay); + container.addShape(overlay); + } + overlay.setVisible(true); + overlay.setValue(resource[i], minder.getResourceColor(resource[i])); + overlay.setTranslate(x, -1); + + x += overlay.width; + } + + while ((overlay = overlays[i++])) overlay.setVisible(false); + + container.setTranslate(box.right, 0); + + return new kity.Box({ + x: box.right, + y: Math.round(-overlays[0].height / 2), + width: x, + height: overlays[0].height, + }); + }, + }); + + return { + commands: { + resource: ResourceCommand, + }, + + renderers: { + right: ResourceRenderer, + }, + }; + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/select.js b/packages/client/src/thirtypart/kityminder/kity-core/module/select.js new file mode 100644 index 00000000..f778c3a5 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/select.js @@ -0,0 +1,185 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + + var Minder = require('../core/minder'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + var Renderer = require('../core/render'); + + Module.register('Select', function () { + var minder = this; + var rc = minder.getRenderContainer(); + + // 在实例上渲染框选矩形、计算框选范围的对象 + var marqueeActivator = (function () { + // 记录选区的开始位置(mousedown的位置) + var startPosition = null; + + // 选区的图形 + var marqueeShape = new kity.Path(); + + // 标记是否已经启动框选状态 + // 并不是 mousedown 发生之后就启动框选状态,而是检测到移动了一定的距离(MARQUEE_MODE_THRESHOLD)之后 + var marqueeMode = false; + var MARQUEE_MODE_THRESHOLD = 10; + + return { + selectStart: function (e) { + // 只接受左键 + if (e.originEvent.button || e.originEvent.altKey) return; + + // 清理不正确状态 + if (startPosition) { + return this.selectEnd(); + } + + startPosition = e.getPosition(rc).round(); + }, + selectMove: function (e) { + if (minder.getStatus() == 'textedit') { + return; + } + if (!startPosition) return; + + var p1 = startPosition, + p2 = e.getPosition(rc); + + // 检测是否要进入选区模式 + if (!marqueeMode) { + // 距离没达到阈值,退出 + if (kity.Vector.fromPoints(p1, p2).length() < MARQUEE_MODE_THRESHOLD) { + return; + } + // 已经达到阈值,记录下来并且重置选区形状 + marqueeMode = true; + rc.addShape(marqueeShape); + marqueeShape + .fill(minder.getStyle('marquee-background')) + .stroke(minder.getStyle('marquee-stroke')) + .setOpacity(0.8) + .getDrawer() + .clear(); + } + + var marquee = new kity.Box(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y), + selectedNodes = []; + + // 使其犀利 + marquee.left = Math.round(marquee.left); + marquee.top = Math.round(marquee.top); + marquee.right = Math.round(marquee.right); + marquee.bottom = Math.round(marquee.bottom); + + // 选区形状更新 + marqueeShape.getDrawer().pipe(function () { + this.clear(); + this.moveTo(marquee.left, marquee.top); + this.lineTo(marquee.right, marquee.top); + this.lineTo(marquee.right, marquee.bottom); + this.lineTo(marquee.left, marquee.bottom); + this.close(); + }); + + // 计算选中范围 + minder.getRoot().traverse(function (node) { + var renderBox = node.getLayoutBox(); + if (!renderBox.intersect(marquee).isEmpty()) { + selectedNodes.push(node); + } + }); + + // 应用选中范围 + minder.select(selectedNodes, true); + + // 清除多余的东西 + window.getSelection().removeAllRanges(); + }, + selectEnd: function (e) { + if (startPosition) { + startPosition = null; + } + if (marqueeMode) { + marqueeShape.fadeOut(200, 'ease', 0, function () { + if (marqueeShape.remove) marqueeShape.remove(); + }); + marqueeMode = false; + } + }, + }; + })(); + + var lastDownNode = null, + lastDownPosition = null; + return { + init: function () { + window.addEventListener('mouseup', function () { + marqueeActivator.selectEnd(); + }); + }, + events: { + 'mousedown': function (e) { + var downNode = e.getTargetNode(); + + // 没有点中节点: + // 清除选中状态,并且标记选区开始位置 + if (!downNode) { + this.removeAllSelectedNodes(); + marqueeActivator.selectStart(e); + + this.setStatus('normal'); + } + + // 点中了节点,并且按了 shift 键: + // 被点中的节点切换选中状态 + else if (e.isShortcutKey('Ctrl')) { + this.toggleSelect(downNode); + } + + // 点中的节点没有被选择: + // 单选点中的节点 + else if (!downNode.isSelected()) { + this.select(downNode, true); + } + + // 点中的节点被选中了,并且不是单选: + // 完成整个点击之后需要使其变为单选。 + // 不能马上变为单选,因为可能是需要拖动选中的多个节点 + else if (!this.isSingleSelect()) { + lastDownNode = downNode; + lastDownPosition = e.getPosition(); + } + }, + 'mousemove': marqueeActivator.selectMove, + 'mouseup': function (e) { + var upNode = e.getTargetNode(); + + // 如果 mouseup 发生在 lastDownNode 外,是无需理会的 + if (upNode && upNode == lastDownNode) { + var upPosition = e.getPosition(); + var movement = kity.Vector.fromPoints(lastDownPosition, upPosition); + if (movement.length() < 1) this.select(lastDownNode, true); + lastDownNode = null; + } + + // 清理一下选择状态 + marqueeActivator.selectEnd(e); + }, + //全选操作 + 'normal.keydown': function (e) { + if (e.isShortcutKey('ctrl+a')) { + var selectedNodes = []; + + this.getRoot().traverse(function (node) { + selectedNodes.push(node); + }); + this.select(selectedNodes, true); + e.preventDefault(); + } + }, + }, + }; + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/style.js b/packages/client/src/thirtypart/kityminder/kity-core/module/style.js new file mode 100644 index 00000000..a6f0f818 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/style.js @@ -0,0 +1,114 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + + var Minder = require('../core/minder'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + var Renderer = require('../core/render'); + + Module.register('StyleModule', function () { + var styleNames = ['font-size', 'font-family', 'font-weight', 'font-style', 'background', 'color']; + var styleClipBoard = null; + + function hasStyle(node) { + var data = node.getData(); + for (var i = 0; i < styleNames.length; i++) { + if (styleNames[i] in data) return true; + } + } + + return { + commands: { + /** + * @command CopyStyle + * @description 拷贝选中节点的当前样式,包括字体、字号、粗体、斜体、背景色、字体色 + * @state + * 0: 当前有选中的节点 + * -1: 当前没有选中的节点 + */ + copystyle: kity.createClass('CopyStyleCommand', { + base: Command, + + execute: function (minder) { + var node = minder.getSelectedNode(); + var nodeData = node.getData(); + styleClipBoard = {}; + styleNames.forEach(function (name) { + if (name in nodeData) styleClipBoard[name] = nodeData[name]; + else { + styleClipBoard[name] = null; + delete styleClipBoard[name]; + } + }); + return styleClipBoard; + }, + + queryState: function (minder) { + var nodes = minder.getSelectedNodes(); + if (nodes.length !== 1) return -1; + return hasStyle(nodes[0]) ? 0 : -1; + }, + }), + + /** + * @command PasteStyle + * @description 粘贴已拷贝的样式到选中的节点上,包括字体、字号、粗体、斜体、背景色、字体色 + * @state + * 0: 当前有选中的节点,并且已经有复制的样式 + * -1: 当前没有选中的节点,或者没有复制的样式 + */ + pastestyle: kity.createClass('PastStyleCommand', { + base: Command, + + execute: function (minder) { + minder.getSelectedNodes().forEach(function (node) { + for (var name in styleClipBoard) { + if (styleClipBoard.hasOwnProperty(name)) node.setData(name, styleClipBoard[name]); + } + }); + minder.renderNodeBatch(minder.getSelectedNodes()); + minder.layout(300); + return styleClipBoard; + }, + + queryState: function (minder) { + return styleClipBoard && minder.getSelectedNodes().length ? 0 : -1; + }, + }), + + /** + * @command ClearStyle + * @description 移除选中节点的样式,包括字体、字号、粗体、斜体、背景色、字体色 + * @state + * 0: 当前有选中的节点,并且至少有一个设置了至少一种样式 + * -1: 其它情况 + */ + clearstyle: kity.createClass('ClearStyleCommand', { + base: Command, + execute: function (minder) { + minder.getSelectedNodes().forEach(function (node) { + styleNames.forEach(function (name) { + node.setData(name); + }); + }); + minder.renderNodeBatch(minder.getSelectedNodes()); + minder.layout(300); + return styleClipBoard; + }, + + queryState: function (minder) { + var nodes = minder.getSelectedNodes(); + if (!nodes.length) return -1; + for (var i = 0; i < nodes.length; i++) { + if (hasStyle(nodes[i])) return 0; + } + return -1; + }, + }), + }, + }; + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/text.js b/packages/client/src/thirtypart/kityminder/kity-core/module/text.js new file mode 100644 index 00000000..a2fd2e40 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/text.js @@ -0,0 +1,285 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + + var Minder = require('../core/minder'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + var Renderer = require('../core/render'); + /** + * 针对不同系统、不同浏览器、不同字体做居中兼容性处理 + * 暂时未增加Linux的处理 + */ + var FONT_ADJUST = { + safari: { + '微软雅黑,Microsoft YaHei': -0.17, + '楷体,楷体_GB2312,SimKai': -0.1, + '隶书, SimLi': -0.1, + 'comic sans ms': -0.23, + 'impact,chicago': -0.15, + 'times new roman': -0.1, + 'arial black,avant garde': -0.17, + 'default': 0, + }, + ie: { + 10: { + '微软雅黑,Microsoft YaHei': -0.17, + 'comic sans ms': -0.17, + 'impact,chicago': -0.08, + 'times new roman': 0.04, + 'arial black,avant garde': -0.17, + 'default': -0.15, + }, + 11: { + '微软雅黑,Microsoft YaHei': -0.17, + 'arial,helvetica,sans-serif': -0.17, + 'comic sans ms': -0.17, + 'impact,chicago': -0.08, + 'times new roman': 0.04, + 'sans-serif': -0.16, + 'arial black,avant garde': -0.17, + 'default': -0.15, + }, + }, + edge: { + '微软雅黑,Microsoft YaHei': -0.15, + 'arial,helvetica,sans-serif': -0.17, + 'comic sans ms': -0.17, + 'impact,chicago': -0.08, + 'sans-serif': -0.16, + 'arial black,avant garde': -0.17, + 'default': -0.15, + }, + sg: { + '微软雅黑,Microsoft YaHei': -0.15, + 'arial,helvetica,sans-serif': -0.05, + 'comic sans ms': -0.22, + 'impact,chicago': -0.16, + 'times new roman': -0.03, + 'arial black,avant garde': -0.22, + 'default': -0.15, + }, + chrome: { + Mac: { + 'andale mono': -0.05, + 'comic sans ms': -0.3, + 'impact,chicago': -0.13, + 'times new roman': -0.1, + 'arial black,avant garde': -0.17, + 'default': 0, + }, + Win: { + '微软雅黑,Microsoft YaHei': -0.15, + 'arial,helvetica,sans-serif': -0.02, + 'arial black,avant garde': -0.2, + 'comic sans ms': -0.2, + 'impact,chicago': -0.12, + 'times new roman': -0.02, + 'default': -0.15, + }, + Lux: { + 'andale mono': -0.05, + 'comic sans ms': -0.3, + 'impact,chicago': -0.13, + 'times new roman': -0.1, + 'arial black,avant garde': -0.17, + 'default': 0, + }, + }, + firefox: { + Mac: { + '微软雅黑,Microsoft YaHei': -0.2, + '宋体,SimSun': 0.05, + 'comic sans ms': -0.2, + 'impact,chicago': -0.15, + 'arial black,avant garde': -0.17, + 'times new roman': -0.1, + 'default': 0.05, + }, + Win: { + '微软雅黑,Microsoft YaHei': -0.16, + 'andale mono': -0.17, + 'arial,helvetica,sans-serif': -0.17, + 'comic sans ms': -0.22, + 'impact,chicago': -0.23, + 'times new roman': -0.22, + 'sans-serif': -0.22, + 'arial black,avant garde': -0.17, + 'default': -0.16, + }, + Lux: { + '宋体,SimSun': -0.2, + '微软雅黑,Microsoft YaHei': -0.2, + '黑体, SimHei': -0.2, + '隶书, SimLi': -0.2, + '楷体,楷体_GB2312,SimKai': -0.2, + 'andale mono': -0.2, + 'arial,helvetica,sans-serif': -0.2, + 'comic sans ms': -0.2, + 'impact,chicago': -0.2, + 'times new roman': -0.2, + 'sans-serif': -0.2, + 'arial black,avant garde': -0.2, + 'default': -0.16, + }, + }, + }; + + var TextRenderer = kity.createClass('TextRenderer', { + base: Renderer, + + create: function () { + return new kity.Group().setId(utils.uuid('node_text')); + }, + + update: function (textGroup, node) { + function getDataOrStyle(name) { + return node.getData(name) || node.getStyle(name); + } + + var nodeText = node.getText(); + var textArr = nodeText ? nodeText.split('\n') : [' ']; + + var lineHeight = node.getStyle('line-height'); + + var fontSize = getDataOrStyle('font-size'); + var fontFamily = getDataOrStyle('font-family') || 'default'; + + var height = lineHeight * fontSize * textArr.length - (lineHeight - 1) * fontSize; + var yStart = -height / 2; + var Browser = kity.Browser; + var adjust; + + if (Browser.chrome || Browser.opera || Browser.bd || Browser.lb === 'chrome') { + adjust = FONT_ADJUST['chrome'][Browser.platform][fontFamily]; + } else if (Browser.gecko) { + adjust = FONT_ADJUST['firefox'][Browser.platform][fontFamily]; + } else if (Browser.sg) { + adjust = FONT_ADJUST['sg'][fontFamily]; + } else if (Browser.safari) { + adjust = FONT_ADJUST['safari'][fontFamily]; + } else if (Browser.ie) { + adjust = FONT_ADJUST['ie'][Browser.version][fontFamily]; + } else if (Browser.edge) { + adjust = FONT_ADJUST['edge'][fontFamily]; + } else if (Browser.lb) { + // 猎豹浏览器的ie内核兼容性模式下 + adjust = 0.9; + } + + textGroup.setTranslate(0, (adjust || 0) * fontSize); + + var rBox = new kity.Box(), + r = Math.round; + + this.setTextStyle(node, textGroup); + + var textLength = textArr.length; + + var textGroupLength = textGroup.getItems().length; + + var i, ci, textShape, text; + + if (textLength < textGroupLength) { + for (i = textLength, ci; (ci = textGroup.getItem(i)); ) { + textGroup.removeItem(i); + } + } else if (textLength > textGroupLength) { + var growth = textLength - textGroupLength; + while (growth--) { + textShape = new kity.Text().setAttr('text-rendering', 'inherit'); + if (kity.Browser.ie || kity.Browser.edge) { + textShape.setVerticalAlign('top'); + } else { + textShape.setAttr('dominant-baseline', 'text-before-edge'); + } + textGroup.addItem(textShape); + } + } + + for (i = 0, text, textShape; (text = textArr[i]), (textShape = textGroup.getItem(i)); i++) { + textShape.setContent(text); + if (kity.Browser.ie || kity.Browser.edge) { + textShape.fixPosition(); + } + } + + this.setTextStyle(node, textGroup); + + var textHash = + node.getText() + ['font-size', 'font-name', 'font-weight', 'font-style'].map(getDataOrStyle).join('/'); + + if (node._currentTextHash == textHash && node._currentTextGroupBox) return node._currentTextGroupBox; + + node._currentTextHash = textHash; + + return function () { + textGroup.eachItem(function (i, textShape) { + var y = yStart + i * fontSize * lineHeight; + + textShape.setY(y); + var bbox = textShape.getBoundaryBox(); + 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; + }; + }, + + setTextStyle: function (node, text) { + var hooks = TextRenderer._styleHooks; + hooks.forEach(function (hook) { + hook(node, text); + }); + }, + }); + + var TextCommand = kity.createClass({ + base: Command, + execute: function (minder, text) { + var node = minder.getSelectedNode(); + if (node) { + node.setText(text); + node.render(); + minder.layout(); + } + }, + queryState: function (minder) { + return minder.getSelectedNodes().length == 1 ? 0 : -1; + }, + queryValue: function (minder) { + var node = minder.getSelectedNode(); + return node ? node.getText() : null; + }, + }); + + utils.extend(TextRenderer, { + _styleHooks: [], + + registerStyleHook: function (fn) { + TextRenderer._styleHooks.push(fn); + }, + }); + + kity.extendClass(MinderNode, { + getTextGroup: function () { + return this.getRenderer('TextRenderer').getRenderShape(); + }, + }); + + Module.register('text', { + commands: { + text: TextCommand, + }, + renderers: { + center: TextRenderer, + }, + }); + + module.exports = TextRenderer; +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/view.js b/packages/client/src/thirtypart/kityminder/kity-core/module/view.js new file mode 100644 index 00000000..81bc7fcd --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/view.js @@ -0,0 +1,404 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + + var Minder = require('../core/minder'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + var Renderer = require('../core/render'); + + var ViewDragger = kity.createClass('ViewDragger', { + constructor: function (minder) { + this._minder = minder; + this._enabled = false; + this._bind(); + var me = this; + this._minder.getViewDragger = function () { + return me; + }; + this.setEnabled(false); + }, + + isEnabled: function () { + return this._enabled; + }, + + setEnabled: function (value) { + var paper = this._minder.getPaper(); + paper.setStyle('cursor', value ? 'pointer' : 'default'); + paper.setStyle('cursor', value ? '-webkit-grab' : 'default'); + this._enabled = value; + }, + timeline: function () { + return this._moveTimeline; + }, + + move: function (offset, duration) { + var minder = this._minder; + + var targetPosition = this.getMovement().offset(offset); + + this.moveTo(targetPosition, duration); + }, + + moveTo: function (position, duration) { + if (duration) { + var dragger = this; + + if (this._moveTimeline) this._moveTimeline.stop(); + + this._moveTimeline = this._minder + .getRenderContainer() + .animate( + new kity.Animator(this.getMovement(), position, function (target, value) { + dragger.moveTo(value); + }), + duration, + 'easeOutCubic' + ) + .timeline(); + + this._moveTimeline.on('finish', function () { + dragger._moveTimeline = null; + }); + + return this; + } + + this._minder.getRenderContainer().setTranslate(position.round()); + this._minder.fire('viewchange'); + }, + + getMovement: function () { + var translate = this._minder.getRenderContainer().transform.translate; + return translate ? translate[0] : new kity.Point(); + }, + + getView: function () { + var minder = this._minder; + var c = minder._lastClientSize || { + width: minder.getRenderTarget().clientWidth, + height: minder.getRenderTarget().clientHeight, + }; + var m = this.getMovement(); + var box = new kity.Box(0, 0, c.width, c.height); + var viewMatrix = minder.getPaper().getViewPortMatrix(); + return viewMatrix.inverse().translate(-m.x, -m.y).transformBox(box); + }, + + _bind: function () { + var dragger = this, + isTempDrag = false, + lastPosition = null, + currentPosition = null; + + function dragEnd(e) { + if (!lastPosition) return; + + lastPosition = null; + + e.stopPropagation(); + + // 临时拖动需要还原状态 + if (isTempDrag) { + dragger.setEnabled(false); + isTempDrag = false; + if (dragger._minder.getStatus() == 'hand') dragger._minder.rollbackStatus(); + } + var paper = dragger._minder.getPaper(); + paper.setStyle('cursor', dragger._minder.getStatus() == 'hand' ? '-webkit-grab' : 'default'); + + dragger._minder.fire('viewchanged'); + } + + this._minder + .on( + 'normal.mousedown normal.touchstart ' + + 'inputready.mousedown inputready.touchstart ' + + 'readonly.mousedown readonly.touchstart', + function (e) { + if (e.originEvent.button == 2) { + e.originEvent.preventDefault(); // 阻止中键拉动 + } + // 点击未选中的根节点临时开启 + if (e.getTargetNode() == this.getRoot() || e.originEvent.button == 2 || e.originEvent.altKey) { + lastPosition = e.getPosition('view'); + isTempDrag = true; + } + } + ) + + .on( + 'normal.mousemove normal.touchmove ' + + 'readonly.mousemove readonly.touchmove ' + + 'inputready.mousemove inputready.touchmove', + function (e) { + if (e.type == 'touchmove') { + e.preventDefault(); // 阻止浏览器的后退事件 + } + if (!isTempDrag) return; + var offset = kity.Vector.fromPoints(lastPosition, e.getPosition('view')); + if (offset.length() > 10) { + this.setStatus('hand', true); + var paper = dragger._minder.getPaper(); + paper.setStyle('cursor', '-webkit-grabbing'); + } + } + ) + + .on('hand.beforemousedown hand.beforetouchstart', function (e) { + // 已经被用户打开拖放模式 + if (dragger.isEnabled()) { + lastPosition = e.getPosition('view'); + e.stopPropagation(); + var paper = dragger._minder.getPaper(); + paper.setStyle('cursor', '-webkit-grabbing'); + } + }) + + .on('hand.beforemousemove hand.beforetouchmove', function (e) { + if (lastPosition) { + currentPosition = e.getPosition('view'); + + // 当前偏移加上历史偏移 + var offset = kity.Vector.fromPoints(lastPosition, currentPosition); + dragger.move(offset); + e.stopPropagation(); + e.preventDefault(); + e.originEvent.preventDefault(); + lastPosition = currentPosition; + } + }) + + .on('mouseup touchend', dragEnd); + + window.addEventListener('mouseup', dragEnd); + this._minder.on('contextmenu', function (e) { + e.preventDefault(); + }); + }, + }); + + Module.register('View', function () { + var km = this; + + /** + * @command Hand + * @description 切换抓手状态,抓手状态下,鼠标拖动将拖动视野,而不是创建选区 + * @state + * 0: 当前不是抓手状态 + * 1: 当前是抓手状态 + */ + var ToggleHandCommand = kity.createClass('ToggleHandCommand', { + base: Command, + execute: function (minder) { + if (minder.getStatus() != 'hand') { + minder.setStatus('hand', true); + } else { + minder.rollbackStatus(); + } + this.setContentChanged(false); + }, + queryState: function (minder) { + return minder.getStatus() == 'hand' ? 1 : 0; + }, + enableReadOnly: true, + }); + + /** + * @command Camera + * @description 设置当前视野的中心位置到某个节点上 + * @param {kityminder.MinderNode} focusNode 要定位的节点 + * @param {number} duration 设置视野移动的动画时长(单位 ms),设置为 0 不使用动画 + * @state + * 0: 始终可用 + */ + var CameraCommand = kity.createClass('CameraCommand', { + base: Command, + execute: function (km, focusNode) { + const dragger = km._viewDragger; + const duration = km.getOption('viewAnimationDuration'); + let dx = 0; + let dy = 0; + + if (!focusNode || focusNode.type === 'root') { + // 默认居中 + const parentNode = km.getPaper().node; + const shapeNode = km.getRoot().rc.container.node; + const { width: pw, height: ph, x: px, y: py } = parentNode.getBoundingClientRect(); + const { width: sw, height: sh, x, y } = shapeNode.getBBox(); + dx = pw / 2 - x - sw / 2; + dy = ph / 2 - y - sh / 2; + dragger.moveTo(new kity.Point(dx, dy), duration); + } else { + var viewport = km.getPaper().getViewPort(); + var offset = focusNode.getRenderContainer().getRenderBox('view'); + dx = viewport.center.x - offset.x - offset.width / 2; + dy = viewport.center.y - offset.y; + dragger.move(new kity.Point(dx, dy), duration); + } + + this.setContentChanged(false); + }, + enableReadOnly: true, + }); + + /** + * @command Move + * @description 指定方向移动当前视野 + * @param {string} dir 移动方向 + * 取值为 'left',视野向左移动一半 + * 取值为 'right',视野向右移动一半 + * 取值为 'up',视野向上移动一半 + * 取值为 'down',视野向下移动一半 + * @param {number} duration 视野移动的动画时长(单位 ms),设置为 0 不使用动画 + * @state + * 0: 始终可用 + */ + var MoveCommand = kity.createClass('MoveCommand', { + base: Command, + + execute: function (km, dir) { + var dragger = km._viewDragger; + var size = km._lastClientSize; + var duration = km.getOption('viewAnimationDuration'); + switch (dir) { + case 'up': + dragger.move(new kity.Point(0, size.height / 2), duration); + break; + case 'down': + dragger.move(new kity.Point(0, -size.height / 2), duration); + break; + case 'left': + dragger.move(new kity.Point(size.width / 2, 0), duration); + break; + case 'right': + dragger.move(new kity.Point(-size.width / 2, 0), duration); + break; + } + }, + + enableReadOnly: true, + }); + + return { + init: function () { + this._viewDragger = new ViewDragger(this); + }, + commands: { + hand: ToggleHandCommand, + camera: CameraCommand, + move: MoveCommand, + }, + events: { + 'statuschange': function (e) { + this._viewDragger.setEnabled(e.currentStatus == 'hand'); + }, + 'mousewheel': function (e) { + var dx, dy; + e = e.originEvent; + if (e.ctrlKey || e.shiftKey) return; + if ('wheelDeltaX' in e) { + dx = e.wheelDeltaX || 0; + dy = e.wheelDeltaY || 0; + } else { + dx = 0; + dy = e.wheelDelta; + } + + this._viewDragger.move({ + x: dx / 2.5, + y: dy / 2.5, + }); + + var me = this; + clearTimeout(this._mousewheeltimer); + this._mousewheeltimer = setTimeout(function () { + me.fire('viewchanged'); + }, 100); + + e.preventDefault(); + }, + 'normal.dblclick readonly.dblclick': function (e) { + if (e.kityEvent.targetShape instanceof kity.Paper) { + this.execCommand('camera', this.getRoot(), 800); + } + }, + 'paperrender finishInitHook': function () { + if (!this.getRenderTarget()) { + return; + } + this.execCommand('camera', null, 0); + this._lastClientSize = { + width: this.getRenderTarget().clientWidth, + height: this.getRenderTarget().clientHeight, + }; + }, + 'resize': function (e) { + var a = { + width: this.getRenderTarget().clientWidth, + height: this.getRenderTarget().clientHeight, + }, + b = this._lastClientSize; + this._viewDragger.move(new kity.Point(((a.width - b.width) / 2) | 0, ((a.height - b.height) / 2) | 0)); + this._lastClientSize = a; + }, + 'selectionchange layoutallfinish': function (e) { + var selected = this.getSelectedNode(); + var minder = this; + + /* + * Added by zhangbobell 2015.9.9 + * windows 10 的 edge 浏览器在全部动画停止后,优先级图标不显示 text, + * 因此再次触发一次 render 事件,让浏览器重绘 + * */ + if (kity.Browser.edge) { + this.fire('paperrender'); + } + if (!selected) return; + + var dragger = this._viewDragger; + var timeline = dragger.timeline(); + + /* + * Added by zhangbobell 2015.09.25 + * 如果之前有动画,那么就先暂时返回,等之前动画结束之后再次执行本函数 + * 以防止 view 动画变动了位置,导致本函数执行的时候位置计算不对 + * + * fixed bug : 初始化的时候中心节点位置不固定(有的时候在左上角,有的时候在中心) + * */ + if (timeline) { + timeline.on('finish', function () { + minder.fire('selectionchange'); + }); + + return; + } + + var view = dragger.getView(); + var focus = selected.getLayoutBox(); + var space = 50; + var dx = 0, + dy = 0; + + if (focus.right > view.right) { + dx += view.right - focus.right - space; + } else if (focus.left < view.left) { + dx += view.left - focus.left + space; + } + + if (focus.bottom > view.bottom) { + dy += view.bottom - focus.bottom - space; + } + if (focus.top < view.top) { + dy += view.top - focus.top + space; + } + + if (dx || dy) dragger.move(new kity.Point(dx, dy), 100); + }, + }, + }; + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/module/zoom.js b/packages/client/src/thirtypart/kityminder/kity-core/module/zoom.js new file mode 100644 index 00000000..b22c3e5d --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/module/zoom.js @@ -0,0 +1,201 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var utils = require('../core/utils'); + + var Minder = require('../core/minder'); + var MinderNode = require('../core/node'); + var Command = require('../core/command'); + var Module = require('../core/module'); + var Renderer = require('../core/render'); + + Module.register('Zoom', function () { + var me = this; + + var timeline; + + function setTextRendering() { + var value = me._zoomValue >= 100 ? 'optimize-speed' : 'geometricPrecision'; + me.getRenderContainer().setAttr('text-rendering', value); + } + + function fixPaperCTM(paper) { + var node = paper.shapeNode; + var ctm = node.getCTM(); + var matrix = new kity.Matrix(ctm.a, ctm.b, ctm.c, ctm.d, (ctm.e | 0) + 0.5, (ctm.f | 0) + 0.5); + node.setAttribute('transform', 'matrix(' + matrix.toString() + ')'); + } + + kity.extendClass(Minder, { + zoom: function (value) { + var paper = this.getPaper(); + var viewport = paper.getViewPort(); + viewport.zoom = value / 100; + viewport.center = { + x: viewport.center.x, + y: viewport.center.y, + }; + paper.setViewPort(viewport); + if (value == 100) fixPaperCTM(paper); + }, + getZoomValue: function () { + return this._zoomValue; + }, + }); + + function zoomMinder(minder, value) { + var paper = minder.getPaper(); + var viewport = paper.getViewPort(); + + if (!value) return; + + setTextRendering(); + + var duration = minder.getOption('zoomAnimationDuration'); + if (minder.getRoot().getComplex() > 200 || !duration) { + minder._zoomValue = value; + minder.zoom(value); + minder.fire('viewchange'); + } else { + var animator = new kity.Animator({ + beginValue: minder._zoomValue, + finishValue: value, + setter: function (target, value) { + target.zoom(value); + }, + }); + minder._zoomValue = value; + if (timeline) { + timeline.pause(); + } + timeline = animator.start(minder, duration, 'easeInOutSine'); + timeline.on('finish', function () { + minder.fire('viewchange'); + }); + } + minder.fire('zoom', { + zoom: value, + }); + } + + /** + * @command Zoom + * @description 缩放当前的视野到一定的比例(百分比) + * @param {number} value 设置的比例,取值 100 则为原尺寸 + * @state + * 0: 始终可用 + */ + var ZoomCommand = kity.createClass('Zoom', { + base: Command, + execute: zoomMinder, + queryValue: function (minder) { + return minder._zoomValue; + }, + }); + + /** + * @command ZoomIn + * @description 放大当前的视野到下一个比例等级(百分比) + * @shortcut = + * @state + * 0: 如果当前脑图的配置中还有下一个比例等级 + * -1: 其它情况 + */ + var ZoomInCommand = kity.createClass('ZoomInCommand', { + base: Command, + execute: function (minder) { + zoomMinder(minder, this.nextValue(minder)); + }, + queryState: function (minder) { + return +!this.nextValue(minder); + }, + nextValue: function (minder) { + var stack = minder.getOption('zoom'), + i; + for (i = 0; i < stack.length; i++) { + if (stack[i] > minder._zoomValue) return stack[i]; + } + return 0; + }, + enableReadOnly: true, + }); + + /** + * @command ZoomOut + * @description 缩小当前的视野到上一个比例等级(百分比) + * @shortcut - + * @state + * 0: 如果当前脑图的配置中还有上一个比例等级 + * -1: 其它情况 + */ + var ZoomOutCommand = kity.createClass('ZoomOutCommand', { + base: Command, + execute: function (minder) { + zoomMinder(minder, this.nextValue(minder)); + }, + queryState: function (minder) { + return +!this.nextValue(minder); + }, + nextValue: function (minder) { + var stack = minder.getOption('zoom'), + i; + for (i = stack.length - 1; i >= 0; i--) { + if (stack[i] < minder._zoomValue) return stack[i]; + } + return 0; + }, + enableReadOnly: true, + }); + + return { + init: function () { + this._zoomValue = 100; + this.setDefaultOptions({ + zoom: [10, 20, 50, 100, 200], + }); + setTextRendering(); + }, + commands: { + zoomin: ZoomInCommand, + zoomout: ZoomOutCommand, + zoom: ZoomCommand, + }, + events: { + 'normal.mousewheel readonly.mousewheel': function (e) { + if (!e.originEvent.ctrlKey && !e.originEvent.metaKey) return; + + var delta = e.originEvent.wheelDelta; + var me = this; + + if (!kity.Browser.mac) { + delta = -delta; + } + + // 稀释 + if (Math.abs(delta) > 100) { + clearTimeout(this._wheelZoomTimeout); + } else { + return; + } + + this._wheelZoomTimeout = setTimeout(function () { + var value; + var lastValue = me.getPaper()._zoom || 1; + if (delta < 0) { + me.execCommand('zoomin'); + } else if (delta > 0) { + me.execCommand('zoomout'); + } + }, 100); + + e.originEvent.preventDefault(); + }, + }, + + commandShortcutKeys: { + zoomin: 'ctrl+=', + zoomout: 'ctrl+-', + }, + }; + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/protocol/json.js b/packages/client/src/thirtypart/kityminder/kity-core/protocol/json.js new file mode 100644 index 00000000..30348a4a --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/protocol/json.js @@ -0,0 +1,22 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var data = require('../core/data'); + + data.registerProtocol( + 'json', + (module.exports = { + fileDescription: 'KityMinder 格式', + fileExtension: '.km', + dataType: 'text', + mineType: 'application/json', + + encode: function (json) { + return JSON.stringify(json); + }, + + decode: function (local) { + return JSON.parse(local); + }, + }) + ); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/protocol/markdown.js b/packages/client/src/thirtypart/kityminder/kity-core/protocol/markdown.js new file mode 100644 index 00000000..9b1521d7 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/protocol/markdown.js @@ -0,0 +1,168 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var data = require('../core/data'); + var LINE_ENDING_SPLITER = /\r\n|\r|\n/; + var EMPTY_LINE = ''; + var NOTE_MARK_START = ''; + var NOTE_MARK_CLOSE = ''; + + function encode(json) { + return _build(json, 1).join('\n'); + } + + function _build(node, level) { + var lines = []; + + level = level || 1; + + var sharps = _generateHeaderSharp(level); + lines.push(sharps + ' ' + node.data.text); + lines.push(EMPTY_LINE); + + var note = node.data.note; + if (note) { + var hasSharp = /^#/.test(note); + if (hasSharp) { + lines.push(NOTE_MARK_START); + note = note.replace(/^#+/gm, function ($0) { + return sharps + $0; + }); + } + lines.push(note); + if (hasSharp) { + lines.push(NOTE_MARK_CLOSE); + } + lines.push(EMPTY_LINE); + } + + if (node.children) + node.children.forEach(function (child) { + lines = lines.concat(_build(child, level + 1)); + }); + + return lines; + } + + function _generateHeaderSharp(level) { + var sharps = ''; + while (level--) sharps += '#'; + return sharps; + } + + function decode(markdown) { + var json, + parentMap = {}, + lines, + line, + lineInfo, + level, + node, + parent, + noteProgress, + codeBlock; + + // 一级标题转换 `{title}\n===` => `# {title}` + markdown = markdown.replace(/^(.+)\n={3,}/, function ($0, $1) { + return '# ' + $1; + }); + + lines = markdown.split(LINE_ENDING_SPLITER); + + // 按行分析 + for (var i = 0; i < lines.length; i++) { + line = lines[i]; + + lineInfo = _resolveLine(line); + + // 备注标记处理 + if (lineInfo.noteClose) { + noteProgress = false; + continue; + } else if (lineInfo.noteStart) { + noteProgress = true; + continue; + } + + // 代码块处理 + codeBlock = lineInfo.codeBlock ? !codeBlock : codeBlock; + + // 备注条件:备注标签中,非标题定义,或标题越位 + if (noteProgress || codeBlock || !lineInfo.level || lineInfo.level > level + 1) { + if (node) _pushNote(node, line); + continue; + } + + // 标题处理 + level = lineInfo.level; + node = _initNode(lineInfo.content, parentMap[level - 1]); + parentMap[level] = node; + } + + _cleanUp(parentMap[1]); + return parentMap[1]; + } + + function _initNode(text, parent) { + var node = { + data: { + text: text, + note: '', + }, + }; + if (parent) { + if (parent.children) parent.children.push(node); + else parent.children = [node]; + } + return node; + } + + function _pushNote(node, line) { + node.data.note += line + '\n'; + } + + function _isEmpty(line) { + return !/\S/.test(line); + } + + function _resolveLine(line) { + var match = /^(#+)?\s*(.*)$/.exec(line); + return { + level: (match[1] && match[1].length) || null, + content: match[2], + noteStart: line == NOTE_MARK_START, + noteClose: line == NOTE_MARK_CLOSE, + codeBlock: /^\s*```/.test(line), + }; + } + + function _cleanUp(node) { + if (!/\S/.test(node.data.note)) { + node.data.note = null; + delete node.data.note; + } else { + var notes = node.data.note.split('\n'); + while (notes.length && !/\S/.test(notes[0])) notes.shift(); + while (notes.length && !/\S/.test(notes[notes.length - 1])) notes.pop(); + node.data.note = notes.join('\n'); + } + if (node.children) node.children.forEach(_cleanUp); + } + + data.registerProtocol( + 'markdown', + (module.exports = { + fileDescription: 'Markdown/GFM 格式', + fileExtension: '.md', + mineType: 'text/markdown', + dataType: 'text', + + encode: function (json) { + return encode(json.root); + }, + + decode: function (markdown) { + return decode(markdown); + }, + }) + ); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/protocol/png.js b/packages/client/src/thirtypart/kityminder/kity-core/protocol/png.js new file mode 100644 index 00000000..1a5f4032 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/protocol/png.js @@ -0,0 +1,290 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var data = require('../core/data'); + var Promise = require('../core/promise'); + + var DomURL = window.URL || window.webkitURL || window; + + function loadImage(info, callback) { + return new Promise(function (resolve, reject) { + var image = document.createElement('img'); + image.onload = function () { + resolve({ + element: this, + x: info.x, + y: info.y, + width: info.width, + height: info.height, + }); + }; + image.onerror = function (err) { + reject(err); + }; + + image.crossOrigin = 'anonymous'; + image.src = info.url; + }); + } + + /** + * xhrLoadImage: 通过 xhr 加载保存在 BOS 上的图片 + * @note: BOS 上的 CORS 策略是取 headers 里面的 Origin 字段进行判断 + * 而通过 image 的 src 的方式是无法传递 origin 的,因此需要通过 xhr 进行 + */ + function xhrLoadImage(info, callback) { + return Promise(function (resolve, reject) { + var xmlHttp = new XMLHttpRequest(); + + xmlHttp.open('GET', info.url + '?_=' + Date.now(), true); + xmlHttp.responseType = 'blob'; + xmlHttp.onreadystatechange = function () { + if (xmlHttp.readyState === 4 && xmlHttp.status === 200) { + var blob = xmlHttp.response; + + var image = document.createElement('img'); + + image.src = DomURL.createObjectURL(blob); + image.onload = function () { + DomURL.revokeObjectURL(image.src); + resolve({ + element: image, + x: info.x, + y: info.y, + width: info.width, + height: info.height, + }); + }; + } + }; + + xmlHttp.send(); + }); + } + + function getSVGInfo(minder) { + var paper = minder.getPaper(), + paperTransform, + domContainer = paper.container, + svgXml, + svgContainer, + svgDom, + renderContainer = minder.getRenderContainer(), + renderBox = renderContainer.getRenderBox(), + width = renderBox.width + 1, + height = renderBox.height + 1, + blob, + svgUrl, + img; + + // 保存原始变换,并且移动到合适的位置 + paperTransform = paper.shapeNode.getAttribute('transform'); + paper.shapeNode.setAttribute('transform', 'translate(0.5, 0.5)'); + renderContainer.translate(-renderBox.x, -renderBox.y); + + // 获取当前的 XML 代码 + svgXml = paper.container.innerHTML; + + // 回复原始变换及位置 + renderContainer.translate(renderBox.x, renderBox.y); + paper.shapeNode.setAttribute('transform', paperTransform); + + // 过滤内容 + svgContainer = document.createElement('div'); + svgContainer.innerHTML = svgXml; + svgDom = svgContainer.querySelector('svg'); + svgDom.setAttribute('width', renderBox.width + 1); + svgDom.setAttribute('height', renderBox.height + 1); + svgDom.setAttribute('style', 'font-family: Arial, "Microsoft Yahei","Heiti SC";'); + + svgContainer = document.createElement('div'); + svgContainer.appendChild(svgDom); + + svgXml = svgContainer.innerHTML; + + // Dummy IE + svgXml = svgXml.replace( + ' xmlns="http://www.w3.org/2000/svg" ' + + 'xmlns:NS1="" NS1:ns1:xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:NS2="" NS2:xmlns:ns1=""', + '' + ); + + // svg 含有   符号导出报错 Entity 'nbsp' not defined ,含有控制字符触发Load Image 会触发报错 + svgXml = svgXml.replace(/ |[\x00-\x1F\x7F-\x9F]/g, ''); + + // fix title issue in safari + // @ http://stackoverflow.com/questions/30273775/namespace-prefix-ns1-for-href-on-tagelement-is-not-defined-setattributens + svgXml = svgXml.replace(/NS\d+:title/gi, 'xlink:title'); + + blob = new Blob([svgXml], { + type: 'image/svg+xml', + }); + + svgUrl = DomURL.createObjectURL(blob); + + //svgUrl = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgXml); + + var imagesInfo = []; + + // 遍历取出图片信息 + traverse(minder.getRoot()); + + function traverse(node) { + var nodeData = node.data; + + if (nodeData.image) { + minder.renderNode(node); + var nodeData = node.data; + var imageUrl = nodeData.image; + var imageSize = nodeData.imageSize; + var imageRenderBox = node.getRenderBox('ImageRenderer', minder.getRenderContainer()); + var imageInfo = { + url: imageUrl, + width: imageSize.width, + height: imageSize.height, + x: -renderContainer.getBoundaryBox().x + imageRenderBox.x, + y: -renderContainer.getBoundaryBox().y + imageRenderBox.y, + }; + + imagesInfo.push(imageInfo); + } + + // 若节点折叠,则直接返回 + if (nodeData.expandState === 'collapse') { + return; + } + + var children = node.getChildren(); + for (var i = 0; i < children.length; i++) { + traverse(children[i]); + } + } + + return { + width: width, + height: height, + dataUrl: svgUrl, + xml: svgXml, + imagesInfo: imagesInfo, + }; + } + + function encode(json, minder, option) { + var resultCallback; + + /* 绘制 PNG 的画布及上下文 */ + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + + /* 尝试获取背景图片 URL 或背景颜色 */ + var bgDeclare = minder.getStyle('background').toString(); + var bgUrl = /url\(\"(.+)\"\)/.exec(bgDeclare); + var bgColor = kity.Color.parse(bgDeclare); + + /* 获取 SVG 文件内容 */ + var svgInfo = getSVGInfo(minder); + var width = option && option.width && option.width > svgInfo.width ? option.width : svgInfo.width; + var height = option && option.height && option.height > svgInfo.height ? option.height : svgInfo.height; + var offsetX = option && option.width && option.width > svgInfo.width ? (option.width - svgInfo.width) / 2 : 0; + var offsetY = option && option.height && option.height > svgInfo.height ? (option.height - svgInfo.height) / 2 : 0; + var svgDataUrl = svgInfo.dataUrl; + var imagesInfo = svgInfo.imagesInfo; + + /* 画布的填充大小 */ + var padding = 20; + + canvas.width = width + padding * 2; + canvas.height = height + padding * 2; + + function fillBackground(ctx, style) { + ctx.save(); + ctx.fillStyle = style; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + } + + function drawImage(ctx, image, x, y, width, height) { + if (width && height) { + ctx.drawImage(image, x + padding, y + padding, width, height); + } else { + ctx.drawImage(image, x + padding, y + padding); + } + } + + function generateDataUrl(canvas) { + return canvas.toDataURL('image/png'); + } + + // 加载节点上的图片 + function loadImages(imagesInfo) { + var imagePromises = imagesInfo.map(function (imageInfo) { + return xhrLoadImage(imageInfo); + }); + + return Promise.all(imagePromises); + } + + function drawSVG() { + var svgData = { url: svgDataUrl }; + + return loadImage(svgData) + .then(function ($image) { + drawImage(ctx, $image.element, offsetX, offsetY, $image.width, $image.height); + return loadImages(imagesInfo); + }) + .then( + function ($images) { + for (var i = 0; i < $images.length; i++) { + drawImage( + ctx, + $images[i].element, + $images[i].x + offsetX, + $images[i].y + offsetY, + $images[i].width, + $images[i].height + ); + } + + DomURL.revokeObjectURL(svgDataUrl); + document.body.appendChild(canvas); + var pngBase64 = generateDataUrl(canvas); + + document.body.removeChild(canvas); + return pngBase64; + }, + function (err) { + // 这里处理 reject,出错基本上是因为跨域, + // 出错后依然导出,只不过没有图片。 + alert('脑图的节点中包含跨域图片,导出的 png 中节点图片不显示,你可以替换掉这些跨域的图片并重试。'); + DomURL.revokeObjectURL(svgDataUrl); + document.body.appendChild(canvas); + + var pngBase64 = generateDataUrl(canvas); + document.body.removeChild(canvas); + return pngBase64; + } + ); + } + + if (bgUrl) { + var bgInfo = { url: bgUrl[1] }; + return loadImage(bgInfo).then(function ($image) { + fillBackground(ctx, ctx.createPattern($image.element, 'repeat')); + return drawSVG(); + }); + } else { + fillBackground(ctx, bgColor.toString()); + return drawSVG(); + } + } + data.registerProtocol( + 'png', + (module.exports = { + fileDescription: 'PNG 图片', + fileExtension: '.png', + mineType: 'image/png', + dataType: 'base64', + encode: encode, + }) + ); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/protocol/svg.js b/packages/client/src/thirtypart/kityminder/kity-core/protocol/svg.js new file mode 100644 index 00000000..74531d2e --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/protocol/svg.js @@ -0,0 +1,260 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var data = require('../core/data'); + + /** + * 导出svg时删除全部svg元素中的transform + * @auth Naixor + * @method removeTransform + * @param {[type]} svgDom [description] + * @return {[type]} [description] + */ + function cleanSVG(svgDom, x, y) { + function getTransformToElement(target, source) { + var matrix; + try { + matrix = source.getScreenCTM().inverse(); + } catch (e) { + throw new Error("Can not inverse source element' ctm."); + } + return matrix.multiply(target.getScreenCTM()); + } + function dealWithPath(d, dealWithPattern) { + if (!(dealWithPattern instanceof Function)) { + dealWithPattern = function () {}; + } + var strArr = [], + pattern = [], + cache = []; + for (var i = 0, l = d.length; i < l; i++) { + switch (d[i]) { + case 'M': + case 'L': + case 'T': + case 'S': + case 'A': + case 'C': + case 'H': + case 'V': + case 'Q': { + if (cache.length) { + pattern.push(cache.join('')); + cache = []; + } + // 脑图的path格式真奇怪...偶尔就给我蹦出来一个"..V123 C..", 那空格几个意思 - - + if (pattern[pattern.length - 1] === ',') { + pattern.pop(); + } + if (pattern.length) { + dealWithPattern(pattern); + strArr.push(pattern.join('')); + pattern = []; + } + pattern.push(d[i]); + break; + } + case 'Z': + case 'z': { + pattern.push(cache.join(''), d[i]); + dealWithPattern(pattern); + strArr.push(pattern.join('')); + cache = []; + pattern = []; + break; + } + case '.': + case 'e': { + cache.push(d[i]); + break; + } + case '-': { + if (d[i - 1] !== 'e') { + if (cache.length) { + pattern.push(cache.join(''), ','); + } + cache = []; + } + cache.push('-'); + break; + } + case ' ': + case ',': { + if (cache.length) { + pattern.push(cache.join(''), ','); + cache = []; + } + break; + } + default: { + if (/\d/.test(d[i])) { + cache.push(d[i]); + } else { + // m a c s q h v l t z情况 + if (cache.length) { + pattern.push(cache.join(''), d[i]); + cache = []; + } else { + // 脑图的path格式真奇怪...偶尔就给我蹦出来一个"..V123 c..", 那空格几个意思 - - + if (pattern[pattern.length - 1] === ',') { + pattern.pop(); + } + pattern.push(d[i]); + } + } + if (i + 1 === l) { + if (cache.length) { + pattern.push(cache.join('')); + } + dealWithPattern(pattern); + strArr.push(pattern.join('')); + cache = null; + pattern = null; + } + } + } + } + return strArr.join(''); + } + + function replaceWithNode(svgNode, parentX, parentY) { + if (!svgNode) { + return; + } + if (svgNode.tagName === 'defs') { + return; + } + if (svgNode.getAttribute('fill') === 'transparent') { + svgNode.setAttribute('fill', 'none'); + } + if (svgNode.getAttribute('marker-end')) { + svgNode.removeAttribute('marker-end'); + } + parentX = parentX || 0; + parentY = parentY || 0; + if (svgNode.getAttribute('transform')) { + var ctm = getTransformToElement(svgNode, svgNode.parentElement); + parentX -= ctm.e; + parentY -= ctm.f; + svgNode.removeAttribute('transform'); + } + switch (svgNode.tagName.toLowerCase()) { + case 'g': + break; + case 'path': { + var d = svgNode.getAttribute('d'); + if (d) { + d = dealWithPath(d, function (pattern) { + switch (pattern[0]) { + case 'V': { + pattern[1] = +pattern[1] - parentY; + break; + } + case 'H': { + pattern[1] = +pattern[1] - parentX; + break; + } + case 'M': + case 'L': + case 'T': { + pattern[1] = +pattern[1] - parentX; + pattern[3] = +pattern[3] - parentY; + break; + } + case 'Q': + case 'S': { + pattern[1] = +pattern[1] - parentX; + pattern[3] = +pattern[3] - parentY; + pattern[5] = +pattern[5] - parentX; + pattern[7] = +pattern[7] - parentY; + break; + } + case 'A': { + pattern[11] = +pattern[11] - parentX; + pattern[13] = +pattern[13] - parentY; + break; + } + case 'C': { + pattern[1] = +pattern[1] - parentX; + pattern[3] = +pattern[3] - parentY; + pattern[5] = +pattern[5] - parentX; + pattern[7] = +pattern[7] - parentY; + pattern[9] = +pattern[9] - parentX; + pattern[11] = +pattern[11] - parentY; + } + } + }); + svgNode.setAttribute('d', d); + svgNode.removeAttribute('transform'); + } + return; + } + case 'image': + case 'text': { + if (parentX && parentY) { + var x = +svgNode.getAttribute('x') || 0, + y = +svgNode.getAttribute('y') || 0; + svgNode.setAttribute('x', x - parentX); + svgNode.setAttribute('y', y - parentY); + } + if (svgNode.getAttribute('dominant-baseline')) { + svgNode.removeAttribute('dominant-baseline'); + svgNode.setAttribute('dy', '.8em'); + } + svgNode.removeAttribute('transform'); + return; + } + } + if (svgNode.children) { + for (var i = 0, l = svgNode.children.length; i < l; i++) { + replaceWithNode(svgNode.children[i], parentX, parentY); + } + } + } + svgDom.style.visibility = 'hidden'; + replaceWithNode(svgDom, x || 0, y || 0); + svgDom.style.visibility = 'visible'; + } + data.registerProtocol( + 'svg', + (module.exports = { + fileDescription: 'SVG 矢量图', + fileExtension: '.svg', + mineType: 'image/svg+xml', + dataType: 'text', + encode: function (json, minder) { + var paper = minder.getPaper(), + paperTransform = paper.shapeNode.getAttribute('transform'), + svgXml, + svgContainer, + svgDom, + renderContainer = minder.getRenderContainer(), + renderBox = renderContainer.getRenderBox(), + transform = renderContainer.getTransform(), + width = renderBox.width, + height = renderBox.height, + padding = 20; + paper.shapeNode.setAttribute('transform', 'translate(0.5, 0.5)'); + svgXml = paper.container.innerHTML; + paper.shapeNode.setAttribute('transform', paperTransform); + svgContainer = document.createElement('div'); + document.body.appendChild(svgContainer); + svgContainer.innerHTML = svgXml; + svgDom = svgContainer.querySelector('svg'); + svgDom.setAttribute('width', (width + padding * 2) | 0); + svgDom.setAttribute('height', (height + padding * 2) | 0); + svgDom.setAttribute('style', 'background: ' + minder.getStyle('background')); //"font-family: Arial, Microsoft Yahei, Heiti SC; " + + svgDom.setAttribute('viewBox', [0, 0, (width + padding * 2) | 0, (height + padding * 2) | 0].join(' ')); + tempSvgContainer = document.createElement('div'); + cleanSVG(svgDom, (renderBox.x - padding) | 0, (renderBox.y - padding) | 0); + document.body.removeChild(svgContainer); + tempSvgContainer.appendChild(svgDom); + // need a xml with width and height + svgXml = tempSvgContainer.innerHTML; + // svg 含有   符号导出报错 Entity 'nbsp' not defined + svgXml = svgXml.replace(/ /g, ' '); + // svg 含有   符号导出报错 Entity 'nbsp' not defined + return svgXml; + }, + }) + ); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/protocol/text.js b/packages/client/src/thirtypart/kityminder/kity-core/protocol/text.js new file mode 100644 index 00000000..8bb7cae1 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/protocol/text.js @@ -0,0 +1,243 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var data = require('../core/data'); + var Browser = require('../core/kity').Browser; + + /** + * @Desc: 增加对不容浏览器下节点中文本\t匹配的处理,不同浏览器下\t无法正确匹配,导致无法使用TAB来批量导入节点 + * @Editor: Naixor + * @Date: 2015.9.17 + */ + var LINE_ENDING = '\r', + LINE_ENDING_SPLITER = /\r\n|\r|\n/, + TAB_CHAR = (function (Browser) { + if (Browser.gecko) { + return { + REGEXP: new RegExp('^(\t|' + String.fromCharCode(160, 160, 32, 160) + ')'), + DELETE: new RegExp('^(\t|' + String.fromCharCode(160, 160, 32, 160) + ')+'), + }; + } else if (Browser.ie || Browser.edge) { + // ie系列和edge比较特别,\t在div中会被直接转义成SPACE故只好使用SPACE来做处理 + return { + REGEXP: new RegExp('^(' + String.fromCharCode(32) + '|' + String.fromCharCode(160) + ')'), + DELETE: new RegExp('^(' + String.fromCharCode(32) + '|' + String.fromCharCode(160) + ')+'), + }; + } else { + return { + REGEXP: /^(\t|\x20{4})/, + DELETE: /^(\t|\x20{4})+/, + }; + } + })(Browser); + + function repeat(s, n) { + var result = ''; + while (n--) result += s; + return result; + } + + /** + * 对节点text中的换行符进行处理 + * @method encodeWrap + * @param {String} nodeText MinderNode.data.text + * @return {String} \n -> '\n'; \\n -> '\\n' + */ + function encodeWrap(nodeText) { + if (!nodeText) { + return ''; + } + var textArr = [], + WRAP_TEXT = ['\\', 'n']; + for (var i = 0, j = 0, l = nodeText.length; i < l; i++) { + if (nodeText[i] === '\n' || nodeText[i] === '\r') { + textArr.push('\\n'); + j = 0; + continue; + } + if (nodeText[i] === WRAP_TEXT[j]) { + j++; + if (j === 2) { + j = 0; + textArr.push('\\\\n'); + } + continue; + } + switch (j) { + case 0: { + textArr.push(nodeText[i]); + break; + } + case 1: { + textArr.push(nodeText[i - 1], nodeText[i]); + } + } + j = 0; + } + return textArr.join(''); + } + + /** + * 将文本内容中的'\n'和'\\n'分别转换成\n和\\n + * @method decodeWrap + * @param {[type]} text [description] + * @return {[type]} [description] + */ + function decodeWrap(text) { + if (!text) { + return ''; + } + var textArr = [], + WRAP_TEXT = ['\\', '\\', 'n']; + for (var i = 0, j = 0, l = text.length; i < l; i++) { + if (text[i] === WRAP_TEXT[j]) { + j++; + if (j === 3) { + j = 0; + textArr.push('\\n'); + } + continue; + } + switch (j) { + case 0: { + textArr.push(text[i]); + j = 0; + break; + } + case 1: { + if (text[i] === 'n') { + textArr.push('\n'); + } else { + textArr.push(text[i - 1], text[i]); + } + j = 0; + break; + } + case 2: { + textArr.push(text[i - 2]); + if (text[i] !== '\\') { + j = 0; + textArr.push(text[i - 1], text[i]); + } + break; + } + } + } + return textArr.join(''); + } + + function encode(json, level) { + var local = ''; + level = level || 0; + local += repeat('\t', level); + local += encodeWrap(json.data.text) + LINE_ENDING; + if (json.children) { + json.children.forEach(function (child) { + local += encode(child, level + 1); + }); + } + return local; + } + + function isEmpty(line) { + return !/\S/.test(line); + } + + function getLevel(line) { + var level = 0; + while (TAB_CHAR.REGEXP.test(line)) { + line = line.replace(TAB_CHAR.REGEXP, ''); + level++; + } + + return level; + } + + function getNode(line) { + return { + data: { + text: decodeWrap(line.replace(TAB_CHAR.DELETE, '')), + }, + }; + } + + function decode(local) { + var json, + parentMap = {}, + lines = local.split(LINE_ENDING_SPLITER), + line, + level, + node; + + function addChild(parent, child) { + var children = parent.children || (parent.children = []); + children.push(child); + } + + for (var i = 0; i < lines.length; i++) { + line = lines[i]; + if (isEmpty(line)) continue; + + level = getLevel(line); + node = getNode(line); + + if (level === 0) { + if (json) { + throw new Error('Invalid local format'); + } + json = node; + } else { + if (!parentMap[level - 1]) { + throw new Error('Invalid local format'); + } + addChild(parentMap[level - 1], node); + } + parentMap[level] = node; + } + return json; + } + + /** + * @Desc: 增加一个将当前选中节点转换成text的方法 + * @Editor: Naixor + * @Date: 2015.9.21 + */ + function Node2Text(node) { + function exportNode(node) { + var exported = {}; + exported.data = node.getData(); + var childNodes = node.getChildren(); + exported.children = []; + for (var i = 0; i < childNodes.length; i++) { + exported.children.push(exportNode(childNodes[i])); + } + return exported; + } + if (!node) return; + if (/^\s*$/.test(node.data.text)) { + node.data.text = '分支主题'; + } + return encode(exportNode(node)); + } + + data.registerProtocol( + 'text', + (module.exports = { + fileDescription: '大纲文本', + fileExtension: '.txt', + dataType: 'text', + mineType: 'text/plain', + + encode: function (json) { + return encode(json.root, 0); + }, + + decode: function (local) { + return decode(local); + }, + + Node2Text: function (node) { + return Node2Text(node); + }, + }) + ); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/template/default.js b/packages/client/src/thirtypart/kityminder/kity-core/template/default.js new file mode 100644 index 00000000..c14af22c --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/template/default.js @@ -0,0 +1,37 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 默认模板 - 脑图模板 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + var template = require('../core/template'); + + template.register('default', { + getLayout: function (node) { + if (node.getData('layout')) return node.getData('layout'); + + var level = node.getLevel(); + + // 根节点 + if (level === 0) { + return 'mind'; + } + + // 一级节点 + if (level === 1) { + return node.getLayoutPointPreview().x > 0 ? 'right' : 'left'; + } + + return node.parent.getLayout(); + }, + + getConnect: function (node) { + if (node.getLevel() == 1) return 'arc'; + return 'under'; + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/template/filetree.js b/packages/client/src/thirtypart/kityminder/kity-core/template/filetree.js new file mode 100644 index 00000000..9c81bd01 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/template/filetree.js @@ -0,0 +1,28 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 文件夹模板 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + var template = require('../core/template'); + + template.register('filetree', { + getLayout: function (node) { + if (node.getData('layout')) return node.getData('layout'); + if (node.isRoot()) return 'bottom'; + + return 'filetree-down'; + }, + + getConnect: function (node) { + if (node.getLevel() == 1) { + return 'poly'; + } + return 'l'; + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/template/fish-bone.js b/packages/client/src/thirtypart/kityminder/kity-core/template/fish-bone.js new file mode 100644 index 00000000..fbf7a2db --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/template/fish-bone.js @@ -0,0 +1,43 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 默认模板 - 鱼骨头模板 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + var template = require('../core/template'); + + template.register('fish-bone', { + getLayout: function (node) { + if (node.getData('layout')) return node.getData('layout'); + + var level = node.getLevel(); + + // 根节点 + if (level === 0) { + return 'fish-bone-master'; + } + + // 一级节点 + if (level === 1) { + return 'fish-bone-slave'; + } + + return node.getLayoutPointPreview().y > 0 ? 'filetree-up' : 'filetree-down'; + }, + + getConnect: function (node) { + switch (node.getLevel()) { + case 1: + return 'fish-bone-master'; + case 2: + return 'line'; + default: + return 'l'; + } + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/template/right.js b/packages/client/src/thirtypart/kityminder/kity-core/template/right.js new file mode 100644 index 00000000..6509fe11 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/template/right.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 往右布局结构模板 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + var template = require('../core/template'); + + template.register('right', { + getLayout: function (node) { + return node.getData('layout') || 'right'; + }, + + getConnect: function (node) { + if (node.getLevel() == 1) return 'arc'; + return 'bezier'; + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/template/structure.js b/packages/client/src/thirtypart/kityminder/kity-core/template/structure.js new file mode 100644 index 00000000..17ab3ea4 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/template/structure.js @@ -0,0 +1,22 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 组织结构图模板 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + var template = require('../core/template'); + + template.register('structure', { + getLayout: function (node) { + return node.getData('layout') || 'bottom'; + }, + + getConnect: function (node) { + return 'poly'; + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/template/tianpan.js b/packages/client/src/thirtypart/kityminder/kity-core/template/tianpan.js new file mode 100644 index 00000000..ff00744b --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/template/tianpan.js @@ -0,0 +1,30 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 天盘模板 + * + * @author: along + * @copyright: bpd729@163.com, 2015 + */ +define(function (require, exports, module) { + var template = require('../core/template'); + + template.register('tianpan', { + getLayout: function (node) { + if (node.getData('layout')) return node.getData('layout'); + var level = node.getLevel(); + + // 根节点 + if (level === 0) { + return 'tianpan'; + } + + return node.parent.getLayout(); + }, + + getConnect: function (node) { + return 'arc_tp'; + }, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/theme/default.js b/packages/client/src/thirtypart/kityminder/kity-core/theme/default.js new file mode 100644 index 00000000..1ea38427 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/theme/default.js @@ -0,0 +1,68 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var theme = require('../core/theme'); + + ['classic', 'classic-compact'].forEach(function (name) { + var compact = name == 'classic-compact'; + + /* jscs:disable maximumLineLength */ + theme.register(name, { + 'background': + '#3A4144 url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowQzg5QTQ0NDhENzgxMUUzOENGREE4QTg0RDgzRTZDNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDowQzg5QTQ0NThENzgxMUUzOENGREE4QTg0RDgzRTZDNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkMwOEQ1NDRGOEQ3NzExRTM4Q0ZEQThBODREODNFNkM3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkMwOEQ1NDUwOEQ3NzExRTM4Q0ZEQThBODREODNFNkM3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+e9P33AAAACVJREFUeNpisXJ0YUACTAyoAMr/+eM7EGGRZ4FQ7BycEAZAgAEAHbEGtkoQm/wAAAAASUVORK5CYII=") repeat', + + 'root-color': '#430', + 'root-background': '#e9df98', + 'root-stroke': '#e9df98', + 'root-font-size': 24, + 'root-padding': compact ? [10, 25] : [15, 25], + 'root-margin': compact ? [15, 25] : [30, 100], + 'root-radius': 30, + 'root-space': 10, + 'root-shadow': 'rgba(0, 0, 0, .25)', + + 'main-color': '#333', + 'main-background': '#a4c5c0', + 'main-stroke': '#a4c5c0', + 'main-font-size': 16, + 'main-padding': compact ? [5, 15] : [6, 20], + 'main-margin': compact ? [5, 10] : 20, + 'main-radius': 10, + 'main-space': 5, + 'main-shadow': 'rgba(0, 0, 0, .25)', + + 'sub-color': 'white', + 'sub-background': 'transparent', + 'sub-stroke': 'none', + 'sub-font-size': 12, + 'sub-padding': [5, 10], + 'sub-margin': compact ? [5, 10] : [15, 20], + 'sub-tree-margin': 30, + 'sub-radius': 5, + 'sub-space': 5, + + 'connect-color': 'white', + 'connect-width': 2, + 'main-connect-width': 3, + 'connect-radius': 5, + + 'selected-background': 'rgb(254, 219, 0)', + 'selected-stroke': 'rgb(254, 219, 0)', + 'selected-color': 'black', + + 'marquee-background': 'rgba(255,255,255,.3)', + 'marquee-stroke': 'white', + + 'drop-hint-color': 'yellow', + 'sub-drop-hint-width': 2, + 'main-drop-hint-width': 4, + 'root-drop-hint-width': 4, + + 'order-hint-area-color': 'rgba(0, 255, 0, .5)', + 'order-hint-path-color': '#0f0', + 'order-hint-path-width': 1, + + 'text-selection-color': 'rgb(27,171,255)', + 'line-height': 1.5, + }); + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/theme/fish.js b/packages/client/src/thirtypart/kityminder/kity-core/theme/fish.js new file mode 100644 index 00000000..e61dd035 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/theme/fish.js @@ -0,0 +1,59 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var theme = require('../core/theme'); + + theme.register('fish', { + 'background': + '#3A4144 url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowQzg5QTQ0NDhENzgxMUUzOENGREE4QTg0RDgzRTZDNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDowQzg5QTQ0NThENzgxMUUzOENGREE4QTg0RDgzRTZDNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkMwOEQ1NDRGOEQ3NzExRTM4Q0ZEQThBODREODNFNkM3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkMwOEQ1NDUwOEQ3NzExRTM4Q0ZEQThBODREODNFNkM3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+e9P33AAAACVJREFUeNpisXJ0YUACTAyoAMr/+eM7EGGRZ4FQ7BycEAZAgAEAHbEGtkoQm/wAAAAASUVORK5CYII=") repeat', + + 'root-color': '#430', + 'root-background': '#e9df98', + 'root-stroke': '#e9df98', + 'root-font-size': 24, + 'root-padding': [35, 35], + 'root-margin': 30, + 'root-radius': 100, + 'root-space': 10, + 'root-shadow': 'rgba(0, 0, 0, .25)', + + 'main-color': '#333', + 'main-background': '#a4c5c0', + 'main-stroke': '#a4c5c0', + 'main-font-size': 16, + 'main-padding': [6, 20], + 'main-margin': [20, 20], + 'main-radius': 5, + 'main-space': 5, + 'main-shadow': 'rgba(0, 0, 0, .25)', + + 'sub-color': 'black', + 'sub-background': 'white', + 'sub-stroke': 'white', + 'sub-font-size': 12, + 'sub-padding': [5, 10], + 'sub-margin': [10], + 'sub-radius': 5, + 'sub-space': 5, + + 'connect-color': 'white', + 'connect-width': 3, + 'main-connect-width': 3, + 'connect-radius': 5, + + 'selected-background': 'rgb(254, 219, 0)', + 'selected-stroke': 'rgb(254, 219, 0)', + + 'marquee-background': 'rgba(255,255,255,.3)', + 'marquee-stroke': 'white', + + 'drop-hint-color': 'yellow', + 'drop-hint-width': 4, + + 'order-hint-area-color': 'rgba(0, 255, 0, .5)', + 'order-hint-path-color': '#0f0', + 'order-hint-path-width': 1, + + 'text-selection-color': 'rgb(27,171,255)', + 'line-height': 1.5, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/theme/fresh.js b/packages/client/src/thirtypart/kityminder/kity-core/theme/fresh.js new file mode 100644 index 00000000..46362b0d --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/theme/fresh.js @@ -0,0 +1,78 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var kity = require('../core/kity'); + var theme = require('../core/theme'); + + function hsl(h, s, l) { + return kity.Color.createHSL(h, s, l); + } + + function generate(h, compat) { + return { + 'background': '#fbfbfb', + + 'root-color': 'white', + 'root-background': hsl(h, 37, 60), + 'root-stroke': hsl(h, 37, 60), + 'root-font-size': 16, + 'root-padding': compat ? [6, 12] : [12, 24], + 'root-margin': compat ? 10 : [30, 100], + 'root-radius': 5, + 'root-space': 10, + + 'main-color': 'black', + 'main-background': hsl(h, 33, 95), + 'main-stroke': hsl(h, 37, 60), + 'main-stroke-width': 1, + 'main-font-size': 14, + 'main-padding': [6, 20], + 'main-margin': compat ? 8 : 20, + 'main-radius': 3, + 'main-space': 5, + + 'sub-color': 'black', + 'sub-background': 'transparent', + 'sub-stroke': 'none', + 'sub-font-size': 12, + 'sub-padding': compat ? [3, 5] : [5, 10], + 'sub-margin': compat ? [4, 8] : [15, 20], + 'sub-radius': 5, + 'sub-space': 5, + + 'connect-color': hsl(h, 37, 60), + 'connect-width': 1, + 'connect-radius': 5, + + 'selected-stroke': hsl(h, 26, 30), + 'selected-stroke-width': '3', + 'blur-selected-stroke': hsl(h, 10, 60), + + 'marquee-background': hsl(h, 100, 80).set('a', 0.1), + 'marquee-stroke': hsl(h, 37, 60), + + 'drop-hint-color': hsl(h, 26, 35), + 'drop-hint-width': 5, + + 'order-hint-area-color': hsl(h, 100, 30).set('a', 0.5), + 'order-hint-path-color': hsl(h, 100, 25), + 'order-hint-path-width': 1, + + 'text-selection-color': hsl(h, 100, 20), + 'line-height': 1.5, + }; + } + + var plans = { + red: 0, + soil: 25, + green: 122, + blue: 204, + purple: 246, + pink: 334, + }; + var name; + for (name in plans) { + theme.register('fresh-' + name, generate(plans[name])); + theme.register('fresh-' + name + '-compat', generate(plans[name], true)); + } +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/theme/snow.js b/packages/client/src/thirtypart/kityminder/kity-core/theme/snow.js new file mode 100644 index 00000000..07a29140 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/theme/snow.js @@ -0,0 +1,64 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var theme = require('../core/theme'); + + ['snow', 'snow-compact'].forEach(function (name) { + var compact = name == 'snow-compact'; + + /* jscs:disable maximumLineLength */ + theme.register(name, { + 'background': + '#3A4144 url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowQzg5QTQ0NDhENzgxMUUzOENGREE4QTg0RDgzRTZDNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDowQzg5QTQ0NThENzgxMUUzOENGREE4QTg0RDgzRTZDNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkMwOEQ1NDRGOEQ3NzExRTM4Q0ZEQThBODREODNFNkM3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkMwOEQ1NDUwOEQ3NzExRTM4Q0ZEQThBODREODNFNkM3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+e9P33AAAACVJREFUeNpisXJ0YUACTAyoAMr/+eM7EGGRZ4FQ7BycEAZAgAEAHbEGtkoQm/wAAAAASUVORK5CYII=") repeat', + + 'root-color': '#430', + 'root-background': '#e9df98', + 'root-stroke': '#e9df98', + 'root-font-size': 24, + 'root-padding': compact ? [5, 10] : [15, 25], + 'root-margin': compact ? 15 : 30, + 'root-radius': 5, + 'root-space': 10, + 'root-shadow': 'rgba(0, 0, 0, .25)', + + 'main-color': '#333', + 'main-background': '#a4c5c0', + 'main-stroke': '#a4c5c0', + 'main-font-size': 16, + 'main-padding': compact ? [4, 10] : [6, 20], + 'main-margin': compact ? [5, 10] : [20, 40], + 'main-radius': 5, + 'main-space': 5, + 'main-shadow': 'rgba(0, 0, 0, .25)', + + 'sub-color': 'black', + 'sub-background': 'white', + 'sub-stroke': 'white', + 'sub-font-size': 12, + 'sub-padding': [5, 10], + 'sub-margin': compact ? [5, 10] : [10, 20], + 'sub-radius': 5, + 'sub-space': 5, + + 'connect-color': 'white', + 'connect-width': 2, + 'main-connect-width': 3, + 'connect-radius': 5, + + 'selected-background': 'rgb(254, 219, 0)', + 'selected-stroke': 'rgb(254, 219, 0)', + + 'marquee-background': 'rgba(255,255,255,.3)', + 'marquee-stroke': 'white', + + 'drop-hint-color': 'yellow', + 'drop-hint-width': 4, + + 'order-hint-area-color': 'rgba(0, 255, 0, .5)', + 'order-hint-path-color': '#0f0', + 'order-hint-path-width': 1, + + 'text-selection-color': 'rgb(27,171,255)', + 'line-height': 1.5, + }); + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/theme/tianpan.js b/packages/client/src/thirtypart/kityminder/kity-core/theme/tianpan.js new file mode 100644 index 00000000..c85f85e5 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/theme/tianpan.js @@ -0,0 +1,71 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var theme = require('../core/theme'); + + ['tianpan', 'tianpan-compact'].forEach(function (name) { + var compact = name == 'tianpan-compact'; + + theme.register(name, { + 'background': + '#3A4144 url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAIAAAACDbGyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowQzg5QTQ0NDhENzgxMUUzOENGREE4QTg0RDgzRTZDNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDowQzg5QTQ0NThENzgxMUUzOENGREE4QTg0RDgzRTZDNyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkMwOEQ1NDRGOEQ3NzExRTM4Q0ZEQThBODREODNFNkM3IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkMwOEQ1NDUwOEQ3NzExRTM4Q0ZEQThBODREODNFNkM3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+e9P33AAAACVJREFUeNpisXJ0YUACTAyoAMr/+eM7EGGRZ4FQ7BycEAZAgAEAHbEGtkoQm/wAAAAASUVORK5CYII=") repeat', + + 'root-color': '#430', + 'root-background': '#e9df98', + 'root-stroke': '#e9df98', + 'root-font-size': 25, + 'root-padding': compact ? 15 : 20, + 'root-margin': compact ? [15, 25] : 100, + 'root-radius': 30, + 'root-space': 10, + 'root-shadow': 'rgba(0, 0, 0, .25)', + 'root-shape': 'circle', + + 'main-color': '#333', + 'main-background': '#a4c5c0', + 'main-stroke': '#a4c5c0', + 'main-font-size': 15, + 'main-padding': compact ? 10 : 12, + 'main-margin': compact ? 10 : 12, + 'main-radius': 10, + 'main-space': 5, + 'main-shadow': 'rgba(0, 0, 0, .25)', + 'main-shape': 'circle', + + 'sub-color': '#333', + 'sub-background': '#99ca6a', + 'sub-stroke': '#a4c5c0', + 'sub-font-size': 13, + 'sub-padding': 5, + 'sub-margin': compact ? 6 : 10, + 'sub-tree-margin': 30, + 'sub-radius': 5, + 'sub-space': 5, + 'sub-shadow': 'rgba(0, 0, 0, .25)', + 'sub-shape': 'circle', + + 'connect-color': 'white', + 'connect-width': 2, + 'main-connect-width': 3, + 'connect-radius': 5, + + 'selected-background': 'rgb(254, 219, 0)', + 'selected-stroke': 'rgb(254, 219, 0)', + 'selected-color': 'black', + + 'marquee-background': 'rgba(255,255,255,.3)', + 'marquee-stroke': 'white', + + 'drop-hint-color': 'yellow', + 'sub-drop-hint-width': 2, + 'main-drop-hint-width': 4, + 'root-drop-hint-width': 4, + + 'order-hint-area-color': 'rgba(0, 255, 0, .5)', + 'order-hint-path-color': '#0f0', + 'order-hint-path-width': 1, + + 'text-selection-color': 'rgb(27,171,255)', + 'line-height': 1.4, + }); + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-core/theme/wire.js b/packages/client/src/thirtypart/kityminder/kity-core/theme/wire.js new file mode 100644 index 00000000..0443f774 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-core/theme/wire.js @@ -0,0 +1,35 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var theme = require('../core/theme'); + + theme.register('wire', { + 'background': 'black', + + 'color': '#999', + 'stroke': 'none', + 'padding': 10, + 'margin': 20, + 'font-size': 14, + + 'connect-color': '#999', + 'connect-width': 1, + + 'selected-background': '#999', + 'selected-color': 'black', + + 'marquee-background': 'rgba(255,255,255,.3)', + 'marquee-stroke': 'white', + + 'drop-hint-color': 'yellow', + 'sub-drop-hint-width': 2, + 'main-drop-hint-width': 4, + 'root-drop-hint-width': 4, + + 'order-hint-area-color': 'rgba(0, 255, 0, .5)', + 'order-hint-path-color': '#0f0', + 'order-hint-path-width': 1, + + 'text-selection-color': 'rgb(27,171,255)', + 'line-height': 1.5, + }); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/editor.js b/packages/client/src/thirtypart/kityminder/kity-editor/editor.js new file mode 100644 index 00000000..10c40ca9 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/editor.js @@ -0,0 +1,39 @@ +/* eslint-disable */ +define(function (require, exports, module) { + /** + * 运行时 + */ + var runtimes = []; + + function assemble(runtime) { + runtimes.push(runtime); + } + + function KMEditor(selector, blackList) { + this.selector = selector; + for (var i = 0; i < runtimes.length; i++) { + if (typeof runtimes[i] == 'function') { + runtimes[i].call(this, this); + } + } + } + + KMEditor.assemble = assemble; + + assemble(require('./runtime/container')); + assemble(require('./runtime/fsm')); + assemble(require('./runtime/minder')); + assemble(require('./runtime/receiver')); + assemble(require('./runtime/hotbox')); + assemble(require('./runtime/input')); + assemble(require('./runtime/clipboard-mimetype')); + assemble(require('./runtime/clipboard')); + assemble(require('./runtime/drag')); + assemble(require('./runtime/node')); + assemble(require('./runtime/history')); + assemble(require('./runtime/jumping')); + assemble(require('./runtime/priority')); + assemble(require('./runtime/progress')); + + return (module.exports = KMEditor); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/expose-editor.js b/packages/client/src/thirtypart/kityminder/kity-editor/expose-editor.js new file mode 100644 index 00000000..80f614f0 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/expose-editor.js @@ -0,0 +1,12 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 打包暴露 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define('expose-editor', function (require, exports, module) { + return (module.exports = window.kityminder.Editor = require('./editor')); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/hotbox.js b/packages/client/src/thirtypart/kityminder/kity-editor/hotbox.js new file mode 100644 index 00000000..ca142525 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/hotbox.js @@ -0,0 +1,6 @@ +/* eslint-disable */ +import Hotbox from '../hotbox/hotbox'; + +define(function (require, exports, module) { + return (module.exports = Hotbox); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/lang.js b/packages/client/src/thirtypart/kityminder/kity-editor/lang.js new file mode 100644 index 00000000..82c74952 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/lang.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +define(function (require, exports, module) {}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/minder.js b/packages/client/src/thirtypart/kityminder/kity-editor/minder.js new file mode 100644 index 00000000..c9d6bc69 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/minder.js @@ -0,0 +1,4 @@ +/* eslint-disable */ +define(function (require, exports, module) { + return (module.exports = window.kityminder.Minder); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/runtime/clipboard-mimetype.js b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/clipboard-mimetype.js new file mode 100644 index 00000000..a380d711 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/clipboard-mimetype.js @@ -0,0 +1,124 @@ +/* eslint-disable */ +/** + * @Desc: 新增一个用于处理系统ctrl+c ctrl+v等方式导入导出节点的MIMETYPE处理,如系统不支持clipboardEvent或者是FF则不初始化改class + * @Editor: Naixor + * @Date: 2015.9.21 + */ +define(function (require, exports, module) { + function MimeType() { + /** + * 私有变量 + */ + var SPLITOR = '\uFEFF'; + var MIMETYPE = { + 'application/km': '\uFFFF', + }; + var SIGN = { + '\uFEFF': 'SPLITOR', + '\uFFFF': 'application/km', + }; + + /** + * 用于将一段纯文本封装成符合其数据格式的文本 + * @method process private + * @param {MIMETYPE} mimetype 数据格式 + * @param {String} text 原始文本 + * @return {String} 符合该数据格式下的文本 + * @example + * var str = "123"; + * str = process('application/km', str); // 返回的内容再经过MimeType判断会读取出其数据格式为application/km + * process('text/plain', str); // 若接受到一个非纯文本信息,则会将其转换为新的数据格式 + */ + function process(mimetype, text) { + if (!this.isPureText(text)) { + var _mimetype = this.whichMimeType(text); + if (!_mimetype) { + throw new Error('unknow mimetype!'); + } + text = this.getPureText(text); + } + if (mimetype === false) { + return text; + } + return mimetype + SPLITOR + text; + } + + /** + * 注册数据类型的标识 + * @method registMimeTypeProtocol public + * @param {String} type 数据类型 + * @param {String} sign 标识 + */ + this.registMimeTypeProtocol = function (type, sign) { + if (sign && SIGN[sign]) { + throw new Error('sing has registed!'); + } + if (type && !!MIMETYPE[type]) { + throw new Error('mimetype has registed!'); + } + SIGN[sign] = type; + MIMETYPE[type] = sign; + }; + + /** + * 获取已注册数据类型的协议 + * @method getMimeTypeProtocol public + * @param {String} type 数据类型 + * @param {String} text|undefiend 文本内容或不传入 + * @return {String|Function} + * @example + * text若不传入则直接返回对应数据格式的处理(process)方法 + * 若传入文本则直接调用对应的process方法进行处理,此时返回处理后的内容 + * var m = new MimeType(); + * var kmprocess = m.getMimeTypeProtocol('application/km'); + * kmprocess("123") === m.getMimeTypeProtocol('application/km', "123"); + * + */ + this.getMimeTypeProtocol = function (type, text) { + var mimetype = MIMETYPE[type] || false; + + if (text === undefined) { + return process.bind(this, mimetype); + } + + return process(mimetype, text); + }; + + this.getSpitor = function () { + return SPLITOR; + }; + + this.getMimeType = function (sign) { + if (sign !== undefined) { + return SIGN[sign] || null; + } + return MIMETYPE; + }; + } + + MimeType.prototype.isPureText = function (text) { + return !~text.indexOf(this.getSpitor()); + }; + + MimeType.prototype.getPureText = function (text) { + if (this.isPureText(text)) { + return text; + } + return text.split(this.getSpitor())[1]; + }; + + MimeType.prototype.whichMimeType = function (text) { + if (this.isPureText(text)) { + return null; + } + return this.getMimeType(text.split(this.getSpitor())[0]); + }; + + function MimeTypeRuntime() { + if (this.minder.supportClipboardEvent && !kity.Browser.gecko) { + this.MimeType = new MimeType(); + } + } + + return (module.exports = MimeTypeRuntime); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/runtime/clipboard.js b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/clipboard.js new file mode 100644 index 00000000..3d1d89a7 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/clipboard.js @@ -0,0 +1,192 @@ +/* eslint-disable */ +/** + * @Desc: 处理editor的clipboard事件,只在支持ClipboardEvent并且不是FF的情况下工作 + * @Editor: Naixor + * @Date: 2015.9.21 + */ +define(function (require, exports, module) { + function ClipboardRuntime() { + var minder = this.minder; + var Data = window.kityminder.data; + + if (!minder.supportClipboardEvent || kity.Browser.gecko) { + return; + } + + var fsm = this.fsm; + var receiver = this.receiver; + var MimeType = this.MimeType; + + var kmencode = MimeType.getMimeTypeProtocol('application/km'), + decode = Data.getRegisterProtocol('json').decode; + var _selectedNodes = []; + + /* + * 增加对多节点赋值粘贴的处理 + */ + function encode(nodes) { + var _nodes = []; + for (var i = 0, l = nodes.length; i < l; i++) { + _nodes.push(minder.exportNode(nodes[i])); + } + return kmencode(Data.getRegisterProtocol('json').encode(_nodes)); + } + + var beforeCopy = function (e) { + if (document.activeElement == receiver.element) { + var clipBoardEvent = e; + var state = fsm.state(); + + switch (state) { + case 'input': { + break; + } + case 'normal': { + var nodes = [].concat(minder.getSelectedNodes()); + if (nodes.length) { + // 这里由于被粘贴复制的节点的id信息也都一样,故做此算法 + // 这里有个疑问,使用node.getParent()或者node.parent会离奇导致出现非选中节点被渲染成选中节点,因此使用isAncestorOf,而没有使用自行回溯的方式 + if (nodes.length > 1) { + var targetLevel; + nodes.sort(function (a, b) { + return a.getLevel() - b.getLevel(); + }); + targetLevel = nodes[0].getLevel(); + if (targetLevel !== nodes[nodes.length - 1].getLevel()) { + var plevel, + pnode, + idx = 0, + l = nodes.length, + pidx = l - 1; + + pnode = nodes[pidx]; + + while (pnode.getLevel() !== targetLevel) { + idx = 0; + while (idx < l && nodes[idx].getLevel() === targetLevel) { + if (nodes[idx].isAncestorOf(pnode)) { + nodes.splice(pidx, 1); + break; + } + idx++; + } + pidx--; + pnode = nodes[pidx]; + } + } + } + var str = encode(nodes); + clipBoardEvent.clipboardData.setData('text/plain', str); + } + e.preventDefault(); + break; + } + } + } + }; + + var beforeCut = function (e) { + if (document.activeElement == receiver.element) { + if (minder.getStatus() !== 'normal') { + e.preventDefault(); + return; + } + + var clipBoardEvent = e; + var state = fsm.state(); + + switch (state) { + case 'input': { + break; + } + case 'normal': { + var nodes = minder.getSelectedNodes(); + if (nodes.length) { + clipBoardEvent.clipboardData.setData('text/plain', encode(nodes)); + minder.execCommand('removenode'); + } + e.preventDefault(); + break; + } + } + } + }; + + var beforePaste = function (e) { + if (document.activeElement == receiver.element) { + if (minder.getStatus() !== 'normal') { + e.preventDefault(); + return; + } + + var clipBoardEvent = e; + var state = fsm.state(); + var textData = clipBoardEvent.clipboardData.getData('text/plain'); + + switch (state) { + case 'input': { + // input状态下如果格式为application/km则不进行paste操作 + if (!MimeType.isPureText(textData)) { + e.preventDefault(); + return; + } + break; + } + case 'normal': { + /* + * 针对normal状态下通过对选中节点粘贴导入子节点文本进行单独处理 + */ + var sNodes = minder.getSelectedNodes(); + + if (MimeType.whichMimeType(textData) === 'application/km') { + var nodes = decode(MimeType.getPureText(textData)); + var _node; + sNodes.forEach(function (node) { + // 由于粘贴逻辑中为了排除子节点重新排序导致逆序,因此复制的时候倒过来 + for (var i = nodes.length - 1; i >= 0; i--) { + _node = minder.createNode(null, node); + minder.importNode(_node, nodes[i]); + _selectedNodes.push(_node); + node.appendChild(_node); + } + }); + minder.select(_selectedNodes, true); + _selectedNodes = []; + + minder.refresh(); + } else if ( + clipBoardEvent.clipboardData && + clipBoardEvent.clipboardData.items[0].type.indexOf('image') > -1 + ) { + var imageFile = clipBoardEvent.clipboardData.items[0].getAsFile(); + var serverService = angular.element(document.body).injector().get('server'); + + return serverService.uploadImage(imageFile).then(function (json) { + var resp = json.data; + if (resp.errno === 0) { + minder.execCommand('image', resp.data.url); + } + }); + } else { + sNodes.forEach(function (node) { + minder.Text2Children(node, textData); + }); + } + e.preventDefault(); + break; + } + } + } + }; + /** + * 由editor的receiver统一处理全部事件,包括clipboard事件 + * @Editor: Naixor + * @Date: 2015.9.24 + */ + document.addEventListener('copy', beforeCopy); + document.addEventListener('cut', beforeCut); + document.addEventListener('paste', beforePaste); + } + + return (module.exports = ClipboardRuntime); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/runtime/container.js b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/container.js new file mode 100644 index 00000000..f4e2074e --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/container.js @@ -0,0 +1,35 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 初始化编辑器的容器 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + /** + * 最先执行的 Runtime,初始化编辑器容器 + */ + function ContainerRuntime() { + var container; + + if (typeof this.selector == 'string') { + container = document.querySelector(this.selector); + } else { + container = this.selector; + } + + if (!container) { + return; + } + + // 这个类名用于给编辑器添加样式 + container.classList.add('km-editor'); + + // 暴露容器给其他运行时使用 + this.container = container; + } + + return (module.exports = ContainerRuntime); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/runtime/drag.js b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/drag.js new file mode 100644 index 00000000..75c59b97 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/drag.js @@ -0,0 +1,149 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 用于拖拽节点时屏蔽键盘事件 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + var Hotbox = require('../hotbox'); + var Debug = require('../tool/debug'); + var debug = new Debug('drag'); + + function DragRuntime() { + var fsm = this.fsm; + var minder = this.minder; + var hotbox = this.hotbox; + var receiver = this.receiver; + var receiverElement = receiver.element; + + // setup everything to go + setupFsm(); + + // listen the fsm changes, make action. + function setupFsm() { + // when jumped to drag mode, enter + fsm.when('* -> drag', function () { + // now is drag mode + }); + + fsm.when('drag -> *', function (exit, enter, reason) { + if (reason == 'drag-finish') { + // now exit drag mode + } + }); + } + + var downX, downY; + var MOUSE_HAS_DOWN = 0; + var MOUSE_HAS_UP = 1; + var BOUND_CHECK = 20; + var flag = MOUSE_HAS_UP; + var maxX, maxY, osx, osy, containerY; + var freeHorizen = false, + freeVirtical = false; + var frame; + + function move(direction, speed) { + if (!direction) { + freeHorizen = freeVirtical = false; + frame && kity.releaseFrame(frame); + frame = null; + return; + } + if (!frame) { + frame = kity.requestFrame( + (function (direction, speed, minder) { + return function (frame) { + switch (direction) { + case 'left': + minder._viewDragger.move({ x: -speed, y: 0 }, 0); + break; + case 'top': + minder._viewDragger.move({ x: 0, y: -speed }, 0); + break; + case 'right': + minder._viewDragger.move({ x: speed, y: 0 }, 0); + break; + case 'bottom': + minder._viewDragger.move({ x: 0, y: speed }, 0); + break; + default: + return; + } + frame.next(); + }; + })(direction, speed, minder) + ); + } + } + + minder.on('mousedown', function (e) { + flag = MOUSE_HAS_DOWN; + var rect = minder.getPaper().container.getBoundingClientRect(); + downX = e.originEvent.clientX; + downY = e.originEvent.clientY; + containerY = rect.top; + maxX = rect.width; + maxY = rect.height; + }); + + minder.on('mousemove', function (e) { + if ( + fsm.state() === 'drag' && + flag == MOUSE_HAS_DOWN && + minder.getSelectedNode() && + (Math.abs(downX - e.originEvent.clientX) > BOUND_CHECK || Math.abs(downY - e.originEvent.clientY) > BOUND_CHECK) + ) { + osx = e.originEvent.clientX; + osy = e.originEvent.clientY - containerY; + + if (osx < BOUND_CHECK) { + move('right', BOUND_CHECK - osx); + } else if (osx > maxX - BOUND_CHECK) { + move('left', BOUND_CHECK + osx - maxX); + } else { + freeHorizen = true; + } + if (osy < BOUND_CHECK) { + move('bottom', osy); + } else if (osy > maxY - BOUND_CHECK) { + move('top', BOUND_CHECK + osy - maxY); + } else { + freeVirtical = true; + } + if (freeHorizen && freeVirtical) { + move(false); + } + } + if ( + fsm.state() !== 'drag' && + flag === MOUSE_HAS_DOWN && + minder.getSelectedNode() && + (Math.abs(downX - e.originEvent.clientX) > BOUND_CHECK || Math.abs(downY - e.originEvent.clientY) > BOUND_CHECK) + ) { + if (fsm.state() === 'hotbox') { + hotbox.active(Hotbox.STATE_IDLE); + } + + return fsm.jump('drag', 'user-drag'); + } + }); + + window.addEventListener( + 'mouseup', + function () { + flag = MOUSE_HAS_UP; + if (fsm.state() === 'drag') { + move(false); + return fsm.jump('normal', 'drag-finish'); + } + }, + false + ); + } + + return (module.exports = DragRuntime); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/runtime/fsm.js b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/fsm.js new file mode 100644 index 00000000..43372679 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/fsm.js @@ -0,0 +1,121 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 编辑器状态机 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + var Debug = require('../tool/debug'); + var debug = new Debug('fsm'); + + function handlerConditionMatch(condition, when, exit, enter) { + if (condition.when != when) return false; + if (condition.enter != '*' && condition.enter != enter) return false; + if (condition.exit != '*' && condition.exit != exit) return; + return true; + } + + function FSM(defaultState) { + var currentState = defaultState; + var BEFORE_ARROW = ' - '; + var AFTER_ARROW = ' -> '; + var handlers = []; + + /** + * 状态跳转 + * + * 会通知所有的状态跳转监视器 + * + * @param {string} newState 新状态名称 + * @param {any} reason 跳转的原因,可以作为参数传递给跳转监视器 + */ + this.jump = function (newState, reason) { + if (!reason) throw new Error('Please tell fsm the reason to jump'); + + var oldState = currentState; + var notify = [oldState, newState].concat([].slice.call(arguments, 1)); + var i, handler; + + // 跳转前 + for (i = 0; i < handlers.length; i++) { + handler = handlers[i]; + if (handlerConditionMatch(handler.condition, 'before', oldState, newState)) { + if (handler.apply(null, notify)) return; + } + } + + currentState = newState; + debug.log('[{0}] {1} -> {2}', reason, oldState, newState); + + // 跳转后 + for (i = 0; i < handlers.length; i++) { + handler = handlers[i]; + if (handlerConditionMatch(handler.condition, 'after', oldState, newState)) { + handler.apply(null, notify); + } + } + return currentState; + }; + + /** + * 返回当前状态 + * @return {string} + */ + this.state = function () { + return currentState; + }; + + /** + * 添加状态跳转监视器 + * + * @param {string} condition + * 监视的时机 + * "* => *" (默认) + * + * @param {Function} handler + * 监视函数,当状态跳转的时候,会接收三个参数 + * * from - 跳转前的状态 + * * to - 跳转后的状态 + * * reason - 跳转的原因 + */ + this.when = function (condition, handler) { + if (arguments.length == 1) { + handler = condition; + condition = '* -> *'; + } + + var when, resolved, exit, enter; + + resolved = condition.split(BEFORE_ARROW); + if (resolved.length == 2) { + when = 'before'; + } else { + resolved = condition.split(AFTER_ARROW); + if (resolved.length == 2) { + when = 'after'; + } + } + if (!when) throw new Error('Illegal fsm condition: ' + condition); + + exit = resolved[0]; + enter = resolved[1]; + + handler.condition = { + when: when, + exit: exit, + enter: enter, + }; + + handlers.push(handler); + }; + } + + function FSMRumtime() { + this.fsm = new FSM('normal'); + } + + return (module.exports = FSMRumtime); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/runtime/history.js b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/history.js new file mode 100644 index 00000000..c46abf7a --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/history.js @@ -0,0 +1,133 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 历史管理 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ + +define(function (require, exports, module) { + var jsonDiff = require('../tool/jsondiff'); + + function HistoryRuntime() { + var minder = this.minder; + var hotbox = this.hotbox; + + var MAX_HISTORY = 100; + + var lastSnap; + var patchLock; + var undoDiffs; + var redoDiffs; + + function reset() { + undoDiffs = []; + redoDiffs = []; + lastSnap = minder.exportJson(); + } + + function makeUndoDiff() { + var headSnap = minder.exportJson(); + var diff = jsonDiff(headSnap, lastSnap); + if (diff.length) { + undoDiffs.push(diff); + while (undoDiffs.length > MAX_HISTORY) { + undoDiffs.shift(); + } + lastSnap = headSnap; + return true; + } + } + + function makeRedoDiff() { + var revertSnap = minder.exportJson(); + redoDiffs.push(jsonDiff(revertSnap, lastSnap)); + lastSnap = revertSnap; + } + + function undo() { + patchLock = true; + var undoDiff = undoDiffs.pop(); + if (undoDiff) { + minder.applyPatches(undoDiff); + makeRedoDiff(); + } + patchLock = false; + } + + function redo() { + patchLock = true; + var redoDiff = redoDiffs.pop(); + if (redoDiff) { + minder.applyPatches(redoDiff); + makeUndoDiff(); + } + patchLock = false; + } + + function changed() { + if (patchLock) return; + if (makeUndoDiff()) redoDiffs = []; + } + + function hasUndo() { + return !!undoDiffs.length; + } + + function hasRedo() { + return !!redoDiffs.length; + } + + function updateSelection(e) { + if (!patchLock) return; + var patch = e.patch; + switch (patch.express) { + case 'node.add': + minder.select(patch.node.getChild(patch.index), true); + break; + case 'node.remove': + case 'data.replace': + case 'data.remove': + case 'data.add': + minder.select(patch.node, true); + break; + } + } + + this.history = { + reset: reset, + undo: undo, + redo: redo, + hasUndo: hasUndo, + hasRedo: hasRedo, + }; + reset(); + minder.on('contentchange', changed); + minder.on('import', reset); + minder.on('patch', updateSelection); + + var main = hotbox.state('main'); + main.button({ + position: 'top', + label: '撤销', + key: 'Ctrl + Z', + enable: hasUndo, + action: undo, + next: 'idle', + }); + main.button({ + position: 'top', + label: '重做', + key: 'Ctrl + Y', + enable: hasRedo, + action: redo, + next: 'idle', + }); + } + + window.diff = jsonDiff; + + return (module.exports = HistoryRuntime); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/runtime/hotbox.js b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/hotbox.js new file mode 100644 index 00000000..020b86d5 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/hotbox.js @@ -0,0 +1,51 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 热盒 Runtime + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + var Hotbox = require('../hotbox'); + + function HotboxRuntime() { + var fsm = this.fsm; + var minder = this.minder; + var receiver = this.receiver; + var container = this.container; + var hotbox = new Hotbox(container); + hotbox.setParentFSM(fsm); + fsm.when('normal -> hotbox', function (exit, enter, reason) { + var node = minder.getSelectedNode(); + var position; + if (node) { + var box = node.getRenderBox(); + position = { + x: box.cx, + y: box.cy, + }; + } + hotbox.active('main', position); + }); + fsm.when('normal -> normal', function (exit, enter, reason, e) { + if (reason == 'shortcut-handle') { + var handleResult = hotbox.dispatch(e); + if (handleResult) { + e.preventDefault(); + } else { + minder.dispatchKeyEvent(e); + } + } + }); + fsm.when('modal -> normal', function (exit, enter, reason, e) { + if (reason == 'import-text-finish') { + receiver.element.focus(); + } + }); + this.hotbox = hotbox; + } + + return (module.exports = HotboxRuntime); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/runtime/input.js b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/input.js new file mode 100644 index 00000000..1bb3a9d2 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/input.js @@ -0,0 +1,361 @@ +/* eslint-disable */ +/** + * @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'); + + 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 () { + 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 = ' ', // 针对FF,SG,BD,LB,IE等浏览器下SPACE的charCode存在为32和160的情况做处理 + SPACE_CHAR_REGEXP = new RegExp('( |' + 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 会给字体加上/标签来实现黑体和斜体 + 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 { + } + } + 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 += ''; + } + } + } + } + } + text = text.replace(/^\n*|\n*$/g, ''); + text = text.replace(new RegExp('(\n|\r|\n\r)( |' + 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); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/runtime/jumping.js b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/jumping.js new file mode 100644 index 00000000..83e8426d --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/jumping.js @@ -0,0 +1,194 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 根据按键控制状态机的跳转 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + var Hotbox = require('../hotbox'); + + // Nice: http://unixpapa.com/js/key.html + function isIntendToInput(e) { + if (e.ctrlKey || e.metaKey || e.altKey) return false; + + // a-zA-Z + if (e.keyCode >= 65 && e.keyCode <= 90) return true; + + // 0-9 以及其上面的符号 + if (e.keyCode >= 48 && e.keyCode <= 57) return true; + + // 小键盘区域 (除回车外) + if (e.keyCode != 108 && e.keyCode >= 96 && e.keyCode <= 111) return true; + + // 小键盘区域 (除回车外) + // @yinheli from pull request + if (e.keyCode != 108 && e.keyCode >= 96 && e.keyCode <= 111) return true; + + // 输入法 + if (e.keyCode == 229 || e.keyCode === 0) return true; + + return false; + } + /** + * @Desc: 下方使用receiver.enable()和receiver.disable()通过 + * 修改div contenteditable属性的hack来解决开启热核后依然无法屏蔽浏览器输入的bug; + * 特别: win下FF对于此种情况必须要先blur在focus才能解决,但是由于这样做会导致用户 + * 输入法状态丢失,因此对FF暂不做处理 + * @Editor: Naixor + * @Date: 2015.09.14 + */ + function JumpingRuntime() { + var fsm = this.fsm; + var minder = this.minder; + var receiver = this.receiver; + var container = this.container; + var receiverElement = receiver.element; + var hotbox = this.hotbox; + var compositionLock = false; + + // normal -> * + receiver.listen('normal', function (e) { + // 为了防止处理进入edit模式而丢失处理的首字母,此时receiver必须为enable + receiver.enable(); + // normal -> hotbox + if (e.is('Space')) { + e.preventDefault(); + // safari下Space触发hotbox,然而这时Space已在receiver上留下作案痕迹,因此抹掉 + if (kity.Browser.safari) { + receiverElement.innerHTML = ''; + } + return fsm.jump('hotbox', 'space-trigger'); + } + + /** + * check + * @editor Naixor + * @Date 2015-12-2 + */ + switch (e.type) { + case 'keydown': { + if (minder.getSelectedNode()) { + if (isIntendToInput(e)) { + return fsm.jump('input', 'user-input'); + } + } else { + receiverElement.innerHTML = ''; + } + // normal -> normal shortcut + fsm.jump('normal', 'shortcut-handle', e); + break; + } + case 'keyup': { + break; + } + default: { + } + } + }); + + // hotbox -> normal + receiver.listen('hotbox', function (e) { + receiver.disable(); + e.preventDefault(); + var handleResult = hotbox.dispatch(e); + if (hotbox.state() == Hotbox.STATE_IDLE && fsm.state() == 'hotbox') { + return fsm.jump('normal', 'hotbox-idle'); + } + }); + + // input => normal + receiver.listen('input', function (e) { + receiver.enable(); + if (e.type == 'keydown') { + if (e.is('Enter')) { + e.preventDefault(); + return fsm.jump('normal', 'input-commit'); + } + if (e.is('Esc')) { + e.preventDefault(); + return fsm.jump('normal', 'input-cancel'); + } + if (e.is('Tab') || e.is('Shift + Tab')) { + e.preventDefault(); + } + } else if (e.type == 'keyup' && e.is('Esc')) { + e.preventDefault(); + if (!compositionLock) { + return fsm.jump('normal', 'input-cancel'); + } + } else if (e.type == 'compositionstart') { + compositionLock = true; + } else if (e.type == 'compositionend') { + setTimeout(function () { + compositionLock = false; + }); + } + }); + + ////////////////////////////////////////////// + /// 右键呼出热盒 + /// 判断的标准是:按下的位置和结束的位置一致 + ////////////////////////////////////////////// + var downX, downY; + var MOUSE_RB = 2; // 右键 + + container.addEventListener( + 'mousedown', + function (e) { + if (e.button == MOUSE_RB) { + e.preventDefault(); + } + if (fsm.state() == 'hotbox') { + hotbox.active(Hotbox.STATE_IDLE); + fsm.jump('normal', 'blur'); + } else if (fsm.state() == 'normal' && e.button == MOUSE_RB) { + downX = e.clientX; + downY = e.clientY; + } + }, + false + ); + + container.addEventListener( + 'mousewheel', + function (e) { + if (fsm.state() == 'hotbox') { + hotbox.active(Hotbox.STATE_IDLE); + fsm.jump('normal', 'mousemove-blur'); + } + }, + false + ); + + container.addEventListener('contextmenu', function (e) { + e.preventDefault(); + }); + + container.addEventListener( + 'mouseup', + function (e) { + if (fsm.state() != 'normal') { + return; + } + if (e.button != MOUSE_RB || e.clientX != downX || e.clientY != downY) { + return; + } + if (!minder.getSelectedNode()) { + return; + } + fsm.jump('hotbox', 'content-menu'); + }, + false + ); + + // 阻止热盒事件冒泡,在热盒正确执行前导致热盒关闭 + hotbox.$element.addEventListener('mousedown', function (e) { + e.stopPropagation(); + }); + } + + return (module.exports = JumpingRuntime); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/runtime/minder.js b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/minder.js new file mode 100644 index 00000000..cd80afac --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/minder.js @@ -0,0 +1,31 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 脑图示例运行时 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + var Minder = require('../minder'); + + function MinderRuntime() { + // 不使用 kityminder 的按键处理,由 ReceiverRuntime 统一处理 + var minder = new Minder({ + enableKeyReceiver: false, + enableAnimation: true, + }); + + // 渲染,初始化 + minder.renderTo(this.selector); + minder.setTheme(null); + minder.select(minder.getRoot(), true); + minder.execCommand('name', '中心主题'); + + // 导出给其它 Runtime 使用 + this.minder = minder; + } + + return (module.exports = MinderRuntime); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/runtime/node.js b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/node.js new file mode 100644 index 00000000..9d926f23 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/node.js @@ -0,0 +1,112 @@ +/* eslint-disable */ +define(function (require, exports, module) { + function NodeRuntime() { + var runtime = this; + var minder = this.minder; + var hotbox = this.hotbox; + var fsm = this.fsm; + + var main = hotbox.state('main'); + + var buttons = [ + '前移:Alt+Up:ArrangeUp', + '下级:Tab|Insert:AppendChildNode', + '同级:Enter:AppendSiblingNode', + '后移:Alt+Down:ArrangeDown', + '删除:Delete|Backspace:RemoveNode', + '上级:Shift+Tab|Shift+Insert:AppendParentNode', + //'全选:Ctrl+A:SelectAll' + ]; + + var AppendLock = 0; + + buttons.forEach(function (button) { + var parts = button.split(':'); + var label = parts.shift(); + var key = parts.shift(); + var command = parts.shift(); + main.button({ + position: 'ring', + label: label, + key: key, + action: function () { + if (command.indexOf('Append') === 0) { + AppendLock++; + minder.execCommand(command, '分支主题'); + + // provide in input runtime + function afterAppend() { + if (!--AppendLock) { + runtime.editText(); + } + minder.off('layoutallfinish', afterAppend); + } + minder.on('layoutallfinish', afterAppend); + } else { + minder.execCommand(command); + fsm.jump('normal', 'command-executed'); + } + }, + enable: function () { + return minder.queryCommandState(command) != -1; + }, + }); + }); + + main.button({ + position: 'bottom', + label: '导入节点', + key: 'Alt + V', + enable: function () { + var selectedNodes = minder.getSelectedNodes(); + return selectedNodes.length == 1; + }, + action: importNodeData, + next: 'idle', + }); + + main.button({ + position: 'bottom', + label: '导出节点', + key: 'Alt + C', + enable: function () { + var selectedNodes = minder.getSelectedNodes(); + return selectedNodes.length == 1; + }, + action: exportNodeData, + next: 'idle', + }); + + function importNodeData() { + minder.fire('importNodeData'); + } + + function exportNodeData() { + minder.fire('exportNodeData'); + } + + //main.button({ + // position: 'ring', + // key: '/', + // action: function(){ + // if (!minder.queryCommandState('expand')) { + // minder.execCommand('expand'); + // } else if (!minder.queryCommandState('collapse')) { + // minder.execCommand('collapse'); + // } + // }, + // enable: function() { + // return minder.queryCommandState('expand') != -1 || minder.queryCommandState('collapse') != -1; + // }, + // beforeShow: function() { + // if (!minder.queryCommandState('expand')) { + // this.$button.children[0].innerHTML = '展开'; + // } else { + // this.$button.children[0].innerHTML = '收起'; + // } + // } + //}) + } + + return (module.exports = NodeRuntime); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/runtime/priority.js b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/priority.js new file mode 100644 index 00000000..03d88f2b --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/priority.js @@ -0,0 +1,49 @@ +/* eslint-disable */ +define(function (require, exports, module) { + function PriorityRuntime() { + var minder = this.minder; + var hotbox = this.hotbox; + + var main = hotbox.state('main'); + + main.button({ + position: 'top', + label: '优先级', + key: 'P', + next: 'priority', + enable: function () { + return minder.queryCommandState('priority') != -1; + }, + }); + + var priority = hotbox.state('priority'); + '123456789'.replace(/./g, function (p) { + priority.button({ + position: 'ring', + label: 'P' + p, + key: p, + action: function () { + minder.execCommand('Priority', p); + }, + }); + }); + + priority.button({ + position: 'center', + label: '移除', + key: 'Del', + action: function () { + minder.execCommand('Priority', 0); + }, + }); + + priority.button({ + position: 'top', + label: '返回', + key: 'esc', + next: 'back', + }); + } + + return (module.exports = PriorityRuntime); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/runtime/progress.js b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/progress.js new file mode 100644 index 00000000..dca83230 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/progress.js @@ -0,0 +1,49 @@ +/* eslint-disable */ +define(function (require, exports, module) { + function ProgressRuntime() { + var minder = this.minder; + var hotbox = this.hotbox; + + var main = hotbox.state('main'); + + main.button({ + position: 'top', + label: '进度', + key: 'G', + next: 'progress', + enable: function () { + return minder.queryCommandState('progress') != -1; + }, + }); + + var progress = hotbox.state('progress'); + '012345678'.replace(/./g, function (p) { + progress.button({ + position: 'ring', + label: 'G' + p, + key: p, + action: function () { + minder.execCommand('Progress', parseInt(p) + 1); + }, + }); + }); + + progress.button({ + position: 'center', + label: '移除', + key: 'Del', + action: function () { + minder.execCommand('Progress', 0); + }, + }); + + progress.button({ + position: 'top', + label: '返回', + key: 'esc', + next: 'back', + }); + } + + return (module.exports = ProgressRuntime); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/runtime/receiver.js b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/receiver.js new file mode 100644 index 00000000..63796ecf --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/runtime/receiver.js @@ -0,0 +1,144 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 键盘事件接收/分发器 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ + +define(function (require, exports, module) { + var key = require('../tool/key'); + + function ReceiverRuntime() { + var fsm = this.fsm; + var minder = this.minder; + var me = this; + + // 接收事件的 div + var element = document.createElement('div'); + element.contentEditable = true; + /** + * @Desc: 增加tabindex属性使得element的contenteditable不管是trur还是false都能有focus和blur事件 + * @Editor: Naixor + * @Date: 2015.09.14 + */ + element.setAttribute('tabindex', -1); + element.classList.add('receiver'); + element.onkeydown = element.onkeypress = element.onkeyup = dispatchKeyEvent; + element.addEventListener('compositionstart', dispatchKeyEvent); + // element.addEventListener('compositionend', dispatchKeyEvent); + this.container.appendChild(element); + + // receiver 对象 + var receiver = { + element: element, + selectAll: function () { + // 保证有被选中的 + if (!element.innerHTML) element.innerHTML = ' '; + var range = document.createRange(); + var selection = window.getSelection(); + range.selectNodeContents(element); + selection.removeAllRanges(); + selection.addRange(range); + element.focus(); + }, + /** + * @Desc: 增加enable和disable方法用于解决热核态的输入法屏蔽问题 + * @Editor: Naixor + * @Date: 2015.09.14 + */ + enable: function () { + element.setAttribute('contenteditable', true); + }, + disable: function () { + element.setAttribute('contenteditable', false); + }, + /** + * @Desc: hack FF下div contenteditable的光标丢失BUG + * @Editor: Naixor + * @Date: 2015.10.15 + */ + fixFFCaretDisappeared: function () { + element.removeAttribute('contenteditable'); + element.setAttribute('contenteditable', 'true'); + element.blur(); + element.focus(); + }, + /** + * 以此事件代替通过mouse事件来判断receiver丢失焦点的事件 + * @editor Naixor + * @Date 2015-12-2 + */ + onblur: function (handler) { + element.onblur = handler; + }, + }; + receiver.selectAll(); + + minder.on('beforemousedown', receiver.selectAll); + minder.on('receiverfocus', receiver.selectAll); + minder.on('readonly', function () { + // 屏蔽minder的事件接受,删除receiver和hotbox + minder.disable(); + editor.receiver.element.parentElement.removeChild(editor.receiver.element); + editor.hotbox.$container.removeChild(editor.hotbox.$element); + }); + + // 侦听器,接收到的事件会派发给所有侦听器 + var listeners = []; + + // 侦听指定状态下的事件,如果不传 state,侦听所有状态 + receiver.listen = function (state, listener) { + if (arguments.length == 1) { + listener = state; + state = '*'; + } + listener.notifyState = state; + listeners.push(listener); + }; + + function dispatchKeyEvent(e) { + e.is = function (keyExpression) { + var subs = keyExpression.split('|'); + for (var i = 0; i < subs.length; i++) { + if (key.is(this, subs[i])) return true; + } + return false; + }; + var listener, jumpState; + for (var i = 0; i < listeners.length; i++) { + listener = listeners[i]; + // 忽略不在侦听状态的侦听器 + if (listener.notifyState != '*' && listener.notifyState != fsm.state()) { + continue; + } + + /** + * + * 对于所有的侦听器,只允许一种处理方式:跳转状态。 + * 如果侦听器确定要跳转,则返回要跳转的状态。 + * 每个事件只允许一个侦听器进行状态跳转 + * 跳转动作由侦听器自行完成(因为可能需要在跳转时传递 reason),返回跳转结果即可。 + * 比如: + * + * ```js + * receiver.listen('normal', function(e) { + * if (isSomeReasonForJumpState(e)) { + * return fsm.jump('newstate', e); + * } + * }); + * ``` + */ + if (listener.call(null, e)) { + return; + } + } + } + + this.receiver = receiver; + } + + return (module.exports = ReceiverRuntime); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/tool/debug.js b/packages/client/src/thirtypart/kityminder/kity-editor/tool/debug.js new file mode 100644 index 00000000..dacdd531 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/tool/debug.js @@ -0,0 +1,52 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * 支持各种调试后门 + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ +define(function (require, exports, module) { + var format = require('./format'); + + function noop() {} + + function stringHash(str) { + var hash = 0; + for (var i = 0; i < str.length; i++) { + hash += str.charCodeAt(i); + } + return hash; + } + + /* global console */ + function Debug(flag) { + if (typeof window === 'undefined') return; + + var debugMode = (this.flaged = window.location.search.indexOf(flag) != -1); + + if (debugMode) { + var h = stringHash(flag) % 360; + + var flagStyle = format( + 'background: hsl({0}, 50%, 80%); ' + + 'color: hsl({0}, 100%, 30%); ' + + 'padding: 2px 3px; ' + + 'margin: 1px 3px 0 0;' + + 'border-radius: 2px;', + h + ); + + var textStyle = 'background: none; color: black;'; + this.log = function () { + var output = format.apply(null, arguments); + console.log(format('%c{0}%c{1}', flag, output), flagStyle, textStyle); + }; + } else { + this.log = noop; + } + } + + return (module.exports = Debug); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/tool/format.js b/packages/client/src/thirtypart/kityminder/kity-editor/tool/format.js new file mode 100644 index 00000000..b2725bf9 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/tool/format.js @@ -0,0 +1,12 @@ +/* eslint-disable */ +define(function (require, exports, module) { + function format(template, args) { + if (typeof args != 'object') { + args = [].slice.call(arguments, 1); + } + return String(template).replace(/\{(\w+)\}/gi, function (match, $key) { + return args[$key] || $key; + }); + } + return (module.exports = format); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/tool/innertext.js b/packages/client/src/thirtypart/kityminder/kity-editor/tool/innertext.js new file mode 100644 index 00000000..80c1822b --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/tool/innertext.js @@ -0,0 +1,55 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * innerText polyfill + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ + +define(function (require, exports, module) { + if (!('innerText' in document.createElement('a')) && 'getSelection' in window) { + HTMLElement.prototype.__defineGetter__('innerText', function () { + var selection = window.getSelection(), + ranges = [], + str, + i; + + // Save existing selections. + for (i = 0; i < selection.rangeCount; i++) { + ranges[i] = selection.getRangeAt(i); + } + + // Deselect everything. + selection.removeAllRanges(); + + // Select `el` and all child nodes. + // 'this' is the element .innerText got called on + selection.selectAllChildren(this); + + // Get the string representation of the selected nodes. + str = selection.toString(); + + // Deselect everything. Again. + selection.removeAllRanges(); + + // Restore all formerly existing selections. + for (i = 0; i < ranges.length; i++) { + selection.addRange(ranges[i]); + } + + // Oh look, this is what we wanted. + // String representation of the element, close to as rendered. + return str; + }); + HTMLElement.prototype.__defineSetter__('innerText', function (text) { + /** + * @Desc: 解决FireFox节点内容删除后text为null,出现报错的问题 + * @Editor: Naixor + * @Date: 2015.9.16 + */ + this.innerHTML = (text || '').replace(//g, '>').replace(/\n/g, '
'); + }); + } +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/tool/jsondiff.js b/packages/client/src/thirtypart/kityminder/kity-editor/tool/jsondiff.js new file mode 100644 index 00000000..dfdf1ee1 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/tool/jsondiff.js @@ -0,0 +1,89 @@ +/* eslint-disable */ +/** + * @fileOverview + * + * + * + * @author: techird + * @copyright: Baidu FEX, 2014 + */ + +define(function (require, exports, module) { + /*! + * https://github.com/Starcounter-Jack/Fast-JSON-Patch + * json-patch-duplex.js 0.5.0 + * (c) 2013 Joachim Wester + * MIT license + */ + + var _objectKeys = (function () { + if (Object.keys) return Object.keys; + + return function (o) { + var keys = []; + for (var i in o) { + if (o.hasOwnProperty(i)) { + keys.push(i); + } + } + return keys; + }; + })(); + function escapePathComponent(str) { + if (str.indexOf('/') === -1 && str.indexOf('~') === -1) return str; + return str.replace(/~/g, '~0').replace(/\//g, '~1'); + } + function deepClone(obj) { + if (typeof obj === 'object') { + return JSON.parse(JSON.stringify(obj)); + } else { + return obj; + } + } + + // Dirty check if obj is different from mirror, generate patches and update mirror + function _generate(mirror, obj, patches, path) { + var newKeys = _objectKeys(obj); + var oldKeys = _objectKeys(mirror); + var changed = false; + var deleted = false; + + for (var t = oldKeys.length - 1; t >= 0; t--) { + var key = oldKeys[t]; + var oldVal = mirror[key]; + if (obj.hasOwnProperty(key)) { + var newVal = obj[key]; + if (typeof oldVal == 'object' && oldVal != null && typeof newVal == 'object' && newVal != null) { + _generate(oldVal, newVal, patches, path + '/' + escapePathComponent(key)); + } else { + if (oldVal != newVal) { + changed = true; + patches.push({ op: 'replace', path: path + '/' + escapePathComponent(key), value: deepClone(newVal) }); + } + } + } else { + patches.push({ op: 'remove', path: path + '/' + escapePathComponent(key) }); + deleted = true; // property has been deleted + } + } + + if (!deleted && newKeys.length == oldKeys.length) { + return; + } + + for (var t = 0; t < newKeys.length; t++) { + var key = newKeys[t]; + if (!mirror.hasOwnProperty(key)) { + patches.push({ op: 'add', path: path + '/' + escapePathComponent(key), value: deepClone(obj[key]) }); + } + } + } + + function compare(tree1, tree2) { + var patches = []; + _generate(tree1, tree2, patches, ''); + return patches; + } + + return (module.exports = compare); +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/tool/key.js b/packages/client/src/thirtypart/kityminder/kity-editor/tool/key.js new file mode 100644 index 00000000..6243589a --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/tool/key.js @@ -0,0 +1,72 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var keymap = require('./keymap'); + + var CTRL_MASK = 0x1000; + var ALT_MASK = 0x2000; + var SHIFT_MASK = 0x4000; + + function hash(unknown) { + if (typeof unknown == 'string') { + return hashKeyExpression(unknown); + } + return hashKeyEvent(unknown); + } + function is(a, b) { + return a && b && hash(a) == hash(b); + } + exports.hash = hash; + exports.is = is; + + function hashKeyEvent(keyEvent) { + var hashCode = 0; + if (keyEvent.ctrlKey || keyEvent.metaKey) { + hashCode |= CTRL_MASK; + } + if (keyEvent.altKey) { + hashCode |= ALT_MASK; + } + if (keyEvent.shiftKey) { + hashCode |= SHIFT_MASK; + } + // Shift, Control, Alt KeyCode ignored. + if ([16, 17, 18, 91].indexOf(keyEvent.keyCode) === -1) { + /** + * 解决浏览器输入法状态下对keyDown的keyCode判断不准确的问题,使用keyIdentifier, + * 可以解决chrome和safari下的各种问题,其他浏览器依旧有问题,然而那并不影响我们对特 + * 需判断的按键进行判断(比如Space在safari输入法态下就是229,其他的就不是) + * @editor Naixor + * @Date 2015-12-2 + */ + if (keyEvent.keyCode === 229 && keyEvent.keyIdentifier) { + return (hashCode |= parseInt(keyEvent.keyIdentifier.substr(2), 16)); + } + hashCode |= keyEvent.keyCode; + } + return hashCode; + } + + function hashKeyExpression(keyExpression) { + var hashCode = 0; + keyExpression + .toLowerCase() + .split(/\s*\+\s*/) + .forEach(function (name) { + switch (name) { + case 'ctrl': + case 'cmd': + hashCode |= CTRL_MASK; + break; + case 'alt': + hashCode |= ALT_MASK; + break; + case 'shift': + hashCode |= SHIFT_MASK; + break; + default: + hashCode |= keymap[name]; + } + }); + return hashCode; + } +}); diff --git a/packages/client/src/thirtypart/kityminder/kity-editor/tool/keymap.js b/packages/client/src/thirtypart/kityminder/kity-editor/tool/keymap.js new file mode 100644 index 00000000..27ffea65 --- /dev/null +++ b/packages/client/src/thirtypart/kityminder/kity-editor/tool/keymap.js @@ -0,0 +1,82 @@ +/* eslint-disable */ +define(function (require, exports, module) { + var keymap = { + 'Shift': 16, + 'Control': 17, + 'Alt': 18, + 'CapsLock': 20, + + 'BackSpace': 8, + 'Tab': 9, + 'Enter': 13, + 'Esc': 27, + 'Space': 32, + + 'PageUp': 33, + 'PageDown': 34, + 'End': 35, + 'Home': 36, + + 'Insert': 45, + + 'Left': 37, + 'Up': 38, + 'Right': 39, + 'Down': 40, + + 'Direction': { + 37: 1, + 38: 1, + 39: 1, + 40: 1, + }, + + 'Del': 46, + + 'NumLock': 144, + + 'Cmd': 91, + 'CmdFF': 224, + 'F1': 112, + 'F2': 113, + 'F3': 114, + 'F4': 115, + 'F5': 116, + 'F6': 117, + 'F7': 118, + 'F8': 119, + 'F9': 120, + 'F10': 121, + 'F11': 122, + 'F12': 123, + + '`': 192, + '=': 187, + '-': 189, + + '/': 191, + '.': 190, + }; + + // 小写适配 + for (var key in keymap) { + if (keymap.hasOwnProperty(key)) { + keymap[key.toLowerCase()] = keymap[key]; + } + } + var aKeyCode = 65; + var aCharCode = 'a'.charCodeAt(0); + + // letters + 'abcdefghijklmnopqrstuvwxyz'.split('').forEach(function (letter) { + keymap[letter] = aKeyCode + (letter.charCodeAt(0) - aCharCode); + }); + + // numbers + var n = 9; + do { + keymap[n.toString()] = n + 48; + } while (--n); + + module.exports = keymap; +}); diff --git a/packages/client/src/tiptap/core/extensions/mind.ts b/packages/client/src/tiptap/core/extensions/mind.ts index 639241eb..96530567 100644 --- a/packages/client/src/tiptap/core/extensions/mind.ts +++ b/packages/client/src/tiptap/core/extensions/mind.ts @@ -1,12 +1,13 @@ import { mergeAttributes, Node, nodeInputRule } from '@tiptap/core'; import { ReactNodeViewRenderer } from '@tiptap/react'; -import { Plugin, PluginKey } from 'prosemirror-state'; import { MindWrapper } from 'tiptap/core/wrappers/mind'; import { getDatasetAttribute } from 'tiptap/prose-utils'; const DEFAULT_MIND_DATA = { - nodeData: { topic: '中心节点', root: true, children: [] }, - linkData: {}, + root: { data: { text: '中心节点' }, children: [] }, + template: 'default', + theme: 'fresh-purple', + version: '1.4.43', }; export interface IMindAttrs { @@ -36,7 +37,7 @@ export const Mind = Node.create({ addAttributes() { return { width: { - default: null, + default: '100%', parseHTML: getDatasetAttribute('width'), }, height: { @@ -47,18 +48,6 @@ export const Mind = Node.create({ default: DEFAULT_MIND_DATA, parseHTML: getDatasetAttribute('data', true), }, - template: { - default: 'default', - parseHTML: getDatasetAttribute('template'), - }, - theme: { - default: 'classic', - parseHTML: getDatasetAttribute('theme'), - }, - zoom: { - default: 100, - parseHTML: getDatasetAttribute('zoom'), - }, }; }, @@ -124,21 +113,4 @@ export const Mind = Node.create({ }), ]; }, - - addProseMirrorPlugins() { - const { editor } = this; - - return [ - new Plugin({ - key: new PluginKey('mind'), - props: { - handleKeyDown(view, event) { - if (editor.isActive('mind')) { - return true; - } - }, - }, - }), - ]; - }, }); diff --git a/packages/client/src/tiptap/core/styles/index.scss b/packages/client/src/tiptap/core/styles/index.scss index 935fcb57..72c9d469 100644 --- a/packages/client/src/tiptap/core/styles/index.scss +++ b/packages/client/src/tiptap/core/styles/index.scss @@ -16,4 +16,4 @@ @import './selection.scss'; @import './table.scss'; @import './title.scss'; -@import './mind/index.scss'; +@import './kityminder.scss'; diff --git a/packages/client/src/tiptap/core/styles/kityminder.scss b/packages/client/src/tiptap/core/styles/kityminder.scss new file mode 100644 index 00000000..9589ab64 --- /dev/null +++ b/packages/client/src/tiptap/core/styles/kityminder.scss @@ -0,0 +1,2140 @@ +/* stylelint-disable */ +.km-view { + position: relative; + font-family: STHeitiSC-Light, STHeiti, Hei, 'Heiti SC', 'Microsoft Yahei', Arial, sans-serif; + user-select: none; + user-select: none; +} + +.km-view .km-receiver { + position: absolute; + top: -99999px; + left: -99999px; + width: 20px; + height: 20px; + margin: 0; + outline: none; +} + +.km-view image { + cursor: zoom-in; +} + +.km-image-viewer { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 99999; + background: rgb(0 0 0 / 75%); +} + +.km-image-viewer .km-image-viewer-container { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + overflow: auto; + text-align: center; + white-space: nowrap; +} + +.km-image-viewer .km-image-viewer-container::before { + display: inline-block; + width: 0; + height: 100%; + font-size: 0; + vertical-align: middle; + content: ''; +} + +.km-image-viewer .km-image-viewer-container img { + vertical-align: middle; + cursor: zoom-out; +} + +.km-image-viewer .km-image-viewer-container img.limited { + max-width: 100%; + max-height: 100%; + cursor: zoom-in; +} + +.km-image-viewer .km-image-viewer-toolbar { + z-index: 1; + text-align: right; + background: rgb(0 0 0 / 75%); + transition: all 0.25s; +} + +.km-image-viewer .km-image-viewer-toolbar.hidden { + opacity: 0; + transform: translate(0, -100%); +} + +.km-image-viewer .km-image-viewer-btn { + width: 44px; + height: 44px; + cursor: pointer; + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjY0IiBoZWlnaHQ9Ijg4IiB2aWV3Qm94PSIwIDAgMjY0IDg4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjx0aXRsZT5kZWZhdWx0LXNraW4gMjwvdGl0bGU+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48Zz48cGF0aCBkPSJNNjcuMDAyIDU5LjV2My43NjhjLTYuMzA3Ljg0LTkuMTg0IDUuNzUtMTAuMDAyIDkuNzMyIDIuMjItMi44MyA1LjU2NC01LjA5OCAxMC4wMDItNS4wOThWNzEuNUw3MyA2NS41ODUgNjcuMDAyIDU5LjV6IiBpZD0iU2hhcGUiIGZpbGw9IiNmZmYiLz48ZyBmaWxsPSIjZmZmIj48cGF0aCBkPSJNMTMgMjl2LTVoMnYzaDN2MmgtNXpNMTMgMTVoNXYyaC0zdjNoLTJ2LTV6TTMxIDE1djVoLTJ2LTNoLTN2LTJoNXpNMzEgMjloLTV2LTJoM3YtM2gydjV6IiBpZD0iU2hhcGUiLz48L2c+PGcgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTYyIDI0djVoLTJ2LTNoLTN2LTJoNXpNNjIgMjBoLTV2LTJoM3YtM2gydjV6TTcwIDIwdi01aDJ2M2gzdjJoLTV6TTcwIDI0aDV2MmgtM3YzaC0ydi01eiIvPjwvZz48cGF0aCBkPSJNMjAuNTg2IDY2bC01LjY1Ni01LjY1NiAxLjQxNC0xLjQxNEwyMiA2NC41ODZsNS42NTYtNS42NTYgMS40MTQgMS40MTRMMjMuNDE0IDY2bDUuNjU2IDUuNjU2LTEuNDE0IDEuNDE0TDIyIDY3LjQxNGwtNS42NTYgNS42NTYtMS40MTQtMS40MTRMMjAuNTg2IDY2eiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0xMTEuNzg1IDY1LjAzTDExMCA2My41bDMtMy41aC0xMHYtMmgxMGwtMy0zLjUgMS43ODUtMS40NjhMMTE3IDU5bC01LjIxNSA2LjAzeiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0xNTIuMjE1IDY1LjAzTDE1NCA2My41bC0zLTMuNWgxMHYtMmgtMTBsMy0zLjUtMS43ODUtMS40NjhMMTQ3IDU5bDUuMjE1IDYuMDN6IiBmaWxsPSIjZmZmIi8+PGc+PHBhdGggaWQ9IlJlY3RhbmdsZS0xMSIgZmlsbD0iI2ZmZiIgZD0iTTE2MC45NTcgMjguNTQzbC0zLjI1LTMuMjUtMS40MTMgMS40MTQgMy4yNSAzLjI1eiIvPjxwYXRoIGQ9Ik0xNTIuNSAyN2MzLjAzOCAwIDUuNS0yLjQ2MiA1LjUtNS41cy0yLjQ2Mi01LjUtNS41LTUuNS01LjUgMi40NjItNS41IDUuNSAyLjQ2MiA1LjUgNS41IDUuNXoiIGlkPSJPdmFsLTEiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTUwIDIxaDV2MWgtNXoiLz48L2c+PGc+PHBhdGggZD0iTTExNi45NTcgMjguNTQzbC0xLjQxNCAxLjQxNC0zLjI1LTMuMjUgMS40MTQtMS40MTQgMy4yNSAzLjI1eiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0xMDguNSAyN2MzLjAzOCAwIDUuNS0yLjQ2MiA1LjUtNS41cy0yLjQ2Mi01LjUtNS41LTUuNS01LjUgMi40NjItNS41IDUuNSAyLjQ2MiA1LjUgNS41IDUuNXoiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTA2IDIxaDV2MWgtNXoiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTA5LjA0MyAxOS4wMDhsLS4wODUgNS0xLS4wMTcuMDg1LTV6Ii8+PC9nPjwvZz48L2c+PC9zdmc+); + border: 0; + outline: 0; +} + +.km-image-viewer .km-image-viewer-toolbar { + position: absolute; + top: 0; + right: 0; + left: 0; +} + +.km-image-viewer .km-image-viewer-close { + background-position: 0 -44px; +} + +.km-editor { + position: relative; + z-index: 2; + overflow: hidden; +} + +.km-editor > .mask { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + display: block; + background-color: transparent; +} + +.km-editor > .receiver { + position: absolute; + top: 0; + left: 0; + z-index: -1000; + width: auto; + max-width: 300px; + min-height: 1.4em; + padding: 3px 5px; + margin-top: -5px; + margin-left: -3px; + overflow: hidden; + font-size: 14px; + line-height: 1.4em; + word-break: break-all; + word-wrap: break-word; + pointer-events: none; + background: white; + border: none; + outline: none; + opacity: 0; + box-shadow: 0 0 20px rgb(0 0 0 / 50%); + box-sizing: border-box; + user-select: text; +} + +.km-editor > .receiver.debug { + z-index: 0; + background: none; + outline: 1px solid green; + opacity: 1; +} + +.km-editor > .receiver.input { + z-index: 999; + pointer-events: all; + background: white; + outline: none; + opacity: 1; +} + +div.minder-editor-container { + position: absolute; + top: 40px; + right: 0; + bottom: 0; + left: 0; + font-family: Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif; +} + +.minder-editor { + position: absolute; + top: 92px; + right: 0; + bottom: 0; + left: 0; +} + +.minder-viewer { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; +} + +.control-panel { + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 250px; + border-left: 1px solid #ccc; +} + +.minder-divider { + position: absolute; + top: 0; + right: 250px; + bottom: 0; + width: 2px; + cursor: ew-resize; + background-color: #fbfbfb; +} + +.panel-body { + padding: 10px; +} + +.upload-image { + position: absolute; + z-index: -1; + width: 0.1px; + height: 0.1px; + overflow: hidden; + opacity: 0; +} + +.top-tab .nav-tabs { + height: 32px; + background-color: #e1e1e1; + border: 0; +} + +.top-tab .nav-tabs li { + margin: 0; +} + +.top-tab .nav-tabs li a { + padding: 6px 15px; + margin: 0; + vertical-align: middle; + border: 0; + border-radius: 0; +} + +.top-tab .nav-tabs li a:hover, +.top-tab .nav-tabs li a:focus { + background: inherit; + border: 0; +} + +.top-tab .nav-tabs li.active a { + background-color: #fff; + border: 0; +} + +.top-tab .nav-tabs li.active a:hover, +.top-tab .nav-tabs li.active a:focus { + border: 0; +} + +.top-tab .tab-content { + height: 60px; + background-color: #fff; + border-bottom: 1px solid #dbdbdb; +} + +.top-tab .tab-pane { + font-size: 0; +} + +.km-btn-group { + display: inline-block; + padding: 0 5px; + margin: 5px 0; + vertical-align: middle; + border-right: 1px dashed #eee; +} + +.km-btn-item { + display: inline-block; + margin: 0 3px; + font-size: 0; + cursor: default; +} + +.km-btn-item[disabled] { + opacity: 0.5; +} + +.km-btn-item[disabled]:hover, +.km-btn-item[disabled]:active { + background-color: #fff; +} + +.km-btn-item .km-btn-icon { + display: inline-block; + + /* background: url(images/icons.png) no-repeat; */ + background-position: 0 20px; + width: 20px; + height: 20px; + padding: 2px; + margin: 1px; + vertical-align: middle; +} + +.km-btn-item .km-btn-caption { + display: inline-block; + font-size: 12px; + vertical-align: middle; +} + +.km-btn-item:hover { + background-color: #eff3fa; +} + +.km-btn-item:active { + background-color: #c4d0ee; +} + +.do-group { + width: 38px; +} + +.undo .km-btn-icon { + background-position: 0 -1240px; +} + +.redo .km-btn-icon { + background-position: 0 -1220px; +} + +.append-group { + width: 212px; +} + +.append-child-node .km-btn-icon { + background-position: 0 0; +} + +.append-sibling-node .km-btn-icon { + background-position: 0 -20px; +} + +.append-parent-node .km-btn-icon { + background-position: 0 -40px; +} + +.arrange-group { + width: 64px; +} + +.arrange-up .km-btn-icon { + background-position: 0 -280px; +} + +.arrange-down .km-btn-icon { + background-position: 0 -300px; +} + +.operation-group { + width: 64px; +} + +.edit-node .km-btn-icon { + background-position: 0 -60px; +} + +.remove-node .km-btn-icon { + background-position: 0 -80px; +} + +.btn-group-vertical { + margin: 5px; + vertical-align: middle; +} + +.btn-group-vertical .hyperlink, +.btn-group-vertical .hyperlink-caption { + width: 40px; + padding: 0; + margin: 0; + border: none !important; + border-radius: 0 !important; +} + +.btn-group-vertical .hyperlink:hover, +.btn-group-vertical .hyperlink-caption:hover { + background-color: #eff3fa; +} + +.btn-group-vertical .hyperlink:active, +.btn-group-vertical .hyperlink-caption:active { + background-color: #c4d0ee; +} + +.btn-group-vertical .hyperlink.active, +.btn-group-vertical .hyperlink-caption.active { + box-shadow: none; + background-color: #eff3fa; +} + +.btn-group-vertical .hyperlink { + height: 25px; + + /* background: url(images/icons.png) no-repeat center -100px; */ +} + +.btn-group-vertical .hyperlink-caption { + height: 20px; +} + +.btn-group-vertical .hyperlink-caption .caption { + font-size: 12px; +} + +.open > .dropdown-toggle.btn-default { + background-color: #eff3fa; +} + +.btn-group-vertical .image-btn, +.btn-group-vertical .image-btn-caption { + width: 40px; + padding: 0; + margin: 0; + border: none !important; + border-radius: 0 !important; +} + +.btn-group-vertical .image-btn:hover, +.btn-group-vertical .image-btn-caption:hover { + background-color: #eff3fa; +} + +.btn-group-vertical .image-btn:active, +.btn-group-vertical .image-btn-caption:active { + background-color: #c4d0ee; +} + +.btn-group-vertical .image-btn.active, +.btn-group-vertical .image-btn-caption.active { + box-shadow: none; + background-color: #eff3fa; +} + +.btn-group-vertical .image-btn { + height: 25px; + + /* background: url(images/icons.png) no-repeat center -125px; */ +} + +.btn-group-vertical .image-btn-caption { + height: 20px; +} + +.btn-group-vertical .image-btn-caption .caption { + font-size: 12px; +} + +.image-preview { + display: block; + max-width: 50%; +} + +.modal-body .tab-pane { + padding-top: 15px; + font-size: inherit; +} + +.search-result { + height: 370px; + margin-top: 15px; + overflow: hidden; +} + +.search-result ul { + height: 100%; + padding: 0; + margin: 0; + overflow-x: hidden; + overflow-y: auto; + clear: both; + list-style: none; +} + +.search-result ul li { + position: relative; + display: block; + float: left; + width: 130px; + height: 130px; + padding: 0; + margin: 6px; + overflow: hidden; + font-size: 12px; + line-height: 130px; + text-align: center; + vertical-align: top; + list-style: none; + cursor: pointer; + border: 2px solid #fcfcfc; +} + +.search-result ul li.selected { + border: 2px solid #fc8383; +} + +.search-result ul li img { + max-width: 126px; + max-height: 130px; + vertical-align: middle; +} + +.search-result ul li span { + position: absolute; + right: 0; + bottom: 0; + left: 0; + display: block; + height: 20px; + overflow: hidden; + line-height: 20px; + color: white; + text-overflow: ellipsis; + word-break: break-all; + white-space: nowrap; + background: rgb(0 0 0 / 50%); + opacity: 0; + transform: translate(0, 20px); + transform: translate(0, 20px); + transform: translate(0, 20px); + transition: all 0.2s ease; + transition: all 0.2s ease; +} + +.search-result ul li:hover span { + opacity: 1; + transform: translate(0, 0); + transform: translate(0, 0); + transform: translate(0, 0); +} +@media (min-width: 768px) { + .form-inline .form-control { + width: 422px; + } +} + +.btn-group-vertical { + margin: 5px; + vertical-align: top; +} + +.btn-group-vertical.note-btn-group { + border-right: 1px dashed #eee; + padding-right: 5px; +} + +.btn-group-vertical .note-btn, +.btn-group-vertical .note-btn-caption { + width: 40px; + padding: 0; + margin: 0; + border: none !important; + border-radius: 0 !important; +} + +.btn-group-vertical .note-btn:hover, +.btn-group-vertical .note-btn-caption:hover { + background-color: #eff3fa; +} + +.btn-group-vertical .note-btn:active, +.btn-group-vertical .note-btn-caption:active { + background-color: #c4d0ee; +} + +.btn-group-vertical .note-btn.active, +.btn-group-vertical .note-btn-caption.active { + box-shadow: none; + background-color: #eff3fa; +} + +.btn-group-vertical .note-btn { + height: 25px; + + /* background: url(images/icons.png) no-repeat center -1150px; */ +} + +.btn-group-vertical .note-btn-caption { + height: 20px; +} + +.btn-group-vertical .note-btn-caption .caption { + font-size: 12px; +} + +.open > .dropdown-toggle.btn-default { + background-color: #eff3fa; +} + +.gfm-render { + font-size: 12px; + line-height: 1.8em; + color: #333; + user-select: text; +} + +.gfm-render blockquote, +.gfm-render ul, +.gfm-render table, +.gfm-render p, +.gfm-render pre, +.gfm-render hr { + margin: 1em 0; + cursor: text; +} + +.gfm-render blockquote:first-child:last-child, +.gfm-render ul:first-child:last-child, +.gfm-render table:first-child:last-child, +.gfm-render p:first-child:last-child, +.gfm-render pre:first-child:last-child, +.gfm-render hr:first-child:last-child { + margin: 0; +} + +.gfm-render img { + max-width: 100%; +} + +.gfm-render a { + color: blue; +} + +.gfm-render a:hover { + color: red; +} + +.gfm-render blockquote { + display: block; + padding-left: 10px; + margin-left: 2em; + font-style: italic; + color: #da8e68; + border-left: 4px solid #e4ad91; +} + +.gfm-render ul, +.gfm-render ol { + padding-left: 3em; +} + +.gfm-render table { + width: 100%; + border-collapse: collapse; + margin: 1em 0; +} + +.gfm-render table th, +.gfm-render table td { + padding: 2px 4px; + border: 1px solid #666; +} + +.gfm-render table th { + background: rgb(45 141 234 / 20%); +} + +.gfm-render table tr:nth-child(even) td { + background: rgb(45 141 234 / 3%); +} + +.gfm-render em { + color: red; +} + +.gfm-render del { + color: #999; +} + +.gfm-render pre { + padding: 5px; + word-break: break-all; + word-wrap: break-word; + background: rgb(45 141 234 / 10%); + border-radius: 5px; +} + +.gfm-render code { + /* display: inline-block; */ + padding: 0 5px; + background: rgb(45 141 234 / 10%); + border-radius: 3px; +} + +.gfm-render pre code { + background: none; +} + +.gfm-render hr { + border: none; + border-top: 1px solid #ccc; +} + +.gfm-render .highlight { + color: red; + background: yellow; +} + +.km-note { + position: absolute; + top: 92px; + right: 0; + bottom: 0; + left: auto; + z-index: 3; + width: 300px; + padding: 5px 10px; + background: white; + border-left: 1px solid #babfcd; +} + +.km-note.panel { + padding: 0; + margin: 0; +} + +.km-note.panel .panel-heading h3 { + display: inline-block; +} + +.km-note.panel .panel-heading .close-note-editor { + display: inline-block; + float: right; + width: 15px; + height: 15px; +} + +.km-note.panel .panel-heading .close-note-editor:hover { + cursor: pointer; +} + +.km-note.panel .panel-body { + padding: 0; +} + +.km-note .CodeMirror { + position: absolute; + top: 41px; + bottom: 0; + height: auto; + font-family: consolas; + font-size: 14px; + line-height: 1.3em; + cursor: text; +} + +.km-note-tips { + padding: 3px 8px; + color: #ccc; +} + +#previewer-content { + position: absolute; + z-index: 10; + max-width: 400px; + max-height: 200px; + padding: 5px 15px; + overflow: auto; + font-size: 12px; + line-height: 1.8em; + color: #333; + word-break: break-all; + background: #ffd; + border-radius: 5px; + box-shadow: 0 0 15px rgb(0 0 0 / 50%); + user-select: text; +} + +#previewer-content blockquote, +#previewer-content ul, +#previewer-content table, +#previewer-content p, +#previewer-content pre, +#previewer-content hr { + margin: 1em 0; + cursor: text; +} + +#previewer-content blockquote:first-child:last-child, +#previewer-content ul:first-child:last-child, +#previewer-content table:first-child:last-child, +#previewer-content p:first-child:last-child, +#previewer-content pre:first-child:last-child, +#previewer-content hr:first-child:last-child { + margin: 0; +} + +#previewer-content img { + max-width: 100%; +} + +#previewer-content a { + color: blue; +} + +#previewer-content a:hover { + color: red; +} + +#previewer-content blockquote { + display: block; + padding-left: 10px; + margin-left: 2em; + font-style: italic; + color: #da8e68; + border-left: 4px solid #e4ad91; +} + +#previewer-content ul, +#previewer-content ol { + padding-left: 3em; +} + +#previewer-content table { + width: 100%; + border-collapse: collapse; + margin: 1em 0; +} + +#previewer-content table th, +#previewer-content table td { + padding: 2px 4px; + border: 1px solid #666; +} + +#previewer-content table th { + background: rgb(45 141 234 / 20%); +} + +#previewer-content table tr:nth-child(even) td { + background: rgb(45 141 234 / 3%); +} + +#previewer-content em { + color: red; +} + +#previewer-content del { + color: #999; +} + +#previewer-content pre { + padding: 5px; + word-break: break-all; + word-wrap: break-word; + background: rgb(45 141 234 / 10%); + border-radius: 5px; +} + +#previewer-content code { + /* display: inline-block; */ + padding: 0 5px; + background: rgb(45 141 234 / 10%); + border-radius: 3px; +} + +#previewer-content pre code { + background: none; +} + +#previewer-content hr { + border: none; + border-top: 1px solid #ccc; +} + +#previewer-content .highlight { + color: red; + background: yellow; +} + +#previewer-content.ng-hide { + top: -99999px !important; + left: -99999px !important; + display: block !important; +} + +.panel-body { + padding: 10px; +} + +.tab-content .km-priority { + display: inline-block; + width: 140px; + margin: 5px; + font-size: inherit; + vertical-align: middle; + border-right: 1px dashed #eee; +} + +.tab-content .km-priority .km-priority-item { + padding: 1px; + margin: 0 1px; +} + +.tab-content .km-priority .km-priority-item .km-priority-icon { + /* background: url(images/iconpriority.png) repeat-y; */ + background-color: transparent; +} + +.tab-content .km-priority .km-priority-item .km-priority-icon.priority-0 { + background-position: 0 20px; +} + +.tab-content .km-priority .km-priority-item .km-priority-icon.priority-1 { + background-position: 0 0; +} + +.tab-content .km-priority .km-priority-item .km-priority-icon.priority-2 { + background-position: 0 -20px; +} + +.tab-content .km-priority .km-priority-item .km-priority-icon.priority-3 { + background-position: 0 -40px; +} + +.tab-content .km-priority .km-priority-item .km-priority-icon.priority-4 { + background-position: 0 -60px; +} + +.tab-content .km-priority .km-priority-item .km-priority-icon.priority-5 { + background-position: 0 -80px; +} + +.tab-content .km-priority .km-priority-item .km-priority-icon.priority-6 { + background-position: 0 -100px; +} + +.tab-content .km-priority .km-priority-item .km-priority-icon.priority-7 { + background-position: 0 -120px; +} + +.tab-content .km-priority .km-priority-item .km-priority-icon.priority-8 { + background-position: 0 -140px; +} + +.tab-content .km-priority .km-priority-item .km-priority-icon.priority-9 { + background-position: 0 -160px; +} + +.tab-content .km-progress { + display: inline-block; + width: 140px; + margin: 5px; + font-size: inherit; + vertical-align: middle; + border-right: 1px dashed #eee; +} + +.tab-content .km-progress .km-progress-item { + padding: 1px; + margin: 0 1px; +} + +.tab-content .km-progress .km-progress-item .km-progress-icon { + /* background: url(images/iconprogress.png) repeat-y; */ + background-color: transparent; +} + +.tab-content .km-progress .km-progress-item .km-progress-icon.progress-0 { + background-position: 0 20px; +} + +.tab-content .km-progress .km-progress-item .km-progress-icon.progress-1 { + background-position: 0 0; +} + +.tab-content .km-progress .km-progress-item .km-progress-icon.progress-2 { + background-position: 0 -20px; +} + +.tab-content .km-progress .km-progress-item .km-progress-icon.progress-3 { + background-position: 0 -40px; +} + +.tab-content .km-progress .km-progress-item .km-progress-icon.progress-4 { + background-position: 0 -60px; +} + +.tab-content .km-progress .km-progress-item .km-progress-icon.progress-5 { + background-position: 0 -80px; +} + +.tab-content .km-progress .km-progress-item .km-progress-icon.progress-6 { + background-position: 0 -100px; +} + +.tab-content .km-progress .km-progress-item .km-progress-icon.progress-7 { + background-position: 0 -120px; +} + +.tab-content .km-progress .km-progress-item .km-progress-icon.progress-8 { + background-position: 0 -140px; +} + +.tab-content .km-progress .km-progress-item .km-progress-icon.progress-9 { + background-position: 0 -160px; +} + +.resource-editor { + display: inline-block; + margin: 5px; + vertical-align: middle; +} + +.resource-editor .input-group, +.resource-editor .km-resource { + font-size: 12px; +} + +.resource-editor .input-group { + width: 168px; + height: 20px; +} + +.resource-editor .resource-dropdown { + position: relative; + width: 168px; + margin-top: -1px; + border: 1px solid #ccc; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} + +.resource-editor .resource-dropdown .km-resource { + position: absolute; + width: 154px; + max-height: 500px; + padding: 0; + margin-bottom: 3px; + overflow: scroll; + list-style-type: none; +} + +.resource-editor .resource-dropdown .km-resource.open { + z-index: 3; + background-color: #fff; +} + +.resource-editor .resource-dropdown .km-resource li { + display: inline-block; + padding: 1px 2px; + margin: 2px 3px; + border-radius: 4px; +} + +.resource-editor .resource-dropdown .km-resource li[disabled] { + opacity: 0.5; +} + +.resource-editor .resource-dropdown .resource-caret { + display: block; + float: right; + width: 12px; + height: 24px; + padding: 8px 1px; + vertical-align: middle; +} + +.resource-editor .resource-dropdown .resource-caret:hover { + background-color: #eff3fa; +} + +.resource-editor .resource-dropdown .resource-caret:active { + background-color: #c4d0ee; +} + +.resource-editor input.form-control, +.resource-editor .btn { + font-size: 12px; +} + +.resource-editor input.form-control { + height: 24px; + padding: 2px 4px; + border-bottom-left-radius: 0; +} + +.resource-editor .input-group-btn { + line-height: 24px; +} + +.resource-editor .input-group-btn .btn { + height: 24px; + padding: 2px 4px; + border-bottom-right-radius: 0; +} + +.temp-panel { + display: inline-block; + margin: 5px 5px 5px 10px; + vertical-align: middle; + border-right: 1px dashed #eee; +} + +.temp-list { + min-width: 124px; +} + +.temp-item-wrap { + display: inline-block; + width: 50px; + height: 40px; + padding: 0 2px; + margin: 5px; +} + +.temp-item { + display: inline-block; + width: 50px; + height: 40px; + + /* background-image: url(images/template.png); */ + background-repeat: no-repeat; +} + +.temp-item.default { + background-position: 0 0; +} + +.temp-item.structure { + background-position: -50px 0; +} + +.temp-item.filetree { + background-position: -100px 0; +} + +.temp-item.right { + background-position: -150px 0; +} + +.temp-item.fish-bone { + background-position: -200px 0; +} + +.temp-item.tianpan { + background-position: -250px 0; +} + +.current-temp-item { + width: 74px; + padding: 0 0 0 5px; + border: 1px solid #fff; +} + +.current-temp-item:hover { + background-color: #eff3fa; +} + +.current-temp-item[disabled] { + opacity: 0.5; +} + +.current-temp-item .caret { + margin-left: 5px; +} + +.temp-item-selected { + background-color: #87a9da; +} + +.theme-panel { + display: inline-block; + height: 42px; + padding: 0 5px 0 0; + margin: 5px; + vertical-align: middle; + border-right: 1px dashed #eee; +} + +.theme-list { + min-width: 162px; +} + +div a.theme-item { + display: inline-block; + width: 70px; + height: 30px; + padding: 0 5px; + font-size: 12px; + line-height: 30px; + color: #000; + text-align: center; + text-decoration: none; + cursor: pointer; +} + +.theme-item-selected { + width: 100px; + padding: 6px 7px; + border: 1px solid #fff; +} + +.theme-item-selected:hover { + background-color: #eff3fa; +} + +.theme-item-selected .caret { + margin-left: 5px; +} + +.theme-item-selected[disabled] { + opacity: 0.5; +} + +.theme-item-wrap { + display: inline-block; + width: 80px; + height: 40px; + padding: 5px; +} + +.theme-item-wrap:hover { + background-color: #eff3fa; +} + +.readjust-layout { + display: inline-block; + padding: 0 10px 0 5px; + vertical-align: middle; + border-right: 1px dashed #eee; +} + +.btn-icon { + display: block; + width: 25px; + height: 25px; + margin-left: 12px; +} + +.btn-label { + font-size: 12px; +} + +.btn-wrap { + display: inline-block; + width: 50px; + height: 42px; + text-decoration: none; + cursor: pointer; +} + +.btn-wrap[disabled] span { + opacity: 0.5; +} + +.btn-wrap[disabled] { + cursor: default; +} + +.btn-wrap[disabled]:hover { + background-color: transparent; +} + +.btn-wrap[disabled]:active { + background-color: transparent; +} + +.btn-wrap:link { + text-decoration: none; +} + +.btn-wrap:visited { + text-decoration: none; +} + +.btn-wrap:hover { + text-decoration: none; + background-color: #eff3fa; +} + +.btn-wrap:active { + background-color: #c4d0ee; +} + +.reset-layout-icon { + /* background: url(images/icons.png) no-repeat; */ + background-position: 0 -150px; +} + +.style-operator { + display: inline-block; + padding: 0 5px; + vertical-align: middle; + border-right: 1px dashed #eee; +} + +.style-operator .clear-style { + vertical-align: middle; +} + +.clear-style-icon { + /* background: url(images/icons.png) no-repeat; */ + background-position: 0 -175px; +} + +.s-btn-group-vertical { + display: inline-block; + vertical-align: middle; +} + +.s-btn-icon { + display: inline-block; + width: 20px; + height: 20px; + margin-right: 3px; + vertical-align: middle; +} + +.s-btn-label { + display: inline-block; + font-size: 12px; + vertical-align: middle; +} + +.s-btn-wrap { + display: inline-block; + padding: 0 5px 0 3px; + font-size: 0; + text-decoration: none; +} + +.s-btn-wrap[disabled] span { + opacity: 0.5; +} + +.s-btn-wrap[disabled] { + cursor: default; +} + +.s-btn-wrap[disabled]:hover { + background-color: transparent; +} + +.s-btn-wrap[disabled]:active { + background-color: transparent; +} + +.s-btn-wrap:hover { + text-decoration: none; + background-color: #eff3fa; +} + +.s-btn-wrap:active { + background-color: #c4d0ee; +} + +.copy-style-icon { + /* background: url(images/icons.png) no-repeat; */ + background-position: 0 -200px; +} + +.paste-style-wrap { + display: block; +} + +.paste-style-icon { + /* background: url(images/icons.png) no-repeat; */ + background-position: 0 -220px; +} + +.font-operator { + display: inline-block; + width: 170px; + padding: 0 5px; + font-size: 12px; + vertical-align: middle; +} + +.font-operator .font-size-list { + display: inline-block; + padding: 2px 4px; + border: 1px solid #eee; +} + +.font-operator .font-family-list { + display: inline-block; + padding: 2px 4px; + border: 1px solid #eee; +} + +.current-font-item a { + display: inline-block; + text-decoration: none; +} + +.current-font-family { + width: 75px; + height: 18px; + overflow: hidden; + vertical-align: bottom; +} + +.current-font-size { + width: 32px; + height: 18px; + overflow: hidden; + vertical-align: bottom; +} + +.current-font-item[disabled] { + opacity: 0.5; +} + +.font-item { + line-height: 1em; + text-align: left; +} + +.font-item-selected { + background-color: #87a9da; +} + +.font-bold, +.font-italics { + display: inline-block; + margin: 0 3px; + + /* background: url(images/icons.png) no-repeat; */ + cursor: pointer; +} + +.font-bold:hover, +.font-italics:hover { + background-color: #eff3fa; +} + +.font-bold:active, +.font-italics:active { + background-color: #c4d0ee; +} + +.font-bold[disabled], +.font-italics[disabled] { + opacity: 0.5; +} + +.font-bold { + background-position: 0 -240px; +} + +.font-italics { + background-position: 0 -260px; +} + +.font-bold-selected, +.font-italics-selected { + background-color: #87a9da; +} + +.font-color-wrap { + display: inline-block; + width: 30px; + height: 22px; + margin: 3px 3px 0 0; + font-size: 0; + vertical-align: middle; + border: 1px #efefef solid; + user-select: none; + user-select: none; + user-select: none; + user-select: none; +} + +.font-color-wrap[disabled] { + opacity: 0.5; +} + +.font-color-wrap .quick-font-color { + display: inline-block; + width: 20px; + height: 16px; + font-size: 14px; + line-height: 16px; + color: #000; + text-align: center; + vertical-align: top; + cursor: default; +} + +.font-color-wrap .quick-font-color:hover { + background-color: #eff3fa; +} + +.font-color-wrap .quick-font-color:active { + background-color: #c4d0ee; +} + +.font-color-wrap .quick-font-color[disabled] { + opacity: 0.5; +} + +.font-color-wrap .font-color-preview { + display: inline-block; + width: 12px; + height: 2px; + margin: 0 4px; + background-color: #000; +} + +.font-color-wrap .font-color-preview[disabled] { + opacity: 0.5; +} + +.font-color { + display: inline-block; + width: 8px; + height: 16px; +} + +.font-color:hover { + background-color: #eff3fa; +} + +.font-color:active { + background-color: #c4d0ee; +} + +.font-color[disabled] { + opacity: 0.5; +} + +.font-color .caret { + margin-top: 7px; + margin-left: -2px; +} + +.bg-color-wrap { + display: inline-block; + width: 30px; + height: 22px; + margin: 3px 3px 0 0; + font-size: 0; + vertical-align: middle; + border: 1px #efefef solid; + user-select: none; + user-select: none; + user-select: none; + user-select: none; +} + +.bg-color-wrap[disabled] { + opacity: 0.5; +} + +.bg-color-wrap .quick-bg-color { + display: inline-block; + width: 20px; + height: 16px; + font-size: 14px; + line-height: 16px; + color: #000; + text-align: center; + vertical-align: top; + cursor: default; + + /* background: url(images/icons.png) no-repeat center -1260px; */ +} + +.bg-color-wrap .quick-bg-color:hover { + background-color: #eff3fa; +} + +.bg-color-wrap .quick-bg-color:active { + background-color: #c4d0ee; +} + +.bg-color-wrap .quick-bg-color[disabled] { + opacity: 0.5; +} + +.bg-color-wrap .bg-color-preview { + display: inline-block; + width: 12px; + height: 2px; + margin: 0 4px; + background-color: #fff; +} + +.bg-color-wrap .bg-color-preview[disabled] { + opacity: 0.5; +} + +.bg-color { + display: inline-block; + width: 8px; + height: 16px; +} + +.bg-color:hover { + background-color: #eff3fa; +} + +.bg-color:active { + background-color: #c4d0ee; +} + +.bg-color[disabled] { + opacity: 0.5; +} + +.bg-color .caret { + margin-top: 7px; + margin-left: -2px; +} + +.btn-group-vertical { + margin: 5px; + vertical-align: middle; +} + +.btn-group-vertical .expand, +.btn-group-vertical .expand-caption { + width: 40px; + padding: 0; + margin: 0; + border: none !important; + border-radius: 0 !important; +} + +.btn-group-vertical .expand:hover, +.btn-group-vertical .expand-caption:hover { + background-color: #eff3fa; +} + +.btn-group-vertical .expand:active, +.btn-group-vertical .expand-caption:active { + background-color: #c4d0ee; +} + +.btn-group-vertical .expand.active, +.btn-group-vertical .expand-caption.active { + box-shadow: none; + background-color: #eff3fa; +} + +.btn-group-vertical .expand { + height: 25px; + + /* background: url(images/icons.png) no-repeat 0 -995px; */ + background-position-x: 50%; +} + +.btn-group-vertical .expand-caption { + height: 20px; +} + +.btn-group-vertical .expand-caption .caption { + font-size: 12px; +} + +.btn-group-vertical { + margin: 5px; + vertical-align: middle; +} + +.btn-group-vertical .select, +.btn-group-vertical .select-caption { + width: 40px; + padding: 0; + margin: 0; + border: none !important; + border-radius: 0 !important; +} + +.btn-group-vertical .select:hover, +.btn-group-vertical .select-caption:hover { + background-color: #eff3fa; +} + +.btn-group-vertical .select:active, +.btn-group-vertical .select-caption:active { + background-color: #c4d0ee; +} + +.btn-group-vertical .select.active, +.btn-group-vertical .select-caption.active { + box-shadow: none; + background-color: #eff3fa; +} + +.btn-group-vertical .select { + height: 25px; + + /* background: url(images/icons.png) no-repeat 7px -1175px; */ +} + +.btn-group-vertical .select-caption { + height: 20px; +} + +.btn-group-vertical .select-caption .caption { + font-size: 12px; +} + +.btn-group-vertical { + margin: 5px; + vertical-align: middle; +} + +.btn-group-vertical .search, +.btn-group-vertical .search-caption { + width: 40px; + padding: 0; + margin: 0; + border: none !important; + border-radius: 0 !important; +} + +.btn-group-vertical .search:hover, +.btn-group-vertical .search-caption:hover { + background-color: #eff3fa; +} + +.btn-group-vertical .search:active, +.btn-group-vertical .search-caption:active { + background-color: #c4d0ee; +} + +.btn-group-vertical .search.active, +.btn-group-vertical .search-caption.active { + box-shadow: none; + background-color: #eff3fa; +} + +.btn-group-vertical .search { + height: 25px; + + /* background: url(images/icons.png) no-repeat 0 -345px; */ + background-position-x: 50%; +} + +.btn-group-vertical .search-caption { + height: 20px; +} + +.btn-group-vertical .search-caption .caption { + font-size: 12px; +} + +.search-box { + position: relative; + top: 0; + z-index: 3; + float: right; + width: 360px; + height: 40px; + padding: 3px 6px; + background-color: #fff; + border: 1px solid #dbdbdb; + opacity: 1; +} + +.search-box .search-input-wrap, +.search-box .prev-and-next-btn { + float: left; +} + +.search-box .close-search { + float: right; + width: 16px; + height: 16px; + padding: 1px; + margin-top: 6px; + margin-right: 10px; + border-radius: 100%; +} + +.search-box .close-search .glyphicon { + top: -1px; +} + +.search-box .close-search:hover { + background-color: #efefef; +} + +.search-box .close-search:active { + background-color: #999; +} + +.search-box .search-input-wrap { + width: 240px; +} + +.search-box .prev-and-next-btn { + margin-left: 5px; +} + +.search-box .prev-and-next-btn .btn:focus { + outline: none; +} + +.search-box .search-addon { + background-color: #fff; +} + +.tool-group { + padding: 0; +} + +.tool-group[disabled] { + opacity: 0.5; +} + +.tool-group .tool-group-item { + display: inline-block; + border-radius: 4px; +} + +.tool-group .tool-group-item .tool-group-icon { + width: 20px; + height: 20px; + padding: 2px; + margin: 1px; +} + +.tool-group .tool-group-item:hover { + background-color: #eff3fa; +} + +.tool-group .tool-group-item:active { + background-color: #c4d0ee; +} + +.tool-group .tool-group-item.active { + background-color: #c4d0ee; +} + +.nav-bar { + position: absolute; + bottom: 10px; + left: 10px; + z-index: 10; + width: 35px; + height: 240px; + padding: 5px 0; + color: #fff; + background: #fc8383; + border-radius: 4px; + box-shadow: 3px 3px 10px rgb(0 0 0 / 20%); + transition: -webkit-transform 0.7s 0.1s ease; + transition: transform 0.7s 0.1s ease; +} + +.nav-bar .nav-btn { + width: 35px; + height: 24px; + line-height: 24px; + text-align: center; +} + +.nav-bar .nav-btn .icon { + display: block; + + /* background: url(images/icons.png); */ + width: 20px; + height: 20px; + margin: 2px auto; +} + +.nav-bar .nav-btn.active { + background-color: #5a6378; +} + +.nav-bar .zoom-in .icon { + background-position: 0 -730px; +} + +.nav-bar .zoom-out .icon { + background-position: 0 -750px; +} + +.nav-bar .hand .icon { + background-position: 0 -770px; + width: 25px; + height: 25px; + margin: 0 auto; +} + +.nav-bar .camera .icon { + background-position: 0 -870px; + width: 25px; + height: 25px; + margin: 0 auto; +} + +.nav-bar .nav-trigger .icon { + background-position: 0 -845px; + width: 25px; + height: 25px; + margin: 0 auto; +} + +.nav-bar .zoom-pan { + position: relative; + width: 2px; + height: 70px; + margin: 3px auto; + overflow: visible; + background: white; + box-shadow: 0 1px #e50000; +} + +.nav-bar .zoom-pan .origin { + position: absolute; + left: -9px; + width: 20px; + height: 8px; + margin-top: -4px; + background: transparent; +} + +.nav-bar .zoom-pan .origin::after { + position: absolute; + top: 3px; + left: 7px; + display: block; + width: 6px; + height: 2px; + background: white; + content: ' '; +} + +.nav-bar .zoom-pan .indicator { + position: absolute; + left: -3px; + width: 8px; + height: 8px; + margin-top: -4px; + background: white; + border-radius: 100%; +} + +.nav-previewer { + position: absolute; + bottom: 30px; + left: 45px; + z-index: 9; + width: 140px; + height: 120px; + padding: 1px; + cursor: crosshair; + background: #fff; + border-radius: 0 2px 2px 0; + box-shadow: 0 0 8px rgb(0 0 0 / 20%); + transition: -webkit-transform 0.7s 0.1s ease; + transition: transform 0.7s 0.1s ease; +} + +.nav-previewer.grab { + cursor: move; + cursor: grabbing; + cursor: grabbing; + cursor: grabbing; +} + +.hotbox { + position: absolute; + top: 0; + left: 0; + overflow: visible; + font-family: Arial, 'Hiragino Sans GB', 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif; +} + +.hotbox .state { + position: absolute; + display: none; + overflow: visible; +} + +.hotbox .state .center .button, +.hotbox .state .ring .button { + position: absolute; + width: 70px; + height: 70px; + margin-top: -35px; + margin-left: -35px; + border-radius: 100%; + box-shadow: 0 0 30px rgb(0 0 0 / 30%); +} + +.hotbox .state .center .label, +.hotbox .state .ring .label, +.hotbox .state .center .key, +.hotbox .state .ring .key { + display: block; + line-height: 1.4em; + text-align: center; + vertical-align: middle; +} + +.hotbox .state .center .label, +.hotbox .state .ring .label { + margin-top: 17px; + font-size: 16px; + font-weight: normal; + line-height: 1em; + color: black; +} + +.hotbox .state .center .key, +.hotbox .state .ring .key { + font-size: 12px; + color: #999; +} + +.hotbox .state .ring-shape { + position: absolute; + top: -25px; + left: -25px; + border: 25px solid rgb(0 0 0 / 30%); + border-radius: 100%; + box-sizing: content-box; +} + +.hotbox .state .top, +.hotbox .state .bottom { + position: absolute; + white-space: nowrap; +} + +.hotbox .state .top .button, +.hotbox .state .bottom .button { + position: relative; + display: inline-block; + padding: 8px 15px; + margin: 0 10px; + border-radius: 15px; + box-shadow: 0 0 30px rgb(0 0 0 / 30%); +} + +.hotbox .state .top .button .label, +.hotbox .state .bottom .button .label { + font-size: 14px; + line-height: 14px; + line-height: 1em; + color: black; + vertical-align: middle; +} + +.hotbox .state .top .button .key, +.hotbox .state .bottom .button .key { + margin-left: 3px; + font-size: 12px; + line-height: 12px; + color: #999; + vertical-align: middle; +} + +.hotbox .state .top .button .key::before, +.hotbox .state .bottom .button .key::before { + content: '('; +} + +.hotbox .state .top .button .key::after, +.hotbox .state .bottom .button .key::after { + content: ')'; +} + +.hotbox .state .button { + overflow: hidden; + cursor: default; + background: #f9f9f9; +} + +.hotbox .state .button .key, +.hotbox .state .button .label { + opacity: 0.3; +} + +.hotbox .state .button.enabled { + background: white; +} + +.hotbox .state .button.enabled .key, +.hotbox .state .button.enabled .label { + opacity: 1; +} + +.hotbox .state .button.enabled:hover { + background: #e87372; +} + +.hotbox .state .button.enabled:hover .label { + color: white; +} + +.hotbox .state .button.enabled:hover .key { + color: #fadfdf; +} + +.hotbox .state .button.enabled.selected { + animation: selected 0.1s ease; + background: #e45d5c; +} + +.hotbox .state .button.enabled.selected .label { + color: white; +} + +.hotbox .state .button.enabled.selected .key { + color: #fadfdf; +} + +.hotbox .state .button.enabled.pressed, +.hotbox .state .button.enabled:active { + background: #ff974d; +} + +.hotbox .state .button.enabled.pressed .label, +.hotbox .state .button.enabled:active .label { + color: white; +} + +.hotbox .state .button.enabled.pressed .key, +.hotbox .state .button.enabled:active .key { + color: #fff0e6; +} + +.hotbox .state.active { + display: block; +} + +@keyframes selected { + 0% { + transform: scale(1); + } + + 50% { + transform: scale(1.1); + } + + 100% { + transform: scale(1); + } +} + +.hotbox-key-receiver { + position: absolute; + top: -999999px; + left: -999999px; + width: 20px; + height: 20px; + margin: 0; + outline: none; +} diff --git a/packages/client/src/tiptap/core/styles/mind/context-menu.scss b/packages/client/src/tiptap/core/styles/mind/context-menu.scss deleted file mode 100644 index be314e5c..00000000 --- a/packages/client/src/tiptap/core/styles/mind/context-menu.scss +++ /dev/null @@ -1,62 +0,0 @@ -/* stylelint-disable */ -cmenu { - position: fixed; - top: 0; - left: 0; - z-index: 99; - width: 100%; - height: 100%; -} - -cmenu .menu-list { - position: fixed; - padding: 0; - margin: 0; - font: 300 15px Roboto, sans-serif; - color: var(--semi-color-text-0); - list-style: none; - box-shadow: 0 12px 15px 0 rgb(0 0 0 / 20%); - background-color: var(--semi-color-nav-bg); -} - -cmenu .menu-list * { - transition: color 0.4s, background-color 0.4s; -} - -cmenu .menu-list li { - min-width: 150px; - padding: 6px 10px; - overflow: hidden; - white-space: nowrap; - border-bottom: 1px solid var(--semi-color-border); -} - -cmenu .menu-list li a { - color: #333; - text-decoration: none; -} - -cmenu .menu-list li.disabled { - color: #5e5e5e; -} - -cmenu .menu-list li.disabled:hover { - cursor: default; -} - -cmenu .menu-list li:hover { - cursor: pointer; -} - -cmenu .menu-list li:first-child { - border-radius: 5px 5px 0 0; -} - -cmenu .menu-list li:last-child { - border-bottom: 0; - border-radius: 0 0 5px 5px; -} - -cmenu .menu-list li span:last-child { - float: right; -} diff --git a/packages/client/src/tiptap/core/styles/mind/index.scss b/packages/client/src/tiptap/core/styles/mind/index.scss deleted file mode 100644 index cbaae83e..00000000 --- a/packages/client/src/tiptap/core/styles/mind/index.scss +++ /dev/null @@ -1,30 +0,0 @@ -/* stylelint-disable */ -@import './context-menu.scss'; -@import './mobile-menu.scss'; -@import './node-menu.scss'; -@import './toolbar.scss'; -@import './mind.scss'; - -.map-container { - &.is-fullscreen { - position: fixed; - top: 0; - left: 0; - z-index: 1000; - width: 100vw; - height: 100vh; - } -} - -.map-container .map-canvas .selected { - outline: none !important; - border-color: rgb(9 109 217); -} - -.map-container .map-canvas root tpc { - line-height: 20px; -} - -#input-box { - color: #333 !important; -} diff --git a/packages/client/src/tiptap/core/styles/mind/mind.scss b/packages/client/src/tiptap/core/styles/mind/mind.scss deleted file mode 100644 index 19a8fa1d..00000000 --- a/packages/client/src/tiptap/core/styles/mind/mind.scss +++ /dev/null @@ -1,290 +0,0 @@ -/* stylelint-disable */ -.mind-elixir { - position: relative; - -webkit-tap-highlight-color: rgb(0 0 0 / 0%); -} - -.mind-elixir .hyper-link { - text-decoration: none; -} - -.map-container { - width: 100%; - height: 100%; - overflow: scroll; - font-size: 15px; - user-select: none; -} - -.map-container::-webkit-scrollbar { - width: 0; - height: 0; -} - -.map-container .focus-mode { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: #fff; -} - -.map-container .map-canvas { - position: relative; - width: 20000px; - height: 20000px; - background-color: var(--semi-color-fill-0); - transform: scale(1); - user-select: none; - transition: all 0.3s; -} - -.map-container .map-canvas .selected { - outline: 2px solid #4dc4ff; -} - -.map-container .map-canvas root { - position: absolute; -} - -tpc { - border: 1px solid transparent; - - &::selection { - background-color: transparent !important; - } - - &.is-editing, - &.selected { - border: 1px solid rgb(9 109 217) !important; - - &::selection { - background-color: rgb(180 213 254 / 50%) !important; - } - } -} - -.map-container .map-canvas root tpc { - display: block; - padding: 10px 15px; - font-size: 25px; - color: #fff; - white-space: pre-wrap; - background-color: #3370ff; - border: 1px solid transparent; - border-radius: 5px; -} - -.map-container .map-canvas root tpc #input-box { - padding: 10px 15px; -} - -.map-container .box > grp { - position: absolute; -} - -.map-container .box > grp > t > tpc { - padding: 8px 10px; - margin: 0; - color: var(--semi-color-text-0); - background-color: var(--semi-color-fill-1); - border-radius: 5px; -} - -.map-container .box > grp > t > tpc #input-box { - padding: 8px 10px; -} - -.map-container .box .lhs { - direction: rtl; -} - -.map-container .box .lhs tpc { - direction: ltr; -} - -.map-container .box grp { - display: block; - pointer-events: none; -} - -.map-container .box children, -.map-container .box t { - display: inline-block; - vertical-align: middle; -} - -.map-container .box t { - position: relative; - padding: 0 15px; - margin-top: 10px; - cursor: pointer; -} - -.map-container .box t tpc { - position: relative; - display: block; - max-width: 800px; - padding: 5px; - line-height: 1; - color: var(--semi-color-text-1); - white-space: pre-wrap; - pointer-events: all; - border-radius: 3px; -} - -.map-container .box t tpc #input-box { - padding: 5px; -} - -.map-container .box t tpc .tags { - direction: ltr; -} - -.map-container .box t tpc .tags span { - display: inline-block; - height: 16px; - padding: 2px 4px; - margin: 0; - margin-top: 2px; - margin-right: 3px; - font-size: 12px; - line-height: 16px; - color: #276f86; - background: #d6f0f8; - border-radius: 3px; -} - -.map-container .box t tpc .icons { - display: inline-block; - direction: ltr; - margin-right: 10px; -} - -.map-container .box t tpc .insert-preview { - position: absolute; - left: 0; - z-index: 9; - width: 100%; -} - -.map-container .box t tpc .before { - top: -14px; - height: 14px; -} - -.map-container .box t tpc .show { - pointer-events: none; - background: #7ad5ff; - opacity: 0.7; -} - -.map-container .box t tpc .in { - top: 0; - height: 100%; -} - -.map-container .box t tpc .after { - bottom: -14px; - height: 14px; -} - -.map-container .box t epd { - position: absolute; - z-index: 9; - width: 12px; - height: 12px; - line-height: 8px; - color: #494c4f; - text-align: center; - pointer-events: all; - background-color: #fff; - border: 1px solid #4f4f4f; - border-radius: 50%; -} - -.map-container .box t epd.minus { - transition: all 0.3s; - opacity: 0; -} - -.map-container .box t epd.minus:hover { - opacity: 1; -} - -.map-container .icon { - width: 1em; - height: 1em; - overflow: hidden; - vertical-align: -0.15em; - fill: currentcolor; -} - -.map-container .svg2nd, -.map-container .svg3rd, -.map-container .topiclinks, -.map-container .linkcontroller { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 102%; -} - -.map-container .topiclinks, -.map-container .linkcontroller { - pointer-events: none; -} - -.map-container .topiclinks g, -.map-container .linkcontroller g { - pointer-events: all; -} - -.map-container .svg2nd, -.map-container .svg3rd { - z-index: -1; - pointer-events: none; -} - -.map-container .topiclinks *, -.map-container .linkcontroller * { - z-index: 100; -} - -.map-container .topiclinks g { - cursor: pointer; -} - -.down t, -.down children { - display: block !important; -} - -.down grp { - display: inline-block !important; -} - -.circle { - position: absolute; - width: 10px; - height: 10px; - margin-top: -5px; - margin-left: -5px; - cursor: pointer; - background: #aaa; - border-radius: 100%; -} - -#input-box { - position: absolute; - top: 0; - left: 0; - z-index: 11; - width: max-content; - max-width: 800px; - background-color: #fff; - direction: ltr; - user-select: auto; -} diff --git a/packages/client/src/tiptap/core/styles/mind/mobile-menu.scss b/packages/client/src/tiptap/core/styles/mind/mobile-menu.scss deleted file mode 100644 index 2267657d..00000000 --- a/packages/client/src/tiptap/core/styles/mind/mobile-menu.scss +++ /dev/null @@ -1,48 +0,0 @@ -/* stylelint-disable */ -mmenu { - position: absolute; - bottom: 70px; - left: 20px; - z-index: 99; - padding: 0; - margin: 0; - overflow: hidden; - color: #333; - border-radius: 5px; - box-shadow: 0 12px 15px 0 rgb(0 0 0 / 20%); -} - -mmenu * { - transition: color 0.4s, background-color 0.4s; -} - -mmenu div { - float: left; - width: 30px; - padding: 8px; - overflow: hidden; - text-align: center; - white-space: nowrap; - background-color: #fff; - border-bottom: 1px solid #ecf0f1; -} - -mmenu div a { - color: #333; - text-decoration: none; -} - -mmenu div.disabled { - color: #5e5e5e; - background-color: #f7f7f7; -} - -mmenu div.disabled:hover { - cursor: default; - background-color: #f7f7f7; -} - -mmenu div:hover { - cursor: pointer; - background-color: #ecf0f1; -} diff --git a/packages/client/src/tiptap/core/styles/mind/node-menu.scss b/packages/client/src/tiptap/core/styles/mind/node-menu.scss deleted file mode 100644 index 612e0dc1..00000000 --- a/packages/client/src/tiptap/core/styles/mind/node-menu.scss +++ /dev/null @@ -1,11 +0,0 @@ -.node-toolbar-container { - position: absolute; - display: flex; - padding: 4px; - overflow-x: auto; - background-color: var(--semi-color-nav-bg); - border: 1px solid var(--semi-color-border); - border-radius: 3px; - align-items: center; - box-shadow: var(--box-shadow); -} diff --git a/packages/client/src/tiptap/core/styles/mind/toolbar.scss b/packages/client/src/tiptap/core/styles/mind/toolbar.scss deleted file mode 100644 index 09c58d28..00000000 --- a/packages/client/src/tiptap/core/styles/mind/toolbar.scss +++ /dev/null @@ -1,56 +0,0 @@ -/* stylelint-disable */ -.toolbar { - position: absolute; - display: flex; - padding: 2px 4px; - overflow-x: auto; - color: #fff; - background-color: var(--semi-color-nav-bg); - border: 1px solid var(--semi-color-border); - border-radius: 3px; - align-items: center; - box-shadow: var(--box-shadow); -} - -.toolbar span:active { - opacity: 0.5; -} - -.rb1 { - right: 60px; - bottom: 20px; - font-family: iconfont; - - width: 0; - padding: 0; - - &.is-visible { - width: auto; - padding: 2px 4px; - } -} - -.rb1 span + span { - margin-left: 10px; -} - -.rb2 { - right: 20px; - bottom: 20px; - font-family: iconfont; -} - -.lt { - top: 20px; - left: 20px; - font-size: 20px; - flex-direction: column; -} - -.lt span { - display: block; -} - -.lt span + span { - margin-top: 10px; -} diff --git a/packages/client/src/tiptap/core/wrappers/mind/index.module.scss b/packages/client/src/tiptap/core/wrappers/mind/index.module.scss index f62e7e52..90f28972 100644 --- a/packages/client/src/tiptap/core/wrappers/mind/index.module.scss +++ b/packages/client/src/tiptap/core/wrappers/mind/index.module.scss @@ -12,4 +12,16 @@ background-color: transparent !important; } } + + .mindHandlerWrap { + position: absolute; + right: 20px; + bottom: 20px; + z-index: 1000; + padding: 2px 4px; + background-color: var(--semi-color-bg-2); + border: 1px solid var(--node-border-color); + border-radius: var(--border-radius); + box-shadow: var(--box-shadow); + } } diff --git a/packages/client/src/tiptap/core/wrappers/mind/index.tsx b/packages/client/src/tiptap/core/wrappers/mind/index.tsx index 26c99933..3cb9661e 100644 --- a/packages/client/src/tiptap/core/wrappers/mind/index.tsx +++ b/packages/client/src/tiptap/core/wrappers/mind/index.tsx @@ -1,13 +1,16 @@ -import { Spin, Typography } from '@douyinfe/semi-ui'; +import { Button, Spin, Typography } from '@douyinfe/semi-ui'; import { NodeViewWrapper } from '@tiptap/react'; import cls from 'classnames'; -import clone from 'clone'; +import { IconMindCenter, IconZoomIn, IconZoomOut } from 'components/icons'; import { Resizeable } from 'components/resizeable'; +import { Tooltip } from 'components/tooltip'; import deepEqual from 'deep-equal'; import { useToggle } from 'hooks/use-toggle'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { load, renderMind } from 'thirtypart/kityminder'; import { Mind } from 'tiptap/core/extensions/mind'; -import { getEditorContainerDOMSize, uuid } from 'tiptap/prose-utils'; +import { MAX_ZOOM, MIN_ZOOM, ZOOM_STEP } from 'tiptap/editor/menus/mind/constant'; +import { clamp, getEditorContainerDOMSize } from 'tiptap/prose-utils'; import styles from './index.module.scss'; @@ -16,9 +19,7 @@ const { Text } = Typography; const INHERIT_SIZE_STYLE = { width: '100%', height: '100%', maxWidth: '100%' }; export const MindWrapper = ({ editor, node, updateAttributes }) => { - const $container = useRef(); const $mind = useRef(null); - const containerId = useRef(`js-mind-container-${uuid()}`); const isEditable = editor.isEditable; const isActive = editor.isActive(Mind.name); const { width: maxWidth } = getEditorContainerDOMSize(editor); @@ -26,123 +27,119 @@ export const MindWrapper = ({ editor, node, updateAttributes }) => { const [loading, toggleLoading] = useToggle(true); const [error, setError] = useState(null); - const content = useMemo(() => { - if (error) { - return ( -
- {error.message || error} -
- ); - } + const setCenter = useCallback(() => { + const mind = $mind.current; + if (!mind) return; + mind.execCommand('camera'); + }, []); - if (loading) { - return ; - } - - return ( -
- ); - }, [loading, error]); + const setZoom = useCallback((type: 'minus' | 'plus') => { + return () => { + const mind = $mind.current; + if (!mind) return; + const currentZoom = mind.getZoomValue(); + const nextZoom = clamp(type === 'minus' ? currentZoom - ZOOM_STEP : currentZoom + ZOOM_STEP, MIN_ZOOM, MAX_ZOOM); + mind.execCommand('zoom', nextZoom); + }; + }, []); const onResize = useCallback( (size) => { updateAttributes({ width: size.width, height: size.height }); setTimeout(() => { - $mind.current && $mind.current.toCenter(); + setCenter(); }); }, - [updateAttributes] + [updateAttributes, setCenter] + ); + + const render = useCallback( + (div) => { + if (!div) return; + + if (!$mind.current) { + const graph = renderMind({ + container: div, + data, + isEditable: false, + }); + $mind.current = graph; + } + }, + [data] + ); + + const setMind = useCallback( + (div) => { + render(div); + }, + [render] ); - // 加载依赖 useEffect(() => { - import('./mind-elixir') - .then((module) => { - toggleLoading(false); - window.MindElixir = module.default; - }) - .catch((e) => { - setError(e); - }); + load() + .catch(setError) + .finally(() => toggleLoading(false)); }, [toggleLoading]); - // 初始化渲染 - useEffect(() => { - const container = $container.current; - if (loading || !container) return; - - const onChange = () => { - updateAttributes({ data: mind.getAllData() }); - }; - - let mind = null; - - let isEnter = false; - const onMouseEnter = () => { - isEnter = true; - }; - const onMouseLeave = () => { - isEnter = false; - }; - - container.addEventListener('onmouseenter', onMouseEnter); - container.addEventListener('onMouseLeave', onMouseLeave); - - try { - mind = new window.MindElixir({ - el: `#${containerId.current}`, - direction: window.MindElixir.SIDE, - data: clone(data), - editable: editor.isEditable, - contextMenu: editor.isEditable, - keypress: editor.isEditable, - nodeMenu: editor.isEditable, - toolBar: true, - draggable: false, // TODO: 需要修复 - locale: 'zh_CN', - }); - mind.shouldPreventDefault = () => isEnter && editor.isActive('mind'); - mind.init(); - mind.bus.addListener('operation', onChange); - $mind.current = mind; - toggleLoading(false); - } catch (e) { - setError(e); - } - - return () => { - if (container) { - container.removeEventListener('onmouseenter', onMouseEnter); - container.removeEventListener('onMouseLeave', onMouseLeave); - } - - if (mind) { - mind.destroy(); - } - }; - // data 的更新交给下方 effect - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loading, editor, updateAttributes, toggleLoading]); - + // 数据同步渲染 useEffect(() => { const mind = $mind.current; if (!mind) return; - const newData = clone(data); - if (!deepEqual(newData, mind.getAllData())) { - mind.update(newData); - } + const currentData = mind.exportJson(); + const isEqual = deepEqual(currentData, data); + if (isEqual) return; + mind.importJson(data); }, [data]); + useEffect(() => { + setCenter(); + }, [width, height, setCenter]); + return ( - {content} +
+ {error && ( +
+ {error.message || error} +
+ )} + {loading && } + {!loading && !error && ( +
+ )} + +
+ +
+
); diff --git a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/const.ts b/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/const.ts deleted file mode 100644 index cd8f03ba..00000000 --- a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/const.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const LEFT = 0; -export const RIGHT = 1; -export const SIDE = 2; -export const DOWN = 3; - -export const GAP = 15; -export const PRIMARY_NODE_HORIZONTAL_GAP = 65; -export const PRIMARY_NODE_VERTICAL_GAP = 25; -export const TURNPOINT_R = 8; diff --git a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/customLink.ts b/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/customLink.ts deleted file mode 100644 index e9f22ee5..00000000 --- a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/customLink.ts +++ /dev/null @@ -1,269 +0,0 @@ -import { calcP1, calcP4, generateUUID, getArrowPoints, LinkDragMoveHelper } from './utils/index'; -import { createSvgGroup } from './utils/svg'; - -// TODO Link label -export const createLink = function (from, to, isInitPaint, obj) { - const map = this.map.getBoundingClientRect(); - if (!from || !to) { - return; // not expand - } - const pfrom = from.getBoundingClientRect(); - const pto = to.getBoundingClientRect(); - const fromCenterX = (pfrom.x + pfrom.width / 2 - map.x) / this.scaleVal; - const fromCenterY = (pfrom.y + pfrom.height / 2 - map.y) / this.scaleVal; - const toCenterX = (pto.x + pto.width / 2 - map.x) / this.scaleVal; - const toCenterY = (pto.y + pto.height / 2 - map.y) / this.scaleVal; - - let p2x, p2y, p3x, p3y; - if (isInitPaint) { - p2x = fromCenterX + obj.delta1.x; - p2y = fromCenterY + obj.delta1.y; - p3x = toCenterX + obj.delta2.x; - p3y = toCenterY + obj.delta2.y; - } else { - if ((fromCenterY + toCenterY) / 2 - fromCenterY <= pfrom.height / 2) { - // the situation that two div is too close - p2x = (pfrom.x + pfrom.width - map.x) / this.scaleVal + 100; - p2y = fromCenterY; - p3x = (pto.x + pto.width - map.x) / this.scaleVal + 100; - p3y = toCenterY; - } else { - p2x = (fromCenterX + toCenterX) / 2; - p2y = (fromCenterY + toCenterY) / 2; - p3x = (fromCenterX + toCenterX) / 2; - p3y = (fromCenterY + toCenterY) / 2; - } - } - - const fromData = { - cx: fromCenterX, - cy: fromCenterY, - w: pfrom.width, - h: pfrom.height, - }; - const toData = { - cx: toCenterX, - cy: toCenterY, - w: pto.width, - h: pto.height, - }; - - const p1 = calcP1(fromData, p2x, p2y); - const p1x = p1.x; - const p1y = p1.y; - - const p4 = calcP4(toData, p3x, p3y); - const p4x = p4.x; - const p4y = p4.y; - - const arrowPoint = getArrowPoints(p3x, p3y, p4x, p4y); - - // TODO link lable - // const halfx = p1x / 8 + (p2x * 3) / 8 + (p3x * 3) / 8 + p4x / 8 - // const halfy = p1y / 8 + (p2y * 3) / 8 + (p3y * 3) / 8 + p4y / 8 - - const newSvgGroup = createSvgGroup( - `M ${p1x} ${p1y} C ${p2x} ${p2y} ${p3x} ${p3y} ${p4x} ${p4y}`, - `M ${arrowPoint.x1} ${arrowPoint.y1} L ${p4x} ${p4y} L ${arrowPoint.x2} ${arrowPoint.y2}` - ); - - let newLinkObj; - if (isInitPaint) { - newLinkObj = { - id: obj.id, - label: '', - from, - to, - delta1: { - x: p2x - fromCenterX, - y: p2y - fromCenterY, - }, - delta2: { - x: p3x - toCenterX, - y: p3y - toCenterY, - }, - }; - // overwrite - this.linkData[obj.id] = newLinkObj; - newSvgGroup.linkObj = newLinkObj; - newSvgGroup.dataset.linkid = obj.id; - } else { - newLinkObj = { - id: generateUUID(), - label: '', - from, - to, - delta1: { - x: p2x - fromCenterX, - y: p2y - fromCenterY, - }, - delta2: { - x: p3x - toCenterX, - y: p3y - toCenterY, - }, - }; - // new - this.linkData[newLinkObj.id] = newLinkObj; - newSvgGroup.linkObj = newLinkObj; - newSvgGroup.dataset.linkid = newLinkObj.id; - this.currentLink = newSvgGroup; - } - this.linkSvgGroup.appendChild(newSvgGroup); - if (!isInitPaint) { - this.showLinkController(p2x, p2y, p3x, p3y, newLinkObj, fromData, toData); - } - - this.bus.fire('operation', { - name: 'createLink', - linkObj: newLinkObj, - }); -}; - -export const removeLink = function (linkSvg) { - let link; - if (linkSvg) { - link = linkSvg; - } else { - link = this.currentLink; - } - if (!link) return; - console.log(link); - this.hideLinkController(); - const id = link.linkObj.id; - console.log(id); - delete this.linkData[id]; - link.remove(); - link = null; - - this.bus.fire('operation', { - name: 'removeLink', - }); -}; -export const selectLink = function (targetElement) { - this.currentLink = targetElement; - const obj = targetElement.linkObj; - const from = obj.from; - const to = obj.to; - - const map = this.map.getBoundingClientRect(); - const pfrom = from.getBoundingClientRect(); - const pto = to.getBoundingClientRect(); - const fromCenterX = (pfrom.x + pfrom.width / 2 - map.x) / this.scaleVal; - const fromCenterY = (pfrom.y + pfrom.height / 2 - map.y) / this.scaleVal; - const toCenterX = (pto.x + pto.width / 2 - map.x) / this.scaleVal; - const toCenterY = (pto.y + pto.height / 2 - map.y) / this.scaleVal; - - const fromData = { - cx: fromCenterX, - cy: fromCenterY, - w: pfrom.width, - h: pfrom.height, - }; - const toData = { - cx: toCenterX, - cy: toCenterY, - w: pto.width, - h: pto.height, - }; - - const p2x = fromCenterX + obj.delta1.x; - const p2y = fromCenterY + obj.delta1.y; - const p3x = toCenterX + obj.delta2.x; - const p3y = toCenterY + obj.delta2.y; - - this.showLinkController(p2x, p2y, p3x, p3y, obj, fromData, toData); -}; -export const hideLinkController = function () { - this.linkController.style.display = 'none'; - this.P2.style.display = 'none'; - this.P3.style.display = 'none'; -}; -export const showLinkController = function (p2x, p2y, p3x, p3y, linkObj, fromData, toData) { - this.linkController.style.display = 'initial'; - this.P2.style.display = 'initial'; - this.P3.style.display = 'initial'; - - const p1 = calcP1(fromData, p2x, p2y); - let p1x = p1.x; - let p1y = p1.y; - - const p4 = calcP4(toData, p3x, p3y); - let p4x = p4.x; - let p4y = p4.y; - - this.P2.style.cssText = `top:${p2y}px;left:${p2x}px;`; - this.P3.style.cssText = `top:${p3y}px;left:${p3x}px;`; - this.line1.setAttribute('x1', p1x); - this.line1.setAttribute('y1', p1y); - this.line1.setAttribute('x2', p2x); - this.line1.setAttribute('y2', p2y); - this.line2.setAttribute('x1', p3x); - this.line2.setAttribute('y1', p3y); - this.line2.setAttribute('x2', p4x); - this.line2.setAttribute('y2', p4y); - - if (this.helper1) { - this.helper1.destory(this.map); - this.helper2.destory(this.map); - } - - this.helper1 = new LinkDragMoveHelper(this.P2); - this.helper2 = new LinkDragMoveHelper(this.P3); - - this.helper1.init(this.map, (deltaX, deltaY) => { - /** - * user will control bezier with p2 & p3 - * p1 & p4 is depend on p2 & p3 - */ - p2x = p2x - deltaX / this.scaleVal; - p2y = p2y - deltaY / this.scaleVal; - - const p1 = calcP1(fromData, p2x, p2y); - p1x = p1.x; - p1y = p1.y; - - this.P2.style.top = p2y + 'px'; - this.P2.style.left = p2x + 'px'; - this.currentLink.children[0].setAttribute('d', `M ${p1x} ${p1y} C ${p2x} ${p2y} ${p3x} ${p3y} ${p4x} ${p4y}`); - this.line1.setAttribute('x1', p1x); - this.line1.setAttribute('y1', p1y); - this.line1.setAttribute('x2', p2x); - this.line1.setAttribute('y2', p2y); - linkObj.delta1.x = p2x - fromData.cx; - linkObj.delta1.y = p2y - fromData.cy; - - this.bus.fire('operation', { - name: 'updateLink', - linkObj, - }); - }); - - this.helper2.init(this.map, (deltaX, deltaY) => { - p3x = p3x - deltaX / this.scaleVal; - p3y = p3y - deltaY / this.scaleVal; - - const p4 = calcP4(toData, p3x, p3y); - p4x = p4.x; - p4y = p4.y; - const arrowPoint = getArrowPoints(p3x, p3y, p4x, p4y); - - this.P3.style.top = p3y + 'px'; - this.P3.style.left = p3x + 'px'; - this.currentLink.children[0].setAttribute('d', `M ${p1x} ${p1y} C ${p2x} ${p2y} ${p3x} ${p3y} ${p4x} ${p4y}`); - this.currentLink.children[1].setAttribute( - 'd', - `M ${arrowPoint.x1} ${arrowPoint.y1} L ${p4x} ${p4y} L ${arrowPoint.x2} ${arrowPoint.y2}` - ); - this.line2.setAttribute('x1', p3x); - this.line2.setAttribute('y1', p3y); - this.line2.setAttribute('x2', p4x); - this.line2.setAttribute('y2', p4y); - linkObj.delta2.x = p3x - toData.cx; - linkObj.delta2.y = p3y - toData.cy; - - this.bus.fire('operation', { - name: 'updateLink', - linkObj, - }); - }); -}; diff --git a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/i18n.ts b/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/i18n.ts deleted file mode 100644 index 9df9c67c..00000000 --- a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/i18n.ts +++ /dev/null @@ -1,23 +0,0 @@ -const cn = { - addChild: '插入子节点', - addParent: '插入父节点', - addSibling: '插入同级节点', - removeNode: '删除节点', - focus: '专注', - cancelFocus: '取消专注', - moveUp: '上移', - moveDown: '下移', - link: '连接', - clickTips: '请点击目标节点', - - font: '文字', - background: '背景', - tag: '标签', - icon: '图标', - tagsSeparate: '多个标签半角逗号分隔', - iconsSeparate: '多个图标半角逗号分隔', -}; -export default { - cn, - zh_CN: cn, -}; diff --git a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/iconfont/iconfont.js b/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/iconfont/iconfont.js deleted file mode 100644 index 9b967b5a..00000000 --- a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/iconfont/iconfont.js +++ /dev/null @@ -1,62 +0,0 @@ -/* eslint-disable */ -(function (t) { - var c, - l, - a, - e, - o, - i, - n = - '', - h = (h = document.getElementsByTagName('script'))[h.length - 1].getAttribute('data-injectcss'); - if (h && !t.__iconfont__svg__cssinject__) { - t.__iconfont__svg__cssinject__ = !0; - try { - document.write( - '' - ); - } catch (t) { - console && console.log(t); - } - } - function d() { - o || ((o = !0), a()); - } - (c = function () { - var t, c, l, a; - ((a = document.createElement('div')).innerHTML = n), - (n = null), - (l = a.getElementsByTagName('svg')[0]) && - (l.setAttribute('aria-hidden', 'true'), - (l.style.position = 'absolute'), - (l.style.width = 0), - (l.style.height = 0), - (l.style.overflow = 'hidden'), - (t = l), - (c = document.body).firstChild - ? ((a = t), (l = c.firstChild).parentNode.insertBefore(a, l)) - : c.appendChild(t)); - }), - document.addEventListener - ? ~['complete', 'loaded', 'interactive'].indexOf(document.readyState) - ? setTimeout(c, 0) - : ((l = function () { - document.removeEventListener('DOMContentLoaded', l, !1), c(); - }), - document.addEventListener('DOMContentLoaded', l, !1)) - : document.attachEvent && - ((a = c), - (e = t.document), - (o = !1), - (i = function () { - try { - e.documentElement.doScroll('left'); - } catch (t) { - return void setTimeout(i, 50); - } - d(); - })(), - (e.onreadystatechange = function () { - 'complete' == e.readyState && ((e.onreadystatechange = null), d()); - })); -})(window); diff --git a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/index.ts b/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/index.ts deleted file mode 100644 index f5443105..00000000 --- a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/index.ts +++ /dev/null @@ -1,449 +0,0 @@ -import './iconfont/iconfont.js'; - -import { LEFT, RIGHT, SIDE } from './const'; -import { createLink, hideLinkController, removeLink, selectLink, showLinkController } from './customLink'; -import { - cancelFocus, - disableEdit, - enableEdit, - expandNode, - focusNode, - getAllData, - getAllDataMd, - getAllDataString, - initLeft, - initRight, - initSide, - refresh, - scale, - selectFirstChild, - selectNextSibling, - selectNode, - selectParent, - selectPrevSibling, - setLocale, - toCenter, - unselectNode, -} from './interact'; -import linkDiv from './linkDiv'; -import initMouseEvent from './mouse'; -import { - addChild, - beginEdit, - copyNode, - insertBefore, - insertParent, - insertSibling, - moveDownNode, - moveNode, - moveNodeAfter, - moveNodeBefore, - moveUpNode, - processPrimaryNode, - removeNode, - setNodeTopic, - updateNodeHyperLink, - updateNodeIcons, - updateNodeStyle, - updateNodeTags, -} from './nodeOperation'; -import contextMenu from './plugin/contextMenu'; -import keypress from './plugin/keypress'; -import mobileMenu from './plugin/mobileMenu'; -import nodeDraggable from './plugin/nodeDraggable'; -import nodeMenu from './plugin/nodeMenu'; -import toolBar from './plugin/toolBar'; -import { - createChildren, - createGroup, - createInputDiv, - createTop, - createTopic, - findEle, - layout, - Topic, -} from './utils/dom'; -import { addParentLink, generateNewObj, generateUUID, getObjById, isMobile } from './utils/index'; -import Bus from './utils/pubsub'; -import { createLine, createLinkSvg } from './utils/svg'; - -// TODO show up animation -export const E = findEle; -type LinkObj = object; -type operation = { - name: string; -}; -export interface NodeObj { - topic: string; - id: string; - style?: { - fontSize?: string; - color?: string; - background?: string; - fontWeight?: string; - }; - parent?: NodeObj; - children?: NodeObj[]; - tags?: string[]; - icons?: string[]; - hyperLink?: string; - expanded?: boolean; - direction?: number; - root?: boolean; -} - -export interface NodeElement extends HTMLElement { - nodeObj: Object; -} -export interface MindElixirData { - nodeData: NodeObj; - linkData?: LinkObj; -} -export interface MindElixirInstance { - mindElixirBox: HTMLElement; - nodeData: NodeObj; - linkData: LinkObj; - currentNode: Topic | null; - currentLink: SVGElement | null; - inputDiv: HTMLElement | null; - scaleVal: number; - tempDirection: number | null; - bus: object; // wip - - // wip - history: operation[]; - isUndo: boolean; - undo: () => void; - - direction: number; - locale: string; - draggable: boolean; - editable: boolean; - contextMenu: boolean; - contextMenuOption: object; - toolBar: boolean; - nodeMenu: boolean; - keypress: boolean; - before: object; - newTopicName: string; - allowUndo: boolean; - overflowHidden: boolean; - primaryLinkStyle: number; - primaryNodeHorizontalGap: number; - primaryNodeVerticalGap: number; - mobileMenu: boolean; -} -export interface Options { - el: string; - data: MindElixirData; - direction?: number; - locale?: string; - draggable?: boolean; - editable?: boolean; - contextMenu?: boolean; - contextMenuOption?: object; - toolBar?: boolean; - nodeMenu?: boolean; - keypress?: boolean; - before?: object; - newTopicName?: string; - allowUndo?: boolean; - overflowHidden?: boolean; - primaryLinkStyle?: number; - primaryNodeHorizontalGap?: number; - primaryNodeVerticalGap?: number; - mobileMenu?: boolean; -} -const $d = document; -/** - * @export MindElixir - * @example - * let mind = new MindElixir({ - el: '#map', - direction: 2, - data: data, - draggable: true, - editable: true, - contextMenu: true, - toolBar: true, - nodeMenu: true, - keypress: true, -}) -mind.init() - * - */ -function MindElixir( - this: MindElixirInstance, - { - el, - data, - direction, - locale, - draggable, - editable, - contextMenu, - contextMenuOption, - toolBar, - nodeMenu, - keypress, - before, - newTopicName, - allowUndo, - primaryLinkStyle, - overflowHidden, - primaryNodeHorizontalGap, - primaryNodeVerticalGap, - mobileMenu, - }: Options -) { - const box = document.querySelector(el); - if (!box) return; - // @ts-ignore - this.mindElixirBox = box; - this.before = before || {}; - this.nodeData = data.nodeData; - this.linkData = data.linkData || {}; - this.locale = locale; - this.contextMenuOption = contextMenuOption; - this.contextMenu = contextMenu === undefined ? true : contextMenu; - this.toolBar = toolBar === undefined ? true : toolBar; - this.nodeMenu = nodeMenu === undefined ? true : nodeMenu; - this.keypress = keypress === undefined ? true : keypress; - this.mobileMenu = mobileMenu; - // record the direction before enter focus mode, must true in focus mode, reset to null after exit focus - // todo move direction to data - this.direction = typeof direction === 'number' ? direction : 1; - this.draggable = draggable === undefined ? true : draggable; - this.newTopicName = newTopicName; - this.editable = editable === undefined ? true : editable; - this.allowUndo = allowUndo === undefined ? true : allowUndo; - // this.parentMap = {} // deal with large amount of nodes - this.currentNode = null; // the selected element - this.currentLink = null; // the selected link svg element - this.inputDiv = null; // editor - this.scaleVal = 1; - this.tempDirection = null; - this.primaryLinkStyle = primaryLinkStyle || 0; - this.overflowHidden = overflowHidden; - this.primaryNodeHorizontalGap = primaryNodeHorizontalGap; - this.primaryNodeVerticalGap = primaryNodeVerticalGap; - - this.bus = new Bus(); - (this.bus as any).addListener('operation', (operation: operation) => { - if (this.isUndo) { - this.isUndo = false; - return; - } - if ( - ['moveNode', 'removeNode', 'addChild', 'finishEdit', 'editStyle', 'editTags', 'editIcons'].includes( - operation.name - ) - ) { - this.history.push(operation); - // console.log(operation, this.history) - } - }); - - this.history = []; // TODO - this.isUndo = false; - this.undo = function () { - const operation = this.history.pop(); - if (!operation) return; - this.isUndo = true; - if (operation.name === 'moveNode') { - this.moveNode(E(operation.obj.fromObj.id), E(operation.obj.originParentId)); - } else if (operation.name === 'removeNode') { - if (operation.originSiblingId) { - this.insertBefore(E(operation.originSiblingId), operation.obj); - } else { - this.addChild(E(operation.originParentId), operation.obj); - } - } else if (operation.name === 'addChild' || operation.name === 'copyNode') { - this.removeNode(E(operation.obj.id)); - } else if (operation.name === 'finishEdit') { - this.setNodeTopic(E(operation.obj.id), operation.origin); - } else { - this.isUndo = false; - } - }; -} - -function beforeHook(fn: (el: any, node?: any) => void) { - return async function (...args: unknown[]) { - if (!this.before[fn.name] || (await this.before[fn.name].apply(this, args))) { - fn.apply(this, args); - } - }; -} - -MindElixir.prototype = { - addParentLink, - getObjById, - generateNewObj, - // node operation - insertSibling: beforeHook(insertSibling), - insertBefore: beforeHook(insertBefore), - insertParent: beforeHook(insertParent), - addChild: beforeHook(addChild), - copyNode: beforeHook(copyNode), - moveNode: beforeHook(moveNode), - removeNode: beforeHook(removeNode), - moveUpNode: beforeHook(moveUpNode), - moveDownNode: beforeHook(moveDownNode), - beginEdit: beforeHook(beginEdit), - moveNodeBefore: beforeHook(moveNodeBefore), - moveNodeAfter: beforeHook(moveNodeAfter), - updateNodeStyle, - updateNodeTags, - updateNodeIcons, - updateNodeHyperLink, - processPrimaryNode, - setNodeTopic, - - createLink, - removeLink, - selectLink, - hideLinkController, - showLinkController, - - layout, - linkDiv, - createInputDiv, - - createChildren, - createGroup, - createTop, - createTopic, - - selectNode, - unselectNode, - selectNextSibling, - selectPrevSibling, - selectFirstChild, - selectParent, - getAllDataString, - getAllData, - getAllDataMd, - scale, - toCenter, - focusNode, - cancelFocus, - initLeft, - initRight, - initSide, - setLocale, - enableEdit, - disableEdit, - expandNode, - refresh, - - init: function () { - /** - * @function - * @global - * @name E - * @param {string} id Node id. - * @return {TargetElement} Target element. - * @example - * E('bd4313fbac40284b') - */ - addParentLink(this.nodeData); - this.mindElixirBox.className += ' mind-elixir'; - this.mindElixirBox.innerHTML = ''; - - this.container = $d.createElement('div'); // map container - this.container.className = 'map-container'; - - if (this.overflowHidden) this.container.style.overflow = 'hidden'; - - this.map = $d.createElement('div'); // map-canvas Element - this.map.className = 'map-canvas'; - this.map.setAttribute('tabindex', '0'); - this.container.appendChild(this.map); - this.mindElixirBox.appendChild(this.container); - this.root = $d.createElement('root'); - - this.box = $d.createElement('children'); - this.box.className = 'box'; - - this.svg2nd = createLinkSvg('svg2nd'); // main link container - this.linkController = createLinkSvg('linkcontroller'); // bezier controller container - this.P2 = $d.createElement('div'); // bezier P2 - this.P3 = $d.createElement('div'); // bezier P3 - this.P2.className = this.P3.className = 'circle'; - this.line1 = createLine(0, 0, 0, 0); // bezier auxiliary line1 - this.line2 = createLine(0, 0, 0, 0); // bezier auxiliary line2 - this.linkController.appendChild(this.line1); - this.linkController.appendChild(this.line2); - - this.linkSvgGroup = createLinkSvg('topiclinks'); // storage user custom link svg - - this.map.appendChild(this.root); - this.map.appendChild(this.box); - this.map.appendChild(this.svg2nd); - this.map.appendChild(this.linkController); - this.map.appendChild(this.linkSvgGroup); - this.map.appendChild(this.P2); - this.map.appendChild(this.P3); - - // plugin - this.toolBar && toolBar(this); - this.nodeMenu && nodeMenu(this); - this.keypress && keypress(this); - - if (isMobile() && this.mobileMenu) { - mobileMenu(this); - } else { - this.contextMenu && contextMenu(this, this.contextMenuOption); - } - this.draggable && nodeDraggable(this); - - this.toCenter(); - this.layout(); - this.linkDiv(); - if (!this.overflowHidden) initMouseEvent(this); - }, - - update(data) { - const { linkData, nodeData } = data || MindElixir.new('新文档'); - if (linkData) { - this.linkData = linkData; - } - if (nodeData) { - this.nodeData = nodeData; - } - this.refresh(); - }, - - destroy: function () { - this.bus && this.bus.destroy(); - }, -}; - -MindElixir.LEFT = LEFT; -MindElixir.RIGHT = RIGHT; -MindElixir.SIDE = SIDE; -/** - * @memberof MindElixir - * @static - */ -MindElixir.version = '0.17.0'; -MindElixir.E = findEle; - -/** - * @function new - * @memberof MindElixir - * @static - * @param {String} topic root topic - */ -MindElixir.new = (topic: string): MindElixirData => ({ - nodeData: { - id: generateUUID(), - topic: topic || 'new topic', - root: true, - children: [], - }, - linkData: {}, -}); - -export default MindElixir; diff --git a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/interact.ts b/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/interact.ts deleted file mode 100644 index 5dd3ab4d..00000000 --- a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/interact.ts +++ /dev/null @@ -1,334 +0,0 @@ -import { findEle } from './utils/dom'; -/** - * @exports - - * workaround for jsdoc - */ -/** - * @exports MapInteraction - * @namespace MapInteraction - */ -function getData(instance) { - return instance.isFocusMode ? instance.nodeDataBackup : instance.nodeData; -} -/** - * @function - * @instance - * @name selectNode - * @memberof MapInteraction - * @description Select a node and add solid border to it. - * @param {TargetElement} el - Target element return by E('...'), default value: currentTarget. - */ -export const selectNode = function (targetElement, isNewNode, clickEvent) { - if (!targetElement) return; - console.time('selectNode'); - if (typeof targetElement === 'string') { - return this.selectNode(findEle(targetElement)); - } - if (this.currentNode) this.currentNode.classList.remove('selected'); - targetElement.classList.add('selected'); - this.currentNode = targetElement; - if (isNewNode) { - console.log('selectNewNode 1'); - this.bus.fire('selectNewNode', targetElement.nodeObj, clickEvent); - } else { - this.bus.fire('selectNode', targetElement.nodeObj, clickEvent); - } - console.timeEnd('selectNode'); -}; -export const unselectNode = function () { - if (this.currentNode) { - this.currentNode.classList.remove('selected'); - } - this.currentNode = null; - this.bus.fire('unselectNode'); -}; -export const selectNextSibling = function () { - if (!this.currentNode || this.currentNode.dataset.nodeid === 'meroot') return; - - const sibling = this.currentNode.parentElement.parentElement.nextSibling; - let target: HTMLElement; - const grp = this.currentNode.parentElement.parentElement; - if (grp.className === 'rhs' || grp.className === 'lhs') { - const siblingList = this.mindElixirBox.querySelectorAll('.' + grp.className); - const i = Array.from(siblingList).indexOf(grp); - if (i + 1 < siblingList.length) { - target = siblingList[i + 1].firstChild.firstChild; - } else { - return false; - } - } else if (sibling) { - target = sibling.firstChild.firstChild; - } else { - return false; - } - this.selectNode(target); - return true; -}; -export const selectPrevSibling = function () { - if (!this.currentNode || this.currentNode.dataset.nodeid === 'meroot') return; - - const sibling = this.currentNode.parentElement.parentElement.previousSibling; - let target; - const grp = this.currentNode.parentElement.parentElement; - if (grp.className === 'rhs' || grp.className === 'lhs') { - const siblingList = this.mindElixirBox.querySelectorAll('.' + grp.className); - const i = Array.from(siblingList).indexOf(grp); - if (i - 1 >= 0) { - target = siblingList[i - 1].firstChild.firstChild; - } else { - return false; - } - } else if (sibling) { - target = sibling.firstChild.firstChild; - } else { - return false; - } - this.selectNode(target); - return true; -}; -export const selectFirstChild = function () { - if (!this.currentNode) return; - const children = this.currentNode.parentElement.nextSibling; - if (children && children.firstChild) { - const target = children.firstChild.firstChild.firstChild; - this.selectNode(target); - } -}; -export const selectParent = function () { - if (!this.currentNode || this.currentNode.dataset.nodeid === 'meroot') return; - - const parent = this.currentNode.parentElement.parentElement.parentElement.previousSibling; - if (parent) { - const target = parent.firstChild; - this.selectNode(target); - } -}; -/** - * @function - * @instance - * @name getAllDataString - * @description Get all node data as string. - * @memberof MapInteraction - * @return {string} - */ -export const getAllDataString = function () { - const data = { - nodeData: getData(this), - linkData: this.linkData, - }; - return JSON.stringify(data, (k, v) => { - if (k === 'parent') return undefined; - if (k === 'from') return v.nodeObj.id; - if (k === 'to') return v.nodeObj.id; - return v; - }); -}; -/** - * @function - * @instance - * @name getAllData - * @description Get all node data as object. - * @memberof MapInteraction - * @return {Object} - */ -export const getAllData = function (): object { - const data = { - nodeData: getData(this), - linkData: this.linkData, - }; - return JSON.parse( - JSON.stringify(data, (k, v) => { - if (k === 'parent') return undefined; - if (k === 'from') return v.nodeObj.id; - if (k === 'to') return v.nodeObj.id; - return v; - }) - ); -}; - -/** - * @function - * @instance - * @name getAllDataMd - * @description Get all node data as markdown. - * @memberof MapInteraction - * @return {String} - */ -export const getAllDataMd = function (): string { - const data = getData(this); - let mdString = '# ' + data.topic + '\n\n'; - function writeMd(children, deep) { - for (let i = 0; i < children.length; i++) { - if (deep <= 6) { - mdString += ''.padStart(deep, '#') + ' ' + children[i].topic + '\n\n'; - } else { - mdString += ''.padStart(deep - 7, '\t') + '- ' + children[i].topic + '\n'; - } - if (children[i].children) { - writeMd(children[i].children, deep + 1); - } - } - } - writeMd(data.children, 2); - return mdString; -}; - -/** - * @function - * @instance - * @name enableEdit - * @memberof MapInteraction - */ -export const enableEdit = function () { - this.editable = true; -}; - -/** - * @function - * @instance - * @name disableEdit - * @memberof MapInteraction - */ -export const disableEdit = function () { - this.editable = false; -}; - -/** - * @function - * @instance - * @name scale - * @description Change the scale of the mind map. - * @memberof MapInteraction - * @param {number} - */ -export const scale = function (scaleVal) { - this.scaleVal = scaleVal; - this.map.style.transform = 'scale(' + scaleVal + ')'; -}; -/** - * @function - * @instance - * @name toCenter - * @description Reset position of the map to center. - * @memberof MapInteraction - */ -export const toCenter = function () { - this.container.scrollTo(10000 - this.container.offsetWidth / 2, 10000 - this.container.offsetHeight / 2); -}; -export const install = function (plugin) { - plugin(this); -}; -/** - * @function - * @instance - * @name focusNode - * @description Enter focus mode, set the target element as root. - * @memberof MapInteraction - * @param {TargetElement} el - Target element return by E('...'), default value: currentTarget. - */ -export const focusNode = function (tpcEl) { - if (tpcEl.nodeObj.root) return; - if (this.tempDirection === null) { - this.tempDirection = this.direction; - } - if (!this.isFocusMode) { - this.nodeDataBackup = this.nodeData; // help reset focus mode - this.isFocusMode = true; - } - this.nodeData = tpcEl.nodeObj; - this.nodeData.root = true; - this.initRight(); -}; -/** - * @function - * @instance - * @name cancelFocus - * @description Exit focus mode. - * @memberof MapInteraction - */ -export const cancelFocus = function () { - this.isFocusMode = false; - if (this.tempDirection !== null) { - delete this.nodeData.root; - this.nodeData = this.nodeDataBackup; - this.direction = this.tempDirection; - this.tempDirection = null; - this.init(); - } -}; -/** - * @function - * @instance - * @name initLeft - * @description Child nodes will distribute on the left side of the root node. - * @memberof MapInteraction - */ -export const initLeft = function () { - this.direction = 0; - this.init(); -}; -/** - * @function - * @instance - * @name initRight - * @description Child nodes will distribute on the right side of the root node. - * @memberof MapInteraction - */ -export const initRight = function () { - this.direction = 1; - this.init(); -}; -/** - * @function - * @instance - * @name initSide - * @description Child nodes will distribute on both left and right side of the root node. - * @memberof MapInteraction - */ -export const initSide = function () { - this.direction = 2; - this.init(); -}; - -/** - * @function - * @instance - * @name setLocale - * @memberof MapInteraction - */ -export const setLocale = function (locale) { - this.locale = locale; - this.init(); -}; - -export const expandNode = function (el, isExpand) { - const node = el.nodeObj; - if (typeof isExpand === 'boolean') { - node.expanded = isExpand; - } else if (node.expanded !== false) { - node.expanded = false; - } else { - node.expanded = true; - } - // TODO 在此函数构造 html 结构,而非调用 layout - this.layout(); - // linkDiv 已实现只更新特定主节点 - this.linkDiv(); - this.bus.fire('expandNode', node); -}; - -/** - * @function - * @instance - * @name refresh - * @description Refresh mind map, you can use it after modified `this.nodeData` - * @memberof MapInteraction - */ -export const refresh = function () { - // add parent property to every node - this.addParentLink(this.nodeData); - // create dom element for every node - this.layout(); - // generate links between nodes - this.linkDiv(); -}; diff --git a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/linkDiv.ts b/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/linkDiv.ts deleted file mode 100644 index 17705085..00000000 --- a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/linkDiv.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { GAP, PRIMARY_NODE_HORIZONTAL_GAP, PRIMARY_NODE_VERTICAL_GAP, SIDE, TURNPOINT_R } from './const'; -import { Expander, findEle } from './utils/dom'; -import { createLinkSvg, createMainPath, createPath } from './utils/svg'; - -/** - * Link nodes with svg, - * only link specific node if `primaryNode` is present - * - * procedure: - * 1. calculate position of primary nodes - * 2. layout primary node, generate primary link - * 3. generate links inside primary node - * 4. generate custom link - * @param {object} primaryNode process the specific primary node only - */ -export default function linkDiv(primaryNode) { - const primaryNodeHorizontalGap = this.primaryNodeHorizontalGap || PRIMARY_NODE_HORIZONTAL_GAP; - const primaryNodeVerticalGap = this.primaryNodeVerticalGap || PRIMARY_NODE_VERTICAL_GAP; - console.time('linkDiv'); - const root = this.root; - root.style.cssText = `top:${10000 - root.offsetHeight / 2}px;left:${10000 - root.offsetWidth / 2}px;`; - const primaryNodeList = this.box.children; - this.svg2nd.innerHTML = ''; - - // 1. calculate position of primary nodes - let totalHeight = 0; - let shortSide: string; // l or r - let shortSideGap = 0; // balance heigt of two side - let currentOffsetL = 0; // left side total offset - let currentOffsetR = 0; // right side total offset - let totalHeightL = 0; - let totalHeightR = 0; - let base: number; - - if (this.direction === SIDE) { - let countL = 0; - let countR = 0; - let totalHeightLWithoutGap = 0; - let totalHeightRWithoutGap = 0; - for (let i = 0; i < primaryNodeList.length; i++) { - const el = primaryNodeList[i]; - if (el.className === 'lhs') { - totalHeightL += el.offsetHeight + primaryNodeVerticalGap; - totalHeightLWithoutGap += el.offsetHeight; - countL += 1; - } else { - totalHeightR += el.offsetHeight + primaryNodeVerticalGap; - totalHeightRWithoutGap += el.offsetHeight; - countR += 1; - } - } - if (totalHeightL > totalHeightR) { - base = 10000 - Math.max(totalHeightL) / 2; - shortSide = 'r'; - shortSideGap = (totalHeightL - totalHeightRWithoutGap) / (countR - 1); - } else { - base = 10000 - Math.max(totalHeightR) / 2; - shortSide = 'l'; - shortSideGap = (totalHeightR - totalHeightLWithoutGap) / (countL - 1); - } - } else { - for (let i = 0; i < primaryNodeList.length; i++) { - const el = primaryNodeList[i]; - totalHeight += el.offsetHeight + primaryNodeVerticalGap; - } - base = 10000 - totalHeight / 2; - } - - // 2. layout primary node, generate primary link - let primaryPath = ''; - const alignRight = 10000 - root.offsetWidth / 2 - primaryNodeHorizontalGap; - const alignLeft = 10000 + root.offsetWidth / 2 + primaryNodeHorizontalGap; - for (let i = 0; i < primaryNodeList.length; i++) { - let x2, y2; - const el = primaryNodeList[i]; - const elOffsetH = el.offsetHeight; - if (el.className === 'lhs') { - el.style.top = base + currentOffsetL + 'px'; - el.style.left = alignRight - el.offsetWidth + 'px'; - x2 = alignRight - 15; // padding - y2 = base + currentOffsetL + elOffsetH / 2; - - // https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d#path_commands - let LEFT = 10000; - if (this.primaryLinkStyle === 2) { - if (this.direction === SIDE) { - LEFT = 10000 - root.offsetWidth / 6; - } - if (y2 < 10000) { - primaryPath += `M ${LEFT} 10000 V ${y2 + 20} C ${LEFT} ${y2} ${LEFT} ${y2} ${LEFT - 20} ${y2} H ${x2}`; - } else { - primaryPath += `M ${LEFT} 10000 V ${y2 - 20} C ${LEFT} ${y2} ${LEFT} ${y2} ${LEFT - 20} ${y2} H ${x2}`; - } - } else { - primaryPath += `M 10000 10000 C 10000 10000 ${10000 + 2 * primaryNodeHorizontalGap * 0.03} ${y2} ${x2} ${y2}`; - } - - if (shortSide === 'l') { - currentOffsetL += elOffsetH + shortSideGap; - } else { - currentOffsetL += elOffsetH + primaryNodeVerticalGap; - } - } else { - el.style.top = base + currentOffsetR + 'px'; - el.style.left = alignLeft + 'px'; - x2 = alignLeft + 15; // padding - y2 = base + currentOffsetR + elOffsetH / 2; - - let LEFT = 10000; - if (this.primaryLinkStyle === 2) { - if (this.direction === SIDE) { - LEFT = 10000 + root.offsetWidth / 6; - } - if (y2 < 10000) { - primaryPath += `M ${LEFT} 10000 V ${y2 + 20} C ${LEFT} ${y2} ${LEFT} ${y2} ${LEFT + 20} ${y2} H ${x2}`; - } else { - primaryPath += `M ${LEFT} 10000 V ${y2 - 20} C ${LEFT} ${y2} ${LEFT} ${y2} ${LEFT + 20} ${y2} H ${x2}`; - } - } else { - primaryPath += `M 10000 10000 C 10000 10000 ${10000 + 2 * primaryNodeHorizontalGap * 0.03} ${y2} ${x2} ${y2}`; - } - if (shortSide === 'r') { - currentOffsetR += elOffsetH + shortSideGap; - } else { - currentOffsetR += elOffsetH + primaryNodeVerticalGap; - } - } - // set position of expander - const expander = el.children[0].children[1]; - if (expander) { - expander.style.top = (expander.parentNode.offsetHeight - expander.offsetHeight) / 2 + 'px'; - if (el.className === 'lhs') { - expander.style.left = -10 + 'px'; - } else { - expander.style.left = expander.parentNode.offsetWidth - 10 + 'px'; - } - } - } - this.svg2nd.appendChild(createMainPath(primaryPath)); - - // 3. generate link inside primary node - for (let i = 0; i < primaryNodeList.length; i++) { - const el = primaryNodeList[i]; - if (primaryNode && primaryNode !== primaryNodeList[i]) { - continue; - } - if (el.childElementCount) { - const svg = createLinkSvg('svg3rd'); - // svg tag name is lower case - if (el.lastChild.tagName === 'svg') el.lastChild.remove(); - el.appendChild(svg); - const parent = el.children[0]; - const children = el.children[1].children; - path = ''; - loopChildren(children, parent, true); - svg.appendChild(createPath(path)); - } - } - - // 4. generate custom link - this.linkSvgGroup.innerHTML = ''; - for (const prop in this.linkData) { - const link = this.linkData[prop]; - if (typeof link.from === 'string') { - this.createLink(findEle(link.from), findEle(link.to), true, link); - } else { - this.createLink(findEle(link.from.nodeObj.id), findEle(link.to.nodeObj.id), true, link); - } - } - console.timeEnd('linkDiv'); -} - -let path = ''; -function loopChildren(children: HTMLCollection, parent: HTMLElement, first?: boolean) { - const parentOT = parent.offsetTop; - const parentOL = parent.offsetLeft; - const parentOW = parent.offsetWidth; - const parentOH = parent.offsetHeight; - for (let i = 0; i < children.length; i++) { - const child: HTMLElement = children[i] as HTMLElement; - const childT: HTMLElement = child.children[0] as HTMLElement; // t tag inside the child dom - const childTOT = childT.offsetTop; - const childTOH = childT.offsetHeight; - let y1: number; - if (first) { - y1 = parentOT + parentOH / 2; - } else { - y1 = parentOT + parentOH; - } - const y2 = childTOT + childTOH; - let x1: number, x2: number, xMiddle: number; - const direction = child.offsetParent.className; - if (direction === 'lhs') { - x1 = parentOL + GAP; - xMiddle = parentOL; - x2 = parentOL - childT.offsetWidth; - - if (childTOT + childTOH < parentOT + parentOH / 2 + 50 && childTOT + childTOH > parentOT + parentOH / 2 - 50) { - // 相差+-50内直接直线 - path += `M ${x1} ${y1} H ${xMiddle} V ${y2} H ${x2}`; - } else if (childTOT + childTOH >= parentOT + parentOH / 2) { - // 子底部低于父中点 - path += `M ${x1} ${y1} H ${xMiddle} V ${y2 - TURNPOINT_R} A ${TURNPOINT_R} ${TURNPOINT_R} 0 0 1 ${ - xMiddle - TURNPOINT_R - } ${y2} H ${x2}`; - } else { - // 子底部高于父中点 - path += `M ${x1} ${y1} H ${xMiddle} V ${y2 + TURNPOINT_R} A ${TURNPOINT_R} ${TURNPOINT_R} 0 0 0 ${ - xMiddle - TURNPOINT_R - } ${y2} H ${x2}`; - } - } else if (direction === 'rhs') { - x1 = parentOL + parentOW - GAP; - xMiddle = parentOL + parentOW; - x2 = parentOL + parentOW + childT.offsetWidth; - - if (childTOT + childTOH < parentOT + parentOH / 2 + 50 && childTOT + childTOH > parentOT + parentOH / 2 - 50) { - path += `M ${x1} ${y1} H ${xMiddle} V ${y2} H ${x2}`; - } else if (childTOT + childTOH >= parentOT + parentOH / 2) { - path += `M ${x1} ${y1} H ${xMiddle} V ${y2 - TURNPOINT_R} A ${TURNPOINT_R} ${TURNPOINT_R} 0 0 0 ${ - xMiddle + TURNPOINT_R - } ${y2} H ${x2}`; - } else { - path += `M ${x1} ${y1} H ${xMiddle} V ${y2 + TURNPOINT_R} A ${TURNPOINT_R} ${TURNPOINT_R} 0 0 1 ${ - xMiddle + TURNPOINT_R - } ${y2} H ${x2}`; - } - } - - const expander = childT.children[1] as Expander; - if (expander) { - expander.style.top = (childT.offsetHeight - expander.offsetHeight) / 2 + 'px'; - if (direction === 'lhs') { - expander.style.left = -10 + 'px'; - } else if (direction === 'rhs') { - expander.style.left = childT.offsetWidth - 10 + 'px'; - } - // this property is added in the layout phase - if (!expander.expanded) continue; - } else { - // expander not exist - continue; - } - // traversal - const nextChildren = child.children[1].children; - if (nextChildren.length > 0) loopChildren(nextChildren, childT); - } -} diff --git a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/mouse.ts b/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/mouse.ts deleted file mode 100644 index 3bee4152..00000000 --- a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/mouse.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { dragMoveHelper } from './utils/index'; - -export default function (mind) { - const onClick = (e) => { - // if (dragMoveHelper.afterMoving) return - // e.preventDefault() // can cause a tag don't work - if (e.target.nodeName === 'EPD') { - mind.expandNode(e.target.previousSibling); - } else if (e.target.parentElement.nodeName === 'T' || e.target.parentElement.nodeName === 'ROOT') { - mind.selectNode(e.target, false, e); - } else if (e.target.nodeName === 'path') { - if (e.target.parentElement.nodeName === 'g') { - mind.selectLink(e.target.parentElement); - } - } else if (e.target.className === 'circle') { - // skip circle - } else { - mind.unselectNode(); - mind.hideLinkController(); - } - }; - - const onDbClick = (e) => { - e.preventDefault(); - if (!mind.editable) return; - if (e.target.parentElement.nodeName === 'T' || e.target.parentElement.nodeName === 'ROOT') { - mind.beginEdit(e.target); - } - }; - - const onMouseMove = (e) => { - // click trigger mousemove in windows chrome - // the 'true' is a string - if (e.target.contentEditable !== 'true') { - dragMoveHelper.onMove(e, mind.container); - } - }; - - const onMouseDown = (e) => { - if (e.target.contentEditable !== 'true') { - dragMoveHelper.afterMoving = false; - dragMoveHelper.mousedown = true; - } - }; - - const onMouseLeave = (e) => { - dragMoveHelper.clear(); - }; - - const onMouseUp = (e) => { - dragMoveHelper.clear(); - }; - - mind.map.addEventListener('click', onClick); - mind.map.addEventListener('dblclick', onDbClick); - mind.map.addEventListener('mousemove', onMouseMove); - mind.map.addEventListener('mousedown', onMouseDown); - mind.map.addEventListener('mouseleave', onMouseLeave); - mind.map.addEventListener('mouseup', onMouseUp); - - mind.bus.addListener('destroy', () => { - mind.map.removeEventListener('click', onClick); - mind.map.removeEventListener('dblclick', onDbClick); - mind.map.removeEventListener('mousemove', onMouseMove); - mind.map.removeEventListener('mousedown', onMouseDown); - mind.map.removeEventListener('mouseleave', onMouseLeave); - mind.map.removeEventListener('mouseup', onMouseUp); - }); -} diff --git a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/nodeOperation.ts b/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/nodeOperation.ts deleted file mode 100644 index 16f525f1..00000000 --- a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/nodeOperation.ts +++ /dev/null @@ -1,607 +0,0 @@ -import { LEFT, RIGHT, SIDE } from './const'; -import type { NodeElement, NodeObj } from './index'; -import { createExpander, findEle, shapeTpc } from './utils/dom'; -import { - addParentLink, - checkMoveValid, - insertBeforeNodeObj, - insertNodeObj, - insertParentNodeObj, - moveDownObj, - moveNodeAfterObj, - moveNodeBeforeObj, - moveNodeObj, - moveUpObj, - refreshIds, - removeNodeObj, -} from './utils/index'; -import { rgbHex } from './utils/index'; -const $d = document; - -/** - * @exports NodeOperation - * @namespace NodeOperation - */ -export const updateNodeStyle = function (object) { - if (!object.style) return; - const nodeEle = findEle(object.id, this); - const origin = { - color: nodeEle.style.color && rgbHex(nodeEle.style.color), - background: nodeEle.style.background && rgbHex(nodeEle.style.background), - fontSize: nodeEle.style.fontSize && nodeEle.style.fontSize + 'px', - fontWeight: nodeEle.style.fontWeight, - }; - nodeEle.style.color = object.style.color; - nodeEle.style.background = object.style.background; - nodeEle.style.fontSize = object.style.fontSize + 'px'; - nodeEle.style.fontWeight = object.style.fontWeight || 'normal'; - this.linkDiv(); - this.bus.fire('operation', { - name: 'editStyle', - obj: object, - origin, - }); -}; - -export const updateNodeTags = function (object, tags) { - if (!tags) return; - const oldVal = object.tags; - object.tags = tags; - const nodeEle = findEle(object.id); - shapeTpc(nodeEle, object); - this.linkDiv(); - this.bus.fire('operation', { - name: 'editTags', - obj: object, - origin: oldVal, - }); -}; - -export const updateNodeIcons = function (object, icons) { - if (!icons) return; - const oldVal = object.icons; - object.icons = icons; - const nodeEle = findEle(object.id); - shapeTpc(nodeEle, object); - this.linkDiv(); - this.bus.fire('operation', { - name: 'editIcons', - obj: object, - origin: oldVal, - }); -}; - -export const updateNodeHyperLink = function (object, hyperLink) { - const nodeEle = findEle(object.id); - - if (!hyperLink) { - const linkEle = nodeEle.querySelector('a.hyper-link') as HTMLElement; - if (linkEle) { - linkEle.parentNode.removeChild(linkEle); - } - } else { - const oldVal = object.hyperLink; - object.hyperLink = hyperLink; - shapeTpc(nodeEle, object); - this.linkDiv(); - this.bus.fire('operation', { - name: 'editHyperLink', - obj: object, - origin: oldVal, - }); - } -}; - -export const updateNodeSvgChart = function () { - // TODO -}; - -/** - * @function - * @instance - * @name insertSibling - * @memberof NodeOperation - * @description Create a sibling node. - * @param {TargetElement} el - Target element return by E('...'), default value: currentTarget. - * @param {node} node - New node information. - * @example - * insertSibling(E('bd4313fbac40284b')) - */ -export const insertSibling = function (el, node) { - const nodeEle = el || this.currentNode; - if (!nodeEle) return; - const nodeObj = nodeEle.nodeObj; - if (nodeObj.root === true) { - this.addChild(); - return; - } - const newNodeObj = node || this.generateNewObj(); - insertNodeObj(nodeObj, newNodeObj); - addParentLink(this.nodeData); - const t = nodeEle.parentElement; - console.time('insertSibling_DOM'); - - const { grp, top } = this.createGroup(newNodeObj); - - const children = t.parentNode.parentNode; - children.insertBefore(grp, t.parentNode.nextSibling); - if (children.className === 'box') { - this.processPrimaryNode(grp, newNodeObj); - this.linkDiv(); - } else { - this.linkDiv(grp.offsetParent); - } - if (!node) { - this.createInputDiv(top.children[0]); - } - this.selectNode(top.children[0], true); - console.timeEnd('insertSibling_DOM'); - this.bus.fire('operation', { - name: 'insertSibling', - obj: newNodeObj, - }); -}; - -/** - * @function - * @instance - * @name insertBefore - * @memberof NodeOperation - * @description Create a sibling node before the selected node. - * @param {TargetElement} el - Target element return by E('...'), default value: currentTarget. - * @param {node} node - New node information. - * @example - * insertBefore(E('bd4313fbac40284b')) - */ -export const insertBefore = function (el, node) { - const nodeEle = el || this.currentNode; - if (!nodeEle) return; - const nodeObj = nodeEle.nodeObj; - if (nodeObj.root === true) { - this.addChild(); - return; - } - const newNodeObj = node || this.generateNewObj(); - insertBeforeNodeObj(nodeObj, newNodeObj); - addParentLink(this.nodeData); - const t = nodeEle.parentElement; - console.time('insertSibling_DOM'); - - const { grp, top } = this.createGroup(newNodeObj); - - const children = t.parentNode.parentNode; - children.insertBefore(grp, t.parentNode); - if (children.className === 'box') { - this.processPrimaryNode(grp, newNodeObj); - this.linkDiv(); - } else { - this.linkDiv(grp.offsetParent); - } - if (!node) { - this.createInputDiv(top.children[0]); - } - this.selectNode(top.children[0], true); - console.timeEnd('insertSibling_DOM'); - this.bus.fire('operation', { - name: 'insertSibling', - obj: newNodeObj, - }); -}; - -/** - * @function - * @instance - * @name insertParent - * @memberof NodeOperation - * @description Create a parent node of the selected node. - * @param {TargetElement} el - Target element return by E('...'), default value: currentTarget. - * @param {node} node - New node information. - * @example - * insertParent(E('bd4313fbac40284b')) - */ -export const insertParent = function (el, node) { - const nodeEle = el || this.currentNode; - if (!nodeEle) return; - const nodeObj = nodeEle.nodeObj; - if (nodeObj.root === true) { - return; - } - const newNodeObj = node || this.generateNewObj(); - insertParentNodeObj(nodeObj, newNodeObj); - addParentLink(this.nodeData); - const grp0 = nodeEle.parentElement.parentElement; - console.time('insertParent_DOM'); - const { grp, top } = this.createGroup(newNodeObj, true); - const children = grp0.parentNode; - children.insertBefore(grp, grp0.nextSibling); - - const c = $d.createElement('children'); - c.appendChild(grp0); - top.appendChild(createExpander(true)); - top.parentElement.insertBefore(c, top.nextSibling); - - if (children.className === 'box') { - grp.className = grp0.className; - grp0.className = ''; - grp0.querySelector('.svg3rd').remove(); - this.linkDiv(); - } else { - this.linkDiv(grp.offsetParent); - } - - if (!node) { - this.createInputDiv(top.children[0]); - } - this.selectNode(top.children[0], true); - console.timeEnd('insertParent_DOM'); - this.bus.fire('operation', { - name: 'insertParent', - obj: newNodeObj, - }); -}; - -export const addChildFunction = function (nodeEle, node) { - if (!nodeEle) return; - const nodeObj = nodeEle.nodeObj; - if (nodeObj.expanded === false) { - this.expandNode(nodeEle, true); - // dom had resetted - nodeEle = findEle(nodeObj.id); - } - const newNodeObj = node || this.generateNewObj(); - if (nodeObj.children) nodeObj.children.push(newNodeObj); - else nodeObj.children = [newNodeObj]; - addParentLink(this.nodeData); - - const top = nodeEle.parentElement; - - const { grp, top: newTop } = this.createGroup(newNodeObj); - - if (top.tagName === 'T') { - if (top.children[1]) { - top.nextSibling.appendChild(grp); - } else { - const c = $d.createElement('children'); - c.appendChild(grp); - top.appendChild(createExpander(true)); - top.parentElement.insertBefore(c, top.nextSibling); - } - this.linkDiv(grp.offsetParent); - } else if (top.tagName === 'ROOT') { - this.processPrimaryNode(grp, newNodeObj); - top.nextSibling.appendChild(grp); - this.linkDiv(); - } - return { newTop, newNodeObj }; -}; - -/** - * @function - * @instance - * @name addChild - * @memberof NodeOperation - * @description Create a child node. - * @param {TargetElement} el - Target element return by E('...'), default value: currentTarget. - * @param {node} node - New node information. - * @example - * addChild(E('bd4313fbac40284b')) - */ -export const addChild = function (el: NodeElement, node: NodeObj) { - console.time('addChild'); - const nodeEle = el || this.currentNode; - if (!nodeEle) return; - const { newTop, newNodeObj } = addChildFunction.call(this, nodeEle, node); - console.timeEnd('addChild'); - if (!node) { - this.createInputDiv(newTop.children[0]); - } - this.selectNode(newTop.children[0], true); - this.bus.fire('operation', { - name: 'addChild', - obj: newNodeObj, - }); -}; -// uncertain link disappear sometimes?? -// TODO while direction = SIDE, move up won't change the direction of primary node - -/** - * @function - * @instance - * @name copyNode - * @memberof NodeOperation - * @description Copy node to another node. - * @param {TargetElement} node - Target element return by E('...'), default value: currentTarget. - * @param {TargetElement} to - The target(as parent node) you want to copy to. - * @example - * copyNode(E('bd4313fbac402842'),E('bd4313fbac402839')) - */ -export const copyNode = function (node: NodeElement, to: NodeElement) { - console.time('copyNode'); - const deepCloneObj = JSON.parse( - JSON.stringify(node.nodeObj, (k, v) => { - if (k === 'parent') return undefined; - return v; - }) - ); - refreshIds(deepCloneObj); - const { newNodeObj } = addChildFunction.call(this, to, deepCloneObj); - console.timeEnd('copyNode'); - this.bus.fire('operation', { - name: 'copyNode', - obj: newNodeObj, - }); -}; - -/** - * @function - * @instance - * @name moveUpNode - * @memberof NodeOperation - * @description Move the target node up. - * @param {TargetElement} el - Target element return by E('...'), default value: currentTarget. - * @example - * moveUpNode(E('bd4313fbac40284b')) - */ -export const moveUpNode = function (el) { - const nodeEle = el || this.currentNode; - if (!nodeEle) return; - const grp = nodeEle.parentNode.parentNode; - const obj = nodeEle.nodeObj; - moveUpObj(obj); - grp.parentNode.insertBefore(grp, grp.previousSibling); - this.linkDiv(); - this.bus.fire('operation', { - name: 'moveUpNode', - obj, - }); -}; - -/** - * @function - * @instance - * @name moveDownNode - * @memberof NodeOperation - * @description Move the target node down. - * @param {TargetElement} el - Target element return by E('...'), default value: currentTarget. - * @example - * moveDownNode(E('bd4313fbac40284b')) - */ -export const moveDownNode = function (el) { - const nodeEle = el || this.currentNode; - if (!nodeEle) return; - const grp = nodeEle.parentNode.parentNode; - const obj = nodeEle.nodeObj; - moveDownObj(obj); - if (grp.nextSibling) { - grp.parentNode.insertBefore(grp, grp.nextSibling.nextSibling); - } else { - grp.parentNode.prepend(grp); - } - this.linkDiv(); - this.bus.fire('operation', { - name: 'moveDownNode', - obj, - }); -}; - -/** - * @function - * @instance - * @name removeNode - * @memberof NodeOperation - * @description Remove the target node. - * @param {TargetElement} el - Target element return by E('...'), default value: currentTarget. - * @example - * removeNode(E('bd4313fbac40284b')) - */ -export const removeNode = function (el) { - const nodeEle = el || this.currentNode; - if (!nodeEle) return; - const nodeObj = nodeEle.nodeObj; - if (nodeObj.root === true) { - throw new Error('Can not remove root node'); - } - const index = nodeObj.parent.children.findIndex((node) => node === nodeObj); - const next = nodeObj.parent.children[index + 1]; - const originSiblingId = next && next.id; - - const childrenLength = removeNodeObj(nodeObj); - const t = nodeEle.parentNode; - if (t.tagName === 'ROOT') { - return; - } - this.bus.fire('removeNode', nodeObj); - if (childrenLength === 0) { - // remove epd when children length === 0 - const parentT = t.parentNode.parentNode.previousSibling; - // root doesn't have epd - if (parentT.tagName !== 'ROOT') { - parentT.children[1].remove(); - } - this.selectParent(); - } else { - // select sibling automatically - const success = this.selectPrevSibling(); - if (!success) this.selectNextSibling(); - } - for (const prop in this.linkData) { - // MAYBEBUG should traversal all children node - const link = this.linkData[prop]; - if (link.from === t.firstChild || link.to === t.firstChild) { - this.removeLink(this.mindElixirBox.querySelector(`[data-linkid=${this.linkData[prop].id}]`)); - } - } - // remove GRP - t.parentNode.remove(); - this.linkDiv(); - this.bus.fire('operation', { - name: 'removeNode', - obj: nodeObj, - originSiblingId, - originParentId: nodeObj.parent.id, - }); -}; - -/** - * @function - * @instance - * @name moveNode - * @memberof NodeOperation - * @description Move a node to another node (as child node). - * @param {TargetElement} from - The target you want to move. - * @param {TargetElement} to - The target(as parent node) you want to move to. - * @example - * moveNode(E('bd4313fbac402842'),E('bd4313fbac402839')) - */ -export const moveNode = function (from, to) { - const fromObj = from.nodeObj; - const toObj = to.nodeObj; - const originParentId = fromObj.parent.id; - if (toObj.expanded === false) { - this.expandNode(to, true); - from = findEle(fromObj.id); - to = findEle(toObj.id); - } - if (!checkMoveValid(fromObj, toObj)) { - console.warn('Invalid move'); - return; - } - console.time('moveNode'); - moveNodeObj(fromObj, toObj); - addParentLink(this.nodeData); // update parent property - const fromTop = from.parentElement; - const fromChilren = fromTop.parentNode.parentNode; - const toTop = to.parentElement; - if (fromChilren.className === 'box') { - // clear svg group of primary node - fromTop.parentNode.lastChild.remove(); - } else if (fromTop.parentNode.className === 'box') { - fromTop.style.cssText = ''; // clear style - } - if (toTop.tagName === 'T') { - if (fromChilren.className === 'box') { - // clear direaction class of primary node - fromTop.parentNode.className = ''; - } - if (toTop.children[1]) { - // expander exist - toTop.nextSibling.appendChild(fromTop.parentNode); - } else { - // expander not exist, no child - const c = $d.createElement('children'); - c.appendChild(fromTop.parentNode); - toTop.appendChild(createExpander(true)); - toTop.parentElement.insertBefore(c, toTop.nextSibling); - } - } else if (toTop.tagName === 'ROOT') { - this.processPrimaryNode(fromTop.parentNode, fromObj); - toTop.nextSibling.appendChild(fromTop.parentNode); - } - this.linkDiv(); - this.bus.fire('operation', { - name: 'moveNode', - obj: { fromObj, toObj, originParentId }, - }); - console.timeEnd('moveNode'); -}; - -/** - * @function - * @instance - * @name moveNodeBefore - * @memberof NodeOperation - * @description Move a node and become previous node of another node. - * @param {TargetElement} from - * @param {TargetElement} to - * @example - * moveNodeBefore(E('bd4313fbac402842'),E('bd4313fbac402839')) - */ -export const moveNodeBefore = function (from, to) { - const fromObj = from.nodeObj; - const toObj = to.nodeObj; - const originParentId = fromObj.parent.id; - moveNodeBeforeObj(fromObj, toObj); - addParentLink(this.nodeData); - const fromTop = from.parentElement; - const fromGrp = fromTop.parentNode; - const toTop = to.parentElement; - const toGrp = toTop.parentNode; - const toChilren = toTop.parentNode.parentNode; - toChilren.insertBefore(fromGrp, toGrp); - this.linkDiv(); - this.bus.fire('operation', { - name: 'moveNodeBefore', - obj: { fromObj, toObj, originParentId }, - }); -}; - -/** - * @function - * @instance - * @name moveNodeAfter - * @memberof NodeOperation - * @description Move a node and become next node of another node. - * @param {TargetElement} from - * @param {TargetElement} to - * @example - * moveNodeAfter(E('bd4313fbac402842'),E('bd4313fbac402839')) - */ -export const moveNodeAfter = function (from, to) { - const fromObj = from.nodeObj; - const toObj = to.nodeObj; - const originParentId = fromObj.parent.id; - moveNodeAfterObj(fromObj, toObj); - addParentLink(this.nodeData); - const fromTop = from.parentElement; - const fromGrp = fromTop.parentNode; - const toTop = to.parentElement; - const toGrp = toTop.parentNode; - const toChilren = toTop.parentNode.parentNode; - toChilren.insertBefore(fromGrp, toGrp.nextSibling); - this.linkDiv(); - this.bus.fire('operation', { - name: 'moveNodeAfter', - obj: { fromObj, toObj, originParentId }, - }); -}; - -/** - * @function - * @instance - * @name beginEdit - * @memberof NodeOperation - * @description Begin to edit the target node. - * @param {TargetElement} el - Target element return by E('...'), default value: currentTarget. - * @example - * beginEdit(E('bd4313fbac40284b')) - */ -export const beginEdit = function (el) { - const nodeEle = el || this.currentNode; - if (!nodeEle) return; - this.createInputDiv(nodeEle); -}; - -export const setNodeTopic = function (tpc, topic) { - tpc.childNodes[0].textContent = topic; - tpc.nodeObj.topic = topic; - this.linkDiv(); -}; - -// Judge L or R -export function processPrimaryNode(primaryNode, obj) { - if (this.direction === LEFT) { - primaryNode.className = 'lhs'; - } else if (this.direction === RIGHT) { - primaryNode.className = 'rhs'; - } else if (this.direction === SIDE) { - const l = $d.querySelectorAll('.lhs').length; - const r = $d.querySelectorAll('.rhs').length; - if (l <= r) { - primaryNode.className = 'lhs'; - obj.direction = LEFT; - } else { - primaryNode.className = 'rhs'; - obj.direction = RIGHT; - } - } -} diff --git a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/plugin/contextMenu.ts b/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/plugin/contextMenu.ts deleted file mode 100644 index 10073f7f..00000000 --- a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/plugin/contextMenu.ts +++ /dev/null @@ -1,167 +0,0 @@ -import i18n from '../i18n'; -import { encodeHTML } from '../utils/index'; - -export default function (mind, option) { - const createTips = (words) => { - const div = document.createElement('div'); - div.innerText = words; - div.style.cssText = 'position:absolute;bottom:20px;left:50%;transform:translateX(-50%);'; - return div; - }; - const createLi = (id, name, keyname) => { - const li = document.createElement('li'); - li.id = id; - li.innerHTML = `${encodeHTML(name)}${encodeHTML(keyname)}`; - return li; - }; - const locale = i18n[mind.locale] ? mind.locale : 'en'; - - const add_child = createLi('cm-add_child', i18n[locale].addChild, 'tab'); - const add_parent = createLi('cm-add_parent', i18n[locale].addParent, ''); - const add_sibling = createLi('cm-add_sibling', i18n[locale].addSibling, 'enter'); - const remove_child = createLi('cm-remove_child', i18n[locale].removeNode, 'delete'); - const focus = createLi('cm-fucus', i18n[locale].focus, ''); - const unfocus = createLi('cm-unfucus', i18n[locale].cancelFocus, ''); - const up = createLi('cm-up', i18n[locale].moveUp, 'PgUp'); - const down = createLi('cm-down', i18n[locale].moveDown, 'Pgdn'); - const link = createLi('cm-down', i18n[locale].link, ''); - - const menuUl = document.createElement('ul'); - menuUl.className = 'menu-list'; - menuUl.appendChild(add_child); - menuUl.appendChild(add_parent); - menuUl.appendChild(add_sibling); - menuUl.appendChild(remove_child); - if (!option || option.focus) { - menuUl.appendChild(focus); - menuUl.appendChild(unfocus); - } - menuUl.appendChild(up); - menuUl.appendChild(down); - if (!option || option.link) { - menuUl.appendChild(link); - } - if (option && option.extend) { - for (let i = 0; i < option.extend.length; i++) { - const item = option.extend[i]; - const dom = createLi(item.name, item.name, item.key || ''); - menuUl.appendChild(dom); - dom.onclick = (e) => { - item.onclick(e); - }; - } - } - const menuContainer = document.createElement('cmenu'); - menuContainer.appendChild(menuUl); - menuContainer.hidden = true; - - mind.container.append(menuContainer); - let isRoot = true; - mind.container.oncontextmenu = function (e) { - e.preventDefault(); - if (!mind.editable) return; - // console.log(e.pageY, e.screenY, e.clientY) - const target = e.target; - if (target.tagName === 'TPC') { - if (target.parentElement.tagName === 'ROOT') { - isRoot = true; - } else { - isRoot = false; - } - if (isRoot) { - focus.className = 'disabled'; - up.className = 'disabled'; - down.className = 'disabled'; - add_sibling.className = 'disabled'; - remove_child.className = 'disabled'; - } else { - focus.className = ''; - up.className = ''; - down.className = ''; - add_sibling.className = ''; - remove_child.className = ''; - } - mind.selectNode(target); - menuContainer.hidden = false; - const height = menuUl.offsetHeight; - const width = menuUl.offsetWidth; - if (height + e.clientY > window.innerHeight) { - menuUl.style.top = ''; - menuUl.style.bottom = '0px'; - } else { - menuUl.style.bottom = ''; - menuUl.style.top = e.clientY + 15 + 'px'; - } - if (width + e.clientX > window.innerWidth) { - menuUl.style.left = ''; - menuUl.style.right = '0px'; - } else { - menuUl.style.right = ''; - menuUl.style.left = e.clientX + 10 + 'px'; - } - } - }; - - menuContainer.onclick = (e) => { - if (e.target === menuContainer) menuContainer.hidden = true; - }; - - add_child.onclick = (e) => { - mind.addChild(); - menuContainer.hidden = true; - }; - add_parent.onclick = (e) => { - mind.insertParent(); - menuContainer.hidden = true; - }; - add_sibling.onclick = (e) => { - if (isRoot) return; - mind.insertSibling(); - menuContainer.hidden = true; - }; - remove_child.onclick = (e) => { - if (isRoot) return; - mind.removeNode(); - menuContainer.hidden = true; - }; - focus.onclick = (e) => { - if (isRoot) return; - mind.focusNode(mind.currentNode); - menuContainer.hidden = true; - }; - unfocus.onclick = (e) => { - mind.cancelFocus(); - menuContainer.hidden = true; - }; - up.onclick = (e) => { - if (isRoot) return; - mind.moveUpNode(); - menuContainer.hidden = true; - }; - down.onclick = (e) => { - if (isRoot) return; - mind.moveDownNode(); - menuContainer.hidden = true; - }; - link.onclick = (e) => { - menuContainer.hidden = true; - const from = mind.currentNode; - const tips = createTips(i18n[locale].clickTips); - mind.container.appendChild(tips); - mind.map.addEventListener( - 'click', - (e) => { - e.preventDefault(); - tips.remove(); - if (e.target.parentElement.nodeName === 'T' || e.target.parentElement.nodeName === 'ROOT') { - mind.createLink(from, mind.currentNode); - } else { - console.log('取消连接'); - } - }, - { - once: true, - } - ); - }; -} diff --git a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/plugin/keypress.ts b/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/plugin/keypress.ts deleted file mode 100644 index edd04862..00000000 --- a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/plugin/keypress.ts +++ /dev/null @@ -1,100 +0,0 @@ -export default function (mind) { - const key2func = { - 13: () => { - // enter - mind.insertSibling(); - }, - 9: () => { - // tab - mind.addChild(); - }, - 113: () => { - // f2 - mind.beginEdit(); - }, - 38: () => { - // up - mind.selectPrevSibling(); - }, - 40: () => { - // down - mind.selectNextSibling(); - }, - 37: () => { - // left - if (!mind.currentNode) return; - if (mind.currentNode.offsetParent.offsetParent.className === 'rhs') { - mind.selectParent(); - } else if (mind.currentNode.offsetParent.offsetParent.className === 'lhs' || mind.currentNode.nodeObj.root) { - mind.selectFirstChild(); - } - }, - 39: () => { - // right - if (!mind.currentNode) return; - if (mind.currentNode.offsetParent.offsetParent.className === 'rhs' || mind.currentNode.nodeObj.root) { - mind.selectFirstChild(); - } else if (mind.currentNode.offsetParent.offsetParent.className === 'lhs') { - mind.selectParent(); - } - }, - 33() { - // pageUp - mind.moveUpNode(); - }, - 34() { - // pageDown - mind.moveDownNode(); - }, - 67(e) { - if (e.metaKey || e.ctrlKey) { - // ctrl c - mind.waitCopy = mind.currentNode; - } - }, - 86(e) { - if (!mind.waitCopy) return; - if (e.metaKey || e.ctrlKey) { - // ctrl v - mind.copyNode(mind.waitCopy, mind.currentNode); - mind.waitCopy = null; - } - }, - // ctrl z - 90: (e) => { - if (!mind.allowUndo) return; - if (e.metaKey || e.ctrlKey) mind.undo(); - }, - // ctrl + - 187: (e) => { - if (e.metaKey || e.ctrlKey) { - if (mind.scaleVal > 1.6) return; - mind.scale((mind.scaleVal += 0.2)); - } - }, - // ctrl - - 189: (e) => { - if (e.metaKey || e.ctrlKey) { - if (mind.scaleVal < 0.6) return; - mind.scale((mind.scaleVal -= 0.2)); - } - }, - }; - - document.addEventListener('keydown', (e) => { - if (mind.isInnerEditing) return; // 脑图内部操作 - if (!mind.editable) return; - - if (mind.shouldPreventDefault && mind.shouldPreventDefault()) { - e.preventDefault(); - } - - if (e.keyCode === 8 || e.keyCode === 46) { - // del,backspace - if (mind.currentLink) mind.removeLink(); - else mind.removeNode(); - } else { - key2func[e.keyCode] && key2func[e.keyCode](e); - } - }); -} diff --git a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/plugin/mobileMenu.ts b/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/plugin/mobileMenu.ts deleted file mode 100644 index f29181c5..00000000 --- a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/plugin/mobileMenu.ts +++ /dev/null @@ -1,80 +0,0 @@ -export default function (mind, option?) { - const createLi = (id, name) => { - const div = document.createElement('div'); - div.id = id; - div.innerHTML = ``; - return div; - }; - - const add_child = createLi('cm-add_child', 'zijiedian'); - const add_sibling = createLi('cm-add_sibling', 'tongjijiedian-'); - const remove_child = createLi('cm-remove_child', 'shanchu2'); - const up = createLi('cm-up', 'rising'); - const down = createLi('cm-down', 'falling'); - const edit = createLi('cm-edit', 'edit'); - - const menuUl = document.createElement('ul'); - menuUl.className = 'menu-list'; - - if (option && option.extend) { - for (let i = 0; i < option.extend.length; i++) { - const item = option.extend[i]; - const dom = createLi(item.name, item.name); - menuUl.appendChild(dom); - dom.onclick = (e) => { - item.onclick(e); - }; - } - } - const menuContainer = document.createElement('mmenu'); - menuContainer.appendChild(add_child); - menuContainer.appendChild(add_sibling); - menuContainer.appendChild(remove_child); - menuContainer.appendChild(up); - menuContainer.appendChild(down); - menuContainer.appendChild(edit); - menuContainer.hidden = true; - - mind.container.append(menuContainer); - let isRoot = true; - - mind.bus.addListener('unselectNode', function () { - menuContainer.hidden = true; - }); - mind.bus.addListener('selectNode', function (nodeObj) { - menuContainer.hidden = false; - if (nodeObj.root) { - isRoot = true; - } else { - isRoot = false; - } - }); - menuContainer.onclick = (e) => { - if (e.target === menuContainer) menuContainer.hidden = true; - }; - - add_child.onclick = (e) => { - mind.addChild(); - }; - add_sibling.onclick = (e) => { - if (isRoot) return; - mind.insertSibling(); - }; - remove_child.onclick = (e) => { - if (isRoot) return; - mind.removeNode(); - }; - up.onclick = (e) => { - if (isRoot) return; - mind.moveUpNode(); - }; - down.onclick = (e) => { - if (isRoot) return; - mind.moveDownNode(); - }; - edit.onclick = (e) => { - mind.beginEdit(); - }; -} diff --git a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/plugin/nodeDraggable.ts b/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/plugin/nodeDraggable.ts deleted file mode 100644 index ca68fe7a..00000000 --- a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/plugin/nodeDraggable.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { findEle as E, Group, Topic } from '../utils/dom'; -import { dragMoveHelper, throttle } from '../utils/index'; - -const $d = document; - -const insertPreview = function (el, insertLocation) { - if (!insertLocation) { - clearPreview(el); - return el; - } - const query = el.getElementsByClassName('insert-preview'); - const className = `insert-preview ${insertLocation} show`; - if (query.length > 0) { - query[0].className = className; - } else { - const insertPreviewEL = $d.createElement('div'); - insertPreviewEL.className = className; - el.appendChild(insertPreviewEL); - } - return el; -}; - -const clearPreview = function (el) { - if (!el) return; - const query = el.getElementsByClassName('insert-preview'); - for (const queryElement of query || []) { - queryElement.remove(); - } -}; - -const canPreview = function (el: Element, dragged: Topic) { - const isContain = dragged.parentNode.parentNode.contains(el); - return el && el.tagName === 'TPC' && el !== dragged && !isContain && (el as Topic).nodeObj.root !== true; -}; - -export default function (mind) { - let dragged: Topic; - let insertLocation: string; - let meet: Element; - const threshold = 12; - - const onDragStart = function (e) { - dragged = e.target; - (dragged.parentNode.parentNode as Group).style.opacity = '0.5'; - dragMoveHelper.clear(); - }; - - const onDragEnd = async function (e: DragEvent) { - e.preventDefault(); - (e.target as HTMLElement).style.opacity = ''; - clearPreview(meet); - const obj = dragged.nodeObj; - switch (insertLocation) { - case 'before': - mind.moveNodeBefore(dragged, meet); - mind.selectNode(E(obj.id)); - break; - case 'after': - mind.moveNodeAfter(dragged, meet); - mind.selectNode(E(obj.id)); - break; - case 'in': - mind.moveNode(dragged, meet); - break; - } - (dragged.parentNode.parentNode as Group).style.opacity = '1'; - dragged = null; - }; - - const onDragOver = throttle(function (e: DragEvent) { - clearPreview(meet); - const topMeet = $d.elementFromPoint(e.clientX, e.clientY - threshold); - if (canPreview(topMeet, dragged)) { - meet = topMeet; - const y = topMeet.getBoundingClientRect().y; - if (e.clientY > y + topMeet.clientHeight) { - insertLocation = 'after'; - } else if (e.clientY > y + topMeet.clientHeight / 2) { - insertLocation = 'in'; - } - } else { - const bottomMeet = $d.elementFromPoint(e.clientX, e.clientY + threshold); - if (canPreview(bottomMeet, dragged)) { - meet = bottomMeet; - const y = bottomMeet.getBoundingClientRect().y; - if (e.clientY < y) { - insertLocation = 'before'; - } else if (e.clientY < y + bottomMeet.clientHeight / 2) { - insertLocation = 'in'; - } - } else { - insertLocation = meet = null; - } - } - if (meet) insertPreview(meet, insertLocation); - }, 200); - - mind.map.addEventListener('dragstart', onDragStart); - mind.map.addEventListener('dragend', onDragEnd); - mind.map.addEventListener('dragover', onDragOver); - - mind.bus.addListener('destroy', () => { - mind.map.removeEventListener('dragstart', onDragStart); - mind.map.removeEventListener('dragend', onDragEnd); - mind.map.removeEventListener('dragover', onDragOver); - }); -} diff --git a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/plugin/nodeMenu.tsx b/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/plugin/nodeMenu.tsx deleted file mode 100644 index 3d87996f..00000000 --- a/packages/client/src/tiptap/core/wrappers/mind/mind-elixir/plugin/nodeMenu.tsx +++ /dev/null @@ -1,239 +0,0 @@ -import { IconBold, IconFont, IconLink, IconMark } from '@douyinfe/semi-icons'; -import { Button, Dropdown, Form, Space, Tooltip } from '@douyinfe/semi-ui'; -import { FormApi } from '@douyinfe/semi-ui/lib/es/form'; -import { useCallback, useEffect, useRef, useState } from 'react'; -import ReactDOM from 'react-dom'; -import tippy from 'tippy.js'; -import { ColorPicker } from 'tiptap/components/color-picker'; - -import { findEle } from '../utils/dom'; - -export const Link = ({ mind, link, setLink }) => { - const $form = useRef(); - const $input = useRef(); - const [initialState, setInitialState] = useState({ link }); - - const handleOk = useCallback(() => { - $form.current.validate().then((values) => { - setLink(values.link); - }); - }, [setLink]); - - useEffect(() => { - setInitialState({ link }); - }, [link]); - - return ( - -
($form.current = formApi)} - labelPosition="left" - onSubmit={handleOk} - layout="horizontal" - > - (mind.isInnerEditing = true)} - onBlur={() => (mind.isInnerEditing = false)} - /> - - - - } - > - - -