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
349 lines (348 loc) • 11.8 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 I = (c, t, e) => t in c ? y(c, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : c[t] = e;
var n = (c, t, e) => I(c, typeof t != "symbol" ? t + "" : t, e);
class N {
constructor(t) {
n(this, "fieldsConfig", []);
n(this, "currentFormId", null);
n(this, "currentFormAction", "");
n(this, "currentFormMethod", "POST");
n(this, "editor");
this.editor = t;
}
/**
* Добавляет поле в форму
*/
addField(t, e) {
let a;
if (typeof t == "string") {
const l = `field_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
let r = {
name: l,
placeholder: ""
};
t === "checkbox" && (r = {
name: l,
placeholder: "",
value: this.editor.t("New Checkbox"),
checked: !1
}), a = {
id: l,
type: t,
label: this.editor.t("New Field"),
options: r,
validation: {
required: !1
},
position: this.fieldsConfig.length,
...e
};
} else
a = {
...t,
...e,
id: (e == null ? void 0 : e.id) || t.id || this.generateFieldId(),
position: this.fieldsConfig.length
};
this.fieldsConfig.push(a);
}
/**
* Обновляет поле по ID
*/
updateField(t, e) {
const a = this.fieldsConfig.findIndex((l) => l.id === t);
if (a !== -1) {
const l = this.fieldsConfig[a];
return e.options && l.options && (e.options = { ...l.options, ...e.options }), this.fieldsConfig[a] = { ...l, ...e }, !0;
}
return !1;
}
/**
* Удаляет поле по ID
*/
removeField(t) {
const e = this.fieldsConfig.findIndex((a) => a.id === t);
return e !== -1 ? (this.fieldsConfig.splice(e, 1), this.fieldsConfig.forEach((a, l) => {
a.position = l;
}), !0) : !1;
}
/**
* Перемещает поле
*/
moveField(t, e) {
const a = this.fieldsConfig.findIndex((r) => r.id === t);
if (a === -1 || e < 0 || e >= this.fieldsConfig.length)
return !1;
const l = this.fieldsConfig.splice(a, 1)[0];
return this.fieldsConfig.splice(e, 0, l), this.fieldsConfig.forEach((r, d) => {
r.position = d;
}), !0;
}
/**
* Возвращает поле по ID
*/
getField(t) {
return this.fieldsConfig.find((e) => e.id === t);
}
/**
* Возвращает текущий список полей
*/
getFields() {
return [...this.fieldsConfig];
}
/**
* Очищает все поля
*/
clearFields() {
this.fieldsConfig = [], this.currentFormId = null;
}
/**
* Создает конфигурацию формы
*/
createFormConfig(t, e = "GET") {
return {
id: this.generateFormId(),
method: e,
action: t,
className: "generated-form",
fields: this.getFields()
};
}
/**
* Создает и возвращает HTML форму
*/
createForm(t) {
const { id: e, method: a, action: l, className: r, fields: d } = t;
let i = `<form id="${e}" method="${a}" action="${l}" class="${r}">`;
return d.forEach((s) => {
i += this.createFieldHTML(s);
}), i += `<button type="submit" class="submit-button">${this.editor.t("Submit")}</button>`, i += "</form>", i;
}
/**
* Create field HTML
*/
createFieldHTML(t) {
const { id: e, type: a, label: l, options: r, validation: d } = t;
let i = `<div class="form-field${d != null && d.required ? " required-field" : ""}">`;
l && (i += `<label for="${e}">${l}</label>`);
const s = this.buildCommonAttributes(t);
switch (a) {
case "text":
case "email":
case "password":
case "number":
case "tel":
case "url":
case "date":
case "time":
case "datetime-local":
case "month":
case "week":
case "color":
case "range":
i += `<input type="${a}" id="${e}" name="${(r == null ? void 0 : r.name) || e}"${s}>`;
break;
case "textarea":
const $ = r != null && r.rows ? ` rows="${r.rows}"` : "", u = r != null && r.cols ? ` cols="${r.cols}"` : "";
i += `<textarea id="${e}" name="${(r == null ? void 0 : r.name) || e}"${s}${$}${u}></textarea>`;
break;
case "select":
const g = r != null && r.multiple ? " multiple" : "";
i += `<select id="${e}" name="${(r == null ? void 0 : r.name) || e}"${s}${g}>`, r != null && r.options && r.options.forEach((m) => {
i += `<option value="${m}">${m}</option>`;
}), i += "</select>";
break;
case "checkbox":
const f = r != null && r.checked ? " checked" : "", h = (r == null ? void 0 : r.value) || l || "";
h ? (i += '<div class="checkbox-container">', i += `<input type="checkbox" id="${e}" name="${(r == null ? void 0 : r.name) || e}"${s}${f}>`, i += `<label for="${e}">${h}</label>`, i += "</div>") : i += `<input type="checkbox" id="${e}" name="${(r == null ? void 0 : r.name) || e}"${s}${f}>`;
break;
case "radio":
r != null && r.options && r.options.forEach((m, v) => {
const o = `${e}_${v}`, k = (r == null ? void 0 : r.value) === m ? " checked" : "";
i += `<input type="radio" id="${o}" name="${(r == null ? void 0 : r.name) || e}" value="${m}"${s}${k}>`, i += `<label for="${o}">${m}</label>`;
});
break;
case "file":
const b = r != null && r.accept ? ` accept="${r.accept}"` : "", F = r != null && r.multiple ? " multiple" : "";
i += `<input type="file" id="${e}" name="${(r == null ? void 0 : r.name) || e}"${s}${b}${F}>`;
break;
case "hidden":
i += `<input type="hidden" id="${e}" name="${(r == null ? void 0 : r.name) || e}"${s}>`;
break;
case "image":
const x = r != null && r.src ? ` src="${r.src}"` : "", C = r != null && r.alt ? ` alt="${r.alt}"` : "";
i += `<input type="image" id="${e}" name="${(r == null ? void 0 : r.name) || e}"${s}${x}${C}>`;
break;
case "button":
case "submit":
case "reset":
i += `<button type="${a}" id="${e}" name="${(r == null ? void 0 : r.name) || e}"${s}>${l || a}</button>`;
break;
default:
i += `<input type="text" id="${e}" name="${(r == null ? void 0 : r.name) || e}"${s}>`;
}
return i += "</div>", i;
}
/**
* Build common attributes for form fields
*/
buildCommonAttributes(t) {
const { options: e, validation: a } = t;
let l = "";
return e != null && e.placeholder && (l += ` placeholder="${e.placeholder}"`), e != null && e.value && (l += ` value="${e.value}"`), e != null && e.className && (l += ` class="${e.className}"`), e != null && e.readonly && (l += " readonly"), e != null && e.disabled && (l += " disabled"), e != null && e.size && (l += ` size="${e.size}"`), e != null && e.maxlength && (l += ` maxlength="${e.maxlength}"`), e != null && e.minlength && (l += ` minlength="${e.minlength}"`), (e == null ? void 0 : e.min) !== void 0 && (l += ` min="${e.min}"`), (e == null ? void 0 : e.max) !== void 0 && (l += ` max="${e.max}"`), (e == null ? void 0 : e.step) !== void 0 && (l += ` step="${e.step}"`), e != null && e.autocomplete && (l += ` autocomplete="${e.autocomplete}"`), a != null && a.required && (l += " required"), a != null && a.pattern && (l += ` pattern="${a.pattern}"`), a && (l += ` data-validation="${JSON.stringify(a)}"`), l;
}
/**
* Загружает конфигурацию формы
*/
loadFormConfig(t) {
this.clearFields(), this.currentFormId = t.id, this.currentFormAction = t.action || "", this.currentFormMethod = t.method || "POST", this.fieldsConfig = [...t.fields];
}
/**
* Генерирует уникальный ID для поля
*/
generateFieldId() {
return `field_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
/**
* Генерирует уникальный ID для формы
*/
generateFormId() {
return `form_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// Методы для работы с опциями (для обратной совместимости)
/**
* Parse form element
*/
parseForm(t) {
if (t.tagName !== "FORM") return null;
const e = t, a = [];
return e.querySelectorAll("input, select, textarea").forEach((r, d) => {
const i = this.parseField(
r,
d
);
i && a.push(i);
}), {
id: e.id || `form_${Date.now()}`,
method: e.method,
action: e.action,
className: e.className,
fields: a
};
}
/**
* Parse field element
*/
parseField(t, e) {
let a;
if (t.tagName === "TEXTAREA" ? a = "textarea" : t.tagName === "SELECT" ? a = "select" : a = t.type || "text", a === "submit" || a === "reset")
return null;
const l = {
id: t.id || `field_${Date.now()}_${e}`,
type: a,
label: this.getFieldLabel(t),
options: {
name: t.name,
id: t.id,
placeholder: t.placeholder,
value: t.value,
className: t.className,
readonly: t.readOnly,
disabled: t.disabled,
multiple: t.multiple,
min: t.min ? parseInt(t.min) : void 0,
max: t.max ? parseInt(t.max) : void 0,
step: t.step ? parseFloat(t.step) : void 0,
rows: t.rows,
cols: t.cols,
accept: t.accept,
size: t.size,
maxlength: t.maxLength,
minlength: t.minLength,
src: t.src,
alt: t.alt
},
validation: {
required: t.required,
pattern: t.pattern,
minLength: t.minLength,
maxLength: t.maxLength,
min: t.min ? parseInt(t.min) : void 0,
max: t.max ? parseInt(t.max) : void 0,
step: t.step ? parseFloat(t.step) : void 0
},
position: e
};
if (a === "select") {
const r = t, d = [];
r.querySelectorAll("option").forEach((i) => {
d.push(i.value);
}), l.options = { ...l.options, options: d };
}
return l;
}
/**
* Get field label
*/
getFieldLabel(t) {
if (t.id) {
const a = this.editor.getDOMContext().querySelector(`label[for="${t.id}"]`);
if (a)
return a.textContent || "";
}
let e = t.previousElementSibling;
for (; e; ) {
if (e.tagName === "LABEL")
return e.textContent || "";
e = e.previousElementSibling;
}
return t.placeholder || t.name || this.editor.t("Untitled Field");
}
/**
* Обновляет action URL формы
*/
updateFormAction(t) {
this.currentFormAction = t;
}
/**
* Обновляет метод формы
*/
updateFormMethod(t) {
this.currentFormMethod = t;
}
/**
* Получает текущий action URL формы
*/
getFormAction() {
return this.currentFormAction;
}
/**
* Получает текущий метод формы
*/
getFormMethod() {
return this.currentFormMethod;
}
/**
* Возвращает текущую конфигурацию формы
*/
getFormConfig() {
return {
id: this.currentFormId || this.generateFormId(),
method: this.currentFormMethod,
action: this.currentFormAction,
className: "generated-form",
fields: this.getFields()
};
}
/**
* Загружает форму из HTMLElement
*/
loadForm(t) {
const e = this.parseForm(t);
e && this.loadFormConfig(e);
}
}
export {
N as FormManager
};