├── .babelrc ├── .editorconfig ├── .eslintrc.json ├── .gitattributes ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── __test__ └── index.html ├── assets └── .gitkeep ├── build ├── build-style.js ├── webpack.dev.config.js ├── webpack.dist.prod.config.js └── webpack.docs.config.js ├── docs ├── css │ └── main.css ├── index.html ├── js │ ├── 1.chunk.js │ └── main.js ├── styles │ ├── images │ │ ├── iconpriority.png │ │ ├── iconprogress.png │ │ ├── icons.png │ │ └── template.png │ └── minder.css └── vendors.js ├── examples ├── app.vue ├── docs.html ├── index.html ├── main.js ├── routers │ └── editor.vue └── styles │ ├── images │ ├── iconpriority.png │ ├── iconprogress.png │ ├── icons.png │ └── template.png │ └── minder.css ├── package.json └── src ├── components ├── .gitkeep ├── breadcrumb │ └── index.vue ├── editor │ └── index.vue ├── icon │ ├── icons.js │ └── index.vue ├── navigator │ └── index.vue ├── search │ └── index.vue ├── template-list │ └── index.vue └── theme-list │ └── index.vue ├── directives ├── clickoutside.js └── visible.js ├── editor.js ├── expose-editor.js ├── filter └── lang.js ├── hotbox.js ├── index.js ├── lang.js ├── locale ├── format.js ├── index.js └── lang │ ├── en-US.js │ ├── minder │ └── zh-CN.js │ ├── zh-CN.js │ └── zh-TW.js ├── minder.js ├── mixins └── locale.js ├── module └── imageicon.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 ├── services ├── config.js └── memory.js ├── styles ├── README.md ├── _navigator.less ├── _tool_group.less ├── _vars.less ├── editor.less ├── imageDialog.less ├── images │ ├── iconpriority.png │ ├── iconprogress.png │ ├── icons.png │ └── template.png └── 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 │ ├── searchBox.less │ ├── topTab.less │ └── view │ ├── expand.less │ ├── search.less │ └── select.less ├── tool ├── debug.js ├── format.js ├── innertext.js ├── jsondiff.js ├── key.js └── keymap.js └── utils ├── assist.js ├── calcTextareaHeight.js ├── csv.js └── date.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": [ 7 | "> 1%", 8 | "last 2 versions", 9 | "not ie <= 20", 10 | "not ie_mob <= 100", 11 | "not ff <= 100", 12 | "not and_ff <= 100", 13 | "not Edge <= 100", 14 | "Android >= 4.0" 15 | ] 16 | } 17 | }], 18 | "stage-2" 19 | ], 20 | "plugins": ["transform-runtime", "add-module-exports", "transform-es2015-modules-umd"], 21 | "comments": false 22 | } 23 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://github.com/jiangtao/mydotfiles/blob/master/.config/.editorconfig 2 | root = true 3 | 4 | [*] 5 | 6 | # We recommend you to keep these unchanged 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = false 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 2 13 | [*.{html}] 14 | indent_size = 4 15 | 16 | 17 | [*.md] 18 | insert_final_newline = false 19 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parserOptions": { 4 | "ecmaVersion": 6, 5 | "sourceType": "module" 6 | }, 7 | "env": { 8 | "browser": true 9 | }, 10 | "extends": "eslint:recommended", 11 | "plugins": [ "html" ], 12 | "rules": { 13 | "no-undef":"warn", 14 | "no-mixed-spaces-and-tabs": "warn" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | src/styles/**/* linguist-vendored=false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 7 | 8 | 9 | ### 操作系统/浏览器 版本号 10 | 11 | 12 | ### Vue 版本号 13 | 14 | 15 | ### 能够复现问题的在线示例 16 | 17 | 18 | ### 复现步骤 19 | 20 | ### 问题现象,以及你期望的结果是怎样的? 21 | 22 | ### 你估计可能的原因是什么(选填)? 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | .ipr 4 | .iws 5 | *.diff 6 | *.patch 7 | *.bak 8 | .DS_Store 9 | node_modules/ 10 | node_modules2/ 11 | .project 12 | .settings 13 | npm-debug.log 14 | .*proj 15 | .svn/ 16 | *.swp 17 | *.swo 18 | *.log 19 | test/dist/ 20 | dist/ 21 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | *.md 3 | *.yml 4 | build/ 5 | node_modules/ 6 | test/ 7 | gulpfile.js 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | script: 5 | - npm run test 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue Minder 2 | 3 | 本组件主要内聚百度脑图的能力,使脑图可以便于在Vue生态下进行扩展。 4 | [DEMO地址](https://www.imjiangtao.com/vue-minder/#!/editor) 5 | 6 | --- 7 | 8 | Vue Minder 基于百度脑图核心代码[kityminder-core](https://github.com/fex-team/kityminder-core),主要满足在Vue项目中开发和使用。感谢[FEX Team](https://github.com/fex-team) 9 | 10 | ## 安装 11 | 12 | ``` 13 | yarn add vue-minder 14 | npm install vue-minder 15 | ``` 16 | 17 | ## 使用 18 | 19 | 20 | 21 | ```vue 22 | 30 | 31 | ``` 32 | 33 | ### props说明 34 | 35 | - show-search-box 是否显示搜索框 36 | - show-template 是否显示模板 37 | - show-theme 是否显示主题 38 | - show-navigator 是否显示导航器 39 | - enable 禁用或编辑模式 40 | - import-data 导入的数据 41 | 42 | [具体使用可参考](./examples) 43 | 44 | ## 与百度脑图不同之处 45 | 46 | - 数据中的字段 text 改为 name 47 | 48 | ## 开发和贡献 49 | 50 | 目前主要用于vue1.0项目中,感兴趣可升级为2.0版本,甚至以后3.0版本 51 | 52 | ## 版本规范 53 | 54 | ### 分支规范 55 | 56 | - master 为 vue1.0版本 57 | - 2.x 为 vue2.x版本 58 | - 3.x 为 vue3.x版本 59 | 60 | ### npm包规范 61 | 62 | - 2.x 为vue2.x版本 63 | 64 | ## 文件说明 65 | 66 | ``` 67 | ├── __test__ 测试dist包运行 68 | ├── assets 资源文件 69 | ├── build build脚本 70 | ├── dist 生产环境目录 71 | ├── docs 预览demo目录 72 | ├── examples 预览demo源码,便于本地开发 73 | ├── src 核心源代码 74 | ``` 75 | ## TODO 76 | - [ ] Support Vue2.7, Vue2.7作为过渡版本,相对来说较稳定,考虑支持 77 | -------------------------------------------------------------------------------- /__test__/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtao/vue-minder/b4cbaec72624f0fe761f7cc5286cec193cb3a571/assets/.gitkeep -------------------------------------------------------------------------------- /build/build-style.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var cleanCSS = require('gulp-clean-css'); 3 | var less = require('gulp-less'); 4 | var rename = require('gulp-rename'); 5 | var autoprefixer = require('gulp-autoprefixer'); 6 | 7 | // 编译less 8 | gulp.task('css', function () { 9 | gulp.src('../src/styles/editor.less') 10 | .pipe(less()) 11 | .pipe(autoprefixer({ 12 | browsers: ['last 2 versions', 'ie > 8'] 13 | })) 14 | .pipe(cleanCSS()) 15 | .pipe(rename('minder.css')) 16 | .pipe(gulp.dest('../dist/styles')); 17 | }); 18 | 19 | // 拷贝图片 20 | gulp.task('images', function () { 21 | gulp.src('../src/styles/images/*.*') 22 | .pipe(gulp.dest('../dist/styles/images')); 23 | }); 24 | 25 | gulp.task('default', ['css', 'images']); 26 | -------------------------------------------------------------------------------- /build/webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 本地预览 3 | */ 4 | 5 | var path = require('path'); 6 | var webpack = require('webpack'); 7 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 8 | var HtmlWebpackIncludeAssetsPlugin = require('html-webpack-include-assets-plugin'); 9 | 10 | 11 | module.exports = { 12 | // 入口 13 | entry: { 14 | main: './examples/main', 15 | vendors: ['vue', 'vue-router'] 16 | }, 17 | // 输出 18 | output: { 19 | path: path.join(__dirname, '../examples/docs'), 20 | publicPath: '/examples/docs', 21 | filename: '[name].js', 22 | chunkFilename: '[name].chunk.js' 23 | }, 24 | // 加载器 25 | module: { 26 | loaders: [ 27 | {test: /\.vue$/, loader: 'vue'}, 28 | {test: /\.js$/, loader: 'babel', exclude: /node_modules/}, 29 | {test: /\.js$/, loader: 'babel', include: /vue/}, 30 | {test: /\.css$/, loader: 'style!css!autoprefixer'}, 31 | {test: /\.less$/, loader: 'style!css!less'}, 32 | {test: /\.scss$/, loader: 'style!css!sass?sourceMap'}, 33 | {test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/, loader: 'url-loader?limit=8192'}, 34 | {test: /\.(html|tpl)$/, loader: 'html-loader'} 35 | ] 36 | }, 37 | vue: { 38 | loaders: { 39 | css: ExtractTextPlugin.extract( 40 | 'style-loader', 41 | 'css-loader?sourceMap', 42 | { 43 | publicPath: '/examples/docs' 44 | } 45 | ), 46 | less: ExtractTextPlugin.extract( 47 | 'vue-style-loader', 48 | 'css-loader!less-loader', 49 | { 50 | publicPath: '/examples/docs' 51 | } 52 | ), 53 | js: 'babel' 54 | } 55 | }, 56 | resolve: { 57 | // require时省略的扩展名,如:require('module') 不需要module.js 58 | extensions: ['', '.js', '.vue'], 59 | alias: { 60 | minder: '../../src/index' 61 | } 62 | }, 63 | plugins: [ 64 | new ExtractTextPlugin('[name].css', {allChunks: true, resolve: ['modules']}), // 提取CSS 65 | new webpack.optimize.CommonsChunkPlugin('vendors', 'vendors.js') 66 | ], 67 | devServer: { 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /build/webpack.dist.prod.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: { 6 | main: './src/index.js' 7 | }, 8 | output: { 9 | path: path.resolve(__dirname, '../dist'), 10 | publicPath: '/dist/', 11 | filename: 'minder.min.js', 12 | library: 'minder', 13 | libraryTarget: 'umd', 14 | umdNamedDefine: true 15 | }, 16 | externals: { 17 | vue: { 18 | root: 'Vue', 19 | commonjs: 'vue', 20 | commonjs2: 'vue', 21 | amd: 'vue' 22 | } 23 | }, 24 | resolve: { 25 | extensions: ['', '.js', '.vue'] 26 | }, 27 | module: { 28 | loaders: [{ 29 | test: /\.vue$/, 30 | loader: 'vue' 31 | }, { 32 | test: /\.js$/, 33 | loader: 'babel', 34 | exclude: /node_modules/ 35 | }, { 36 | test: /\.css$/, 37 | loader: 'style!css!autoprefixer' 38 | }, { 39 | test: /\.less$/, 40 | loader: 'style!css!less' 41 | }, { 42 | test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/, 43 | loader: 'url?limit=8192' 44 | }, { 45 | test: /\.(html|tpl)$/, 46 | loader: 'vue-html' 47 | }] 48 | }, 49 | plugins: [ 50 | new webpack.DefinePlugin({ 51 | 'process.env': { 52 | NODE_ENV: '"production"' 53 | } 54 | }), 55 | new webpack.optimize.UglifyJsPlugin({ 56 | compress: { 57 | warnings: false 58 | } 59 | }), 60 | new webpack.optimize.OccurenceOrderPlugin() 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /build/webpack.docs.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 本地预览 3 | */ 4 | 5 | var path = require('path'); 6 | var webpack = require('webpack'); 7 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 8 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 9 | 10 | module.exports = { 11 | // 入口 12 | entry: { 13 | main: './examples/main', 14 | vendors: ['vue', 'vue-router'] 15 | }, 16 | // 输出 17 | output: { 18 | path: path.join(__dirname, '../docs'), 19 | publicPath: '', 20 | filename: 'js/[name].js', 21 | chunkFilename: 'js/[name].chunk.js' 22 | }, 23 | // 加载器 24 | module: { 25 | loaders: [ 26 | {test: /\.vue$/, loader: 'vue'}, 27 | {test: /\.js$/, loader: 'babel', exclude: /node_modules/}, 28 | {test: /\.js$/, loader: 'babel', include: /vue/}, 29 | {test: /\.css$/, loader: 'style!css!autoprefixer'}, 30 | {test: /\.less$/, loader: 'style!css!less'}, 31 | {test: /\.scss$/, loader: 'style!css!sass?sourceMap'}, 32 | {test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/, loader: 'url-loader?limit=8192'}, 33 | {test: /\.(html|tpl)$/, loader: 'html-loader'} 34 | ] 35 | }, 36 | vue: { 37 | loaders: { 38 | css: ExtractTextPlugin.extract( 39 | 'style-loader', 40 | 'css-loader?sourceMap', 41 | { 42 | publicPath: '/examples/docs' 43 | } 44 | ), 45 | less: ExtractTextPlugin.extract( 46 | 'vue-style-loader', 47 | 'css-loader!less-loader', 48 | { 49 | publicPath: '/examples/docs' 50 | } 51 | ), 52 | js: 'babel' 53 | } 54 | }, 55 | resolve: { 56 | // require时省略的扩展名,如:require('module') 不需要module.js 57 | extensions: ['', '.js', '.vue'], 58 | alias: { 59 | minder: '../../src/index' 60 | } 61 | }, 62 | plugins: [ 63 | new ExtractTextPlugin('css/[name].css', {allChunks: true, resolve: ['modules']}), // 提取CSS 64 | new webpack.optimize.CommonsChunkPlugin('vendors', 'vendors.js'), 65 | new webpack.DefinePlugin({ 66 | 'process.env': { 67 | NODE_ENV: '"production"' 68 | } 69 | }), 70 | new webpack.optimize.UglifyJsPlugin({ 71 | compress: { 72 | warnings: false 73 | } 74 | }), 75 | new webpack.optimize.OccurenceOrderPlugin(), 76 | new HtmlWebpackPlugin({ 77 | title: 'Vue Minder based on kityminder-core', 78 | prod: true, 79 | template: 'examples/docs.html' 80 | }), 81 | ] 82 | }; 83 | 84 | 85 | // var path = require('path'); 86 | // var webpack = require('webpack'); 87 | // var HtmlWebpackPlugin = require('html-webpack-plugin'); 88 | // var HtmlWebpackIncludeAssetsPlugin = require('html-webpack-include-assets-plugin'); 89 | // 90 | // module.exports = { 91 | // entry: { 92 | // main: './examples/main', 93 | // vendors: ['vue', 'vue-router'] 94 | // }, 95 | // output: { 96 | // path: path.join(__dirname, '../docs'), 97 | // publicPath: '', 98 | // filename: 'js/[name].js?v=[hash:7]', 99 | // chunkFilename: 'js/[name].chunk.js' 100 | // }, 101 | // externals: { 102 | // vue: { 103 | // root: 'Vue', 104 | // commonjs: 'vue', 105 | // commonjs2: 'vue', 106 | // amd: 'vue' 107 | // } 108 | // }, 109 | // resolve: { 110 | // // require时省略的扩展名,如:require('module') 不需要module.js 111 | // extensions: ['', '.js', '.vue'], 112 | // alias: { 113 | // minder: path.join(__dirname, '../src/index.js') 114 | // } 115 | // }, 116 | // module: { 117 | // loaders: [{ 118 | // test: /\.vue$/, 119 | // loader: 'vue' 120 | // }, { 121 | // test: /\.js$/, 122 | // loader: 'babel', 123 | // exclude: /node_modules/ 124 | // }, { 125 | // test: /\.css$/, 126 | // loader: 'style!css!autoprefixer' 127 | // }, { 128 | // test: /\.less$/, 129 | // loader: 'style!css!less' 130 | // }, { 131 | // test: /\.(gif|jpg|png|woff|svg|eot|ttf)\??.*$/, 132 | // loader: 'url?limit=8192' 133 | // }, { 134 | // test: /\.(html|tpl)$/, 135 | // loader: 'vue-html' 136 | // }] 137 | // }, 138 | // plugins: [ 139 | // new webpack.DefinePlugin({ 140 | // 'process.env': { 141 | // NODE_ENV: '"production"' 142 | // } 143 | // }), 144 | // new webpack.optimize.OccurenceOrderPlugin(), 145 | // new HtmlWebpackPlugin({ 146 | // title: 'Vue Minder based on kityminder-core', 147 | // prod: true 148 | // }), 149 | // new HtmlWebpackIncludeAssetsPlugin({ 150 | // assets: [ 151 | // 'https://unpkg.com/vue@1.0.28/dist/vue.min.js' 152 | // ], 153 | // append: false 154 | // })// 提取第三方库 155 | // ] 156 | // }; 157 | // if(process.env.DEV_ENV !== 'local') { 158 | // module.exports.plugins.unshift(new webpack.optimize.UglifyJsPlugin({ 159 | // compress: { 160 | // warnings: false 161 | // } 162 | // })); 163 | // } 164 | -------------------------------------------------------------------------------- /docs/css/main.css: -------------------------------------------------------------------------------- 1 | .hotbox{font-family:Arial,Hiragino Sans GB,Microsoft YaHei,WenQuanYi Micro Hei,sans-serif;left:0;top:0}.hotbox,.hotbox .state{position:absolute;overflow:visible}.hotbox .state{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,.3)}.hotbox .state .center .key,.hotbox .state .center .label,.hotbox .state .ring .key,.hotbox .state .ring .label{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:#000;font-weight:400;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,.3);border-radius:100%;box-sizing:content-box}.hotbox .state .bottom,.hotbox .state .top{position:absolute;white-space:nowrap}.hotbox .state .bottom .button,.hotbox .state .top .button{display:inline-block;padding:8px 15px;margin:0 10px;border-radius:15px;box-shadow:0 0 30px rgba(0,0,0,.3);position:relative}.hotbox .state .bottom .button .label,.hotbox .state .top .button .label{font-size:14px;line-height:14px;vertical-align:middle;color:#000;line-height:1em}.hotbox .state .bottom .button .key,.hotbox .state .top .button .key{font-size:12px;line-height:12px;vertical-align:middle;color:#999;margin-left:3px}.hotbox .state .bottom .button .key:before,.hotbox .state .top .button .key:before{content:"("}.hotbox .state .bottom .button .key:after,.hotbox .state .top .button .key:after{content:")"}.hotbox .state .button{background:#f9f9f9;overflow:hidden;cursor:default}.hotbox .state .button .key,.hotbox .state .button .label{opacity:.3}.hotbox .state .button.enabled{background:#fff}.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:#fff}.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:#fff}.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:#fff}.hotbox .state .button.enabled.pressed .key,.hotbox .state .button.enabled:active .key{color:#fff0e6}.hotbox .state.active{display:block}.hotbox-key-receiver{position:absolute;left:-999999px;top:-999999px;width:20px;height:20px;outline:none;margin:0}nav[_v-af8a0ebe]{margin-bottom:40px}li[_v-af8a0ebe]{display:inline-block}li+li[_v-af8a0ebe]{border-left:1px solid #bbb;padding-left:5px;margin-left:5px}.v-link-active[_v-af8a0ebe]{color:#bbb}.template__list,.theme__panel{float:left}.minder__top-tab{position:relative;z-index:3}.nav-bar{background-color:#fff!important}.nav-btn{vertical-align:middle}.icon{width:20px!important;height:20px!important}.zoom--pan .text{color:#222;display:inline-block;text-align:center;width:30px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.zoom-in .icon{font-size:24px}.template__list .dropdown-menu{display:block}.theme__panel .dropdown-menu{display:block}.theme__panel .theme-item-selected .caret{margin-left:0}.breadcrumb__list{display:-ms-flexbox;display:flex;color:#777;line-height:30px;font-size:16px;padding:5px 0;margin-left:10px;float:left}.breadcrumb__list li{list-style:none;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.breadcrumb__list .item{cursor:pointer;display:block;max-width:100px;border-radius:3px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.breadcrumb__list .arrow{float:left;margin:0 6px;font-size:13px} -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | Vue Minder Based kityminder
-------------------------------------------------------------------------------- /docs/js/1.chunk.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([1],{79:function(e,n,t){var o,a,d;!function(r,i){a=[n,t(113)],o=i,d="function"==typeof o?o.apply(n,a):o,!(void 0!==d&&(e.exports=d))}(this,function(n,t){"use strict";function o(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(n,"__esModule",{value:!0});o(t);n.default={data:function(){return{defaultStyle:{width:"1000px",height:"600px"},enable:!1}},computed:{},methods:{generateIndex:function(e){return"_"+(e.data.id||e.data._id)},setColor:function(e,n){e.setData(n),e.render()}},beforeDestroy:function(){this.$refs.minder.setMemory()},ready:function(){var e=this,n={root:{data:{name:"中心主题",hexType:"project",id:1},children:[{data:{name:"111",expandState:"collapse",hexType:"project",id:2},children:[{data:{name:"1111111",hexType:"dir",id:3},children:[]},{data:{name:"1111111",hexType:"dir",id:4},children:[]},{data:{name:"1111111",hexType:"dir",id:5},children:[{data:{name:"哈哈",hexType:"page",id:6},children:[]},{data:{name:"哈哈1",hexType:"page",id:7},children:[]},{data:{name:"哈哈2",hexType:"page",id:8},children:[]}]},{data:{name:"1111111",hexType:"dir",id:9},children:[]},{data:{name:"11在",hexType:"page",id:10},children:[]}]},{data:{name:"222",id:11,hexType:"page"},children:[]},{data:{name:"哈哈哈",hexType:"page",id:12},children:[]},{data:{name:"333",id:13,hexType:"page"},children:[]},{data:{name:"444",hexType:"app",id:14},children:[]},{data:{name:"555",id:15,hexType:"page"},children:[]},{data:{name:"666",id:16,hexType:"page"},children:[]}]},template:"default",theme:"fresh-blue",version:"1.4.43"};this.$nextTick(function(){var t=this,o=this.$refs.minder.minder;setTimeout(function(){o.importJson(t.$refs.minder.getMemory(n))},1e3),window.onbeforeunload=function(e){t.$refs.minder.setMemory()},o.on("editText",function(e,n){e.minder.getSelectedNode()}),o.on("selectionchange",function(n){e.minder=n.minder;var t=n.minder.getSelectedNode();e.currentNode=t,t&&t.parent&&setTimeout(function(){t.parent.layout()},0),e.lock&&t,e.lock=!0}),o.on("beforeExecCommand",function(e){var n=e.minder.getSelectedNode();/arrange/i.test(e.commandName)?console.log("arrange",n,e):/append/i.test(e.commandName)?console.log("add",n):/remove/i.test(e.commandName)&&console.log("remove",n)}),o.on("beforeExecCommand",function(e){var n=e.minder.getSelectedNode();console.log(n),/append/i.test(e.commandName)?console.log("add",n):/remove/i.test(e.commandName)&&console.log("remove",n)}),o.on("AfterExecCommand",function(e){var n=e.minder.getSelectedNode();console.log("after",n),/append/i.test(e.commandName)?console.log("after add",n):/remove/i.test(e.commandName)&&console.log("after remove",n)})})}},e.exports=n.default})},113:function(e,n,t){e.exports={default:t(124),__esModule:!0}},124:function(e,n,t){t(32),t(31),e.exports=t(162)},162:function(e,n,t){var o=t(8),a=t(75);e.exports=t(1).getIterator=function(e){var n=a(e);if("function"!=typeof n)throw TypeError(e+" is not iterable!");return o(n.call(e))}},183:function(e,n){},201:function(e,n){e.exports=" "},210:function(e,n,t){var o,a,d={};t(183),o=t(79),a=t(201),e.exports=o||{},e.exports.__esModule&&(e.exports=e.exports.default);var r="function"==typeof e.exports?e.exports.options||(e.exports.options={}):e.exports;a&&(r.template=a),r.computed||(r.computed={}),Object.keys(d).forEach(function(e){var n=d[e];r.computed[e]=function(){return n}})}}); -------------------------------------------------------------------------------- /docs/styles/images/iconpriority.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtao/vue-minder/b4cbaec72624f0fe761f7cc5286cec193cb3a571/docs/styles/images/iconpriority.png -------------------------------------------------------------------------------- /docs/styles/images/iconprogress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtao/vue-minder/b4cbaec72624f0fe761f7cc5286cec193cb3a571/docs/styles/images/iconprogress.png -------------------------------------------------------------------------------- /docs/styles/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtao/vue-minder/b4cbaec72624f0fe761f7cc5286cec193cb3a571/docs/styles/images/icons.png -------------------------------------------------------------------------------- /docs/styles/images/template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtao/vue-minder/b4cbaec72624f0fe761f7cc5286cec193cb3a571/docs/styles/images/template.png -------------------------------------------------------------------------------- /examples/app.vue: -------------------------------------------------------------------------------- 1 | 4 | 23 | 26 | 40 | -------------------------------------------------------------------------------- /examples/docs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue Minder Based kityminder 6 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= htmlWebpackPlugin.options.title %> 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by aresn on 16/6/20. 3 | */ 4 | import Vue from 'vue' 5 | import VueRouter from 'vue-router'; 6 | import App from './app.vue'; 7 | import minder from '../src/index'; 8 | 9 | Vue.use(VueRouter); 10 | Vue.use(minder); 11 | // 开启debug模式 12 | Vue.config.debug = true; 13 | 14 | // 路由配置 15 | var router = new VueRouter(); 16 | 17 | router.map({ 18 | '/editor': { 19 | component: function (resolve) { 20 | require(['./routers/editor.vue'], resolve); 21 | } 22 | } 23 | }); 24 | 25 | router.beforeEach(function () { 26 | window.scrollTo(0, 0); 27 | }); 28 | 29 | router.afterEach(function (transition) { 30 | 31 | }); 32 | 33 | router.redirect({ 34 | '*': "/editor" 35 | }); 36 | router.start(App, '#app'); 37 | -------------------------------------------------------------------------------- /examples/routers/editor.vue: -------------------------------------------------------------------------------- 1 | 4 | 279 | 281 | -------------------------------------------------------------------------------- /examples/styles/images/iconpriority.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtao/vue-minder/b4cbaec72624f0fe761f7cc5286cec193cb3a571/examples/styles/images/iconpriority.png -------------------------------------------------------------------------------- /examples/styles/images/iconprogress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtao/vue-minder/b4cbaec72624f0fe761f7cc5286cec193cb3a571/examples/styles/images/iconprogress.png -------------------------------------------------------------------------------- /examples/styles/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtao/vue-minder/b4cbaec72624f0fe761f7cc5286cec193cb3a571/examples/styles/images/icons.png -------------------------------------------------------------------------------- /examples/styles/images/template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtao/vue-minder/b4cbaec72624f0fe761f7cc5286cec193cb3a571/examples/styles/images/template.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-minder", 3 | "version": "1.0.0", 4 | "title": "Vue kityminder", 5 | "description": "Vue Kityminder", 6 | "homepage": "https://github.com/hexyun/vue-minder", 7 | "keywords": [ 8 | "kityminder", 9 | "vue", 10 | "vue.js", 11 | "component", 12 | "components", 13 | "ui", 14 | "framework", 15 | "baidu naotu" 16 | ], 17 | "main": "dist/minder.min.js", 18 | "files": [ 19 | "dist", 20 | "src" 21 | ], 22 | "scripts": { 23 | "dev": "npm run build:style && npm run copy:example && webpack-dev-server --content-base examples/ --open --inline --hot --compress --history-api-fallback --port 10000 --config build/webpack.dev.config.js", 24 | "build:style": "gulp --gulpfile build/build-style.js", 25 | "docs": "webpack --config build/webpack.docs.config.js && npm run copy:docs", 26 | "build:prod": "npm run build:style && webpack --config build/webpack.dist.prod.config.js", 27 | "build": "rm -fr docs dist && npm run svgo && npm run build:prod && npm run docs", 28 | "svgo": "svgo --enable=removeXMLNS src/components/icon/icons/*.svg", 29 | "copy:docs": "cp -r dist/styles docs", 30 | "copy:example": "cp -r dist/styles examples", 31 | "copy": "npm run copy:docs && npm run copy:example", 32 | "lint": "eslint --fix --ext .js,.vue src", 33 | "test": "npm run build && npm run lint", 34 | "prepublish": "npm run build" 35 | }, 36 | "repository": { 37 | "type": "git", 38 | "url": "https://github.com/vue-minder" 39 | }, 40 | "author": "jiangtao", 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/vue-minder/issues" 44 | }, 45 | "dependencies": { 46 | "bootstrap": "^3.4.1", 47 | "core-js": "^2.4.1", 48 | "hotbox": "fex-team/hotbox", 49 | "kity": "^2.0.4", 50 | "kityminder-core": "github:jiangtao/kityminder-core", 51 | "vue": "1.0.28" 52 | }, 53 | "devDependencies": { 54 | "autoprefixer-loader": "^2.0.0", 55 | "babel": "^6.3.13", 56 | "babel-core": "^6.11.4", 57 | "babel-loader": "^6.2.4", 58 | "babel-plugin-add-module-exports": "^1.0.0", 59 | "babel-plugin-transform-runtime": "^6.23.0", 60 | "babel-preset-env": "^1.7.0", 61 | "babel-preset-es2015": "^6.24.1", 62 | "babel-preset-stage-2": "^6.24.1", 63 | "css-loader": "^0.23.1", 64 | "ejs-html-loader": "^4.0.1", 65 | "eslint": "^3.12.2", 66 | "eslint-plugin-html": "^1.7.0", 67 | "extract-text-webpack-plugin": "^1.0.1", 68 | "file-loader": "^0.8.5", 69 | "gulp": "^3.9.1", 70 | "gulp-autoprefixer": "^3.1.1", 71 | "gulp-clean-css": "^2.0.13", 72 | "gulp-less": "^3.1.0", 73 | "gulp-rename": "^1.2.2", 74 | "html-loader": "^0.3.0", 75 | "html-webpack-include-assets-plugin": "^1.0.7", 76 | "html-webpack-plugin": "^3.2.0", 77 | "less": "^2.7.1", 78 | "less-loader": "^2.2.3", 79 | "style-loader": "^0.13.1", 80 | "url-loader": "^0.5.7", 81 | "vue-hot-reload-api": "^1.3.3", 82 | "vue-html-loader": "^1.2.3", 83 | "vue-loader": "^8.5.3", 84 | "vue-router": "^0.7.13", 85 | "vue-style-loader": "^1.0.0", 86 | "webpack": "^1.13.1", 87 | "webpack-dev-server": "^1.16.1" 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtao/vue-minder/b4cbaec72624f0fe761f7cc5286cec193cb3a571/src/components/.gitkeep -------------------------------------------------------------------------------- /src/components/breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 42 | 128 | -------------------------------------------------------------------------------- /src/components/editor/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 29 | 252 | -------------------------------------------------------------------------------- /src/components/icon/icons.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'expand': { 3 | name: '缩放', 4 | svg: require('./icons/expand.svg') 5 | }, 6 | 'zoom_out': { 7 | name: '缩放', 8 | svg: require('./icons/zoom_out.svg') 9 | }, 10 | 'camera': { 11 | name: '聚焦', 12 | svg: require('./icons/camera.svg') 13 | }, 14 | 'zoom_out': { 15 | name: '缩放', 16 | svg: require('./icons/zoom_out.svg') 17 | }, 18 | 'zoom_out': { 19 | name: '缩放', 20 | svg: require('./icons/zoom_out.svg') 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/icon/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 34 | -------------------------------------------------------------------------------- /src/components/search/index.vue: -------------------------------------------------------------------------------- 1 | 27 | 172 | -------------------------------------------------------------------------------- /src/components/template-list/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 21 | 87 | -------------------------------------------------------------------------------- /src/components/theme-list/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 27 | 118 | -------------------------------------------------------------------------------- /src/directives/clickoutside.js: -------------------------------------------------------------------------------- 1 | export default { 2 | bind () { 3 | this.documentHandler = (e) => { 4 | if (this.el.contains(e.target)) { 5 | return false; 6 | } 7 | if (this.expression) { 8 | this.vm[this.expression](); 9 | } 10 | }; 11 | document.addEventListener('click', this.documentHandler); 12 | }, 13 | update () { 14 | 15 | }, 16 | unbind () { 17 | document.removeEventListener('click', this.documentHandler); 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /src/directives/visible.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Date 2019-06-04 3 | * @author jiangtao 4 | * 目的是支持子组件中的method可以被父调用 5 | * 处理vue1.0中 v-if v-show false情况下, refs为空的问题 6 | * 7 | */ 8 | export default { 9 | bind() { 10 | this.documentHandler = null; 11 | this._i = 0; 12 | }, 13 | update() { 14 | let flag = true; 15 | this._i = 0; 16 | if(this.expression) { 17 | let splits = this.expression.split(/\&\&/g), key; 18 | while(flag && this._i < splits.length) { 19 | key = splits[this._i++]; 20 | if(key && (key = key.trim())) { 21 | flag &= this.vm[key]; 22 | } 23 | } 24 | } 25 | this.documentHandler = (e) => { 26 | this.el.style.visibility = !flag ? 'hidden' : 'visible'; 27 | }; 28 | window.requestAnimationFrame(this.documentHandler); 29 | }, 30 | unbind() { 31 | if(this.documentHandler) window.cancelAnimationFrame(this.documentHandler); 32 | this._i = 0; 33 | this.documentHandler = null; 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /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, blackList) { 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 | 39 | return module.exports = KMEditor; 40 | }); 41 | -------------------------------------------------------------------------------- /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/filter/lang.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | // minder lang 3 | Vue.filter('ml', function(value, key, lang = 'zh-CN') { 4 | const conf = require(`../locale/lang/minder/${lang}.js`) 5 | return conf[key][value] 6 | }) 7 | -------------------------------------------------------------------------------- /src/hotbox.js: -------------------------------------------------------------------------------- 1 | import Hotbox from 'hotbox/src/hotbox' 2 | define(function(require, exports, module) { 3 | return module.exports = Hotbox; 4 | }); 5 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import kity from 'kity'; 2 | import kityminder from 'kityminder-core'; 3 | import Editor from './components/editor'; 4 | import './module/imageicon' 5 | 6 | const MindEditor = { 7 | Minder: Editor 8 | }; 9 | 10 | const install = function(Vue, opts = {}) { 11 | Object.keys(MindEditor).forEach((key) => { 12 | Vue.component(key, MindEditor[key]); 13 | }); 14 | }; 15 | 16 | // auto install 17 | if(typeof window !== 'undefined' && window.Vue) { 18 | install(window.Vue); 19 | } 20 | 21 | module.exports = Object.assign(MindEditor, {install}); // eslint-disable-line no-undef 22 | -------------------------------------------------------------------------------- /src/lang.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | 3 | }); -------------------------------------------------------------------------------- /src/locale/format.js: -------------------------------------------------------------------------------- 1 | /** 2 | * String format template 3 | * - Inspired: 4 | * https://github.com/Matt-Esch/string-template/index.js 5 | */ 6 | 7 | const RE_NARGS = /(%|)\{([0-9a-zA-Z_]+)\}/g; 8 | 9 | export default function(Vue) { 10 | const { hasOwn } = Vue.util; 11 | 12 | /** 13 | * template 14 | * 15 | * @param {String} string 16 | * @param {Array} ...args 17 | * @return {String} 18 | */ 19 | 20 | function template(string, ...args) { 21 | if (args.length === 1 && typeof args[0] === 'object') { 22 | args = args[0]; 23 | } 24 | 25 | if (!args || !args.hasOwnProperty) { 26 | args = {}; 27 | } 28 | 29 | return string.replace(RE_NARGS, (match, prefix, i, index) => { 30 | let result; 31 | 32 | if (string[index - 1] === '{' && 33 | string[index + match.length] === '}') { 34 | return i; 35 | } else { 36 | result = hasOwn(args, i) ? args[i] : null; 37 | if (result === null || result === undefined) { 38 | return ''; 39 | } 40 | 41 | return result; 42 | } 43 | }); 44 | } 45 | 46 | return template; 47 | } 48 | -------------------------------------------------------------------------------- /src/locale/index.js: -------------------------------------------------------------------------------- 1 | // https://github.com/ElemeFE/element/blob/dev/src/locale/index.js 2 | 3 | import defaultLang from './lang/zh-CN'; 4 | import Vue from 'vue'; 5 | import deepmerge from 'deepmerge'; 6 | import Format from './format'; 7 | 8 | const format = Format(Vue); 9 | let lang = defaultLang; 10 | let merged = false; 11 | let i18nHandler = function() { 12 | const vuei18n = Object.getPrototypeOf(this || Vue).$t; 13 | if (typeof vuei18n === 'function') { 14 | if (!merged) { 15 | merged = true; 16 | Vue.locale( 17 | Vue.config.lang, 18 | deepmerge(lang, Vue.locale(Vue.config.lang) || {}, { clone: true }) 19 | ); 20 | } 21 | return vuei18n.apply(this, arguments); 22 | } 23 | }; 24 | 25 | export const t = function(path, options) { 26 | let value = i18nHandler.apply(this, arguments); 27 | if (value !== null && value !== undefined) return value; 28 | 29 | const array = path.split('.'); 30 | let current = lang; 31 | 32 | for (let i = 0, j = array.length; i < j; i++) { 33 | const property = array[i]; 34 | value = current[property]; 35 | if (i === j - 1) return format(value, options); 36 | if (!value) return ''; 37 | current = value; 38 | } 39 | return ''; 40 | }; 41 | 42 | export const use = function(l) { 43 | lang = l || lang; 44 | }; 45 | 46 | export const i18n = function(fn) { 47 | i18nHandler = fn || i18nHandler; 48 | }; 49 | 50 | export default { use, t, i18n }; -------------------------------------------------------------------------------- /src/locale/lang/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | i: { 3 | select: { 4 | placeholder: 'Select', 5 | noMatch: 'No matching data' 6 | }, 7 | table: { 8 | noDataText: 'No Data', 9 | noFilteredDataText: 'No filter data', 10 | confirmFilter: 'Confirm', 11 | resetFilter: 'Reset', 12 | clearFilter: 'All' 13 | }, 14 | datepicker: { 15 | selectDate: 'Select date', 16 | selectTime: 'Select time', 17 | startTime: 'Start Time', 18 | endTime: 'End Time', 19 | clear: 'Clear', 20 | ok: 'OK', 21 | month: '', 22 | month1: 'January', 23 | month2: 'February', 24 | month3: 'March', 25 | month4: 'April', 26 | month5: 'May', 27 | month6: 'June', 28 | month7: 'July', 29 | month8: 'August', 30 | month9: 'September', 31 | month10: 'October', 32 | month11: 'November', 33 | month12: 'December', 34 | year: '', 35 | weeks: { 36 | sun: 'Sun', 37 | mon: 'Mon', 38 | tue: 'Tue', 39 | wed: 'Wed', 40 | thu: 'Thu', 41 | fri: 'Fri', 42 | sat: 'Sat' 43 | }, 44 | months: { 45 | m1: 'Jan', 46 | m2: 'Feb', 47 | m3: 'Mar', 48 | m4: 'Apr', 49 | m5: 'May', 50 | m6: 'Jun', 51 | m7: 'Jul', 52 | m8: 'Aug', 53 | m9: 'Sep', 54 | m10: 'Oct', 55 | m11: 'Nov', 56 | m12: 'Dec' 57 | } 58 | }, 59 | transfer: { 60 | titles: { 61 | source: 'Source', 62 | target: 'Target' 63 | }, 64 | filterPlaceholder: 'Search here', 65 | notFoundText: 'Not Found' 66 | }, 67 | modal: { 68 | okText: 'OK', 69 | cancelText: 'Cancel' 70 | }, 71 | poptip: { 72 | okText: 'OK', 73 | cancelText: 'Cancel' 74 | }, 75 | page: { 76 | prev: 'Previous Page', 77 | next: 'Next Page', 78 | total: 'Total', 79 | item: 'item', 80 | items: 'items', 81 | prev5: 'Previous 5 Pages', 82 | next5: 'Next 5 Pages', 83 | page: '/page', 84 | goto: 'Goto', 85 | p: '' 86 | }, 87 | rate: { 88 | star: 'Star', 89 | stars: 'Stars' 90 | }, 91 | tree: { 92 | emptyText: 'No Data' 93 | } 94 | } 95 | }; -------------------------------------------------------------------------------- /src/locale/lang/minder/zh-CN.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'template': { 3 | 'default': '思维导图', 4 | 'tianpan': '天盘图', 5 | 'structure': '组织结构图', 6 | 'filetree': '目录组织图', 7 | 'right': '逻辑结构图', 8 | 'fish-bone': '鱼骨头图' 9 | }, 10 | 'theme': { 11 | 'classic': '脑图经典', 12 | 'classic-compact': '紧凑经典', 13 | 'snow': '温柔冷光', 14 | 'snow-compact': '紧凑冷光', 15 | 'fish': '鱼骨图', 16 | 'wire': '线框', 17 | 'fresh-red': '清新红', 18 | 'fresh-soil': '泥土黄', 19 | 'fresh-green': '文艺绿', 20 | 'fresh-blue': '天空蓝', 21 | 'fresh-purple': '浪漫紫', 22 | 'fresh-pink': '脑残粉', 23 | 'fresh-red-compat': '紧凑红', 24 | 'fresh-soil-compat': '紧凑黄', 25 | 'fresh-green-compat': '紧凑绿', 26 | 'fresh-blue-compat': '紧凑蓝', 27 | 'fresh-purple-compat': '紧凑紫', 28 | 'fresh-pink-compat': '紧凑粉', 29 | 'tianpan': '经典天盘', 30 | 'tianpan-compact': '紧凑天盘' 31 | }, 32 | 'maintopic': '中心主题', 33 | 'topic': '分支主题', 34 | 'panels': { 35 | 'history': '历史', 36 | 'template': '模板', 37 | 'theme': '皮肤', 38 | 'layout': '布局', 39 | 'style': '样式', 40 | 'font': '文字', 41 | 'color': '颜色', 42 | 'background': '背景', 43 | 'insert': '插入', 44 | 'arrange': '调整', 45 | 'nodeop': '当前', 46 | 'priority': '优先级', 47 | 'progress': '进度', 48 | 'resource': '资源', 49 | 'note': '备注', 50 | 'attachment': '附件', 51 | 'word': '文字' 52 | }, 53 | 'error_message': { 54 | 'title': '哎呀,脑图出错了', 55 | 56 | 'err_load': '加载脑图失败', 57 | 'err_save': '保存脑图失败', 58 | 'err_network': '网络错误', 59 | 'err_doc_resolve': '文档解析失败', 60 | 'err_unknown': '发生了奇怪的错误', 61 | 'err_localfile_read': '文件读取错误', 62 | 'err_download': '文件下载失败', 63 | 'err_remove_share': '取消分享失败', 64 | 'err_create_share': '分享失败', 65 | 'err_mkdir': '目录创建失败', 66 | 'err_ls': '读取目录失败', 67 | 'err_share_data': '加载分享内容出错', 68 | 'err_share_sync_fail': '分享内容同步失败', 69 | 'err_move_file': '文件移动失败', 70 | 'err_rename': '重命名失败', 71 | 72 | 'unknownreason': '可能是外星人篡改了代码...', 73 | 'pcs_code': { 74 | 3: '不支持此接口', 75 | 4: '没有权限执行此操作', 76 | 5: 'IP未授权', 77 | 110: '用户会话已过期,请重新登录', 78 | 31001: '数据库查询错误', 79 | 31002: '数据库连接错误', 80 | 31003: '数据库返回空结果', 81 | 31021: '网络错误', 82 | 31022: '暂时无法连接服务器', 83 | 31023: '输入参数错误', 84 | 31024: 'app id为空', 85 | 31025: '后端存储错误', 86 | 31041: '用户的cookie不是合法的百度cookie', 87 | 31042: '用户未登陆', 88 | 31043: '用户未激活', 89 | 31044: '用户未授权', 90 | 31045: '用户不存在', 91 | 31046: '用户已经存在', 92 | 31061: '文件已经存在', 93 | 31062: '文件名非法', 94 | 31063: '文件父目录不存在', 95 | 31064: '无权访问此文件', 96 | 31065: '目录已满', 97 | 31066: '文件不存在', 98 | 31067: '文件处理出错', 99 | 31068: '文件创建失败', 100 | 31069: '文件拷贝失败', 101 | 31070: '文件删除失败', 102 | 31071: '不能读取文件元信息', 103 | 31072: '文件移动失败', 104 | 31073: '文件重命名失败', 105 | 31079: '未找到文件MD5,请使用上传API上传整个文件。', 106 | 31081: 'superfile创建失败', 107 | 31082: 'superfile 块列表为空', 108 | 31083: 'superfile 更新失败', 109 | 31101: 'tag系统内部错误', 110 | 31102: 'tag参数错误', 111 | 31103: 'tag系统错误', 112 | 31110: '未授权设置此目录配额', 113 | 31111: '配额管理只支持两级目录', 114 | 31112: '超出配额', 115 | 31113: '配额不能超出目录祖先的配额', 116 | 31114: '配额不能比子目录配额小', 117 | 31141: '请求缩略图服务失败', 118 | 31201: '签名错误', 119 | 31202: '文件不存在', 120 | 31203: '设置acl失败', 121 | 31204: '请求acl验证失败', 122 | 31205: '获取acl失败', 123 | 31206: 'acl不存在', 124 | 31207: 'bucket已存在', 125 | 31208: '用户请求错误', 126 | 31209: '服务器错误', 127 | 31210: '服务器不支持', 128 | 31211: '禁止访问', 129 | 31212: '服务不可用', 130 | 31213: '重试出错', 131 | 31214: '上传文件data失败', 132 | 31215: '上传文件meta失败', 133 | 31216: '下载文件data失败', 134 | 31217: '下载文件meta失败', 135 | 31218: '容量超出限额', 136 | 31219: '请求数超出限额', 137 | 31220: '流量超出限额', 138 | 31298: '服务器返回值KEY非法', 139 | 31299: '服务器返回值KEY不存在' 140 | } 141 | }, 142 | 'ui': { 143 | 'shared_file_title': '[分享的] {0} (只读)', 144 | 'load_share_for_edit': '正在加载分享的文件...', 145 | 'share_sync_success': '分享内容已同步', 146 | 'recycle_clear_confirm': '确认清空回收站么?清空后的文件无法恢复。', 147 | 148 | 'fullscreen_exit_hint': '按 Esc 或 F11 退出全屏', 149 | 150 | 'error_detail': '详细信息', 151 | 'copy_and_feedback': '复制并反馈', 152 | 'move_file_confirm': '确定把 "{0}" 移动到 "{1}" 吗?', 153 | 'rename': '重命名', 154 | 'rename_success': '{0} 重命名成功', 155 | 'move_success': '{0} 移动成功到 {1}', 156 | 'command': { 157 | 'appendsiblingnode': '插入同级主题', 158 | 'appendparentnode': '插入上级主题', 159 | 'appendchildnode': '插入下级主题', 160 | 'removenode': '删除', 161 | 'editnode': '编辑', 162 | 'arrangeup': '上移', 163 | 'arrangedown': '下移', 164 | 'resetlayout': '整理布局', 165 | 'expandtoleaf': '展开全部节点', 166 | 'expandtolevel1': '展开到一级节点', 167 | 'expandtolevel2': '展开到二级节点', 168 | 'expandtolevel3': '展开到三级节点', 169 | 'expandtolevel4': '展开到四级节点', 170 | 'expandtolevel5': '展开到五级节点', 171 | 'expandtolevel6': '展开到六级节点', 172 | 'fullscreen': '全屏', 173 | 'outline': '大纲' 174 | }, 175 | 176 | 'search': '搜索', 177 | 178 | 'expandtoleaf': '展开', 179 | 180 | 'back': '返回', 181 | 182 | 'undo': '撤销 (Ctrl + Z)', 183 | 'redo': '重做 (Ctrl + Y)', 184 | 185 | 'tabs': { 186 | 'idea': '思路', 187 | 'appearence': '外观', 188 | 'view': '视图' 189 | }, 190 | 191 | 'quickvisit': { 192 | 'new': '新建 (Ctrl + Alt + N)', 193 | 'save': '保存 (Ctrl + S)', 194 | 'share': '分享 (Ctrl + Alt + S)', 195 | 'feedback': '反馈问题(F1)', 196 | 'editshare': '编辑' 197 | }, 198 | 199 | 'menu': { 200 | 201 | 'mainmenutext': '百度脑图', // 主菜单按钮文本 202 | 203 | 'newtab': '新建', 204 | 'opentab': '打开', 205 | 'savetab': '保存', 206 | 'sharetab': '分享', 207 | 'preferencetab': '设置', 208 | 'helptab': '帮助', 209 | 'feedbacktab': '反馈', 210 | 'recenttab': '最近使用', 211 | 'netdisktab': '百度云存储', 212 | 'localtab': '本地文件', 213 | 'drafttab': '草稿箱', 214 | 'downloadtab': '导出到本地', 215 | 'createsharetab': '当前脑图', 216 | 'managesharetab': '已分享', 217 | 218 | 'newheader': '新建脑图', 219 | 'openheader': '打开', 220 | 'saveheader': '保存到', 221 | 'draftheader': '草稿箱', 222 | 'shareheader': '分享我的脑图', 223 | 'downloadheader': '导出到指定格式', 224 | 'preferenceheader': '偏好设置', 225 | 'helpheader': '帮助', 226 | 'feedbackheader': '反馈' 227 | }, 228 | 229 | 'mydocument': '我的文档', 230 | 'emptydir': '目录为空!', 231 | 'pickfile': '选择文件...', 232 | 'acceptfile': '支持的格式:{0}', 233 | 'dropfile': '或将文件拖至此处', 234 | 'unsupportedfile': '不支持的文件格式', 235 | 'untitleddoc': '未命名文档', 236 | 'overrideconfirm': '{0} 已存在,确认覆盖吗?', 237 | 'checklogin': '检查登录状态中...', 238 | 'loggingin': '正在登录...', 239 | 'recent': '最近打开', 240 | 'clearrecent': '清空', 241 | 'clearrecentconfirm': '确认清空最近文档列表?', 242 | 'cleardraft': '清空', 243 | 'cleardraftconfirm': '确认清空草稿箱?', 244 | 245 | 'none_share': '不分享', 246 | 'public_share': '公开分享', 247 | 'password_share': '私密分享', 248 | 'email_share': '邮件邀请', 249 | 'url_share': '脑图 URL 地址:', 250 | 'sns_share': '社交网络分享:', 251 | 'sns_share_text': '“{0}” - 我用百度脑图制作的思维导图,快看看吧!(地址:{1})', 252 | 'none_share_description': '不分享当前脑图', 253 | 'public_share_description': '创建任何人可见的分享', 254 | 'share_button_text': '创建', 255 | 'password_share_description': '创建需要密码才可见的分享', 256 | 'email_share_description': '创建指定人可见的分享,您还可以允许他们编辑', 257 | 'ondev': '敬请期待!', 258 | 'create_share_failed': '分享失败:{0}', 259 | 'remove_share_failed': '删除失败:{1}', 260 | 'copy': '复制', 261 | 'copied': '已复制', 262 | 'shared_tip': '当前脑图被 {0} 分享,你可以修改之后保存到自己的网盘上或再次分享', 263 | 'current_share': '当前脑图', 264 | 'manage_share': '我的分享', 265 | 'share_remove_action': '不分享该脑图', 266 | 'share_view_action': '打开分享地址', 267 | 'share_edit_action': '编辑分享的文件', 268 | 269 | 'login': '登录', 270 | 'logout': '注销', 271 | 'switchuser': '切换账户', 272 | 'userinfo': '个人信息', 273 | 'gotonetdisk': '我的网盘', 274 | 'requirelogin': '请 登录 后使用', 275 | 'saveas': '保存为', 276 | 'filename': '文件名', 277 | 'fileformat': '保存格式', 278 | 'save': '保存', 279 | 'mkdir': '新建目录', 280 | 'recycle': '回收站', 281 | 'newdir': '未命名目录', 282 | 283 | 'bold': '加粗', 284 | 'italic': '斜体', 285 | 'forecolor': '字体颜色', 286 | 'fontfamily': '字体', 287 | 'fontsize': '字号', 288 | 'layoutstyle': '主题', 289 | 'node': '节点操作', 290 | 'saveto': '另存为', 291 | 'hand': '允许拖拽', 292 | 'camera': '定位根节点', 293 | 'zoom-in': '放大(Ctrl+)', 294 | 'zoom-out': '缩小(Ctrl-)', 295 | 'markers': '标签', 296 | 'resource': '资源', 297 | 'help': '帮助', 298 | 'preference': '偏好设置', 299 | 'expandnode': '展开到叶子', 300 | 'collapsenode': '收起到一级节点', 301 | 'template': '模板', 302 | 'theme': '皮肤', 303 | 'clearstyle': '清除样式', 304 | 'copystyle': '复制样式', 305 | 'pastestyle': '粘贴样式', 306 | 'appendsiblingnode': '同级主题', 307 | 'appendchildnode': '下级主题', 308 | 'arrangeup': '前调', 309 | 'arrangedown': '后调', 310 | 'editnode': '编辑', 311 | 'removenode': '移除', 312 | 'priority': '优先级', 313 | 'progress': { 314 | 'p1': '未开始', 315 | 'p2': '完成 1/8', 316 | 'p3': '完成 1/4', 317 | 'p4': '完成 3/8', 318 | 'p5': '完成一半', 319 | 'p6': '完成 5/8', 320 | 'p7': '完成 3/4', 321 | 'p8': '完成 7/8', 322 | 'p9': '已完成', 323 | 'p0': '清除进度' 324 | }, 325 | 'link': '链接', 326 | 'image': '图片', 327 | 'note': '备注', 328 | 'insertlink': '插入链接', 329 | 'insertimage': '插入图片', 330 | 'insertnote': '插入备注', 331 | 'removelink': '移除已有链接', 332 | 'removeimage': '移除已有图片', 333 | 'removenote': '移除已有备注', 334 | 'resetlayout': '整理', 335 | 336 | 'justnow': '刚刚', 337 | 'minutesago': '{0} 分钟前', 338 | 'hoursago': '{0} 小时前', 339 | 'yesterday': '昨天', 340 | 'daysago': '{0} 天前', 341 | 'longago': '很久之前', 342 | 343 | 'redirect': '您正在打开连接 {0},百度脑图不能保证连接的安全性,是否要继续?', 344 | 'navigator': '导航器', 345 | 'topbar': '头部导航', 346 | 347 | 'unsavedcontent': '当前文件还没有保存到网盘:\n\n{0}\n\n虽然未保存的数据会缓存在草稿箱,但是清除浏览器缓存会导致草稿箱清除。', 348 | 349 | 'shortcuts': '快捷键', 350 | 'contact': '联系与反馈', 351 | 'email': '邮件组', 352 | 'qq_group': 'QQ 群', 353 | 'github_issue': 'Github', 354 | 'baidu_tieba': '贴吧', 355 | 356 | 'clipboardunsupported': '您的浏览器不支持剪贴板,请使用快捷键复制', 357 | 358 | 'load_success': '{0} 加载成功', 359 | 'save_success': '{0} 已保存于 {1}', 360 | 'autosave_success': '{0} 已自动保存于 {1}', 361 | 362 | 'selectall': '全选', 363 | 'selectrevert': '反选', 364 | 'selectsiblings': '选择兄弟节点', 365 | 'selectlevel': '选择同级节点', 366 | 'selectpath': '选择路径', 367 | 'selecttree': '选择子树' 368 | }, 369 | 'popupcolor': { 370 | 'clearColor': '清空颜色', 371 | 'standardColor': '标准颜色', 372 | 'themeColor': '主题颜色' 373 | }, 374 | 'dialogs': { 375 | 'markers': { 376 | 'static': { 377 | 'lang_input_text': '文本内容:', 378 | 'lang_input_url': '链接地址:', 379 | 'lang_input_title': '标题:', 380 | 'lang_input_target': '是否在新窗口:' 381 | }, 382 | 'priority': '优先级', 383 | 'none': '无', 384 | 'progress': { 385 | 'title': '进度', 386 | 'notdone': '未完成', 387 | 'done1': '完成 1/8', 388 | 'done2': '完成 1/4', 389 | 'done3': '完成 3/8', 390 | 'done4': '完成 1/2', 391 | 'done5': '完成 5/8', 392 | 'done6': '完成 3/4', 393 | 'done7': '完成 7/8', 394 | 'done': '已完成' 395 | } 396 | }, 397 | 'help': {}, 398 | 'hyperlink': {}, 399 | 'image': {}, 400 | 'resource': {} 401 | }, 402 | 'hyperlink': { 403 | 'hyperlink': '链接...', 404 | 'unhyperlink': '移除链接' 405 | }, 406 | 'image': { 407 | 'image': '图片...', 408 | 'removeimage': '移除图片' 409 | }, 410 | 'marker': { 411 | 'marker': '进度/优先级...' 412 | }, 413 | 'resource': { 414 | 'resource': '资源...' 415 | } 416 | }; 417 | -------------------------------------------------------------------------------- /src/locale/lang/zh-CN.js: -------------------------------------------------------------------------------- 1 | export default { 2 | i: { 3 | select: { 4 | placeholder: '请选择', 5 | noMatch: '无匹配数据' 6 | }, 7 | table: { 8 | noDataText: '暂无数据', 9 | noFilteredDataText: '暂无筛选结果', 10 | confirmFilter: '筛选', 11 | resetFilter: '重置', 12 | clearFilter: '全部' 13 | }, 14 | datepicker: { 15 | selectDate: '选择日期', 16 | selectTime: '选择时间', 17 | startTime: '开始时间', 18 | endTime: '结束时间', 19 | clear: '清空', 20 | ok: '确定', 21 | month: '月', 22 | month1: '1 月', 23 | month2: '2 月', 24 | month3: '3 月', 25 | month4: '4 月', 26 | month5: '5 月', 27 | month6: '6 月', 28 | month7: '7 月', 29 | month8: '8 月', 30 | month9: '9 月', 31 | month10: '10 月', 32 | month11: '11 月', 33 | month12: '12 月', 34 | year: '年', 35 | weeks: { 36 | sun: '日', 37 | mon: '一', 38 | tue: '二', 39 | wed: '三', 40 | thu: '四', 41 | fri: '五', 42 | sat: '六' 43 | }, 44 | months: { 45 | m1: '1月', 46 | m2: '2月', 47 | m3: '3月', 48 | m4: '4月', 49 | m5: '5月', 50 | m6: '6月', 51 | m7: '7月', 52 | m8: '8月', 53 | m9: '9月', 54 | m10: '10月', 55 | m11: '11月', 56 | m12: '12月' 57 | } 58 | }, 59 | transfer: { 60 | titles: { 61 | source: '源列表', 62 | target: '目的列表' 63 | }, 64 | filterPlaceholder: '请输入搜索内容', 65 | notFoundText: '列表为空' 66 | }, 67 | modal: { 68 | okText: '确定', 69 | cancelText: '取消' 70 | }, 71 | poptip: { 72 | okText: '确定', 73 | cancelText: '取消' 74 | }, 75 | page: { 76 | prev: '上一页', 77 | next: '下一页', 78 | total: '共', 79 | item: '条', 80 | items: '条', 81 | prev5: '向前 5 页', 82 | next5: '向后 5 页', 83 | page: '条/页', 84 | goto: '跳至', 85 | p: '页' 86 | }, 87 | rate: { 88 | star: '星', 89 | stars: '星' 90 | }, 91 | tree: { 92 | emptyText: '暂无数据' 93 | } 94 | } 95 | }; -------------------------------------------------------------------------------- /src/locale/lang/zh-TW.js: -------------------------------------------------------------------------------- 1 | export default { 2 | i: { 3 | select: { 4 | placeholder: '請選擇', 5 | noMatch: '無匹配數據' 6 | }, 7 | table: { 8 | noDataText: '暫無數據', 9 | noFilteredDataText: '暫無篩選結果', 10 | confirmFilter: '篩選', 11 | resetFilter: '重置', 12 | clearFilter: '全部' 13 | }, 14 | datepicker: { 15 | selectDate: '選擇日期', 16 | selectTime: '選擇時間', 17 | startTime: '開始時間', 18 | endTime: '結束時間', 19 | clear: '清空', 20 | ok: '確定', 21 | month: '月', 22 | month1: '1 月', 23 | month2: '2 月', 24 | month3: '3 月', 25 | month4: '4 月', 26 | month5: '5 月', 27 | month6: '6 月', 28 | month7: '7 月', 29 | month8: '8 月', 30 | month9: '9 月', 31 | month10: '10 月', 32 | month11: '11 月', 33 | month12: '12 月', 34 | year: '年', 35 | weeks: { 36 | sun: '日', 37 | mon: '一', 38 | tue: '二', 39 | wed: '三', 40 | thu: '四', 41 | fri: '五', 42 | sat: '六' 43 | }, 44 | months: { 45 | m1: '1月', 46 | m2: '2月', 47 | m3: '3月', 48 | m4: '4月', 49 | m5: '5月', 50 | m6: '6月', 51 | m7: '7月', 52 | m8: '8月', 53 | m9: '9月', 54 | m10: '10月', 55 | m11: '11月', 56 | m12: '12月' 57 | } 58 | }, 59 | transfer: { 60 | titles: { 61 | source: '源列表', 62 | target: '目的列表' 63 | }, 64 | filterPlaceholder: '請輸入搜索內容', 65 | notFoundText: '列表爲空' 66 | }, 67 | modal: { 68 | okText: '確定', 69 | cancelText: '取消' 70 | }, 71 | poptip: { 72 | okText: '確定', 73 | cancelText: '取消' 74 | }, 75 | page: { 76 | prev: '上壹頁', 77 | next: '下壹頁', 78 | total: '共', 79 | item: '條', 80 | items: '條', 81 | prev5: '向前 5 頁', 82 | next5: '向後 5 頁', 83 | page: '條/頁', 84 | goto: '跳至', 85 | p: '頁' 86 | }, 87 | rate: { 88 | star: '星', 89 | stars: '星' 90 | }, 91 | tree: { 92 | emptyText: '暫無數據' 93 | } 94 | } 95 | }; -------------------------------------------------------------------------------- /src/minder.js: -------------------------------------------------------------------------------- 1 | define(function(require, exports, module) { 2 | return module.exports = window.kityminder.Minder; 3 | }); 4 | -------------------------------------------------------------------------------- /src/mixins/locale.js: -------------------------------------------------------------------------------- 1 | import { t } from '../locale'; 2 | 3 | export default { 4 | methods: { 5 | t(...args) { 6 | return t.apply(this, args); 7 | } 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /src/module/imageicon.js: -------------------------------------------------------------------------------- 1 | var kity = window.kity; 2 | var utils = require('kityminder-core/src/core/utils'); 3 | var Minder = window.kityminder.Minder; 4 | var MinderNode = window.kityminder.Node; 5 | var Command = window.kityminder.Command; 6 | var Module = window.kityminder.Module; 7 | var Renderer = window.kityminder.Render; 8 | 9 | module.exports = Module.register('imageicon', function() { 10 | var self = this; 11 | 12 | function loadImageSize(url, callback) { 13 | var img = document.createElement('img'); 14 | img.onload = function() { 15 | callback(img.width, img.height); 16 | }; 17 | img.onerror = function() { 18 | callback(null); 19 | }; 20 | img.src = url; 21 | } 22 | 23 | function fitImageSize(width, height, maxWidth, maxHeight) { 24 | var ratio = width / height, 25 | fitRatio = maxWidth / maxHeight; 26 | 27 | // 宽高比大于最大尺寸的宽高比,以宽度为标准适应 28 | if(width > maxWidth && ratio > fitRatio) { 29 | width = maxWidth; 30 | height = width / ratio; 31 | } else if(height > maxHeight) { 32 | height = maxHeight; 33 | width = height * ratio; 34 | } 35 | 36 | return { 37 | width: width | 0, 38 | height: height | 0 39 | }; 40 | } 41 | 42 | /** 43 | * @command Image 44 | * @description 为选中的节点添加图片 45 | * @param {string} url 图片的 URL,设置为 null 移除 46 | * @param {string} title 图片的说明 47 | * @state 48 | * 0: 当前有选中的节点 49 | * -1: 当前没有选中的节点 50 | * @return 返回首个选中节点的图片信息,JSON 对象: `{url: url, title: title}` 51 | */ 52 | var ImageIconCommand = kity.createClass('ImageIconCommand', { 53 | base: Command, 54 | 55 | execute: function(km, url, title) { 56 | var nodes = km.getSelectedNodes(); 57 | 58 | loadImageSize(url, function(width, height) { 59 | nodes.forEach(function(n) { 60 | var size = fitImageSize( 61 | width, height, 62 | km.getOption('maxImageWidth'), 63 | km.getOption('maxImageHeight')); 64 | n.setData('imageicon', url); 65 | n.setData('imageTitle', url && title); 66 | n.setData('imageSize', url && size); 67 | n.render(); 68 | }); 69 | km.fire('saveScene'); 70 | km.layout(300); 71 | }); 72 | 73 | }, 74 | queryState: function(km) { 75 | var nodes = km.getSelectedNodes(), 76 | result = 0; 77 | if(nodes.length === 0) { 78 | return -1; 79 | } 80 | nodes.forEach(function(n) { 81 | if(n && n.getData('image')) { 82 | result = 0; 83 | return false; 84 | } 85 | }); 86 | return result; 87 | }, 88 | queryValue: function(km) { 89 | var node = km.getSelectedNode(); 90 | return { 91 | url: node.getData('image'), 92 | title: node.getData('imageTitle') 93 | }; 94 | } 95 | }); 96 | 97 | var ImageRenderer = kity.createClass('ImageRenderer', { 98 | base: Renderer, 99 | 100 | create: function(node) { 101 | return new kity.Image(node.getData('imageicon')); 102 | }, 103 | 104 | shouldRender: function(node) { 105 | return node.getData('imageicon') || node.getData('hexType'); 106 | }, 107 | 108 | update: function(image, node, box) { 109 | var url = node.getData('imageicon'); 110 | var title = node.getData('imageTitle'); 111 | var size = node.getData('imageSize'); 112 | var spaceTop = node.getStyle('space-top'); 113 | var type = node.getData('hexType'); 114 | 115 | 116 | var HEX_WIDTH = self.getOption('hex_width') || 20; 117 | var HEX_HEIGHT = self.getOption('hex_height') || 20; 118 | var urls = { 119 | app: self.getOption('app_url'), 120 | project: self.getOption('project_url'), 121 | dir: self.getOption('dir_url'), 122 | page: self.getOption('page_url') 123 | }; 124 | 125 | if(type && urls[type]) { 126 | size = size || {width: HEX_WIDTH, height: HEX_HEIGHT}; 127 | url = urls[type] 128 | } 129 | 130 | if(!size ) return; 131 | 132 | if(title) { 133 | image.node.setAttributeNS('http://www.w3.org/1999/xlink', 'title', title); 134 | } 135 | 136 | var x = -1 * size.width ; 137 | var y = box.y - spaceTop; 138 | 139 | image.setUrl(url).setX(x | 0).setY(y | 0).setWidth(size.width | 0).setHeight(size.height | 0); 140 | 141 | // use kity svg 142 | var items = node.rc.getItems(); 143 | var group = items.find(a => a.constructor.name === 'Group'); 144 | group.setTranslate(2, (size.height - group.getHeight()) / 2 - spaceTop); 145 | 146 | node.layout(); 147 | 148 | return new kity.Box(x | 0, y | 0, size.width | 0, size.height | 0); 149 | } 150 | }); 151 | 152 | return { 153 | 'defaultOptions': { 154 | 'hex_width': 20, 155 | 'hex_height': 20 156 | }, 157 | 'commands': { 158 | 'imageicon': ImageIconCommand 159 | }, 160 | 'renderers': { 161 | 'top': ImageRenderer 162 | } 163 | }; 164 | }); 165 | -------------------------------------------------------------------------------- /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/clipboard.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @Desc: 处理editor的clipboard事件,只在支持ClipboardEvent并且不是FF的情况下工作 3 | * @Editor: Naixor 4 | * @Date: 2015.9.21 5 | */ 6 | define(function(require, exports, module) { 7 | 8 | function ClipboardRuntime () { 9 | var minder = this.minder; 10 | var Data = window.kityminder.data; 11 | 12 | if (!minder.supportClipboardEvent || kity.Browser.gecko) { 13 | return; 14 | }; 15 | 16 | var fsm = this.fsm; 17 | var receiver = this.receiver; 18 | var MimeType = this.MimeType; 19 | 20 | var kmencode = MimeType.getMimeTypeProtocol('application/km'), 21 | decode = Data.getRegisterProtocol('json').decode; 22 | var _selectedNodes = []; 23 | 24 | /* 25 | * 增加对多节点赋值粘贴的处理 26 | */ 27 | function encode (nodes) { 28 | var _nodes = []; 29 | for (var i = 0, l = nodes.length; i < l; i++) { 30 | _nodes.push(minder.exportNode(nodes[i])); 31 | } 32 | return kmencode(Data.getRegisterProtocol('json').encode(_nodes)); 33 | } 34 | 35 | var beforeCopy = function (e) { 36 | if (document.activeElement == receiver.element) { 37 | var clipBoardEvent = e; 38 | var state = fsm.state(); 39 | 40 | switch (state) { 41 | case 'input': { 42 | break; 43 | } 44 | case 'normal': { 45 | var nodes = [].concat(minder.getSelectedNodes()); 46 | if (nodes.length) { 47 | // 这里由于被粘贴复制的节点的id信息也都一样,故做此算法 48 | // 这里有个疑问,使用node.getParent()或者node.parent会离奇导致出现非选中节点被渲染成选中节点,因此使用isAncestorOf,而没有使用自行回溯的方式 49 | if (nodes.length > 1) { 50 | var targetLevel; 51 | nodes.sort(function(a, b) { 52 | return a.getLevel() - b.getLevel(); 53 | }); 54 | targetLevel = nodes[0].getLevel(); 55 | if (targetLevel !== nodes[nodes.length-1].getLevel()) { 56 | var plevel, pnode, 57 | idx = 0, l = nodes.length, pidx = l-1; 58 | 59 | pnode = nodes[pidx]; 60 | 61 | while (pnode.getLevel() !== targetLevel) { 62 | idx = 0; 63 | while (idx < l && nodes[idx].getLevel() === targetLevel) { 64 | if (nodes[idx].isAncestorOf(pnode)) { 65 | nodes.splice(pidx, 1); 66 | break; 67 | } 68 | idx++; 69 | } 70 | pidx--; 71 | pnode = nodes[pidx]; 72 | } 73 | }; 74 | }; 75 | var str = encode(nodes); 76 | clipBoardEvent.clipboardData.setData('text/plain', str); 77 | } 78 | e.preventDefault(); 79 | break; 80 | } 81 | } 82 | } 83 | } 84 | 85 | var beforeCut = function (e) { 86 | if (document.activeElement == receiver.element) { 87 | if (minder.getStatus() !== 'normal') { 88 | e.preventDefault(); 89 | return; 90 | }; 91 | 92 | var clipBoardEvent = e; 93 | var state = fsm.state(); 94 | 95 | switch (state) { 96 | case 'input': { 97 | break; 98 | } 99 | case 'normal': { 100 | var nodes = minder.getSelectedNodes(); 101 | if (nodes.length) { 102 | clipBoardEvent.clipboardData.setData('text/plain', encode(nodes)); 103 | minder.execCommand('removenode'); 104 | } 105 | e.preventDefault(); 106 | break; 107 | } 108 | } 109 | }; 110 | } 111 | 112 | var beforePaste = function(e) { 113 | if (document.activeElement == receiver.element) { 114 | if (minder.getStatus() !== 'normal') { 115 | e.preventDefault(); 116 | return; 117 | }; 118 | 119 | var clipBoardEvent = e; 120 | var state = fsm.state(); 121 | var textData = clipBoardEvent.clipboardData.getData('text/plain'); 122 | 123 | switch (state) { 124 | case 'input': { 125 | // input状态下如果格式为application/km则不进行paste操作 126 | if (!MimeType.isPureText(textData)) { 127 | e.preventDefault(); 128 | return; 129 | }; 130 | break; 131 | } 132 | case 'normal': { 133 | /* 134 | * 针对normal状态下通过对选中节点粘贴导入子节点文本进行单独处理 135 | */ 136 | var sNodes = minder.getSelectedNodes(); 137 | 138 | if (MimeType.whichMimeType(textData) === 'application/km') { 139 | var nodes = decode(MimeType.getPureText(textData)); 140 | var _node; 141 | sNodes.forEach(function(node) { 142 | // 由于粘贴逻辑中为了排除子节点重新排序导致逆序,因此复制的时候倒过来 143 | for (var i = nodes.length-1; i >= 0; i--) { 144 | _node = minder.createNode(null, node); 145 | minder.importNode(_node, nodes[i]); 146 | _selectedNodes.push(_node); 147 | node.appendChild(_node); 148 | } 149 | }); 150 | minder.select(_selectedNodes, true); 151 | _selectedNodes = []; 152 | 153 | minder.refresh(); 154 | } 155 | else if (clipBoardEvent.clipboardData && clipBoardEvent.clipboardData.items[0].type.indexOf('image') > -1) { 156 | var imageFile = clipBoardEvent.clipboardData.items[0].getAsFile(); 157 | var serverService = angular.element(document.body).injector().get('server'); 158 | 159 | return serverService.uploadImage(imageFile).then(function (json) { 160 | var resp = json.data; 161 | if (resp.errno === 0) { 162 | minder.execCommand('image', resp.data.url); 163 | } 164 | }); 165 | } 166 | else { 167 | sNodes.forEach(function(node) { 168 | minder.Text2Children(node, textData); 169 | }); 170 | } 171 | e.preventDefault(); 172 | break; 173 | } 174 | } 175 | } 176 | } 177 | /** 178 | * 由editor的receiver统一处理全部事件,包括clipboard事件 179 | * @Editor: Naixor 180 | * @Date: 2015.9.24 181 | */ 182 | document.addEventListener('copy', beforeCopy); 183 | document.addEventListener('cut', beforeCut); 184 | document.addEventListener('paste', beforePaste); 185 | } 186 | 187 | return module.exports = ClipboardRuntime; 188 | }); -------------------------------------------------------------------------------- /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/jumping.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 | 13 | 14 | // Nice: http://unixpapa.com/js/key.html 15 | function isIntendToInput(e) { 16 | if (e.ctrlKey || e.metaKey || e.altKey) return false; 17 | 18 | // a-zA-Z 19 | if (e.keyCode >= 65 && e.keyCode <= 90) return true; 20 | 21 | // 0-9 以及其上面的符号 22 | if (e.keyCode >= 48 && e.keyCode <= 57) return true; 23 | 24 | // 小键盘区域 (除回车外) 25 | if (e.keyCode != 108 && e.keyCode >= 96 && e.keyCode <= 111) return true; 26 | 27 | // 小键盘区域 (除回车外) 28 | // @yinheli from pull request 29 | if (e.keyCode != 108 && e.keyCode >= 96 && e.keyCode <= 111) return true; 30 | 31 | // 输入法 32 | if (e.keyCode == 229 || e.keyCode === 0) return true; 33 | 34 | return false; 35 | } 36 | /** 37 | * @Desc: 下方使用receiver.enable()和receiver.disable()通过 38 | * 修改div contenteditable属性的hack来解决开启热核后依然无法屏蔽浏览器输入的bug; 39 | * 特别: win下FF对于此种情况必须要先blur在focus才能解决,但是由于这样做会导致用户 40 | * 输入法状态丢失,因此对FF暂不做处理 41 | * @Editor: Naixor 42 | * @Date: 2015.09.14 43 | */ 44 | function JumpingRuntime() { 45 | var fsm = this.fsm; 46 | var minder = this.minder; 47 | var receiver = this.receiver; 48 | var container = this.container; 49 | var receiverElement = receiver.element; 50 | var hotbox = this.hotbox; 51 | var compositionLock = false; 52 | 53 | // normal -> * 54 | receiver.listen('normal', function(e) { 55 | // 为了防止处理进入edit模式而丢失处理的首字母,此时receiver必须为enable 56 | receiver.enable(); 57 | // normal -> hotbox 58 | if (e.is('Space')) { 59 | e.preventDefault(); 60 | // safari下Space触发hotbox,然而这时Space已在receiver上留下作案痕迹,因此抹掉 61 | if (kity.Browser.safari) { 62 | receiverElement.innerHTML = ''; 63 | } 64 | return fsm.jump('hotbox', 'space-trigger'); 65 | } 66 | 67 | /** 68 | * check 69 | * @editor Naixor 70 | * @Date 2015-12-2 71 | */ 72 | switch (e.type) { 73 | case 'keydown': { 74 | if (minder.getSelectedNode()) { 75 | if (isIntendToInput(e)) { 76 | return fsm.jump('input', 'user-input'); 77 | }; 78 | } else { 79 | receiverElement.innerHTML = ''; 80 | } 81 | // normal -> normal shortcut 82 | fsm.jump('normal', 'shortcut-handle', e); 83 | break; 84 | } 85 | case 'keyup': { 86 | break; 87 | } 88 | default: {} 89 | } 90 | }); 91 | 92 | // hotbox -> normal 93 | receiver.listen('hotbox', function(e) { 94 | receiver.disable(); 95 | e.preventDefault(); 96 | var handleResult = hotbox.dispatch(e); 97 | if (hotbox.state() == Hotbox.STATE_IDLE && fsm.state() == 'hotbox') { 98 | return fsm.jump('normal', 'hotbox-idle'); 99 | } 100 | }); 101 | 102 | // input => normal 103 | receiver.listen('input', function(e) { 104 | receiver.enable(); 105 | if (e.type == 'keydown') { 106 | if (e.is('Enter')) { 107 | e.preventDefault(); 108 | return fsm.jump('normal', 'input-commit'); 109 | } 110 | if (e.is('Esc')) { 111 | e.preventDefault(); 112 | return fsm.jump('normal', 'input-cancel'); 113 | } 114 | if (e.is('Tab') || e.is('Shift + Tab')) { 115 | e.preventDefault(); 116 | } 117 | } else if (e.type == 'keyup' && e.is('Esc')) { 118 | e.preventDefault(); 119 | if (!compositionLock) { 120 | return fsm.jump('normal', 'input-cancel'); 121 | } 122 | } 123 | else if (e.type == 'compositionstart') { 124 | compositionLock = true; 125 | } 126 | else if (e.type == 'compositionend') { 127 | setTimeout(function () { 128 | compositionLock = false; 129 | }); 130 | } 131 | }); 132 | 133 | ////////////////////////////////////////////// 134 | /// 右键呼出热盒 135 | /// 判断的标准是:按下的位置和结束的位置一致 136 | ////////////////////////////////////////////// 137 | var downX, downY; 138 | var MOUSE_RB = 2; // 右键 139 | 140 | container.addEventListener('mousedown', function(e) { 141 | if (e.button == MOUSE_RB) { 142 | e.preventDefault(); 143 | } 144 | if (fsm.state() == 'hotbox') { 145 | hotbox.active(Hotbox.STATE_IDLE); 146 | fsm.jump('normal', 'blur'); 147 | } else if (fsm.state() == 'normal' && e.button == MOUSE_RB) { 148 | downX = e.clientX; 149 | downY = e.clientY; 150 | } 151 | }, false); 152 | 153 | container.addEventListener('mousewheel', function(e) { 154 | if (fsm.state() == 'hotbox') { 155 | hotbox.active(Hotbox.STATE_IDLE); 156 | fsm.jump('normal', 'mousemove-blur'); 157 | } 158 | }, false); 159 | 160 | container.addEventListener('contextmenu', function(e) { 161 | e.preventDefault(); 162 | }); 163 | 164 | container.addEventListener('mouseup', function(e) { 165 | if (fsm.state() != 'normal') { 166 | return; 167 | } 168 | if (e.button != MOUSE_RB || e.clientX != downX || e.clientY != downY) { 169 | return; 170 | } 171 | if (!minder.getSelectedNode()) { 172 | return; 173 | } 174 | fsm.jump('hotbox', 'content-menu'); 175 | }, false); 176 | 177 | // 阻止热盒事件冒泡,在热盒正确执行前导致热盒关闭 178 | hotbox.$element.addEventListener('mousedown', function(e) { 179 | e.stopPropagation(); 180 | }); 181 | } 182 | 183 | return module.exports = JumpingRuntime; 184 | }); 185 | -------------------------------------------------------------------------------- /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('name', '中心主题'); 25 | 26 | // 导出给其它 Runtime 使用 27 | this.minder = minder; 28 | } 29 | 30 | return module.exports = MinderRuntime; 31 | }); -------------------------------------------------------------------------------- /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; 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 | 13 | function ReceiverRuntime() { 14 | var fsm = this.fsm; 15 | var minder = this.minder; 16 | var me = this; 17 | 18 | // 接收事件的 div 19 | var element = document.createElement('div'); 20 | element.contentEditable = true; 21 | /** 22 | * @Desc: 增加tabindex属性使得element的contenteditable不管是trur还是false都能有focus和blur事件 23 | * @Editor: Naixor 24 | * @Date: 2015.09.14 25 | */ 26 | element.setAttribute('tabindex', -1); 27 | element.classList.add('receiver'); 28 | element.onkeydown = element.onkeypress = element.onkeyup = dispatchKeyEvent; 29 | element.addEventListener('compositionstart', dispatchKeyEvent); 30 | // element.addEventListener('compositionend', dispatchKeyEvent); 31 | this.container.appendChild(element); 32 | 33 | // receiver 对象 34 | var receiver = { 35 | element: element, 36 | selectAll: function() { 37 | // 保证有被选中的 38 | if (!element.innerHTML) element.innerHTML = ' '; 39 | var range = document.createRange(); 40 | var selection = window.getSelection(); 41 | range.selectNodeContents(element); 42 | selection.removeAllRanges(); 43 | selection.addRange(range); 44 | element.focus(); 45 | }, 46 | /** 47 | * @Desc: 增加enable和disable方法用于解决热核态的输入法屏蔽问题 48 | * @Editor: Naixor 49 | * @Date: 2015.09.14 50 | */ 51 | enable: function() { 52 | element.setAttribute("contenteditable", true); 53 | }, 54 | disable: function() { 55 | element.setAttribute("contenteditable", false); 56 | }, 57 | /** 58 | * @Desc: hack FF下div contenteditable的光标丢失BUG 59 | * @Editor: Naixor 60 | * @Date: 2015.10.15 61 | */ 62 | fixFFCaretDisappeared: function() { 63 | element.removeAttribute("contenteditable"); 64 | element.setAttribute("contenteditable", "true"); 65 | element.blur(); 66 | element.focus(); 67 | }, 68 | /** 69 | * 以此事件代替通过mouse事件来判断receiver丢失焦点的事件 70 | * @editor Naixor 71 | * @Date 2015-12-2 72 | */ 73 | onblur: function (handler) { 74 | element.onblur = handler; 75 | } 76 | }; 77 | receiver.selectAll(); 78 | 79 | minder.on('beforemousedown', receiver.selectAll); 80 | minder.on('receiverfocus', receiver.selectAll); 81 | minder.on('readonly', function() { 82 | // 屏蔽minder的事件接受,删除receiver和hotbox 83 | minder.disable(); 84 | editor.receiver.element.parentElement.removeChild(editor.receiver.element); 85 | editor.hotbox.$container.removeChild(editor.hotbox.$element); 86 | }); 87 | 88 | // 侦听器,接收到的事件会派发给所有侦听器 89 | var listeners = []; 90 | 91 | // 侦听指定状态下的事件,如果不传 state,侦听所有状态 92 | receiver.listen = function(state, listener) { 93 | if (arguments.length == 1) { 94 | listener = state; 95 | state = '*'; 96 | } 97 | listener.notifyState = state; 98 | listeners.push(listener); 99 | }; 100 | 101 | function dispatchKeyEvent(e) { 102 | e.is = function(keyExpression) { 103 | var subs = keyExpression.split('|'); 104 | for (var i = 0; i < subs.length; i++) { 105 | if (key.is(this, subs[i])) return true; 106 | } 107 | return false; 108 | }; 109 | var listener, jumpState; 110 | for (var i = 0; i < listeners.length; i++) { 111 | 112 | listener = listeners[i]; 113 | // 忽略不在侦听状态的侦听器 114 | if (listener.notifyState != '*' && listener.notifyState != fsm.state()) { 115 | continue; 116 | } 117 | 118 | /** 119 | * 120 | * 对于所有的侦听器,只允许一种处理方式:跳转状态。 121 | * 如果侦听器确定要跳转,则返回要跳转的状态。 122 | * 每个事件只允许一个侦听器进行状态跳转 123 | * 跳转动作由侦听器自行完成(因为可能需要在跳转时传递 reason),返回跳转结果即可。 124 | * 比如: 125 | * 126 | * ```js 127 | * receiver.listen('normal', function(e) { 128 | * if (isSomeReasonForJumpState(e)) { 129 | * return fsm.jump('newstate', e); 130 | * } 131 | * }); 132 | * ``` 133 | */ 134 | if (listener.call(null, e)) { 135 | return; 136 | } 137 | } 138 | } 139 | 140 | this.receiver = receiver; 141 | } 142 | 143 | return module.exports = ReceiverRuntime; 144 | 145 | }); 146 | -------------------------------------------------------------------------------- /src/services/config.js: -------------------------------------------------------------------------------- 1 | var conf = {}; 2 | 3 | function initConf() { 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 = (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 = key => { 50 | var me = this 51 | if(!key) { 52 | return me.config; 53 | } 54 | 55 | if(me.config.hasOwnProperty(key)) { 56 | return me.config[key]; 57 | } 58 | 59 | console.warn('Missing config key pair for : ', key); 60 | return ''; 61 | } 62 | return this 63 | } 64 | 65 | module.exports = initConf.call(conf) 66 | -------------------------------------------------------------------------------- /src/services/memory.js: -------------------------------------------------------------------------------- 1 | function isQuotaExceeded(e) { 2 | var quotaExceeded = false; 3 | if(e) { 4 | if(e.code) { 5 | switch(e.code) { 6 | case 22: 7 | quotaExceeded = true; 8 | break; 9 | case 1014: 10 | // Firefox 11 | if(e.name === 'NS_ERROR_DOM_QUOTA_REACHED') { 12 | quotaExceeded = true; 13 | } 14 | break; 15 | } 16 | } else if(e.number === -2147024882) { 17 | // Internet Explorer 8 18 | quotaExceeded = true; 19 | } 20 | } 21 | return quotaExceeded; 22 | } 23 | 24 | module.exports = { 25 | get: function(key) { 26 | var value = window.localStorage.getItem(key); 27 | return null || JSON.parse(value); 28 | }, 29 | 30 | set: function(key, value) { 31 | try { 32 | window.localStorage.setItem(key, JSON.stringify(value)); 33 | return true; 34 | } catch(e) { 35 | if(isQuotaExceeded(e)) { 36 | return false; 37 | } 38 | } 39 | }, 40 | remove: function(key) { 41 | var value = window.localStorage.getItem(key); 42 | window.localStorage.removeItem(key); 43 | return value; 44 | }, 45 | clear: function() { 46 | window.localStorage.clear(); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /src/styles/README.md: -------------------------------------------------------------------------------- 1 | # 样式库说明 2 | 3 | ## 目录 4 | 5 | |-- animation (动画) 6 | 7 | |-- common (全局样式) 8 | 9 | |-- components (组件样式) 10 | 11 | |-- mixins (混入) 12 | -------------------------------------------------------------------------------- /src/styles/_navigator.less: -------------------------------------------------------------------------------- 1 | .nav-bar { 2 | position: absolute; 3 | width: 240px; 4 | height: 35px; 5 | padding: 5px 0; 6 | display: flex; 7 | align-items: center; 8 | justify-content: space-evenly; 9 | left: 10px; 10 | bottom: 10px; 11 | background: #dc8686; 12 | color: #fff; 13 | border-radius: 4px; 14 | z-index: 10; 15 | box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.2); 16 | transition: -webkit-transform .7s 0.1s ease; 17 | transition: transform .7s 0.1s ease; 18 | 19 | .nav-btn { 20 | width: 35px; 21 | height: 35px; 22 | line-height: 35px; 23 | text-align: center; 24 | display: flex; 25 | justify-content: center; 26 | align-items: center; 27 | } 28 | .nav-btn:hover .icon, 29 | .nav-btn.active .icon{ 30 | fill: #5c94f3; 31 | } 32 | 33 | .zoom-in .icon { 34 | background-position: 0 -730px; 35 | } 36 | 37 | .zoom-out .icon { 38 | background-position: 0 -750px; 39 | } 40 | 41 | .hand .icon { 42 | background-position: 0 -770px; 43 | width: 25px; 44 | height: 25px; 45 | margin: 0 auto; 46 | } 47 | 48 | 49 | .nav-trigger .icon { 50 | background-position: 0 -845px; 51 | width: 25px; 52 | height: 25px; 53 | margin: 0 auto; 54 | } 55 | 56 | .zoom-pan { 57 | width: 2px; 58 | height: 70px; 59 | box-shadow: 0 1px #E50000; 60 | position: relative; 61 | background: white; 62 | margin: 3px auto; 63 | overflow: visible; 64 | 65 | .origin { 66 | position: absolute; 67 | width: 20px; 68 | height: 8px; 69 | left: -9px; 70 | margin-top: -4px; 71 | background: transparent; 72 | 73 | &:after { 74 | content: ' '; 75 | display: block; 76 | width: 6px; 77 | height: 2px; 78 | background: white; 79 | left: 7px; 80 | top: 3px; 81 | position: absolute; 82 | } 83 | } 84 | 85 | .indicator { 86 | position: absolute; 87 | width: 8px; 88 | height: 8px; 89 | left: -3px; 90 | background: white; 91 | border-radius: 100%; 92 | margin-top: -4px; 93 | } 94 | 95 | } 96 | } 97 | 98 | .nav-previewer { 99 | background: #fff; 100 | width: 140px; 101 | height: 120px; 102 | position: absolute; 103 | left: 45px; 104 | bottom: 30px; 105 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.2); 106 | border-radius: 0 2px 2px 0; 107 | padding: 1px; 108 | z-index: 9; 109 | cursor: crosshair; 110 | transition: -webkit-transform .7s 0.1s ease; 111 | transition: transform .7s 0.1s ease; 112 | 113 | &.grab { 114 | cursor: move; 115 | cursor: -webkit-grabbing; 116 | cursor: -moz-grabbing; 117 | cursor: grabbing; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/styles/_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 | } -------------------------------------------------------------------------------- /src/styles/_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; -------------------------------------------------------------------------------- /src/styles/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 | bottom: 0; 60 | left: 0; 61 | right: 0; 62 | top: 0; 63 | font-family: Arial, "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif; 64 | } 65 | 66 | 67 | .minder-editor { 68 | position: absolute; 69 | top: 0px; 70 | left: 0; 71 | right: 0; 72 | bottom: 0; 73 | } 74 | 75 | .minder-viewer { 76 | position: absolute; 77 | top: 0; 78 | left: 0; 79 | right: 0; 80 | bottom: 0; 81 | } 82 | 83 | 84 | .control-panel { 85 | position: absolute; 86 | top: 0; 87 | right: 0; 88 | width: 250px; 89 | bottom: 0; 90 | border-left: 1px solid #CCC; 91 | } 92 | .minder-divider { 93 | position: absolute; 94 | top: 0; 95 | right: 250px; 96 | bottom: 0; 97 | width: 2px; 98 | background-color: rgb(251, 251, 251); 99 | cursor: ew-resize; 100 | } 101 | 102 | // @override bootstrap 103 | .panel-body { 104 | padding: 10px; 105 | } 106 | 107 | @import (less) "_vars.less"; 108 | @import (less) "imageDialog.less"; 109 | @import (less) "topTab/topTab.less"; 110 | @import (less) "topTab/idea/undoRedo.less"; 111 | @import (less) "topTab/idea/appendNode.less"; 112 | @import (less) "topTab/idea/arrange.less"; 113 | @import (less) "topTab/idea/operation.less"; 114 | @import (less) "topTab/idea/hyperlink.less"; 115 | @import (less) "topTab/idea/image.less"; 116 | @import (less) "topTab/idea/note.less"; 117 | @import (less) "topTab/idea/noteEditor.less"; 118 | @import (less) "topTab/idea/priority.less"; 119 | @import (less) "topTab/idea/progress.less"; 120 | @import (less) "topTab/idea/resource.less"; 121 | @import (less) "topTab/appearance/templatePanel.less"; 122 | @import (less) "topTab/appearance/themePanel.less"; 123 | @import (less) "topTab/appearance/layout.less"; 124 | @import (less) "topTab/appearance/styleOperator.less"; 125 | @import (less) "topTab/appearance/fontOperator.less"; 126 | @import (less) "topTab/appearance/colorPanel.less"; 127 | @import (less) "topTab/view/expand.less"; 128 | @import (less) "topTab/view/select.less"; 129 | @import (less) "topTab/view/search.less"; 130 | @import (less) "topTab/searchBox.less"; 131 | @import (less) "_tool_group.less"; 132 | @import (less) "_navigator.less"; 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /src/styles/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 | } -------------------------------------------------------------------------------- /src/styles/images/iconpriority.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtao/vue-minder/b4cbaec72624f0fe761f7cc5286cec193cb3a571/src/styles/images/iconpriority.png -------------------------------------------------------------------------------- /src/styles/images/iconprogress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtao/vue-minder/b4cbaec72624f0fe761f7cc5286cec193cb3a571/src/styles/images/iconprogress.png -------------------------------------------------------------------------------- /src/styles/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtao/vue-minder/b4cbaec72624f0fe761f7cc5286cec193cb3a571/src/styles/images/icons.png -------------------------------------------------------------------------------- /src/styles/images/template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jiangtao/vue-minder/b4cbaec72624f0fe761f7cc5286cec193cb3a571/src/styles/images/template.png -------------------------------------------------------------------------------- /src/styles/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 | } 78 | -------------------------------------------------------------------------------- /src/styles/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 | } 161 | -------------------------------------------------------------------------------- /src/styles/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 | -------------------------------------------------------------------------------- /src/styles/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(../../../../assets/images/icons.png) no-repeat; 82 | background-position: 0 -220px; 83 | } 84 | -------------------------------------------------------------------------------- /src/styles/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 | } 72 | -------------------------------------------------------------------------------- /src/styles/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 | } -------------------------------------------------------------------------------- /src/styles/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 | } -------------------------------------------------------------------------------- /src/styles/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 | } -------------------------------------------------------------------------------- /src/styles/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 | -------------------------------------------------------------------------------- /src/styles/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: 422px; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/styles/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 | -------------------------------------------------------------------------------- /src/styles/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 | } -------------------------------------------------------------------------------- /src/styles/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 | } -------------------------------------------------------------------------------- /src/styles/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 | } 27 | -------------------------------------------------------------------------------- /src/styles/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 | } 27 | -------------------------------------------------------------------------------- /src/styles/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 | -------------------------------------------------------------------------------- /src/styles/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 | } -------------------------------------------------------------------------------- /src/styles/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: 330px; 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 | } 60 | -------------------------------------------------------------------------------- /src/styles/topTab/topTab.less: -------------------------------------------------------------------------------- 1 | .top-tab { 2 | .nav-tabs { 3 | background-color: #e1e1e1; 4 | border: 0; 5 | height: 32px; 6 | 7 | li { 8 | margin: 0; 9 | 10 | a { 11 | margin: 0; 12 | border: 0; 13 | padding: 6px 15px; 14 | border-radius: 0; 15 | vertical-align: middle; 16 | 17 | 18 | &:hover, &:focus { 19 | background: inherit; 20 | border: 0; 21 | } 22 | } 23 | 24 | 25 | &.active a { 26 | border: 0; 27 | background-color: #fff; 28 | 29 | &:hover, &:focus { 30 | border: 0; 31 | } 32 | } 33 | } 34 | } 35 | 36 | .tab-content { 37 | height: 60px; 38 | background-color: #fff; 39 | border-bottom: 1px solid #dbdbdb; 40 | } 41 | 42 | .tab-pane { 43 | font-size: 0; 44 | } 45 | } 46 | 47 | .km-btn-group { 48 | display: inline-block; 49 | margin: 5px 0; 50 | padding: 0 5px; 51 | vertical-align: middle; 52 | border-right: 1px dashed #eee; 53 | } 54 | 55 | .km-btn-item { 56 | display: inline-block; 57 | margin: 0 3px; 58 | font-size: 0; 59 | cursor: default; 60 | 61 | &[disabled] { 62 | opacity: 0.5; 63 | 64 | &:hover, &:active { 65 | background-color: #fff; 66 | } 67 | } 68 | 69 | .km-btn-icon { 70 | display: inline-block; 71 | background: url(images/icons.png) no-repeat; 72 | background-position: 0 20px; 73 | width: 20px; 74 | height: 20px; 75 | padding: 2px; 76 | margin: 1px; 77 | vertical-align: middle; 78 | } 79 | 80 | .km-btn-caption { 81 | display: inline-block; 82 | font-size: 12px; 83 | vertical-align: middle; 84 | } 85 | 86 | &:hover {background-color: @button-hover;} 87 | &:active {background-color: @button-active;} 88 | } 89 | -------------------------------------------------------------------------------- /src/styles/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 | } 40 | -------------------------------------------------------------------------------- /src/styles/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 | } 40 | -------------------------------------------------------------------------------- /src/styles/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 | } 39 | -------------------------------------------------------------------------------- /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 | }; 40 | } else { 41 | this.log = noop; 42 | } 43 | } 44 | 45 | return module.exports = Debug; 46 | }); 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 | }); -------------------------------------------------------------------------------- /src/utils/assist.js: -------------------------------------------------------------------------------- 1 | // 判断参数是否是其中之一 2 | export function oneOf (value, validList) { 3 | for (let i = 0; i < validList.length; i++) { 4 | if (value === validList[i]) { 5 | return true; 6 | } 7 | } 8 | return false; 9 | } 10 | 11 | export function camelcaseToHyphen (str) { 12 | return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); 13 | } 14 | 15 | // For Modal scrollBar hidden 16 | let cached; 17 | export function getScrollBarSize (fresh) { 18 | if (fresh || cached === undefined) { 19 | const inner = document.createElement('div'); 20 | inner.style.width = '100%'; 21 | inner.style.height = '200px'; 22 | 23 | const outer = document.createElement('div'); 24 | const outerStyle = outer.style; 25 | 26 | outerStyle.position = 'absolute'; 27 | outerStyle.top = 0; 28 | outerStyle.left = 0; 29 | outerStyle.pointerEvents = 'none'; 30 | outerStyle.visibility = 'hidden'; 31 | outerStyle.width = '200px'; 32 | outerStyle.height = '150px'; 33 | outerStyle.overflow = 'hidden'; 34 | 35 | outer.appendChild(inner); 36 | 37 | document.body.appendChild(outer); 38 | 39 | const widthContained = inner.offsetWidth; 40 | outer.style.overflow = 'scroll'; 41 | let widthScroll = inner.offsetWidth; 42 | 43 | if (widthContained === widthScroll) { 44 | widthScroll = outer.clientWidth; 45 | } 46 | 47 | document.body.removeChild(outer); 48 | 49 | cached = widthContained - widthScroll; 50 | } 51 | return cached; 52 | } 53 | 54 | // watch DOM change 55 | export const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver || false; 56 | 57 | const SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 58 | const MOZ_HACK_REGEXP = /^moz([A-Z])/; 59 | 60 | function camelCase(name) { 61 | return name.replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 62 | return offset ? letter.toUpperCase() : letter; 63 | }).replace(MOZ_HACK_REGEXP, 'Moz$1'); 64 | } 65 | // getStyle 66 | export function getStyle (element, styleName) { 67 | if (!element || !styleName) return null; 68 | styleName = camelCase(styleName); 69 | if (styleName === 'float') { 70 | styleName = 'cssFloat'; 71 | } 72 | try { 73 | const computed = document.defaultView.getComputedStyle(element, ''); 74 | return element.style[styleName] || computed ? computed[styleName] : null; 75 | } catch(e) { 76 | return element.style[styleName]; 77 | } 78 | } 79 | 80 | // firstUpperCase 81 | function firstUpperCase(str) { 82 | return str.toString()[0].toUpperCase() + str.toString().slice(1); 83 | } 84 | export {firstUpperCase}; 85 | 86 | // Warn 87 | export function warnProp(component, prop, correctType, wrongType) { 88 | correctType = firstUpperCase(correctType); 89 | wrongType = firstUpperCase(wrongType); 90 | console.error(`[iView warn]: Invalid prop: type check failed for prop ${prop}. Expected ${correctType}, got ${wrongType}. (found in component: ${component})`); // eslint-disable-line 91 | } 92 | 93 | function typeOf(obj) { 94 | const toString = Object.prototype.toString; 95 | const map = { 96 | '[object Boolean]' : 'boolean', 97 | '[object Number]' : 'number', 98 | '[object String]' : 'string', 99 | '[object Function]' : 'function', 100 | '[object Array]' : 'array', 101 | '[object Date]' : 'date', 102 | '[object RegExp]' : 'regExp', 103 | '[object Undefined]': 'undefined', 104 | '[object Null]' : 'null', 105 | '[object Object]' : 'object' 106 | }; 107 | return map[toString.call(obj)]; 108 | } 109 | 110 | // deepCopy 111 | function deepCopy(data) { 112 | const t = typeOf(data); 113 | let o; 114 | 115 | if (t === 'array') { 116 | o = []; 117 | } else if ( t === 'object') { 118 | o = {}; 119 | } else { 120 | return data; 121 | } 122 | 123 | if (t === 'array') { 124 | for (let i = 0; i < data.length; i++) { 125 | o.push(deepCopy(data[i])); 126 | } 127 | } else if ( t === 'object') { 128 | for (let i in data) { 129 | o[i] = deepCopy(data[i]); 130 | } 131 | } 132 | return o; 133 | } 134 | 135 | export {deepCopy}; 136 | 137 | // scrollTop animation 138 | export function scrollTop(el, from = 0, to, duration = 500) { 139 | if (!window.requestAnimationFrame) { 140 | window.requestAnimationFrame = ( 141 | window.webkitRequestAnimationFrame || 142 | window.mozRequestAnimationFrame || 143 | window.msRequestAnimationFrame || 144 | function (callback) { 145 | return window.setTimeout(callback, 1000/60); 146 | } 147 | ); 148 | } 149 | const difference = Math.abs(from - to); 150 | const step = Math.ceil(difference / duration * 50); 151 | 152 | function scroll(start, end, step) { 153 | if (start === end) return; 154 | 155 | let d = (start + step > end) ? end : start + step; 156 | if (start > end) { 157 | d = (start - step < end) ? end : start - step; 158 | } 159 | 160 | if (el === window) { 161 | window.scrollTo(d, d); 162 | } else { 163 | el.scrollTop = d; 164 | } 165 | window.requestAnimationFrame(() => scroll(d, end, step)); 166 | } 167 | scroll(from, to, step); 168 | } -------------------------------------------------------------------------------- /src/utils/calcTextareaHeight.js: -------------------------------------------------------------------------------- 1 | // Thanks to 2 | // https://github.com/andreypopp/react-textarea-autosize/ 3 | // https://github.com/ElemeFE/element/blob/master/packages/input/src/calcTextareaHeight.js 4 | 5 | let hiddenTextarea; 6 | 7 | const HIDDEN_STYLE = ` 8 | height:0 !important; 9 | min-height:0 !important; 10 | max-height:none !important; 11 | visibility:hidden !important; 12 | overflow:hidden !important; 13 | position:absolute !important; 14 | z-index:-1000 !important; 15 | top:0 !important; 16 | right:0 !important 17 | `; 18 | 19 | const CONTEXT_STYLE = [ 20 | 'letter-spacing', 21 | 'line-height', 22 | 'padding-top', 23 | 'padding-bottom', 24 | 'font-family', 25 | 'font-weight', 26 | 'font-size', 27 | 'text-rendering', 28 | 'text-transform', 29 | 'width', 30 | 'text-indent', 31 | 'padding-left', 32 | 'padding-right', 33 | 'border-width', 34 | 'box-sizing' 35 | ]; 36 | 37 | function calculateNodeStyling(node) { 38 | const style = window.getComputedStyle(node); 39 | 40 | const boxSizing = style.getPropertyValue('box-sizing'); 41 | 42 | const paddingSize = ( 43 | parseFloat(style.getPropertyValue('padding-bottom')) + 44 | parseFloat(style.getPropertyValue('padding-top')) 45 | ); 46 | 47 | const borderSize = ( 48 | parseFloat(style.getPropertyValue('border-bottom-width')) + 49 | parseFloat(style.getPropertyValue('border-top-width')) 50 | ); 51 | 52 | const contextStyle = CONTEXT_STYLE 53 | .map(name => `${name}:${style.getPropertyValue(name)}`) 54 | .join(';'); 55 | 56 | return {contextStyle, paddingSize, borderSize, boxSizing}; 57 | } 58 | 59 | export default function calcTextareaHeight(targetNode, minRows = null, maxRows = null) { 60 | if (!hiddenTextarea) { 61 | hiddenTextarea = document.createElement('textarea'); 62 | document.body.appendChild(hiddenTextarea); 63 | } 64 | 65 | let { 66 | paddingSize, 67 | borderSize, 68 | boxSizing, 69 | contextStyle 70 | } = calculateNodeStyling(targetNode); 71 | 72 | hiddenTextarea.setAttribute('style', `${contextStyle};${HIDDEN_STYLE}`); 73 | hiddenTextarea.value = targetNode.value || targetNode.placeholder || ''; 74 | 75 | let height = hiddenTextarea.scrollHeight; 76 | let minHeight = -Infinity; 77 | let maxHeight = Infinity; 78 | 79 | if (boxSizing === 'border-box') { 80 | height = height + borderSize; 81 | } else if (boxSizing === 'content-box') { 82 | height = height - paddingSize; 83 | } 84 | 85 | hiddenTextarea.value = ''; 86 | let singleRowHeight = hiddenTextarea.scrollHeight - paddingSize; 87 | 88 | if (minRows !== null) { 89 | minHeight = singleRowHeight * minRows; 90 | if (boxSizing === 'border-box') { 91 | minHeight = minHeight + paddingSize + borderSize; 92 | } 93 | height = Math.max(minHeight, height); 94 | } 95 | if (maxRows !== null) { 96 | maxHeight = singleRowHeight * maxRows; 97 | if (boxSizing === 'border-box') { 98 | maxHeight = maxHeight + paddingSize + borderSize; 99 | } 100 | height = Math.min(maxHeight, height); 101 | } 102 | 103 | return { 104 | height: `${height}px`, 105 | minHeight: `${minHeight}px`, 106 | maxHeight: `${maxHeight}px` 107 | }; 108 | } -------------------------------------------------------------------------------- /src/utils/csv.js: -------------------------------------------------------------------------------- 1 | // https://github.com/Terminux/react-csv-downloader/blob/master/src/lib/csv.js 2 | 3 | const newLine = '\r\n'; 4 | 5 | export default function csv(columns, datas, separator = ',', noHeader = false) { 6 | let columnOrder; 7 | const content = []; 8 | const column = []; 9 | 10 | if (columns) { 11 | columnOrder = columns.map(v => { 12 | if (typeof v === 'string') { 13 | return v; 14 | } 15 | if (!noHeader) { 16 | column.push((typeof v.title !== 'undefined') ? v.title : v.key); 17 | } 18 | return v.key; 19 | }); 20 | if (column.length > 0) { 21 | content.push(column.join(separator)); 22 | } 23 | } else { 24 | columnOrder = []; 25 | datas.forEach(v => { 26 | if (!Array.isArray(v)) { 27 | columnOrder = columnOrder.concat(Object.keys(v)); 28 | } 29 | }); 30 | if (columnOrder.length > 0) { 31 | columnOrder = columnOrder.filter((value, index, self) => self.indexOf(value) === index); 32 | 33 | if (!noHeader) { 34 | content.push(columnOrder.join(separator)); 35 | } 36 | } 37 | } 38 | 39 | if (Array.isArray(datas)) { 40 | datas.map(v => { 41 | if (Array.isArray(v)) { 42 | return v; 43 | } 44 | return columnOrder.map(k => { 45 | if (typeof v[k] !== 'undefined') { 46 | return v[k]; 47 | } 48 | return ''; 49 | }); 50 | }).forEach(v => { 51 | content.push(v.join(separator)); 52 | }); 53 | } 54 | return content.join(newLine); 55 | } -------------------------------------------------------------------------------- /src/utils/date.js: -------------------------------------------------------------------------------- 1 | /*eslint-disable*/ 2 | // 把 YYYY-MM-DD 改成了 yyyy-MM-dd 3 | (function (main) { 4 | 'use strict'; 5 | 6 | /** 7 | * Parse or format dates 8 | * @class fecha 9 | */ 10 | var fecha = {}; 11 | var token = /d{1,4}|M{1,4}|yy(?:yy)?|S{1,3}|Do|ZZ|([HhMsDm])\1?|[aA]|"[^"]*"|'[^']*'/g; 12 | var twoDigits = /\d\d?/; 13 | var threeDigits = /\d{3}/; 14 | var fourDigits = /\d{4}/; 15 | var word = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i; 16 | var noop = function () { 17 | }; 18 | 19 | function shorten(arr, sLen) { 20 | var newArr = []; 21 | for (var i = 0, len = arr.length; i < len; i++) { 22 | newArr.push(arr[i].substr(0, sLen)); 23 | } 24 | return newArr; 25 | } 26 | 27 | function monthUpdate(arrName) { 28 | return function (d, v, i18n) { 29 | var index = i18n[arrName].indexOf(v.charAt(0).toUpperCase() + v.substr(1).toLowerCase()); 30 | if (~index) { 31 | d.month = index; 32 | } 33 | }; 34 | } 35 | 36 | function pad(val, len) { 37 | val = String(val); 38 | len = len || 2; 39 | while (val.length < len) { 40 | val = '0' + val; 41 | } 42 | return val; 43 | } 44 | 45 | var dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; 46 | var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; 47 | var monthNamesShort = shorten(monthNames, 3); 48 | var dayNamesShort = shorten(dayNames, 3); 49 | fecha.i18n = { 50 | dayNamesShort: dayNamesShort, 51 | dayNames: dayNames, 52 | monthNamesShort: monthNamesShort, 53 | monthNames: monthNames, 54 | amPm: ['am', 'pm'], 55 | DoFn: function DoFn(D) { 56 | return D + ['th', 'st', 'nd', 'rd'][D % 10 > 3 ? 0 : (D - D % 10 !== 10) * D % 10]; 57 | } 58 | }; 59 | 60 | var formatFlags = { 61 | D: function (dateObj) { 62 | return dateObj.getDay(); 63 | }, 64 | DD: function (dateObj) { 65 | return pad(dateObj.getDay()); 66 | }, 67 | Do: function (dateObj, i18n) { 68 | return i18n.DoFn(dateObj.getDate()); 69 | }, 70 | d: function (dateObj) { 71 | return dateObj.getDate(); 72 | }, 73 | dd: function (dateObj) { 74 | return pad(dateObj.getDate()); 75 | }, 76 | ddd: function (dateObj, i18n) { 77 | return i18n.dayNamesShort[dateObj.getDay()]; 78 | }, 79 | dddd: function (dateObj, i18n) { 80 | return i18n.dayNames[dateObj.getDay()]; 81 | }, 82 | M: function (dateObj) { 83 | return dateObj.getMonth() + 1; 84 | }, 85 | MM: function (dateObj) { 86 | return pad(dateObj.getMonth() + 1); 87 | }, 88 | MMM: function (dateObj, i18n) { 89 | return i18n.monthNamesShort[dateObj.getMonth()]; 90 | }, 91 | MMMM: function (dateObj, i18n) { 92 | return i18n.monthNames[dateObj.getMonth()]; 93 | }, 94 | yy: function (dateObj) { 95 | return String(dateObj.getFullYear()).substr(2); 96 | }, 97 | yyyy: function (dateObj) { 98 | return dateObj.getFullYear(); 99 | }, 100 | h: function (dateObj) { 101 | return dateObj.getHours() % 12 || 12; 102 | }, 103 | hh: function (dateObj) { 104 | return pad(dateObj.getHours() % 12 || 12); 105 | }, 106 | H: function (dateObj) { 107 | return dateObj.getHours(); 108 | }, 109 | HH: function (dateObj) { 110 | return pad(dateObj.getHours()); 111 | }, 112 | m: function (dateObj) { 113 | return dateObj.getMinutes(); 114 | }, 115 | mm: function (dateObj) { 116 | return pad(dateObj.getMinutes()); 117 | }, 118 | s: function (dateObj) { 119 | return dateObj.getSeconds(); 120 | }, 121 | ss: function (dateObj) { 122 | return pad(dateObj.getSeconds()); 123 | }, 124 | S: function (dateObj) { 125 | return Math.round(dateObj.getMilliseconds() / 100); 126 | }, 127 | SS: function (dateObj) { 128 | return pad(Math.round(dateObj.getMilliseconds() / 10), 2); 129 | }, 130 | SSS: function (dateObj) { 131 | return pad(dateObj.getMilliseconds(), 3); 132 | }, 133 | a: function (dateObj, i18n) { 134 | return dateObj.getHours() < 12 ? i18n.amPm[0] : i18n.amPm[1]; 135 | }, 136 | A: function (dateObj, i18n) { 137 | return dateObj.getHours() < 12 ? i18n.amPm[0].toUpperCase() : i18n.amPm[1].toUpperCase(); 138 | }, 139 | ZZ: function (dateObj) { 140 | var o = dateObj.getTimezoneOffset(); 141 | return (o > 0 ? '-' : '+') + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4); 142 | } 143 | }; 144 | 145 | var parseFlags = { 146 | d: [twoDigits, function (d, v) { 147 | d.day = v; 148 | }], 149 | M: [twoDigits, function (d, v) { 150 | d.month = v - 1; 151 | }], 152 | yy: [twoDigits, function (d, v) { 153 | var da = new Date(), cent = +('' + da.getFullYear()).substr(0, 2); 154 | d.year = '' + (v > 68 ? cent - 1 : cent) + v; 155 | }], 156 | h: [twoDigits, function (d, v) { 157 | d.hour = v; 158 | }], 159 | m: [twoDigits, function (d, v) { 160 | d.minute = v; 161 | }], 162 | s: [twoDigits, function (d, v) { 163 | d.second = v; 164 | }], 165 | yyyy: [fourDigits, function (d, v) { 166 | d.year = v; 167 | }], 168 | S: [/\d/, function (d, v) { 169 | d.millisecond = v * 100; 170 | }], 171 | SS: [/\d{2}/, function (d, v) { 172 | d.millisecond = v * 10; 173 | }], 174 | SSS: [threeDigits, function (d, v) { 175 | d.millisecond = v; 176 | }], 177 | D: [twoDigits, noop], 178 | ddd: [word, noop], 179 | MMM: [word, monthUpdate('monthNamesShort')], 180 | MMMM: [word, monthUpdate('monthNames')], 181 | a: [word, function (d, v, i18n) { 182 | var val = v.toLowerCase(); 183 | if (val === i18n.amPm[0]) { 184 | d.isPm = false; 185 | } else if (val === i18n.amPm[1]) { 186 | d.isPm = true; 187 | } 188 | }], 189 | ZZ: [/[\+\-]\d\d:?\d\d/, function (d, v) { 190 | var parts = (v + '').match(/([\+\-]|\d\d)/gi), minutes; 191 | 192 | if (parts) { 193 | minutes = +(parts[1] * 60) + parseInt(parts[2], 10); 194 | d.timezoneOffset = parts[0] === '+' ? minutes : -minutes; 195 | } 196 | }] 197 | }; 198 | parseFlags.DD = parseFlags.DD; 199 | parseFlags.dddd = parseFlags.ddd; 200 | parseFlags.Do = parseFlags.dd = parseFlags.d; 201 | parseFlags.mm = parseFlags.m; 202 | parseFlags.hh = parseFlags.H = parseFlags.HH = parseFlags.h; 203 | parseFlags.MM = parseFlags.M; 204 | parseFlags.ss = parseFlags.s; 205 | parseFlags.A = parseFlags.a; 206 | 207 | 208 | // Some common format strings 209 | fecha.masks = { 210 | 'default': 'ddd MMM dd yyyy HH:mm:ss', 211 | shortDate: 'M/D/yy', 212 | mediumDate: 'MMM d, yyyy', 213 | longDate: 'MMMM d, yyyy', 214 | fullDate: 'dddd, MMMM d, yyyy', 215 | shortTime: 'HH:mm', 216 | mediumTime: 'HH:mm:ss', 217 | longTime: 'HH:mm:ss.SSS' 218 | }; 219 | 220 | /*** 221 | * Format a date 222 | * @method format 223 | * @param {Date|number} dateObj 224 | * @param {string} mask Format of the date, i.e. 'mm-dd-yy' or 'shortDate' 225 | */ 226 | fecha.format = function (dateObj, mask, i18nSettings) { 227 | var i18n = i18nSettings || fecha.i18n; 228 | 229 | if (typeof dateObj === 'number') { 230 | dateObj = new Date(dateObj); 231 | } 232 | 233 | if (Object.prototype.toString.call(dateObj) !== '[object Date]' || isNaN(dateObj.getTime())) { 234 | throw new Error('Invalid Date in fecha.format'); 235 | } 236 | 237 | mask = fecha.masks[mask] || mask || fecha.masks['default']; 238 | 239 | return mask.replace(token, function ($0) { 240 | return $0 in formatFlags ? formatFlags[$0](dateObj, i18n) : $0.slice(1, $0.length - 1); 241 | }); 242 | }; 243 | 244 | /** 245 | * Parse a date string into an object, changes - into / 246 | * @method parse 247 | * @param {string} dateStr Date string 248 | * @param {string} format Date parse format 249 | * @returns {Date|boolean} 250 | */ 251 | fecha.parse = function (dateStr, format, i18nSettings) { 252 | var i18n = i18nSettings || fecha.i18n; 253 | 254 | if (typeof format !== 'string') { 255 | throw new Error('Invalid format in fecha.parse'); 256 | } 257 | 258 | format = fecha.masks[format] || format; 259 | 260 | // Avoid regular expression denial of service, fail early for really long strings 261 | // https://www.owasp.org/index.php/Regular_expression_Denial_of_Service_-_ReDoS 262 | if (dateStr.length > 1000) { 263 | return false; 264 | } 265 | 266 | var isValid = true; 267 | var dateInfo = {}; 268 | format.replace(token, function ($0) { 269 | if (parseFlags[$0]) { 270 | var info = parseFlags[$0]; 271 | var index = dateStr.search(info[0]); 272 | if (!~index) { 273 | isValid = false; 274 | } else { 275 | dateStr.replace(info[0], function (result) { 276 | info[1](dateInfo, result, i18n); 277 | dateStr = dateStr.substr(index + result.length); 278 | return result; 279 | }); 280 | } 281 | } 282 | 283 | return parseFlags[$0] ? '' : $0.slice(1, $0.length - 1); 284 | }); 285 | 286 | if (!isValid) { 287 | return false; 288 | } 289 | 290 | var today = new Date(); 291 | if (dateInfo.isPm === true && dateInfo.hour != null && +dateInfo.hour !== 12) { 292 | dateInfo.hour = +dateInfo.hour + 12; 293 | } else if (dateInfo.isPm === false && +dateInfo.hour === 12) { 294 | dateInfo.hour = 0; 295 | } 296 | 297 | var date; 298 | if (dateInfo.timezoneOffset != null) { 299 | dateInfo.minute = +(dateInfo.minute || 0) - +dateInfo.timezoneOffset; 300 | date = new Date(Date.UTC(dateInfo.year || today.getFullYear(), dateInfo.month || 0, dateInfo.day || 1, 301 | dateInfo.hour || 0, dateInfo.minute || 0, dateInfo.second || 0, dateInfo.millisecond || 0)); 302 | } else { 303 | date = new Date(dateInfo.year || today.getFullYear(), dateInfo.month || 0, dateInfo.day || 1, 304 | dateInfo.hour || 0, dateInfo.minute || 0, dateInfo.second || 0, dateInfo.millisecond || 0); 305 | } 306 | return date; 307 | }; 308 | 309 | /* istanbul ignore next */ 310 | if (typeof module !== 'undefined' && module.exports) { 311 | module.exports = fecha; 312 | } else if (typeof define === 'function' && define.amd) { 313 | define(function () { 314 | return fecha; 315 | }); 316 | } else { 317 | main.fecha = fecha; 318 | } 319 | })(this); 320 | --------------------------------------------------------------------------------