Browse Source

feat: add braft-editor

node 6 years ago
parent
commit
9a795f4afb
88 changed files with 28848 additions and 0 deletions
  1. 20
    0
      source/braft-editor/.babelrc
  2. 3
    0
      source/braft-editor/.eslintignore
  3. 49
    0
      source/braft-editor/.eslintrc
  4. 40
    0
      source/braft-editor/.eslintrc.js
  5. 2
    0
      source/braft-editor/.gitattributes
  6. 42
    0
      source/braft-editor/.gitignore
  7. 8
    0
      source/braft-editor/.npmignore
  8. 4
    0
      source/braft-editor/.prettierrc
  9. 21
    0
      source/braft-editor/LICENSE
  10. 100
    0
      source/braft-editor/README.md
  11. 65
    0
      source/braft-editor/build/webpack.base.js
  12. 29
    0
      source/braft-editor/build/webpack.development.js
  13. 49
    0
      source/braft-editor/build/webpack.production.js
  14. 1
    0
      source/braft-editor/dist/index.css
  15. 4459
    0
      source/braft-editor/dist/index.js
  16. 1
    0
      source/braft-editor/dist/index.js.map
  17. 32
    0
      source/braft-editor/example/index.html
  18. 128
    0
      source/braft-editor/example/index.jsx
  19. 5
    0
      source/braft-editor/example/package.json
  20. 107
    0
      source/braft-editor/example/server/index.js
  21. 7
    0
      source/braft-editor/example/yarn.lock
  22. 11106
    0
      source/braft-editor/package-lock.json
  23. 74
    0
      source/braft-editor/package.json
  24. 1194
    0
      source/braft-editor/src/assets/icons/icons-backup.json
  25. BIN
      source/braft-editor/src/assets/icons/icons.eot
  26. BIN
      source/braft-editor/src/assets/icons/icons.ttf
  27. 3
    0
      source/braft-editor/src/assets/scss/_base.scss
  28. 245
    0
      source/braft-editor/src/assets/scss/_braft.scss
  29. 4
    0
      source/braft-editor/src/assets/scss/_config.scss
  30. 236
    0
      source/braft-editor/src/assets/scss/_icons.scss
  31. 1
    0
      source/braft-editor/src/assets/scss/_inc.scss
  32. 341
    0
      source/braft-editor/src/components/business/ControlBar/index.jsx
  33. 151
    0
      source/braft-editor/src/components/business/ControlBar/style.scss
  34. 50
    0
      source/braft-editor/src/components/business/EmojiPicker/index.jsx
  35. 56
    0
      source/braft-editor/src/components/business/EmojiPicker/style.scss
  36. 63
    0
      source/braft-editor/src/components/business/FontFamily/index.jsx
  37. 9
    0
      source/braft-editor/src/components/business/FontFamily/style.scss
  38. 60
    0
      source/braft-editor/src/components/business/FontSize/index.jsx
  39. 34
    0
      source/braft-editor/src/components/business/FontSize/style.scss
  40. 39
    0
      source/braft-editor/src/components/business/Headings/index.jsx
  41. 35
    0
      source/braft-editor/src/components/business/Headings/style.scss
  42. 60
    0
      source/braft-editor/src/components/business/LetterSpacing/index.jsx
  43. 34
    0
      source/braft-editor/src/components/business/LetterSpacing/style.scss
  44. 60
    0
      source/braft-editor/src/components/business/LineHeight/index.jsx
  45. 34
    0
      source/braft-editor/src/components/business/LineHeight/style.scss
  46. 136
    0
      source/braft-editor/src/components/business/LinkEditor/index.jsx
  47. 70
    0
      source/braft-editor/src/components/business/LinkEditor/style.scss
  48. 62
    0
      source/braft-editor/src/components/business/TextAlign/index.jsx
  49. 114
    0
      source/braft-editor/src/components/business/TextColor/index.jsx
  50. 38
    0
      source/braft-editor/src/components/business/TextColor/style.scss
  51. 60
    0
      source/braft-editor/src/components/business/TextIndent/index.jsx
  52. 34
    0
      source/braft-editor/src/components/business/TextIndent/style.scss
  53. 29
    0
      source/braft-editor/src/components/common/ColorPicker/index.jsx
  54. 30
    0
      source/braft-editor/src/components/common/ColorPicker/style.scss
  55. 170
    0
      source/braft-editor/src/components/common/DropDown/index.jsx
  56. 135
    0
      source/braft-editor/src/components/common/DropDown/style.scss
  57. 170
    0
      source/braft-editor/src/components/common/Modal/index.jsx
  58. 119
    0
      source/braft-editor/src/components/common/Modal/style.scss
  59. 13
    0
      source/braft-editor/src/components/common/StaticContainer/index.jsx
  60. 12
    0
      source/braft-editor/src/components/common/Switch/index.jsx
  61. 28
    0
      source/braft-editor/src/components/common/Switch/style.scss
  62. 188
    0
      source/braft-editor/src/configs/controls.js
  63. 18
    0
      source/braft-editor/src/configs/keybindings.js
  64. 58
    0
      source/braft-editor/src/configs/maps.js
  65. 113
    0
      source/braft-editor/src/configs/props.js
  66. 381
    0
      source/braft-editor/src/editor/index.jsx
  67. 35
    0
      source/braft-editor/src/helpers/Responsive.js
  68. 70
    0
      source/braft-editor/src/index.jsx
  69. 70
    0
      source/braft-editor/src/languages/en.js
  70. 9
    0
      source/braft-editor/src/languages/index.js
  71. 71
    0
      source/braft-editor/src/languages/zh-hant.js
  72. 71
    0
      source/braft-editor/src/languages/zh.js
  73. 53
    0
      source/braft-editor/src/renderers/atomics/Audio/index.jsx
  74. 12
    0
      source/braft-editor/src/renderers/atomics/Audio/style.scss
  75. 53
    0
      source/braft-editor/src/renderers/atomics/Embed/index.jsx
  76. 13
    0
      source/braft-editor/src/renderers/atomics/Embed/style.scss
  77. 23
    0
      source/braft-editor/src/renderers/atomics/HorizontalLine/index.jsx
  78. 26
    0
      source/braft-editor/src/renderers/atomics/HorizontalLine/style.scss
  79. 324
    0
      source/braft-editor/src/renderers/atomics/Image/index.jsx
  80. 89
    0
      source/braft-editor/src/renderers/atomics/Image/style.scss
  81. 53
    0
      source/braft-editor/src/renderers/atomics/Video/index.jsx
  82. 16
    0
      source/braft-editor/src/renderers/atomics/Video/style.scss
  83. 40
    0
      source/braft-editor/src/renderers/decorators/Link/index.jsx
  84. 3
    0
      source/braft-editor/src/renderers/decorators/index.js
  85. 86
    0
      source/braft-editor/src/renderers/index.js
  86. 22
    0
      source/braft-editor/src/renderers/styles/blockStyles.js
  87. 59
    0
      source/braft-editor/src/renderers/styles/inlineStyles.js
  88. 6611
    0
      source/braft-editor/yarn.lock

+ 20
- 0
source/braft-editor/.babelrc View File

@@ -0,0 +1,20 @@
1
+{
2
+  "presets": [
3
+    [
4
+      "@babel/env", {
5
+        "targets": {
6
+          "browsers": [
7
+            "last 2 versions",
8
+            "not IE <= 10"
9
+          ]
10
+        },
11
+        "modules": false
12
+      }
13
+    ],
14
+    "@babel/react"
15
+  ],
16
+  "plugins": [
17
+    "@babel/transform-runtime",
18
+    "@babel/proposal-class-properties"
19
+  ]
20
+}

+ 3
- 0
source/braft-editor/.eslintignore View File

@@ -0,0 +1,3 @@
1
+node_modules
2
+dist
3
+example

+ 49
- 0
source/braft-editor/.eslintrc View File

@@ -0,0 +1,49 @@
1
+{
2
+  "parser": "babel-eslint",
3
+  "extends": ["airbnb", "prettier", "prettier/react"],
4
+  "globals": {
5
+    "_": false
6
+  },
7
+  "env": {
8
+    "browser": true,
9
+    "node": true
10
+  },
11
+  "rules": {
12
+    "indent": [
13
+      "error",
14
+      2,
15
+      {
16
+        "ArrayExpression": "first"
17
+      }
18
+    ],
19
+    "comma-dangle": 0,
20
+    "max-len": 0,
21
+    "jsx-quotes": [2, "prefer-double"],
22
+    "quotes": [
23
+      2,
24
+      "double",
25
+      {
26
+        "avoidEscape": true
27
+      }
28
+    ],
29
+    "arrow-parens": ["error", "always"],
30
+    "no-plusplus": [
31
+      "error",
32
+      {
33
+        "allowForLoopAfterthoughts": true
34
+      }
35
+    ],
36
+    "eol-last": ["error", "always"],
37
+    "class-methods-use-this": 0,
38
+    "function-paren-newline": 0,
39
+    "react/prefer-stateless-function": 0,
40
+    "react/forbid-prop-types": 0,
41
+    "jsx-a11y/anchor-is-valid": 0,
42
+    "react/sort-comp": 0,
43
+    "prefer-destructuring": 0,
44
+    "react/require-default-props": 0,
45
+    "import/no-extraneous-dependencies": 0,
46
+    "import/extensions": 0,
47
+    "import/no-unresolved": 0
48
+  }
49
+}

+ 40
- 0
source/braft-editor/.eslintrc.js View File

@@ -0,0 +1,40 @@
1
+module.exports = {
2
+    "parser": "babel-eslint",
3
+    "env": {
4
+        "browser": true,
5
+        "commonjs": true,
6
+        "es6": true,
7
+        "node": true
8
+    },
9
+    "extends": "eslint:recommended",
10
+    "parserOptions": {
11
+        "ecmaFeatures": {
12
+            "jsx": true
13
+        },
14
+        "ecmaVersion": 2018,
15
+        "sourceType": "module"
16
+    },
17
+    "plugins": [
18
+        "react"
19
+    ],
20
+    "rules": {
21
+        "react/jsx-uses-react": [1],
22
+        "react/jsx-uses-vars": [2],
23
+        "indent": [
24
+            "error",
25
+            2
26
+        ],
27
+        "linebreak-style": [
28
+            "error",
29
+            "unix"
30
+        ],
31
+        "quotes": [
32
+            "error",
33
+            "single"
34
+        ],
35
+        "semi": [
36
+            "error",
37
+            "never"
38
+        ]
39
+    }
40
+};

+ 2
- 0
source/braft-editor/.gitattributes View File

@@ -0,0 +1,2 @@
1
+braft.css diff=nodiff
2
+braft.js  diff=nodiff

+ 42
- 0
source/braft-editor/.gitignore View File

