You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4656 regels
180 KiB

4 jaren geleden
  1. /*!
  2. * ====================================================
  3. * kityminder-editor - v1.0.67 - 2021-01-07
  4. * https://github.com/fex-team/kityminder-editor
  5. * GitHub: https://github.com/fex-team/kityminder-editor
  6. * Copyright (c) 2021 ; Licensed
  7. * ====================================================
  8. */
  9. (function () {
  10. var _p = {
  11. r: function(index) {
  12. if (_p[index].inited) {
  13. return _p[index].value;
  14. }
  15. if (typeof _p[index].value === "function") {
  16. var module = {
  17. exports: {}
  18. }, returnValue = _p[index].value(null, module.exports, module);
  19. _p[index].inited = true;
  20. _p[index].value = returnValue;
  21. if (returnValue !== undefined) {
  22. return returnValue;
  23. } else {
  24. for (var key in module.exports) {
  25. if (module.exports.hasOwnProperty(key)) {
  26. _p[index].inited = true;
  27. _p[index].value = module.exports;
  28. return module.exports;
  29. }
  30. }
  31. }
  32. } else {
  33. _p[index].inited = true;
  34. return _p[index].value;
  35. }
  36. }
  37. };
  38. //src/editor.js
  39. _p[0] = {
  40. value: function(require, exports, module) {
  41. /**
  42. * 运行时
  43. */
  44. var runtimes = [];
  45. function assemble(runtime) {
  46. runtimes.push(runtime);
  47. }
  48. function KMEditor(selector) {
  49. this.selector = selector;
  50. for (var i = 0; i < runtimes.length; i++) {
  51. if (typeof runtimes[i] == "function") {
  52. runtimes[i].call(this, this);
  53. }
  54. }
  55. }
  56. KMEditor.assemble = assemble;
  57. assemble(_p.r(7));
  58. assemble(_p.r(9));
  59. assemble(_p.r(14));
  60. assemble(_p.r(18));
  61. assemble(_p.r(11));
  62. assemble(_p.r(12));
  63. assemble(_p.r(5));
  64. assemble(_p.r(6));
  65. assemble(_p.r(8));
  66. assemble(_p.r(15));
  67. assemble(_p.r(10));
  68. assemble(_p.r(13));
  69. assemble(_p.r(16));
  70. assemble(_p.r(17));
  71. return module.exports = KMEditor;
  72. }
  73. };
  74. //src/expose-editor.js
  75. /**
  76. * @fileOverview
  77. *
  78. * 打包暴露
  79. *
  80. * @author: techird
  81. * @copyright: Baidu FEX, 2014
  82. */
  83. _p[1] = {
  84. value: function(require, exports, module) {
  85. return module.exports = kityminder.Editor = _p.r(0);
  86. }
  87. };
  88. //src/hotbox.js
  89. _p[2] = {
  90. value: function(require, exports, module) {
  91. return module.exports = window.HotBox;
  92. }
  93. };
  94. //src/lang.js
  95. _p[3] = {
  96. value: function(require, exports, module) {}
  97. };
  98. //src/minder.js
  99. _p[4] = {
  100. value: function(require, exports, module) {
  101. return module.exports = window.kityminder.Minder;
  102. }
  103. };
  104. //src/runtime/clipboard-mimetype.js
  105. /**
  106. * @Desc: 新增一个用于处理系统ctrl+c ctrl+v等方式导入导出节点的MIMETYPE处理如系统不支持clipboardEvent或者是FF则不初始化改class
  107. * @Editor: Naixor
  108. * @Date: 2015.9.21
  109. */
  110. _p[5] = {
  111. value: function(require, exports, module) {
  112. function MimeType() {
  113. /**
  114. * 私有变量
  115. */
  116. var SPLITOR = "\ufeff";
  117. var MIMETYPE = {
  118. "application/km": "￿"
  119. };
  120. var SIGN = {
  121. "\ufeff": "SPLITOR",
  122. "￿": "application/km"
  123. };
  124. /**
  125. * 用于将一段纯文本封装成符合其数据格式的文本
  126. * @method process private
  127. * @param {MIMETYPE} mimetype 数据格式
  128. * @param {String} text 原始文本
  129. * @return {String} 符合该数据格式下的文本
  130. * @example
  131. * var str = "123";
  132. * str = process('application/km', str); // 返回的内容再经过MimeType判断会读取出其数据格式为application/km
  133. * process('text/plain', str); // 若接受到一个非纯文本信息,则会将其转换为新的数据格式
  134. */
  135. function process(mimetype, text) {
  136. if (!this.isPureText(text)) {
  137. var _mimetype = this.whichMimeType(text);
  138. if (!_mimetype) {
  139. throw new Error("unknow mimetype!");
  140. }
  141. text = this.getPureText(text);
  142. }
  143. if (mimetype === false) {
  144. return text;
  145. }
  146. return mimetype + SPLITOR + text;
  147. }
  148. /**
  149. * 注册数据类型的标识
  150. * @method registMimeTypeProtocol public
  151. * @param {String} type 数据类型
  152. * @param {String} sign 标识
  153. */
  154. this.registMimeTypeProtocol = function(type, sign) {
  155. if (sign && SIGN[sign]) {
  156. throw new Error("sing has registed!");
  157. }
  158. if (type && !!MIMETYPE[type]) {
  159. throw new Error("mimetype has registed!");
  160. }
  161. SIGN[sign] = type;
  162. MIMETYPE[type] = sign;
  163. };
  164. /**
  165. * 获取已注册数据类型的协议
  166. * @method getMimeTypeProtocol public
  167. * @param {String} type 数据类型
  168. * @param {String} text|undefiend 文本内容或不传入
  169. * @return {String|Function}
  170. * @example
  171. * text若不传入则直接返回对应数据格式的处理(process)方法
  172. * 若传入文本则直接调用对应的process方法进行处理此时返回处理后的内容
  173. * var m = new MimeType();
  174. * var kmprocess = m.getMimeTypeProtocol('application/km');
  175. * kmprocess("123") === m.getMimeTypeProtocol('application/km', "123");
  176. *
  177. */
  178. this.getMimeTypeProtocol = function(type, text) {
  179. var mimetype = MIMETYPE[type] || false;
  180. if (text === undefined) {
  181. return process.bind(this, mimetype);
  182. }
  183. return process(mimetype, text);
  184. };
  185. this.getSpitor = function() {
  186. return SPLITOR;
  187. };
  188. this.getMimeType = function(sign) {
  189. if (sign !== undefined) {
  190. return SIGN[sign] || null;
  191. }
  192. return MIMETYPE;
  193. };
  194. }
  195. MimeType.prototype.isPureText = function(text) {
  196. return !~text.indexOf(this.getSpitor());
  197. };
  198. MimeType.prototype.getPureText = function(text) {
  199. if (this.isPureText(text)) {
  200. return text;
  201. }
  202. return text.split(this.getSpitor())[1];
  203. };
  204. MimeType.prototype.whichMimeType = function(text) {
  205. if (this.isPureText(text)) {
  206. return null;
  207. }
  208. return this.getMimeType(text.split(this.getSpitor())[0]);
  209. };
  210. function MimeTypeRuntime() {
  211. if (this.minder.supportClipboardEvent && !kity.Browser.gecko) {
  212. this.MimeType = new MimeType();
  213. }
  214. }
  215. return module.exports = MimeTypeRuntime;
  216. }
  217. };
  218. //src/runtime/clipboard.js
  219. /**
  220. * @Desc: 处理editor的clipboard事件只在支持ClipboardEvent并且不是FF的情况下工作
  221. * @Editor: Naixor
  222. * @Date: 2015.9.21
  223. */
  224. _p[6] = {
  225. value: function(require, exports, module) {
  226. function ClipboardRuntime() {
  227. var minder = this.minder;
  228. var Data = window.kityminder.data;
  229. if (!minder.supportClipboardEvent || kity.Browser.gecko) {
  230. return;
  231. }
  232. var fsm = this.fsm;
  233. var receiver = this.receiver;
  234. var MimeType = this.MimeType;
  235. var kmencode = MimeType.getMimeTypeProtocol("application/km"), decode = Data.getRegisterProtocol("json").decode;
  236. var _selectedNodes = [];
  237. /*
  238. * 增加对多节点赋值粘贴的处理
  239. */
  240. function encode(nodes) {
  241. var _nodes = [];
  242. for (var i = 0, l = nodes.length; i < l; i++) {
  243. _nodes.push(minder.exportNode(nodes[i]));
  244. }
  245. return kmencode(Data.getRegisterProtocol("json").encode(_nodes));
  246. }
  247. var beforeCopy = function(e) {
  248. if (document.activeElement == receiver.element) {
  249. var clipBoardEvent = e;
  250. var state = fsm.state();
  251. switch (state) {
  252. case "input":
  253. {
  254. break;
  255. }
  256. case "normal":
  257. {
  258. var nodes = [].concat(minder.getSelectedNodes());
  259. if (nodes.length) {
  260. // 这里由于被粘贴复制的节点的id信息也都一样,故做此算法
  261. // 这里有个疑问,使用node.getParent()或者node.parent会离奇导致出现非选中节点被渲染成选中节点,因此使用isAncestorOf,而没有使用自行回溯的方式
  262. if (nodes.length > 1) {
  263. var targetLevel;
  264. nodes.sort(function(a, b) {
  265. return a.getLevel() - b.getLevel();
  266. });
  267. targetLevel = nodes[0].getLevel();
  268. if (targetLevel !== nodes[nodes.length - 1].getLevel()) {
  269. var plevel, pnode, idx = 0, l = nodes.length, pidx = l - 1;
  270. pnode = nodes[pidx];
  271. while (pnode.getLevel() !== targetLevel) {
  272. idx = 0;
  273. while (idx < l && nodes[idx].getLevel() === targetLevel) {
  274. if (nodes[idx].isAncestorOf(pnode)) {
  275. nodes.splice(pidx, 1);
  276. break;
  277. }
  278. idx++;
  279. }
  280. pidx--;
  281. pnode = nodes[pidx];
  282. }
  283. }
  284. }
  285. var str = encode(nodes);
  286. clipBoardEvent.clipboardData.setData("text/plain", str);
  287. }
  288. e.preventDefault();
  289. break;
  290. }
  291. }
  292. }
  293. };
  294. var beforeCut = function(e) {
  295. if (document.activeElement == receiver.element) {
  296. if (minder.getStatus() !== "normal") {
  297. e.preventDefault();
  298. return;
  299. }
  300. var clipBoardEvent = e;
  301. var state = fsm.state();
  302. switch (state) {
  303. case "input":
  304. {
  305. break;
  306. }
  307. case "normal":
  308. {
  309. var nodes = minder.getSelectedNodes();
  310. if (nodes.length) {
  311. clipBoardEvent.clipboardData.setData("text/plain", encode(nodes));
  312. minder.execCommand("removenode");
  313. }
  314. e.preventDefault();
  315. break;
  316. }
  317. }
  318. }
  319. };
  320. var beforePaste = function(e) {
  321. if (document.activeElement == receiver.element) {
  322. if (minder.getStatus() !== "normal") {
  323. e.preventDefault();
  324. return;
  325. }
  326. var clipBoardEvent = e;
  327. var state = fsm.state();
  328. var textData = clipBoardEvent.clipboardData.getData("text/plain");
  329. switch (state) {
  330. case "input":
  331. {
  332. // input状态下如果格式为application/km则不进行paste操作
  333. if (!MimeType.isPureText(textData)) {
  334. e.preventDefault();
  335. return;
  336. }
  337. break;
  338. }
  339. case "normal":
  340. {
  341. /*
  342. * 针对normal状态下通过对选中节点粘贴导入子节点文本进行单独处理
  343. */
  344. var sNodes = minder.getSelectedNodes();
  345. if (MimeType.whichMimeType(textData) === "application/km") {
  346. var nodes = decode(MimeType.getPureText(textData));
  347. var _node;
  348. sNodes.forEach(function(node) {
  349. // 由于粘贴逻辑中为了排除子节点重新排序导致逆序,因此复制的时候倒过来
  350. for (var i = nodes.length - 1; i >= 0; i--) {
  351. _node = minder.createNode(null, node);
  352. minder.importNode(_node, nodes[i]);
  353. _selectedNodes.push(_node);
  354. node.appendChild(_node);
  355. }
  356. });
  357. minder.select(_selectedNodes, true);
  358. _selectedNodes = [];
  359. minder.refresh();
  360. } else if (clipBoardEvent.clipboardData && clipBoardEvent.clipboardData.items[0].type.indexOf("image") > -1) {
  361. var imageFile = clipBoardEvent.clipboardData.items[0].getAsFile();
  362. var serverService = angular.element(document.body).injector().get("server");
  363. return serverService.uploadImage(imageFile).then(function(json) {
  364. var resp = json.data;
  365. if (resp.errno === 0) {
  366. minder.execCommand("image", resp.data.url);
  367. }
  368. });
  369. } else {
  370. sNodes.forEach(function(node) {
  371. minder.Text2Children(node, textData);
  372. });
  373. }
  374. e.preventDefault();
  375. break;
  376. }
  377. }
  378. }
  379. };
  380. /**
  381. * 由editor的receiver统一处理全部事件包括clipboard事件
  382. * @Editor: Naixor
  383. * @Date: 2015.9.24
  384. */
  385. document.addEventListener("copy", beforeCopy);
  386. document.addEventListener("cut", beforeCut);
  387. document.addEventListener("paste", beforePaste);
  388. }
  389. return module.exports = ClipboardRuntime;
  390. }
  391. };
  392. //src/runtime/container.js
  393. /**
  394. * @fileOverview
  395. *
  396. * 初始化编辑器的容器
  397. *
  398. * @author: techird
  399. * @copyright: Baidu FEX, 2014
  400. */
  401. _p[7] = {
  402. value: function(require, exports, module) {
  403. /**
  404. * 最先执行的 Runtime初始化编辑器容器
  405. */
  406. function ContainerRuntime() {
  407. var container;
  408. if (typeof this.selector == "string") {
  409. container = document.querySelector(this.selector);
  410. } else {
  411. container = this.selector;
  412. }
  413. if (!container) throw new Error("Invalid selector: " + this.selector);
  414. // 这个类名用于给编辑器添加样式
  415. container.classList.add("km-editor");
  416. // 暴露容器给其他运行时使用
  417. this.container = container;
  418. }
  419. return module.exports = ContainerRuntime;
  420. }
  421. };
  422. //src/runtime/drag.js
  423. /**
  424. * @fileOverview
  425. *
  426. * 用于拖拽节点时屏蔽键盘事件
  427. *
  428. * @author: techird
  429. * @copyright: Baidu FEX, 2014
  430. */
  431. _p[8] = {
  432. value: function(require, exports, module) {
  433. var Hotbox = _p.r(2);
  434. var Debug = _p.r(19);
  435. var debug = new Debug("drag");
  436. function DragRuntime() {
  437. var fsm = this.fsm;
  438. var minder = this.minder;
  439. var hotbox = this.hotbox;
  440. var receiver = this.receiver;
  441. var receiverElement = receiver.element;
  442. // setup everything to go
  443. setupFsm();
  444. // listen the fsm changes, make action.
  445. function setupFsm() {
  446. // when jumped to drag mode, enter
  447. fsm.when("* -> drag", function() {});
  448. fsm.when("drag -> *", function(exit, enter, reason) {
  449. if (reason == "drag-finish") {}
  450. });
  451. }
  452. var downX, downY;
  453. var MOUSE_HAS_DOWN = 0;
  454. var MOUSE_HAS_UP = 1;
  455. var BOUND_CHECK = 20;
  456. var flag = MOUSE_HAS_UP;
  457. var maxX, maxY, osx, osy, containerY;
  458. var freeHorizen = false, freeVirtical = false;
  459. var frame;
  460. function move(direction, speed) {
  461. if (!direction) {
  462. freeHorizen = freeVirtical = false;
  463. frame && kity.releaseFrame(frame);
  464. frame = null;
  465. return;
  466. }
  467. if (!frame) {
  468. frame = kity.requestFrame(function(direction, speed, minder) {
  469. return function(frame) {
  470. switch (direction) {
  471. case "left":
  472. minder._viewDragger.move({
  473. x: -speed,
  474. y: 0
  475. }, 0);
  476. break;
  477. case "top":
  478. minder._viewDragger.move({
  479. x: 0,
  480. y: -speed
  481. }, 0);
  482. break;
  483. case "right":
  484. minder._viewDragger.move({
  485. x: speed,
  486. y: 0
  487. }, 0);
  488. break;
  489. case "bottom":
  490. minder._viewDragger.move({
  491. x: 0,
  492. y: speed
  493. }, 0);
  494. break;
  495. default:
  496. return;
  497. }
  498. frame.next();
  499. };
  500. }(direction, speed, minder));
  501. }
  502. }
  503. minder.on("mousedown", function(e) {
  504. flag = MOUSE_HAS_DOWN;
  505. var rect = minder.getPaper().container.getBoundingClientRect();
  506. downX = e.originEvent.clientX;
  507. downY = e.originEvent.clientY;
  508. containerY = rect.top;
  509. maxX = rect.width;
  510. maxY = rect.height;
  511. });
  512. minder.on("mousemove", function(e) {
  513. 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)) {
  514. osx = e.originEvent.clientX;
  515. osy = e.originEvent.clientY - containerY;
  516. if (osx < BOUND_CHECK) {
  517. move("right", BOUND_CHECK - osx);
  518. } else if (osx > maxX - BOUND_CHECK) {
  519. move("left", BOUND_CHECK + osx - maxX);
  520. } else {
  521. freeHorizen = true;
  522. }
  523. if (osy < BOUND_CHECK) {
  524. move("bottom", osy);
  525. } else if (osy > maxY - BOUND_CHECK) {
  526. move("top", BOUND_CHECK + osy - maxY);
  527. } else {
  528. freeVirtical = true;
  529. }
  530. if (freeHorizen && freeVirtical) {
  531. move(false);
  532. }
  533. }
  534. 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)) {
  535. if (fsm.state() === "hotbox") {
  536. hotbox.active(Hotbox.STATE_IDLE);
  537. }
  538. return fsm.jump("drag", "user-drag");
  539. }
  540. });
  541. window.addEventListener("mouseup", function() {
  542. flag = MOUSE_HAS_UP;
  543. if (fsm.state() === "drag") {
  544. move(false);
  545. return fsm.jump("normal", "drag-finish");
  546. }
  547. }, false);
  548. }
  549. return module.exports = DragRuntime;
  550. }
  551. };
  552. //src/runtime/fsm.js
  553. /**
  554. * @fileOverview
  555. *
  556. * 编辑器状态机
  557. *
  558. * @author: techird
  559. * @copyright: Baidu FEX, 2014
  560. */
  561. _p[9] = {
  562. value: function(require, exports, module) {
  563. var Debug = _p.r(19);
  564. var debug = new Debug("fsm");
  565. function handlerConditionMatch(condition, when, exit, enter) {
  566. if (condition.when != when) return false;
  567. if (condition.enter != "*" && condition.enter != enter) return false;
  568. if (condition.exit != "*" && condition.exit != exit) return;
  569. return true;
  570. }
  571. function FSM(defaultState) {
  572. var currentState = defaultState;
  573. var BEFORE_ARROW = " - ";
  574. var AFTER_ARROW = " -> ";
  575. var handlers = [];
  576. /**
  577. * 状态跳转
  578. *
  579. * 会通知所有的状态跳转监视器
  580. *
  581. * @param {string} newState 新状态名称
  582. * @param {any} reason 跳转的原因可以作为参数传递给跳转监视器
  583. */
  584. this.jump = function(newState, reason) {
  585. if (!reason) throw new Error("Please tell fsm the reason to jump");
  586. var oldState = currentState;
  587. var notify = [ oldState, newState ].concat([].slice.call(arguments, 1));
  588. var i, handler;
  589. // 跳转前
  590. for (i = 0; i < handlers.length; i++) {
  591. handler = handlers[i];
  592. if (handlerConditionMatch(handler.condition, "before", oldState, newState)) {
  593. if (handler.apply(null, notify)) return;
  594. }
  595. }
  596. currentState = newState;
  597. debug.log("[{0}] {1} -> {2}", reason, oldState, newState);
  598. // 跳转后
  599. for (i = 0; i < handlers.length; i++) {
  600. handler = handlers[i];
  601. if (handlerConditionMatch(handler.condition, "after", oldState, newState)) {
  602. handler.apply(null, notify);
  603. }
  604. }
  605. return currentState;
  606. };
  607. /**
  608. * 返回当前状态
  609. * @return {string}
  610. */
  611. this.state = function() {
  612. return currentState;
  613. };
  614. /**
  615. * 添加状态跳转监视器
  616. *
  617. * @param {string} condition
  618. * 监视的时机
  619. * "* => *" 默认
  620. *
  621. * @param {Function} handler
  622. * 监视函数当状态跳转的时候会接收三个参数
  623. * * from - 跳转前的状态
  624. * * to - 跳转后的状态
  625. * * reason - 跳转的原因
  626. */
  627. this.when = function(condition, handler) {
  628. if (arguments.length == 1) {
  629. handler = condition;
  630. condition = "* -> *";
  631. }
  632. var when, resolved, exit, enter;
  633. resolved = condition.split(BEFORE_ARROW);
  634. if (resolved.length == 2) {
  635. when = "before";
  636. } else {
  637. resolved = condition.split(AFTER_ARROW);
  638. if (resolved.length == 2) {
  639. when = "after";
  640. }
  641. }
  642. if (!when) throw new Error("Illegal fsm condition: " + condition);
  643. exit = resolved[0];
  644. enter = resolved[1];
  645. handler.condition = {
  646. when: when,
  647. exit: exit,
  648. enter: enter
  649. };
  650. handlers.push(handler);
  651. };
  652. }
  653. function FSMRumtime() {
  654. this.fsm = new FSM("normal");
  655. }
  656. return module.exports = FSMRumtime;
  657. }
  658. };
  659. //src/runtime/history.js
  660. /**
  661. * @fileOverview
  662. *
  663. * 历史管理
  664. *
  665. * @author: techird
  666. * @copyright: Baidu FEX, 2014
  667. */
  668. _p[10] = {
  669. value: function(require, exports, module) {
  670. var jsonDiff = _p.r(22);
  671. function HistoryRuntime() {
  672. var minder = this.minder;
  673. var hotbox = this.hotbox;
  674. var MAX_HISTORY = 100;
  675. var lastSnap;
  676. var patchLock;
  677. var undoDiffs;
  678. var redoDiffs;
  679. function reset() {
  680. undoDiffs = [];
  681. redoDiffs = [];
  682. lastSnap = minder.exportJson();
  683. }
  684. function makeUndoDiff() {
  685. var headSnap = minder.exportJson();
  686. var diff = jsonDiff(headSnap, lastSnap);
  687. if (diff.length) {
  688. undoDiffs.push(diff);
  689. while (undoDiffs.length > MAX_HISTORY) {
  690. undoDiffs.shift();
  691. }
  692. lastSnap = headSnap;
  693. return true;
  694. }
  695. }
  696. function makeRedoDiff() {
  697. var revertSnap = minder.exportJson();
  698. redoDiffs.push(jsonDiff(revertSnap, lastSnap));
  699. lastSnap = revertSnap;
  700. }
  701. function undo() {
  702. patchLock = true;
  703. var undoDiff = undoDiffs.pop();
  704. if (undoDiff) {
  705. minder.applyPatches(undoDiff);
  706. makeRedoDiff();
  707. }
  708. patchLock = false;
  709. }
  710. function redo() {
  711. patchLock = true;
  712. var redoDiff = redoDiffs.pop();
  713. if (redoDiff) {
  714. minder.applyPatches(redoDiff);
  715. makeUndoDiff();
  716. }
  717. patchLock = false;
  718. }
  719. function changed() {
  720. if (patchLock) return;
  721. if (makeUndoDiff()) redoDiffs = [];
  722. }
  723. function hasUndo() {
  724. return !!undoDiffs.length;
  725. }
  726. function hasRedo() {
  727. return !!redoDiffs.length;
  728. }
  729. function updateSelection(e) {
  730. if (!patchLock) return;
  731. var patch = e.patch;
  732. switch (patch.express) {
  733. case "node.add":
  734. minder.select(patch.node.getChild(patch.index), true);
  735. break;
  736. case "node.remove":
  737. case "data.replace":
  738. case "data.remove":
  739. case "data.add":
  740. minder.select(patch.node, true);
  741. break;
  742. }
  743. }
  744. this.history = {
  745. reset: reset,
  746. undo: undo,
  747. redo: redo,
  748. hasUndo: hasUndo,
  749. hasRedo: hasRedo
  750. };
  751. reset();
  752. minder.on("contentchange", changed);
  753. minder.on("import", reset);
  754. minder.on("patch", updateSelection);
  755. var main = hotbox.state("main");
  756. main.button({
  757. position: "top",
  758. label: "撤销",
  759. key: "Ctrl + Z",
  760. enable: hasUndo,
  761. action: undo,
  762. next: "idle"
  763. });
  764. main.button({
  765. position: "top",
  766. label: "重做",
  767. key: "Ctrl + Y",
  768. enable: hasRedo,
  769. action: redo,
  770. next: "idle"
  771. });
  772. }
  773. window.diff = jsonDiff;
  774. return module.exports = HistoryRuntime;
  775. }
  776. };
  777. //src/runtime/hotbox.js
  778. /**
  779. * @fileOverview
  780. *
  781. * 热盒 Runtime
  782. *
  783. * @author: techird
  784. * @copyright: Baidu FEX, 2014
  785. */
  786. _p[11] = {
  787. value: function(require, exports, module) {
  788. var Hotbox = _p.r(2);
  789. function HotboxRuntime() {
  790. var fsm = this.fsm;
  791. var minder = this.minder;
  792. var receiver = this.receiver;
  793. var container = this.container;
  794. var hotbox = new Hotbox(container);
  795. hotbox.setParentFSM(fsm);
  796. fsm.when("normal -> hotbox", function(exit, enter, reason) {
  797. var node = minder.getSelectedNode();
  798. var position;
  799. if (node) {
  800. var box = node.getRenderBox();
  801. position = {
  802. x: box.cx,
  803. y: box.cy
  804. };
  805. }
  806. hotbox.active("main", position);
  807. });
  808. fsm.when("normal -> normal", function(exit, enter, reason, e) {
  809. if (reason == "shortcut-handle") {
  810. var handleResult = hotbox.dispatch(e);
  811. if (handleResult) {
  812. e.preventDefault();
  813. } else {
  814. minder.dispatchKeyEvent(e);
  815. }
  816. }
  817. });
  818. fsm.when("modal -> normal", function(exit, enter, reason, e) {
  819. if (reason == "import-text-finish") {
  820. receiver.element.focus();
  821. }
  822. });
  823. this.hotbox = hotbox;
  824. }
  825. return module.exports = HotboxRuntime;
  826. }
  827. };
  828. //src/runtime/input.js
  829. /**
  830. * @fileOverview
  831. *
  832. * 文本输入支持
  833. *
  834. * @author: techird
  835. * @copyright: Baidu FEX, 2014
  836. */
  837. _p[12] = {
  838. value: function(require, exports, module) {
  839. _p.r(21);
  840. var Debug = _p.r(19);
  841. var debug = new Debug("input");
  842. function InputRuntime() {
  843. var fsm = this.fsm;
  844. var minder = this.minder;
  845. var hotbox = this.hotbox;
  846. var receiver = this.receiver;
  847. var receiverElement = receiver.element;
  848. var isGecko = window.kity.Browser.gecko;
  849. // setup everything to go
  850. setupReciverElement();
  851. setupFsm();
  852. setupHotbox();
  853. // expose editText()
  854. this.editText = editText;
  855. // listen the fsm changes, make action.
  856. function setupFsm() {
  857. // when jumped to input mode, enter
  858. fsm.when("* -> input", enterInputMode);
  859. // when exited, commit or exit depends on the exit reason
  860. fsm.when("input -> *", function(exit, enter, reason) {
  861. switch (reason) {
  862. case "input-cancel":
  863. return exitInputMode();
  864. case "input-commit":
  865. default:
  866. return commitInputResult();
  867. }
  868. });
  869. // lost focus to commit
  870. receiver.onblur(function(e) {
  871. if (fsm.state() == "input") {
  872. fsm.jump("normal", "input-commit");
  873. }
  874. });
  875. minder.on("beforemousedown", function() {
  876. if (fsm.state() == "input") {
  877. fsm.jump("normal", "input-commit");
  878. }
  879. });
  880. minder.on("dblclick", function() {
  881. if (minder.getSelectedNode() && minder._status !== "readonly") {
  882. editText();
  883. }
  884. });
  885. }
  886. // let the receiver follow the current selected node position
  887. function setupReciverElement() {
  888. if (debug.flaged) {
  889. receiverElement.classList.add("debug");
  890. }
  891. receiverElement.onmousedown = function(e) {
  892. e.stopPropagation();
  893. };
  894. minder.on("layoutallfinish viewchange viewchanged selectionchange", function(e) {
  895. // viewchange event is too frequenced, lazy it
  896. if (e.type == "viewchange" && fsm.state() != "input") return;
  897. updatePosition();
  898. });
  899. updatePosition();
  900. }
  901. // edit entrance in hotbox
  902. function setupHotbox() {
  903. hotbox.state("main").button({
  904. position: "center",
  905. label: "编辑",
  906. key: "F2",
  907. enable: function() {
  908. return minder.queryCommandState("text") != -1;
  909. },
  910. action: editText
  911. });
  912. }
  913. /**
  914. * 增加对字体的鉴别以保证用户在编辑状态ctrl/cmd + b/i所触发的加粗斜体与显示一致
  915. * @editor Naixor
  916. * @Date 2015-12-2
  917. */
  918. // edit for the selected node
  919. function editText() {
  920. var node = minder.getSelectedNode();
  921. if (!node) {
  922. return;
  923. }
  924. var textContainer = receiverElement;
  925. receiverElement.innerText = "";
  926. if (node.getData("font-weight") === "bold") {
  927. var b = document.createElement("b");
  928. textContainer.appendChild(b);
  929. textContainer = b;
  930. }
  931. if (node.getData("font-style") === "italic") {
  932. var i = document.createElement("i");
  933. textContainer.appendChild(i);
  934. textContainer = i;
  935. }
  936. textContainer.innerText = minder.queryCommandValue("text");
  937. if (isGecko) {
  938. receiver.fixFFCaretDisappeared();
  939. }
  940. fsm.jump("input", "input-request");
  941. receiver.selectAll();
  942. }
  943. /**
  944. * 增加对字体的鉴别以保证用户在编辑状态ctrl/cmd + b/i所触发的加粗斜体与显示一致
  945. * @editor Naixor
  946. * @Date 2015-12-2
  947. */
  948. function enterInputMode() {
  949. var node = minder.getSelectedNode();
  950. if (node) {
  951. var fontSize = node.getData("font-size") || node.getStyle("font-size");
  952. receiverElement.style.fontSize = fontSize + "px";
  953. receiverElement.style.minWidth = 0;
  954. receiverElement.style.minWidth = receiverElement.clientWidth + "px";
  955. receiverElement.style.fontWeight = node.getData("font-weight") || "";
  956. receiverElement.style.fontStyle = node.getData("font-style") || "";
  957. receiverElement.classList.add("input");
  958. receiverElement.focus();
  959. }
  960. }
  961. /**
  962. * 按照文本提交操作处理
  963. * @Desc: 从其他节点复制文字到另一个节点时部分浏览器(chrome)会自动包裹一个span标签这样试用一下逻辑出来的就不是text节点二是span节点因此导致undefined的情况发生
  964. * @Warning: 下方代码使用[].slice.call来将HTMLDomCollection处理成为Arrayie8及以下会有问题
  965. * @Editor: Naixor
  966. * @Date: 2015.9.16
  967. */
  968. function commitInputText(textNodes) {
  969. var text = "";
  970. var TAB_CHAR = "\t", ENTER_CHAR = "\n", STR_CHECK = /\S/, SPACE_CHAR = " ", // 针对FF,SG,BD,LB,IE等浏览器下SPACE的charCode存在为32和160的情况做处理
  971. SPACE_CHAR_REGEXP = new RegExp("( |" + String.fromCharCode(160) + ")"), BR = document.createElement("br");
  972. var isBold = false, isItalic = false;
  973. for (var str, _divChildNodes, space_l, space_num, tab_num, i = 0, l = textNodes.length; i < l; i++) {
  974. str = textNodes[i];
  975. switch (Object.prototype.toString.call(str)) {
  976. // 正常情况处理
  977. case "[object HTMLBRElement]":
  978. {
  979. text += ENTER_CHAR;
  980. break;
  981. }
  982. case "[object Text]":
  983. {
  984. // SG下会莫名其妙的加上&nbsp;影响后续判断,干掉!
  985. /**
  986. * FF下的wholeText会导致如下问题
  987. * |123| -> 在一个节点中输入一段字符此时TextNode为[#Text 123]
  988. * 提交并重新编辑在后面追加几个字符
  989. * |123abc| -> 此时123为一个TextNode为[#Text 123, #Text abc]但是对这两个任意取值wholeText均为全部内容123abc
  990. * 上述BUG仅存在在FF中故将wholeText更改为textContent
  991. */
  992. str = str.textContent.replace("&nbsp;", " ");
  993. if (!STR_CHECK.test(str)) {
  994. space_l = str.length;
  995. while (space_l--) {
  996. if (SPACE_CHAR_REGEXP.test(str[space_l])) {
  997. text += SPACE_CHAR;
  998. } else if (str[space_l] === TAB_CHAR) {
  999. text += TAB_CHAR;
  1000. }
  1001. }
  1002. } else {
  1003. text += str;
  1004. }
  1005. break;
  1006. }
  1007. // ctrl + b/i 会给字体加上<b>/<i>标签来实现黑体和斜体
  1008. case "[object HTMLElement]":
  1009. {
  1010. switch (str.nodeName) {
  1011. case "B":
  1012. {
  1013. isBold = true;
  1014. break;
  1015. }
  1016. case "I":
  1017. {
  1018. isItalic = true;
  1019. break;
  1020. }
  1021. default:
  1022. {}
  1023. }
  1024. [].splice.apply(textNodes, [ i, 1 ].concat([].slice.call(str.childNodes)));
  1025. l = textNodes.length;
  1026. i--;
  1027. break;
  1028. }
  1029. // 被增加span标签的情况会被处理成正常情况并会推交给上面处理
  1030. case "[object HTMLSpanElement]":
  1031. {
  1032. [].splice.apply(textNodes, [ i, 1 ].concat([].slice.call(str.childNodes)));
  1033. l = textNodes.length;
  1034. i--;
  1035. break;
  1036. }
  1037. // 若标签为image标签,则判断是否为合法url,是将其加载进来
  1038. case "[object HTMLImageElement]":
  1039. {
  1040. if (str.src) {
  1041. if (/http(|s):\/\//.test(str.src)) {
  1042. minder.execCommand("Image", str.src, str.alt);
  1043. } else {}
  1044. }
  1045. break;
  1046. }
  1047. // 被增加div标签的情况会被处理成正常情况并会推交给上面处理
  1048. case "[object HTMLDivElement]":
  1049. {
  1050. _divChildNodes = [];
  1051. for (var di = 0, l = str.childNodes.length; di < l; di++) {
  1052. _divChildNodes.push(str.childNodes[di]);
  1053. }
  1054. _divChildNodes.push(BR);
  1055. [].splice.apply(textNodes, [ i, 1 ].concat(_divChildNodes));
  1056. l = textNodes.length;
  1057. i--;
  1058. break;
  1059. }
  1060. default:
  1061. {
  1062. if (str && str.childNodes.length) {
  1063. _divChildNodes = [];
  1064. for (var di = 0, l = str.childNodes.length; di < l; di++) {
  1065. _divChildNodes.push(str.childNodes[di]);
  1066. }
  1067. _divChildNodes.push(BR);
  1068. [].splice.apply(textNodes, [ i, 1 ].concat(_divChildNodes));
  1069. l = textNodes.length;
  1070. i--;
  1071. } else {
  1072. if (str && str.textContent !== undefined) {
  1073. text += str.textContent;
  1074. } else {
  1075. text += "";
  1076. }
  1077. }
  1078. }
  1079. }
  1080. }
  1081. text = text.replace(/^\n*|\n*$/g, "");
  1082. text = text.replace(new RegExp("(\n|\r|\n\r)( |" + String.fromCharCode(160) + "){4}", "g"), "$1\t");
  1083. minder.getSelectedNode().setText(text);
  1084. if (isBold) {
  1085. minder.queryCommandState("bold") || minder.execCommand("bold");
  1086. } else {
  1087. minder.queryCommandState("bold") && minder.execCommand("bold");
  1088. }
  1089. if (isItalic) {
  1090. minder.queryCommandState("italic") || minder.execCommand("italic");
  1091. } else {
  1092. minder.queryCommandState("italic") && minder.execCommand("italic");
  1093. }
  1094. exitInputMode();
  1095. return text;
  1096. }
  1097. /**
  1098. * 判断节点的文本信息是否是
  1099. * @Desc: 从其他节点复制文字到另一个节点时部分浏览器(chrome)会自动包裹一个span标签这样使用以下逻辑出来的就不是text节点二是span节点因此导致undefined的情况发生
  1100. * @Notice: 此处逻辑应该拆分到 kityminder-core/core/data中去单独增加一个对某个节点importJson的事件
  1101. * @Editor: Naixor
  1102. * @Date: 2015.9.16
  1103. */
  1104. function commitInputNode(node, text) {
  1105. try {
  1106. minder.decodeData("text", text).then(function(json) {
  1107. function importText(node, json, minder) {
  1108. var data = json.data;
  1109. node.setText(data.text || "");
  1110. var childrenTreeData = json.children || [];
  1111. for (var i = 0; i < childrenTreeData.length; i++) {
  1112. var childNode = minder.createNode(null, node);
  1113. importText(childNode, childrenTreeData[i], minder);
  1114. }
  1115. return node;
  1116. }
  1117. importText(node, json, minder);
  1118. minder.fire("contentchange");
  1119. minder.getRoot().renderTree();
  1120. minder.layout(300);
  1121. });
  1122. } catch (e) {
  1123. minder.fire("contentchange");
  1124. minder.getRoot().renderTree();
  1125. // 无法被转换成脑图节点则不处理
  1126. if (e.toString() !== "Error: Invalid local format") {
  1127. throw e;
  1128. }
  1129. }
  1130. }
  1131. function commitInputResult() {
  1132. /**
  1133. * @Desc: 进行如下处理
  1134. * 根据用户的输入判断是否生成新的节点
  1135. * fix #83 https://github.com/fex-team/kityminder-editor/issues/83
  1136. * @Editor: Naixor
  1137. * @Date: 2015.9.16
  1138. */
  1139. var textNodes = [].slice.call(receiverElement.childNodes);
  1140. /**
  1141. * @Desc: 增加setTimeout的原因ie下receiverElement.innerHTML=""会导致后
  1142. * 面commitInputText中使用textContent报错不要问我什么原因
  1143. * @Editor: Naixor
  1144. * @Date: 2015.12.14
  1145. */
  1146. setTimeout(function() {
  1147. // 解决过大内容导致SVG窜位问题
  1148. receiverElement.innerHTML = "";
  1149. }, 0);
  1150. var node = minder.getSelectedNode();
  1151. textNodes = commitInputText(textNodes);
  1152. commitInputNode(node, textNodes);
  1153. if (node.type == "root") {
  1154. var rootText = minder.getRoot().getText();
  1155. minder.fire("initChangeRoot", {
  1156. text: rootText
  1157. });
  1158. }
  1159. }
  1160. function exitInputMode() {
  1161. receiverElement.classList.remove("input");
  1162. receiver.selectAll();
  1163. }
  1164. function updatePosition() {
  1165. var planed = updatePosition;
  1166. var focusNode = minder.getSelectedNode();
  1167. if (!focusNode) return;
  1168. if (!planed.timer) {
  1169. planed.timer = setTimeout(function() {
  1170. var box = focusNode.getRenderBox("TextRenderer");
  1171. receiverElement.style.left = Math.round(box.x) + "px";
  1172. receiverElement.style.top = (debug.flaged ? Math.round(box.bottom + 30) : Math.round(box.y)) + "px";
  1173. //receiverElement.focus();
  1174. planed.timer = 0;
  1175. });
  1176. }
  1177. }
  1178. }
  1179. return module.exports = InputRuntime;
  1180. }
  1181. };
  1182. //src/runtime/jumping.js
  1183. /**
  1184. * @fileOverview
  1185. *
  1186. * 根据按键控制状态机的跳转
  1187. *
  1188. * @author: techird
  1189. * @copyright: Baidu FEX, 2014
  1190. */
  1191. _p[13] = {
  1192. value: function(require, exports, module) {
  1193. var Hotbox = _p.r(2);
  1194. // Nice: http://unixpapa.com/js/key.html
  1195. function isIntendToInput(e) {
  1196. if (e.ctrlKey || e.metaKey || e.altKey) return false;
  1197. // a-zA-Z
  1198. if (e.keyCode >= 65 && e.keyCode <= 90) return true;
  1199. // 0-9 以及其上面的符号
  1200. if (e.keyCode >= 48 && e.keyCode <= 57) return true;
  1201. // 小键盘区域 (除回车外)
  1202. if (e.keyCode != 108 && e.keyCode >= 96 && e.keyCode <= 111) return true;
  1203. // 小键盘区域 (除回车外)
  1204. // @yinheli from pull request
  1205. if (e.keyCode != 108 && e.keyCode >= 96 && e.keyCode <= 111) return true;
  1206. // 输入法
  1207. if (e.keyCode == 229 || e.keyCode === 0) return true;
  1208. return false;
  1209. }
  1210. /**
  1211. * @Desc: 下方使用receiver.enable()和receiver.disable()通过
  1212. * 修改div contenteditable属性的hack来解决开启热核后依然无法屏蔽浏览器输入的bug;
  1213. * 特别: win下FF对于此种情况必须要先blur在focus才能解决但是由于这样做会导致用户
  1214. * 输入法状态丢失因此对FF暂不做处理
  1215. * @Editor: Naixor
  1216. * @Date: 2015.09.14
  1217. */
  1218. function JumpingRuntime() {
  1219. var fsm = this.fsm;
  1220. var minder = this.minder;
  1221. var receiver = this.receiver;
  1222. var container = this.container;
  1223. var receiverElement = receiver.element;
  1224. var hotbox = this.hotbox;
  1225. var compositionLock = false;
  1226. // normal -> *
  1227. receiver.listen("normal", function(e) {
  1228. // 为了防止处理进入edit模式而丢失处理的首字母,此时receiver必须为enable
  1229. receiver.enable();
  1230. // normal -> hotbox
  1231. if (e.is("Space")) {
  1232. e.preventDefault();
  1233. // safari下Space触发hotbox,然而这时Space已在receiver上留下作案痕迹,因此抹掉
  1234. if (kity.Browser.safari) {
  1235. receiverElement.innerHTML = "";
  1236. }
  1237. return fsm.jump("hotbox", "space-trigger");
  1238. }
  1239. /**
  1240. * check
  1241. * @editor Naixor
  1242. * @Date 2015-12-2
  1243. */
  1244. switch (e.type) {
  1245. case "keydown":
  1246. {
  1247. if (minder.getSelectedNode()) {
  1248. if (isIntendToInput(e)) {
  1249. return fsm.jump("input", "user-input");
  1250. }
  1251. } else {
  1252. receiverElement.innerHTML = "";
  1253. }
  1254. // normal -> normal shortcut
  1255. fsm.jump("normal", "shortcut-handle", e);
  1256. break;
  1257. }
  1258. case "keyup":
  1259. {
  1260. break;
  1261. }
  1262. default:
  1263. {}
  1264. }
  1265. });
  1266. // hotbox -> normal
  1267. receiver.listen("hotbox", function(e) {
  1268. receiver.disable();
  1269. e.preventDefault();
  1270. var handleResult = hotbox.dispatch(e);
  1271. if (hotbox.state() == Hotbox.STATE_IDLE && fsm.state() == "hotbox") {
  1272. return fsm.jump("normal", "hotbox-idle");
  1273. }
  1274. });
  1275. // input => normal
  1276. receiver.listen("input", function(e) {
  1277. receiver.enable();
  1278. if (e.type == "keydown") {
  1279. if (e.is("Enter")) {
  1280. e.preventDefault();
  1281. return fsm.jump("normal", "input-commit");
  1282. }
  1283. if (e.is("Esc")) {
  1284. e.preventDefault();
  1285. return fsm.jump("normal", "input-cancel");
  1286. }
  1287. if (e.is("Tab") || e.is("Shift + Tab")) {
  1288. e.preventDefault();
  1289. }
  1290. } else if (e.type == "keyup" && e.is("Esc")) {
  1291. e.preventDefault();
  1292. if (!compositionLock) {
  1293. return fsm.jump("normal", "input-cancel");
  1294. }
  1295. } else if (e.type == "compositionstart") {
  1296. compositionLock = true;
  1297. } else if (e.type == "compositionend") {
  1298. setTimeout(function() {
  1299. compositionLock = false;
  1300. });
  1301. }
  1302. });
  1303. //////////////////////////////////////////////
  1304. /// 右键呼出热盒
  1305. /// 判断的标准是:按下的位置和结束的位置一致
  1306. //////////////////////////////////////////////
  1307. var downX, downY;
  1308. var MOUSE_RB = 2;
  1309. // 右键
  1310. container.addEventListener("mousedown", function(e) {
  1311. if (e.button == MOUSE_RB) {
  1312. e.preventDefault();
  1313. }
  1314. if (fsm.state() == "hotbox") {
  1315. hotbox.active(Hotbox.STATE_IDLE);
  1316. fsm.jump("normal", "blur");
  1317. } else if (fsm.state() == "normal" && e.button == MOUSE_RB) {
  1318. downX = e.clientX;
  1319. downY = e.clientY;
  1320. }
  1321. }, false);
  1322. container.addEventListener("mousewheel", function(e) {
  1323. if (fsm.state() == "hotbox") {
  1324. hotbox.active(Hotbox.STATE_IDLE);
  1325. fsm.jump("normal", "mousemove-blur");
  1326. }
  1327. }, false);
  1328. container.addEventListener("contextmenu", function(e) {
  1329. e.preventDefault();
  1330. });
  1331. container.addEventListener("mouseup", function(e) {
  1332. if (fsm.state() != "normal") {
  1333. return;
  1334. }
  1335. if (e.button != MOUSE_RB || e.clientX != downX || e.clientY != downY) {
  1336. return;
  1337. }
  1338. if (!minder.getSelectedNode()) {
  1339. return;
  1340. }
  1341. fsm.jump("hotbox", "content-menu");
  1342. }, false);
  1343. // 阻止热盒事件冒泡,在热盒正确执行前导致热盒关闭
  1344. hotbox.$element.addEventListener("mousedown", function(e) {
  1345. e.stopPropagation();
  1346. });
  1347. }
  1348. return module.exports = JumpingRuntime;
  1349. }
  1350. };
  1351. //src/runtime/minder.js
  1352. /**
  1353. * @fileOverview
  1354. *
  1355. * 脑图示例运行时
  1356. *
  1357. * @author: techird
  1358. * @copyright: Baidu FEX, 2014
  1359. */
  1360. _p[14] = {
  1361. value: function(require, exports, module) {
  1362. var Minder = _p.r(4);
  1363. function MinderRuntime() {
  1364. // 不使用 kityminder 的按键处理,由 ReceiverRuntime 统一处理
  1365. var minder = new Minder({
  1366. enableKeyReceiver: false,
  1367. enableAnimation: true
  1368. });
  1369. // 渲染,初始化
  1370. minder.renderTo(this.selector);
  1371. minder.setTheme(null);
  1372. minder.select(minder.getRoot(), true);
  1373. minder.execCommand("text", "中心主题");
  1374. // 导出给其它 Runtime 使用
  1375. this.minder = minder;
  1376. }
  1377. return module.exports = MinderRuntime;
  1378. }
  1379. };
  1380. //src/runtime/node.js
  1381. _p[15] = {
  1382. value: function(require, exports, module) {
  1383. function NodeRuntime() {
  1384. var runtime = this;
  1385. var minder = this.minder;
  1386. var hotbox = this.hotbox;
  1387. var fsm = this.fsm;
  1388. var main = hotbox.state("main");
  1389. var buttons = [ "前移:Alt+Up:ArrangeUp", "下级:Tab|Insert:AppendChildNode", "同级:Enter:AppendSiblingNode", "后移:Alt+Down:ArrangeDown", "删除:Delete|Backspace:RemoveNode", "上级:Shift+Tab|Shift+Insert:AppendParentNode" ];
  1390. var AppendLock = 0;
  1391. buttons.forEach(function(button) {
  1392. var parts = button.split(":");
  1393. var label = parts.shift();
  1394. var key = parts.shift();
  1395. var command = parts.shift();
  1396. main.button({
  1397. position: "ring",
  1398. label: label,
  1399. key: key,
  1400. action: function() {
  1401. if (command.indexOf("Append") === 0) {
  1402. AppendLock++;
  1403. minder.execCommand(command, "分支主题");
  1404. // provide in input runtime
  1405. function afterAppend() {
  1406. if (!--AppendLock) {
  1407. runtime.editText();
  1408. }
  1409. minder.off("layoutallfinish", afterAppend);
  1410. }
  1411. minder.on("layoutallfinish", afterAppend);
  1412. } else {
  1413. minder.execCommand(command);
  1414. fsm.jump("normal", "command-executed");
  1415. }
  1416. },
  1417. enable: function() {
  1418. return minder.queryCommandState(command) != -1;
  1419. }
  1420. });
  1421. });
  1422. main.button({
  1423. position: "bottom",
  1424. label: "导入节点",
  1425. key: "Alt + V",
  1426. enable: function() {
  1427. var selectedNodes = minder.getSelectedNodes();
  1428. return selectedNodes.length == 1;
  1429. },
  1430. action: importNodeData,
  1431. next: "idle"
  1432. });
  1433. main.button({
  1434. position: "bottom",
  1435. label: "导出节点",
  1436. key: "Alt + C",
  1437. enable: function() {
  1438. var selectedNodes = minder.getSelectedNodes();
  1439. return selectedNodes.length == 1;
  1440. },
  1441. action: exportNodeData,
  1442. next: "idle"
  1443. });
  1444. function importNodeData() {
  1445. minder.fire("importNodeData");
  1446. }
  1447. function exportNodeData() {
  1448. minder.fire("exportNodeData");
  1449. }
  1450. }
  1451. return module.exports = NodeRuntime;
  1452. }
  1453. };
  1454. //src/runtime/priority.js
  1455. _p[16] = {
  1456. value: function(require, exports, module) {
  1457. function PriorityRuntime() {
  1458. var minder = this.minder;
  1459. var hotbox = this.hotbox;
  1460. var main = hotbox.state("main");
  1461. main.button({
  1462. position: "top",
  1463. label: "优先级",
  1464. key: "P",
  1465. next: "priority",
  1466. enable: function() {
  1467. return minder.queryCommandState("priority") != -1;
  1468. }
  1469. });
  1470. var priority = hotbox.state("priority");
  1471. "123456789".replace(/./g, function(p) {
  1472. priority.button({
  1473. position: "ring",
  1474. label: "P" + p,
  1475. key: p,
  1476. action: function() {
  1477. minder.execCommand("Priority", p);
  1478. }
  1479. });
  1480. });
  1481. priority.button({
  1482. position: "center",
  1483. label: "移除",
  1484. key: "Del",
  1485. action: function() {
  1486. minder.execCommand("Priority", 0);
  1487. }
  1488. });
  1489. priority.button({
  1490. position: "top",
  1491. label: "返回",
  1492. key: "esc",
  1493. next: "back"
  1494. });
  1495. }
  1496. return module.exports = PriorityRuntime;
  1497. }
  1498. };
  1499. //src/runtime/progress.js
  1500. _p[17] = {
  1501. value: function(require, exports, module) {
  1502. function ProgressRuntime() {
  1503. var minder = this.minder;
  1504. var hotbox = this.hotbox;
  1505. var main = hotbox.state("main");
  1506. main.button({
  1507. position: "top",
  1508. label: "进度",
  1509. key: "G",
  1510. next: "progress",
  1511. enable: function() {
  1512. return minder.queryCommandState("progress") != -1;
  1513. }
  1514. });
  1515. var progress = hotbox.state("progress");
  1516. "012345678".replace(/./g, function(p) {
  1517. progress.button({
  1518. position: "ring",
  1519. label: "G" + p,
  1520. key: p,
  1521. action: function() {
  1522. minder.execCommand("Progress", parseInt(p) + 1);
  1523. }
  1524. });
  1525. });
  1526. progress.button({
  1527. position: "center",
  1528. label: "移除",
  1529. key: "Del",
  1530. action: function() {
  1531. minder.execCommand("Progress", 0);
  1532. }
  1533. });
  1534. progress.button({
  1535. position: "top",
  1536. label: "返回",
  1537. key: "esc",
  1538. next: "back"
  1539. });
  1540. }
  1541. return module.exports = ProgressRuntime;
  1542. }
  1543. };
  1544. //src/runtime/receiver.js
  1545. /**
  1546. * @fileOverview
  1547. *
  1548. * 键盘事件接收/分发器
  1549. *
  1550. * @author: techird
  1551. * @copyright: Baidu FEX, 2014
  1552. */
  1553. _p[18] = {
  1554. value: function(require, exports, module) {
  1555. var key = _p.r(23);
  1556. var hotbox = _p.r(2);
  1557. function ReceiverRuntime() {
  1558. var fsm = this.fsm;
  1559. var minder = this.minder;
  1560. var me = this;
  1561. // 接收事件的 div
  1562. var element = document.createElement("div");
  1563. element.contentEditable = true;
  1564. /**
  1565. * @Desc: 增加tabindex属性使得element的contenteditable不管是trur还是false都能有focus和blur事件
  1566. * @Editor: Naixor
  1567. * @Date: 2015.09.14
  1568. */
  1569. element.setAttribute("tabindex", -1);
  1570. element.classList.add("receiver");
  1571. element.onkeydown = element.onkeypress = element.onkeyup = dispatchKeyEvent;
  1572. element.addEventListener("compositionstart", dispatchKeyEvent);
  1573. // element.addEventListener('compositionend', dispatchKeyEvent);
  1574. this.container.appendChild(element);
  1575. // receiver 对象
  1576. var receiver = {
  1577. element: element,
  1578. selectAll: function() {
  1579. // 保证有被选中的
  1580. if (!element.innerHTML) element.innerHTML = "&nbsp;";
  1581. var range = document.createRange();
  1582. var selection = window.getSelection();
  1583. range.selectNodeContents(element);
  1584. selection.removeAllRanges();
  1585. selection.addRange(range);
  1586. element.focus();
  1587. },
  1588. /**
  1589. * @Desc: 增加enable和disable方法用于解决热核态的输入法屏蔽问题
  1590. * @Editor: Naixor
  1591. * @Date: 2015.09.14
  1592. */
  1593. enable: function() {
  1594. element.setAttribute("contenteditable", true);
  1595. },
  1596. disable: function() {
  1597. element.setAttribute("contenteditable", false);
  1598. },
  1599. /**
  1600. * @Desc: hack FF下div contenteditable的光标丢失BUG
  1601. * @Editor: Naixor
  1602. * @Date: 2015.10.15
  1603. */
  1604. fixFFCaretDisappeared: function() {
  1605. element.removeAttribute("contenteditable");
  1606. element.setAttribute("contenteditable", "true");
  1607. element.blur();
  1608. element.focus();
  1609. },
  1610. /**
  1611. * 以此事件代替通过mouse事件来判断receiver丢失焦点的事件
  1612. * @editor Naixor
  1613. * @Date 2015-12-2
  1614. */
  1615. onblur: function(handler) {
  1616. element.onblur = handler;
  1617. }
  1618. };
  1619. receiver.selectAll();
  1620. minder.on("beforemousedown", receiver.selectAll);
  1621. minder.on("receiverfocus", receiver.selectAll);
  1622. minder.on("readonly", function() {
  1623. // 屏蔽minder的事件接受,删除receiver和hotbox
  1624. minder.disable();
  1625. editor.receiver.element.parentElement.removeChild(editor.receiver.element);
  1626. editor.hotbox.$container.removeChild(editor.hotbox.$element);
  1627. });
  1628. // 侦听器,接收到的事件会派发给所有侦听器
  1629. var listeners = [];
  1630. // 侦听指定状态下的事件,如果不传 state,侦听所有状态
  1631. receiver.listen = function(state, listener) {
  1632. if (arguments.length == 1) {
  1633. listener = state;
  1634. state = "*";
  1635. }
  1636. listener.notifyState = state;
  1637. listeners.push(listener);
  1638. };
  1639. function dispatchKeyEvent(e) {
  1640. e.is = function(keyExpression) {
  1641. var subs = keyExpression.split("|");
  1642. for (var i = 0; i < subs.length; i++) {
  1643. if (key.is(this, subs[i])) return true;
  1644. }
  1645. return false;
  1646. };
  1647. var listener, jumpState;
  1648. for (var i = 0; i < listeners.length; i++) {
  1649. listener = listeners[i];
  1650. // 忽略不在侦听状态的侦听器
  1651. if (listener.notifyState != "*" && listener.notifyState != fsm.state()) {
  1652. continue;
  1653. }
  1654. /**
  1655. *
  1656. * 对于所有的侦听器只允许一种处理方式跳转状态
  1657. * 如果侦听器确定要跳转则返回要跳转的状态
  1658. * 每个事件只允许一个侦听器进行状态跳转
  1659. * 跳转动作由侦听器自行完成因为可能需要在跳转时传递 reason返回跳转结果即可
  1660. * 比如
  1661. *
  1662. * ```js
  1663. * receiver.listen('normal', function(e) {
  1664. * if (isSomeReasonForJumpState(e)) {
  1665. * return fsm.jump('newstate', e);
  1666. * }
  1667. * });
  1668. * ```
  1669. */
  1670. if (listener.call(null, e)) {
  1671. return;
  1672. }
  1673. }
  1674. }
  1675. this.receiver = receiver;
  1676. }
  1677. return module.exports = ReceiverRuntime;
  1678. }
  1679. };
  1680. //src/tool/debug.js
  1681. /**
  1682. * @fileOverview
  1683. *
  1684. * 支持各种调试后门
  1685. *
  1686. * @author: techird
  1687. * @copyright: Baidu FEX, 2014
  1688. */
  1689. _p[19] = {
  1690. value: function(require, exports, module) {
  1691. var format = _p.r(20);
  1692. function noop() {}
  1693. function stringHash(str) {
  1694. var hash = 0;
  1695. for (var i = 0; i < str.length; i++) {
  1696. hash += str.charCodeAt(i);
  1697. }
  1698. return hash;
  1699. }
  1700. /* global console */
  1701. function Debug(flag) {
  1702. var debugMode = this.flaged = window.location.search.indexOf(flag) != -1;
  1703. if (debugMode) {
  1704. var h = stringHash(flag) % 360;
  1705. 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);
  1706. var textStyle = "background: none; color: black;";
  1707. this.log = function() {
  1708. var output = format.apply(null, arguments);
  1709. console.log(format("%c{0}%c{1}", flag, output), flagStyle, textStyle);
  1710. };
  1711. } else {
  1712. this.log = noop;
  1713. }
  1714. }
  1715. return module.exports = Debug;
  1716. }
  1717. };
  1718. //src/tool/format.js
  1719. _p[20] = {
  1720. value: function(require, exports, module) {
  1721. function format(template, args) {
  1722. if (typeof args != "object") {
  1723. args = [].slice.call(arguments, 1);
  1724. }
  1725. return String(template).replace(/\{(\w+)\}/gi, function(match, $key) {
  1726. return args[$key] || $key;
  1727. });
  1728. }
  1729. return module.exports = format;
  1730. }
  1731. };
  1732. //src/tool/innertext.js
  1733. /**
  1734. * @fileOverview
  1735. *
  1736. * innerText polyfill
  1737. *
  1738. * @author: techird
  1739. * @copyright: Baidu FEX, 2014
  1740. */
  1741. _p[21] = {
  1742. value: function(require, exports, module) {
  1743. if (!("innerText" in document.createElement("a")) && "getSelection" in window) {
  1744. HTMLElement.prototype.__defineGetter__("innerText", function() {
  1745. var selection = window.getSelection(), ranges = [], str, i;
  1746. // Save existing selections.
  1747. for (i = 0; i < selection.rangeCount; i++) {
  1748. ranges[i] = selection.getRangeAt(i);
  1749. }
  1750. // Deselect everything.
  1751. selection.removeAllRanges();
  1752. // Select `el` and all child nodes.
  1753. // 'this' is the element .innerText got called on
  1754. selection.selectAllChildren(this);
  1755. // Get the string representation of the selected nodes.
  1756. str = selection.toString();
  1757. // Deselect everything. Again.
  1758. selection.removeAllRanges();
  1759. // Restore all formerly existing selections.
  1760. for (i = 0; i < ranges.length; i++) {
  1761. selection.addRange(ranges[i]);
  1762. }
  1763. // Oh look, this is what we wanted.
  1764. // String representation of the element, close to as rendered.
  1765. return str;
  1766. });
  1767. HTMLElement.prototype.__defineSetter__("innerText", function(text) {
  1768. /**
  1769. * @Desc: 解决FireFox节点内容删除后text为null出现报错的问题
  1770. * @Editor: Naixor
  1771. * @Date: 2015.9.16
  1772. */
  1773. this.innerHTML = (text || "").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br>");
  1774. });
  1775. }
  1776. }
  1777. };
  1778. //src/tool/jsondiff.js
  1779. /**
  1780. * @fileOverview
  1781. *
  1782. *
  1783. *
  1784. * @author: techird
  1785. * @copyright: Baidu FEX, 2014
  1786. */
  1787. _p[22] = {
  1788. value: function(require, exports, module) {
  1789. /*!
  1790. * https://github.com/Starcounter-Jack/Fast-JSON-Patch
  1791. * json-patch-duplex.js 0.5.0
  1792. * (c) 2013 Joachim Wester
  1793. * MIT license
  1794. */
  1795. var _objectKeys = function() {
  1796. if (Object.keys) return Object.keys;
  1797. return function(o) {
  1798. var keys = [];
  1799. for (var i in o) {
  1800. if (o.hasOwnProperty(i)) {
  1801. keys.push(i);
  1802. }
  1803. }
  1804. return keys;
  1805. };
  1806. }();
  1807. function escapePathComponent(str) {
  1808. if (str.indexOf("/") === -1 && str.indexOf("~") === -1) return str;
  1809. return str.replace(/~/g, "~0").replace(/\//g, "~1");
  1810. }
  1811. function deepClone(obj) {
  1812. if (typeof obj === "object") {
  1813. return JSON.parse(JSON.stringify(obj));
  1814. } else {
  1815. return obj;
  1816. }
  1817. }
  1818. // Dirty check if obj is different from mirror, generate patches and update mirror
  1819. function _generate(mirror, obj, patches, path) {
  1820. var newKeys = _objectKeys(obj);
  1821. var oldKeys = _objectKeys(mirror);
  1822. var changed = false;
  1823. var deleted = false;
  1824. for (var t = oldKeys.length - 1; t >= 0; t--) {
  1825. var key = oldKeys[t];
  1826. var oldVal = mirror[key];
  1827. if (obj.hasOwnProperty(key)) {
  1828. var newVal = obj[key];
  1829. if (typeof oldVal == "object" && oldVal != null && typeof newVal == "object" && newVal != null) {
  1830. _generate(oldVal, newVal, patches, path + "/" + escapePathComponent(key));
  1831. } else {
  1832. if (oldVal != newVal) {
  1833. changed = true;
  1834. patches.push({
  1835. op: "replace",
  1836. path: path + "/" + escapePathComponent(key),
  1837. value: deepClone(newVal)
  1838. });
  1839. }
  1840. }
  1841. } else {
  1842. patches.push({
  1843. op: "remove",
  1844. path: path + "/" + escapePathComponent(key)
  1845. });
  1846. deleted = true;
  1847. }
  1848. }
  1849. if (!deleted && newKeys.length == oldKeys.length) {
  1850. return;
  1851. }
  1852. for (var t = 0; t < newKeys.length; t++) {
  1853. var key = newKeys[t];
  1854. if (!mirror.hasOwnProperty(key)) {
  1855. patches.push({
  1856. op: "add",
  1857. path: path + "/" + escapePathComponent(key),
  1858. value: deepClone(obj[key])
  1859. });
  1860. }
  1861. }
  1862. }
  1863. function compare(tree1, tree2) {
  1864. var patches = [];
  1865. _generate(tree1, tree2, patches, "");
  1866. return patches;
  1867. }
  1868. return module.exports = compare;
  1869. }
  1870. };
  1871. //src/tool/key.js
  1872. _p[23] = {
  1873. value: function(require, exports, module) {
  1874. var keymap = _p.r(24);
  1875. var CTRL_MASK = 4096;
  1876. var ALT_MASK = 8192;
  1877. var SHIFT_MASK = 16384;
  1878. function hash(unknown) {
  1879. if (typeof unknown == "string") {
  1880. return hashKeyExpression(unknown);
  1881. }
  1882. return hashKeyEvent(unknown);
  1883. }
  1884. function is(a, b) {
  1885. return a && b && hash(a) == hash(b);
  1886. }
  1887. exports.hash = hash;
  1888. exports.is = is;
  1889. function hashKeyEvent(keyEvent) {
  1890. var hashCode = 0;
  1891. if (keyEvent.ctrlKey || keyEvent.metaKey) {
  1892. hashCode |= CTRL_MASK;
  1893. }
  1894. if (keyEvent.altKey) {
  1895. hashCode |= ALT_MASK;
  1896. }
  1897. if (keyEvent.shiftKey) {
  1898. hashCode |= SHIFT_MASK;
  1899. }
  1900. // Shift, Control, Alt KeyCode ignored.
  1901. if ([ 16, 17, 18, 91 ].indexOf(keyEvent.keyCode) === -1) {
  1902. /**
  1903. * 解决浏览器输入法状态下对keyDown的keyCode判断不准确的问题,使用keyIdentifier,
  1904. * 可以解决chrome和safari下的各种问题,其他浏览器依旧有问题,然而那并不影响我们对特
  1905. * 需判断的按键进行判断(比如Space在safari输入法态下就是229,其他的就不是)
  1906. * @editor Naixor
  1907. * @Date 2015-12-2
  1908. */
  1909. if (keyEvent.keyCode === 229 && keyEvent.keyIdentifier) {
  1910. return hashCode |= parseInt(keyEvent.keyIdentifier.substr(2), 16);
  1911. }
  1912. hashCode |= keyEvent.keyCode;
  1913. }
  1914. return hashCode;
  1915. }
  1916. function hashKeyExpression(keyExpression) {
  1917. var hashCode = 0;
  1918. keyExpression.toLowerCase().split(/\s*\+\s*/).forEach(function(name) {
  1919. switch (name) {
  1920. case "ctrl":
  1921. case "cmd":
  1922. hashCode |= CTRL_MASK;
  1923. break;
  1924. case "alt":
  1925. hashCode |= ALT_MASK;
  1926. break;
  1927. case "shift":
  1928. hashCode |= SHIFT_MASK;
  1929. break;
  1930. default:
  1931. hashCode |= keymap[name];
  1932. }
  1933. });
  1934. return hashCode;
  1935. }
  1936. }
  1937. };
  1938. //src/tool/keymap.js
  1939. _p[24] = {
  1940. value: function(require, exports, module) {
  1941. var keymap = {
  1942. Shift: 16,
  1943. Control: 17,
  1944. Alt: 18,
  1945. CapsLock: 20,
  1946. BackSpace: 8,
  1947. Tab: 9,
  1948. Enter: 13,
  1949. Esc: 27,
  1950. Space: 32,
  1951. PageUp: 33,
  1952. PageDown: 34,
  1953. End: 35,
  1954. Home: 36,
  1955. Insert: 45,
  1956. Left: 37,
  1957. Up: 38,
  1958. Right: 39,
  1959. Down: 40,
  1960. Direction: {
  1961. 37: 1,
  1962. 38: 1,
  1963. 39: 1,
  1964. 40: 1
  1965. },
  1966. Del: 46,
  1967. NumLock: 144,
  1968. Cmd: 91,
  1969. CmdFF: 224,
  1970. F1: 112,
  1971. F2: 113,
  1972. F3: 114,
  1973. F4: 115,
  1974. F5: 116,
  1975. F6: 117,
  1976. F7: 118,
  1977. F8: 119,
  1978. F9: 120,
  1979. F10: 121,
  1980. F11: 122,
  1981. F12: 123,
  1982. "`": 192,
  1983. "=": 187,
  1984. "-": 189,
  1985. "/": 191,
  1986. ".": 190
  1987. };
  1988. // 小写适配
  1989. for (var key in keymap) {
  1990. if (keymap.hasOwnProperty(key)) {
  1991. keymap[key.toLowerCase()] = keymap[key];
  1992. }
  1993. }
  1994. var aKeyCode = 65;
  1995. var aCharCode = "a".charCodeAt(0);
  1996. // letters
  1997. "abcdefghijklmnopqrstuvwxyz".split("").forEach(function(letter) {
  1998. keymap[letter] = aKeyCode + (letter.charCodeAt(0) - aCharCode);
  1999. });
  2000. // numbers
  2001. var n = 9;
  2002. do {
  2003. keymap[n.toString()] = n + 48;
  2004. } while (--n);
  2005. module.exports = keymap;
  2006. }
  2007. };
  2008. var moduleMapping = {
  2009. "expose-editor": 1
  2010. };
  2011. function use(name) {
  2012. _p.r([ moduleMapping[name] ]);
  2013. }
  2014. angular.module('kityminderEditor', [
  2015. 'ui.bootstrap',
  2016. 'ui.codemirror',
  2017. 'ui.colorpicker'
  2018. ])
  2019. .config(["$sceDelegateProvider", function($sceDelegateProvider) {
  2020. $sceDelegateProvider.resourceUrlWhitelist([
  2021. // Allow same origin resource loads.
  2022. 'self',
  2023. // Allow loading from our assets domain. Notice the difference between * and **.
  2024. 'http://agroup.baidu.com:8910/**',
  2025. 'http://cq01-fe-rdtest01.vm.baidu.com:8910/**',
  2026. 'http://agroup.baidu.com:8911/**'
  2027. ]);
  2028. }]);
  2029. angular.module('kityminderEditor').run(['$templateCache', function($templateCache) {
  2030. 'use strict';
  2031. $templateCache.put('ui/directive/appendNode/appendNode.html',
  2032. "<div class=\"km-btn-group append-group\"><div class=\"km-btn-item append-child-node\" ng-disabled=\"minder.queryCommandState('AppendChildNode') === -1\" ng-click=\"minder.queryCommandState('AppendChildNode') === -1 || execCommand('AppendChildNode')\" title=\"{{ 'appendchildnode' | lang:'ui/command' }}\"><i class=\"km-btn-icon\"></i> <span class=\"km-btn-caption\">{{ 'appendchildnode' | lang:'ui/command' }}</span></div><div class=\"km-btn-item append-parent-node\" ng-disabled=\"minder.queryCommandState('AppendParentNode') === -1\" ng-click=\"minder.queryCommandState('AppendParentNode') === -1 || execCommand('AppendParentNode')\" title=\"{{ 'appendparentnode' | lang:'ui/command' }}\"><i class=\"km-btn-icon\"></i> <span class=\"km-btn-caption\">{{ 'appendparentnode' | lang:'ui/command' }}</span></div><div class=\"km-btn-item append-sibling-node\" ng-disabled=\"minder.queryCommandState('AppendSiblingNode') === -1\" ng-click=\"minder.queryCommandState('AppendSiblingNode') === -1 ||execCommand('AppendSiblingNode')\" title=\"{{ 'appendsiblingnode' | lang:'ui/command' }}\"><i class=\"km-btn-icon\"></i> <span class=\"km-btn-caption\">{{ 'appendsiblingnode' | lang:'ui/command' }}</span></div></div>"
  2033. );
  2034. $templateCache.put('ui/directive/arrange/arrange.html',
  2035. "<div class=\"km-btn-group arrange-group\"><div class=\"km-btn-item arrange-up\" ng-disabled=\"minder.queryCommandState('ArrangeUp') === -1\" ng-click=\"minder.queryCommandState('ArrangeUp') === -1 || minder.execCommand('ArrangeUp')\" title=\"{{ 'arrangeup' | lang:'ui/command' }}\"><i class=\"km-btn-icon\"></i> <span class=\"km-btn-caption\">{{ 'arrangeup' | lang:'ui/command' }}</span></div><div class=\"km-btn-item arrange-down\" ng-disabled=\"minder.queryCommandState('ArrangeDown') === -1\" ng-click=\"minder.queryCommandState('ArrangeDown') === -1 || minder.execCommand('ArrangeDown');\" title=\"{{ 'arrangedown' | lang:'ui/command' }}\"><i class=\"km-btn-icon\"></i> <span class=\"km-btn-caption\">{{ 'arrangedown' | lang:'ui/command' }}</span></div></div>"
  2036. );
  2037. $templateCache.put('ui/directive/colorPanel/colorPanel.html',
  2038. "<div class=\"bg-color-wrap\"><span class=\"quick-bg-color\" ng-click=\"minder.queryCommandState('background') === -1 || minder.execCommand('background', bgColor)\" ng-disabled=\"minder.queryCommandState('background') === -1\"></span> <span color-picker class=\"bg-color\" set-color=\"setDefaultBg()\" ng-disabled=\"minder.queryCommandState('background') === -1\"><span class=\"caret\"></span></span> <span class=\"bg-color-preview\" ng-style=\"{ 'background-color': bgColor }\" ng-click=\"minder.queryCommandState('background') === -1 || minder.execCommand('background', bgColor)\" ng-disabled=\"minder.queryCommandState('background') === -1\"></span></div>"
  2039. );
  2040. $templateCache.put('ui/directive/expandLevel/expandLevel.html',
  2041. "<div class=\"btn-group-vertical\" dropdown is-open=\"isopen\"><button type=\"button\" class=\"btn btn-default expand\" title=\"{{ 'expandtoleaf' | lang:'ui' }}\" ng-class=\"{'active': isopen}\" ng-click=\"minder.execCommand('ExpandToLevel', 9999)\"></button> <button type=\"button\" class=\"btn btn-default expand-caption dropdown-toggle\" title=\"{{ 'expandtoleaf' | lang:'ui' }}\" dropdown-toggle><span class=\"caption\">{{ 'expandtoleaf' | lang:'ui' }}</span> <span class=\"caret\"></span> <span class=\"sr-only\">{{ 'expandtoleaf' | lang:'ui' }}</span></button><ul class=\"dropdown-menu\" role=\"menu\"><li ng-repeat=\"level in levels\"><a href ng-click=\"minder.execCommand('ExpandToLevel', level)\">{{ 'expandtolevel' + level | lang:'ui/command' }}</a></li></ul></div>"
  2042. );
  2043. $templateCache.put('ui/directive/fontOperator/fontOperator.html',
  2044. "<div class=\"font-operator\"><div class=\"dropdown font-family-list\" dropdown><div class=\"dropdown-toggle current-font-item\" dropdown-toggle ng-disabled=\"minder.queryCommandState('fontfamily') === -1\"><a href class=\"current-font-family\" title=\"{{ 'fontfamily' | lang: 'ui' }}\">{{ getFontfamilyName(minder.queryCommandValue('fontfamily')) || '字体' }}</a> <span class=\"caret\"></span></div><ul class=\"dropdown-menu font-list\"><li ng-repeat=\"f in fontFamilyList\" class=\"font-item-wrap\"><a ng-click=\"minder.execCommand('fontfamily', f.val)\" class=\"font-item\" ng-class=\"{ 'font-item-selected' : f == minder.queryCommandValue('fontfamily') }\" ng-style=\"{'font-family': f.val }\">{{ f.name }}</a></li></ul></div><div class=\"dropdown font-size-list\" dropdown><div class=\"dropdown-toggle current-font-item\" dropdown-toggle ng-disabled=\"minder.queryCommandState('fontsize') === -1\"><a href class=\"current-font-size\" title=\"{{ 'fontsize' | lang: 'ui' }}\">{{ minder.queryCommandValue('fontsize') || '字号' }}</a> <span class=\"caret\"></span></div><ul class=\"dropdown-menu font-list\"><li ng-repeat=\"f in fontSizeList\" class=\"font-item-wrap\"><a ng-click=\"minder.execCommand('fontsize', f)\" class=\"font-item\" ng-class=\"{ 'font-item-selected' : f == minder.queryCommandValue('fontsize') }\" ng-style=\"{'font-size': f + 'px'}\">{{ f }}</a></li></ul></div><span class=\"s-btn-icon font-bold\" ng-click=\"minder.queryCommandState('bold') === -1 || minder.execCommand('bold')\" ng-class=\"{'font-bold-selected' : minder.queryCommandState('bold') == 1}\" ng-disabled=\"minder.queryCommandState('bold') === -1\"></span> <span class=\"s-btn-icon font-italics\" ng-click=\"minder.queryCommandState('italic') === -1 || minder.execCommand('italic')\" ng-class=\"{'font-italics-selected' : minder.queryCommandState('italic') == 1}\" ng-disabled=\"minder.queryCommandState('italic') === -1\"></span><div class=\"font-color-wrap\"><span class=\"quick-font-color\" ng-click=\"minder.queryCommandState('forecolor') === -1 || minder.execCommand('forecolor', foreColor)\" ng-disabled=\"minder.queryCommandState('forecolor') === -1\">A</span> <span color-picker class=\"font-color\" set-color=\"setDefaultColor()\" ng-disabled=\"minder.queryCommandState('forecolor') === -1\"><span class=\"caret\"></span></span> <span class=\"font-color-preview\" ng-style=\"{ 'background-color': foreColor }\" ng-click=\"minder.queryCommandState('forecolor') === -1 || minder.execCommand('forecolor', foreColor)\" ng-disabled=\"minder.queryCommandState('forecolor') === -1\"></span></div><color-panel minder=\"minder\" class=\"inline-directive\"></color-panel></div>"
  2045. );
  2046. $templateCache.put('ui/directive/hyperLink/hyperLink.html',
  2047. "<div class=\"btn-group-vertical\" dropdown is-open=\"isopen\"><button type=\"button\" class=\"btn btn-default hyperlink\" title=\"{{ 'link' | lang:'ui' }}\" ng-class=\"{'active': isopen}\" ng-click=\"addHyperlink()\" ng-disabled=\"minder.queryCommandState('HyperLink') === -1\"></button> <button type=\"button\" class=\"btn btn-default hyperlink-caption dropdown-toggle\" ng-disabled=\"minder.queryCommandState('HyperLink') === -1\" title=\"{{ 'link' | lang:'ui' }}\" dropdown-toggle><span class=\"caption\">{{ 'link' | lang:'ui' }}</span> <span class=\"caret\"></span> <span class=\"sr-only\">{{ 'link' | lang:'ui' }}</span></button><ul class=\"dropdown-menu\" role=\"menu\"><li><a href ng-click=\"addHyperlink()\">{{ 'insertlink' | lang:'ui' }}</a></li><li><a href ng-click=\"minder.execCommand('HyperLink', null)\">{{ 'removelink' | lang:'ui' }}</a></li></ul></div>"
  2048. );
  2049. $templateCache.put('ui/directive/imageBtn/imageBtn.html',
  2050. "<div class=\"btn-group-vertical\" dropdown is-open=\"isopen\"><button type=\"button\" class=\"btn btn-default image-btn\" title=\"{{ 'image' | lang:'ui' }}\" ng-class=\"{'active': isopen}\" ng-click=\"addImage()\" ng-disabled=\"minder.queryCommandState('Image') === -1\"></button> <button type=\"button\" class=\"btn btn-default image-btn-caption dropdown-toggle\" ng-disabled=\"minder.queryCommandState('Image') === -1\" title=\"{{ 'image' | lang:'ui' }}\" dropdown-toggle><span class=\"caption\">{{ 'image' | lang:'ui' }}</span> <span class=\"caret\"></span> <span class=\"sr-only\">{{ 'image' | lang:'ui' }}</span></button><ul class=\"dropdown-menu\" role=\"menu\"><li><a href ng-click=\"addImage()\">{{ 'insertimage' | lang:'ui' }}</a></li><li><a href ng-click=\"minder.execCommand('Image', '')\">{{ 'removeimage' | lang:'ui' }}</a></li></ul></div>"
  2051. );
  2052. $templateCache.put('ui/directive/kityminderEditor/kityminderEditor.html',
  2053. "<div class=\"minder-editor-container\"><div class=\"top-tab\" top-tab=\"minder\" editor=\"editor\" ng-if=\"minder\"></div><div search-box minder=\"minder\" ng-if=\"minder\"></div><div class=\"minder-editor\"></div><div class=\"km-note\" note-editor minder=\"minder\" ng-if=\"minder\"></div><div class=\"note-previewer\" note-previewer ng-if=\"minder\"></div><div class=\"navigator\" navigator minder=\"minder\" ng-if=\"minder\"></div></div>"
  2054. );
  2055. $templateCache.put('ui/directive/kityminderViewer/kityminderViewer.html',
  2056. "<div class=\"minder-editor-container\"><div class=\"minder-viewer\"></div><div class=\"note-previewer\" note-previewer ng-if=\"minder\"></div><div class=\"navigator\" navigator minder=\"minder\" ng-if=\"minder\"></div></div>"
  2057. );
  2058. $templateCache.put('ui/directive/layout/layout.html',
  2059. "<div class=\"readjust-layout\"><a ng-click=\"minder.queryCommandState('resetlayout') === -1 || minder.execCommand('resetlayout')\" class=\"btn-wrap\" ng-disabled=\"minder.queryCommandState('resetlayout') === -1\"><span class=\"btn-icon reset-layout-icon\"></span> <span class=\"btn-label\">{{ 'resetlayout' | lang: 'ui/command' }}</span></a></div>"
  2060. );
  2061. $templateCache.put('ui/directive/navigator/navigator.html',
  2062. "<div class=\"nav-bar\"><div class=\"nav-btn zoom-in\" ng-click=\"minder.execCommand('zoomIn')\" title=\"{{ 'zoom-in' | lang : 'ui' }}\" ng-class=\"{ 'active' : getZoomRadio(zoom) == 0 }\"><div class=\"icon\"></div></div><div class=\"zoom-pan\"><div class=\"origin\" ng-style=\"{'transform': 'translate(0, ' + getHeight(100) + 'px)'}\" ng-click=\"minder.execCommand('zoom', 100);\"></div><div class=\"indicator\" ng-style=\"{\n" +
  2063. " 'transform': 'translate(0, ' + getHeight(zoom) + 'px)',\n" +
  2064. " 'transition': 'transform 200ms'\n" +
  2065. " }\"></div></div><div class=\"nav-btn zoom-out\" ng-click=\"minder.execCommand('zoomOut')\" title=\"{{ 'zoom-out' | lang : 'ui' }}\" ng-class=\"{ 'active' : getZoomRadio(zoom) == 1 }\"><div class=\"icon\"></div></div><div class=\"nav-btn hand\" ng-click=\"minder.execCommand('hand')\" title=\"{{ 'hand' | lang : 'ui' }}\" ng-class=\"{ 'active' : minder.queryCommandState('hand') == 1 }\"><div class=\"icon\"></div></div><div class=\"nav-btn camera\" ng-click=\"minder.execCommand('camera', minder.getRoot(), 600);\" title=\"{{ 'camera' | lang : 'ui' }}\"><div class=\"icon\"></div></div><div class=\"nav-btn nav-trigger\" ng-class=\"{'active' : isNavOpen}\" ng-click=\"toggleNavOpen()\" title=\"{{ 'navigator' | lang : 'ui' }}\"><div class=\"icon\"></div></div></div><div class=\"nav-previewer\" ng-show=\"isNavOpen\"></div>"
  2066. );
  2067. $templateCache.put('ui/directive/noteBtn/noteBtn.html',
  2068. "<div class=\"btn-group-vertical note-btn-group\" dropdown is-open=\"isopen\"><button type=\"button\" class=\"btn btn-default note-btn\" title=\"{{ 'note' | lang:'ui' }}\" ng-class=\"{'active': isopen}\" ng-click=\"addNote()\" ng-disabled=\"minder.queryCommandState('note') === -1\"></button> <button type=\"button\" class=\"btn btn-default note-btn-caption dropdown-toggle\" ng-disabled=\"minder.queryCommandState('note') === -1\" title=\"{{ 'note' | lang:'ui' }}\" dropdown-toggle><span class=\"caption\">{{ 'note' | lang:'ui' }}</span> <span class=\"caret\"></span> <span class=\"sr-only\">{{ 'note' | lang:'ui' }}</span></button><ul class=\"dropdown-menu\" role=\"menu\"><li><a href ng-click=\"addNote()\">{{ 'insertnote' | lang:'ui' }}</a></li><li><a href ng-click=\"minder.execCommand('note', null)\">{{ 'removenote' | lang:'ui' }}</a></li></ul></div>"
  2069. );
  2070. $templateCache.put('ui/directive/noteEditor/noteEditor.html',
  2071. "<div class=\"panel panel-default\" ng-init=\"noteEditorOpen = false\" ng-show=\"noteEditorOpen\"><div class=\"panel-heading\"><h3 class=\"panel-title\">备注</h3><span>(<a class=\"help\" href=\"https://www.zybuluo.com/techird/note/46064\" target=\"_blank\">支持 GFM 语法书写</a>)</span> <i class=\"close-note-editor glyphicon glyphicon-remove\" ng-click=\"closeNoteEditor()\"></i></div><div class=\"panel-body\"><div ng-show=\"noteEnabled\" ui-codemirror=\"{ onLoad: codemirrorLoaded }\" ng-model=\"noteContent\" ui-codemirror-opts=\"{\n" +
  2072. " gfm: true,\n" +
  2073. " breaks: true,\n" +
  2074. " lineWrapping : true,\n" +
  2075. " mode: 'gfm',\n" +
  2076. " dragDrop: false,\n" +
  2077. " lineNumbers:true\n" +
  2078. " }\"></div><p ng-show=\"!noteEnabled\" class=\"km-note-tips\">请选择节点编辑备注</p></div></div>"
  2079. );
  2080. $templateCache.put('ui/directive/notePreviewer/notePreviewer.html',
  2081. "<div id=\"previewer-content\" ng-show=\"showNotePreviewer\" ng-style=\"previewerStyle\" ng-bind-html=\"noteContent\"></div>"
  2082. );
  2083. $templateCache.put('ui/directive/operation/operation.html',
  2084. "<div class=\"km-btn-group operation-group\"><div class=\"km-btn-item edit-node\" ng-disabled=\"minder.queryCommandState('text') === -1\" ng-click=\"minder.queryCommandState('text') === -1 || editNode()\" title=\"{{ 'editnode' | lang:'ui/command' }}\"><i class=\"km-btn-icon\"></i> <span class=\"km-btn-caption\">{{ 'editnode' | lang:'ui/command' }}</span></div><div class=\"km-btn-item remove-node\" ng-disabled=\"minder.queryCommandState('RemoveNode') === -1\" ng-click=\"minder.queryCommandState('RemoveNode') === -1 || minder.execCommand('RemoveNode');\" title=\"{{ 'removenode' | lang:'ui/command' }}\"><i class=\"km-btn-icon\"></i> <span class=\"km-btn-caption\">{{ 'removenode' | lang:'ui/command' }}</span></div></div>"
  2085. );
  2086. $templateCache.put('ui/directive/priorityEditor/priorityEditor.html',
  2087. "<ul class=\"km-priority tool-group\" ng-disabled=\"commandDisabled\"><li class=\"km-priority-item tool-group-item\" ng-repeat=\"p in priorities\" ng-click=\"commandDisabled || minder.execCommand('priority', p)\" ng-class=\"{ active: commandValue == p }\" title=\"{{ getPriorityTitle(p) }}\"><div class=\"km-priority-icon tool-group-icon priority-{{p}}\"></div></li></ul>"
  2088. );
  2089. $templateCache.put('ui/directive/progressEditor/progressEditor.html',
  2090. "<ul class=\"km-progress tool-group\" ng-disabled=\"commandDisabled\"><li class=\"km-progress-item tool-group-item\" ng-repeat=\"p in progresses\" ng-click=\"commandDisabled || minder.execCommand('progress', p)\" ng-class=\"{ active: commandValue == p }\" title=\"{{ getProgressTitle(p) }}\"><div class=\"km-progress-icon tool-group-icon progress-{{p}}\"></div></li></ul>"
  2091. );
  2092. $templateCache.put('ui/directive/resourceEditor/resourceEditor.html',
  2093. "<div class=\"resource-editor\"><div class=\"input-group\"><input class=\"form-control\" type=\"text\" ng-model=\"newResourceName\" ng-required ng-keypress=\"$event.keyCode == 13 && addResource(newResourceName)\" ng-disabled=\"!enabled\"> <span class=\"input-group-btn\"><button class=\"btn btn-default\" ng-click=\"addResource(newResourceName)\" ng-disabled=\"!enabled\">添加</button></span></div><div class=\"resource-dropdown clearfix\" id=\"resource-dropdown\"><ul class=\"km-resource\" ng-init=\"resourceListOpen = false\" ng-class=\"{'open': resourceListOpen}\"><li ng-repeat=\"resource in used\" ng-disabled=\"!enabled\" ng-blur=\"blurCB()\"><label style=\"background: {{resourceColor(resource.name)}}\"><input type=\"checkbox\" ng-model=\"resource.selected\" ng-disabled=\"!enabled\"> <span>{{resource.name}}</span></label></li></ul><div class=\"resource-caret\" click-anywhere-but-here=\"resourceListOpen = false\" is-active=\"resourceListOpen\" ng-click=\"resourceListOpen = !resourceListOpen\"><span class=\"caret\"></span></div></div></div>"
  2094. );
  2095. $templateCache.put('ui/directive/searchBox/searchBox.html',
  2096. "<div id=\"search\" class=\"search-box clearfix\" ng-show=\"showSearch\"><div class=\"input-group input-group-sm search-input-wrap\"><input type=\"text\" id=\"search-input\" class=\"form-control search-input\" ng-model=\"keyword\" ng-keydown=\"handleKeyDown($event)\" aria-describedby=\"basic-addon2\"> <span class=\"input-group-addon search-addon\" id=\"basic-addon2\" ng-show=\"showTip\" ng-bind=\"'第 ' + curIndex + ' 条,共 ' + resultNum + ' 条'\"></span></div><div class=\"btn-group btn-group-sm prev-and-next-btn\" role=\"group\"><button type=\"button\" class=\"btn btn-default\" ng-click=\"doSearch(keyword, 'prev')\"><span class=\"glyphicon glyphicon-chevron-up\"></span></button> <button type=\"button\" class=\"btn btn-default\" ng-click=\"doSearch(keyword, 'next')\"><span class=\"glyphicon glyphicon-chevron-down\"></span></button></div><div class=\"close-search\" ng-click=\"exitSearch()\"><span class=\"glyphicon glyphicon-remove\"></span></div></div>"
  2097. );
  2098. $templateCache.put('ui/directive/searchBtn/searchBtn.html',
  2099. "<div class=\"btn-group-vertical\" dropdown is-open=\"isopen\"><button type=\"button\" class=\"btn btn-default search\" title=\"{{ 'search' | lang:'ui' }}\" ng-class=\"{'active': isopen}\" ng-click=\"enterSearch()\"></button> <button type=\"button\" class=\"btn btn-default search-caption dropdown-toggle\" ng-click=\"enterSearch()\" title=\"{{ 'search' | lang:'ui' }}\"><span class=\"caption\">{{ 'search' | lang:'ui' }}</span> <span class=\"sr-only\">{{ 'search' | lang:'ui' }}</span></button></div>"
  2100. );
  2101. $templateCache.put('ui/directive/selectAll/selectAll.html',
  2102. "<div class=\"btn-group-vertical\" dropdown is-open=\"isopen\"><button type=\"button\" class=\"btn btn-default select\" title=\"{{ 'selectall' | lang:'ui' }}\" ng-class=\"{'active': isopen}\" ng-click=\"select['all']()\"></button> <button type=\"button\" class=\"btn btn-default select-caption dropdown-toggle\" title=\"{{ 'selectall' | lang:'ui' }}\" dropdown-toggle><span class=\"caption\">{{ 'selectall' | lang:'ui' }}</span> <span class=\"caret\"></span> <span class=\"sr-only\">{{ 'selectall' | lang:'ui' }}</span></button><ul class=\"dropdown-menu\" role=\"menu\"><li ng-repeat=\"item in items\"><a href ng-click=\"select[item]()\">{{ 'select' + item | lang:'ui' }}</a></li></ul></div>"
  2103. );
  2104. $templateCache.put('ui/directive/styleOperator/styleOperator.html',
  2105. "<div class=\"style-operator\"><a ng-click=\"minder.queryCommandState('clearstyle') === -1 || minder.execCommand('clearstyle')\" class=\"btn-wrap clear-style\" ng-disabled=\"minder.queryCommandState('clearstyle') === -1\"><span class=\"btn-icon clear-style-icon\"></span> <span class=\"btn-label\">{{ 'clearstyle' | lang: 'ui' }}</span></a><div class=\"s-btn-group-vertical\"><a class=\"s-btn-wrap\" href ng-click=\"minder.queryCommandState('copystyle') === -1 || minder.execCommand('copystyle')\" ng-disabled=\"minder.queryCommandState('copystyle') === -1\"><span class=\"s-btn-icon copy-style-icon\"></span> <span class=\"s-btn-label\">{{ 'copystyle' | lang: 'ui' }}</span></a> <a class=\"s-btn-wrap paste-style-wrap\" href ng-click=\"minder.queryCommandState('pastestyle') === -1 || minder.execCommand('pastestyle')\" ng-disabled=\"minder.queryCommandState('pastestyle') === -1\"><span class=\"s-btn-icon paste-style-icon\"></span> <span class=\"s-btn-label\">{{ 'pastestyle' | lang: 'ui' }}</span></a></div></div>"
  2106. );
  2107. $templateCache.put('ui/directive/templateList/templateList.html',
  2108. "<div class=\"dropdown temp-panel\" dropdown on-toggle=\"toggled(open)\"><div class=\"dropdown-toggle current-temp-item\" ng-disabled=\"minder.queryCommandState('template') === -1\" dropdown-toggle><a href class=\"temp-item {{ minder.queryCommandValue('template') }}\" title=\"{{ minder.queryCommandValue('template') | lang: 'template' }}\"></a> <span class=\"caret\"></span></div><ul class=\"dropdown-menu temp-list\"><li ng-repeat=\"(key, templateObj) in templateList\" class=\"temp-item-wrap\"><a ng-click=\"minder.execCommand('template', key);\" class=\"temp-item {{key}}\" ng-class=\"{ 'temp-item-selected' : key == minder.queryCommandValue('template') }\" title=\"{{ key | lang: 'template' }}\"></a></li></ul></div>"
  2109. );
  2110. $templateCache.put('ui/directive/themeList/themeList.html',
  2111. "<div class=\"dropdown theme-panel\" dropdown><div class=\"dropdown-toggle theme-item-selected\" dropdown-toggle ng-disabled=\"minder.queryCommandState('theme') === -1\"><a href class=\"theme-item\" ng-style=\"getThemeThumbStyle(minder.queryCommandValue('theme'))\" title=\"{{ minder.queryCommandValue('theme') | lang: 'theme'; }}\">{{ minder.queryCommandValue('theme') | lang: 'theme'; }}</a> <span class=\"caret\"></span></div><ul class=\"dropdown-menu theme-list\"><li ng-repeat=\"key in themeKeyList\" class=\"theme-item-wrap\"><a ng-click=\"minder.execCommand('theme', key);\" class=\"theme-item\" ng-style=\"getThemeThumbStyle(key)\" title=\"{{ key | lang: 'theme'; }}\">{{ key | lang: 'theme'; }}</a></li></ul></div>"
  2112. );
  2113. $templateCache.put('ui/directive/topTab/topTab.html',
  2114. "<tabset><tab heading=\"{{ 'idea' | lang: 'ui/tabs'; }}\" ng-click=\"toggleTopTab('idea')\" select=\"setCurTab('idea')\"><undo-redo editor=\"editor\"></undo-redo><append-node minder=\"minder\"></append-node><arrange minder=\"minder\"></arrange><operation minder=\"minder\"></operation><hyper-link minder=\"minder\"></hyper-link><image-btn minder=\"minder\"></image-btn><note-btn minder=\"minder\"></note-btn><priority-editor minder=\"minder\"></priority-editor><progress-editor minder=\"minder\"></progress-editor><resource-editor minder=\"minder\"></resource-editor></tab><tab heading=\"{{ 'appearence' | lang: 'ui/tabs'; }}\" ng-click=\"toggleTopTab('appearance')\" select=\"setCurTab('appearance')\"><template-list minder=\"minder\" class=\"inline-directive\"></template-list><theme-list minder=\"minder\"></theme-list><layout minder=\"minder\" class=\"inline-directive\"></layout><style-operator minder=\"minder\" class=\"inline-directive\"></style-operator><font-operator minder=\"minder\" class=\"inline-directive\"></font-operator></tab><tab heading=\"{{ 'view' | lang: 'ui/tabs'; }}\" ng-click=\"toggleTopTab('view')\" select=\"setCurTab('view')\"><expand-level minder=\"minder\"></expand-level><select-all minder=\"minder\"></select-all><search-btn minder=\"minder\"></search-btn></tab></tabset>"
  2115. );
  2116. $templateCache.put('ui/directive/undoRedo/undoRedo.html',
  2117. "<div class=\"km-btn-group do-group\"><div class=\"km-btn-item undo\" ng-disabled=\"editor.history.hasUndo() == false\" ng-click=\"editor.history.hasUndo() == false || editor.history.undo();\" title=\"{{ 'undo' | lang:'ui' }}\"><i class=\"km-btn-icon\"></i></div><div class=\"km-btn-item redo\" ng-disabled=\"editor.history.hasRedo() == false\" ng-click=\"editor.history.hasRedo() == false || editor.history.redo()\" title=\"{{ 'redo' | lang:'ui' }}\"><i class=\"km-btn-icon\"></i></div></div>"
  2118. );
  2119. $templateCache.put('ui/dialog/hyperlink/hyperlink.tpl.html',
  2120. "<div class=\"modal-header\"><h3 class=\"modal-title\">链接</h3></div><div class=\"modal-body\"><form><div class=\"form-group\" id=\"link-url-wrap\" ng-class=\"{true: 'has-success', false: 'has-error'}[urlPassed]\"><label for=\"link-url\">链接地址:</label><input type=\"text\" class=\"form-control\" ng-model=\"url\" ng-blur=\"urlPassed = R_URL.test(url)\" ng-focus=\"this.value = url\" ng-keydown=\"shortCut($event)\" id=\"link-url\" placeholder=\"必填:以 http(s):// 或 ftp:// 开头\"></div><div class=\"form-group\" ng-class=\"{'has-success' : titlePassed}\"><label for=\"link-title\">提示文本:</label><input type=\"text\" class=\"form-control\" ng-model=\"title\" ng-blur=\"titlePassed = true\" id=\"link-title\" placeholder=\"选填:鼠标在链接上悬停时提示的文本\"></div></form></div><div class=\"modal-footer\"><button class=\"btn btn-primary\" ng-click=\"ok()\">确定</button> <button class=\"btn btn-warning\" ng-click=\"cancel()\">取消</button></div>"
  2121. );
  2122. $templateCache.put('ui/dialog/imExportNode/imExportNode.tpl.html',
  2123. "<div class=\"modal-header\"><h3 class=\"modal-title\">{{ title }}</h3></div><div class=\"modal-body\"><textarea type=\"text\" class=\"form-control single-input\" rows=\"8\" ng-keydown=\"shortCut($event);\" ng-model=\"value\" ng-readonly=\"type === 'export'\">\n" +
  2124. " </textarea></div><div class=\"modal-footer\"><button class=\"btn btn-primary\" ng-click=\"ok()\" ng-disabled=\"type === 'import' && value == ''\">OK</button> <button class=\"btn btn-warning\" ng-click=\"cancel()\">Cancel</button></div>"
  2125. );
  2126. $templateCache.put('ui/dialog/image/image.tpl.html',
  2127. "<div class=\"modal-header\"><h3 class=\"modal-title\">图片</h3></div><div class=\"modal-body\"><tabset><tab heading=\"图片搜索\"><form class=\"form-inline\"><div class=\"form-group\"><label for=\"search-keyword\">关键词:</label><input type=\"text\" class=\"form-control\" ng-model=\"data.searchKeyword2\" id=\"search-keyword\" placeholder=\"请输入搜索的关键词\"></div><button class=\"btn btn-primary\" ng-click=\"searchImage()\">百度一下</button></form><div class=\"search-result\" id=\"search-result\"><ul><li ng-repeat=\"image in list\" id=\"{{ 'img-item' + $index }}\" ng-class=\"{'selected' : isSelected}\" ng-click=\"selectImage($event)\"><img id=\"{{ 'img-' + $index }}\" ng-src=\"{{ image.src || '' }}\" alt=\"{{ image.title }}\" onerror=\"this.parentNode.removeChild(this)\"> <span>{{ image.title }}</span></li></ul></div></tab><tab heading=\"外链图片\"><form><div class=\"form-group\" ng-class=\"{true: 'has-success', false: 'has-error'}[urlPassed]\"><label for=\"image-url\">链接地址:</label><input type=\"text\" class=\"form-control\" ng-model=\"data.url\" ng-blur=\"urlPassed = data.R_URL.test(data.url)\" ng-focus=\"this.value = data.url\" ng-keydown=\"shortCut($event)\" id=\"image-url\" placeholder=\"必填:以 http(s):// 开头\"></div><div class=\"form-group\" ng-class=\"{'has-success' : titlePassed}\"><label for=\"image-title\">提示文本:</label><input type=\"text\" class=\"form-control\" ng-model=\"data.title\" ng-blur=\"titlePassed = true\" id=\"image-title\" placeholder=\"选填:鼠标在图片上悬停时提示的文本\"></div><div class=\"form-group\"><label for=\"image-preview\">图片预览:</label><img class=\"image-preview\" id=\"image-preview\" ng-src=\"{{ data.url }}\" alt=\"{{ data.title }}\"></div></form></tab><tab heading=\"上传图片\" active=\"true\"><form><div class=\"form-group\"><input type=\"file\" name=\"upload-image\" id=\"upload-image\" class=\"upload-image\" accept=\".jpg,.JPG,jpeg,JPEG,.png,.PNG,.gif,.GIF\" onchange=\"angular.element(this).scope().uploadImage()\"><label for=\"upload-image\" class=\"btn btn-primary\"><span>选择文件&hellip;</span></label></div><div class=\"form-group\" ng-class=\"{'has-success' : titlePassed}\"><label for=\"image-title\">提示文本:</label><input type=\"text\" class=\"form-control\" ng-model=\"data.title\" ng-blur=\"titlePassed = true\" id=\"image-title\" placeholder=\"选填:鼠标在图片上悬停时提示的文本\"></div><div class=\"form-group\"><label for=\"image-preview\">图片预览:</label><img class=\"image-preview\" id=\"image-preview\" ng-src=\"{{ data.url }}\" title=\"{{ data.title }}\" alt=\"{{ data.title }}\"></div></form></tab></tabset></div><div class=\"modal-footer\"><button class=\"btn btn-primary\" ng-click=\"ok()\">确定</button> <button class=\"btn btn-warning\" ng-click=\"cancel()\">取消</button></div>"
  2128. );
  2129. }]);
  2130. angular.module('kityminderEditor').service('commandBinder', function() {
  2131. return {
  2132. bind: function(minder, command, scope) {
  2133. minder.on('interactchange', function() {
  2134. scope.commandDisabled = minder.queryCommandState(command) === -1;
  2135. scope.commandValue = minder.queryCommandValue(command);
  2136. scope.$apply();
  2137. });
  2138. }
  2139. };
  2140. });
  2141. angular.module('kityminderEditor')
  2142. .provider('config', function() {
  2143. this.config = {
  2144. // 右侧面板最小宽度
  2145. ctrlPanelMin: 250,
  2146. // 右侧面板宽度
  2147. ctrlPanelWidth: parseInt(window.localStorage.__dev_minder_ctrlPanelWidth) || 250,
  2148. // 分割线宽度
  2149. dividerWidth: 3,
  2150. // 默认语言
  2151. defaultLang: 'zh-cn',
  2152. // 放大缩小比例
  2153. zoom: [10, 20, 30, 50, 80, 100, 120, 150, 200],
  2154. // 图片上传接口
  2155. imageUpload: 'server/imageUpload.php'
  2156. };
  2157. this.set = function(key, value) {
  2158. var supported = Object.keys(this.config);
  2159. var configObj = {};
  2160. // 支持全配置
  2161. if (typeof key === 'object') {
  2162. configObj = key;
  2163. }
  2164. else {
  2165. configObj[key] = value;
  2166. }
  2167. for (var i in configObj) {
  2168. if (configObj.hasOwnProperty(i) && supported.indexOf(i) !== -1) {
  2169. this.config[i] = configObj[i];
  2170. }
  2171. else {
  2172. console.error('Unsupported config key: ', key, ', please choose in :', supported.join(', '));
  2173. return false;
  2174. }
  2175. }
  2176. return true;
  2177. };
  2178. this.$get = function () {
  2179. var me = this;
  2180. return {
  2181. get: function (key) {
  2182. if (arguments.length === 0) {
  2183. return me.config;
  2184. }
  2185. if (me.config.hasOwnProperty(key)) {
  2186. return me.config[key];
  2187. }
  2188. console.warn('Missing config key pair for : ', key);
  2189. return '';
  2190. }
  2191. };
  2192. }
  2193. });
  2194. angular.module('kityminderEditor')
  2195. .service('lang.zh-cn', function() {
  2196. return {
  2197. 'zh-cn': {
  2198. 'template': {
  2199. 'default': '思维导图',
  2200. 'tianpan': '天盘图',
  2201. 'structure': '组织结构图',
  2202. 'filetree': '目录组织图',
  2203. 'right': '逻辑结构图',
  2204. 'fish-bone': '鱼骨头图'
  2205. },
  2206. 'theme': {
  2207. 'classic': '脑图经典',
  2208. 'classic-compact': '紧凑经典',
  2209. 'snow': '温柔冷光',
  2210. 'snow-compact': '紧凑冷光',
  2211. 'fish': '鱼骨图',
  2212. 'wire': '线框',
  2213. 'fresh-red': '清新红',
  2214. 'fresh-soil': '泥土黄',
  2215. 'fresh-green': '文艺绿',
  2216. 'fresh-blue': '天空蓝',
  2217. 'fresh-purple': '浪漫紫',
  2218. 'fresh-pink': '脑残粉',
  2219. 'fresh-red-compat': '紧凑红',
  2220. 'fresh-soil-compat': '紧凑黄',
  2221. 'fresh-green-compat': '紧凑绿',
  2222. 'fresh-blue-compat': '紧凑蓝',
  2223. 'fresh-purple-compat': '紧凑紫',
  2224. 'fresh-pink-compat': '紧凑粉',
  2225. 'tianpan':'经典天盘',
  2226. 'tianpan-compact': '紧凑天盘'
  2227. },
  2228. 'maintopic': '中心主题',
  2229. 'topic': '分支主题',
  2230. 'panels': {
  2231. 'history': '历史',
  2232. 'template': '模板',
  2233. 'theme': '皮肤',
  2234. 'layout': '布局',
  2235. 'style': '样式',
  2236. 'font': '文字',
  2237. 'color': '颜色',
  2238. 'background': '背景',
  2239. 'insert': '插入',
  2240. 'arrange': '调整',
  2241. 'nodeop': '当前',
  2242. 'priority': '优先级',
  2243. 'progress': '进度',
  2244. 'resource': '资源',
  2245. 'note': '备注',
  2246. 'attachment': '附件',
  2247. 'word': '文字'
  2248. },
  2249. 'error_message': {
  2250. 'title': '哎呀,脑图出错了',
  2251. 'err_load': '加载脑图失败',
  2252. 'err_save': '保存脑图失败',
  2253. 'err_network': '网络错误',
  2254. 'err_doc_resolve': '文档解析失败',
  2255. 'err_unknown': '发生了奇怪的错误',
  2256. 'err_localfile_read': '文件读取错误',
  2257. 'err_download': '文件下载失败',
  2258. 'err_remove_share': '取消分享失败',
  2259. 'err_create_share': '分享失败',
  2260. 'err_mkdir': '目录创建失败',
  2261. 'err_ls': '读取目录失败',
  2262. 'err_share_data': '加载分享内容出错',
  2263. 'err_share_sync_fail': '分享内容同步失败',
  2264. 'err_move_file': '文件移动失败',
  2265. 'err_rename': '重命名失败',
  2266. 'unknownreason': '可能是外星人篡改了代码...',
  2267. 'pcs_code': {
  2268. 3: "不支持此接口",
  2269. 4: "没有权限执行此操作",
  2270. 5: "IP未授权",
  2271. 110: "用户会话已过期,请重新登录",
  2272. 31001: "数据库查询错误",
  2273. 31002: "数据库连接错误",
  2274. 31003: "数据库返回空结果",
  2275. 31021: "网络错误",
  2276. 31022: "暂时无法连接服务器",
  2277. 31023: "输入参数错误",
  2278. 31024: "app id为空",
  2279. 31025: "后端存储错误",
  2280. 31041: "用户的cookie不是合法的百度cookie",
  2281. 31042: "用户未登陆",
  2282. 31043: "用户未激活",
  2283. 31044: "用户未授权",
  2284. 31045: "用户不存在",
  2285. 31046: "用户已经存在",
  2286. 31061: "文件已经存在",
  2287. 31062: "文件名非法",
  2288. 31063: "文件父目录不存在",
  2289. 31064: "无权访问此文件",
  2290. 31065: "目录已满",
  2291. 31066: "文件不存在",
  2292. 31067: "文件处理出错",
  2293. 31068: "文件创建失败",
  2294. 31069: "文件拷贝失败",
  2295. 31070: "文件删除失败",
  2296. 31071: "不能读取文件元信息",
  2297. 31072: "文件移动失败",
  2298. 31073: "文件重命名失败",
  2299. 31079: "未找到文件MD5,请使用上传API上传整个文件。",
  2300. 31081: "superfile创建失败",
  2301. 31082: "superfile 块列表为空",
  2302. 31083: "superfile 更新失败",
  2303. 31101: "tag系统内部错误",
  2304. 31102: "tag参数错误",
  2305. 31103: "tag系统错误",
  2306. 31110: "未授权设置此目录配额",
  2307. 31111: "配额管理只支持两级目录",
  2308. 31112: "超出配额",
  2309. 31113: "配额不能超出目录祖先的配额",
  2310. 31114: "配额不能比子目录配额小",
  2311. 31141: "请求缩略图服务失败",
  2312. 31201: "签名错误",
  2313. 31202: "文件不存在",
  2314. 31203: "设置acl失败",
  2315. 31204: "请求acl验证失败",
  2316. 31205: "获取acl失败",
  2317. 31206: "acl不存在",
  2318. 31207: "bucket已存在",
  2319. 31208: "用户请求错误",
  2320. 31209: "服务器错误",
  2321. 31210: "服务器不支持",
  2322. 31211: "禁止访问",
  2323. 31212: "服务不可用",
  2324. 31213: "重试出错",
  2325. 31214: "上传文件data失败",
  2326. 31215: "上传文件meta失败",
  2327. 31216: "下载文件data失败",
  2328. 31217: "下载文件meta失败",
  2329. 31218: "容量超出限额",
  2330. 31219: "请求数超出限额",
  2331. 31220: "流量超出限额",
  2332. 31298: "服务器返回值KEY非法",
  2333. 31299: "服务器返回值KEY不存在"
  2334. }
  2335. },
  2336. 'ui': {
  2337. 'shared_file_title': '[分享的] {0} (只读)',
  2338. 'load_share_for_edit': '正在加载分享的文件...',
  2339. 'share_sync_success': '分享内容已同步',
  2340. 'recycle_clear_confirm': '确认清空回收站么?清空后的文件无法恢复。',
  2341. 'fullscreen_exit_hint': '按 Esc 或 F11 退出全屏',
  2342. 'error_detail': '详细信息',
  2343. 'copy_and_feedback': '复制并反馈',
  2344. 'move_file_confirm': '确定把 "{0}" 移动到 "{1}" 吗?',
  2345. 'rename': '重命名',
  2346. 'rename_success': '{0} 重命名成功',
  2347. 'move_success': '{0} 移动成功到 {1}',
  2348. 'command': {
  2349. 'appendsiblingnode': '插入同级主题',
  2350. 'appendparentnode': '插入上级主题',
  2351. 'appendchildnode': '插入下级主题',
  2352. 'removenode': '删除',
  2353. 'editnode': '编辑',
  2354. 'arrangeup': '上移',
  2355. 'arrangedown': '下移',
  2356. 'resetlayout': '整理布局',
  2357. 'expandtoleaf': '展开全部节点',
  2358. 'expandtolevel1': '展开到一级节点',
  2359. 'expandtolevel2': '展开到二级节点',
  2360. 'expandtolevel3': '展开到三级节点',
  2361. 'expandtolevel4': '展开到四级节点',
  2362. 'expandtolevel5': '展开到五级节点',
  2363. 'expandtolevel6': '展开到六级节点',
  2364. 'fullscreen': '全屏',
  2365. 'outline': '大纲'
  2366. },
  2367. 'search':'搜索',
  2368. 'expandtoleaf': '展开',
  2369. 'back': '返回',
  2370. 'undo': '撤销 (Ctrl + Z)',
  2371. 'redo': '重做 (Ctrl + Y)',
  2372. 'tabs': {
  2373. 'idea': '思路',
  2374. 'appearence': '外观',
  2375. 'view': '视图'
  2376. },
  2377. 'quickvisit': {
  2378. 'new': '新建 (Ctrl + Alt + N)',
  2379. 'save': '保存 (Ctrl + S)',
  2380. 'share': '分享 (Ctrl + Alt + S)',
  2381. 'feedback': '反馈问题(F1)',
  2382. 'editshare': '编辑'
  2383. },
  2384. 'menu': {
  2385. 'mainmenutext': '百度脑图', // 主菜单按钮文本
  2386. 'newtab': '新建',
  2387. 'opentab': '打开',
  2388. 'savetab': '保存',
  2389. 'sharetab': '分享',
  2390. 'preferencetab': '设置',
  2391. 'helptab': '帮助',
  2392. 'feedbacktab': '反馈',
  2393. 'recenttab': '最近使用',
  2394. 'netdisktab': '百度云存储',
  2395. 'localtab': '本地文件',
  2396. 'drafttab': '草稿箱',
  2397. 'downloadtab': '导出到本地',
  2398. 'createsharetab': '当前脑图',
  2399. 'managesharetab': '已分享',
  2400. 'newheader': '新建脑图',
  2401. 'openheader': '打开',
  2402. 'saveheader': '保存到',
  2403. 'draftheader': '草稿箱',
  2404. 'shareheader': '分享我的脑图',
  2405. 'downloadheader': '导出到指定格式',
  2406. 'preferenceheader': '偏好设置',
  2407. 'helpheader': '帮助',
  2408. 'feedbackheader': '反馈'
  2409. },
  2410. 'mydocument': '我的文档',
  2411. 'emptydir': '目录为空!',
  2412. 'pickfile': '选择文件...',
  2413. 'acceptfile': '支持的格式:{0}',
  2414. 'dropfile': '或将文件拖至此处',
  2415. 'unsupportedfile': '不支持的文件格式',
  2416. 'untitleddoc': '未命名文档',
  2417. 'overrideconfirm': '{0} 已存在,确认覆盖吗?',
  2418. 'checklogin': '检查登录状态中...',
  2419. 'loggingin': '正在登录...',
  2420. 'recent': '最近打开',
  2421. 'clearrecent': '清空',
  2422. 'clearrecentconfirm': '确认清空最近文档列表?',
  2423. 'cleardraft': '清空',
  2424. 'cleardraftconfirm': '确认清空草稿箱?',
  2425. 'none_share': '不分享',
  2426. 'public_share': '公开分享',
  2427. 'password_share': '私密分享',
  2428. 'email_share': '邮件邀请',
  2429. 'url_share': '脑图 URL 地址:',
  2430. 'sns_share': '社交网络分享:',
  2431. 'sns_share_text': '“{0}” - 我用百度脑图制作的思维导图,快看看吧!(地址:{1})',
  2432. 'none_share_description': '不分享当前脑图',
  2433. 'public_share_description': '创建任何人可见的分享',
  2434. 'share_button_text': '创建',
  2435. 'password_share_description': '创建需要密码才可见的分享',
  2436. 'email_share_description': '创建指定人可见的分享,您还可以允许他们编辑',
  2437. 'ondev': '敬请期待!',
  2438. 'create_share_failed': '分享失败:{0}',
  2439. 'remove_share_failed': '删除失败:{1}',
  2440. 'copy': '复制',
  2441. 'copied': '已复制',
  2442. 'shared_tip': '当前脑图被 {0} 分享,你可以修改之后保存到自己的网盘上或再次分享',
  2443. 'current_share': '当前脑图',
  2444. 'manage_share': '我的分享',
  2445. 'share_remove_action': '不分享该脑图',
  2446. 'share_view_action': '打开分享地址',
  2447. 'share_edit_action': '编辑分享的文件',
  2448. 'login': '登录',
  2449. 'logout': '注销',
  2450. 'switchuser': '切换账户',
  2451. 'userinfo': '个人信息',
  2452. 'gotonetdisk': '我的网盘',
  2453. 'requirelogin': '请 <a class="login-button">登录</a> 后使用',
  2454. 'saveas': '保存为',
  2455. 'filename': '文件名',
  2456. 'fileformat': '保存格式',
  2457. 'save': '保存',
  2458. 'mkdir': '新建目录',
  2459. 'recycle': '回收站',
  2460. 'newdir': '未命名目录',
  2461. 'bold': '加粗',
  2462. 'italic': '斜体',
  2463. 'forecolor': '字体颜色',
  2464. 'fontfamily': '字体',
  2465. 'fontsize': '字号',
  2466. 'layoutstyle': '主题',
  2467. 'node': '节点操作',
  2468. 'saveto': '另存为',
  2469. 'hand': '允许拖拽',
  2470. 'camera': '定位根节点',
  2471. 'zoom-in': '放大(Ctrl+)',
  2472. 'zoom-out': '缩小(Ctrl-)',
  2473. 'markers': '标签',
  2474. 'resource': '资源',
  2475. 'help': '帮助',
  2476. 'preference': '偏好设置',
  2477. 'expandnode': '展开到叶子',
  2478. 'collapsenode': '收起到一级节点',
  2479. 'template': '模板',
  2480. 'theme': '皮肤',
  2481. 'clearstyle': '清除样式',
  2482. 'copystyle': '复制样式',
  2483. 'pastestyle': '粘贴样式',
  2484. 'appendsiblingnode': '同级主题',
  2485. 'appendchildnode': '下级主题',
  2486. 'arrangeup': '前调',
  2487. 'arrangedown': '后调',
  2488. 'editnode': '编辑',
  2489. 'removenode': '移除',
  2490. 'priority': '优先级',
  2491. 'progress': {
  2492. 'p1': '未开始',
  2493. 'p2': '完成 1/8',
  2494. 'p3': '完成 1/4',
  2495. 'p4': '完成 3/8',
  2496. 'p5': '完成一半',
  2497. 'p6': '完成 5/8',
  2498. 'p7': '完成 3/4',
  2499. 'p8': '完成 7/8',
  2500. 'p9': '已完成',
  2501. 'p0': '清除进度'
  2502. },
  2503. 'link': '链接',
  2504. 'image': '图片',
  2505. 'note': '备注',
  2506. 'insertlink': '插入链接',
  2507. 'insertimage': '插入图片',
  2508. 'insertnote': '插入备注',
  2509. 'removelink': '移除已有链接',
  2510. 'removeimage': '移除已有图片',
  2511. 'removenote': '移除已有备注',
  2512. 'resetlayout': '整理',
  2513. 'justnow': '刚刚',
  2514. 'minutesago': '{0} 分钟前',
  2515. 'hoursago': '{0} 小时前',
  2516. 'yesterday': '昨天',
  2517. 'daysago': '{0} 天前',
  2518. 'longago': '很久之前',
  2519. 'redirect': '您正在打开连接 {0},百度脑图不能保证连接的安全性,是否要继续?',
  2520. 'navigator': '导航器',
  2521. 'unsavedcontent': '当前文件还没有保存到网盘:\n\n{0}\n\n虽然未保存的数据会缓存在草稿箱,但是清除浏览器缓存会导致草稿箱清除。',
  2522. 'shortcuts': '快捷键',
  2523. 'contact': '联系与反馈',
  2524. 'email': '邮件组',
  2525. 'qq_group': 'QQ 群',
  2526. 'github_issue': 'Github',
  2527. 'baidu_tieba': '贴吧',
  2528. 'clipboardunsupported': '您的浏览器不支持剪贴板,请使用快捷键复制',
  2529. 'load_success': '{0} 加载成功',
  2530. 'save_success': '{0} 已保存于 {1}',
  2531. 'autosave_success': '{0} 已自动保存于 {1}',
  2532. 'selectall': '全选',
  2533. 'selectrevert': '反选',
  2534. 'selectsiblings': '选择兄弟节点',
  2535. 'selectlevel': '选择同级节点',
  2536. 'selectpath': '选择路径',
  2537. 'selecttree': '选择子树'
  2538. },
  2539. 'popupcolor': {
  2540. 'clearColor': '清空颜色',
  2541. 'standardColor': '标准颜色',
  2542. 'themeColor': '主题颜色'
  2543. },
  2544. 'dialogs': {
  2545. 'markers': {
  2546. 'static': {
  2547. 'lang_input_text': '文本内容:',
  2548. 'lang_input_url': '链接地址:',
  2549. 'lang_input_title': '标题:',
  2550. 'lang_input_target': '是否在新窗口:'
  2551. },
  2552. 'priority': '优先级',
  2553. 'none': '无',
  2554. 'progress': {
  2555. 'title': '进度',
  2556. 'notdone': '未完成',
  2557. 'done1': '完成 1/8',
  2558. 'done2': '完成 1/4',
  2559. 'done3': '完成 3/8',
  2560. 'done4': '完成 1/2',
  2561. 'done5': '完成 5/8',
  2562. 'done6': '完成 3/4',
  2563. 'done7': '完成 7/8',
  2564. 'done': '已完成'
  2565. }
  2566. },
  2567. 'help': {
  2568. },
  2569. 'hyperlink': {},
  2570. 'image': {},
  2571. 'resource': {}
  2572. },
  2573. 'hyperlink': {
  2574. 'hyperlink': '链接...',
  2575. 'unhyperlink': '移除链接'
  2576. },
  2577. 'image': {
  2578. 'image': '图片...',
  2579. 'removeimage': '移除图片'
  2580. },
  2581. 'marker': {
  2582. 'marker': '进度/优先级...'
  2583. },
  2584. 'resource': {
  2585. 'resource': '资源...'
  2586. }
  2587. }
  2588. }
  2589. });
  2590. /**
  2591. * @fileOverview
  2592. *
  2593. * UI 状态的 LocalStorage 的存取文件未来可能在离线编辑的时候升级
  2594. *
  2595. * @author: zhangbobell
  2596. * @email : zhangbobell@163.com
  2597. *
  2598. * @copyright: Baidu FEX, 2015
  2599. */
  2600. angular.module('kityminderEditor')
  2601. .service('memory', function() {
  2602. function isQuotaExceeded(e) {
  2603. var quotaExceeded = false;
  2604. if (e) {
  2605. if (e.code) {
  2606. switch (e.code) {
  2607. case 22:
  2608. quotaExceeded = true;
  2609. break;
  2610. case 1014:
  2611. // Firefox
  2612. if (e.name === 'NS_ERROR_DOM_QUOTA_REACHED') {
  2613. quotaExceeded = true;
  2614. }
  2615. break;
  2616. }
  2617. } else if (e.number === -2147024882) {
  2618. // Internet Explorer 8
  2619. quotaExceeded = true;
  2620. }
  2621. }
  2622. return quotaExceeded;
  2623. }
  2624. return {
  2625. get: function(key) {
  2626. var value = window.localStorage.getItem(key);
  2627. return null || JSON.parse(value);
  2628. },
  2629. set: function(key, value) {
  2630. try {
  2631. window.localStorage.setItem(key, JSON.stringify(value));
  2632. return true;
  2633. } catch(e) {
  2634. if (isQuotaExceeded(e)) {
  2635. return false;
  2636. }
  2637. }
  2638. },
  2639. remove: function(key) {
  2640. var value = window.localStorage.getItem(key);
  2641. window.localStorage.removeItem(key);
  2642. return value;
  2643. },
  2644. clear: function() {
  2645. window.localStorage.clear();
  2646. }
  2647. }
  2648. });
  2649. angular.module('kityminderEditor')
  2650. .service('minder.service', function() {
  2651. var callbackQueue = [];
  2652. function registerEvent(callback) {
  2653. callbackQueue.push(callback);
  2654. }
  2655. function executeCallback() {
  2656. callbackQueue.forEach(function(ele) {
  2657. ele.apply(this, arguments);
  2658. })
  2659. }
  2660. return {
  2661. registerEvent: registerEvent,
  2662. executeCallback: executeCallback
  2663. }
  2664. });
  2665. angular.module('kityminderEditor')
  2666. .service('resourceService', ['$document', function($document) {
  2667. var openScope = null;
  2668. this.open = function( dropdownScope ) {
  2669. if ( !openScope ) {
  2670. $document.bind('click', closeDropdown);
  2671. $document.bind('keydown', escapeKeyBind);
  2672. }
  2673. if ( openScope && openScope !== dropdownScope ) {
  2674. openScope.resourceListOpen = false;
  2675. }
  2676. openScope = dropdownScope;
  2677. };
  2678. this.close = function( dropdownScope ) {
  2679. if ( openScope === dropdownScope ) {
  2680. openScope = null;
  2681. $document.unbind('click', closeDropdown);
  2682. $document.unbind('keydown', escapeKeyBind);
  2683. }
  2684. };
  2685. var closeDropdown = function( evt ) {
  2686. // This method may still be called during the same mouse event that
  2687. // unbound this event handler. So check openScope before proceeding.
  2688. //console.log(evt, openScope);
  2689. if (!openScope) { return; }
  2690. var toggleElement = openScope.getToggleElement();
  2691. if ( evt && toggleElement && toggleElement[0].contains(evt.target) ) {
  2692. return;
  2693. }
  2694. openScope.$apply(function() {
  2695. console.log('to close the resourcelist');
  2696. openScope.resourceListOpen = false;
  2697. });
  2698. };
  2699. var escapeKeyBind = function( evt ) {
  2700. if ( evt.which === 27 ) {
  2701. openScope.focusToggleElement();
  2702. closeDropdown();
  2703. }
  2704. };
  2705. }])
  2706. angular.module('kityminderEditor').service('revokeDialog', ['$modal', 'minder.service', function($modal, minderService) {
  2707. minderService.registerEvent(function() {
  2708. // 触发导入节点或导出节点对话框
  2709. var minder = window.minder;
  2710. var editor = window.editor;
  2711. var parentFSM = editor.hotbox.getParentFSM();
  2712. minder.on('importNodeData', function() {
  2713. parentFSM.jump('modal', 'import-text-modal');
  2714. var importModal = $modal.open({
  2715. animation: true,
  2716. templateUrl: 'ui/dialog/imExportNode/imExportNode.tpl.html',
  2717. controller: 'imExportNode.ctrl',
  2718. size: 'md',
  2719. resolve: {
  2720. title: function() {
  2721. return '导入节点';
  2722. },
  2723. defaultValue: function() {
  2724. return '';
  2725. },
  2726. type: function() {
  2727. return 'import';
  2728. }
  2729. }
  2730. });
  2731. importModal.result.then(function(result) {
  2732. try{
  2733. minder.Text2Children(minder.getSelectedNode(), result);
  2734. } catch(e) {
  2735. alert(e);
  2736. }
  2737. parentFSM.jump('normal', 'import-text-finish');
  2738. editor.receiver.selectAll();
  2739. }, function() {
  2740. parentFSM.jump('normal', 'import-text-finish');
  2741. editor.receiver.selectAll();
  2742. });
  2743. });
  2744. minder.on('exportNodeData', function() {
  2745. parentFSM.jump('modal', 'export-text-modal');
  2746. var exportModal = $modal.open({
  2747. animation: true,
  2748. templateUrl: 'ui/dialog/imExportNode/imExportNode.tpl.html',
  2749. controller: 'imExportNode.ctrl',
  2750. size: 'md',
  2751. resolve: {
  2752. title: function() {
  2753. return '导出节点';
  2754. },
  2755. defaultValue: function() {
  2756. var selectedNode = minder.getSelectedNode(),
  2757. Node2Text = window.kityminder.data.getRegisterProtocol('text').Node2Text;
  2758. return Node2Text(selectedNode);
  2759. },
  2760. type: function() {
  2761. return 'export';
  2762. }
  2763. }
  2764. });
  2765. exportModal.result.then(function(result) {
  2766. parentFSM.jump('normal', 'export-text-finish');
  2767. editor.receiver.selectAll();
  2768. }, function() {
  2769. parentFSM.jump('normal', 'export-text-finish');
  2770. editor.receiver.selectAll();
  2771. });
  2772. });
  2773. });
  2774. return {};
  2775. }]);
  2776. /**
  2777. * @fileOverview
  2778. *
  2779. * 与后端交互的服务
  2780. *
  2781. * @author: zhangbobell
  2782. * @email : zhangbobell@163.com
  2783. *
  2784. * @copyright: Baidu FEX, 2015
  2785. */
  2786. angular.module('kityminderEditor')
  2787. .service('server', ['config', '$http', function(config, $http) {
  2788. return {
  2789. uploadImage: function(file) {
  2790. var url = config.get('imageUpload');
  2791. var fd = new FormData();
  2792. fd.append('upload_file', file);
  2793. return $http.post(url, fd, {
  2794. transformRequest: angular.identity,
  2795. headers: {'Content-Type': undefined}
  2796. });
  2797. }
  2798. }
  2799. }]);
  2800. angular.module('kityminderEditor')
  2801. .service('valueTransfer', function() {
  2802. return {};
  2803. });
  2804. angular.module('kityminderEditor')
  2805. .filter('commandState', function() {
  2806. return function(minder, command) {
  2807. return minder.queryCommandState(command);
  2808. }
  2809. })
  2810. .filter('commandValue', function() {
  2811. return function(minder, command) {
  2812. return minder.queryCommandValue(command);
  2813. }
  2814. });
  2815. angular.module('kityminderEditor')
  2816. .filter('lang', ['config', 'lang.zh-cn', function(config, lang) {
  2817. return function(text, block) {
  2818. var defaultLang = config.get('defaultLang');
  2819. if (lang[defaultLang] == undefined) {
  2820. return '未发现对应语言包,请检查 lang.xxx.service.js!';
  2821. } else {
  2822. var dict = lang[defaultLang];
  2823. block.split('/').forEach(function(ele, idx) {
  2824. dict = dict[ele];
  2825. });
  2826. return dict[text] || null;
  2827. }
  2828. };
  2829. }]);
  2830. angular.module('kityminderEditor')
  2831. .controller('hyperlink.ctrl', ["$scope", "$modalInstance", "link", function ($scope, $modalInstance, link) {
  2832. var urlRegex = '^(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$';
  2833. $scope.R_URL = new RegExp(urlRegex, 'i');
  2834. $scope.url = link.url || '';
  2835. $scope.title = link.title || '';
  2836. setTimeout(function() {
  2837. var $linkUrl = $('#link-url');
  2838. $linkUrl.focus();
  2839. $linkUrl[0].setSelectionRange(0, $scope.url.length);
  2840. }, 30);
  2841. $scope.shortCut = function(e) {
  2842. e.stopPropagation();
  2843. if (e.keyCode == 13) {
  2844. $scope.ok();
  2845. } else if (e.keyCode == 27) {
  2846. $scope.cancel();
  2847. }
  2848. };
  2849. $scope.ok = function () {
  2850. if($scope.R_URL.test($scope.url)) {
  2851. $modalInstance.close({
  2852. url: $scope.url,
  2853. title: $scope.title
  2854. });
  2855. } else {
  2856. $scope.urlPassed = false;
  2857. var $linkUrl = $('#link-url');
  2858. $linkUrl.focus();
  2859. $linkUrl[0].setSelectionRange(0, $scope.url.length);
  2860. }
  2861. editor.receiver.selectAll();
  2862. };
  2863. $scope.cancel = function () {
  2864. $modalInstance.dismiss('cancel');
  2865. editor.receiver.selectAll();
  2866. };
  2867. }]);
  2868. angular.module('kityminderEditor')
  2869. .controller('imExportNode.ctrl', ["$scope", "$modalInstance", "title", "defaultValue", "type", function ($scope, $modalInstance, title, defaultValue, type) {
  2870. $scope.title = title;
  2871. $scope.value = defaultValue;
  2872. $scope.type = type;
  2873. $scope.ok = function () {
  2874. if ($scope.value == '') {
  2875. return;
  2876. }
  2877. $modalInstance.close($scope.value);
  2878. editor.receiver.selectAll();
  2879. };
  2880. $scope.cancel = function () {
  2881. $modalInstance.dismiss('cancel');
  2882. editor.receiver.selectAll();
  2883. };
  2884. setTimeout(function() {
  2885. $('.single-input').focus();
  2886. $('.single-input')[0].setSelectionRange(0, defaultValue.length);
  2887. }, 30);
  2888. $scope.shortCut = function(e) {
  2889. e.stopPropagation();
  2890. //if (e.keyCode == 13 && e.shiftKey == false) {
  2891. // $scope.ok();
  2892. //}
  2893. if (e.keyCode == 27) {
  2894. $scope.cancel();
  2895. }
  2896. // tab 键屏蔽默认事件 和 backspace 键屏蔽默认事件
  2897. if (e.keyCode == 8 && type == 'export') {
  2898. e.preventDefault();
  2899. }
  2900. if (e.keyCode == 9) {
  2901. e.preventDefault();
  2902. var $textarea = e.target;
  2903. var pos = getCursortPosition($textarea);
  2904. var str = $textarea.value;
  2905. $textarea.value = str.substr(0, pos) + '\t' + str.substr(pos);
  2906. setCaretPosition($textarea, pos + 1);
  2907. }
  2908. };
  2909. /*
  2910. * 获取 textarea 的光标位置
  2911. * @Author: Naixor
  2912. * @date: 2015.09.23
  2913. * */
  2914. function getCursortPosition (ctrl) {
  2915. var CaretPos = 0; // IE Support
  2916. if (document.selection) {
  2917. ctrl.focus ();
  2918. var Sel = document.selection.createRange ();
  2919. Sel.moveStart ('character', -ctrl.value.length);
  2920. CaretPos = Sel.text.length;
  2921. }
  2922. // Firefox support
  2923. else if (ctrl.selectionStart || ctrl.selectionStart == '0') {
  2924. CaretPos = ctrl.selectionStart;
  2925. }
  2926. return (CaretPos);
  2927. }
  2928. /*
  2929. * 设置 textarea 的光标位置
  2930. * @Author: Naixor
  2931. * @date: 2015.09.23
  2932. * */
  2933. function setCaretPosition(ctrl, pos){
  2934. if(ctrl.setSelectionRange) {
  2935. ctrl.focus();
  2936. ctrl.setSelectionRange(pos,pos);
  2937. } else if (ctrl.createTextRange) {
  2938. var range = ctrl.createTextRange();
  2939. range.collapse(true);
  2940. range.moveEnd('character', pos);
  2941. range.moveStart('character', pos);
  2942. range.select();
  2943. }
  2944. }
  2945. }]);
  2946. angular.module('kityminderEditor')
  2947. .controller('image.ctrl', ['$http', '$scope', '$modalInstance', 'image', 'server', function($http, $scope, $modalInstance, image, server) {
  2948. $scope.data = {
  2949. list: [],
  2950. url: image.url || '',
  2951. title: image.title || '',
  2952. R_URL: /^https?\:\/\/\w+/
  2953. };
  2954. setTimeout(function() {
  2955. var $imageUrl = $('#image-url');
  2956. $imageUrl.focus();
  2957. $imageUrl[0].setSelectionRange(0, $scope.data.url.length);
  2958. }, 300);
  2959. // 搜索图片按钮点击事件
  2960. $scope.searchImage = function() {
  2961. $scope.list = [];
  2962. getImageData()
  2963. .success(function(json) {
  2964. if(json && json.data) {
  2965. for(var i = 0; i < json.data.length; i++) {
  2966. if(json.data[i].objURL) {
  2967. $scope.list.push({
  2968. title: json.data[i].fromPageTitleEnc,
  2969. src: json.data[i].middleURL,
  2970. url: json.data[i].middleURL
  2971. });
  2972. }
  2973. }
  2974. }
  2975. })
  2976. .error(function() {
  2977. });
  2978. };
  2979. // 选择图片的鼠标点击事件
  2980. $scope.selectImage = function($event) {
  2981. var targetItem = $('#img-item'+ (this.$index));
  2982. var targetImg = $('#img-'+ (this.$index));
  2983. targetItem.siblings('.selected').removeClass('selected');
  2984. targetItem.addClass('selected');
  2985. $scope.data.url = targetImg.attr('src');
  2986. $scope.data.title = targetImg.attr('alt');
  2987. };
  2988. // 自动上传图片,后端需要直接返回图片 URL
  2989. $scope.uploadImage = function() {
  2990. var fileInput = $('#upload-image');
  2991. if (!fileInput.val()) {
  2992. return;
  2993. }
  2994. if (/^.*\.(jpg|JPG|jpeg|JPEG|gif|GIF|png|PNG)$/.test(fileInput.val())) {
  2995. var file = fileInput[0].files[0];
  2996. return server.uploadImage(file).then(function (json) {
  2997. var resp = json.data;
  2998. if (resp.errno === 0) {
  2999. $scope.data.url = resp.data.url;
  3000. }
  3001. });
  3002. } else {
  3003. alert("后缀只能是 jpg、gif 及 png");
  3004. }
  3005. };
  3006. $scope.shortCut = function(e) {
  3007. e.stopPropagation();
  3008. if (e.keyCode == 13) {
  3009. $scope.ok();
  3010. } else if (e.keyCode == 27) {
  3011. $scope.cancel();
  3012. }
  3013. };
  3014. $scope.ok = function () {
  3015. if($scope.data.R_URL.test($scope.data.url)) {
  3016. $modalInstance.close({
  3017. url: $scope.data.url,
  3018. title: $scope.data.title
  3019. });
  3020. } else {
  3021. $scope.urlPassed = false;
  3022. var $imageUrl = $('#image-url');
  3023. if ($imageUrl) {
  3024. $imageUrl.focus();
  3025. $imageUrl[0].setSelectionRange(0, $scope.data.url.length);
  3026. }
  3027. }
  3028. editor.receiver.selectAll();
  3029. };
  3030. $scope.cancel = function () {
  3031. $modalInstance.dismiss('cancel');
  3032. editor.receiver.selectAll();
  3033. };
  3034. function getImageData() {
  3035. var key = $scope.data.searchKeyword2;
  3036. var currentTime = new Date();
  3037. var url = 'http://image.baidu.com/search/acjson?tn=resultjson_com&ipn=rj&ct=201326592&fp=result&queryWord='+ key +'&cl=2&lm=-1&ie=utf-8&oe=utf-8&st=-1&ic=0&word='+ key +'&face=0&istype=2&nc=1&pn=60&rn=60&gsm=3c&'+ currentTime.getTime() +'=&callback=JSON_CALLBACK';
  3038. return $http.jsonp(url);
  3039. }
  3040. }]);
  3041. angular.module('kityminderEditor')
  3042. .directive('appendNode', ['commandBinder', function(commandBinder) {
  3043. return {
  3044. restrict: 'E',
  3045. templateUrl: 'ui/directive/appendNode/appendNode.html',
  3046. scope: {
  3047. minder: '='
  3048. },
  3049. replace: true,
  3050. link: function($scope) {
  3051. var minder = $scope.minder;
  3052. commandBinder.bind(minder, 'appendchildnode', $scope)
  3053. $scope.execCommand = function(command) {
  3054. minder.execCommand(command, '分支主题');
  3055. editText();
  3056. };
  3057. function editText() {
  3058. var receiverElement = editor.receiver.element;
  3059. var fsm = editor.fsm;
  3060. var receiver = editor.receiver;
  3061. receiverElement.innerText = minder.queryCommandValue('text');
  3062. fsm.jump('input', 'input-request');
  3063. receiver.selectAll();
  3064. }
  3065. }
  3066. }
  3067. }]);
  3068. angular.module('kityminderEditor')
  3069. .directive('arrange', ['commandBinder', function(commandBinder) {
  3070. return {
  3071. restrict: 'E',
  3072. templateUrl: 'ui/directive/arrange/arrange.html',
  3073. scope: {
  3074. minder: '='
  3075. },
  3076. replace: true,
  3077. link: function($scope) {
  3078. var minder = $scope.minder;
  3079. //commandBinder.bind(minder, 'priority', $scope);
  3080. }
  3081. }
  3082. }]);
  3083. angular.module('kityminderEditor')
  3084. .directive('colorPanel', function() {
  3085. return {
  3086. restrict: 'E',
  3087. templateUrl: 'ui/directive/colorPanel/colorPanel.html',
  3088. scope: {
  3089. minder: '='
  3090. },
  3091. replace: true,
  3092. link: function(scope) {
  3093. var minder = scope.minder;
  3094. var currentTheme = minder.getThemeItems();
  3095. scope.$on('colorPicked', function(event, color) {
  3096. event.stopPropagation();
  3097. scope.bgColor = color;
  3098. minder.execCommand('background', color);
  3099. });
  3100. scope.setDefaultBg = function() {
  3101. var currentNode = minder.getSelectedNode();
  3102. var bgColor = minder.getNodeStyle(currentNode, 'background');
  3103. // 有可能是 kity 的颜色类
  3104. return typeof bgColor === 'object' ? bgColor.toHEX() : bgColor;
  3105. };
  3106. scope.bgColor = scope.setDefaultBg() || '#fff';
  3107. }
  3108. }
  3109. });
  3110. angular.module('kityminderEditor')
  3111. .directive('expandLevel', function() {
  3112. return {
  3113. restrict: 'E',
  3114. templateUrl: 'ui/directive/expandLevel/expandLevel.html',
  3115. scope: {
  3116. minder: '='
  3117. },
  3118. replace: true,
  3119. link: function($scope) {
  3120. $scope.levels = [1, 2, 3, 4, 5, 6];
  3121. }
  3122. }
  3123. });
  3124. angular.module('kityminderEditor')
  3125. .directive('fontOperator', function() {
  3126. return {
  3127. restrict: 'E',
  3128. templateUrl: 'ui/directive/fontOperator/fontOperator.html',
  3129. scope: {
  3130. minder: '='
  3131. },
  3132. replace: true,
  3133. link: function(scope) {
  3134. var minder = scope.minder;
  3135. var currentTheme = minder.getThemeItems();
  3136. scope.fontSizeList = [10, 12, 16, 18, 24, 32, 48];
  3137. scope.fontFamilyList = [{
  3138. name: '宋体',
  3139. val: '宋体,SimSun'
  3140. }, {
  3141. name: '微软雅黑',
  3142. val: '微软雅黑,Microsoft YaHei'
  3143. }, {
  3144. name: '楷体',
  3145. val: '楷体,楷体_GB2312,SimKai'
  3146. }, {
  3147. name: '黑体',
  3148. val: '黑体, SimHei'
  3149. }, {
  3150. name: '隶书',
  3151. val: '隶书, SimLi'
  3152. }, {
  3153. name: 'Andale Mono',
  3154. val: 'andale mono'
  3155. }, {
  3156. name: 'Arial',
  3157. val: 'arial,helvetica,sans-serif'
  3158. }, {
  3159. name: 'arialBlack',
  3160. val: 'arial black,avant garde'
  3161. }, {
  3162. name: 'Comic Sans Ms',
  3163. val: 'comic sans ms'
  3164. }, {
  3165. name: 'Impact',
  3166. val: 'impact,chicago'
  3167. }, {
  3168. name: 'Times New Roman',
  3169. val: 'times new roman'
  3170. }, {
  3171. name: 'Sans-Serif',
  3172. val: 'sans-serif'
  3173. }];
  3174. scope.$on('colorPicked', function(event, color) {
  3175. event.stopPropagation();
  3176. scope.foreColor = color;
  3177. minder.execCommand('forecolor', color);
  3178. });
  3179. scope.setDefaultColor = function() {
  3180. var currentNode = minder.getSelectedNode();
  3181. var fontColor = minder.getNodeStyle(currentNode, 'color');
  3182. // 有可能是 kity 的颜色类
  3183. return typeof fontColor === 'object' ? fontColor.toHEX() : fontColor;
  3184. };
  3185. scope.foreColor = scope.setDefaultColor() || '#000';
  3186. scope.getFontfamilyName = function(val) {
  3187. var fontName = '';
  3188. scope.fontFamilyList.forEach(function(ele, idx, arr) {
  3189. if (ele.val === val) {
  3190. fontName = ele.name;
  3191. return '';
  3192. }
  3193. });
  3194. return fontName;
  3195. }
  3196. }
  3197. }
  3198. });
  3199. angular.module('kityminderEditor')
  3200. .directive('hyperLink', ['$modal', function($modal) {
  3201. return {
  3202. restrict: 'E',
  3203. templateUrl: 'ui/directive/hyperLink/hyperLink.html',
  3204. scope: {
  3205. minder: '='
  3206. },
  3207. replace: true,
  3208. link: function($scope) {
  3209. var minder = $scope.minder;
  3210. $scope.addHyperlink = function() {
  3211. var link = minder.queryCommandValue('HyperLink');
  3212. var hyperlinkModal = $modal.open({
  3213. animation: true,
  3214. templateUrl: 'ui/dialog/hyperlink/hyperlink.tpl.html',
  3215. controller: 'hyperlink.ctrl',
  3216. size: 'md',
  3217. resolve: {
  3218. link: function() {
  3219. return link;
  3220. }
  3221. }
  3222. });
  3223. hyperlinkModal.result.then(function(result) {
  3224. minder.execCommand('HyperLink', result.url, result.title || '');
  3225. });
  3226. }
  3227. }
  3228. }
  3229. }]);
  3230. angular.module('kityminderEditor')
  3231. .directive('imageBtn', ['$modal', function($modal) {
  3232. return {
  3233. restrict: 'E',
  3234. templateUrl: 'ui/directive/imageBtn/imageBtn.html',
  3235. scope: {
  3236. minder: '='
  3237. },
  3238. replace: true,
  3239. link: function($scope) {
  3240. var minder = $scope.minder;
  3241. $scope.addImage = function() {
  3242. var image = minder.queryCommandValue('image');
  3243. var imageModal = $modal.open({
  3244. animation: true,
  3245. templateUrl: 'ui/dialog/image/image.tpl.html',
  3246. controller: 'image.ctrl',
  3247. size: 'md',
  3248. resolve: {
  3249. image: function() {
  3250. return image;
  3251. }
  3252. }
  3253. });
  3254. imageModal.result.then(function(result) {
  3255. minder.execCommand('image', result.url, result.title || '');
  3256. });
  3257. }
  3258. }
  3259. }
  3260. }]);
  3261. angular.module('kityminderEditor')
  3262. .directive('kityminderEditor', ['config', 'minder.service', 'revokeDialog', function(config, minderService, revokeDialog) {
  3263. return {
  3264. restrict: 'EA',
  3265. templateUrl: 'ui/directive/kityminderEditor/kityminderEditor.html',
  3266. replace: true,
  3267. scope: {
  3268. onInit: '&'
  3269. },
  3270. link: function(scope, element, attributes) {
  3271. var $minderEditor = element.children('.minder-editor')[0];
  3272. function onInit(editor, minder) {
  3273. scope.onInit({
  3274. editor: editor,
  3275. minder: minder
  3276. });
  3277. minderService.executeCallback();
  3278. }
  3279. if (typeof(seajs) != 'undefined') {
  3280. /* global seajs */
  3281. seajs.config({
  3282. base: './src'
  3283. });
  3284. define('demo', function(require) {
  3285. var Editor = require('editor');
  3286. var editor = window.editor = new Editor($minderEditor);
  3287. if (window.localStorage.__dev_minder_content) {
  3288. editor.minder.importJson(JSON.parse(window.localStorage.__dev_minder_content));
  3289. }
  3290. editor.minder.on('contentchange', function() {
  3291. window.localStorage.__dev_minder_content = JSON.stringify(editor.minder.exportJson());
  3292. });
  3293. window.minder = window.km = editor.minder;
  3294. scope.editor = editor;
  3295. scope.minder = minder;
  3296. scope.config = config.get();
  3297. //scope.minder.setDefaultOptions(scope.config);
  3298. scope.$apply();
  3299. onInit(editor, minder);
  3300. });
  3301. seajs.use('demo');
  3302. } else if (window.kityminder && window.kityminder.Editor) {
  3303. var editor = new kityminder.Editor($minderEditor);
  3304. window.editor = scope.editor = editor;
  3305. window.minder = scope.minder = editor.minder;
  3306. scope.config = config.get();
  3307. //scope.minder.setDefaultOptions(config.getConfig());
  3308. onInit(editor, editor.minder);
  3309. }
  3310. }
  3311. }
  3312. }]);
  3313. angular.module('kityminderEditor')
  3314. .directive('kityminderViewer', ['config', 'minder.service', function(config, minderService) {
  3315. return {
  3316. restrict: 'EA',
  3317. templateUrl: 'ui/directive/kityminderViewer/kityminderViewer.html',
  3318. replace: true,
  3319. scope: {
  3320. onInit: '&'
  3321. },
  3322. link: function(scope, element, attributes) {
  3323. var $minderEditor = element.children('.minder-viewer')[0];
  3324. function onInit(editor, minder) {
  3325. scope.onInit({
  3326. editor: editor,
  3327. minder: minder
  3328. });
  3329. minderService.executeCallback();
  3330. }
  3331. if (window.kityminder && window.kityminder.Editor) {
  3332. var editor = new kityminder.Editor($minderEditor);
  3333. window.editor = scope.editor = editor;
  3334. window.minder = scope.minder = editor.minder;
  3335. onInit(editor, editor.minder);
  3336. }
  3337. }
  3338. }
  3339. }]);
  3340. angular.module('kityminderEditor')
  3341. .directive('layout', function() {
  3342. return {
  3343. restrict: 'E',
  3344. templateUrl: 'ui/directive/layout/layout.html',
  3345. scope: {
  3346. minder: '='
  3347. },
  3348. replace: true,
  3349. link: function(scope) {
  3350. }
  3351. }
  3352. });
  3353. /**
  3354. * @fileOverview
  3355. *
  3356. * 左下角的导航器
  3357. *
  3358. * @author: zhangbobell
  3359. * @email : zhangbobell@163.com
  3360. *
  3361. * @copyright: Baidu FEX, 2015 */
  3362. angular.module('kityminderEditor')
  3363. .directive('navigator', ['memory', 'config', function(memory, config) {
  3364. return {
  3365. restrict: 'A',
  3366. templateUrl: 'ui/directive/navigator/navigator.html',
  3367. scope: {
  3368. minder: '='
  3369. },
  3370. link: function(scope) {
  3371. minder.setDefaultOptions({zoom: config.get('zoom')});
  3372. scope.isNavOpen = !memory.get('navigator-hidden');
  3373. scope.getZoomRadio = function(value) {
  3374. var zoomStack = minder.getOption('zoom');
  3375. var minValue = zoomStack[0];
  3376. var maxValue = zoomStack[zoomStack.length - 1];
  3377. var valueRange = maxValue - minValue;
  3378. return (1 - (value - minValue) / valueRange);
  3379. };
  3380. scope.getHeight = function(value) {
  3381. var totalHeight = $('.zoom-pan').height();
  3382. return scope.getZoomRadio(value) * totalHeight;
  3383. };
  3384. // 初始的缩放倍数
  3385. scope.zoom = 100;
  3386. // 发生缩放事件时
  3387. minder.on('zoom', function(e) {
  3388. scope.zoom = e.zoom;
  3389. });
  3390. scope.toggleNavOpen = function() {
  3391. scope.isNavOpen = !scope.isNavOpen;
  3392. memory.set('navigator-hidden', !scope.isNavOpen);
  3393. if (scope.isNavOpen) {
  3394. bind();
  3395. updateContentView();
  3396. updateVisibleView();
  3397. } else{
  3398. unbind();
  3399. }
  3400. };
  3401. setTimeout(function() {
  3402. if (scope.isNavOpen) {
  3403. bind();
  3404. updateContentView();
  3405. updateVisibleView();
  3406. } else{
  3407. unbind();
  3408. }
  3409. }, 0);
  3410. function bind() {
  3411. minder.on('layout layoutallfinish', updateContentView);
  3412. minder.on('viewchange', updateVisibleView);
  3413. }
  3414. function unbind() {
  3415. minder.off('layout layoutallfinish', updateContentView);
  3416. minder.off('viewchange', updateVisibleView);
  3417. }
  3418. /** *
  3419. * */
  3420. var $previewNavigator = $('.nav-previewer');
  3421. // 画布,渲染缩略图
  3422. var paper = new kity.Paper($previewNavigator[0]);
  3423. // 用两个路径来挥之节点和连线的缩略图
  3424. var nodeThumb = paper.put(new kity.Path());
  3425. var connectionThumb = paper.put(new kity.Path());
  3426. // 表示可视区域的矩形
  3427. var visibleRect = paper.put(new kity.Rect(100, 100).stroke('red', '1%'));
  3428. var contentView = new kity.Box(), visibleView = new kity.Box();
  3429. /**
  3430. * 增加一个对天盘图情况缩略图的处理,
  3431. * @Editor: Naixor line 104~129
  3432. * @Date: 2015.11.3
  3433. */
  3434. var pathHandler = getPathHandler(minder.getTheme());
  3435. // 主题切换事件
  3436. minder.on('themechange', function(e) {
  3437. pathHandler = getPathHandler(e.theme);
  3438. });
  3439. function getPathHandler(theme) {
  3440. switch (theme) {
  3441. case "tianpan":
  3442. case "tianpan-compact":
  3443. return function(nodePathData, x, y, width, height) {
  3444. var r = width >> 1;
  3445. nodePathData.push('M', x, y + r,
  3446. 'a', r, r, 0, 1, 1, 0, 0.01,
  3447. 'z');
  3448. }
  3449. default: {
  3450. return function(nodePathData, x, y, width, height) {
  3451. nodePathData.push('M', x, y,
  3452. 'h', width, 'v', height,
  3453. 'h', -width, 'z');
  3454. }
  3455. }
  3456. }
  3457. }
  3458. navigate();
  3459. function navigate() {
  3460. function moveView(center, duration) {
  3461. var box = visibleView;
  3462. center.x = -center.x;
  3463. center.y = -center.y;
  3464. var viewMatrix = minder.getPaper().getViewPortMatrix();
  3465. box = viewMatrix.transformBox(box);
  3466. var targetPosition = center.offset(box.width / 2, box.height / 2);
  3467. minder.getViewDragger().moveTo(targetPosition, duration);
  3468. }
  3469. var dragging = false;
  3470. paper.on('mousedown', function(e) {
  3471. dragging = true;
  3472. moveView(e.getPosition('top'), 200);
  3473. $previewNavigator.addClass('grab');
  3474. });
  3475. paper.on('mousemove', function(e) {
  3476. if (dragging) {
  3477. moveView(e.getPosition('top'));
  3478. }
  3479. });
  3480. $(window).on('mouseup', function() {
  3481. dragging = false;
  3482. $previewNavigator.removeClass('grab');
  3483. });
  3484. }
  3485. function updateContentView() {
  3486. var view = minder.getRenderContainer().getBoundaryBox();
  3487. contentView = view;
  3488. var padding = 30;
  3489. paper.setViewBox(
  3490. view.x - padding - 0.5,
  3491. view.y - padding - 0.5,
  3492. view.width + padding * 2 + 1,
  3493. view.height + padding * 2 + 1);
  3494. var nodePathData = [];
  3495. var connectionThumbData = [];
  3496. minder.getRoot().traverse(function(node) {
  3497. var box = node.getLayoutBox();
  3498. pathHandler(nodePathData, box.x, box.y, box.width, box.height);
  3499. if (node.getConnection() && node.parent && node.parent.isExpanded()) {
  3500. connectionThumbData.push(node.getConnection().getPathData());
  3501. }
  3502. });
  3503. paper.setStyle('background', minder.getStyle('background'));
  3504. if (nodePathData.length) {
  3505. nodeThumb
  3506. .fill(minder.getStyle('root-background'))
  3507. .setPathData(nodePathData);
  3508. } else {
  3509. nodeThumb.setPathData(null);
  3510. }
  3511. if (connectionThumbData.length) {
  3512. connectionThumb
  3513. .stroke(minder.getStyle('connect-color'), '0.5%')
  3514. .setPathData(connectionThumbData);
  3515. } else {
  3516. connectionThumb.setPathData(null);
  3517. }
  3518. updateVisibleView();
  3519. }
  3520. function updateVisibleView() {
  3521. visibleView = minder.getViewDragger().getView();
  3522. visibleRect.setBox(visibleView.intersect(contentView));
  3523. }
  3524. }
  3525. }
  3526. }]);
  3527. angular.module('kityminderEditor')
  3528. .directive('noteBtn', ['valueTransfer', function(valueTransfer) {
  3529. return {
  3530. restrict: 'E',
  3531. templateUrl: 'ui/directive/noteBtn/noteBtn.html',
  3532. scope: {
  3533. minder: '='
  3534. },
  3535. replace: true,
  3536. link: function($scope) {
  3537. var minder = $scope.minder;
  3538. $scope.addNote =function() {
  3539. valueTransfer.noteEditorOpen = true;
  3540. };
  3541. }
  3542. }
  3543. }]);
  3544. angular.module('kityminderEditor')
  3545. .directive('noteEditor', ['valueTransfer', function(valueTransfer) {
  3546. return {
  3547. restrict: 'A',
  3548. templateUrl: 'ui/directive/noteEditor/noteEditor.html',
  3549. scope: {
  3550. minder: '='
  3551. },
  3552. replace: true,
  3553. controller: ["$scope", function($scope) {
  3554. var minder = $scope.minder;
  3555. var isInteracting = false;
  3556. var cmEditor;
  3557. $scope.codemirrorLoaded = function(_editor) {
  3558. cmEditor = $scope.cmEditor = _editor;
  3559. _editor.setSize('100%', '100%');
  3560. };
  3561. function updateNote() {
  3562. var enabled = $scope.noteEnabled = minder.queryCommandState('note') != -1;
  3563. var noteValue = minder.queryCommandValue('note') || '';
  3564. if (enabled) {
  3565. $scope.noteContent = noteValue;
  3566. }
  3567. isInteracting = true;
  3568. $scope.$apply();
  3569. isInteracting = false;
  3570. }
  3571. $scope.$watch('noteContent', function(content) {
  3572. var enabled = minder.queryCommandState('note') != -1;
  3573. if (content && enabled && !isInteracting) {
  3574. minder.execCommand('note', content);
  3575. }
  3576. setTimeout(function() {
  3577. cmEditor.refresh();
  3578. });
  3579. });
  3580. var noteEditorOpen = function() {
  3581. return valueTransfer.noteEditorOpen;
  3582. };
  3583. // 监听面板状态变量的改变
  3584. $scope.$watch(noteEditorOpen, function(newVal, oldVal) {
  3585. if (newVal) {
  3586. setTimeout(function() {
  3587. cmEditor.refresh();
  3588. cmEditor.focus();
  3589. });
  3590. }
  3591. $scope.noteEditorOpen = valueTransfer.noteEditorOpen;
  3592. }, true);
  3593. $scope.closeNoteEditor = function() {
  3594. valueTransfer.noteEditorOpen = false;
  3595. editor.receiver.selectAll();
  3596. };
  3597. minder.on('interactchange', updateNote);
  3598. }]
  3599. }
  3600. }]);
  3601. // TODO: 使用一个 div 容器作为 previewer,而不是两个
  3602. angular.module('kityminderEditor')
  3603. .directive('notePreviewer', ['$sce', 'valueTransfer', function($sce, valueTransfer) {
  3604. return {
  3605. restrict: 'A',
  3606. templateUrl: 'ui/directive/notePreviewer/notePreviewer.html',
  3607. link: function(scope, element) {
  3608. var minder = scope.minder;
  3609. var $container = element.parent();
  3610. var $previewer = element.children();
  3611. scope.showNotePreviewer = false;
  3612. marked.setOptions({
  3613. gfm: true,
  3614. tables: true,
  3615. breaks: true,
  3616. pedantic: false,
  3617. sanitize: true,
  3618. smartLists: true,
  3619. smartypants: false
  3620. });
  3621. var previewTimer;
  3622. minder.on('shownoterequest', function(e) {
  3623. previewTimer = setTimeout(function() {
  3624. preview(e.node, e.keyword);
  3625. }, 300);
  3626. });
  3627. minder.on('hidenoterequest', function() {
  3628. clearTimeout(previewTimer);
  3629. scope.showNotePreviewer = false;
  3630. //scope.$apply();
  3631. });
  3632. var previewLive = false;
  3633. $(document).on('mousedown mousewheel DOMMouseScroll', function() {
  3634. if (!previewLive) return;
  3635. scope.showNotePreviewer = false;
  3636. scope.$apply();
  3637. });
  3638. element.on('mousedown mousewheel DOMMouseScroll', function(e) {
  3639. e.stopPropagation();
  3640. });
  3641. function preview(node, keyword) {
  3642. var icon = node.getRenderer('NoteIconRenderer').getRenderShape();
  3643. var b = icon.getRenderBox('screen');
  3644. var note = node.getData('note');
  3645. $previewer[0].scrollTop = 0;
  3646. var html = marked(note);
  3647. if (keyword) {
  3648. html = html.replace(new RegExp('(' + keyword + ')', 'ig'), '<span class="highlight">$1</span>');
  3649. }
  3650. scope.noteContent = $sce.trustAsHtml(html);
  3651. scope.$apply(); // 让浏览器重新渲染以获取 previewer 提示框的尺寸
  3652. var cw = $($container[0]).width();
  3653. var ch = $($container[0]).height();
  3654. var pw = $($previewer).outerWidth();
  3655. var ph = $($previewer).outerHeight();
  3656. var x = b.cx - pw / 2 - $container[0].offsetLeft;
  3657. var y = b.bottom + 10 - $container[0].offsetTop;
  3658. if (x < 0) x = 10;
  3659. if (x + pw > cw) x = b.left - pw - 10 - $container[0].offsetLeft;
  3660. if (y + ph > ch) y = b.top - ph - 10 - $container[0].offsetTop;
  3661. scope.previewerStyle = {
  3662. 'left': Math.round(x) + 'px',
  3663. 'top': Math.round(y) + 'px'
  3664. };
  3665. scope.showNotePreviewer = true;
  3666. var view = $previewer[0].querySelector('.highlight');
  3667. if (view) {
  3668. view.scrollIntoView();
  3669. }
  3670. previewLive = true;
  3671. scope.$apply();
  3672. }
  3673. }
  3674. }
  3675. }]);
  3676. angular.module('kityminderEditor')
  3677. .directive('operation', function() {
  3678. return {
  3679. restrict: 'E',
  3680. templateUrl: 'ui/directive/operation/operation.html',
  3681. scope: {
  3682. minder: '='
  3683. },
  3684. replace: true,
  3685. link: function($scope) {
  3686. $scope.editNode = function() {
  3687. var receiverElement = editor.receiver.element;
  3688. var fsm = editor.fsm;
  3689. var receiver = editor.receiver;
  3690. receiverElement.innerText = minder.queryCommandValue('text');
  3691. fsm.jump('input', 'input-request');
  3692. receiver.selectAll();
  3693. }
  3694. }
  3695. }
  3696. });
  3697. angular.module('kityminderEditor')
  3698. .directive('priorityEditor', ['commandBinder', function(commandBinder) {
  3699. return {
  3700. restrict: 'E',
  3701. templateUrl: 'ui/directive/priorityEditor/priorityEditor.html',
  3702. scope: {
  3703. minder: '='
  3704. },
  3705. replace: true,
  3706. link: function($scope) {
  3707. var minder = $scope.minder;
  3708. var priorities = [];
  3709. for (var i = 0; i < 10; i++) {
  3710. priorities.push(i);
  3711. }
  3712. commandBinder.bind(minder, 'priority', $scope);
  3713. $scope.priorities = priorities;
  3714. $scope.getPriorityTitle = function(p) {
  3715. switch(p) {
  3716. case 0: return '移除优先级';
  3717. default: return '优先级' + p;
  3718. }
  3719. }
  3720. }
  3721. }
  3722. }]);
  3723. angular.module('kityminderEditor')
  3724. .directive('progressEditor', ['commandBinder', function(commandBinder) {
  3725. return {
  3726. restrict: 'E',
  3727. templateUrl: 'ui/directive/progressEditor/progressEditor.html',
  3728. scope: {
  3729. minder: '='
  3730. },
  3731. replace: true,
  3732. link: function($scope) {
  3733. var minder = $scope.minder;
  3734. var progresses = [];
  3735. for (var i = 0; i < 10; i++) {
  3736. progresses.push(i);
  3737. }
  3738. commandBinder.bind(minder, 'progress', $scope);
  3739. $scope.progresses = progresses;
  3740. $scope.getProgressTitle = function(p) {
  3741. switch(p) {
  3742. case 0: return '移除进度';
  3743. case 1: return '未开始';
  3744. case 9: return '全部完成';
  3745. default: return '完成' + (p - 1) + '/8';
  3746. }
  3747. }
  3748. }
  3749. }
  3750. }])
  3751. angular.module('kityminderEditor')
  3752. .directive('resourceEditor', function () {
  3753. return {
  3754. restrict: 'E',
  3755. templateUrl: 'ui/directive/resourceEditor/resourceEditor.html',
  3756. scope: {
  3757. minder: '='
  3758. },
  3759. replace: true,
  3760. controller: ["$scope", function ($scope) {
  3761. var minder = $scope.minder;
  3762. var isInteracting = false;
  3763. minder.on('interactchange', function () {
  3764. var enabled = $scope.enabled = minder.queryCommandState('resource') != -1;
  3765. var selected = enabled ? minder.queryCommandValue('resource') : [];
  3766. var used = minder.getUsedResource().map(function (resourceName) {
  3767. return {
  3768. name: resourceName,
  3769. selected: selected.indexOf(resourceName) > -1
  3770. }
  3771. });
  3772. $scope.used = used;
  3773. isInteracting = true;
  3774. $scope.$apply();
  3775. isInteracting = false;
  3776. });
  3777. $scope.$watch('used', function (used) {
  3778. if (minder.queryCommandState('resource') != -1 && used) {
  3779. var resource = used.filter(function (resource) {
  3780. return resource.selected;
  3781. }).map(function (resource) {
  3782. return resource.name;
  3783. });
  3784. // 由于 interactchange 带来的改变则不用执行 resource 命令
  3785. if (isInteracting) {
  3786. return;
  3787. }
  3788. minder.execCommand('resource', resource);
  3789. }
  3790. }, true);
  3791. $scope.resourceColor = function (resource) {
  3792. return minder.getResourceColor(resource).toHEX();
  3793. };
  3794. $scope.addResource = function (resourceName) {
  3795. var origin = minder.queryCommandValue('resource');
  3796. if (!resourceName || !/\S/.test(resourceName)) return;
  3797. if (origin.indexOf(resourceName) == -1) {
  3798. $scope.used.push({
  3799. name: resourceName,
  3800. selected: true
  3801. });
  3802. }
  3803. $scope.newResourceName = null;
  3804. };
  3805. }]
  3806. };
  3807. })
  3808. .directive('clickAnywhereButHere', ['$document', function ($document) {
  3809. return {
  3810. link: function(scope, element, attrs) {
  3811. var onClick = function (event) {
  3812. var isChild = $('#resource-dropdown').has(event.target).length > 0;
  3813. var isSelf = $('#resource-dropdown') == event.target;
  3814. var isInside = isChild || isSelf;
  3815. if (!isInside) {
  3816. scope.$apply(attrs.clickAnywhereButHere)
  3817. }
  3818. };
  3819. scope.$watch(attrs.isActive, function(newValue, oldValue) {
  3820. if (newValue !== oldValue && newValue == true) {
  3821. $document.bind('click', onClick);
  3822. }
  3823. else if (newValue !== oldValue && newValue == false) {
  3824. $document.unbind('click', onClick);
  3825. }
  3826. });
  3827. }
  3828. };
  3829. }]);
  3830. angular.module('kityminderEditor')
  3831. .directive('searchBox', function() {
  3832. return {
  3833. restrict: 'A',
  3834. templateUrl: 'ui/directive/searchBox/searchBox.html',
  3835. scope: {
  3836. minder: '='
  3837. },
  3838. replace: true,
  3839. controller: ["$scope", function ($scope) {
  3840. var minder = $scope.minder;
  3841. var editor = window.editor;
  3842. $scope.handleKeyDown = handleKeyDown;
  3843. $scope.doSearch = doSearch;
  3844. $scope.exitSearch = exitSearch;
  3845. $scope.showTip = false;
  3846. $scope.showSearch = false;
  3847. // 处理输入框按键事件
  3848. function handleKeyDown(e) {
  3849. if (e.keyCode == 13) {
  3850. var direction = e.shiftKey ? 'prev' : 'next';
  3851. doSearch($scope.keyword, direction);
  3852. }
  3853. if (e.keyCode == 27) {
  3854. exitSearch();
  3855. }
  3856. }
  3857. function exitSearch() {
  3858. $('#search-input').blur();
  3859. $scope.showSearch = false;
  3860. minder.fire('hidenoterequest');
  3861. editor.receiver.selectAll();
  3862. }
  3863. function enterSearch() {
  3864. $scope.showSearch = true;
  3865. setTimeout(function() {
  3866. $('#search-input').focus();
  3867. }, 10);
  3868. if ($scope.keyword) {
  3869. $('#search-input')[0].setSelectionRange(0, $scope.keyword.length);
  3870. }
  3871. }
  3872. $('body').on('keydown', function(e) {
  3873. if (e.keyCode == 70 && (e.ctrlKey || e.metaKey) && !e.shiftKey) {
  3874. enterSearch();
  3875. $scope.$apply();
  3876. e.preventDefault();
  3877. }
  3878. });
  3879. minder.on('searchNode', function() {
  3880. enterSearch();
  3881. });
  3882. var nodeSequence = [];
  3883. var searchSequence = [];
  3884. minder.on('contentchange', makeNodeSequence);
  3885. makeNodeSequence();
  3886. function makeNodeSequence() {
  3887. nodeSequence = [];
  3888. minder.getRoot().traverse(function(node) {
  3889. nodeSequence.push(node);
  3890. });
  3891. }
  3892. function makeSearchSequence(keyword) {
  3893. searchSequence = [];
  3894. for (var i = 0; i < nodeSequence.length; i++) {
  3895. var node = nodeSequence[i];
  3896. var text = node.getText().toLowerCase();
  3897. if (text.indexOf(keyword) != -1) {
  3898. searchSequence.push({node:node});
  3899. }
  3900. var note = node.getData('note');
  3901. if (note && note.toLowerCase().indexOf(keyword) != -1) {
  3902. searchSequence.push({node: node, keyword: keyword});
  3903. }
  3904. }
  3905. }
  3906. function doSearch(keyword, direction) {
  3907. $scope.showTip = false;
  3908. minder.fire('hidenoterequest');
  3909. if (!keyword || !/\S/.exec(keyword)) {
  3910. $('#search-input').focus();
  3911. return;
  3912. }
  3913. // 当搜索不到节点时候默认的选项
  3914. $scope.showTip = true;
  3915. $scope.curIndex = 0;
  3916. $scope.resultNum = 0;
  3917. keyword = keyword.toLowerCase();
  3918. var newSearch = doSearch.lastKeyword != keyword;
  3919. doSearch.lastKeyword = keyword;
  3920. if (newSearch) {
  3921. makeSearchSequence(keyword);
  3922. }
  3923. $scope.resultNum = searchSequence.length;
  3924. if (searchSequence.length) {
  3925. var curIndex = newSearch ? 0 : (direction === 'next' ? doSearch.lastIndex + 1 : doSearch.lastIndex - 1) || 0;
  3926. curIndex = (searchSequence.length + curIndex) % searchSequence.length;
  3927. setSearchResult(searchSequence[curIndex].node, searchSequence[curIndex].keyword);
  3928. doSearch.lastIndex = curIndex;
  3929. $scope.curIndex = curIndex + 1;
  3930. function setSearchResult(node, previewKeyword) {
  3931. minder.execCommand('camera', node, 50);
  3932. setTimeout(function () {
  3933. minder.select(node, true);
  3934. if (!node.isExpanded()) minder.execCommand('expand', true);
  3935. if (previewKeyword) {
  3936. minder.fire('shownoterequest', {node: node, keyword: previewKeyword});
  3937. }
  3938. }, 60);
  3939. }
  3940. }
  3941. }
  3942. }]
  3943. }
  3944. });
  3945. angular.module('kityminderEditor')
  3946. .directive('searchBtn', function() {
  3947. return {
  3948. restrict: 'E',
  3949. templateUrl: 'ui/directive/searchBtn/searchBtn.html',
  3950. scope: {
  3951. minder: '='
  3952. },
  3953. replace: true,
  3954. link: function (scope) {
  3955. scope.enterSearch = enterSearch;
  3956. function enterSearch() {
  3957. minder.fire('searchNode');
  3958. }
  3959. }
  3960. }
  3961. });
  3962. angular.module('kityminderEditor')
  3963. .directive('selectAll', function() {
  3964. return {
  3965. restrict: 'E',
  3966. templateUrl: 'ui/directive/selectAll/selectAll.html',
  3967. scope: {
  3968. minder: '='
  3969. },
  3970. replace: true,
  3971. link: function($scope) {
  3972. var minder = $scope.minder;
  3973. $scope.items = ['revert', 'siblings', 'level', 'path', 'tree'];
  3974. $scope.select = {
  3975. all: function() {
  3976. var selection = [];
  3977. minder.getRoot().traverse(function(node) {
  3978. selection.push(node);
  3979. });
  3980. minder.select(selection, true);
  3981. minder.fire('receiverfocus');
  3982. },
  3983. revert: function() {
  3984. var selected = minder.getSelectedNodes();
  3985. var selection = [];
  3986. minder.getRoot().traverse(function(node) {
  3987. if (selected.indexOf(node) == -1) {
  3988. selection.push(node);
  3989. }
  3990. });
  3991. minder.select(selection, true);
  3992. minder.fire('receiverfocus');
  3993. },
  3994. siblings: function() {
  3995. var selected = minder.getSelectedNodes();
  3996. var selection = [];
  3997. selected.forEach(function(node) {
  3998. if (!node.parent) return;
  3999. node.parent.children.forEach(function(sibling) {
  4000. if (selection.indexOf(sibling) == -1) selection.push(sibling);
  4001. });
  4002. });
  4003. minder.select(selection, true);
  4004. minder.fire('receiverfocus');
  4005. },
  4006. level: function() {
  4007. var selectedLevel = minder.getSelectedNodes().map(function(node) {
  4008. return node.getLevel();
  4009. });
  4010. var selection = [];
  4011. minder.getRoot().traverse(function(node) {
  4012. if (selectedLevel.indexOf(node.getLevel()) != -1) {
  4013. selection.push(node);
  4014. }
  4015. });
  4016. minder.select(selection, true);
  4017. minder.fire('receiverfocus');
  4018. },
  4019. path: function() {
  4020. var selected = minder.getSelectedNodes();
  4021. var selection = [];
  4022. selected.forEach(function(node) {
  4023. while(node && selection.indexOf(node) == -1) {
  4024. selection.push(node);
  4025. node = node.parent;
  4026. }
  4027. });
  4028. minder.select(selection, true);
  4029. minder.fire('receiverfocus');
  4030. },
  4031. tree: function() {
  4032. var selected = minder.getSelectedNodes();
  4033. var selection = [];
  4034. selected.forEach(function(parent) {
  4035. parent.traverse(function(node) {
  4036. if (selection.indexOf(node) == -1) selection.push(node);
  4037. });
  4038. });
  4039. minder.select(selection, true);
  4040. minder.fire('receiverfocus');
  4041. }
  4042. };
  4043. }
  4044. }
  4045. });
  4046. angular.module('kityminderEditor')
  4047. .directive('styleOperator', function() {
  4048. return {
  4049. restrict: 'E',
  4050. templateUrl: 'ui/directive/styleOperator/styleOperator.html',
  4051. scope: {
  4052. minder: '='
  4053. },
  4054. replace: true
  4055. }
  4056. });
  4057. angular.module('kityminderEditor')
  4058. .directive('templateList', function() {
  4059. return {
  4060. restrict: 'E',
  4061. templateUrl: 'ui/directive/templateList/templateList.html',
  4062. scope: {
  4063. minder: '='
  4064. },
  4065. replace: true,
  4066. link: function($scope) {
  4067. $scope.templateList = kityminder.Minder.getTemplateList();
  4068. }
  4069. }
  4070. });
  4071. angular.module('kityminderEditor')
  4072. .directive('themeList', function() {
  4073. return {
  4074. restrict: 'E',
  4075. templateUrl: 'ui/directive/themeList/themeList.html',
  4076. replace: true,
  4077. link: function($scope) {
  4078. var themeList = kityminder.Minder.getThemeList();
  4079. //$scope.themeList = themeList;
  4080. $scope.getThemeThumbStyle = function (theme) {
  4081. var themeObj = themeList[theme];
  4082. if (!themeObj) {
  4083. return;
  4084. }
  4085. var style = {
  4086. 'color': themeObj['root-color'],
  4087. 'border-radius': themeObj['root-radius'] / 2
  4088. };
  4089. if (themeObj['root-background']) {
  4090. style['background'] = themeObj['root-background'].toString();
  4091. }
  4092. return style;
  4093. };
  4094. // 维护 theme key 列表以保证列表美观(不按字母顺序排序)
  4095. $scope.themeKeyList = [
  4096. 'classic',
  4097. 'classic-compact',
  4098. 'fresh-blue',
  4099. 'fresh-blue-compat',
  4100. 'fresh-green',
  4101. 'fresh-green-compat',
  4102. 'fresh-pink',
  4103. 'fresh-pink-compat',
  4104. 'fresh-purple',
  4105. 'fresh-purple-compat',
  4106. 'fresh-red',
  4107. 'fresh-red-compat',
  4108. 'fresh-soil',
  4109. 'fresh-soil-compat',
  4110. 'snow',
  4111. 'snow-compact',
  4112. 'tianpan',
  4113. 'tianpan-compact',
  4114. 'fish',
  4115. 'wire'
  4116. ];
  4117. }
  4118. }
  4119. });
  4120. angular.module('kityminderEditor')
  4121. .directive('topTab', function() {
  4122. return {
  4123. restrict: 'A',
  4124. templateUrl: 'ui/directive/topTab/topTab.html',
  4125. scope: {
  4126. minder: '=topTab',
  4127. editor: '='
  4128. },
  4129. link: function(scope) {
  4130. /*
  4131. *
  4132. * 用户选择一个新的选项卡会执行 setCurTab foldTopTab 两个函数
  4133. * 用户点击原来的选项卡会执行 foldTopTop 一个函数
  4134. *
  4135. * 也就是每次选择新的选项卡都会执行 setCurTab初始化的时候也会执行 setCurTab 函数
  4136. * 因此用 executedCurTab 记录是否已经执行了 setCurTab 函数
  4137. * isInit 记录是否是初始化的状态在任意一个函数时候 isInit 设置为 false
  4138. * isOpen 记录是否打开了 topTab
  4139. *
  4140. * 因此用到了三个 mutex
  4141. * */
  4142. var executedCurTab = false;
  4143. var isInit = true;
  4144. var isOpen = true;
  4145. scope.setCurTab = function(tabName) {
  4146. setTimeout(function() {
  4147. //console.log('set cur tab to : ' + tabName);
  4148. executedCurTab = true;
  4149. //isOpen = false;
  4150. if (tabName != 'idea') {
  4151. isInit = false;
  4152. }
  4153. });
  4154. };
  4155. scope.toggleTopTab = function() {
  4156. setTimeout(function() {
  4157. if(!executedCurTab || isInit) {
  4158. isInit = false;
  4159. isOpen ? closeTopTab(): openTopTab();
  4160. isOpen = !isOpen;
  4161. }
  4162. executedCurTab = false;
  4163. });
  4164. };
  4165. function closeTopTab() {
  4166. var $tabContent = $('.tab-content');
  4167. var $minderEditor = $('.minder-editor');
  4168. $tabContent.animate({
  4169. height: 0,
  4170. display: 'none'
  4171. });
  4172. $minderEditor.animate({
  4173. top: '32px'
  4174. });
  4175. }
  4176. function openTopTab() {
  4177. var $tabContent = $('.tab-content');
  4178. var $minderEditor = $('.minder-editor');
  4179. $tabContent.animate({
  4180. height: '60px',
  4181. display: 'block'
  4182. });
  4183. $minderEditor.animate({
  4184. top: '92px'
  4185. });
  4186. }
  4187. }
  4188. }
  4189. });
  4190. angular.module('kityminderEditor')
  4191. .directive('undoRedo', function() {
  4192. return {
  4193. restrict: 'E',
  4194. templateUrl: 'ui/directive/undoRedo/undoRedo.html',
  4195. scope: {
  4196. editor: '='
  4197. },
  4198. replace: true,
  4199. link: function($scope) {
  4200. }
  4201. }
  4202. });
  4203. use('expose-editor');
  4204. })();