├── .babelrc
├── .editorconfig
├── .gitignore
├── .npmrc
├── .nvmrc
├── .postcssrc.js
├── LICENSE
├── README.md
├── build
├── build.js
├── check-versions.js
├── dev-client.js
├── dev-server.js
├── utils.js
├── vue-loader.conf.js
├── webpack.base.conf.js
├── webpack.dev.conf.js
└── webpack.prod.conf.js
├── config
├── dev.env.js
├── index.js
├── prod.env.js
└── test.env.js
├── demo
├── App.vue
├── main.js
└── test
│ ├── dev-test.vue
│ ├── i18n
│ ├── en-US.js
│ ├── index.js
│ ├── zh-CN.js
│ └── zh-TW.js
│ ├── ms-test.vue
│ └── test-plugin.vue
├── index.html
├── package-lock.json
├── package.json
└── src
├── assets
└── minder
│ ├── iconpriority.png
│ ├── iconprogress.png
│ ├── icons.png
│ └── mold.png
├── components
├── main
│ ├── footer.vue
│ ├── header.vue
│ ├── mainEditor.vue
│ └── navigator.vue
├── menu
│ ├── edit
│ │ ├── attachment.vue
│ │ ├── editDel.vue
│ │ ├── editMenu.vue
│ │ ├── expand.vue
│ │ ├── insertBox.vue
│ │ ├── moveBox.vue
│ │ ├── progressBox.vue
│ │ ├── selection.vue
│ │ ├── sequenceBox.vue
│ │ └── tagBox.vue
│ └── view
│ │ ├── arrange.vue
│ │ ├── fontOperation.vue
│ │ ├── mold.vue
│ │ ├── styleOperation.vue
│ │ ├── theme.vue
│ │ └── viewMenu.vue
└── minderEditor.vue
├── index.js
├── locale
├── format.js
├── index.js
└── lang
│ ├── en-US.js
│ ├── zh-CN.js
│ └── zh-TW.js
├── mixins.js
├── mixins
└── locale.js
├── props.js
├── script
├── editor.js
├── expose-editor.js
├── hotbox.js
├── lang.js
├── minder.js
├── protocol
│ ├── freemind.js
│ ├── json.js
│ ├── markdown.js
│ ├── plain.js
│ ├── png.js
│ ├── svg.js
│ └── xmind.js
├── runtime
│ ├── clipboard-mimetype.js
│ ├── clipboard.js
│ ├── container.js
│ ├── drag.js
│ ├── exports.js
│ ├── fsm.js
│ ├── history.js
│ ├── hotbox.js
│ ├── input.js
│ ├── jumping.js
│ ├── minder.js
│ ├── node.js
│ ├── priority.js
│ ├── progress.js
│ ├── receiver.js
│ └── tag.js
├── store.js
└── tool
│ ├── debug.js
│ ├── format.js
│ ├── innertext.js
│ ├── key.js
│ ├── keymap.js
│ └── utils.js
└── style
├── dropdown-list.scss
├── editor.scss
├── header.scss
├── hotbox.scss
├── mixin.scss
├── navigator.scss
└── normalize.css
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "env",
5 | {
6 | "modules": false,
7 | "targets": {
8 | "browsers": [
9 | "> 1%",
10 | "last 2 versions",
11 | "not ie <= 8"
12 | ]
13 | }
14 | }
15 | ],
16 | "stage-2",
17 | [
18 | "es2015",
19 | {
20 | "modules": false
21 | }
22 | ]
23 | ],
24 | "plugins": [
25 | "transform-vue-jsx",
26 | "syntax-dynamic-import"
27 | ],
28 | "env": {
29 | "test": {
30 | "presets": [
31 | "env",
32 | "stage-2"
33 | ],
34 | "plugins": [
35 | "istanbul"
36 | ]
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | npm-debug.log
4 | test/unit/coverage
5 | test/e2e/reports
6 | selenium-debug.log
7 | /.idea/
8 | dist
9 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | tag-version-prefix=""
2 | message="Chore(release): %s"
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v18
2 |
--------------------------------------------------------------------------------
/.postcssrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "plugins": {
3 | "autoprefixer": {}
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2019, 刘毅
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/build/build.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')()
2 |
3 | process.env.NODE_ENV = 'production'
4 |
5 | const ora = require('ora')
6 | const rm = require('rimraf')
7 | const path = require('path')
8 | const chalk = require('chalk')
9 | const webpack = require('webpack')
10 | const config = require('../config')
11 | const webpackConfig = require('./webpack.prod.conf')
12 |
13 | const spinner = ora('building for production...')
14 | spinner.start()
15 |
16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
17 | if (err) throw err
18 | webpack(webpackConfig, (err, stats) => {
19 | spinner.stop()
20 | if (err) throw err
21 | process.stdout.write(stats.toString({
22 | colors: true,
23 | modules: false,
24 | children: false,
25 | chunks: false,
26 | chunkModules: false
27 | }) + '\n\n')
28 |
29 | if (stats.hasErrors()) {
30 | console.log(chalk.red(' Build failed with errors.\n'))
31 | process.exit(1)
32 | }
33 |
34 | console.log(chalk.cyan(' Build complete.\n'))
35 | console.log(chalk.yellow(
36 | ' Tip: built files are meant to be served over an HTTP server.\n' +
37 | ' Opening index.html over file:// won\'t work.\n'
38 | ))
39 | })
40 | })
41 |
--------------------------------------------------------------------------------
/build/check-versions.js:
--------------------------------------------------------------------------------
1 | var chalk = require('chalk')
2 | var semver = require('semver')
3 | var packageConfig = require('../package.json')
4 | var shell = require('shelljs')
5 | function exec (cmd) {
6 | return require('child_process').execSync(cmd).toString().trim()
7 | }
8 |
9 | var versionRequirements = [
10 | {
11 | name: 'node',
12 | currentVersion: semver.clean(process.version),
13 | versionRequirement: packageConfig.engines.node
14 | },
15 | ]
16 |
17 | if (shell.which('npm')) {
18 | versionRequirements.push({
19 | name: 'npm',
20 | currentVersion: exec('npm --version'),
21 | versionRequirement: packageConfig.engines.npm
22 | })
23 | }
24 |
25 | module.exports = function () {
26 | var warnings = []
27 | for (var i = 0; i < versionRequirements.length; i++) {
28 | var mod = versionRequirements[i]
29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
30 | warnings.push(mod.name + ': ' +
31 | chalk.red(mod.currentVersion) + ' should be ' +
32 | chalk.green(mod.versionRequirement)
33 | )
34 | }
35 | }
36 |
37 | if (warnings.length) {
38 | console.log('')
39 | console.log(chalk.yellow('To use this template, you must update following to modules:'))
40 | console.log()
41 | for (var i = 0; i < warnings.length; i++) {
42 | var warning = warnings[i]
43 | console.log(' ' + warning)
44 | }
45 | console.log()
46 | process.exit(1)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/build/dev-client.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | require('eventsource-polyfill')
3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
4 |
5 | hotClient.subscribe(function (event) {
6 | if (event.action === 'reload') {
7 | window.location.reload()
8 | }
9 | })
10 |
--------------------------------------------------------------------------------
/build/dev-server.js:
--------------------------------------------------------------------------------
1 | require('./check-versions')()
2 |
3 | const config = require('../config')
4 | if (!process.env.NODE_ENV) {
5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
6 | }
7 |
8 | const opn = require('opn')
9 | const path = require('path')
10 | const os = require('os')
11 | const express = require('express')
12 | const webpack = require('webpack')
13 | const proxyMiddleware = require('http-proxy-middleware')
14 | const webpackConfig = require('./webpack.dev.conf')
15 | const port = process.env.PORT || config.dev.port
16 | const autoOpenBrowser = !!config.dev.autoOpenBrowser
17 | const proxyTable = config.dev.proxyTable
18 | const app = express()
19 | const compiler = webpack(webpackConfig)
20 | const chalk = require('chalk')
21 |
22 | const devMiddleware = require('webpack-dev-middleware')(compiler, {
23 | publicPath: webpackConfig.output.publicPath,
24 | quiet: true
25 | })
26 |
27 | const hotMiddleware = require('webpack-hot-middleware')(compiler, {
28 | log: () => {}
29 | })
30 | compiler.hooks.compilation.tap('WebpackTranslationPlugin', (compilation) => {
31 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
32 | hotMiddleware.publish({ action: 'reload' })
33 | if (typeof cb == "function") {
34 | cb()
35 | }
36 | })
37 | })
38 |
39 | Object.keys(proxyTable).forEach(function (context) {
40 | let options = proxyTable[context]
41 | if (typeof options === 'string') {
42 | options = { target: options }
43 | }
44 | app.use(proxyMiddleware(options.filter || context, options))
45 | })
46 |
47 | app.use(require('connect-history-api-fallback')())
48 | app.use(devMiddleware)
49 | app.use(hotMiddleware)
50 |
51 | const staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
52 | app.use(staticPath, express.static('./static'))
53 |
54 | let networkIP = ''
55 | try {
56 | networkIP = os.networkInterfaces().en0[1].address
57 | } catch (e) {
58 | networkIP = '';
59 | }
60 |
61 | let _resolve
62 | const readyPromise = new Promise(resolve => {
63 | _resolve = resolve
64 | })
65 |
66 | const localhostURL = `http://localhost:${port}`
67 | const networkURL = `http://${networkIP}:${port}`
68 | console.log(chalk.green('> 正在启动本地服务...'))
69 | devMiddleware.waitUntilValid(() => {
70 | console.log('\n App running at:')
71 | console.log(` - Local: ${chalk.cyan(localhostURL)}`)
72 | console.log(` - Network: ${chalk.cyan(networkURL)}\n`)
73 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
74 | opn(localhostURL)
75 | }
76 | _resolve()
77 | })
78 |
79 | const server = app.listen(port)
80 |
81 | module.exports = {
82 | ready: readyPromise,
83 | close: () => {
84 | server.close()
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/build/utils.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 | var config = require('../config')
3 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
4 | const packageConfig = require('../package.json')
5 |
6 | exports.assetsPath = function (_path) {
7 | var assetsSubDirectory = process.env.NODE_ENV === 'production' ?
8 | config.build.assetsSubDirectory :
9 | config.dev.assetsSubDirectory
10 | return path.posix.join(assetsSubDirectory, _path)
11 | },
12 |
13 | exports.createNotifierCallback = () => {
14 | const notifier = require('node-notifier')
15 |
16 | return (severity, errors) => {
17 | if (severity !== 'error') return
18 |
19 | const error = errors[0]
20 | const filename = error.file && error.file.split('!').pop()
21 |
22 | notifier.notify({
23 | title: packageConfig.name,
24 | message: severity + ': ' + error.name,
25 | subtitle: filename || '',
26 | icon: path.join(__dirname, 'logo.png')
27 | })
28 | }
29 | }
30 |
31 | exports.cssLoaders = function (options) {
32 | options = options || {}
33 |
34 | var cssLoader = {
35 | loader: 'css-loader',
36 | options: {
37 | minimize: process.env.NODE_ENV === 'production',
38 | sourceMap: options.sourceMap
39 | }
40 | }
41 |
42 | function generateLoaders(loader, loaderOptions) {
43 | var loaders = [cssLoader]
44 | if (loader) {
45 | loaders.push({
46 | loader: loader + '-loader',
47 | options: Object.assign({}, loaderOptions, {
48 | sourceMap: options.sourceMap
49 | })
50 | })
51 | }
52 |
53 | if (options.extract) {
54 | return [MiniCssExtractPlugin.loader].concat(loaders)
55 | } else {
56 | return ['vue-style-loader'].concat(loaders)
57 | }
58 | }
59 |
60 | return {
61 | css: generateLoaders(),
62 | postcss: generateLoaders(),
63 | scss: generateLoaders('sass'),
64 | }
65 | }
66 |
67 | exports.styleLoaders = function (options) {
68 | var output = []
69 | var loaders = exports.cssLoaders(options)
70 | for (var extension in loaders) {
71 | var loader = loaders[extension]
72 | output.push({
73 | test: new RegExp('\\.' + extension + '$'),
74 | use: loader
75 | })
76 | }
77 | return output
78 | }
79 |
--------------------------------------------------------------------------------
/build/vue-loader.conf.js:
--------------------------------------------------------------------------------
1 | const utils = require('./utils')
2 | const config = require('../config')
3 | const isProduction = process.env.NODE_ENV === 'production'
4 | const sourceMapEnabled = isProduction
5 | ? config.build.productionSourceMap
6 | : config.dev.cssSourceMap
7 |
8 | module.exports = {
9 | loaders: utils.cssLoaders({
10 | sourceMap: sourceMapEnabled,
11 | extract: isProduction
12 | }),
13 | cssSourceMap: sourceMapEnabled,
14 | cacheBusting: config.dev.cacheBusting,
15 | transformToRequire: {
16 | video: ['src', 'poster'],
17 | source: 'src',
18 | img: 'src',
19 | image: 'xlink:href'
20 | }
21 | }
--------------------------------------------------------------------------------
/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const utils = require('./utils')
3 | const config = require('../config')
4 | const vueLoaderConfig = require('./vue-loader.conf')
5 | const VueLoaderPlugin = require('vue-loader/lib/plugin');
6 | const webpack = require('webpack')
7 |
8 | function resolve(dir) {
9 | return path.join(__dirname, '..', dir)
10 | }
11 |
12 | module.exports = {
13 | context: path.resolve(__dirname, '../'),
14 | entry: {
15 | app: './demo/main.js'
16 | },
17 | output: {
18 | path: config.build.assetsRoot,
19 | filename: '[name].js',
20 | globalObject: "this",
21 | publicPath: process.env.NODE_ENV === 'production' ?
22 | config.build.assetsPublicPath : config.dev.assetsPublicPath
23 | },
24 | resolve: {
25 | extensions: ['*', '.js', '.vue', '.json'],
26 | alias: {
27 | 'vue$': 'vue/dist/vue.esm.js',
28 | '@': resolve('src')
29 | }
30 | },
31 | module: {
32 | rules: [{
33 | test: /\.vue$/,
34 | loader: 'vue-loader',
35 | options: vueLoaderConfig
36 | },
37 | {
38 | test: /\.js$/,
39 | loader: 'babel-loader',
40 | include: [
41 | resolve('src'),
42 | resolve('test'),
43 | resolve('node_modules/element-ui/packages'),
44 | resolve('node_modules/element-ui/src'),
45 | resolve('node_modules/element-ui/src'),
46 | resolve('node_modules/hotbox-minder/src'),
47 | resolve('node_modules/@7polo/kity/src'),
48 | resolve('node_modules/@7polo/kityminder-core/src'),
49 | ]
50 | },
51 | {
52 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
53 | loader: 'url-loader',
54 | options: {
55 | limit: 40000,
56 | name: utils.assetsPath('img/[name].[hash:7].[ext]')
57 | }
58 | },
59 | {
60 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
61 | loader: 'url-loader',
62 | options: {
63 | limit: 40000,
64 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
65 | }
66 | },
67 | {
68 | test: /\.scss$/,
69 | loaders: ["style-loader", "css-loader", "sass-loader"]
70 | },
71 | {
72 | test: /.md$/,
73 | loader: "text-loader"
74 | }
75 | ]
76 | },
77 | plugins: [
78 | new VueLoaderPlugin(),
79 | ],
80 | node: {
81 | setImmediate: false,
82 | dgram: 'empty',
83 | fs: 'empty',
84 | net: 'empty',
85 | tls: 'empty',
86 | child_process: 'empty'
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | const utils = require('./utils')
2 | const webpack = require('webpack')
3 | const config = require('../config')
4 | const merge = require('webpack-merge')
5 | const path = require('path')
6 | const baseWebpackConfig = require('./webpack.base.conf')
7 | const HtmlWebpackPlugin = require('html-webpack-plugin')
8 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
9 |
10 | const HOST = process.env.HOST
11 | const PORT = process.env.PORT && Number(process.env.PORT)
12 |
13 | Object.keys(baseWebpackConfig.entry).forEach(function (name) {
14 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
15 | })
16 |
17 | module.exports = merge(baseWebpackConfig, {
18 | mode: 'development',
19 | module: {
20 | rules: [{
21 | test: /\.less$/,
22 | use: [
23 | 'vue-style-loader',
24 | {
25 | loader: 'css-loader',
26 | options: {
27 | sourceMap: config.dev.cssSourceMap
28 | }
29 | },
30 | {
31 | loader: 'less-loader'
32 | },
33 | 'postcss-loader'
34 | ]
35 | },
36 | {
37 | test: /\.css$/,
38 | use: [
39 | 'vue-style-loader',
40 | {
41 | loader: 'css-loader',
42 | options: {
43 | importLoaders: 1,
44 | sourceMap: config.dev.cssSourceMap
45 | }
46 | },
47 | 'postcss-loader'
48 | ]
49 | }
50 | ]
51 | },
52 | devtool: config.dev.devtool,
53 | devServer: {
54 | clientLogLevel: 'warning',
55 | historyApiFallback: {
56 | rewrites: [{
57 | from: /.*/,
58 | to: path.posix.join(config.dev.assetsPublicPath, 'index.html')
59 | },],
60 | },
61 | hot: true,
62 | contentBase: false,
63 | compress: true,
64 | host: HOST || config.dev.host,
65 | port: PORT || config.dev.port,
66 | open: config.dev.autoOpenBrowser,
67 | overlay: config.dev.errorOverlay ?
68 | {
69 | errors: true
70 | } :
71 | false,
72 | publicPath: config.dev.assetsPublicPath,
73 | proxy: config.dev.proxyTable,
74 | quiet: true // necessary for FriendlyErrorsPlugin
75 | },
76 | watchOptions: {
77 | ignored: /node_modules/,
78 | poll: 1000, //每秒钟询问变化次数,建议设置1000
79 | aggregateTimeout: 500 //累计的超时
80 | },
81 | optimization: {
82 | noEmitOnErrors: true,
83 | namedModules: true
84 | },
85 | plugins: [
86 | new webpack.DefinePlugin({
87 | 'process.env': config.dev.env
88 | }),
89 | new webpack.HotModuleReplacementPlugin(),
90 | new webpack.NoEmitOnErrorsPlugin(),
91 | new HtmlWebpackPlugin({
92 | filename: 'index.html',
93 | template: 'index.html',
94 | inject: true
95 | }),
96 | new FriendlyErrorsPlugin(),
97 | ]
98 | })
99 |
--------------------------------------------------------------------------------
/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | const utils = require('./utils')
2 | const path = require('path')
3 | const webpack = require('webpack')
4 | const config = require('../config')
5 | const merge = require('webpack-merge')
6 | const baseWebpackConfig = require('./webpack.base.conf')
7 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
8 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
9 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
10 |
11 | const env = config.build.env
12 |
13 | const webpackConfig = merge(baseWebpackConfig, {
14 | mode: 'production',
15 | // mode: "development",
16 | entry: './src/index.js',
17 | output: {
18 | path: config.build.assetsRoot,
19 | publicPath: '/dist/',
20 | filename: utils.assetsPath('vue-minder-editor-extended.js'),
21 | chunkFilename: utils.assetsPath('[name].[chunkhash].js'),
22 | library: 'vueMinderEditorExtended',
23 | libraryTarget: 'umd',
24 | umdNamedDefine: true,
25 | },
26 | module: {
27 | rules: [
28 | {
29 | test: /\.css$/,
30 | use: [
31 | 'vue-style-loader',
32 | 'css-loader',
33 | 'postcss-loader'
34 | ]
35 | }
36 | ]
37 | },
38 | devtool: config.build.productionSourceMap ? config.build.devtool : false,
39 | performance: {
40 | hints: false, // 关闭性能提示
41 | },
42 | optimization: {
43 | },
44 | plugins: [
45 | new webpack.ProgressPlugin(),
46 | new webpack.DefinePlugin({
47 | 'process.env': env
48 | }),
49 | new webpack.HashedModuleIdsPlugin(),
50 | new webpack.optimize.ModuleConcatenationPlugin(),
51 | new UglifyJsPlugin({
52 | uglifyOptions: {
53 | show_copyright: false,
54 | comments: false,
55 | compress: {
56 | drop_debugger: true,
57 | drop_console: false
58 | }
59 | },
60 | sourceMap: config.build.productionSourceMap,
61 | parallel: true
62 | }),
63 | new OptimizeCSSPlugin({
64 | cssProcessorOptions: config.build.productionSourceMap
65 | ? { safe: true, map: { inline: false } }
66 | : { safe: true }
67 | }),
68 | new MiniCssExtractPlugin({
69 | filename: 'focus.index.[contenthash:8].css'
70 | }),
71 | ]
72 | })
73 |
74 | if (config.build.productionGzip) {
75 | var CompressionWebpackPlugin = require('compression-webpack-plugin')
76 | webpackConfig.plugins.push(
77 | new CompressionWebpackPlugin({
78 | asset: '[path].gz[query]',
79 | algorithm: 'gzip',
80 | test: new RegExp(
81 | '\\.(' +
82 | config.build.productionGzipExtensions.join('|') +
83 | ')$'
84 | ),
85 | threshold: 10240,
86 | minRatio: 0.8
87 | })
88 | )
89 | }
90 |
91 | if (config.build.bundleAnalyzerReport) {
92 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
93 | webpackConfig.plugins.push(new BundleAnalyzerPlugin())
94 | }
95 |
96 | module.exports = webpackConfig
97 |
--------------------------------------------------------------------------------
/config/dev.env.js:
--------------------------------------------------------------------------------
1 | var merge = require('webpack-merge')
2 | var prodEnv = require('./prod.env')
3 |
4 | module.exports = merge(prodEnv, {
5 | NODE_ENV: '"development"'
6 | })
7 |
--------------------------------------------------------------------------------
/config/index.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = {
4 | build: {
5 | env: require('./prod.env'),
6 | index: path.resolve(__dirname, '../dist/index.html'),
7 | assetsRoot: path.resolve(__dirname, '../dist'),
8 | assetsSubDirectory: 'static',
9 | assetsPublicPath: '/',
10 | productionSourceMap: true,
11 | productionGzip: true,
12 | productionGzipExtensions: ['js', 'css'],
13 | bundleAnalyzerReport: process.env.npm_config_report,
14 | devtool: "eval"
15 | //devtool: 'cheap-module-eval-source-map'
16 | },
17 | dev: {
18 | env: require('./dev.env'),
19 | port: 8088,
20 | autoOpenBrowser: true,
21 | assetsSubDirectory: 'static',
22 | assetsPublicPath: '/',
23 | proxyTable: {},
24 | cssSourceMap: true,
25 | cacheBusting: true,
26 | devtool: 'cheap-module-eval-source-map',
27 | poll: false,
28 | errorOverlay: true,
29 | notifyOnErrors: true
30 | },
31 | }
32 |
--------------------------------------------------------------------------------
/config/prod.env.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | NODE_ENV: '"production"'
3 | }
4 |
--------------------------------------------------------------------------------
/config/test.env.js:
--------------------------------------------------------------------------------
1 | var merge = require('webpack-merge')
2 | var devEnv = require('./dev.env')
3 |
4 | module.exports = merge(devEnv, {
5 | NODE_ENV: '"testing"'
6 | })
7 |
--------------------------------------------------------------------------------
/demo/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
24 |
25 |
30 |
--------------------------------------------------------------------------------
/demo/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import App from './App';
3 | import 'element-ui/lib/theme-chalk/index.css';
4 | import ElementUI from 'element-ui';
5 | import vueMinderEditorExtended from "../src/index"
6 | // 使用打包后的文件
7 | // import vueMinderEditorExtended from "../dist/static/vue-minder-editor-extended";
8 | Vue.config.productionTip = true;
9 |
10 | // 方式一
11 | // import locale from '/src/locale/lang/en-US'
12 | // Vue.use(vueMinderEditorExtended, {
13 | // locale
14 | // });
15 |
16 | // 方式二
17 | // import lang from '/src/locale/lang/en-US'
18 | // import locale from '/src/locale'
19 | // // 设置语言
20 | // locale.use(lang)
21 | // Vue.use(vueMinderEditorExtended);
22 |
23 | // 方式三
24 | import i18n from "./test/i18n/index";
25 | Vue.use(vueMinderEditorExtended, {
26 | i18n: (key, value) => i18n.t(key, value)
27 | });
28 |
29 | Vue.use(ElementUI, {
30 | i18n: (key, value) => i18n.t(key, value)
31 | });
32 |
33 | new Vue({
34 | el: '#app',
35 | template: '',
36 | components: {
37 | App
38 | },
39 | i18n
40 | })
41 |
--------------------------------------------------------------------------------
/demo/test/dev-test.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
重载
13 |
14 |
39 |
40 | edit-menu 插槽自定义内容
41 |
42 |
43 | view-menu 插槽自定义内容
44 |
45 | 默认插槽内容
46 |
47 |
48 |
49 |
50 |
265 |
266 |
269 |
--------------------------------------------------------------------------------
/demo/test/i18n/en-US.js:
--------------------------------------------------------------------------------
1 | export default {
2 | en_US: 'English',
3 | zh_CN: 'Chinese simplified',
4 | zh_TW: 'Chinese traditional'
5 | }
6 |
--------------------------------------------------------------------------------
/demo/test/i18n/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import VueI18n from "vue-i18n";
3 | import enLocale from "element-ui/lib/locale/lang/en";
4 | import zh_CNLocale from "element-ui/lib/locale/lang/zh-CN";
5 | import zh_TWLocale from "element-ui/lib/locale/lang/zh-TW";
6 | import zh_CN from "./zh-CN";
7 | import en_US from "./en-US";
8 | import zh_TW from "./zh-TW";
9 |
10 | import minder_zh_CN from "../../../src/locale/lang/zh-CN";
11 | import minder_en_US from "../../../src/locale/lang/en-US";
12 | import minder_zh_TW from "../../../src/locale/lang/zh-TW";
13 |
14 | export const CURRENT_LANGUAGE = 'current_language';
15 |
16 | Vue.use(VueI18n);
17 |
18 | const messages = {
19 | 'en_US': {
20 | ...enLocale,
21 | ...en_US,
22 | ...minder_en_US
23 | },
24 | 'zh_CN': {
25 | ...zh_CNLocale,
26 | ...zh_CN,
27 | ...minder_zh_CN
28 |
29 | },
30 | 'zh_TW': {
31 | ...zh_TWLocale,
32 | ...zh_TW,
33 | ...minder_zh_TW
34 |
35 | }
36 | };
37 |
38 | const index = new VueI18n({
39 | locale: 'zh_CN',
40 | messages,
41 | silentTranslationWarn: true
42 | });
43 |
44 | const loadedLanguages = ['en_US', 'zh_CN', 'zh_TW'];
45 |
46 | function setI18nLanguage(lang) {
47 | index.locale = lang;
48 | document.querySelector('html').setAttribute('lang', lang);
49 | localStorage.setItem(CURRENT_LANGUAGE, lang);
50 | return lang;
51 | }
52 |
53 | Vue.prototype.$setLang = function (lang) {
54 | if (index.locale !== lang) {
55 | if (!loadedLanguages.includes(lang)) {
56 | let file = lang.replace("_", "-");
57 | return import(`./${file}`).then(response => {
58 | index.mergeLocaleMessage(lang, response.default);
59 | loadedLanguages.push(lang);
60 | return setI18nLanguage(lang)
61 | })
62 | }
63 | return Promise.resolve(setI18nLanguage(lang))
64 | }
65 | return Promise.resolve(lang)
66 | };
67 |
68 | export default index;
69 |
--------------------------------------------------------------------------------
/demo/test/i18n/zh-CN.js:
--------------------------------------------------------------------------------
1 | export default {
2 | en_US: '英语',
3 | zh_CN: '中文简体',
4 | zh_TW: '中文繁体'
5 | }
6 |
--------------------------------------------------------------------------------
/demo/test/i18n/zh-TW.js:
--------------------------------------------------------------------------------
1 | export default {
2 | en_US: '英語',
3 | zh_CN: '中文簡體',
4 | zh_TW: '中文繁體'
5 | }
6 |
--------------------------------------------------------------------------------
/demo/test/ms-test.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | caaaa
10 |
11 |
12 |
13 | cccc
14 |
15 |
16 |
17 |
18 | sfsdfc
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
72 |
73 |
76 |
--------------------------------------------------------------------------------
/demo/test/test-plugin.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | caaaa
8 |
9 |
10 |
11 | cccc
12 |
13 |
14 |
15 |
16 | sfsdf
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
81 |
82 |
85 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vue-Minder-Editor
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-minder-editor-extended",
3 | "version": "1.3.3",
4 | "description": "基于 Vue2 的脑图编辑组件",
5 | "author": "Lruihao (https://lruihao.cn)",
6 | "original": "AgAngle <1323481023@qq.com>",
7 | "license": "BSD 3-Clause",
8 | "scripts": {
9 | "dev": "node build/dev-server.js",
10 | "build": "node build/build.js",
11 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
12 | "e2e": "node test/e2e/runner.js",
13 | "test": "npm run unit && npm run e2e",
14 | "version": "npm run build && npm publish"
15 | },
16 | "main": "dist/static/vue-minder-editor-extended.js",
17 | "dependencies": {
18 | "@7polo/kity": "^2.0.8",
19 | "@cell-x/kityminder-core": "^1.4.52",
20 | "element-ui": "^2.15.14",
21 | "hotbox-minder": "^1.0.15",
22 | "vue": "2.6.14",
23 | "vue-i18n": "^8.28.2"
24 | },
25 | "devDependencies": {
26 | "acorn": "^6.4.2",
27 | "assets-webpack-plugin": "^3.9.12",
28 | "autoprefixer": "^6.7.7",
29 | "babel-core": "^6.26.3",
30 | "babel-helper-vue-jsx-merge-props": "^2.0.3",
31 | "babel-loader": "^7.1.5",
32 | "babel-plugin-syntax-dynamic-import": "^6.18.0",
33 | "babel-plugin-syntax-jsx": "^6.18.0",
34 | "babel-plugin-transform-imports": "1.5.0",
35 | "babel-plugin-transform-vue-jsx": "^3.7.0",
36 | "babel-polyfill": "^6.26.0",
37 | "babel-preset-env": "^1.7.0",
38 | "babel-preset-es2015": "^6.24.1",
39 | "babel-preset-stage-2": "^6.24.1",
40 | "babel-register": "^6.26.0",
41 | "chai": "^3.5.0",
42 | "chalk": "^1.1.3",
43 | "compression-webpack-plugin": "^1.1.12",
44 | "connect-history-api-fallback": "^1.6.0",
45 | "cross-env": "^3.2.4",
46 | "cross-spawn": "^5.1.0",
47 | "css-loader": "^0.28.11",
48 | "eslint": "^5.16.0",
49 | "eslint-config-standard": "^12.0.0",
50 | "eslint-plugin-import": "^2.31.0",
51 | "eslint-plugin-node": "^8.0.1",
52 | "eslint-plugin-promise": "^4.3.1",
53 | "eslint-plugin-standard": "^4.1.0",
54 | "eventsource-polyfill": "^0.9.6",
55 | "express": "^4.21.2",
56 | "extract-zip": "^1.7.0",
57 | "file-loader": "^2.0.0",
58 | "friendly-errors-webpack-plugin": "^1.7.0",
59 | "function-bind": "^1.1.2",
60 | "html-loader": "^0.5.5",
61 | "html-webpack-plugin": "^3.2.0",
62 | "http-proxy-middleware": "^0.17.4",
63 | "inject-loader": "^2.0.1",
64 | "lolex": "^1.6.0",
65 | "mini-css-extract-plugin": "1.6.2",
66 | "nightwatch": "^0.9.21",
67 | "opn": "^4.0.2",
68 | "optimize-css-assets-webpack-plugin": "^1.3.2",
69 | "ora": "^1.4.0",
70 | "phantomjs-prebuilt": "^2.1.16",
71 | "postcss-import": "^12.0.1",
72 | "postcss-loader": "^3.0.0",
73 | "postcss-url": "^8.0.0",
74 | "rimraf": "^2.7.1",
75 | "sass": "~1.32.6",
76 | "sass-loader": "^10.5.2",
77 | "semver": "^5.7.2",
78 | "shelljs": "^0.8.5",
79 | "sinon": "^1.17.7",
80 | "sinon-chai": "^2.14.0",
81 | "style-loader": "^0.21.0",
82 | "text-loader": "0.0.1",
83 | "uglifyjs-webpack-plugin": "^2.2.0",
84 | "url-loader": "^0.5.9",
85 | "vue-loader": "^15.11.1",
86 | "vue-style-loader": "^2.0.5",
87 | "vue-template-compiler": "2.6.14",
88 | "webpack": "^4.47.0",
89 | "webpack-bundle-analyzer": "^3.9.0",
90 | "webpack-cli": "^3.3.12",
91 | "webpack-dev-middleware": "^3.7.3",
92 | "webpack-dev-server": "^3.11.3",
93 | "webpack-hot-middleware": "^2.26.1",
94 | "webpack-merge": "^4.2.2"
95 | },
96 | "engines": {
97 | "node": ">= 14 < 20",
98 | "npm": ">= 9"
99 | },
100 | "browserslist": [
101 | "> 1%",
102 | "last 2 versions",
103 | "not ie <= 8"
104 | ],
105 | "keywords": [
106 | "vue",
107 | "minder",
108 | "editor"
109 | ],
110 | "repository": {
111 | "type": "git",
112 | "url": "https://github.com/Lruihao/vue-minder-editor-extended.git"
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/assets/minder/iconpriority.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lruihao/vue-minder-editor-extended/98f321f3b2f087fa92173f8e6735e24e5f6cf018/src/assets/minder/iconpriority.png
--------------------------------------------------------------------------------
/src/assets/minder/iconprogress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lruihao/vue-minder-editor-extended/98f321f3b2f087fa92173f8e6735e24e5f6cf018/src/assets/minder/iconprogress.png
--------------------------------------------------------------------------------
/src/assets/minder/icons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lruihao/vue-minder-editor-extended/98f321f3b2f087fa92173f8e6735e24e5f6cf018/src/assets/minder/icons.png
--------------------------------------------------------------------------------
/src/assets/minder/mold.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lruihao/vue-minder-editor-extended/98f321f3b2f087fa92173f8e6735e24e5f6cf018/src/assets/minder/mold.png
--------------------------------------------------------------------------------
/src/components/main/footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
--------------------------------------------------------------------------------
/src/components/main/header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
88 |
89 |
92 |
--------------------------------------------------------------------------------
/src/components/main/mainEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ t('minder.main.main.save') }}
9 |
10 |
11 |
12 |
13 |
14 |
171 |
172 |
175 |
176 |
186 |
--------------------------------------------------------------------------------
/src/components/menu/edit/attachment.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 链接
8 |
9 |
10 |
11 | 插入链接
12 | 移除已有链接
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 图片
21 |
22 |
23 |
24 | 插入图片
25 | 移除已有图片
26 |
27 |
28 |
29 |
42 |
43 |
44 |
45 |
122 |
--------------------------------------------------------------------------------
/src/components/menu/edit/editDel.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
15 |
16 |
17 |
18 |
86 |
--------------------------------------------------------------------------------
/src/components/menu/edit/editMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
26 |
27 |
28 |
60 |
--------------------------------------------------------------------------------
/src/components/menu/edit/expand.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
19 |
20 |
21 |
38 |
--------------------------------------------------------------------------------
/src/components/menu/edit/insertBox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
15 |
16 |
17 |
18 |
73 |
--------------------------------------------------------------------------------
/src/components/menu/edit/moveBox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
12 |
13 |
14 |
62 |
--------------------------------------------------------------------------------
/src/components/menu/edit/progressBox.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
88 |
--------------------------------------------------------------------------------
/src/components/menu/edit/selection.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
27 |
28 |
29 |
30 |
131 |
--------------------------------------------------------------------------------
/src/components/menu/edit/sequenceBox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ priority }}
13 |
14 |
15 | {{ priorityPrefix }}{{ priorityStartWithZero ? index : item }}
23 |
24 |
25 |
26 |
27 |
98 |
188 |
--------------------------------------------------------------------------------
/src/components/menu/edit/tagBox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{item}}
10 |
11 |
12 |
13 |
92 |
93 |
--------------------------------------------------------------------------------
/src/components/menu/view/arrange.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
34 |
--------------------------------------------------------------------------------
/src/components/menu/view/fontOperation.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
19 |
20 |
28 |
35 |
36 |
37 |
38 |
39 |
45 |
51 |
52 |
53 |
54 |
55 |
292 |
--------------------------------------------------------------------------------
/src/components/menu/view/mold.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
26 |
27 |
28 |
29 |
72 |
73 |
79 |
--------------------------------------------------------------------------------
/src/components/menu/view/styleOperation.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
12 |
16 |
17 |
18 |
19 |
20 |
70 |
--------------------------------------------------------------------------------
/src/components/menu/view/theme.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
29 |
30 |
31 |
32 |
91 |
--------------------------------------------------------------------------------
/src/components/menu/view/viewMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
16 |
47 |
48 |
50 |
--------------------------------------------------------------------------------
/src/components/minderEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
90 |
91 |
93 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import mindEditor from './components/minderEditor';
2 | import * as locale from "./locale";
3 | import PackageJSON from "../package.json";
4 | require('@7polo/kity/dist/kity.js');
5 | require('hotbox-minder/hotbox.js');
6 | require('@cell-x/kityminder-core');
7 | require('./script/expose-editor.js');
8 |
9 | const install = function (Vue, options = {}) {
10 | locale.use(options.locale);
11 | locale.i18n(options.i18n);
12 | Vue.component(mindEditor.name, mindEditor);
13 | }
14 |
15 | const plugin = {
16 | name: "vueMinderEditorExtended",
17 | version: PackageJSON.version,
18 | locale: locale.use,
19 | i18n: locale.i18n,
20 | install,
21 | }
22 |
23 | if (typeof window !== 'undefined' && window.Vue) {
24 | window.Vue.use(plugin);
25 | }
26 |
27 | export default plugin;
28 |
--------------------------------------------------------------------------------
/src/locale/format.js:
--------------------------------------------------------------------------------
1 | import { hasOwn } from 'element-ui/src/utils/util';
2 |
3 | const RE_NARGS = /(%|)\{([0-9a-zA-Z_]+)\}/g;
4 | /**
5 | * String format template
6 | * - Inspired:
7 | * https://github.com/Matt-Esch/string-template/index.js
8 | */
9 | export default function(Vue) {
10 |
11 | /**
12 | * template
13 | *
14 | * @param {String} string
15 | * @param {Array} ...args
16 | * @return {String}
17 | */
18 |
19 | function template(string, ...args) {
20 | if (args.length === 1 && typeof args[0] === 'object') {
21 | args = args[0];
22 | }
23 |
24 | if (!args || !args.hasOwnProperty) {
25 | args = {};
26 | }
27 |
28 | return string.replace(RE_NARGS, (match, prefix, i, index) => {
29 | let result;
30 |
31 | if (string[index - 1] === '{' &&
32 | string[index + match.length] === '}') {
33 | return i;
34 | } else {
35 | result = hasOwn(args, i) ? args[i] : null;
36 | if (result === null || result === undefined) {
37 | return '';
38 | }
39 |
40 | return result;
41 | }
42 | });
43 | }
44 |
45 | return template;
46 | }
47 |
--------------------------------------------------------------------------------
/src/locale/index.js:
--------------------------------------------------------------------------------
1 | import defaultLang from './lang/zh-CN';
2 | import Vue from 'vue';
3 | import deepmerge from 'deepmerge';
4 | import Format from './format';
5 |
6 |
7 | const format = Format(Vue);
8 | let lang = defaultLang;
9 | let merged = false;
10 |
11 | let i18nHandler = function() {
12 | const vuei18n = Object.getPrototypeOf(this || Vue).$t;
13 | if (typeof vuei18n === 'function' && !!Vue.locale) {
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 (!value) return '';
36 | if (i === j - 1) return format(value, options);
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};
51 |
--------------------------------------------------------------------------------
/src/locale/lang/en-US.js:
--------------------------------------------------------------------------------
1 | export default {
2 | minder: {
3 | commons: {
4 | confirm: 'Confirm',
5 | clear: 'Clear',
6 | export: 'Export',
7 | cancel: 'Cancel',
8 | edit: 'Edit',
9 | delete: 'Delete',
10 | remove: 'Remove',
11 | return: 'Return',
12 | },
13 | menu: {
14 | expand: {
15 | expand: 'Expand',
16 | folding: 'Folding',
17 | expand_one: 'Expand one level',
18 | expand_tow: 'Expand tow level',
19 | expand_three: 'Expand three level',
20 | expand_four: 'Expand four level',
21 | expand_five: 'Expand five level',
22 | expand_six: 'Expand six level'
23 | },
24 | insert: {
25 | down: 'Subordinate',
26 | up: 'Superior',
27 | same: 'Same',
28 | _same: 'Same level',
29 | _down: 'Subordinate level',
30 | _up: 'Superior level',
31 | },
32 | move: {
33 | up: 'Up',
34 | down: 'Down',
35 | forward: 'Forward',
36 | backward: 'Backward',
37 | },
38 | progress: {
39 | progress: 'Progress',
40 | remove_progress: 'Remove progress',
41 | prepare: 'Prepare',
42 | complete_all: 'Complete all',
43 | complete: 'Complete',
44 | },
45 | selection: {
46 | all: 'Select all',
47 | invert: 'Select invert',
48 | sibling: 'Select sibling node',
49 | same: 'Select same node',
50 | path: 'Select path',
51 | subtree: 'Select subtree',
52 | },
53 | arrange: {
54 | arrange_layout: 'Arrange layout'
55 | },
56 | font: {
57 | font: 'Font',
58 | size: 'Size'
59 | },
60 | style: {
61 | clear: 'Clear style',
62 | copy: 'Copy style',
63 | paste: 'Paste style',
64 | }
65 | },
66 | main: {
67 | header: {
68 | minder: 'Minder',
69 | style: 'Appearance style'
70 | },
71 | main: {
72 | save: 'Save'
73 | },
74 | navigator: {
75 | amplification: 'Amplification',
76 | narrow: 'Narrow',
77 | drag: 'Drag',
78 | locating_root: 'Locating root node',
79 | navigator: 'Navigator',
80 | },
81 | history: {
82 | undo: 'Undo',
83 | redo: 'Redo'
84 | },
85 | subject: {
86 | central: 'Central subject',
87 | branch: 'Subject'
88 | },
89 | priority: 'Priority',
90 | tag: 'Tag'
91 | }
92 | }
93 | };
94 |
--------------------------------------------------------------------------------
/src/locale/lang/zh-CN.js:
--------------------------------------------------------------------------------
1 | export default {
2 | minder: {
3 | commons: {
4 | confirm: '确定',
5 | clear: '清空',
6 | export: '导出',
7 | cancel: '取消',
8 | edit: '编辑',
9 | delete: '删除',
10 | remove: '移除',
11 | return: '返回',
12 | },
13 | menu: {
14 | expand: {
15 | expand: '展开',
16 | folding: '收起',
17 | expand_one: '展开到一级节点',
18 | expand_tow: '展开到二级节点',
19 | expand_three: '展开到三级节点',
20 | expand_four: '展开到四级节点',
21 | expand_five: '展开到五级节点',
22 | expand_six: '展开到六级节点'
23 | },
24 | insert: {
25 | down: '插入下级主题',
26 | up: '插入上级主题',
27 | same: '插入同级主题',
28 | _same: '同级',
29 | _down: '下级',
30 | _up: '上级',
31 | },
32 | move: {
33 | up: '上移',
34 | down: '下移',
35 | forward: '前移',
36 | backward: '后移',
37 | },
38 | progress: {
39 | progress: '进度',
40 | remove_progress: '移除进度',
41 | prepare: '未开始',
42 | complete_all: '全部完成',
43 | complete: '完成',
44 | },
45 | selection: {
46 | all: '全选',
47 | invert: '反选',
48 | sibling: '选择兄弟节点',
49 | same: '选择同级节点',
50 | path: '选择路径',
51 | subtree: '选择子树',
52 | },
53 | arrange: {
54 | arrange_layout: '整理布局'
55 | },
56 | font: {
57 | font: '字体',
58 | size: '字号'
59 | },
60 | theme: {
61 | classic: '脑图经典',
62 | 'classic-compact': '紧凑经典',
63 | 'fresh-blue': '天空蓝',
64 | 'fresh-blue-compat': '紧凑蓝',
65 | 'fresh-green': '文艺绿',
66 | 'fresh-green-compat': '紧凑绿',
67 | 'fresh-pink': '脑残粉',
68 | 'fresh-pink-compat': '紧凑粉',
69 | 'fresh-purple': '浪漫紫',
70 | 'fresh-purple-compat': '紧凑紫',
71 | 'fresh-red': '清新红',
72 | 'fresh-red-compat': '紧凑红',
73 | 'fresh-soil': '泥土黄',
74 | 'fresh-soil-compat': '紧凑黄',
75 | snow: '温柔冷光',
76 | 'snow-compact': '紧凑冷光',
77 | tianpan: '经典天盘',
78 | 'tianpan-compact': '紧凑天盘',
79 | fish: '鱼骨图',
80 | wire: '线框',
81 | 'custom': '自定义主题',
82 | 'custom-theme': '自定义主题',
83 | },
84 | style: {
85 | clear: '清除样式',
86 | copy: '复制样式',
87 | paste: '粘贴样式',
88 | }
89 | },
90 | main: {
91 | header: {
92 | minder: '思维导图',
93 | style: '外观样式'
94 | },
95 | main: {
96 | save: '保存'
97 | },
98 | navigator: {
99 | amplification: '放大',
100 | narrow: '缩小',
101 | drag: '拖拽',
102 | locating_root: '定位根节点',
103 | navigator: '导航器',
104 | },
105 | history: {
106 | undo: '撤销',
107 | redo: '重做'
108 | },
109 | subject: {
110 | central: '中心主题',
111 | branch: '分支主题'
112 | },
113 | priority: '优先级',
114 | tag: '标签'
115 | }
116 | }
117 | };
118 |
--------------------------------------------------------------------------------
/src/locale/lang/zh-TW.js:
--------------------------------------------------------------------------------
1 | export default {
2 | minder: {
3 | commons: {
4 | confirm: '確定',
5 | clear: '清空',
6 | export: '導出',
7 | cancel: '取消',
8 | edit: '編輯',
9 | delete: '刪除',
10 | remove: '移除',
11 | return: '返回',
12 | },
13 | menu: {
14 | expand: {
15 | expand: '展開',
16 | folding: '收起',
17 | expand_one: '展開到一級節點',
18 | expand_tow: '展開到二級節點',
19 | expand_three: '展開到三級節點',
20 | expand_four: '展開到四級節點',
21 | expand_five: '展開到五級節點',
22 | expand_six: '展開到六級節點'
23 | },
24 | insert: {
25 | down: '插入下級主題',
26 | up: '插入上級主題',
27 | same: '插入同級主題',
28 | _same: '同級',
29 | _down: '下級',
30 | _up: '上級',
31 | },
32 | move: {
33 | up: '上移',
34 | down: '下移',
35 | forward: '前移',
36 | backward: '後移',
37 | },
38 | progress: {
39 | progress: '進度',
40 | remove_progress: '移除進度',
41 | prepare: '未開始',
42 | complete_all: '全部完成',
43 | complete: '完成',
44 | },
45 | selection: {
46 | all: '全選',
47 | invert: '反選',
48 | sibling: '選擇兄弟節點',
49 | same: '選擇同級節點',
50 | path: '選擇路徑',
51 | subtree: '選擇子樹',
52 | },
53 | arrange: {
54 | arrange_layout: '整理布局'
55 | },
56 | font: {
57 | font: '字體',
58 | size: '字號'
59 | },
60 | style: {
61 | clear: '清除樣式',
62 | copy: '復製樣式',
63 | paste: '粘貼樣式',
64 | }
65 | },
66 | main: {
67 | header: {
68 | minder: '思維導圖',
69 | style: '外觀樣式'
70 | },
71 | main: {
72 | save: '保存'
73 | },
74 | navigator: {
75 | amplification: '放大',
76 | narrow: '縮小',
77 | drag: '拖拽',
78 | locating_root: '定位根節點',
79 | navigator: '導航器',
80 | },
81 | history: {
82 | undo: '撤銷',
83 | redo: '重做'
84 | },
85 | subject: {
86 | central: '中心主題',
87 | branch: '分支主題'
88 | },
89 | priority: '優先級',
90 | tag: '標簽'
91 | }
92 | }
93 | };
94 |
--------------------------------------------------------------------------------
/src/mixins.js:
--------------------------------------------------------------------------------
1 | import Locale from "@/mixins/locale";
2 |
3 | export default {
4 | ...Locale,
5 | computed: {
6 | operatorLabel() {
7 | for (let operator of this.operators) {
8 | if (operator.value === this.operator) {
9 | return this.t(operator.label)
10 | }
11 | }
12 | return this.operator
13 | }
14 | }
15 | };
16 |
--------------------------------------------------------------------------------
/src/mixins/locale.js:
--------------------------------------------------------------------------------
1 | import { t } from '/src/locale';
2 |
3 | export default {
4 | methods: {
5 | t(...args) {
6 | return t.apply(this, args);
7 | }
8 | }
9 | };
10 |
--------------------------------------------------------------------------------
/src/props.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Api 列表
3 | */
4 |
5 | export let mainEditorProps = {
6 | importJson: {
7 | type: Object,
8 | },
9 | height: {
10 | type: Number,
11 | default: 500,
12 | },
13 | // classic, classic-compact, snow, snow-compact, fresh-red, fresh-red-compat,
14 | // fresh-soil, fresh-soil-compat, fresh-green, fresh-green-compat,
15 | // fresh-blue, fresh-blue-compat, fresh-purple, fresh-purple-compat,
16 | // fresh-pink, fresh-pink-compat, fish, wire, tianpan, tianpan-compact
17 | theme: {
18 | type: String,
19 | default: 'fresh-blue',
20 | },
21 | // 注册主题
22 | registerTheme: {
23 | type: Object,
24 | },
25 | disabled: Boolean
26 | }
27 |
28 | export let priorityProps = {
29 | priorities: {
30 | // 自定义优先级
31 | type: Array,
32 | default() {
33 | return []
34 | }
35 | },
36 | priorityCount: {
37 | type: Number,
38 | default: 4,
39 | validator: function (value) {
40 | // 优先级最多支持 9 个级别
41 | return value <= 9;
42 | }
43 | },
44 | priorityStartWithZero: {
45 | // 优先级是否从0开始
46 | type: Boolean,
47 | default: true
48 | },
49 | priorityPrefix: {
50 | // 优先级显示的前缀
51 | type: String,
52 | default: 'P'
53 | },
54 | priorityDisableCheck: Function,
55 | operators: []
56 | }
57 |
58 | export let tagProps = {
59 | tags: {
60 | // 自定义标签
61 | type: Array,
62 | default() {
63 | return []
64 | }
65 | },
66 | distinctTags: {
67 | // 个别标签二选一
68 | type: Array,
69 | default() {
70 | return []
71 | }
72 | },
73 | tagDisableCheck: Function,
74 | tagEditCheck: Function
75 | }
76 |
77 | export let editMenuProps = {
78 | sequenceEnable: {
79 | type: Boolean,
80 | default: true
81 | },
82 | tagEnable: {
83 | type: Boolean,
84 | default: true
85 | },
86 | progressEnable: {
87 | type: Boolean,
88 | default: true
89 | },
90 | moveEnable: {
91 | type: Boolean,
92 | default: true
93 | },
94 | }
95 |
96 | export let moleProps = {
97 | // 默认样式
98 | defaultMold: {
99 | type: Number,
100 | default: 3
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/script/editor.js:
--------------------------------------------------------------------------------
1 | define(function (require, exports, module) {
2 | const runtimes = [];
3 |
4 | function assemble(runtime) {
5 | runtimes.push(runtime);
6 | }
7 |
8 | function KMEditor(selector, editMenuProps) {
9 | this.selector = selector;
10 | for (const runtime of runtimes) {
11 | if (typeof runtime == 'function' && isEnable(editMenuProps, runtime)) {
12 | runtime.call(this, this);
13 | }
14 | }
15 | }
16 |
17 | function isEnable(editMenuProps, runtime) {
18 | switch (runtime.name) {
19 | case "PriorityRuntime":
20 | return !!editMenuProps.sequenceEnable;
21 | case "TagRuntime":
22 | return !!editMenuProps.tagEnable;
23 | case "ProgressRuntime":
24 | return !!editMenuProps.progressEnable;
25 | default:
26 | return true
27 | }
28 | }
29 |
30 | KMEditor.assemble = assemble;
31 |
32 | assemble(require('./runtime/container'));
33 | assemble(require('./runtime/fsm'));
34 | assemble(require('./runtime/minder'));
35 | assemble(require('./runtime/receiver'));
36 | assemble(require('./runtime/hotbox'));
37 | assemble(require('./runtime/input'));
38 | assemble(require('./runtime/clipboard-mimetype'));
39 | assemble(require('./runtime/clipboard'));
40 | assemble(require('./runtime/drag'));
41 | assemble(require('./runtime/node'));
42 | assemble(require('./runtime/history'));
43 | assemble(require('./runtime/jumping'));
44 | assemble(require('./runtime/priority'));
45 | assemble(require('./runtime/progress'));
46 | assemble(require('./runtime/exports'));
47 | assemble(require('./runtime/tag'));
48 |
49 | return module.exports = KMEditor;
50 | });
51 |
--------------------------------------------------------------------------------
/src/script/expose-editor.js:
--------------------------------------------------------------------------------
1 | define('expose-editor', function(require, exports, module) {
2 | return module.exports = kityminder.Editor = require('./editor');
3 | });
4 |
--------------------------------------------------------------------------------
/src/script/hotbox.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module) {
2 | return module.exports = window.HotBox;
3 | });
--------------------------------------------------------------------------------
/src/script/lang.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module) {
2 |
3 | });
--------------------------------------------------------------------------------
/src/script/minder.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module) {
2 | return module.exports = window.kityminder.Minder;
3 | });
4 |
--------------------------------------------------------------------------------
/src/script/protocol/freemind.js:
--------------------------------------------------------------------------------
1 | const priorities = [
2 | {jp: 1, mp: 'full-1'},
3 | {jp: 2, mp: 'full-2'},
4 | {jp: 3, mp: 'full-3'},
5 | {jp: 4, mp: 'full-4'},
6 | {jp: 5, mp: 'full-5'},
7 | {jp: 6, mp: 'full-6'},
8 | {jp: 7, mp: 'full-7'},
9 | {jp: 8, mp: 'full-8'}
10 | ];
11 | const mmVersion = '';
20 |
21 | function exportFreeMind(minder) {
22 | var minds = minder.exportJson();
23 | var mmContent = mmVersion + traverseJson(minds.root) + entityNode + entityMap;
24 | try {
25 | const link = document.createElement('a');
26 | const blob = new Blob(["\ufeff" + mmContent], {
27 | type: 'text/xml'
28 | });
29 | link.href = window.URL.createObjectURL(blob);
30 | link.download = `${minds.root.data.text}.mm`;
31 | document.body.appendChild(link);
32 | link.click();
33 | document.body.removeChild(link);
34 | } catch (err) {
35 | alert(err);
36 | }
37 | }
38 |
39 | function traverseJson(node){
40 | var result = "";
41 | if (!node) {
42 | return;
43 | }
44 | result += concatNodes(node);
45 | if (node.children && node.children.length > 0) {
46 | for (var i = 0; i < node.children.length; i++) {
47 | result += traverseJson(node.children[i]);
48 | result += entityNode;
49 | }
50 | }
51 | return result;
52 | }
53 |
54 | function concatNodes(node) {
55 | var result = "";
56 | var datas = node.data;
57 | result += nodeCreated + datas.created + nodeId + datas.id + nodeText + datas.text + nodeSuffix;
58 | if (datas.priority) {
59 | var mapped = priorities.find(d => {
60 | return d.jp == datas.priority
61 | });
62 | if (mapped) {
63 | result += iconTextPrefix + mapped.mp + iconTextSuffix;
64 | }
65 | }
66 | return result;
67 | }
68 |
69 | export {
70 | exportFreeMind
71 | }
72 |
--------------------------------------------------------------------------------
/src/script/protocol/json.js:
--------------------------------------------------------------------------------
1 | function exportJson(minder) {
2 | var minds = minder.exportJson();
3 | try {
4 | const link = document.createElement('a');
5 | const blob = new Blob(["\ufeff" + JSON.stringify(minds)], {
6 | type: 'text/json'
7 | });
8 | link.href = window.URL.createObjectURL(blob);
9 | link.download = `${minds.root.data.text}.json`;
10 | document.body.appendChild(link);
11 | link.click();
12 | document.body.removeChild(link);
13 | } catch (err) {
14 | alert(err);
15 | }
16 | }
17 |
18 | export {
19 | exportJson
20 | }
21 |
--------------------------------------------------------------------------------
/src/script/protocol/markdown.js:
--------------------------------------------------------------------------------
1 | const LINE_ENDING_SPLITER = /\r\n|\r|\n/;
2 | const EMPTY_LINE = '';
3 | const NOTE_MARK_START = '';
4 | const NOTE_MARK_CLOSE = '';
5 |
6 | function exportMarkdown(minder) {
7 | var minds = minder.exportJson();
8 | try {
9 | const link = document.createElement('a');
10 | const blob = new Blob(["\ufeff" + encode(minds.root, 0)], {
11 | type: 'markdown'
12 | });
13 | link.href = window.URL.createObjectURL(blob);
14 | link.download = `${minds.root.data.text}.md`;
15 | document.body.appendChild(link);
16 | link.click();
17 | document.body.removeChild(link);
18 | } catch (err) {
19 | alert(err);
20 | }
21 | }
22 |
23 | function encode(json) {
24 | return _build(json, 1).join('\n');
25 | }
26 |
27 | function _build(node, level) {
28 | var lines = [];
29 |
30 | level = level || 1;
31 |
32 | var sharps = _generateHeaderSharp(level);
33 | lines.push(sharps + ' ' + node.data.text);
34 | lines.push(EMPTY_LINE);
35 |
36 | var note = node.data.note;
37 | if (note) {
38 | var hasSharp = /^#/.test(note);
39 | if (hasSharp) {
40 | lines.push(NOTE_MARK_START);
41 | note = note.replace(/^#+/gm, function ($0) {
42 | return sharps + $0;
43 | });
44 | }
45 | lines.push(note);
46 | if (hasSharp) {
47 | lines.push(NOTE_MARK_CLOSE);
48 | }
49 | lines.push(EMPTY_LINE);
50 | }
51 |
52 | if (node.children) node.children.forEach(function (child) {
53 | lines = lines.concat(_build(child, level + 1));
54 | });
55 |
56 | return lines;
57 | }
58 |
59 | function _generateHeaderSharp(level) {
60 | var sharps = '';
61 | while (level--) sharps += '#';
62 | return sharps;
63 | }
64 |
65 | function decode(markdown) {
66 |
67 | var json,
68 | parentMap = {},
69 | lines, line, lineInfo, level, node, parent, noteProgress, codeBlock;
70 |
71 | // 一级标题转换 `{title}\n===` => `# {title}`
72 | markdown = markdown.replace(/^(.+)\n={3,}/, function ($0, $1) {
73 | return '# ' + $1;
74 | });
75 |
76 | lines = markdown.split(LINE_ENDING_SPLITER);
77 |
78 | // 按行分析
79 | for (var i = 0; i < lines.length; i++) {
80 | line = lines[i];
81 |
82 | lineInfo = _resolveLine(line);
83 |
84 | // 备注标记处理
85 | if (lineInfo.noteClose) {
86 | noteProgress = false;
87 | continue;
88 | } else if (lineInfo.noteStart) {
89 | noteProgress = true;
90 | continue;
91 | }
92 |
93 | // 代码块处理
94 | codeBlock = lineInfo.codeBlock ? !codeBlock : codeBlock;
95 |
96 | // 备注条件:备注标签中,非标题定义,或标题越位
97 | if (noteProgress || codeBlock || !lineInfo.level || lineInfo.level > level + 1) {
98 | if (node) _pushNote(node, line);
99 | continue;
100 | }
101 |
102 | // 标题处理
103 | level = lineInfo.level;
104 | node = _initNode(lineInfo.content, parentMap[level - 1]);
105 | parentMap[level] = node;
106 | }
107 |
108 | _cleanUp(parentMap[1]);
109 | return parentMap[1];
110 | }
111 |
112 | function _initNode(text, parent) {
113 | var node = {
114 | data: {
115 | text: text,
116 | note: ''
117 | }
118 | };
119 | if (parent) {
120 | if (parent.children) parent.children.push(node);
121 | else parent.children = [node];
122 | }
123 | return node;
124 | }
125 |
126 | function _pushNote(node, line) {
127 | node.data.note += line + '\n';
128 | }
129 |
130 | function _isEmpty(line) {
131 | return !/\S/.test(line);
132 | }
133 |
134 | function _resolveLine(line) {
135 | var match = /^(#+)?\s*(.*)$/.exec(line);
136 | return {
137 | level: match[1] && match[1].length || null,
138 | content: match[2],
139 | noteStart: line == NOTE_MARK_START,
140 | noteClose: line == NOTE_MARK_CLOSE,
141 | codeBlock: /^\s*```/.test(line)
142 | };
143 | }
144 |
145 | function _cleanUp(node) {
146 | if (!/\S/.test(node.data.note)) {
147 | node.data.note = null;
148 | delete node.data.note;
149 | } else {
150 | var notes = node.data.note.split('\n');
151 | while (notes.length && !/\S/.test(notes[0])) notes.shift();
152 | while (notes.length && !/\S/.test(notes[notes.length - 1])) notes.pop();
153 | node.data.note = notes.join('\n');
154 | }
155 | if (node.children) node.children.forEach(_cleanUp);
156 | }
157 |
158 | export {
159 | exportMarkdown
160 | }
161 |
--------------------------------------------------------------------------------
/src/script/protocol/plain.js:
--------------------------------------------------------------------------------
1 | const LINE_ENDING = '\r';
2 | const LINE_ENDING_SPLITER = /\r\n|\r|\n/;
3 | const TAB_CHAR = '\t';
4 |
5 | function exportTextTree(minder) {
6 | var minds = minder.exportJson();
7 | try {
8 | const link = document.createElement('a');
9 | const blob = new Blob(["\ufeff" + encode(minds.root, 0)], {
10 | type: 'text/plain'
11 | });
12 | link.href = window.URL.createObjectURL(blob);
13 | link.download = `${minds.root.data.text}.txt`;
14 | document.body.appendChild(link);
15 | link.click();
16 | document.body.removeChild(link);
17 | } catch (err) {
18 | alert(err);
19 | }
20 | }
21 |
22 | function repeat(s, n) {
23 | var result = '';
24 | while (n--) result += s;
25 | return result;
26 | }
27 |
28 | function encode(json, level) {
29 | var local = '';
30 | level = level || 0;
31 | local += repeat(TAB_CHAR, level);
32 | local += json.data.text + LINE_ENDING;
33 | if (json.children) {
34 | json.children.forEach(function (child) {
35 | local += encode(child, level + 1);
36 | });
37 | }
38 | return local;
39 | }
40 |
41 | function isEmpty(line) {
42 | return !/\S/.test(line);
43 | }
44 |
45 | function getLevel(line) {
46 | var level = 0;
47 | while (line.charAt(level) === TAB_CHAR) level++;
48 | return level;
49 | }
50 |
51 | function getNode(line) {
52 | return {
53 | data: {
54 | text: line.replace(new RegExp('^' + TAB_CHAR + '*'), '')
55 | }
56 | };
57 | }
58 |
59 | /**
60 | * 文本解码
61 | *
62 | * @param {string} local 文本内容
63 | * @param {=boolean} root 自动根节点
64 | * @return {Object} 返回解析后节点
65 | */
66 | function decode(local, root) {
67 | var json,
68 | offset,
69 | parentMap = {},
70 | lines = local.split(LINE_ENDING_SPLITER),
71 | line, level, node;
72 |
73 | function addChild(parent, child) {
74 | var children = parent.children || (parent.children = []);
75 | children.push(child);
76 | }
77 | if (root) {
78 | parentMap[0] = json = getNode('root');
79 | offset = 1;
80 | } else {
81 | offset = 0;
82 | }
83 |
84 | for (var i = 0; i < lines.length; i++) {
85 | line = lines[i];
86 | if (isEmpty(line)) continue;
87 |
88 | level = getLevel(line) + offset;
89 | node = getNode(line);
90 |
91 | if (level === 0) {
92 | if (json) {
93 | throw new Error('Invalid local format');
94 | }
95 | json = node;
96 | } else {
97 | if (!parentMap[level - 1]) {
98 | throw new Error('Invalid local format');
99 | }
100 | addChild(parentMap[level - 1], node);
101 | }
102 | parentMap[level] = node;
103 | }
104 | return json;
105 | }
106 |
107 | export {
108 | exportTextTree
109 | }
110 |
--------------------------------------------------------------------------------
/src/script/protocol/png.js:
--------------------------------------------------------------------------------
1 | var DOMURL = window.URL || window.webkitURL || window;
2 |
3 | function downloadImage(fileURI, fileName) {
4 | try {
5 | const link = document.createElement('a');
6 | link.href = fileURI;
7 | link.download = `${fileName}.png`;
8 | document.body.appendChild(link);
9 | link.click();
10 | document.body.removeChild(link);
11 | } catch (err) {
12 | alert(err);
13 | }
14 | }
15 |
16 | function loadImage(url, callback) {
17 | return new Promise(function (resolve, reject) {
18 | var image = document.createElement('img');
19 | image.onload = function () {
20 | resolve(this);
21 | };
22 | image.onerror = function (err) {
23 | reject(err);
24 | };
25 | image.crossOrigin = '';
26 | image.src = url;
27 | });
28 | }
29 |
30 | function getSVGInfo(minder) {
31 | var paper = minder.getPaper(),
32 | paperTransform,
33 | domContainer = paper.container,
34 | svgXml,
35 | $svg,
36 |
37 | renderContainer = minder.getRenderContainer(),
38 | renderBox = renderContainer.getRenderBox(),
39 | width = renderBox.width + 1,
40 | height = renderBox.height + 1,
41 |
42 | blob, svgUrl, img;
43 |
44 | // 保存原始变换,并且移动到合适的位置
45 | paperTransform = paper.shapeNode.getAttribute('transform');
46 | paper.shapeNode.setAttribute('transform', 'translate(0.5, 0.5)');
47 | renderContainer.translate(-renderBox.x, -renderBox.y);
48 |
49 | // 获取当前的 XML 代码
50 | svgXml = paper.container.innerHTML;
51 |
52 | // 回复原始变换及位置
53 | renderContainer.translate(renderBox.x, renderBox.y);
54 | paper.shapeNode.setAttribute('transform', paperTransform);
55 |
56 | // 过滤内容
57 | let el = document.createElement("div");
58 | el.innerHTML = svgXml;
59 | $svg = el.getElementsByTagName('svg');
60 |
61 | let index = $svg.length - 1;
62 |
63 | $svg[index].setAttribute('width', renderBox.width + 1);
64 | $svg[index].setAttribute('height', renderBox.height + 1);
65 | $svg[index].setAttribute('style', 'font-family: Arial, "Microsoft Yahei","Heiti SC";');
66 |
67 | let div = document.createElement("div");
68 | div.appendChild($svg[index]);
69 | svgXml = div.innerHTML;
70 |
71 | // Dummy IE
72 | svgXml = svgXml.replace(' xmlns="http://www.w3.org/2000/svg" xmlns:NS1="" NS1:ns1:xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:NS2="" NS2:xmlns:ns1=""', '');
73 |
74 | // svg 含有 符号导出报错 Entity 'nbsp' not defined
75 | svgXml = svgXml.replace(/ /g, ' ');
76 |
77 | blob = new Blob([svgXml], {
78 | type: 'image/svg+xml'
79 | });
80 |
81 | svgUrl = DOMURL.createObjectURL(blob);
82 |
83 | return {
84 | width: width,
85 | height: height,
86 | dataUrl: svgUrl,
87 | xml: svgXml
88 | };
89 | }
90 |
91 | function exportPNGImage(minder) {
92 |
93 | /* 绘制 PNG 的画布及上下文 */
94 | var canvas = document.createElement('canvas');
95 | var ctx = canvas.getContext('2d');
96 |
97 | /* 尝试获取背景图片 URL 或背景颜色 */
98 | var bgDeclare = minder.getStyle('background').toString();
99 | var bgUrl = /url\((.+)\)/.exec(bgDeclare);
100 | var bgColor = kity.Color.parse(bgDeclare);
101 |
102 | /* 获取 SVG 文件内容 */
103 | var svgInfo = getSVGInfo(minder);
104 | var width = svgInfo.width;
105 | var height = svgInfo.height;
106 | var svgDataUrl = svgInfo.dataUrl;
107 |
108 | /* 画布的填充大小 */
109 | var padding = 20;
110 |
111 | canvas.width = width + padding * 2;
112 | canvas.height = height + padding * 2;
113 |
114 | function fillBackground(ctx, style) {
115 | ctx.save();
116 | ctx.fillStyle = style;
117 | ctx.fillRect(0, 0, canvas.width, canvas.height);
118 | ctx.restore();
119 | }
120 |
121 | function drawImage(ctx, image, x, y) {
122 | ctx.drawImage(image, x, y);
123 | }
124 |
125 | function generateDataUrl(canvas) {
126 | try {
127 | var url = canvas.toDataURL('png');
128 | return url;
129 | } catch (e) {
130 | throw new Error('当前浏览器版本不支持导出 PNG 功能,请尝试升级到最新版本!');
131 | }
132 | }
133 |
134 | function drawSVG(minder) {
135 | var mind = editor.minder.exportJson();
136 | if (typeof (window.canvg) != 'undefined') {
137 | return new Promise(function (resolve) {
138 | window.canvg(canvas, svgInfo.xml, {
139 | ignoreMouse: true,
140 | ignoreAnimation: true,
141 | ignoreDimensions: true,
142 | ignoreClear: true,
143 | offsetX: padding,
144 | offsetY: padding,
145 | renderCallback: function () {
146 | downloadImage(generateDataUrl(canvas), mind.root.data.text);
147 | }
148 | });
149 | });
150 | } else {
151 | return loadImage(svgDataUrl).then(function (svgImage) {
152 | drawImage(ctx, svgImage, padding, padding);
153 | DOMURL.revokeObjectURL(svgDataUrl);
154 | downloadImage(generateDataUrl(canvas), mind.root.data.text);
155 | });
156 | }
157 | }
158 |
159 | if (bgUrl) {
160 | loadImage(bgUrl[1]).then(function (image) {
161 | fillBackground(ctx, ctx.createPattern(image, 'repeat'));
162 | drawSVG(minder);
163 | });
164 | } else {
165 | fillBackground(ctx, bgColor.toString());
166 | drawSVG(minder);
167 | }
168 | }
169 |
170 | export {
171 | exportPNGImage
172 | }
173 |
--------------------------------------------------------------------------------
/src/script/protocol/svg.js:
--------------------------------------------------------------------------------
1 | function exportSVG(minder) {
2 |
3 | var paper = minder.getPaper();
4 | var paperTransform = paper.shapeNode.getAttribute('transform');
5 | var svgXml;
6 | var $svg;
7 |
8 | var renderContainer = minder.getRenderContainer();
9 | var renderBox = renderContainer.getRenderBox();
10 | var transform = renderContainer.getTransform();
11 | var width = renderBox.width;
12 | var height = renderBox.height;
13 | var padding = 20;
14 |
15 | paper.shapeNode.setAttribute('transform', 'translate(0.5, 0.5)');
16 | svgXml = paper.container.innerHTML;
17 | console.log(svgXml);
18 | paper.shapeNode.setAttribute('transform', paperTransform);
19 |
20 | let document = window.document;
21 | let el = document.createElement("div");
22 | el.innerHTML = svgXml;
23 | $svg = el.getElementsByTagName('svg');
24 |
25 | let index = $svg.length - 1;
26 |
27 | $svg[index].setAttribute('width', width + padding * 2 | 0);
28 | $svg[index].setAttribute('height', height + padding * 2 | 0);
29 | $svg[index].setAttribute('style', 'font-family: Arial, "Microsoft Yahei", "Heiti SC"; background: ' + minder.getStyle('background'));
30 |
31 | $svg[index].setAttribute('viewBox', [renderBox.x - padding | 0,
32 | renderBox.y - padding | 0,
33 | width + padding * 2 | 0,
34 | height + padding * 2 | 0
35 | ].join(' '));
36 |
37 | let div = document.createElement("div");
38 | div.appendChild($svg[index]);
39 | svgXml = div.innerHTML;
40 | svgXml = svgXml.replace(/ /g, ' ');
41 |
42 | var blob = new Blob([svgXml], {
43 | type: 'image/svg+xml'
44 | });
45 |
46 | var DOMURL = window.URL || window.webkitURL || window;
47 | var svgUrl = DOMURL.createObjectURL(blob);
48 |
49 | var mind = editor.minder.exportJson();
50 | downloadSVG(svgUrl, mind.root.data.text);
51 | }
52 |
53 | function downloadSVG(fileURI, fileName) {
54 | try {
55 | const link = document.createElement('a');
56 | link.href = fileURI;
57 | link.download = `${fileName}.svg`;
58 | document.body.appendChild(link);
59 | link.click();
60 | document.body.removeChild(link);
61 | } catch (err) {
62 | alert(err);
63 | }
64 | }
65 |
66 | export {
67 | exportSVG
68 | }
69 |
--------------------------------------------------------------------------------
/src/script/protocol/xmind.js:
--------------------------------------------------------------------------------
1 | const priorities = [
2 | {jp: 1, mp: 'full-1'},
3 | {jp: 2, mp: 'full-2'},
4 | {jp: 3, mp: 'full-3'},
5 | {jp: 4, mp: 'full-4'},
6 | {jp: 5, mp: 'full-5'},
7 | {jp: 6, mp: 'full-6'},
8 | {jp: 7, mp: 'full-7'},
9 | {jp: 8, mp: 'full-8'}
10 | ];
11 | const mmVersion = '';
20 |
21 | function exportXMind(minder) {
22 | var minds = minder.exportJson();
23 | var mmContent = mmVersion + traverseJson(minds.root) + entityNode + entityMap;
24 | try {
25 | const link = document.createElement('a');
26 | const blob = new Blob(["\ufeff" + mmContent], {
27 | type: 'text/xml'
28 | });
29 | link.href = window.URL.createObjectURL(blob);
30 | link.download = `${minds.root.data.text}.mm`;
31 | document.body.appendChild(link);
32 | link.click();
33 | document.body.removeChild(link);
34 | } catch (err) {
35 | alert(err);
36 | }
37 | }
38 |
39 | function traverseJson(node){
40 | var result = "";
41 | if (!node) {
42 | return;
43 | }
44 | result += concatNodes(node);
45 | if (node.children && node.children.length > 0) {
46 | for (var i = 0; i < node.children.length; i++) {
47 | result += traverseJson(node.children[i]);
48 | result += entityNode;
49 | }
50 | }
51 | return result;
52 | }
53 |
54 | function concatNodes(node) {
55 | var result = "";
56 | var datas = node.data;
57 | result += nodeCreated + datas.created + nodeId + datas.id + nodeText + datas.text + nodeSuffix;
58 | if (datas.priority) {
59 | var mapped = priorities.find(d => {
60 | return d.jp == datas.priority
61 | });
62 | if (mapped) {
63 | result += iconTextPrefix + mapped.mp + iconTextSuffix;
64 | }
65 | }
66 | return result;
67 | }
68 |
69 | export {
70 | exportXMind
71 | }
72 |
--------------------------------------------------------------------------------
/src/script/runtime/clipboard-mimetype.js:
--------------------------------------------------------------------------------
1 | define(function (require, exports, module) {
2 | function MimeType() {
3 | var SPLITOR = '\uFEFF';
4 | var MIMETYPE = {
5 | 'application/km': '\uFFFF'
6 | };
7 | var SIGN = {
8 | '\uFEFF': 'SPLITOR',
9 | '\uFFFF': 'application/km'
10 | };
11 |
12 | function process(mimetype, text) {
13 | if (!this.isPureText(text)) {
14 | var _mimetype = this.whichMimeType(text);
15 | if (!_mimetype) {
16 | throw new Error('unknow mimetype!');
17 | };
18 | text = this.getPureText(text);
19 | };
20 | if (mimetype === false) {
21 | return text;
22 | };
23 | return mimetype + SPLITOR + text;
24 | }
25 |
26 | this.registMimeTypeProtocol = function (type, sign) {
27 | if (sign && SIGN[sign]) {
28 | throw new Error('sing has registed!');
29 | }
30 | if (type && !!MIMETYPE[type]) {
31 | throw new Error('mimetype has registed!');
32 | };
33 | SIGN[sign] = type;
34 | MIMETYPE[type] = sign;
35 | }
36 |
37 | this.getMimeTypeProtocol = function (type, text) {
38 | var mimetype = MIMETYPE[type] || false;
39 |
40 | if (text === undefined) {
41 | return process.bind(this, mimetype);
42 | };
43 |
44 | return process(mimetype, text);
45 | }
46 |
47 | this.getSpitor = function () {
48 | return SPLITOR;
49 | }
50 |
51 | this.getMimeType = function (sign) {
52 | if (sign !== undefined) {
53 | return SIGN[sign] || null;
54 | };
55 | return MIMETYPE;
56 | }
57 | }
58 |
59 | MimeType.prototype.isPureText = function (text) {
60 | return !(~text.indexOf(this.getSpitor()));
61 | }
62 |
63 | MimeType.prototype.getPureText = function (text) {
64 | if (this.isPureText(text)) {
65 | return text;
66 | };
67 | return text.split(this.getSpitor())[1];
68 | }
69 |
70 | MimeType.prototype.whichMimeType = function (text) {
71 | if (this.isPureText(text)) {
72 | return null;
73 | };
74 | return this.getMimeType(text.split(this.getSpitor())[0]);
75 | }
76 |
77 | function MimeTypeRuntime() {
78 | if (this.minder.supportClipboardEvent && !kity.Browser.gecko) {
79 | this.MimeType = new MimeType();
80 | };
81 | }
82 |
83 | return module.exports = MimeTypeRuntime;
84 | });
85 |
--------------------------------------------------------------------------------
/src/script/runtime/clipboard.js:
--------------------------------------------------------------------------------
1 | define(function (require, exports, module) {
2 |
3 | function ClipboardRuntime() {
4 | var minder = this.minder;
5 | var Data = window.kityminder.data;
6 |
7 | var {markDeleteNode, resetNodes} = require('../tool/utils');
8 |
9 |
10 | if (!minder.supportClipboardEvent || kity.Browser.gecko) {
11 | return;
12 | };
13 |
14 | var fsm = this.fsm;
15 | var receiver = this.receiver;
16 | var MimeType = this.MimeType;
17 |
18 | var kmencode = MimeType.getMimeTypeProtocol('application/km'),
19 | decode = Data.getRegisterProtocol('json').decode;
20 | var _selectedNodes = [];
21 |
22 | /*
23 | * 增加对多节点赋值粘贴的处理
24 | */
25 | function encode(nodes) {
26 | var _nodes = [];
27 | for (var i = 0, l = nodes.length; i < l; i++) {
28 | _nodes.push(minder.exportNode(nodes[i]));
29 | }
30 | return kmencode(Data.getRegisterProtocol('json').encode(_nodes));
31 | }
32 |
33 | var beforeCopy = function (e) {
34 | if (document.activeElement == receiver.element) {
35 | var clipBoardEvent = e;
36 | var state = fsm.state();
37 |
38 | switch (state) {
39 | case 'input': {
40 | break;
41 | }
42 | case 'normal': {
43 | var nodes = [].concat(minder.getSelectedNodes());
44 | if (nodes.length) {
45 | // 这里由于被粘贴复制的节点的id信息也都一样,故做此算法
46 | // 这里有个疑问,使用node.getParent()或者node.parent会离奇导致出现非选中节点被渲染成选中节点,因此使用isAncestorOf,而没有使用自行回溯的方式
47 | if (nodes.length > 1) {
48 | var targetLevel;
49 | nodes.sort(function (a, b) {
50 | return a.getLevel() - b.getLevel();
51 | });
52 | targetLevel = nodes[0].getLevel();
53 | if (targetLevel !== nodes[nodes.length - 1].getLevel()) {
54 | var plevel, pnode,
55 | idx = 0,
56 | l = nodes.length,
57 | 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 | markDeleteNode(minder);
101 | var nodes = minder.getSelectedNodes();
102 | if (nodes.length) {
103 | clipBoardEvent.clipboardData.setData('text/plain', encode(nodes));
104 | minder.execCommand('removenode');
105 | }
106 | e.preventDefault();
107 | break;
108 | }
109 | }
110 | };
111 | }
112 |
113 | var beforePaste = function (e) {
114 | if (document.activeElement == receiver.element) {
115 | if (minder.getStatus() !== 'normal') {
116 | e.preventDefault();
117 | return;
118 | };
119 |
120 | var clipBoardEvent = e;
121 | var state = fsm.state();
122 | var textData = clipBoardEvent.clipboardData.getData('text/plain');
123 |
124 | switch (state) {
125 | case 'input': {
126 | // input状态下如果格式为application/km则不进行paste操作
127 | if (!MimeType.isPureText(textData)) {
128 | e.preventDefault();
129 | return;
130 | };
131 | break;
132 | }
133 | case 'normal': {
134 | /*
135 | * 针对normal状态下通过对选中节点粘贴导入子节点文本进行单独处理
136 | */
137 | var sNodes = minder.getSelectedNodes();
138 |
139 | if (MimeType.whichMimeType(textData) === 'application/km') {
140 | var nodes = decode(MimeType.getPureText(textData));
141 | resetNodes(nodes);
142 | var _node;
143 | sNodes.forEach(function (node) {
144 | // 由于粘贴逻辑中为了排除子节点重新排序导致逆序,因此复制的时候倒过来
145 | for (var i = nodes.length - 1; i >= 0; i--) {
146 | _node = minder.createNode(null, node);
147 | minder.importNode(_node, nodes[i]);
148 | _selectedNodes.push(_node);
149 | node.appendChild(_node);
150 | }
151 | });
152 | minder.select(_selectedNodes, true);
153 | _selectedNodes = [];
154 |
155 | minder.refresh();
156 | } else if (clipBoardEvent.clipboardData && clipBoardEvent.clipboardData.items[0].type.indexOf('image') > -1) {
157 | var imageFile = clipBoardEvent.clipboardData.items[0].getAsFile();
158 | var serverService = angular.element(document.body).injector().get('server');
159 |
160 | return serverService.uploadImage(imageFile).then(function (json) {
161 | var resp = json.data;
162 | if (resp.errno === 0) {
163 | minder.execCommand('image', resp.data.url);
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 | minder.execCommand('paste');
177 | }
178 | }
179 |
180 | /**
181 | * 由editor的receiver统一处理全部事件,包括clipboard事件
182 | * @Editor: Naixor
183 | * @Date: 2015.9.24
184 | */
185 | document.addEventListener('copy', beforeCopy);
186 | document.addEventListener('cut', beforeCut);
187 | document.addEventListener('paste', beforePaste);
188 | }
189 |
190 | return module.exports = ClipboardRuntime;
191 | });
192 |
--------------------------------------------------------------------------------
/src/script/runtime/container.js:
--------------------------------------------------------------------------------
1 | define(function (require, exports, module) {
2 | function ContainerRuntime() {
3 | var container;
4 |
5 | if (typeof (this.selector) == 'string') {
6 | container = document.querySelector(this.selector);
7 | } else {
8 | container = this.selector;
9 | }
10 |
11 | if (!container) throw new Error('Invalid selector: ' + this.selector);
12 |
13 | // 这个类名用于给编辑器添加样式
14 | container.classList.add('km-editor');
15 |
16 | // 暴露容器给其他运行时使用
17 | this.container = container;
18 | }
19 |
20 | return module.exports = ContainerRuntime;
21 | });
22 |
--------------------------------------------------------------------------------
/src/script/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,
47 | freeVirtical = false;
48 | var frame;
49 |
50 | function move(direction, speed) {
51 | if (!direction) {
52 | freeHorizen = freeVirtical = false;
53 | frame && kity.releaseFrame(frame);
54 | frame = null;
55 | return;
56 | }
57 | if (!frame) {
58 | frame = kity.requestFrame((function (direction, speed, minder) {
59 | return function (frame) {
60 | switch (direction) {
61 | case 'left':
62 | minder._viewDragger.move({
63 | x: -speed,
64 | y: 0
65 | }, 0);
66 | break;
67 | case 'top':
68 | minder._viewDragger.move({
69 | x: 0,
70 | y: -speed
71 | }, 0);
72 | break;
73 | case 'right':
74 | minder._viewDragger.move({
75 | x: speed,
76 | y: 0
77 | }, 0);
78 | break;
79 | case 'bottom':
80 | minder._viewDragger.move({
81 | x: 0,
82 | y: speed
83 | }, 0);
84 | break;
85 | default:
86 | return;
87 | }
88 | frame.next();
89 | };
90 | })(direction, speed, minder));
91 | }
92 | }
93 |
94 | minder.on('mousedown', function (e) {
95 | flag = MOUSE_HAS_DOWN;
96 | var rect = minder.getPaper().container.getBoundingClientRect();
97 | downX = e.originEvent.clientX;
98 | downY = e.originEvent.clientY;
99 | containerY = rect.top;
100 | maxX = rect.width;
101 | maxY = rect.height;
102 | });
103 |
104 | minder.on('mousemove', function (e) {
105 | if (fsm.state() === 'drag' && flag == MOUSE_HAS_DOWN && minder.getSelectedNode() &&
106 | (Math.abs(downX - e.originEvent.clientX) > BOUND_CHECK ||
107 | Math.abs(downY - e.originEvent.clientY) > BOUND_CHECK)) {
108 | osx = e.originEvent.clientX;
109 | osy = e.originEvent.clientY - containerY;
110 |
111 | if (osx < BOUND_CHECK) {
112 | move('right', BOUND_CHECK - osx);
113 | } else if (osx > maxX - BOUND_CHECK) {
114 | move('left', BOUND_CHECK + osx - maxX);
115 | } else {
116 | freeHorizen = true;
117 | }
118 | if (osy < BOUND_CHECK) {
119 | move('bottom', osy);
120 | } else if (osy > maxY - BOUND_CHECK) {
121 | move('top', BOUND_CHECK + osy - maxY);
122 | } else {
123 | freeVirtical = true;
124 | }
125 | if (freeHorizen && freeVirtical) {
126 | move(false);
127 | }
128 | }
129 | if (fsm.state() !== 'drag' &&
130 | flag === MOUSE_HAS_DOWN &&
131 | minder.getSelectedNode() &&
132 | (Math.abs(downX - e.originEvent.clientX) > BOUND_CHECK ||
133 | Math.abs(downY - e.originEvent.clientY) > BOUND_CHECK)) {
134 |
135 | if (fsm.state() === 'hotbox') {
136 | hotbox.active(Hotbox.STATE_IDLE);
137 | }
138 |
139 | return fsm.jump('drag', 'user-drag');
140 | }
141 | });
142 |
143 | window.addEventListener('mouseup', function () {
144 | flag = MOUSE_HAS_UP;
145 | if (fsm.state() === 'drag') {
146 | move(false);
147 | return fsm.jump('normal', 'drag-finish');
148 | }
149 | }, false);
150 | }
151 |
152 | return module.exports = DragRuntime;
153 | });
154 |
--------------------------------------------------------------------------------
/src/script/runtime/exports.js:
--------------------------------------------------------------------------------
1 |
2 | define(function (require, exports, module) {
3 | var png = require("../protocol/png");
4 | var svg = require("../protocol/svg");
5 | var json = require("../protocol/json");
6 | var plain = require("../protocol/plain");
7 | var md = require("../protocol/markdown");
8 | var mm = require("../protocol/freemind");
9 | var {t} = require("../../locale");
10 |
11 | function ExportRuntime() {
12 | var minder = this.minder;
13 | var hotbox = this.hotbox;
14 | var exps = [
15 | {label: '.json', key: 'j', cmd: exportJson},
16 | {label: '.png', key: 'p', cmd: exportImage},
17 | {label: '.svg', key: 's', cmd: exportSVG},
18 | {label: '.txt', key: 't', cmd: exportTextTree},
19 | {label: '.md', key: 'm', cmd: exportMarkdown},
20 | {label: '.mm', key: 'f', cmd: exportFreeMind}
21 | ];
22 |
23 |
24 | var main = hotbox.state('main');
25 | main.button({
26 | position: 'top',
27 | label: t('minder.commons.export'),
28 | key: 'E',
29 | enable: canExp,
30 | next: 'exp'
31 | });
32 |
33 | var exp = hotbox.state('exp');
34 | exps.forEach(item => {
35 | exp.button({
36 | position: 'ring',
37 | label: item.label,
38 | key: null,
39 | action: item.cmd
40 | });
41 | });
42 |
43 | exp.button({
44 | position: 'center',
45 | label: t('minder.commons.cancel'),
46 | key: 'esc',
47 | next: 'back'
48 | });
49 |
50 | function canExp() {
51 | return true;
52 | }
53 |
54 | function exportJson(){
55 | json.exportJson(minder);
56 | }
57 |
58 | function exportImage (){
59 | png.exportPNGImage(minder);
60 | }
61 |
62 | function exportSVG (){
63 | svg.exportSVG(minder);
64 | }
65 |
66 | function exportTextTree (){
67 | plain.exportTextTree(minder);
68 | }
69 |
70 | function exportMarkdown (){
71 | md.exportMarkdown(minder);
72 | }
73 |
74 | function exportFreeMind (){
75 | mm.exportFreeMind(minder);
76 | }
77 | }
78 |
79 | return module.exports = ExportRuntime;
80 | });
81 |
--------------------------------------------------------------------------------
/src/script/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 | });
122 |
--------------------------------------------------------------------------------
/src/script/runtime/history.js:
--------------------------------------------------------------------------------
1 | define(function (require, exports, module) {
2 |
3 | function HistoryRuntime() {
4 | var minder = this.minder;
5 | var hotbox = this.hotbox;
6 | var {isDisableNode} = require('../tool/utils');
7 | var {t} = require("../../locale");
8 |
9 | var MAX_HISTORY = 100;
10 |
11 | var lastSnap;
12 | var patchLock;
13 | var undoDiffs;
14 | var redoDiffs;
15 |
16 | function reset() {
17 | undoDiffs = [];
18 | redoDiffs = [];
19 | lastSnap = minder.exportJson();
20 | }
21 |
22 | var _objectKeys = (function () {
23 | if (Object.keys)
24 | return Object.keys;
25 |
26 | return function (o) {
27 | var keys = [];
28 | for (var i in o) {
29 | if (o.hasOwnProperty(i)) {
30 | keys.push(i);
31 | }
32 | }
33 | return keys;
34 | };
35 | })();
36 |
37 | function escapePathComponent(str) {
38 | if (str.indexOf('/') === -1 && str.indexOf('~') === -1)
39 | return str;
40 | return str.replace(/~/g, '~0').replace(/\//g, '~1');
41 | }
42 |
43 | function deepClone(obj) {
44 | if (typeof obj === "object") {
45 | return JSON.parse(JSON.stringify(obj));
46 | } else {
47 | return obj;
48 | }
49 | }
50 |
51 | function _generate(mirror, obj, patches, path) {
52 | var newKeys = _objectKeys(obj);
53 | var oldKeys = _objectKeys(mirror);
54 | var changed = false;
55 | var deleted = false;
56 |
57 | for (var t = oldKeys.length - 1; t >= 0; t--) {
58 | var key = oldKeys[t];
59 | var oldVal = mirror[key];
60 | if (obj.hasOwnProperty(key)) {
61 | var newVal = obj[key];
62 | if (typeof oldVal == "object" && oldVal != null && typeof newVal == "object" && newVal != null) {
63 | _generate(oldVal, newVal, patches, path + "/" + escapePathComponent(key));
64 | } else {
65 | if (oldVal != newVal) {
66 | changed = true;
67 | patches.push({
68 | op: "replace",
69 | path: path + "/" + escapePathComponent(key),
70 | value: deepClone(newVal)
71 | });
72 | }
73 | }
74 | } else {
75 | patches.push({
76 | op: "remove",
77 | path: path + "/" + escapePathComponent(key)
78 | });
79 | deleted = true; // property has been deleted
80 | }
81 | }
82 |
83 | if (!deleted && newKeys.length == oldKeys.length) {
84 | return;
85 | }
86 |
87 | for (var t = 0; t < newKeys.length; t++) {
88 | var key = newKeys[t];
89 | if (!mirror.hasOwnProperty(key)) {
90 | patches.push({
91 | op: "add",
92 | path: path + "/" + escapePathComponent(key),
93 | value: deepClone(obj[key])
94 | });
95 | }
96 | }
97 | }
98 |
99 | function jsonDiff(tree1, tree2) {
100 | var patches = [];
101 | _generate(tree1, tree2, patches, '');
102 | return patches;
103 | }
104 |
105 | function makeUndoDiff() {
106 | var headSnap = minder.exportJson();
107 | var diff = jsonDiff(headSnap, lastSnap);
108 | if (diff.length) {
109 | undoDiffs.push(diff);
110 | while (undoDiffs.length > MAX_HISTORY) {
111 | undoDiffs.shift();
112 | }
113 | lastSnap = headSnap;
114 | return true;
115 | }
116 | }
117 |
118 | function makeRedoDiff() {
119 | var revertSnap = minder.exportJson();
120 | redoDiffs.push(jsonDiff(revertSnap, lastSnap));
121 | lastSnap = revertSnap;
122 | }
123 |
124 | function undo() {
125 | patchLock = true;
126 | var undoDiff = undoDiffs.pop();
127 | if (undoDiff) {
128 | minder.applyPatches(undoDiff);
129 | makeRedoDiff();
130 | }
131 | patchLock = false;
132 | }
133 |
134 | function redo() {
135 | patchLock = true;
136 | var redoDiff = redoDiffs.pop();
137 | if (redoDiff) {
138 | minder.applyPatches(redoDiff);
139 | makeUndoDiff();
140 | }
141 | patchLock = false;
142 | }
143 |
144 | function changed() {
145 | if (patchLock)
146 | return;
147 | if (makeUndoDiff())
148 | redoDiffs = [];
149 | }
150 |
151 | function hasUndo() {
152 | return !!undoDiffs.length;
153 | }
154 |
155 | function hasRedo() {
156 | return !!redoDiffs.length;
157 | }
158 |
159 | function updateSelection(e) {
160 | if (!patchLock)
161 | return;
162 | var patch = e.patch;
163 | switch (patch.express) {
164 | case 'node.add':
165 | minder.select(patch.node.getChild(patch.index), true);
166 | break;
167 | case 'node.remove':
168 | case 'data.replace':
169 | case 'data.remove':
170 | case 'data.add':
171 | minder.select(patch.node, true);
172 | break;
173 | }
174 | }
175 |
176 | this.history = {
177 | reset: reset,
178 | undo: undo,
179 | redo: redo,
180 | hasUndo: hasUndo,
181 | hasRedo: hasRedo
182 | };
183 | reset();
184 | minder.on('contentchange', changed);
185 | minder.on('import', reset);
186 | minder.on('patch', updateSelection);
187 |
188 | var main = hotbox.state('main');
189 | main.button({
190 | position: 'bottom',
191 | label: t('minder.main.history.undo'),
192 | key: 'Ctrl + Z',
193 | enable: function() {
194 | if (isDisableNode(minder)) {
195 | return false;
196 | }
197 | return hasUndo;
198 | },
199 | action: undo,
200 | next: 'idle'
201 | });
202 | main.button({
203 | position: 'bottom',
204 | label: t('minder.main.history.redo'),
205 | key: 'Ctrl + Y',
206 | enable: function() {
207 | if (isDisableNode(minder)) {
208 | return false;
209 | }
210 | return hasRedo;
211 | },
212 | action: redo,
213 | next: 'idle'
214 | });
215 | }
216 |
217 | // window.diff = jsonDiff;
218 |
219 | return module.exports = HistoryRuntime;
220 | });
221 |
--------------------------------------------------------------------------------
/src/script/runtime/hotbox.js:
--------------------------------------------------------------------------------
1 | define(function (require, exports, module) {
2 | var Hotbox = require('../hotbox');
3 |
4 | function HotboxRuntime() {
5 | var fsm = this.fsm;
6 | var minder = this.minder;
7 | var receiver = this.receiver;
8 | var container = this.container;
9 |
10 | var hotbox = new Hotbox(container);
11 |
12 | hotbox.setParentFSM(fsm);
13 |
14 | fsm.when('normal -> hotbox', function (exit, enter, reason) {
15 | var node = minder.getSelectedNode();
16 | var position;
17 | if (node) {
18 | var box = node.getRenderBox();
19 | position = {
20 | x: box.cx,
21 | y: box.cy
22 | };
23 | }
24 | hotbox.active('main', position);
25 | });
26 |
27 | fsm.when('normal -> normal', function (exit, enter, reason, e) {
28 | if (reason == 'shortcut-handle') {
29 | var handleResult = hotbox.dispatch(e);
30 | if (handleResult) {
31 | e.preventDefault();
32 | } else {
33 | minder.dispatchKeyEvent(e);
34 | }
35 | }
36 | });
37 |
38 | fsm.when('modal -> normal', function (exit, enter, reason, e) {
39 | if (reason == 'import-text-finish') {
40 | receiver.element.focus();
41 | }
42 | });
43 |
44 | this.hotbox = hotbox;
45 | minder.hotbox = hotbox;
46 | }
47 |
48 | return module.exports = HotboxRuntime;
49 | });
50 |
--------------------------------------------------------------------------------
/src/script/runtime/jumping.js:
--------------------------------------------------------------------------------
1 | define(function (require, exports, module) {
2 |
3 | var Hotbox = require('../hotbox');
4 |
5 | // Nice: http://unixpapa.com/js/key.html
6 | function isIntendToInput(e) {
7 | if (e.ctrlKey || e.metaKey || e.altKey) return false;
8 |
9 | // a-zA-Z
10 | if (e.keyCode >= 65 && e.keyCode <= 90) return true;
11 |
12 | // 0-9 以及其上面的符号
13 | if (e.keyCode >= 48 && e.keyCode <= 57) return true;
14 |
15 | // 小键盘区域 (除回车外)
16 | if (e.keyCode != 108 && e.keyCode >= 96 && e.keyCode <= 111) return true;
17 |
18 | // 小键盘区域 (除回车外)
19 | // @yinheli from pull request
20 | if (e.keyCode != 108 && e.keyCode >= 96 && e.keyCode <= 111) return true;
21 |
22 | // 输入法
23 | if (e.keyCode == 229 || e.keyCode === 0) return true;
24 |
25 | return false;
26 | }
27 | /**
28 | * @Desc: 下方使用receiver.enable()和receiver.disable()通过
29 | * 修改div contenteditable属性的hack来解决开启热核后依然无法屏蔽浏览器输入的bug;
30 | * 特别: win下FF对于此种情况必须要先blur在focus才能解决,但是由于这样做会导致用户
31 | * 输入法状态丢失,因此对FF暂不做处理
32 | * @Editor: Naixor
33 | * @Date: 2015.09.14
34 | */
35 | function JumpingRuntime() {
36 | var fsm = this.fsm;
37 | var minder = this.minder;
38 | var receiver = this.receiver;
39 | var container = this.container;
40 | var receiverElement = receiver.element;
41 | var hotbox = this.hotbox;
42 |
43 | // normal -> *
44 | receiver.listen('normal', function (e) {
45 | // 为了防止处理进入edit模式而丢失处理的首字母,此时receiver必须为enable
46 | receiver.enable();
47 | // normal -> hotbox
48 | if (e.is('Space')) {
49 | e.preventDefault();
50 | // safari下Space触发hotbox,然而这时Space已在receiver上留下作案痕迹,因此抹掉
51 | if (kity.Browser.safari) {
52 | receiverElement.innerHTML = '';
53 | }
54 | return fsm.jump('hotbox', 'space-trigger');
55 | }
56 |
57 | /**
58 | * check
59 | * @editor Naixor
60 | * @Date 2015-12-2
61 | */
62 | switch (e.type) {
63 | case 'keydown': {
64 | if (minder.getSelectedNode()) {
65 | if (isIntendToInput(e)) {
66 | return fsm.jump('input', 'user-input');
67 | };
68 | } else {
69 | receiverElement.innerHTML = '';
70 | }
71 | // normal -> normal shortcut
72 | fsm.jump('normal', 'shortcut-handle', e);
73 | break;
74 | }
75 | case 'keyup': {
76 | break;
77 | }
78 | default: {}
79 | }
80 | });
81 |
82 | // hotbox -> normal
83 | receiver.listen('hotbox', function (e) {
84 | receiver.disable();
85 | e.preventDefault();
86 | var handleResult = hotbox.dispatch(e);
87 | if (hotbox.state() == Hotbox.STATE_IDLE && fsm.state() == 'hotbox') {
88 | return fsm.jump('normal', 'hotbox-idle');
89 | }
90 | });
91 |
92 | // input => normal
93 | receiver.listen('input', function (e) {
94 | receiver.enable();
95 | if (e.type == 'keydown') {
96 | if (e.is('Enter')) {
97 | e.preventDefault();
98 | return fsm.jump('normal', 'input-commit');
99 | }
100 | if (e.is('Esc')) {
101 | e.preventDefault();
102 | return fsm.jump('normal', 'input-cancel');
103 | }
104 | if (e.is('Tab') || e.is('Shift + Tab')) {
105 | e.preventDefault();
106 | }
107 | } else if (e.type == 'keyup' && e.is('Esc')) {
108 | e.preventDefault();
109 | return fsm.jump('normal', 'input-cancel');
110 | }
111 | });
112 |
113 |
114 | //////////////////////////////////////////////
115 | /// 右键呼出热盒
116 | /// 判断的标准是:按下的位置和结束的位置一致
117 | //////////////////////////////////////////////
118 | var downX, downY;
119 | var MOUSE_RB = 2; // 右键
120 |
121 | container.addEventListener('mousedown', function (e) {
122 | if (e.button == MOUSE_RB) {
123 | e.preventDefault();
124 | }
125 | if (fsm.state() == 'hotbox') {
126 | hotbox.active(Hotbox.STATE_IDLE);
127 | fsm.jump('normal', 'blur');
128 | } else if (fsm.state() == 'normal' && e.button == MOUSE_RB) {
129 | downX = e.clientX;
130 | downY = e.clientY;
131 | }
132 | }, false);
133 |
134 | container.addEventListener('mousewheel', function (e) {
135 | if (fsm.state() == 'hotbox') {
136 | hotbox.active(Hotbox.STATE_IDLE);
137 | fsm.jump('normal', 'mousemove-blur');
138 | }
139 | }, false);
140 |
141 | container.addEventListener('contextmenu', function (e) {
142 | e.preventDefault();
143 | });
144 |
145 | container.addEventListener('mouseup', function (e) {
146 | if (fsm.state() != 'normal') {
147 | return;
148 | }
149 | if (e.button != MOUSE_RB || e.clientX != downX || e.clientY != downY) {
150 | return;
151 | }
152 | if (!minder.getSelectedNode()) {
153 | return;
154 | }
155 | fsm.jump('hotbox', 'content-menu');
156 | }, false);
157 |
158 | // 阻止热盒事件冒泡,在热盒正确执行前导致热盒关闭
159 | hotbox.$element.addEventListener('mousedown', function (e) {
160 | e.stopPropagation();
161 | });
162 | }
163 |
164 | return module.exports = JumpingRuntime;
165 | });
166 |
--------------------------------------------------------------------------------
/src/script/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 | var {t} = require("../../locale");
12 |
13 | function MinderRuntime() {
14 |
15 | // 不使用 kityminder 的按键处理,由 ReceiverRuntime 统一处理
16 | var minder = new Minder({
17 | enableKeyReceiver: false,
18 | enableAnimation: true
19 | });
20 |
21 | // 渲染,初始化
22 | minder.renderTo(this.selector);
23 | minder.setTheme(null);
24 | minder.select(minder.getRoot(), true);
25 | minder.execCommand('text', t('minder.main.subject.central'));
26 |
27 | // 导出给其它 Runtime 使用
28 | this.minder = minder;
29 | }
30 |
31 | return module.exports = MinderRuntime;
32 | });
33 |
--------------------------------------------------------------------------------
/src/script/runtime/node.js:
--------------------------------------------------------------------------------
1 | define(function (require, exports, module) {
2 | function NodeRuntime() {
3 | var runtime = this;
4 | var minder = this.minder;
5 | var hotbox = this.hotbox;
6 | var fsm = this.fsm;
7 | var {t} = require("../../locale");
8 |
9 | var main = hotbox.state('main');
10 | var {isDisableNode, markDeleteNode, isDeleteDisableNode} = require('../tool/utils');
11 |
12 | const buttons = [
13 | t('minder.menu.move.forward') + ':Alt+Up:ArrangeUp',
14 | t('minder.menu.insert._down') + ':Tab|Insert:AppendChildNode',
15 | t('minder.menu.insert._same') + ':Enter:AppendSiblingNode',
16 | t('minder.menu.move.backward') + ':Alt+Down:ArrangeDown',
17 | t('minder.commons.delete') + ':Delete|Backspace:RemoveNode',
18 | t('minder.menu.insert._up') + ':Shift+Tab|Shift+Insert:AppendParentNode'
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, t('minder.main.subject.branch'));
36 |
37 | function afterAppend() {
38 | if (!--AppendLock) {
39 | runtime.editText();
40 | }
41 | minder.off('layoutallfinish', afterAppend);
42 | }
43 | minder.on('layoutallfinish', afterAppend);
44 | } else {
45 | if (command.indexOf('RemoveNode') > -1) {
46 | markDeleteNode(minder);
47 | }
48 | minder.execCommand(command);
49 | //fsm.jump('normal', 'command-executed');
50 | }
51 | },
52 | enable: function () {
53 | if (command.indexOf("RemoveNode") > -1) {
54 | if (isDeleteDisableNode(minder) &&
55 | (command.indexOf("AppendChildNode") < 0 && command.indexOf("AppendSiblingNode") < 0) ) {
56 | return false;
57 | }
58 | } else if(command.indexOf("ArrangeUp") > 0 || command.indexOf("ArrangeDown") > 0) {
59 | if (!minder.moveEnable) {
60 | return false;
61 | }
62 | } else if (command.indexOf("AppendChildNode") < 0 && command.indexOf("AppendSiblingNode") < 0) {
63 | if (isDisableNode(minder)) return false;
64 | }
65 | let node = minder.getSelectedNode();
66 | if (node && node.parent === null && command.indexOf("AppendSiblingNode") > -1) {
67 | return false;
68 | }
69 | return minder.queryCommandState(command) != -1;
70 | }
71 | });
72 | });
73 |
74 | main.button({
75 | position: 'ring',
76 | key: '/',
77 | action: function () {
78 | if (!minder.queryCommandState('expand')) {
79 | minder.execCommand('expand');
80 | } else if (!minder.queryCommandState('collapse')) {
81 | minder.execCommand('collapse');
82 | }
83 | },
84 | enable: function () {
85 | return minder.queryCommandState('expand') != -1 || minder.queryCommandState('collapse') != -1;
86 | },
87 | beforeShow: function () {
88 | if (!minder.queryCommandState('expand')) {
89 | this.$button.children[0].innerHTML = t('minder.menu.expand.expand');
90 | } else {
91 | this.$button.children[0].innerHTML = t('minder.menu.expand.folding');
92 | }
93 | }
94 | })
95 | }
96 |
97 | return module.exports = NodeRuntime;
98 | });
99 |
--------------------------------------------------------------------------------
/src/script/runtime/priority.js:
--------------------------------------------------------------------------------
1 | define(function (require, exports, module) {
2 |
3 | function PriorityRuntime() {
4 | var minder = this.minder;
5 | var hotbox = this.hotbox;
6 | var {isDisableNode} = require('../tool/utils');
7 | var {t} = require("../../locale");
8 |
9 | var main = hotbox.state('main');
10 |
11 | main.button({
12 | position: 'top',
13 | label: t('minder.main.priority'),
14 | key: 'P',
15 | next: 'priority',
16 | enable: function () {
17 | if (isDisableNode(minder)) {
18 | return false;
19 | }
20 | return minder.queryCommandState('priority') != -1;
21 | }
22 | });
23 |
24 | let priority = hotbox.state('priority')
25 |
26 | priority.button({
27 | position: 'center',
28 | label: t('minder.commons.remove'),
29 | key: 'Del',
30 | action: function () {
31 | minder.execCommand('Priority', 0);
32 | }
33 | });
34 |
35 | priority.button({
36 | position: 'top',
37 | label: t('minder.commons.return'),
38 | key: 'esc',
39 | next: 'back'
40 | });
41 |
42 | }
43 | return module.exports = PriorityRuntime;
44 | });
45 |
--------------------------------------------------------------------------------
/src/script/runtime/progress.js:
--------------------------------------------------------------------------------
1 | define(function (require, exports, module) {
2 |
3 | function ProgressRuntime() {
4 | var minder = this.minder;
5 | var hotbox = this.hotbox;
6 | var {isDisableNode} = require('../tool/utils');
7 | var {t} = require("../../locale");
8 |
9 | var main = hotbox.state('main');
10 |
11 | main.button({
12 | position: 'top',
13 | label: t('minder.menu.progress.progress'),
14 | key: 'G',
15 | next: 'progress',
16 | enable: function () {
17 | if (isDisableNode(minder)) {
18 | return false;
19 | }
20 | return minder.queryCommandState('progress') != -1;
21 | }
22 | });
23 |
24 | var progress = hotbox.state('progress');
25 | '012345678'.replace(/./g, function (p) {
26 | progress.button({
27 | position: 'ring',
28 | label: 'G' + p,
29 | key: p,
30 | action: function () {
31 | minder.execCommand('Progress', parseInt(p) + 1);
32 | }
33 | });
34 | });
35 |
36 | progress.button({
37 | position: 'center',
38 | label: t('minder.commons.remove'),
39 | key: 'Del',
40 | action: function () {
41 | minder.execCommand('Progress', 0);
42 | }
43 | });
44 |
45 | progress.button({
46 | position: 'top',
47 | label: t('minder.commons.return'),
48 | key: 'esc',
49 | next: 'back'
50 | });
51 | }
52 |
53 | return module.exports = ProgressRuntime;
54 |
55 | });
56 |
--------------------------------------------------------------------------------
/src/script/runtime/receiver.js:
--------------------------------------------------------------------------------
1 | define(function (require, exports, module) {
2 | var key = require('../tool/key');
3 | var hotbox = require('./hotbox');
4 |
5 | function ReceiverRuntime() {
6 | var fsm = this.fsm;
7 | var minder = this.minder;
8 | var me = this;
9 |
10 | // 接收事件的 div
11 | var element = document.createElement('div');
12 | element.contentEditable = true;
13 | element.setAttribute("tabindex", -1);
14 | element.classList.add('receiver');
15 | element.onkeydown = element.onkeypress = element.onkeyup = dispatchKeyEvent;
16 | this.container.appendChild(element);
17 |
18 | // receiver 对象
19 | var receiver = {
20 | element: element,
21 | selectAll: function () {
22 | // 保证有被选中的
23 | if (!element.innerHTML) element.innerHTML = ' ';
24 | var range = document.createRange();
25 | var selection = window.getSelection();
26 | range.selectNodeContents(element);
27 | selection.removeAllRanges();
28 | selection.addRange(range);
29 | element.focus();
30 | },
31 | enable: function () {
32 | element.setAttribute("contenteditable", true);
33 | },
34 | disable: function () {
35 | element.setAttribute("contenteditable", false);
36 | },
37 | fixFFCaretDisappeared: function () {
38 | element.removeAttribute("contenteditable");
39 | element.setAttribute("contenteditable", "true");
40 | element.blur();
41 | element.focus();
42 | },
43 | onblur: function (handler) {
44 | element.onblur = handler;
45 | }
46 | };
47 | receiver.selectAll();
48 | minder.on('beforemousedown', receiver.selectAll);
49 | minder.on('receiverfocus', receiver.selectAll);
50 | minder.on('readonly', function () {
51 | // 屏蔽minder的事件接受,删除receiver和hotbox
52 | minder.disable();
53 | editor.receiver.element.parentElement.removeChild(editor.receiver.element);
54 | editor.hotbox.$container.removeChild(editor.hotbox.$element);
55 | });
56 |
57 | // 侦听器,接收到的事件会派发给所有侦听器
58 | var listeners = [];
59 |
60 | // 侦听指定状态下的事件,如果不传 state,侦听所有状态
61 | receiver.listen = function (state, listener) {
62 | if (arguments.length == 1) {
63 | listener = state;
64 | state = '*';
65 | }
66 | listener.notifyState = state;
67 | listeners.push(listener);
68 | };
69 |
70 | function dispatchKeyEvent(e) {
71 | e.is = function (keyExpression) {
72 | var subs = keyExpression.split('|');
73 | for (var i = 0; i < subs.length; i++) {
74 | if (key.is(this, subs[i])) return true;
75 | }
76 | return false;
77 | };
78 | var listener, jumpState;
79 | for (var i = 0; i < listeners.length; i++) {
80 |
81 | listener = listeners[i];
82 | // 忽略不在侦听状态的侦听器
83 | if (listener.notifyState != '*' && listener.notifyState != fsm.state()) {
84 | continue;
85 | }
86 |
87 | if (listener.call(null, e)) {
88 | return;
89 | }
90 | }
91 | }
92 |
93 | this.receiver = receiver;
94 | }
95 |
96 | return module.exports = ReceiverRuntime;
97 | });
98 |
--------------------------------------------------------------------------------
/src/script/runtime/tag.js:
--------------------------------------------------------------------------------
1 |
2 | define(function (require, exports, module) {
3 |
4 | function TagRuntime() {
5 | var minder = this.minder;
6 | var hotbox = this.hotbox;
7 | var {isDisableNode, isTagEnable} = require('../tool/utils');
8 | var {t} = require("../../locale");
9 | var main = hotbox.state('main');
10 |
11 | main.button({
12 | position: 'top',
13 | label: t('minder.main.tag'),
14 | key: 'H',
15 | next: 'tag',
16 | enable: function () {
17 | if (isDisableNode(minder) && !isTagEnable(minder)) {
18 | return false;
19 | }
20 | return minder.queryCommandState('tag') != -1;
21 | }
22 | });
23 |
24 | let tag = hotbox.state('tag');
25 |
26 | tag.button({
27 | position: 'center',
28 | label: t('minder.commons.remove'),
29 | key: 'Del',
30 | action: function () {
31 | minder.execCommand('Tag', 0);
32 | }
33 | });
34 |
35 | tag.button({
36 | position: 'top',
37 | label: t('minder.commons.return'),
38 | key: 'esc',
39 | next: 'back'
40 | });
41 | }
42 |
43 | return module.exports = TagRuntime;
44 |
45 | });
46 |
--------------------------------------------------------------------------------
/src/script/store.js:
--------------------------------------------------------------------------------
1 | export function setLocalStorage(k, v) {
2 | window.localStorage.setItem(k, JSON.stringify(v));
3 | }
4 |
5 | export function getLocalStorage(k) {
6 | let v = window.localStorage.getItem(k);
7 | return JSON.parse(v);
8 | }
9 |
10 | export function rmLocalStorage(k) {
11 | window.localStorage.removeItem(k);
12 | }
13 |
14 | export function clearLocalStorage() {
15 | window.localStorage.clear();
16 | }
17 |
--------------------------------------------------------------------------------
/src/script/tool/debug.js:
--------------------------------------------------------------------------------
1 | define(function (require, exports, module) {
2 | var format = require('./format');
3 |
4 | function noop() {}
5 |
6 | function stringHash(str) {
7 | var hash = 0;
8 | for (var i = 0; i < str.length; i++) {
9 | hash += str.charCodeAt(i);
10 | }
11 | return hash;
12 | }
13 |
14 | function Debug(flag) {
15 | var debugMode = this.flaged = window.location.search.indexOf(flag) != -1;
16 |
17 | if (debugMode) {
18 | var h = stringHash(flag) % 360;
19 |
20 | var flagStyle = format(
21 | 'background: hsl({0}, 50%, 80%); ' +
22 | 'color: hsl({0}, 100%, 30%); ' +
23 | 'padding: 2px 3px; ' +
24 | 'margin: 1px 3px 0 0;' +
25 | 'border-radius: 2px;', h);
26 |
27 | var textStyle = 'background: none; color: black;';
28 | this.log = function () {
29 | var output = format.apply(null, arguments);
30 | console.log(format('%c{0}%c{1}', flag, output), flagStyle, textStyle);
31 | };
32 | } else {
33 | this.log = noop;
34 | }
35 | }
36 |
37 | return module.exports = Debug;
38 | });
39 |
--------------------------------------------------------------------------------
/src/script/tool/format.js:
--------------------------------------------------------------------------------
1 | function format(template, args) {
2 | if (typeof(args) != 'object') {
3 | args = [].slice.call(arguments, 1);
4 | }
5 | return String(template).replace(/\{(\w+)\}/ig, function (match, $key) {
6 | return args[$key] || $key;
7 | });
8 | }
9 | export {format}
10 |
--------------------------------------------------------------------------------
/src/script/tool/innertext.js:
--------------------------------------------------------------------------------
1 | define(function (require, exports, module) {
2 | if ((!('innerText' in document.createElement('a'))) && ('getSelection' in window)) {
3 | HTMLElement.prototype.__defineGetter__('innerText', function () {
4 | var selection = window.getSelection(),
5 | ranges = [],
6 | str, i;
7 |
8 | for (i = 0; i < selection.rangeCount; i++) {
9 | ranges[i] = selection.getRangeAt(i);
10 | }
11 |
12 | selection.removeAllRanges();
13 | selection.selectAllChildren(this);
14 | str = selection.toString();
15 | selection.removeAllRanges();
16 | for (i = 0; i < ranges.length; i++) {
17 | selection.addRange(ranges[i]);
18 | }
19 | return str;
20 | });
21 | HTMLElement.prototype.__defineSetter__('innerText', function (text) {
22 | this.innerHTML = (text || '').replace(//g, '>').replace(/\n/g, '
');
23 | });
24 | }
25 | });
26 |
--------------------------------------------------------------------------------
/src/script/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 |
15 | function is(a, b) {
16 | return a && b && hash(a) == hash(b);
17 | }
18 | exports.hash = hash;
19 | exports.is = is;
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 | if ([16, 17, 18, 91].indexOf(keyEvent.keyCode) === -1) {
33 | if (keyEvent.keyCode === 229 && keyEvent.keyIdentifier) {
34 | return hashCode |= parseInt(keyEvent.keyIdentifier.substr(2), 16);
35 | }
36 | hashCode |= keyEvent.keyCode;
37 | }
38 | return hashCode;
39 | }
40 |
41 | function hashKeyExpression(keyExpression) {
42 | var hashCode = 0;
43 | keyExpression.toLowerCase().split(/\s*\+\s*/).forEach(function (name) {
44 | switch (name) {
45 | case 'ctrl':
46 | case 'cmd':
47 | hashCode |= CTRL_MASK;
48 | break;
49 | case 'alt':
50 | hashCode |= ALT_MASK;
51 | break;
52 | case 'shift':
53 | hashCode |= SHIFT_MASK;
54 | break;
55 | default:
56 | hashCode |= keymap[name];
57 | }
58 | });
59 | return hashCode;
60 | }
61 | });
62 |
--------------------------------------------------------------------------------
/src/script/tool/keymap.js:
--------------------------------------------------------------------------------
1 | define(function (require, exports, module) {
2 | const 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 | for (var key in keymap) {
62 | if (keymap.hasOwnProperty(key)) {
63 | keymap[key.toLowerCase()] = keymap[key];
64 | }
65 | }
66 | var aKeyCode = 65;
67 | var aCharCode = 'a'.charCodeAt(0);
68 |
69 | 'abcdefghijklmnopqrstuvwxyz'.split('').forEach(function (letter) {
70 | keymap[letter] = aKeyCode + (letter.charCodeAt(0) - aCharCode);
71 | });
72 |
73 | var n = 9;
74 | do {
75 | keymap[n.toString()] = n + 48;
76 | } while (--n);
77 |
78 | module.exports = keymap;
79 | });
80 |
--------------------------------------------------------------------------------
/src/script/tool/utils.js:
--------------------------------------------------------------------------------
1 | export function isDisableNode(minder) {
2 | let node = undefined;
3 | if (minder && minder.getSelectedNode) {
4 | node = minder.getSelectedNode();
5 | }
6 | if (node && node.data.disable === true) {
7 | return true;
8 | }
9 | return false;
10 | }
11 |
12 | export function isDeleteDisableNode(minder) {
13 | let node = undefined;
14 | if (minder && minder.getSelectedNode) {
15 | node = minder.getSelectedNode();
16 | }
17 | if (node && node.data.disable === true && !node.data.allowDelete) {
18 | return true;
19 | }
20 | return false;
21 | }
22 |
23 | export function isTagEnable(minder) {
24 | let node = undefined;
25 | if (minder && minder.getSelectedNode) {
26 | node = minder.getSelectedNode();
27 | }
28 | if (node && node.data.tagEnable === true) {
29 | return true;
30 | }
31 | return false;
32 | }
33 |
34 | export function markChangeNode(node) {
35 | if (node && node.data) {
36 | // 修改的该节点标记为 contextChanged
37 | node.data.contextChanged = true;
38 | while (node) {
39 | // 该路径上的节点都标记为 changed
40 | node.data.changed = true;
41 | node = node.parent;
42 | }
43 | }
44 | }
45 |
46 | // 在父节点记录删除的节点
47 | export function markDeleteNode(minder) {
48 | if (minder) {
49 | let nodes = minder.getSelectedNodes();
50 | nodes.forEach(node => {
51 | if (node && node.parent) {
52 | let pData = node.parent.data;
53 | if (!pData.deleteChild) {
54 | pData.deleteChild = [];
55 | }
56 | _markDeleteNode(node, pData.deleteChild);
57 | }
58 | });
59 | }
60 | }
61 |
62 | function _markDeleteNode(node, deleteChild) {
63 | deleteChild.push(node.data);
64 | if (node.children) {
65 | node.children.forEach(child => {
66 | _markDeleteNode(child, deleteChild);
67 | });
68 | }
69 | }
70 |
71 | export function isPriority(e) {
72 | if (e.getAttribute('text-rendering') === 'geometricPrecision'
73 | && e.getAttribute('text-anchor') === 'middle'
74 | ) {
75 | return true;
76 | }
77 | return false;
78 | }
79 |
80 | export function setPriorityView(priorityStartWithZero, priorityPrefix) {
81 | //手动将优先级前面加上P显示
82 | let items = document.getElementsByTagName('text');
83 | if (items) {
84 | for (let i = 0; i < items.length; i++) {
85 | let item = items[i];
86 | if (isPriority(item)) {
87 | let content = item.innerHTML;
88 | if (content.indexOf(priorityPrefix) < 0) {
89 | if (priorityStartWithZero) {
90 | content = parseInt(content) - 1 + '';
91 | }
92 | item.innerHTML = priorityPrefix + content;
93 | }
94 | }
95 | }
96 | }
97 | }
98 |
99 | /**
100 | * 手动将特定优先级显示到脑图中
101 | * @param {Array} priorities 特定优先级集合
102 | */
103 | export function setPriorityViewSpecial(priorities) {
104 | let items = document.getElementsByTagName('text');
105 | if (items) {
106 | for (let i = 0; i < items.length; i++) {
107 | let item = items[i];
108 | if (isPriority(item)) {
109 | let content = item.innerHTML;
110 | if (priorities[content - 1]) {
111 | item.innerHTML = priorities[content - 1];
112 | }
113 | }
114 | }
115 | }
116 | }
117 |
118 | /**
119 | * 将节点及其子节点id置为null,changed 标记为true
120 | * @param node
121 | */
122 | export function resetNodes(nodes) {
123 | if (nodes) {
124 | nodes.forEach(item => {
125 | if (item.data) {
126 | item.data.id = null;
127 | item.data.contextChanged = true;
128 | item.data.changed = true;
129 | resetNodes(item.children);
130 | }
131 | });
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/style/dropdown-list.scss:
--------------------------------------------------------------------------------
1 | .link-dropdown-list,
2 | .img-dropdown-list,
3 | .remark-dropdown-list,
4 | .selection-dropdown-list,
5 | .expand-dropdown-list {
6 | font-size: 12px;
7 | }
8 |
9 | .mold-dropdown-list {
10 | width: 126px;
11 | height: 170px;
12 | font-size: 12px;
13 | .dropdown-item {
14 | display: inline-block;
15 | width: 50px;
16 | height: 40px;
17 | padding: 0;
18 | margin: 5px;
19 | }
20 | @for $i from 1 through 6 {
21 | .mold-#{$i} {
22 | background-position: (1-$i) * 50px 0;
23 | }
24 | }
25 | }
26 |
27 | .theme-dropdown-list {
28 | width: 160px;
29 | max-height: 400px;
30 | padding-block: 5px;
31 | overflow-y: auto;
32 | &::-webkit-scrollbar {
33 | width: 0;
34 | }
35 | .dropdown-item {
36 | font-size: 12px;
37 | display: inline-block;
38 | text-align: center;
39 | width: 70px;
40 | height: 30px;
41 | line-height: 30px;
42 | padding: 0;
43 | margin: 5px;
44 | }
45 | }
46 |
47 | .expand-dropdown-list {
48 | .dropdown-item {
49 | line-height: 25px;
50 | }
51 | }
52 |
53 | .selection-dropdown-list {
54 | .dropdown-item {
55 | line-height: 25px;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/style/editor.scss:
--------------------------------------------------------------------------------
1 | @import "~@cell-x/kityminder-core/dist/kityminder.core.css";
2 | @import "navigator.scss";
3 | @import "hotbox.scss";
4 |
5 | .km-editor {
6 | overflow: hidden;
7 | z-index: 2;
8 | }
9 |
10 | .km-editor > .mask {
11 | display: block;
12 | position: absolute;
13 | left: 0;
14 | right: 0;
15 | top: 0;
16 | bottom: 0;
17 | background-color: transparent;
18 | }
19 |
20 | .km-editor > .receiver {
21 | position: absolute;
22 | background: white;
23 | outline: none;
24 | box-shadow: 0 0 20px;
25 | left: 0;
26 | top: 0;
27 | padding: 3px 5px;
28 | margin-left: -3px;
29 | margin-top: -5px;
30 | max-width: 300px;
31 | width: auto;
32 | overflow: hidden;
33 | font-size: 14px;
34 | line-height: 1.4em;
35 | min-height: 1.4em;
36 | box-sizing: border-box;
37 | overflow: hidden;
38 | word-break: break-all;
39 | word-wrap: break-word;
40 | border: none;
41 | -webkit-user-select: text;
42 | pointer-events: none;
43 | opacity: 0;
44 | z-index: -1000;
45 |
46 | &.debug {
47 | opacity: 1;
48 | outline: 1px solid green;
49 | background: none;
50 | z-index: 0;
51 | }
52 |
53 | &.input {
54 | pointer-events: all;
55 | opacity: 1;
56 | z-index: 999;
57 | background: white;
58 | outline: none;
59 | }
60 | }
61 |
62 | div.minder-editor-container {
63 | position: absolute;
64 | top: 40px;
65 | bottom: 0;
66 | left: 0;
67 | right: 0;
68 | font-family: Arial, "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
69 | }
70 |
71 | .minder-editor {
72 | position: absolute;
73 | top: 92px;
74 | left: 0;
75 | right: 0;
76 | bottom: 0;
77 | }
78 |
79 | .minder-viewer {
80 | position: absolute;
81 | top: 0;
82 | left: 0;
83 | right: 0;
84 | bottom: 0;
85 | }
86 |
87 | .control-panel {
88 | position: absolute;
89 | top: 0;
90 | right: 0;
91 | width: 250px;
92 | bottom: 0;
93 | border-left: 1px solid #ccc;
94 | }
95 |
96 | .minder-divider {
97 | position: absolute;
98 | top: 0;
99 | right: 250px;
100 | bottom: 0;
101 | width: 2px;
102 | background-color: rgb(251, 251, 251);
103 | cursor: ew-resize;
104 | }
105 |
106 | .hotbox .state .button.enabled.selected .key,
107 | .hotbox .state .ring .key {
108 | margin-top: 5px;
109 | font-size: 13px;
110 | }
111 |
112 | .hotbox .state .bottom .button .label,
113 | .hotbox .state .top .button .label {
114 | font-weight: 600;
115 | }
116 |
117 | .hotbox .exp .ring .button .label {
118 | margin-top: 28px;
119 | margin-left: -2px;
120 | }
121 |
122 | .hotbox .exp .ring .button .key {
123 | display: none;
124 | }
125 |
--------------------------------------------------------------------------------
/src/style/header.scss:
--------------------------------------------------------------------------------
1 | @import "mixin.scss";
2 | @import "dropdown-list.scss";
3 | header {
4 | font-size: 12px;
5 | * {
6 | box-sizing: content-box;
7 | }
8 | & > ul {
9 | display: flex;
10 | align-items: center;
11 | height: 30px;
12 | margin: 0;
13 | padding: 0;
14 | background-color: #e1e1e1;
15 | li {
16 | line-height: 30px;
17 | display: inline-flex;
18 | width: 80px;
19 | height: 100%;
20 | list-style: none;
21 | a {
22 | font-size: 14px;
23 | width: inherit;
24 | text-align: center;
25 | text-decoration: none;
26 | color: #337ab7;
27 | }
28 | a:hover,
29 | a:focus {
30 | color: #23527c;
31 | }
32 | }
33 | li.selected {
34 | background: #fff;
35 | a {
36 | color: #000;
37 | }
38 | }
39 | }
40 | }
41 |
42 | .mind_tab-content {
43 | border-bottom: 1px solid #eee;
44 | .el-tabs__header {
45 | margin-bottom: 0;
46 | }
47 | }
48 |
49 | .mind-tab-panel {
50 | width: 100%;
51 | height: 100%;
52 | padding-block: 6px;
53 | .menu-container {
54 | display: flex;
55 | min-height: 52px;
56 | & > div {
57 | display: flex;
58 | overflow: hidden;
59 | flex-wrap: wrap;
60 | padding: 4px 10px;
61 | line-height: 12px;
62 | &:not(:last-of-type) {
63 | border-right: 1px dashed #eee;
64 | }
65 | }
66 | .expand-group,
67 | .selection-group {
68 | flex-direction: column;
69 | align-items: center;
70 | justify-content: space-around;
71 | .el-dropdown-link {
72 | display: flex;
73 | gap: 4px;
74 | }
75 | button {
76 | border: none;
77 | outline: none;
78 | padding: 10px 20px;
79 | }
80 | span {
81 | font-size: 12px;
82 | }
83 | }
84 | .expand-group .tab-icons {
85 | background-position: center -995px;
86 | }
87 | .selection-group .tab-icons {
88 | background-position: 7px -1175px;
89 | }
90 | .insert-group {
91 | max-width: 208px;
92 | .menu-btn {
93 | padding: 0 4px;
94 | }
95 | .insert-sibling-box .tab-icons {
96 | background-position: 0 -20px;
97 | }
98 | .insert-parent-box .tab-icons{
99 | background-position: 0 -40px;
100 | }
101 | }
102 | .move-group,
103 | .edit-del-group {
104 | display: flex;
105 | flex-direction: column;
106 | align-items: center;
107 | justify-content: space-around;
108 | }
109 | .move-group {
110 | .move-up .tab-icons {
111 | background-position: 3px -280px;
112 | }
113 | .move-down .tab-icons {
114 | background-position: 3px -300px;
115 | }
116 | }
117 | .edit-del-group {
118 | .edit .tab-icons {
119 | background-position: 0 -60px;
120 | }
121 | .del .tab-icons {
122 | background-position: 0 -80px;
123 | }
124 | }
125 | }
126 | .menu-btn {
127 | cursor: pointer;
128 | gap: 4px;
129 | @include flexcenter;
130 | }
131 | .menu-btn:not([disabled]):hover {
132 | background-color: $btn-hover-color;
133 | }
134 | .tab-icons {
135 | display: inline-block;
136 | width: 20px;
137 | height: 20px;
138 | background-image: url("../../assets/minder/icons.png");
139 | background-repeat: no-repeat;
140 | }
141 | .do-group {
142 | width: 40px;
143 | height: 100%;
144 | padding: 0 5px;
145 | p {
146 | height: 50%;
147 | margin: 0;
148 | @include flexcenter;
149 | }
150 | .undo i {
151 | background-position: 0 -1240px;
152 | }
153 | .redo i {
154 | background-position: 0 -1220px;
155 | }
156 | }
157 | .attachment-group {
158 | width: 185px;
159 | @include flexcenter;
160 | .el-dropdown-link {
161 | font-size: 12px;
162 | }
163 | button {
164 | font-size: inherit;
165 | width: 45px;
166 | height: 20px;
167 | padding: 0;
168 | background-repeat: no-repeat;
169 | background-position: right;
170 | @include button;
171 | @include flexcenter;
172 | span {
173 | margin-left: 15px;
174 | }
175 | }
176 | button:hover {
177 | background-color: $btn-hover-color;
178 | }
179 | & > div {
180 | font-size: inherit;
181 | flex-wrap: wrap;
182 | height: 100%;
183 | @include flexcenter;
184 | }
185 | .insert {
186 | height: 25px;
187 | background-repeat: no-repeat;
188 | }
189 | .link {
190 | .insert {
191 | background-position: 50% -100px;
192 | }
193 | }
194 | .img {
195 | .insert {
196 | background-position: 50% -125px;
197 | }
198 | }
199 | .remark {
200 | .insert {
201 | background-position: 50% -1150px;
202 | }
203 | }
204 | .el-dropdown {
205 | cursor: default;
206 | }
207 | }
208 | .progress-group {
209 | ul {
210 | width: 116px;
211 | margin: 0;
212 | padding: 0;
213 | list-style: none;
214 | display: flex;
215 | flex-wrap: wrap;
216 | gap: 4px;
217 | li {
218 | width: 20px;
219 | height: 20px;
220 | transform: scale(1.1);
221 | background-image: url("../../assets/minder/iconprogress.png");
222 | }
223 | }
224 | @for $i from 0 through 9 {
225 | .progress-#{$i} {
226 | background-position: 0 -20px * (-1 + $i);
227 | }
228 | }
229 | }
230 | .mold-group {
231 | @for $i from 1 through 6 {
232 | .mold-#{$i} {
233 | background-position: (1-$i) * 50px 0;
234 | }
235 | }
236 | .dropdown-toggle {
237 | font-size: 12px;
238 | .current-mold {
239 | display: inline-block;
240 | width: 50px;
241 | height: 42px;
242 | line-height: 42px;
243 | padding: 0;
244 | vertical-align: middle;
245 | }
246 | i {
247 | vertical-align: middle;
248 | }
249 | }
250 | }
251 | .theme-group {
252 | .dropdown-toggle {
253 | font-size: 12px;
254 | .current-theme {
255 | font-size: 12px;
256 | display: inline-block;
257 | text-align: center;
258 | width: 70px;
259 | height: 30px;
260 | line-height: 30px;
261 | padding: 0;
262 | }
263 | }
264 | }
265 | .arrange-group {
266 | .arrange {
267 | display: flex;
268 | flex-direction: column;
269 | align-items: center;
270 | }
271 | .tab-icons {
272 | display: inline-block;
273 | width: 25px;
274 | height: 25px;
275 | margin: 0;
276 | background-repeat: no-repeat;
277 | background-position: 0 -150px;
278 | }
279 | }
280 | .style-group {
281 | align-items: center;
282 | gap: 8px;
283 | .clear-style-btn,
284 | .copy-paste-panel {
285 | display: flex;
286 | flex-direction: column;
287 | align-items: center;
288 | }
289 | .clear-style-btn {
290 | .tab-icons {
291 | display: inline-block;
292 | width: 25px;
293 | height: 25px;
294 | margin: 0;
295 | background-repeat: no-repeat;
296 | background-position: 0 -175px;
297 | }
298 | }
299 | .copy-paste-panel {
300 | gap: 4px;
301 | .tab-icons {
302 | display: inline-block;
303 | width: 20px;
304 | height: 20px;
305 | }
306 | .copy-style {
307 | .tab-icons {
308 | background-position: 0 -200px;
309 | }
310 | }
311 | .paste-style {
312 | .tab-icons {
313 | background-position: 0 -220px;
314 | }
315 | }
316 | }
317 | }
318 | .font-group {
319 | display: flex;
320 | flex-direction: column;
321 | gap: 4px;
322 | .el-input__inner {
323 | width: 80px;
324 | height: 22px !important;
325 | line-height: 22px !important;
326 | }
327 | .el-input__suffix {
328 | right: 0;
329 | }
330 | .el-input__icon {
331 | line-height: 22px !important;
332 | }
333 | .font-bold,
334 | .font-italic {
335 | display: inline-block;
336 | width: 20px;
337 | height: 16px;
338 | margin: 0 3px;
339 | }
340 | .font-bold {
341 | background-position: 0 -242px;
342 | }
343 | .font-italic {
344 | background-position: 0 -262px;
345 | }
346 | }
347 | }
348 |
--------------------------------------------------------------------------------
/src/style/hotbox.scss:
--------------------------------------------------------------------------------
1 | .hotbox {
2 | font-family: Arial, "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
3 | position: absolute;
4 | left: 0;
5 | top: 0;
6 | overflow: visible;
7 |
8 | .state {
9 | position: absolute;
10 | overflow: visible;
11 | display: none;
12 |
13 | .center,
14 | .ring {
15 | .button {
16 | position: absolute;
17 | width: 70px;
18 | height: 70px;
19 | margin-left: -35px;
20 | margin-top: -35px;
21 | border-radius: 100%;
22 | box-shadow: 0 0 30px rgba(0, 0, 0, 0.3);
23 | }
24 |
25 | .label,
26 | .key {
27 | display: block;
28 | text-align: center;
29 | line-height: 1.4em;
30 | vertical-align: middle;
31 | }
32 |
33 | .label {
34 | font-size: 16px;
35 | margin-top: 17px;
36 | color: black;
37 | font-weight: normal;
38 | line-height: 1em;
39 | }
40 |
41 | .key {
42 | font-size: 12px;
43 | color: #999;
44 | }
45 | }
46 |
47 | .ring-shape {
48 | position: absolute;
49 | left: -25px;
50 | top: -25px;
51 | border: 25px solid rgba(0, 0, 0, 0.3);
52 | border-radius: 100%;
53 | box-sizing: content-box;
54 | }
55 |
56 | .top,
57 | .bottom {
58 | position: absolute;
59 | white-space: nowrap;
60 |
61 | .button {
62 | display: inline-block;
63 | padding: 8px 15px;
64 | margin: 0 10px;
65 | border-radius: 15px;
66 | box-shadow: 0 0 30px rgba(0, 0, 0, 0.3);
67 | position: relative;
68 |
69 | .label {
70 | font-size: 14px;
71 | line-height: 14px;
72 | vertical-align: middle;
73 | color: black;
74 | line-height: 1em;
75 | }
76 |
77 | .key {
78 | font-size: 12px;
79 | line-height: 12px;
80 | vertical-align: middle;
81 | color: #999;
82 | margin-left: 3px;
83 |
84 | &:before {
85 | content: "(";
86 | }
87 |
88 | &:after {
89 | content: ")";
90 | }
91 | }
92 | }
93 | }
94 |
95 | .button {
96 | background: #f9f9f9;
97 | overflow: hidden;
98 | cursor: default;
99 |
100 | .key,
101 | .label {
102 | opacity: 0.3;
103 | }
104 | }
105 |
106 | .button.enabled {
107 | background: white;
108 |
109 | .key,
110 | .label {
111 | opacity: 1;
112 | }
113 |
114 | &:hover {
115 | background: lighten(rgb(228, 93, 92), 5%);
116 |
117 | .label {
118 | color: white;
119 | }
120 |
121 | .key {
122 | color: lighten(rgb(228, 93, 92), 30%);
123 | }
124 | }
125 |
126 | &.selected {
127 | -webkit-animation: selected 0.1s ease;
128 | background: rgb(228, 93, 92);
129 |
130 | .label {
131 | color: white;
132 | }
133 |
134 | .key {
135 | color: lighten(rgb(228, 93, 92), 30%);
136 | }
137 | }
138 |
139 | &.pressed,
140 | &:active {
141 | background: #ff974d;
142 |
143 | .label {
144 | color: white;
145 | }
146 |
147 | .key {
148 | color: lighten(#ff974d, 30%);
149 | }
150 | }
151 | }
152 | }
153 |
154 | .state.active {
155 | display: block;
156 | }
157 | }
158 |
159 | @-webkit-keyframes selected {
160 | 0% {
161 | transform: scale(1);
162 | }
163 |
164 | 50% {
165 | transform: scale(1.1);
166 | }
167 |
168 | 100% {
169 | transform: scale(1);
170 | }
171 | }
172 |
173 | .hotbox-key-receiver {
174 | position: absolute;
175 | left: -999999px;
176 | top: -999999px;
177 | width: 20px;
178 | height: 20px;
179 | outline: none;
180 | margin: 0;
181 | }
182 |
--------------------------------------------------------------------------------
/src/style/mixin.scss:
--------------------------------------------------------------------------------
1 | $btn-hover-color: #eee;
2 | *[disabled] {
3 | opacity: 0.5;
4 | }
5 |
6 | @mixin block {
7 | width: 100%;
8 | height: 100%;
9 | }
10 |
11 | @mixin button {
12 | background: transparent;
13 | border: none;
14 | outline: none;
15 | }
16 |
17 | @mixin flexcenter {
18 | display: flex;
19 | justify-content: center;
20 | align-items: center;
21 | }
22 |
--------------------------------------------------------------------------------
/src/style/navigator.scss:
--------------------------------------------------------------------------------
1 | .nav-bar {
2 | position: absolute;
3 | width: 35px;
4 | height: 200px;
5 | padding: 5px 0;
6 | left: 10px;
7 | bottom: 10px;
8 | background: #fc8383;
9 | color: #fff;
10 | border-radius: 4px;
11 | z-index: 10;
12 | box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.2);
13 | transition: -webkit-transform 0.7s 0.1s ease;
14 | transition: transform 0.7s 0.1s ease;
15 |
16 | .nav-btn {
17 | width: 35px;
18 | height: 24px;
19 | line-height: 24px;
20 | text-align: center;
21 |
22 | .icon {
23 | width: 20px;
24 | height: 20px;
25 | margin: 2px auto;
26 | display: block;
27 | }
28 |
29 | &.active {
30 | background-color: #5a6378;
31 | }
32 | }
33 |
34 | .zoom-in .icon {
35 | background-position: 0 -730px;
36 | }
37 |
38 | .zoom-out .icon {
39 | background-position: 0 -750px;
40 | }
41 |
42 | .hand .icon {
43 | background-position: 0 -770px;
44 | width: 25px;
45 | height: 25px;
46 | margin: 0 auto;
47 | }
48 |
49 | .camera .icon {
50 | background-position: 0 -870px;
51 | width: 25px;
52 | height: 25px;
53 | margin: 0 auto;
54 | }
55 |
56 | .nav-trigger .icon {
57 | background-position: 0 -845px;
58 | width: 25px;
59 | height: 25px;
60 | margin: 0 auto;
61 | }
62 |
63 | .zoom-pan {
64 | width: 2px;
65 | height: 70px;
66 | box-shadow: 0 1px #e50000;
67 | position: relative;
68 | background: white;
69 | margin: 3px auto;
70 | overflow: visible;
71 |
72 | .origin {
73 | position: absolute;
74 | width: 20px;
75 | height: 8px;
76 | left: -9px;
77 | margin-top: -4px;
78 | background: transparent;
79 |
80 | &:after {
81 | content: " ";
82 | display: block;
83 | width: 6px;
84 | height: 2px;
85 | background: white;
86 | left: 7px;
87 | top: 3px;
88 | position: absolute;
89 | }
90 | }
91 |
92 | .indicator {
93 | position: absolute;
94 | width: 8px;
95 | height: 8px;
96 | left: -3px;
97 | background: white;
98 | border-radius: 100%;
99 | margin-top: -4px;
100 | }
101 | }
102 | }
103 |
104 | .nav-previewer {
105 | background: #fff;
106 | width: 140px;
107 | height: 120px;
108 | position: absolute;
109 | left: 45px;
110 | bottom: 30px;
111 | box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
112 | border-radius: 0 2px 2px 0;
113 | padding: 1px;
114 | z-index: 9;
115 | cursor: crosshair;
116 | transition: -webkit-transform 0.7s 0.1s ease;
117 | transition: transform 0.7s 0.1s ease;
118 |
119 | &.grab {
120 | cursor: move;
121 | cursor: -webkit-grabbing;
122 | cursor: -moz-grabbing;
123 | cursor: grabbing;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/style/normalize.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-family: sans-serif;
3 | line-height: 1.15;
4 | -ms-text-size-adjust: 100%;
5 | -webkit-text-size-adjust: 100%;
6 | }
7 |
8 | body {
9 | margin: 0;
10 | }
11 |
12 | article, aside, footer, header, nav, section {
13 | display: block;
14 | }
15 |
16 | h1 {
17 | font-size: 2em;
18 | margin: .67em 0;
19 | }
20 |
21 | figcaption, figure, main {
22 | display: block;
23 | }
24 |
25 | figure {
26 | margin: 1em 40px;
27 | }
28 |
29 | hr {
30 | overflow: visible;
31 | box-sizing: content-box;
32 | height: 0;
33 | }
34 |
35 | pre {
36 | font-family: monospace, monospace;
37 | font-size: 1em;
38 | }
39 |
40 | a {
41 | background-color: transparent;
42 | -webkit-text-decoration-skip: objects;
43 | }
44 |
45 | a:active, a:hover {
46 | outline-width: 0;
47 | }
48 |
49 | abbr[title] {
50 | text-decoration: underline;
51 | text-decoration: underline dotted;
52 | border-bottom: none;
53 | }
54 |
55 | b, strong {
56 | font-weight: inherit;
57 | }
58 |
59 | b, strong {
60 | font-weight: bolder;
61 | }
62 |
63 | code, kbd, samp {
64 | font-family: monospace, monospace;
65 | font-size: 1em;
66 | }
67 |
68 | dfn {
69 | font-style: italic;
70 | }
71 |
72 | mark {
73 | color: #000;
74 | background-color: #ff0;
75 | }
76 |
77 | small {
78 | font-size: 80%;
79 | }
80 |
81 | sub, sup {
82 | font-size: 75%;
83 | line-height: 0;
84 | position: relative;
85 | vertical-align: baseline;
86 | }
87 |
88 | sub {
89 | bottom: -.25em;
90 | }
91 |
92 | sup {
93 | top: -.5em;
94 | }
95 |
96 | audio, video {
97 | display: inline-block;
98 | }
99 |
100 | audio:not([controls]) {
101 | display: none;
102 | height: 0;
103 | }
104 |
105 | img {
106 | border-style: none;
107 | }
108 |
109 | svg:not(:root) {
110 | overflow: hidden;
111 | }
112 |
113 | button, input, optgroup, select, textarea {
114 | font-family: sans-serif;
115 | font-size: 100%;
116 | line-height: 1.15;
117 | margin: 0;
118 | }
119 |
120 | button, input {
121 | overflow: visible;
122 | }
123 |
124 | button, select {
125 | text-transform: none;
126 | }
127 |
128 | button, html [type='button'], [type='reset'], [type='submit'] {
129 | -webkit-appearance: button;
130 | }
131 |
132 | [type='button']::-moz-focus-inner, [type='reset']::-moz-focus-inner, [type='submit']::-moz-focus-inner, button::-moz-focus-inner {
133 | padding: 0;
134 | border-style: none;
135 | }
136 |
137 | [type='button']:-moz-focusring, [type='reset']:-moz-focusring, [type='submit']:-moz-focusring, button:-moz-focusring {
138 | outline: 1px dotted ButtonText;
139 | }
140 |
141 | fieldset {
142 | margin: 0 2px;
143 | padding: .35em .625em .75em;
144 | border: 1px solid #c0c0c0;
145 | }
146 |
147 | legend {
148 | display: table;
149 | box-sizing: border-box;
150 | max-width: 100%;
151 | padding: 0;
152 | white-space: normal;
153 | color: inherit;
154 | }
155 |
156 | progress {
157 | display: inline-block;
158 | vertical-align: baseline;
159 | }
160 |
161 | textarea {
162 | overflow: auto;
163 | }
164 |
165 | [type='checkbox'], [type='radio'] {
166 | box-sizing: border-box;
167 | padding: 0;
168 | }
169 |
170 | [type='number']::-webkit-inner-spin-button, [type='number']::-webkit-outer-spin-button {
171 | height: auto;
172 | }
173 |
174 | [type='search'] {
175 | outline-offset: -2px;
176 | -webkit-appearance: textfield;
177 | }
178 |
179 | [type='search']::-webkit-search-cancel-button, [type='search']::-webkit-search-decoration {
180 | -webkit-appearance: none;
181 | }
182 |
183 | ::-webkit-file-upload-button {
184 | font: inherit;
185 | -webkit-appearance: button;
186 | }
187 |
188 | details, menu {
189 | display: block;
190 | }
191 |
192 | summary {
193 | display: list-item;
194 | }
195 |
196 | canvas {
197 | display: inline-block;
198 | }
199 |
200 | template {
201 | display: none;
202 | }
203 |
204 | [hidden] {
205 | display: none;
206 | }
207 |
--------------------------------------------------------------------------------