├── .gitignore
├── .node-version
├── fonts
├── .DS_Store
├── lite-editor-fonts-20161209.eot
├── lite-editor-fonts-20161209.ttf
├── lite-editor-fonts-20161209.woff
└── lite-editor-fonts-20161209.svg
├── src
├── index.js
├── scss
│ ├── _variables.scss
│ ├── _fonts.scss
│ └── lite-editor.scss
├── core
│ ├── editor.html
│ ├── tooltip.html
│ ├── btn.html
│ └── index.js
└── lib
│ └── util.js
├── examples
├── style.css
├── index.html
├── multi.html
└── complex.html
├── images
├── icon_arrow_prev.svg
├── icon_arrow_next.svg
├── icon_close.svg
└── Slice.svg
├── vite.config.js
├── .editorconfig
├── package.json
├── LICENSE
├── readme.md
├── css
└── lite-editor.css
└── .csscomb.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/.node-version:
--------------------------------------------------------------------------------
1 | 22.20.0
2 |
--------------------------------------------------------------------------------
/fonts/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appleple/lite-editor/HEAD/fonts/.DS_Store
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import LiteEditor from './core';
2 | import './scss/lite-editor.scss';
3 |
4 | export default LiteEditor;
5 |
--------------------------------------------------------------------------------
/fonts/lite-editor-fonts-20161209.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appleple/lite-editor/HEAD/fonts/lite-editor-fonts-20161209.eot
--------------------------------------------------------------------------------
/fonts/lite-editor-fonts-20161209.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appleple/lite-editor/HEAD/fonts/lite-editor-fonts-20161209.ttf
--------------------------------------------------------------------------------
/examples/style.css:
--------------------------------------------------------------------------------
1 | .red {
2 | color: red;
3 | }
4 | .blue {
5 | color: blue;
6 | }
7 | .yellow {
8 | color: yellow;
9 | }
--------------------------------------------------------------------------------
/fonts/lite-editor-fonts-20161209.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appleple/lite-editor/HEAD/fonts/lite-editor-fonts-20161209.woff
--------------------------------------------------------------------------------
/images/icon_arrow_prev.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/icon_arrow_next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/icon_close.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/scss/_variables.scss:
--------------------------------------------------------------------------------
1 | $icomoon-font-path: "../fonts" !default;
2 |
3 | $lite-editor-font-back: "\e900";
4 | $lite-editor-font-go: "\e901";
5 | $lite-editor-font-bold: "\e902";
6 | $lite-editor-font-italic: "\e903";
7 | $lite-editor-font-underline: "\e904";
8 | $lite-editor-font-link: "\e905";
9 | $lite-editor-font-source: "\e906";
10 | $lite-editor-font-remove: "\e907";
11 | $lite-editor-font-update: "\e908";
12 | $lite-editor-font-close: "\e909";
13 | $lite-editor-font-abc: "\e90a";
14 |
15 | $lite-editor-font-color: #7A7676;
16 | $lite-editor-btn-color: #7A7676;
17 | $lite-editor-btn-active-color: #333;
18 | $lite-editor-border-color: #CCC;
19 | $lite-editor-border-active-color: #CCC;
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | // vite.config.js
2 | import { defineConfig } from 'vite';
3 | import string from 'vite-plugin-string';
4 | import path from 'path';
5 |
6 | export default defineConfig({
7 | server: {
8 | open: '/examples/', // ← 起動時に /examples/ を開く
9 | },
10 | build: {
11 | outDir: 'js', // JS 出力先
12 | emptyOutDir: false, // js/ を消さない
13 | lib: {
14 | entry: path.resolve(__dirname, 'src/index.js'),
15 | name: 'LiteEditor',
16 | fileName: (format) => `lite-editor.${format}.js`,
17 | formats: ['es', 'iife'],
18 | },
19 | minify: true,
20 | },
21 | css: {
22 | devSourcemap: true,
23 | },
24 | plugins: [
25 | string({
26 | include: ['**/*.html'], // .html を文字列としてインポート
27 | }),
28 | ],
29 | resolve: {
30 | alias: {
31 | buffer: 'buffer',
32 | },
33 | },
34 | });
35 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 | # 初めの行に記述する。この記述をすることで、フォルダ以下のディレクトリのみにルールが適用される
3 |
4 | #[*.css] // 適用したいファイルを指定
5 | #indent_style = space // シャープでコメントできる
6 |
7 | [*]
8 | # すべてのファイルに適用する
9 | charset = utf-8
10 | # 文字コードを統一
11 | indent_style = space
12 | #インデントを統一する。「tab」か「 space」
13 | indent_size = 2
14 | # インデントの数を統一
15 | trim_trailing_whitespace = true
16 | # 行末のホワイトスペースを削除
17 | insert_final_newline = true
18 | # フォルダの最後の行に改行
19 | end_of_line = lf
20 |
21 | [*.php]
22 | charset = utf-8
23 | end_of_line = lf
24 | insert_final_newline = true
25 | trim_trailing_whitespace = true
26 | indent_style = space
27 | indent_size = 4
28 |
29 | [*.js]
30 | charset = utf-8
31 | end_of_line = lf
32 | insert_final_newline = true
33 | trim_trailing_whitespace = true
34 | indent_style = space
35 | indent_size = 2
36 |
37 | [*.yaml]
38 | indent_style = space
39 | indent_size = 4
40 |
--------------------------------------------------------------------------------
/src/core/editor.html:
--------------------------------------------------------------------------------
1 |
min-height: {minHeight}px;max-height:{maxHeight}px;">{value}
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lite-editor",
3 | "version": "2.0.0",
4 | "description": "lite-editor",
5 | "homepage": "http://developer.a-blogcms.jp",
6 | "main": "./js/lite-editor.es.js",
7 | "module": "./js/lite-editor.es.js",
8 | "scripts": {
9 | "test": "vitest",
10 | "dev": "vite",
11 | "build": "vite build && vite build --mode minify && npm run move-css",
12 | "move-css": "mkdir -p css && mv js/lite-editor.css css/lite-editor.css && mv js/lite-editor.min.css css/lite-editor.min.css"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "https://github.com/appleple/lite-editor/"
17 | },
18 | "author": "appleple",
19 | "license": "MIT",
20 | "devDependencies": {
21 | "sass": "^1.93.2",
22 | "vite": "^7.1.7",
23 | "vite-plugin-string": "^1.2.3",
24 | "vitest": "^3.2.4"
25 | },
26 | "dependencies": {
27 | "a-template": "^0.5.4",
28 | "deep-extend": "^0.6.0",
29 | "field-selection": "^0.2.2",
30 | "html-entities": "^2.6.0",
31 | "upndown": "^2.1.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Lite Editor
10 |
11 |
12 |
18 |
19 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 appleple
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 |
--------------------------------------------------------------------------------
/images/Slice.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/multi.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | SimpleWysiwyg
11 |
12 |
13 |
18 |
22 |
23 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # LiteEditor
2 | [](https://badge.fury.io/js/lite-editor)
3 | [](https://circleci.com/gh/appleple/lite-editor/tree/master)
4 | [](https://www.npmjs.com/package/lite-editor)
5 | [](https://raw.githubusercontent.com/appleple/lite-editor/master/LICENSE)
6 |
7 | A Modern WYSIWYG Editor especially for inline elements
8 |
9 | ## Feature
10 |
11 | - focuses on inline elements such as b, a, i, strong
12 | - Prevent unnecessary tags insertion
13 | - Control how to make newlines
14 | - You can register custom button easily
15 |
16 | ## Installation
17 |
18 | - [npm](https://www.npmjs.com/package/lite-editor)
19 | - [standalone](https://unpkg.com/lite-editor@2.0.0/js/lite-editor.es.js)
20 |
21 | via npm
22 | ```shell
23 | npm install lite-editor --save
24 | ```
25 |
26 | or yarn
27 |
28 | ```shell
29 | yarn add lite-editor
30 | ```
31 |
32 | ## Usage
33 |
34 | require
35 | ```js
36 | import LiteEditor from 'lite-editor';
37 | ```
38 |
39 | ```js
40 | window.addEventListener('DOMContentLoaded',function(){
41 | new LiteEditor('.js-editor');
42 | });
43 | ```
44 |
45 | ## Document
46 | [https://appleple.github.io/lite-editor/about.html](https://appleple.github.io/lite-editor/about.html)
47 |
48 | ## Download
49 | [Download ZIP](https://github.com/appleple/lite-editor/archive/master.zip)
50 |
51 | ## Plugins
52 | - [lite-editor-emoji-picker-plugin](https://github.com/appleple/lite-editor-emoji-picker-plugin)
53 |
54 | ## Github
55 | [https://github.com/appleple/lite-editor](https://github.com/appleple/lite-editor)
56 |
57 | ## Contributor
58 | [@steelydylan](https://github.com/steelydylan)
59 |
60 | ## License
61 | Code and documentation copyright 2017 by appleple, Inc. Code released under the [MIT License](https://github.com/appleple/lite-editor/blob/master/LICENSE).
62 |
--------------------------------------------------------------------------------
/src/scss/_fonts.scss:
--------------------------------------------------------------------------------
1 | @use 'variables' as *;
2 |
3 | @font-face {
4 | font-family: 'lite-editor-fonts-20161209';
5 | src: url('#{$icomoon-font-path}/lite-editor-fonts-20161209.eot?d39wrm');
6 | src:
7 | url('#{$icomoon-font-path}/lite-editor-fonts-20161209.eot?d39wrm#iefix') format('embedded-opentype'),
8 | url('#{$icomoon-font-path}/lite-editor-fonts-20161209.ttf?d39wrm') format('truetype'),
9 | url('#{$icomoon-font-path}/lite-editor-fonts-20161209.woff?d39wrm') format('woff'),
10 | url('#{$icomoon-font-path}/lite-editor-fonts-20161209.svg?d39wrm#lite-editor-fonts-20161209') format('svg');
11 | font-weight: normal;
12 | font-style: normal;
13 | }
14 |
15 | [class^='lite-editor-font'],
16 | [class*=' lite-editor-font'] {
17 | /* use !important to prevent issues with browser extensions that change fonts */
18 | font-family: 'lite-editor-fonts-20161209' !important;
19 | speak: none;
20 | font-style: normal;
21 | font-weight: normal;
22 | font-variant: normal;
23 | text-transform: none;
24 | line-height: 1;
25 |
26 | /* Better Font Rendering =========== */
27 | -webkit-font-smoothing: antialiased;
28 | -moz-osx-font-smoothing: grayscale;
29 | color: #7a7676;
30 | }
31 |
32 | .lite-editor-font-back {
33 | &:before {
34 | content: '\e900';
35 | }
36 | }
37 | .lite-editor-font-go {
38 | &:before {
39 | content: $lite-editor-font-go;
40 | }
41 | }
42 | .lite-editor-font-bold {
43 | &:before {
44 | content: $lite-editor-font-bold;
45 | }
46 | }
47 | .lite-editor-font-italic {
48 | &:before {
49 | content: $lite-editor-font-italic;
50 | }
51 | }
52 | .lite-editor-font-underline {
53 | &:before {
54 | content: $lite-editor-font-underline;
55 | }
56 | }
57 | .lite-editor-font-link {
58 | &:before {
59 | content: $lite-editor-font-link;
60 | }
61 | }
62 | .lite-editor-font-source {
63 | &:before {
64 | content: $lite-editor-font-source;
65 | }
66 | }
67 | .lite-editor-font-remove {
68 | &:before {
69 | content: $lite-editor-font-remove;
70 | }
71 | }
72 | .lite-editor-font-update {
73 | &:before {
74 | content: $lite-editor-font-update;
75 | }
76 | }
77 | .lite-editor-font-close {
78 | &:before {
79 | content: $lite-editor-font-close;
80 | }
81 | }
82 | .lite-editor-font-abc {
83 | &:before {
84 | content: $lite-editor-font-abc;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/core/tooltip.html:
--------------------------------------------------------------------------------
1 |
75 |
--------------------------------------------------------------------------------
/examples/complex.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | SimpleWysiwyg
10 |
11 |
12 |
18 |
19 |
96 |
97 |
113 |
114 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/src/core/btn.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/css/lite-editor.css:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";@font-face{font-family:lite-editor-fonts-20161209;src:url(../fonts/lite-editor-fonts-20161209.eot?d39wrm);src:url(../fonts/lite-editor-fonts-20161209.eot?d39wrm#iefix) format("embedded-opentype"),url(../fonts/lite-editor-fonts-20161209.ttf?d39wrm) format("truetype"),url(../fonts/lite-editor-fonts-20161209.woff?d39wrm) format("woff"),url(../fonts/lite-editor-fonts-20161209.svg?d39wrm#lite-editor-fonts-20161209) format("svg");font-weight:400;font-style:normal}[class^=lite-editor-font],[class*=" lite-editor-font"]{font-family:lite-editor-fonts-20161209!important;speak:none;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#7a7676}.lite-editor-font-back:before{content:""}.lite-editor-font-go:before{content:""}.lite-editor-font-bold:before{content:""}.lite-editor-font-italic:before{content:""}.lite-editor-font-underline:before{content:""}.lite-editor-font-link:before{content:""}.lite-editor-font-source:before{content:""}.lite-editor-font-remove:before{content:""}.lite-editor-font-update:before{content:""}.lite-editor-font-close:before{content:""}.lite-editor-font-abc:before{content:""}.lite-editor,.lite-editor-source{min-height:32px;border:1px solid #CCC;background-color:#fff;border-radius:0 0 5px 5px;font-family:Hiragino Kaku Gothic Pro,ヒラギノ角ゴ Pro W3,MS Pゴシック,MS PGothic,sans-serif;font-size:13px;line-height:1.7;padding:5px;margin-bottom:10px;width:100%;box-sizing:border-box;overflow-y:scroll}.lite-editor:focus,.lite-editor-source:focus{outline:none}.lite-editor img{max-width:100%;width:auto;height:auto}.lite-editor a{text-decoration:underline;color:#337ab7}.lite-editor-btn-group{border-collapse:separate;display:inline-table;padding:7px 10px 7px 0}.lite-editor-btn-group-wrap{display:inline-block;vertical-align:middle;padding:0 10px}.lite-editor-btn-group-wrap-right{vertical-align:middle;display:block;float:right}.lite-editor-btn-group-wrap-right .lite-editor-btn{width:44px}.lite-editor-btn-group-wrap-right .lite-editor-btn-active{background:#65b9ff;opacity:1;border:1px solid #a2adb9;box-shadow:inset 1px 1px 6px #0000004d;color:#fff}.lite-editor-btn-group-wrap-right .lite-editor-btn-active [class^=lite-editor-font]{color:#fff}.lite-editor-btn-group-wrap-right .lite-editor-font-abc:before{font-size:10px}.lite-editor-btn,.lite-editor-btn-active{background-color:#fff;text-decoration:none;text-align:center;font-size:14px;line-height:1;color:#7a7676;border-left:none;border:1px solid #CCC;font-size:12px;padding:3px 5px;border-radius:3px;display:table-cell;height:28px;margin:0;cursor:pointer}.lite-editor-btn-close{padding:1px 6px;line-height:1;vertical-align:middle}.lite-editor-btn-close-wrap{float:right;text-align:center;display:block;font-size:14px;color:#7a7676;margin-right:10px;margin-top:10px}.lite-editor-btn-close-label{display:inline-block;vertical-align:bottom;margin-right:5px}.lite-editor-btn:disabled{pointer-events:none;opacity:.65;filter:alpha(opacity=65);box-shadow:none}.lite-editor-btn-active{border:1px solid #CCC;background-color:#e1e1e1;box-shadow:inset 0 1px 2px #b2b2b2;color:#333;text-decoration:none}.lite-editor-btn-group .lite-editor-btn{border-right:none;border-radius:0}.lite-editor-btn-group .lite-editor-btn:first-child{border-top-left-radius:3px;border-bottom-left-radius:3px}.lite-editor-btn-group .lite-editor-btn:last-child{border-top-right-radius:3px;border-bottom-right-radius:3px;border-right:1px solid #CCC}.lite-editor-select{-webkit-appearance:button;display:inline-block;vertical-align:middle;border:1px solid #CCC;border-radius:3px;background-color:#ccc;text-decoration:none;text-align:center;background-repeat:no-repeat;color:#333;padding-right:20px\ ;background-color:#f0f0f0;text-align:left;min-height:27px;padding:5px 30px 5px 10px;margin-right:10px;font-size:11px;line-height:1.2}.lite-editor-select-wrap{display:inline-block;vertical-align:middle}.lite-editor-toolbox{background-color:#f0f0f0;border:1px solid #CCC;padding-right:5px;padding-left:5px;border-bottom:0;border-top-left-radius:3px;border-top-right-radius:3px}.lite-editor+.lite-editor-toolbox{border-top:0;border-bottom:1px solid #CCC;border-radius:0 0 3px 3px}.lite-editor-tooltip-wrap{position:fixed;z-index:1;top:0;left:0;width:100%;height:100%;background-color:#0000004d}.lite-editor-tooltip-outer{display:table;width:100%;height:100%}.lite-editor-tooltip-inner{display:table-cell;width:100%;height:100%;vertical-align:middle}.lite-editor-tooltip{max-width:500px;margin:auto;top:30%;border:1px solid #CCC;background-color:#fff;border-radius:5px;z-index:1}.lite-editor-tooltip-title{font-weight:400;font-size:18px;margin:0;background-color:#f0f0f0;padding:10px;color:#7a7676}.lite-editor-tooltip-title [class^=lite-editor-font]{margin-right:12px}.lite-editor-tooltip-body{padding:10px 10px 5px}.lite-editor-tooltip-body *{box-sizing:border-box}.lite-editor-tooltip-table{width:100%;font-size:14px}.lite-editor-tooltip-table th{width:80px}.lite-editor-tooltip-table th,.lite-editor-tooltip-table td{font-weight:400;text-align:left;padding:5px;font-size:13px;color:#7a7676}.lite-editor-tooltip-table .lite-editor-btn{width:90px;text-align:center;font-size:14px;margin-right:20px}.lite-editor-tooltip-table input[type=checkbox]{margin-right:5px}.lite-editor-tooltip-table [class^=lite-editor-font]{vertical-align:middle;margin-right:5px}.lite-editor-tooltip-input{border-radius:3px;padding:5px;border:1px solid #CCC;width:100%;font-size:16px;color:#7a7676}.lite-editor-extend-input{margin-right:10px;border-radius:3px;padding:5px;border:1px solid #CCC;width:100px;vertical-align:middle}.lite-editor-btn .lite-editor-font-back,.lite-editor-btn .lite-editor-font-go{color:#333}.lite-editor-btn[disabled] .lite-editor-font-back,.lite-editor-btn[disabled] .lite-editor-font-go{color:#7a7676}
2 |
--------------------------------------------------------------------------------
/src/scss/lite-editor.scss:
--------------------------------------------------------------------------------
1 | @use './fonts';
2 | @use 'variables' as *;
3 |
4 | .lite-editor,
5 | .lite-editor-source {
6 | min-height: 32px;
7 | border: 1px solid $lite-editor-border-color;
8 | background-color: #fff;
9 | border-radius: 5px;
10 | border-top-right-radius: 0;
11 | border-top-left-radius: 0;
12 | font-family: 'Hiragino Kaku Gothic Pro', 'ヒラギノ角ゴ Pro W3', 'MS Pゴシック', 'MS PGothic', sans-serif;
13 | font-size: 13px;
14 | line-height: 1.7;
15 | padding: 5px;
16 | margin-bottom: 10px;
17 | width: 100%;
18 | box-sizing: border-box;
19 | overflow-y: scroll;
20 | &:focus {
21 | outline: none;
22 | }
23 | }
24 |
25 | .lite-editor img {
26 | max-width: 100%;
27 | width: auto;
28 | height: auto;
29 | }
30 |
31 | .lite-editor a {
32 | text-decoration: underline;
33 | color: #337ab7;
34 | }
35 |
36 | .lite-editor-btn-group {
37 | border-collapse: separate;
38 | display: inline-table;
39 | padding: 7px 10px;
40 | padding-left: 0;
41 | }
42 |
43 | .lite-editor-btn-group-wrap {
44 | display: inline-block;
45 | vertical-align: middle;
46 | padding: 0 10px;
47 | }
48 |
49 | .lite-editor-btn-group-wrap-right {
50 | vertical-align: middle;
51 | display: block;
52 | float: right;
53 | .lite-editor-btn {
54 | width: 44px;
55 | }
56 | .lite-editor-btn-active {
57 | background: #65b9ff;
58 | opacity: 1;
59 | border: 1px solid #a2adb9;
60 | box-shadow: inset 1px 1px 6px 0 rgba(0, 0, 0, 0.3);
61 | color: #fff;
62 | [class^='lite-editor-font'] {
63 | color: #fff;
64 | }
65 | }
66 | .lite-editor-font-abc {
67 | &:before {
68 | font-size: 10px;
69 | }
70 | }
71 | }
72 |
73 | .lite-editor-btn,
74 | .lite-editor-btn-active {
75 | background-color: #fff;
76 | text-decoration: none;
77 | text-align: center;
78 | font-size: 14px;
79 | line-height: 1;
80 | color: $lite-editor-btn-color;
81 | display: table-cell;
82 | border-left: none;
83 | border: 1px solid $lite-editor-border-color;
84 | font-size: 12px;
85 | padding: 3px 5px;
86 | border-radius: 3px;
87 | display: table-cell;
88 | height: 28px;
89 | margin: 0;
90 | cursor: pointer;
91 | }
92 |
93 | .lite-editor-btn-close {
94 | padding: 1px 6px;
95 | line-height: 1;
96 | vertical-align: middle;
97 | }
98 |
99 | .lite-editor-btn-close-wrap {
100 | float: right;
101 | text-align: center;
102 | display: block;
103 | font-size: 14px;
104 | color: $lite-editor-btn-color;
105 | margin-right: 10px;
106 | margin-top: 10px;
107 | }
108 |
109 | .lite-editor-btn-close-label {
110 | display: inline-block;
111 | vertical-align: bottom;
112 | margin-right: 5px;
113 | }
114 |
115 | .lite-editor-btn:disabled {
116 | pointer-events: none;
117 | opacity: 0.65;
118 | filter: alpha(opacity=65);
119 | box-shadow: none;
120 | }
121 |
122 | .lite-editor-btn-active {
123 | border: 1px solid $lite-editor-border-active-color;
124 | background-color: #e1e1e1;
125 | box-shadow: inset 0 1px 2px #b2b2b2;
126 | color: $lite-editor-btn-active-color;
127 | text-decoration: none;
128 | }
129 |
130 | .lite-editor-btn-group {
131 | .lite-editor-btn {
132 | border-right: none;
133 | border-radius: 0;
134 | }
135 | .lite-editor-btn:first-child {
136 | border-top-left-radius: 3px;
137 | border-bottom-left-radius: 3px;
138 | }
139 |
140 | .lite-editor-btn:last-child {
141 | border-top-right-radius: 3px;
142 | border-bottom-right-radius: 3px;
143 | border-right: 1px solid $lite-editor-border-active-color;
144 | }
145 | }
146 |
147 | .lite-editor-select {
148 | -webkit-appearance: button;
149 | display: inline-block;
150 | vertical-align: middle;
151 | border: 1px solid $lite-editor-border-color;
152 | border-radius: 3px;
153 | background-color: $lite-editor-border-color;
154 | text-decoration: none;
155 | text-align: center;
156 | background-repeat: no-repeat;
157 | color: #333;
158 | padding-right: 20px\9;
159 | background-color: #f0f0f0;
160 | text-align: left;
161 | min-height: 27px;
162 | padding: 5px 30px 5px 10px;
163 | margin-right: 10px;
164 | font-size: 11px;
165 | line-height: 1.2;
166 | }
167 |
168 | .lite-editor-select-wrap {
169 | display: inline-block;
170 | vertical-align: middle;
171 | }
172 |
173 | .lite-editor-toolbox {
174 | background-color: #f0f0f0;
175 | border: 1px solid $lite-editor-border-color;
176 | padding-right: 5px;
177 | padding-left: 5px;
178 | border-bottom: 0;
179 | border-top-left-radius: 3px;
180 | border-top-right-radius: 3px;
181 | }
182 |
183 | .lite-editor + .lite-editor-toolbox {
184 | border-top: 0;
185 | border-bottom: 1px solid $lite-editor-border-color;
186 | border-top-left-radius: 0;
187 | border-top-right-radius: 0;
188 | border-bottom-right-radius: 3px;
189 | border-bottom-left-radius: 3px;
190 | }
191 |
192 | .lite-editor-tooltip-wrap {
193 | position: fixed;
194 | z-index: 1;
195 | top: 0;
196 | left: 0;
197 | width: 100%;
198 | height: 100%;
199 | background-color: rgba(0, 0, 0, 0.3);
200 | }
201 |
202 | .lite-editor-tooltip-outer {
203 | display: table;
204 | width: 100%;
205 | height: 100%;
206 | }
207 |
208 | .lite-editor-tooltip-inner {
209 | display: table-cell;
210 | width: 100%;
211 | height: 100%;
212 | vertical-align: middle;
213 | }
214 |
215 | .lite-editor-tooltip {
216 | max-width: 500px;
217 | margin: auto;
218 | top: 30%;
219 | border: 1px solid $lite-editor-border-color;
220 | background-color: #fff;
221 | border-radius: 5px;
222 | z-index: 1;
223 | }
224 |
225 | .lite-editor-tooltip-title {
226 | font-weight: normal;
227 | font-size: 18px;
228 | margin: 0;
229 | background-color: #f0f0f0;
230 | padding: 10px;
231 | color: $lite-editor-font-color;
232 | }
233 |
234 | .lite-editor-tooltip-title [class^='lite-editor-font'] {
235 | margin-right: 12px;
236 | }
237 |
238 | .lite-editor-tooltip-body {
239 | padding: 10px 10px 5px 10px;
240 | * {
241 | box-sizing: border-box;
242 | }
243 | }
244 |
245 | .lite-editor-tooltip-table {
246 | width: 100%;
247 | font-size: 14px;
248 | th {
249 | width: 80px;
250 | }
251 | th,
252 | td {
253 | font-weight: normal;
254 | text-align: left;
255 | padding: 5px;
256 | font-size: 13px;
257 | color: $lite-editor-font-color;
258 | }
259 | .lite-editor-btn {
260 | width: 90px;
261 | text-align: center;
262 | font-size: 14px;
263 | margin-right: 20px;
264 | }
265 | input[type='checkbox'] {
266 | margin-right: 5px;
267 | }
268 | [class^='lite-editor-font'] {
269 | vertical-align: middle;
270 | margin-right: 5px;
271 | }
272 | }
273 |
274 | .lite-editor-tooltip-input {
275 | border-radius: 3px;
276 | padding: 5px;
277 | border: 1px solid $lite-editor-border-color;
278 | width: 100%;
279 | font-size: 16px;
280 | color: $lite-editor-font-color;
281 | }
282 |
283 | .lite-editor-extend-input {
284 | margin-right: 10px;
285 | border-radius: 3px;
286 | padding: 5px;
287 | border: 1px solid $lite-editor-border-color;
288 | width: 100px;
289 | vertical-align: middle;
290 | }
291 |
292 | .lite-editor-btn .lite-editor-font-back,
293 | .lite-editor-btn .lite-editor-font-go {
294 | color: $lite-editor-btn-active-color;
295 | }
296 |
297 | .lite-editor-btn[disabled] .lite-editor-font-back,
298 | .lite-editor-btn[disabled] .lite-editor-font-go {
299 | color: $lite-editor-btn-color;
300 | }
301 |
--------------------------------------------------------------------------------
/fonts/lite-editor-fonts-20161209.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.csscomb.json:
--------------------------------------------------------------------------------
1 | {
2 | "remove-empty-rulesets": true,
3 | "always-semicolon": true,
4 | "color-case": "upper",
5 | "block-indent": " ",
6 | "color-shorthand": true,
7 | "element-case": "lower",
8 | "eof-newline": false,
9 | "leading-zero": false,
10 | "quotes": "double",
11 | "sort-order-fallback": "abc",
12 | "space-before-colon": "",
13 | "space-after-colon": " ",
14 | "space-before-combinator": " ",
15 | "space-after-combinator": " ",
16 | "space-between-declarations": "\n",
17 | "space-before-opening-brace": " ",
18 | "space-after-opening-brace": "\n",
19 | "space-after-selector-delimiter": "\n",
20 | "space-before-selector-delimiter": "",
21 | "space-before-closing-brace": "\n",
22 | "strip-spaces": true,
23 | "tab-size": true,
24 | "unitless-zero": true,
25 | "vendor-prefix-align": true,
26 | "sort-order": [
27 | [
28 | "display",
29 | "visibility",
30 | "overflow",
31 | "overflow-x",
32 | "overflow-y",
33 | "-ms-overflow-x",
34 | "-ms-overflow-y",
35 | "flex-direction",
36 | "flex-order",
37 | "flex-pack",
38 | "flex-align",
39 | "-webkit-box-sizing",
40 | "-moz-box-sizing",
41 | "box-sizing",
42 | "table-layout",
43 | "empty-cells",
44 | "caption-side",
45 | "border-spacing",
46 | "border-collapse",
47 | "list-style",
48 | "list-style-position",
49 | "list-style-type",
50 | "list-style-image",
51 | "position",
52 | "z-index",
53 | "top",
54 | "right",
55 | "bottom",
56 | "left",
57 | "float",
58 | "clear",
59 | "min-width",
60 | "max-width",
61 | "width",
62 | "min-height",
63 | "max-height",
64 | "height",
65 | "margin",
66 | "margin-top",
67 | "margin-right",
68 | "margin-bottom",
69 | "margin-left",
70 | "padding",
71 | "padding-top",
72 | "padding-right",
73 | "padding-bottom",
74 | "padding-left",
75 | "border",
76 | "border-width",
77 | "border-style",
78 | "border-color",
79 | "border-top",
80 | "border-top-width",
81 | "border-top-style",
82 | "border-top-color",
83 | "border-right",
84 | "border-right-width",
85 | "border-right-style",
86 | "border-right-color",
87 | "border-bottom",
88 | "border-bottom-width",
89 | "border-bottom-style",
90 | "border-bottom-color",
91 | "border-left",
92 | "border-left-width",
93 | "border-left-style",
94 | "border-left-color",
95 | "-webkit-border-radius",
96 | "-moz-border-radius",
97 | "border-radius",
98 | "-webkit-border-top-left-radius",
99 | "-moz-border-radius-topleft",
100 | "border-top-left-radius",
101 | "-webkit-border-top-right-radius",
102 | "-moz-border-radius-topright",
103 | "border-top-right-radius",
104 | "-webkit-border-bottom-right-radius",
105 | "-moz-border-radius-bottomright",
106 | "border-bottom-right-radius",
107 | "-webkit-border-bottom-left-radius",
108 | "-moz-border-radius-bottomleft",
109 | "border-bottom-left-radius",
110 | "-webkit-border-image",
111 | "-moz-border-image",
112 | "-o-border-image",
113 | "border-image",
114 | "-webkit-border-image-source",
115 | "-moz-border-image-source",
116 | "-o-border-image-source",
117 | "border-image-source",
118 | "-webkit-border-image-slice",
119 | "-moz-border-image-slice",
120 | "-o-border-image-slice",
121 | "border-image-slice",
122 | "-webkit-border-image-width",
123 | "-moz-border-image-width",
124 | "-o-border-image-width",
125 | "border-image-width",
126 | "-webkit-border-image-outset",
127 | "-moz-border-image-outset",
128 | "-o-border-image-outset",
129 | "border-image-outset",
130 | "-webkit-border-image-repeat",
131 | "-moz-border-image-repeat",
132 | "-o-border-image-repeat",
133 | "border-image-repeat",
134 | "background",
135 | "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader",
136 | "background-color",
137 | "background-image",
138 | "background-repeat",
139 | "background-attachment",
140 | "background-position",
141 | "background-position-x",
142 | "-ms-background-position-x",
143 | "background-position-y",
144 | "-ms-background-position-y",
145 | "-webkit-background-clip",
146 | "-moz-background-clip",
147 | "background-clip",
148 | "background-origin",
149 | "-webkit-background-size",
150 | "-moz-background-size",
151 | "-o-background-size",
152 | "background-size",
153 | "opacity",
154 | "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity",
155 | "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha",
156 | "-ms-interpolation-mode",
157 | "outline",
158 | "outline-width",
159 | "outline-style",
160 | "outline-color",
161 | "outline-offset",
162 | "box-decoration-break",
163 | "-webkit-box-shadow",
164 | "-moz-box-shadow",
165 | "box-shadow",
166 | "filter:progid:DXImageTransform.Microsoft.gradient",
167 | "-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient",
168 | "text-shadow",
169 | "color",
170 | "font",
171 | "font-family",
172 | "font-size",
173 | "font-weight",
174 | "font-style",
175 | "font-variant",
176 | "font-size-adjust",
177 | "font-stretch",
178 | "font-effect",
179 | "font-emphasize",
180 | "font-emphasize-position",
181 | "font-emphasize-style",
182 | "font-smooth",
183 | "text-decoration",
184 | "text-emphasis",
185 | "text-emphasis-color",
186 | "text-emphasis-style",
187 | "text-emphasis-position",
188 | "text-indent",
189 | "text-align",
190 | "-webkit-text-align-last",
191 | "-moz-text-align-last",
192 | "-ms-text-align-last",
193 | "text-align-last",
194 | "vertical-align",
195 | "line-height",
196 | "white-space",
197 | "-ms-text-justify",
198 | "text-justify",
199 | "letter-spacing",
200 | "word-spacing",
201 | "-ms-writing-mode",
202 | "text-outline",
203 | "text-transform",
204 | "text-wrap",
205 | "text-overflow",
206 | "-ms-text-overflow",
207 | "text-overflow-ellipsis",
208 | "text-overflow-mode",
209 | "-ms-word-wrap",
210 | "word-wrap",
211 | "word-break",
212 | "-ms-word-break",
213 | "-moz-tab-size",
214 | "-o-tab-size",
215 | "tab-size",
216 | "-webkit-hyphens",
217 | "-moz-hyphens",
218 | "hyphens",
219 | "pointer-events",
220 | "content",
221 | "quotes",
222 | "counter-reset",
223 | "counter-increment",
224 | "resize",
225 | "cursor",
226 | "-webkit-user-select",
227 | "-moz-user-select",
228 | "-ms-user-select",
229 | "user-select",
230 | "nav-index",
231 | "nav-up",
232 | "nav-right",
233 | "nav-down",
234 | "nav-left",
235 | "-webkit-transition",
236 | "-moz-transition",
237 | "-ms-transition",
238 | "-o-transition",
239 | "transition",
240 | "-webkit-transition-delay",
241 | "-moz-transition-delay",
242 | "-ms-transition-delay",
243 | "-o-transition-delay",
244 | "transition-delay",
245 | "-webkit-transition-timing-function",
246 | "-moz-transition-timing-function",
247 | "-ms-transition-timing-function",
248 | "-o-transition-timing-function",
249 | "transition-timing-function",
250 | "-webkit-transition-duration",
251 | "-moz-transition-duration",
252 | "-ms-transition-duration",
253 | "-o-transition-duration",
254 | "transition-duration",
255 | "-webkit-transition-property",
256 | "-moz-transition-property",
257 | "-ms-transition-property",
258 | "-o-transition-property",
259 | "transition-property",
260 | "-webkit-transform",
261 | "-moz-transform",
262 | "-ms-transform",
263 | "-o-transform",
264 | "transform",
265 | "-webkit-transform-origin",
266 | "-moz-transform-origin",
267 | "-ms-transform-origin",
268 | "-o-transform-origin",
269 | "transform-origin",
270 | "-webkit-animation",
271 | "-moz-animation",
272 | "-ms-animation",
273 | "-o-animation",
274 | "animation",
275 | "-webkit-animation-name",
276 | "-moz-animation-name",
277 | "-ms-animation-name",
278 | "-o-animation-name",
279 | "animation-name",
280 | "-webkit-animation-duration",
281 | "-moz-animation-duration",
282 | "-ms-animation-duration",
283 | "-o-animation-duration",
284 | "animation-duration",
285 | "-webkit-animation-play-state",
286 | "-moz-animation-play-state",
287 | "-ms-animation-play-state",
288 | "-o-animation-play-state",
289 | "animation-play-state",
290 | "-webkit-animation-timing-function",
291 | "-moz-animation-timing-function",
292 | "-ms-animation-timing-function",
293 | "-o-animation-timing-function",
294 | "animation-timing-function",
295 | "-webkit-animation-delay",
296 | "-moz-animation-delay",
297 | "-ms-animation-delay",
298 | "-o-animation-delay",
299 | "animation-delay",
300 | "-webkit-animation-iteration-count",
301 | "-moz-animation-iteration-count",
302 | "-ms-animation-iteration-count",
303 | "-o-animation-iteration-count",
304 | "animation-iteration-count",
305 | "-webkit-animation-direction",
306 | "-moz-animation-direction",
307 | "-ms-animation-direction",
308 | "-o-animation-direction",
309 | "animation-direction",
310 | "clip",
311 | "zoom"
312 | ]
313 | ]
314 | }
315 |
--------------------------------------------------------------------------------
/src/lib/util.js:
--------------------------------------------------------------------------------
1 | export const isSmartPhone = () => {
2 | const agent = navigator.userAgent;
3 | if (agent.indexOf('iPhone') > 0 || agent.indexOf('iPad') > 0
4 | || agent.indexOf('ipod') > 0 || agent.indexOf('Android') > 0) {
5 | return true;
6 | } else {
7 | return false;
8 | }
9 | }
10 |
11 | export const triggerEvent = (el, eventName, options) => {
12 | let event;
13 | if (window.CustomEvent) {
14 | event = new CustomEvent(eventName, { cancelable: true });
15 | } else {
16 | event = document.createEvent('CustomEvent');
17 | event.initCustomEvent(eventName, false, false, options);
18 | }
19 | el.dispatchEvent(event);
20 | }
21 |
22 | export const parseQuery = (query) => {
23 | var s = query.split('&'),
24 | data = {},
25 | i = 0,
26 | iz = s.length,
27 | param, key, value;
28 | for (; i < iz; i++) {
29 | param = s[i].split('=');
30 | if (param[0] !== void 0) {
31 | key = param[0];
32 | value = (param[1] !== void 0) ? param.slice(1).join('=') : key;
33 | data[key] = decodeURIComponent(value);
34 | }
35 | }
36 | return data;
37 | }
38 |
39 | export const getViewPos = (element) => {
40 | return {
41 | left: element.getBoundingClientRect().left,
42 | top: element.getBoundingClientRect().top,
43 | }
44 | }
45 |
46 | export const removeElement = (element) => {
47 | if (element && element.parentNode) {
48 | element.parentNode.removeChild(element);
49 | }
50 | }
51 |
52 | export const append = (element, string) => {
53 | const parser = new DOMParser();
54 | const doc = parser.parseFromString(string, 'text/html');
55 | element.appendChild(doc.querySelector('body').childNodes[0]);
56 | }
57 |
58 | export const addClass = (element, className) => {
59 | if (element.classList) {
60 | element.classList.add(className);
61 | } else {
62 | element.className += ` ${className}`;
63 | }
64 | }
65 |
66 | export const removeClass = (element, className) => {
67 | if (element.classList) {
68 | element.classList.remove(className);
69 | } else {
70 | element.className = element.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
71 | }
72 | }
73 |
74 | export const before = (el, html) => {
75 | el.insertAdjacentHTML('beforebegin', html);
76 | }
77 |
78 | export const getSelection = (ele) => {
79 | if (window.getSelection && window.getSelection().toString()) {
80 | return window.getSelection();
81 | } else if (document.getSelection && document.getSelection().toString()) {
82 | return document.getSelection();
83 | } else if (ele && typeof ele.selectionStart === 'number') {
84 | return ele.value.substr(ele.selectionStart, ele.selectionEnd - ele.selectionStart);
85 | }
86 | return '';
87 | }
88 |
89 | export const saveSelection = () => {
90 | if (window.getSelection) {
91 | const sel = window.getSelection();
92 | if (sel.getRangeAt && sel.rangeCount) {
93 | return sel.getRangeAt(0);
94 | }
95 | } else if (document.selection && document.selection.createRange) {
96 | return document.selection.createRange();
97 | }
98 | return null;
99 | }
100 |
101 | export const restoreSelection = (range) => {
102 | if (window.getSelection) {
103 | const sel = window.getSelection();
104 | sel.removeAllRanges();
105 | sel.addRange(range);
106 | } else if (document.selection && range.select) {
107 | range.select();
108 | }
109 | }
110 |
111 | export const insertHtmlAtCursor = (html) => {
112 | let range;
113 | if (window.getSelection && window.getSelection().getRangeAt) {
114 | range = window.getSelection().getRangeAt(0);
115 | range.deleteContents();
116 | const div = document.createElement("div");
117 | div.innerHTML = html;
118 | let frag = document.createDocumentFragment(), child;
119 | while ((child = div.firstChild)) {
120 | frag.appendChild(child);
121 | }
122 | range.insertNode(frag);
123 | } else if (document.selection && document.selection.createRange) {
124 | range = document.selection.createRange();
125 | range.pasteHTML(html);
126 | }
127 | }
128 |
129 | export const replaceSelectionWithHtml = (html) => {
130 | let range;
131 | if (window.getSelection && window.getSelection().getRangeAt) {
132 | const selection = window.getSelection();
133 | range = selection.getRangeAt(0);
134 | range.deleteContents();
135 | const div = document.createElement("div");
136 | div.innerHTML = html;
137 | const frag = document.createDocumentFragment();
138 | let child;
139 | while ((child = div.firstChild)) {
140 | frag.appendChild(child);
141 | }
142 | const temp = getFirstfirstElementChild(frag);
143 | const newrange = document.createRange();
144 | range.insertNode(temp);
145 | newrange.setStart(temp.firstChild, 0);
146 | newrange.setEnd(temp.lastChild, temp.lastChild.textContent.length);
147 | clearSelection();
148 | selection.addRange(newrange);
149 | } else if (document.selection && document.selection.createRange) {
150 | range = document.selection.createRange();
151 | range.pasteHTML(html);
152 | }
153 | }
154 |
155 | export const moveCaretAfter = (node) => {
156 | if (window.getSelection && window.getSelection().getRangeAt) {
157 | const selection = window.getSelection();
158 | const range = selection.getRangeAt(0);
159 | const newnode = node.cloneNode(true);
160 | let frag = document.createDocumentFragment();
161 | node.remove();
162 | frag.appendChild(newnode);
163 | const lastChild = frag.appendChild(document.createTextNode("\u200B"));
164 | const newrange = document.createRange();
165 | range.insertNode(frag);
166 | newrange.setStartAfter(lastChild);
167 | newrange.setEndAfter(lastChild);
168 | selection.removeAllRanges();
169 | selection.addRange(newrange);
170 | }
171 | }
172 |
173 | export const unwrapTag = (element) => {
174 | const parent = element.parentNode;
175 | while (element.firstChild) {
176 | parent.insertBefore(element.firstChild, element);
177 | }
178 | parent.removeChild(element);
179 | }
180 |
181 | export const getElementBySelection = () => {
182 | if (window.getSelection) {
183 | const selection = window.getSelection();
184 | if (selection.rangeCount > 0) {
185 | return selection.getRangeAt(0).startContainer.parentNode;
186 | }
187 | } else if (document.selection) {
188 | return document.selection.createRange().parentElement();
189 | }
190 | }
191 |
192 | export const clearSelection = () => {
193 | if (window.getSelection) {
194 | if (window.getSelection().empty) { // Chrome
195 | window.getSelection().empty();
196 | } else if (window.getSelection().removeAllRanges) { // Firefox
197 | window.getSelection().removeAllRanges();
198 | }
199 | } else if (document.selection) { // IE?
200 | document.selection.empty();
201 | }
202 | }
203 |
204 | export const replaceSelectionWithText = (ele, text) => {
205 | const selectionStart = ele.selectionStart;
206 | ele.value = `${ele.value.substring(0, selectionStart)}${text}${ele.value.substring(ele.selectionEnd)}`;
207 | ele.focus();
208 | ele.setSelectionRange(selectionStart, selectionStart + text.length);
209 | }
210 |
211 | export const getSelectionLength = () => {
212 | if (window.getSelection) {
213 | return window.getSelection().toString().length;
214 | } else if (document.selection) {
215 | return document.selection().toString().length;
216 | }
217 | }
218 |
219 | export const setCaretPos = (el, pos, length) => {
220 | // Loop through all child nodes
221 | const nodes = [].slice.call(el.childNodes);
222 | for (let i = 0, n = nodes.length; i < n; i++) {
223 | const node = nodes[i];
224 | if (node.nodeType === 3) { // we have a text node
225 | if (node.length >= pos) {
226 | // finally add our range
227 | const range = document.createRange();
228 | const sel = window.getSelection();
229 |
230 | if (length) {
231 | range.setStart(node, 0);
232 | range.setEnd(node, length);
233 | } else {
234 | range.setStart(node, pos);
235 | range.collapse(true);
236 | }
237 | sel.removeAllRanges();
238 | sel.addRange(range);
239 | return -1; // we are done
240 | } else {
241 | pos -= node.length;
242 | }
243 | } else {
244 | pos = setCaretPos(node, pos);
245 | if (pos === -1) {
246 | return -1; // no need to finish the for loop
247 | }
248 | }
249 | }
250 | return pos; // needed because of recursion stuff
251 | }
252 |
253 | export const replaceWhiteSpaceWithNbsp = (el) => {
254 | // Loop through all child nodes
255 | const nodes = [].slice.call(el.childNodes);
256 | for (let i = 0, n = nodes.length; i < n; i++) {
257 | const node = nodes[i];
258 | if (node.nodeType === 3) { // we have a text node
259 | node.textContent = node.textContent.replace(/ /g, '\u00A0');
260 | }
261 | }
262 | }
263 |
264 | export const getCaretPos = (element) => {
265 | let caretOffset = 0;
266 | if (window.getSelection) {
267 | const range = window.getSelection().getRangeAt(0);
268 | const preCaretRange = range.cloneRange();
269 | preCaretRange.selectNodeContents(element);
270 | preCaretRange.setEnd(range.endContainer, range.endOffset);
271 | caretOffset = preCaretRange.toString().length;
272 | } else if (document.selection && document.selection.createRange) {
273 | const textRange = document.selection.createRange();
274 | const preCaretTextRange = document.body.createTextRange();
275 | preCaretTextRange.moveToElementText(element);
276 | preCaretTextRange.setEndPoint("EndToEnd", textRange);
277 | caretOffset = preCaretTextRange.text.length;
278 | }
279 | return caretOffset;
280 | }
281 |
282 | export const hasLastBr = (element) => {
283 | const childNodes = element.childNodes;
284 | const length = childNodes.length;
285 | let offset = 1;
286 | if (!childNodes[length - offset]) {
287 | return false;
288 | }
289 | return childNodes[length - offset].tagName === 'BR';
290 | }
291 |
292 | export const removeIndentNewline = (str) => {
293 | return str.replace(/(\n|\t)/g, '');
294 | };
295 |
296 | export const getBrowser = () => {
297 | const ua = window.navigator.userAgent.toLowerCase();
298 | const ver = window.navigator.appVersion.toLowerCase();
299 | let name = 'unknown';
300 |
301 | if (ua.indexOf('msie') != -1) {
302 | if (ver.indexOf('msie 6.') != -1) {
303 | name = 'ie6';
304 | } else if (ver.indexOf('msie 7.') != -1) {
305 | name = 'ie7';
306 | } else if (ver.indexOf('msie 8.') != -1) {
307 | name = 'ie8';
308 | } else if (ver.indexOf('msie 9.') != -1) {
309 | name = 'ie9';
310 | } else if (ver.indexOf('msie 10.') != -1) {
311 | name = 'ie10';
312 | } else {
313 | name = 'ie';
314 | }
315 | } else if (ua.indexOf('trident/7') != -1) {
316 | name = 'ie11';
317 | } else if (ua.indexOf('chrome') != -1) {
318 | name = 'chrome';
319 | } else if (ua.indexOf('safari') != -1) {
320 | name = 'safari';
321 | } else if (ua.indexOf('opera') != -1) {
322 | name = 'opera';
323 | } else if (ua.indexOf('firefox') != -1) {
324 | name = 'firefox';
325 | }
326 | return name;
327 | };
328 |
329 | export const getFirstfirstElementChild = (ele) => {
330 | let node;
331 | const nodes = ele.childNodes;
332 | let i = 0;
333 | if (nodes && nodes.length) {
334 | while ((node = nodes[i++])) {
335 | if (node.nodeType === 1) {
336 | return node;
337 | }
338 | }
339 | }
340 | return null;
341 | };
342 |
343 | export const deepMerge = (target, ...sources) => {
344 | for (const source of sources) {
345 | if (!source) continue;
346 | for (const key of Object.keys(source)) {
347 | const value = source[key];
348 | if (value && typeof value === 'object' && !Array.isArray(value)) {
349 | if (!target[key] || typeof target[key] !== 'object') {
350 | target[key] = {};
351 | }
352 | deepMerge(target[key], value);
353 | } else {
354 | target[key] = value;
355 | }
356 | }
357 | }
358 | return target;
359 | };
360 |
--------------------------------------------------------------------------------
/src/core/index.js:
--------------------------------------------------------------------------------
1 | import aTemplate from 'a-template';
2 | import { encode, decode } from 'html-entities';
3 | import Upndown from 'upndown';
4 | import editorHtml from './editor.html?raw';
5 | import btnHtml from './btn.html?raw';
6 | import tooltipHtml from './tooltip.html?raw';
7 | import * as util from '../lib/util';
8 |
9 | const und = new Upndown({ decodeEntities: false });
10 |
11 | const defaultbtnOptions = [
12 | {
13 | label: '',
14 | action: 'undo',
15 | group: 'action',
16 | },
17 | {
18 | label: '',
19 | action: 'redo',
20 | group: 'action',
21 | },
22 | {
23 | label: '',
24 | tag: 'a',
25 | className: '',
26 | group: 'link',
27 | sampleText: 'link text',
28 | },
29 | {
30 | label: '',
31 | tag: 'strong',
32 | className: '',
33 | group: 'mark',
34 | sampleText: ' ',
35 | },
36 | {
37 | label: '',
38 | tag: 'i',
39 | className: '',
40 | group: 'mark',
41 | sampleText: ' ',
42 | },
43 | {
44 | label: '',
45 | tag: 'u',
46 | className: '',
47 | group: 'mark',
48 | sampleText: ' ',
49 | },
50 | ];
51 |
52 | const defaults = {
53 | mode: 'html',
54 | classNames: {
55 | LiteEditor: 'lite-editor',
56 | LiteEditorSource: 'lite-editor-source',
57 | LiteEditorBtn: 'lite-editor-btn',
58 | LiteEditorBtnClose: 'lite-editor-btn-close',
59 | LiteEditorBtnActive: 'lite-editor-btn-active',
60 | LiteEditorBtnGroup: 'lite-editor-btn-group',
61 | LiteEditorBtnGroupWrap: 'lite-editor-btn-group-wrap',
62 | LiteEditorBtnGroupWrapRight: 'lite-editor-btn-group-wrap-right',
63 | LiteEditorBtnCloseWrap: 'lite-editor-btn-close-wrap',
64 | LiteEditorBtnCloseLabel: 'lite-editor-btn-close-label',
65 | LiteEditorSelect: 'lite-editor-select',
66 | LiteEditorSelectWrap: 'lite-editor-select-wrap',
67 | LiteEditorToolBox: 'lite-editor-toolbox',
68 | LiteEditorTooltip: 'lite-editor-tooltip',
69 | LiteEditorTooltipWrap: 'lite-editor-tooltip-wrap',
70 | LiteEditorTooltipOuter: 'lite-editor-tooltip-outer',
71 | LiteEditorTooltipInner: 'lite-editor-tooltip-inner',
72 | LiteEditorTooltipTable: 'lite-editor-tooltip-table',
73 | LiteEditorTooltipTitle: 'lite-editor-tooltip-title',
74 | LiteEditorTooltipBody: 'lite-editor-tooltip-body',
75 | LiteEditorTooltipInput: 'lite-editor-tooltip-input',
76 | LiteEditorExtendInput: 'lite-editor-extend-input',
77 | LiteEditorFontLink: 'lite-editor-font-link',
78 | LiteEditorFontRemove: 'lite-editor-font-remove',
79 | LiteEditorFontUpdate: 'lite-editor-font-update',
80 | LiteEditorFontClose: 'lite-editor-font-close',
81 | LiteEditorFontSource: 'lite-editor-font-source',
82 | LiteEditorFontAbc: 'lite-editor-font-abc',
83 | },
84 | message: {
85 | addLinkTitle: 'link',
86 | updateLinkTitle: 'link',
87 | addLink: 'add',
88 | updateLink: 'update',
89 | removeLink: 'remove',
90 | linkUrl: 'URL',
91 | linkLabel: 'label',
92 | closeLabel: 'close',
93 | targetBlank: 'target',
94 | targetBlankLabel: 'Opens the linked page in a new window or tab',
95 | },
96 | voidElements: [
97 | 'area',
98 | 'base',
99 | 'basefont',
100 | 'bgsound',
101 | 'br',
102 | 'col',
103 | 'command',
104 | 'embed',
105 | 'frame',
106 | 'hr',
107 | 'image',
108 | 'img',
109 | 'input',
110 | 'isindex',
111 | 'keygen',
112 | 'link',
113 | 'menuitem',
114 | 'meta',
115 | 'nextid',
116 | 'param',
117 | 'source',
118 | 'track',
119 | 'wbr',
120 | ],
121 | minHeight: 50,
122 | maxHeight: 400,
123 | decodeSource: false,
124 | sourceFirst: false,
125 | escapeNotRegisteredTags: false,
126 | preserveSpace: false,
127 | nl2br: true,
128 | source: true,
129 | selectOptions: [],
130 | selectedOption: '',
131 | btnOptions: defaultbtnOptions,
132 | btnPosition: 'top',
133 | relAttrForTargetBlank: 'noopener noreferrer',
134 | };
135 |
136 | export default class LiteEditor extends aTemplate {
137 | constructor(ele, settings) {
138 | super();
139 | this.id = this._getUniqId();
140 | const selector = typeof ele === 'string' ? document.querySelector(ele) : ele;
141 | const html = ``;
142 | this.data = util.deepMerge({}, defaults, settings);
143 | this.data.showSource = this.data.sourceFirst;
144 | this.data.disableEditorMode = false;
145 | this.data.hideEditor = false;
146 | this.data.tooltipLabel = '';
147 | this.data.tooltipUrl = '';
148 | this.data.tooltipClassName = '';
149 | this.data.attr = '';
150 | this.data.targetBlank = 'false';
151 | this.data.linkNew = true;
152 | if (settings && settings.btnOptions) {
153 | this.data.btnOptions = settings.btnOptions;
154 | }
155 | this.data.groups = this.makeBtnGroups();
156 | this.stack = [];
157 | this.stackPosition = 0;
158 | let template = '';
159 | let attrStr = '';
160 | this.convert = {
161 | format: this.format,
162 | insertExtend: this.insertExtend,
163 | };
164 |
165 | if (this.data.btnPosition === 'bottom') {
166 | template = util.removeIndentNewline(`${editorHtml}${btnHtml}${tooltipHtml}`);
167 | } else {
168 | template = util.removeIndentNewline(`${btnHtml}${editorHtml}${tooltipHtml}`);
169 | }
170 |
171 | this.addTemplate(this.id, template);
172 |
173 | if (selector.value) {
174 | let value = selector.value;
175 | if (!this.data.sourceFirst) {
176 | value = this.makeEditableHtml(value);
177 | } else {
178 | value = selector.innerHTML;
179 | }
180 | if (this.data.escapeNotRegisteredTags) {
181 | value = this.escapeNotRegisteredTags(value);
182 | }
183 | this.data.firstValue = selector.value;
184 | this.data.value = value;
185 | }
186 | if (this.data.value) {
187 | this.data.value = this.data.value.replace(/([\\]+)/g, '$1\\\\'); // CMS-5637 バックスラッシュが消えてしまう問題に対処
188 | }
189 |
190 | if (selector.attributes) {
191 | [].forEach.call(selector.attributes, (attr) => {
192 | attrStr += ` ${attr.nodeName}="${attr.nodeValue}"`;
193 | });
194 | this.data.attr = attrStr;
195 | }
196 |
197 | if (
198 | !this.data.selectedOption &&
199 | this.data.selectOptions &&
200 | this.data.selectOptions[0] &&
201 | this.data.selectOptions[0].value
202 | ) {
203 | this.data.selectedOption = this.data.selectOptions[0].value;
204 | }
205 |
206 | util.before(selector, html);
207 | util.removeElement(selector);
208 | this.update();
209 | this.selector = this._getElementByQuery('[data-selector="lite-editor-source"]');
210 | const item = this.data.selectOptions.find((option) => option.value === this.data.selectedOption);
211 | if (item) {
212 | this.data.extendLabel = item.extendLabel;
213 | if (item.onSelect) {
214 | item.onSelect(this);
215 | }
216 | }
217 |
218 | this._fireEvent('init');
219 | }
220 |
221 | focus() {
222 | const { showSource } = this.data;
223 | if (showSource === true) {
224 | this._getElementByQuery('[data-selector="lite-editor-source"]').focus();
225 | } else {
226 | this._getElementByQuery('[data-selector="lite-editor"]').focus();
227 | }
228 | }
229 |
230 | registerButton(btn) {
231 | this.data.btnOptions.push(btn);
232 | this.data.groups = this.makeBtnGroups();
233 | this.update('html', '[data-selector="lite-editor-toolbox"]');
234 | }
235 |
236 | activateEditorMode() {
237 | this.data.disableEditorMode = false;
238 | this.update('html', '[data-selector="lite-editor-toolbox"]');
239 | }
240 |
241 | deactivateEditorMode() {
242 | this.data.disableEditorMode = true;
243 | this.update('html', '[data-selector="lite-editor-toolbox"]');
244 | }
245 |
246 | makeEditableHtml(value) {
247 | if (this.data.preserveSpace) {
248 | const dom = document.createElement('div');
249 | dom.innerHTML = value;
250 | util.replaceWhiteSpaceWithNbsp(dom);
251 | value = dom.innerHTML;
252 | }
253 |
254 | if (this.data.nl2br === false && value.slice(-1) === '\n') {
255 | value += '
';
256 | }
257 |
258 | value = value.replace(/
(\r\n|\r|\n)/g, '
');
259 | value = value.replace(/\r\n|\r|\n/g, '
');
260 |
261 | return value;
262 | }
263 |
264 | makeBtnGroups() {
265 | const btns = this.data.btnOptions;
266 | const groups = [];
267 | btns.forEach((btn, index) => {
268 | btn.index = index;
269 | let flag = true;
270 | if (!btn.group) {
271 | btn.group = 'none';
272 | }
273 | groups.forEach((group) => {
274 | if (group.name === btn.group) {
275 | group.items.push(btn);
276 | flag = false;
277 | }
278 | });
279 | if (flag) {
280 | const group = {
281 | name: btn.group,
282 | items: [btn],
283 | };
284 | groups.push(group);
285 | }
286 | });
287 | return groups;
288 | }
289 |
290 | _getSelf() {
291 | return document.querySelector(`[data-id='${this.id}']`);
292 | }
293 |
294 | _getUniqId() {
295 | return (Date.now().toString(36) + Math.random().toString(36).substr(2, 5)).toUpperCase();
296 | }
297 |
298 | _getElementByQuery(query) {
299 | return document.querySelector(`[data-id='${this.id}'] ${query}`);
300 | }
301 |
302 | _fireEvent(eventName) {
303 | const source = this._getElementByQuery('[data-selector="lite-editor-source"]');
304 | if (source) {
305 | util.triggerEvent(source, eventName);
306 | }
307 | }
308 |
309 | on(event, fn) {
310 | const source = this._getElementByQuery('[data-selector="lite-editor-source"]');
311 | source.addEventListener(event, (e) => {
312 | fn.call(this, e);
313 | });
314 | }
315 |
316 | escapeNotRegisteredTags(value) {
317 | const btns = this.data.btnOptions;
318 | value = value.replace(/<([a-zA-Z0-9._-]+)\s?(.*?)>(([\n\r\t]|.)*?)<\/\1>/g, (component, tag, attr, content) => {
319 | const className = (attr.match(/class=["|'](.*?)["|']/i) || [null, ''])[1];
320 | let flag = false;
321 | if (attr) {
322 | attr = ` ${attr}`;
323 | }
324 | btns.forEach((btn) => {
325 | if (btn.className === className && btn.tag === tag) {
326 | flag = true;
327 | }
328 | });
329 | if (flag) {
330 | return component;
331 | }
332 | if (/<([a-zA-Z0-9._-]+)\s?(.*?)>(([\n\r\t]|.)*?)<\/\1>/.exec(content)) {
333 | content = this.escapeNotRegisteredTags(content);
334 | }
335 | return `<${tag}${attr}>${content}</${tag}>`;
336 | });
337 |
338 | return value.replace(/<([a-zA-Z0-9._-]+)\s?([^>]*?)\/?>/g, (component, tag, attr) => {
339 | const className = (attr.match(/class=["|'](.*?)["|']/i) || [null, ''])[1];
340 | let flag = false;
341 | if (attr) {
342 | attr = ` ${attr}`;
343 | }
344 | btns.forEach((btn) => {
345 | if (btn.className === className && btn.tag === tag) {
346 | flag = true;
347 | }
348 | });
349 | if (flag) {
350 | return component;
351 | }
352 | if (tag !== 'br') {
353 | return `<${tag}${attr}>`;
354 | }
355 | return '
';
356 | });
357 | }
358 |
359 | encodeValue() {
360 | this.data.value = encode(this.data.value);
361 | this.update();
362 | }
363 |
364 | decodeValue() {
365 | this.data.value = decode(this.data.value);
366 | this.update();
367 | }
368 |
369 | hideEditor() {
370 | this.data.hideEditor = true;
371 | this.update();
372 | }
373 |
374 | showEditor() {
375 | this.data.hideEditor = false;
376 | this.update();
377 | }
378 |
379 | hideBtns() {
380 | this.data.hideBtns = true;
381 | this.update();
382 | }
383 |
384 | showBtns() {
385 | this.data.hideBtns = false;
386 | this.update();
387 | }
388 |
389 | resetStyle() {
390 | const selection = util.getSelection();
391 | const insertText = `${selection}`.replace(/<[^>]*>/g, '');
392 | if (this._isFocused()) {
393 | document.execCommand('insertText', false, insertText);
394 | }
395 | }
396 |
397 | insertHtml(html) {
398 | util.replaceSelectionWithHtml(html);
399 | const editor = this._getElementByQuery('[data-selector="lite-editor"]');
400 | this.data.value = editor.innerHTML;
401 | }
402 |
403 | insertHtmlAtCursor(html) {
404 | if (this.data.showSource) {
405 | const source = this._getElementByQuery('[data-selector="lite-editor-source"]');
406 | util.replaceSelectionWithText(source, html);
407 | this.data.value = this.makeEditableHtml(source.value);
408 | } else {
409 | util.insertHtmlAtCursor(html);
410 | const editor = this._getElementByQuery('[data-selector="lite-editor"]');
411 | this.data.value = editor.innerHTML;
412 | }
413 | }
414 |
415 | saveSelection() {
416 | this.selection = util.saveSelection();
417 | }
418 |
419 | restoreSelection() {
420 | if (!this.selection) {
421 | return;
422 | }
423 | util.restoreSelection(this.selection);
424 | }
425 |
426 | _isFocused() {
427 | const selector = this._getElementByQuery('[data-selector="lite-editor"]');
428 | return selector === document.activeElement;
429 | }
430 |
431 | _isVoidElement(tag) {
432 | return this.data.voidElements.find((item) => {
433 | if (item === tag) {
434 | return true;
435 | }
436 | return false;
437 | });
438 | }
439 |
440 | insertTag(tag, className, sampleText) {
441 | const groups = this.data.groups;
442 | const editor = this._getElementByQuery('[data-selector="lite-editor"]');
443 | const source = this._getElementByQuery('[data-selector="lite-editor-source"]');
444 | const element = util.getElementBySelection();
445 | let selection = util.getSelection(source);
446 |
447 | if (!this.data.showSource && !editor.contains(element)) {
448 | return;
449 | }
450 | if (!selection) {
451 | selection = sampleText;
452 | }
453 | if (tag === 'a') {
454 | this.saveSelection();
455 | this.showLinkDialog(`${selection}`, className);
456 | return;
457 | }
458 |
459 | let classAttr = '';
460 | if (className) {
461 | classAttr = ` class="${className}"`;
462 | }
463 | let insertHtml = `<${tag}${classAttr}>${selection}${tag}>`;
464 | if (this._isVoidElement(tag)) {
465 | insertHtml = `${selection}<${tag}>`;
466 | }
467 | if (this.data.showSource) {
468 | if (this.data.mode === 'markdown') {
469 | und.convert(insertHtml, (err, markdown) => {
470 | util.replaceSelectionWithText(source, markdown);
471 | this.data.value = this.makeEditableHtml(source.value);
472 | });
473 | } else {
474 | util.replaceSelectionWithText(source, insertHtml);
475 | this.data.value = this.makeEditableHtml(source.value);
476 | }
477 | } else {
478 | this.insertHtml(insertHtml.replace(/\r\n|\r|\n/g, '
'));
479 | groups.forEach((group) => {
480 | group.items.forEach((btn) => {
481 | if (btn.tag === tag && btn.className === className) {
482 | btn.selected = true;
483 | }
484 | });
485 | });
486 | this.update('html', '[data-selector="lite-editor-toolbox"]');
487 | }
488 | this._fireEvent('insertTag');
489 | }
490 |
491 | showLinkDialog(text, className) {
492 | this.data.tooltipLabel = text;
493 | this.data.tooltipClassName = className;
494 | this.data.linkNew = true;
495 | this.update('html', '[data-selector="lite-editor-tooltip"]');
496 | const urlInput = this._getElementByQuery('[data-bind="tooltipUrl"]');
497 | urlInput.focus();
498 | }
499 |
500 | updateTargetBlank() {
501 | const target = this.e.target;
502 | if (target.checked) {
503 | this.data.targetBlank = 'true';
504 | } else {
505 | this.data.targetBlank = 'false';
506 | }
507 | }
508 |
509 | insertAtag() {
510 | this.restoreSelection();
511 | const label = this.data.tooltipLabel;
512 | const link = this.data.tooltipUrl;
513 | const className = this.data.tooltipClassName;
514 | const targetBlank = this.data.targetBlank;
515 | const relAttrForTargetBlank = this.data.relAttrForTargetBlank;
516 | let classAttr = '';
517 | if (className) {
518 | classAttr = ` class="${className}"`;
519 | }
520 | const insertHtml = `${label}`;
521 | if (this.data.showSource) {
522 | const source = this._getElementByQuery('[data-selector="lite-editor-source"]');
523 | if (this.data.mode === 'markdown') {
524 | und.convert(insertHtml, (err, markdown) => {
525 | util.replaceSelectionWithText(source, markdown);
526 | this.data.value = this.makeEditableHtml(source.value);
527 | });
528 | } else {
529 | util.replaceSelectionWithText(source, insertHtml);
530 | this.data.value = this.makeEditableHtml(source.value);
531 | }
532 | } else {
533 | this.insertHtml(insertHtml.replace(/\r\n|\r|\n/g, '
'));
534 | this.updateToolBox();
535 | }
536 | this.closeTooltip();
537 | }
538 |
539 | onClick(i) {
540 | const number = parseInt(i, 10);
541 | const btn = this.data.btnOptions[number];
542 | if (btn.onClick) {
543 | btn.onClick(this);
544 | }
545 | }
546 |
547 | onInit(i) {
548 | const number = parseInt(i, 10);
549 | const btn = this.data.btnOptions[number];
550 | const btnElement = this._getElementByQuery(`[data-selector="btn-group"] [data-index="${i}"]`);
551 | if (btn.onInit && !btn.init) {
552 | btn.onInit(this, btnElement);
553 | btn.init = true;
554 | }
555 | }
556 |
557 | onRender(i) {
558 | const number = parseInt(i, 10);
559 | const btn = this.data.btnOptions[number];
560 | const btnElement = this._getElementByQuery(`[data-selector="btn-group"] [data-index="${i}"]`);
561 | if (btn.onRender) {
562 | btn.onRender(this, btnElement);
563 | }
564 | }
565 |
566 | beforeUpdated() {
567 | const data = this.data;
568 | data.canUndo = this.canUndo();
569 | data.canRedo = this.canRedo();
570 | if (data.firstValue) {
571 | data.formatedValue = this.data.firstValue;
572 | data.firstValue = null;
573 | } else if (!data.showSource) {
574 | data.formatedValue = this.format(data.value);
575 | }
576 | if (data.value) {
577 | data.value = data.value.replace(/{/g, '{').replace(/}/g, '}');
578 | }
579 | this._fireEvent('prerender');
580 | }
581 |
582 | onUpdated() {
583 | const editor = this._getElementByQuery('[data-selector="lite-editor"]');
584 | const source = this._getElementByQuery('[data-selector="lite-editor-source"]');
585 | this.data.btnOptions.forEach((btn, index) => {
586 | this.onInit(index);
587 | this.onRender(index);
588 | });
589 | if (this.data.showSource === true) {
590 | source.style.height = `${source.scrollHeight}px`;
591 | } else {
592 | this.data.value = editor.innerHTML;
593 | }
594 | if (!editor) {
595 | return;
596 | }
597 | this.saveSelection();
598 | if (this.stopStack) {
599 | this.stopStack = false;
600 | } else if (`${this.stack[this.stackPosition - 1]}` !== `${this.data.value}`) {
601 | this.stack = this.stack.slice(0, this.stackPosition + 1);
602 | this.stack.push(this.data.value);
603 | this.stackPosition += 1;
604 | if (this.data.showSource === false && this.selector) {
605 | this.selector.value = this.format(this.data.value);
606 | }
607 | }
608 | this._fireEvent('render');
609 | }
610 |
611 | redo() {
612 | if (!this.canRedo()) {
613 | return;
614 | }
615 | this.stackPosition += 1;
616 | this.data.value = this.stack[this.stackPosition];
617 | this.stopStack = true;
618 | this.update();
619 | this._fireEvent('redo');
620 | }
621 |
622 | canRedo() {
623 | if (this.stackPosition < this.stack.length - 1) {
624 | return true;
625 | }
626 | return false;
627 | }
628 |
629 | undo() {
630 | if (!this.canUndo()) {
631 | return;
632 | }
633 | this.stackPosition -= 1;
634 | this.data.value = this.stack[this.stackPosition];
635 | this.stopStack = true;
636 | this.update();
637 | this._fireEvent('undo');
638 | }
639 |
640 | canUndo() {
641 | if (this.stackPosition > 0) {
642 | return true;
643 | }
644 | return false;
645 | }
646 |
647 | onPaste() {
648 | const e = this.e;
649 | const editor = this._getElementByQuery('[data-selector="lite-editor"]');
650 | const textarea = this._getElementByQuery('[data-selector="lite-editor-source"]');
651 | e.preventDefault();
652 | let insertText = '';
653 | if (e.clipboardData) {
654 | insertText = e.clipboardData.getData('text/plain');
655 | } else if (window.clipboardData) {
656 | insertText = window.clipboardData.getData('Text');
657 | }
658 | if (this._isFocused() && insertText) {
659 | this.insertHtmlAtCursor(
660 | insertText
661 | .replace(//g, '>')
663 | .replace(/(\r\n|\n\r|\n|\r)/g, '
')
664 | .replace(/ /g, ' ')
665 | .replace(/\t/g, ' ')
666 | );
667 | this.data.value = editor.innerHTML;
668 | this.data.formatedValue = this.format(this.data.value);
669 | textarea.value = this.data.formatedValue;
670 | // ⌘VVVVVVV
671 | const pos = util.getCaretPos(editor);
672 | util.clearSelection();
673 | util.setCaretPos(editor, pos);
674 | }
675 | this._fireEvent('paste');
676 | }
677 |
678 | onKeyDown() {
679 | const editor = this._getElementByQuery('[data-selector="lite-editor"]');
680 | const textarea = this._getElementByQuery('[data-selector="lite-editor-source"]');
681 | const e = this.e;
682 | const pos = util.getCaretPos(editor);
683 |
684 | if (e.ctrlKey || e.metaKey) {
685 | if (e.which === 90 || e.keyCode === 90) {
686 | e.preventDefault();
687 | if (e.shiftKey) {
688 | this.redo();
689 | } else {
690 | this.undo();
691 | }
692 | }
693 | return;
694 | }
695 |
696 | if (e.keyCode !== 13) {
697 | this.data.value = editor.innerHTML;
698 | this.onPutCaret();
699 | return;
700 | }
701 | // on purpose
702 | const oldCoordinate = this.checkCaretCoordinate();
703 | this.insertHtmlAtCursor('
');
704 | let innerHTML = editor.innerHTML.replace(/
<\/(.*?)>/g, '$1>
');
705 | if (!util.hasLastBr(editor)) {
706 | innerHTML += '
';
707 | }
708 | editor.innerHTML = innerHTML;
709 | this.data.value = innerHTML;
710 | this.data.formatedValue = this.format(this.data.value);
711 | textarea.value = this.data.formatedValue;
712 | let coordinate = this.checkCaretCoordinate();
713 | editor.focus();
714 | util.setCaretPos(editor, pos + 1);
715 | if (util.getBrowser().indexOf('ie') === -1) {
716 | coordinate = this.checkCaretCoordinate();
717 | }
718 | if (coordinate.y > this.data.maxHeight) {
719 | editor.scrollTop += coordinate.y - oldCoordinate.y;
720 | }
721 | e.preventDefault();
722 | }
723 |
724 | checkCaretCoordinate() {
725 | const editor = this._getElementByQuery('[data-selector="lite-editor"]');
726 | const id = this._getUniqId();
727 | this.insertHtmlAtCursor(``);
728 | const span = this._getElementByQuery(`#${id}`);
729 | const rect = span.getBoundingClientRect();
730 | const editorRect = editor.getBoundingClientRect();
731 | const coordinate = {
732 | x: rect.x - editorRect.x,
733 | y: rect.y - editorRect.y,
734 | };
735 | util.removeElement(span);
736 | this.data.value = editor.innerHTML;
737 | return coordinate;
738 | }
739 |
740 | onInput() {
741 | const editor = this._getElementByQuery('[data-selector="lite-editor"]');
742 | // if (!util.hasLastBr(editor)) {
743 | // editor.appendChild(document.createElement('br'));
744 | // }
745 | const textarea = this._getElementByQuery('[data-selector="lite-editor-source"]');
746 | this.data.value = editor.innerHTML;
747 | this.data.formatedValue = this.format(this.data.value);
748 | textarea.value = this.data.formatedValue;
749 | }
750 |
751 | preventSubmit() {
752 | const e = this.e;
753 | if (e.keyCode === 13) {
754 | e.preventDefault();
755 | }
756 | }
757 |
758 | onPutCaret() {
759 | setTimeout(() => {
760 | const target = this.getSelectionNode();
761 | const tags = [];
762 | const editor = this._getElementByQuery('[data-selector="lite-editor"]');
763 | if (target && target !== editor) {
764 | tags.push({ tagName: target.tagName.toLowerCase(), className: target.getAttribute('class') || '' });
765 | let parent = target.parentElement;
766 | while (parent !== editor) {
767 | if (!parent) {
768 | break;
769 | }
770 | const tagName = parent.tagName.toLowerCase();
771 | tags.push({
772 | tagName,
773 | className: parent.getAttribute('class') || '',
774 | });
775 | parent = parent.parentElement;
776 | }
777 | }
778 | this.updateToolBox(tags);
779 | }, 1);
780 | }
781 |
782 | onDirectInput() {
783 | const source = this._getElementByQuery('[data-selector="lite-editor-source"]');
784 | const value = this.e.target.value;
785 | this.data.value = this.makeEditableHtml(value);
786 | // source.style.height = 'auto';
787 | source.style.height = `${source.scrollHeight}px`;
788 | }
789 |
790 | updateToolBox(tags = []) {
791 | const groups = this.data.groups;
792 | groups.forEach((group) => {
793 | group.items.forEach((btn) => {
794 | btn.selected = false;
795 | tags.forEach((tag) => {
796 | if (btn.tag === tag.tagName && btn.className === tag.className) {
797 | btn.selected = true;
798 | }
799 | });
800 | });
801 | });
802 | this.saveSelection();
803 | this.update('html', '[data-selector="lite-editor-toolbox"]');
804 | }
805 |
806 | updateTooltip(item) {
807 | if (item === null) {
808 | this.data.linkNew = true;
809 | this.data.tooltipLabel = '';
810 | this.data.tooltipUrl = '';
811 | this.data.targetBlank = 'false';
812 | } else {
813 | this.data.linkNew = false;
814 | this.data.tooltipLabel = item.innerHTML;
815 | this.data.tooltipUrl = item.getAttribute('href');
816 | this.savedLinkNode = item;
817 | if (item.getAttribute('target') === '_blank') {
818 | this.data.targetBlank = 'true';
819 | } else {
820 | this.data.targetBlank = 'false';
821 | }
822 | }
823 | this.update('html', '[data-selector="lite-editor-tooltip"]');
824 | }
825 |
826 | closeTooltip() {
827 | this.data.tooltipLabel = '';
828 | this.data.tooltipUrl = '';
829 | this.data.tooltipClassName = '';
830 | this.data.targetBlank = 'false';
831 | this.update('html', '[data-selector="lite-editor-tooltip"]');
832 | }
833 |
834 | updateLink() {
835 | this.restoreSelection();
836 | const editor = this._getElementByQuery('[data-selector="lite-editor"]');
837 | const pos = util.getCaretPos(editor);
838 | const label = this.data.tooltipLabel;
839 | const targetBlank = this.data.targetBlank;
840 | const url = this.data.tooltipUrl;
841 | const node = this.savedLinkNode;
842 | const relAttrForTargetBlank = this.data.relAttrForTargetBlank;
843 | node.setAttribute('href', url);
844 | node.innerHTML = label;
845 | if (targetBlank === 'true') {
846 | node.setAttribute('target', '_blank');
847 | node.setAttribute('rel', relAttrForTargetBlank);
848 | } else {
849 | node.removeAttribute('target');
850 | node.removeAttribute('rel');
851 | }
852 | this.data.value = editor.innerHTML;
853 | editor.focus();
854 | util.setCaretPos(editor, pos);
855 | this.onPutCaret();
856 | this.closeTooltip();
857 | }
858 |
859 | removeLink() {
860 | this.restoreSelection();
861 | const editor = this._getElementByQuery('[data-selector="lite-editor"]');
862 | const pos = util.getCaretPos(editor);
863 | const node = this.savedLinkNode;
864 | util.unwrapTag(node);
865 | editor.focus();
866 | util.setCaretPos(editor, pos);
867 | this.onPutCaret();
868 | this.closeTooltip();
869 | }
870 |
871 | getSelectionNode() {
872 | const node = document.getSelection().anchorNode;
873 | return node.nodeType === 3 ? node.parentNode : node;
874 | }
875 |
876 | unwrapTag(tag, className) {
877 | const editor = this._getElementByQuery('[data-selector="lite-editor"]');
878 | const pos = util.getCaretPos(editor);
879 | let node = util.getElementBySelection();
880 | const length = util.getSelectionLength();
881 | const nodePos = util.getCaretPos(node);
882 | if (node.parentElement === editor && node.textContent && nodePos === node.textContent.length && length === 0) {
883 | util.moveCaretAfter(node);
884 | } else {
885 | while (true) {
886 | const nodeClassName = node.getAttribute('class') || '';
887 | if (node.tagName.toLowerCase() === tag && nodeClassName === className) {
888 | if (tag === 'a') {
889 | this.updateTooltip(node);
890 | } else {
891 | util.unwrapTag(node);
892 | }
893 | break;
894 | }
895 | node = node.parentElement;
896 | }
897 | this.data.value = editor.innerHTML;
898 | editor.focus();
899 | util.setCaretPos(editor, pos, length);
900 | }
901 | this.onPutCaret();
902 | this._fireEvent('unwrapTag');
903 | }
904 |
905 | changeMode(mode) {
906 | this.data.mode = mode;
907 | }
908 |
909 | toggleSource() {
910 | this.data.showSource = !this.data.showSource;
911 | if (this.data.showSource) {
912 | this.data.formatedValue = this.format(this.data.value);
913 | this.data.groups.forEach((group) => {
914 | group.items.forEach((btn) => {
915 | btn.selected = false;
916 | });
917 | });
918 | } else if (this.data.value) {
919 | this.data.value = this.data.value.replace(/([\\]+)/g, '$1\\'); // CMS-5637 バックスラッシュが消えてしまう問題に対処
920 | }
921 | this.update();
922 | }
923 |
924 | showSource() {
925 | this.data.showSource = true;
926 | if (this.data.showSource) {
927 | this.data.formatedValue = this.format(this.data.value);
928 | this.data.groups.forEach((group) => {
929 | group.items.forEach((btn) => {
930 | btn.selected = false;
931 | });
932 | });
933 | }
934 | this.update();
935 | }
936 |
937 | hideSource() {
938 | this.data.showSource = false;
939 | this.update();
940 | }
941 |
942 | insertExtend(txt) {
943 | return txt.replace(/text_tag/g, 'text_extend_tag');
944 | }
945 |
946 | format(txt) {
947 | if (!txt) {
948 | return '';
949 | }
950 | let replaced = txt
951 | .replace(/
( *)/g, '\n')
952 | .replace(/
/g, '\n')
953 | .replace(/ /g, ' ')
954 | .replace(/