UNPKG

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
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 };