mirror of
https://github.com/webislife/wc-wysiwyg.git
synced 2025-07-27 13:01:23 +00:00
Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
bff5b6befa | |||
8ae8da7c4a | |||
d12b5a55c8 | |||
9e9b0d75fd | |||
fa51c1ba5f | |||
bc2926ce61 | |||
9f597ce45a | |||
26f9927437 | |||
3a9424118c | |||
338e91724e | |||
5c29bb3bb9 | |||
e095392d54 | |||
dc0482166a | |||
92d68cefc8 |
@ -40,6 +40,10 @@ See full demo - [wc-wysiwyg demo](https://webislife.ru/demo/wc-wysiwyg/) list an
|
|||||||
|
|
||||||
✅ Inserting `<video>` element
|
✅ Inserting `<video>` element
|
||||||
|
|
||||||
|
- ✅ Suppoer extensions
|
||||||
|
- Color text and background editor
|
||||||
|
- Emoji table
|
||||||
|
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
@ -77,9 +81,12 @@ npm run build
|
|||||||
</wc-wysiwyg>
|
</wc-wysiwyg>
|
||||||
```
|
```
|
||||||
-->
|
-->
|
||||||
First, include JS and define custom element
|
First need integrate wc-wysiwyg styles, you have 2 way, vanila css in `dist/sass` or scss in `src/sass` just include in your web project
|
||||||
|
|
||||||
|
Second, include JS and define custom element
|
||||||
```javascript
|
```javascript
|
||||||
import('/src/components/wc-wysiwyg.js').then(esm => {
|
import('/src/components/wc-wysiwyg.js').then(esm => {
|
||||||
|
//you can pass any name into define fn
|
||||||
esm.define();
|
esm.define();
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
2
dist/core/el.js
vendored
2
dist/core/el.js
vendored
@ -1 +1 @@
|
|||||||
export const el=(tagName,{classList,styles,props,attrs,options,append}={})=>{if(!tagName)throw new Error(`Undefined tag ${tagName}`);const element=document.createElement(tagName,options);if(classList)for(let i=0;i<classList.length;i++){const styleClass=classList[i];element.classList.add(styleClass)}if(styles){const stylesKeys=Object.keys(styles);for(let i=0;i<stylesKeys.length;i++){const key=stylesKeys[i];element.style[key]=styles[key]}}if(props){const propKeys=Object.keys(props);for(let i=0;i<propKeys.length;i++){const key=propKeys[i];element[key]=props[key]}}if(attrs){const attrsKeys=Object.keys(attrs);for(let i=0;i<attrsKeys.length;i++){const key=attrsKeys[i];attrs[key]&&element.setAttribute(key,attrs[key])}}if(append)for(let i=0;i<append.length;i++){const appendEl=append[i];element.append(appendEl)}return element};
|
export const el=(tagName,{classList,styles,props,attrs,options,append}={})=>{if(!tagName)throw new Error(`Undefined tag ${tagName}`);const element=document.createElement(tagName,options);if(classList)for(let i=0;i<classList.length;i++)classList[i]&&element.classList.add(classList[i]);if(styles&&Object.assign(element.style,styles),props){const propKeys=Object.keys(props);for(let i=0;i<propKeys.length;i++){const key=propKeys[i];element[key]=props[key]}}if(attrs){const attrsKeys=Object.keys(attrs);for(let i=0;i<attrsKeys.length;i++){const key=attrsKeys[i];attrs[key]&&element.setAttribute(key,attrs[key])}}return append&&element.append(...append),element};
|
2
dist/core/translates.js
vendored
2
dist/core/translates.js
vendored
File diff suppressed because one or more lines are too long
12
dist/sass/content.css
vendored
12
dist/sass/content.css
vendored
@ -75,19 +75,10 @@ h5 {
|
|||||||
h6 {
|
h6 {
|
||||||
font-size: 1em; }
|
font-size: 1em; }
|
||||||
|
|
||||||
/* h1[id]::before,
|
|
||||||
h2[id]::before,
|
|
||||||
h3[id]::before,
|
|
||||||
h4[id]::before,
|
|
||||||
h5[id]::before,
|
|
||||||
h6[id]::before {
|
|
||||||
content: '§';
|
|
||||||
color: var(--color-blue-gray-300);
|
|
||||||
margin-right: 0.5em;
|
|
||||||
} */
|
|
||||||
/* del\ins */
|
/* del\ins */
|
||||||
del {
|
del {
|
||||||
color: var(--color-red-900);
|
color: var(--color-red-900);
|
||||||
|
border-bottom: 1px solid var(--color-red-900);
|
||||||
background-color: var(--color-red-50);
|
background-color: var(--color-red-50);
|
||||||
text-decoration: none; }
|
text-decoration: none; }
|
||||||
|
|
||||||
@ -104,6 +95,7 @@ del:before {
|
|||||||
|
|
||||||
ins {
|
ins {
|
||||||
color: var(--color-green-900);
|
color: var(--color-green-900);
|
||||||
|
border-bottom: 1px solid var(--color-green-900);
|
||||||
background-color: var(--color-green-50);
|
background-color: var(--color-green-50);
|
||||||
text-decoration: none; }
|
text-decoration: none; }
|
||||||
|
|
||||||
|
2
dist/wc-wysiwyg.js
vendored
2
dist/wc-wysiwyg.js
vendored
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@webislife/wc-wysiwyg",
|
"name": "@webislife/wc-wysiwyg",
|
||||||
"version": "0.9.2",
|
"version": "1.0.1",
|
||||||
"description": "WYWSIWYG HTML5 Editor written in ts and designed by web-componennt, support all JS frameworks and browsers",
|
"description": "WYWSIWYG HTML5 Editor written in ts and designed by web-componennt, support all JS frameworks and browsers",
|
||||||
"main": "dist/wc-wysiwyg.js",
|
"main": "dist/wc-wysiwyg.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@ -38,4 +38,4 @@
|
|||||||
"@webislife:registry": "https://npm.pkg.github.com"
|
"@webislife:registry": "https://npm.pkg.github.com"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* Short
|
* Short for document.createElement
|
||||||
* @param tagName element tag name
|
* @param tagName element tag name
|
||||||
* @param params list of object params for document.createElements
|
* @param params list of object params for document.createElements
|
||||||
* @returns
|
* @returns HTML\CUSTOMElement
|
||||||
*/
|
*/
|
||||||
export const el = (tagName:keyof HTMLElementTagNameMap|string, {classList, styles, props, attrs, options, append}:{
|
export const el = (tagName:keyof HTMLElementTagNameMap|string, {classList, styles, props, attrs, options, append}:{
|
||||||
classList?: string[],
|
classList?: string[],
|
||||||
@ -21,17 +21,14 @@
|
|||||||
// element.classList
|
// element.classList
|
||||||
if(classList) {
|
if(classList) {
|
||||||
for (let i = 0; i < classList.length; i++) {
|
for (let i = 0; i < classList.length; i++) {
|
||||||
const styleClass = classList[i];
|
if(classList[i]){
|
||||||
element.classList.add(styleClass)
|
element.classList.add(classList[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// element.style[prop]
|
// element.style[prop]
|
||||||
if(styles) {
|
if(styles) {
|
||||||
const stylesKeys = Object.keys(styles);
|
Object.assign(element.style, styles);
|
||||||
for (let i = 0; i < stylesKeys.length; i++) {
|
|
||||||
const key = stylesKeys[i];
|
|
||||||
element.style[key] = styles[key];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// element[prop]
|
// element[prop]
|
||||||
if(props) {
|
if(props) {
|
||||||
@ -51,11 +48,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//append child elements
|
||||||
if(append) {
|
if(append) {
|
||||||
for (let i = 0; i < append.length; i++) {
|
element.append(...append);
|
||||||
const appendEl = append[i];
|
|
||||||
element.append(appendEl);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
};
|
};
|
@ -43,6 +43,7 @@ export const t = {
|
|||||||
minlength: 'Min length is:',
|
minlength: 'Min length is:',
|
||||||
maxlength: 'Max length is:',
|
maxlength: 'Max length is:',
|
||||||
filtertags: 'Found filter tag:',
|
filtertags: 'Found filter tag:',
|
||||||
|
details: 'Spoiler block',
|
||||||
},
|
},
|
||||||
ru: {
|
ru: {
|
||||||
h1:'Заголовок 1 уровня',
|
h1:'Заголовок 1 уровня',
|
||||||
@ -60,7 +61,7 @@ export const t = {
|
|||||||
b:'Жирный',
|
b:'Жирный',
|
||||||
i:'Курсив',
|
i:'Курсив',
|
||||||
u:'Подчеркнутый',
|
u:'Подчеркнутый',
|
||||||
s:'Маленький',
|
s:'Перечеркнутый',
|
||||||
sup:'Надстрочный',
|
sup:'Надстрочный',
|
||||||
sub:'Подстрочный',
|
sub:'Подстрочный',
|
||||||
kbd:'Кнопка',
|
kbd:'Кнопка',
|
||||||
@ -87,5 +88,6 @@ export const t = {
|
|||||||
minlength: 'Минимальная длинна:',
|
minlength: 'Минимальная длинна:',
|
||||||
maxlength: 'Максимальная длинна:',
|
maxlength: 'Максимальная длинна:',
|
||||||
filtertags: 'Найден запрещенный тег:',
|
filtertags: 'Найден запрещенный тег:',
|
||||||
|
details: 'Блок спойлера',
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -90,19 +90,10 @@ h5 {
|
|||||||
h6 {
|
h6 {
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
/* h1[id]::before,
|
|
||||||
h2[id]::before,
|
|
||||||
h3[id]::before,
|
|
||||||
h4[id]::before,
|
|
||||||
h5[id]::before,
|
|
||||||
h6[id]::before {
|
|
||||||
content: '§';
|
|
||||||
color: var(--color-blue-gray-300);
|
|
||||||
margin-right: 0.5em;
|
|
||||||
} */
|
|
||||||
/* del\ins */
|
/* del\ins */
|
||||||
del {
|
del {
|
||||||
color: var(--color-red-900);
|
color: var(--color-red-900);
|
||||||
|
border-bottom: 1px solid var(--color-red-900);
|
||||||
background-color: var(--color-red-50);
|
background-color: var(--color-red-50);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
@ -119,6 +110,7 @@ del:before {
|
|||||||
}
|
}
|
||||||
ins {
|
ins {
|
||||||
color: var(--color-green-900);
|
color: var(--color-green-900);
|
||||||
|
border-bottom: 1px solid var(--color-green-900);
|
||||||
background-color: var(--color-green-50);
|
background-color: var(--color-green-50);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,142 @@
|
|||||||
|
:root {
|
||||||
|
// orange
|
||||||
|
--color-orange-50: #FFF8E1;
|
||||||
|
--color-orange-100: #FFF8E1;
|
||||||
|
--color-orange-500: #FF9800;
|
||||||
|
--color-orange-700: #F57C00;
|
||||||
|
|
||||||
|
--color-green-50: #E8F5E9;
|
||||||
|
--color-green-100: #C8E6C9;
|
||||||
|
--color-green-300: #AED581;
|
||||||
|
--color-green-500: #4CAF50;
|
||||||
|
--color-green-900: #1B5E20;
|
||||||
|
|
||||||
|
--color-red-50: #FFEBEE;
|
||||||
|
--color-red-100: #FFCDD2;
|
||||||
|
--color-red-200: #FF8A65;
|
||||||
|
--color-red-500: #F44336;
|
||||||
|
--color-red-900: #B71C1C;
|
||||||
|
|
||||||
|
--color-amber-50: #FFF8E1;
|
||||||
|
--color-amber-100: #FFE0B2;
|
||||||
|
--color-amber-900: #FF6F00;
|
||||||
|
|
||||||
|
--color-indigo-100: #C5CAE9;
|
||||||
|
|
||||||
|
// orange
|
||||||
|
--color-deep-orange-50: #FBE9E7;
|
||||||
|
--color-deep-orange-900: #BF360C;
|
||||||
|
|
||||||
|
// lime
|
||||||
|
--color-lime-50: #F9FBE7;
|
||||||
|
|
||||||
|
// grey
|
||||||
|
--color-grey-50: #FAFAFA;
|
||||||
|
--color-grey-100: #F5F5F5;
|
||||||
|
--color-grey-200: #EEEEEE;
|
||||||
|
--color-grey-300: #E0E0E0;
|
||||||
|
--color-grey-400: #BDBDBD;
|
||||||
|
--color-grey-500: #9E9E9E;
|
||||||
|
--color-grey-600: #757575;
|
||||||
|
--color-grey-700: #757575;
|
||||||
|
--color-grey-800: #424242;
|
||||||
|
--color-grey-900: #212121;
|
||||||
|
|
||||||
|
// blue
|
||||||
|
--color-blue-50: #E3F2FD;
|
||||||
|
--color-blue-100: #BBDEFB;
|
||||||
|
--color-blue-200: #90CAF9;
|
||||||
|
--color-blue-300: #64B5F6;
|
||||||
|
--color-blue-400: #42A5F5;
|
||||||
|
--color-blue-500: #2196F3;
|
||||||
|
--color-blue-800: #1565C0;
|
||||||
|
--color-blue-900: #0D47A1;
|
||||||
|
|
||||||
|
// blue-gray
|
||||||
|
--color-blue-gray-50: #ECEFF1;
|
||||||
|
--color-blue-gray-100: #CFD8DC;
|
||||||
|
--color-blue-gray-200: #B0BEC5;
|
||||||
|
--color-blue-gray-300: #90A4AE;
|
||||||
|
--color-blue-gray-400: #78909C;
|
||||||
|
--color-blue-gray-700: #455A64;
|
||||||
|
--color-blue-gray-800: #37474F;
|
||||||
|
--color-blue-gray-900: #263238;
|
||||||
|
// blue-light
|
||||||
|
--color-blue-light-50: #E1F5FE;
|
||||||
|
--color-blue-light-100: #B3E5FC;
|
||||||
|
|
||||||
|
--wc-wysiwyg-light: #fff;
|
||||||
|
--wc-wysiwyg-dark: #37474F;
|
||||||
|
}
|
||||||
|
wc-wysiwyg.-word .wc-wysiwyg_pf > label {
|
||||||
|
background-color: var(--color-blue-500);
|
||||||
|
}
|
||||||
|
wc-wysiwyg.-word .wc-wysiwyg_pf {
|
||||||
|
background-color: var(--color-blue-400);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
wc-wysiwyg.-word .wc-wysiwyg_content {
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
wc-wysiwyg.-word .wc-wysiwyg_bt {
|
||||||
|
background-color: var(--color-blue-300);
|
||||||
|
padding:5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
wc-wysiwyg.-word .wc-wysiwyg_ec {
|
||||||
|
background-color: var(--wc-wysiwyg-light);
|
||||||
|
padding:5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
margin: 5px 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
.wc-wysiwyg {
|
.wc-wysiwyg {
|
||||||
background-color: #eee;
|
background-color: var(--wc-wysiwyg-light);
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
position: relative;
|
position: relative;
|
||||||
border: 1px solid var(--color-blue-gray-400);
|
padding:5px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
display: block;
|
display: block;
|
||||||
|
&_dialog {
|
||||||
|
border-radius: 10px;
|
||||||
|
border:none;
|
||||||
|
outline: none;
|
||||||
|
&::backdrop {
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
&.-modal {
|
||||||
|
min-width: 90vw;
|
||||||
|
min-height: 90vh;
|
||||||
|
max-height: 90vh;
|
||||||
|
max-height: 90vh;
|
||||||
|
}
|
||||||
|
&.-colors {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
max-width: 300px;
|
||||||
|
&:not([open]) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
& fieldset.-palette {
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
max-width: 20px;
|
||||||
|
& > button {
|
||||||
|
flex: 1;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
&_bt {
|
&_bt {
|
||||||
display: block;
|
display: block;
|
||||||
padding:5px;
|
padding:5px;
|
||||||
@ -27,31 +158,23 @@
|
|||||||
|
|
||||||
&_ce {
|
&_ce {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0;
|
margin-bottom: 5px;
|
||||||
padding: 7px;
|
padding: 5px;
|
||||||
border: 1px solid var(--color-blue-500);
|
border-radius: 5px;
|
||||||
border-radius: 3px;
|
border: none;
|
||||||
background-color: var(--color-blue-50);
|
background: rgba(0,0,0,0.2);
|
||||||
}
|
&:before {
|
||||||
&_ce:before {
|
content: 'HTML5 custom-elements';
|
||||||
content: 'HTML5 custom-elements';
|
color: #fff;
|
||||||
color: #fff;
|
background-color: var(--color-blue-500);
|
||||||
background-color: var(--color-blue-500);
|
position: absolute;
|
||||||
position: absolute;
|
font-size: 10px;
|
||||||
font-size: 10px;
|
line-height: 0.6em;
|
||||||
line-height: 0.6em;
|
padding: 3px;
|
||||||
padding: 3px;
|
border-radius: 3px;
|
||||||
border-radius: 3px;
|
transform: translate(0, -50%);
|
||||||
transform: translate(0, -50%);
|
top:0;
|
||||||
top:0;
|
left:0;
|
||||||
left:0;
|
|
||||||
}
|
|
||||||
&_ce > button {
|
|
||||||
border-color: var(--color-blue-500);
|
|
||||||
background-color: var(--color-blue-100);
|
|
||||||
&:hover {
|
|
||||||
border-color: var(--color-blue-900);
|
|
||||||
background-color: var(--color-blue-200);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* preview */
|
/* preview */
|
||||||
@ -62,7 +185,7 @@
|
|||||||
min-height: 200px;
|
min-height: 200px;
|
||||||
}
|
}
|
||||||
&_content {
|
&_content {
|
||||||
padding:5px 5px 2em 5px;
|
padding:0.5em;
|
||||||
border:1px solid #ccc;
|
border:1px solid #ccc;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
@ -72,7 +195,7 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: inline-block;
|
display: block;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
& .-selected {
|
& .-selected {
|
||||||
background-color: var(--color-blue-100);
|
background-color: var(--color-blue-100);
|
||||||
@ -86,52 +209,237 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&_ec {
|
&_ec {
|
||||||
background: var(--color-blue-gray-100);
|
background: #2b393f;
|
||||||
padding: 0.5em 0.25em 0.25em 0.25em;
|
padding: 5px;
|
||||||
border-radius: 3px;
|
border-radius: 5px;
|
||||||
border: 1px solid;
|
margin-bottom: 10px;
|
||||||
border-color: var(--color-blue-gray-100);
|
top:0;
|
||||||
}
|
position: sticky;
|
||||||
&_ec:focus-within {
|
z-index: 2;
|
||||||
border-color: var(--color-blue-500);
|
|
||||||
}
|
}
|
||||||
&_btn {
|
&_btn {
|
||||||
background: var(--color-blue-gray-50);
|
min-width: 30px;
|
||||||
outline: none;
|
line-height: 20px;
|
||||||
padding: 3px 6px;
|
background-color: var(--wc-wysiwyg-light);
|
||||||
border-radius: 3px;
|
box-shadow: 1px 2px 5px rgba(0,0,0,0.3);
|
||||||
border: 1px solid var(--color-blue-gray-200);
|
border:0;
|
||||||
border-bottom: 3px solid var(--color-blue-gray-200);
|
border-radius: 5px;
|
||||||
color: #333;
|
padding:2px 5px;
|
||||||
min-width: 0px;
|
margin-right:5px;
|
||||||
text-align: center;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: inline-block;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 0.85em;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1;
|
|
||||||
white-space: nowrap;
|
|
||||||
margin-right: 5px;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--color-blue-gray-100);
|
box-shadow: 0 2px 5px rgba(0, 110, 253, 0.9);
|
||||||
border-color: var(--color-blue-gray-300);
|
|
||||||
}
|
}
|
||||||
&:focus {
|
&:focus {
|
||||||
border-color: var(--color-blue-500);
|
box-shadow: 0 2px 5px rgba(0, 110, 253, 0.9);
|
||||||
}
|
}
|
||||||
&:active {
|
&:active {
|
||||||
padding-top:2px;
|
box-shadow: 0 2px 5px rgba(1, 181, 52, 0.9);
|
||||||
background: var(--color-blue-gray-100);
|
|
||||||
border-color: var(--color-blue-gray-700);
|
|
||||||
border-bottom: 1px solid;
|
|
||||||
}
|
}
|
||||||
&.-clear {
|
&.-clear {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
&.-emoji {
|
||||||
|
border:none;
|
||||||
|
font-size: 20px;
|
||||||
|
min-width: 32px;
|
||||||
|
min-height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding:0;
|
||||||
|
border-radius: 1em;
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
&.-color {
|
||||||
|
min-width: 20px;
|
||||||
|
min-height: 20px;
|
||||||
|
border-color: rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
&.-prevcolor {
|
||||||
|
// background-color: transparent;
|
||||||
|
// border: 1px solid #ccc;
|
||||||
|
&::before {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: var(--colorer);
|
||||||
|
width: 12px;
|
||||||
|
content: '';
|
||||||
|
height: 12px;
|
||||||
|
margin-right: 5px;
|
||||||
|
border-radius: 2px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.-b {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
&.-i {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
&.-u {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
&.-s {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
&.-sub {
|
||||||
|
vertical-align: sub;
|
||||||
|
font-size: 0.5em;
|
||||||
|
}
|
||||||
|
&.-del::before {
|
||||||
|
content: '- ';
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
&.-h1::before {
|
||||||
|
content: '§ ';
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
&.-del {
|
||||||
|
color: var(--color-red-900);
|
||||||
|
border-bottom: 1px solid var(--color-red-900);
|
||||||
|
background-color: var(--color-red-50);
|
||||||
|
}
|
||||||
|
&.-a::before {
|
||||||
|
content: "🔗 ";
|
||||||
|
}
|
||||||
|
&.-ul::before {
|
||||||
|
content: "● ";
|
||||||
|
}
|
||||||
|
&.-ol::before {
|
||||||
|
content: "1. ";
|
||||||
|
}
|
||||||
|
&.-var::before {
|
||||||
|
content: "∫ ";
|
||||||
|
}
|
||||||
|
&.-var {
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
&.-details:before {
|
||||||
|
content: "→";
|
||||||
|
}
|
||||||
|
&.-details {
|
||||||
|
text-decoration: dotted;
|
||||||
|
border: 1px dashed var(--color-blue-gray-200);
|
||||||
|
}
|
||||||
|
&.-pre::before {
|
||||||
|
content: "...";
|
||||||
|
display: inline-block;
|
||||||
|
background-color: var(--color-blue-gray-100);
|
||||||
|
color: var(--color-blue-grey-900);
|
||||||
|
border-radius: 2px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
padding: 0 3px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.8em;
|
||||||
|
line-height: 10px;
|
||||||
|
}
|
||||||
|
&.-pre {
|
||||||
|
background-color: var(--color-blue-gray-50);
|
||||||
|
color: var(--color-blue-grey-900);
|
||||||
|
padding-left: 15px;
|
||||||
|
}
|
||||||
|
&.-ins::before {
|
||||||
|
content: '+ ';
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
&.-ins {
|
||||||
|
color: var(--color-green-900);
|
||||||
|
border-bottom: 1px solid var(--color-green-900);
|
||||||
|
background-color: var(--color-green-50);
|
||||||
|
}
|
||||||
|
&.-sup {
|
||||||
|
vertical-align: super;
|
||||||
|
font-size: 0.5em;
|
||||||
|
}
|
||||||
|
&.-q:before {
|
||||||
|
content: open-quote;
|
||||||
|
}
|
||||||
|
&.-samp:before {
|
||||||
|
content: '> ';
|
||||||
|
color: var(--color-blue-gray-300);
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
&.-samp {
|
||||||
|
background-color: var(--color-blue-gray-50);
|
||||||
|
border-bottom: 1px solid var(--color-blue-gray-300);
|
||||||
|
}
|
||||||
|
&.-blockquote::before {
|
||||||
|
content: '❝';
|
||||||
|
font-size: 1em;
|
||||||
|
color: #F57F17;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 4px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
&.-blockquote {
|
||||||
|
background-color: var(--color-amber-50);
|
||||||
|
color: #412207;
|
||||||
|
padding-left: 15px;
|
||||||
|
border-left: 2px solid #F57F17;
|
||||||
|
}
|
||||||
|
&.-time:before {
|
||||||
|
content: "📅 ";
|
||||||
|
}
|
||||||
|
&.-img:before {
|
||||||
|
content: "🌅 ";
|
||||||
|
}
|
||||||
|
&.-video:before {
|
||||||
|
content: "🎦 ";
|
||||||
|
}
|
||||||
|
&.-audio:before {
|
||||||
|
content: "🎵 ";
|
||||||
|
}
|
||||||
|
&.-details:before {
|
||||||
|
content: "▸ ";
|
||||||
|
}
|
||||||
|
&.-code {
|
||||||
|
content: "<code>";
|
||||||
|
background-color: var(--color-blue-gray-50);
|
||||||
|
color: var(--color-blue-grey-900);
|
||||||
|
}
|
||||||
|
&.-strong {
|
||||||
|
background-color: var(--color-deep-orange-50);
|
||||||
|
color: var(--color-deep-orange-900);
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
&.-abbr {
|
||||||
|
color: #1A237E;
|
||||||
|
}
|
||||||
|
&.-q {
|
||||||
|
color:#000;
|
||||||
|
background-color: #FFF8E1;
|
||||||
|
}
|
||||||
|
&.-small {
|
||||||
|
font-size: 0.5em;
|
||||||
|
}
|
||||||
|
&.-dfn {
|
||||||
|
color: var(--color-blue-900);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
&.-mark {
|
||||||
|
background-color: var(--color-lime-100);
|
||||||
|
color: var(--color-lime-900);
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-lime-200);
|
||||||
|
color: var(--color-lime-900);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.-kbd {
|
||||||
|
background: var(--color-blue-gray-50);
|
||||||
|
outline: none;
|
||||||
|
padding: 3px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid var(--color-blue-gray-200);
|
||||||
|
border-bottom: 3px solid var(--color-blue-gray-200);
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&_ia {
|
&_ia {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -148,11 +456,10 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
bottom:0;
|
bottom:0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: var(--color-blue-gray-50);
|
|
||||||
padding:3px;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid var(--color-blue-gray-300);
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
background-color: var(--wc-wysiwyg-light);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px;
|
||||||
& > form {
|
& > form {
|
||||||
&:nth-child(1n+2) {
|
&:nth-child(1n+2) {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
@ -184,7 +491,8 @@
|
|||||||
background-color: var(--color-blue-200);
|
background-color: var(--color-blue-200);
|
||||||
color: var(--color-blue-gray-800);
|
color: var(--color-blue-gray-800);
|
||||||
padding: 3px 3px 3px 5px;
|
padding: 3px 3px 3px 5px;
|
||||||
display: flex;
|
margin: 5px 0;
|
||||||
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
@ -239,4 +547,23 @@
|
|||||||
& .-display-none {
|
& .-display-none {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
.wc-wysiwyg {
|
||||||
|
background-color: var(--wc-wysiwyg-dark);
|
||||||
|
&_di {
|
||||||
|
background-color: var(--wc-wysiwyg-dark);
|
||||||
|
}
|
||||||
|
&_bt {
|
||||||
|
background-color: var(--wc-wysiwyg-dark);
|
||||||
|
}
|
||||||
|
&_btn {
|
||||||
|
background-color: rgba(0,0,0,0.5);
|
||||||
|
color: #ccc;
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0,0,0,0.5);
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,18 +1,14 @@
|
|||||||
import {t} from './core/translates.js';
|
import { t } from './core/translates.js';
|
||||||
import { el } from "./core/el.js";
|
import { el } from "./core/el.js";
|
||||||
|
import test from './extensions/colorerDialog.js';
|
||||||
interface WCWYSIWYGTag {
|
import test2 from './extensions/presetList.js';
|
||||||
tag:string
|
/**
|
||||||
method?:Function,
|
* Translate function
|
||||||
hint?:string,
|
* @param key:string phrase key
|
||||||
is?: string,
|
* @param lang:string language key defaul navigatol.language
|
||||||
}
|
* @returns
|
||||||
interface WCWYSIWYGActions {
|
*/
|
||||||
wrapTag: Function,
|
const _t = (key:string, lang = navigator.language):string => t[lang] ? t[lang][key] || "-" : t["en"][key];
|
||||||
insertImageBlock: Function,
|
|
||||||
insertAudio: Function,
|
|
||||||
insertVideo: Function,
|
|
||||||
}
|
|
||||||
|
|
||||||
//All semantic html5 known editor tags
|
//All semantic html5 known editor tags
|
||||||
const allTags = [
|
const allTags = [
|
||||||
@ -50,14 +46,32 @@ const allTags = [
|
|||||||
{ tag: 'audio'},
|
{ tag: 'audio'},
|
||||||
{ tag: 'video'},
|
{ tag: 'video'},
|
||||||
{ tag: 'blockquote'},
|
{ tag: 'blockquote'},
|
||||||
|
{ tag: 'details'},
|
||||||
] as WCWYSIWYGTag[];
|
] as WCWYSIWYGTag[];
|
||||||
|
/**
|
||||||
|
* Base class for WCWYSIWYG web component
|
||||||
|
*/
|
||||||
class WCWYSIWYG extends HTMLElement {
|
class WCWYSIWYG extends HTMLElement {
|
||||||
|
/**
|
||||||
|
* Tags included to action bar in editor
|
||||||
|
*/
|
||||||
public EditorTags:WCWYSIWYGTag[]
|
public EditorTags:WCWYSIWYGTag[]
|
||||||
|
/**
|
||||||
|
* Custom tags included to action bar in editor
|
||||||
|
*/
|
||||||
public EditorCustomTags:WCWYSIWYGTag[]
|
public EditorCustomTags:WCWYSIWYGTag[]
|
||||||
//Content editable wc-editor element
|
/**
|
||||||
|
* Main ContentEditable element
|
||||||
|
*/
|
||||||
public EditorNode:HTMLElement
|
public EditorNode:HTMLElement
|
||||||
|
/**
|
||||||
|
* Editor actions block elements with EditorTags buttons
|
||||||
|
*/
|
||||||
public EditorActionsSection:HTMLElement
|
public EditorActionsSection:HTMLElement
|
||||||
|
/**
|
||||||
|
* Custom web-components buttons <fieldset>
|
||||||
|
*/
|
||||||
|
public EditorCustomTagsForm?:HTMLElement
|
||||||
//Inline edites
|
//Inline edites
|
||||||
public EditorInlineActions:any[]
|
public EditorInlineActions:any[]
|
||||||
public EditorInlineDialog:HTMLDialogElement
|
public EditorInlineDialog:HTMLDialogElement
|
||||||
@ -65,7 +79,7 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
//Editor props
|
//Editor props
|
||||||
public EditorPropertyForm?:HTMLElement
|
public EditorPropertyForm?:HTMLElement
|
||||||
//Clear btn
|
//Clear btn
|
||||||
public EditorClearFormatBtn:HTMLElement
|
public EditorClearFormatBtn:HTMLButtonElement
|
||||||
//Autocomplete area
|
//Autocomplete area
|
||||||
public EditorAutoCompleteForm?:HTMLElement
|
public EditorAutoCompleteForm?:HTMLElement
|
||||||
//Bottom actions
|
//Bottom actions
|
||||||
@ -74,7 +88,6 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
public EditorBottomFormViewToggle?:HTMLElement
|
public EditorBottomFormViewToggle?:HTMLElement
|
||||||
|
|
||||||
public EditorPreviewText:HTMLTextAreaElement
|
public EditorPreviewText:HTMLTextAreaElement
|
||||||
public EditorCustomTagsForm?:HTMLElement
|
|
||||||
public EditorTagsMethods:WCWYSIWYGActions
|
public EditorTagsMethods:WCWYSIWYGActions
|
||||||
public EditorAllowTags:string[]
|
public EditorAllowTags:string[]
|
||||||
public EditorFullScreenButton?:HTMLElement
|
public EditorFullScreenButton?:HTMLElement
|
||||||
@ -85,19 +98,21 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
|
|
||||||
#EditProps:boolean|object
|
#EditProps:boolean|object
|
||||||
#Autocomplete:boolean
|
#Autocomplete:boolean
|
||||||
|
#Extensions?: any[]
|
||||||
#SotrageKey:string|null
|
#SotrageKey:string|null
|
||||||
#HideBottomActions:boolean
|
#HideBottomActions:boolean
|
||||||
#Connected:boolean = false;
|
#Connected:boolean = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
this.#checkExtensions();
|
||||||
this.classList.add('wc-wysiwyg');
|
this.classList.add('wc-wysiwyg');
|
||||||
|
|
||||||
//Listen root element events
|
//Listen root element events
|
||||||
this.onpointerup = (event) => {
|
this.onpointerup = (event) => {
|
||||||
const selection = window.getSelection();
|
const editorSelection = this.getSelection();
|
||||||
//if check exist selection string
|
//if check exist selection string
|
||||||
if(selection !== null && selection.toString().length > 0) {
|
if(editorSelection.selection !== null && editorSelection.selection.text !== null) {
|
||||||
this.EditorInlineActionsForm.style.display = '';
|
this.EditorInlineActionsForm.style.display = '';
|
||||||
if(this.EditorPropertyForm){
|
if(this.EditorPropertyForm){
|
||||||
this.EditorPropertyForm.style.display = 'none';
|
this.EditorPropertyForm.style.display = 'none';
|
||||||
@ -115,6 +130,25 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
if(this.#Connected === false) {
|
if(this.#Connected === false) {
|
||||||
|
const asyncExtensions = this.getAttribute('data-async-extensions') || false;
|
||||||
|
if(asyncExtensions !== false) {
|
||||||
|
const asyncExtensionsPaths = asyncExtensions.split(',');
|
||||||
|
console.log('need load', asyncExtensionsPaths);
|
||||||
|
asyncExtensionsPaths.forEach((extensionPath:string) => {
|
||||||
|
import(extensionPath).then(esm => {
|
||||||
|
console.log('asyncExtensionsPaths loaded', esm);
|
||||||
|
const Extension = new esm.default(this);
|
||||||
|
if(typeof Extension.connectedCallback === 'function') {
|
||||||
|
Extension.connectedCallback();
|
||||||
|
}
|
||||||
|
if(this.#Extensions instanceof Array) {
|
||||||
|
this.#Extensions.push(Extension);
|
||||||
|
} else {
|
||||||
|
this.#Extensions = [Extension];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
//Check Tags
|
//Check Tags
|
||||||
const allowTags = this.getAttribute('data-allow-tags') || allTags.map(t => t.tag).join(',');
|
const allowTags = this.getAttribute('data-allow-tags') || allTags.map(t => t.tag).join(',');
|
||||||
|
|
||||||
@ -133,41 +167,43 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
this.#EditProps = this.getAttribute('data-edit-props') !== null ? JSON.parse(this.getAttribute('data-edit-props') || '') : false;
|
this.#EditProps = this.getAttribute('data-edit-props') !== null ? JSON.parse(this.getAttribute('data-edit-props') || '') : false;
|
||||||
this.#Autocomplete = this.getAttribute('data-autocomplete') === '1';
|
this.#Autocomplete = this.getAttribute('data-autocomplete') === '1';
|
||||||
this.#HideBottomActions = this.getAttribute('data-hide-bottom-actions') === '1';
|
this.#HideBottomActions = this.getAttribute('data-hide-bottom-actions') === '1';
|
||||||
|
|
||||||
//allow inline without ['video','audio','img']
|
//allow inline without ['video','audio','img']
|
||||||
this.EditorInlineActions = this.EditorTags.filter(action => ['video','audio','img'].includes(action.tag) === false);
|
this.EditorInlineActions = this.EditorTags.filter(action => ['video','audio','img'].includes(action.tag) === false);
|
||||||
|
|
||||||
//Check local storage key
|
|
||||||
this.#SotrageKey = this.getAttribute('data-storage');
|
|
||||||
if(this.#SotrageKey) {
|
|
||||||
let storeValue = window.localStorage.getItem(this.#SotrageKey);
|
|
||||||
if(storeValue) {
|
|
||||||
this.value = storeValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.EditorActionsSection = el('section', { classList: ['wc-wysiwyg_ec'] });
|
|
||||||
//Clear format button
|
//Clear format button
|
||||||
this.EditorClearFormatBtn = el('button', {
|
this.EditorClearFormatBtn = el('button', {
|
||||||
classList: ['wc-wysiwyg_btn', '-clear'],
|
classList: ['wc-wysiwyg_btn', '-clear'],
|
||||||
attrs: {
|
attrs: {
|
||||||
'data-hint': this.#t('clearFormat'),
|
'data-hint': _t('clearFormat'),
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
innerHTML:'Ⱦ',
|
innerHTML:'Ⱦ',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
//Inline selection actions panel
|
//Top editor actions sections
|
||||||
this.EditorInlineActionsForm = el('form');
|
this.EditorActionsSection = el('section', {
|
||||||
this.EditorInlineDialog = el('dialog', {
|
classList: ['wc-wysiwyg_ec'],
|
||||||
classList: ['wc-wysiwyg_di'],
|
|
||||||
append: [this.EditorInlineActionsForm, this.EditorClearFormatBtn],
|
|
||||||
props: {
|
|
||||||
//prevent submit
|
|
||||||
onsubmit: event => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
this.#makeActionButtons(this.EditorActionsSection, this.EditorTags);
|
||||||
|
this.EditorActionsSection.append(this.EditorClearFormatBtn);
|
||||||
|
|
||||||
|
//Inline selection actions panel
|
||||||
|
// this.EditorInlineActionsForm = el('form');
|
||||||
|
// this.EditorInlineDialog = el('dialog', {
|
||||||
|
// classList: ['wc-wysiwyg_di'],
|
||||||
|
// append: [this.EditorInlineActionsForm],
|
||||||
|
// props: {
|
||||||
|
// //prevent submit
|
||||||
|
// onsubmit: event => {
|
||||||
|
// event.preventDefault();
|
||||||
|
// event.stopPropagation();
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// attrs: {
|
||||||
|
// tabIndex: -1,
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
//Edit props
|
//Edit props
|
||||||
if(this.#EditProps) {
|
if(this.#EditProps) {
|
||||||
@ -178,12 +214,12 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
props: {
|
props: {
|
||||||
onsubmit: event => {
|
onsubmit: event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.hideEditorInlineDialog();
|
this.EditorPropertyForm.style.display = 'none';
|
||||||
},
|
},
|
||||||
onpointerup: event => event.stopPropagation(),
|
onpointerup: event => event.stopPropagation(),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.EditorInlineDialog.append(this.EditorPropertyForm);
|
this.EditorActionsSection.append(this.EditorPropertyForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Autocomplete form
|
//Autocomplete form
|
||||||
@ -208,9 +244,7 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Actions in footer
|
//Actions in footer
|
||||||
this.EditorBottomForm = el('fieldset', {
|
this.EditorBottomForm = el('fieldset', { classList: ['wc-wysiwyg_bt'] });
|
||||||
classList: ['wc-wysiwyg_bt'],
|
|
||||||
});
|
|
||||||
|
|
||||||
//Check custom tags
|
//Check custom tags
|
||||||
this.EditorCustomTags = JSON.parse( String(this.getAttribute('data-custom-tags')) );
|
this.EditorCustomTags = JSON.parse( String(this.getAttribute('data-custom-tags')) );
|
||||||
@ -218,11 +252,11 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
//Custom panel tags
|
//Custom panel tags
|
||||||
this.EditorCustomTagsForm = el('fieldset', {
|
this.EditorCustomTagsForm = el('fieldset', {
|
||||||
classList: ['wc-wysiwyg_ce'],
|
classList: ['wc-wysiwyg_ce'],
|
||||||
});
|
}) as HTMLElement;
|
||||||
//Make custom actions buttons panel
|
//Make custom actions buttons panel
|
||||||
this.#makeActionButtons(this.EditorCustomTagsForm as HTMLElement, this.EditorCustomTags);
|
this.#makeActionButtons(this.EditorCustomTagsForm, this.EditorCustomTags);
|
||||||
|
|
||||||
this.appendChild(this.EditorCustomTagsForm as HTMLElement);
|
this.EditorActionsSection.prepend(this.EditorCustomTagsForm);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Node editable
|
//Node editable
|
||||||
@ -230,72 +264,48 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
classList: ['wc-wysiwyg_content', this.getAttribute('data-content-class') || ''],
|
classList: ['wc-wysiwyg_content', this.getAttribute('data-content-class') || ''],
|
||||||
props: {
|
props: {
|
||||||
contentEditable: true,
|
contentEditable: true,
|
||||||
|
//Pointer event behaviors
|
||||||
onpointerup: event => {
|
onpointerup: event => {
|
||||||
this.checkCanClearElement(event);
|
this.#checkCanClearElement(event);
|
||||||
if(this.#EditProps) {
|
if(this.#EditProps) {
|
||||||
this.checkEditProps(event);
|
this.#checkEditProps(event);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
//Update content on input event
|
||||||
oninput: event => {
|
oninput: event => {
|
||||||
this.updateContent();
|
this.updateContent();
|
||||||
if(this.#Autocomplete) {
|
if(this.#Autocomplete) {
|
||||||
this.checkAutoComplete();
|
this.#checkAutoComplete();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
//Handle key bindings
|
//Check hot keys is pressed
|
||||||
onkeydown: event => {
|
onkeydown: event => {
|
||||||
//check hold alt
|
this.#checkKeyBindings(event);
|
||||||
if(event.altKey) {
|
},
|
||||||
//alt+space - move caret to parent node next sibling
|
onpaste: (event:ClipboardEvent) => {
|
||||||
if(event.code === 'Space') {
|
// if(event.type === 'paste') {
|
||||||
const Selection = window.getSelection();
|
// window.navigator.clipboard.readText().then(text => {
|
||||||
if(Selection?.type === 'Caret') {
|
// const target = event.target as HTMLElement;
|
||||||
//insertAdjacentElement dont support textNodes, first insert span
|
// //Check isnert html symbol
|
||||||
const span = el('span');
|
// if(text.startsWith('&#') === false) {
|
||||||
Selection?.anchorNode?.parentElement?.insertAdjacentElement('afterend', span)
|
// if(target.tagName === 'BR') {
|
||||||
//after replace span with textnode and select it
|
// target.insertAdjacentText('afterend',text);
|
||||||
const textN = document.createTextNode(' ');
|
// }
|
||||||
span.replaceWith(textN);
|
// this.updateContent();
|
||||||
const range = document.createRange();
|
// event.preventDefault();
|
||||||
range.selectNodeContents(textN);
|
// event.stopPropagation();
|
||||||
Selection.removeAllRanges();
|
|
||||||
Selection.addRange(range);
|
// return false;
|
||||||
}
|
// }
|
||||||
}
|
// });
|
||||||
}
|
// }
|
||||||
//tag - hide editor dialog
|
|
||||||
if(event.code === 'Escape') {
|
|
||||||
this.hideEditorInlineDialog();
|
|
||||||
}
|
|
||||||
//enter - set p as default tag in newline
|
|
||||||
if(event.code === 'Enter' && event.shiftKey === false) {
|
|
||||||
const Selection = window.getSelection();
|
|
||||||
let tagName = 'p';
|
|
||||||
//tags with return default browser behavior
|
|
||||||
if(['LI', 'ARTICLE', 'P'].includes(Selection.anchorNode.parentElement.tagName)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const p = el(tagName, { props: { innerHTML: ` ` } });
|
|
||||||
Selection?.anchorNode?.parentElement?.insertAdjacentElement('afterend', p);
|
|
||||||
const range = document.createRange();
|
|
||||||
range.selectNodeContents(p);
|
|
||||||
Selection?.removeAllRanges();
|
|
||||||
Selection?.addRange(range);
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//Make action buttons
|
|
||||||
this.#makeActionButtons(this.EditorActionsSection, this.EditorTags);
|
|
||||||
this.#makeActionButtons(this.EditorInlineActionsForm, this.EditorInlineActions);
|
|
||||||
|
|
||||||
//Inser wc-editor after textarea node
|
//Inser wc-editor after textarea node
|
||||||
this.append(
|
this.append(
|
||||||
this.EditorActionsSection,
|
this.EditorActionsSection,
|
||||||
this.EditorInlineDialog,
|
|
||||||
this.EditorNode,
|
this.EditorNode,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -304,7 +314,7 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
this.EditorBottomFormViewToggle = el('button', {
|
this.EditorBottomFormViewToggle = el('button', {
|
||||||
classList: ['wc-wysiwyg_btn'],
|
classList: ['wc-wysiwyg_btn'],
|
||||||
attrs: {
|
attrs: {
|
||||||
'data-hint': this.#t('toggleViewMode'),
|
'data-hint': _t('toggleViewMode'),
|
||||||
'data-mode': 'html5',
|
'data-mode': 'html5',
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
@ -326,11 +336,11 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
this.EditorBottomFormNewP = el('button', {
|
this.EditorBottomFormNewP = el('button', {
|
||||||
classList: ['wc-wysiwyg_btn'],
|
classList: ['wc-wysiwyg_btn'],
|
||||||
attrs: {
|
attrs: {
|
||||||
'data-hint': this.#t('addNewParahraph'),
|
'data-hint': _t('addNewParahraph'),
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
type:'button',
|
type:'button',
|
||||||
innerText: '+ P',
|
innerText: '+ ❡',
|
||||||
onpointerup: event => {
|
onpointerup: event => {
|
||||||
const P = el('p', {props: {innerText: '/'}});
|
const P = el('p', {props: {innerText: '/'}});
|
||||||
this.EditorNode.appendChild(P);
|
this.EditorNode.appendChild(P);
|
||||||
@ -342,7 +352,7 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
this.EditorFullScreenButton = el('button', {
|
this.EditorFullScreenButton = el('button', {
|
||||||
classList: ['wc-wysiwyg_btn'],
|
classList: ['wc-wysiwyg_btn'],
|
||||||
attrs: {
|
attrs: {
|
||||||
'data-hint': this.#t('fullScreen'),
|
'data-hint': _t('fullScreen'),
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
type: "button",
|
type: "button",
|
||||||
@ -362,6 +372,18 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
this.append(this.EditorBottomForm);
|
this.append(this.EditorBottomForm);
|
||||||
}
|
}
|
||||||
this.EditorNode.innerHTML = this.EditorPreviewText.value;
|
this.EditorNode.innerHTML = this.EditorPreviewText.value;
|
||||||
|
//Check local storage key
|
||||||
|
this.#SotrageKey = this.getAttribute('data-storage');
|
||||||
|
if(this.#SotrageKey) {
|
||||||
|
let storeValue = window.localStorage.getItem(this.#SotrageKey);
|
||||||
|
if(storeValue) {
|
||||||
|
this.EditorNode.innerHTML = storeValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Check and call connectedCallback in extensions
|
||||||
|
if(this.#Extensions instanceof Array) {
|
||||||
|
this.#Extensions.forEach(extension => typeof extension.connectedCallback === 'function' ? extension.connectedCallback(this) : false);
|
||||||
|
}
|
||||||
this.updateContent();
|
this.updateContent();
|
||||||
|
|
||||||
this.#Connected = true;
|
this.#Connected = true;
|
||||||
@ -372,14 +394,20 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
* Update content value and update behaviors
|
* Update content value and update behaviors
|
||||||
*/
|
*/
|
||||||
updateContent() {
|
updateContent() {
|
||||||
|
// Update the value of the editor node to the current innerHTML
|
||||||
this.value = this.EditorNode.innerHTML;
|
this.value = this.EditorNode.innerHTML;
|
||||||
|
// Update the value of the EditorPreviewText to the current value
|
||||||
this.EditorPreviewText.value = this.value;
|
this.EditorPreviewText.value = this.value;
|
||||||
|
// Check the validity of the current value
|
||||||
this.checkValidity();
|
this.checkValidity();
|
||||||
|
// Dispatch an event to indicate that the input has been changed
|
||||||
|
this.dispatchEvent(new Event('oninput', { bubbles: true, cancelable: false }));
|
||||||
|
// Update the preview element with the data attribute 'data-preview-el'
|
||||||
|
this.updatePreviewEl(this.getAttribute('data-preview-el'));
|
||||||
|
//Update storage value if StorageKey exits
|
||||||
if(this.#SotrageKey) {
|
if(this.#SotrageKey) {
|
||||||
window.localStorage.setItem(this.#SotrageKey, this.value);
|
window.localStorage.setItem(this.#SotrageKey, this.value);
|
||||||
}
|
}
|
||||||
this.dispatchEvent(new Event('oninput', { bubbles: true, cancelable: false }));
|
|
||||||
this.updatePreviewEl(this.getAttribute('data-preview-el'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -407,19 +435,19 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
if(this.getAttribute('required') !== null) {
|
if(this.getAttribute('required') !== null) {
|
||||||
if(String(this.EditorNode.textContent).length === 0) {
|
if(String(this.EditorNode.textContent).length === 0) {
|
||||||
hasErros = true;
|
hasErros = true;
|
||||||
errors.push(this.#t('required'));
|
errors.push(_t('required'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(Number(this.getAttribute('minlength'))) {
|
if(Number(this.getAttribute('minlength'))) {
|
||||||
if(String(this.EditorNode.textContent).length < Number(this.getAttribute('minlength'))) {
|
if(String(this.EditorNode.textContent).length < Number(this.getAttribute('minlength'))) {
|
||||||
hasErros = true;
|
hasErros = true;
|
||||||
errors.push(`${this.#t('minlength')} ${this.getAttribute('minlength')}`);
|
errors.push(`${_t('minlength')} ${this.getAttribute('minlength')}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(Number(this.getAttribute('maxlength'))) {
|
if(Number(this.getAttribute('maxlength'))) {
|
||||||
if(String(this.EditorNode.textContent).length > Number(this.getAttribute('maxlength'))) {
|
if(String(this.EditorNode.textContent).length > Number(this.getAttribute('maxlength'))) {
|
||||||
hasErros = true;
|
hasErros = true;
|
||||||
errors.push(`${this.#t('maxlength')} ${this.getAttribute('maxlength')}`);
|
errors.push(`${_t('maxlength')} ${this.getAttribute('maxlength')}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const filterTags = this.getAttribute('filtertags');
|
const filterTags = this.getAttribute('filtertags');
|
||||||
@ -429,7 +457,7 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
const checkTag = disallowTags[i];
|
const checkTag = disallowTags[i];
|
||||||
if(this.EditorNode.querySelector(checkTag)) {
|
if(this.EditorNode.querySelector(checkTag)) {
|
||||||
hasErros = true;
|
hasErros = true;
|
||||||
errors.push(`${this.#t('filtertags')} ${checkTag}`);
|
errors.push(`${_t('filtertags')} ${checkTag}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -455,19 +483,19 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
/**
|
/**
|
||||||
* Check if need append autocompleted tags variants
|
* Check if need append autocompleted tags variants
|
||||||
*/
|
*/
|
||||||
checkAutoComplete() {
|
#checkAutoComplete() {
|
||||||
//CHeck autococmplete
|
//CHeck autococmplete
|
||||||
const Selecton = window.getSelection();
|
const editorSelection = this.getSelection();
|
||||||
if(Selecton !== null && Selecton.anchorNode !== null) {
|
if(editorSelection.selection !== null && editorSelection.selection.anchorNode !== null) {
|
||||||
const SelectionParentEl = Selecton.anchorNode.parentElement as HTMLParagraphElement;
|
const SelectionParentEl = editorSelection.selection.anchorNode.parentElement as HTMLParagraphElement;
|
||||||
const AutoCompleteForm = this.EditorAutoCompleteForm as HTMLElement;
|
const AutoCompleteForm = this.EditorAutoCompleteForm as HTMLElement;
|
||||||
if(SelectionParentEl !== null &&
|
if(SelectionParentEl !== null &&
|
||||||
//if empty selection
|
//if empty selection
|
||||||
Selecton.toString() === '' &&
|
editorSelection.text === null &&
|
||||||
//and parent node is <p>
|
//and parent node is <p>
|
||||||
SelectionParentEl.nodeName === 'P' &&
|
SelectionParentEl.nodeName === 'P' &&
|
||||||
//and parent <p> is parentElement in EditorNode
|
//and parent <p> is parentElement in EditorNode
|
||||||
SelectionParentEl.parentElement === this.EditorNode) {
|
SelectionParentEl.parentElement === this.EditorNode) {
|
||||||
//and parent <p> inner text starts with `/`
|
//and parent <p> inner text starts with `/`
|
||||||
if(SelectionParentEl.innerText.startsWith('/')) {
|
if(SelectionParentEl.innerText.startsWith('/')) {
|
||||||
const parsedTagName = SelectionParentEl.innerText.replace('/', '');
|
const parsedTagName = SelectionParentEl.innerText.replace('/', '');
|
||||||
@ -478,7 +506,7 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
AutoCompleteForm?.appendChild(el('button', {
|
AutoCompleteForm?.appendChild(el('button', {
|
||||||
classList: ['wc-wysiwyg_btn', `-${action.tag}`],
|
classList: ['wc-wysiwyg_btn', `-${action.tag}`],
|
||||||
attrs: {
|
attrs: {
|
||||||
'data-hint': this.#t(action.tag) || null,
|
'data-hint': _t(action.tag) || null,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
type: 'submit',
|
type: 'submit',
|
||||||
@ -497,7 +525,7 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,7 +533,17 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
* Show and position inline actions dialog at targetNode
|
* Show and position inline actions dialog at targetNode
|
||||||
**/
|
**/
|
||||||
showEditorInlineDialog() {
|
showEditorInlineDialog() {
|
||||||
this.EditorInlineDialog.show();
|
//Save selection range
|
||||||
|
const editorSelection = this.getSelection();
|
||||||
|
if(editorSelection.selection !== null) {
|
||||||
|
const range = editorSelection.selection.getRangeAt(0).cloneRange();
|
||||||
|
this.EditorInlineDialog.show();
|
||||||
|
//Restore selection range after dialog show
|
||||||
|
editorSelection.selection.removeAllRanges();
|
||||||
|
editorSelection.selection.addRange(range);
|
||||||
|
} else {
|
||||||
|
this.EditorInlineDialog.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -519,23 +557,24 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checking and clear tag, if can do it
|
* Checking clear form and clear, if can do it
|
||||||
* @param event
|
* @param event
|
||||||
*/
|
*/
|
||||||
checkCanClearElement(event:Event) {
|
#checkCanClearElement(event:Event) {
|
||||||
const eventTarget = event.target as HTMLElement;
|
const eventTarget = event.target as HTMLElement,
|
||||||
|
clearBtn = this.EditorClearFormatBtn;
|
||||||
if(eventTarget !== this.EditorNode) {
|
if(eventTarget !== this.EditorNode) {
|
||||||
if(eventTarget.nodeName !== 'P'
|
if(eventTarget.nodeName !== 'P'
|
||||||
&& eventTarget.nodeName !== 'SPAN') {
|
&& eventTarget.nodeName !== 'SPAN') {
|
||||||
this.EditorClearFormatBtn.style.display = 'inline-block';
|
clearBtn.style.display = 'inline-block';
|
||||||
this.EditorClearFormatBtn.innerHTML = `Ⱦ ${eventTarget.nodeName}`,
|
clearBtn.innerHTML = `Ⱦ ${eventTarget.nodeName}`,
|
||||||
this.EditorClearFormatBtn.onpointerup = (event) => {
|
clearBtn.onpointerup = (event) => {
|
||||||
eventTarget.replaceWith(document.createTextNode(eventTarget.textContent));
|
eventTarget.replaceWith(document.createTextNode(eventTarget.textContent));
|
||||||
}
|
};
|
||||||
this.showEditorInlineDialog();
|
this.showEditorInlineDialog();
|
||||||
} else {
|
} else {
|
||||||
this.EditorClearFormatBtn.style.display = 'none';
|
clearBtn.style.display = 'none';
|
||||||
this.EditorClearFormatBtn.onpointerup = null;
|
clearBtn.onpointerup = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -543,16 +582,18 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
/**
|
/**
|
||||||
* Checking click tag for editable props
|
* Checking click tag for editable props
|
||||||
**/
|
**/
|
||||||
checkEditProps(event) {
|
#checkEditProps(event) {
|
||||||
//Check need edit props
|
|
||||||
const eventTarget = event.target as HTMLElement;
|
const eventTarget = event.target as HTMLElement;
|
||||||
|
|
||||||
//Check exist prop\attr
|
//Check exist prop\attr
|
||||||
if(this.#EditProps[eventTarget.nodeName]) {
|
if(this.#EditProps[eventTarget.nodeName]) {
|
||||||
const props = this.#EditProps[eventTarget.nodeName];
|
const props = this.#EditProps[eventTarget.nodeName];
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
this.dispatchEvent(new CustomEvent('editprops', {
|
||||||
|
detail: { eventTarget }
|
||||||
|
}));
|
||||||
this.EditorPropertyForm.style.display = '';
|
this.EditorPropertyForm.style.display = '';
|
||||||
this.showEditorInlineDialog();
|
this.EditorPropertyForm.style.display = 'block';
|
||||||
this.EditorPropertyForm.setAttribute('data-tag', eventTarget.nodeName);
|
this.EditorPropertyForm.setAttribute('data-tag', eventTarget.nodeName);
|
||||||
this.EditorPropertyForm.innerHTML = '';
|
this.EditorPropertyForm.innerHTML = '';
|
||||||
for (let i = 0; i < props.length; i++) {
|
for (let i = 0; i < props.length; i++) {
|
||||||
@ -594,23 +635,79 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#makeActionButtons(toEl:HTMLElement, actions) {
|
/**
|
||||||
|
* Cheking hot keys when keydown pressed
|
||||||
|
* @param event Keyboard event
|
||||||
|
*/
|
||||||
|
#checkKeyBindings(event:KeyboardEvent) {
|
||||||
|
//check hold alt
|
||||||
|
if(event.altKey) {
|
||||||
|
//alt+space - move caret to parent node next sibling
|
||||||
|
if(event.code === 'Space') {
|
||||||
|
const editorSelection = this.getSelection();
|
||||||
|
const Selection = editorSelection.selection;
|
||||||
|
if(Selection !== null) {
|
||||||
|
if(Selection.type === 'Caret') {
|
||||||
|
//insertAdjacentElement dont support textNodes, first insert span
|
||||||
|
const span = el('span');
|
||||||
|
Selection.anchorNode?.parentElement?.insertAdjacentElement('afterend', span)
|
||||||
|
//after replace span with textnode and select it
|
||||||
|
const textN = document.createTextNode(' ');
|
||||||
|
span.replaceWith(textN);
|
||||||
|
const range = document.createRange();
|
||||||
|
range.selectNodeContents(textN);
|
||||||
|
Selection.removeAllRanges();
|
||||||
|
Selection.addRange(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//tag - hide editor dialog
|
||||||
|
if(event.code === 'Escape') {
|
||||||
|
this.hideEditorInlineDialog();
|
||||||
|
}
|
||||||
|
//enter - set p as default tag in newline
|
||||||
|
if(event.code === 'Enter' && event.shiftKey === false) {
|
||||||
|
const Selection = this.getSelection().selection;
|
||||||
|
let tagName = 'p';
|
||||||
|
//tags with return default browser behavior
|
||||||
|
if(['LI', 'ARTICLE', 'P'].includes(Selection.anchorNode.parentElement.tagName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const p = el(tagName, { props: { innerHTML: ` ` } });
|
||||||
|
Selection?.anchorNode?.parentElement?.insertAdjacentElement('afterend', p);
|
||||||
|
const range = document.createRange();
|
||||||
|
range.selectNodeContents(p);
|
||||||
|
Selection?.removeAllRanges();
|
||||||
|
Selection?.addRange(range);
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make buttons and bind actions
|
||||||
|
* @param toEl htmlelement where append el
|
||||||
|
* @param actions
|
||||||
|
*/
|
||||||
|
#makeActionButtons(toEl:HTMLElement, actions:WCWYSIWYGTag[]) {
|
||||||
for (let i = 0; i < actions.length; i++) {
|
for (let i = 0; i < actions.length; i++) {
|
||||||
const action = actions[i];
|
const action = actions[i];
|
||||||
const button = el('button', {
|
const button = el('button', {
|
||||||
classList: ['wc-wysiwyg_btn', `-${action.tag}`],
|
classList: ['wc-wysiwyg_btn', `-${action.tag}`],
|
||||||
props: {
|
props: {
|
||||||
tabIndex: -1,
|
tabIndex: 0,
|
||||||
type:'button',
|
type:'button',
|
||||||
textContent: action.is ? `${action.tag} is=${action.is}` : action.tag,
|
textContent: action.is ? `${action.tag} is=${action.is}` : action.tag,
|
||||||
onpointerup: (event) => this.#tag(action.tag, event, action.is),
|
onpointerup: (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
this.#tag(action)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
attrs: {
|
attrs: {
|
||||||
'data-hint': action.hint ? action.hint : this.#t(action.tag) || '-',
|
'data-hint': action.hint ? action.hint : _t(action.tag) || '-',
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
//-wc
|
|
||||||
//Make button with
|
|
||||||
toEl.appendChild(button);
|
toEl.appendChild(button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -619,44 +716,66 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
/**
|
/**
|
||||||
* Default behaviors fot tag actions
|
* Default behaviors fot tag actions
|
||||||
*/
|
*/
|
||||||
#tag = (tag:string, event:Event|false = false, is:boolean|string = false) => {
|
#tag = (tag:WCWYSIWYGTag) => {
|
||||||
switch (tag) {
|
switch (tag.tag) {
|
||||||
case 'audio':
|
case 'audio':
|
||||||
this.#Media('audio');
|
this.#Media('audio');
|
||||||
break;
|
break;
|
||||||
case 'video':
|
case 'video':
|
||||||
this.#Media('video');
|
this.#Media('video');
|
||||||
break;
|
break;
|
||||||
|
case 'details':
|
||||||
|
this.#Details();
|
||||||
|
break;
|
||||||
case 'img':
|
case 'img':
|
||||||
this.#Image();
|
this.#Image();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this.#wrapTag(tag, is);
|
if(typeof tag.method === 'function') {
|
||||||
|
tag.method.apply(this, tag);
|
||||||
|
} else {
|
||||||
|
this.#wrapTag(tag, tag.is);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert spoiler
|
||||||
|
**/
|
||||||
|
#Details() {
|
||||||
|
const summaryTitle = prompt('Title', '');
|
||||||
|
if(summaryTitle === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const mediaEl = el('details', {
|
||||||
|
append: [
|
||||||
|
el('summary', { props: {innerText: summaryTitle} }),
|
||||||
|
el('p', { props: {innerText: '...'} })] }
|
||||||
|
);
|
||||||
|
this.EditorNode.append(mediaEl);
|
||||||
|
this.updateContent();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap content in <tag>
|
* Wrap content in <tag>
|
||||||
|
* @param tag:WCWYSIWYGTag
|
||||||
**/
|
**/
|
||||||
#wrapTag = (tag, is:boolean|string = false) => {
|
#wrapTag = (tag:WCWYSIWYGTag, is:boolean|string = false) => {
|
||||||
const isList = ['ul', 'ol'].includes(tag);
|
//First check if this tag is list item
|
||||||
const Selection = window.getSelection();
|
const listTag = ['ul', 'ol'].includes(tag.tag) ? tag.tag : false;
|
||||||
let className = null;
|
//If list tag is true - set newTag is list item as <li>
|
||||||
let defaultOptions = {
|
const newtag = listTag !== false ? 'li' : tag.tag;
|
||||||
classList: className ? className : undefined,
|
const Selection = this.getSelection().selection;
|
||||||
} as any;
|
let defaultOptions = {} as any;
|
||||||
if(isList) {
|
|
||||||
tag = 'li';
|
|
||||||
}
|
|
||||||
if(is) {
|
if(is) {
|
||||||
defaultOptions.options = {is};
|
defaultOptions.options = {is};
|
||||||
}
|
}
|
||||||
let tagNode = el(tag, defaultOptions);
|
let tagNode = el(newtag, defaultOptions);
|
||||||
|
|
||||||
if (Selection !== null && Selection.rangeCount) {
|
if (Selection !== null && Selection.rangeCount) {
|
||||||
if(['ul', 'ol'].includes(tag)) {
|
if(listTag !== false) {
|
||||||
const list = el(tag);
|
const list = el(listTag);
|
||||||
tagNode.replaceWith(list);
|
tagNode.replaceWith(list);
|
||||||
list.append(tagNode)
|
list.append(tagNode)
|
||||||
}
|
}
|
||||||
@ -664,10 +783,13 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
range.surroundContents(tagNode);
|
range.surroundContents(tagNode);
|
||||||
Selection.removeAllRanges();
|
Selection.removeAllRanges();
|
||||||
Selection.addRange(range);
|
Selection.addRange(range);
|
||||||
|
//If selection has text, insert it
|
||||||
if(Selection.toString().length === 0) {
|
if(Selection.toString().length === 0) {
|
||||||
tagNode.innerText = tag;
|
tagNode.innerText = tag;
|
||||||
}
|
}
|
||||||
this.updateContent();
|
this.updateContent();
|
||||||
|
//Check if new tag has editable props
|
||||||
|
this.#checkEditProps({target:tagNode, stopPropagation: () => false});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -699,15 +821,16 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(caption) {
|
if(caption) {
|
||||||
const figure = el('figure');
|
const figure = el('figure', {
|
||||||
|
append: [
|
||||||
const figcaption = el('figcaption', {
|
img,
|
||||||
props: {
|
el('figcaption', {
|
||||||
textContent: caption
|
props: {
|
||||||
}
|
textContent: caption
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
});
|
});
|
||||||
figure.appendChild(img);
|
|
||||||
figure.appendChild(figcaption);
|
|
||||||
|
|
||||||
img.setAttribute('alt', caption);
|
img.setAttribute('alt', caption);
|
||||||
this.EditorNode.appendChild(figure);
|
this.EditorNode.appendChild(figure);
|
||||||
@ -717,13 +840,46 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translate function
|
* Check available extensions in global scope
|
||||||
* @param key:string phrase key
|
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
#t(key:string):string {
|
#checkExtensions() {
|
||||||
let lang = this.lang;
|
// Check extensions in global
|
||||||
return t[lang] ? t[lang][key] || "-" : t["en"][key];
|
const _WCWYSIWYG = window._WCWYSIWYG;
|
||||||
|
console.log('#checkExtensions',_WCWYSIWYG);
|
||||||
|
if(_WCWYSIWYG !== undefined && _WCWYSIWYG.extensions.length > 0) {
|
||||||
|
this.#Extensions = [];
|
||||||
|
for (let i = 0; i < _WCWYSIWYG.extensions.length; i++) {
|
||||||
|
const Extension = new _WCWYSIWYG.extensions[i](this);
|
||||||
|
this.#Extensions.push(Extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get selection info from editor
|
||||||
|
* @returns editorSelection
|
||||||
|
*/
|
||||||
|
getSelection() {
|
||||||
|
const windowSelection = window.getSelection();
|
||||||
|
const editorSelection = {
|
||||||
|
selection: null,
|
||||||
|
element: null,
|
||||||
|
text: null,
|
||||||
|
node: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
if(windowSelection !== null) {
|
||||||
|
editorSelection.selection = windowSelection;
|
||||||
|
editorSelection.node = windowSelection.anchorNode;
|
||||||
|
editorSelection.element = windowSelection.anchorNode.parentElement;
|
||||||
|
let selectionText = windowSelection.toString();
|
||||||
|
if(selectionText.length > 0) {
|
||||||
|
editorSelection.text = selectionText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('wc selection', editorSelection);
|
||||||
|
|
||||||
|
return editorSelection;
|
||||||
}
|
}
|
||||||
//define WCWYSIWYG as custom element
|
//define WCWYSIWYG as custom element
|
||||||
static define(name = 'wc-wysiwyg') {
|
static define(name = 'wc-wysiwyg') {
|
||||||
@ -731,4 +887,28 @@ class WCWYSIWYG extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default WCWYSIWYG;
|
export default WCWYSIWYG;
|
||||||
export const define = WCWYSIWYG.define;
|
export const define = WCWYSIWYG.define;
|
||||||
|
export interface WCWYSIWYGTag {
|
||||||
|
// The HTML tag name
|
||||||
|
tag: string,
|
||||||
|
// Optional method to be called when the tag is used
|
||||||
|
method?: Function,
|
||||||
|
// Optional hint to be displayed when the tag button is pressed\hovered
|
||||||
|
hint?: string,
|
||||||
|
// Optional string to specify the custom tag element
|
||||||
|
is?: string,
|
||||||
|
}
|
||||||
|
export interface WCWYSIWYGActions {
|
||||||
|
wrapTag: Function,
|
||||||
|
insertImageBlock: Function,
|
||||||
|
insertAudio: Function,
|
||||||
|
insertVideo: Function,
|
||||||
|
}
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
_WCWYSIWYG: {
|
||||||
|
extensions?:any[],
|
||||||
|
asyncExtensions?:string[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user