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
385 lines (384 loc) • 18 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 O = Object.defineProperty;
var A = (u, e, t) => e in u ? O(u, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : u[e] = t;
var a = (u, e, t) => A(u, typeof e != "symbol" ? e + "" : e, t);
import { PopupManager as H } from "../../../core/ui/PopupManager.mjs";
import { FormManager as U } from "../services/FormManager.mjs";
import { FieldEditor as W } from "./FieldEditor.mjs";
import { FormPreview as q } from "./FormPreview.mjs";
import { createContainer as s, createButton as f, createLabel as D, createSelectField as K, createInputField as R, createLineBreak as _ } from "../../../utils/helpers.mjs";
import { TemplatesModal as $ } from "./TemplatesModal.mjs";
class N {
constructor(e) {
a(this, "editor");
a(this, "popup", null);
a(this, "formManager");
a(this, "fieldEditor", null);
a(this, "formPreview", null);
a(this, "callback", null);
a(this, "selectedFieldId", null);
a(this, "previewContainer", null);
a(this, "fieldsListContainer", null);
a(this, "fieldEditorContainer", null);
a(this, "isEditMode", !1);
a(this, "existingFormElement", null);
a(this, "templatesModal", null);
a(this, "handleEscapeKey", (e) => {
var t, i;
e.key === "Escape" && ((t = this.popup) != null && t.isOpen()) && ((i = this.popup) == null || i.hide(), this.destroy());
});
// Добавляю методы drag and drop
a(this, "dragStartIndex", null);
this.editor = e, this.formManager = new U(e), this.templatesModal = new $(e), this.popup = new H(e, {
title: e.t("Form Builder"),
className: "form-builder-modal",
closeOnClickOutside: !0,
buttons: [
{
label: e.t("Cancel"),
variant: "secondary",
onClick: () => {
var t;
(t = this.popup) == null || t.hide(), this.destroy();
}
},
{
label: e.t("Save Form"),
variant: "primary",
onClick: () => this.saveForm()
}
],
items: [
{
type: "custom",
id: "form-builder-content",
content: () => this.createContent()
}
]
}), this.setupCloseHandlers();
}
setupCloseHandlers() {
this.handleEscapeKey = (e) => {
var t;
e.key === "Escape" && ((t = this.popup) != null && t.isOpen()) && (this.popup.hide(), this.destroy());
}, this.editor.getDOMContext().addEventListener("keydown", this.handleEscapeKey);
}
createContent() {
const e = s("form-builder-modal-content"), t = s(
"form-builder-header-panel flex justify-between items-center mb-2"
), i = s("form-builder-title text-xl font-bold");
i.textContent = this.isEditMode ? this.editor.t("Edit Form") : this.editor.t("Form Builder");
const o = f(
this.editor.t("Templates"),
() => this.openTemplatesModal(),
"secondary"
);
t.appendChild(i), t.appendChild(o), e.appendChild(t);
const n = this.createFormSettings();
e.appendChild(n);
const d = s("form-builder-main"), r = this.createFieldsPanel();
d.appendChild(r);
const h = this.createFieldEditorPanel();
d.appendChild(h), e.appendChild(d);
const l = this.createPreviewPanel();
return e.appendChild(l), e;
}
createFormSettings() {
const e = s("form-settings-panel"), t = s("settings-title");
t.textContent = this.editor.t("Form Settings"), e.appendChild(t);
const i = s("setting-group"), o = D(this.editor.t("Method:")), n = K(
[
{ value: "GET", label: "GET" },
{ value: "POST", label: "POST" },
{ value: "PUT", label: "PUT" },
{ value: "DELETE", label: "DELETE" },
{ value: "PATCH", label: "PATCH" }
],
this.formManager.getFormMethod(),
(l) => {
this.formManager.updateFormMethod(l);
}
);
i.appendChild(o), i.appendChild(n), e.appendChild(i);
const d = s("setting-group"), r = D(this.editor.t("Action URL:")), h = R(
"text",
this.editor.t("Enter form action URL"),
this.formManager.getFormAction(),
(l) => {
this.formManager.updateFormAction(l);
}
);
return h.id = "form-action-url", d.appendChild(r), d.appendChild(h), e.appendChild(d), e;
}
createFieldsPanel() {
const e = s("fields-panel"), t = s("fields-header"), i = s("fields-title");
i.textContent = this.editor.t("Form Fields");
const o = f("+", () => this.addField(), "primary");
return o.className = "add-field-button", t.appendChild(i), t.appendChild(o), e.appendChild(t), this.fieldsListContainer = s("fields-list"), e.appendChild(this.fieldsListContainer), this.renderFieldsList(), e;
}
createFieldEditorPanel() {
const e = s("field-editor-panel"), t = s("editor-title");
return t.textContent = this.editor.t("Field Editor"), e.appendChild(t), this.fieldEditorContainer = s("field-editor-content"), e.appendChild(this.fieldEditorContainer), this.fieldEditor || (this.fieldEditor = new W(
(i, o) => this.updateField(i, o),
(i) => this.removeField(i),
(i) => this.cloneField(i),
this.editor,
this.formManager,
(i, o) => this.onFieldTypeChange(i, o),
(i) => this.onOptionsChange(i)
)), e;
}
createPreviewPanel() {
const e = s("preview-panel"), t = s("preview-title");
t.textContent = this.editor.t("Form Preview"), e.appendChild(t);
const i = s("preview-content");
return e.appendChild(i), this.formPreview || (this.formPreview = new q(i, this.editor)), this.previewContainer = i, e;
}
renderFieldsList() {
if (!this.fieldsListContainer) return;
this.fieldsListContainer.innerHTML = "";
const e = this.formManager.getFields();
if (e.length === 0) {
const t = s("no-fields");
t.textContent = this.editor.t("No fields added yet"), this.fieldsListContainer.appendChild(t);
return;
}
e.forEach((t, i) => {
const o = s("field-item");
o.setAttribute("draggable", "true"), o.addEventListener("dragstart", (c) => this.handleDragStart(c, i)), o.addEventListener("dragover", (c) => this.handleDragOver(c, i)), o.addEventListener("drop", (c) => this.handleDrop(c, i)), this.selectedFieldId === t.id && o.classList.add("selected");
const n = s("field-title");
n.textContent = t.label || this.editor.t("Untitled Field");
const d = s("field-type");
d.textContent = t.type;
const r = s("field-actions"), h = f("↑", () => this.moveField(t.id, i - 1), "secondary");
h.className = "field-action-button", h.disabled = i === 0;
const l = f("↓", () => this.moveField(t.id, i + 1), "secondary");
l.className = "field-action-button", l.disabled = i === e.length - 1;
const p = f("×", () => this.removeField(t.id), "danger");
p.className = "field-action-button", r.appendChild(h), r.appendChild(l), r.appendChild(p), o.appendChild(n), o.appendChild(d), o.appendChild(r), o.addEventListener("click", () => this.selectField(t.id)), this.fieldsListContainer.appendChild(o);
}), this.updatePreview();
}
selectField(e) {
this.selectedFieldId = e, this.renderFieldsList(), this.renderFieldEditor();
}
renderFieldEditor() {
if (!(!this.fieldEditorContainer || !this.fieldEditor))
if (this.fieldEditorContainer.innerHTML = "", this.selectedFieldId) {
const e = this.formManager.getField(this.selectedFieldId);
if (e) {
const t = this.fieldEditor.createFieldEditor(e);
this.fieldEditorContainer.appendChild(t);
}
} else {
const e = s("no-field-message");
e.textContent = this.editor.t("Select a field to edit"), this.fieldEditorContainer.appendChild(e);
}
}
updateFieldEditorTitle() {
if (!this.fieldEditorContainer || !this.selectedFieldId) return;
const e = this.formManager.getField(this.selectedFieldId);
if (!e) return;
const t = this.fieldEditorContainer.querySelector(".field-title");
t && (t.textContent = e.label || this.editor.t("Untitled Field"));
}
addField(e = "text") {
this.formManager.addField(e);
const t = this.formManager.getFields();
t.length > 0 && (this.selectedFieldId = t[t.length - 1].id), this.renderFieldsList(), this.renderFieldEditor();
}
renderFieldsListWithoutPreview() {
if (!this.fieldsListContainer) return;
this.fieldsListContainer.innerHTML = "";
const e = this.formManager.getFields();
if (e.length === 0) {
const t = s("no-fields");
t.textContent = this.editor.t("No fields added yet"), this.fieldsListContainer.appendChild(t);
return;
}
e.forEach((t, i) => {
const o = s("field-item");
this.selectedFieldId === t.id && o.classList.add("selected");
const n = s("field-title");
n.textContent = t.label || this.editor.t("Untitled Field");
const d = s("field-type");
d.textContent = t.type;
const r = s("field-actions"), h = f("↑", () => this.moveField(t.id, i - 1), "secondary");
h.className = "field-action-button", h.disabled = i === 0;
const l = f("↓", () => this.moveField(t.id, i + 1), "secondary");
l.className = "field-action-button", l.disabled = i === e.length - 1;
const p = f("×", () => this.removeField(t.id), "danger");
p.className = "field-action-button", r.appendChild(h), r.appendChild(l), r.appendChild(p), o.appendChild(n), o.appendChild(d), o.appendChild(r), o.addEventListener("click", () => this.selectField(t.id)), this.fieldsListContainer.appendChild(o);
});
}
updateField(e, t) {
this.formManager.updateField(e, t), this.renderFieldsListWithoutPreview(), this.updateFieldEditorTitle(), this.updatePreview();
}
removeField(e) {
if (confirm(this.editor.t("Are you sure you want to delete this field?"))) {
const i = this.formManager.getFields().findIndex((n) => n.id === e);
this.formManager.removeField(e);
const o = this.formManager.getFields();
o.length > 0 ? this.selectedFieldId === e && (i < o.length ? this.selectedFieldId = o[i].id : this.selectedFieldId = o[o.length - 1].id) : this.selectedFieldId = null, this.renderFieldsListWithoutPreview(), this.renderFieldEditor();
}
}
cloneField(e) {
const t = this.formManager.getField(e);
if (t) {
const i = {
...t,
id: `field_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
label: `${t.label} (Copy)`
};
this.formManager.addField(t.type, i), this.selectedFieldId = i.id, this.renderFieldsListWithoutPreview(), this.renderFieldEditor();
}
}
moveField(e, t) {
this.formManager.moveField(e, t), this.renderFieldsListWithoutPreview();
}
updatePreview() {
if (!this.previewContainer || !this.formPreview) return;
const e = this.formManager.getFormConfig();
e && e.fields.length > 0 ? this.formPreview.createPreview(e) : this.previewContainer.innerHTML = '<p class="no-preview-message">No fields to preview</p>';
}
saveForm() {
var t, i, o;
const e = this.formManager.getFormConfig();
e && (this.isEditMode && this.existingFormElement ? confirm(this.editor.t("Are you sure you want to update this form?")) && (this.existingFormElement.outerHTML = this.formManager.createForm(e), (t = this.popup) == null || t.hide(), this.destroy()) : ((i = this.callback) == null || i.call(this, e), (o = this.popup) == null || o.hide(), this.destroy()));
}
show(e, t = !1, i = null) {
var o;
this.callback = e, this.isEditMode = t, this.existingFormElement = i, t && i ? (this.formManager.loadForm(i), this.updatePopupForEditMode(), this.updateFormSettings(), this.renderFieldsListWithoutPreview(), this.renderFieldEditor()) : (this.selectedFieldId = null, this.updatePopupForCreateMode()), (o = this.popup) == null || o.show(), setTimeout(() => {
const n = this.editor.getDOMContext().getElementById("form-action-url");
n && n.focus();
}, 0), this.updatePreview();
}
updatePopupForEditMode() {
var i, o;
const e = (i = this.popup) == null ? void 0 : i.getElement().querySelector(".popup-header-title");
e && (e.textContent = this.editor.t("Edit Form"));
const t = (o = this.popup) == null ? void 0 : o.getElement().querySelector('.popup-footer button[data-variant="primary"]');
t && (t.textContent = this.editor.t("Update Form"));
}
updatePopupForCreateMode() {
var i, o;
const e = (i = this.popup) == null ? void 0 : i.getElement().querySelector(".popup-header-title");
e && (e.textContent = this.editor.t("Form Builder"));
const t = (o = this.popup) == null ? void 0 : o.getElement().querySelector('.popup-footer button[data-variant="primary"]');
t && (t.textContent = this.editor.t("Save Form"));
}
updateFormSettings() {
var i, o;
const e = (i = this.popup) == null ? void 0 : i.getElement().querySelector(".form-settings-panel select");
e && (e.value = this.formManager.getFormMethod());
const t = (o = this.popup) == null ? void 0 : o.getElement().querySelector('.form-settings-panel input[type="text"]');
t && (t.value = this.formManager.getFormAction() || "");
}
destroy() {
this.editor.getDOMContext().removeEventListener("keydown", this.handleEscapeKey), this.popup && (this.popup.destroy(), this.popup = null), this.fieldEditor && (this.fieldEditor = null), this.formPreview && (this.formPreview = null);
}
openTemplatesModal() {
var t, i;
(t = this.popup) == null || t.hide();
const e = this.editor.saveCursorPosition();
(i = this.templatesModal) == null || i.show((o) => {
if (o && o.config) {
const n = new N(this.editor);
n.show(
(d) => {
e && this.editor.restoreCursorPosition(e);
const r = this.formManager.createForm(d);
this.editor.insertContent(r), this.editor.insertContent(_());
},
!1,
null
), n.loadFormConfig(o.config);
}
});
}
// Добавляем метод для загрузки конфигурации формы
loadFormConfig(e) {
this.formManager.loadFormConfig(e), this.selectedFieldId = null, this.updateFormSettings(), this.renderFieldsListWithoutPreview(), this.renderFieldEditor(), this.updatePreview();
}
handleDragStart(e, t) {
var i;
this.dragStartIndex = t, (i = e.dataTransfer) == null || i.setData("text/plain", t.toString());
}
handleDragOver(e, t) {
e.preventDefault();
}
handleDrop(e, t) {
if (e.preventDefault(), this.dragStartIndex !== null && this.dragStartIndex !== t) {
const o = this.formManager.getFields()[this.dragStartIndex];
this.formManager.moveField(o.id, t), this.renderFieldsListWithoutPreview(), this.dragStartIndex = null;
}
}
onFieldTypeChange(e, t) {
var d, r, h, l, p, c, F, g, C, v, E, M, b, w, P, y, L, x, S, I, k, T, B;
const i = this.formManager.getField(e);
if (!i) return;
const o = {
name: ((d = i.options) == null ? void 0 : d.name) || e,
placeholder: ((r = i.options) == null ? void 0 : r.placeholder) || "",
className: ((h = i.options) == null ? void 0 : h.className) || "",
readonly: ((l = i.options) == null ? void 0 : l.readonly) || !1,
disabled: ((p = i.options) == null ? void 0 : p.disabled) || !1
};
switch (t) {
case "select":
case "radio":
if (o.options = ((c = i.options) == null ? void 0 : c.options) || [], o.multiple = t === "select" && ((F = i.options) == null ? void 0 : F.multiple) || !1, o.options.length === 0) {
const m = t === "radio" ? this.editor.t("New Radio") : this.editor.t("Option 1");
o.options = [m];
}
break;
case "checkbox":
o.value = ((g = i.options) == null ? void 0 : g.value) || i.label || this.editor.t("New Checkbox"), o.checked = ((C = i.options) == null ? void 0 : C.checked) || !1;
break;
case "number":
case "range":
o.min = ((v = i.options) == null ? void 0 : v.min) || "", o.max = ((E = i.options) == null ? void 0 : E.max) || "", o.step = ((M = i.options) == null ? void 0 : M.step) || "";
break;
case "textarea":
o.rows = ((b = i.options) == null ? void 0 : b.rows) || 3, o.cols = ((w = i.options) == null ? void 0 : w.cols) || 50;
break;
case "file":
o.accept = ((P = i.options) == null ? void 0 : P.accept) || "", o.multiple = ((y = i.options) == null ? void 0 : y.multiple) || !1;
break;
case "date":
case "time":
case "datetime-local":
case "month":
case "week":
o.min = ((L = i.options) == null ? void 0 : L.min) || "", o.max = ((x = i.options) == null ? void 0 : x.max) || "";
break;
case "text":
case "email":
case "password":
case "tel":
case "url":
o.maxlength = ((S = i.options) == null ? void 0 : S.maxlength) || "", o.minlength = ((I = i.options) == null ? void 0 : I.minlength) || "";
break;
case "color":
o.value = ((k = i.options) == null ? void 0 : k.value) || "#000000";
break;
case "image":
o.src = ((T = i.options) == null ? void 0 : T.src) || "", o.alt = ((B = i.options) == null ? void 0 : B.alt) || "";
break;
}
const n = {
type: t,
options: o
};
if (i.validation) {
const m = { ...i.validation };
t !== "email" && t !== "url" && (delete m.email, delete m.url), t !== "number" && t !== "range" && delete m.numeric, n.validation = m;
}
this.formManager.updateField(e, n), this.renderFieldEditor(), this.updatePreview();
}
onOptionsChange(e) {
this.renderFieldEditor(), this.updatePreview();
}
}
export {
N as FormBuilderModal
};