tex-editor 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +233 -0
- package/package.json +34 -0
- package/src/tex.css +91 -0
- package/src/tex.js +406 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Marcello Violini (marcellov7)
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
<img src="images/tex_logo.png" width="250" alt="Logo">
|
|
2
|
+
|
|
3
|
+
TEX is a ultra-lightweight and straightforward JavaScript library for creating rich text editors (WYSIWYG) directly in the browser. It is designed to work with both `<textarea>` and `<div>` elements.
|
|
4
|
+
|
|
5
|
+
> Live demo: [https://codepen.io/marcellov7/pen/BabGydp](https://codepen.io/marcellov7/pen/BabGydp)
|
|
6
|
+
|
|
7
|
+
## Key Features
|
|
8
|
+
|
|
9
|
+
- Pure JavaScript, no dependencies, written in ES6.
|
|
10
|
+
- Simple and intuitive user interface.
|
|
11
|
+
- Wide selection of predefined buttons for text formatting.
|
|
12
|
+
- Support for inserting HTML directly into the editor.
|
|
13
|
+
- Support for plugins.
|
|
14
|
+
- Easily Customizable themes to fit your website design.
|
|
15
|
+
- Ultra lightweight and fast.
|
|
16
|
+
- Light and dark mode.
|
|
17
|
+
|
|
18
|
+
[](/images/screenshot.jpg)
|
|
19
|
+
|
|
20
|
+
## Comparisons
|
|
21
|
+
|
|
22
|
+
| library | size (min+gzip) | size (min) | jquery | bootstrap | react | link |
|
|
23
|
+
|---------------|-----------------|------------|--------|-----------|-------|------|
|
|
24
|
+
| TEX | 2.3kB | 6.3kB | | | | https://github.com/marcellov7/tex |
|
|
25
|
+
| quill | 43kB | 205kB | | | | https://github.com/quilljs/quill |
|
|
26
|
+
| trix | 47kB | 204kB | | | | https://github.com/basecamp/trix |
|
|
27
|
+
| ckeditor | 163kB | 551kB | | | | https://ckeditor.com |
|
|
28
|
+
| trumbowyg | 8kB | 23kB | x | | | https://github.com/Alex-D/Trumbowyg |
|
|
29
|
+
| summernote | 26kB | 93kB | x | x | | https://github.com/summernote/summernote |
|
|
30
|
+
| froala | 52kB | 186kB | x | | | https://github.com/froala/wysiwyg-editor |
|
|
31
|
+
| tinymce | 157kB | 491kB | x | | | https://github.com/tinymce/tinymce |
|
|
32
|
+
|
|
33
|
+
## How to Use TEX
|
|
34
|
+
|
|
35
|
+
You can use TEX either via a `<script>` tag (CDN) or by installing it from npm.
|
|
36
|
+
|
|
37
|
+
### Option 1 — CDN / `<script>` tag
|
|
38
|
+
|
|
39
|
+
1. Link TEX to your HTML:
|
|
40
|
+
```html
|
|
41
|
+
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/marcellov7/tex@main/src/tex.min.css">
|
|
42
|
+
<script src="https://cdn.jsdelivr.net/gh/marcellov7/tex@main/src/tex.min.js"></script>
|
|
43
|
+
```
|
|
44
|
+
The library is then available as the global `window.tex`.
|
|
45
|
+
|
|
46
|
+
### Option 2 — npm (bundlers, React, Vue, etc.)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install tex-editor
|
|
50
|
+
```
|
|
51
|
+
```javascript
|
|
52
|
+
import tex from 'tex-editor'
|
|
53
|
+
// don't forget the stylesheet:
|
|
54
|
+
import 'tex-editor/src/tex.css'
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Then add HTML elements where you want to display the text editors:
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<div id="editor">Hello</div>
|
|
61
|
+
<!--or-->
|
|
62
|
+
<textarea id="editor">Hello</textarea>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
### Initialization
|
|
68
|
+
|
|
69
|
+
To initialize TEX, use the `tex.init()` method, passing in an object with the desired settings. Here's how you can do it:
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
const tex = window.tex;
|
|
73
|
+
|
|
74
|
+
tex.init({
|
|
75
|
+
element: document.getElementById("editor"),
|
|
76
|
+
buttons: ['bold', 'italic', 'underline', 'textColor', 'heading1', 'heading2', 'paragraph', 'removeFormat', 'olist', 'ulist', 'code', 'line', 'link', 'image', 'html'],
|
|
77
|
+
onChange: (content) => {
|
|
78
|
+
console.log("Editor :", content);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### API
|
|
84
|
+
```javascript
|
|
85
|
+
// ES6 (after `npm install tex-editor`)
|
|
86
|
+
import tex from 'tex-editor'
|
|
87
|
+
// or
|
|
88
|
+
import { exec, init, destroy, getContent, setContent } from 'tex-editor'
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Parameters
|
|
92
|
+
|
|
93
|
+
- `element`: The HTML element (either `<textarea>` or `<div>`) to be converted into a text editor.
|
|
94
|
+
- `buttons`: An array of buttons to be displayed in the editor toolbar.
|
|
95
|
+
- `plugins`: An array of plugins.
|
|
96
|
+
- `paragraphSeparator` : 'p', // optional, default = 'div'
|
|
97
|
+
- `cssStyle`: false | true, // Outputs <span style="font-weight: bold;"></span> instead of <b></b>
|
|
98
|
+
- `theme`: 'dark' | default (light),
|
|
99
|
+
- `onChange`: A callback function to be executed when the content of the editor changes.
|
|
100
|
+
|
|
101
|
+
### Get Content
|
|
102
|
+
```javascript
|
|
103
|
+
tex.getContent(document.getElementById("editor"));
|
|
104
|
+
```
|
|
105
|
+
### Set Content
|
|
106
|
+
```javascript
|
|
107
|
+
tex.setContent(document.getElementById("editor"), "<p>Text in <strong>HTML</strong> format</p>")
|
|
108
|
+
```
|
|
109
|
+
### Exec
|
|
110
|
+
```javascript
|
|
111
|
+
// Execute a document command.
|
|
112
|
+
// Reference: https://developer.mozilla.org/en/docs/Web/API/Document/execCommand
|
|
113
|
+
tex.exec(command<string>, value<string>)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Destroy
|
|
117
|
+
```javascript
|
|
118
|
+
tex.destroy(document.getElementById("editor"));
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Usage in React
|
|
122
|
+
|
|
123
|
+
TEX works on a real DOM node, so in React you initialize it inside `useEffect`
|
|
124
|
+
using a `ref`, and tear it down in the cleanup function.
|
|
125
|
+
|
|
126
|
+
```jsx
|
|
127
|
+
import { useEffect, useRef } from 'react'
|
|
128
|
+
import tex from 'tex-editor'
|
|
129
|
+
import 'tex-editor/src/tex.css'
|
|
130
|
+
|
|
131
|
+
export default function TexEditor({ value = '', onChange }) {
|
|
132
|
+
const ref = useRef(null)
|
|
133
|
+
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
const element = ref.current
|
|
136
|
+
tex.init({
|
|
137
|
+
element,
|
|
138
|
+
buttons: ['bold', 'italic', 'underline', 'heading1', 'link', 'html'],
|
|
139
|
+
theme: 'light',
|
|
140
|
+
onChange,
|
|
141
|
+
})
|
|
142
|
+
if (value) tex.setContent(element, value)
|
|
143
|
+
|
|
144
|
+
return () => tex.destroy(element)
|
|
145
|
+
}, [])
|
|
146
|
+
|
|
147
|
+
// The element MUST have a unique `id` — TEX uses it internally.
|
|
148
|
+
return <div id="my-tex-editor" ref={ref} />
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
> Run `tex.init` only once (empty dependency array). Use `tex.setContent` /
|
|
153
|
+
> `tex.getContent` to read or update the content afterwards. The target element
|
|
154
|
+
> must have a unique `id`, since TEX relies on it to manage the editor instance.
|
|
155
|
+
|
|
156
|
+
### List of predefined buttons
|
|
157
|
+
|
|
158
|
+
- fontSize
|
|
159
|
+
- bold
|
|
160
|
+
- italic
|
|
161
|
+
- underline
|
|
162
|
+
- strikethrough
|
|
163
|
+
- heading1
|
|
164
|
+
- heading2
|
|
165
|
+
- paragraph
|
|
166
|
+
- removeFormat
|
|
167
|
+
- quote
|
|
168
|
+
- olist
|
|
169
|
+
- ulist
|
|
170
|
+
- code
|
|
171
|
+
- line
|
|
172
|
+
- link
|
|
173
|
+
- image
|
|
174
|
+
- html
|
|
175
|
+
- textColor
|
|
176
|
+
- textBackColor
|
|
177
|
+
- indent
|
|
178
|
+
- outdent
|
|
179
|
+
- undo
|
|
180
|
+
- redo
|
|
181
|
+
- justifyCenter
|
|
182
|
+
- justifyFull
|
|
183
|
+
- justifyLeft
|
|
184
|
+
- justifyRight
|
|
185
|
+
|
|
186
|
+
## Plugins
|
|
187
|
+
> Plugin demo: [https://codepen.io/marcellov7/pen/poYqEMV](https://codepen.io/marcellov7/pen/poYqEMV)
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
var pluginImageUpload = {
|
|
191
|
+
name: 'pluginImageUpload',
|
|
192
|
+
icon: '-↑-',
|
|
193
|
+
title: 'Image Upload',
|
|
194
|
+
result: function(res) {
|
|
195
|
+
//Example function to display an input and upload the image.
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Initialise the button in the position you want and the plugin, like this:
|
|
201
|
+
```js
|
|
202
|
+
tex.init({
|
|
203
|
+
element: document.getElementById("editor"),
|
|
204
|
+
buttons: ['pluginImageUpload', 'bold', 'fontSize', 'bold', 'italic'],
|
|
205
|
+
plugins: [pluginImageUpload],
|
|
206
|
+
onChange: () => {
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Styles
|
|
212
|
+
For example:
|
|
213
|
+
If you want to change the height of the editor after the DOM has been initialized, you can do so by targeting the ".tex-content" class and adjusting the height property in your CSS file.
|
|
214
|
+
|
|
215
|
+
```css
|
|
216
|
+
.tex-content {
|
|
217
|
+
height: 400px;
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Contributing
|
|
222
|
+
|
|
223
|
+
If you'd like to contribute to TEX, follow these steps:
|
|
224
|
+
|
|
225
|
+
1. Fork this repository.
|
|
226
|
+
2. Create a new branch for your changes: `git checkout -b feature/new-feature`.
|
|
227
|
+
3. Commit your changes: `git commit -am 'Add new feature'`.
|
|
228
|
+
4. Push your branch: `git push origin feature/new-feature`.
|
|
229
|
+
5. Submit a pull request for your changes.
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
TEX is released under the [MIT License](LICENSE).
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tex-editor",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Ultra-lightweight WYSIWYG rich text editor in pure JavaScript, no dependencies. Works with both <textarea> and <div> elements.",
|
|
5
|
+
"main": "src/tex.js",
|
|
6
|
+
"unpkg": "src/tex.js",
|
|
7
|
+
"jsdelivr": "src/tex.js",
|
|
8
|
+
"style": "src/tex.css",
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"wysiwyg",
|
|
16
|
+
"editor",
|
|
17
|
+
"rich-text",
|
|
18
|
+
"rte",
|
|
19
|
+
"contenteditable",
|
|
20
|
+
"textarea",
|
|
21
|
+
"lightweight",
|
|
22
|
+
"no-dependencies"
|
|
23
|
+
],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/marcellov7/tex.git"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/marcellov7/tex#readme",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/marcellov7/tex/issues"
|
|
31
|
+
},
|
|
32
|
+
"author": "Marcello Violini (marcellov7)",
|
|
33
|
+
"license": "MIT"
|
|
34
|
+
}
|
package/src/tex.css
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
.tex-container {
|
|
2
|
+
border: 1px solid #efefef;
|
|
3
|
+
box-sizing: border-box;
|
|
4
|
+
border-radius: 4px;
|
|
5
|
+
overflow: hidden;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.tex-container.theme-dark {
|
|
9
|
+
border-color: #383838;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.tex-content, .htmlContent {
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
background-color: #fff;
|
|
15
|
+
height: 300px;
|
|
16
|
+
width: 100%;
|
|
17
|
+
outline: 0;
|
|
18
|
+
overflow-y: auto;
|
|
19
|
+
padding: 10px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.theme-dark .tex-content, .theme-dark .htmlContent {
|
|
23
|
+
background-color: #10131a;
|
|
24
|
+
color: white;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.tex-actionbar {
|
|
28
|
+
background-color: #FFF;
|
|
29
|
+
border-bottom: 1px solid #efefef;
|
|
30
|
+
padding:5px;
|
|
31
|
+
}
|
|
32
|
+
.theme-dark .tex-actionbar {
|
|
33
|
+
background-color: #10131a;
|
|
34
|
+
border-color: #383838;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.tex-button {
|
|
38
|
+
position: relative;
|
|
39
|
+
background-color: #f7f7f7;
|
|
40
|
+
border: none;
|
|
41
|
+
cursor: pointer;
|
|
42
|
+
height: 30px;
|
|
43
|
+
outline: 0;
|
|
44
|
+
min-width: 30px;
|
|
45
|
+
vertical-align: bottom;
|
|
46
|
+
color: #000;
|
|
47
|
+
margin-right: 3px;
|
|
48
|
+
border-radius: 4px;
|
|
49
|
+
border-bottom: 1px solid lightgray;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.theme-dark .tex-button {
|
|
53
|
+
background-color: #181d27;
|
|
54
|
+
color: #ffF;
|
|
55
|
+
border-bottom: 1px solid #383838;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.tex-button:hover{
|
|
59
|
+
background-color: #f2f2f2;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.theme-dark .tex-button:hover{
|
|
63
|
+
background-color: #353434;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.tex-button-selected {
|
|
67
|
+
background-color: #F0F0F0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.tex-modal{
|
|
71
|
+
position: absolute;
|
|
72
|
+
background-color: white;
|
|
73
|
+
border: 1px solid #ccc;
|
|
74
|
+
padding:5px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.tex-button > *:nth-child(2) {
|
|
78
|
+
position: absolute;
|
|
79
|
+
top: 0;
|
|
80
|
+
left: 0;
|
|
81
|
+
opacity: 0;
|
|
82
|
+
height: 30px;
|
|
83
|
+
width: 30px;
|
|
84
|
+
-webkit-appearance: none;
|
|
85
|
+
-moz-appearance: none;
|
|
86
|
+
appearance: none;
|
|
87
|
+
background: transparent;
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
border: 0;
|
|
90
|
+
padding: 0;
|
|
91
|
+
}
|
package/src/tex.js
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
4
|
+
factory((global.tex = {}));
|
|
5
|
+
}(this, (function (exports) { 'use strict'
|
|
6
|
+
|
|
7
|
+
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key] } } } return target }
|
|
8
|
+
|
|
9
|
+
var colorPicker = function(button, type) {
|
|
10
|
+
var input = button.querySelector('input[type="color"]')
|
|
11
|
+
input = document.createElement('input')
|
|
12
|
+
input.type = 'color'
|
|
13
|
+
input.style.display = 'block'
|
|
14
|
+
button.appendChild(input)
|
|
15
|
+
input.addEventListener('input', () => {
|
|
16
|
+
exec(type, input.value)
|
|
17
|
+
if (type == "textColor") {
|
|
18
|
+
button.querySelector('span').style.color = input.value
|
|
19
|
+
exec('foreColor', input.value)
|
|
20
|
+
}
|
|
21
|
+
if (type == "textBackColor") {
|
|
22
|
+
button.querySelector('#textBackColor').style.background = input.value
|
|
23
|
+
exec('backColor', input.value)
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
var selectOptions = function(button) {
|
|
31
|
+
var input = button.querySelector('select')
|
|
32
|
+
input = document.createElement('select')
|
|
33
|
+
const fontSizes = [3, 4, 5, 6, 7]
|
|
34
|
+
fontSizes.forEach(size => {
|
|
35
|
+
const option = document.createElement('option')
|
|
36
|
+
option.value = size
|
|
37
|
+
option.textContent = size
|
|
38
|
+
input.appendChild(option)
|
|
39
|
+
})
|
|
40
|
+
button.appendChild(input)
|
|
41
|
+
input.addEventListener('change', () => {
|
|
42
|
+
exec('fontSize', input.value)
|
|
43
|
+
})
|
|
44
|
+
return false
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
var paragraphSeparatorString = 'defaultParagraphSeparator'
|
|
48
|
+
var formatBlock = 'formatBlock'
|
|
49
|
+
var addEventListener = function addEventListener(parent, type, listener) {
|
|
50
|
+
return parent.addEventListener(type, listener)
|
|
51
|
+
}
|
|
52
|
+
var appendChild = function appendChild(parent, child) {
|
|
53
|
+
return parent.appendChild(child)
|
|
54
|
+
}
|
|
55
|
+
var createElement = function createElement(tag) {
|
|
56
|
+
return document.createElement(tag)
|
|
57
|
+
}
|
|
58
|
+
var queryCommandState = function queryCommandState(command) {
|
|
59
|
+
return document.queryCommandState(command)
|
|
60
|
+
}
|
|
61
|
+
var queryCommandValue = function queryCommandValue(command) {
|
|
62
|
+
return document.queryCommandValue(command)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
var exec = function exec(command) {
|
|
66
|
+
var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null
|
|
67
|
+
return document.execCommand(command, false, value)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
var defaultActions = {
|
|
71
|
+
fontSize: {
|
|
72
|
+
icon: '🗚',
|
|
73
|
+
title: 'Font Size',
|
|
74
|
+
result: () => {}
|
|
75
|
+
},
|
|
76
|
+
bold: {
|
|
77
|
+
icon: '<b>B</b>',
|
|
78
|
+
title: 'Bold',
|
|
79
|
+
state: () => queryCommandState('bold'),
|
|
80
|
+
result: () => exec('bold')
|
|
81
|
+
},
|
|
82
|
+
italic: {
|
|
83
|
+
icon: '<i>I</i>',
|
|
84
|
+
title: 'Italic',
|
|
85
|
+
state: () => queryCommandState('italic'),
|
|
86
|
+
result: () => exec('italic')
|
|
87
|
+
},
|
|
88
|
+
underline: {
|
|
89
|
+
icon: '<u>U</u>',
|
|
90
|
+
title: 'Underline',
|
|
91
|
+
state: () => queryCommandState('underline'),
|
|
92
|
+
result: () => exec('underline')
|
|
93
|
+
},
|
|
94
|
+
removeFormat: {
|
|
95
|
+
icon: '⌫',
|
|
96
|
+
title: 'Remove Format',
|
|
97
|
+
result: () => exec('removeFormat')
|
|
98
|
+
},
|
|
99
|
+
strikethrough: {
|
|
100
|
+
icon: '<strike>S</strike>',
|
|
101
|
+
title: 'Strike-through',
|
|
102
|
+
state: () => queryCommandState('strikeThrough'),
|
|
103
|
+
result: () => exec('strikeThrough')
|
|
104
|
+
},
|
|
105
|
+
heading1: {
|
|
106
|
+
icon: '<b>H<sub>1</sub></b>',
|
|
107
|
+
title: 'Heading 1',
|
|
108
|
+
result: () => exec(formatBlock, '<h1>')
|
|
109
|
+
},
|
|
110
|
+
heading2: {
|
|
111
|
+
icon: '<b>H<sub>2</sub></b>',
|
|
112
|
+
title: 'Heading 2',
|
|
113
|
+
result: () => exec(formatBlock, '<h2>')
|
|
114
|
+
},
|
|
115
|
+
paragraph: {
|
|
116
|
+
icon: '¶',
|
|
117
|
+
title: 'Paragraph',
|
|
118
|
+
result: () => exec(formatBlock, '<p>')
|
|
119
|
+
},
|
|
120
|
+
quote: {
|
|
121
|
+
icon: '“ ”',
|
|
122
|
+
title: 'Quote',
|
|
123
|
+
result: () => exec(formatBlock, '<blockquote>')
|
|
124
|
+
},
|
|
125
|
+
olist: {
|
|
126
|
+
icon: '#',
|
|
127
|
+
title: 'Ordered List',
|
|
128
|
+
result: () => exec('insertOrderedList')
|
|
129
|
+
},
|
|
130
|
+
ulist: {
|
|
131
|
+
icon: '•',
|
|
132
|
+
title: 'Unordered List',
|
|
133
|
+
result: () => exec('insertUnorderedList')
|
|
134
|
+
},
|
|
135
|
+
code: {
|
|
136
|
+
icon: '</>',
|
|
137
|
+
title: 'Code',
|
|
138
|
+
result: () => exec(formatBlock, '<pre>')
|
|
139
|
+
},
|
|
140
|
+
line: {
|
|
141
|
+
icon: '―',
|
|
142
|
+
title: 'Horizontal Line',
|
|
143
|
+
result: () => exec('insertHorizontalRule')
|
|
144
|
+
},
|
|
145
|
+
link: {
|
|
146
|
+
icon: '🔗',
|
|
147
|
+
title: 'Link',
|
|
148
|
+
result: () => {
|
|
149
|
+
const url = window.prompt('Enter the link URL')
|
|
150
|
+
if (url) exec('createLink', url)
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
image: {
|
|
154
|
+
icon: 'Img',
|
|
155
|
+
title: 'Image',
|
|
156
|
+
result: () => {
|
|
157
|
+
const url = window.prompt('Enter the image URL')
|
|
158
|
+
if (url) exec('insertImage', url)
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
html: {
|
|
162
|
+
icon: 'HTML',
|
|
163
|
+
title: 'Html',
|
|
164
|
+
result: ({ content }) => {
|
|
165
|
+
const parentElement = content.parentNode
|
|
166
|
+
const htmlContentElement = parentElement.querySelector('.htmlContent')
|
|
167
|
+
const elementContentElement = parentElement.querySelector('.tex-content')
|
|
168
|
+
|
|
169
|
+
if (htmlContentElement) {
|
|
170
|
+
if (htmlContentElement.style.display === 'none') {
|
|
171
|
+
htmlContentElement.style.display = 'block'
|
|
172
|
+
elementContentElement.style.display = 'none'
|
|
173
|
+
} else {
|
|
174
|
+
htmlContentElement.style.display = 'none'
|
|
175
|
+
elementContentElement.style.display = 'block'
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
textColor: {
|
|
181
|
+
icon: 'A',
|
|
182
|
+
title: 'Text Color',
|
|
183
|
+
result: () => {}
|
|
184
|
+
},
|
|
185
|
+
textBackColor: {
|
|
186
|
+
icon: '<span id="textBackColor" style="padding:3px; background:lightgray;">A</span>',
|
|
187
|
+
title: 'Text Color',
|
|
188
|
+
result: () => {}
|
|
189
|
+
},
|
|
190
|
+
indent: {
|
|
191
|
+
icon: '›',
|
|
192
|
+
title: 'Indent',
|
|
193
|
+
result: () => exec('indent')
|
|
194
|
+
},
|
|
195
|
+
outdent: {
|
|
196
|
+
icon: '‹',
|
|
197
|
+
title: 'Outdent',
|
|
198
|
+
result: () => exec('outdent')
|
|
199
|
+
},
|
|
200
|
+
undo: {
|
|
201
|
+
icon: '↶',
|
|
202
|
+
title: 'Undo',
|
|
203
|
+
result: () => exec('undo')
|
|
204
|
+
},
|
|
205
|
+
redo: {
|
|
206
|
+
icon: '↷',
|
|
207
|
+
title: 'Redo',
|
|
208
|
+
result: () => exec('redo')
|
|
209
|
+
},
|
|
210
|
+
justifyCenter: {
|
|
211
|
+
icon: '⇥⇤',
|
|
212
|
+
title: 'Justify Center',
|
|
213
|
+
result: () => exec('justifyCenter')
|
|
214
|
+
},
|
|
215
|
+
justifyFull: {
|
|
216
|
+
icon: '⇤⇥',
|
|
217
|
+
title: 'Justify Full',
|
|
218
|
+
result: () => exec('justifyFull')
|
|
219
|
+
},
|
|
220
|
+
justifyLeft: {
|
|
221
|
+
icon: '⇤',
|
|
222
|
+
title: 'Justify Left',
|
|
223
|
+
result: () => exec('justifyLeft')
|
|
224
|
+
},
|
|
225
|
+
justifyRight: {
|
|
226
|
+
icon: '⇥',
|
|
227
|
+
title: 'Justify Right',
|
|
228
|
+
result: () => exec('justifyRight')
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
var destroy = el => {
|
|
233
|
+
var element = document.querySelector(`[tex-id="${el.id}"]`)
|
|
234
|
+
var elementBase = document.getElementById(el.id)
|
|
235
|
+
var content = element.querySelector('.tex-content')
|
|
236
|
+
var actionbar = element.querySelector('.tex-actionbar')
|
|
237
|
+
if (element) {
|
|
238
|
+
var contentClone = content.cloneNode(true)
|
|
239
|
+
content.parentNode.replaceChild(contentClone, content)
|
|
240
|
+
|
|
241
|
+
var actionbarClone = actionbar.cloneNode(true)
|
|
242
|
+
actionbar.parentNode.replaceChild(actionbarClone, actionbar)
|
|
243
|
+
|
|
244
|
+
actionbarClone.remove()
|
|
245
|
+
|
|
246
|
+
if (elementBase.tagName.toLowerCase() === 'textarea') {
|
|
247
|
+
elementBase.style.display = 'block'
|
|
248
|
+
element.remove()
|
|
249
|
+
} else {
|
|
250
|
+
element.innerHTML = contentClone.innerHTML
|
|
251
|
+
element.removeAttribute("tex-id")
|
|
252
|
+
element.classList.remove("theme-light", "theme-dark", "tex-container")
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
var getContent = el => {
|
|
258
|
+
const element = document.querySelector(`[tex-id="${el.id}"]`)
|
|
259
|
+
const content = element.querySelector('.tex-content')
|
|
260
|
+
return content.innerHTML
|
|
261
|
+
}
|
|
262
|
+
var setContent = (el, content) => {
|
|
263
|
+
const element = document.querySelector(`[tex-id="${el.id}"]`)
|
|
264
|
+
const contentElement = element.querySelector('.tex-content')
|
|
265
|
+
contentElement.innerHTML = content
|
|
266
|
+
element.querySelector('.htmlContent').value = content
|
|
267
|
+
el.value = content
|
|
268
|
+
if (element.texOnChange) element.texOnChange(content)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
var init = settings => {
|
|
272
|
+
var theme = settings.theme || 'light'
|
|
273
|
+
var paragraphSeparator = settings[paragraphSeparatorString] || 'div'
|
|
274
|
+
var editorContainer = createElement('div')
|
|
275
|
+
editorContainer.className = 'tex-container'
|
|
276
|
+
editorContainer.classList.add(`theme-${theme}`)
|
|
277
|
+
editorContainer.setAttribute("tex-id", settings.element.id)
|
|
278
|
+
editorContainer.texOnChange = settings.onChange
|
|
279
|
+
var actionbar = createElement('div')
|
|
280
|
+
actionbar.className = 'tex-actionbar'
|
|
281
|
+
appendChild(editorContainer, actionbar)
|
|
282
|
+
|
|
283
|
+
var content = settings.element.content = createElement('div')
|
|
284
|
+
content.contentEditable = true
|
|
285
|
+
content.className = 'tex-content tex-editor'
|
|
286
|
+
|
|
287
|
+
if (settings.element.tagName.toLowerCase() === 'textarea') {
|
|
288
|
+
content.innerHTML = settings.element.value
|
|
289
|
+
} else {
|
|
290
|
+
content.innerHTML = settings.element.innerHTML
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
content.onkeydown = event => {
|
|
294
|
+
if (event.key === 'Enter' && queryCommandValue(formatBlock) === 'blockquote') {
|
|
295
|
+
setTimeout(() => exec(formatBlock, `<${paragraphSeparator}>`), 0)
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
appendChild(editorContainer, content)
|
|
299
|
+
settings.element.parentNode.insertBefore(editorContainer, settings.element.nextSibling);
|
|
300
|
+
|
|
301
|
+
let actions = []
|
|
302
|
+
|
|
303
|
+
if (settings.buttons) {
|
|
304
|
+
|
|
305
|
+
if (settings.plugins) {
|
|
306
|
+
console.log(settings.plugins)
|
|
307
|
+
settings.plugins.forEach(function(plugin) {
|
|
308
|
+
defaultActions[plugin.name] = plugin
|
|
309
|
+
})
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
actions = settings.buttons.map(buttonKey => {
|
|
313
|
+
var defaultAction = defaultActions[buttonKey]
|
|
314
|
+
if (defaultAction) {
|
|
315
|
+
return { icon: defaultAction.icon, title: defaultAction.title, key: buttonKey, result: defaultAction.result }
|
|
316
|
+
}
|
|
317
|
+
}).filter(action => action !== undefined)
|
|
318
|
+
} else if (settings.actions) {
|
|
319
|
+
actions = settings.actions.map(action => {
|
|
320
|
+
if (typeof action === 'string') return defaultActions[action]
|
|
321
|
+
else if (action.name === 'custom') return { icon: action.icon, title: action.title, result: action.result }
|
|
322
|
+
else if (defaultActions[action.name]) return { ...defaultActions[action.name], ...action }
|
|
323
|
+
return action
|
|
324
|
+
})
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
actions.forEach(action => {
|
|
328
|
+
var button = createElement('button')
|
|
329
|
+
button.className = 'tex-button'
|
|
330
|
+
button.innerHTML = "<span>"+action.icon+"</span>"
|
|
331
|
+
button.title = action.title
|
|
332
|
+
button.setAttribute('type', 'button')
|
|
333
|
+
button.onclick = () => action.result({action, content, button}) && content.focus()
|
|
334
|
+
|
|
335
|
+
if (action.state) {
|
|
336
|
+
var handler = () => button.classList[action.state() ? 'add' : 'remove']('tex-button-selected')
|
|
337
|
+
addEventListener(content, 'keyup', handler)
|
|
338
|
+
addEventListener(content, 'mouseup', handler)
|
|
339
|
+
addEventListener(button, 'click', handler)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
appendChild(actionbar, button)
|
|
343
|
+
|
|
344
|
+
if(action.key == 'fontSize'){
|
|
345
|
+
selectOptions(button)
|
|
346
|
+
}
|
|
347
|
+
if(action.key=='textColor'){
|
|
348
|
+
colorPicker(button, 'textColor')
|
|
349
|
+
}
|
|
350
|
+
if(action.key=='textBackColor'){
|
|
351
|
+
colorPicker(button, 'textBackColor')
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
if (settings.cssStyle) exec('styleWithCSS')
|
|
357
|
+
exec(paragraphSeparatorString, paragraphSeparator)
|
|
358
|
+
|
|
359
|
+
if (settings.element.tagName.toLowerCase() === 'textarea') {
|
|
360
|
+
settings.element.style.display = 'none'
|
|
361
|
+
} else {
|
|
362
|
+
document.getElementById(settings.element.id).remove()
|
|
363
|
+
editorContainer.id = settings.element.id
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
var htmlContent = createElement('textarea')
|
|
367
|
+
htmlContent.setAttribute('class', 'htmlContent')
|
|
368
|
+
appendChild(editorContainer, htmlContent)
|
|
369
|
+
|
|
370
|
+
htmlContent.value = content.innerHTML
|
|
371
|
+
htmlContent.style.display = 'none'
|
|
372
|
+
|
|
373
|
+
content.oninput = ({ target: { firstChild } }) => {
|
|
374
|
+
if (firstChild && firstChild.nodeType === 3) exec(formatBlock, `<${paragraphSeparator}>`)
|
|
375
|
+
else if (content.innerHTML === '<br>') content.innerHTML = ''
|
|
376
|
+
|
|
377
|
+
settings.onChange(content.innerHTML)
|
|
378
|
+
|
|
379
|
+
if (settings.element.tagName.toLowerCase() === 'textarea') {
|
|
380
|
+
settings.element.value = content.innerHTML
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
htmlContent.value = content.innerHTML
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
htmlContent.oninput = ({ target: { firstChild } }) => {
|
|
387
|
+
content.innerHTML = htmlContent.value
|
|
388
|
+
settings.element.value = content.innerHTML
|
|
389
|
+
settings.onChange(content.innerHTML)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return settings.element
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
var tex = { exec: exec, init: init, destroy: destroy, getContent: getContent, setContent: setContent }
|
|
396
|
+
|
|
397
|
+
exports.exec = exec
|
|
398
|
+
exports.init = init
|
|
399
|
+
exports.destroy = destroy
|
|
400
|
+
exports.getContent = getContent
|
|
401
|
+
exports.setContent = setContent
|
|
402
|
+
exports['default'] = tex
|
|
403
|
+
|
|
404
|
+
Object.defineProperty(exports, '__esModule', { value: true })
|
|
405
|
+
|
|
406
|
+
})))
|