@@ -0,0 +1,42 @@
1
+static
2
+
3
+node_modules
4
+# Numerous always-ignore extensions
5
+*.diff
6
+*.err
7
+*.orig
8
+*.log*
9
+*.rej
10
+*.swo
11
+*.swp
12
+*.zip
13
+*.vi
14
+*~
15
+*.sass-cache
16
+/example/server/temp/*
17
+
18
+# OS or Editor folders
19
+.DS_Store
20
+._*
21
+Thumbs.db
22
+.cache
23
+.project
24
+.settings
25
+.tmproj
26
+*.esproj
27
+nbproject
28
+*.sublime-project
29
+*.sublime-workspace
30
+
31
+# Files
32
+
33
+# Komodo
34
+*.komodoproject
35
+.komodotools
36
+
37
+# Folders to ignore
38
+.hg
39
+.svn
40
+.CVS
41
+.idea
42
+git-m

+ 8
- 0
source/braft-editor/.npmignore View File

@@ -0,0 +1,8 @@
1
+/example
2
+/src
3
+/node_modules
4
+.babelrc
5
+.gitignore
6
+webpack.config.base.js
7
+webpack.config.js
8
+webpack.config.prod.js

+ 4
- 0
source/braft-editor/.prettierrc View File

@@ -0,0 +1,4 @@
1
+{
2
+  "trailingComma": "all",
3
+  "arrowParens": "always"
4
+}

+ 21
- 0
source/braft-editor/LICENSE View File

@@ -0,0 +1,21 @@
1
+MIT License
2
+
3
+Copyright (c) 2017 Margox (http://margox.cn)
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in all
13
+copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+SOFTWARE.

+ 100
- 0
source/braft-editor/README.md View File

@@ -0,0 +1,100 @@
1
+# Braft Editor
2
+
3
+#### 一个基于draft-js的Web富文本编辑器,适用于React框架,兼容主流现代浏览器。
4
+##### 注意,项目当前版本为2.0,如果你使用的是1.x.x版本,请参阅[旧版本文档](https://github.com/margox/braft-editor/blob/old-master/README.md)
5
+
6
+
7
+交流反馈请加QQ群:725634541
8
+
9
+## 特性
10
+- 完善的文本内容编辑功能
11
+- 诸多开放的编辑接口,良好的可扩展性
12
+- 允许插入图片、音视频等多媒体内容
13
+- 允许自定义多媒体内容的上传接口
14
+- 允许设置图片的左右浮动(即文字绕排功能)
15
+- 允许设置编辑器可用的颜色列表、字号以及字体
16
+- 允许自定义需要展示的控制按钮和展示顺序
17
+- 允许增加额外的自定义按钮
18
+- 多语言支持(目前已支持简体中文、繁体中文和英文)
19
+- ...更多特性开发中
20
+
21
+## 更新记录
22
+- 2018-09-06 v2.0.5
23
+  - 打包字体文件,免除额外的loader配置
24
+- 2018-09-05 v2.0.4
25
+  - 修复defaultValue中包含图片时,鼠标移入图片导致报错的问题
26
+- 2018-09-04 v2.0.3
27
+  - 修复代码块中按Tab导致报错的问题
28
+  - 修复控件文字提示被遮挡的问题
29
+- 2018-09-04 v2.0.1
30
+  - 图片工具栏自定义控件增加render属性
31
+- 2018-09-03 v2.0.0
32
+  - v2.0.0船新版本发布,几需体验3分钟,你就会爱上杰个编辑器
33
+
34
+## 安装
35
+```bash
36
+# 使用yarn安装
37
+yarn add braft-editor
38
+# 使用npm安装
39
+npm install braft-editor --save
40
+```
41
+## 使用
42
+
43
+编辑器支持**value**和**onChange**属性,这类似于React中原生的input组件。通常情况下,可以用典型的**受控组件**的形式来使用本编辑器:
44
+
45
+```jsx
46
+import React from 'react'
47
+// 引入编辑器以及EditorState子模块
48
+import BraftEditor, { EditorState } from 'braft-editor'
49
+// 引入编辑器样式
50
+import 'braft-editor/dist/index.css'
51
+
52
+export default class EditorDemo extends React.Component {
53
+
54
+  state = {
55
+      editorState: null
56
+  }
57
+
58
+  async componentDidMount () {
59
+    // 假设此处从服务端获取html格式的编辑器内容
60
+    const htmlContent = await fetchEditorContent()
61
+    // 使用EditorState.createFrom将html字符串转换为编辑器需要的editorState数据
62
+    this.setState({
63
+      editorState: EditorState.createFrom(htmlContent)
64
+    })
65
+  }
66
+
67
+  submitContent = async () => {
68
+    // 在编辑器获得焦点时按下ctrl+s会执行此方法
69
+    // 编辑器内容提交到服务端之前,可直接调用editorState.toHTML()来获取HTML格式的内容
70
+    const htmlContent = this.state.editorState.toHTML()
71
+    const result = await saveEditorContent(htmlContent)
72
+  }
73
+
74
+  handleEditorChange = (editorState) => {
75
+    this.setState({ editorState })
76
+  }
77
+
78
+  render () {
79
+
80
+    const { editorState } = this.state
81
+    return (
82
+      <div className="my-component">
83
+        <BraftEditor
84
+          value={editorState}
85
+          onChange={this.handleEditorChange}
86
+          onSave={this.submitContent}
87
+        />
88
+      </div>
89
+    )
90
+
91
+  }
92
+
93
+}
94
+```
95
+
96
+当然本编辑器也支持**defaultValue**属性,因此你也可以将本编辑器作为一个**非受控组件**来使用。
97
+
98
+-------
99
+
100
+## 更多介绍请查看[详细文档](https://www.yuque.com/margox/be/lzwpnr#zrs7hr)

+ 65
- 0
source/braft-editor/build/webpack.base.js View File

@@ -0,0 +1,65 @@
1
+var path = require('path')
2
+  , fs = require('fs')
3
+  , ExtractTextPlugin = require('extract-text-webpack-plugin')
4
+
5
+module.exports = {
6
+  module: {
7
+    //加载器配置
8
+    rules: [
9
+      { 
10
+        test: /\.(scss|css)$/,
11
+        use: ExtractTextPlugin.extract([
12
+          // 'style-loader',
13
+          'css-loader',
14
+          'sass-loader'
15
+        ])
16
+      }, {
17
+        test: /\.(js|jsx)$/,
18
+        exclude: [
19
+          /node_modules/,
20
+          /dist/
21
+        ],
22
+        use: [
23
+          {
24
+            loader: 'babel-loader',
25
+            options: {
26
+              ...JSON.parse(fs.readFileSync(path.resolve(__dirname, '../.babelrc'))),
27
+            },
28
+          },
29
+          'eslint-loader'
30
+        ]
31
+      }, {
32
+        test: /\.(png|svg)$/,
33
+        use: [
34
+          {
35
+            loader: 'url-loader',
36
+            options: {
37
+              limit: 8192,
38
+              name: '[name]_[hash:6].[ext]'
39
+            }
40
+          }
41
+        ]
42
+      }, {
43
+        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
44
+        use: [
45
+          {
46
+            loader: 'url-loader',
47
+            options: {
48
+              limit: 20000,
49
+              name: '[name].[ext]',
50
+              publicPath: './'
51
+            }
52
+          }
53
+        ]
54
+      }
55
+    ]
56
+  },
57
+  resolve: {
58
+    modules: [path.resolve(__dirname, '../src'), 'node_modules'],
59
+    alias: {
60
+      'react': path.join(__dirname, '../node_modules', 'react'),
61
+      'scssinc': path.join(__dirname, '../src/assets/scss/_inc.scss')
62
+    },
63
+    extensions: ['.js', '.jsx']
64
+  }
65
+}

+ 29
- 0
source/braft-editor/build/webpack.development.js View File

@@ -0,0 +1,29 @@
1
+var merge = require('webpack-merge')
2
+  , path = require('path')
3
+  , ExtractTextPlugin = require('extract-text-webpack-plugin')
4
+  , HtmlWebpackPlugin = require('html-webpack-plugin')
5
+  , baseConfigs = require('./webpack.base')
6
+
7
+module.exports = merge(baseConfigs, {
8
+  mode: 'development',
9
+  devtool: 'source-map',
10
+  entry: {
11
+    index : './example/index.jsx'
12
+  },
13
+  output: {
14
+    path: path.join(__dirname, './dist'),
15
+    filename: '[name].js'
16
+  },
17
+  plugins: [
18
+    new ExtractTextPlugin('index.css'),
19
+    new HtmlWebpackPlugin({
20
+      template: './example/index.html'
21
+    })
22
+  ],
23
+  devServer: {
24
+    stats: { chunks:false },
25
+    contentBase: './example',
26
+    port: 5998,
27
+    hot: true
28
+  }
29
+})

+ 49
- 0
source/braft-editor/build/webpack.production.js View File

@@ -0,0 +1,49 @@
1
+var merge = require('webpack-merge')
2
+  , ExtractTextPlugin = require('extract-text-webpack-plugin')
3
+  , OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
4
+  , path = require('path')
5
+  , baseConfigs = require('./webpack.base')
6
+
7
+module.exports = merge(baseConfigs, {
8
+  mode: 'production',
9
+  devtool: 'source-map',
10
+  context: path.join(__dirname, '../src'),
11
+  entry: {
12
+    index: './index.jsx'
13
+  },
14
+  output: {
15
+    path: path.join(__dirname, '../dist'),
16
+    filename: 'index.js',
17
+    publicPath: '/',
18
+    libraryTarget: 'umd'
19
+  },
20
+  externals: {
21
+    'react': 'react',
22
+    'react-dom': 'react-dom',
23
+    'draft-js': 'draft-js',
24
+    'draft-convert': 'draft-convert',
25
+    'draftjs-utils': 'draftjs-utils',
26
+    'braft-finder': 'braft-finder',
27
+    'braft-utils': 'braft-utils',
28
+    'braft-convert': 'braft-convert',
29
+    'immutable': 'immutable'
30
+  },
31
+  optimization: {
32
+    minimize: false,
33
+  },
34
+  plugins: [
35
+    new ExtractTextPlugin('index.css'),
36
+    new OptimizeCssAssetsPlugin({
37
+      assetNameRegExp: /.css$/,
38
+      cssProcessor: require('cssnano'),
39
+      sourceMap: true,
40
+      cssProcessorOptions: {
41
+        discardComments: {
42
+          removeAll: true
43
+        },
44
+        zindex: false,
45
+        safe: true
46
+      }
47
+    }),
48
+  ]
49
+})

+ 1
- 0
source/braft-editor/dist/index.css
File diff suppressed because it is too large
View File


+ 4459
- 0
source/braft-editor/dist/index.js
File diff suppressed because it is too large
View File


+ 1
- 0
source/braft-editor/dist/index.js.map
File diff suppressed because it is too large
View File


+ 32
- 0
source/braft-editor/example/index.html View File

@@ -0,0 +1,32 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+<head>
4
+<meta charset="UTF-8">
5
+<title>Demo</title>
6
+<style>
7
+html,body{
8
+  height: 100%;
9
+  margin: 0;
10
+  padding: 0;
11
+  font-family: Helvetica;
12
+  background-color: #fff;
13
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
14
+}
15
+#root{
16
+  position: relative;
17
+}
18
+.demo{
19
+  height: 100%;
20
+}
21
+.preview-button,
22
+.modal-button{
23
+  width: auto;
24
+  padding: 0 10px !important;
25
+  font-size: 14px !important;
26
+}
27
+</style>
28
+</head>
29
+<body>
30
+<div id="root"></div>
31
+</body>
32
+</html>

+ 128
- 0
source/braft-editor/example/index.jsx View File

@@ -0,0 +1,128 @@
1
+import React from 'react'
2
+import ReactDOM from 'react-dom'
3
+import BraftEditor, { EditorState } from '../dist'
4
+import { ContentUtils } from 'braft-utils'
5
+import '../dist/index.css'
6
+
7
+class Demo extends React.Component {
8
+
9
+  constructor(props) {
10
+
11
+    super(props)
12
+    this.state = {
13
+      editorState: EditorState.createFrom()
14
+    }
15
+    this.editorInstance = null
16
+
17
+  }
18
+
19
+  handleChange = (editorState) => {
20
+    this.setState({ editorState })
21
+  }
22
+
23
+  insertText = (text) => {
24
+    this.setState({
25
+        editorState: ContentUtils.insertText(this.state.editorState, text)
26
+    })
27
+  }
28
+
29
+  uploadFn = (param) => {
30
+
31
+    const xhr = new XMLHttpRequest
32
+    const fd = new FormData()
33
+
34
+    const successFn = () => {
35
+      param.success({
36
+        url: JSON.parse(xhr.responseText)[0].url,
37
+        meta: {
38
+          controls: true,
39
+          loop: true,
40
+          autoPlay: false,
41
+        }
42
+      })
43
+    }
44
+
45
+    const progressFn = (event) => {
46
+      param.progress(event.loaded / event.total * 100)
47
+    }
48
+
49
+    const errorFn = () => {
50
+      param.error({
51
+        msg: "unable to upload."
52
+      })
53
+    }
54
+
55
+    xhr.upload.addEventListener("progress", progressFn, false)
56
+    xhr.addEventListener("load", successFn, false)
57
+    xhr.addEventListener("error", errorFn, false)
58
+    xhr.addEventListener("abort", errorFn, false)
59
+
60
+    fd.append("file", param.file)
61
+    xhr.open("POST", "http://localhost:9090", true)
62
+    xhr.send(fd)
63
+
64
+  }
65
+
66
+  hooks = {
67
+    'change-block-type': console.log,
68
+    'insert-emoji': (fontSize) => {
69
+      return '123'
70
+    },
71
+    'insert-medias': console.log,
72
+    'exec-editor-command': console.log,
73
+    'select-medias': console.log,
74
+    'remove-medias': (medias) => {
75
+      return [medias[0]]
76
+    },
77
+    'deselect-medias': console.log,
78
+    'toggle-link': () => {
79
+      return {
80
+        href: 'http://www.baidu.com',
81
+        target: '_blank'
82
+      }
83
+    }
84
+  }
85
+
86
+  render() {
87
+
88
+    const controls = BraftEditor.defaultProps.controls.map(item => {
89
+      return item === 'bold' ? {
90
+        key: 'bold', // 使用key来指定控件类型
91
+        title: '加粗选中文字哦', // 自定义控件title
92
+        text: '点我加粗', // 使用自定义文案来代替默认图标(B),此处也可传入jsx
93
+      } : item
94
+    })
95
+
96
+    const extendControls = [
97
+      {
98
+        key: 'insert-123',
99
+        type: 'button',
100
+        text: 'Insert 123',
101
+        onClick: () => {
102
+          this.insertText('123')
103
+        }
104
+      }
105
+    ]
106
+
107
+    return (
108
+      <div>
109
+        <div className="demo" id="demo">
110
+          <BraftEditor
111
+            hooks={this.hooks}
112
+            controls={controls}
113
+            extendControls={extendControls}
114
+            media={{
115
+              // uploadFn: this.uploadFn
116
+            }}
117
+            onChange={this.handleChange}
118
+            defaultValue={this.state.editorState}
119
+          />
120
+        </div>
121
+      </div>
122
+    )
123
+
124
+  }
125
+
126
+}
127
+
128
+ReactDOM.render(<Demo />, document.querySelector('#root'))

+ 5
- 0
source/braft-editor/example/package.json View File

@@ -0,0 +1,5 @@
1
+{
2
+  "devDependencies": {
3
+    "formidable": "^1.2.1"
4
+  }
5
+}

+ 107
- 0
source/braft-editor/example/server/index.js View File

@@ -0,0 +1,107 @@
1
+const http = require('http')
2
+const fs = require('fs')
3
+const path = require('path')
4
+const url = require('url')
5
+const formidable = require('formidable')
6
+
7
+const mimeTypes = {
8
+  'jpeg': 'image/jpeg',
9
+  'jpg': 'image/jpeg',
10
+  'png': 'image/png',
11
+  'svg': 'image/svg',
12
+  'gif': 'image/gif',
13
+  'mp4': 'video/mp4',
14
+  'mp3': 'audio/mp3'
15
+}
16
+
17
+const port = 9090
18
+const fileSaveDir = path.join(__dirname, 'temp')
19
+
20
+const uploadServer = (req, res) => {
21
+
22
+  !fs.existsSync(fileSaveDir) && fs.mkdirSync(fileSaveDir)
23
+  let responseData = []
24
+
25
+  const form = new formidable.IncomingForm()
26
+  form.uploadDir = fileSaveDir
27
+  form.type = true
28
+  form.keepExtensions = true
29
+
30
+  form.parse(req, function(err, fields, files){
31
+
32
+    if(!err) {
33
+
34
+      Object.keys(files).forEach(function(key){
35
+
36
+        const file = files[key]
37
+        const filename = path.basename(file.path)
38
+
39
+        // 塞入响应数据中
40
+        responseData.push({
41
+          type: file.type,
42
+          name: filename,
43
+          url: 'http://localhost:' + port + '/temp/' + filename,
44
+          size: file.size / 1024 > 1024 ? (~~(10 * file.size / 1024 / 1024)) / 10 + 'MB' : ~~(file.size / 1024) + 'KB'
45
+        })
46
+
47
+      })
48
+    } else {
49
+      // console.warn(err)
50
+    }
51
+
52
+    res.setHeader('Access-Control-Allow-Origin', '*')
53
+    res.writeHead(200)
54
+    res.end(JSON.stringify(responseData))
55
+
56
+  })
57
+
58
+}
59
+
60
+const staticServer = (req, res) => {
61
+
62
+  var uri = url.parse(req.url).pathname
63
+  var filename = path.join(process.cwd(), unescape(uri))
64
+  var stats
65
+
66
+  try {
67
+    stats = fs.lstatSync(filename) // throws if path doesn't exist
68
+  } catch (e) {
69
+    res.writeHead(404, {'Content-Type': 'text/plain'})
70
+    res.write('404 Not Found\n')
71
+    res.end()
72
+    return
73
+  }
74
+
75
+  if (stats.isFile()) {
76
+    // path exists, is a file
77
+    var mimeType = mimeTypes[path.extname(filename).split('.').reverse()[0]]
78
+    res.writeHead(200, {'Content-Type': mimeType} )
79
+    var fileStream = fs.createReadStream(filename)
80
+    fileStream.pipe(res)
81
+  } else if (stats.isDirectory()) {
82
+    // path exists, is a directory
83
+    res.writeHead(200, {'Content-Type': 'text/plain'})
84
+    res.write('Index of '+uri+'\n')
85
+    res.write('TODO, show index?\n')
86
+    res.end()
87
+  } else {
88
+    // Symbolic link, other?
89
+    // TODO: follow symlinks?  security?
90
+    res.writeHead(500, {'Content-Type': 'text/plain'})
91
+    res.write('500 Internal server error\n')
92
+    res.end()
93
+  }
94
+
95
+}
96
+
97
+const server = http.createServer((req, res) => {
98
+
99
+  if (req.method.toLowerCase() === 'get') {
100
+    staticServer(req, res)
101
+  } else {
102
+    uploadServer(req, res)
103
+  }
104
+
105
+})
106
+
107
+server.listen(port)

+ 7
- 0
source/braft-editor/example/yarn.lock View File

@@ -0,0 +1,7 @@
1
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2
+# yarn lockfile v1
3
+
4
+
5
+formidable@^1.2.1:
6
+  version "1.2.1"
7
+  resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659"

+ 11106
- 0
source/braft-editor/package-lock.json
File diff suppressed because it is too large
View File


+ 74
- 0
source/braft-editor/package.json View File

@@ -0,0 +1,74 @@
1
+{
2
+  "name": "braft-editor",
3
+  "version": "2.0.5",
4
+  "description": "Rich Text Editor Based On Draft.js",
5
+  "main": "dist/index.js",
6
+  "author": "Margox",
7
+  "scripts": {
8
+    "start": "webpack-dev-server --config ./build/webpack.development.js -d --history-api-fallback --no-inline --progress --colors",
9
+    "build": "rm -rf dist/ && NODE_ENV=production webpack --config ./build/webpack.production.js --progress --colors",
10
+    "lint": "node_modules/eslint/bin/eslint.js .",
11
+    "test": "echo \"Error: no test specified\" && exit 1"
12
+  },
13
+  "repository": "https://github.com/margox/braft",
14
+  "keywords": [
15
+    "braft",
16
+    "draft",
17
+    "draft-js",
18
+    "rich",
19
+    "text",
20
+    "editor",
21
+    "rich-text-editor",
22
+    "wysiwyg"
23
+  ],
24
+  "license": "MIT",
25
+  "devDependencies": {
26
+    "@babel/cli": "^7.0.0",
27
+    "@babel/core": "^7.0.0",
28
+    "@babel/plugin-proposal-class-properties": "^7.0.0",
29
+    "@babel/plugin-transform-runtime": "^7.0.0",
30
+    "@babel/preset-env": "^7.0.0",
31
+    "@babel/preset-react": "^7.0.0",
32
+    "babel-eslint": "^9.0.0",
33
+    "babel-loader": "^8.0.0",
34
+    "babel-plugin-transform-runtime": "^6.23.0",
35
+    "css-loader": "^0.28.11",
36
+    "eslint": "^5.4.0",
37
+    "eslint-config-standard": "^12.0.0",
38
+    "eslint-loader": "^2.1.0",
39
+    "eslint-plugin-import": "^2.14.0",
40
+    "eslint-plugin-node": "^7.0.1",
41
+    "eslint-plugin-promise": "^4.0.0",
42
+    "eslint-plugin-react": "^7.11.1",
43
+    "eslint-plugin-standard": "^4.0.0",
44
+    "extract-text-webpack-plugin": "next",
45
+    "file-loader": "^2.0.0",
46
+    "html-webpack-plugin": "next",
47
+    "node-sass": "^4.9.0",
48
+    "optimize-css-assets-webpack-plugin": "^4.0.2",
49
+    "react": "^16.4.1",
50
+    "react-dom": "^16.4.1",
51
+    "react-hot-loader": "^4.3.3",
52
+    "sass-loader": "^7.0.3",
53
+    "style-loader": "^0.21.0",
54
+    "url-loader": "^1.0.1",
55
+    "webpack": "^4.12.0",
56
+    "webpack-cli": "^3.0.8",
57
+    "webpack-dev-server": "^3.1.5",
58
+    "webpack-merge": "^4.1.3"
59
+  },
60
+  "dependencies": {
61
+    "@babel/runtime": "^7.0.0",
62
+    "braft-convert": "^2.0.2",
63
+    "braft-finder": "^0.0.9",
64
+    "braft-utils": "^0.0.16",
65
+    "draft-convert": "^2.0.0",
66
+    "draft-js": "^0.10.3",
67
+    "draftjs-utils": "^0.9.4"
68
+  },
69
+  "peerDependencies": {
70
+    "braft-convert": "^1.9.7",
71
+    "react": "^15.0.2|| ^16.0.0-rc || ^16.0.0",
72
+    "react-dom": "^15.0.2|| ^16.0.0-rc || ^16.0.0"
73
+  }
74
+}

+ 1194
- 0
source/braft-editor/src/assets/icons/icons-backup.json
File diff suppressed because it is too large
View File


BIN
source/braft-editor/src/assets/icons/icons.eot View File


BIN
source/braft-editor/src/assets/icons/icons.ttf View File


+ 3
- 0
source/braft-editor/src/assets/scss/_base.scss View File

@@ -0,0 +1,3 @@
1
+@import 'inc';
2
+@import 'icons';
3
+@import 'braft';

+ 245
- 0
source/braft-editor/src/assets/scss/_braft.scss View File

@@ -0,0 +1,245 @@
1
+@import '~scssinc';
2
+
3
+.bf-content{
4
+  height: 500px;
5
+  overflow: auto;
6
+  font-size: 16px;
7
+  img{
8
+    user-select: none;
9
+  }
10
+  *{
11
+    line-height: initial;
12
+  }
13
+}
14
+
15
+.bf-container{
16
+  position: relative;
17
+  height: 100%;
18
+  padding: 0;
19
+
20
+  &.disabled{
21
+    pointer-events: none;
22
+    opacity: .7;
23
+    filter: grayscale(70%);
24
+  }
25
+
26
+  .input-group{
27
+    display: block;
28
+    input{
29
+      box-sizing: border-box;
30
+      width: 100%;
31
+      height: 36px;
32
+      padding: 0 15px;
33
+      font-size: 14px;
34
+    }
35
+  }
36
+
37
+  .pull-left{
38
+    float: left;
39
+  }
40
+  .pull-right{
41
+    float: right;
42
+  }
43
+
44
+  button{
45
+    &.ghost,
46
+    &.default,
47
+    &.primary{
48
+      height: 32px;
49
+      padding: 0 20px;
50
+      color: #fff;
51
+      font-size: 12px;
52
+    }
53
+    &.default{
54
+      background-color: rgba(#fff, .15);
55
+      border: none;
56
+      &:hover{
57
+        background-color: rgba(#fff, .1);
58
+      }
59
+    }
60
+    &.ghost{
61
+      background-color: transparent;
62
+      border: none;
63
+      box-shadow: inset 0 0 0 .5px rgba(#fff, .5);
64
+      &:hover{
65
+        box-shadow: inset 0 0 0 .5px rgba(#fff, .7);
66
+      }
67
+    }
68
+    &.primary{
69
+      background-color: $COLOR_ACTIVE;
70
+      border: none;
71
+      color: #fff;
72
+      &:hover{
73
+        background-color: $COLOR_ACTIVE - 20;
74
+      }
75
+    }
76
+  }
77
+  .public-DraftEditorPlaceholder-root{
78
+    top: 15px;
79
+    left: 15px;
80
+    font-size: 16px;
81
+    pointer-events: none;
82
+  }
83
+  .DraftEditor-editorContainer{
84
+    box-sizing: border-box;
85
+    border: none;
86
+  }
87
+  .DraftEditor-root,
88
+  .public-DraftEditor-content{
89
+    height: 100%;
90
+  }
91
+  .public-DraftEditor-content{
92
+    box-sizing: border-box;
93
+    padding: 15px;
94
+    word-wrap: break-word;
95
+    word-break: break-all;
96
+    .braft-link{
97
+      color: #4078c0;
98
+    }
99
+    blockquote{
100
+      margin: 0 0 10px 0;
101
+      padding: 15px 20px;
102
+      background-color: #f1f2f3;
103
+      border-left: solid 5px #ccc;
104
+      color: #666;
105
+      font-style: italic;
106
+    }
107
+    pre{
108
+      max-width: 100%;
109
+      max-height: 100%;
110
+      margin: 0 0 10px 0;
111
+      padding: 15px;
112
+      overflow: auto;
113
+      background-color: #f1f2f3;
114
+      border-radius: 3px;
115
+      color: #666;
116
+      font-family: monospace;
117
+      font-size: 14px;
118
+      font-weight: normal;
119
+      line-height: 16px;
120
+      word-wrap: break-word;
121
+      white-space: pre-wrap;
122
+      pre{
123
+        margin: 0;
124
+        padding: 0;
125
+      }
126
+    }
127
+  }
128
+  .bfa-left {
129
+    text-align: left;
130
+    .public-DraftStyleDefault-ltr{
131
+      text-align: left;
132
+    }
133
+  }
134
+  .bfa-right {
135
+    text-align: right;
136
+    .public-DraftStyleDefault-ltr{
137
+      text-align: right;
138
+    }
139
+  }
140
+  .bfa-center {
141
+    text-align: center;
142
+    .public-DraftStyleDefault-ltr{
143
+      text-align: center;
144
+    }
145
+  }
146
+  .bfa-justify {
147
+    text-align: justify;
148
+    .public-DraftStyleDefault-ltr{
149
+      text-align: justify;
150
+    }
151
+  }
152
+  .bfa-left,
153
+  .bfa-right,
154
+  .bfa-center,
155
+  .bfa-justify{
156
+    & > div{
157
+      display: inline-block;
158
+    }
159
+  }
160
+  .bff-left{
161
+    position: relative;
162
+    z-index: 1;
163
+    float: left;
164
+    margin: 0 10px 0 0;
165
+  }
166
+  .bff-right{
167
+    position: relative;
168
+    z-index: 1;
169
+    float: right;
170
+    margin: 0 0 0 10px;
171
+  }
172
+
173
+  .bf-media{
174
+    position: relative;
175
+  }
176
+
177
+  .bf-image{
178
+    position: relative;
179
+    img{
180
+      display: inline-block;
181
+      max-width: 100%;
182
+      height: auto;
183
+      font-size: 0;
184
+      resize: both;
185
+      outline-offset: 1px;
186
+      &:hover{
187
+        outline: solid 1px $COLOR_ACTIVE;
188
+      }
189
+    }
190
+  }
191
+
192
+  .bf-media-toolbar{
193
+    position: absolute;
194
+    z-index: 3;
195
+    bottom: 15px;
196
+    left: 50%;
197
+    width: auto;
198
+    background-color: $COLOR_DARK;
199
+    border-radius: 2px;
200
+    font-weight: normal;
201
+    text-align: center;
202
+    white-space: nowrap;
203
+    transform: translateX(-50%);
204
+    box-shadow: 0 5px 15px rgba(#000, .2);
205
+    user-select: none;
206
+    &::before,
207
+    .bf-media-toolbar-arrow{
208
+      position: absolute;
209
+      bottom: -10px;
210
+      left: 50%;
211
+      display: block;
212
+      border: solid 5px transparent;
213
+      border-top-color: $COLOR_DARK;
214
+      content: '';
215
+      transform: translateX(-5px);
216
+    }
217
+    a{
218
+      display: inline-block;
219
+      width: 40px;
220
+      height: 40px;
221
+      color: rgba(#fff, .5);
222
+      font-family: 'braft-icons';
223
+      font-size: 18px;
224
+      font-weight: normal;
225
+      line-height: 40px;
226
+      text-align: center;
227
+      text-decoration: none;
228
+      text-transform: uppercase;
229
+      cursor: pointer;
230
+      &:hover{
231
+        color: #fff;
232
+      }
233
+      &:first-child{
234
+        border-radius: 2px 0 0 2px;
235
+      }
236
+      &:last-child{
237
+        border-radius: 0 2px 2px 0;
238
+      }
239
+      &.active{
240
+        color: $COLOR_ACTIVE;
241
+      }
242
+    }
243
+  }
244
+
245
+}

+ 4
- 0
source/braft-editor/src/assets/scss/_config.scss View File

@@ -0,0 +1,4 @@
1
+$COLOR_GRAY: #6a6f7b;
2
+$COLOR_ACTIVE: #3498db;
3
+$COLOR_DANGER: #e74c3c;
4
+$COLOR_DARK: #21242a;

+ 236
- 0
source/braft-editor/src/assets/scss/_icons.scss View File

@@ -0,0 +1,236 @@
1
+$eotPath: '../icons/icons.eot';
2
+$ttfPath: '../icons/icons.ttf';
3
+
4
+@font-face {
5
+  font-family: 'braft-icons';
6
+  src: url($eotPath);
7
+  src: url($ttfPath) format('truetype');
8
+  font-weight: normal;
9
+  font-style: normal;
10
+}
11
+
12
+.bf-container,
13
+.bf-modal-root{
14
+
15
+  [class^="bfi-"], [class*=" bfi-"] {
16
+    /* use !important to prevent issues with browser extensions that change fonts */
17
+    font-family: 'braft-icons' !important;
18
+    speak: none;
19
+    font-style: normal;
20
+    font-weight: normal;
21
+    font-variant: normal;
22
+    text-transform: none;
23
+    /* Better Font Rendering =========== */
24
+    -webkit-font-smoothing: antialiased;
25
+    -moz-osx-font-smoothing: grayscale;
26
+  }
27
+  .bfi-format_clear:before {
28
+    content: "\e239";
29
+  }
30
+  .bfi-hr:before {
31
+    content: "\e925";
32
+  }
33
+  .bfi-colorize:before {
34
+    content: "\e3b8";
35
+  }
36
+  .bfi-crop_free:before {
37
+    content: "\e3c2";
38
+  }
39
+  .bfi-pause:before {
40
+    content: "\e034";
41
+  }
42
+  .bfi-play_arrow:before {
43
+    content: "\e037";
44
+  }
45
+  .bfi-bin:before {
46
+    content: "\e9ac";
47
+  }
48
+  .bfi-replay:before {
49
+    content: "\e042";
50
+  }
51
+  .bfi-tune:before {
52
+    content: "\e429";
53
+  }
54
+  .bfi-close:before {
55
+    content: "\e913";
56
+  }
57
+  .bfi-align-center:before {
58
+    content: "\e028";
59
+  }
60
+  .bfi-align-justify:before {
61
+    content: "\e026";
62
+  }
63
+  .bfi-align-left:before {
64
+    content: "\e027";
65
+  }
66
+  .bfi-align-right:before {
67
+    content: "\e029";
68
+  }
69
+  .bfi-image-right:before {
70
+    content: "\e914";
71
+  }
72
+  .bfi-image-left:before {
73
+    content: "\e91e";
74
+  }
75
+  .bfi-music:before {
76
+    content: "\e90e";
77
+  }
78
+  .bfi-camera:before {
79
+    content: "\e911";
80
+  }
81
+  .bfi-copy:before {
82
+    content: "\e92c";
83
+  }
84
+  .bfi-file-text:before {
85
+    content: "\e926";
86
+  }
87
+  .bfi-film:before {
88
+    content: "\e91c";
89
+  }
90
+  .bfi-github:before {
91
+    content: "\eab0";
92
+  }
93
+  .bfi-ltr:before {
94
+    content: "\ea74";
95
+  }
96
+  .bfi-page-break:before {
97
+    content: "\ea68";
98
+  }
99
+  .bfi-pagebreak:before {
100
+    content: "\ea6e";
101
+  }
102
+  .bfi-paint-format:before {
103
+    content: "\e90c";
104
+  }
105
+  .bfi-paste:before {
106
+    content: "\e92d";
107
+  }
108
+  .bfi-pilcrow:before {
109
+    content: "\ea73";
110
+  }
111
+  .bfi-pushpin:before {
112
+    content: "\e946";
113
+  }
114
+  .bfi-redo:before {
115
+    content: "\e968";
116
+  }
117
+  .bfi-rtl:before {
118
+    content: "\ea75";
119
+  }
120
+  .bfi-spinner:before {
121
+    content: "\e980";
122
+  }
123
+  .bfi-subscript:before {
124
+    content: "\ea6c";
125
+  }
126
+  .bfi-superscript:before {
127
+    content: "\ea6b";
128
+  }
129
+  .bfi-undo:before {
130
+    content: "\e967";
131
+  }
132
+  .bfi-media:before {
133
+    content: "\e90f";
134
+  }
135
+  .bfi-add:before {
136
+    content: "\e918";
137
+  }
138
+  .bfi-bold:before {
139
+    content: "\e904";
140
+  }
141
+  .bfi-code:before {
142
+    content: "\e903";
143
+  }
144
+  .bfi-done:before {
145
+    content: "\e912";
146
+  }
147
+  .bfi-drop-down:before {
148
+    content: "\e906";
149
+  }
150
+  .bfi-drop-up:before {
151
+    content: "\e909";
152
+  }
153
+  .bfi-emoji:before {
154
+    content: "\e91b";
155
+  }
156
+  .bfi-font-size:before {
157
+    content: "\e920";
158
+  }
159
+  .bfi-fullscreen:before {
160
+    content: "\e910";
161
+  }
162
+  .bfi-fullscreen-exit:before {
163
+    content: "\e90d";
164
+  }
165
+  .bfi-help:before {
166
+    content: "\e902";
167
+  }
168
+  .bfi-indent-decrease:before {
169
+    content: "\e92f";
170
+  }
171
+  .bfi-indent-increase:before {
172
+    content: "\e92e";
173
+  }
174
+  .bfi-info:before {
175
+    content: "\e901";
176
+  }
177
+  .bfi-italic:before {
178
+    content: "\e924";
179
+  }
180
+  .bfi-link:before {
181
+    content: "\e91a";
182
+  }
183
+  .bfi-link-off:before {
184
+    content: "\e919";
185
+  }
186
+  .bfi-list:before {
187
+    content: "\e923";
188
+  }
189
+  .bfi-list-numbered:before {
190
+    content: "\e922";
191
+  }
192
+  .bfi-menu:before {
193
+    content: "\e908";
194
+  }
195
+  .bfi-more-horiz:before {
196
+    content: "\e90b";
197
+  }
198
+  .bfi-more-vert:before {
199
+    content: "\e90a";
200
+  }
201
+  .bfi-not-disturb:before {
202
+    content: "\e907";
203
+  }
204
+  .bfi-print:before {
205
+    content: "\e915";
206
+  }
207
+  .bfi-quote:before {
208
+    content: "\e921";
209
+  }
210
+  .bfi-search:before {
211
+    content: "\e916";
212
+  }
213
+  .bfi-settingsx:before {
214
+    content: "\e917";
215
+  }
216
+  .bfi-share:before {
217
+    content: "\e905";
218
+  }
219
+  .bfi-share-square:before {
220
+    content: "\e900";
221
+  }
222
+  .bfi-strikethrough:before {
223
+    content: "\e91f";
224
+  }
225
+  .bfi-text-color .path1:before {
226
+    content: "\e930";
227
+    opacity: 0.36;
228
+  }
229
+  .bfi-text-color .path2:before {
230
+    content: "\e931";
231
+    margin-left: -1em;
232
+  }
233
+  .bfi-underlined:before {
234
+    content: "\e91d";
235
+  }
236
+}

