├── .bowerrc ├── .gitignore ├── .jscsrc ├── .jshintrc ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── dist ├── images │ ├── iconpriority.png │ ├── iconprogress.png │ ├── icons.png │ └── template.png ├── kityminder.editor.css ├── kityminder.editor.css.map ├── kityminder.editor.js ├── kityminder.editor.min.css └── kityminder.editor.min.js ├── docs ├── CNAME ├── baiduNaotu-json │ └── baiduNaotu-json.js ├── demo.json ├── favicon.ico ├── hotbox │ ├── hotbox.css │ └── hotbox.min.js ├── index.html ├── json-diff │ └── json-diff.js ├── kity │ ├── kity.js │ └── kity.min.js ├── kityminder-core │ ├── kityminder.core.css │ ├── kityminder.core.js │ └── kityminder.core.min.js └── kityminder-editor │ ├── images │ ├── iconpriority.png │ ├── iconprogress.png │ ├── icons.png │ └── template.png │ ├── kityminder.editor.css │ ├── kityminder.editor.css.map │ ├── kityminder.editor.js │ ├── kityminder.editor.min.css │ └── kityminder.editor.min.js ├── favicon.ico ├── index.html ├── less ├── _navigator.less ├── _tool_group.less ├── _vars.less ├── editor.less ├── imageDialog.less └── topTab │ ├── appearance │ ├── colorPanel.less │ ├── fontOperator.less │ ├── layout.less │ ├── styleOperator.less │ ├── templatePanel.less │ └── themePanel.less │ ├── idea │ ├── appendNode.less │ ├── arrange.less │ ├── hyperlink.less │ ├── image.less │ ├── note.less │ ├── noteEditor.less │ ├── operation.less │ ├── priority.less │ ├── progress.less │ ├── resource.less │ └── undoRedo.less │ ├── other │ └── other.less │ ├── searchBox.less │ ├── topTab.less │ └── view │ ├── expand.less │ ├── search.less │ └── select.less ├── package-lock.json ├── package.json ├── relations.png ├── server └── imageUpload.php ├── src ├── editor.js ├── expose-editor.js ├── hotbox.js ├── lang.js ├── minder.js ├── runtime │ ├── clipboard-mimetype.js │ ├── clipboard.js │ ├── container.js │ ├── drag.js │ ├── fsm.js │ ├── history.js │ ├── hotbox.js │ ├── input.js │ ├── jumping.js │ ├── minder.js │ ├── node.js │ ├── priority.js │ ├── progress.js │ └── receiver.js └── tool │ ├── debug.js │ ├── format.js │ ├── innertext.js │ ├── jsondiff.js │ ├── key.js │ └── keymap.js └── ui ├── dialog ├── hyperlink │ ├── hyperlink.ctrl.js │ └── hyperlink.tpl.html ├── imExportNode │ ├── imExportNode.ctrl.js │ └── imExportNode.tpl.html └── image │ ├── image.ctrl.js │ └── image.tpl.html ├── directive ├── appendNode │ ├── appendNode.directive.js │ └── appendNode.html ├── arrange │ ├── arrange.directive.js │ └── arrange.html ├── colorPanel │ ├── colorPanel.directive.js │ └── colorPanel.html ├── expandLevel │ ├── expandLevel.directive.js │ └── expandLevel.html ├── fontOperator │ ├── fontOperator.directive.js │ └── fontOperator.html ├── hyperLink │ ├── hyperLink.directive.js │ └── hyperLink.html ├── imageBtn │ ├── imageBtn.directive.js │ └── imageBtn.html ├── kityminderEditor │ ├── kityminderEditor.directive.js │ └── kityminderEditor.html ├── kityminderViewer │ ├── kityminderViewer.directive.js │ └── kityminderViewer.html ├── layout │ ├── layout.directive.js │ └── layout.html ├── navigator │ ├── navigator.directive.js │ └── navigator.html ├── noteBtn │ ├── noteBtn.directive.js │ └── noteBtn.html ├── noteEditor │ ├── noteEditor.directive.js │ └── noteEditor.html ├── notePreviewer │ ├── notePreviewer.directive.js │ └── notePreviewer.html ├── operation │ ├── operation.directive.js │ └── operation.html ├── other │ ├── other.directive.js │ └── other.html ├── priorityEditor │ ├── priorityEditor.directive.js │ └── priorityEditor.html ├── progressEditor │ ├── progressEditor.directive.js │ └── progressEditor.html ├── resourceEditor │ ├── resourceEditor.directive.js │ └── resourceEditor.html ├── searchBox │ ├── searchBox.directive.js │ └── searchBox.html ├── searchBtn │ ├── searchBtn.directive.js │ └── searchBtn.html ├── selectAll │ ├── selectAll.directive.js │ └── selectAll.html ├── styleOperator │ ├── styleOperator.directive.js │ └── styleOperator.html ├── templateList │ ├── templateList.directive.js │ └── templateList.html ├── themeList │ ├── themeList.directive.js │ └── themeList.html ├── topTab │ ├── topTab.directive.js │ └── topTab.html └── undoRedo │ ├── undoRedo.directive.js │ └── undoRedo.html ├── filter ├── command.filters.js └── lang.filter.js ├── images ├── iconpriority.png ├── iconprogress.png ├── icons.png └── template.png ├── kityminder.app.js ├── service ├── commandBinder.service.js ├── config.service.js ├── lang.zh-cn.service.js ├── memory.service.js ├── minder.service.js ├── resource.service.js ├── revokeDialog.service.js ├── server.service.js └── valueTransfer.service.js └── templates.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "allow_root": true, 4 | "registry": "https://registry.bower.io" 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | bower_components/ 4 | node_modules/ 5 | dist/ 6 | ui/templates.js 7 | .tmp/ 8 | .vscode/ 9 | upload/ -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | /** 2 | * FEX Style Guide (Javascript) 3 | * 4 | * TODO: 5 | * 6 | * 1. 找不到选项:每行只允许一个语句 7 | * 2. 找不到选项:块状代码需要用大括号括起来 8 | */ 9 | { 10 | // 缩进「MUST」使用 4 个空格 11 | "validateIndentation": 4, 12 | 13 | // 大括号(块状代码)前「MUST」使用空格 14 | "requireSpaceBeforeBlockStatements": true, 15 | 16 | // 下列关键字「MUST」使用空格 17 | "requireSpaceAfterKeywords": ["if", "else", "for", "while", 18 | "do", "try", "catch", "finally" 19 | ], 20 | 21 | // `,` 和 `;` 前面不允许「MUST NOT」使用空格。 22 | "requireLeftStickedOperators": [",", ";"], 23 | 24 | // 二元运算符前后「MUST」使用空格 25 | "requireSpaceBeforeBinaryOperators": [ 26 | "+", 27 | "-", 28 | "*", 29 | "/", 30 | "=", 31 | "==", 32 | "===", 33 | "!=", 34 | "!==", 35 | "|", 36 | "||", 37 | "&", 38 | "&&" 39 | ], 40 | "requireSpaceAfterBinaryOperators": [ 41 | "+", 42 | "-", 43 | "*", 44 | "/", 45 | "=", 46 | "==", 47 | "===", 48 | "!=", 49 | "!==", 50 | "|", 51 | "||", 52 | "&", 53 | "&&", 54 | ":" 55 | ], 56 | 57 | // 一元运算符与操作对象间「MUST NOT」使用空格 58 | "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"], 59 | 60 | // 函数参数小括号前「MUST NOT」使用空格 61 | "disallowSpacesInFunctionExpression": { 62 | "beforeOpeningRoundBrace": true 63 | }, 64 | 65 | // 小括号里面「MUST NOT」使用空格 66 | "disallowSpacesInsideParentheses": true, 67 | 68 | // 行尾「MUST NOT」使用空格 69 | "disallowTrailingWhitespace": true, 70 | 71 | // 每行「MUST NOT」超过 120 个字符 72 | "maximumLineLength": 120, 73 | 74 | // 一下操作符「MUST NOT」放在一行的最前面,需要放在上一行的后面 75 | "requireOperatorBeforeLineBreak": [ 76 | "?", 77 | "+", 78 | "-", 79 | "/", 80 | "*", 81 | "=", 82 | "==", 83 | "===", 84 | "!=", 85 | "!==", 86 | ">", 87 | ">=", 88 | "<", 89 | "<=", 90 | ",", 91 | ";", 92 | "&&", 93 | "&", 94 | "||", 95 | "|" 96 | ], 97 | 98 | // 字符串统一「MUST」使用单引号 99 | "validateQuoteMarks": "'", 100 | 101 | // 「MUST NOT」使用多行字符串 102 | "disallowMultipleLineStrings": true 103 | } -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "undef" : true, 3 | "unused" : false, 4 | "strict" : false, 5 | "curly" : false, 6 | "newcap" : true, 7 | "trailing" : true, 8 | "white": false, 9 | "quotmark": false, 10 | "browser": true, 11 | "boss": true, 12 | "indent": 4, 13 | "predef" : [ 14 | "define" 15 | ] 16 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | KityMinder Editor 2 | ========== 3 | 4 | 5 | 6 | ## 简介 7 | 8 | 基于 [kityminder-editor](https://github.com/fex-team/kityminder-editor) 修改,由于没有学习过AngularJS,都是照葫芦画瓢添加的代码,只是为了方便个人在不登录的情况下编辑和查看思维导图。 9 | 10 | 11 | 12 | **在线:**[http://mind.clboy.cn](http://mind.clboy.cn) 13 | 14 | 可以在访问链接中加上md参数,指向json数据的请求地址(带上http/https),md参数要进行URL编码,请求的地址要允许跨域,为了方便,加载后的思维导图禁止直接编辑,可先下载到本地,然后从本地加载 15 | 16 | **例:**[http://mind.clboy.cn?md=http%3a%2f%2fmind.clboy.cn%2fdemo.json](http://mind.clboy.cn?md=http%3a%2f%2fmind.clboy.cn%2fdemo.json) 17 | 18 | 19 | 20 | ## 新增 21 | 22 | - 导出HTML+SVG 23 | - 根据md参数远程加载,远程加载的目的就是为了方便查看,所以禁止直接编辑,可下载到本地后从本地加载 24 | - 原图片搜索由于跨域拿不到搜索数据,改为Bing图片搜索 25 | - 新增`更多...`选项卡 26 | - 新增收起全部节点按钮(原先是只收起节点Leave相同的,现改为>=),意思就是说如果折叠2级节点,2级节点下面的都会被折叠 27 | - 新增关闭顶部菜单按钮 28 | - 新增只读模式和编辑模式切换 29 | - 新增全屏模式 30 | - 修复在只读模式开启拖拽的情况下双击会调用编辑方法,原先是根据状态判断是否是'readonly',但是选择开启拖拽后的状态是'hand',所以会进入编辑方法,但是此时是看不到编辑框的,就会导致选择的节点的文本变为空字符 31 | 32 | 33 | 34 | ## `已知`问题 35 | 36 | - 图片跨域无法生成PNG 37 | - 360安全浏览器右键菜单无法显示 38 | - 火狐浏览器节点中如果存在链接导出PNG可能会无效,导入json可能会错位 39 | - Chrome暂未发现问题 :white_check_mark:(强烈建议) 40 | 41 | 42 | 43 | ## 本地运行 44 | 45 | ```shell 46 | git clone git@github.com:cloudlandboy/kityminder-editor.git 47 | ``` 48 | 49 | - 进入kityminder-editor的目录执行`bower install`(使用cdn则不需要下载) 50 | 51 | - 打开`docs`目录下的`index.html`即可 52 | - 脱离网络,把`docs/index.html`中css和js,bower部分注释打开,cdn部分注释掉 53 | 54 | 55 | 56 | ## 其他格式导入 57 | 58 | 对于以下格式可以在百度脑图官方的网站导入,然后以JSON格式导出。 59 | 60 | - Freemind 格式(.mm) 61 | - XMind 格式(.xmind) 62 | - MindManager 格式(.mmap) 63 | 64 | 65 | 66 | 由于百度脑图官方并没有提供导出和导入JSON的快捷方式,这里借助浏览器插件[Tampermonkey](https://greasyfork.org/zh-CN) ,脚本已经上传 67 | 68 | [](https://greasyfork.org/zh-CN/scripts/394815-%E7%99%BE%E5%BA%A6%E8%84%91%E5%9B%BE%E5%AF%BC%E5%85%A5%E5%AF%BC%E5%87%BAjson%E6%A0%BC%E5%BC%8F) 69 | 70 | 点击安装,然后刷新页面即可看到导入导出按钮 -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kityminder-editor", 3 | "version": "1.0.61", 4 | "authors": [ 5 | "fex" 6 | ], 7 | "description": "Kity Minder Editor", 8 | "main": [ 9 | "dist/kityminder.editor.js", 10 | "dist/kityminder.editor.css" 11 | ], 12 | "keywords": [ 13 | "kityminder", 14 | "fex", 15 | "ui", 16 | "javascript", 17 | "html5", 18 | "svg" 19 | ], 20 | "license": "BSD", 21 | "homepage": "https://github.com/cloudlandboy/kityminder-editor", 22 | "ignore": [ 23 | "**/.*", 24 | "node_modules", 25 | "bower_components", 26 | "test", 27 | "tests", 28 | "less", 29 | "ui", 30 | "src", 31 | "Gruntfile.js", 32 | "package.json" 33 | ], 34 | "devDependencies": { 35 | "seajs": "~2.3.0" 36 | }, 37 | "dependencies": { 38 | "bootstrap": "~3.3.4", 39 | "angular": "~1.3.15", 40 | "angular-bootstrap": "~0.12.1", 41 | "angular-ui-codemirror": "~0.2.3", 42 | "codemirror": "~4.8.0", 43 | "marked": "git://github.com/chjj/marked.git#master", 44 | "hotbox": "~1.0.2", 45 | "color-picker": "~1.0.2", 46 | "kity": "^2.0.5", 47 | "json-diff": "*", 48 | "toastr": "^2.1.3" 49 | }, 50 | "overrides": { 51 | "codemirror": { 52 | "main": [ 53 | "lib/codemirror.js", 54 | "lib/codemirror.css", 55 | "mode/xml/xml.js", 56 | "mode/javascript/javascript.js", 57 | "mode/css/css.js", 58 | "mode/htmlmixed/htmlmixed.js", 59 | "mode/markdown/markdown.js", 60 | "addon/mode/overlay.js", 61 | "mode/gfm/gfm.js" 62 | ] 63 | } 64 | }, 65 | "resolutions": { 66 | "angular": "~1.3.8" 67 | } 68 | } -------------------------------------------------------------------------------- /dist/images/iconpriority.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudlandboy/kityminder-editor/38425bb672d6c8dd160ec60248c173e57de0530c/dist/images/iconpriority.png -------------------------------------------------------------------------------- /dist/images/iconprogress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudlandboy/kityminder-editor/38425bb672d6c8dd160ec60248c173e57de0530c/dist/images/iconprogress.png -------------------------------------------------------------------------------- /dist/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudlandboy/kityminder-editor/38425bb672d6c8dd160ec60248c173e57de0530c/dist/images/icons.png -------------------------------------------------------------------------------- /dist/images/template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudlandboy/kityminder-editor/38425bb672d6c8dd160ec60248c173e57de0530c/dist/images/template.png -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | mind.clboy.cn -------------------------------------------------------------------------------- /docs/baiduNaotu-json/baiduNaotu-json.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name 百度脑图导入导出JSON格式 3 | // @namespace http://mind.clboy.cn/ 4 | // @version 0.1 5 | // @description 百度脑图导入导出JSON格式数据,可以配合http://mind.clboy.cn使用 6 | // @author cloudlandboy 7 | // @match *://naotu.baidu.com/* 8 | // @require https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js 9 | // ==/UserScript== 10 | 11 | (function() { 12 | 'use strict'; 13 | var LINK=document.createElement('a'); 14 | var LOCALFILE=document.createElement('input'); 15 | LINK.style.display='none'; 16 | LOCALFILE.style.display='none'; 17 | LOCALFILE.type='file'; 18 | LOCALFILE.onchange=function(){ 19 | var file = this.files[0]; 20 | var fileName = file.name.split('.').pop(); 21 | if (!(fileName == 'json')) { 22 | alert('不是.json文件'); 23 | return; 24 | } 25 | var fileReader = new FileReader(); 26 | fileReader.readAsText(file); 27 | fileReader.onload = () => { 28 | try{ 29 | var data = JSON.parse(fileReader.result); 30 | minder.importJson(data); 31 | }catch(e){ 32 | alert('json文件解析失败!'); 33 | } 34 | } 35 | }; 36 | $('body').append(LINK) 37 | $('body').append(LOCALFILE) 38 | var elei=$('
导入JSON
'); 39 | var eleo=$('
导出JSON
'); 40 | var css={ 41 | 'height': '40px', 42 | 'display': 'inline-block', 43 | 'float': 'right', 44 | 'padding': '0 5px', 45 | 'user-select': 'none', 46 | 'cursor':'pointer', 47 | 'background-color':'red', 48 | 'margin-right':'10px' 49 | }; 50 | elei.css(css); 51 | eleo.css(css); 52 | elei.hover(function(){ 53 | $(this).css("background-color","green"); 54 | },function(){ 55 | $(this).css("background-color","red"); 56 | }); 57 | eleo.hover(function(){ 58 | $(this).css("background-color","green"); 59 | },function(){ 60 | $(this).css("background-color","red"); 61 | }); 62 | eleo.click(function(){ 63 | var blob = new Blob([JSON.stringify(minder.exportJson())]); 64 | var fileName=minder.getRoot().data.text; 65 | LINK.download = fileName.replace(fileName[0], fileName[0].toLowerCase()) + ".json"; 66 | LINK.href = URL.createObjectURL(blob); 67 | LINK.click(); 68 | }); 69 | elei.click(function(){ 70 | LOCALFILE.click(); 71 | }); 72 | eleo.appendTo('header'); 73 | elei.appendTo('header'); 74 | })(); -------------------------------------------------------------------------------- /docs/demo.json: -------------------------------------------------------------------------------- 1 | {"root":{"data":{"id":"bzpe8kxwteg0","created":1578383454622,"text":"Language","expandState":"expand"},"children":[{"data":{"id":"bzpe8penyc80","created":1578383464342,"text":"Python","expandState":"collapse","progress":null,"layout":null},"children":[{"data":{"id":"bzpeas6ss880","created":1578383627125,"text":"速度快","layout":null,"expandState":"expand"},"children":[]},{"data":{"id":"bzpeawnej6g0","created":1578383636836,"text":"免费、开源","layout":null,"expandState":"expand"},"children":[]},{"data":{"id":"bzpecy5exb40","created":1578383796830,"text":"可移植性","layout":null,"expandState":"expand"},"children":[]},{"data":{"id":"bzped4ngzg00","created":1578383810983,"text":"面向对象","layout":null,"expandState":"expand"},"children":[]}]},{"data":{"id":"bzpe8phb6lc0","created":1578383464502,"text":"Java","expandState":"collapse","progress":9,"layout":null},"children":[{"data":{"id":"bzpebel041k0","created":1578383675873,"text":"Jsp","layout":null,"expandState":"expand"},"children":[{"data":{"id":"bzpedtzsaf40","created":1578383866147,"text":"跨平台","layout":null,"expandState":"expand"},"children":[]},{"data":{"id":"bzpedy2go5c0","created":1578383875016,"text":"业务代码分离","expandState":"expand","layout":null},"children":[]}]},{"data":{"id":"bzpebh7atqo0","created":1578383681575,"text":"Servlet","layout":null,"expandState":"expand"},"children":[]}]},{"data":{"id":"bzpe8pjnf600","created":1578383464643,"text":"C++","layout":null,"expandState":"expand"},"children":[]},{"data":{"id":"bzpe8pmc3ds0","created":1578383464805,"text":"Ruby","layout":null,"expandState":"expand"},"children":[]},{"data":{"id":"bzpe8porswo0","created":1578383464953,"text":"Lua","layout":null,"expandState":"expand"},"children":[]},{"data":{"id":"bzpe8prmtc00","created":1578383465126,"text":"Go","expandState":"collapse","layout":null},"children":[{"data":{"id":"bzpeenumss00","created":1578383931139,"text":"自动垃圾回收","layout":null,"expandState":"expand"},"children":[]},{"data":{"id":"bzpeeqz6ro00","created":1578383937944,"text":"更丰富的内置类型","layout":null,"expandState":"expand"},"children":[]},{"data":{"id":"bzpef21fkgg0","created":1578383962025,"text":"函数多返回值","layout":null,"expandState":"expand"},"children":[]}]},{"data":{"id":"bzpe8puk0680","created":1578383465303,"text":"PHP","expandState":"collapse","note":"PHP 教程\n\nPHP 是一种创建动态交互性站点的强有力的服务器端脚本语言。\n\nPHP 是免费的,并且使用非常广泛。同时,对于像微软 ASP 这样的竞争者来说,PHP 无疑是另一种高效率的选项。","layout":null},"children":[{"data":{"id":"bzpeftc5hw80","created":1578384021446,"text":"PHP 是一种创建动态交互性站点的强有力的服务器端脚本语言","layout":null,"expandState":"expand"},"children":[]}]},{"data":{"id":"bzpe8pxclxc0","created":1578383465471,"text":"ASP","layout":null,"expandState":"expand"},"children":[]}]},"template":"structure","theme":"fresh-blue","version":"1.4.43"} -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudlandboy/kityminder-editor/38425bb672d6c8dd160ec60248c173e57de0530c/docs/favicon.ico -------------------------------------------------------------------------------- /docs/hotbox/hotbox.css: -------------------------------------------------------------------------------- 1 | .hotbox{font-family:Arial,"Hiragino Sans GB","Microsoft YaHei","WenQuanYi Micro Hei",sans-serif;position:absolute;left:0;top:0;overflow:visible}.hotbox .state{position:absolute;overflow:visible;display:none}.hotbox .state .center .button,.hotbox .state .ring .button{position:absolute;width:70px;height:70px;margin-left:-35px;margin-top:-35px;border-radius:100%;box-shadow:0 0 30px rgba(0,0,0,0.3)}.hotbox .state .center .label,.hotbox .state .ring .label,.hotbox .state .center .key,.hotbox .state .ring .key{display:block;text-align:center;line-height:1.4em;vertical-align:middle}.hotbox .state .center .label,.hotbox .state .ring .label{font-size:16px;margin-top:17px;color:black;font-weight:normal;line-height:1em}.hotbox .state .center .key,.hotbox .state .ring .key{font-size:12px;color:#999}.hotbox .state .ring-shape{position:absolute;left:-25px;top:-25px;border:25px solid rgba(0,0,0,0.3);border-radius:100%;box-sizing:content-box}.hotbox .state .top,.hotbox .state .bottom{position:absolute;white-space:nowrap}.hotbox .state .top .button,.hotbox .state .bottom .button{display:inline-block;padding:8px 15px;margin:0 10px;border-radius:15px;box-shadow:0 0 30px rgba(0,0,0,0.3);position:relative}.hotbox .state .top .button .label,.hotbox .state .bottom .button .label{font-size:14px;line-height:14px;vertical-align:middle;color:black;line-height:1em}.hotbox .state .top .button .key,.hotbox .state .bottom .button .key{font-size:12px;line-height:12px;vertical-align:middle;color:#999;margin-left:3px}.hotbox .state .top .button .key:before,.hotbox .state .bottom .button .key:before{content:'('}.hotbox .state .top .button .key:after,.hotbox .state .bottom .button .key:after{content:')'}.hotbox .state .button{background:#F9F9F9;overflow:hidden;cursor:default}.hotbox .state .button .key,.hotbox .state .button .label{opacity:0.3}.hotbox .state .button.enabled{background:white}.hotbox .state .button.enabled .key,.hotbox .state .button.enabled .label{opacity:1}.hotbox .state .button.enabled:hover{background:#e87372}.hotbox .state .button.enabled:hover .label{color:white}.hotbox .state .button.enabled:hover .key{color:#fadfdf}.hotbox .state .button.enabled.selected{-webkit-animation:selected .1s ease;background:#e45d5c}.hotbox .state .button.enabled.selected .label{color:white}.hotbox .state .button.enabled.selected .key{color:#fadfdf}.hotbox .state .button.enabled.pressed,.hotbox .state .button.enabled:active{background:#FF974D}.hotbox .state .button.enabled.pressed .label,.hotbox .state .button.enabled:active .label{color:white}.hotbox .state .button.enabled.pressed .key,.hotbox .state .button.enabled:active .key{color:#fff0e6}.hotbox .state.active{display:block}@-webkit-keyframes selected{0%{-webkit-transform:scale(1);transform:scale(1)}50%{-webkit-transform:scale(1.1);transform:scale(1.1)}100%{-webkit-transform:scale(1);transform:scale(1)}}.hotbox-key-receiver{position:absolute;left:-999999px;top:-999999px;width:20px;height:20px;outline:none;margin:0} -------------------------------------------------------------------------------- /docs/kityminder-core/kityminder.core.css: -------------------------------------------------------------------------------- 1 | .km-view { 2 | font-family: "STHeitiSC-Light", "STHeiti", "Hei", "Heiti SC", "Microsoft Yahei", Arial, sans-serif; 3 | -webkit-user-select: none; 4 | user-select: none; 5 | position: relative; 6 | } 7 | 8 | .km-view .km-receiver { 9 | position: absolute; 10 | left: -99999px; 11 | top: -99999px; 12 | width: 20px; 13 | height: 20px; 14 | outline: none; 15 | margin: 0; 16 | } 17 | 18 | .km-view image { 19 | cursor: zoom-in; 20 | } 21 | 22 | .km-image-viewer { 23 | position: fixed; 24 | z-index: 99999; 25 | top: 0; 26 | bottom: 0; 27 | left: 0; 28 | right: 0; 29 | background: rgba(0, 0, 0, .75); 30 | } 31 | 32 | .km-image-viewer .km-image-viewer-container { 33 | position: absolute; 34 | top: 0; 35 | bottom: 0; 36 | left: 0; 37 | right: 0; 38 | text-align: center; 39 | white-space: nowrap; 40 | overflow: auto; 41 | } 42 | 43 | .km-image-viewer .km-image-viewer-container::before { 44 | content: ''; 45 | display: inline-block; 46 | height: 100%; 47 | width: 0; 48 | font-size: 0; 49 | vertical-align: middle; 50 | } 51 | 52 | .km-image-viewer .km-image-viewer-container img { 53 | cursor: zoom-out; 54 | vertical-align: middle; 55 | } 56 | 57 | .km-image-viewer .km-image-viewer-container img.limited { 58 | cursor: zoom-in; 59 | max-width: 100%; 60 | max-height: 100%; 61 | } 62 | 63 | .km-image-viewer .km-image-viewer-toolbar { 64 | z-index: 1; 65 | background: rgba(0, 0, 0, .75); 66 | text-align: right; 67 | transition: all .25s; 68 | } 69 | 70 | .km-image-viewer .km-image-viewer-toolbar.hidden { 71 | transform: translate(0, -100%); 72 | opacity: 0; 73 | } 74 | 75 | .km-image-viewer .km-image-viewer-btn { 76 | cursor: pointer; 77 | outline: 0; 78 | border: 0; 79 | width: 44px; 80 | height: 44px; 81 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjY0IiBoZWlnaHQ9Ijg4IiB2aWV3Qm94PSIwIDAgMjY0IDg4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjx0aXRsZT5kZWZhdWx0LXNraW4gMjwvdGl0bGU+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48Zz48cGF0aCBkPSJNNjcuMDAyIDU5LjV2My43NjhjLTYuMzA3Ljg0LTkuMTg0IDUuNzUtMTAuMDAyIDkuNzMyIDIuMjItMi44MyA1LjU2NC01LjA5OCAxMC4wMDItNS4wOThWNzEuNUw3MyA2NS41ODUgNjcuMDAyIDU5LjV6IiBpZD0iU2hhcGUiIGZpbGw9IiNmZmYiLz48ZyBmaWxsPSIjZmZmIj48cGF0aCBkPSJNMTMgMjl2LTVoMnYzaDN2MmgtNXpNMTMgMTVoNXYyaC0zdjNoLTJ2LTV6TTMxIDE1djVoLTJ2LTNoLTN2LTJoNXpNMzEgMjloLTV2LTJoM3YtM2gydjV6IiBpZD0iU2hhcGUiLz48L2c+PGcgZmlsbD0iI2ZmZiI+PHBhdGggZD0iTTYyIDI0djVoLTJ2LTNoLTN2LTJoNXpNNjIgMjBoLTV2LTJoM3YtM2gydjV6TTcwIDIwdi01aDJ2M2gzdjJoLTV6TTcwIDI0aDV2MmgtM3YzaC0ydi01eiIvPjwvZz48cGF0aCBkPSJNMjAuNTg2IDY2bC01LjY1Ni01LjY1NiAxLjQxNC0xLjQxNEwyMiA2NC41ODZsNS42NTYtNS42NTYgMS40MTQgMS40MTRMMjMuNDE0IDY2bDUuNjU2IDUuNjU2LTEuNDE0IDEuNDE0TDIyIDY3LjQxNGwtNS42NTYgNS42NTYtMS40MTQtMS40MTRMMjAuNTg2IDY2eiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0xMTEuNzg1IDY1LjAzTDExMCA2My41bDMtMy41aC0xMHYtMmgxMGwtMy0zLjUgMS43ODUtMS40NjhMMTE3IDU5bC01LjIxNSA2LjAzeiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0xNTIuMjE1IDY1LjAzTDE1NCA2My41bC0zLTMuNWgxMHYtMmgtMTBsMy0zLjUtMS43ODUtMS40NjhMMTQ3IDU5bDUuMjE1IDYuMDN6IiBmaWxsPSIjZmZmIi8+PGc+PHBhdGggaWQ9IlJlY3RhbmdsZS0xMSIgZmlsbD0iI2ZmZiIgZD0iTTE2MC45NTcgMjguNTQzbC0zLjI1LTMuMjUtMS40MTMgMS40MTQgMy4yNSAzLjI1eiIvPjxwYXRoIGQ9Ik0xNTIuNSAyN2MzLjAzOCAwIDUuNS0yLjQ2MiA1LjUtNS41cy0yLjQ2Mi01LjUtNS41LTUuNS01LjUgMi40NjItNS41IDUuNSAyLjQ2MiA1LjUgNS41IDUuNXoiIGlkPSJPdmFsLTEiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTUwIDIxaDV2MWgtNXoiLz48L2c+PGc+PHBhdGggZD0iTTExNi45NTcgMjguNTQzbC0xLjQxNCAxLjQxNC0zLjI1LTMuMjUgMS40MTQtMS40MTQgMy4yNSAzLjI1eiIgZmlsbD0iI2ZmZiIvPjxwYXRoIGQ9Ik0xMDguNSAyN2MzLjAzOCAwIDUuNS0yLjQ2MiA1LjUtNS41cy0yLjQ2Mi01LjUtNS41LTUuNS01LjUgMi40NjItNS41IDUuNSAyLjQ2MiA1LjUgNS41IDUuNXoiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTA2IDIxaDV2MWgtNXoiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNMTA5LjA0MyAxOS4wMDhsLS4wODUgNS0xLS4wMTcuMDg1LTV6Ii8+PC9nPjwvZz48L2c+PC9zdmc+); 82 | } 83 | 84 | .km-image-viewer .km-image-viewer-toolbar { 85 | position: absolute; 86 | top: 0; 87 | left: 0; 88 | right: 0; 89 | } 90 | 91 | .km-image-viewer .km-image-viewer-close { 92 | background-position: 0 -44px; 93 | } -------------------------------------------------------------------------------- /docs/kityminder-editor/images/iconpriority.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudlandboy/kityminder-editor/38425bb672d6c8dd160ec60248c173e57de0530c/docs/kityminder-editor/images/iconpriority.png -------------------------------------------------------------------------------- /docs/kityminder-editor/images/iconprogress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudlandboy/kityminder-editor/38425bb672d6c8dd160ec60248c173e57de0530c/docs/kityminder-editor/images/iconprogress.png -------------------------------------------------------------------------------- /docs/kityminder-editor/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudlandboy/kityminder-editor/38425bb672d6c8dd160ec60248c173e57de0530c/docs/kityminder-editor/images/icons.png -------------------------------------------------------------------------------- /docs/kityminder-editor/images/template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudlandboy/kityminder-editor/38425bb672d6c8dd160ec60248c173e57de0530c/docs/kityminder-editor/images/template.png -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudlandboy/kityminder-editor/38425bb672d6c8dd160ec60248c173e57de0530c/favicon.ico -------------------------------------------------------------------------------- /less/_navigator.less: -------------------------------------------------------------------------------- 1 | .nav-bar { 2 | opacity: 0.5; 3 | position: absolute; 4 | width: 35px; 5 | height: 240px; 6 | padding: 5px 0; 7 | left: 10px; 8 | bottom: -210px; 9 | background: #fc8383; 10 | color: #fff; 11 | border-radius: 4px; 12 | z-index: 10; 13 | box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.2); 14 | transition: -webkit-transform .7s 0.1s ease; 15 | transition: transform .7s 0.1s ease; 16 | transition: bottom 0.8s; 17 | .nav-btn { 18 | width: 35px; 19 | height: 24px; 20 | line-height: 24px; 21 | text-align: center; 22 | 23 | .icon { 24 | background: url(images/icons.png); 25 | width: 20px; 26 | height: 20px; 27 | margin: 2px auto; 28 | display: block; 29 | } 30 | 31 | &.active { 32 | background-color: #5A6378; 33 | } 34 | } 35 | 36 | .zoom-in .icon { 37 | background-position: 0 -730px; 38 | } 39 | 40 | .zoom-out .icon { 41 | background-position: 0 -750px; 42 | } 43 | 44 | .hand .icon { 45 | background-position: 0 -770px; 46 | width: 25px; 47 | height: 25px; 48 | margin: 0 auto; 49 | } 50 | 51 | .camera .icon { 52 | background-position: 0 -870px; 53 | width: 25px; 54 | height: 25px; 55 | margin: 0 auto; 56 | } 57 | 58 | .nav-trigger .icon { 59 | background-position: 0 -845px; 60 | width: 25px; 61 | height: 25px; 62 | margin: 0 auto; 63 | } 64 | 65 | .zoom-pan { 66 | width: 2px; 67 | height: 70px; 68 | box-shadow: 0 1px #E50000; 69 | position: relative; 70 | background: white; 71 | margin: 3px auto; 72 | overflow: visible; 73 | 74 | .origin { 75 | position: absolute; 76 | width: 20px; 77 | height: 8px; 78 | left: -9px; 79 | margin-top: -4px; 80 | background: transparent; 81 | 82 | &:after { 83 | content: ' '; 84 | display: block; 85 | width: 6px; 86 | height: 2px; 87 | background: white; 88 | left: 7px; 89 | top: 3px; 90 | position: absolute; 91 | } 92 | } 93 | 94 | .indicator { 95 | position: absolute; 96 | width: 8px; 97 | height: 8px; 98 | left: -3px; 99 | background: white; 100 | border-radius: 100%; 101 | margin-top: -4px; 102 | } 103 | 104 | } 105 | } 106 | .nav-bar:hover{ 107 | opacity: 1; 108 | bottom: 10px; 109 | } 110 | 111 | .nav-previewer { 112 | background: #fff; 113 | width: 140px; 114 | height: 120px; 115 | position: absolute; 116 | left: 45px; 117 | bottom: 30px; 118 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.2); 119 | border-radius: 0 2px 2px 0; 120 | padding: 1px; 121 | z-index: 9; 122 | cursor: crosshair; 123 | transition: -webkit-transform .7s 0.1s ease; 124 | transition: transform .7s 0.1s ease; 125 | 126 | &.grab { 127 | cursor: move; 128 | cursor: -webkit-grabbing; 129 | cursor: -moz-grabbing; 130 | cursor: grabbing; 131 | } 132 | } -------------------------------------------------------------------------------- /less/_tool_group.less: -------------------------------------------------------------------------------- 1 | .tool-group { 2 | padding: 0; 3 | 4 | &[disabled] { 5 | opacity: 0.5; 6 | } 7 | 8 | .tool-group-item { 9 | display: inline-block; 10 | border-radius: 4px; 11 | 12 | .tool-group-icon { 13 | width: 20px; 14 | height: 20px; 15 | padding: 2px; 16 | margin: 1px; 17 | } 18 | 19 | &:hover {background-color: @button-hover;} 20 | &:active {background-color: @button-active;} 21 | 22 | &.active {background-color: @button-active;} 23 | } 24 | } -------------------------------------------------------------------------------- /less/_vars.less: -------------------------------------------------------------------------------- 1 | @button-hover: hsl(222, 55%, 96%); 2 | @button-active: hsl(222, 55%, 85%); 3 | @button-pressed: hsl(222, 55%, 90%); 4 | 5 | @tool-hover: #eff3fa; 6 | @tool-active: #c4d0ee; 7 | @tool-selected: #87a9da; -------------------------------------------------------------------------------- /less/editor.less: -------------------------------------------------------------------------------- 1 | .km-editor { 2 | overflow: hidden; 3 | z-index: 2; 4 | } 5 | 6 | .km-editor > .mask { 7 | display: block; 8 | position: absolute; 9 | left: 0; 10 | right: 0; 11 | top: 0; 12 | bottom: 0; 13 | background-color: transparent; 14 | } 15 | 16 | .km-editor > .receiver { 17 | position: absolute; 18 | background: white; 19 | outline: none; 20 | box-shadow: 0 0 20px fadeout(black, 50%); 21 | left: 0; 22 | top: 0; 23 | padding: 3px 5px; 24 | margin-left: -3px; 25 | margin-top: -5px; 26 | max-width: 300px; 27 | width: auto; 28 | overflow: hidden; 29 | font-size: 14px; 30 | line-height: 1.4em; 31 | min-height: 1.4em; 32 | box-sizing: border-box; 33 | overflow: hidden; 34 | word-break: break-all; 35 | word-wrap: break-word; 36 | border: none; 37 | -webkit-user-select: text; 38 | pointer-events: none; 39 | opacity: 0; 40 | z-index: -1000; 41 | &.debug { 42 | opacity: 1; 43 | outline: 1px solid green; 44 | background: none; 45 | z-index: 0; 46 | } 47 | 48 | &.input { 49 | pointer-events: all; 50 | opacity: 1; 51 | z-index: 999; 52 | background: white; 53 | outline: none; 54 | } 55 | } 56 | 57 | div.minder-editor-container { 58 | position: absolute; 59 | top: 0px; 60 | bottom: 0; 61 | left: 0; 62 | right: 0; 63 | font-family: Arial, "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif; 64 | } 65 | 66 | .minder-editor { 67 | position: absolute; 68 | top: 92px; 69 | left: 0; 70 | right: 0; 71 | bottom: 0; 72 | } 73 | 74 | .minder-viewer { 75 | position: absolute; 76 | top: 0; 77 | left: 0; 78 | right: 0; 79 | bottom: 0; 80 | } 81 | 82 | 83 | .control-panel { 84 | position: absolute; 85 | top: 0; 86 | right: 0; 87 | width: 250px; 88 | bottom: 0; 89 | border-left: 1px solid #CCC; 90 | } 91 | .minder-divider { 92 | position: absolute; 93 | top: 0; 94 | right: 250px; 95 | bottom: 0; 96 | width: 2px; 97 | background-color: rgb(251, 251, 251); 98 | cursor: ew-resize; 99 | } 100 | 101 | // @override bootstrap 102 | .panel-body { 103 | padding: 10px; 104 | } 105 | 106 | @import (less) "_vars.less"; 107 | @import (less) "imageDialog.less"; 108 | @import (less) "topTab/topTab.less"; 109 | @import (less) "topTab/idea/undoRedo.less"; 110 | @import (less) "topTab/idea/appendNode.less"; 111 | @import (less) "topTab/idea/arrange.less"; 112 | @import (less) "topTab/idea/operation.less"; 113 | @import (less) "topTab/idea/hyperlink.less"; 114 | @import (less) "topTab/idea/image.less"; 115 | @import (less) "topTab/idea/note.less"; 116 | @import (less) "topTab/idea/noteEditor.less"; 117 | @import (less) "topTab/idea/priority.less"; 118 | @import (less) "topTab/idea/progress.less"; 119 | @import (less) "topTab/idea/resource.less"; 120 | @import (less) "topTab/appearance/templatePanel.less"; 121 | @import (less) "topTab/appearance/themePanel.less"; 122 | @import (less) "topTab/appearance/layout.less"; 123 | @import (less) "topTab/appearance/styleOperator.less"; 124 | @import (less) "topTab/appearance/fontOperator.less"; 125 | @import (less) "topTab/appearance/colorPanel.less"; 126 | @import (less) "topTab/view/expand.less"; 127 | @import (less) "topTab/view/select.less"; 128 | @import (less) "topTab/view/search.less"; 129 | @import (less) "topTab/other/other.less"; 130 | @import (less) "topTab/searchBox.less"; 131 | @import (less) "_tool_group.less"; 132 | @import (less) "_navigator.less"; 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /less/imageDialog.less: -------------------------------------------------------------------------------- 1 | .upload-image { 2 | width: 0.1px; 3 | height: 0.1px; 4 | opacity: 0; 5 | overflow: hidden; 6 | position: absolute; 7 | z-index: -1; 8 | } -------------------------------------------------------------------------------- /less/topTab/appearance/colorPanel.less: -------------------------------------------------------------------------------- 1 | .bg-color-wrap { 2 | display: inline-block; 3 | width: 30px; 4 | height: 22px; 5 | margin: 3px 3px 0 0; 6 | border: 1px #efefef solid; 7 | vertical-align: middle; 8 | font-size: 0; 9 | -webkit-user-select: none; 10 | -moz-user-select: none; 11 | -ms-user-select: none; 12 | user-select: none; 13 | 14 | &[disabled] { 15 | opacity: 0.5; 16 | } 17 | 18 | .quick-bg-color { 19 | display: inline-block; 20 | width: 20px; 21 | height: 16px; 22 | font-size: 14px; 23 | line-height: 16px; 24 | vertical-align: top; 25 | text-align: center; 26 | cursor: default; 27 | color: #000; 28 | background: url(images/icons.png) no-repeat center -1260px; 29 | 30 | &:hover { 31 | background-color: @tool-hover; 32 | } 33 | 34 | &:active { 35 | background-color: @tool-active; 36 | } 37 | 38 | &[disabled] { 39 | opacity: 0.5; 40 | } 41 | } 42 | 43 | .bg-color-preview { 44 | display: inline-block; 45 | width: 12px; 46 | height: 2px; 47 | margin: 0 4px 0; 48 | background-color: #fff; 49 | 50 | &[disabled] { 51 | opacity: 0.5; 52 | } 53 | } 54 | } 55 | 56 | .bg-color { 57 | display: inline-block; 58 | width: 8px; 59 | height: 16px; 60 | 61 | &:hover { 62 | background-color: @tool-hover; 63 | } 64 | 65 | &:active { 66 | background-color: @tool-active; 67 | } 68 | 69 | &[disabled] { 70 | opacity: 0.5; 71 | } 72 | 73 | .caret { 74 | margin-left: -2px; 75 | margin-top: 7px; 76 | } 77 | } -------------------------------------------------------------------------------- /less/topTab/appearance/fontOperator.less: -------------------------------------------------------------------------------- 1 | .font-operator { 2 | width: 170px; 3 | display: inline-block; 4 | vertical-align: middle; 5 | font-size: 12px; 6 | padding: 0 5px; 7 | 8 | .font-size-list { 9 | display: inline-block; 10 | border: 1px solid #eee; 11 | padding: 2px 4px; 12 | } 13 | 14 | .font-family-list { 15 | display: inline-block; 16 | border: 1px solid #eee; 17 | padding: 2px 4px; 18 | } 19 | 20 | } 21 | 22 | .current-font-item a { 23 | text-decoration: none; 24 | display: inline-block; 25 | } 26 | 27 | .current-font-family { 28 | width: 75px; 29 | height: 18px; 30 | overflow: hidden; 31 | vertical-align: bottom; 32 | } 33 | .current-font-size { 34 | width: 32px; 35 | height: 18px; 36 | overflow: hidden; 37 | vertical-align: bottom; 38 | } 39 | 40 | .current-font-item[disabled] { 41 | opacity: 0.5; 42 | } 43 | 44 | .font-item { 45 | line-height: 1em; 46 | text-align: left; 47 | } 48 | 49 | .font-item-selected { 50 | background-color: @tool-selected; 51 | } 52 | 53 | .font-bold, .font-italics { 54 | display: inline-block; 55 | background: url(images/icons.png) no-repeat; 56 | cursor: pointer; 57 | margin: 0 3px; 58 | 59 | 60 | &:hover { 61 | background-color: @tool-hover; 62 | } 63 | 64 | &:active { 65 | background-color: @tool-active; 66 | } 67 | 68 | &[disabled] { 69 | opacity: 0.5; 70 | } 71 | } 72 | 73 | .font-bold { 74 | background-position: 0 -240px; 75 | } 76 | 77 | .font-italics { 78 | background-position: 0 -260px; 79 | } 80 | 81 | .font-bold-selected, .font-italics-selected { 82 | background-color: @tool-selected; 83 | } 84 | 85 | .font-color-wrap { 86 | display: inline-block; 87 | width: 30px; 88 | height: 22px; 89 | margin: 3px 3px 0 0; 90 | border: 1px #efefef solid; 91 | vertical-align: middle; 92 | font-size: 0; 93 | -webkit-user-select: none; 94 | -moz-user-select: none; 95 | -ms-user-select: none; 96 | user-select: none; 97 | 98 | &[disabled] { 99 | opacity: 0.5; 100 | } 101 | 102 | .quick-font-color { 103 | display: inline-block; 104 | width: 20px; 105 | height: 16px; 106 | font-size: 14px; 107 | line-height: 16px; 108 | vertical-align: top; 109 | text-align: center; 110 | cursor: default; 111 | color: #000; 112 | 113 | &:hover { 114 | background-color: @tool-hover; 115 | } 116 | 117 | &:active { 118 | background-color: @tool-active; 119 | } 120 | 121 | &[disabled] { 122 | opacity: 0.5; 123 | } 124 | } 125 | 126 | .font-color-preview { 127 | display: inline-block; 128 | width: 12px; 129 | height: 2px; 130 | margin: 0 4px 0; 131 | background-color: #000; 132 | 133 | &[disabled] { 134 | opacity: 0.5; 135 | } 136 | } 137 | } 138 | 139 | .font-color { 140 | display: inline-block; 141 | width: 8px; 142 | height: 16px; 143 | 144 | &:hover { 145 | background-color: @tool-hover; 146 | } 147 | 148 | &:active { 149 | background-color: @tool-active; 150 | } 151 | 152 | &[disabled] { 153 | opacity: 0.5; 154 | } 155 | 156 | .caret { 157 | margin-left: -2px; 158 | margin-top: 7px; 159 | } 160 | } -------------------------------------------------------------------------------- /less/topTab/appearance/layout.less: -------------------------------------------------------------------------------- 1 | .readjust-layout { 2 | display: inline-block; 3 | vertical-align: middle; 4 | padding: 0 10px 0 5px; 5 | border-right: 1px dashed #eee; 6 | } 7 | 8 | .btn-icon { 9 | width: 25px; 10 | height: 25px; 11 | margin-left: 12px; 12 | display: block; 13 | } 14 | 15 | .btn-label { 16 | font-size: 12px; 17 | } 18 | 19 | .btn-wrap { 20 | width: 50px; 21 | height: 42px; 22 | cursor: pointer; 23 | display: inline-block; 24 | text-decoration: none; 25 | 26 | &[disabled] span { 27 | opacity: 0.5; 28 | } 29 | 30 | &[disabled] { 31 | cursor: default; 32 | } 33 | 34 | &[disabled]:hover { 35 | background-color: transparent; 36 | } 37 | 38 | &[disabled]:active { 39 | background-color: transparent; 40 | } 41 | 42 | &:link { 43 | text-decoration: none; 44 | } 45 | 46 | &:visited { 47 | text-decoration: none; 48 | } 49 | 50 | &:hover { 51 | background-color: @tool-hover; 52 | text-decoration: none; 53 | } 54 | 55 | &:active { 56 | background-color: @tool-active; 57 | } 58 | 59 | } 60 | 61 | .reset-layout-icon { 62 | background: url(images/icons.png) no-repeat; 63 | background-position: 0 -150px; 64 | } 65 | 66 | -------------------------------------------------------------------------------- /less/topTab/appearance/styleOperator.less: -------------------------------------------------------------------------------- 1 | .style-operator { 2 | display: inline-block; 3 | vertical-align: middle; 4 | padding: 0 5px; 5 | border-right: 1px dashed #eee; 6 | 7 | .clear-style { 8 | vertical-align: middle; 9 | } 10 | 11 | } 12 | 13 | .clear-style-icon { 14 | background: url(images/icons.png) no-repeat; 15 | background-position: 0 -175px;; 16 | } 17 | 18 | .s-btn-group-vertical { 19 | display: inline-block; 20 | vertical-align: middle; 21 | } 22 | 23 | .s-btn-icon { 24 | width: 20px; 25 | height: 20px; 26 | margin-right: 3px; 27 | display: inline-block; 28 | vertical-align: middle; 29 | } 30 | 31 | .s-btn-label { 32 | font-size: 12px; 33 | vertical-align: middle; 34 | display: inline-block; 35 | } 36 | 37 | .s-btn-wrap { 38 | // margin-bottom: 2px; 39 | padding: 0 5px 0 3px; 40 | display: inline-block; 41 | text-decoration: none; 42 | font-size: 0; 43 | 44 | &[disabled] span { 45 | opacity: 0.5; 46 | } 47 | 48 | &[disabled] { 49 | cursor: default; 50 | } 51 | 52 | &[disabled]:hover { 53 | background-color: transparent; 54 | } 55 | 56 | &[disabled]:active { 57 | background-color: transparent; 58 | } 59 | 60 | &:hover { 61 | background-color: @tool-hover; 62 | text-decoration: none; 63 | } 64 | 65 | &:active { 66 | background-color: @tool-active; 67 | } 68 | 69 | } 70 | 71 | .copy-style-icon { 72 | background: url(images/icons.png) no-repeat; 73 | background-position: 0 -200px; 74 | } 75 | 76 | .paste-style-wrap { 77 | display: block; 78 | } 79 | 80 | .paste-style-icon { 81 | background: url(images/icons.png) no-repeat; 82 | background-position: 0 -220px; 83 | } -------------------------------------------------------------------------------- /less/topTab/appearance/templatePanel.less: -------------------------------------------------------------------------------- 1 | .temp-panel { 2 | margin: 5px 5px 5px 10px; 3 | border-right: 1px dashed #eee; 4 | display: inline-block; 5 | vertical-align: middle; 6 | } 7 | 8 | .temp-list { 9 | min-width: 124px; 10 | } 11 | 12 | .temp-item-wrap { 13 | width: 50px; 14 | height: 40px; 15 | padding: 0 2px; 16 | margin: 5px; 17 | display: inline-block; 18 | } 19 | 20 | .temp-item { 21 | display: inline-block; 22 | width: 50px; 23 | height: 40px; 24 | background-image: url(images/template.png); 25 | background-repeat: no-repeat; 26 | 27 | &.default { 28 | background-position: 0 0; 29 | } 30 | 31 | &.structure { 32 | background-position: -50px 0; 33 | } 34 | 35 | &.filetree { 36 | background-position: -100px 0; 37 | } 38 | 39 | &.right { 40 | background-position: -150px 0; 41 | } 42 | 43 | &.fish-bone { 44 | background-position: -200px 0; 45 | } 46 | 47 | &.tianpan { 48 | background-position: -250px 0; 49 | } 50 | } 51 | 52 | .current-temp-item { 53 | width: 74px; 54 | padding: 0 0 0 5px; 55 | border: 1px solid #fff; 56 | 57 | &:hover { 58 | background-color: @tool-hover; 59 | } 60 | 61 | &[disabled] { 62 | opacity: 0.5; 63 | } 64 | 65 | .caret { 66 | margin-left: 5px; 67 | } 68 | } 69 | .temp-item-selected { 70 | background-color: @tool-selected; 71 | } -------------------------------------------------------------------------------- /less/topTab/appearance/themePanel.less: -------------------------------------------------------------------------------- 1 | .theme-panel { 2 | height: 42px; 3 | margin: 5px; 4 | padding: 0 5px 0 0; 5 | border-right: 1px dashed #eee; 6 | display: inline-block; 7 | vertical-align: middle; 8 | } 9 | 10 | .theme-list { 11 | min-width: 162px; 12 | } 13 | 14 | div a.theme-item { 15 | display: inline-block; 16 | width: 70px; 17 | height: 30px; 18 | text-align: center; 19 | line-height: 30px; 20 | padding: 0 5px; 21 | font-size: 12px; 22 | cursor: pointer; 23 | text-decoration: none; 24 | color: #000; 25 | } 26 | 27 | .theme-item-selected { 28 | width: 100px; 29 | padding: 6px 7px; 30 | border: 1px solid #fff; 31 | 32 | &:hover { 33 | background-color: @tool-hover; 34 | } 35 | 36 | .caret { 37 | margin-left: 5px; 38 | } 39 | 40 | &[disabled] { 41 | opacity: 0.5; 42 | } 43 | } 44 | 45 | .theme-item-wrap { 46 | display: inline-block; 47 | width: 80px; 48 | height: 40px; 49 | padding: 5px; 50 | } 51 | .theme-item-wrap:hover { 52 | background-color: #eff3fa; 53 | } -------------------------------------------------------------------------------- /less/topTab/idea/appendNode.less: -------------------------------------------------------------------------------- 1 | .append-group { 2 | width: 212px; 3 | } 4 | 5 | .append-child-node { 6 | .km-btn-icon { 7 | background-position: 0 0; 8 | } 9 | } 10 | 11 | .append-sibling-node { 12 | .km-btn-icon { 13 | background-position: 0 -20px; 14 | } 15 | } 16 | 17 | .append-parent-node { 18 | .km-btn-icon { 19 | background-position: 0 -40px; 20 | } 21 | } -------------------------------------------------------------------------------- /less/topTab/idea/arrange.less: -------------------------------------------------------------------------------- 1 | .arrange-group { 2 | width: 64px; 3 | } 4 | 5 | .arrange-up { 6 | .km-btn-icon { 7 | background-position: 0 -280px; 8 | } 9 | } 10 | 11 | .arrange-down { 12 | .km-btn-icon { 13 | background-position: 0 -300px; 14 | } 15 | } -------------------------------------------------------------------------------- /less/topTab/idea/hyperlink.less: -------------------------------------------------------------------------------- 1 | .btn-group-vertical { 2 | vertical-align: middle; 3 | margin: 5px; 4 | 5 | .hyperlink, .hyperlink-caption { 6 | width: 40px; 7 | margin: 0; 8 | padding: 0; 9 | border: none!important; 10 | border-radius: 0!important; 11 | 12 | &:hover { 13 | background-color: @tool-hover; 14 | } 15 | 16 | &:active { 17 | background-color: @tool-active; 18 | } 19 | 20 | &.active { 21 | box-shadow: none; 22 | background-color: @tool-hover; 23 | } 24 | } 25 | 26 | .hyperlink { 27 | height: 25px; 28 | background: url(images/icons.png) no-repeat center -100px; 29 | } 30 | 31 | .hyperlink-caption { 32 | height: 20px; 33 | 34 | .caption { 35 | font-size: 12px; 36 | } 37 | } 38 | } 39 | 40 | //override bootstrap 41 | .open > .dropdown-toggle.btn-default { 42 | background-color: @tool-hover; 43 | } 44 | -------------------------------------------------------------------------------- /less/topTab/idea/image.less: -------------------------------------------------------------------------------- 1 | .btn-group-vertical { 2 | 3 | .image-btn, .image-btn-caption { 4 | width: 40px; 5 | margin: 0; 6 | padding: 0; 7 | border: none!important; 8 | border-radius: 0!important; 9 | 10 | &:hover { 11 | background-color: @tool-hover; 12 | } 13 | 14 | &:active { 15 | background-color: @tool-active; 16 | } 17 | 18 | &.active { 19 | box-shadow: none; 20 | background-color: @tool-hover; 21 | } 22 | } 23 | 24 | .image-btn { 25 | height: 25px; 26 | background: url(images/icons.png) no-repeat center -125px; 27 | } 28 | 29 | .image-btn-caption { 30 | height: 20px; 31 | 32 | .caption { 33 | font-size: 12px; 34 | } 35 | } 36 | } 37 | 38 | .image-preview { 39 | display: block; 40 | max-width: 50%; 41 | } 42 | 43 | .modal-body { 44 | .tab-pane { 45 | font-size: inherit; 46 | padding-top: 15px; 47 | } 48 | } 49 | 50 | .search-result { 51 | margin-top: 15px; 52 | height: 370px; 53 | overflow: hidden; 54 | 55 | ul { 56 | margin: 0; 57 | padding: 0; 58 | list-style: none; 59 | clear: both; 60 | height: 100%; 61 | overflow-x: hidden; 62 | overflow-y: auto; 63 | 64 | li { 65 | list-style: none; 66 | float: left; 67 | display: block; 68 | width: 130px; 69 | height: 130px; 70 | line-height: 130px; 71 | margin: 6px; 72 | padding: 0; 73 | font-size: 12px; 74 | position: relative; 75 | vertical-align: top; 76 | text-align: center; 77 | overflow: hidden; 78 | cursor: pointer; 79 | border: 2px solid #fcfcfc; 80 | 81 | &.selected { 82 | border: 2px solid #fc8383; 83 | } 84 | 85 | 86 | img { 87 | max-width: 126px; 88 | max-height: 130px; 89 | vertical-align: middle; 90 | } 91 | 92 | span { 93 | display: block; 94 | position: absolute; 95 | bottom: 0; 96 | height: 20px; 97 | background: rgba(0, 0, 0, 0.5); 98 | left: 0; 99 | right: 0; 100 | color: white; 101 | line-height: 20px; 102 | overflow: hidden; 103 | text-overflow: ellipsis; 104 | word-break: break-all; 105 | white-space: nowrap; 106 | opacity: 0; 107 | -webkit-transform: translate(0, 20px); 108 | -ms-transform: translate(0, 20px); 109 | transform: translate(0, 20px); 110 | -webkit-transition: all .2s ease; 111 | transition: all .2s ease; 112 | } 113 | } 114 | 115 | li:hover span { 116 | opacity: 1; 117 | -webkit-transform: translate(0, 0); 118 | -ms-transform: translate(0, 0); 119 | transform: translate(0, 0); 120 | } 121 | } 122 | } 123 | 124 | // 覆盖 bootstrap 样式 125 | @media (min-width: 768px){ 126 | .form-inline .form-control { 127 | width: 380px; 128 | } 129 | } -------------------------------------------------------------------------------- /less/topTab/idea/note.less: -------------------------------------------------------------------------------- 1 | .btn-group-vertical { 2 | vertical-align: top; 3 | margin: 5px; 4 | 5 | &.note-btn-group { 6 | border-right: 1px dashed #eee; 7 | padding-right: 5px; 8 | } 9 | 10 | .note-btn, .note-btn-caption { 11 | width: 40px; 12 | margin: 0; 13 | padding: 0; 14 | border: none!important; 15 | border-radius: 0!important; 16 | 17 | &:hover { 18 | background-color: @tool-hover; 19 | } 20 | 21 | &:active { 22 | background-color: @tool-active; 23 | } 24 | 25 | &.active { 26 | box-shadow: none; 27 | background-color: @tool-hover; 28 | } 29 | } 30 | 31 | .note-btn { 32 | height: 25px; 33 | background: url(images/icons.png) no-repeat center -1150px; 34 | } 35 | 36 | .note-btn-caption { 37 | height: 20px; 38 | 39 | .caption { 40 | font-size: 12px; 41 | } 42 | } 43 | } 44 | 45 | //override bootstrap 46 | .open > .dropdown-toggle.btn-default { 47 | background-color: @tool-hover; 48 | } 49 | -------------------------------------------------------------------------------- /less/topTab/idea/noteEditor.less: -------------------------------------------------------------------------------- 1 | .gfm-render { 2 | 3 | font-size: 12px; 4 | -webkit-user-select: text; 5 | color: #333; 6 | line-height: 1.8em; 7 | 8 | blockquote, ul, table, p, pre, hr { 9 | margin: 1em 0; 10 | cursor: text; 11 | &:first-child:last-child { 12 | margin: 0; 13 | } 14 | } 15 | 16 | img { 17 | max-width: 100%; 18 | } 19 | 20 | a { 21 | color: blue; 22 | &:hover { 23 | color: red; 24 | } 25 | } 26 | 27 | blockquote { 28 | display: block; 29 | border-left: 4px solid #E4AD91; 30 | color: darken(#E4AD91, 10%); 31 | padding-left: 10px; 32 | font-style: italic; 33 | margin-left: 2em; 34 | } 35 | 36 | ul, ol { 37 | padding-left: 3em; 38 | } 39 | 40 | table { 41 | width: 100%; 42 | border-collapse: collapse; 43 | th, td { 44 | border: 1px solid #666; 45 | padding: 2px 4px; 46 | } 47 | th { 48 | background: rgba(45, 141, 234, 0.2); 49 | } 50 | tr:nth-child(even) td { 51 | background: rgba(45, 141, 234, 0.03); 52 | } 53 | margin: 1em 0; 54 | } 55 | 56 | em { 57 | color: red; 58 | } 59 | 60 | del { 61 | color: #999; 62 | } 63 | 64 | pre { 65 | background: rgba(45, 141, 234, 0.1); 66 | padding: 5px; 67 | border-radius: 5px; 68 | word-break: break-all; 69 | word-wrap: break-word; 70 | } 71 | 72 | code { 73 | background: rgba(45, 141, 234, 0.1); 74 | /* display: inline-block; */ 75 | padding: 0 5px; 76 | border-radius: 3px; 77 | } 78 | 79 | pre code { 80 | background: none; 81 | } 82 | 83 | hr { 84 | border: none; 85 | border-top: 1px solid #CCC; 86 | } 87 | 88 | .highlight { 89 | background: yellow; 90 | color: red; 91 | } 92 | } 93 | 94 | .km-note { 95 | width: 300px; 96 | border-left: 1px solid #babfcd; 97 | padding: 5px 10px; 98 | background: white; 99 | position: absolute; 100 | top: 92px; 101 | right: 0; 102 | bottom: 0; 103 | left: auto; 104 | z-index: 3; 105 | 106 | &.panel { 107 | margin: 0; 108 | padding: 0; 109 | 110 | .panel-heading { 111 | 112 | h3 { 113 | display: inline-block; 114 | } 115 | 116 | .close-note-editor { 117 | width: 15px; 118 | height: 15px; 119 | display: inline-block; 120 | float: right; 121 | 122 | &:hover { 123 | cursor: pointer; 124 | } 125 | } 126 | } 127 | 128 | .panel-body { 129 | padding: 0; 130 | } 131 | } 132 | 133 | .CodeMirror { 134 | position: absolute; 135 | top: 41px; 136 | bottom: 0; 137 | height: auto; 138 | cursor: text; 139 | font-size: 14px; 140 | line-height: 1.3em; 141 | font-family: consolas; 142 | } 143 | } 144 | .km-note-tips { 145 | color: #ccc; 146 | padding: 3px 8px; 147 | } 148 | #previewer-content { 149 | position: absolute; 150 | background: #FFD; 151 | padding: 5px 15px; 152 | border-radius: 5px; 153 | max-width: 400px; 154 | max-height: 200px; 155 | overflow: auto; 156 | z-index: 10; 157 | box-shadow: 0 0 15px rgba(0, 0, 0, .5); 158 | word-break: break-all; 159 | .gfm-render; 160 | } 161 | #previewer-content.ng-hide { 162 | display: block!important; 163 | left: -99999px!important; 164 | top: -99999px!important; 165 | } 166 | .panel-body { 167 | padding: 10px; 168 | } -------------------------------------------------------------------------------- /less/topTab/idea/operation.less: -------------------------------------------------------------------------------- 1 | .operation-group { 2 | width: 64px; 3 | } 4 | 5 | .edit-node { 6 | .km-btn-icon { 7 | background-position: 0 -60px; 8 | } 9 | } 10 | 11 | .remove-node { 12 | .km-btn-icon { 13 | background-position: 0 -80px; 14 | } 15 | } -------------------------------------------------------------------------------- /less/topTab/idea/priority.less: -------------------------------------------------------------------------------- 1 | .priority-sprite(@count) when (@count >= 0) { 2 | .priority-sprite(@count - 1); 3 | &.priority-@{count} { 4 | background-position: 0 (-20px * (@count - 1)); 5 | } 6 | } 7 | 8 | .tab-content .km-priority { 9 | vertical-align: middle; 10 | font-size: inherit; 11 | display: inline-block; 12 | width: 140px; 13 | margin: 5px; 14 | border-right: 1px dashed #eee; 15 | 16 | .km-priority-item { 17 | margin: 0 1px; 18 | padding: 1px; 19 | 20 | .km-priority-icon { 21 | .priority-sprite(9); 22 | background: url(images/iconpriority.png) repeat-y; 23 | background-color: transparent; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /less/topTab/idea/progress.less: -------------------------------------------------------------------------------- 1 | .progress-sprite(@count) when (@count >= 0) { 2 | .progress-sprite(@count - 1); 3 | &.progress-@{count} { 4 | background-position: 0 (-20px * (@count - 1)); 5 | } 6 | } 7 | 8 | .tab-content .km-progress { 9 | vertical-align: middle; 10 | font-size: inherit; 11 | display: inline-block; 12 | width: 140px; 13 | margin: 5px; 14 | border-right: 1px dashed #eee; 15 | 16 | .km-progress-item { 17 | margin: 0 1px; 18 | padding: 1px; 19 | 20 | .km-progress-icon { 21 | .progress-sprite(9); 22 | background: url(images/iconprogress.png) repeat-y; 23 | background-color: transparent; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /less/topTab/idea/resource.less: -------------------------------------------------------------------------------- 1 | .resource-editor { 2 | vertical-align: middle; 3 | display: inline-block; 4 | margin: 5px; 5 | 6 | .input-group, .km-resource { 7 | font-size: 12px; 8 | } 9 | 10 | .input-group { 11 | height: 20px; 12 | width: 168px; 13 | } 14 | 15 | .resource-dropdown { 16 | position: relative; 17 | width: 168px; 18 | border: 1px solid #ccc; 19 | margin-top: -1px; 20 | border-bottom-right-radius: 4px; 21 | border-bottom-left-radius: 4px; 22 | 23 | .km-resource { 24 | position: absolute; 25 | width: 154px; 26 | margin-bottom: 3px; 27 | padding: 0; 28 | list-style-type: none; 29 | overflow: scroll; 30 | max-height: 500px; 31 | 32 | &.open { 33 | z-index: 3; 34 | background-color: #fff; 35 | } 36 | 37 | li { 38 | display: inline-block; 39 | padding: 1px 2px; 40 | border-radius: 4px; 41 | margin: 2px 3px; 42 | 43 | &[disabled] { 44 | opacity: 0.5; 45 | } 46 | } 47 | } 48 | 49 | .resource-caret { 50 | display: block; 51 | float: right; 52 | vertical-align: middle; 53 | width: 12px; 54 | height: 24px; 55 | padding: 8px 1px; 56 | 57 | &:hover {background-color: @button-hover;} 58 | &:active {background-color: @button-active;} 59 | } 60 | } 61 | 62 | // 覆盖 bootstrap 63 | input.form-control, .btn { 64 | font-size: 12px; 65 | } 66 | 67 | input.form-control { 68 | padding: 2px 4px; 69 | height: 24px; 70 | border-bottom-left-radius: 0; 71 | } 72 | 73 | .input-group-btn { 74 | line-height: 24px; 75 | 76 | .btn { 77 | padding: 2px 4px; 78 | height: 24px; 79 | border-bottom-right-radius: 0; 80 | } 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /less/topTab/idea/undoRedo.less: -------------------------------------------------------------------------------- 1 | .do-group { 2 | width: 38px; 3 | } 4 | 5 | .undo { 6 | .km-btn-icon { 7 | background-position: 0 -1240px; 8 | } 9 | } 10 | 11 | .redo { 12 | .km-btn-icon { 13 | background-position: 0 -1220px; 14 | } 15 | } -------------------------------------------------------------------------------- /less/topTab/other/other.less: -------------------------------------------------------------------------------- 1 | .other-btn{ 2 | button{ 3 | margin:0 6px; 4 | } 5 | } 6 | #toast-container{ 7 | top: 10px; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /less/topTab/searchBox.less: -------------------------------------------------------------------------------- 1 | .search-box { 2 | float: right; 3 | background-color: #fff; 4 | border: 1px solid #dbdbdb; 5 | position: relative; 6 | top: 0; 7 | z-index: 3; 8 | width: 360px; 9 | height: 40px; 10 | padding: 3px 6px; 11 | opacity: 1; 12 | 13 | .search-input-wrap, .prev-and-next-btn { 14 | float: left; 15 | } 16 | 17 | .close-search { 18 | float: right; 19 | height: 16px; 20 | width: 16px; 21 | padding: 1px; 22 | border-radius: 100%; 23 | margin-top: 6px; 24 | margin-right: 10px; 25 | 26 | .glyphicon { 27 | top: -1px 28 | } 29 | 30 | &:hover { 31 | background-color: #efefef; 32 | } 33 | 34 | &:active { 35 | background-color: #999; 36 | } 37 | } 38 | 39 | .search-input-wrap { 40 | width: 240px; 41 | } 42 | 43 | .prev-and-next-btn { 44 | margin-left: 5px; 45 | 46 | .btn:focus { 47 | outline: none; 48 | } 49 | } 50 | 51 | .search-input { 52 | //border-right: none; 53 | } 54 | 55 | .search-addon { 56 | background-color: #fff; 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /less/topTab/topTab.less: -------------------------------------------------------------------------------- 1 | .top-tab { 2 | 3 | .nav-tabs { 4 | background-color: #e1e1e1; 5 | border: 0; 6 | height: 32px; 7 | 8 | li { 9 | margin: 0; 10 | 11 | a { 12 | margin: 0; 13 | border: 0; 14 | padding: 6px 15px; 15 | border-radius: 0; 16 | vertical-align: middle; 17 | 18 | 19 | &:hover, &:focus { 20 | background: inherit; 21 | border: 0; 22 | } 23 | } 24 | 25 | 26 | &.active a { 27 | border: 0; 28 | background-color: #fff; 29 | 30 | &:hover, &:focus { 31 | border: 0; 32 | } 33 | } 34 | } 35 | } 36 | 37 | .tab-content { 38 | height: 60px; 39 | background-color: #fff; 40 | border-bottom: 1px solid #dbdbdb; 41 | } 42 | 43 | .tab-pane { 44 | font-size: 0; 45 | } 46 | } 47 | 48 | .km-btn-group { 49 | display: inline-block; 50 | margin: 5px 0; 51 | padding: 0 5px; 52 | vertical-align: middle; 53 | border-right: 1px dashed #eee; 54 | } 55 | 56 | .km-btn-item { 57 | display: inline-block; 58 | margin: 0 3px; 59 | font-size: 0; 60 | cursor: default; 61 | 62 | &[disabled] { 63 | opacity: 0.5; 64 | 65 | &:hover, &:active { 66 | background-color: #fff; 67 | } 68 | } 69 | 70 | .km-btn-icon { 71 | display: inline-block; 72 | background: url(images/icons.png) no-repeat; 73 | background-position: 0 20px; 74 | width: 20px; 75 | height: 20px; 76 | padding: 2px; 77 | margin: 1px; 78 | vertical-align: middle; 79 | } 80 | 81 | .km-btn-caption { 82 | display: inline-block; 83 | font-size: 12px; 84 | vertical-align: middle; 85 | } 86 | 87 | &:hover {background-color: @button-hover;} 88 | &:active {background-color: @button-active;} 89 | } -------------------------------------------------------------------------------- /less/topTab/view/expand.less: -------------------------------------------------------------------------------- 1 | .btn-group-vertical { 2 | vertical-align: middle; 3 | margin: 5px; 4 | 5 | .expand, .expand-caption { 6 | width: 40px; 7 | margin: 0; 8 | padding: 0; 9 | border: none!important; 10 | border-radius: 0!important; 11 | 12 | &:hover { 13 | background-color: @tool-hover; 14 | } 15 | 16 | &:active { 17 | background-color: @tool-active; 18 | } 19 | 20 | &.active { 21 | box-shadow: none; 22 | background-color: @tool-hover; 23 | } 24 | } 25 | 26 | .expand { 27 | height: 25px; 28 | background: url(images/icons.png) no-repeat 0 -995px; 29 | background-position-x: 50%; 30 | } 31 | 32 | .expand-caption { 33 | height: 20px; 34 | 35 | .caption { 36 | font-size: 12px; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /less/topTab/view/search.less: -------------------------------------------------------------------------------- 1 | .btn-group-vertical { 2 | vertical-align: middle; 3 | margin: 5px; 4 | 5 | .search, .search-caption { 6 | width: 40px; 7 | margin: 0; 8 | padding: 0; 9 | border: none!important; 10 | border-radius: 0!important; 11 | 12 | &:hover { 13 | background-color: @tool-hover; 14 | } 15 | 16 | &:active { 17 | background-color: @tool-active; 18 | } 19 | 20 | &.active { 21 | box-shadow: none; 22 | background-color: @tool-hover; 23 | } 24 | } 25 | 26 | .search { 27 | height: 25px; 28 | background: url(images/icons.png) no-repeat 0 -345px; 29 | background-position-x: 50%; 30 | } 31 | 32 | .search-caption { 33 | height: 20px; 34 | 35 | .caption { 36 | font-size: 12px; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /less/topTab/view/select.less: -------------------------------------------------------------------------------- 1 | .btn-group-vertical { 2 | vertical-align: middle; 3 | margin: 5px; 4 | 5 | .select, .select-caption { 6 | width: 40px; 7 | margin: 0; 8 | padding: 0; 9 | border: none!important; 10 | border-radius: 0!important; 11 | 12 | &:hover { 13 | background-color: @tool-hover; 14 | } 15 | 16 | &:active { 17 | background-color: @tool-active; 18 | } 19 | 20 | &.active { 21 | box-shadow: none; 22 | background-color: @tool-hover; 23 | } 24 | } 25 | 26 | .select { 27 | height: 25px; 28 | background: url(images/icons.png) no-repeat 7px -1175px; 29 | } 30 | 31 | .select-caption { 32 | height: 20px; 33 | 34 | .caption { 35 | font-size: 12px; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kityminder-editor", 3 | "version": "1.0.67", 4 | "description": "A powerful mind map editor", 5 | "main": "kityminder.editor.js", 6 | "scripts": { 7 | "init": "npm i -g wr && npm install -g less && npm install -g bower && bower install && npm install", 8 | "build": "grunt build", 9 | "dev": "grunt dev", 10 | "watch": "wr --exec \"lessc --source-map less/editor.less dist/kityminder.editor.css && grunt build\" less ui", 11 | "postinstall": "bower install" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/fex-team/kityminder-editor" 16 | }, 17 | "keywords": [ 18 | "kityminder", 19 | "editor", 20 | "html5", 21 | "js", 22 | "mindmap" 23 | ], 24 | "author": "fex ", 25 | "license": "GPL-2.0", 26 | "bugs": { 27 | "url": "https://github.com/fex-team/kityminder-editor/issues" 28 | }, 29 | "homepage": "https://github.com/fex-team/kityminder-editor", 30 | "devDependencies": { 31 | "cz-conventional-changelog": "^1.1.5", 32 | "grunt": "~0.4.1", 33 | "grunt-angular-templates": "~0.5.0", 34 | "grunt-browser-sync": "^2.2.0", 35 | "grunt-contrib-clean": "^0.5.0", 36 | "grunt-contrib-concat": "~0.5.0", 37 | "grunt-contrib-copy": "^0.5.0", 38 | "grunt-contrib-cssmin": "^0.12.0", 39 | "grunt-contrib-less": "^1.0.0", 40 | "grunt-contrib-uglify": "^3.3.0", 41 | "grunt-contrib-watch": "^1.0.0", 42 | "grunt-module-dependence": "~0.2.0", 43 | "grunt-ng-annotate": "^0.9.2", 44 | "grunt-replace": "~0.8.0", 45 | "grunt-wiredep": "^2.0.0", 46 | "jshint-stylish": "^1.0.0", 47 | "load-grunt-tasks": "^3.1.0", 48 | "uglify-js": "^2.8.29" 49 | }, 50 | "dependencies": { 51 | "kityminder-core": "^1.4.50" 52 | }, 53 | "config": { 54 | "commitizen": { 55 | "path": "./node_modules/cz-conventional-changelog" 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /relations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudlandboy/kityminder-editor/38425bb672d6c8dd160ec60248c173e57de0530c/relations.png -------------------------------------------------------------------------------- /server/imageUpload.php: -------------------------------------------------------------------------------- 1 | , msg: <错误信息>, data: {url: <返回的 URL>}}; 7 | * 2. 由于要兼容两种情况的上传:通过对话框选择本地文件上传和直接 Ctrl + V(多见于截图后),因此本文件分别进行了判断 8 | * 9 | * 10 | * 注意: 11 | * 1. 本文件的路径可以进行配置,详见 README.md 中「初始化配置」部分。 12 | * 2. 由于使用场景不同,请根据实际场景编写上传文件的处理。 13 | * 3. 本文件并没有做任何的安全方面的防护,请勿用于生产环境。 14 | * 15 | * @author: zhangbobell 16 | * 17 | * @date: 2016.07.06 18 | * 19 | */ 20 | 21 | // 返回给前端的地址是绝对地址,这里是前缀 22 | $HTTP_PREFIX = 'http://localhost/kityminder-editor/'; 23 | 24 | 25 | $errno = 0; 26 | $msg = 'ok'; 27 | $url = ''; 28 | 29 | 30 | if ((($_FILES["upload_file"]["type"] == "image/gif") 31 | || ($_FILES["upload_file"]["type"] == "image/jpeg") 32 | || ($_FILES["upload_file"]["type"] == "image/jpg") 33 | || ($_FILES["upload_file"]["type"] == "image/png")) 34 | && ($_FILES["upload_file"]["size"] < 1 * 1000 * 1000)) { 35 | 36 | if ($_FILES["upload_file"]["error"] > 0) { 37 | $errno = 414; 38 | $msg = $_FILES["upload_file"]["error"]; 39 | } else { 40 | 41 | // 分为两种情况 `Ctrl + V` 和普通上传 42 | if ($_FILES["upload_file"]["name"] === 'blob') { 43 | $ext_name = 'png'; 44 | } else { 45 | $ext_name = array_pop(explode('.', $_FILES["upload_file"]["name"])); 46 | } 47 | 48 | $sha1_name = sha1_file($_FILES["upload_file"]["tmp_name"]) . '.' . $ext_name; 49 | 50 | move_uploaded_file($_FILES["upload_file"]["tmp_name"], "upload/" . $sha1_name); 51 | $url = $HTTP_PREFIX . "server/upload/" . $sha1_name; 52 | } 53 | } else { 54 | $errno = 416; 55 | $msg = 'File is invalid'; 56 | } 57 | 58 | 59 | $result = array( 60 | 'errno' => $errno, 61 | 'msg' => $msg, 62 | 'data' => array( 63 | 'url' => $url 64 | ) 65 | ); 66 | 67 | echo json_encode($result); -------------------------------------------------------------------------------- /src/editor.js: -------------------------------------------------------------------------------- 1 | define(function (require, exports, module) { 2 | 3 | /** 4 | * 运行时 5 | */ 6 | var runtimes = []; 7 | 8 | function assemble(runtime) { 9 | runtimes.push(runtime); 10 | } 11 | 12 | function KMEditor(selector) { 13 | this.selector = selector; 14 | for (var i = 0; i < runtimes.length; i++) { 15 | if (typeof runtimes[i] == 'function') { 16 | runtimes[i].call(this, this); 17 | } 18 | } 19 | } 20 | 21 | KMEditor.assemble = assemble; 22 | 23 | assemble(require('./runtime/container')); 24 | assemble(require('./runtime/fsm')); 25 | assemble(require('./runtime/minder')); 26 | assemble(require('./runtime/receiver')); 27 | assemble(require('./runtime/hotbox')); 28 | assemble(require('./runtime/input')); 29 | assemble(require('./runtime/clipboard-mimetype')); 30 | assemble(require('./runtime/clipboard')); 31 | assemble(require('./runtime/drag')); 32 | assemble(require('./runtime/node')); 33 | assemble(require('./runtime/history')); 34 | assemble(require('./runtime/jumping')); 35 | assemble(require('./runtime/priority')); 36 | assemble(require('./runtime/progress')); 37 | 38 | return module.exports = KMEditor; 39 | }); -------------------------------------------------------------------------------- /src/expose-editor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 打包暴露 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | define('expose-editor', function(require, exports, module) { 10 | return module.exports = kityminder.Editor = require('./editor'); 11 | }); -------------------------------------------------------------------------------- /src/hotbox.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | return module.exports = window.HotBox; 3 | }); -------------------------------------------------------------------------------- /src/lang.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | 3 | }); -------------------------------------------------------------------------------- /src/minder.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | return module.exports = window.kityminder.Minder; 3 | }); -------------------------------------------------------------------------------- /src/runtime/clipboard-mimetype.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Desc: 新增一个用于处理系统ctrl+c ctrl+v等方式导入导出节点的MIMETYPE处理,如系统不支持clipboardEvent或者是FF则不初始化改class 3 | * @Editor: Naixor 4 | * @Date: 2015.9.21 5 | */ 6 | define(function(require, exports, module) { 7 | function MimeType() { 8 | /** 9 | * 私有变量 10 | */ 11 | var SPLITOR = '\uFEFF'; 12 | var MIMETYPE = { 13 | 'application/km': '\uFFFF' 14 | }; 15 | var SIGN = { 16 | '\uFEFF': 'SPLITOR', 17 | '\uFFFF': 'application/km' 18 | }; 19 | 20 | /** 21 | * 用于将一段纯文本封装成符合其数据格式的文本 22 | * @method process private 23 | * @param {MIMETYPE} mimetype 数据格式 24 | * @param {String} text 原始文本 25 | * @return {String} 符合该数据格式下的文本 26 | * @example 27 | * var str = "123"; 28 | * str = process('application/km', str); // 返回的内容再经过MimeType判断会读取出其数据格式为application/km 29 | * process('text/plain', str); // 若接受到一个非纯文本信息,则会将其转换为新的数据格式 30 | */ 31 | function process(mimetype, text) { 32 | if (!this.isPureText(text)) { 33 | var _mimetype = this.whichMimeType(text); 34 | if (!_mimetype) { 35 | throw new Error('unknow mimetype!'); 36 | }; 37 | text = this.getPureText(text); 38 | }; 39 | if (mimetype === false) { 40 | return text; 41 | }; 42 | return mimetype + SPLITOR + text; 43 | } 44 | 45 | /** 46 | * 注册数据类型的标识 47 | * @method registMimeTypeProtocol public 48 | * @param {String} type 数据类型 49 | * @param {String} sign 标识 50 | */ 51 | this.registMimeTypeProtocol = function(type, sign) { 52 | if (sign && SIGN[sign]) { 53 | throw new Error('sing has registed!'); 54 | } 55 | if (type && !!MIMETYPE[type]) { 56 | throw new Error('mimetype has registed!'); 57 | }; 58 | SIGN[sign] = type; 59 | MIMETYPE[type] = sign; 60 | } 61 | 62 | /** 63 | * 获取已注册数据类型的协议 64 | * @method getMimeTypeProtocol public 65 | * @param {String} type 数据类型 66 | * @param {String} text|undefiend 文本内容或不传入 67 | * @return {String|Function} 68 | * @example 69 | * text若不传入则直接返回对应数据格式的处理(process)方法 70 | * 若传入文本则直接调用对应的process方法进行处理,此时返回处理后的内容 71 | * var m = new MimeType(); 72 | * var kmprocess = m.getMimeTypeProtocol('application/km'); 73 | * kmprocess("123") === m.getMimeTypeProtocol('application/km', "123"); 74 | * 75 | */ 76 | this.getMimeTypeProtocol = function(type, text) { 77 | var mimetype = MIMETYPE[type] || false; 78 | 79 | if (text === undefined) { 80 | return process.bind(this, mimetype); 81 | }; 82 | 83 | return process(mimetype, text); 84 | } 85 | 86 | this.getSpitor = function() { 87 | return SPLITOR; 88 | } 89 | 90 | this.getMimeType = function(sign) { 91 | if (sign !== undefined) { 92 | return SIGN[sign] || null; 93 | }; 94 | return MIMETYPE; 95 | } 96 | } 97 | 98 | MimeType.prototype.isPureText = function(text) { 99 | return !(~text.indexOf(this.getSpitor())); 100 | } 101 | 102 | MimeType.prototype.getPureText = function(text) { 103 | if (this.isPureText(text)) { 104 | return text; 105 | }; 106 | return text.split(this.getSpitor())[1]; 107 | } 108 | 109 | MimeType.prototype.whichMimeType = function(text) { 110 | if (this.isPureText(text)) { 111 | return null; 112 | }; 113 | return this.getMimeType(text.split(this.getSpitor())[0]); 114 | } 115 | 116 | function MimeTypeRuntime() { 117 | if (this.minder.supportClipboardEvent && !kity.Browser.gecko) { 118 | this.MimeType = new MimeType(); 119 | }; 120 | } 121 | 122 | return module.exports = MimeTypeRuntime; 123 | }); -------------------------------------------------------------------------------- /src/runtime/container.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 初始化编辑器的容器 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | define(function(require, exports, module) { 10 | 11 | /** 12 | * 最先执行的 Runtime,初始化编辑器容器 13 | */ 14 | function ContainerRuntime() { 15 | var container; 16 | 17 | if (typeof(this.selector) == 'string') { 18 | container = document.querySelector(this.selector); 19 | } else { 20 | container = this.selector; 21 | } 22 | 23 | if (!container) throw new Error('Invalid selector: ' + this.selector); 24 | 25 | // 这个类名用于给编辑器添加样式 26 | container.classList.add('km-editor'); 27 | 28 | // 暴露容器给其他运行时使用 29 | this.container = container; 30 | } 31 | 32 | return module.exports = ContainerRuntime; 33 | }); -------------------------------------------------------------------------------- /src/runtime/drag.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 用于拖拽节点时屏蔽键盘事件 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | define(function(require, exports, module) { 10 | 11 | var Hotbox = require('../hotbox'); 12 | var Debug = require('../tool/debug'); 13 | var debug = new Debug('drag'); 14 | 15 | function DragRuntime() { 16 | var fsm = this.fsm; 17 | var minder = this.minder; 18 | var hotbox = this.hotbox; 19 | var receiver = this.receiver; 20 | var receiverElement = receiver.element; 21 | 22 | // setup everything to go 23 | setupFsm(); 24 | 25 | // listen the fsm changes, make action. 26 | function setupFsm() { 27 | 28 | // when jumped to drag mode, enter 29 | fsm.when('* -> drag', function() { 30 | // now is drag mode 31 | }); 32 | 33 | fsm.when('drag -> *', function(exit, enter, reason) { 34 | if (reason == 'drag-finish') { 35 | // now exit drag mode 36 | } 37 | }); 38 | } 39 | 40 | var downX, downY; 41 | var MOUSE_HAS_DOWN = 0; 42 | var MOUSE_HAS_UP = 1; 43 | var BOUND_CHECK = 20; 44 | var flag = MOUSE_HAS_UP; 45 | var maxX, maxY, osx, osy, containerY; 46 | var freeHorizen = false, freeVirtical = false; 47 | var frame; 48 | 49 | function move(direction, speed) { 50 | if (!direction) { 51 | freeHorizen = freeVirtical = false; 52 | frame && kity.releaseFrame(frame); 53 | frame = null; 54 | return; 55 | } 56 | if (!frame) { 57 | frame = kity.requestFrame((function (direction, speed, minder) { 58 | return function (frame) { 59 | switch (direction) { 60 | case 'left': 61 | minder._viewDragger.move({x: -speed, y: 0}, 0); 62 | break; 63 | case 'top': 64 | minder._viewDragger.move({x: 0, y: -speed}, 0); 65 | break; 66 | case 'right': 67 | minder._viewDragger.move({x: speed, y: 0}, 0); 68 | break; 69 | case 'bottom': 70 | minder._viewDragger.move({x: 0, y: speed}, 0); 71 | break; 72 | default: 73 | return; 74 | } 75 | frame.next(); 76 | }; 77 | })(direction, speed, minder)); 78 | } 79 | } 80 | 81 | minder.on('mousedown', function(e) { 82 | flag = MOUSE_HAS_DOWN; 83 | var rect = minder.getPaper().container.getBoundingClientRect(); 84 | downX = e.originEvent.clientX; 85 | downY = e.originEvent.clientY; 86 | containerY = rect.top; 87 | maxX = rect.width; 88 | maxY = rect.height; 89 | }); 90 | 91 | minder.on('mousemove', function(e) { 92 | if (fsm.state() === 'drag' && flag == MOUSE_HAS_DOWN && minder.getSelectedNode() 93 | && (Math.abs(downX - e.originEvent.clientX) > BOUND_CHECK 94 | || Math.abs(downY - e.originEvent.clientY) > BOUND_CHECK)) { 95 | osx = e.originEvent.clientX; 96 | osy = e.originEvent.clientY - containerY; 97 | 98 | if (osx < BOUND_CHECK) { 99 | move('right', BOUND_CHECK - osx); 100 | } else if (osx > maxX - BOUND_CHECK) { 101 | move('left', BOUND_CHECK + osx - maxX); 102 | } else { 103 | freeHorizen = true; 104 | } 105 | if (osy < BOUND_CHECK) { 106 | move('bottom', osy); 107 | } else if (osy > maxY - BOUND_CHECK) { 108 | move('top', BOUND_CHECK + osy - maxY); 109 | } else { 110 | freeVirtical = true; 111 | } 112 | if (freeHorizen && freeVirtical) { 113 | move(false); 114 | } 115 | } 116 | if (fsm.state() !== 'drag' 117 | && flag === MOUSE_HAS_DOWN 118 | && minder.getSelectedNode() 119 | && (Math.abs(downX - e.originEvent.clientX) > BOUND_CHECK 120 | || Math.abs(downY - e.originEvent.clientY) > BOUND_CHECK)) { 121 | 122 | if (fsm.state() === 'hotbox') { 123 | hotbox.active(Hotbox.STATE_IDLE); 124 | } 125 | 126 | return fsm.jump('drag', 'user-drag'); 127 | } 128 | }); 129 | 130 | window.addEventListener('mouseup', function () { 131 | flag = MOUSE_HAS_UP; 132 | if (fsm.state() === 'drag') { 133 | move(false); 134 | return fsm.jump('normal', 'drag-finish'); 135 | } 136 | }, false); 137 | } 138 | 139 | return module.exports = DragRuntime; 140 | }); 141 | -------------------------------------------------------------------------------- /src/runtime/fsm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 编辑器状态机 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | define(function(require, exports, module) { 10 | 11 | var Debug = require('../tool/debug'); 12 | var debug = new Debug('fsm'); 13 | 14 | function handlerConditionMatch(condition, when, exit, enter) { 15 | if (condition.when != when) return false; 16 | if (condition.enter != '*' && condition.enter != enter) return false; 17 | if (condition.exit != '*' && condition.exit != exit) return; 18 | return true; 19 | } 20 | 21 | function FSM(defaultState) { 22 | var currentState = defaultState; 23 | var BEFORE_ARROW = ' - '; 24 | var AFTER_ARROW = ' -> '; 25 | var handlers = []; 26 | 27 | /** 28 | * 状态跳转 29 | * 30 | * 会通知所有的状态跳转监视器 31 | * 32 | * @param {string} newState 新状态名称 33 | * @param {any} reason 跳转的原因,可以作为参数传递给跳转监视器 34 | */ 35 | this.jump = function(newState, reason) { 36 | if (!reason) throw new Error('Please tell fsm the reason to jump'); 37 | 38 | var oldState = currentState; 39 | var notify = [oldState, newState].concat([].slice.call(arguments, 1)); 40 | var i, handler; 41 | 42 | // 跳转前 43 | for (i = 0; i < handlers.length; i++) { 44 | handler = handlers[i]; 45 | if (handlerConditionMatch(handler.condition, 'before', oldState, newState)) { 46 | if (handler.apply(null, notify)) return; 47 | } 48 | } 49 | 50 | currentState = newState; 51 | debug.log('[{0}] {1} -> {2}', reason, oldState, newState); 52 | 53 | // 跳转后 54 | for (i = 0; i < handlers.length; i++) { 55 | handler = handlers[i]; 56 | if (handlerConditionMatch(handler.condition, 'after', oldState, newState)) { 57 | handler.apply(null, notify); 58 | } 59 | } 60 | return currentState; 61 | }; 62 | 63 | /** 64 | * 返回当前状态 65 | * @return {string} 66 | */ 67 | this.state = function() { 68 | return currentState; 69 | }; 70 | 71 | /** 72 | * 添加状态跳转监视器 73 | * 74 | * @param {string} condition 75 | * 监视的时机 76 | * "* => *" (默认) 77 | * 78 | * @param {Function} handler 79 | * 监视函数,当状态跳转的时候,会接收三个参数 80 | * * from - 跳转前的状态 81 | * * to - 跳转后的状态 82 | * * reason - 跳转的原因 83 | */ 84 | this.when = function(condition, handler) { 85 | if (arguments.length == 1) { 86 | handler = condition; 87 | condition = '* -> *'; 88 | } 89 | 90 | var when, resolved, exit, enter; 91 | 92 | resolved = condition.split(BEFORE_ARROW); 93 | if (resolved.length == 2) { 94 | when = 'before'; 95 | } else { 96 | resolved = condition.split(AFTER_ARROW); 97 | if (resolved.length == 2) { 98 | when = 'after'; 99 | } 100 | } 101 | if (!when) throw new Error('Illegal fsm condition: ' + condition); 102 | 103 | exit = resolved[0]; 104 | enter = resolved[1]; 105 | 106 | handler.condition = { 107 | when: when, 108 | exit: exit, 109 | enter: enter 110 | }; 111 | 112 | handlers.push(handler); 113 | }; 114 | } 115 | 116 | function FSMRumtime() { 117 | this.fsm = new FSM('normal'); 118 | } 119 | 120 | return module.exports = FSMRumtime; 121 | }); -------------------------------------------------------------------------------- /src/runtime/history.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 历史管理 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | 10 | 11 | define(function(require, exports, module) { 12 | var jsonDiff = require('../tool/jsondiff'); 13 | 14 | function HistoryRuntime() { 15 | var minder = this.minder; 16 | var hotbox = this.hotbox; 17 | 18 | var MAX_HISTORY = 100; 19 | 20 | var lastSnap; 21 | var patchLock; 22 | var undoDiffs; 23 | var redoDiffs; 24 | 25 | function reset() { 26 | undoDiffs = []; 27 | redoDiffs = []; 28 | lastSnap = minder.exportJson(); 29 | } 30 | 31 | function makeUndoDiff() { 32 | var headSnap = minder.exportJson(); 33 | var diff = jsonDiff(headSnap, lastSnap); 34 | if (diff.length) { 35 | undoDiffs.push(diff); 36 | while (undoDiffs.length > MAX_HISTORY) { 37 | undoDiffs.shift(); 38 | } 39 | lastSnap = headSnap; 40 | return true; 41 | } 42 | } 43 | 44 | function makeRedoDiff() { 45 | var revertSnap = minder.exportJson(); 46 | redoDiffs.push(jsonDiff(revertSnap, lastSnap)); 47 | lastSnap = revertSnap; 48 | } 49 | 50 | function undo() { 51 | patchLock = true; 52 | var undoDiff = undoDiffs.pop(); 53 | if (undoDiff) { 54 | minder.applyPatches(undoDiff); 55 | makeRedoDiff(); 56 | } 57 | patchLock = false; 58 | } 59 | 60 | function redo() { 61 | patchLock = true; 62 | var redoDiff = redoDiffs.pop(); 63 | if (redoDiff) { 64 | minder.applyPatches(redoDiff); 65 | makeUndoDiff(); 66 | } 67 | patchLock = false; 68 | } 69 | 70 | function changed() { 71 | if (patchLock) return; 72 | if (makeUndoDiff()) redoDiffs = []; 73 | } 74 | 75 | function hasUndo() { 76 | return !!undoDiffs.length; 77 | } 78 | 79 | function hasRedo() { 80 | return !!redoDiffs.length; 81 | } 82 | 83 | function updateSelection(e) { 84 | if (!patchLock) return; 85 | var patch = e.patch; 86 | switch (patch.express) { 87 | case 'node.add': 88 | minder.select(patch.node.getChild(patch.index), true); 89 | break; 90 | case 'node.remove': 91 | case 'data.replace': 92 | case 'data.remove': 93 | case 'data.add': 94 | minder.select(patch.node, true); 95 | break; 96 | } 97 | } 98 | 99 | this.history = { 100 | reset: reset, 101 | undo: undo, 102 | redo: redo, 103 | hasUndo: hasUndo, 104 | hasRedo: hasRedo 105 | }; 106 | reset(); 107 | minder.on('contentchange', changed); 108 | minder.on('import', reset); 109 | minder.on('patch', updateSelection); 110 | 111 | var main = hotbox.state('main'); 112 | main.button({ 113 | position: 'top', 114 | label: '撤销', 115 | key: 'Ctrl + Z', 116 | enable: hasUndo, 117 | action: undo, 118 | next: 'idle' 119 | }); 120 | main.button({ 121 | position: 'top', 122 | label: '重做', 123 | key: 'Ctrl + Y', 124 | enable: hasRedo, 125 | action: redo, 126 | next: 'idle' 127 | }); 128 | } 129 | 130 | window.diff = jsonDiff; 131 | 132 | return module.exports = HistoryRuntime; 133 | }); -------------------------------------------------------------------------------- /src/runtime/hotbox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 热盒 Runtime 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | define(function(require, exports, module) { 10 | var Hotbox = require('../hotbox'); 11 | 12 | function HotboxRuntime() { 13 | var fsm = this.fsm; 14 | var minder = this.minder; 15 | var receiver = this.receiver; 16 | var container = this.container; 17 | 18 | var hotbox = new Hotbox(container); 19 | 20 | hotbox.setParentFSM(fsm); 21 | 22 | fsm.when('normal -> hotbox', function(exit, enter, reason) { 23 | var node = minder.getSelectedNode(); 24 | var position; 25 | if (node) { 26 | var box = node.getRenderBox(); 27 | position = { 28 | x: box.cx, 29 | y: box.cy 30 | }; 31 | } 32 | hotbox.active('main', position); 33 | }); 34 | 35 | fsm.when('normal -> normal', function(exit, enter, reason, e) { 36 | if (reason == 'shortcut-handle') { 37 | var handleResult = hotbox.dispatch(e); 38 | if (handleResult) { 39 | e.preventDefault(); 40 | } else { 41 | minder.dispatchKeyEvent(e); 42 | } 43 | } 44 | }); 45 | 46 | fsm.when('modal -> normal', function(exit, enter, reason, e) { 47 | if (reason == 'import-text-finish') { 48 | receiver.element.focus(); 49 | } 50 | }); 51 | 52 | this.hotbox = hotbox; 53 | } 54 | 55 | return module.exports = HotboxRuntime; 56 | }); -------------------------------------------------------------------------------- /src/runtime/minder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 脑图示例运行时 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | define(function (require, exports, module) { 10 | var Minder = require('../minder'); 11 | 12 | function MinderRuntime() { 13 | 14 | // 不使用 kityminder 的按键处理,由 ReceiverRuntime 统一处理 15 | var minder = new Minder({ 16 | enableKeyReceiver: false, 17 | enableAnimation: true, 18 | }); 19 | 20 | // 渲染,初始化 21 | minder.renderTo(this.selector); 22 | minder.setTheme(null); 23 | minder.select(minder.getRoot(), true); 24 | minder.execCommand('text', '中心主题'); 25 | // 导出给其它 Runtime 使用 26 | this.minder = minder; 27 | } 28 | 29 | return module.exports = MinderRuntime; 30 | }); -------------------------------------------------------------------------------- /src/runtime/node.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | 3 | function NodeRuntime() { 4 | var runtime = this; 5 | var minder = this.minder; 6 | var hotbox = this.hotbox; 7 | var fsm = this.fsm; 8 | 9 | var main = hotbox.state('main'); 10 | 11 | var buttons = [ 12 | '前移:Alt+Up:ArrangeUp', 13 | '下级:Tab|Insert:AppendChildNode', 14 | '同级:Enter:AppendSiblingNode', 15 | '后移:Alt+Down:ArrangeDown', 16 | '删除:Delete|Backspace:RemoveNode', 17 | '上级:Shift+Tab|Shift+Insert:AppendParentNode' 18 | //'全选:Ctrl+A:SelectAll' 19 | ]; 20 | 21 | var AppendLock = 0; 22 | 23 | buttons.forEach(function(button) { 24 | var parts = button.split(':'); 25 | var label = parts.shift(); 26 | var key = parts.shift(); 27 | var command = parts.shift(); 28 | main.button({ 29 | position: 'ring', 30 | label: label, 31 | key: key, 32 | action: function() { 33 | if (command.indexOf('Append') === 0) { 34 | AppendLock++; 35 | minder.execCommand(command, '分支主题'); 36 | 37 | // provide in input runtime 38 | function afterAppend () { 39 | if (!--AppendLock) { 40 | runtime.editText(); 41 | } 42 | minder.off('layoutallfinish', afterAppend); 43 | } 44 | minder.on('layoutallfinish', afterAppend); 45 | } else { 46 | minder.execCommand(command); 47 | fsm.jump('normal', 'command-executed'); 48 | } 49 | }, 50 | enable: function() { 51 | return minder.queryCommandState(command) != -1; 52 | } 53 | }); 54 | }); 55 | 56 | main.button({ 57 | position: 'bottom', 58 | label: '导入节点', 59 | key: 'Alt + V', 60 | enable: function() { 61 | var selectedNodes = minder.getSelectedNodes(); 62 | return selectedNodes.length == 1&&!minder.isReadonly; 63 | }, 64 | action: importNodeData, 65 | next: 'idle' 66 | }); 67 | 68 | main.button({ 69 | position: 'bottom', 70 | label: '导出节点', 71 | key: 'Alt + C', 72 | enable: function() { 73 | var selectedNodes = minder.getSelectedNodes(); 74 | return selectedNodes.length == 1; 75 | }, 76 | action: exportNodeData, 77 | next: 'idle' 78 | }); 79 | 80 | function importNodeData() { 81 | minder.fire('importNodeData'); 82 | } 83 | 84 | function exportNodeData() { 85 | minder.fire('exportNodeData'); 86 | } 87 | 88 | //main.button({ 89 | // position: 'ring', 90 | // key: '/', 91 | // action: function(){ 92 | // if (!minder.queryCommandState('expand')) { 93 | // minder.execCommand('expand'); 94 | // } else if (!minder.queryCommandState('collapse')) { 95 | // minder.execCommand('collapse'); 96 | // } 97 | // }, 98 | // enable: function() { 99 | // return minder.queryCommandState('expand') != -1 || minder.queryCommandState('collapse') != -1; 100 | // }, 101 | // beforeShow: function() { 102 | // if (!minder.queryCommandState('expand')) { 103 | // this.$button.children[0].innerHTML = '展开'; 104 | // } else { 105 | // this.$button.children[0].innerHTML = '收起'; 106 | // } 107 | // } 108 | //}) 109 | } 110 | 111 | return module.exports = NodeRuntime; 112 | }); 113 | -------------------------------------------------------------------------------- /src/runtime/priority.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module){ 2 | 3 | function PriorityRuntime() { 4 | var minder = this.minder; 5 | var hotbox = this.hotbox; 6 | 7 | var main = hotbox.state('main'); 8 | 9 | main.button({ 10 | position: 'top', 11 | label: '优先级', 12 | key: 'P', 13 | next: 'priority', 14 | enable: function() { 15 | return minder.queryCommandState('priority') != -1; 16 | } 17 | }); 18 | 19 | var priority = hotbox.state('priority'); 20 | '123456789'.replace(/./g, function(p) { 21 | priority.button({ 22 | position: 'ring', 23 | label: 'P' + p, 24 | key: p, 25 | action: function() { 26 | minder.execCommand('Priority', p); 27 | } 28 | }); 29 | }); 30 | 31 | priority.button({ 32 | position: 'center', 33 | label: '移除', 34 | key: 'Del', 35 | action: function() { 36 | minder.execCommand('Priority', 0); 37 | } 38 | }); 39 | 40 | priority.button({ 41 | position: 'top', 42 | label: '返回', 43 | key: 'esc', 44 | next: 'back' 45 | }); 46 | 47 | } 48 | 49 | return module.exports = PriorityRuntime; 50 | 51 | }); -------------------------------------------------------------------------------- /src/runtime/progress.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module){ 2 | 3 | function ProgressRuntime() { 4 | var minder = this.minder; 5 | var hotbox = this.hotbox; 6 | 7 | var main = hotbox.state('main'); 8 | 9 | main.button({ 10 | position: 'top', 11 | label: '进度', 12 | key: 'G', 13 | next: 'progress', 14 | enable: function() { 15 | return minder.queryCommandState('progress') != -1; 16 | } 17 | }); 18 | 19 | var progress = hotbox.state('progress'); 20 | '012345678'.replace(/./g, function(p) { 21 | progress.button({ 22 | position: 'ring', 23 | label: 'G' + p, 24 | key: p, 25 | action: function() { 26 | minder.execCommand('Progress', parseInt(p) + 1); 27 | } 28 | }); 29 | }); 30 | 31 | progress.button({ 32 | position: 'center', 33 | label: '移除', 34 | key: 'Del', 35 | action: function() { 36 | minder.execCommand('Progress', 0); 37 | } 38 | }); 39 | 40 | progress.button({ 41 | position: 'top', 42 | label: '返回', 43 | key: 'esc', 44 | next: 'back' 45 | }); 46 | 47 | 48 | } 49 | 50 | return module.exports = ProgressRuntime; 51 | 52 | }); -------------------------------------------------------------------------------- /src/runtime/receiver.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 键盘事件接收/分发器 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | 10 | define(function(require, exports, module) { 11 | var key = require('../tool/key'); 12 | var hotbox = require('hotbox'); 13 | 14 | function ReceiverRuntime() { 15 | var fsm = this.fsm; 16 | var minder = this.minder; 17 | var me = this; 18 | 19 | // 接收事件的 div 20 | var element = document.createElement('div'); 21 | element.contentEditable = true; 22 | /** 23 | * @Desc: 增加tabindex属性使得element的contenteditable不管是trur还是false都能有focus和blur事件 24 | * @Editor: Naixor 25 | * @Date: 2015.09.14 26 | */ 27 | element.setAttribute('tabindex', -1); 28 | element.classList.add('receiver'); 29 | element.onkeydown = element.onkeypress = element.onkeyup = dispatchKeyEvent; 30 | element.addEventListener('compositionstart', dispatchKeyEvent); 31 | // element.addEventListener('compositionend', dispatchKeyEvent); 32 | this.container.appendChild(element); 33 | 34 | // receiver 对象 35 | var receiver = { 36 | element: element, 37 | selectAll: function() { 38 | // 保证有被选中的 39 | if (!element.innerHTML) element.innerHTML = ' '; 40 | var range = document.createRange(); 41 | var selection = window.getSelection(); 42 | range.selectNodeContents(element); 43 | selection.removeAllRanges(); 44 | selection.addRange(range); 45 | element.focus(); 46 | }, 47 | /** 48 | * @Desc: 增加enable和disable方法用于解决热核态的输入法屏蔽问题 49 | * @Editor: Naixor 50 | * @Date: 2015.09.14 51 | */ 52 | enable: function() { 53 | element.setAttribute("contenteditable", true); 54 | }, 55 | disable: function() { 56 | element.setAttribute("contenteditable", false); 57 | }, 58 | /** 59 | * @Desc: hack FF下div contenteditable的光标丢失BUG 60 | * @Editor: Naixor 61 | * @Date: 2015.10.15 62 | */ 63 | fixFFCaretDisappeared: function() { 64 | element.removeAttribute("contenteditable"); 65 | element.setAttribute("contenteditable", "true"); 66 | element.blur(); 67 | element.focus(); 68 | }, 69 | /** 70 | * 以此事件代替通过mouse事件来判断receiver丢失焦点的事件 71 | * @editor Naixor 72 | * @Date 2015-12-2 73 | */ 74 | onblur: function (handler) { 75 | element.onblur = handler; 76 | } 77 | }; 78 | receiver.selectAll(); 79 | 80 | minder.on('beforemousedown', receiver.selectAll); 81 | minder.on('receiverfocus', receiver.selectAll); 82 | minder.on('readonly', function() { 83 | // 屏蔽minder的事件接受,删除receiver和hotbox 84 | minder.disable(); 85 | editor.receiver.element.parentElement.removeChild(editor.receiver.element); 86 | editor.hotbox.$container.removeChild(editor.hotbox.$element); 87 | }); 88 | 89 | // 侦听器,接收到的事件会派发给所有侦听器 90 | var listeners = []; 91 | 92 | // 侦听指定状态下的事件,如果不传 state,侦听所有状态 93 | receiver.listen = function(state, listener) { 94 | if (arguments.length == 1) { 95 | listener = state; 96 | state = '*'; 97 | } 98 | listener.notifyState = state; 99 | listeners.push(listener); 100 | }; 101 | 102 | function dispatchKeyEvent(e) { 103 | e.is = function(keyExpression) { 104 | var subs = keyExpression.split('|'); 105 | for (var i = 0; i < subs.length; i++) { 106 | if (key.is(this, subs[i])) return true; 107 | } 108 | return false; 109 | }; 110 | var listener, jumpState; 111 | for (var i = 0; i < listeners.length; i++) { 112 | 113 | listener = listeners[i]; 114 | // 忽略不在侦听状态的侦听器 115 | if (listener.notifyState != '*' && listener.notifyState != fsm.state()) { 116 | continue; 117 | } 118 | 119 | /** 120 | * 121 | * 对于所有的侦听器,只允许一种处理方式:跳转状态。 122 | * 如果侦听器确定要跳转,则返回要跳转的状态。 123 | * 每个事件只允许一个侦听器进行状态跳转 124 | * 跳转动作由侦听器自行完成(因为可能需要在跳转时传递 reason),返回跳转结果即可。 125 | * 比如: 126 | * 127 | * ```js 128 | * receiver.listen('normal', function(e) { 129 | * if (isSomeReasonForJumpState(e)) { 130 | * return fsm.jump('newstate', e); 131 | * } 132 | * }); 133 | * ``` 134 | */ 135 | if (listener.call(null, e)) { 136 | return; 137 | } 138 | } 139 | } 140 | 141 | this.receiver = receiver; 142 | } 143 | 144 | return module.exports = ReceiverRuntime; 145 | 146 | }); 147 | -------------------------------------------------------------------------------- /src/tool/debug.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 支持各种调试后门 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | define(function(require, exports, module) { 10 | var format = require('./format'); 11 | 12 | function noop() {} 13 | 14 | function stringHash(str) { 15 | var hash = 0; 16 | for (var i = 0; i < str.length; i++) { 17 | hash += str.charCodeAt(i); 18 | } 19 | return hash; 20 | } 21 | 22 | /* global console */ 23 | function Debug(flag) { 24 | var debugMode = this.flaged = window.location.search.indexOf(flag) != -1; 25 | 26 | if (debugMode) { 27 | var h = stringHash(flag) % 360; 28 | 29 | var flagStyle = format( 30 | 'background: hsl({0}, 50%, 80%); ' + 31 | 'color: hsl({0}, 100%, 30%); ' + 32 | 'padding: 2px 3px; ' + 33 | 'margin: 1px 3px 0 0;' + 34 | 'border-radius: 2px;', h); 35 | 36 | var textStyle = 'background: none; color: black;'; 37 | this.log = function() { 38 | var output = format.apply(null, arguments); 39 | console.log(format('%c{0}%c{1}', flag, output), flagStyle, textStyle); 40 | }; 41 | } else { 42 | this.log = noop; 43 | } 44 | } 45 | 46 | return module.exports = Debug; 47 | }); -------------------------------------------------------------------------------- /src/tool/format.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | function format(template, args) { 3 | if (typeof(args) != 'object') { 4 | args = [].slice.call(arguments, 1); 5 | } 6 | return String(template).replace(/\{(\w+)\}/ig, function(match, $key) { 7 | return args[$key] || $key; 8 | }); 9 | } 10 | return module.exports = format; 11 | }); -------------------------------------------------------------------------------- /src/tool/innertext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * innerText polyfill 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | 10 | 11 | define(function(require, exports, module) { 12 | if ((!('innerText' in document.createElement('a'))) && ('getSelection' in window)) { 13 | HTMLElement.prototype.__defineGetter__('innerText', function() { 14 | var selection = window.getSelection(), 15 | ranges = [], 16 | str, i; 17 | 18 | // Save existing selections. 19 | for (i = 0; i < selection.rangeCount; i++) { 20 | ranges[i] = selection.getRangeAt(i); 21 | } 22 | 23 | // Deselect everything. 24 | selection.removeAllRanges(); 25 | 26 | // Select `el` and all child nodes. 27 | // 'this' is the element .innerText got called on 28 | selection.selectAllChildren(this); 29 | 30 | // Get the string representation of the selected nodes. 31 | str = selection.toString(); 32 | 33 | // Deselect everything. Again. 34 | selection.removeAllRanges(); 35 | 36 | // Restore all formerly existing selections. 37 | for (i = 0; i < ranges.length; i++) { 38 | selection.addRange(ranges[i]); 39 | } 40 | 41 | // Oh look, this is what we wanted. 42 | // String representation of the element, close to as rendered. 43 | return str; 44 | }); 45 | HTMLElement.prototype.__defineSetter__('innerText', function(text) { 46 | /** 47 | * @Desc: 解决FireFox节点内容删除后text为null,出现报错的问题 48 | * @Editor: Naixor 49 | * @Date: 2015.9.16 50 | */ 51 | this.innerHTML = (text || '').replace(//g, '>').replace(/\n/g, '
'); 52 | }); 53 | } 54 | }); -------------------------------------------------------------------------------- /src/tool/jsondiff.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 5 | * 6 | * @author: techird 7 | * @copyright: Baidu FEX, 2014 8 | */ 9 | 10 | 11 | define(function(require, exports, module) { 12 | /*! 13 | * https://github.com/Starcounter-Jack/Fast-JSON-Patch 14 | * json-patch-duplex.js 0.5.0 15 | * (c) 2013 Joachim Wester 16 | * MIT license 17 | */ 18 | 19 | var _objectKeys = (function () { 20 | if (Object.keys) 21 | return Object.keys; 22 | 23 | return function (o) { 24 | var keys = []; 25 | for (var i in o) { 26 | if (o.hasOwnProperty(i)) { 27 | keys.push(i); 28 | } 29 | } 30 | return keys; 31 | }; 32 | })(); 33 | function escapePathComponent(str) { 34 | if (str.indexOf('/') === -1 && str.indexOf('~') === -1) 35 | return str; 36 | return str.replace(/~/g, '~0').replace(/\//g, '~1'); 37 | } 38 | function deepClone(obj) { 39 | if (typeof obj === "object") { 40 | return JSON.parse(JSON.stringify(obj)); 41 | } else { 42 | return obj; 43 | } 44 | } 45 | 46 | // Dirty check if obj is different from mirror, generate patches and update mirror 47 | function _generate(mirror, obj, patches, path) { 48 | var newKeys = _objectKeys(obj); 49 | var oldKeys = _objectKeys(mirror); 50 | var changed = false; 51 | var deleted = false; 52 | 53 | for (var t = oldKeys.length - 1; t >= 0; t--) { 54 | var key = oldKeys[t]; 55 | var oldVal = mirror[key]; 56 | if (obj.hasOwnProperty(key)) { 57 | var newVal = obj[key]; 58 | if (typeof oldVal == "object" && oldVal != null && typeof newVal == "object" && newVal != null) { 59 | _generate(oldVal, newVal, patches, path + "/" + escapePathComponent(key)); 60 | } else { 61 | if (oldVal != newVal) { 62 | changed = true; 63 | patches.push({ op: "replace", path: path + "/" + escapePathComponent(key), value: deepClone(newVal) }); 64 | } 65 | } 66 | } else { 67 | patches.push({ op: "remove", path: path + "/" + escapePathComponent(key) }); 68 | deleted = true; // property has been deleted 69 | } 70 | } 71 | 72 | if (!deleted && newKeys.length == oldKeys.length) { 73 | return; 74 | } 75 | 76 | for (var t = 0; t < newKeys.length; t++) { 77 | var key = newKeys[t]; 78 | if (!mirror.hasOwnProperty(key)) { 79 | patches.push({ op: "add", path: path + "/" + escapePathComponent(key), value: deepClone(obj[key]) }); 80 | } 81 | } 82 | } 83 | 84 | function compare(tree1, tree2) { 85 | var patches = []; 86 | _generate(tree1, tree2, patches, ''); 87 | return patches; 88 | } 89 | 90 | return module.exports = compare; 91 | }); -------------------------------------------------------------------------------- /src/tool/key.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | var keymap = require('./keymap'); 3 | 4 | var CTRL_MASK = 0x1000; 5 | var ALT_MASK = 0x2000; 6 | var SHIFT_MASK = 0x4000; 7 | 8 | function hash(unknown) { 9 | if (typeof(unknown) == 'string') { 10 | return hashKeyExpression(unknown); 11 | } 12 | return hashKeyEvent(unknown); 13 | } 14 | function is(a, b) { 15 | return a && b && hash(a) == hash(b); 16 | } 17 | exports.hash = hash; 18 | exports.is = is; 19 | 20 | 21 | function hashKeyEvent(keyEvent) { 22 | var hashCode = 0; 23 | if (keyEvent.ctrlKey || keyEvent.metaKey) { 24 | hashCode |= CTRL_MASK; 25 | } 26 | if (keyEvent.altKey) { 27 | hashCode |= ALT_MASK; 28 | } 29 | if (keyEvent.shiftKey) { 30 | hashCode |= SHIFT_MASK; 31 | } 32 | // Shift, Control, Alt KeyCode ignored. 33 | if ([16, 17, 18, 91].indexOf(keyEvent.keyCode) === -1) { 34 | /** 35 | * 解决浏览器输入法状态下对keyDown的keyCode判断不准确的问题,使用keyIdentifier, 36 | * 可以解决chrome和safari下的各种问题,其他浏览器依旧有问题,然而那并不影响我们对特 37 | * 需判断的按键进行判断(比如Space在safari输入法态下就是229,其他的就不是) 38 | * @editor Naixor 39 | * @Date 2015-12-2 40 | */ 41 | if (keyEvent.keyCode === 229 && keyEvent.keyIdentifier) { 42 | return hashCode |= parseInt(keyEvent.keyIdentifier.substr(2), 16); 43 | } 44 | hashCode |= keyEvent.keyCode; 45 | } 46 | return hashCode; 47 | } 48 | 49 | function hashKeyExpression(keyExpression) { 50 | var hashCode = 0; 51 | keyExpression.toLowerCase().split(/\s*\+\s*/).forEach(function(name) { 52 | switch(name) { 53 | case 'ctrl': 54 | case 'cmd': 55 | hashCode |= CTRL_MASK; 56 | break; 57 | case 'alt': 58 | hashCode |= ALT_MASK; 59 | break; 60 | case 'shift': 61 | hashCode |= SHIFT_MASK; 62 | break; 63 | default: 64 | hashCode |= keymap[name]; 65 | } 66 | }); 67 | return hashCode; 68 | } 69 | }); 70 | -------------------------------------------------------------------------------- /src/tool/keymap.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | var keymap = { 3 | 4 | 'Shift': 16, 5 | 'Control': 17, 6 | 'Alt': 18, 7 | 'CapsLock': 20, 8 | 9 | 'BackSpace': 8, 10 | 'Tab': 9, 11 | 'Enter': 13, 12 | 'Esc': 27, 13 | 'Space': 32, 14 | 15 | 'PageUp': 33, 16 | 'PageDown': 34, 17 | 'End': 35, 18 | 'Home': 36, 19 | 20 | 'Insert': 45, 21 | 22 | 'Left': 37, 23 | 'Up': 38, 24 | 'Right': 39, 25 | 'Down': 40, 26 | 27 | 'Direction': { 28 | 37: 1, 29 | 38: 1, 30 | 39: 1, 31 | 40: 1 32 | }, 33 | 34 | 'Del': 46, 35 | 36 | 'NumLock': 144, 37 | 38 | 'Cmd': 91, 39 | 'CmdFF': 224, 40 | 'F1': 112, 41 | 'F2': 113, 42 | 'F3': 114, 43 | 'F4': 115, 44 | 'F5': 116, 45 | 'F6': 117, 46 | 'F7': 118, 47 | 'F8': 119, 48 | 'F9': 120, 49 | 'F10': 121, 50 | 'F11': 122, 51 | 'F12': 123, 52 | 53 | '`': 192, 54 | '=': 187, 55 | '-': 189, 56 | 57 | '/': 191, 58 | '.': 190 59 | }; 60 | 61 | // 小写适配 62 | for (var key in keymap) { 63 | if (keymap.hasOwnProperty(key)) { 64 | keymap[key.toLowerCase()] = keymap[key]; 65 | } 66 | } 67 | var aKeyCode = 65; 68 | var aCharCode = 'a'.charCodeAt(0); 69 | 70 | // letters 71 | 'abcdefghijklmnopqrstuvwxyz'.split('').forEach(function(letter) { 72 | keymap[letter] = aKeyCode + (letter.charCodeAt(0) - aCharCode); 73 | }); 74 | 75 | // numbers 76 | var n = 9; 77 | do { 78 | keymap[n.toString()] = n + 48; 79 | } while (--n); 80 | 81 | module.exports = keymap; 82 | }); -------------------------------------------------------------------------------- /ui/dialog/hyperlink/hyperlink.ctrl.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .controller('hyperlink.ctrl', function ($scope, $modalInstance, link) { 3 | 4 | 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]*)?$'; 5 | $scope.R_URL = new RegExp(urlRegex, 'i'); 6 | 7 | $scope.url = link.url || ''; 8 | $scope.title = link.title || ''; 9 | 10 | setTimeout(function() { 11 | var $linkUrl = $('#link-url'); 12 | $linkUrl.focus(); 13 | $linkUrl[0].setSelectionRange(0, $scope.url.length); 14 | }, 30); 15 | 16 | $scope.shortCut = function(e) { 17 | e.stopPropagation(); 18 | 19 | if (e.keyCode == 13) { 20 | $scope.ok(); 21 | } else if (e.keyCode == 27) { 22 | $scope.cancel(); 23 | } 24 | }; 25 | 26 | $scope.ok = function () { 27 | if($scope.R_URL.test($scope.url)) { 28 | $modalInstance.close({ 29 | url: $scope.url, 30 | title: $scope.title 31 | }); 32 | } else { 33 | $scope.urlPassed = false; 34 | 35 | var $linkUrl = $('#link-url'); 36 | $linkUrl.focus(); 37 | $linkUrl[0].setSelectionRange(0, $scope.url.length); 38 | } 39 | editor.receiver.selectAll(); 40 | }; 41 | 42 | $scope.cancel = function () { 43 | $modalInstance.dismiss('cancel'); 44 | editor.receiver.selectAll(); 45 | }; 46 | 47 | }); -------------------------------------------------------------------------------- /ui/dialog/hyperlink/hyperlink.tpl.html: -------------------------------------------------------------------------------- 1 | 4 | 23 | -------------------------------------------------------------------------------- /ui/dialog/imExportNode/imExportNode.ctrl.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .controller('imExportNode.ctrl', function ($scope, $modalInstance, title, defaultValue, type) { 3 | 4 | $scope.title = title; 5 | 6 | $scope.value = defaultValue; 7 | 8 | $scope.type = type; 9 | 10 | $scope.ok = function () { 11 | if ($scope.value == '') { 12 | return; 13 | } 14 | $modalInstance.close($scope.value); 15 | editor.receiver.selectAll(); 16 | }; 17 | 18 | $scope.cancel = function () { 19 | $modalInstance.dismiss('cancel'); 20 | editor.receiver.selectAll(); 21 | }; 22 | 23 | setTimeout(function() { 24 | $('.single-input').focus(); 25 | 26 | $('.single-input')[0].setSelectionRange(0, defaultValue.length); 27 | 28 | }, 30); 29 | 30 | $scope.shortCut = function(e) { 31 | e.stopPropagation(); 32 | 33 | //if (e.keyCode == 13 && e.shiftKey == false) { 34 | // $scope.ok(); 35 | //} 36 | 37 | if (e.keyCode == 27) { 38 | $scope.cancel(); 39 | } 40 | 41 | // tab 键屏蔽默认事件 和 backspace 键屏蔽默认事件 42 | if (e.keyCode == 8 && type == 'export') { 43 | e.preventDefault(); 44 | } 45 | 46 | if (e.keyCode == 9) { 47 | e.preventDefault(); 48 | var $textarea = e.target; 49 | var pos = getCursortPosition($textarea); 50 | var str = $textarea.value; 51 | $textarea.value = str.substr(0, pos) + '\t' + str.substr(pos); 52 | setCaretPosition($textarea, pos + 1); 53 | } 54 | 55 | }; 56 | 57 | /* 58 | * 获取 textarea 的光标位置 59 | * @Author: Naixor 60 | * @date: 2015.09.23 61 | * */ 62 | function getCursortPosition (ctrl) { 63 | var CaretPos = 0; // IE Support 64 | if (document.selection) { 65 | ctrl.focus (); 66 | var Sel = document.selection.createRange (); 67 | Sel.moveStart ('character', -ctrl.value.length); 68 | CaretPos = Sel.text.length; 69 | } 70 | // Firefox support 71 | else if (ctrl.selectionStart || ctrl.selectionStart == '0') { 72 | CaretPos = ctrl.selectionStart; 73 | } 74 | return (CaretPos); 75 | } 76 | 77 | /* 78 | * 设置 textarea 的光标位置 79 | * @Author: Naixor 80 | * @date: 2015.09.23 81 | * */ 82 | function setCaretPosition(ctrl, pos){ 83 | if(ctrl.setSelectionRange) { 84 | ctrl.focus(); 85 | ctrl.setSelectionRange(pos,pos); 86 | } else if (ctrl.createTextRange) { 87 | var range = ctrl.createTextRange(); 88 | range.collapse(true); 89 | range.moveEnd('character', pos); 90 | range.moveStart('character', pos); 91 | range.select(); 92 | } 93 | } 94 | 95 | }); -------------------------------------------------------------------------------- /ui/dialog/imExportNode/imExportNode.tpl.html: -------------------------------------------------------------------------------- 1 | 4 | 13 | -------------------------------------------------------------------------------- /ui/dialog/image/image.ctrl.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .controller('image.ctrl', ['$http', '$scope', '$modalInstance', 'image', 'server', function ($http, $scope, $modalInstance, image, server) { 3 | $scope.searchImgLink = document.createElement('a'); 4 | $scope.searchImgLink.target = "_blank"; 5 | $scope.searchImgLink.style.display = "none"; 6 | $('body').append($scope.searchImgLink); 7 | $scope.data = { 8 | list: [], 9 | url: image.url || '', 10 | title: image.title || '', 11 | R_URL: /^https?\:\/\/\w+/ 12 | }; 13 | 14 | setTimeout(function () { 15 | var $imageUrl = $('#image-url'); 16 | $imageUrl.focus(); 17 | $imageUrl[0].setSelectionRange(0, $scope.data.url.length); 18 | }, 300); 19 | 20 | 21 | // 搜索图片按钮点击事件 22 | $scope.searchImage = function (type) { 23 | var key = $scope.data.searchKeyword2; 24 | if (type == 'adoutu') { 25 | $scope.searchImgLink.href = "http://www.adoutu.com/search?keyword=" + key; 26 | } else { 27 | $scope.searchImgLink.href = "https://cn.bing.com/images/search?q=" + key; 28 | } 29 | $scope.searchImgLink.click(); 30 | // $scope.list = []; 31 | 32 | // getImageData() 33 | // .success(function (json) { 34 | // console.log(json); 35 | 36 | // if (json && json.data) { 37 | // for (var i = 0; i < json.data.length; i++) { 38 | // if (json.data[i].objURL) { 39 | // $scope.list.push({ 40 | // title: json.data[i].fromPageTitleEnc, 41 | // src: json.data[i].middleURL, 42 | // url: json.data[i].middleURL 43 | // }); 44 | // } 45 | // } 46 | // } 47 | // }) 48 | // .error(function () { 49 | 50 | // }); 51 | }; 52 | 53 | // 选择图片的鼠标点击事件 54 | $scope.selectImage = function ($event) { 55 | var targetItem = $('#img-item' + (this.$index)); 56 | var targetImg = $('#img-' + (this.$index)); 57 | 58 | targetItem.siblings('.selected').removeClass('selected'); 59 | targetItem.addClass('selected'); 60 | 61 | $scope.data.url = targetImg.attr('src'); 62 | $scope.data.title = targetImg.attr('alt'); 63 | }; 64 | 65 | // 自动上传图片,后端需要直接返回图片 URL 66 | $scope.uploadImage = function () { 67 | var fileInput = $('#upload-image'); 68 | if (!fileInput.val()) { 69 | return; 70 | } 71 | if (/^.*\.(jpg|JPG|jpeg|JPEG|gif|GIF|png|PNG)$/.test(fileInput.val())) { 72 | var file = fileInput[0].files[0]; 73 | return server.uploadImage(file).then(function (json) { 74 | var resp = json.data; 75 | if (resp.errno === 0) { 76 | $scope.data.url = resp.data.url; 77 | } 78 | }); 79 | } else { 80 | alert("后缀只能是 jpg、gif 及 png"); 81 | } 82 | }; 83 | 84 | $scope.shortCut = function (e) { 85 | e.stopPropagation(); 86 | 87 | if (e.keyCode == 13) { 88 | $scope.ok(); 89 | } else if (e.keyCode == 27) { 90 | $scope.cancel(); 91 | } 92 | }; 93 | 94 | $scope.ok = function () { 95 | if ($scope.data.R_URL.test($scope.data.url)) { 96 | $modalInstance.close({ 97 | url: $scope.data.url, 98 | title: $scope.data.title 99 | }); 100 | } else { 101 | $scope.urlPassed = false; 102 | 103 | var $imageUrl = $('#image-url'); 104 | if ($imageUrl) { 105 | $imageUrl.focus(); 106 | $imageUrl[0].setSelectionRange(0, $scope.data.url.length); 107 | } 108 | 109 | } 110 | 111 | editor.receiver.selectAll(); 112 | }; 113 | 114 | $scope.cancel = function () { 115 | $modalInstance.dismiss('cancel'); 116 | editor.receiver.selectAll(); 117 | }; 118 | 119 | function getImageData() { 120 | var key = $scope.data.searchKeyword2; 121 | var currentTime = new Date(); 122 | 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(); 123 | 124 | return $http.get(url); 125 | 126 | } 127 | }]); -------------------------------------------------------------------------------- /ui/dialog/image/image.tpl.html: -------------------------------------------------------------------------------- 1 | 4 | 77 | -------------------------------------------------------------------------------- /ui/directive/appendNode/appendNode.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('appendNode', ['commandBinder', function(commandBinder) { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/appendNode/appendNode.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | var minder = $scope.minder; 12 | 13 | commandBinder.bind(minder, 'appendchildnode', $scope) 14 | 15 | $scope.execCommand = function(command) { 16 | minder.execCommand(command, '分支主题'); 17 | editText(); 18 | }; 19 | 20 | function editText() { 21 | var receiverElement = editor.receiver.element; 22 | var fsm = editor.fsm; 23 | var receiver = editor.receiver; 24 | 25 | receiverElement.innerText = minder.queryCommandValue('text'); 26 | fsm.jump('input', 'input-request'); 27 | receiver.selectAll(); 28 | } 29 | } 30 | } 31 | }]); -------------------------------------------------------------------------------- /ui/directive/appendNode/appendNode.html: -------------------------------------------------------------------------------- 1 |
2 |
6 | 7 | {{ 'appendchildnode' | lang:'ui/command' }} 8 |
9 |
13 | 14 | {{ 'appendparentnode' | lang:'ui/command' }} 15 |
16 |
20 | 21 | {{ 'appendsiblingnode' | lang:'ui/command' }} 22 |
23 |
-------------------------------------------------------------------------------- /ui/directive/arrange/arrange.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('arrange', ['commandBinder', function(commandBinder) { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/arrange/arrange.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | var minder = $scope.minder; 12 | 13 | //commandBinder.bind(minder, 'priority', $scope); 14 | } 15 | } 16 | }]); -------------------------------------------------------------------------------- /ui/directive/arrange/arrange.html: -------------------------------------------------------------------------------- 1 |
2 |
6 | 7 | {{ 'arrangeup' | lang:'ui/command' }} 8 |
9 |
13 | 14 | {{ 'arrangedown' | lang:'ui/command' }} 15 |
16 |
-------------------------------------------------------------------------------- /ui/directive/colorPanel/colorPanel.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('colorPanel', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/colorPanel/colorPanel.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function(scope) { 11 | 12 | var minder = scope.minder; 13 | var currentTheme = minder.getThemeItems(); 14 | 15 | scope.$on('colorPicked', function(event, color) { 16 | event.stopPropagation(); 17 | scope.bgColor = color; 18 | minder.execCommand('background', color); 19 | }); 20 | 21 | scope.setDefaultBg = function() { 22 | var currentNode = minder.getSelectedNode(); 23 | var bgColor = minder.getNodeStyle(currentNode, 'background'); 24 | 25 | // 有可能是 kity 的颜色类 26 | return typeof bgColor === 'object' ? bgColor.toHEX() : bgColor; 27 | }; 28 | 29 | scope.bgColor = scope.setDefaultBg() || '#fff'; 30 | 31 | } 32 | } 33 | }); -------------------------------------------------------------------------------- /ui/directive/colorPanel/colorPanel.html: -------------------------------------------------------------------------------- 1 |
2 | 5 | 9 | 10 | 11 | 15 | 16 |
-------------------------------------------------------------------------------- /ui/directive/expandLevel/expandLevel.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('expandLevel', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/expandLevel/expandLevel.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | 12 | $scope.levels = [1, 2, 3, 4, 5, 6]; 13 | } 14 | } 15 | }); -------------------------------------------------------------------------------- /ui/directive/expandLevel/expandLevel.html: -------------------------------------------------------------------------------- 1 |
2 | 4 | 10 | 16 |
-------------------------------------------------------------------------------- /ui/directive/fontOperator/fontOperator.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('fontOperator', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/fontOperator/fontOperator.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function(scope) { 11 | var minder = scope.minder; 12 | var currentTheme = minder.getThemeItems(); 13 | 14 | scope.fontSizeList = [10, 12, 16, 18, 24, 32, 48]; 15 | scope.fontFamilyList = [{ 16 | name: '宋体', 17 | val: '宋体,SimSun' 18 | }, { 19 | name: '微软雅黑', 20 | val: '微软雅黑,Microsoft YaHei' 21 | }, { 22 | name: '楷体', 23 | val: '楷体,楷体_GB2312,SimKai' 24 | }, { 25 | name: '黑体', 26 | val: '黑体, SimHei' 27 | }, { 28 | name: '隶书', 29 | val: '隶书, SimLi' 30 | }, { 31 | name: 'Andale Mono', 32 | val: 'andale mono' 33 | }, { 34 | name: 'Arial', 35 | val: 'arial,helvetica,sans-serif' 36 | }, { 37 | name: 'arialBlack', 38 | val: 'arial black,avant garde' 39 | }, { 40 | name: 'Comic Sans Ms', 41 | val: 'comic sans ms' 42 | }, { 43 | name: 'Impact', 44 | val: 'impact,chicago' 45 | }, { 46 | name: 'Times New Roman', 47 | val: 'times new roman' 48 | }, { 49 | name: 'Sans-Serif', 50 | val: 'sans-serif' 51 | }]; 52 | 53 | scope.$on('colorPicked', function(event, color) { 54 | event.stopPropagation(); 55 | 56 | scope.foreColor = color; 57 | minder.execCommand('forecolor', color); 58 | }); 59 | 60 | scope.setDefaultColor = function() { 61 | var currentNode = minder.getSelectedNode(); 62 | var fontColor = minder.getNodeStyle(currentNode, 'color'); 63 | 64 | // 有可能是 kity 的颜色类 65 | return typeof fontColor === 'object' ? fontColor.toHEX() : fontColor; 66 | }; 67 | 68 | scope.foreColor = scope.setDefaultColor() || '#000'; 69 | 70 | scope.getFontfamilyName = function(val) { 71 | var fontName = ''; 72 | scope.fontFamilyList.forEach(function(ele, idx, arr) { 73 | if (ele.val === val) { 74 | fontName = ele.name; 75 | return ''; 76 | } 77 | }); 78 | 79 | return fontName; 80 | } 81 | } 82 | } 83 | }); -------------------------------------------------------------------------------- /ui/directive/fontOperator/fontOperator.html: -------------------------------------------------------------------------------- 1 |
2 | 13 | 24 | 28 | 32 | 33 |
34 | A 37 | 41 | 42 | 43 | 47 | 48 |
49 | 50 | 51 |
52 | -------------------------------------------------------------------------------- /ui/directive/hyperLink/hyperLink.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('hyperLink', ['$modal', function($modal) { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/hyperLink/hyperLink.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | var minder = $scope.minder; 12 | 13 | $scope.addHyperlink = function() { 14 | 15 | var link = minder.queryCommandValue('HyperLink'); 16 | 17 | var hyperlinkModal = $modal.open({ 18 | animation: true, 19 | templateUrl: 'ui/dialog/hyperlink/hyperlink.tpl.html', 20 | controller: 'hyperlink.ctrl', 21 | size: 'md', 22 | resolve: { 23 | link: function() { 24 | return link; 25 | } 26 | } 27 | }); 28 | 29 | hyperlinkModal.result.then(function(result) { 30 | minder.execCommand('HyperLink', result.url, result.title || ''); 31 | }); 32 | } 33 | } 34 | } 35 | }]); -------------------------------------------------------------------------------- /ui/directive/hyperLink/hyperLink.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | 17 | 27 |
-------------------------------------------------------------------------------- /ui/directive/imageBtn/imageBtn.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('imageBtn', ['$modal', function($modal) { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/imageBtn/imageBtn.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | var minder = $scope.minder; 12 | 13 | $scope.addImage = function() { 14 | 15 | var image = minder.queryCommandValue('image'); 16 | 17 | var imageModal = $modal.open({ 18 | animation: true, 19 | templateUrl: 'ui/dialog/image/image.tpl.html', 20 | controller: 'image.ctrl', 21 | size: 'md', 22 | resolve: { 23 | image: function() { 24 | return image; 25 | } 26 | } 27 | }); 28 | 29 | imageModal.result.then(function(result) { 30 | minder.execCommand('image', result.url, result.title || ''); 31 | }); 32 | } 33 | } 34 | } 35 | }]); -------------------------------------------------------------------------------- /ui/directive/imageBtn/imageBtn.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | 17 | 27 |
-------------------------------------------------------------------------------- /ui/directive/kityminderEditor/kityminderEditor.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('kityminderEditor', ['config', 'minder.service', 'revokeDialog', function(config, minderService, revokeDialog) { 3 | return { 4 | restrict: 'EA', 5 | templateUrl: 'ui/directive/kityminderEditor/kityminderEditor.html', 6 | replace: true, 7 | scope: { 8 | onInit: '&' 9 | }, 10 | link: function(scope, element, attributes) { 11 | 12 | var $minderEditor = element.children('.minder-editor')[0]; 13 | 14 | function onInit(editor, minder) { 15 | scope.onInit({ 16 | editor: editor, 17 | minder: minder 18 | }); 19 | 20 | minderService.executeCallback(); 21 | } 22 | 23 | if (typeof(seajs) != 'undefined') { 24 | /* global seajs */ 25 | seajs.config({ 26 | base: './src' 27 | }); 28 | 29 | define('demo', function(require) { 30 | var Editor = require('editor'); 31 | 32 | var editor = window.editor = new Editor($minderEditor); 33 | 34 | if (window.localStorage.__dev_minder_content) { 35 | editor.minder.importJson(JSON.parse(window.localStorage.__dev_minder_content)); 36 | } 37 | 38 | editor.minder.on('contentchange', function() { 39 | window.localStorage.__dev_minder_content = JSON.stringify(editor.minder.exportJson()); 40 | }); 41 | 42 | window.minder = window.km = editor.minder; 43 | 44 | scope.editor = editor; 45 | scope.minder = minder; 46 | scope.config = config.get(); 47 | 48 | //scope.minder.setDefaultOptions(scope.config); 49 | scope.$apply(); 50 | 51 | onInit(editor, minder); 52 | }); 53 | 54 | seajs.use('demo'); 55 | 56 | } else if (window.kityminder && window.kityminder.Editor) { 57 | var editor = new kityminder.Editor($minderEditor); 58 | 59 | window.editor = scope.editor = editor; 60 | window.minder = scope.minder = editor.minder; 61 | 62 | scope.config = config.get(); 63 | 64 | //scope.minder.setDefaultOptions(config.getConfig()); 65 | 66 | onInit(editor, editor.minder); 67 | } 68 | 69 | } 70 | } 71 | }]); -------------------------------------------------------------------------------- /ui/directive/kityminderEditor/kityminderEditor.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | 8 |
-------------------------------------------------------------------------------- /ui/directive/kityminderViewer/kityminderViewer.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('kityminderViewer', ['config', 'minder.service', function(config, minderService) { 3 | return { 4 | restrict: 'EA', 5 | templateUrl: 'ui/directive/kityminderViewer/kityminderViewer.html', 6 | replace: true, 7 | scope: { 8 | onInit: '&' 9 | }, 10 | link: function(scope, element, attributes) { 11 | 12 | var $minderEditor = element.children('.minder-viewer')[0]; 13 | 14 | function onInit(editor, minder) { 15 | scope.onInit({ 16 | editor: editor, 17 | minder: minder 18 | }); 19 | 20 | minderService.executeCallback(); 21 | } 22 | 23 | if (window.kityminder && window.kityminder.Editor) { 24 | var editor = new kityminder.Editor($minderEditor); 25 | 26 | window.editor = scope.editor = editor; 27 | window.minder = scope.minder = editor.minder; 28 | 29 | onInit(editor, editor.minder); 30 | } 31 | 32 | } 33 | } 34 | }]); -------------------------------------------------------------------------------- /ui/directive/kityminderViewer/kityminderViewer.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
-------------------------------------------------------------------------------- /ui/directive/layout/layout.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('layout', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/layout/layout.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function(scope) { 11 | 12 | } 13 | } 14 | }); -------------------------------------------------------------------------------- /ui/directive/layout/layout.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /ui/directive/navigator/navigator.html: -------------------------------------------------------------------------------- 1 | 39 | -------------------------------------------------------------------------------- /ui/directive/noteBtn/noteBtn.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('noteBtn', ['valueTransfer', function(valueTransfer) { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/noteBtn/noteBtn.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | var minder = $scope.minder; 12 | 13 | $scope.addNote =function() { 14 | valueTransfer.noteEditorOpen = true; 15 | }; 16 | } 17 | } 18 | }]); -------------------------------------------------------------------------------- /ui/directive/noteBtn/noteBtn.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | 17 | 27 |
-------------------------------------------------------------------------------- /ui/directive/noteEditor/noteEditor.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | 3 | .directive('noteEditor', ['valueTransfer', function(valueTransfer) { 4 | return { 5 | restrict: 'A', 6 | templateUrl: 'ui/directive/noteEditor/noteEditor.html', 7 | scope: { 8 | minder: '=' 9 | }, 10 | replace: true, 11 | controller: function($scope) { 12 | var minder = $scope.minder; 13 | var isInteracting = false; 14 | var cmEditor; 15 | 16 | $scope.codemirrorLoaded = function(_editor) { 17 | 18 | cmEditor = $scope.cmEditor = _editor; 19 | 20 | _editor.setSize('100%', '100%'); 21 | }; 22 | 23 | function updateNote() { 24 | var enabled = $scope.noteEnabled = minder.queryCommandState('note') != -1; 25 | var noteValue = minder.queryCommandValue('note') || ''; 26 | 27 | if (enabled) { 28 | $scope.noteContent = noteValue; 29 | } 30 | 31 | isInteracting = true; 32 | $scope.$apply(); 33 | isInteracting = false; 34 | } 35 | 36 | 37 | $scope.$watch('noteContent', function(content) { 38 | var enabled = minder.queryCommandState('note') != -1; 39 | 40 | if (content && enabled && !isInteracting) { 41 | minder.execCommand('note', content); 42 | } 43 | 44 | setTimeout(function() { 45 | cmEditor.refresh(); 46 | }); 47 | }); 48 | 49 | 50 | var noteEditorOpen = function() { 51 | return valueTransfer.noteEditorOpen; 52 | }; 53 | 54 | // 监听面板状态变量的改变 55 | $scope.$watch(noteEditorOpen, function(newVal, oldVal) { 56 | if (newVal) { 57 | setTimeout(function() { 58 | cmEditor.refresh(); 59 | cmEditor.focus(); 60 | }); 61 | } 62 | $scope.noteEditorOpen = valueTransfer.noteEditorOpen; 63 | }, true); 64 | 65 | 66 | $scope.closeNoteEditor = function() { 67 | valueTransfer.noteEditorOpen = false; 68 | editor.receiver.selectAll(); 69 | }; 70 | 71 | 72 | 73 | minder.on('interactchange', updateNote); 74 | } 75 | } 76 | }]); -------------------------------------------------------------------------------- /ui/directive/noteEditor/noteEditor.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

备注

4 | 支持 GFM 语法书写 5 | 6 |
7 |
8 |
19 |
20 |

21 | 请选择节点编辑备注 22 |

23 |
24 |
-------------------------------------------------------------------------------- /ui/directive/notePreviewer/notePreviewer.directive.js: -------------------------------------------------------------------------------- 1 | // TODO: 使用一个 div 容器作为 previewer,而不是两个 2 | angular.module('kityminderEditor') 3 | 4 | .directive('notePreviewer', ['$sce', 'valueTransfer', function($sce, valueTransfer) { 5 | return { 6 | restrict: 'A', 7 | templateUrl: 'ui/directive/notePreviewer/notePreviewer.html', 8 | link: function(scope, element) { 9 | var minder = scope.minder; 10 | var $container = element.parent(); 11 | var $previewer = element.children(); 12 | scope.showNotePreviewer = false; 13 | 14 | marked.setOptions({ 15 | gfm: true, 16 | tables: true, 17 | breaks: true, 18 | pedantic: false, 19 | sanitize: true, 20 | smartLists: true, 21 | smartypants: false 22 | }); 23 | 24 | 25 | var previewTimer; 26 | minder.on('shownoterequest', function(e) { 27 | 28 | previewTimer = setTimeout(function() { 29 | preview(e.node, e.keyword); 30 | }, 300); 31 | }); 32 | minder.on('hidenoterequest', function() { 33 | clearTimeout(previewTimer); 34 | 35 | scope.showNotePreviewer = false; 36 | //scope.$apply(); 37 | }); 38 | 39 | var previewLive = false; 40 | $(document).on('mousedown mousewheel DOMMouseScroll', function() { 41 | if (!previewLive) return; 42 | scope.showNotePreviewer = false; 43 | scope.$apply(); 44 | }); 45 | 46 | element.on('mousedown mousewheel DOMMouseScroll', function(e) { 47 | e.stopPropagation(); 48 | }); 49 | 50 | function preview(node, keyword) { 51 | var icon = node.getRenderer('NoteIconRenderer').getRenderShape(); 52 | var b = icon.getRenderBox('screen'); 53 | var note = node.getData('note'); 54 | 55 | $previewer[0].scrollTop = 0; 56 | 57 | var html = marked(note); 58 | if (keyword) { 59 | html = html.replace(new RegExp('(' + keyword + ')', 'ig'), '$1'); 60 | } 61 | scope.noteContent = $sce.trustAsHtml(html); 62 | scope.$apply(); // 让浏览器重新渲染以获取 previewer 提示框的尺寸 63 | 64 | var cw = $($container[0]).width(); 65 | var ch = $($container[0]).height(); 66 | var pw = $($previewer).outerWidth(); 67 | var ph = $($previewer).outerHeight(); 68 | 69 | var x = b.cx - pw / 2 - $container[0].offsetLeft; 70 | var y = b.bottom + 10 - $container[0].offsetTop; 71 | 72 | if (x < 0) x = 10; 73 | if (x + pw > cw) x = b.left - pw - 10 - $container[0].offsetLeft; 74 | if (y + ph > ch) y = b.top - ph - 10 - $container[0].offsetTop; 75 | 76 | 77 | scope.previewerStyle = { 78 | 'left': Math.round(x) + 'px', 79 | 'top': Math.round(y) + 'px' 80 | }; 81 | 82 | scope.showNotePreviewer = true; 83 | 84 | var view = $previewer[0].querySelector('.highlight'); 85 | if (view) { 86 | view.scrollIntoView(); 87 | } 88 | previewLive = true; 89 | 90 | scope.$apply(); 91 | } 92 | } 93 | } 94 | }]); -------------------------------------------------------------------------------- /ui/directive/notePreviewer/notePreviewer.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /ui/directive/operation/operation.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('operation', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/operation/operation.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | $scope.editNode = function() { 12 | 13 | var receiverElement = editor.receiver.element; 14 | var fsm = editor.fsm; 15 | var receiver = editor.receiver; 16 | 17 | receiverElement.innerText = minder.queryCommandValue('text'); 18 | fsm.jump('input', 'input-request'); 19 | receiver.selectAll(); 20 | 21 | } 22 | 23 | } 24 | } 25 | }); -------------------------------------------------------------------------------- /ui/directive/operation/operation.html: -------------------------------------------------------------------------------- 1 |
2 |
6 | 7 | {{ 'editnode' | lang:'ui/command' }} 8 |
9 |
13 | 14 | {{ 'removenode' | lang:'ui/command' }} 15 |
16 |
17 | -------------------------------------------------------------------------------- /ui/directive/other/other.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 5 | 7 | 23 |
24 | 28 | 32 |
33 | 35 | 37 | 39 | 41 | 42 |
43 | 45 | 49 |
50 | 51 | 53 | 54 |
55 | 56 | -------------------------------------------------------------------------------- /ui/directive/priorityEditor/priorityEditor.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | 3 | .directive('priorityEditor', ['commandBinder', function(commandBinder) { 4 | return { 5 | restrict: 'E', 6 | templateUrl: 'ui/directive/priorityEditor/priorityEditor.html', 7 | scope: { 8 | minder: '=' 9 | }, 10 | replace: true, 11 | link: function($scope) { 12 | var minder = $scope.minder; 13 | var priorities = []; 14 | 15 | for (var i = 0; i < 10; i++) { 16 | priorities.push(i); 17 | } 18 | 19 | commandBinder.bind(minder, 'priority', $scope); 20 | 21 | $scope.priorities = priorities; 22 | 23 | $scope.getPriorityTitle = function(p) { 24 | switch(p) { 25 | case 0: return '移除优先级'; 26 | default: return '优先级' + p; 27 | } 28 | } 29 | } 30 | 31 | } 32 | }]); -------------------------------------------------------------------------------- /ui/directive/priorityEditor/priorityEditor.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • 7 |
    8 |
  • 9 |
-------------------------------------------------------------------------------- /ui/directive/progressEditor/progressEditor.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('progressEditor', ['commandBinder', function(commandBinder) { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/progressEditor/progressEditor.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | var minder = $scope.minder; 12 | var progresses = []; 13 | 14 | for (var i = 0; i < 10; i++) { 15 | progresses.push(i); 16 | } 17 | 18 | commandBinder.bind(minder, 'progress', $scope); 19 | 20 | $scope.progresses = progresses; 21 | 22 | $scope.getProgressTitle = function(p) { 23 | switch(p) { 24 | case 0: return '移除进度'; 25 | case 1: return '未开始'; 26 | case 9: return '全部完成'; 27 | default: return '完成' + (p - 1) + '/8'; 28 | 29 | } 30 | } 31 | } 32 | } 33 | }]) -------------------------------------------------------------------------------- /ui/directive/progressEditor/progressEditor.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • 7 |
    8 |
  • 9 |
-------------------------------------------------------------------------------- /ui/directive/resourceEditor/resourceEditor.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('resourceEditor', function () { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/resourceEditor/resourceEditor.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | controller: function ($scope) { 11 | var minder = $scope.minder; 12 | 13 | var isInteracting = false; 14 | 15 | minder.on('interactchange', function () { 16 | var enabled = $scope.enabled = minder.queryCommandState('resource') != -1; 17 | var selected = enabled ? minder.queryCommandValue('resource') : []; 18 | var used = minder.getUsedResource().map(function (resourceName) { 19 | return { 20 | name: resourceName, 21 | selected: selected.indexOf(resourceName) > -1 22 | } 23 | }); 24 | $scope.used = used; 25 | 26 | isInteracting = true; 27 | $scope.$apply(); 28 | isInteracting = false; 29 | }); 30 | 31 | $scope.$watch('used', function (used) { 32 | if (minder.queryCommandState('resource') != -1 && used) { 33 | var resource = used.filter(function (resource) { 34 | return resource.selected; 35 | }).map(function (resource) { 36 | return resource.name; 37 | }); 38 | 39 | // 由于 interactchange 带来的改变则不用执行 resource 命令 40 | if (isInteracting) { 41 | return; 42 | } 43 | minder.execCommand('resource', resource); 44 | } 45 | }, true); 46 | 47 | $scope.resourceColor = function (resource) { 48 | return minder.getResourceColor(resource).toHEX(); 49 | }; 50 | 51 | $scope.addResource = function (resourceName) { 52 | var origin = minder.queryCommandValue('resource'); 53 | if (!resourceName || !/\S/.test(resourceName)) return; 54 | 55 | if (origin.indexOf(resourceName) == -1) { 56 | $scope.used.push({ 57 | name: resourceName, 58 | selected: true 59 | }); 60 | } 61 | 62 | $scope.newResourceName = null; 63 | }; 64 | 65 | } 66 | }; 67 | }) 68 | 69 | .directive('clickAnywhereButHere', ['$document', function ($document) { 70 | return { 71 | link: function(scope, element, attrs) { 72 | var onClick = function (event) { 73 | var isChild = $('#resource-dropdown').has(event.target).length > 0; 74 | var isSelf = $('#resource-dropdown') == event.target; 75 | var isInside = isChild || isSelf; 76 | if (!isInside) { 77 | scope.$apply(attrs.clickAnywhereButHere) 78 | } 79 | }; 80 | 81 | scope.$watch(attrs.isActive, function(newValue, oldValue) { 82 | if (newValue !== oldValue && newValue == true) { 83 | $document.bind('click', onClick); 84 | } 85 | else if (newValue !== oldValue && newValue == false) { 86 | $document.unbind('click', onClick); 87 | } 88 | }); 89 | } 90 | }; 91 | }]); -------------------------------------------------------------------------------- /ui/directive/resourceEditor/resourceEditor.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 7 | 8 | 9 | 10 |
11 |
12 |
    15 |
  • 16 | 20 |
  • 21 |
22 |
26 | 27 |
28 |
29 |
30 | -------------------------------------------------------------------------------- /ui/directive/searchBox/searchBox.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/directive/searchBtn/searchBtn.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('searchBtn', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/searchBtn/searchBtn.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function (scope) { 11 | scope.enterSearch = enterSearch; 12 | 13 | function enterSearch() { 14 | minder.fire('searchNode'); 15 | } 16 | } 17 | } 18 | }); -------------------------------------------------------------------------------- /ui/directive/searchBtn/searchBtn.html: -------------------------------------------------------------------------------- 1 |
2 | 7 | 14 |
-------------------------------------------------------------------------------- /ui/directive/selectAll/selectAll.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('selectAll', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/selectAll/selectAll.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | var minder = $scope.minder; 12 | 13 | $scope.items = ['revert', 'siblings', 'level', 'path', 'tree']; 14 | 15 | $scope.select = { 16 | all: function() { 17 | var selection = []; 18 | minder.getRoot().traverse(function(node) { 19 | selection.push(node); 20 | }); 21 | minder.select(selection, true); 22 | minder.fire('receiverfocus'); 23 | }, 24 | revert: function() { 25 | var selected = minder.getSelectedNodes(); 26 | var selection = []; 27 | minder.getRoot().traverse(function(node) { 28 | if (selected.indexOf(node) == -1) { 29 | selection.push(node); 30 | } 31 | }); 32 | minder.select(selection, true); 33 | minder.fire('receiverfocus'); 34 | }, 35 | siblings: function() { 36 | var selected = minder.getSelectedNodes(); 37 | var selection = []; 38 | selected.forEach(function(node) { 39 | if (!node.parent) return; 40 | node.parent.children.forEach(function(sibling) { 41 | if (selection.indexOf(sibling) == -1) selection.push(sibling); 42 | }); 43 | }); 44 | minder.select(selection, true); 45 | minder.fire('receiverfocus'); 46 | }, 47 | level: function() { 48 | var selectedLevel = minder.getSelectedNodes().map(function(node) { 49 | return node.getLevel(); 50 | }); 51 | var selection = []; 52 | minder.getRoot().traverse(function(node) { 53 | if (selectedLevel.indexOf(node.getLevel()) != -1) { 54 | selection.push(node); 55 | } 56 | }); 57 | minder.select(selection, true); 58 | minder.fire('receiverfocus'); 59 | }, 60 | path: function() { 61 | var selected = minder.getSelectedNodes(); 62 | var selection = []; 63 | selected.forEach(function(node) { 64 | while(node && selection.indexOf(node) == -1) { 65 | selection.push(node); 66 | node = node.parent; 67 | } 68 | }); 69 | minder.select(selection, true); 70 | minder.fire('receiverfocus'); 71 | }, 72 | tree: function() { 73 | var selected = minder.getSelectedNodes(); 74 | var selection = []; 75 | selected.forEach(function(parent) { 76 | parent.traverse(function(node) { 77 | if (selection.indexOf(node) == -1) selection.push(node); 78 | }); 79 | }); 80 | minder.select(selection, true); 81 | minder.fire('receiverfocus'); 82 | } 83 | }; 84 | } 85 | } 86 | }); -------------------------------------------------------------------------------- /ui/directive/selectAll/selectAll.html: -------------------------------------------------------------------------------- 1 |
2 | 7 | 15 | 21 |
-------------------------------------------------------------------------------- /ui/directive/styleOperator/styleOperator.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('styleOperator', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/styleOperator/styleOperator.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true 10 | } 11 | }); -------------------------------------------------------------------------------- /ui/directive/styleOperator/styleOperator.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/directive/templateList/templateList.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('templateList', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/templateList/templateList.html', 6 | scope: { 7 | minder: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | $scope.templateList = kityminder.Minder.getTemplateList(); 12 | 13 | } 14 | } 15 | }); -------------------------------------------------------------------------------- /ui/directive/templateList/templateList.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/directive/themeList/themeList.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('themeList', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/themeList/themeList.html', 6 | replace: true, 7 | link: function($scope) { 8 | var themeList = kityminder.Minder.getThemeList(); 9 | 10 | //$scope.themeList = themeList; 11 | 12 | $scope.getThemeThumbStyle = function (theme) { 13 | var themeObj = themeList[theme]; 14 | if (!themeObj) { 15 | return; 16 | } 17 | var style = { 18 | 'color': themeObj['root-color'], 19 | 'border-radius': themeObj['root-radius'] / 2 20 | }; 21 | 22 | if (themeObj['root-background']) { 23 | style['background'] = themeObj['root-background'].toString(); 24 | } 25 | 26 | return style; 27 | }; 28 | 29 | // 维护 theme key 列表以保证列表美观(不按字母顺序排序) 30 | $scope.themeKeyList = [ 31 | 'classic', 32 | 'classic-compact', 33 | 'fresh-blue', 34 | 'fresh-blue-compat', 35 | 'fresh-green', 36 | 'fresh-green-compat', 37 | 'fresh-pink', 38 | 'fresh-pink-compat', 39 | 'fresh-purple', 40 | 'fresh-purple-compat', 41 | 'fresh-red', 42 | 'fresh-red-compat', 43 | 'fresh-soil', 44 | 'fresh-soil-compat', 45 | 'snow', 46 | 'snow-compact', 47 | 'tianpan', 48 | 'tianpan-compact', 49 | 'fish', 50 | 'wire' 51 | ]; 52 | } 53 | } 54 | }); -------------------------------------------------------------------------------- /ui/directive/themeList/themeList.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ui/directive/topTab/topTab.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('topTab', function () { 3 | return { 4 | restrict: 'A', 5 | templateUrl: 'ui/directive/topTab/topTab.html', 6 | scope: { 7 | minder: '=topTab', 8 | editor: '=' 9 | }, 10 | link: function (scope) { 11 | 12 | /* 13 | * 14 | * 用户选择一个新的选项卡会执行 setCurTab 和 foldTopTab 两个函数 15 | * 用户点击原来的选项卡会执行 foldTopTop 一个函数 16 | * 17 | * 也就是每次选择新的选项卡都会执行 setCurTab,初始化的时候也会执行 setCurTab 函数 18 | * 因此用 executedCurTab 记录是否已经执行了 setCurTab 函数 19 | * 用 isInit 记录是否是初始化的状态,在任意一个函数时候 isInit 设置为 false 20 | * 用 isOpen 记录是否打开了 topTab 21 | * 22 | * 因此用到了三个 mutex 23 | * */ 24 | var executedCurTab = false; 25 | var isInit = true; 26 | var isOpen = true; 27 | 28 | scope.setCurTab = function (tabName) { 29 | setTimeout(function () { 30 | //console.log('set cur tab to : ' + tabName); 31 | executedCurTab = true; 32 | //isOpen = false; 33 | if (tabName != 'idea') { 34 | isInit = false; 35 | } 36 | }); 37 | }; 38 | 39 | scope.toggleTopTab = function () { 40 | setTimeout(function () { 41 | if (!executedCurTab || isInit) { 42 | isInit = false; 43 | 44 | isOpen ? closeTopTab() : openTopTab(); 45 | isOpen = !isOpen; 46 | } 47 | 48 | executedCurTab = false; 49 | }); 50 | }; 51 | 52 | //只读和可编辑 53 | minder.readOnly = function () { 54 | if (!this.isReadonly) { 55 | this.fire('readonly'); 56 | this.isReadonly = true; 57 | scope.$$childHead.tabs[3].active = true; 58 | } 59 | }; 60 | minder.editable = function () { 61 | if (this.isReadonly) { 62 | if (minder.isRemote) { 63 | toastr.info("远程数据请下载到本地然后加载方可编辑!"); 64 | return; 65 | } 66 | editor.container.appendChild(editor.receiver.element) 67 | editor.hotbox.$container.appendChild(editor.hotbox.$element); 68 | this.enable(); 69 | this.setStatus("normal", true); 70 | this.isReadonly = false; 71 | scope.$$childHead.tabs[0].active = true; 72 | 73 | } 74 | }; 75 | 76 | function closeTopTab() { 77 | var $tabContent = $('.tab-content'); 78 | var $minderEditor = $('.minder-editor'); 79 | 80 | $tabContent.animate({ 81 | height: 0, 82 | display: 'none' 83 | }); 84 | 85 | $minderEditor.animate({ 86 | top: '32px' 87 | }); 88 | } 89 | 90 | function openTopTab() { 91 | var $tabContent = $('.tab-content'); 92 | var $minderEditor = $('.minder-editor'); 93 | 94 | $tabContent.animate({ 95 | height: '60px', 96 | display: 'block' 97 | }); 98 | 99 | $minderEditor.animate({ 100 | top: '92px' 101 | }); 102 | } 103 | } 104 | } 105 | }); -------------------------------------------------------------------------------- /ui/directive/topTab/topTab.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 30 |
31 |
32 | 33 | 35 | 38 | 39 | 40 |
-------------------------------------------------------------------------------- /ui/directive/undoRedo/undoRedo.directive.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .directive('undoRedo', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: 'ui/directive/undoRedo/undoRedo.html', 6 | scope: { 7 | editor: '=' 8 | }, 9 | replace: true, 10 | link: function($scope) { 11 | 12 | } 13 | } 14 | }); -------------------------------------------------------------------------------- /ui/directive/undoRedo/undoRedo.html: -------------------------------------------------------------------------------- 1 |
2 |
6 | 7 | 8 |
9 |
13 | 14 | 15 |
16 |
-------------------------------------------------------------------------------- /ui/filter/command.filters.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .filter('commandState', function() { 3 | return function(minder, command) { 4 | return minder.queryCommandState(command); 5 | } 6 | }) 7 | .filter('commandValue', function() { 8 | return function(minder, command) { 9 | return minder.queryCommandValue(command); 10 | } 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /ui/filter/lang.filter.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .filter('lang', ['config', 'lang.zh-cn', function(config, lang) { 3 | return function(text, block) { 4 | var defaultLang = config.get('defaultLang'); 5 | 6 | if (lang[defaultLang] == undefined) { 7 | return '未发现对应语言包,请检查 lang.xxx.service.js!'; 8 | } else { 9 | 10 | var dict = lang[defaultLang]; 11 | block.split('/').forEach(function(ele, idx) { 12 | dict = dict[ele]; 13 | }); 14 | 15 | return dict[text] || null; 16 | } 17 | 18 | }; 19 | }]); -------------------------------------------------------------------------------- /ui/images/iconpriority.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudlandboy/kityminder-editor/38425bb672d6c8dd160ec60248c173e57de0530c/ui/images/iconpriority.png -------------------------------------------------------------------------------- /ui/images/iconprogress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudlandboy/kityminder-editor/38425bb672d6c8dd160ec60248c173e57de0530c/ui/images/iconprogress.png -------------------------------------------------------------------------------- /ui/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudlandboy/kityminder-editor/38425bb672d6c8dd160ec60248c173e57de0530c/ui/images/icons.png -------------------------------------------------------------------------------- /ui/images/template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudlandboy/kityminder-editor/38425bb672d6c8dd160ec60248c173e57de0530c/ui/images/template.png -------------------------------------------------------------------------------- /ui/kityminder.app.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor', [ 2 | 'ui.bootstrap', 3 | 'ui.codemirror', 4 | 'ui.colorpicker' 5 | ]) 6 | .config(function($sceDelegateProvider) { 7 | $sceDelegateProvider.resourceUrlWhitelist([ 8 | // Allow same origin resource loads. 9 | 'self', 10 | // Allow loading from our assets domain. Notice the difference between * and **. 11 | 'http://agroup.baidu.com:8910/**', 12 | 'http://cq01-fe-rdtest01.vm.baidu.com:8910/**', 13 | 'http://agroup.baidu.com:8911/**' 14 | ]); 15 | }); -------------------------------------------------------------------------------- /ui/service/commandBinder.service.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor').service('commandBinder', function() { 2 | return { 3 | bind: function(minder, command, scope) { 4 | 5 | minder.on('interactchange', function() { 6 | scope.commandDisabled = minder.queryCommandState(command) === -1; 7 | scope.commandValue = minder.queryCommandValue(command); 8 | scope.$apply(); 9 | }); 10 | } 11 | }; 12 | }); -------------------------------------------------------------------------------- /ui/service/config.service.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .provider('config', function() { 3 | 4 | this.config = { 5 | // 右侧面板最小宽度 6 | ctrlPanelMin: 250, 7 | 8 | // 右侧面板宽度 9 | ctrlPanelWidth: parseInt(window.localStorage.__dev_minder_ctrlPanelWidth) || 250, 10 | 11 | // 分割线宽度 12 | dividerWidth: 3, 13 | 14 | // 默认语言 15 | defaultLang: 'zh-cn', 16 | 17 | // 放大缩小比例 18 | zoom: [10, 20, 30, 50, 80, 100, 120, 150, 200], 19 | 20 | // 图片上传接口 21 | imageUpload: 'server/imageUpload.php' 22 | }; 23 | 24 | this.set = function(key, value) { 25 | var supported = Object.keys(this.config); 26 | var configObj = {}; 27 | 28 | // 支持全配置 29 | if (typeof key === 'object') { 30 | configObj = key; 31 | } 32 | else { 33 | configObj[key] = value; 34 | } 35 | 36 | for (var i in configObj) { 37 | if (configObj.hasOwnProperty(i) && supported.indexOf(i) !== -1) { 38 | this.config[i] = configObj[i]; 39 | } 40 | else { 41 | console.error('Unsupported config key: ', key, ', please choose in :', supported.join(', ')); 42 | return false; 43 | } 44 | } 45 | 46 | return true; 47 | }; 48 | 49 | this.$get = function () { 50 | var me = this; 51 | 52 | return { 53 | get: function (key) { 54 | if (arguments.length === 0) { 55 | return me.config; 56 | } 57 | 58 | if (me.config.hasOwnProperty(key)) { 59 | return me.config[key]; 60 | } 61 | 62 | console.warn('Missing config key pair for : ', key); 63 | return ''; 64 | } 65 | 66 | }; 67 | } 68 | }); -------------------------------------------------------------------------------- /ui/service/memory.service.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * UI 状态的 LocalStorage 的存取文件,未来可能在离线编辑的时候升级 5 | * 6 | * @author: zhangbobell 7 | * @email : zhangbobell@163.com 8 | * 9 | * @copyright: Baidu FEX, 2015 10 | */ 11 | angular.module('kityminderEditor') 12 | .service('memory', function() { 13 | 14 | function isQuotaExceeded(e) { 15 | var quotaExceeded = false; 16 | if (e) { 17 | if (e.code) { 18 | switch (e.code) { 19 | case 22: 20 | quotaExceeded = true; 21 | break; 22 | case 1014: 23 | // Firefox 24 | if (e.name === 'NS_ERROR_DOM_QUOTA_REACHED') { 25 | quotaExceeded = true; 26 | } 27 | break; 28 | } 29 | } else if (e.number === -2147024882) { 30 | // Internet Explorer 8 31 | quotaExceeded = true; 32 | } 33 | } 34 | return quotaExceeded; 35 | } 36 | 37 | return { 38 | get: function(key) { 39 | var value = window.localStorage.getItem(key); 40 | return null || JSON.parse(value); 41 | }, 42 | 43 | set: function(key, value) { 44 | try { 45 | window.localStorage.setItem(key, JSON.stringify(value)); 46 | return true; 47 | } catch(e) { 48 | if (isQuotaExceeded(e)) { 49 | return false; 50 | } 51 | } 52 | }, 53 | remove: function(key) { 54 | var value = window.localStorage.getItem(key); 55 | window.localStorage.removeItem(key); 56 | return value; 57 | }, 58 | clear: function() { 59 | window.localStorage.clear(); 60 | } 61 | } 62 | }); -------------------------------------------------------------------------------- /ui/service/minder.service.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .service('minder.service', function() { 3 | 4 | var callbackQueue = []; 5 | 6 | function registerEvent(callback) { 7 | callbackQueue.push(callback); 8 | } 9 | 10 | function executeCallback() { 11 | callbackQueue.forEach(function(ele) { 12 | ele.apply(this, arguments); 13 | }) 14 | } 15 | 16 | return { 17 | registerEvent: registerEvent, 18 | executeCallback: executeCallback 19 | } 20 | }); -------------------------------------------------------------------------------- /ui/service/resource.service.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .service('resourceService', ['$document', function($document) { 3 | var openScope = null; 4 | 5 | this.open = function( dropdownScope ) { 6 | if ( !openScope ) { 7 | $document.bind('click', closeDropdown); 8 | $document.bind('keydown', escapeKeyBind); 9 | } 10 | 11 | if ( openScope && openScope !== dropdownScope ) { 12 | openScope.resourceListOpen = false; 13 | } 14 | 15 | openScope = dropdownScope; 16 | }; 17 | 18 | this.close = function( dropdownScope ) { 19 | if ( openScope === dropdownScope ) { 20 | openScope = null; 21 | $document.unbind('click', closeDropdown); 22 | $document.unbind('keydown', escapeKeyBind); 23 | } 24 | }; 25 | 26 | var closeDropdown = function( evt ) { 27 | // This method may still be called during the same mouse event that 28 | // unbound this event handler. So check openScope before proceeding. 29 | //console.log(evt, openScope); 30 | if (!openScope) { return; } 31 | 32 | var toggleElement = openScope.getToggleElement(); 33 | if ( evt && toggleElement && toggleElement[0].contains(evt.target) ) { 34 | return; 35 | } 36 | 37 | openScope.$apply(function() { 38 | console.log('to close the resourcelist'); 39 | openScope.resourceListOpen = false; 40 | }); 41 | }; 42 | 43 | var escapeKeyBind = function( evt ) { 44 | if ( evt.which === 27 ) { 45 | openScope.focusToggleElement(); 46 | closeDropdown(); 47 | } 48 | }; 49 | }]) -------------------------------------------------------------------------------- /ui/service/revokeDialog.service.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor').service('revokeDialog', ['$modal', 'minder.service', function($modal, minderService) { 2 | 3 | minderService.registerEvent(function() { 4 | 5 | // 触发导入节点或导出节点对话框 6 | var minder = window.minder; 7 | var editor = window.editor; 8 | var parentFSM = editor.hotbox.getParentFSM(); 9 | 10 | 11 | minder.on('importNodeData', function() { 12 | parentFSM.jump('modal', 'import-text-modal'); 13 | 14 | var importModal = $modal.open({ 15 | animation: true, 16 | templateUrl: 'ui/dialog/imExportNode/imExportNode.tpl.html', 17 | controller: 'imExportNode.ctrl', 18 | size: 'md', 19 | resolve: { 20 | title: function() { 21 | return '导入节点'; 22 | }, 23 | defaultValue: function() { 24 | return ''; 25 | }, 26 | type: function() { 27 | return 'import'; 28 | } 29 | } 30 | }); 31 | 32 | importModal.result.then(function(result) { 33 | try{ 34 | minder.Text2Children(minder.getSelectedNode(), result); 35 | } catch(e) { 36 | alert(e); 37 | } 38 | parentFSM.jump('normal', 'import-text-finish'); 39 | editor.receiver.selectAll(); 40 | }, function() { 41 | parentFSM.jump('normal', 'import-text-finish'); 42 | editor.receiver.selectAll(); 43 | }); 44 | }); 45 | 46 | minder.on('exportNodeData', function() { 47 | parentFSM.jump('modal', 'export-text-modal'); 48 | 49 | var exportModal = $modal.open({ 50 | animation: true, 51 | templateUrl: 'ui/dialog/imExportNode/imExportNode.tpl.html', 52 | controller: 'imExportNode.ctrl', 53 | size: 'md', 54 | resolve: { 55 | title: function() { 56 | return '导出节点'; 57 | }, 58 | defaultValue: function() { 59 | var selectedNode = minder.getSelectedNode(), 60 | Node2Text = window.kityminder.data.getRegisterProtocol('text').Node2Text; 61 | 62 | return Node2Text(selectedNode); 63 | }, 64 | type: function() { 65 | return 'export'; 66 | } 67 | } 68 | }); 69 | 70 | exportModal.result.then(function(result) { 71 | parentFSM.jump('normal', 'export-text-finish'); 72 | editor.receiver.selectAll(); 73 | }, function() { 74 | parentFSM.jump('normal', 'export-text-finish'); 75 | editor.receiver.selectAll(); 76 | }); 77 | }); 78 | 79 | }); 80 | 81 | return {}; 82 | }]); -------------------------------------------------------------------------------- /ui/service/server.service.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview 3 | * 4 | * 与后端交互的服务 5 | * 6 | * @author: zhangbobell 7 | * @email : zhangbobell@163.com 8 | * 9 | * @copyright: Baidu FEX, 2015 10 | */ 11 | angular.module('kityminderEditor') 12 | .service('server', ['config', '$http', function(config, $http) { 13 | 14 | return { 15 | uploadImage: function(file) { 16 | var url = config.get('imageUpload'); 17 | var fd = new FormData(); 18 | fd.append('upload_file', file); 19 | 20 | return $http.post(url, fd, { 21 | transformRequest: angular.identity, 22 | headers: {'Content-Type': undefined} 23 | }); 24 | } 25 | } 26 | }]); -------------------------------------------------------------------------------- /ui/service/valueTransfer.service.js: -------------------------------------------------------------------------------- 1 | angular.module('kityminderEditor') 2 | .service('valueTransfer', function() { 3 | return {}; 4 | }); --------------------------------------------------------------------------------