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

349 lines (348 loc) 11.8 kB
/*! 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 };