├── .all-contributorsrc
├── .babelrc.js
├── .editorconfig
├── .eslintrc.js
├── .github
└── badge.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .stylelintrc
├── .travis.yml
├── LICENSE
├── README-zh.md
├── README.md
├── build.sh
├── build
├── rollup.config.js
└── webpack.config.js
├── docs
├── autosave.md
├── autosize.md
├── basic.md
├── dialog.md
├── disabled.md
├── editor-options.md
├── faq.md
├── height.md
├── placeholder.md
└── upload-options.md
├── netlify.sh
├── notify.sh
├── package.json
├── src
├── assets
│ ├── attachment.svg
│ ├── bold.svg
│ ├── bulletedlist.svg
│ ├── dropdown-arrow.svg
│ ├── eraser.svg
│ ├── fontcolor.svg
│ ├── fullscreen.vue
│ ├── fullscreenexit.vue
│ ├── horizontalline.svg
│ ├── image.svg
│ ├── italic.svg
│ ├── link.svg
│ ├── maximize.svg
│ ├── media.svg
│ ├── minimize.svg
│ ├── numberedlist.svg
│ ├── quotes.svg
│ ├── redo.svg
│ ├── richtext.less
│ ├── strikethrough.svg
│ ├── table.svg
│ ├── todolist.svg
│ ├── underline.svg
│ ├── undo.svg
│ └── zoom.svg
├── defaultEditorOptions.js
├── index.js
├── plugin
│ ├── AttachmentUpload.js
│ ├── Autoformat.js
│ ├── Blockquote.js
│ ├── Bold.js
│ ├── DropdownButtonView.js
│ ├── Essentials.js
│ ├── ExtraFormat.js
│ ├── FixComposing.js
│ ├── Font
│ │ ├── ColorTableView.js
│ │ ├── ColorUI.js
│ │ ├── FontBackgroundColor.js
│ │ ├── FontColor.js
│ │ ├── index.js
│ │ └── utils.js
│ ├── FullScreen.js
│ ├── Heading.js
│ ├── Horizontalline.js
│ ├── ImagePreview.js
│ ├── ImageUpload.js
│ ├── Italic.js
│ ├── Link.js
│ ├── List.js
│ ├── Mediaembed
│ │ ├── AutoMediaEmbed.js
│ │ └── index.js
│ ├── RemoveFormat
│ │ ├── RemoveFormatLinks.js
│ │ ├── RemoveFormatUI.js
│ │ └── index.js
│ ├── Strikethrough.js
│ ├── Table.js
│ ├── TodoList.js
│ ├── TransformMD.js
│ ├── Underline.js
│ ├── Undo.js
│ ├── Uploader.js
│ └── generateUIPlugin.js
├── translations
│ ├── en.js
│ ├── index.js
│ └── zh-cn.js
├── utils
│ ├── adapter.js
│ └── index.js
├── v-editor.d.ts
└── v-editor.vue
├── styleguide.config.js
├── styleguide
├── element.js
└── upload-to-ali.js
├── test
├── index.test.js
└── sum.js
└── yarn.lock
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "v-editor",
3 | "projectOwner": "FEMessage",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "imageSize": 100,
10 | "commit": false,
11 | "commitConvention": "angular",
12 | "contributors": [
13 | {
14 | "login": "kunzhijia",
15 | "name": "kunzhijia",
16 | "avatar_url": "https://avatars2.githubusercontent.com/u/4848041?v=4",
17 | "profile": "https://github.com/kunzhijia",
18 | "contributions": [
19 | "code",
20 | "bug",
21 | "doc"
22 | ]
23 | },
24 | {
25 | "login": "listars",
26 | "name": "listars",
27 | "avatar_url": "https://avatars2.githubusercontent.com/u/20613509?v=4",
28 | "profile": "https://github.com/listars",
29 | "contributions": [
30 | "bug",
31 | "doc"
32 | ]
33 | },
34 | {
35 | "login": "donaldshen",
36 | "name": "Donald Shen",
37 | "avatar_url": "https://avatars3.githubusercontent.com/u/19591950?v=4",
38 | "profile": "https://donaldshen.github.io/portfolio",
39 | "contributions": [
40 | "bug",
41 | "doc",
42 | "plugin",
43 | "review"
44 | ]
45 | },
46 | {
47 | "login": "levy9527",
48 | "name": "levy",
49 | "avatar_url": "https://avatars3.githubusercontent.com/u/9384365?v=4",
50 | "profile": "http://levy.work",
51 | "contributions": [
52 | "review",
53 | "infra",
54 | "ideas"
55 | ]
56 | },
57 | {
58 | "login": "colmugx",
59 | "name": "ColMugX",
60 | "avatar_url": "https://avatars1.githubusercontent.com/u/21327913?v=4",
61 | "profile": "https://colmugx.github.io",
62 | "contributions": [
63 | "code",
64 | "blog",
65 | "design",
66 | "plugin"
67 | ]
68 | },
69 | {
70 | "login": "snowlocked",
71 | "name": "snowlocked",
72 | "avatar_url": "https://avatars0.githubusercontent.com/u/19562649?v=4",
73 | "profile": "https://github.com/snowlocked",
74 | "contributions": [
75 | "bug"
76 | ]
77 | }
78 | ],
79 | "contributorsPerLine": 7,
80 | "skipCi": true
81 | }
82 |
--------------------------------------------------------------------------------
/.babelrc.js:
--------------------------------------------------------------------------------
1 | module.exports = api => {
2 | return {
3 | presets: [['@babel/env', {modules: api.env('test') ? 'commonjs' : false}]],
4 | plugins: [
5 | [
6 | '@babel/transform-runtime',
7 | {
8 | regenerator: true
9 | }
10 | ]
11 | ]
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_style = space
8 | indent_size = 2
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | insert_final_newline = false
14 | trim_trailing_whitespace = false
15 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true,
5 | node: true
6 | },
7 | parserOptions: {
8 | parser: 'babel-eslint'
9 | },
10 | extends: [
11 | 'eslint:recommended',
12 | 'plugin:jest/recommended',
13 | 'plugin:vue/recommended',
14 | 'plugin:prettier/recommended',
15 | 'prettier/vue'
16 | ],
17 | plugins: ['vue', 'prettier'],
18 | rules: {
19 | 'no-console': [
20 | 'error',
21 | {
22 | allow: ['warn', 'error']
23 | }
24 | ],
25 | 'no-debugger': 'error',
26 | 'prettier/prettier': 'error'
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.github/badge.yml:
--------------------------------------------------------------------------------
1 | types:
2 | feat: 'enhancement'
3 | fix: 'bug'
4 | docs: 'documentation'
5 | refactor: 'refactor'
6 | test: 'test'
7 | perf: 'performance'
8 | chore:
9 | deps: 'dependencies'
10 | default: 'chore'
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 | dist
7 | docs/build
8 | docs/index.html
9 | docs/*.woff
10 | docs/*.ttf
11 | docs/**/*.js
12 |
13 | # Editor directories and files
14 | .idea
15 | .vscode
16 | *.suo
17 | *.ntvs*
18 | *.njsproj
19 | *.sln
20 | .env
21 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | docs
2 | dist
3 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | semi: false
2 | singleQuote: true
3 | bracketSpacing: false
4 |
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "stylelint-config-standard",
3 | "rules": {
4 | "no-empty-source": null,
5 | "selector-pseudo-element-no-unknown": [true, {
6 | "ignorePseudoElements": ["v-deep"]
7 | }]
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | branches:
2 | only:
3 | - master
4 | language: node_js
5 | node_js:
6 | - lts/*
7 | git:
8 | depth: 30
9 | install:
10 | - yarn --frozen-lockfile
11 | - yarn test
12 | script:
13 | - ./build.sh
14 | after_script:
15 | - ./notify.sh
16 | cache: yarn
17 | deploy:
18 | - provider: pages
19 | local-dir: docs
20 | github-token: $GITHUB_TOKEN
21 | skip-cleanup: true
22 | keep-history: true
23 | - provider: npm
24 | email: levy9527@qq.com
25 | api_key: $NPM_TOKEN
26 | skip-cleanup: true
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 FEMessage
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.
22 |
--------------------------------------------------------------------------------
/README-zh.md:
--------------------------------------------------------------------------------
1 | # v-editor
2 |
3 | [](https://travis-ci.com/FEMessage/v-editor)
4 | [](https://www.npmjs.com/package/@femessage/v-editor)
5 | [](https://www.npmjs.com/package/@femessage/v-editor)
6 | [](https://github.com/FEMessage/v-editor/blob/master/LICENSE)
7 | [](https://github.com/FEMessage/v-editor/pulls)
8 | [](https://github-tools.github.io/github-release-notes/)
9 |
10 | 根据 [ckeditor5](https://github.com/ckeditor/ckeditor5) 以及 [upload-to-ali](https://github.com/femessage/upload-to-ali) 封装的轻量级富文本编辑器。
11 |
12 | 
13 |
14 | ## Table of Contents
15 |
16 | - [Feature](#feature)
17 | - [Demo](#demo)
18 | - [Install](#install)
19 | - [Quick start](#quick-start)
20 | - [Links](#Links)
21 | - [License](#license)
22 | - [Contributors](#contributors)
23 |
24 | ## Feature
25 |
26 | - **oss 上传**:整合了上传组件,只需配置 OSS 的基本信息([配置参考](https://github.com/FEMessage/upload-to-ali/blob/dev/README-zh.md#dotenv)),即可将图片或文件上传到 oss,支持截图粘贴上传
27 | - **添加网络图片**:可以使用 markdown 图片语法(`![]()`)快速添加网络图片,也可以直接粘贴添加
28 | - **全屏编辑**:可以让编辑器覆盖整个页面
29 |
30 | [⬆ Back to Top](#table-of-contents)
31 |
32 | ## Demo
33 |
34 | - [doc and online demo](https://femessage.github.io/v-editor/)
35 |
36 | [⬆ Back to Top](#table-of-contents)
37 |
38 | ## Install
39 |
40 | ```sh
41 | # 上传图片功能依赖upload-to-ali组件
42 | yarn add @femessage/upload-to-ali @femessage/v-editor
43 | ```
44 |
45 | [⬆ Back to Top](#table-of-contents)
46 |
47 | ## Quick start
48 |
49 | ```vue
50 | //step1 确保oss配置 //step2 在需要使用该渲染器的.vue文件中
51 |
52 |
53 |
54 |
67 | ```
68 |
69 | [⬆ Back to Top](#table-of-contents)
70 |
71 | ## Links
72 |
73 | - [插件开发指南](https://www.yuque.com/docs/share/d52c00bf-d379-45c6-955f-8eb218a4dabf)
74 |
75 | [⬆ Back to Top](#table-of-contents)
76 |
77 | ## License
78 |
79 | [MIT](./LICENSE)
80 |
81 | [⬆ Back to Top](#table-of-contents)
82 |
83 | ## Contributors
84 |
85 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
94 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # v-editor
2 |
3 | [](https://travis-ci.com/FEMessage/v-editor)
4 | [](https://www.npmjs.com/package/@femessage/v-editor)
5 | [](https://www.npmjs.com/package/@femessage/v-editor)
6 | [](https://github.com/FEMessage/v-editor/blob/master/LICENSE)
7 | [](https://github.com/FEMessage/v-editor/pulls)
8 | [](https://github-tools.github.io/github-release-notes/)
9 |
10 | Lightweight rich text editor based on [ckeditor5](https://github.com/ckeditor/ckeditor5) and [upload-to-ali](https://github.com/FEMessage/upload-to-ali).
11 |
12 | 
13 |
14 | [中文文档](./README-zh.md)
15 |
16 | ## Table of Contents
17 |
18 | - [Feature](#feature)
19 | - [Demo](#demo)
20 | - [Install](#install)
21 | - [Quick Start](#quick-start)
22 | - [Links](#Links)
23 | - [License](#license)
24 | - [Contributors](#contributors)
25 |
26 | ## Feature
27 |
28 | - **File Upload** : Integrated upload components, just configure the basic information of OSS ([Configuration Reference](https://github.com/FEMessage/upload-to-ali/blob/dev/README.md#dotenv)), you can upload the picture or file to oss, support screenshot paste upload
29 | - **Add Net Image**: Can quickly add a net picture using the markdown picture syntax(`![]()`), or you can paste it directly
30 | - **Fullscreen Editing**: Allows the editor to cover the window
31 |
32 | [⬆Back to Top](#table-of-contents)
33 |
34 | ## Demo
35 |
36 | - [Doc and online demo](https://femessage.github.io/v-editor/)
37 |
38 | [⬆Back to Top](#table-of-contents)
39 |
40 | ## Install
41 |
42 | ```sh
43 | # Upload image function depends on upload-to-ali component
44 | yarn add @femessage/upload-to-ali @femessage/v-editor
45 | ```
46 |
47 | [⬆ Back to Top](#table-of-contents)
48 |
49 | ## Quick start
50 |
51 | ```html
52 |
53 |
54 | renderer
55 |
56 |
57 |
58 |
59 |
73 | ```
74 |
75 | [⬆ Back to Top](#table-of-contents)
76 |
77 | ## Links
78 |
79 | - [how to create a plugin](https://www.yuque.com/docs/share/d52c00bf-d379-45c6-955f-8eb218a4dabf?translate=en)
80 |
81 | [⬆ Back to Top](#table-of-contents)
82 |
83 | ## License
84 |
85 | [MIT](./LICENSE)
86 |
87 | [⬆ Back to Top](#table-of-contents)
88 |
89 | ## Contributors
90 |
91 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
92 |
93 |
94 |
95 |
96 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
113 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | yarn stdver
3 |
4 | yarn build
5 |
--------------------------------------------------------------------------------
/build/rollup.config.js:
--------------------------------------------------------------------------------
1 | // rollup.config.js
2 | import vue from 'rollup-plugin-vue'
3 | import babel from 'rollup-plugin-babel'
4 | import commonjs from 'rollup-plugin-commonjs'
5 | import {terser} from 'rollup-plugin-terser'
6 | import minimist from 'minimist'
7 |
8 | const argv = minimist(process.argv.slice(2))
9 |
10 | const config = {
11 | input: 'src/index.js',
12 | output: {
13 | name: 'VEditor',
14 | exports: 'named'
15 | },
16 | plugins: [
17 | commonjs(),
18 | vue({
19 | css: true,
20 | compileTemplate: true
21 | }),
22 | babel({
23 | runtimeHelpers: true,
24 | exclude: 'node_modules/**'
25 | })
26 | ]
27 | }
28 |
29 | // Only minify browser (iife) version
30 | if (argv.format === 'iife') {
31 | config.plugins.push(terser())
32 | }
33 |
34 | export default config
35 |
--------------------------------------------------------------------------------
/build/webpack.config.js:
--------------------------------------------------------------------------------
1 | const CKEditorWebpackPlugin = require('@ckeditor/ckeditor5-dev-webpack-plugin')
2 | const {bundler, styles} = require('@ckeditor/ckeditor5-dev-utils')
3 | const {VueLoaderPlugin} = require('vue-loader')
4 | const TerserPlugin = require('terser-webpack-plugin')
5 | const webpack = require('webpack')
6 | const path = require('path')
7 |
8 | module.exports = {
9 | entry: [
10 | require.resolve('regenerator-runtime/runtime.js'),
11 | path.resolve('src', 'index.js')
12 | ],
13 | output: {
14 | library: 'VEditor',
15 | path: path.resolve('dist'),
16 | filename: 'v-editor.umd.js',
17 | libraryTarget: 'umd',
18 | libraryExport: 'default'
19 | },
20 | optimization: {
21 | usedExports: true,
22 | minimizer: [
23 | new TerserPlugin({
24 | terserOptions: {
25 | output: {
26 | comments: /^!/
27 | }
28 | },
29 | extractComments: false
30 | })
31 | ]
32 | },
33 | plugins: [
34 | new CKEditorWebpackPlugin({
35 | language: 'zh-cn'
36 | }),
37 | new webpack.BannerPlugin({
38 | banner: bundler.getLicenseBanner(),
39 | raw: true
40 | }),
41 | new VueLoaderPlugin()
42 | ],
43 | externals: ['@femessage/upload-to-ali'],
44 | module: {
45 | rules: [
46 | {
47 | test: /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/,
48 | use: [
49 | {
50 | loader: 'style-loader',
51 | options: {
52 | injectType: 'singletonStyleTag'
53 | }
54 | },
55 | {
56 | loader: 'postcss-loader',
57 | options: styles.getPostCssConfig({
58 | themeImporter: {
59 | themePath: require.resolve('@ckeditor/ckeditor5-theme-lark')
60 | },
61 | minify: true
62 | })
63 | }
64 | ]
65 | },
66 | {
67 | test: /\.svg$/,
68 | use: ['raw-loader']
69 | },
70 | {
71 | test: /\.vue$/,
72 | loader: 'vue-loader'
73 | },
74 | {
75 | test: /\.css$/,
76 | exclude: /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/,
77 | loaders: ['style-loader', 'css-loader']
78 | },
79 | {
80 | test: /\.less$/,
81 | loaders: ['vue-style-loader', 'css-loader', 'less-loader']
82 | }
83 | ]
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/docs/autosave.md:
--------------------------------------------------------------------------------
1 | 如果 8 秒内没有任何操作,会触发自动保存
2 |
3 | It will autosave if no any operation in 8s
4 |
5 | ```vue
6 |
7 |
8 |
9 |
23 | ```
24 |
--------------------------------------------------------------------------------
/docs/autosize.md:
--------------------------------------------------------------------------------
1 | 设置自适应内容高度
2 |
3 | ```vue
4 |
5 |
6 | autosize: {{autosize}}
7 |
8 | 增加maxRows
9 | 增加minRows
10 |
11 |
12 |
25 | ```
26 |
--------------------------------------------------------------------------------
/docs/basic.md:
--------------------------------------------------------------------------------
1 | 基本用法
2 |
3 | ```vue
4 |
5 |
6 |
7 | {{flag ? '关闭' : '打开'}}数据结构视图
8 |
9 |
10 |
39 | ```
40 |
--------------------------------------------------------------------------------
/docs/dialog.md:
--------------------------------------------------------------------------------
1 | 弹窗例子
2 |
3 | ```vue
4 |
5 |
6 | 点击打开 Dialog
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
33 | ```
34 |
--------------------------------------------------------------------------------
/docs/disabled.md:
--------------------------------------------------------------------------------
1 | 禁用
2 |
3 | ```vue
4 |
5 |
6 |
7 |
8 |
9 |
10 |
27 | ```
28 |
--------------------------------------------------------------------------------
/docs/editor-options.md:
--------------------------------------------------------------------------------
1 | 自定义菜单
2 |
3 | > lodash.merge 的覆盖规则是数组项合并类似 concat;
4 | > 目前似乎无法自定义 toolbar;要么这里改例子要么改代码
5 |
6 | ```vue
7 |
8 |
9 |
10 |
38 | ```
39 |
--------------------------------------------------------------------------------
/docs/faq.md:
--------------------------------------------------------------------------------
1 | ## 在 TypeScript 中指定组件的类型
2 |
3 | ```html
4 |
13 | ```
--------------------------------------------------------------------------------
/docs/height.md:
--------------------------------------------------------------------------------
1 | 自定义编辑区(不包括 toolbar)高度,height 支持传 css 长度和 Number 类型(默认单位 px)
2 |
3 | ```vue
4 |
5 |
6 |
7 |
8 |
9 |
10 |
20 | ```
21 |
--------------------------------------------------------------------------------
/docs/placeholder.md:
--------------------------------------------------------------------------------
1 | 基本用法
2 |
3 | ```vue
4 |
5 |
6 |
7 |
8 |
9 |
19 | ```
20 |
--------------------------------------------------------------------------------
/docs/upload-options.md:
--------------------------------------------------------------------------------
1 | 覆盖默认的上传行为,可以自定义上传的实现
2 |
3 | ```vue
4 |
5 |
6 |
7 |
8 |
35 | ```
36 |
37 | 设置upload-to-ali的大小限制,兼容overSize等事件
38 |
39 | ```vue
40 |
41 |
42 |
43 |
44 |
73 | ```
74 |
--------------------------------------------------------------------------------
/netlify.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | echo "is netlify: $NETLIFY"
3 | echo "in branch: $BRANCH"
4 | echo "head: $HEAD"
5 |
6 | if [ "$NETLIFY" != "true" ]
7 | then
8 | echo "this script only runs in netlify, bye"
9 | exit 1
10 | fi
11 |
12 | if [ "$BRANCH" != "dev" ] && [ "$HEAD" != "dev" ]
13 | then
14 | yarn doc
15 | else
16 | echo "this script only runs in targeting dev's PR deploy preview, bye"
17 | fi
18 |
--------------------------------------------------------------------------------
/notify.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # https://stackoverflow.com/questions/13872048/bash-script-what-does-bin-bash-mean
3 | echo "1/5: checking TRAVIS_TEST_RESULT"
4 | if [ "$TRAVIS_TEST_RESULT" != "0" ]
5 | then
6 | echo "build not success, bye"
7 | exit 1
8 | fi
9 |
10 | ORG_NAME=$(echo "$TRAVIS_REPO_SLUG" | cut -d '/' -f 1)
11 | REPO_NAME=$(echo "$TRAVIS_REPO_SLUG" | cut -d '/' -f 2)
12 |
13 | echo "2/5: pushing commit and tag to github"
14 | # 该命令很可能报错,但不影响实际进行,因而不能简单地在脚本开头 set -e
15 | git remote add github https://$GITHUB_TOKEN@github.com/$TRAVIS_REPO_SLUG.git > /dev/null 2>&1
16 | git push github HEAD:master --follow-tags
17 |
18 | echo "3/5: generating github release notes"
19 | GREN_GITHUB_TOKEN=$GITHUB_TOKEN yarn release
20 |
21 | # 避免发送错误信息
22 | if [ $? -ne 0 ]
23 | then
24 | echo "gren fails, bye"
25 | exit 1
26 | fi
27 |
28 | echo "4/5: downloading github release info"
29 | url=https://api.github.com/repos/$TRAVIS_REPO_SLUG/releases/latest
30 | resp_tmp_file=resp.tmp
31 |
32 | curl -H "Authorization: token $GITHUB_TOKEN" $url > $resp_tmp_file
33 |
34 | html_url=$(sed -n 5p $resp_tmp_file | sed 's/\"html_url\"://g' | awk -F '"' '{print $2}')
35 | body=$(grep body < $resp_tmp_file | sed 's/\"body\"://g;s/\"//g')
36 | version=$(echo $html_url | awk -F '/' '{print $NF}')
37 |
38 | echo "5/5: notifying with dingtalk bot"
39 | msg='{"msgtype": "markdown", "markdown": {"title": "'$REPO_NAME'更新", "text": "@所有人\n# ['$REPO_NAME'('$version')]('$html_url')\n'$body'"}}'
40 |
41 | curl -X POST https://oapi.dingtalk.com/robot/send\?access_token\=$DINGTALK_ROBOT_TOKEN -H 'Content-Type: application/json' -d "$msg"
42 |
43 | rm $resp_tmp_file
44 |
45 | echo "executing notify.sh successfully"
46 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@femessage/v-editor",
3 | "version": "1.2.0",
4 | "description": "upload img and write rich text easily",
5 | "author": "https://github.com/FEMessage",
6 | "license": "MIT",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/FEMessage/v-editor.git"
10 | },
11 | "keywords": [
12 | "vue",
13 | "sfc",
14 | "component"
15 | ],
16 | "files": [
17 | "src",
18 | "dist",
19 | "yarn.lock"
20 | ],
21 | "main": "dist/v-editor.umd.js",
22 | "browser": {
23 | "./sfc": "src/v-editor.vue"
24 | },
25 | "types": "src/v-editor.d.ts",
26 | "scripts": {
27 | "dev": "vue-styleguidist server",
28 | "test": "jest --verbose",
29 | "doc": "vue-styleguidist build",
30 | "build": "npm run build:umd & npm run doc",
31 | "precommit": "pretty-quick --staged",
32 | "stdver": "standard-version -m '[skip ci] chore(release): v%s'",
33 | "release": "gren release --override",
34 | "lint": "eslint \"**/*.@(js|vue)\" --fix",
35 | "build:umd": "webpack --mode production --config build/webpack.config.js"
36 | },
37 | "dependencies": {
38 | "@ckeditor/ckeditor5-adapter-ckfinder": "^16.0.0",
39 | "@ckeditor/ckeditor5-autoformat": "^16.0.0",
40 | "@ckeditor/ckeditor5-autosave": "^16.0.0",
41 | "@ckeditor/ckeditor5-basic-styles": "^16.0.0",
42 | "@ckeditor/ckeditor5-block-quote": "^16.0.0",
43 | "@ckeditor/ckeditor5-ckfinder": "^16.0.0",
44 | "@ckeditor/ckeditor5-core": "^16.0.0",
45 | "@ckeditor/ckeditor5-dev-utils": "^12.0.0",
46 | "@ckeditor/ckeditor5-dev-webpack-plugin": "^8.0.0",
47 | "@ckeditor/ckeditor5-editor-classic": "^16.0.0",
48 | "@ckeditor/ckeditor5-essentials": "^16.0.0",
49 | "@ckeditor/ckeditor5-font": "^16.0.0",
50 | "@ckeditor/ckeditor5-heading": "^16.0.0",
51 | "@ckeditor/ckeditor5-horizontal-line": "^16.0.0",
52 | "@ckeditor/ckeditor5-image": "^16.0.0",
53 | "@ckeditor/ckeditor5-indent": "^16.0.0",
54 | "@ckeditor/ckeditor5-link": "^16.0.0",
55 | "@ckeditor/ckeditor5-list": "^16.0.0",
56 | "@ckeditor/ckeditor5-media-embed": "^16.0.0",
57 | "@ckeditor/ckeditor5-paragraph": "^16.0.0",
58 | "@ckeditor/ckeditor5-paste-from-office": "^16.0.0",
59 | "@ckeditor/ckeditor5-remove-format": "^16.0.0",
60 | "@ckeditor/ckeditor5-table": "^16.0.0",
61 | "@ckeditor/ckeditor5-theme-lark": "^16.0.0",
62 | "@ckeditor/ckeditor5-upload": "^16.0.0",
63 | "@ckeditor/ckeditor5-utils": "^16.0.0",
64 | "@ckeditor/ckeditor5-vue": "^1.0.1",
65 | "@femessage/img-preview": "^1.4.1",
66 | "github-markdown-css": "^3.0.1",
67 | "lodash-es": "^4.17.15",
68 | "marked": "^1.0.0"
69 | },
70 | "devDependencies": {
71 | "@babel/core": "^7.8.3",
72 | "@babel/plugin-transform-runtime": "^7.4.3",
73 | "@babel/preset-env": "^7.8.3",
74 | "@ckeditor/ckeditor5-inspector": "^1.5.0",
75 | "@femessage/github-release-notes": "latest",
76 | "@femessage/upload-to-ali": "^2.1.2",
77 | "babel-eslint": "^10.0.3",
78 | "babel-loader": "^8.0.5",
79 | "css-loader": "^3.4.2",
80 | "dotenv": "^7.0.0",
81 | "element-ui": "^2.7.2",
82 | "eslint": "^6.6.0",
83 | "eslint-config-prettier": "^6.5.0",
84 | "eslint-plugin-jest": "^23.0.3",
85 | "eslint-plugin-prettier": "^3.1.1",
86 | "eslint-plugin-vue": "^6.0.0",
87 | "file-loader": "^3.0.1",
88 | "glob": "^7.1.3",
89 | "husky": "1.3.1",
90 | "jest": "^24.8.0",
91 | "less": "^3.10.3",
92 | "less-loader": "^5.0.0",
93 | "lint-staged": "^8.1.0",
94 | "minimist": "^1.2.0",
95 | "postcss-loader": "^3.0.0",
96 | "prettier": "1.18.2",
97 | "raw-loader": "^4.0.0",
98 | "regenerator-runtime": "^0.13.3",
99 | "rollup": "^1.11.0",
100 | "rollup-plugin-babel": "^4.3.2",
101 | "rollup-plugin-commonjs": "^9.3.4",
102 | "rollup-plugin-terser": "^4.0.4",
103 | "rollup-plugin-vue": "^4.7.2",
104 | "standard-version": "^6.0.1",
105 | "style-loader": "^1.1.2",
106 | "stylelint": "^13.0.0",
107 | "stylelint-config-standard": "^19.0.0",
108 | "terser-webpack-plugin": "^2.3.2",
109 | "vue": "^2.5.16",
110 | "vue-loader": "^15.8.3",
111 | "vue-styleguidist": "3.11.4",
112 | "vue-template-compiler": "^2.5.16",
113 | "webpack": "^4.41.5",
114 | "webpack-cli": "^3.3.10"
115 | },
116 | "publishConfig": {
117 | "access": "public"
118 | },
119 | "vue-sfc-cli": "1.12.0",
120 | "engines": {
121 | "node": ">= 4.0.0",
122 | "npm": ">= 3.0.0"
123 | },
124 | "husky": {
125 | "hooks": {
126 | "pre-commit": "lint-staged",
127 | "post-commit": "git update-index --again",
128 | "pre-push": "yarn test"
129 | }
130 | },
131 | "lint-staged": {
132 | "*.@(md|json)": [
133 | "prettier --write",
134 | "git add"
135 | ],
136 | "*.js": [
137 | "eslint --fix",
138 | "prettier --write",
139 | "git add"
140 | ],
141 | "*.vue": [
142 | "eslint --fix",
143 | "prettier --write",
144 | "stylelint --fix",
145 | "git add"
146 | ]
147 | },
148 | "gren": "@femessage/grenrc"
149 | }
150 |
--------------------------------------------------------------------------------
/src/assets/attachment.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/bold.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/bulletedlist.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/dropdown-arrow.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/eraser.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fontcolor.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/fullscreen.vue:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
--------------------------------------------------------------------------------
/src/assets/fullscreenexit.vue:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
--------------------------------------------------------------------------------
/src/assets/horizontalline.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/image.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/italic.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/link.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/maximize.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/media.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/minimize.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/numberedlist.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/quotes.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/redo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/richtext.less:
--------------------------------------------------------------------------------
1 | @import url('~github-markdown-css/github-markdown.css');
2 |
3 | .v-editor .markdown-body,
4 | .v-editor.markdown-body {
5 | hr {
6 | background-color: #e8e8e8;
7 | height: 2px;
8 | margin: 12px 0;
9 | }
10 |
11 | p {
12 | margin-bottom: 0;
13 | }
14 |
15 | ol,
16 | ul {
17 | padding-left: 1.5em;
18 | display: block;
19 | margin-block-start: 1em;
20 | margin-block-end: 1em;
21 | margin-inline-start: 0;
22 | margin-inline-end: 0;
23 | padding-inline-start: 40px;
24 | }
25 |
26 | ul:not(.todo-list) {
27 | list-style-type: disc;
28 | }
29 |
30 | ol {
31 | list-style-type: decimal;
32 | }
33 |
34 | li {
35 | display: list-item;
36 | text-align: -webkit-match-parent;
37 | }
38 |
39 | ol ol {
40 | list-style-type: lower-alpha;
41 |
42 | & ol {
43 | list-style-type: lower-roman;
44 | }
45 | }
46 |
47 | i {
48 | font-style: italic;
49 | }
50 |
51 | // 图片、表格默认靠左
52 | figure {
53 | &.image,
54 | &.table {
55 | margin: 1em 0;
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/assets/strikethrough.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/table.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/todolist.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/underline.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/undo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/zoom.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/defaultEditorOptions.js:
--------------------------------------------------------------------------------
1 | import Autosave from '@ckeditor/ckeditor5-autosave/src/autosave'
2 | import CKFinder from '@ckeditor/ckeditor5-ckfinder/src/ckfinder'
3 |
4 | import Image from '@ckeditor/ckeditor5-image/src/image'
5 | import ImageCaption from '@ckeditor/ckeditor5-image/src/imagecaption'
6 | import ImageStyle from '@ckeditor/ckeditor5-image/src/imagestyle'
7 | import ImageToolbar from '@ckeditor/ckeditor5-image/src/imagetoolbar'
8 | import ImageResize from '@ckeditor/ckeditor5-image/src/imageresize'
9 |
10 | import Indent from '@ckeditor/ckeditor5-indent/src/indent'
11 | import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph'
12 | import PasteFromOffice from '@ckeditor/ckeditor5-paste-from-office/src/pastefromoffice'
13 | import TableToolbar from '@ckeditor/ckeditor5-table/src/tabletoolbar'
14 |
15 | // 本地魔改插件
16 | import Essentials from './plugin/Essentials'
17 | import Font from './plugin/Font'
18 | import Bold from './plugin/Bold'
19 | import Italic from './plugin/Italic'
20 | import Strikethrough from './plugin/Strikethrough'
21 | import Underline from './plugin/Underline'
22 | import List from './plugin/List'
23 | import TodoList from './plugin/TodoList'
24 | import Link from './plugin/Link'
25 | import BlockQuote from './plugin/Blockquote'
26 | import HorizontalLine from './plugin/Horizontalline'
27 | import ImageUpload from './plugin/ImageUpload'
28 | import Table from './plugin/Table'
29 | import MediaEmbed from './plugin/Mediaembed'
30 | import Heading from './plugin/Heading'
31 |
32 | // 本地插件
33 | import Autoformat from './plugin/Autoformat'
34 | import ExtraFormat from './plugin/ExtraFormat'
35 | import RemoveFormat from './plugin/RemoveFormat'
36 | import AttachmentUpload from './plugin/AttachmentUpload'
37 | import FixComposing from './plugin/FixComposing'
38 | import TransformMD from './plugin/TransformMD'
39 |
40 | export default {
41 | plugins: [
42 | Essentials,
43 | Autosave,
44 | Autoformat,
45 | Font,
46 | Bold,
47 | Italic,
48 | Strikethrough,
49 | Underline,
50 | BlockQuote,
51 | HorizontalLine,
52 | CKFinder,
53 | Heading,
54 | Image,
55 | ImageCaption,
56 | ImageStyle,
57 | ImageToolbar,
58 | ImageUpload,
59 | ImageResize,
60 | Indent,
61 | Link,
62 | List,
63 | TodoList,
64 | MediaEmbed,
65 | Paragraph,
66 | PasteFromOffice,
67 | Table,
68 | TableToolbar,
69 | ExtraFormat,
70 | RemoveFormat,
71 | AttachmentUpload,
72 | TransformMD,
73 | FixComposing
74 | ],
75 | toolbar: [
76 | 'undo',
77 | 'redo',
78 | 'removeFormat',
79 | '|',
80 | 'heading',
81 | '|',
82 | 'bold',
83 | 'italic',
84 | 'strikethrough',
85 | 'underline',
86 | '|',
87 | 'fontColor',
88 | 'fontBackgroundColor',
89 | '|',
90 | 'bulletedList',
91 | 'numberedList',
92 | 'todoList',
93 | '|',
94 | 'link',
95 | 'blockQuote',
96 | 'horizontalLine',
97 | '|',
98 | 'imageUpload',
99 | 'AttachmentUpload',
100 | 'insertTable',
101 | 'mediaEmbed'
102 | ],
103 | heading: {
104 | options: [
105 | {model: 'paragraph', title: '正文', class: 'ck-heading_paragraph'},
106 | {
107 | model: 'heading1',
108 | view: 'h1',
109 | title: 'Heading 1',
110 | class: 'ck-heading_heading1'
111 | },
112 | {
113 | model: 'heading2',
114 | view: 'h2',
115 | title: 'Heading 2',
116 | class: 'ck-heading_heading2'
117 | },
118 | {
119 | model: 'heading3',
120 | view: 'h3',
121 | title: 'Heading 3',
122 | class: 'ck-heading_heading3'
123 | },
124 | {
125 | model: 'heading4',
126 | view: 'h4',
127 | title: 'Heading 4'
128 | }
129 | ]
130 | },
131 | image: {
132 | resizeUnit: 'px',
133 | toolbar: [
134 | 'imageStyle:full',
135 | 'imageStyle:side',
136 | '|',
137 | 'imageTextAlternative',
138 | 'imagePreview'
139 | ]
140 | },
141 | table: {
142 | contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells']
143 | },
144 | link: {
145 | addTargetToExternalLinks: true
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | // Import vue component
2 | import Component from './v-editor.vue'
3 |
4 | // `Vue.use` automatically prevents you from using
5 | // the same plugin more than once,
6 | // so calling it multiple times on the same plugin
7 | // will install the plugin only once
8 | Component.install = Vue => {
9 | Vue.component(Component.name, Component)
10 | }
11 |
12 | // To auto-install when vue is found
13 | let GlobalVue = null
14 | if (typeof window !== 'undefined') {
15 | GlobalVue = window.Vue
16 | } else if (typeof global !== 'undefined') {
17 | GlobalVue = global.Vue
18 | }
19 | if (GlobalVue) {
20 | GlobalVue.use(Component)
21 | }
22 |
23 | // To allow use as module (npm/webpack/etc.) export component
24 | export default Component
25 |
26 | // It's possible to expose named exports when writing components that can
27 | // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo';
28 | // export const RollupDemoDirective = component;
29 |
--------------------------------------------------------------------------------
/src/plugin/AttachmentUpload.js:
--------------------------------------------------------------------------------
1 | import Plugin from '@ckeditor/ckeditor5-core/src/plugin'
2 | import Command from '@ckeditor/ckeditor5-core/src/command'
3 | import FileDialogButtonView from '@ckeditor/ckeditor5-upload/src/ui/filedialogbuttonview'
4 |
5 | import attachmentIcon from '../assets/attachment.svg'
6 |
7 | export default class AttachmentUpload extends Plugin {
8 | /**
9 | * @inheritDoc
10 | */
11 | static get pluginName() {
12 | return 'AttachmentUpload'
13 | }
14 |
15 | init() {
16 | const {editor} = this
17 |
18 | // 注册命令
19 | editor.commands.add('attachmentUpload', new AttachmentCommand(editor))
20 |
21 | editor.ui.componentFactory.add('AttachmentUpload', locale => {
22 | // 文件选择器类型按钮
23 | const view = new FileDialogButtonView(locale)
24 |
25 | const command = editor.commands.get('attachmentUpload')
26 |
27 | view.buttonView.set({
28 | label: '附件上传',
29 | icon: attachmentIcon,
30 | tooltip: true
31 | })
32 |
33 | view.buttonView.bind('isEnabled').to(command)
34 |
35 | // 文件选择结束回调
36 | view.on('done', (_, file) => {
37 | editor.execute('attachmentUpload', {file})
38 | })
39 |
40 | return view
41 | })
42 | }
43 | }
44 |
45 | class AttachmentCommand extends Command {
46 | execute(options) {
47 | const {editor} = this
48 | const model = editor.model
49 | const fileRepository = editor.plugins.get('FileRepository')
50 |
51 | // 被调用命令时传入文件
52 | const file = options.file[0]
53 |
54 | model.change(writer => {
55 | const loader = fileRepository.createLoader(file)
56 | /** @type {import ('ckeditor__ckeditor5-engine').model.Range} */
57 | let filenameTxtPlaceholderRange
58 |
59 | // 执行顺序依次:读取,占位,上传
60 | loader
61 | .read()
62 | .then(() => {
63 | const filenameTxtModel = writer.createText(`{{${file.name}}}`)
64 | filenameTxtPlaceholderRange = model.insertContent(
65 | filenameTxtModel,
66 | model.document.selection
67 | )
68 | })
69 | .then(() => loader.upload())
70 | .then(data => {
71 | const url = data.default
72 |
73 | /**
74 | * 没法在 link 里插入 svg 图片
75 | * https://ckeditor.com/docs/ckeditor5/latest/builds/guides/faq.html#where-are-the-editorinserthtml-and-editorinserttext-methods-how-to-insert-some-content
76 | */
77 | // const viewFragment = editor.data.processor.toView(attachmentIcon)
78 | // const modelFragment = editor.data.toModel(viewFragment)
79 | // console.log(modelFragment) // 空。得写插件支持
80 | /**
81 | * 只能 emoji 了
82 | * 备选:🔗📂📚📦
83 | */
84 | const linkText = writer.createText(`🔗 ${file.name}`, {linkHref: url})
85 |
86 | let selection
87 | if (filenameTxtPlaceholderRange) {
88 | selection = writer.createSelection(filenameTxtPlaceholderRange)
89 | }
90 |
91 | model.insertContent(linkText, selection || model.document.selection)
92 |
93 | // 回收 loader
94 | fileRepository.destroyLoader(loader)
95 | })
96 | })
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/plugin/Autoformat.js:
--------------------------------------------------------------------------------
1 | import Autoformat from '@ckeditor/ckeditor5-autoformat/src/autoformat'
2 | import BlockAutoformatEditing from '@ckeditor/ckeditor5-autoformat/src/blockautoformatediting'
3 |
4 | export default class TheAutoformat extends Autoformat {
5 | _addListAutoformats() {
6 | const commands = this.editor.commands
7 |
8 | if (commands.get('bulletedList')) {
9 | // eslint-disable-next-line no-new
10 | new BlockAutoformatEditing(
11 | this.editor,
12 | /^[*-]\s/,
13 | this._composeListener('bulletedList')
14 | )
15 | }
16 |
17 | if (commands.get('numberedList')) {
18 | // eslint-disable-next-line no-new
19 | new BlockAutoformatEditing(
20 | this.editor,
21 | /^1\.\s/,
22 | this._composeListener('numberedList')
23 | )
24 | }
25 | }
26 |
27 | _addHeadingAutoformats() {
28 | const command = this.editor.commands.get('heading')
29 |
30 | if (command) {
31 | command.modelElements
32 | .filter(name => name.match(/^heading[1-6]$/))
33 | .forEach(commandValue => {
34 | const level = commandValue[7]
35 | const pattern = new RegExp(`^(#{${level}})\\s`)
36 |
37 | // eslint-disable-next-line no-new
38 | new BlockAutoformatEditing(this.editor, pattern, () => {
39 | if (!command.isEnabled) {
40 | return false
41 | }
42 |
43 | this.editor.execute('heading', {value: commandValue})
44 | })
45 | })
46 | }
47 | }
48 |
49 | _composeListener(command) {
50 | return () => {
51 | const isComposing = this.editor.editing.view.document.isComposing
52 | if (isComposing) return false
53 | return this.editor.execute(command)
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/plugin/Blockquote.js:
--------------------------------------------------------------------------------
1 | import generateUIPlugin from './generateUIPlugin'
2 | import BlockQuoteEditing from '@ckeditor/ckeditor5-block-quote/src/blockquoteediting'
3 | import BlockQuoteUI from '@ckeditor/ckeditor5-block-quote/src/blockquoteui'
4 |
5 | import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'
6 | import quoteIcon from '../assets/quotes.svg'
7 |
8 | class BlockQuoteUICustom extends BlockQuoteUI {
9 | /**
10 | * @inheritDoc
11 | */
12 | init() {
13 | const editor = this.editor
14 | editor.ui.componentFactory.add('blockQuote', locale => {
15 | const command = editor.commands.get('blockQuote')
16 | const buttonView = new ButtonView(locale)
17 |
18 | buttonView.set({
19 | label: '引用',
20 | icon: quoteIcon,
21 | tooltip: true,
22 | isToggleable: true
23 | })
24 |
25 | // Bind button model to command.
26 | buttonView.bind('isOn', 'isEnabled').to(command, 'value', 'isEnabled')
27 |
28 | // Execute command.
29 | this.listenTo(buttonView, 'execute', () => editor.execute('blockQuote'))
30 |
31 | return buttonView
32 | })
33 | }
34 | }
35 |
36 | export default generateUIPlugin('BlockQuote', [
37 | BlockQuoteEditing,
38 | BlockQuoteUICustom
39 | ])
40 |
--------------------------------------------------------------------------------
/src/plugin/Bold.js:
--------------------------------------------------------------------------------
1 | import generateUIPlugin from './generateUIPlugin'
2 | import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting'
3 | /**
4 | * @module basic-styles/bold/boldui
5 | */
6 |
7 | import Plugin from '@ckeditor/ckeditor5-core/src/plugin'
8 | import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'
9 |
10 | import boldIcon from '../assets/bold.svg'
11 |
12 | const BOLD = 'bold'
13 |
14 | /**
15 | * The bold UI feature. It introduces the Bold button.
16 | *
17 | * @extends module:core/plugin~Plugin
18 | */
19 | class BoldUI extends Plugin {
20 | /**
21 | * @inheritDoc
22 | */
23 | init() {
24 | const editor = this.editor
25 | // Add bold button to feature components.
26 | editor.ui.componentFactory.add(BOLD, locale => {
27 | const command = editor.commands.get(BOLD)
28 | const view = new ButtonView(locale)
29 |
30 | view.set({
31 | label: '加粗',
32 | icon: boldIcon,
33 | keystroke: 'CTRL+B',
34 | tooltip: true,
35 | isToggleable: true
36 | })
37 |
38 | view.bind('isOn', 'isEnabled').to(command, 'value', 'isEnabled')
39 |
40 | // Execute command.
41 | this.listenTo(view, 'execute', () => editor.execute(BOLD))
42 |
43 | return view
44 | })
45 | }
46 | }
47 |
48 | export default generateUIPlugin('Bold', [BoldEditing, BoldUI])
49 |
--------------------------------------------------------------------------------
/src/plugin/DropdownButtonView.js:
--------------------------------------------------------------------------------
1 | import DropdownButtonView from '@ckeditor/ckeditor5-ui/src/dropdown/button/dropdownbuttonview'
2 | import IconView from '@ckeditor/ckeditor5-ui/src/icon/iconview'
3 | import dropdownArrowIcon from '../assets/dropdown-arrow.svg'
4 |
5 | export default class DropdownButtonViewCustom extends DropdownButtonView {
6 | _createArrowView() {
7 | const arrowView = new IconView()
8 |
9 | arrowView.content = dropdownArrowIcon
10 |
11 | arrowView.extendTemplate({
12 | attributes: {
13 | class: 'ck-dropdown__arrow'
14 | }
15 | })
16 |
17 | return arrowView
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/plugin/Essentials.js:
--------------------------------------------------------------------------------
1 | import generateUIPlugin from './generateUIPlugin'
2 | import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'
3 | import Enter from '@ckeditor/ckeditor5-enter/src/enter'
4 | import ShiftEnter from '@ckeditor/ckeditor5-enter/src/shiftenter'
5 | import Typing from '@ckeditor/ckeditor5-typing/src/typing'
6 | import Undo from './Undo'
7 |
8 | export default generateUIPlugin('Essentials', [
9 | Clipboard,
10 | Enter,
11 | ShiftEnter,
12 | Typing,
13 | Undo
14 | ])
15 |
--------------------------------------------------------------------------------
/src/plugin/ExtraFormat.js:
--------------------------------------------------------------------------------
1 | import Plugin from '@ckeditor/ckeditor5-core/src/plugin'
2 | import BlockAutoformatEditing from '@ckeditor/ckeditor5-autoformat/src/blockautoformatediting'
3 | import ModelElement from '@ckeditor/ckeditor5-engine/src/model/element'
4 |
5 | export default class ExtraFormat extends Plugin {
6 | /**
7 | * @inheritDoc
8 | */
9 | static get pluginName() {
10 | return 'ExtraFormat'
11 | }
12 |
13 | afterInit() {
14 | this._addListAutoformat()
15 | this._urlImageAutoformat()
16 | this._addHorizontalLineAutoFormat()
17 | }
18 |
19 | /**
20 | * 任务列表支持快捷键: [](Space)
21 | */
22 | _addListAutoformat() {
23 | const commands = this.editor.commands
24 |
25 | if (commands.get('todoList')) {
26 | new BlockAutoformatEditing(
27 | this.editor,
28 | /^\[\]\s/,
29 | this._composeListener('todoList')
30 | )
31 | }
32 | }
33 |
34 | /**
35 | * URL 图片
36 | */
37 | _urlImageAutoformat() {
38 | new BlockAutoformatEditing(
39 | this.editor,
40 | /^!\[(\S*)\]\((.*?)\)\s$/,
41 | writer => {
42 | const [, imageAlt, imageUrl] = writer.match
43 |
44 | if (!imageUrl) {
45 | return false
46 | }
47 |
48 | const doc = this.editor.model.document
49 |
50 | const imageElement = new ModelElement('image', {
51 | src: imageUrl,
52 | alt: imageAlt
53 | })
54 |
55 | this.editor.model.insertContent(imageElement, doc.selection)
56 | }
57 | )
58 | }
59 |
60 | /**
61 | * 快捷插入分割线
62 | */
63 | _addHorizontalLineAutoFormat() {
64 | const {editor} = this
65 | const cmd = 'horizontalLine'
66 | if (!editor.commands.get(cmd)) return
67 | const pat = /^---\s$/
68 | new BlockAutoformatEditing(editor, pat, () => {
69 | const node = editor.model.document.selection.getFirstPosition().parent
70 | editor.model.enqueueChange(writer => {
71 | writer.remove(node)
72 | // 先 remove 再插入分割线,这样光标才会停在分割线下一行
73 | editor.execute(cmd)
74 | })
75 | })
76 | }
77 |
78 | _composeListener(command) {
79 | return () => {
80 | const isComposing = this.editor.editing.view.document.isComposing
81 | if (isComposing) return false
82 | return this.editor.execute(command)
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/plugin/FixComposing.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @hack 如果哪天崩了要看回 @ckeditor/ckeditor5-engine/src/view/{view|renderer}.js 源码
3 | * 已知负面影响:在线协作功能
4 | *
5 | * 详情看相关 issues & pr:
6 | * - https://github.com/ckeditor/ckeditor5/issues/5877
7 | * - https://github.com/ckeditor/ckeditor5-engine/pull/861
8 | *
9 | * PS:这个可能是更优的方案,如果还有问题的话可以参考
10 | * https://github.com/ckeditor/ckeditor5/issues/5877#issuecomment-566369164
11 | * https://github.com/mycolorway/ckeditor5-engine/commit/7d921207054e327088fe7496a8cde451ddd87423
12 | */
13 | export default function(editor) {
14 | const {_renderer, document} = editor.editing.view
15 | /**
16 | * 注入 isComposing,在 render 中使用
17 | * @see https://github.com/ckeditor/ckeditor5-engine/pull/861/files#diff-644366cecbdf61171130f59482de38e0R114
18 | */
19 | _renderer.bind('isComposing').to(document)
20 | const {render} = _renderer
21 | /**
22 | * 输入法进行间停止 render
23 | * https://github.com/ckeditor/ckeditor5-engine/pull/861/files#diff-4c87133ed830fc4ea0646426deba32cfR188
24 | */
25 | _renderer.render = function() {
26 | // console.log('isComposing', this.isComposing)
27 | if (this.isComposing) return
28 | return render.call(this)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/plugin/Font/ColorTableView.js:
--------------------------------------------------------------------------------
1 | import ColorTableView from '@ckeditor/ckeditor5-font/src/ui/colortableview'
2 | import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'
3 |
4 | import removeButtonIcon from '../../assets/eraser.svg'
5 |
6 | export default class ColorTableViewCustom extends ColorTableView {
7 | _removeColorButton() {
8 | const buttonView = new ButtonView()
9 |
10 | buttonView.set({
11 | withText: true,
12 | icon: removeButtonIcon,
13 | tooltip: true,
14 | label: this.removeButtonLabel
15 | })
16 |
17 | buttonView.class = 'ck-color-table__remove-color'
18 | buttonView.on('execute', () => {
19 | this.fire('execute', {value: null})
20 | })
21 |
22 | return buttonView
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/plugin/Font/ColorUI.js:
--------------------------------------------------------------------------------
1 | import ColorUI from '@ckeditor/ckeditor5-font/src/ui/colorui'
2 | import {addColorTableToDropdown} from './utils'
3 | import {
4 | normalizeColorOptions,
5 | getLocalizedColorOptions
6 | } from '@ckeditor/ckeditor5-font/src/utils'
7 |
8 | import {createDropdown} from '@ckeditor/ckeditor5-ui/src/dropdown/utils'
9 | import DropdownButtonView from '../DropdownButtonView'
10 |
11 | export default class ColorUICustom extends ColorUI {
12 | init() {
13 | const editor = this.editor
14 | const command = editor.commands.get(this.commandName)
15 | const colorsConfig = normalizeColorOptions(
16 | editor.config.get(this.componentName).colors
17 | )
18 | const localizedColors = getLocalizedColorOptions(editor, colorsConfig)
19 | const documentColorsCount = editor.config.get(
20 | `${this.componentName}.documentColors`
21 | )
22 |
23 | // Register the UI component.
24 | editor.ui.componentFactory.add(this.componentName, locale => {
25 | const dropdownView = createDropdown(locale, DropdownButtonView)
26 | this.colorTableView = addColorTableToDropdown({
27 | dropdownView,
28 | colors: localizedColors.map(option => ({
29 | label: option.label,
30 | color: option.model,
31 | options: {
32 | hasBorder: option.hasBorder
33 | }
34 | })),
35 | columns: this.columns,
36 | removeButtonLabel: '移除颜色',
37 | documentColorsLabel:
38 | documentColorsCount !== 0 ? '文档中的颜色' : undefined,
39 | documentColorsCount:
40 | documentColorsCount === undefined ? this.columns : documentColorsCount
41 | })
42 |
43 | this.colorTableView.bind('selectedColor').to(command, 'value')
44 |
45 | dropdownView.buttonView.set({
46 | label: this.dropdownLabel,
47 | icon: this.icon,
48 | tooltip: true
49 | })
50 |
51 | dropdownView.extendTemplate({
52 | attributes: {
53 | class: 'ck-color-ui-dropdown'
54 | }
55 | })
56 |
57 | dropdownView.bind('isEnabled').to(command)
58 |
59 | dropdownView.on('execute', (evt, data) => {
60 | editor.execute(this.commandName, data)
61 | editor.editing.view.focus()
62 | })
63 |
64 | dropdownView.on('change:isOpen', (evt, name, isVisible) => {
65 | if (isVisible) {
66 | if (documentColorsCount !== 0) {
67 | this.colorTableView.updateDocumentColors(
68 | editor.model,
69 | this.componentName
70 | )
71 | }
72 | this.colorTableView.updateSelectedColors()
73 | }
74 | })
75 |
76 | return dropdownView
77 | })
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/plugin/Font/FontBackgroundColor.js:
--------------------------------------------------------------------------------
1 | import generateUIPlugin from '../generateUIPlugin'
2 | import ColorUI from './ColorUI'
3 |
4 | import {FONT_BACKGROUND_COLOR} from '@ckeditor/ckeditor5-font/src/utils'
5 | import fontBackgroundColorIcon from '@ckeditor/ckeditor5-font/theme/icons/font-background.svg'
6 |
7 | import FontBackgroundColorEditing from '@ckeditor/ckeditor5-font/src/fontbackgroundcolor/fontbackgroundcolorediting'
8 | class FontBackgroundColorUI extends ColorUI {
9 | /**
10 | * @inheritDoc
11 | */
12 | constructor(editor) {
13 | super(editor, {
14 | commandName: FONT_BACKGROUND_COLOR,
15 | componentName: FONT_BACKGROUND_COLOR,
16 | icon: fontBackgroundColorIcon,
17 | dropdownLabel: '背景颜色'
18 | })
19 | }
20 |
21 | /**
22 | * @inheritDoc
23 | */
24 | static get pluginName() {
25 | return 'FontBackgroundColorUI'
26 | }
27 | }
28 |
29 | export default generateUIPlugin('FontBackgroundColor', [
30 | FontBackgroundColorEditing,
31 | FontBackgroundColorUI
32 | ])
33 |
--------------------------------------------------------------------------------
/src/plugin/Font/FontColor.js:
--------------------------------------------------------------------------------
1 | import generateUIPlugin from '../generateUIPlugin'
2 | import FontColorEditing from '@ckeditor/ckeditor5-font/src/fontcolor/fontcolorediting'
3 |
4 | import ColorUI from './ColorUI'
5 | import {FONT_COLOR} from '@ckeditor/ckeditor5-font/src/utils'
6 | import fontColorIcon from '@ckeditor/ckeditor5-font/theme/icons/font-color.svg'
7 |
8 | /**
9 | * The font color UI plugin. It introduces the `'fontColor'` dropdown.
10 | *
11 | * @extends module:core/plugin~Plugin
12 | */
13 | class FontColorUI extends ColorUI {
14 | /**
15 | * @inheritDoc
16 | */
17 | constructor(editor) {
18 | super(editor, {
19 | commandName: FONT_COLOR,
20 | componentName: FONT_COLOR,
21 | icon: fontColorIcon,
22 | dropdownLabel: '字体颜色'
23 | })
24 | }
25 |
26 | /**
27 | * @inheritDoc
28 | */
29 | static get pluginName() {
30 | return 'FontColorUI'
31 | }
32 | }
33 |
34 | export default generateUIPlugin('FontColor', [FontColorEditing, FontColorUI])
35 |
--------------------------------------------------------------------------------
/src/plugin/Font/index.js:
--------------------------------------------------------------------------------
1 | import generateUIPlugin from '../generateUIPlugin'
2 | import FontFamily from '@ckeditor/ckeditor5-font/src/fontfamily'
3 | import FontColor from './FontColor'
4 | import FontBackgroundColor from './FontBackgroundColor'
5 |
6 | export default generateUIPlugin('Font', [
7 | FontFamily,
8 | FontColor,
9 | FontBackgroundColor
10 | ])
11 |
--------------------------------------------------------------------------------
/src/plugin/Font/utils.js:
--------------------------------------------------------------------------------
1 | import ColorTableView from './ColorTableView'
2 | export function addColorTableToDropdown({
3 | dropdownView,
4 | colors,
5 | columns,
6 | removeButtonLabel,
7 | documentColorsLabel,
8 | documentColorsCount
9 | }) {
10 | const locale = dropdownView.locale
11 | const colorTableView = new ColorTableView(locale, {
12 | colors,
13 | columns,
14 | removeButtonLabel,
15 | documentColorsLabel,
16 | documentColorsCount
17 | })
18 |
19 | dropdownView.colorTableView = colorTableView
20 | dropdownView.panelView.children.add(colorTableView)
21 |
22 | colorTableView.delegate('execute').to(dropdownView, 'execute')
23 |
24 | return colorTableView
25 | }
26 |
--------------------------------------------------------------------------------
/src/plugin/FullScreen.js:
--------------------------------------------------------------------------------
1 | // 基础功能组件
2 | import Plugin from '@ckeditor/ckeditor5-core/src/plugin'
3 | // 按钮视图组件
4 | import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'
5 |
6 | // 按钮的图标 SVG
7 | import maxIcon from '../assets/maximize.svg'
8 | import minIcon from '../assets/minimize.svg'
9 |
10 | // 自定义组件需要继承基础组件
11 | export default class FullScreen extends Plugin {
12 | // 重写基础组件的初始化函数
13 | init() {
14 | const {editor} = this
15 | // 将自定义按钮添加到当前编辑器的组件工厂
16 | editor.ui.componentFactory.add('fullScreen', locale => {
17 | // 获取当前编辑器的按钮视图
18 | const view = new ButtonView(locale)
19 |
20 | // 在按钮视图中注入自定义按钮的名称、图标
21 | view.set({
22 | label: '切换全屏',
23 | icon: maxIcon,
24 | // 开启提示,当鼠标悬浮到按钮上,会显示 label 指定的 fullscreen 字样
25 | tooltip: true
26 | })
27 |
28 | // 点击按钮
29 | view.on('execute', () => {
30 | view.set({
31 | icon: editor.ui.view.element.classList.contains('full-screen')
32 | ? maxIcon
33 | : minIcon
34 | })
35 | // ckeditor 是挂载在传入 dom 后面的,有意思
36 | editor.ui.view.element.classList.toggle('full-screen')
37 | })
38 |
39 | // 返回修改后的视图内容
40 | return view
41 | })
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/plugin/Heading.js:
--------------------------------------------------------------------------------
1 | import generateUIPlugin from './generateUIPlugin'
2 | import HeadingEditing from '@ckeditor/ckeditor5-heading/src/headingediting'
3 |
4 | import '@ckeditor/ckeditor5-heading/theme/heading.css'
5 |
6 | import Plugin from '@ckeditor/ckeditor5-core/src/plugin'
7 | import Model from '@ckeditor/ckeditor5-ui/src/model'
8 |
9 | import {
10 | createDropdown,
11 | addListToDropdown
12 | } from '@ckeditor/ckeditor5-ui/src/dropdown/utils'
13 | import {getLocalizedOptions} from '@ckeditor/ckeditor5-heading/src/utils'
14 | import DropdownButtonView from './DropdownButtonView'
15 |
16 | import Collection from '@ckeditor/ckeditor5-utils/src/collection'
17 |
18 | import '@ckeditor/ckeditor5-heading/theme/heading.css'
19 |
20 | class HeadingUI extends Plugin {
21 | /**
22 | * @inheritDoc
23 | */
24 | init() {
25 | const editor = this.editor
26 | const options = getLocalizedOptions(editor)
27 | const defaultTitle = '选择标题'
28 | const dropdownTooltip = '标题'
29 |
30 | // Register UI component.
31 | editor.ui.componentFactory.add('heading', locale => {
32 | const titles = {}
33 | const itemDefinitions = new Collection()
34 |
35 | const headingCommand = editor.commands.get('heading')
36 | const paragraphCommand = editor.commands.get('paragraph')
37 |
38 | const commands = [headingCommand]
39 |
40 | for (const option of options) {
41 | const def = {
42 | type: 'button',
43 | model: new Model({
44 | label: option.title,
45 | class: option.class,
46 | withText: true
47 | })
48 | }
49 |
50 | if (option.model === 'paragraph') {
51 | def.model.bind('isOn').to(paragraphCommand, 'value')
52 | def.model.set('commandName', 'paragraph')
53 | commands.push(paragraphCommand)
54 | } else {
55 | def.model
56 | .bind('isOn')
57 | .to(headingCommand, 'value', value => value === option.model)
58 | def.model.set({
59 | commandName: 'heading',
60 | commandValue: option.model
61 | })
62 | }
63 |
64 | // Add the option to the collection.
65 | itemDefinitions.add(def)
66 |
67 | titles[option.model] = option.title
68 | }
69 |
70 | const dropdownView = createDropdown(locale, DropdownButtonView)
71 | addListToDropdown(dropdownView, itemDefinitions)
72 |
73 | dropdownView.buttonView.set({
74 | isOn: false,
75 | withText: true,
76 | tooltip: dropdownTooltip
77 | })
78 |
79 | dropdownView.extendTemplate({
80 | attributes: {
81 | class: ['ck-heading-dropdown']
82 | }
83 | })
84 |
85 | dropdownView
86 | .bind('isEnabled')
87 | .toMany(commands, 'isEnabled', (...areEnabled) => {
88 | return areEnabled.some(isEnabled => isEnabled)
89 | })
90 |
91 | dropdownView.buttonView
92 | .bind('label')
93 | .to(
94 | headingCommand,
95 | 'value',
96 | paragraphCommand,
97 | 'value',
98 | (value, para) => {
99 | const whichModel = value || (para && 'paragraph')
100 | // If none of the commands is active, display default title.
101 | return titles[whichModel] ? titles[whichModel] : defaultTitle
102 | }
103 | )
104 |
105 | // Execute command when an item from the dropdown is selected.
106 | this.listenTo(dropdownView, 'execute', evt => {
107 | editor.execute(
108 | evt.source.commandName,
109 | evt.source.commandValue ? {value: evt.source.commandValue} : undefined
110 | )
111 | editor.editing.view.focus()
112 | })
113 |
114 | return dropdownView
115 | })
116 | }
117 | }
118 |
119 | export default generateUIPlugin('Heading', [HeadingEditing, HeadingUI])
120 |
--------------------------------------------------------------------------------
/src/plugin/Horizontalline.js:
--------------------------------------------------------------------------------
1 | import generateUIPlugin from './generateUIPlugin'
2 | import HorizontalLineEditing from '@ckeditor/ckeditor5-horizontal-line/src/horizontallineediting'
3 | import Plugin from '@ckeditor/ckeditor5-core/src/plugin'
4 | import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'
5 | import horizontalLineIcon from '../assets/horizontalline.svg'
6 |
7 | class HorizontalLineUI extends Plugin {
8 | init() {
9 | const editor = this.editor
10 | // Add the `horizontalLine` button to feature components.
11 | editor.ui.componentFactory.add('horizontalLine', locale => {
12 | const command = editor.commands.get('horizontalLine')
13 | const view = new ButtonView(locale)
14 |
15 | view.set({
16 | label: '分割线',
17 | icon: horizontalLineIcon,
18 | tooltip: true
19 | })
20 |
21 | view.bind('isEnabled').to(command, 'isEnabled')
22 |
23 | // Execute the command.
24 | this.listenTo(view, 'execute', () => editor.execute('horizontalLine'))
25 |
26 | return view
27 | })
28 | }
29 | }
30 |
31 | export default generateUIPlugin('HorizontalLine', [
32 | HorizontalLineEditing,
33 | HorizontalLineUI
34 | ])
35 |
--------------------------------------------------------------------------------
/src/plugin/ImagePreview.js:
--------------------------------------------------------------------------------
1 | import Plugin from '@ckeditor/ckeditor5-core/src/plugin'
2 | import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'
3 |
4 | import imageIcon from '../assets/zoom.svg'
5 |
6 | export default previewFunc =>
7 | class ImagePreview extends Plugin {
8 | static get pluginName() {
9 | return 'imagePreview'
10 | }
11 | init() {
12 | const editor = this.editor
13 |
14 | editor.ui.componentFactory.add('imagePreview', locale => {
15 | const view = new ButtonView(locale)
16 | view.set({
17 | label: editor.t('Preview'),
18 | icon: imageIcon,
19 | tooltip: true
20 | })
21 |
22 | // Callback executed once the image preview button is clicked.
23 | view.on('execute', () => {
24 | const el = this.editor.model.document.selection.getSelectedElement()
25 | const picUrl = el && el.getAttribute('src')
26 | previewFunc(picUrl)
27 | })
28 |
29 | return view
30 | })
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/plugin/ImageUpload.js:
--------------------------------------------------------------------------------
1 | import generateUIPlugin from './generateUIPlugin'
2 | import ImageUploadProgress from '@ckeditor/ckeditor5-image/src/imageupload/imageuploadprogress'
3 | import ImageUploadEditing from '@ckeditor/ckeditor5-image/src/imageupload/imageuploadediting'
4 |
5 | import Plugin from '@ckeditor/ckeditor5-core/src/plugin'
6 | import FileDialogButtonView from '@ckeditor/ckeditor5-upload/src/ui/filedialogbuttonview'
7 | import {createImageTypeRegExp} from '@ckeditor/ckeditor5-image/src/imageupload/utils'
8 | import imageIcon from '../assets/image.svg'
9 |
10 | class ImageUploadUI extends Plugin {
11 | /**
12 | * @inheritDoc
13 | */
14 | init() {
15 | const editor = this.editor
16 | // Setup `imageUpload` button.
17 | editor.ui.componentFactory.add('imageUpload', locale => {
18 | const view = new FileDialogButtonView(locale)
19 | const command = editor.commands.get('imageUpload')
20 | const imageTypes = editor.config.get('image.upload.types')
21 | const imageTypesRegExp = createImageTypeRegExp(imageTypes)
22 |
23 | view.set({
24 | acceptedType: imageTypes.map(type => `image/${type}`).join(','),
25 | allowMultipleFiles: true
26 | })
27 |
28 | view.buttonView.set({
29 | label: '插入图像',
30 | icon: imageIcon,
31 | tooltip: true
32 | })
33 |
34 | view.buttonView.bind('isEnabled').to(command)
35 |
36 | view.on('done', (evt, files) => {
37 | const imagesToUpload = Array.from(files).filter(file =>
38 | imageTypesRegExp.test(file.type)
39 | )
40 |
41 | if (imagesToUpload.length) {
42 | editor.execute('imageUpload', {file: imagesToUpload})
43 | }
44 | })
45 |
46 | return view
47 | })
48 | }
49 | }
50 |
51 | export default generateUIPlugin('ImageUpload', [
52 | ImageUploadEditing,
53 | ImageUploadUI,
54 | ImageUploadProgress
55 | ])
56 |
--------------------------------------------------------------------------------
/src/plugin/Italic.js:
--------------------------------------------------------------------------------
1 | import generateUIPlugin from './generateUIPlugin'
2 | import ItalicEditing from '@ckeditor/ckeditor5-basic-styles/src/italic/italicediting'
3 |
4 | /**
5 | * @module basic-styles/italic/italicui
6 | */
7 |
8 | import Plugin from '@ckeditor/ckeditor5-core/src/plugin'
9 | import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'
10 |
11 | import italicIcon from '../assets/italic.svg'
12 |
13 | const ITALIC = 'italic'
14 |
15 | /**
16 | * The italic UI feature. It introduces the Italic button.
17 | *
18 | * @extends module:core/plugin~Plugin
19 | */
20 | class ItalicUI extends Plugin {
21 | /**
22 | * @inheritDoc
23 | */
24 | init() {
25 | const editor = this.editor
26 |
27 | // Add bold button to feature components.
28 | editor.ui.componentFactory.add(ITALIC, locale => {
29 | const command = editor.commands.get(ITALIC)
30 | const view = new ButtonView(locale)
31 |
32 | view.set({
33 | label: '斜体',
34 | icon: italicIcon,
35 | keystroke: 'CTRL+I',
36 | tooltip: true,
37 | isToggleable: true
38 | })
39 |
40 | view.bind('isOn', 'isEnabled').to(command, 'value', 'isEnabled')
41 |
42 | // Execute command.
43 | this.listenTo(view, 'execute', () => editor.execute(ITALIC))
44 |
45 | return view
46 | })
47 | }
48 | }
49 |
50 | export default generateUIPlugin('Italic', [ItalicEditing, ItalicUI])
51 |
--------------------------------------------------------------------------------
/src/plugin/Link.js:
--------------------------------------------------------------------------------
1 | import generateUIPlugin from './generateUIPlugin'
2 | import LinkEditing from '@ckeditor/ckeditor5-link/src/linkediting'
3 | import LinkUI from '@ckeditor/ckeditor5-link/src/linkui'
4 | import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'
5 |
6 | import linkIcon from '../assets/link.svg'
7 | const linkKeystroke = 'Ctrl+K'
8 | class LinkUICustom extends LinkUI {
9 | _createToolbarLinkButton() {
10 | const editor = this.editor
11 | const linkCommand = editor.commands.get('link')
12 | // Handle the `Ctrl+K` keystroke and show the panel.
13 | editor.keystrokes.set(linkKeystroke, (keyEvtData, cancel) => {
14 | // Prevent focusing the search bar in FF and opening new tab in Edge. #153, #154.
15 | cancel()
16 |
17 | if (linkCommand.isEnabled) {
18 | this._showUI(true)
19 | }
20 | })
21 |
22 | editor.ui.componentFactory.add('link', locale => {
23 | const button = new ButtonView(locale)
24 |
25 | button.isEnabled = true
26 | button.label = '超链接'
27 | button.icon = linkIcon
28 | button.keystroke = linkKeystroke
29 | button.tooltip = true
30 | button.isToggleable = true
31 |
32 | // Bind button to the command.
33 | button.bind('isEnabled').to(linkCommand, 'isEnabled')
34 | button.bind('isOn').to(linkCommand, 'value', value => !!value)
35 |
36 | // Show the panel on button click.
37 | this.listenTo(button, 'execute', () => this._showUI(true))
38 |
39 | return button
40 | })
41 | }
42 | }
43 |
44 | export default generateUIPlugin('Link', [LinkEditing, LinkUICustom])
45 |
--------------------------------------------------------------------------------
/src/plugin/List.js:
--------------------------------------------------------------------------------
1 | import generateUIPlugin from './generateUIPlugin'
2 | import ListEditing from '@ckeditor/ckeditor5-list/src/listediting'
3 |
4 | /**
5 | * @module list/listui
6 | */
7 |
8 | import {createUIComponent} from '@ckeditor/ckeditor5-list/src/utils'
9 |
10 | import numberedListIcon from '../assets/numberedlist.svg'
11 | import bulletedListIcon from '../assets/bulletedlist.svg'
12 |
13 | import Plugin from '@ckeditor/ckeditor5-core/src/plugin'
14 |
15 | /**
16 | * The list UI feature. It introduces the `'numberedList'` and `'bulletedList'` buttons that
17 | * allow to convert paragraphs to and from list items and indent or outdent them.
18 | *
19 | * @extends module:core/plugin~Plugin
20 | */
21 | class ListUI extends Plugin {
22 | /**
23 | * @inheritDoc
24 | */
25 | init() {
26 | // Create two buttons and link them with numberedList and bulletedList commands.
27 | createUIComponent(this.editor, 'numberedList', '有序列表', numberedListIcon)
28 | createUIComponent(this.editor, 'bulletedList', '无序列表', bulletedListIcon)
29 | }
30 | }
31 |
32 | export default generateUIPlugin('List', [ListEditing, ListUI])
33 |
--------------------------------------------------------------------------------
/src/plugin/Mediaembed/AutoMediaEmbed.js:
--------------------------------------------------------------------------------
1 | import AutoMediaEmbed from '@ckeditor/ckeditor5-media-embed/src/automediaembed'
2 | import Clipboard from '@ckeditor/ckeditor5-clipboard/src/clipboard'
3 |
4 | import Undo from '../Undo'
5 |
6 | export default class AutoMediaEmbedCustom extends AutoMediaEmbed {
7 | static get requires() {
8 | return [Clipboard, Undo]
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/plugin/Mediaembed/index.js:
--------------------------------------------------------------------------------
1 | import generateUIPlugin from '../generateUIPlugin'
2 |
3 | import MediaEmbedEditing from '@ckeditor/ckeditor5-media-embed/src/mediaembedediting'
4 | import AutoMediaEmbed from './AutoMediaEmbed'
5 | import MediaEmbedUI from '@ckeditor/ckeditor5-media-embed/src/mediaembedui'
6 | import MediaFormView from '@ckeditor/ckeditor5-media-embed/src/ui/mediaformview'
7 | import Widget from '@ckeditor/ckeditor5-widget/src/widget'
8 |
9 | import {createDropdown} from '@ckeditor/ckeditor5-ui/src/dropdown/utils'
10 | import DropdownButtonView from '../DropdownButtonView'
11 |
12 | import '@ckeditor/ckeditor5-media-embed/theme/mediaembed.css'
13 |
14 | import mediaIcon from '../../assets/media.svg'
15 |
16 | function getFormValidators(registry) {
17 | return [
18 | form => {
19 | if (!form.url.length) {
20 | return 'URL不能为空'
21 | }
22 | },
23 | form => {
24 | if (!registry.hasMedia(form.url)) {
25 | return '这个媒体链接不支持'
26 | }
27 | }
28 | ]
29 | }
30 | class MediaEmbedUICustom extends MediaEmbedUI {
31 | init() {
32 | const editor = this.editor
33 | const command = editor.commands.get('mediaEmbed')
34 | const registry = editor.plugins.get(MediaEmbedEditing).registry
35 |
36 | /**
37 | * The form view displayed inside the drop-down.
38 | *
39 | * @member {module:media-embed/ui/mediaformview~MediaFormView}
40 | */
41 | this.form = new MediaFormView(getFormValidators(registry), editor.locale)
42 |
43 | // Setup `imageUpload` button.
44 | editor.ui.componentFactory.add('mediaEmbed', locale => {
45 | const dropdown = createDropdown(locale, DropdownButtonView)
46 |
47 | this._setUpDropdown(dropdown, this.form, command, editor)
48 | this._setUpForm(this.form, dropdown, command)
49 |
50 | return dropdown
51 | })
52 | }
53 | _setUpDropdown(dropdown, form, command) {
54 | const editor = this.editor
55 | const button = dropdown.buttonView
56 |
57 | dropdown.bind('isEnabled').to(command)
58 | dropdown.panelView.children.add(form)
59 |
60 | button.set({
61 | label: '插入媒体',
62 | icon: mediaIcon,
63 | tooltip: true
64 | })
65 |
66 | button.on(
67 | 'open',
68 | () => {
69 | form.url = command.value || ''
70 | form.urlInputView.select()
71 | form.focus()
72 | },
73 | {priority: 'low'}
74 | )
75 |
76 | dropdown.on('submit', () => {
77 | if (form.isValid()) {
78 | editor.execute('mediaEmbed', form.url)
79 | closeUI()
80 | }
81 | })
82 |
83 | dropdown.on('change:isOpen', () => form.resetFormStatus())
84 | dropdown.on('cancel', () => closeUI())
85 |
86 | function closeUI() {
87 | editor.editing.view.focus()
88 | dropdown.isOpen = false
89 | }
90 | }
91 | }
92 |
93 | export default generateUIPlugin('MediaEmbed', [
94 | MediaEmbedEditing,
95 | MediaEmbedUICustom,
96 | AutoMediaEmbed,
97 | Widget
98 | ])
99 |
--------------------------------------------------------------------------------
/src/plugin/RemoveFormat/RemoveFormatLinks.js:
--------------------------------------------------------------------------------
1 | // https://ckeditor.com/docs/ckeditor5/latest/features/remove-format.html#integrating-with-editor-features
2 | export default function RemoveFormatLinks(editor) {
3 | // Extend the editor schema and mark the "linkHref" model attribute as formatting.
4 | editor.model.schema.setAttributeProperties('linkHref', {
5 | isFormatting: true
6 | })
7 | }
8 |
--------------------------------------------------------------------------------
/src/plugin/RemoveFormat/RemoveFormatUI.js:
--------------------------------------------------------------------------------
1 | import RemoveFormatUI from '@ckeditor/ckeditor5-remove-format/src/removeformatui'
2 | import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'
3 | // 只有图标和原本不一样
4 | import removeButtonIcon from '../../assets/eraser.svg'
5 |
6 | const REMOVE_FORMAT = 'removeFormat'
7 |
8 | export default class extends RemoveFormatUI {
9 | init() {
10 | const editor = this.editor
11 |
12 | editor.ui.componentFactory.add(REMOVE_FORMAT, locale => {
13 | const command = editor.commands.get(REMOVE_FORMAT)
14 | const view = new ButtonView(locale)
15 |
16 | view.set({
17 | label: '清除格式',
18 | icon: removeButtonIcon,
19 | tooltip: true
20 | })
21 |
22 | view.bind('isOn', 'isEnabled').to(command, 'value', 'isEnabled')
23 |
24 | // Execute the command.
25 | this.listenTo(view, 'execute', () => {
26 | editor.execute(REMOVE_FORMAT)
27 | editor.editing.view.focus()
28 | })
29 |
30 | return view
31 | })
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/plugin/RemoveFormat/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * https://github.com/ckeditor/ckeditor5-remove-format/tree/master
3 | * 在官方 remove-format 基础上:
4 | * - links 也会被清除
5 | * - 图标改成橡皮擦,和 fontColor 的清除图标一样
6 | */
7 | import Plugin from '@ckeditor/ckeditor5-core/src/plugin'
8 |
9 | import RemoveFormatUI from './RemoveFormatUI'
10 | import RemoveFormatLinks from './RemoveFormatLinks'
11 | import RemoveFormatEditing from '@ckeditor/ckeditor5-remove-format/src/removeformatediting'
12 |
13 | export default class RemoveFormat extends Plugin {
14 | static get requires() {
15 | return [RemoveFormatEditing, RemoveFormatUI, RemoveFormatLinks]
16 | }
17 | static get pluginName() {
18 | return 'RemoveFormat'
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/plugin/Strikethrough.js:
--------------------------------------------------------------------------------
1 | import generateUIPlugin from './generateUIPlugin'
2 | import StrikethroughEditing from '@ckeditor/ckeditor5-basic-styles/src/strikethrough/strikethroughediting'
3 | /**
4 | * @module basic-styles/strikethrough/strikethroughui
5 | */
6 |
7 | import Plugin from '@ckeditor/ckeditor5-core/src/plugin'
8 | import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'
9 |
10 | import strikethroughIcon from '../assets/strikethrough.svg'
11 |
12 | const STRIKETHROUGH = 'strikethrough'
13 |
14 | /**
15 | * The strikethrough UI feature. It introduces the Strikethrough button.
16 | *
17 | * @extends module:core/plugin~Plugin
18 | */
19 | class StrikethroughUI extends Plugin {
20 | /**
21 | * @inheritDoc
22 | */
23 | init() {
24 | const editor = this.editor
25 |
26 | // Add strikethrough button to feature components.
27 | editor.ui.componentFactory.add(STRIKETHROUGH, locale => {
28 | const command = editor.commands.get(STRIKETHROUGH)
29 | const view = new ButtonView(locale)
30 |
31 | view.set({
32 | label: '删除线',
33 | icon: strikethroughIcon,
34 | keystroke: 'CTRL+SHIFT+X',
35 | tooltip: true,
36 | isToggleable: true
37 | })
38 |
39 | view.bind('isOn', 'isEnabled').to(command, 'value', 'isEnabled')
40 |
41 | // Execute command.
42 | this.listenTo(view, 'execute', () => editor.execute(STRIKETHROUGH))
43 |
44 | return view
45 | })
46 | }
47 | }
48 |
49 | export default generateUIPlugin('Strikethrough', [
50 | StrikethroughEditing,
51 | StrikethroughUI
52 | ])
53 |
--------------------------------------------------------------------------------
/src/plugin/Table.js:
--------------------------------------------------------------------------------
1 | import generateUIPlugin from './generateUIPlugin'
2 |
3 | import TableEditing from '@ckeditor/ckeditor5-table/src/tableediting'
4 | import TableUI from '@ckeditor/ckeditor5-table/src/tableui'
5 | import Widget from '@ckeditor/ckeditor5-widget/src/widget'
6 |
7 | import '@ckeditor/ckeditor5-table/theme/table.css'
8 |
9 | import {createDropdown} from '@ckeditor/ckeditor5-ui/src/dropdown/utils'
10 | import DropdownButtonView from './DropdownButtonView'
11 | import InsertTableView from '@ckeditor/ckeditor5-table/src/ui/inserttableview'
12 |
13 | import tableIcon from '../assets/table.svg'
14 | import tableColumnIcon from '@ckeditor/ckeditor5-table/theme/icons/table-column.svg'
15 | import tableRowIcon from '@ckeditor/ckeditor5-table/theme/icons/table-row.svg'
16 | import tableMergeCellIcon from '@ckeditor/ckeditor5-table/theme/icons/table-merge-cell.svg'
17 |
18 | class TableUICustom extends TableUI {
19 | init() {
20 | this.addInsertTable()
21 | this.addTableColumn()
22 | this.addTableRow()
23 | this.addMergeTableCells()
24 | }
25 | addInsertTable() {
26 | const editor = this.editor
27 | editor.ui.componentFactory.add('insertTable', locale => {
28 | const command = editor.commands.get('insertTable')
29 | const dropdownView = createDropdown(locale, DropdownButtonView)
30 |
31 | dropdownView.bind('isEnabled').to(command)
32 |
33 | // Decorate dropdown's button.
34 | dropdownView.buttonView.set({
35 | icon: tableIcon,
36 | label: '插入表格',
37 | tooltip: true
38 | })
39 |
40 | // Prepare custom view for dropdown's panel.
41 | const insertTableView = new InsertTableView(locale)
42 | dropdownView.panelView.children.add(insertTableView)
43 |
44 | insertTableView.delegate('execute').to(dropdownView)
45 |
46 | dropdownView.buttonView.on('open', () => {
47 | // Reset the chooser before showing it to the user.
48 | insertTableView.rows = 0
49 | insertTableView.columns = 0
50 | })
51 |
52 | dropdownView.on('execute', () => {
53 | editor.execute('insertTable', {
54 | rows: insertTableView.rows,
55 | columns: insertTableView.columns
56 | })
57 | editor.editing.view.focus()
58 | })
59 |
60 | return dropdownView
61 | })
62 | }
63 | addTableColumn() {
64 | const editor = this.editor
65 | const contentLanguageDirection = editor.locale.contentLanguageDirection
66 | const isContentLtr = contentLanguageDirection === 'ltr'
67 | editor.ui.componentFactory.add('tableColumn', locale => {
68 | const options = [
69 | {
70 | type: 'switchbutton',
71 | model: {
72 | commandName: 'setTableColumnHeader',
73 | label: '标题列',
74 | bindIsOn: true
75 | }
76 | },
77 | {type: 'separator'},
78 | {
79 | type: 'button',
80 | model: {
81 | commandName: isContentLtr
82 | ? 'insertTableColumnLeft'
83 | : 'insertTableColumnRight',
84 | label: '向左插入列'
85 | }
86 | },
87 | {
88 | type: 'button',
89 | model: {
90 | commandName: isContentLtr
91 | ? 'insertTableColumnRight'
92 | : 'insertTableColumnLeft',
93 | label: '向右插入列'
94 | }
95 | },
96 | {
97 | type: 'button',
98 | model: {
99 | commandName: 'removeTableColumn',
100 | label: '删除列'
101 | }
102 | }
103 | ]
104 |
105 | return this._prepareDropdown('列', tableColumnIcon, options, locale)
106 | })
107 | }
108 | addTableRow() {
109 | const editor = this.editor
110 | editor.ui.componentFactory.add('tableRow', locale => {
111 | const options = [
112 | {
113 | type: 'switchbutton',
114 | model: {
115 | commandName: 'setTableRowHeader',
116 | label: '标题行',
117 | bindIsOn: true
118 | }
119 | },
120 | {type: 'separator'},
121 | {
122 | type: 'button',
123 | model: {
124 | commandName: 'insertTableRowBelow',
125 | label: '向下插入一行'
126 | }
127 | },
128 | {
129 | type: 'button',
130 | model: {
131 | commandName: 'insertTableRowAbove',
132 | label: '向上插入一行'
133 | }
134 | },
135 | {
136 | type: 'button',
137 | model: {
138 | commandName: 'removeTableRow',
139 | label: '删除本行'
140 | }
141 | }
142 | ]
143 |
144 | return this._prepareDropdown('行', tableRowIcon, options, locale)
145 | })
146 | }
147 | addMergeTableCells() {
148 | const editor = this.editor
149 | const contentLanguageDirection = editor.locale.contentLanguageDirection
150 | const isContentLtr = contentLanguageDirection === 'ltr'
151 | editor.ui.componentFactory.add('mergeTableCells', locale => {
152 | const options = [
153 | {
154 | type: 'button',
155 | model: {
156 | commandName: 'mergeTableCellUp',
157 | label: '向上合并单元格'
158 | }
159 | },
160 | {
161 | type: 'button',
162 | model: {
163 | commandName: isContentLtr
164 | ? 'mergeTableCellRight'
165 | : 'mergeTableCellLeft',
166 | label: '向右合并单元格'
167 | }
168 | },
169 | {
170 | type: 'button',
171 | model: {
172 | commandName: 'mergeTableCellDown',
173 | label: '向下合并单元格'
174 | }
175 | },
176 | {
177 | type: 'button',
178 | model: {
179 | commandName: isContentLtr
180 | ? 'mergeTableCellLeft'
181 | : 'mergeTableCellRight',
182 | label: '向左合并单元格'
183 | }
184 | },
185 | {type: 'separator'},
186 | {
187 | type: 'button',
188 | model: {
189 | commandName: 'splitTableCellVertically',
190 | label: '垂直拆分单元格'
191 | }
192 | },
193 | {
194 | type: 'button',
195 | model: {
196 | commandName: 'splitTableCellHorizontally',
197 | label: '水平拆分单元格'
198 | }
199 | }
200 | ]
201 |
202 | return this._prepareDropdown(
203 | '合并单元格',
204 | tableMergeCellIcon,
205 | options,
206 | locale
207 | )
208 | })
209 | }
210 | }
211 |
212 | export default generateUIPlugin('Table', [TableEditing, TableUICustom, Widget])
213 |
--------------------------------------------------------------------------------
/src/plugin/TodoList.js:
--------------------------------------------------------------------------------
1 | import generateUIPlugin from './generateUIPlugin'
2 | import TodoListEditing from '@ckeditor/ckeditor5-list/src/todolistediting'
3 | /**
4 | * @module list/todolistui
5 | */
6 |
7 | import {createUIComponent} from '@ckeditor/ckeditor5-list/src/utils'
8 | import todoListIcon from '../assets/todolist.svg'
9 | import Plugin from '@ckeditor/ckeditor5-core/src/plugin'
10 | import '@ckeditor/ckeditor5-list/theme/todolist.css'
11 |
12 | /**
13 | * The to-do list UI feature. It introduces the `'todoList'` button that
14 | * allows to convert elements to and from to-do list items and to indent or outdent them.
15 | *
16 | * @extends module:core/plugin~Plugin
17 | */
18 | class TodoListUI extends Plugin {
19 | /**
20 | * @inheritDoc
21 | */
22 | init() {
23 | createUIComponent(this.editor, 'todoList', '待办列表', todoListIcon)
24 | }
25 | }
26 |
27 | export default generateUIPlugin('todoList', [TodoListEditing, TodoListUI])
28 |
--------------------------------------------------------------------------------
/src/plugin/TransformMD.js:
--------------------------------------------------------------------------------
1 | import Plugin from '@ckeditor/ckeditor5-core/src/plugin'
2 | import marked from 'marked'
3 | import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor'
4 |
5 | export default class TransformMD extends Plugin {
6 | afterInit() {
7 | const editor = this.editor
8 | const model = editor.model
9 | const view = editor.editing.view
10 | const viewDocument = view.document
11 | // 把富文本转成编辑器看得懂的内容
12 | const dataProcessor = new HtmlDataProcessor(viewDocument)
13 |
14 | //等同 addEventListener
15 | this.listenTo(viewDocument, 'clipboardInput', (event, data) => {
16 | const dataTransfer = data.dataTransfer
17 | // 1 | - | * | # | ![] | []()
18 | const regex = /[-*1#]{1,6}\s+|!?\[\S*\][\s*|(\S*)].*?[-*1#]?/g
19 | let text = dataTransfer.getData('text/plain')
20 |
21 | // 确认是 markdown 应该八九不离十
22 | if (regex.test(text)) {
23 | let content = marked(text, {
24 | breaks: true
25 | })
26 | content = dataProcessor.toView(content)
27 |
28 | if (!content.isEmpty) {
29 | const dataController = this.editor.data
30 | const modelFragment = dataController.toModel(
31 | content,
32 | '$clipboardHolder'
33 | )
34 |
35 | model.insertContent(modelFragment)
36 | }
37 | view.scrollToTheSelection()
38 | // 终止事件,不会继续往下传;
39 | // 如果不满足,也就是 event 放行,即会接下去的监听事件继续工作
40 | event.stop()
41 | }
42 | })
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/plugin/Underline.js:
--------------------------------------------------------------------------------
1 | import generateUIPlugin from './generateUIPlugin'
2 | import UnderlineEditing from '@ckeditor/ckeditor5-basic-styles/src/underline/underlineediting'
3 |
4 | /**
5 | * @module basic-styles/underline/underlineui
6 | */
7 |
8 | import Plugin from '@ckeditor/ckeditor5-core/src/plugin'
9 | import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'
10 |
11 | import underlineIcon from '../assets/underline.svg'
12 |
13 | const UNDERLINE = 'underline'
14 |
15 | /**
16 | * The underline UI feature. It introduces the Underline button.
17 | *
18 | * @extends module:core/plugin~Plugin
19 | */
20 | class UnderlineUI extends Plugin {
21 | /**
22 | * @inheritDoc
23 | */
24 | init() {
25 | const editor = this.editor
26 |
27 | // Add bold button to feature components.
28 | editor.ui.componentFactory.add(UNDERLINE, locale => {
29 | const command = editor.commands.get(UNDERLINE)
30 | const view = new ButtonView(locale)
31 |
32 | view.set({
33 | label: '下划线',
34 | icon: underlineIcon,
35 | keystroke: 'CTRL+U',
36 | tooltip: true,
37 | isToggleable: true
38 | })
39 |
40 | view.bind('isOn', 'isEnabled').to(command, 'value', 'isEnabled')
41 |
42 | // Execute command.
43 | this.listenTo(view, 'execute', () => editor.execute(UNDERLINE))
44 |
45 | return view
46 | })
47 | }
48 | }
49 |
50 | export default generateUIPlugin('Underline', [UnderlineEditing, UnderlineUI])
51 |
--------------------------------------------------------------------------------
/src/plugin/Undo.js:
--------------------------------------------------------------------------------
1 | import generateUIPlugin from './generateUIPlugin'
2 | import UndoEditing from '@ckeditor/ckeditor5-undo/src/undoediting'
3 | import UndoUI from '@ckeditor/ckeditor5-undo/src/undoui'
4 |
5 | import undoIcon from '../assets/undo.svg'
6 | import redoIcon from '../assets/redo.svg'
7 |
8 | /**
9 | * The undo UI feature. It introduces the `'undo'` and `'redo'` buttons to the editor.
10 | *
11 | * @extends module:core/plugin~Plugin
12 | */
13 | class UndoUICustom extends UndoUI {
14 | /**
15 | * @inheritDoc
16 | */
17 | init() {
18 | const editor = this.editor
19 | const locale = editor.locale
20 |
21 | const localizedUndoIcon =
22 | locale.uiLanguageDirection == 'ltr' ? undoIcon : redoIcon
23 | const localizedRedoIcon =
24 | locale.uiLanguageDirection == 'ltr' ? redoIcon : undoIcon
25 |
26 | this._addButton('undo', '撤销', 'CTRL+Z', localizedUndoIcon)
27 | this._addButton('redo', '重做', 'CTRL+Y', localizedRedoIcon)
28 | }
29 | }
30 | export default generateUIPlugin('Undo', [UndoEditing, UndoUICustom])
31 |
--------------------------------------------------------------------------------
/src/plugin/Uploader.js:
--------------------------------------------------------------------------------
1 | import Plugin from '@ckeditor/ckeditor5-core/src/plugin'
2 | import {UploadAdapter} from '../utils/adapter'
3 |
4 | /**
5 | * @param {function} uploadFunc upload function for upload image and attachment
6 | */
7 | export default uploadFunc =>
8 | class Uploader extends Plugin {
9 | /**
10 | * @inheritDoc
11 | */
12 | static get pluginName() {
13 | return 'Uploader'
14 | }
15 |
16 | /**
17 | * @inheritDoc
18 | */
19 | init() {
20 | const {editor} = this
21 | editor.plugins.get('FileRepository').createUploadAdapter = loader => {
22 | return new UploadAdapter(loader, uploadFunc)
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/plugin/generateUIPlugin.js:
--------------------------------------------------------------------------------
1 | import Plugin from '@ckeditor/ckeditor5-core/src/plugin'
2 |
3 | export default (name, plugins) =>
4 | class VEditorPlugin extends Plugin {
5 | /**
6 | * @inheritDoc
7 | */
8 | static get requires() {
9 | return plugins
10 | }
11 |
12 | /**
13 | * @inheritDoc
14 | */
15 | static get pluginName() {
16 | return name
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/translations/en.js:
--------------------------------------------------------------------------------
1 | import {add} from '@ckeditor/ckeditor5-utils/src/translation-service.js'
2 |
3 | add('en', {
4 | Preview: 'Preview'
5 | })
6 |
--------------------------------------------------------------------------------
/src/translations/index.js:
--------------------------------------------------------------------------------
1 | import './en.js'
2 | import './zh-cn.js'
3 |
--------------------------------------------------------------------------------
/src/translations/zh-cn.js:
--------------------------------------------------------------------------------
1 | import {add} from '@ckeditor/ckeditor5-utils/src/translation-service.js'
2 |
3 | add('zh-cn', {
4 | Preview: '预览'
5 | })
6 |
--------------------------------------------------------------------------------
/src/utils/adapter.js:
--------------------------------------------------------------------------------
1 | export class UploadAdapter {
2 | constructor(loader, uploadFunc) {
3 | this.loader = loader
4 | this.uploadFunc = uploadFunc
5 | }
6 |
7 | /**
8 | *
9 | * @param {string} fileMIMEType mime-type
10 | * see: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types
11 | */
12 | async upload() {
13 | try {
14 | const file = await this.loader.file
15 | // 图片多选时会逐个调用此方法
16 | const url = await this.uploadFunc(file)
17 | // 没有url意味着上传没有执行,需要reject
18 | if (url) {
19 | return {default: url}
20 | } else {
21 | return Promise.reject(url)
22 | }
23 | } catch (error) {
24 | return Promise.reject(error)
25 | }
26 | }
27 |
28 | abort() {}
29 | }
30 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export function replaceNewlineWithBr(str) {
2 | return str.replace(/\r?\n/g, '
')
3 | }
4 |
--------------------------------------------------------------------------------
/src/v-editor.d.ts:
--------------------------------------------------------------------------------
1 | import Vue, {VueConstructor} from 'vue'
2 |
3 | declare module '@femessage/v-editor' {
4 | class FemessageComponent extends Vue {
5 | static install(vue: typeof Vue): void
6 | }
7 |
8 | type CombinedVueInstance<
9 | Instance extends Vue,
10 | Data,
11 | Methods,
12 | Computed,
13 | Props
14 | > = Data & Methods & Computed & Props & Instance
15 |
16 | type ExtendedVue<
17 | Instance extends Vue,
18 | Data,
19 | Methods,
20 | Computed,
21 | Props
22 | > = VueConstructor<
23 | CombinedVueInstance & Vue
24 | >
25 |
26 | type Combined = Data &
27 | Methods &
28 | Computed &
29 | Props
30 |
31 | type VEditorData = {
32 | editor: any
33 | ClassicEditor: any
34 | isFullScreen: boolean
35 | uploaderAccept: string
36 | previewImageUrl: string
37 | }
38 |
39 | type VEditorMethods = {}
40 |
41 | type VEditorComputed = {}
42 |
43 | type VEditorProps = {
44 | placeholder: string
45 | height: number | string
46 | uploadOptions: object
47 | value: string
48 | editorOptions: object
49 | disabled: boolean
50 | onUploadFail: (status: boolean, error?: any) => void
51 | autosize: object
52 | }
53 |
54 | type VEditor = Combined<
55 | VEditorData,
56 | VEditorMethods,
57 | VEditorComputed,
58 | VEditorProps
59 | >
60 |
61 | export interface VEditorType extends FemessageComponent, VEditor {}
62 |
63 | const VEditorConstruction: ExtendedVue<
64 | Vue,
65 | VEditorData,
66 | VEditorMethods,
67 | VEditorComputed,
68 | VEditorProps
69 | >
70 |
71 | export default VEditorConstruction
72 | }
73 |
--------------------------------------------------------------------------------
/src/v-editor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
17 |
18 |
19 |
20 |
21 |
28 |
29 |
30 |
31 |
32 |
269 |
270 |
382 |
383 |
474 |
--------------------------------------------------------------------------------
/styleguide.config.js:
--------------------------------------------------------------------------------
1 | const {VueLoaderPlugin} = require('vue-loader')
2 | const {styles} = require('@ckeditor/ckeditor5-dev-utils')
3 | const CKEditorWebpackPlugin = require('@ckeditor/ckeditor5-dev-webpack-plugin')
4 | const path = require('path')
5 | const glob = require('glob')
6 | const env = Object.assign({}, require('dotenv').config().parsed, {
7 | UPLOAD_ACTION: process.env.UPLOAD_ACTION,
8 | OSS_BUCKET: process.env.OSS_BUCKET,
9 | OSS_REGION: process.env.OSS_REGION
10 | })
11 |
12 | const demos = glob.sync('docs/!(basic).md')
13 | const demoSections = [
14 | {
15 | name: 'basic',
16 | content: 'docs/basic.md'
17 | }
18 | ].concat(
19 | demos.map(filePath => ({
20 | name: path.basename(filePath, '.md'),
21 | content: filePath
22 | }))
23 | )
24 |
25 | module.exports = {
26 | styleguideDir: 'docs',
27 | pagePerSection: true,
28 | ribbon: {
29 | url: 'https://github.com/FEMessage/v-editor'
30 | },
31 | require: ['./styleguide/element.js', './styleguide/upload-to-ali.js'],
32 | sections: [
33 | {
34 | name: 'Components',
35 | components: 'src/*.vue',
36 | usageMode: 'expand'
37 | },
38 | {
39 | name: 'Demo',
40 | sections: demoSections,
41 | sectionDepth: 2
42 | },
43 | {
44 | name: 'FAQ',
45 | content: 'docs/faq.md'
46 | }
47 | ],
48 | webpackConfig: {
49 | module: {
50 | rules: [
51 | {
52 | test: /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/,
53 | use: [
54 | {
55 | loader: 'style-loader',
56 | options: {
57 | injectType: 'singletonStyleTag'
58 | }
59 | },
60 | {
61 | loader: 'postcss-loader',
62 | options: styles.getPostCssConfig({
63 | themeImporter: {
64 | themePath: require.resolve('@ckeditor/ckeditor5-theme-lark')
65 | },
66 | minify: true
67 | })
68 | }
69 | ]
70 | },
71 | {
72 | test: /\.svg$/,
73 | use: ['raw-loader']
74 | },
75 | {
76 | test: /\.vue$/,
77 | loader: 'vue-loader'
78 | },
79 | {
80 | test: /\.css$/,
81 | exclude: /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css$/,
82 | loaders: ['style-loader', 'css-loader']
83 | },
84 | {
85 | test: /\.less$/,
86 | loaders: ['vue-style-loader', 'css-loader', 'less-loader']
87 | },
88 | {
89 | test: /\.(woff2?|eot|[ot]tf)(\?.*)?$/,
90 | loader: 'file-loader'
91 | }
92 | ]
93 | },
94 | plugins: [
95 | new VueLoaderPlugin(),
96 | new CKEditorWebpackPlugin({
97 | language: 'zh-cn',
98 | additionalLanguages: 'all'
99 | }),
100 | new (require('webpack')).DefinePlugin({
101 | 'process.env': JSON.stringify(env)
102 | })
103 | ]
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/styleguide/element.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Elm from 'element-ui'
3 | import 'element-ui/lib/theme-chalk/index.css'
4 | import CKEditorInspector from '@ckeditor/ckeditor5-inspector'
5 |
6 | Vue.use(Elm)
7 | window.CKEditorInspector = CKEditorInspector
8 |
--------------------------------------------------------------------------------
/styleguide/upload-to-ali.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Upload from '@femessage/upload-to-ali'
3 |
4 | Vue.component('UploadToAli', Upload)
5 |
--------------------------------------------------------------------------------
/test/index.test.js:
--------------------------------------------------------------------------------
1 | import {replaceNewlineWithBr} from '../src/utils'
2 |
3 | test('replaceNewlineWithBr', () => {
4 | const strInWindows = '1.登录平台,选择需求池tab\r\n2.需求列表有需求'
5 | const strInUnix = '1.登录平台,选择需求池tab\n2.需求列表有需求'
6 | const normalStr = `1.登录平台,选择需求池tab
2.需求列表有需求`
7 | expect(replaceNewlineWithBr(strInWindows)).toBe(normalStr)
8 | expect(replaceNewlineWithBr(strInUnix)).toBe(normalStr)
9 | })
10 |
--------------------------------------------------------------------------------
/test/sum.js:
--------------------------------------------------------------------------------
1 | export default function sum(a, b) {
2 | return a + b
3 | }
4 |
--------------------------------------------------------------------------------