├── .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 | icon -------------------------------------------------------------------------------- /images/icon_arrow_next.svg: -------------------------------------------------------------------------------- 1 | icon -------------------------------------------------------------------------------- /images/icon_close.svg: -------------------------------------------------------------------------------- 1 | icon -------------------------------------------------------------------------------- /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 |
13 | 17 |
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 | 3 | 4 | Slice 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /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 | [![npm version](https://badge.fury.io/js/lite-editor.svg)](https://badge.fury.io/js/lite-editor) 3 | [![CircleCI](https://circleci.com/gh/appleple/lite-editor/tree/master.svg?style=shield)](https://circleci.com/gh/appleple/lite-editor/tree/master) 4 | [![npm download](http://img.shields.io/npm/dm/lite-editor.svg)](https://www.npmjs.com/package/lite-editor) 5 | [![GitHub license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](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 |
2 | 3 |
4 |
5 |
6 |
7 | 8 | \{message.closeLabel\} 9 | 12 | 13 | 14 |

15 | \{message.addLinkTitle\}

16 | 17 | 18 |

19 | \{message.updateLinkTitle\}

20 | 21 |
22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 43 | 44 | 45 | 46 | 47 | 66 | 67 |
\{message.linkLabel\} 26 | 27 |
\{message.linkUrl\} 32 | 33 |
\{message.targetBlank\} 38 | 42 |
48 | 49 | 53 | 54 | 55 | 56 | 60 | 64 | 65 |
68 |
69 |
70 |
71 |
72 |
73 | 74 |
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 |
2 | 3 | 4 |
5 |
6 | 13 | 18 |
19 |
20 | 21 | 22 | 23 |
24 | 30 | 31 | 32 | 34 | 35 |
36 | 37 | 38 |
style="display:none;" 39 | > 40 | 41 | 42 |
43 | 44 | 45 | 46 | 48 | 49 | 50 | 51 | 53 | 54 | 55 | 56 | 58 | 59 | 60 | 61 | 71 | 72 | 73 |
74 | 75 |
76 |
-------------------------------------------------------------------------------- /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 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.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}`; 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, '
'); 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(/