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
202 lines (201 loc) • 7.3 kB
JavaScript
/*! on-codemerge v1.3.1 @author Pavel Kuzmin @license MIT @homepage https://s00d.github.io/on-codemerge/ @repository git+https://github.com/s00d/on-codemerge.git Copyright (c) 2026 Pavel Kuzmin - Built on 2026-07-02T13:39:17.947Z */
var d = Object.defineProperty;
var u = (l, e, t) => e in l ? d(l, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : l[e] = t;
var c = (l, e, t) => u(l, typeof e != "symbol" ? e + "" : e, t);
import { DomUtils as p } from "./DomUtils.mjs";
import { StyleManager as f } from "./StyleManager.mjs";
import { isFormatDebugEnabled as y, formatDebugMapToString as g } from "./FormatDebugMap.mjs";
import { FormatDebugTracer as T } from "./FormatDebugTracer.mjs";
class R {
constructor(e, t, r) {
c(this, "domUtils");
c(this, "styleManager");
c(this, "shadowRoot", null);
c(this, "debugTracer", new T());
this.container = e, this.domUtils = new p(e), this.styleManager = new f(), this.shadowRoot = t || null, (r ?? y()) && this.enableFormatDebug(!0);
}
enableFormatDebug(e) {
this.debugTracer.setEnabled(e), this.domUtils.setTracer(e ? this.debugTracer : null);
}
isFormatDebugEnabled() {
return this.debugTracer.isEnabled();
}
getLastDebugMap() {
return this.debugTracer.getLastMap();
}
toggleStyle(e) {
const t = this.getSelection();
if (!t || t.rangeCount === 0)
return;
if (this.styleManager.isBlockStyle(e)) {
this.toggleBlockStyle(e, t);
return;
}
this.debugTracer.begin(e, this.container, t);
const r = this.domUtils.getSelectedRoot(t, !1, e);
if (!r || r.length === 0) {
this.debugTracer.setAction("noop");
const n = this.debugTracer.end(this.container);
n && this.debugTracer.logToConsole(n, g);
return;
}
let o = "apply";
r.forEach((n) => {
n.nodeType === Node.TEXT_NODE && (n = this.wrapTextNodeInSpan(n));
const a = n;
this.styleManager.has(a, e) ? (o = "remove", this.domUtils.removeStyleFromNode(
n,
e,
this.styleManager.remove.bind(this.styleManager)
)) : this.domUtils.applyStyleToNode(
n,
e,
this.styleManager.set.bind(this.styleManager)
);
}), this.debugTracer.setAction(o);
const s = this.debugTracer.end(this.container);
s && this.debugTracer.logToConsole(s, g);
}
toggleBlockStyle(e, t) {
this.debugTracer.begin(e, this.container, t);
const r = t.getRangeAt(0), o = this.domUtils.findBlockElementsInRange(r);
if (this.debugTracer.step(
"findBlockElements",
o.map((i) => i.tagName),
o.length === 0 ? "no_blocks_found" : "blocks_in_range"
), o.length === 0) {
this.debugTracer.setAction("noop");
const i = this.debugTracer.end(this.container);
i && this.debugTracer.logToConsole(i, g);
return;
}
const s = o.every((i) => this.styleManager.has(i, e)), n = s ? "remove" : "apply";
o.forEach((i) => {
s ? this.styleManager.remove(i, e) : this.styleManager.set(i, e);
}), this.debugTracer.setNodesToModify(o, this.container), this.debugTracer.setAction(n);
const a = this.debugTracer.end(this.container);
a && this.debugTracer.logToConsole(a, g);
}
setColor(e) {
this.applyStyleToSelectedNodes((t) => {
t.style.color = e;
});
}
setBackgroundColor(e) {
this.applyStyleToSelectedNodes((t) => {
t.style.backgroundColor = e;
});
}
setFont(e, t, r) {
this.applyStyleToSelectedNodes((o) => {
o.style.fontFamily = e, o.style.fontSize = t, o.style.lineHeight = r;
});
}
clearFont() {
this.applyStyleToSelectedNodes((e) => {
e.style.removeProperty("font-family"), e.style.removeProperty("font-size"), e.style.removeProperty("line-height");
});
}
applyBlock(e = "span") {
this.applyStyleToSelectedNodes((t) => {
const r = document.createElement(e);
r.textContent = t.textContent, r.classList.add("format-text-block"), t.parentNode && t.parentNode.replaceChild(r, t);
});
}
clearBlock() {
this.applyStyleToSelectedNodes((e) => {
const t = document.createElement("span");
t.textContent = e.textContent, e.style.removeProperty("font-family"), e.style.removeProperty("font-size"), e.style.removeProperty("line-height"), e.style.removeProperty("format-text-block"), e.parentNode && e.parentNode.replaceChild(t, e);
});
}
applyStyleToSelectedNodes(e) {
const t = this.getSelection();
if (!t) return;
const r = this.domUtils.getSelectedRoot(t, !1);
r && r.forEach((o) => {
o.nodeType === Node.ELEMENT_NODE && o !== this.container && e(o);
});
}
wrapTextNodeInSpan(e) {
var r;
const t = document.createElement("span");
return (r = e.parentNode) == null || r.replaceChild(t, e), t.appendChild(e), t;
}
getSelection() {
if (this.shadowRoot) {
if ("getSelection" in this.shadowRoot) {
const o = this.shadowRoot.getSelection();
if (o && o.rangeCount > 0)
return o;
}
const e = window.getSelection();
if (!e || e.rangeCount === 0)
return null;
const r = e.getRangeAt(0).commonAncestorContainer;
if (this.shadowRoot.contains(r) || r.nodeType === Node.ELEMENT_NODE && this.shadowRoot.contains(r))
return e;
{
const o = this.shadowRoot.activeElement;
if (o && o === this.container) {
const s = this.container;
let n = 1;
return {
get rangeCount() {
return n;
},
getRangeAt: (i) => {
const h = document.createRange();
return h.selectNodeContents(s), h;
},
removeAllRanges: () => {
n = 0;
},
addRange: (i) => {
i && s.contains(i.commonAncestorContainer) && (n = 1);
},
toString: () => s.textContent || ""
};
}
return null;
}
} else
return window.getSelection();
}
hasClass(e) {
const t = this.getSelection();
if (!t || t.rangeCount === 0) return !1;
if (this.styleManager.isBlockStyle(e)) {
const s = t.getRangeAt(0), n = this.domUtils.findBlockElementsInRange(s);
return n.length > 0 && n.every((a) => this.styleManager.has(a, e));
}
const r = t.getRangeAt(0);
if (r.collapsed) {
const s = this.domUtils.getSelectedRoot(t, !0, e);
return (s == null ? void 0 : s.some((n) => n.nodeType === Node.ELEMENT_NODE ? this.styleManager.has(n, e) : !1)) ?? !1;
}
const o = this.domUtils.collectTextNodesInRangeForRead(r).filter((s) => {
var n;
return (n = s.textContent) == null ? void 0 : n.trim();
});
return o.length === 0 ? !1 : o.every((s) => {
let n = s.parentElement;
for (; n && n !== this.container; ) {
if (this.styleManager.has(n, e))
return !0;
n = n.parentElement;
}
return !1;
});
}
getStyle(e) {
const t = this.getSelection();
if (!t || t.rangeCount === 0) return null;
const r = this.domUtils.getSelectedRoot(t, !0);
if (!r) return null;
for (const o of r)
return o.nodeType === Node.ELEMENT_NODE ? o.style[e] ?? null : null;
}
}
export {
R as TextFormatter
};