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
322 lines (321 loc) • 10.5 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 y = Object.defineProperty;
var S = (p, e, t) => e in p ? y(p, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : p[e] = t;
var d = (p, e, t) => S(p, typeof e != "symbol" ? e + "" : e, t);
/* empty css */
/* empty css */
import { createToolbarButton as T } from "../ToolbarPlugin/utils.mjs";
import { PopupManager as A } from "../../core/ui/PopupManager.mjs";
import { createContainer as u, createButton as E, createInputField as I } from "../../utils/helpers.mjs";
import B from "../../icons/style.svg.mjs";
import { AVAILABLE_STYLES as C } from "./constants.mjs";
class P {
constructor() {
d(this, "name", "block-style");
d(this, "editor", null);
d(this, "toolbarButton", null);
d(this, "popup", null);
d(this, "selectedElement", null);
}
initialize(e) {
this.editor = e, this.popup = new A(e, {
title: e.t("Block Style Editor"),
className: "block-style-editor",
closeOnClickOutside: !0,
items: this.createPopupItems(),
buttons: [
{
label: e.t("Apply"),
variant: "primary",
onClick: () => this.applyStyles()
},
{
label: e.t("Cancel"),
variant: "secondary",
onClick: () => {
var t;
return (t = this.popup) == null ? void 0 : t.hide();
}
}
]
}), this.addToolbarButton(), this.editor.on("selectionchange", () => this.handleSelectionChange());
}
addToolbarButton() {
var t, l;
const e = (t = this.editor) == null ? void 0 : t.getToolbar();
e && (this.toolbarButton = T({
icon: B,
title: (l = this.editor) == null ? void 0 : l.t("Block Style"),
onClick: () => this.showPopup()
}), e.appendChild(this.toolbarButton));
}
handleSelectionChange() {
var n, o, s, i, r, a;
const e = (o = (n = this.editor) == null ? void 0 : n.getTextFormatter()) == null ? void 0 : o.getSelection();
if (!e || e.rangeCount === 0) return;
const t = e.getRangeAt(0);
let l = null;
if (this.editor) {
if (t.commonAncestorContainer.nodeType === Node.TEXT_NODE ? l = t.commonAncestorContainer.parentElement : t.commonAncestorContainer.nodeType === Node.ELEMENT_NODE && (l = t.commonAncestorContainer), l === ((s = this.editor) == null ? void 0 : s.getContainer())) {
this.selectedElement = null, (i = this.toolbarButton) == null || i.classList.remove("active");
return;
}
if (l) {
const c = this.findNearestBlockElement(l);
if (c && this.isBlockElement(c)) {
this.selectedElement = c, (r = this.toolbarButton) == null || r.classList.add("active"), console.log("BlockStylePlugin: Selected element:", c.tagName, c);
return;
}
}
this.selectedElement = null, (a = this.toolbarButton) == null || a.classList.remove("active"), console.log("BlockStylePlugin: No block element found");
}
}
findNearestBlockElement(e) {
var l;
let t = e;
for (; t && t !== ((l = this.editor) == null ? void 0 : l.getContainer()); ) {
if (this.isBlockElement(t))
return t;
t = t.parentElement;
}
return null;
}
getCurrentBlockElement() {
var n, o, s;
const e = (o = (n = this.editor) == null ? void 0 : n.getTextFormatter()) == null ? void 0 : o.getSelection();
if (!e || e.rangeCount === 0) return null;
const t = e.getRangeAt(0);
let l = null;
return t.commonAncestorContainer.nodeType === Node.TEXT_NODE ? l = t.commonAncestorContainer.parentElement : t.commonAncestorContainer.nodeType === Node.ELEMENT_NODE && (l = t.commonAncestorContainer), !l || l === ((s = this.editor) == null ? void 0 : s.getContainer()) ? null : this.findNearestBlockElement(l);
}
isBlockElement(e) {
return [
"DIV",
"P",
"H1",
"H2",
"H3",
"H4",
"H5",
"H6",
"SECTION",
"ARTICLE",
"HEADER",
"FOOTER",
"NAV",
"ASIDE",
"MAIN",
"FIGURE",
"FIGCAPTION",
"BLOCKQUOTE",
"UL",
"OL",
"LI",
"DL",
"DT",
"DD",
"PRE",
"HR",
"ADDRESS",
"TABLE",
"THEAD",
"TBODY",
"TFOOT",
"TR",
"TH",
"TD",
"CAPTION",
"FORM",
"FIELDSET",
"LEGEND",
"DETAILS",
"SUMMARY",
"MENU",
"DIALOG",
"IFRAME",
"CANVAS",
"VIDEO",
"AUDIO",
"PICTURE",
"SOURCE",
"TRACK",
"MAP",
"OBJECT",
"PARAM",
"EMBED",
"NOSCRIPT",
"PROGRESS",
"METER",
"TIME",
"MARK",
"RUBY",
"RT",
"RP",
"BDI",
"BDO",
"WBR",
"INS",
"DEL",
"CODE",
"KBD",
"SAMP",
"VAR",
"CITE",
"Q",
"ABBR",
"DFN",
"SMALL",
"STRONG",
"EM",
"I",
"B",
"U",
"S",
"STRIKE",
"SUB",
"SUP",
"SPAN",
"BR",
"IMG",
"A",
"BUTTON",
"INPUT",
"TEXTAREA",
"SELECT",
"OPTION",
"OPTGROUP",
"LABEL",
"OUTPUT",
"DATALIST",
"KEYGEN",
"ISINDEX",
"BASE",
"LINK",
"META",
"STYLE",
"SCRIPT",
"TITLE",
"HEAD",
"BODY",
"HTML"
].includes(e.tagName);
}
createPopupItems() {
return [
{
type: "custom",
id: "style-editor",
content: () => this.createStyleEditor()
},
{
type: "custom",
id: "class-editor",
content: () => this.createClassEditor()
}
];
}
createStyleEditor() {
var s, i, r;
const e = u("popup-item"), t = document.createElement("label");
t.textContent = ((s = this.editor) == null ? void 0 : s.t("Inline Styles")) || "Inline Styles", e.appendChild(t);
const l = u("style-list");
(((r = (i = this.selectedElement) == null ? void 0 : i.getAttribute("style")) == null ? void 0 : r.split(";")) || []).forEach((a) => {
if (a.trim()) {
const [c, m] = a.split(":").map((h) => h.trim());
if (c && m) {
const h = this.createStyleItem(c, m);
l.appendChild(h);
}
}
});
const o = E("Add Style", () => this.addStyle(l));
return o.className = "add-button", e.appendChild(l), e.appendChild(o), e;
}
createStyleItem(e, t) {
const l = u("style-item"), n = document.createElement("select");
n.className = "style-property-select", Object.keys(C).forEach((i) => {
const r = document.createElement("option");
r.value = i, r.textContent = i, r.selected = i === e, n.appendChild(r);
});
const o = document.createElement("select");
o.className = "style-value-select", n.addEventListener("change", () => {
this.updateValueSelect(o, n.value);
}), this.updateValueSelect(o, n.value, t);
const s = E("×", () => l.remove());
return s.className = "remove-button", l.appendChild(n), l.appendChild(o), l.appendChild(s), l;
}
/**
* Обновляет выпадающий список значений в зависимости от выбранного свойства.
*/
updateValueSelect(e, t, l = "") {
e.innerHTML = "", (C[t] || []).forEach((o) => {
const s = document.createElement("option");
s.value = o, s.textContent = o, s.selected = o === l, e.appendChild(s);
});
}
addStyle(e) {
const t = this.createStyleItem("", "");
e.appendChild(t);
}
createClassEditor() {
var s, i, r;
const e = u("popup-item"), t = document.createElement("label");
t.textContent = ((s = this.editor) == null ? void 0 : s.t("CSS Classes")) || "CSS Classes", e.appendChild(t);
const l = u("class-list");
(((r = (i = this.selectedElement) == null ? void 0 : i.getAttribute("class")) == null ? void 0 : r.split(" ")) || []).forEach((a) => {
if (a.trim()) {
const c = this.createClassItem(a);
l.appendChild(c);
}
});
const o = E("Add Class", () => this.addClass(l));
return o.className = "add-button", e.appendChild(l), e.appendChild(o), e;
}
createClassItem(e) {
const t = u("class-item"), l = I("text", "Enter class (e.g., my-class)", e);
l.className = "class-input";
const n = E("×", () => t.remove());
return n.className = "remove-button", t.appendChild(l), t.appendChild(n), t;
}
addClass(e) {
const t = this.createClassItem("");
e.appendChild(t);
}
showPopup() {
if (!this.popup) return;
const e = this.getCurrentBlockElement();
if (console.log("BlockStylePlugin: showPopup called, currentBlockElement:", e), !e) {
const o = document.createElement("div");
o.innerHTML = "<p>Please select a block element to style.</p>", this.popup.setContent(o), this.popup.show();
return;
}
this.selectedElement = e;
const t = document.createElement("div"), l = this.createStyleEditor(), n = this.createClassEditor();
t.appendChild(l), t.appendChild(n), this.popup.setContent(t), this.popup.show();
}
applyStyles() {
var l, n, o;
if (!this.selectedElement || !this.popup) return;
(l = this.editor) == null || l.ensureEditorFocus();
const e = (n = this.popup.getElement()) == null ? void 0 : n.querySelector(".style-list"), t = (o = this.popup.getElement()) == null ? void 0 : o.querySelector(".class-list");
if (e) {
const s = Array.from(e.querySelectorAll(".style-item")).map((i) => {
const r = i.querySelector(".style-property-select"), a = i.querySelector(".style-value-select"), c = r.value.trim(), m = a.value.trim();
return c && m ? `${c}: ${m}` : "";
}).filter((i) => i).join("; ");
this.selectedElement.setAttribute("style", s);
}
if (t) {
const s = Array.from(t.querySelectorAll(".class-input")).map((i) => i.value.trim()).filter((i) => i).join(" ");
this.selectedElement.setAttribute("class", s);
}
this.popup.hide();
}
destroy() {
var e, t, l;
(e = this.toolbarButton) == null || e.remove(), this.toolbarButton = null, (t = this.editor) == null || t.off("selectionchange"), (l = this.popup) == null || l.destroy(), this.popup = null, this.editor = null;
}
}
export {
P as BlockStylePlugin
};