+ 1
- 0
source/braft-editor/src/assets/scss/_inc.scss View File

@@ -0,0 +1 @@
1
+@import 'config';

+ 341
- 0
source/braft-editor/src/components/business/ControlBar/index.jsx View File

@@ -0,0 +1,341 @@
1
+import './style.scss'
2
+import React from 'react'
3
+import getEditorControls from 'configs/controls'
4
+import LinkEditor from 'components/business/LinkEditor'
5
+import HeadingPicker from 'components/business/Headings'
6
+import TextColorPicker from 'components/business/TextColor'
7
+import FontSizePicker from 'components/business/FontSize'
8
+import LineHeightPicker from 'components/business/LineHeight'
9
+import FontFamilyPicker from 'components/business/FontFamily'
10
+import TextAlign from 'components/business/TextAlign'
11
+import EmojiPicker from 'components/business/EmojiPicker'
12
+import LetterSpacingPicker from 'components/business/LetterSpacing'
13
+import TextIndentPicker from 'components/business/TextIndent'
14
+import DropDown from 'components/common/DropDown'
15
+import { ContentUtils } from 'braft-utils'
16
+import { showModal } from 'components/common/Modal'
17
+
18
+const commandHookMap = {
19
+  'inline-style': 'toggle-inline-style',
20
+  'block-type': 'change-block-type',
21
+  'editor-method': 'exec-editor-command'
22
+}
23
+
24
+export default class ControlBar extends React.Component {
25
+
26
+  mediaLibiraryModal = null
27
+  extendedModals = {}
28
+
29
+  componentDidUpdate () {
30
+
31
+    const { controls, language } = this.props
32
+
33
+    controls.forEach(item => {
34
+      if (item.type === 'modal') {
35
+        if (item.modal && item.modal.id && this.extendedModals[item.modal.id]) {
36
+          this.extendedModals[item.modal.id].update({ ...item.modal, language })
37
+        }
38
+      }
39
+    })
40
+
41
+  }
42
+
43
+  getControlItemClassName (data) {
44
+
45
+    let className = 'control-item button'
46
+    let { type, command } = data
47
+
48
+    if (type === 'inline-style' && ContentUtils.selectionHasInlineStyle(this.props.editorState, command)) {
49
+      className += ' active'
50
+    } else if (type === 'block-type' && ContentUtils.getSelectionBlockType(this.props.editorState) === command) {
51
+      className += ' active'
52
+    }
53
+
54
+    return className
55
+
56
+  }
57
+
58
+  applyControl (command, type) {
59
+
60
+    const hookReturns = this.props.hooks(commandHookMap[type] || type, command)(command)
61
+
62
+    if (hookReturns === false) {
63
+      return false
64
+    }
65
+
66
+    if (typeof hookReturns === 'string') {
67
+      command = hookReturns
68
+    }
69
+
70
+    if (type === 'inline-style') {
71
+      this.props.editor.setValue(ContentUtils.toggleSelectionInlineStyle(this.props.editorState, command))
72
+    } else if (type === 'block-type') {
73
+      this.props.editor.setValue(ContentUtils.toggleSelectionBlockType(this.props.editorState, command))
74
+    } else if (type === 'editor-method') {
75
+      this.props.editor[command] && this.props.editor[command]()
76
+    }
77
+
78
+    this.props.editor.requestFocus()
79
+
80
+  }
81
+
82
+  openBraftFinder = () => {
83
+
84
+    if (!this.props.braftFinder || !this.props.braftFinder.ReactComponent) {
85
+      return false
86
+    }
87
+
88
+    if (this.props.hooks('open-braft-finder')() === false) {
89
+      return false
90
+    }
91
+
92
+    const mediaProps = this.props.media
93
+    const MediaLibrary = this.props.braftFinder.ReactComponent
94
+
95
+    this.mediaLibiraryModal = showModal({
96
+      title: this.props.language.controls.mediaLibirary,
97
+      language: this.props.language,
98
+      width: 640,
99
+      showFooter: false,
100
+      children: (
101
+        <MediaLibrary
102
+          onCancel={this.closeBraftFinder}
103
+          onInsert={this.insertMedias}
104
+          onChange={mediaProps.onChange}
105
+          externals={mediaProps.externals}
106
+          onBeforeSelect={this.bindBraftFinderHook('select-medias')}
107
+          onBeforeDeselect={this.bindBraftFinderHook('deselect-medias')}
108
+          onBeforeRemove={this.bindBraftFinderHook('remove-medias')}
109
+          onBeforeInsert={this.bindBraftFinderHook('insert-medias')}
110
+          onFileSelect={this.bindBraftFinderHook('select-files')}
111
+        />
112
+      )
113
+    })
114
+
115
+  }
116
+
117
+  bindBraftFinderHook = (hookName) => (...params) => {
118
+    return this.props.hooks(hookName, params[0])(...params)
119
+  }
120
+
121
+  insertMedias = (medias) => {
122
+
123
+    this.props.editor.setValue(ContentUtils.insertMedias(this.props.editorState, medias))
124
+    this.props.editor.requestFocus()
125
+    this.props.media.onInsert && this.props.media.onInsert(medias)
126
+    this.closeBraftFinder()
127
+
128
+  }
129
+
130
+  closeBraftFinder = () => {
131
+
132
+    this.props.media.onCancel && this.props.media.onCancel()
133
+    this.mediaLibiraryModal && this.mediaLibiraryModal.close()
134
+
135
+  }
136
+
137
+  render() {
138
+
139
+    const { editor, editorState, controls, media, extendControls, language, hooks, colors, fontSizes, fontFamilies, emojis, containerNode, lineHeights, letterSpacings, textAligns, textBackgroundColor, textIndents} = this.props
140
+    const currentBlockType = ContentUtils.getSelectionBlockType(editorState)
141
+    const editorControls = getEditorControls(language)
142
+    const commonProps = { editor, editorState, language, containerNode, hooks }
143
+    const renderedControls = []
144
+
145
+    return (
146
+      <div className='bf-controlbar'>
147
+        {
148
+          [
149
+            ...controls,
150
+            ...extendControls
151
+          ].map((item, index) => {
152
+            let itemKey = typeof item === 'string' ? item : item.key
153
+            if (typeof itemKey !== 'string') {
154
+              return null
155
+            }
156
+            if (renderedControls.indexOf(itemKey) > -1) {
157
+              return null
158
+            }
159
+            if (itemKey.toLowerCase() === 'separator') {
160
+              return <span key={index} className='separator-line'></span>
161
+            }
162
+            let controlItem = editorControls.find((subItem) => {
163
+              return subItem.key.toLowerCase() === itemKey.toLowerCase()
164
+            })
165
+            if (typeof item !== 'string') {
166
+              controlItem = { ...controlItem, ...item }
167
+            }
168
+            if (!controlItem) {
169
+              return null
170
+            }
171
+            renderedControls.push(itemKey)
172
+            if (controlItem.type === 'headings') {
173
+              return <HeadingPicker
174
+                key={index}
175
+                current={currentBlockType}
176
+                onChange={(command) => this.applyControl(command, 'block-type')}
177
+                {...commonProps}
178
+              />
179
+            } else if (controlItem.type === 'text-color') {
180
+              return <TextColorPicker
181
+                key={index}
182
+                colors={colors}
183
+                enableBackgroundColor={textBackgroundColor}
184
+                {...commonProps}
185
+              />
186
+            } else if (controlItem.type === 'font-size') {
187
+              return <FontSizePicker
188
+                key={index}
189
+                fontSizes={fontSizes}
190
+                defaultCaption={controlItem.title}
191
+                {...commonProps}
192
+              />
193
+            } else if (controlItem.type === 'line-height') {
194
+              return <LineHeightPicker
195
+                key={index}
196
+                lineHeights={lineHeights}
197
+                defaultCaption={controlItem.title}
198
+                {...commonProps}
199
+              />
200
+            } else if (controlItem.type === 'letter-spacing') {
201
+              return <LetterSpacingPicker
202
+                key={index}
203
+                letterSpacings={letterSpacings}
204
+                defaultCaption={controlItem.title}
205
+                {...commonProps}
206
+              />
207
+            } else if (controlItem.type === 'text-indent') {
208
+              return <TextIndentPicker
209
+                key={index}
210
+                textIndents={textIndents}
211
+                defaultCaption={controlItem.title}
212
+                {...commonProps}
213
+              />
214
+            } else if (controlItem.type === 'font-family') {
215
+              return <FontFamilyPicker
216
+                key={index}
217
+                fontFamilies={fontFamilies}
218
+                defaultCaption={controlItem.title}
219
+                {...commonProps}
220
+              />
221
+            } else if (controlItem.type === 'emoji') {
222
+              return <EmojiPicker
223
+                key={index}
224
+                emojis={emojis}
225
+                defaultCaption={controlItem.text}
226
+                {...commonProps}
227
+              />
228
+            } else if (controlItem.type === 'link') {
229
+              return <LinkEditor
230
+                key={index}
231
+                {...commonProps}
232
+              />
233
+            } else if (controlItem.type === 'text-align') {
234
+              return (
235
+                <TextAlign
236
+                  key={index}
237
+                  textAligns={textAligns}
238
+                  {...commonProps}
239
+                />
240
+              )
241
+            } else if (controlItem.type === 'media') {
242
+              if (!media.image && !media.video && !media.audio) {
243
+                return null
244
+              }
245
+              return (
246
+                <button
247
+                  type='button'
248
+                  key={index}
249
+                  data-title={controlItem.title}
250
+                  className='control-item media button'
251
+                  onClick={this.openBraftFinder}
252
+                >
253
+                  {controlItem.text}
254
+                </button>
255
+              )
256
+            } else if (controlItem.type === 'dropdown') {
257
+              return (
258
+                <DropDown
259
+                  key={index}
260
+                  className={'control-item extend-control-item dropdown ' + controlItem.className}
261
+                  caption={controlItem.text}
262
+                  htmlCaption={controlItem.html}
263
+                  showArrow={controlItem.showArrow}
264
+                  containerNode={controlItem.containerNode}
265
+                  title={controlItem.title}
266
+                  arrowActive={controlItem.arrowActive}
267
+                  autoHide={controlItem.autoHide}
268
+                  disabled={controlItem.disabled}
269
+                  ref={controlItem.ref}
270
+                >
271
+                  {controlItem.component}
272
+                </DropDown>
273
+              )
274
+            } else if (controlItem.type === 'modal') {
275
+              return (
276
+                <button
277
+                  type='button'
278
+                  key={index}
279
+                  data-title={controlItem.title}
280
+                  className={'control-item extend-control-item button ' + controlItem.className}
281
+                  dangerouslySetInnerHTML={controlItem.html ? { __html: controlItem.html } : null}
282
+                  onClick={(event) => {
283
+                    if (controlItem.modal && controlItem.modal.id) {
284
+                      if (this.extendedModals[controlItem.modal.id]) {
285
+                        this.extendedModals[controlItem.modal.id].active = true
286
+                        this.extendedModals[controlItem.modal.id].update({ ...controlItem.modal, language })
287
+                      } else {
288
+                        this.extendedModals[controlItem.modal.id] = showModal({ ...controlItem.modal, language })
289
+                        controlItem.modal.onCreate && controlItem.modal.onCreate(this.extendedModals[controlItem.modal.id])
290
+                      }
291
+                    }
292
+                    controlItem.onClick && controlItem.onClick(event)
293
+                  }}
294
+                >
295
+                  {!controlItem.html ? controlItem.text : null}
296
+                </button>
297
+              )
298
+            } else if (controlItem.type === 'component') {
299
+              return (
300
+                <div
301
+                  key={index}
302
+                  className={'control-item component-wrapper ' + controlItem.className}
303
+                >{controlItem.component}</div>
304
+              )
305
+            } else if (controlItem.type === 'button') {
306
+              return (
307
+                <button
308
+                  type='button'
309
+                  key={index}
310
+                  data-title={controlItem.title}
311
+                  className={'control-item button ' + controlItem.className}
312
+                  dangerouslySetInnerHTML={controlItem.html ? { __html: controlItem.html } : null}
313
+                  onClick={(event) => controlItem.onClick && controlItem.onClick(event)}
314
+                >
315
+                  {!controlItem.html ? controlItem.text : null}
316
+                </button>
317
+              )
318
+            } else {
319
+              return (
320
+                <button
321
+                  type='button'
322
+                  key={index}
323
+                  data-title={controlItem.title}
324
+                  className={this.getControlItemClassName({
325
+                    type: controlItem.type,
326
+                    command: controlItem.command
327
+                  })}
328
+                  onClick={() => this.applyControl(controlItem.command, controlItem.type)}
329
+                >
330
+                  {controlItem.text}
331
+                </button>
332
+              )
333
+            }
334
+          })
335
+        }
336
+      </div>
337
+    )
338
+
339
+  }
340
+
341
+}

+ 151
- 0
source/braft-editor/src/components/business/ControlBar/style.scss View File

