Reputation: 41
I am working on WYSIWIG editor, where I can update HTML in special popup and insert it back into Quill editor as a semantic HTML. The resulting HTML should be exactly like we edited. But when I try to change tags to "div", they are turned to "p" and classes are removed.
Here is my example: https://codepen.io/Maria2/pen/PwYWVKY
class Dom {
constructor(selector) {
this.$el =
typeof selector === "string"
? document.querySelector(selector)
: selector;
}
html(html) {
if (typeof html === "string") {
this.$el.innerHTML = html;
return this;
}
return this.$el.outerHTML.trim();
}
innerHtml(html) {
if (typeof html === "string") {
this.$el.innerHTML = html;
return this;
}
return this.$el.innerHTML.trim();
}
text(text) {
if (typeof text !== "undefined") {
this.$el.textContent = text;
return this;
}
if (this.$el.tagName.toLowerCase() === "input") {
return this.$el.value.trim();
}
return this.$el.textContent;
}
clear() {
this.html("");
return this;
}
on(eventType, callback) {
this.$el.addEventListener(eventType, callback);
}
off(eventType, callback) {
this.$el.removeEventListener(eventType, callback);
}
append(node) {
if (node instanceof Dom) {
node = node.$el;
}
if (Element.prototype.append) {
this.$el.append(node);
} else {
this.$el.appendChild(node);
}
}
closest(selector) {
return domElt(this.$el.closest(selector));
}
getCoords() {
return this.$el.getBoundingClientRect();
}
get data() {
return this.$el.dataset;
}
findAll(selector) {
return this.$el.querySelectorAll(selector);
}
css(styles = {}) {
Object.keys(styles).forEach((key) => {
this.$el.style[key] = styles[key];
});
return this.$el;
}
getStyles(styles = []) {
return styles.reduce((res, s) => {
res[s] = this.$el.style[s];
return res;
}, {});
}
find(selector) {
return domElt(this.$el.querySelector(selector));
}
addClass(className) {
this.$el.classList.add(className);
return this;
}
removeClass(className) {
this.$el.classList.remove(className);
return this;
}
attr(name, value) {
if (value) {
this.$el.setAttribute(name, value);
return this;
}
return this.$el.getAttribute(name);
}
focus() {
this.$el.focus();
if (
typeof window.getSelection !== "undefined" &&
typeof document.createRange !== "undefined"
) {
const range = document.createRange();
range.selectNodeContents(this.$el);
range.collapse(false);
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (typeof document.body.createTextRange !== "undefined") {
const textRange = document.body.createTextRange();
textRange.moveToElementText(this.$el);
textRange.collapse(false);
textRange.select();
}
return this;
}
destroy() {
this.$el.remove();
this.$el = null;
}
}
function domElt(selector) {
return new Dom(selector);
}
domElt.create = (tagName, classes = "") => {
const el = document.createElement(tagName);
if (classes) {
el.classList.add(classes);
}
return domElt(el);
};
class PopupElt {
constructor({ togglerClass, beforeOpenCallback, onOpenCallback,onCloseCallback, additionalClass }) {
this.togglerClass = togglerClass;
this.additionalClass = additionalClass;
this.popup = null;
this.overlay = null;
this.closeBtn = null;
this.innerBlock = null;
this.onCloseCallback = null;
this.beforeOpenCallback = beforeOpenCallback;
this.onOpenCallback = onOpenCallback;
this.onCloseCallback = onCloseCallback;
this.clickHandler = this.clickHandler.bind(this);
this.closeHandler = this.closeHandler.bind(this);
}
clickHandler(evt) {
evt.preventDefault();
this.start();
}
start() {
if (this.beforeOpenCallback) this.beforeOpenCallback(this);
this.createOverlay();
this.createPopup();
if (this.onOpenCallback) this.onOpenCallback(this);
this.setCloseListeners();
}
closeHandler(evt) {
evt.preventDefault();
this.closePopup();
}
closePopup() {
this.removeCloseListeners();
this.closeBtn.destroy();
this.innerBlock.destroy();
this.popup.destroy();
if (this.overlay) this.overlay.destroy();
if (this.onCloseCallback) this.onCloseCallback()
}
setCloseListeners() {
this.overlay.on('click', this.closeHandler);
this.closeBtn.on('click', this.closeHandler);
}
removeCloseListeners() {
this.overlay.off('click', this.closeHandler);
this.closeBtn.off('click', this.closeHandler);
}
createOverlay() {
this.overlay = domElt.create('div', 'overlay');
domElt(document.body).append(this.overlay);
}
createPopup() {
let wrapperElt = domElt.create('div', 'popup__wrapper');
this.innerBlock = domElt.create('div', 'popup__inside');
this.popup = domElt.create('div', 'popup');
this.closeBtn = domElt.create('button', 'btn')
.addClass('popup__close')
.attr('ariaLabel', 'закрыть попап')
.html(`<svg fill="none" width="20" height="20" id="cross" viewBox="0 0 22 22"><path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="M16.303 5.697 5.697 16.303M5.697 5.697l10.606 10.606"></path></svg>`)
wrapperElt.append(this.closeBtn);
wrapperElt.append(this.innerBlock);
if (this.additionalClass) this.popup.addClass(this.additionalClass);
this.popup.append(wrapperElt);
domElt(document.body).append(this.popup);
}
async setInnerHtml(innerHtml) {
await this.innerBlock.html(innerHtml)
}
}
const options = (id) => ({
debug: "info",
modules: {
toolbar: `#toolbar${id}`,
},
placeholder: "Compose an epic...",
theme: "snow",
});
class QuillEditorClass {
constructor({ item, options }) {
this.item = item;
this.popup = null;
this.id = null;
this.toolbarBlock = null;
this.codeBtn = null;
this.options = options || {};
this.currentOptions = null;
this.codeButton = null;
this.saveCodeBtn = null;
this.saveBtn = null;
this.quill = null;
this.textareaBlock = null;
this.openPopup = this.openPopup.bind(this);
this.getQuillInfo = this.getQuillInfo.bind(this);
this.saveQuill = this.saveQuill.bind(this);
this.setQuillInfo = this.setQuillInfo.bind(this);
this.init();
}
init() {
if (!this.item) return;
this.id = this.item.dataset.editor;
this.currentOptions = this.options(this.id)
this.toolbarBlock = domElt(`#toolbar${this.id}`);
this.quill = new Quill(this.item, this.currentOptions);
this.codeButton = this.toolbarBlock.find('.code-button');
this.saveBtn = this.item.parentElement.querySelector('.saveQuill')
this.setListeners();
}
setListeners() {
if (this.codeButton) this.codeButton.on('click', this.openPopup);
if (this.saveBtn) this.saveBtn.addEventListener('click', this.saveQuill);
}
beforeOpenCallback(quillContent) {
const insidePopupBlock = domElt.create('div', 'popup__textarea-wrapper');
this.textareaBlock = domElt.create('textarea', 'popup__textarea');
this.textareaBlock.attr('name', `#textarea${this.id}`)
this.saveCodeBtn = domElt.create('button');
this.saveCodeBtn.attr('class', 'btn btn--blue btn--lg');
this.saveCodeBtn.text('Сохранить');
this.textareaBlock.text(quillContent)
insidePopupBlock.append(this.textareaBlock)
insidePopupBlock.append(this.saveCodeBtn)
return insidePopupBlock;
}
onCloseCallback() {
this.saveCodeBtn.off('click', this.setQuillInfo);
this.saveCodeBtn = null;
this.textareaBlock = null;
}
openPopup(evt) {
const quillContent = this.getQuillInfo(evt);
this.popup = new PopupElt({
togglerClass: `#toolbar${this.id}`,
onOpenCallback: (popup) => {
popup.innerBlock.append(this.beforeOpenCallback(quillContent).$el) ;
},
onCloseCallback: null,
additionalClass: 'popup--editor'
});
this.popup.start();
this.saveCodeBtn.on('click', this.setQuillInfo)
}
setQuillInfo(evt) {
evt.preventDefault();
console.log(this.textareaBlock.$el.value,123)
var delta = this.quill.clipboard.convert({html: this.textareaBlock.$el.value});
this.quill.setContents(delta);
this.popup.closePopup();
}
getQuillInfo(evt) {
evt.preventDefault();
const length = this.quill.getLength();
const html = this.quill.getSemanticHTML(0, length);
const delta = this.quill.getContents();
console.log(delta);
console.log(html, 'html');
return html;
}
saveQuill(evt) {
evt.preventDefault();
const length = this.quill.getLength();
const html = this.quill.getSemanticHTML(0, length);
const delta = this.quill.getContents();
console.log(delta);
console.log(html, 'html');
return html;
}
}
const startQuill = () => {
const allQuills = document.querySelectorAll('[data-role="editor"]');
if (allQuills.length) {
allQuills.forEach((item) => {
console.log(item)
new QuillEditorClass({ item, options });
});
}
};
startQuill()
:root {
--grey_2: #b5b5b5;;
--padding: 10px;
--white: #fff;
--blue: #04043c;;
}
.label {
position: relative;
display: flex;
flex-direction: column;
gap: 10px;
}
.label__wrapper {
display: flex;
width: 100%;
position: relative;
flex-wrap: wrap;
}
.label__wrapper--quill {
padding-top: 0;
flex-direction: column;
flex-wrap: nowrap;
border-radius: 10px;
}
.ql-toolbar {
border-radius: 10px 10px 0 0;
background-color: var(--grey_2);
}
.ql-container {
margin-bottom: 10px;
border-radius: 0 0 10px 10px;
background-color: var(--grey_2);
}
.ql-editor {
min-height: 200px;
img {
width: auto;
}
}
.saveQuill {
margin-right: auto;
}
.label__wrapper--row{
gap: 20px;
flex-wrap: nowrap;
}
.popup {
position: fixed;
top: 50%;
left: 50%;
z-index: 13;
width: calc(100% - 2 * var(--padding));
max-width: 520px;
border-radius: 10px;
overflow: hidden;
transform: translate(-50%, -50%);
}
.popup--imgs {
max-width: 1440px;
}
.popup__textarea {
padding: 8px;
width: 100%;
border-radius: 10px;
border: 1px solid var(--grey_2);
}
.popup__textarea-wrapper {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
gap: 14px;
}
.popup__wrapper {
padding: 30px 20px 30px 20px;
background-color: var(--white);
max-height: calc(100svh - 2*var(--padding));
overflow: hidden;
overflow-y: auto;
scrollbar-width: thin;
box-sizing: border-box;
}
.popup__inside {
position: relative;
width: 100%;
}
.panel {
width: 100%;
height: 100%;
overflow-y: auto;
}
.panel__name {
margin-top: 0;
margin-bottom: 34px;
font-size: 24px;
font-weight: 700;
line-height: 1.2;
}
.popup__close {
position: absolute;
top: 10px;
right: 10px;
transition: opacity 0.3s;
}
.popup__close:hover {
opacity: 0.8;
}
.panel__form {
width: 100%;
}
.btn {
display: flex;
padding: 0;
margin: 0;
appearance: none;
text-decoration: none;
background-color: transparent;
border: none;
cursor: pointer;
box-sizing: border-box;
}
.btn svg {
flex-shrink: 0;
}
.btn--lg {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
padding: 9px 18px;
font-weight: 700;
}
.btn--blue {
margin-right: auto;
color: var(--white);
background-color: var(--blue);
border: 2px solid var(--blue);
border-radius: 10px;
transition: opacity 0.3s;
}
<div class="label__wrapper label__wrapper--quill">
<div id="toolbar1">
<!-- Add buttons as you would before -->
<button class="ql-bold"></button>
<button class="ql-italic"></button>
<select class="ql-size">
<option value="small"></option>
<!-- Note a missing, thus falsy value, is used to reset to default -->
<option selected></option>
<option value="large"></option>
<option value="huge"></option>
</select>
<!-- Add a bold button -->
<button class="ql-bold"></button>
<!-- Add subscript and superscript buttons -->
<button class="ql-script" value="sub"></button>
<button class="ql-script" value="super"></button>
<!-- But you can also add your own -->
<button class="code-button">code</button>
</div>
<div rows="5" data-role="editor" data-editor="1"></div>
<button class="btn btn--blue btn--lg saveQuill">Сохранить текст</button>
</div>
Button "code" initiates popup? where you can try to edit HTML
Upvotes: 0
Views: 35