on-codemerge
Version:
A WYSIWYG editor for on-codemerge is a user-friendly interface that allows users to edit and view their code in real time, exactly as it will appear in the final product
275 lines (274 loc) • 11.7 kB
JavaScript
var h = Object.defineProperty;
var c = (s, e, t) => e in s ? h(s, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : s[e] = t;
var a = (s, e, t) => c(s, typeof e != "symbol" ? e + "" : e, t);
import { DefaultPluginManager as d } from "./src/core/Plugin.mjs";
import { HTMLFormatter as g } from "./src/core/services/HTMLFormatter.mjs";
import { LocaleManager as u } from "./src/core/services/LocaleManager.mjs";
import { TextFormatter as f } from "./src/core/services/TextFormatter/index.mjs";
import { Selector as v } from "./src/core/services/Selector.mjs";
class L {
constructor(e) {
a(this, "innerContainer");
a(this, "container");
a(this, "plugins");
a(this, "eventHandlers");
a(this, "formatter");
a(this, "localeManager");
a(this, "contentChangeCallbacks", []);
a(this, "mutationObserver");
a(this, "textFormatter", null);
a(this, "selector");
a(this, "_disableObserver", !1);
this.container = document.createElement("div"), this.container.className = "html-editor", this.container.contentEditable = "true", this.localeManager = new u(), e.appendChild(this.container), this.innerContainer = e, this.plugins = new d(), this.eventHandlers = /* @__PURE__ */ new Map(), this.formatter = new g(), this.textFormatter = new f(this.container), this.selector = new v(this.container), this.container.addEventListener("click", () => {
this.container.focus();
}), this.container.addEventListener("dragstart", (t) => this.handleDragStart(t)), this.container.addEventListener("dragend", (t) => this.handleDragEnd(t)), this.container.addEventListener("dragenter", (t) => this.handleDragEnter(t)), this.container.addEventListener("dragover", (t) => this.handleDragOver(t)), this.container.addEventListener("dragleave", (t) => this.handleDragLeave(t)), this.container.addEventListener("drop", (t) => this.handleDrop(t)), this.container.addEventListener("paste", (t) => this.handlePaste(t)), document.addEventListener("selectionchange", (t) => this.handleSelectionChange(t)), this.mutationObserver = new MutationObserver((t) => this.handleMutations(t)), this.mutationObserver.observe(this.container, {
childList: !0,
// Отслеживаем изменения в дочерних элементах
subtree: !0,
// Отслеживаем изменения во всем поддереве
characterData: !0,
// Отслеживаем изменения текста
attributes: !0
// Отслеживаем изменения атрибутов
});
}
async handleSelectionChange(e) {
const t = window.getSelection();
if (!t || t.rangeCount === 0) return;
const r = t.getRangeAt(0).commonAncestorContainer;
this.isSelectionInsideEditor(r) && this.triggerEvent("selectionchange", { event: e });
}
/**
* Проверяет, находится ли выделение внутри контейнера редактора.
*/
isSelectionInsideEditor(e) {
return e.nodeType === Node.TEXT_NODE ? this.container.contains(e.parentElement) : e.nodeType === Node.ELEMENT_NODE ? this.container.contains(e) : !1;
}
handleMutations(e) {
e.some((n) => n.type === "childList" || // Изменения в дочерних элементах
n.type === "characterData" || // Изменения в тексте
n.type === "attributes") && this.handleContentChange();
}
async handlePaste(e) {
e.preventDefault();
const t = e.clipboardData;
if (!t) return;
const n = t.files, r = t.getData("text");
n.length > 0 ? this.handleFileDrop(n, e) : r && this.handleTextDrop(r);
}
handleDragStart(e) {
this.triggerEvent("drag-start", { e });
}
handleDragEnd(e) {
this.triggerEvent("drag-end", { e });
}
handleDragEnter(e) {
e.preventDefault(), this.container.classList.add("drag-overlay"), this.container.draggable = !0, this.triggerEvent("drag-enter", { e });
}
handleDragOver(e) {
e.preventDefault(), e.dataTransfer && (e.dataTransfer.dropEffect = "copy"), this.triggerEvent("drag-over", { e });
}
handleDragLeave(e) {
e.preventDefault(), this.container.classList.remove("drag-overlay"), this.container.draggable = !1, this.triggerEvent("drag-leave", { e });
}
handleDrop(e) {
if (e.preventDefault(), this.container.classList.remove("drag-overlay"), this.container.draggable = !1, e.dataTransfer) {
const t = e.dataTransfer.files, n = e.dataTransfer.getData("text");
t.length > 0 ? this.handleFileDrop(t, e) : n && this.handleTextDrop(n);
}
this.triggerEvent("drag-drop", { e });
}
async handleFileDrop(e, t) {
for (let n = 0; n < e.length; n++) {
const r = e[n], i = r.type, l = r.name, o = await this.readFileContent(r);
this.triggerEvent("file-drop", { type: i, name: l, content: o, e: t });
}
}
handleTextDrop(e) {
this.insertTextAtCursor(e), this.triggerEvent("text-drop", e);
}
async readFileContent(e) {
return new Promise((t, n) => {
const r = new FileReader();
r.onload = () => {
r.result ? t(r.result) : n(new Error("Failed to read file"));
}, r.onerror = () => n(r.error), e.type.startsWith("text/") ? r.readAsText(e) : r.readAsDataURL(e);
});
}
insertTextAtCursor(e) {
const t = window.getSelection();
if (t && t.rangeCount > 0) {
const n = t.getRangeAt(0);
n.deleteContents(), n.insertNode(document.createTextNode(e)), n.collapse(!1);
}
}
getContainer() {
return this.container;
}
getInnerContainer() {
return this.innerContainer;
}
getTextFormatter() {
return this.textFormatter;
}
getSelector() {
return this.selector;
}
isSelectionInsideContainer(e) {
if (!e || e.rangeCount === 0) return !1;
const t = e.getRangeAt(0);
return this.container.contains(t.startContainer) && this.container.contains(t.endContainer);
}
getSelectionInsideContainer() {
const e = window.getSelection();
return e && this.isSelectionInsideContainer(e) ? e : null;
}
saveCursorPosition() {
const e = this.getSelectionInsideContainer();
if (!e || e.rangeCount === 0) return null;
const t = e.getRangeAt(0), n = t.cloneRange();
return n.selectNodeContents(this.container), n.setEnd(t.startContainer, t.startOffset), { offset: n.toString().length };
}
restoreCursorPosition(e) {
const t = window.getSelection();
if (!t) return;
const n = document.createRange();
let r = this.container, i = e.offset;
const l = document.createTreeWalker(this.container, NodeFilter.SHOW_TEXT);
for (; l.nextNode(); ) {
const o = l.currentNode;
if (i <= o.length) {
r = o;
break;
}
i -= o.length;
}
(r.nodeType !== Node.TEXT_NODE || i < 0 || i > r.length) && (r = this.container, i = this.container.childNodes.length), n.setStart(r, i), n.collapse(!0), t.removeAllRanges(), t.addRange(n), this.container.focus();
}
use(e) {
this.plugins.register(e), e.initialize(this);
}
getPlugins() {
return this.plugins.getPlugins();
}
getHotkeys() {
return this.plugins.getHotkeys();
}
on(e, t) {
return this.eventHandlers.has(e) || this.eventHandlers.set(e, []), this.eventHandlers.get(e).push(t), () => {
const n = this.eventHandlers.get(e);
if (n) {
const r = n.indexOf(t);
r !== -1 && n.splice(r, 1), n.length === 0 && this.eventHandlers.delete(e);
}
};
}
triggerEvent(e, ...t) {
const n = this.eventHandlers.get(e);
n && n.forEach((r) => r(...t));
}
off(e, t) {
if (this.eventHandlers.has(e))
if (t) {
const n = this.eventHandlers.get(e), r = n.indexOf(t);
r !== -1 && n.splice(r, 1), n.length === 0 && this.eventHandlers.delete(e);
} else
this.eventHandlers.delete(e);
}
disableObserver() {
this._disableObserver = !0;
}
enableObserver() {
this._disableObserver = !1;
}
/**
* Обработчик изменений контента
*/
handleContentChange() {
if (this._disableObserver) return;
const e = this.getHtml();
this.contentChangeCallbacks.forEach((t) => t(e));
}
/**
* Подписка на изменения контента
* @param callback Функция, которая будет вызвана при изменении контента
*/
subscribeToContentChange(e) {
return this.contentChangeCallbacks.push(e), () => {
const t = this.contentChangeCallbacks.indexOf(e);
t !== -1 && this.contentChangeCallbacks.splice(t, 1);
};
}
ensureEditorFocus() {
const e = this.getContainer();
e.focus();
const t = window.getSelection();
if (!t || !t.rangeCount) {
const n = document.createRange();
n.selectNodeContents(e), n.collapse(!0), t == null || t.removeAllRanges(), t == null || t.addRange(n);
}
}
getHtml() {
const e = this.getContainer();
if (!e) return "";
const n = e.cloneNode(!0).innerHTML;
return this.formatter.format(n);
}
waitForDOMStabilization(e, t = 100) {
return new Promise((n) => {
let r;
const i = new MutationObserver(() => {
clearTimeout(r), r = setTimeout(() => {
i.disconnect(), n();
}, t);
});
i.observe(e, {
childList: !0,
// Отслеживаем изменения в дочерних элементах
subtree: !0,
// Отслеживаем изменения во всем поддереве
attributes: !0,
// Отслеживаем изменения атрибутов
characterData: !0
// Отслеживаем изменения текста
}), r = setTimeout(() => {
i.disconnect(), n();
}, t);
});
}
async setHtml(e) {
await this.waitForDOMStabilization(this.container), this.container.innerHTML = this.formatter.format(e), await new Promise((t) => requestAnimationFrame(t));
}
insertContent(e) {
this.ensureEditorFocus();
const t = window.getSelection();
if (t && t.rangeCount > 0) {
const n = t.getRangeAt(0);
if (n.deleteContents(), typeof e == "string") {
const r = document.createElement("div");
for (r.innerHTML = e; r.firstChild; )
n.insertNode(r.firstChild);
} else
n.insertNode(e);
n.collapse(!1), t.removeAllRanges(), t.addRange(n);
} else {
const n = this.getContainer();
typeof e == "string" ? n.insertAdjacentHTML("beforeend", e) : n.appendChild(e);
}
}
t(e, t = {}) {
return this.localeManager.translate(e, t);
}
async setLocale(e) {
return await this.localeManager.setLocale(e);
}
getLocale() {
return this.localeManager.getCurrentLocale();
}
destroy() {
this.mutationObserver.disconnect(), this.container.removeEventListener("click", () => this.container.focus()), this.container.removeEventListener("dragenter", (e) => this.handleDragEnter(e)), this.container.removeEventListener("dragover", (e) => this.handleDragOver(e)), this.container.removeEventListener("dragleave", (e) => this.handleDragLeave(e)), this.container.removeEventListener("drop", (e) => this.handleDrop(e)), this.container.removeEventListener("paste", (e) => this.handlePaste(e)), this.contentChangeCallbacks = [], this.plugins.destroy(), this.container.parentElement && this.container.parentElement.removeChild(this.container), this.innerContainer = null, this.container = null, this.plugins = null, this.eventHandlers = null, this.formatter = null, this.localeManager = null, this.mutationObserver = null, this.textFormatter = null;
}
}
export {
L as HTMLEditor
};