@@ -0,0 +1,151 @@
1
+@import '~scssinc';
2
+
3
+.bf-controlbar{
4
+  margin: 0;
5
+  padding: 0 5px;
6
+  box-shadow: inset 0 -1px 0 0 rgba(#000, .2);
7
+  &::after{
8
+    display: block;
9
+    content: '';
10
+    clear: both;
11
+  }
12
+
13
+  button{
14
+    padding: 0;
15
+    outline: none;
16
+    &[disabled]{
17
+      pointer-events: none;
18
+      opacity: .3;
19
+    }
20
+  }
21
+
22
+  [data-title] {
23
+    position: relative;
24
+    &::before,
25
+    &::after{
26
+      position: absolute;
27
+      z-index: 10;
28
+      top: 100%;
29
+      left: 50%;
30
+      pointer-events: none;
31
+      opacity: 0;
32
+      transform: translateX(-50%) translateY(-5px);
33
+      transition: opacity .3s, transform .3s;
34
+    }
35
+    &::before{
36
+      margin-top: 3px;
37
+      border: 5px solid transparent;
38
+      border-bottom-color: $COLOR_DARK;
39
+      content: '';
40
+    }
41
+    &::after{
42
+      margin-top: 12px;
43
+      padding: 5px;
44
+      background-color: $COLOR_DARK;
45
+      border-radius: 2px;
46
+      box-shadow: 0 5px 15px rgba(#000, .2);
47
+      color: #fff;
48
+      font-size: 12px;
49
+      line-height: 16px;
50
+      white-space: nowrap;
51
+      content: attr(data-title);
52
+    }
53
+    &:hover{
54
+      &::before,
55
+      &::after{
56
+        opacity: 1;
57
+        transform: translateX(-50%) translateY(0);
58
+      }
59
+    }
60
+  }
61
+
62
+  input {
63
+    outline: none;
64
+  }
65
+
66
+  .separator-line{
67
+    display: block;
68
+    float: left;
69
+    height: 26px;
70
+    width: 1px;
71
+    margin: 10px;
72
+    box-shadow: inset -1px 0 0 0 rgba(#000, .1);
73
+    &+.control-item-group,
74
+    &+.control-item{
75
+      margin-left: 0;
76
+    }
77
+    &+.separator-line,
78
+    &.last-child,
79
+    &.first-child{
80
+      display: none;
81
+    }
82
+  }
83
+
84
+  .control-item-group{
85
+    float: left;
86
+    height: 36px;
87
+    margin: 5px 0 5px 3px;
88
+    &:first-child{
89
+      margin-left: 0;
90
+    }
91
+    & > .control-item{
92
+      margin-top: 0;
93
+      margin-bottom: 0;
94
+    }
95
+  }
96
+
97
+  .dropdown-handler{
98
+    border-radius: 2px;
99
+  }
100
+
101
+  .control-item{
102
+    display: block;
103
+    float: left;
104
+    height: 36px;
105
+    margin: 5px 0 5px 3px;
106
+    border-radius: 2px;
107
+    cursor: pointer;
108
+    &.component-wrapper{
109
+      cursor: default;
110
+    }
111
+    &:first-child{
112
+      margin-left: 0;
113
+    }
114
+    &.button{
115
+      box-sizing: border-box;
116
+      min-width: 36px;
117
+      padding: 0 8px;
118
+      background-color: transparent;
119
+      border: none;
120
+      color: $COLOR_GRAY;
121
+      font-size: 14px;
122
+      &:hover{
123
+        background-color: rgba(#000, .05);
124
+      }
125
+      &.active{
126
+        color: $COLOR_ACTIVE;
127
+      }
128
+      i::before{
129
+        display: block;
130
+        height: 36px;
131
+        font-size: 18px;
132
+        line-height: 36px;
133
+      }
134
+      .bfi-undo,
135
+      .bfi-redo{
136
+        &::before{
137
+          font-size: 14px;
138
+        }
139
+      }
140
+    }
141
+  }
142
+
143
+  .dropdown{
144
+    .control-item{
145
+      width: 100%;
146
+      float: none;
147
+      margin: 0;
148
+    }
149
+  }
150
+
151
+}

+ 50
- 0
source/braft-editor/src/components/business/EmojiPicker/index.jsx View File

@@ -0,0 +1,50 @@
1
+import './style.scss'
2
+import React from 'react'
3
+import DropDown from 'components/common/DropDown'
4
+import { ContentUtils } from 'braft-utils'
5
+
6
+const insertEmoji = (event, props) => {
7
+
8
+  let emoji = event.currentTarget.dataset.emoji
9
+  const hookReturns = props.hooks('insert-emoji', emoji)(emoji)
10
+
11
+  if (hookReturns === false) {
12
+    return false
13
+  }
14
+
15
+  if (typeof hookReturns === 'string') {
16
+    emoji = hookReturns
17
+  }
18
+
19
+  props.editor.setValue(ContentUtils.insertText(props.editorState, emoji))
20
+  props.editor.requestFocus()
21
+
22
+}
23
+
24
+export default (props) => {
25
+
26
+  return (
27
+    <DropDown
28
+      caption={props.defaultCaption}
29
+      showArrow={false}
30
+      containerNode={props.containerNode}
31
+      title={props.language.controls.emoji}
32
+      className={'control-item dropdown bf-emoji-dropdown'}
33
+    >
34
+      <div className='bf-emojis-wrap'>
35
+        <ul className='bf-emojis'>
36
+          {props.emojis.map((item, index) => {
37
+            return (
38
+              <li
39
+                key={index}
40
+                data-emoji={item}
41
+                onClick={(event) => insertEmoji(event, props)}
42
+              >{item}</li>
43
+            )
44
+          })}
45
+        </ul>
46
+      </div>
47
+    </DropDown>
48
+  )
49
+
50
+}

+ 56
- 0
source/braft-editor/src/components/business/EmojiPicker/style.scss View File

@@ -0,0 +1,56 @@
1
+@import '~scssinc';
2
+
3
+.bf-emojis-wrap{
4
+  position: relative;
5
+  width: 210px;
6
+  height: 220px;
7
+  overflow: hidden;
8
+  &::before,
9
+  &::after{
10
+    position: absolute;
11
+    z-index: 1;
12
+    right: 0;
13
+    left: 0;
14
+    height: 30px;
15
+    border-radius: 2px;
16
+    content: '';
17
+    pointer-events: none;
18
+  }
19
+  &::before{
20
+    top: 0;
21
+    background-image: linear-gradient(to top, rgba($COLOR_DARK, 0), rgba($COLOR_DARK, 1));
22
+  }
23
+  &::after{
24
+    bottom: 0;
25
+    background-image: linear-gradient(rgba($COLOR_DARK, 0), rgba($COLOR_DARK, 1));
26
+  }
27
+}
28
+.bf-emojis{
29
+  box-sizing: content-box;
30
+  width: 200px;
31
+  height: 195px;
32
+  list-style: none;
33
+  margin: 0;
34
+  padding: 15px 15px 20px 15px;
35
+  overflow: auto;
36
+  li{
37
+    display: block;
38
+    float: left;
39
+    width: 30px;
40
+    height: 30px;
41
+    margin: 0;
42
+    padding: 0;
43
+    color: #fff;
44
+    border-radius: 2px;
45
+    font-family: "Apple Color Emoji", "Segoe UI", "Segoe UI Emoji", "Segoe UI Symbol";
46
+    font-size: 18px;
47
+    line-height: 32px;
48
+    text-align: center;
49
+    cursor: pointer;
50
+    user-select: none;
51
+    transition: transform .2s;
52
+    &:hover{
53
+      transform: scale(1.5);
54
+    }
55
+  }
56
+}

+ 63
- 0
source/braft-editor/src/components/business/FontFamily/index.jsx View File

@@ -0,0 +1,63 @@
1
+import './style.scss'
2
+import React from 'react'
3
+import DropDown from 'components/common/DropDown'
4
+import { ContentUtils } from 'braft-utils'
5
+
6
+const toggleFontFamily = (event, props) => {
7
+
8
+  let fontFamilyName = event.currentTarget.dataset.name
9
+  const hookReturns = props.hooks('toggle-font-family', fontFamilyName)(fontFamilyName, props.fontFamilies)
10
+
11
+  if (hookReturns === false) {
12
+    return false
13
+  }
14
+
15
+  if (typeof hookReturns === 'string') {
16
+    fontFamilyName = hookReturns
17
+  }
18
+
19
+  props.editor.setValue(ContentUtils.toggleSelectionFontFamily(props.editorState, fontFamilyName, props.fontFamilies))
20
+  props.editor.requestFocus()
21
+
22
+}
23
+
24
+export default (props) => {
25
+
26
+  let caption = null
27
+  let currentIndex = null
28
+
29
+  props.fontFamilies.find((item, index) => {
30
+    if (ContentUtils.selectionHasInlineStyle(props.editorState, 'FONTFAMILY-' + item.name)) {
31
+      caption = item.name
32
+      currentIndex = index
33
+      return true
34
+    }
35
+    return false
36
+  })
37
+
38
+  return (
39
+    <DropDown
40
+      caption={caption || props.defaultCaption}
41
+      containerNode={props.containerNode}
42
+      title={props.language.controls.fontFamily}
43
+      arrowActive={currentIndex === 0}
44
+      className={'control-item dropdown font-family-dropdown'}
45
+    >
46
+      <ul className='menu'>
47
+        {props.fontFamilies.map((item, index) => {
48
+          return (
49
+            <li
50
+              key={index}
51
+              className={'menu-item ' + (index === currentIndex ? 'active' : '')}
52
+              data-name={item.name}
53
+              onClick={(event) => toggleFontFamily(event, props)}
54
+            >
55
+              <span style={{fontFamily: item.family}}>{item.name}</span>
56
+            </li>
57
+          )
58
+        })}
59
+      </ul>
60
+    </DropDown>
61
+  )
62
+
63
+}

+ 9
- 0
source/braft-editor/src/components/business/FontFamily/style.scss View File

@@ -0,0 +1,9 @@
1
+.font-family-dropdown{
2
+  min-width: 120px;
3
+  .dropdown-content{
4
+    width: 180px;
5
+  }
6
+  .menu-item{
7
+    padding: 12px 15px;
8
+  }
9
+}

+ 60
- 0
source/braft-editor/src/components/business/FontSize/index.jsx View File

@@ -0,0 +1,60 @@
1
+import './style.scss'
2
+import React from 'react'
3
+import DropDown from 'components/common/DropDown'
4
+import { ContentUtils } from 'braft-utils'
5
+
6
+const toggleFontSize = (event, props) => {
7
+
8
+  let fontSize = event.currentTarget.dataset.size
9
+  const hookReturns = props.hooks('toggle-font-size', fontSize)(fontSize)
10
+
11
+  if (hookReturns === false) {
12
+    return false
13
+  }
14
+
15
+  if (!isNaN(fontSize)) {
16
+    fontSize = hookReturns
17
+  }
18
+
19
+  props.editor.setValue(ContentUtils.toggleSelectionFontSize(props.editorState, fontSize, props.fontSizes))
20
+  props.editor.requestFocus()
21
+
22
+}
23
+
24
+export default (props) => {
25
+
26
+  let caption = null
27
+  let currentFontSize = null
28
+
29
+  props.fontSizes.find((item) => {
30
+    if (ContentUtils.selectionHasInlineStyle(props.editorState, 'FONTSIZE-' + item)) {
31
+      caption = item + 'px'
32
+      currentFontSize = item
33
+      return true
34
+    }
35
+    return false
36
+  })
37
+
38
+  return (
39
+    <DropDown
40
+      caption={caption || props.defaultCaption}
41
+      containerNode={props.containerNode}
42
+      title={props.language.controls.fontSize}
43
+      className={'control-item dropdown bf-font-size-dropdown'}
44
+    >
45
+      <ul className='bf-font-sizes'>
46
+        {props.fontSizes.map((item, index) => {
47
+          return (
48
+            <li
49
+              key={index}
50
+              className={item === currentFontSize ? 'active' : null}
51
+              data-size={item}
52
+              onClick={(event) => toggleFontSize(event, props)}
53
+            >{item + 'px'}</li>
54
+          )
55
+        })}
56
+      </ul>
57
+    </DropDown>
58
+  )
59
+
60
+}

+ 34
- 0
source/braft-editor/src/components/business/FontSize/style.scss View File

@@ -0,0 +1,34 @@
1
+@import '~scssinc';
2
+
3
+.bf-font-size-dropdown{
4
+  min-width: 95px;
5
+}
6
+.bf-font-sizes{
7
+  box-sizing: content-box;
8
+  width: 210px;
9
+  list-style: none;
10
+  margin: 0;
11
+  padding: 5px;
12
+  overflow: hidden;
13
+  li{
14
+    display: block;
15
+    float: left;
16
+    width: 60px;
17
+    height: 30px;
18
+    background-color: rgba(#fff, .1);
19
+    border-radius: 2px;
20
+    margin: 5px;
21
+    color: #fff;
22
+    font-size: 12px;
23
+    line-height: 30px;
24
+    text-align: center;
25
+    text-transform: uppercase;
26
+    cursor: pointer;
27
+    &:hover{
28
+      background-color: rgba(#fff, .2);
29
+    }
30
+    &.active{
31
+      background-color: $COLOR_ACTIVE;
32
+    }
33
+  }
34
+}

+ 39
- 0
source/braft-editor/src/components/business/Headings/index.jsx View File

@@ -0,0 +1,39 @@
1
+import './style.scss'
2
+import React from 'react'
3
+import { getHeadings } from 'configs/maps'
4
+import DropDown from 'components/common/DropDown'
5
+
6
+export default (props) => {
7
+
8
+  const headings = getHeadings(props.language)
9
+  const currentHeadingIndex = headings.findIndex((item) => item.command === props.current)
10
+  const caption = headings[currentHeadingIndex] ? headings[currentHeadingIndex].title : props.language.controls.normal
11
+
12
+  return (
13
+    <DropDown
14
+      caption={caption}
15
+      containerNode={props.containerNode}
16
+      title={props.language.controls.headings}
17
+      arrowActive={currentHeadingIndex === 0}
18
+      className={'control-item dropdown headings-dropdown'}
19
+    >
20
+      <ul className='menu'>
21
+        {
22
+          headings.map((item, index) => {
23
+            let isActive = props.current === item.command
24
+            return (
25
+              <li
26
+                key={index}
27
+                className={'menu-item' + (isActive ? ' active' : '')}
28
+                onClick={() => props.onChange(item.command, item.type)}
29
+              >
30
+                {item.text}
31
+              </li>
32
+            )
33
+          })
34
+        }
35
+      </ul>
36
+    </DropDown>
37
+  )
38
+
39
+}

+ 35
- 0
source/braft-editor/src/components/business/Headings/style.scss View File

@@ -0,0 +1,35 @@
1
+.headings-dropdown{
2
+  min-width: 110px;
3
+  .menu{
4
+    width: 200px;
5
+    overflow: hidden;
6
+    .menu-item {
7
+      padding: 15px 20px;
8
+      text-align: left;
9
+      line-height: initial;
10
+      h1,h2,h3,h4,h5,h6{
11
+        margin: 0;
12
+        padding: 0;
13
+        color: inherit;
14
+      }
15
+      h1{
16
+        font-size: 28px;
17
+      }
18
+      h2{
19
+        font-size: 24px;
20
+      }
21
+      h3{
22
+        font-size: 20px;
23
+      }
24
+      h4{
25
+        font-size: 16px;
26
+      }
27
+      h5{
28
+        font-size: 14px;
29
+      }
30
+      h6{
31
+        font-size: 12px;
32
+      }
33
+    }
34
+  }
35
+}

+ 60
- 0
source/braft-editor/src/components/business/LetterSpacing/index.jsx View File

@@ -0,0 +1,60 @@
1
+import './style.scss'
2
+import React from 'react'
3
+import DropDown from 'components/common/DropDown'
4
+import { ContentUtils } from 'braft-utils'
5
+
6
+const toggleLetterSpacing = (event, props) => {
7
+
8
+  let letterSpacing = event.currentTarget.dataset.size
9
+  const hookReturns = props.hooks('toggle-letter-spacing', letterSpacing)(letterSpacing)
10
+
11
+  if (hookReturns === false) {
12
+    return false
13
+  }
14
+
15
+  if (!isNaN(hookReturns)) {
16
+    letterSpacing = hookReturns
17
+  }
18
+
19
+  props.editor.setValue(ContentUtils.toggleSelectionLetterSpacing(props.editorState, letterSpacing, props.letterSpacings))
20
+  props.editor.requestFocus()
21
+
22
+}
23
+
24
+export default (props) => {
25
+
26
+  let caption = null
27
+  let currentLetterSpacing = null
28
+
29
+  props.letterSpacings.find((item) => {
30
+    if (ContentUtils.selectionHasInlineStyle(props.editorState, 'LETTERSPACING-' + item)) {
31
+      caption = item
32
+      currentLetterSpacing = item
33
+      return true
34
+    }
35
+    return false
36
+  })
37
+
38
+  return (
39
+    <DropDown
40
+      caption={caption || props.defaultCaption}
41
+      containerNode={props.containerNode}
42
+      title={props.language.controls.letterSpacing}
43
+      className={'control-item dropdown bf-letter-spacing-dropdown'}
44
+    >
45
+      <ul className='bf-letter-spacings'>
46
+        {props.letterSpacings.map((item, index) => {
47
+          return (
48
+            <li
49
+              key={index}
50
+              className={item === currentLetterSpacing ? 'active' : null}
51
+              data-size={item}
52
+              onClick={(event) => toggleLetterSpacing(event, props)}
53
+            >{item}</li>
54
+          )
55
+        })}
56
+      </ul>
57
+    </DropDown>
58
+  )
59
+
60
+}

+ 34
- 0
source/braft-editor/src/components/business/LetterSpacing/style.scss View File

@@ -0,0 +1,34 @@
1
+@import '~scssinc';
2
+
3
+.bf-letter-spacing-dropdown{
4
+  min-width: 95px;
5
+}
6
+.bf-letter-spacings{
7
+  box-sizing: content-box;
8
+  width: 210px;
9
+  list-style: none;
10
+  margin: 0;
11
+  padding: 5px;
12
+  overflow: hidden;
13
+  li{
14
+    display: block;
15
+    float: left;
16
+    width: 60px;
17
+    height: 30px;
18
+    background-color: rgba(#fff, .1);
19
+    border-radius: 2px;
20
+    margin: 5px;
21
+    color: #fff;
22
+    font-size: 12px;
23
+    line-height: 30px;
24
+    text-align: center;
25
+    text-transform: uppercase;
26
+    cursor: pointer;
27
+    &:hover{
28
+      background-color: rgba(#fff, .2);
29
+    }
30
+    &.active{
31
+      background-color: $COLOR_ACTIVE;
32
+    }
33
+  }
34
+}

+ 60
- 0
source/braft-editor/src/components/business/LineHeight/index.jsx View File

@@ -0,0 +1,60 @@
1
+import './style.scss'
2
+import React from 'react'
3
+import DropDown from 'components/common/DropDown'
4
+import { ContentUtils } from 'braft-utils'
5
+
6
+const toggleLineHeight = (event, props) => {
7
+
8
+  let lineHeight = event.currentTarget.dataset.size
9
+  const hookReturns = props.hooks('toggle-line-height', lineHeight)(lineHeight)
10
+
11
+  if (hookReturns === false) {
12
+    return false
13
+  }
14
+
15
+  if (!isNaN(hookReturns)) {
16
+    lineHeight = hookReturns
17
+  }
18
+
19
+  props.editor.setValue(ContentUtils.toggleSelectionLineHeight(props.editorState, lineHeight, props.lineHeights))
20
+  props.editor.requestFocus()
21
+
22
+}
23
+
24
+export default (props) => {
25
+
26
+  let caption = null
27
+  let currentLineHeight = null
28
+
29
+  props.lineHeights.find((item) => {
30
+    if (ContentUtils.selectionHasInlineStyle(props.editorState, 'LINEHEIGHT-' + item)) {
31
+      caption = item
32
+      currentLineHeight = item
33
+      return true
34
+    }
35
+    return false
36
+  })
37
+
38
+  return (
39
+    <DropDown
40
+      caption={caption || props.defaultCaption}
41
+      containerNode={props.containerNode}
42
+      title={props.language.controls.lineHeight}
43
+      className={'control-item dropdown bf-line-height-dropdown'}
44
+    >
45
+      <ul className='bf-line-heights'>
46
+        {props.lineHeights.map((item, index) => {
47
+          return (
48
+            <li
49
+              key={index}
50
+              className={item === currentLineHeight ? 'active' : null}
51
+              data-size={item}
52
+              onClick={(event) => toggleLineHeight(event, props)}
53
+            >{item}</li>
54
+          )
55
+        })}
56
+      </ul>
57
+    </DropDown>
58
+  )
59
+
60
+}

+ 34
- 0
source/braft-editor/src/components/business/LineHeight/style.scss View File

@@ -0,0 +1,34 @@
1
+@import '~scssinc';
2
+
3
+.bf-line-height-dropdown{
4
+  min-width: 95px;
5
+}
6
+.bf-line-heights{
7
+  box-sizing: content-box;
8
+  width: 210px;
9
+  list-style: none;
10
+  margin: 0;
11
+  padding: 5px;
12
+  overflow: hidden;
13
+  li{
14
+    display: block;
15
+    float: left;
16
+    width: 60px;
17
+    height: 30px;
18
+    background-color: rgba(#fff, .1);
19
+    border-radius: 2px;
20
+    margin: 5px;
21
+    color: #fff;
22
+    font-size: 12px;
23
+    line-height: 30px;
24
+    text-align: center;
25
+    text-transform: uppercase;
26
+    cursor: pointer;
27
+    &:hover{
28
+      background-color: rgba(#fff, .2);
29
+    }
30
+    &.active{
31
+      background-color: $COLOR_ACTIVE;
32
+    }
33
+  }
34
+}

+ 136
- 0
source/braft-editor/src/components/business/LinkEditor/index.jsx View File

@@ -0,0 +1,136 @@
1
+import './style.scss'
2
+import React from 'react'
3
+import Switch from 'components/common/Switch'
4
+import DropDown from 'components/common/DropDown'
5
+import { ContentUtils } from 'braft-utils'
6
+
7
+export default class LinkEditor extends React.Component {
8
+
9
+  state = {
10
+    href: '',
11
+    target: ''
12
+  }
13
+
14
+  dropDownComponent = null
15
+
16
+  componentWillReceiveProps (next) {
17
+
18
+    const { href, target } = ContentUtils.getSelectionEntityData(next.editorState, 'LINK')
19
+    this.setState({
20
+      href: href || '',
21
+      target: target || ''
22
+    })
23
+
24
+  }
25
+
26
+  render () {
27
+
28
+    const { href, target } = this.state
29
+    const caption = <i className='bfi-link'></i>
30
+    const textSelected = !ContentUtils.isSelectionCollapsed(this.props.editorState) && ContentUtils.getSelectionBlockType(this.props.editorState) !== 'atomic'
31
+
32
+    return (
33
+      <div className='control-item-group'>
34
+        <DropDown
35
+          caption={caption}
36
+          title={this.props.language.controls.link}
37
+          autoHide={false}
38
+          containerNode={this.props.containerNode}
39
+          showArrow={false}
40
+          disabled={!textSelected}
41
+          ref={(instance) => this.dropDownComponent = instance}
42
+          className={'control-item dropdown link-editor-dropdown'}
43
+        >
44
+          <div className='bf-link-editor'>
45
+            <div className='input-group'>
46
+              <input
47
+                type='text'
48
+                value={href}
49
+                spellCheck={false}
50
+                placeholder={this.props.language.linkEditor.inputPlaceHolder}
51
+                onKeyDown={this.handeKeyDown}
52
+                onChange={this.inputLink}
53
+              />
54
+            </div>
55
+            <div className='switch-group'>
56
+              <Switch
57
+                active={target === '_blank'}
58
+                onClick={this.setTarget}
59
+              />
60
+              <label>{this.props.language.linkEditor.openInNewWindow}</label>
61
+            </div>
62
+            <div className='buttons'>
63
+              <a onClick={this.handleUnlink} className='primary pull-left' href='javascript:void(0);'>
64
+                <i className='bfi-close'></i>
65
+                <span>{this.props.language.linkEditor.removeLink}</span>
66
+              </a>
67
+              <button type='button' onClick={this.handleConfirm} className='primary pull-right'>{this.props.language.base.confirm}</button>
68
+              <button type='button' onClick={this.handleCancel} className='default pull-right'>{this.props.language.base.cancel}</button>
69
+            </div>
70
+          </div>
71
+        </DropDown>
72
+        <button
73
+          type='button'
74
+          data-title={this.props.language.controls.unlink}
75
+          className='control-item button'
76
+          onClick={this.handleUnlink}
77
+          disabled={!textSelected || !href}
78
+        >
79
+          <i className='bfi-link-off'></i>
80
+        </button>
81
+      </div>
82
+    )
83
+
84
+  }
85
+
86
+  handeKeyDown = (e) => {
87
+    if (e.keyCode === 13) {
88
+      this.handleConfirm()
89
+      e.preventDefault()
90
+      return false
91
+    }
92
+  }
93
+
94
+  inputLink = (e) => {
95
+    this.setState({
96
+      href: e.currentTarget.value
97
+    })
98
+  }
99
+
100
+  setTarget = () => {
101
+    this.setState({
102
+      target: this.state.target === '_blank' ? '' : '_blank'
103
+    })
104
+  }
105
+
106
+  handleCancel = () => {
107
+    this.dropDownComponent.hide()
108
+  }
109
+
110
+  handleUnlink = () => {
111
+    this.dropDownComponent.hide()
112
+    this.props.editor.setValue(ContentUtils.toggleSelectionLink(this.props.editorState, false))
113
+  }
114
+
115
+  handleConfirm = () => {
116
+
117
+    let { href, target } = this.state
118
+    const hookReturns = this.props.hooks('toggle-link', { href, target })({ href, target })
119
+
120
+    this.dropDownComponent.hide()
121
+    this.props.editor.requestFocus()
122
+
123
+    if (hookReturns === false) {
124
+      return false
125
+    }
126
+
127
+    if (hookReturns) {
128
+      typeof hookReturns.href === 'string' && (href = hookReturns.href)
129
+      typeof hookReturns.target === 'string' && (target = hookReturns.target)
130
+    }
131
+
132
+    this.props.editor.setValue(ContentUtils.toggleSelectionLink(this.props.editorState, href, target))
133
+
134
+  }
135
+
136
+}

+ 70
- 0
source/braft-editor/src/components/business/LinkEditor/style.scss View File

@@ -0,0 +1,70 @@
1
+@import '~scssinc';
2
+
3
+.bf-link-editor{
4
+  width: 360px;
5
+  padding-top: 25px;
6
+  .input-group{
7
+    margin: 0 15px;
8
+    input{
9
+      background-color: rgba(#fff, .07);
10
+      border: none;
11
+      border-radius: 2px;
12
+      box-shadow: inset 0 0 0 1px rgba(#fff, .1);
13
+      color: #fff;
14
+      font-weight: bold;
15
+      &:hover{
16
+        box-shadow: inset 0 0 0 1px rgba($COLOR_ACTIVE, .5);
17
+      }
18
+      &:focus{
19
+        box-shadow: inset 0 0 0 1px rgba($COLOR_ACTIVE, 1);
20
+      }
21
+    }
22
+  }
23
+
24
+  .switch-group{
25
+    height: 20px;
26
+    margin: 15px;
27
+    .bf-switch{
28
+      float: left;
29
+    }
30
+    label{
31
+      float: left;
32
+      margin-left: 15px;
33
+      color: #999;
34
+      font-size: 12px;
35
+      line-height: 20px;      
36
+    }
37
+  }
38
+
39
+  .buttons{
40
+    box-sizing: content-box;
41
+    height: 32px;
42
+    margin-top: 20px;
43
+    padding: 15px;
44
+    overflow: hidden;
45
+    box-shadow: inset 0 1px 0 0 rgba(#fff, .1);
46
+    a{
47
+      color: #999;
48
+      font-size: 12px;
49
+      line-height: 32px;
50
+      &:hover{
51
+        color: $COLOR_DANGER;
52
+      }
53
+      i{
54
+        margin-right: 5px;
55
+        font-size: 16px;
56
+      }
57
+      i, span{
58
+        display: block;
59
+        float: left;
60
+        line-height: 32px;
61
+      }
62
+    }
63
+    button{
64
+      margin-left: 10px;
65
+      border-radius: 2px;
66
+      font-weight: bold;
67
+      cursor: pointer;
68
+    }
69
+  }
70
+}

+ 62
- 0
source/braft-editor/src/components/business/TextAlign/index.jsx View File

@@ -0,0 +1,62 @@
1
+import React from 'react'
2
+import { ContentUtils } from 'braft-utils'
3
+
4
+export default class TextAlign extends React.Component {
5
+
6
+  state = {
7
+    currentAlignment: undefined
8
+  }
9
+
10
+  componentWillReceiveProps (next) {
11
+    this.setState({
12
+      currentAlignment: ContentUtils.getSelectionBlockData(next.editorState, 'textAlign')
13
+    })
14
+  }
15
+
16
+  setAlignment = (event) => {
17
+
18
+    let { alignment } = event.currentTarget.dataset
19
+    const hookReturns = this.props.hooks('toggle-text-alignment', alignment)(alignment)
20
+
21
+    if (this.props.textAligns.indexOf(hookReturns) > -1) {
22
+      alignment = hookReturns
23
+    }
24
+
25
+    this.props.editor.setValue(ContentUtils.toggleSelectionAlignment(this.props.editorState, alignment))
26
+    this.props.editor.requestFocus()
27
+
28
+  }
29
+
30
+  render () {
31
+
32
+    const textAlignmentTitles = [
33
+      this.props.language.controls.alignLeft,
34
+      this.props.language.controls.alignCenter,
35
+      this.props.language.controls.alignRight,
36
+      this.props.language.controls.alignJustify
37
+    ]
38
+
39
+    return (
40
+      <div className='control-item-group'>
41
+        {
42
+          this.props.textAligns.map((item, index) => {
43
+            return (
44
+              <button
45
+                type='button'
46
+                key={index}
47
+                data-title={textAlignmentTitles[index]}
48
+                data-alignment={item}
49
+                className={'control-item button ' + (item === this.state.currentAlignment ? 'active' : null)}
50
+                onClick={this.setAlignment}
51
+              >
52
+                <i className={'bfi-align-' + item}></i>
53
+              </button>
54
+            )
55
+          })
56
+        }
57
+      </div>
58
+    )
59
+
60
+  }
61
+
62
+}

+ 114
- 0
source/braft-editor/src/components/business/TextColor/index.jsx View File

@@ -0,0 +1,114 @@
1
+import './style.scss'
2
+import React from 'react'
3
+import DropDown from 'components/common/DropDown'
4
+import ColorPicker from 'components/common/ColorPicker'
5
+import { BaseUtils, ContentUtils } from 'braft-utils'
6
+
7
+export default class TextColor extends React.Component {
8
+
9
+  state = {
10
+    colorType: 'color'
11
+  }
12
+
13
+  dropDownComponentId = 'BRAFT-DROPDOWN-' + BaseUtils.UniqueIndex()
14
+
15
+  render () {
16
+
17
+    let captionStyle = {}
18
+    let currentColor = null
19
+    let { colorType } = this.state
20
+
21
+    this.props.colors.forEach((color) => {
22
+      let color_id = color.replace('#', '')
23
+      if (ContentUtils.selectionHasInlineStyle(this.props.editorState, 'COLOR-' + color_id)) {
24
+        captionStyle.color = color
25
+        colorType === 'color' && (currentColor = color)
26
+      }
27
+
28
+      if (ContentUtils.selectionHasInlineStyle(this.props.editorState, 'BGCOLOR-' + color_id)) {
29
+        captionStyle.backgroundColor = color
30
+        colorType === 'background-color' && (currentColor = color)
31
+      }
32
+
33
+    })
34
+
35
+    const caption = (
36
+      <i style={captionStyle} className='bfi-text-color'>
37
+        <span className='path1'></span>
38
+        <span className='path2'></span>
39
+      </i>
40
+    )
41
+
42
+    return (
43
+      <DropDown
44
+        caption={caption}
45
+        title={this.props.language.controls.color}
46
+        showArrow={false}
47
+        containerNode={this.props.containerNode}
48
+        componentId={this.dropDownComponentId}
49
+        ref={(instance) => this.dropDownComponent = instance}
50
+        className={'control-item dropdown text-color-dropdown'}
51
+      >
52
+        <div className='bf-text-color-picker-wrap'>
53
+          <div className='bf-color-switch-buttons' style={this.props.enableBackgroundColor ? {} : {display: 'none'}}>
54
+            <button
55
+              type='button'
56
+              data-type='color'
57
+              data-keep-active={true}
58
+              data-braft-component-id={this.dropDownComponentId}
59
+              className={colorType === 'color' ? 'active' : ''}
60
+              onClick={this.switchColorType}
61
+            >{this.props.language.controls.textColor}</button>
62
+            <button
63
+              type='button'
64
+              data-type='background-color'
65
+              data-keep-active={true}
66
+              data-braft-component-id={this.dropDownComponentId}
67
+              className={colorType === 'background-color' ? 'active' : ''}
68
+              onClick={this.switchColorType}
69
+            >{this.props.language.controls.backgroundColor}</button>
70
+          </div>
71
+          <ColorPicker
72
+            width={200}
73
+            language={this.props.language}
74
+            current={currentColor}
75
+            disableAlpha={true}
76
+            colors={this.props.colors}
77
+            onChange={this.toggleColor}
78
+          />
79
+        </div>
80
+      </DropDown>
81
+    )
82
+
83
+  }
84
+
85
+  switchColorType = ({ currentTarget }) => {
86
+    this.setState({
87
+      colorType: currentTarget.dataset.type
88
+    })
89
+  }
90
+
91
+  toggleColor = (color) => {
92
+
93
+    const hookReturns = this.props.hooks(`toggle-text-${this.state.colorType}`, color)(color)
94
+
95
+    if (hookReturns === false) {
96
+      return false
97
+    }
98
+
99
+    if (typeof hookReturns === 'string') {
100
+      color =  hookReturns
101
+    }
102
+
103
+    if (this.state.colorType === 'color') {
104
+      this.props.editor.setValue(ContentUtils.toggleSelectionColor(this.props.editorState, color, this.props.colors))
105
+    } else {
106
+      this.props.editor.setValue(ContentUtils.toggleSelectionBackgroundColor(this.props.editorState, color, this.props.colors))
107
+    }
108
+
109
+    this.dropDownComponent.hide()
110
+    this.props.editor.requestFocus()
111
+
112
+  }
113
+
114
+}

+ 38
- 0
source/braft-editor/src/components/business/TextColor/style.scss View File

@@ -0,0 +1,38 @@
1
+@import '~scssinc';
2
+
3
+.text-color-dropdown .dropdown-handler {
4
+  span{
5
+    width: 36px;
6
+    padding: 0;
7
+    overflow: hidden;
8
+    border-radius: 2px;
9
+  }
10
+}
11
+
12
+.bf-text-color-picker-wrap{
13
+  margin: 10px;
14
+  overflow: hidden;
15
+}
16
+
17
+.bf-color-switch-buttons{
18
+  height: 30px;
19
+  margin: 10px;
20
+  overflow: hidden;
21
+  border-radius: 2px;
22
+  box-shadow: inset 0 0 0 1px $COLOR_ACTIVE;
23
+  button{
24
+    float: left;
25
+    width: 50%;
26
+    height: 30px;
27
+    background-color: transparent;
28
+    border: none;
29
+    color: #fff;
30
+    font-size: 12px;
31
+    font-weight: normal;
32
+    text-transform: uppercase;
33
+    cursor: pointer;
34
+    &.active{
35
+      background-color: $COLOR_ACTIVE;
36
+    }
37
+  }
38
+}

+ 60
- 0
source/braft-editor/src/components/business/TextIndent/index.jsx View File

@@ -0,0 +1,60 @@
1
+import './style.scss'
2
+import React from 'react'
3
+import DropDown from 'components/common/DropDown'
4
+import { ContentUtils } from 'braft-utils'
5
+
6
+const toggleTextIndent = (event, props) => {
7
+
8
+  let textIndent = event.currentTarget.dataset.size
9
+  const hookReturns = props.hooks('toggle-text-indent', textIndent)(textIndent)
10
+
11
+  if (hookReturns === false) {
12
+    return false
13
+  }
14
+
15
+  if (!isNaN(hookReturns)) {
16
+    textIndent = hookReturns
17
+  }
18
+
19
+  props.editor.setValue(ContentUtils.toggleSelectionIndent(props.editorState, textIndent, props.textIndents))
20
+  props.editor.requestFocus()
21
+
22
+}
23
+
24
+export default (props) => {
25
+
26
+  let caption = null
27
+  let currentIndent = null
28
+
29
+  props.textIndents.find((item) => {
30
+    if (ContentUtils.selectionHasInlineStyle(props.editorState, 'INDENT-' + item)) {
31
+      caption = item
32
+      currentIndent = item
33
+      return true
34
+    }
35
+    return false
36
+  })
37
+
38
+  return (
39
+    <DropDown
40
+      caption={caption || props.defaultCaption}
41
+      containerNode={props.containerNode}
42
+      title={props.language.controls.textIndent}
43
+      className={'control-item dropdown bf-indent-dropdown'}
44
+    >
45
+      <ul className='bf-text-indents'>
46
+        {props.textIndents.map((item, index) => {
47
+          return (
48
+            <li
49
+              key={index}
50
+              className={item === currentIndent ? 'active' : null}
51
+              data-size={item}
52
+              onClick={(event) => toggleTextIndent(event, props)}
53
+            >{item}</li>
54
+          )
55
+        })}
56
+      </ul>
57
+    </DropDown>
58
+  )
59
+
60
+}

+ 34
- 0
source/braft-editor/src/components/business/TextIndent/style.scss View File

@@ -0,0 +1,34 @@
1
+@import '~scssinc';
2
+
3
+.bf-text-indent-dropdown{
4
+  min-width: 95px;
5
+}
6
+.bf-text-indents{
7
+  box-sizing: content-box;
8
+  width: 210px;
9
+  list-style: none;
10
+  margin: 0;
11
+  padding: 5px;
12
+  overflow: hidden;
13
+  li{
14
+    display: block;
15
+    float: left;
16
+    width: 60px;
17
+    height: 30px;
18
+    background-color: rgba(#fff, .1);
19
+    border-radius: 2px;
20
+    margin: 5px;
21
+    color: #fff;
22
+    font-size: 12px;
23
+    line-height: 30px;
24
+    text-align: center;
25
+    text-transform: uppercase;
26
+    cursor: pointer;
27
+    &:hover{
28
+      background-color: rgba(#fff, .2);
29
+    }
30
+    &.active{
31
+      background-color: $COLOR_ACTIVE;
32
+    }
33
+  }
34
+}

+ 29
- 0
source/braft-editor/src/components/common/ColorPicker/index.jsx View File

@@ -0,0 +1,29 @@
1
+import './style.scss'
2
+import React from 'react'
3
+
4
+export default (props) => {
5
+
6
+  const { current, colors } = props
7
+
8
+  return (
9
+    <div className='bf-colors-wrap'>
10
+      <ul className='bf-colors'>
11
+        {colors.map((item, index) => {
12
+          let className = item === current ? 'color-item active' : 'color-item'
13
+          return (
14
+            <li
15
+              key={index}
16
+              title={item}
17
+              className={className}
18
+              style={{color: item}}
19
+              data-color={item.replace('#', '')}
20
+              onClick={(e) => props.onChange(e.currentTarget.dataset.color)}
21
+            >
22
+            </li>
23
+          )
24
+        })}
25
+      </ul>
26
+    </div>
27
+  )
28
+
29
+}

+ 30
- 0
source/braft-editor/src/components/common/ColorPicker/style.scss View File

@@ -0,0 +1,30 @@
1
+@import '~scssinc';
2
+
3
+.bf-colors{
4
+  box-sizing: content-box;
5
+  list-style: none;
6
+  width: 240px;
7
+  margin: 0;
8
+  padding: 5px;
9
+  overflow: hidden;
10
+  li{
11
+    box-sizing: content-box;
12
+    display: block;
13
+    float: left;
14
+    width: 24px;
15
+    height: 24px;
16
+    margin: 5px;
17
+    padding: 0;
18
+    background-color: currentColor;
19
+    border: 3px solid transparent;
20
+    border-radius: 50%;
21
+    cursor: pointer;
22
+    transition: transform .2s;
23
+    &:hover{
24
+      transform: scale(1.3);
25
+    }
26
+    &.active{
27
+      box-shadow: 0 0 0 2px $COLOR_ACTIVE;
28
+    }
29
+  }
30
+}

+ 170
- 0
source/braft-editor/src/components/common/DropDown/index.jsx View File

@@ -0,0 +1,170 @@
1
+import './style.scss'
2
+import React from 'react'
3
+import ResponsiveHelper from 'helpers/responsive'
4
+import { BaseUtils } from 'braft-utils'
5
+
6
+export default class DropDown extends React.Component {
7
+
8
+  alive = false
9
+  responsiveResolveId = null
10
+  dropDownHandlerElement = null
11
+  dropDownContentElement = null
12
+  componentId = this.props.componentId || ('BRAFT-DROPDOWN-' + BaseUtils.UniqueIndex())
13
+  state = {
14
+    active: false,
15
+    offset: 0,
16
+    maxHeight: 100
17
+  }
18
+
19
+  componentDidMount () {
20
+
21
+    this.alive = true
22
+
23
+    document.body.addEventListener('click', (event) => {
24
+      this.registerClickEvent(event)
25
+    })
26
+
27
+    this.responsiveResolveId = ResponsiveHelper.resolve(() => {
28
+      this.fixDropDownPosition()
29
+    })
30
+
31
+  }
32
+
33
+  componentWillReceiveProps (next) {
34
+
35
+    if (!this.props.disabled && next.disabled) {
36
+      this.hide()
37
+    }
38
+
39
+  }
40
+
41
+  componentDidUpdate (prevState) {
42
+
43
+    if (!prevState.active && this.state.active) {
44
+      this.fixDropDownPosition()
45
+    }
46
+
47
+  }
48
+
49
+  componentWillUnmount () {
50
+
51
+    document.body.removeEventListener('click', (event) => {
52
+      this.registerClickEvent(event)
53
+    })
54
+
55
+    this.alive = false
56
+    ResponsiveHelper.unresolve(this.responsiveResolveId)
57
+
58
+  }
59
+
60
+  render () {
61
+
62
+    let { active, offset, maxHeight } = this.state
63
+    let { caption, htmlCaption, title, disabled, showArrow, arrowActive, className, children } = this.props
64
+
65
+    disabled && (active = false)
66
+
67
+    return (
68
+      <div
69
+        id={this.componentId}
70
+        className={'bf-dropdown ' + (active ? 'active ' : '') + (disabled ? 'disabled ' : '') + className}
71
+      >
72
+        {htmlCaption ? (
73
+          <button
74
+            type='button'
75
+            className='dropdown-handler'
76
+            data-title={title}
77
+            data-braft-component-id={this.componentId}
78
+            dangerouslySetInnerHTML={htmlCaption ? {__html: htmlCaption} : null}
79
+            ref={(instance) => this.dropDownHandlerElement = instance}
80
+          ></button>
81
+        ) : (
82
+          <button
83
+            type='button'
84
+            className='dropdown-handler'
85
+            data-title={title}
86
+            data-braft-component-id={this.componentId}
87
+            ref={(instance) => this.dropDownHandlerElement = instance}
88
+          >
89
+            <span>{caption}</span>
90
+            {showArrow !== false ? <i className='bfi-drop-down'></i> : null}
91
+          </button>
92
+        )}
93
+        <div
94
+          className='dropdown-content'
95
+          style={{marginLeft: offset }}
96
+          ref={(instance) => this.dropDownContentElement = instance}
97
+        >
98
+          <i
99
+            style={{marginLeft: offset * -1}}
100
+            className={'dropdown-arrow' + (arrowActive ? ' active' : '')}
101
+          ></i>
102
+          <div
103
+            className='dropdown-content-inner'
104
+            style={{ maxHeight }}
105
+          >
106
+            {children}
107
+          </div>
108
+        </div>
109
+      </div>
110
+    )
111
+
112
+  }
113
+
114
+  fixDropDownPosition () {
115
+
116
+    const viewRect = this.props.containerNode.getBoundingClientRect()
117
+    const editorContentRect = this.props.containerNode.querySelector('.bf-content').getBoundingClientRect()
118
+    const handlerRect = this.dropDownHandlerElement.getBoundingClientRect()
119
+    const contentRect = this.dropDownContentElement.getBoundingClientRect()
120
+
121
+    const maxHeight = editorContentRect.height + (editorContentRect.top - (handlerRect.top + handlerRect.height)) - 30
122
+
123
+    let offset = 0
124
+    let right = handlerRect.right - handlerRect.width / 2 + contentRect.width / 2
125
+    let left = handlerRect.left + handlerRect.width / 2 - contentRect.width / 2
126
+
127
+    right = viewRect.right - right
128
+    left = left - viewRect.left
129
+
130
+    if (right < 10) {
131
+      offset = right - 10
132
+    } else if (left < 10) {
133
+      offset = left * -1 + 10
134
+    }
135
+
136
+    if (offset !== this.state.offset || maxHeight !== this.state.maxHeight) {
137
+      this.setState({ offset, maxHeight })
138
+    }
139
+
140
+  }
141
+
142
+  registerClickEvent (event) {
143
+
144
+    let { autoHide } = this.props
145
+    let active = false
146
+
147
+    if (event.target.dataset.braftComponentId === this.componentId) {
148
+      active = event.target.dataset.keepActive ? true : !this.state.active
149
+    } else if (autoHide === false) {
150
+      active = this.state.active
151
+    }
152
+
153
+    this.alive && this.setState({ active })
154
+
155
+  }
156
+
157
+  show () {
158
+    this.setState({
159
+      active: true
160
+    })
161
+  }
162
+
163
+  hide () {
164
+    this.setState({
165
+      active: false
166
+    })
167
+  }
168
+
169
+
170
+}

+ 135
- 0
source/braft-editor/src/components/common/DropDown/style.scss View File

@@ -0,0 +1,135 @@
1
+@import '~scssinc';
2
+
3
+.bf-dropdown{
4
+  position: relative;
5
+  width: auto;
6
+  height: 36px;
7
+  margin: 0;
8
+  &.disabled{
9
+    pointer-events: none;
10
+    opacity: .3;
11
+  }
12
+  .dropdown-content{
13
+    box-sizing: content-box;
14
+    position: absolute;
15
+    z-index: 10;
16
+    top: 100%;
17
+    left: 50%;
18
+    visibility: hidden;
19
+    float: left;
20
+    width: auto;
21
+    min-width: 100%;
22
+    margin-top: 9px;
23
+    background-color: rgba($COLOR_DARK, 1);
24
+    border-radius: 2px;
25
+    box-shadow: 0 5px 15px rgba(#000, .2);
26
+    opacity: 0;
27
+    cursor: default;
28
+    transform: translate(-50%, 20px);
29
+    transition: .2s;
30
+    ::-webkit-scrollbar-track{
31
+      background-color: transparent;
32
+    }
33
+    ::-webkit-scrollbar{
34
+      width: 4px;
35
+      background-color: transparent;
36
+      border-radius: 2px;
37
+    }
38
+    ::-webkit-scrollbar-thumb{
39
+      background-color: rgba(#fff, .3);
40
+      border-radius: 2px;
41
+    }
42
+    .dropdown-arrow{
43
+      position: absolute;
44
+      top: -10px;
45
+      left: 50%;
46
+      border: solid 5px transparent;
47
+      border-bottom-color: rgba($COLOR_DARK, 1);
48
+      transform: translateX(-50%);
49
+      transition: margin .2s;
50
+      &.active{
51
+        border-bottom-color: $COLOR_ACTIVE;
52
+      }
53
+    }
54
+    .menu{
55
+      list-style: none;
56
+      margin: 0;
57
+      padding: 0;
58
+      overflow: hidden;
59
+      border-radius: 2px;
60
+    }
61
+    .menu-item{
62
+      display: block;
63
+      list-style: none;
64
+      margin: 0;
65
+      font-size: 16px;
66
+      cursor: pointer;
67
+      &:hover{
68
+        background-color: rgba(#000, .1);
69
+      }
70
+      &.active{
71
+        background-color: $COLOR_ACTIVE;
72
+        color: #fff;
73
+      }
74
+      &:not(.active){
75
+        color: rgba(#fff, .6);
76
+        box-shadow: inset 0 -1px 0 0 rgba(#fff, .1);
77
+      }
78
+    }
79
+  }
80
+  .dropdown-content-inner{
81
+    overflow: auto;
82
+  }
83
+  .dropdown-handler{
84
+    position: relative;
85
+    display: block;
86
+    width: 100%;
87
+    height: 36px;
88
+    background-color: transparent;
89
+    border: none;
90
+    color: $COLOR_GRAY;
91
+    cursor: pointer;
92
+    &:hover{
93
+      background-color: rgba(#000, .05);
94
+    }
95
+    *{
96
+      display: inline;
97
+      padding: 0;
98
+      font-size: inherit;
99
+      font-weight: normal;
100
+    }
101
+    > span{
102
+      float: left;
103
+      padding: 0 10px;
104
+      font-size: 14px;
105
+      line-height: 36px;
106
+      pointer-events: none;
107
+      i{
108
+        display: block;
109
+        height: 36px;
110
+        font-size: 16px;
111
+        line-height: 36px;
112
+        text-align: center;
113
+      }
114
+    }
115
+    .bfi-drop-down{
116
+      float: right;
117
+      width: 30px;
118
+      height: 36px;
119
+      font-size: 16px;
120
+      line-height: 36px;
121
+      text-align: center;
122
+      pointer-events: none;
123
+    }
124
+  }
125
+  &.active{
126
+    .dropdown-handler{
127
+      background-color: rgba(#000, .05);
128
+    }
129
+    .dropdown-content{
130
+      visibility: visible;
131
+      opacity: 1;
132
+      transform: translate(-50%, 0);
133
+    }
134
+  }
135
+}

+ 170
- 0
source/braft-editor/src/components/common/Modal/index.jsx View File

@@ -0,0 +1,170 @@
1
+import './style.scss'
2
+import React from 'react'
3
+import ReactDOM from 'react-dom'
4
+import { BaseUtils } from 'braft-utils'
5
+
6
+export default class Modal extends React.Component {
7
+
8
+  constructor (props) {
9
+
10
+    super(props)
11
+    this.active = false
12
+    this.componentId = 'BRAFT-MODAL-' + BaseUtils.UniqueIndex()
13
+
14
+  }
15
+
16
+  static defaultProps = {
17
+    showFooter: true
18
+  }
19
+
20
+  componentDidMount () {
21
+
22
+    if (this.props.visible) {
23
+      this.active = true
24
+      this.renderComponent(this.props)
25
+    }
26
+
27
+  }
28
+
29
+  componentWillReceiveProps (next) {
30
+
31
+    if (this.props.visible && !next.visible) {
32
+      this.unrenderComponent()
33
+    } else if (this.props.visible || next.visible) {
34
+      this.active = true
35
+      this.renderComponent(next)
36
+    }
37
+
38
+  }
39
+
40
+  render () {
41
+    return null
42
+  }
43
+
44
+  handleTransitionEnd = () => {
45
+
46
+    if (!this.rootElement || !this.rootElement.classList) {
47
+      return false
48
+    }
49
+
50
+    if (!this.rootElement.classList.contains('active')) {
51
+      ReactDOM.unmountComponentAtNode(this.rootElement) && this.rootElement.parentNode.removeChild(this.rootElement)
52
+    }
53
+
54
+  }
55
+
56
+  unrenderComponent () {
57
+    this.active = false
58
+    this.activeId && window.clearImmediate(this.activeId)
59
+    if (this.rootElement && this.rootElement.classList) {
60
+      this.rootElement.classList.remove('active')
61
+    }
62
+  }
63
+
64
+  renderComponent (props) {
65
+
66
+    if (!this.active) {
67
+      return false
68
+    }
69
+
70
+    let { title, className, width, height, children, confirmable, showFooter, showCancel, showConfirm, showClose, cancelText, confirmText, bottomText, language } = props
71
+
72
+    typeof showCancel === 'undefined' && (showCancel = true)
73
+    typeof showClose === 'undefined' && (showClose = true)
74
+    typeof showConfirm === 'undefined' && (showConfirm = true)
75
+
76
+    const childComponent = (
77
+      <div className={'bf-modal ' + (className || '')}>
78
+        <div className='bf-modal-mask'></div>
79
+        <div onTransitionEnd={this.handleTransitionEnd} style={{width, height}} className='bf-modal-content'>
80
+          <div className='bf-modal-header'>
81
+            <h3 className='bf-modal-caption'>{title}</h3>
82
+            {showClose && <button type='button' onClick={this.close} className='bf-modal-close-button'><i className='bf-icon-close'></i></button>}
83
+          </div>
84
+          <div className='bf-modal-body'>{children}</div>
85
+          {showFooter ? (
86
+            <div className='bf-modal-footer'>
87
+              <div className='bf-modal-addon-text'>{bottomText}</div>
88
+              <div className='bf-modal-buttons'>
89
+                {showCancel && <button type='button' onClick={this.handleCancel} className='bf-modal-cancel'>{cancelText || language.base.cancel}</button>}
90
+                {showConfirm && <button type='button' onClick={this.handleConfirm} className={'bf-modal-confirm ' + (!confirmable ? 'disabled' : '')}>{confirmText || language.base.confirm}</button>}
91
+              </div>
92
+            </div>
93
+          ) : null}
94
+        </div>
95
+      </div>
96
+    )
97
+
98
+    this.rootElement = document.querySelector('#' + this.componentId)
99
+
100
+    if (!this.rootElement) {
101
+      this.rootElement = document.createElement('div')
102
+      this.rootElement.id = this.componentId
103
+      this.rootElement.className = 'bf-modal-root'
104
+      document.body.appendChild(this.rootElement)
105
+    }
106
+
107
+    ReactDOM.render(childComponent, this.rootElement)
108
+
109
+    this.activeId = window.setImmediate(() => {
110
+      this.rootElement.classList.add('active')
111
+    })
112
+
113
+  }
114
+
115
+  handleCancel = () => {
116
+    this.props.closeOnCancel && this.close()
117
+    this.props.onCancel && this.props.onCancel()
118
+  }
119
+
120
+  handleConfirm = () => {
121
+    this.props.closeOnConfirm && this.close()
122
+    this.props.onConfirm && this.props.onConfirm()
123
+  }
124
+
125
+  close = () => {
126
+    this.unrenderComponent()
127
+    this.props.onClose && this.props.onClose()
128
+  }
129
+
130
+}
131
+
132
+export const showModal = (props) => {
133
+
134
+  const hostNode = document.createElement('div')
135
+  hostNode.style.display = 'none'
136
+  document.body.appendChild(hostNode)
137
+
138
+  const close = () => {
139
+    ReactDOM.unmountComponentAtNode(hostNode) && hostNode.parentNode.removeChild(hostNode)
140
+  }
141
+
142
+  const onConfirm = () => {
143
+    close()
144
+    props.onConfirm && props.onConfirm()
145
+  }
146
+
147
+  const onCancel = () => {
148
+    close()
149
+    props.onCancel && props.onCancel()
150
+  }
151
+
152
+  const onClose = () => {
153
+    close()
154
+    props.onClose && props.onClose()
155
+  }
156
+
157
+  const extProps = {
158
+    onConfirm, onCancel, onClose,
159
+    visible: true,
160
+    closeOnConfirm: true,
161
+    closeOnCancel: true
162
+  }
163
+
164
+  const modalInstance = ReactDOM.render(<Modal { ...props } { ...extProps }/>, hostNode)
165
+  modalInstance.destroy = close
166
+  modalInstance.update = modalInstance.renderComponent
167
+
168
+  return modalInstance
169
+
170
+}

+ 119
- 0
source/braft-editor/src/components/common/Modal/style.scss View File

@@ -0,0 +1,119 @@
1
+@import '~scssinc';
2
+
3
+.bf-modal{
4
+  position: fixed;
5
+  z-index: 99999;
6
+  top: 0;
7
+  left: 0;
8
+  width: 100%;
9
+  height: 100%;
10
+  button{
11
+    outline: none;
12
+  }
13
+}
14
+.bf-modal-mask{
15
+  position: absolute;
16
+  z-index: 1;
17
+  top: 0;
18
+  left: 0;
19
+  width: 100%;
20
+  height: 100%;
21
+  background-color: rgba(#000, .1);
22
+  opacity: 0;
23
+  transition: opacity .2s;
24
+}
25
+.bf-modal-content{
26
+  position: absolute;
27
+  z-index: 2;
28
+  top: 45%;
29
+  left: 50%;
30
+  max-width: 95%;
31
+  background-color: #fff;
32
+  border-radius: 2px;
33
+  box-shadow: 0 15px 30px rgba(#000, .1);
34
+  opacity: 0;
35
+  transform: translate(-50%, -40%);
36
+  transition: transform .2s, opacity .2s;
37
+}
38
+.bf-modal-header{
39
+  height: 50px;
40
+}
41
+.bf-modal-caption{
42
+  float: left;
43
+  margin: 0;
44
+  padding: 0 15px;
45
+  color: rgba(#999, 1);
46
+  font-size: 14px;
47
+  font-weight: normal;
48
+  line-height: 50px;
49
+}
50
+.bf-modal-close-button{
51
+  float: right;
52
+  width: 50px;
53
+  height: 50px;
54
+  background-color: transparent;
55
+  border: none;
56
+  color: #ccc;
57
+  font-size: 18px;
58
+  cursor: pointer;
59
+  &:hover{
60
+    color: $COLOR_DANGER;
61
+  }
62
+}
63
+.bf-modal-body{
64
+  overflow: auto;
65
+}
66
+.bf-modal-footer{
67
+  min-height: 15px;
68
+  padding: 0 15px;
69
+  overflow: hidden;
70
+}
71
+.bf-modal-addon-text{
72
+  float: left;
73
+  color: #999;
74
+  font-size: 12px;
75
+  line-height: 60px;
76
+}
77
+.bf-modal-buttons{
78
+  float: right;
79
+}
80
+.bf-modal-cancel,
81
+.bf-modal-confirm{
82
+  height: 36px;
83
+  margin: 12px 0 12px 15px;
84
+  padding: 0 30px;
85
+  border: none;
86
+  border-radius: 2px;
87
+  font-size: 12px;
88
+  font-weight: bold;
89
+  cursor: pointer;
90
+}
91
+.bf-modal-cancel{
92
+  background-color: #e8e9ea;
93
+  color: #999;
94
+  &:hover{
95
+    background-color: #d8d9da;
96
+  }
97
+}
98
+.bf-modal-confirm{
99
+  background-color: $COLOR_ACTIVE;
100
+  color: #fff;
101
+  &:hover{
102
+    background-color: $COLOR_ACTIVE - 20;
103
+  }
104
+  &.disabled{
105
+    opacity: .3;
106
+    pointer-events: none;
107
+    filter: grayscale(.4);
108
+  }
109
+}
110
+
111
+.bf-modal-root.active{
112
+  .bf-modal-mask{
113
+    opacity: 1;
114
+  }
115
+  .bf-modal-content{
116
+    opacity: 1;
117
+    transform: translate(-50%, -50%);
118
+  }
119
+}

+ 13
- 0
source/braft-editor/src/components/common/StaticContainer/index.jsx View File

@@ -0,0 +1,13 @@
1
+import React from 'react'
2
+
3
+export default class extends React.Component {
4
+
5
+  shouldComponentUpdate () {
6
+    return false
7
+  }
8
+
9
+  render () {
10
+    return <div>{this.props.children}</div>
11
+  }
12
+
13
+}

+ 12
- 0
source/braft-editor/src/components/common/Switch/index.jsx View File

@@ -0,0 +1,12 @@
1
+import './style.scss'
2
+import React from 'react'
3
+
4
+export default (props) => {
5
+
6
+  const { active, onClick, className } = props
7
+
8
+  return (
9
+    <div onClick={() => onClick()} className={'bf-switch ' + className + (active ? ' active' : '')}></div>
10
+  )
11
+
12
+}

+ 28
- 0
source/braft-editor/src/components/common/Switch/style.scss View File

@@ -0,0 +1,28 @@
1
+@import '~scssinc';
2
+
3
+.bf-switch{
4
+  position: relative;
5
+  width: 40px;
6
+  height: 20px;
7
+  background-color: rgba(#fff, .15);
8
+  border-radius: 10px;
9
+  transition: background .3s;
10
+  &.active{
11
+    background-color: $COLOR_ACTIVE;
12
+    &::before{
13
+      left: 20px;
14
+    }
15
+  }
16
+  &::before{
17
+    position: absolute;
18
+    left: 0;
19
+    display: block;
20
+    width: 20px;
21
+    height: 20px;
22
+    border-radius: 10px;
23
+    background-color: #eee;
24
+    content: '';
25
+    transform: scale(1.2);
26
+    transition: .3s;
27
+  }
28
+}

+ 188
- 0
source/braft-editor/src/configs/controls.js View File

@@ -0,0 +1,188 @@
1
+import React from 'react'
2
+
3
+export default (lang) => [
4
+  {
5
+    key: 'undo',
6
+    title: lang.controls.undo,
7
+    text: <i className="bfi-undo"></i>,
8
+    type: 'editor-method',
9
+    command: 'undo'
10
+  }, {
11
+    key: 'redo',
12
+    title: lang.controls.redo,
13
+    text: <i className="bfi-redo"></i>,
14
+    type: 'editor-method',
15
+    command: 'redo'
16
+  }, {
17
+    key: 'remove-styles',
18
+    title: lang.controls.removeStyles,
19
+    text: <i className="bfi-format_clear"></i>,
20
+    type: 'editor-method',
21
+    command: 'removeSelectionInlineStyles'
22
+  }, {
23
+    key: 'hr',
24
+    title: lang.controls.hr,
25
+    text: <i className="bfi-hr"></i>,
26
+    type: 'editor-method',
27
+    command: 'insertHorizontalLine'
28
+  }, {
29
+    key: 'bold',
30
+    title: lang.controls.bold,
31
+    text: <i className="bfi-bold"></i>,
32
+    type: 'inline-style',
33
+    command: 'bold'
34
+  },{
35
+    key: 'italic',
36
+    title: lang.controls.italic,
37
+    text: <i className="bfi-italic"></i>,
38
+    type: 'inline-style',
39
+    command: 'italic'
40
+  }, {
41
+    key: 'underline',
42
+    title: lang.controls.underline,
43
+    text: <i className="bfi-underlined"></i>,
44
+    type: 'inline-style',
45
+    command: 'underline'
46
+  }, {
47
+    key: 'strike-through',
48
+    title: lang.controls.strikeThrough,
49
+    text: <i className="bfi-strikethrough"></i>,
50
+    type: 'inline-style',
51
+    command: 'strikethrough',
52
+  }, {
53
+    key: 'superscript',
54
+    title: lang.controls.superScript,
55
+    text: <i className="bfi-superscript"></i>,
56
+    type: 'inline-style',
57
+    command: 'superscript'
58
+  }, {
59
+    key: 'subscript',
60
+    title: lang.controls.subScript,
61
+    text: <i className="bfi-subscript"></i>,
62
+    type: 'inline-style',
63
+    command: 'subscript'
64
+  }, {
65
+    key: 'headings',
66
+    title: lang.controls.headings,
67
+    type: 'headings',
68
+  }, {
69
+    key: 'blockquote',
70
+    title: lang.controls.blockQuote,
71
+    text: <i className="bfi-quote"></i>,
72
+    type: 'block-type',
73
+    command: 'blockquote',
74
+  }, {
75
+    key: 'code',
76
+    title: lang.controls.code,
77
+    text: <i className="bfi-code"></i>,
78
+    type: 'block-type',
79
+    command: 'code-block'
80
+  }, {
81
+    key: 'list-ul',
82
+    title: lang.controls.unorderedList,
83
+    text: <i className="bfi-list"></i>,
84
+    type: 'block-type',
85
+    command: 'unordered-list-item'
86
+  }, {
87
+    key: 'list-ol',
88
+    title: lang.controls.orderedList,
89
+    text: <i className="bfi-list-numbered"></i>,
90
+    type: 'block-type',
91
+    command: 'ordered-list-item'
92
+  }, {
93
+    key: 'link',
94
+    title: lang.controls.link,
95
+    type: 'link'
96
+  }, {
97
+    key: 'text-color',
98
+    title: lang.controls.color,
99
+    type: 'text-color'
100
+  }, {
101
+    key: 'line-height',
102
+    title: lang.controls.lineHeight, 
103
+    type: 'line-height'
104
+  }, {
105
+    key: 'letter-spacing',
106
+    title: lang.controls.letterSpacing,
107
+    type: 'letter-spacing'
108
+  }, {
109
+    key: 'text-indent',
110
+    title: lang.controls.textIndent,
111
+    type: 'text-indent'
112
+  },{
113
+    key: 'font-size',
114
+    title: lang.controls.fontSize,
115
+    type: 'font-size'
116
+  }, {
117
+    key: 'font-family',
118
+    title: lang.controls.fontFamily,
119
+    type: 'font-family'
120
+  }, {
121
+    key: 'text-align',
122
+    title: lang.controls.textAlign,
123
+    type: 'text-align'
124
+  }, {
125
+    key: 'media',
126
+    title: lang.controls.media,
127
+    text: <i className="bfi-media"></i>,
128
+    type: 'media'
129
+  }, {
130
+    key: 'emoji',
131
+    title: lang.controls.emoji,
132
+    text: <i className="bfi-emoji"></i>,
133
+    type: 'emoji'
134
+  }, {
135
+    key: 'clear',
136
+    title: lang.controls.clear,
137
+    text: lang.controls.clear,
138
+    type: 'editor-method',
139
+    command: 'clearEditorContent'
140
+  }, {
141
+    key: 'modal',
142
+    type: 'modal',
143
+  }, {
144
+    key: 'button',
145
+    type: 'button',
146
+  }, {
147
+    key: 'dropdown',
148
+    type: 'dropdown',
149
+  }, {
150
+    key: 'component',
151
+    type: 'component',
152
+  }
153
+]
154
+
155
+export const imageControlItems = {
156
+  'float-left': {
157
+    text: <span data-float="left">&#xe91e;</span>,
158
+    command: 'setImageFloat|left'
159
+  },
160
+  'float-right': {
161
+    text: <span data-float="right">&#xe914;</span>,
162
+    command: 'setImageFloat|right'
163
+  },
164
+  'align-left': {
165
+    text: <span data-align="left">&#xe027;</span>,
166
+    command: 'setImageAlignment|left'
167
+  },
168
+  'align-center': {
169
+    text: <span data-align="center">&#xe028;</span>,
170
+    command: 'setImageAlignment|center'
171
+  },
172
+  'align-right': {
173
+    text: <span data-align="right">&#xe029;</span>,
174
+    command: 'setImageAlignment|right'
175
+  },
176
+  'size': {
177
+    text: <span>&#xe3c2;</span>,
178
+    command: 'toggleSizeEditor'
179
+  },
180
+  'link': {
181
+    text: <span>&#xe91a;</span>,
182
+    command: 'toggleLinkEditor'
183
+  },
184
+  'remove': {
185
+    text: <span>&#xe9ac;</span>,
186
+    command: 'removeImage'
187
+  }
188
+}

+ 18
- 0
source/braft-editor/src/configs/keybindings.js View File

@@ -0,0 +1,18 @@
1
+import { getDefaultKeyBinding, KeyBindingUtil } from 'draft-js'
2
+
3
+// TODO
4
+// 允许自定义的快捷键设置
5
+
6
+export default (customKeyBindingFn) => (event) => {
7
+
8
+  if (event.keyCode === 83 && (KeyBindingUtil.hasCommandModifier(event) || KeyBindingUtil.isCtrlKeyCommand(event))) {
9
+    return 'braft-save'
10
+  }
11
+
12
+  if (customKeyBindingFn) {
13
+    return customKeyBindingFn(event) || getDefaultKeyBinding(event)
14
+  }
15
+
16
+  return getDefaultKeyBinding(event)
17
+
18
+}

+ 58
- 0
source/braft-editor/src/configs/maps.js View File

@@ -0,0 +1,58 @@
1
+import React from 'react'
2
+
3
+export const getHeadings = (lang) => [
4
+  {
5
+    key: 'header-one',
6
+    title: lang.controls.header + ' 1',
7
+    text: <h1>{lang.controls.header} 1</h1>,
8
+    type: 'block-type',
9
+    command: 'header-one'
10
+  }, {
11
+    key: 'header-two',
12
+    title: lang.controls.header + ' 2',
13
+    text: <h2>{lang.controls.header} 2</h2>,
14
+    type: 'block-type',
15
+    command: 'header-two'
16
+  }, {
17
+    key: 'header-three',
18
+    title: lang.controls.header + ' 3',
19
+    text: <h3>{lang.controls.header} 3</h3>,
20
+    type: 'block-type',
21
+    command: 'header-three'
22
+  }, {
23
+    key: 'header-four',
24
+    title: lang.controls.header + ' 4',
25
+    text: <h4>{lang.controls.header} 4</h4>,
26
+    type: 'block-type',
27
+    command: 'header-four'
28
+  }, {
29
+    key: 'header-five',
30
+    title: lang.controls.header + ' 5',
31
+    text: <h5>{lang.controls.header} 5</h5>,
32
+    type: 'block-type',
33
+    command: 'header-five'
34
+  }, {
35
+    key: 'header-six',
36
+    title: lang.controls.header + ' 6',
37
+    text: <h6>{lang.controls.header} 6</h6>,
38
+    type: 'block-type',
39
+    command: 'header-six'
40
+  }, {
41
+    key: 'unstyled',
42
+    title: lang.controls.normal,
43
+    text: lang.controls.normal,
44
+    type: 'block-type',
45
+    command: 'unstyled'
46
+  }
47
+]
48
+
49
+export const blocks = {
50
+  'header-one': 'h1',
51
+  'header-two': 'h2',
52
+  'header-three': 'h3',
53
+  'header-four': 'h4',
54
+  'header-fiv': 'h5',
55
+  'header-six': 'h6',
56
+  'unstyled': 'p',
57
+  'blockquote': 'blockquote'
58
+}

+ 113
- 0
source/braft-editor/src/configs/props.js View File

@@ -0,0 +1,113 @@
1
+export default {
2
+  language: 'zh',
3
+  controls: [
4
+    'undo', 'redo', 'separator',
5
+    'font-size', 'line-height', 'letter-spacing', 'separator',
6
+    'text-color', 'bold', 'italic', 'underline', 'strike-through', 'separator',
7
+    'superscript', 'subscript', 'remove-styles', 'emoji', 'text-align', 'separator',
8
+    'headings', 'list-ul', 'list-ol', 'blockquote', 'code', 'separator',
9
+    'link', 'split', 'hr', 'separator',
10
+    'media', 'separator',
11
+    'clear'
12
+  ],
13
+  excludeControls: [],
14
+  extendControls: [],
15
+  extendAtomics: [],
16
+  componentBelowControlBar: null,
17
+  media: {
18
+    pasteImage: true,
19
+    image: true,
20
+    video: true,
21
+    audio: true,
22
+    uploadFn: null,
23
+    validateFn: null,
24
+    onBeforeDeselect: null,
25
+    onDeselect: null,
26
+    onBeforeSelect: null,
27
+    onSelect: null,
28
+    onBeforeRemove: null,
29
+    onRemove: null,
30
+    onCancel: null,
31
+    onFileSelect: null,
32
+    onBeforeInsert: null,
33
+    onInsert: null,
34
+    onChange: null,
35
+    externals: {
36
+      audio: true,
37
+      video: true,
38
+      image: true,
39
+      embed: true
40
+    },
41
+  },
42
+  imageControls: [
43
+    'float-left', 'float-right',
44
+    'align-left', 'align-center', 'align-right',
45
+    'link', 'size', 'remove'
46
+  ],
47
+  colors: [
48
+    '#000000', '#333333', '#666666', '#999999', '#cccccc', '#ffffff',
49
+    '#61a951', '#16a085', '#07a9fe', '#003ba5', '#8e44ad', '#f32784',
50
+    '#c0392b', '#d35400', '#f39c12', '#fdda00', '#7f8c8d', '#2c3e50'
51
+  ],
52
+  tabIndents: 2,
53
+  textAligns: ['left', 'center', 'right', 'justify'],
54
+  textBackgroundColor: true,
55
+  letterSpacings: [0, 1, 2, 3, 4, 5, 6],
56
+  textIndents: [0, 14, 21, 28],
57
+  lineHeights: [1, 1.2, 1.5, 1.75, 2, 2.5, 3, 4],
58
+  fontSizes: [
59
+    12, 14, 16, 18, 20, 24,
60
+    28, 30, 32, 36, 40, 48,
61
+    56, 64, 72, 96, 120, 144
62
+  ],
63
+  fontFamilies: [{
64
+    name: 'Araial',
65
+    family: 'Arial, Helvetica, sans-serif'
66
+  }, {
67
+    name: 'Georgia',
68
+    family: 'Georgia, serif'
69
+  }, {
70
+    name: 'Impact',
71
+    family: 'Impact, serif'
72
+  }, {
73
+    name: 'Monospace',
74
+    family: '"Courier New", Courier, monospace'
75
+  }, {
76
+    name: 'Tahoma',
77
+    family: 'tahoma, arial, "Hiragino Sans GB", 宋体, sans-serif'
78
+  }],
79
+  emojis: [
80
+    '🤣', '🙌', '💚', '💛', '👏', '😉', '💯',
81
+    '💕', '💞', '💘', '💙', '💝', '🖤', '💜',
82
+    '❤️', '😍', '😻', '💓', '💗', '😋', '😇',
83
+    '😂', '😹', '😘', '💖', '😁', '😀', '🤞',
84
+    '😲', '😄', '😊', '👍', '😌', '😃', '😅',
85
+    '✌️', '🤗', '💋', '😗', '😽', '😚', '🤠',
86
+    '😙', '😺', '👄', '😸', '😏', '😼', '👌',
87
+    '😎', '😆', '😛', '🙏', '🤝', '🙂', '🤑',
88
+    '😝', '😐', '😑', '🤤', '😤', '🙃', '🤡',
89
+    '😶', '😪', '😴', '😵', '😓', '👊', '😦',
90
+    '😷', '🤐', '😜', '🤓', '👻', '😥', '🙄',
91
+    '🤔', '🤒', '🙁', '😔', '😯', '☹️', '☠️',
92
+    '😰', '😩', '😖', '😕', '😒', '😣', '😢',
93
+    '😮', '😿', '🤧', '😫', '🤥', '😞', '😬',
94
+    '👎', '💀', '😳', '😨', '🤕', '🤢', '😱',
95
+    '😭', '😠', '😈', '😧', '💔', '😟', '🙀',
96
+    '💩', '👿', '😡', '😾', '🖕'
97
+  ],
98
+  stripPastedStyles: false,
99
+  className: '',
100
+  style: {},
101
+  controlBarClassName: '',
102
+  controlBarStyle: {},
103
+  contentClassName: '',
104
+  contentStyle: {},
105
+  draftProps: {},
106
+  hooks: {},
107
+  onChange: null,
108
+  onFocus: null,
109
+  onBlur: null,
110
+  onTab: null,
111
+  onDelete: null,
112
+  onSave: null
113
+}

+ 381
- 0
source/braft-editor/src/editor/index.jsx View File

@@ -0,0 +1,381 @@
1
+import 'draft-js/dist/Draft.css'
2
+import 'assets/scss/_base.scss'
3
+import React from 'react'
4
+import languages from 'languages'
5
+import BraftFinder from 'braft-finder'
6
+import { ColorUtils, ContentUtils } from 'braft-utils'
7
+import { CompositeDecorator, DefaultDraftBlockRenderMap, Editor } from 'draft-js'
8
+import getKeyBindingFn from 'configs/keybindings'
9
+import defaultProps from 'configs/props'
10
+import { getBlockRendererFn, customBlockRenderMap, getBlockStyleFn, getCustomStyleMap, decorators } from 'renderers'
11
+import ControlBar from 'components/business/ControlBar'
12
+
13
+const buildHooks= (hooks) => (hookName, defaultReturns = {}) => {
14
+  return hooks[hookName] || (() => defaultReturns)
15
+}
16
+
17
+export const editorDecorators = new CompositeDecorator(decorators)
18
+
19
+export default class BraftEditor extends React.Component {
20
+
21
+  static defaultProps = defaultProps
22
+
23
+  constructor (props) {
24
+
25
+    super(props)
26
+
27
+    this.isFocused = false
28
+    this.keyBindingFn = getKeyBindingFn(props.customKeyBindingFn)
29
+    this.blockStyleFn = getBlockStyleFn(props.blockStyleFn)
30
+    this.blockRenderMap = DefaultDraftBlockRenderMap.merge(customBlockRenderMap)
31
+
32
+    if (props.blockRenderMapFn) {
33
+      this.blockRenderMap = props.blockRenderMapFn(this.blockRenderMap)
34
+    }
35
+
36
+    this.braftFinder = null
37
+    const defaultEditorState = ContentUtils.isEditorState(props.defaultValue) ? props.defaultValue : ContentUtils.createEmptyEditorState(editorDecorators)
38
+
39
+    this.state = {
40
+      containerNode: null,
41
+      tempColors: [],
42
+      editorState: defaultEditorState,
43
+      draftProps: {}
44
+    }
45
+
46
+  }
47
+
48
+  componentWillMount () {
49
+
50
+    const { controls, extendControls } = this.props
51
+
52
+    if ([...controls, ...extendControls].find(item => item === 'media' || item.key === 'media')) {
53
+      this.braftFinder = new BraftFinder({
54
+        language: this.props.language,
55
+        uploader: this.props.media.uploadFn,
56
+        validator: this.props.media.validateFn
57
+      })
58
+      this.forceUpdate()
59
+    }
60
+
61
+  }
62
+
63
+  componentDidMount () {
64
+
65
+    const { value: editorState } = this.props
66
+
67
+    if (ContentUtils.isEditorState(editorState)) {
68
+      this.setState({ editorState })
69
+    } else if (editorState) {
70
+      // console.warn('')
71
+    }
72
+
73
+  }
74
+
75
+  componentWillReceiveProps (nextProps) {
76
+
77
+    const { value: editorState } = nextProps
78
+
79
+    if (ContentUtils.isEditorState(editorState)) {
80
+      this.setState({ editorState })
81
+    } else if (editorState) {
82
+      // console.warn('')
83
+    }
84
+
85
+  }
86
+
87
+  onChange = (editorState) => {
88
+    this.setState({ editorState }, () => {
89
+      this.props.onChange && this.props.onChange(editorState)
90
+    })
91
+  }
92
+
93
+  getDraftInstance = () => {
94
+    return this.draftInstance
95
+  }
96
+
97
+  getFinderInstance = () => {
98
+    return this.braftFinder
99
+  }
100
+
101
+  getValue = () => {
102
+    return this.state.editorState
103
+  }
104
+
105
+  setValue = (editorState) => {
106
+    return this.onChange(editorState)
107
+  }
108
+
109
+  forceRender = () => {
110
+    return this.setValue(ContentUtils.createEditorState(this.state.editorState.getCurrentContent(), editorDecorators))
111
+  }
112
+
113
+  onTab = (event) => {
114
+
115
+    if (ContentUtils.getSelectionBlockType(this.state.editorState) === 'code-block') {
116
+      this.setValue(ContentUtils.insertText(this.state.editorState, ' '.repeat(this.props.tabIndents)))
117
+      event.preventDefault()
118
+      return false
119
+    }
120
+
121
+    this.props.onTab && this.props.onTab(event)
122
+
123
+  }
124
+
125
+  onFocus = () => {
126
+    this.isFocused = true
127
+    this.props.onFocus && this.props.onFocus()
128
+  }
129
+
130
+  onBlur = () => {
131
+    this.isFocused = false
132
+    this.props.onBlur && this.props.onBlur()
133
+  }
134
+
135
+  requestFocus = () => {
136
+    setTimeout(() => this.draftInstance.focus(), 0)
137
+  }
138
+
139
+  handleKeyCommand = (command) => {
140
+
141
+    if (command === 'braft-save') {
142
+      this.props.onSave && this.props.onSave()
143
+      return 'handled'
144
+    }
145
+
146
+    const nextEditorState = ContentUtils.handleKeyCommand(this.state.editorState, command)
147
+
148
+    if (nextEditorState) {
149
+      this.setValue(nextEditorState)
150
+      return 'handled'
151
+    }
152
+
153
+    return 'not-handled'
154
+
155
+  }
156
+
157
+  handleReturn = (event) => {
158
+
159
+    const currentBlock = ContentUtils.getSelectionBlock(this.state.editorState)
160
+    const currentBlockType = currentBlock.getType()
161
+
162
+    if (currentBlockType === 'unordered-list-item' || currentBlockType === 'ordered-list-item') {
163
+
164
+      if (currentBlock.getLength() === 0) {
165
+        this.setValue(ContentUtils.toggleSelectionBlockType(this.state.editorState, 'unstyled'))
166
+        return 'handled'
167
+      }
168
+
169
+      return 'not-handled'
170
+
171
+    } else if (currentBlockType === 'code-block') {
172
+
173
+      if (
174
+        event.which === 13 && (
175
+          event.getModifierState('Shift') ||
176
+          event.getModifierState('Alt') ||
177
+          event.getModifierState('Control')
178
+        )) {
179
+        this.setValue(ContentUtils.toggleSelectionBlockType(this.state.editorState, 'unstyled'))
180
+        return 'handled'
181
+      }
182
+
183
+      return 'not-handled'
184
+
185
+    } else {
186
+
187
+      const nextEditorState = ContentUtils.handleNewLine(this.state.editorState, event)
188
+
189
+      if (nextEditorState) {
190
+        this.setValue(nextEditorState)
191
+        return 'handled'
192
+      }
193
+
194
+      return 'not-handled'
195
+
196
+    }
197
+
198
+  }
199
+
200
+  handleDrop = (selectionState, dataTransfer) => {
201
+
202
+    if (window && window.__BRAFT_DRAGING__IMAGE__) {
203
+
204
+      let editorState = ContentUtils.removeBlock(this.state.editorState, window.__BRAFT_DRAGING__IMAGE__.block, selectionState)
205
+      editorState = ContentUtils.insertMedias(editorState, [window.__BRAFT_DRAGING__IMAGE__.mediaData])
206
+
207
+      window.__BRAFT_DRAGING__IMAGE__ = null
208
+
209
+      this.setDraftProps({ readOnly: false })
210
+      this.setValue(editorState)
211
+
212
+      return 'handled'
213
+
214
+    } else if (!dataTransfer || !dataTransfer.getText()) {
215
+      return 'handled'
216
+    }
217
+
218
+    return 'not-handled'
219
+
220
+  }
221
+
222
+  handleDroppedFiles = (selectionState, files) => {
223
+    return this.resolveFiles(files)
224
+  }
225
+
226
+  handlePastedFiles = (files) => {
227
+    return this.resolveFiles(files)
228
+  }
229
+
230
+  handlePastedText = (text, htmlString) => {
231
+
232
+    if (!htmlString || this.props.stripPastedStyles) {
233
+      return false
234
+    }
235
+
236
+    const tempColors = ColorUtils.detectColorsFromHTMLString(htmlString)
237
+
238
+    this.setState({
239
+      tempColors: [...this.state.tempColors, ...tempColors].filter(item => this.props.colors.indexOf(item) === -1).filter((item, index, array) => array.indexOf(item) === index)
240
+    }, () => {
241
+      this.setValue(ContentUtils.insertHTML(this.state.editorState, htmlString))
242
+    })
243
+
244
+    return true
245
+
246
+  }
247
+
248
+  resolveFiles = (files) => {
249
+
250
+    if (files[0] && files[0].type.indexOf('image') > -1 && this.props.media && this.props.media.pasteImage) {
251
+
252
+      this.braftFinder.uploadImage(files[0], image => {
253
+        this.setValue(ContentUtils.insertMedias(this.state.editorState, [image]))
254
+      })
255
+  
256
+      return 'handled'
257
+
258
+    }
259
+
260
+    return 'not-handled'
261
+
262
+  }
263
+
264
+  undo = () => {
265
+    this.setValue(ContentUtils.undo(this.state.editorState))
266
+  }
267
+
268
+  redo = () => {
269
+    this.setValue(ContentUtils.redo(this.state.editorState))
270
+  }
271
+
272
+  removeSelectionInlineStyles = () => {
273
+    this.setValue(ContentUtils.removeSelectionInlineStyles(this.state.editorState))
274
+  }
275
+
276
+  insertHorizontalLine = () => {
277
+    this.setValue(ContentUtils.insertHorizontalLine(this.state.editorState))
278
+  }
279
+
280
+  clearEditorContent = () => {
281
+    this.setValue(ContentUtils.clear(this.state.editorState))
282
+  }
283
+
284
+  render () {
285
+
286
+    let {
287
+      controls, excludeControls, extendControls, disabled, media, language, colors, hooks,
288
+      fontSizes, fontFamilies, emojis, placeholder, imageControls, lineHeights, letterSpacings, textIndents, textAligns, textBackgroundColor,
289
+      extendAtomics, className, style, controlBarClassName, controlBarStyle, contentClassName, contentStyle, stripPastedStyles, componentBelowControlBar
290
+    } = this.props
291
+
292
+    hooks = buildHooks(hooks)
293
+    controls = controls.filter(item => excludeControls.indexOf(item) === -1)
294
+    language = languages[language] || languages[defaultProps.language]
295
+
296
+    const externalMedias = media && media.externals ? {
297
+      ...defaultProps.media.externals,
298
+      ...media.externals
299
+    } : defaultProps.media.externals
300
+
301
+    media = { ...defaultProps.media, ...media, externalMedias }
302
+
303
+    if (!media.uploadFn) {
304
+      media.video = false
305
+      media.audio = false
306
+    }
307
+
308
+    const controlBarProps = {
309
+      editor: this,
310
+      editorState: this.state.editorState,
311
+      braftFinder: this.braftFinder,
312
+      ref: instance => this.controlBarInstance = instance,
313
+      containerNode: this.state.containerNode,
314
+      className: controlBarClassName,
315
+      style: controlBarStyle, hooks,
316
+      colors: [...colors, ...this.state.tempColors],
317
+      media, controls, language, extendControls, fontSizes, fontFamilies,
318
+      emojis, lineHeights, letterSpacings, textIndents, textAligns, textBackgroundColor
319
+    }
320
+
321
+    const blockRendererFn = getBlockRendererFn({
322
+      editor: this, hooks,
323
+      editorState: this.state.editorState,
324
+      containerNode: this.state.containerNode,
325
+      imageControls, language, extendAtomics
326
+    }, this.props.blockRendererFn)
327
+
328
+    const customStyleMap = getCustomStyleMap({
329
+      colors: [...colors, ...this.state.tempColors],
330
+      fontSizes, fontFamilies, lineHeights, letterSpacings, textIndents
331
+    }, this.props.customStyleMap)
332
+
333
+    const draftProps = {
334
+      ref: instance => { this.draftInstance = instance },
335
+      editorState: this.state.editorState,
336
+      handleKeyCommand: this.handleKeyCommand,
337
+      handleReturn: this.handleReturn,
338
+      handleDrop: this.handleDrop,
339
+      handleDroppedFiles: this.handleDroppedFiles,
340
+      handlePastedText: this.handlePastedText,
341
+      handlePastedFiles: this.handlePastedFiles,
342
+      onChange: this.onChange,
343
+      onTab: this.onTab,
344
+      onFocus: this.onFocus,
345
+      onBlur: this.onBlur,
346
+      readOnly: disabled,
347
+      blockRenderMap: this.blockRenderMap,
348
+      blockStyleFn: this.blockStyleFn,
349
+      keyBindingFn: this.keyBindingFn,
350
+      customStyleMap, blockRendererFn,
351
+      placeholder, stripPastedStyles,
352
+      ...this.props.draftProps,
353
+      ...this.state.draftProps
354
+    }
355
+
356
+    return (
357
+      <div ref={this.setEditorContainerNode} className={`bf-container ${className} ${(disabled ? 'disabled' : '')}`} style={style}>
358
+        <ControlBar {...controlBarProps} />
359
+        {componentBelowControlBar}
360
+        <div className={`bf-content ${contentClassName}`} style={contentStyle}>
361
+          <Editor {...draftProps} />
362
+        </div>
363
+      </div>
364
+    )
365
+
366
+  }
367
+
368
+  setDraftProps (draftProps) {
369
+    this.setState({
370
+      draftProps: {
371
+        ...this.state.draftProps,
372
+        ...draftProps
373
+      }
374
+    })
375
+  }
376
+
377
+  setEditorContainerNode = (containerNode) => {
378
+    this.setState({ containerNode }, this.forceRender)
379
+  }
380
+
381
+}

+ 35
- 0
source/braft-editor/src/helpers/Responsive.js View File

@@ -0,0 +1,35 @@
1
+import { BaseUtils } from 'braft-utils'
2
+
3
+let resizeEventHandlers = []
4
+let responsiveHelperInited = false
5
+let debouce = false
6
+
7
+export default {
8
+
9
+  resolve (eventHandler) {
10
+    let id = BaseUtils.UniqueIndex()
11
+    resizeEventHandlers.push({ id, eventHandler })
12
+    return id
13
+  },
14
+
15
+  unresolve (id) {
16
+    resizeEventHandlers = resizeEventHandlers.filter(item => item.id !== id)
17
+  }
18
+
19
+}
20
+
21
+if (!responsiveHelperInited) {
22
+
23
+  window.addEventListener('resize', (event) => {
24
+    clearTimeout(debouce)
25
+    debouce = setTimeout(() => {
26
+      resizeEventHandlers.map((item) => {
27
+        typeof item.eventHandler === 'function' && item.eventHandler(event)
28
+      })
29
+      debouce = false
30
+    }, 100)
31
+  })
32
+
33
+  responsiveHelperInited = true
34
+
35
+}

+ 70
- 0
source/braft-editor/src/index.jsx View File

@@ -0,0 +1,70 @@
1
+import BraftEditor, { editorDecorators } from './editor'
2
+import { EditorState } from 'draft-js'
3
+import { convertRawToEditorState, convertHTMLToEditorState, convertEditorStateToRaw, convertEditorStateToHTML } from 'braft-convert'
4
+
5
+// 为EditorState对象增加toHTML原型方法,用于将editorState转换成HTML字符串
6
+EditorState.prototype.toHTML = function () {
7
+  return convertEditorStateToHTML(this)
8
+}
9
+
10
+// 为EditorState对象增加toRAW原型方法,用于将editorState转换成RAW JSON对象或RAW JSON字符串
11
+EditorState.prototype.toRAW = function (noStringify) {
12
+  return noStringify ? convertEditorStateToRaw(this) : JSON.stringify(convertEditorStateToRaw(this))
13
+}
14
+
15
+// 为EditorState对象增加新的静态方法,用于从raw或者html内容创建ediorState
16
+EditorState.createFrom = (content, options) => {
17
+
18
+  if (typeof content === 'object' && content && content.blocks && content.entityMap) {
19
+    return convertRawToEditorState(content, editorDecorators)
20
+  } else if (typeof content === 'string') {
21
+    try {
22
+      return EditorState.createFrom(JSON.parse(content), options)
23
+    } catch (error) {
24
+      return convertHTMLToEditorState(content, editorDecorators, options)
25
+    }
26
+  } else {
27
+    return EditorState.createEmpty(editorDecorators)
28
+  }
29
+
30
+}
31
+
32
+export default BraftEditor
33
+export { EditorState, editorDecorators }
34
+
35
+// 2.0.0开发计划
36
+// [ ]完善各模块文档说明
37
+// [√]添加更多钩子(插入链接、切换样式等)
38
+// [√]优化内置的图片伪上传功能,用base64代替blob
39
+// [√]支持自定义图片工具栏按钮
40
+// [√]支持通过属性扩展customStyleMap, blockStyleFn, keyBindingFn, blockRendererFn, blockRenderMap等
41
+// [√]允许完全设置控制栏的按钮(['media', { key: 'blod', text: 'xxx' })
42
+// [√]允许在工具栏和内容区域直接插入自定义的组件[componentBelowControlBar]
43
+// [√]支持定义DropDown组件的样式
44
+// [√]media.validateFn支持异步函数
45
+// [√]优化音视频在编辑器内的预览体验
46
+// [√]标准化代码,引入ESLint
47
+// ---------------------------
48
+// 优化全选会选择上传中的项目的问题
49
+// 支持param.success时设置媒体文件的更多属性(尺寸等)
50
+
51
+// 2.1.0版本开发计划
52
+// [ ]美化UI,包括图标和界面风格
53
+// [ ]优化控件title提示
54
+
55
+// 2.2.0版本开发计划
56
+// [ ]允许自定义快捷键
57
+// [ ]优化图片param.success,支持传入link等
58
+// [ ]简化上传配置流程
59
+// [ ]支持draftjs插件机制
60
+// [ ]支持媒体库组件的更多个性化配置(placeholder等)
61
+// [ ]支持非媒体类附件
62
+// [ ]优化HTML格式无法存储媒体名称的问题 
63
+// [ ]完成font-size等样式的全量支持
64
+
65
+// 2.3.0版本开发计划
66
+// [ ]优化换行与空格
67
+// [ ]支持自定义Atomic组件
68
+// [ ]图片裁切等简单的编辑功能
69
+// [ ]代码块交互强化
70
+// [ ]初级表格功能

+ 70
- 0
source/braft-editor/src/languages/en.js View File

@@ -0,0 +1,70 @@
1
+export default {
2
+  base: {
3
+    remove: 'Remove',
4
+    cancel: 'Cancel',
5
+    confirm: 'Confirm',
6
+    inert: 'Insert',
7
+    width: 'Width',
8
+    height: 'Height'
9
+  },
10
+  controls: {
11
+    clear: 'Clear',
12
+    undo: 'Undo',
13
+    redo: 'Redo',
14
+    fontSize: 'Font Size',
15
+    color: 'Color',
16
+    textColor: 'Text',
17
+    tempColors: 'Temp Colors',
18
+    backgroundColor: 'Background',
19
+    bold: 'Bold',
20
+    lineHeight:'Line Height',
21
+    letterSpacing:'Letter Spacing',
22
+    textIndent:'Indent at both ends',
23
+    italic: 'Italic',
24
+    underline: 'Underline',
25
+    strikeThrough: 'Strike Through',
26
+    fontFamily: 'Font Family',
27
+    textAlign: 'Text Alignment',
28
+    alignLeft: 'Left Alignment',
29
+    alignCenter: 'Center Alignment',
30
+    alignRight: 'Right Alignment',
31
+    alignJustify: 'Justify Alignment',
32
+    floatLeft: 'Left Float',
33
+    floatRight: 'Right Float',
34
+    superScript: 'Super Script',
35
+    subScript: 'Sub Script',
36
+    removeStyles: 'Remove Styles',
37
+    headings: 'Headings',
38
+    header: 'Header',
39
+    normal: 'Normal',
40
+    orderedList: 'Ordered List',
41
+    unorderedList: 'Unordered List',
42
+    blockQuote: 'Quote',
43
+    code: 'Code',
44
+    link: 'Link',
45
+    unlink: 'Unlink',
46
+    hr: 'Horizontal Line',
47
+    media: 'Media',
48
+    mediaLibirary: 'Media Libirary',
49
+    emoji: 'Emoji'
50
+  },
51
+  linkEditor: {
52
+    inputPlaceHolder: 'Input link URL',
53
+    inputWithEnterPlaceHolder: 'Input link URL and press Enter',
54
+    openInNewWindow: 'Open in new window',
55
+    removeLink: 'Remove Link'
56
+  },
57
+  audioPlayer: {
58
+    title: 'Play Audio'
59
+  },
60
+  videoPlayer: {
61
+    title: 'Play Video',
62
+    embedTitle: 'Embed Media'
63
+  },
64
+  media: {
65
+    image: 'Image',
66
+    video: 'Video',
67
+    audio: 'Audio',
68
+    embed: 'Embed'
69
+  }
70
+}

+ 9
- 0
source/braft-editor/src/languages/index.js View File

@@ -0,0 +1,9 @@
1
+import en from './en'
2
+import zh from './zh'
3
+import zhHant from './zh-hant'
4
+
5
+export default {
6
+  'en': en,
7
+  'zh': zh,
8
+  'zh-hant': zhHant
9
+}

+ 71
- 0
source/braft-editor/src/languages/zh-hant.js View File

@@ -0,0 +1,71 @@
1
+export default {
2
+  base: {
3
+    remove: '刪除',
4
+    cancel: '取消',
5
+    confirm: '確定',
6
+    inert: '插入',
7
+    width: '宽度',
8
+    height: '高度'
9
+  },
10
+  controls: {
11
+    clear: '清除内容',
12
+    undo: '撤銷',
13
+    redo: '重做',
14
+    fontSize: '字號',
15
+    color: '顏色',
16
+    textColor: '文字顏色',
17
+    backgroundColor: '背景顏色',
18
+    tempColors: '臨時顏色',
19
+    bold: '加粗',
20
+    lineHeight: '行高',
21
+    letterSpacing: '字間距',
22
+    textIndent: '兩端縮進',
23
+    border: '邊框',
24
+    italic: '斜體',
25
+    underline: '下劃線',
26
+    strikeThrough: '刪除線',
27
+    fontFamily: '字體',
28
+    textAlign: '文本對齊',
29
+    alignLeft: '居左',
30
+    alignCenter: '居中',
31
+    alignRight: '居右',
32
+    alignJustify: '兩端對齊',
33
+    floatLeft: '左浮動',
34
+    floatRight: '右浮動',
35
+    superScript: '上標',
36
+    subScript: '下標',
37
+    removeStyles: '清除样式',
38
+    headings: '標題',
39
+    header: '標題',
40
+    normal: '常規',
41
+    orderedList: '有序列表',
42
+    unorderedList: '無序列表',
43
+    blockQuote: '引用',
44
+    code: '代碼',
45
+    link: '鏈接',
46
+    unlink: '清除鏈接',
47
+    hr: '水平线',
48
+    media: '媒體',
49
+    mediaLibirary: '媒體库',
50
+    emoji: '小表情'
51
+  },
52
+  linkEditor: {
53
+    inputPlaceHolder: '輸入鏈接地址',
54
+    inputWithEnterPlaceHolder: '輸入鏈接地址並回車',
55
+    openInNewWindow: '在新窗口打開',
56
+    removeLink: '移除鏈接'
57
+  },
58
+  audioPlayer: {
59
+    title: '播放音頻文件'
60
+  },
61
+  videoPlayer: {
62
+    title: '播放視頻文件',
63
+    embedTitle: '嵌入式媒體'
64
+  },
65
+  media: {
66
+    image: '圖像',
67
+    video: '視頻',
68
+    audio: '音頻',
69
+    embed: '嵌入式媒體'
70
+  }
71
+}

+ 71
- 0
source/braft-editor/src/languages/zh.js View File

@@ -0,0 +1,71 @@
1
+export default {
2
+  base: {
3
+    remove: '删除',
4
+    cancel: '取消',
5
+    confirm: '确定',
6
+    inert: '插入',
7
+    width: '宽度',
8
+    height: '高度'
9
+  },
10
+  controls: {
11
+    clear: '清除内容',
12
+    undo: '撤销',
13
+    redo: '重做',
14
+    fontSize: '字号',
15
+    lineHeight: '行高',
16
+    letterSpacing: '字间距',
17
+    textIndent: '两端缩进',
18
+    border: '边框',
19
+    color: '颜色',
20
+    textColor: '文字颜色',
21
+    backgroundColor: '背景颜色',
22
+    tempColors: '临时颜色',
23
+    bold: '加粗',
24
+    italic: '斜体',
25
+    underline: '下划线',
26
+    strikeThrough: '删除线',
27
+    fontFamily: '字体',
28
+    textAlign: '文本对齐',
29
+    alignLeft: '居左',
30
+    alignCenter: '居中',
31
+    alignRight: '居右',
32
+    alignJustify: '两端',
33
+    floatLeft: '左浮动',
34
+    floatRight: '右浮动',
35
+    superScript: '上标',
36
+    subScript: '下标',
37
+    removeStyles: '清除样式',
38
+    headings: '标题',
39
+    header: '标题',
40
+    normal: '常规',
41
+    orderedList: '有序列表',
42
+    unorderedList: '无序列表',
43
+    blockQuote: '引用',
44
+    code: '代码',
45
+    link: '链接',
46
+    unlink: '清除链接',
47
+    hr: '水平线',
48
+    media: '媒体',
49
+    mediaLibirary: '媒体库',
50
+    emoji: '小表情'
51
+  },
52
+  linkEditor: {
53
+    inputPlaceHolder: '输入链接地址',
54
+    inputWithEnterPlaceHolder: '输入链接地址并回车',
55
+    openInNewWindow: '在新窗口打开',
56
+    removeLink: '移除链接'
57
+  },
58
+  audioPlayer: {
59
+    title: '播放音频文件'
60
+  },
61
+  videoPlayer: {
62
+    title: '播放视频文件',
63
+    embedTitle: '嵌入式媒体'
64
+  },
65
+  media: {
66
+    image: '图像',
67
+    video: '视频',
68
+    audio: '音频',
69
+    embed: '嵌入式媒体'
70
+  }
71
+}

+ 53
- 0
source/braft-editor/src/renderers/atomics/Audio/index.jsx View File

@@ -0,0 +1,53 @@
1
+import './style.scss'
2
+import React from 'react'
3
+import StaticContainer from 'components/common/StaticContainer'
4
+import { ContentUtils } from 'braft-utils'
5
+
6
+export default class Audio extends React.Component {
7
+
8
+  state = {
9
+    toolbarVisible: false,
10
+  }
11
+
12
+  render () {
13
+
14
+    const { toolbarVisible } = this.state
15
+    const { mediaData } = this.props
16
+    const { src, url } = mediaData
17
+
18
+    return (
19
+      <div
20
+        className='bf-audio'
21
+        onMouseOver={this.showToolbar}
22
+        onMouseLeave={this.hideToolbar}
23
+      >
24
+        <StaticContainer>
25
+          <audio controls src={src || url}/>
26
+        </StaticContainer>
27
+        {toolbarVisible ? (
28
+          <div className='bf-media-toolbar'>
29
+            <a onClick={this.removeAudio}>&#xe9ac;</a>
30
+          </div>
31
+        ) : null}
32
+      </div>
33
+    )
34
+
35
+  }
36
+
37
+  removeAudio = () => {
38
+    this.props.editor.setValue(ContentUtils.removeBlock(this.props.editorState, this.props.block))
39
+  }
40
+
41
+  showToolbar = () => {
42
+    this.setState({
43
+      toolbarVisible: true
44
+    })
45
+  }
46
+
47
+  hideToolbar = () => {
48
+    this.setState({
49
+      toolbarVisible: false
50
+    })
51
+  }
52
+
53
+}

+ 12
- 0
source/braft-editor/src/renderers/atomics/Audio/style.scss View File

@@ -0,0 +1,12 @@
1
+@import '~scssinc';
2
+
3
+.bf-audio{
4
+  position: relative;
5
+  display: block;
6
+  margin: 10px 0;
7
+  background-color: #f1f2f3;
8
+  audio{
9
+    display: block;
10
+    width: 100%;
11
+  }
12
+}

+ 53
- 0
source/braft-editor/src/renderers/atomics/Embed/index.jsx View File

@@ -0,0 +1,53 @@
1
+import './style.scss'
2
+import React from 'react'
3
+import StaticContainer from 'components/common/StaticContainer'
4
+import { ContentUtils } from 'braft-utils'
5
+
6
+export default class Embed extends React.Component {
7
+
8
+  state = {
9
+    toolbarVisible: false,
10
+  }
11
+
12
+  render () {
13
+
14
+    const { toolbarVisible } = this.state
15
+    const { mediaData } = this.props
16
+    const { src, url } = mediaData
17
+
18
+    return (
19
+      <div
20
+        className='bf-embed'
21
+        onMouseOver={this.showToolbar}
22
+        onMouseLeave={this.hideToolbar}
23
+      >
24
+        <StaticContainer>
25
+          <div className='bf-embed-player' dangerouslySetInnerHTML={{ __html: src || url}}></div>
26
+        </StaticContainer>
27
+        {toolbarVisible ? (
28
+          <div className='bf-media-toolbar'>
29
+            <a onClick={this.removeEmbed}>&#xe9ac;</a>
30
+          </div>
31
+        ) : null}
32
+      </div>
33
+    )
34
+
35
+  }
36
+
37
+  removeEmbed = () => {
38
+    this.props.editor.setValue(ContentUtils.removeBlock(this.props.editorState, this.props.block))
39
+  }
40
+
41
+  showToolbar = () => {
42
+    this.setState({
43
+      toolbarVisible: true
44
+    })
45
+  }
46
+
47
+  hideToolbar = () => {
48
+    this.setState({
49
+      toolbarVisible: false
50
+    })
51
+  }
52
+
53
+}

+ 13
- 0
source/braft-editor/src/renderers/atomics/Embed/style.scss View File

@@ -0,0 +1,13 @@
1
+@import '~scssinc';
2
+
3
+.bf-embed{
4
+  position: relative;
5
+  display: block;
6
+  margin: 10px 0;
7
+  overflow: hidden;
8
+  background-color: #f1f2f3;
9
+  iframe, embed{
10
+    width: 100%;
11
+    margin: 0 auto;
12
+  }
13
+}

+ 23
- 0
source/braft-editor/src/renderers/atomics/HorizontalLine/index.jsx View File

@@ -0,0 +1,23 @@
1
+import React from 'react'
2
+import { ContentUtils } from 'braft-utils'
3
+import './style.scss'
4
+
5
+export default class HorizontalLine extends React.Component {
6
+
7
+  render () {
8
+
9
+    return (
10
+      <div className='bf-hr'>
11
+        <div className='bf-media-toolbar'>
12
+          <a onClick={this.removeHorizontalLine}>&#xe9ac;</a>
13
+        </div>
14
+      </div>
15
+    )
16
+
17
+  }
18
+
19
+  removeHorizontalLine = () => {
20
+    this.props.editor.setValue(ContentUtils.removeBlock(this.props.editorState, this.props.block))
21
+  }
22
+
23
+}

+ 26
- 0
source/braft-editor/src/renderers/atomics/HorizontalLine/style.scss View File

@@ -0,0 +1,26 @@
1
+@import '~scssinc';
2
+
3
+.bf-hr{
4
+  position: relative;
5
+  box-sizing: content-box;
6
+  height: 15px;
7
+  padding-top: 15px;
8
+  text-align: center;
9
+  &::before{
10
+    display: block;
11
+    height: 1px;
12
+    background-color: rgba(#000, .1);
13
+    content: '';
14
+  }
15
+  &:hover {
16
+    &::before {
17
+      background-color: rgba(#000, .3);
18
+    }
19
+    .bf-media-toolbar{
20
+      display: block;
21
+    }
22
+  }
23
+  .bf-media-toolbar{
24
+    display: none;
25
+  }
26
+}

+ 324
- 0
source/braft-editor/src/renderers/atomics/Image/index.jsx View File

@@ -0,0 +1,324 @@
1
+import './style.scss'
2
+import React from 'react'
3
+import { ContentUtils } from 'braft-utils'
4
+import Switch from 'components/common/Switch'
5
+import { imageControlItems } from 'configs/controls'
6
+
7
+export default class Image extends React.Component {
8
+
9
+  state = {
10
+    toolbarVisible: false,
11
+    toolbarOffset: 0,
12
+    linkEditorVisible: false,
13
+    sizeEditorVisible: false,
14
+    tempLink: null,
15
+    tempWidth: null,
16
+    tempHeight: null
17
+  }
18
+
19
+  render () {
20
+
21
+    const { mediaData, language, imageControls } = this.props
22
+    const { toolbarVisible, toolbarOffset, linkEditorVisible, sizeEditorVisible } = this.state
23
+    const blockData = this.props.block.getData()
24
+
25
+    let float = blockData.get('float')
26
+    let alignment = blockData.get('alignment')
27
+    let { src, url, link, link_target, width, height } = mediaData
28
+    let imageStyles = {}
29
+    let clearFix = false
30
+
31
+    if (float) {
32
+      alignment = null
33
+    } else if (alignment === 'left') {
34
+      imageStyles.float = 'left'
35
+      clearFix = true
36
+    } else if (alignment === 'right') {
37
+      imageStyles.float = 'right'
38
+      clearFix = true
39
+    } else if (alignment === 'center') {
40
+      imageStyles.textAlign = 'center'
41
+    } else {
42
+      imageStyles.float = 'left'
43
+      clearFix = true
44
+    }
45
+
46
+    const renderedControlItems = imageControls.map((item, index) => {
47
+
48
+      if (typeof item === 'string' && imageControlItems[item]) {
49
+        return (
50
+          <a className={item === 'link' && link ? 'active' : ''} key={index} href='javascript:void(0);' onClick={() => this.executeCommand(imageControlItems[item].command)}>
51
+            {imageControlItems[item].text}
52
+          </a>
53
+        )
54
+      } else if (item && item.onClick && (item.render || item.text)) {
55
+        return (
56
+          <a key={index} href='javascript:void(0);' onClick={() => this.executeCommand(item.onClick)}>
57
+            {item.render ? item.render(mediaData) : item.text}
58
+          </a>
59
+        )
60
+      } else {
61
+        return null
62
+      }
63
+
64
+    })
65
+
66
+    return (
67
+      <div className='bf-media'>
68
+        <div
69
+          style={imageStyles}
70
+          draggable={true}
71
+          onMouseEnter={this.showToolbar}
72
+          onMouseMove={this.showToolbar}
73
+          onMouseLeave={this.hideToolbar}
74
+          onDragStart={this.handleDragStart}
75
+          onDragEnd={this.handleDragEnd}
76
+          ref={instance => this.mediaEmbederInstance = instance}
77
+          className='bf-image'
78
+        >
79
+          {toolbarVisible ? (
80
+            <div
81
+              style={{marginLeft: toolbarOffset}}
82
+              ref={instance => this.toolbarElement = instance}
83
+              data-float={float}
84
+              data-align={alignment}
85
+              className='bf-media-toolbar'
86
+            >
87
+              {linkEditorVisible ? (
88
+                <div className='bf-image-link-editor'>
89
+                  <div className='editor-input-group'>
90
+                    <input type='text' placeholder={language.linkEditor.inputWithEnterPlaceHolder} onKeyDown={this.handleLinkInputKeyDown} onChange={this.setImageLink} defaultValue={link}/>
91
+                    <button type='button' onClick={this.confirmImageLink}>{language.base.confirm}</button>
92
+                  </div>
93
+                  <div className='switch-group'>
94
+                    <Switch
95
+                      active={link_target === '_blank'}
96
+                      onClick={() => this.setImageLinkTarget(link_target)}
97
+                    />
98
+                    <label>{language.linkEditor.openInNewWindow}</label>
99
+                  </div>
100
+                </div>
101
+              ) : null}
102
+              {sizeEditorVisible ? (
103
+                <div className='bf-image-size-editor'>
104
+                  <div className='editor-input-group'>
105
+                    <input type='text' placeholder={language.base.width} onKeyDown={this.handleSizeInputKeyDown} onChange={this.setImageWidth} defaultValue={width}/>
106
+                    <input type='text' placeholder={language.base.height} onKeyDown={this.handleSizeInputKeyDown} onChange={this.setImageHeight} defaultValue={height}/>
107
+                    <button type='button' onClick={this.confirmImageSize}>{language.base.confirm}</button>
108
+                  </div>
109
+                </div>
110
+              ) : null}
111
+              {renderedControlItems}
112
+              <i style={{marginLeft: toolbarOffset * -1}} className='bf-media-toolbar-arrow'></i>
113
+            </div>
114
+          ) : null}
115
+          <img
116
+            ref={instance => this.imageElement = instance}
117
+            src={src || url} style={{width, height}} width={width} height={height}
118
+          />
119
+        </div>
120
+        {clearFix && <div className='clearfix' style={{clear:'both',height:0,lineHeight:0,float:'none'}}></div>}
121
+      </div>
122
+    )
123
+
124
+  }
125
+
126
+  calcToolbarOffset () {
127
+
128
+    if (!this.props.containerNode) {
129
+      return 0
130
+    }
131
+
132
+    const viewRect = this.props.containerNode.getBoundingClientRect()
133
+    const toolbarRect = this.toolbarElement.getBoundingClientRect()
134
+    const imageRect = this.imageElement.getBoundingClientRect()
135
+
136
+    const right = viewRect.right - (imageRect.right - imageRect.width / 2 + toolbarRect.width / 2)
137
+    const left = (imageRect.left + imageRect.width / 2 - toolbarRect.width / 2) - viewRect.left
138
+
139
+    if (right < 10) {
140
+      return right - 10
141
+    } else if (left < 10) {
142
+      return left * -1 + 10
143
+    } else {
144
+      return 0
145
+    }
146
+
147
+  }
148
+
149
+  handleDragStart = () => {
150
+
151
+    window.__BRAFT_DRAGING__IMAGE__ = {
152
+      block: this.props.block,
153
+      mediaData: {
154
+        type: 'IMAGE',
155
+        ...this.props.mediaData
156
+      }
157
+    }
158
+
159
+    this.setState({
160
+      toolbarVisible: false
161
+    }, () => {
162
+      this.props.editor.setDraftProps({ readOnly: false })
163
+    })
164
+
165
+    return true
166
+
167
+  }
168
+
169
+  handleDragEnd = () => {
170
+
171
+    window.__BRAFT_DRAGING__IMAGE__ = null
172
+    return false
173
+
174
+  }
175
+
176
+  executeCommand = (command) => {
177
+    if (typeof command === 'string') {
178
+      const [method, param] = command.split('|')
179
+      this[method] && this[method](param)
180
+    } else if (typeof command === 'function') {
181
+      command(this.props.block, this.props.editorState)
182
+    }
183
+  }
184
+
185
+  removeImage = () => {
186
+    this.props.editor.setValue(ContentUtils.removeBlock(this.props.editorState, this.props.block))
187
+    this.props.editor.setDraftProps({ readOnly: false })
188
+  }
189
+
190
+  toggleLinkEditor = () => {
191
+    this.setState({
192
+      linkEditorVisible: !this.state.linkEditorVisible,
193
+      sizeEditorVisible: false
194
+    })
195
+  }
196
+
197
+  toggleSizeEditor = () => {
198
+    this.setState({
199
+      linkEditorVisible: false,
200
+      sizeEditorVisible: !this.state.sizeEditorVisible
201
+    })
202
+  }
203
+
204
+  handleLinkInputKeyDown = (e) => {
205
+
206
+    if (e.keyCode === 13) {
207
+      this.confirmImageLink()
208
+    } else {
209
+      return
210
+    }
211
+
212
+  }
213
+
214
+  setImageLink = (e) => {
215
+    this.setState({ tempLink: e.currentTarget.value })
216
+    return
217
+  }
218
+
219
+  setImageLinkTarget (link_target) {
220
+
221
+    link_target = link_target === '_blank' ? '' : '_blank'
222
+    this.props.editor.setValue(ContentUtils.setMediaData(this.props.editorState, this.props.entityKey, { link_target }))
223
+    window.setImmediate(this.props.editor.forceRender)
224
+
225
+  }
226
+
227
+  confirmImageLink = () => {
228
+
229
+    const { tempLink: link } = this.state
230
+
231
+    if (link !== null) {
232
+      this.props.editor.setValue(ContentUtils.setMediaData(this.props.editorState, this.props.entityKey, { link }))
233
+      window.setImmediate(this.props.editor.forceRender)
234
+    }
235
+
236
+  }
237
+
238
+  handleSizeInputKeyDown = (e) => {
239
+    if (e.keyCode === 13) {
240
+      this.confirmImageSize()
241
+    } else {
242
+      return
243
+    }
244
+  }
245
+
246
+  setImageWidth = ({ currentTarget }) => {
247
+
248
+    let { value } = currentTarget
249
+
250
+    value && !isNaN(value) && (value = value + 'px')
251
+
252
+    this.setState({
253
+      tempWidth: value
254
+    })
255
+
256
+    return
257
+
258
+  }
259
+
260
+  setImageHeight = ({ currentTarget }) => {
261
+
262
+    let { value } = currentTarget
263
+
264
+    value && !isNaN(value) && (value = value + 'px')
265
+
266
+    this.setState({
267
+      tempHeight: value
268
+    })
269
+
270
+    return
271
+
272
+  }
273
+
274
+  confirmImageSize = () => {
275
+
276
+    const { tempWidth: width, tempHeight: height } = this.state
277
+    const newImageSize = {}
278
+
279
+    width !== null && (newImageSize.width = width)
280
+    height !== null && (newImageSize.height = height)
281
+
282
+    this.props.editor.setValue(ContentUtils.setMediaData(this.props.editorState, this.props.entityKey, newImageSize))
283
+    window.setImmediate(this.props.editor.forceRender)
284
+
285
+  }
286
+
287
+  setImageFloat = (float) => {
288
+    this.props.editor.setValue(ContentUtils.setMediaPosition(this.props.editorState, this.props.block, { float }))
289
+    this.props.editor.setDraftProps({ readOnly: false })
290
+  }
291
+
292
+  setImageAlignment = (alignment) => {
293
+    this.props.editor.setValue(ContentUtils.setMediaPosition(this.props.editorState, this.props.block, { alignment }))
294
+    this.props.editor.setDraftProps({ readOnly: false })
295
+  }
296
+
297
+  showToolbar = (event) => {
298
+
299
+    event.preventDefault()
300
+
301
+    if (!this.state.toolbarVisible) {
302
+      this.setState({
303
+        toolbarVisible: true
304
+      }, () => {
305
+        this.props.editor.setDraftProps({ readOnly: true })
306
+        this.setState({ toolbarOffset: this.calcToolbarOffset() })
307
+      })
308
+    }
309
+
310
+  }
311
+
312
+  hideToolbar = (event) => {
313
+
314
+    event.preventDefault()
315
+
316
+    this.setState({
317
+      toolbarVisible: false
318
+    }, () => {
319
+      this.props.editor.setDraftProps({ readOnly: false })
320
+    })
321
+
322
+  }
323
+
324
+}

+ 89
- 0
source/braft-editor/src/renderers/atomics/Image/style.scss View File

@@ -0,0 +1,89 @@
1
+@import '~scssinc';
2
+.bf-image-size-editor,
3
+.bf-image-link-editor{
4
+  padding-bottom: 1px;
5
+  overflow: hidden;
6
+  border-radius: 2px 2px 0 0;
7
+  box-shadow: inset 0 -1px 0 0 rgba(#fff, .1);
8
+  .editor-input-group{
9
+    width: 300px;
10
+    margin: 8px 10px;
11
+    overflow: hidden;
12
+  }
13
+  input{
14
+    display: block;
15
+    float: left;
16
+    box-sizing: content-box;
17
+    height: 32px;
18
+    margin: 0 5px 0 0;
19
+    padding: 0 10px;
20
+    background-color: rgba(#fff, .1);
21
+    border: none;
22
+    border-radius: 2px;
23
+    outline: none;
24
+    box-shadow: inset 0 0 0 1px rgba(#fff, .1);
25
+    color: #fff;
26
+    font-weight: bold;
27
+    &:hover{
28
+      box-shadow: inset 0 0 0 1px rgba($COLOR_ACTIVE, .5);
29
+    }
30
+    &:focus{
31
+      box-shadow: inset 0 0 0 1px rgba($COLOR_ACTIVE, 1);
32
+    }
33
+  }
34
+  button{
35
+    float: left;
36
+    width: 60px;
37
+    height: 32px;
38
+    margin: 0;
39
+    padding: 0 20px;
40
+    background-color: $COLOR_ACTIVE;
41
+    border: none;
42
+    color: #fff;
43
+    font-size: 12px;
44
+    border-radius: 2px;
45
+    cursor: pointer;
46
+    &:hover{
47
+      background-color: $COLOR_ACTIVE - 20;
48
+    }
49
+  }
50
+}
51
+.bf-image-size-editor{
52
+  input{
53
+    width: 95px;      
54
+  }
55
+}
56
+.bf-image-link-editor{
57
+  input{
58
+    width: 215px;
59
+  }
60
+  .switch-group{
61
+    height: 20px;
62
+    margin: 10px 10px;
63
+    .bf-switch{
64
+      float: left;
65
+    }
66
+    label{
67
+      float: left;
68
+      margin-left: 15px;
69
+      color: #999;
70
+      font-size: 12px;
71
+      line-height: 20px;      
72
+    }
73
+  }
74
+}
75
+
76
+.bf-image{
77
+  .bf-media-toolbar{
78
+    &::before{
79
+      visibility: hidden;
80
+    }
81
+    &[data-float="left"] [data-float="left"],
82
+    &[data-float="right"] [data-float="right"],
83
+    &[data-align="left"] [data-align="left"],
84
+    &[data-align="center"] [data-align="center"],
85
+    &[data-align="right"] [data-align="right"] {
86
+      color: $COLOR_ACTIVE;
87
+    }
88
+  }
89
+}

+ 53
- 0
source/braft-editor/src/renderers/atomics/Video/index.jsx View File

@@ -0,0 +1,53 @@
1
+import './style.scss'
2
+import React from 'react'
3
+import StaticContainer from 'components/common/StaticContainer'
4
+import { ContentUtils } from 'braft-utils'
5
+
6
+export default class Video extends React.Component {
7
+
8
+  state = {
9
+    toolbarVisible: false,
10
+  }
11
+
12
+  render () {
13
+
14
+    const { toolbarVisible } = this.state
15
+    const { mediaData } = this.props
16
+    const { src, url, meta } = mediaData
17
+
18
+    return (
19
+      <div
20
+        className='bf-video'
21
+        onMouseOver={this.showToolbar}
22
+        onMouseLeave={this.hideToolbar}
23
+      >
24
+        <StaticContainer>
25
+          <video controls src={src || url} poster={meta.poster}/>
26
+        </StaticContainer>
27
+        {toolbarVisible ? (
28
+          <div className='bf-embed-toolbar'>
29
+            <a onClick={this.removeVideo}>&#xe9ac;</a>
30
+          </div>
31
+        ) : null}
32
+      </div>
33
+    )
34
+
35
+  }
36
+
37
+  removeVideo = () => {
38
+    this.props.editor.setValue(ContentUtils.removeBlock(this.props.editorState, this.props.block))
39
+  }
40
+
41
+  showToolbar = () => {
42
+    this.setState({
43
+      toolbarVisible: true
44
+    })
45
+  }
46
+
47
+  hideToolbar = () => {
48
+    this.setState({
49
+      toolbarVisible: false
50
+    })
51
+  }
52
+
53
+}

+ 16
- 0
source/braft-editor/src/renderers/atomics/Video/style.scss View File

@@ -0,0 +1,16 @@
1
+@import '~scssinc';
2
+
3
+.bf-video{
4
+  position: relative;
5
+  display: block;
6
+  height: 480px;
7
+  margin: 10px 0;
8
+  overflow: hidden;
9
+  video{
10
+    display: block;
11
+    width: 100%;
12
+    height: 480px;
13
+    background-color: #000;
14
+    object-fit: contain;
15
+  }
16
+}

+ 40
- 0
source/braft-editor/src/renderers/decorators/Link/index.jsx View File

@@ -0,0 +1,40 @@
1
+import React from 'react'
2
+
3
+function handleStrategy (contentBlock, callback, contentState) {
4
+
5
+  contentBlock.findEntityRanges((character) => {
6
+    const entityKey = character.getEntity()
7
+    return (
8
+      entityKey !== null &&
9
+      contentState.getEntity(entityKey).getType() === 'LINK'
10
+    )
11
+  }, callback)
12
+
13
+}
14
+
15
+const Link = (props) => {
16
+
17
+  const { children, entityKey, contentState } = props
18
+  const { href, target } = contentState.getEntity(entityKey).getData()
19
+
20
+  return (
21
+    <span className='bf-link-wrap'>
22
+      <a onClick={(event) => viewLink(event, href)} className='bf-link' href={href} target={target}>{children}</a>
23
+    </span>
24
+  )
25
+
26
+}
27
+
28
+const viewLink = (event, link) => {
29
+  if (event.getModifierState('Shift')) {
30
+    const tempLink = document.createElement('a')
31
+    tempLink.href = link
32
+    tempLink.target = '_blank'
33
+    tempLink.click()
34
+  }
35
+}
36
+
37
+export default {
38
+  strategy: handleStrategy,
39
+  component: Link
40
+}

+ 3
- 0
source/braft-editor/src/renderers/decorators/index.js View File

@@ -0,0 +1,3 @@
1
+import Link from './Link'
2
+
3
+export default [Link]

+ 86
- 0
source/braft-editor/src/renderers/index.js View File

@@ -0,0 +1,86 @@
1
+import React from 'react'
2
+import { Map } from 'immutable'
3
+import { DefaultDraftBlockRenderMap } from 'draft-js'
4
+import Image from './atomics/Image'
5
+import Video from './atomics/Video'
6
+import Audio from './atomics/Audio'
7
+import Embed from './atomics/Embed'
8
+import HorizontalLine from './atomics/HorizontalLine'
9
+import _getBlockStyleFn from './styles/blockStyles'
10
+import _getCustomStyleMap from './styles/inlineStyles'
11
+import _decorators from './decorators'
12
+
13
+const getAtomicBlockComponent = (block, superProps) => (props) => {
14
+
15
+  const entityKey = props.block.getEntityAt(0)
16
+
17
+  if (!entityKey) {
18
+    return null
19
+  }
20
+
21
+  const entity = props.contentState.getEntity(entityKey)
22
+  const mediaData = entity.getData()
23
+  const mediaType = entity.getType()
24
+  const mediaProps = {
25
+    ...superProps,
26
+    block, mediaData, entityKey
27
+  }
28
+
29
+  if (mediaType === 'IMAGE') {
30
+    return <Image { ...mediaProps } />
31
+  } else if (mediaType === 'AUDIO') {
32
+    return <Audio { ...mediaProps } />
33
+  } else if (mediaType === 'VIDEO') {
34
+    return <Video { ...mediaProps } />
35
+  } else if (mediaType === 'EMBED') {
36
+    return <Embed { ...mediaProps } />
37
+  } else if (mediaType === 'HR') {
38
+    return <HorizontalLine { ...mediaProps } />
39
+  }
40
+
41
+  if (superProps.extendAtomics) {
42
+    const atomics = superProps.extendAtomics
43
+    for (let i = 0; i < atomics.length; i++) {
44
+      if (mediaType === atomics[i].type) {
45
+        const Component = atomics[i].component
46
+        return <Component {...mediaProps} />
47
+      }
48
+    }
49
+  }
50
+
51
+  return null
52
+
53
+}
54
+
55
+export const getBlockRendererFn = (props, customBlockRendererFn) => (block) => {
56
+
57
+  let blockRenderer = null
58
+
59
+  if (block.getType() === 'atomic') {
60
+
61
+    blockRenderer = {
62
+      component: getAtomicBlockComponent(block, props),
63
+      editable: false
64
+    }
65
+
66
+  } else if (customBlockRendererFn) {
67
+    blockRenderer = customBlockRendererFn(block) || null
68
+  }
69
+
70
+  return blockRenderer
71
+
72
+}
73
+
74
+export const customBlockRenderMap = Map({
75
+  'atomic': {
76
+    element: ''
77
+  },
78
+  'code-block': {
79
+    element: 'code',
80
+    wrapper: DefaultDraftBlockRenderMap.get('code-block').wrapper
81
+  }
82
+})
83
+
84
+export const getBlockStyleFn = _getBlockStyleFn
85
+export const getCustomStyleMap = _getCustomStyleMap
86
+export const decorators = _decorators

+ 22
- 0
source/braft-editor/src/renderers/styles/blockStyles.js View File

@@ -0,0 +1,22 @@
1
+export default (customBlockStyleFn) => (block) => {
2
+
3
+  const blockAlignment = block.getData() && block.getData().get('textAlign')
4
+  const blockFloat = block.getData() && block.getData().get('float')
5
+
6
+  let result = ''
7
+
8
+  if (blockAlignment) {
9
+    result = `bfa-${blockAlignment}`
10
+  }
11
+
12
+  if (blockFloat) {
13
+    result += ` bff-${blockFloat}`
14
+  }
15
+
16
+  if (customBlockStyleFn) {
17
+    result += customBlockStyleFn(block)
18
+  }
19
+
20
+  return result
21
+
22
+}

+ 59
- 0
source/braft-editor/src/renderers/styles/inlineStyles.js View File

@@ -0,0 +1,59 @@
1
+export default (props, customStyles = {}) => {
2
+
3
+  const colorStyles = {}
4
+  const bgColorStyles = {}
5
+  const fontSizeStyles = {}
6
+  const fontFamilyStyles = {}
7
+  const lineHeightStyles = {}
8
+  const letterSpacingtStyles = {}
9
+  const indentStyles = {}
10
+
11
+  props.colors.forEach((color) => {
12
+    let color_id = color.replace('#', '').toUpperCase()
13
+    colorStyles['COLOR-' + color_id] = { color }
14
+    bgColorStyles['BGCOLOR-' + color_id] = { backgroundColor: color }
15
+  })
16
+
17
+  props.fontSizes.forEach((fontSize) => {
18
+    fontSizeStyles['FONTSIZE-' + fontSize] = { fontSize: fontSize }
19
+  })
20
+
21
+  props.fontFamilies.forEach((fontFamily) => {
22
+    fontFamilyStyles['FONTFAMILY-' + fontFamily.name.toUpperCase()] = {
23
+      fontFamily: fontFamily.family
24
+    }
25
+  })
26
+
27
+  props.lineHeights.forEach((lineHeight) => {
28
+    lineHeightStyles['LINEHEIGHT-' + lineHeight] = { lineHeight: lineHeight }
29
+  })
30
+
31
+  props.letterSpacings.forEach((letterSpacing) => {
32
+    letterSpacingtStyles['LETTERSPACING-' + letterSpacing] = { letterSpacing: letterSpacing }
33
+  })
34
+  props.textIndents.forEach((indent) => {
35
+    indentStyles['INDENT-' + indent] = { paddingLeft: indent, paddingRight: indent}
36
+  })
37
+  
38
+  return {
39
+    'SUPERSCRIPT': {
40
+      position: 'relative',
41
+      top: '-8px',
42
+      fontSize: '11px'
43
+    },
44
+    'SUBSCRIPT': {
45
+      position: 'relative',
46
+      bottom: '-8px',
47
+      fontSize: '11px'
48
+    },
49
+    ...colorStyles,
50
+    ...bgColorStyles,
51
+    ...fontSizeStyles,
52
+    ...fontFamilyStyles,
53
+    ...lineHeightStyles,
54
+    ...letterSpacingtStyles,
55
+    ...indentStyles,
56
+    ...customStyles
57
+  }
58
+
59
+}

+ 6611
- 0
source/braft-editor/yarn.lock
File diff suppressed because it is too large
View File