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

773 lines (772 loc) 27.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 v = Object.defineProperty; var k = (s, e, l) => e in s ? v(s, e, { enumerable: !0, configurable: !0, writable: !0, value: l }) : s[e] = l; var d = (s, e, l) => k(s, typeof e != "symbol" ? e + "" : e, l); import { ContextMenu as y } from "../../../core/ui/ContextMenu.mjs"; import { AddRowCommand as u } from "../commands/AddRowCommand.mjs"; import { AddColumnCommand as C } from "../commands/AddColumnCommand.mjs"; import { DeleteRowCommand as g } from "../commands/DeleteRowCommand.mjs"; import { DeleteColumnCommand as x } from "../commands/DeleteColumnCommand.mjs"; import { DeleteTableCommand as A } from "../commands/DeleteTableCommand.mjs"; import { StyleTableCellCommand as S } from "../commands/StyleTableCellCommand.mjs"; import { CopyTableCommand as T } from "../commands/CopyTableCommand.mjs"; import { MergeCellsCommand as p } from "../commands/MergeCellsCommand.mjs"; import { SplitCellCommand as b } from "../commands/SplitCellCommand.mjs"; import { SetCellBorderCommand as M } from "../commands/SetCellBorderCommand.mjs"; import { InsertCellCommand as h } from "../commands/InsertCellCommand.mjs"; import { FormatCellCommand as E } from "../commands/FormatCellCommand.mjs"; import { ShowTablePropertiesCommand as z } from "../commands/ShowTablePropertiesCommand.mjs"; import { ExportTableCommand as D } from "../commands/ExportTableCommand.mjs"; import { ImportTableCommand as I } from "../commands/ImportTableCommand.mjs"; import { SortTableCommand as L } from "../commands/SortTableCommand.mjs"; import { ResizeColumnsCommand as H } from "../commands/ResizeColumnsCommand.mjs"; import { SelectAllCommand as R } from "../commands/SelectAllCommand.mjs"; import { CopyCellCommand as N } from "../commands/CopyCellCommand.mjs"; import { CutCellCommand as P } from "../commands/CutCellCommand.mjs"; import { PasteCellCommand as O } from "../commands/PasteCellCommand.mjs"; import { DeleteCellContentCommand as V } from "../commands/DeleteCellContentCommand.mjs"; import { SelectRowCommand as j } from "../commands/SelectRowCommand.mjs"; import { SelectColumnCommand as B } from "../commands/SelectColumnCommand.mjs"; import { AutoFitCommand as F } from "../commands/AutoFitCommand.mjs"; import { ClearTableCommand as Q } from "../commands/ClearTableCommand.mjs"; import { DeleteCellCommand as J } from "../commands/DeleteCellCommand.mjs"; import { LazyTableModal as f } from "./LazyTableModal.mjs"; import $ from "../../../icons/copy.svg.mjs"; import U from "../../../icons/delete-table.svg.mjs"; import q from "../../../icons/delete-row.svg.mjs"; import W from "../../../icons/delete-column.svg.mjs"; import { PopupManager as X } from "../../../core/ui/PopupManager.mjs"; const n = { arrowUp: "↑", arrowDown: "↓", arrowLeft: "←", arrowRight: "→" }, Z = "🟰", _ = "✂️", w = "🎨", G = "📤", K = "📥", Y = "🔄"; class Ne { constructor(e) { d(this, "contextMenu"); d(this, "activeCell", null); d(this, "popupManager"); d(this, "colorPicker", null); d(this, "editor"); this.editor = e, this.contextMenu = new y( e, [ { type: "group", groupTitle: e.t("Insert"), subMenu: [ { title: e.t("Add Row"), icon: "➕", subMenu: [ { title: e.t("Add Row Above"), icon: n.arrowUp, action: "add-row-above", onClick: () => this.executeAction("add-row-above"), hotkey: "Ctrl+Shift+↑" }, { title: e.t("Add Row Below"), icon: n.arrowDown, action: "add-row-below", onClick: () => this.executeAction("add-row-below"), hotkey: "Ctrl+Shift+↓" } ] }, { title: e.t("Add Column"), icon: "➕", subMenu: [ { title: e.t("Add Column Left"), icon: n.arrowLeft, action: "add-col-left", onClick: () => this.executeAction("add-col-left"), hotkey: "Ctrl+Shift+←" }, { title: e.t("Add Column Right"), icon: n.arrowRight, action: "add-col-right", onClick: () => this.executeAction("add-col-right"), hotkey: "Ctrl+Shift+→" } ] }, { title: e.t("Insert Cell"), icon: "➕", subMenu: [ { title: e.t("Insert Cell Left"), icon: n.arrowLeft, action: "insert-cell-left", onClick: () => this.executeAction("insert-cell-left"), hotkey: "Alt+Shift+←" }, { title: e.t("Insert Cell Right"), icon: n.arrowRight, action: "insert-cell-right", onClick: () => this.executeAction("insert-cell-right"), hotkey: "Alt+Shift+→" }, { title: e.t("Insert Cell Above"), icon: n.arrowUp, action: "insert-cell-top", onClick: () => this.executeAction("insert-cell-top"), hotkey: "Alt+Shift+↑" }, { title: e.t("Insert Cell Below"), icon: n.arrowDown, action: "insert-cell-bottom", onClick: () => this.executeAction("insert-cell-bottom"), hotkey: "Alt+Shift+↓" } ] } ] }, { type: "divider" }, { type: "group", groupTitle: e.t("Delete"), subMenu: [ { title: e.t("Delete Elements"), icon: "🗑️", subMenu: [ { title: e.t("Delete Row"), icon: q, action: "delete-row", onClick: () => this.executeAction("delete-row"), variant: "danger", hotkey: "Delete" }, { title: e.t("Delete Column"), icon: W, action: "delete-col", onClick: () => this.executeAction("delete-col"), variant: "danger", hotkey: "Backspace" }, { title: e.t("Clear Cell"), icon: "🧹", action: "clear-cell", onClick: () => this.executeAction("clear-cell"), variant: "warning", hotkey: "Ctrl+Shift+Backspace" }, { title: e.t("Delete Cell"), icon: "🗑️", action: "delete-cell", onClick: () => this.executeAction("delete-cell"), variant: "danger", hotkey: "Delete" }, { title: e.t("Delete Table"), icon: U, action: "delete-table", onClick: () => this.executeAction("delete-table"), variant: "danger", hotkey: "Ctrl+Delete" } ] } ] }, { type: "divider" }, { type: "group", groupTitle: e.t("Table Operations"), subMenu: [ { title: e.t("Clipboard Operations"), icon: "📋", subMenu: [ { title: e.t("Copy Table"), icon: $, action: "copy-table", onClick: () => this.executeAction("copy-table"), hotkey: "Ctrl+C" }, { title: e.t("Copy Cell"), icon: "📋", action: "copy-cell", onClick: () => this.executeAction("copy-cell"), hotkey: "Alt+Shift+C" }, { title: e.t("Cut Cell"), icon: "✂️", action: "cut-cell", onClick: () => this.executeAction("cut-cell"), hotkey: "Ctrl+X" }, { title: e.t("Paste Cell"), icon: "📋", action: "paste-cell", onClick: () => this.executeAction("paste-cell"), hotkey: "Ctrl+V" } ] }, { title: e.t("Cell Operations"), icon: "🔗", subMenu: [ { title: e.t("Merge Cells"), icon: Z, subMenu: [ { title: e.t("Merge Horizontally"), icon: "↔", action: "merge-cells-horizontal", onClick: () => this.executeAction("merge-cells-horizontal"), hotkey: "Ctrl+Shift+Z" }, { title: e.t("Merge Vertically"), icon: "↕", action: "merge-cells-vertical", onClick: () => this.executeAction("merge-cells-vertical"), hotkey: "Ctrl+Shift+V" } ] }, { title: e.t("Split Cell"), icon: _, subMenu: [ { title: e.t("Split Horizontally"), icon: "⇹", action: "split-cell-horizontal", onClick: () => this.executeAction("split-cell-horizontal"), hotkey: "Alt+Shift+H" }, { title: e.t("Split Vertically"), icon: "⇳", action: "split-cell-vertical", onClick: () => this.executeAction("split-cell-vertical"), hotkey: "Alt+Shift+V" } ] } ] } ] }, { type: "divider" }, { type: "group", groupTitle: e.t("Selection"), subMenu: [ { title: e.t("Select Elements"), icon: "☑️", subMenu: [ { title: e.t("Select All"), icon: "☑️", action: "select-all", onClick: () => this.executeAction("select-all"), hotkey: "Ctrl+A" }, { title: e.t("Select Row"), icon: "➡️", action: "select-row", onClick: () => this.executeAction("select-row"), hotkey: "Ctrl+Shift+R" }, { title: e.t("Select Column"), icon: "⬇️", action: "select-column", onClick: () => this.executeAction("select-column"), hotkey: "Alt+Shift+Col" } ] } ] }, { type: "divider" }, { type: "group", groupTitle: e.t("Table Management"), subMenu: [ { title: e.t("Format"), icon: w, subMenu: [ { title: e.t("Format Cell"), icon: w, action: "format-cell", onClick: () => this.executeAction("format-cell"), hotkey: "Ctrl+F" }, { title: e.t("Table Properties"), icon: "⚙️", action: "table-properties", onClick: () => this.executeAction("table-properties"), hotkey: "Ctrl+Shift+P" } ] }, { title: e.t("Table Tools"), icon: "🔧", subMenu: [ { title: e.t("Sort Table"), icon: "📈", action: "sort-table", onClick: () => this.executeAction("sort-table"), hotkey: "Ctrl+S" }, { title: e.t("Resize Columns"), icon: "📏", action: "resize-columns", onClick: () => this.executeAction("resize-columns"), hotkey: "Ctrl+R" }, { title: e.t("Auto Fit"), icon: "🔧", action: "auto-fit", onClick: () => this.executeAction("auto-fit"), hotkey: "Ctrl+Shift+A" } ] }, { title: e.t("Data"), icon: "📊", subMenu: [ { title: e.t("Import/Export"), icon: "📊", subMenu: [ { title: e.t("Export Table"), icon: G, action: "export-table", onClick: () => this.executeAction("export-table"), variant: "success", hotkey: "Ctrl+E" }, { title: e.t("Import Table"), icon: K, action: "import-table", onClick: () => this.executeAction("import-table"), variant: "success", hotkey: "Ctrl+I" } ] }, { title: e.t("Edit Lazy Table"), icon: Y, action: "edit-lazy-table", onClick: () => this.executeAction("edit-lazy-table"), variant: "success", hotkey: "Ctrl+L" }, { title: e.t("Fill Table"), icon: "📥", action: "fill-table", onClick: () => this.executeAction("fill-table"), variant: "success", hotkey: "Ctrl+F" }, { title: e.t("Clear Table"), icon: "🧹", action: "clear-table", onClick: () => this.executeAction("clear-table"), variant: "warning", hotkey: "Ctrl+Shift+Delete" } ] } ] }, { type: "divider" }, { type: "custom", customHTML: `<input type="color" class="table-color-picker" title="${e.t("Background Color")}">` } ], { maxHeight: 350, maxWidth: 280, animation: !0, closeOnClickOutside: !0, closeOnEscape: !0 } ), this.popupManager = new X(e, { title: e.t("Set Cell Border"), closeOnClickOutside: !0, buttons: [ { label: e.t("Confirm"), variant: "primary", onClick: () => this.handleBorderConfirm() }, { label: e.t("Cancel"), variant: "secondary", onClick: () => this.popupManager.hide() } ] }), this.setupColorPicker(); } handleBorderConfirm() { const e = this.popupManager.getValue("border-style"); e && this.activeCell && new M(this.activeCell, e).execute(), this.popupManager.hide(); } setupColorPicker() { this.colorPicker = this.contextMenu.getElement().querySelector(".table-color-picker"), this.colorPicker && (this.colorPicker.addEventListener("input", (e) => { this.activeCell && new S(this.activeCell, { backgroundColor: e.target.value }).execute(); }), this.colorPicker.addEventListener("contextmenu", (e) => e.stopPropagation())); } executeAction(e) { if (!this.activeCell) return; const l = this.activeCell.closest(".html-editor-table"); if (!l || !(l instanceof HTMLElement)) return; let t = null; switch (e) { case "add-row-above": t = new u(l, this.activeCell, !0); break; case "add-row-below": t = new u(l, this.activeCell, !1); break; case "add-col-left": t = new C(l, this.activeCell, !0); break; case "add-col-right": t = new C(l, this.activeCell, !1); break; case "insert-cell-left": t = new h(this.editor, this.activeCell, "left"); break; case "insert-cell-right": t = new h(this.editor, this.activeCell, "right"); break; case "insert-cell-top": t = new h(this.editor, this.activeCell, "top"); break; case "insert-cell-bottom": t = new h(this.editor, this.activeCell, "bottom"); break; case "delete-row": t = new g(l, this.activeCell); break; case "delete-col": t = new x(l, this.activeCell); break; case "delete-table": t = new A(l); break; case "copy-table": t = new T(l); break; case "merge-cells-horizontal": t = new p(l, this.activeCell, "horizontal"); break; case "merge-cells-vertical": t = new p(l, this.activeCell, "vertical"); break; case "split-cell-horizontal": t = new b(l, this.activeCell, "horizontal"); break; case "split-cell-vertical": t = new b(l, this.activeCell, "vertical"); break; case "format-cell": t = new E(this.editor, this.activeCell); break; case "table-properties": t = new z(this.editor, l); break; case "export-table": t = new D(this.editor, l); break; case "import-table": t = new I(this.editor); break; case "sort-table": t = new L(this.editor, l); break; case "resize-columns": t = new H(this.editor, l); break; case "select-all": t = new R(this.editor, l); break; case "copy-cell": t = new N(this.editor, this.activeCell); break; case "cut-cell": t = new P(this.editor, this.activeCell); break; case "paste-cell": t = new O(this.editor, this.activeCell); break; case "delete-cell": t = new J(this.editor, this.activeCell); break; case "select-row": t = new j(this.editor, this.activeCell); break; case "select-column": t = new B(this.editor, this.activeCell); break; case "auto-fit": t = new F(this.editor, l); break; case "clear-table": t = new Q(this.editor, l); break; case "clear-cell": t = new V(this.editor, this.activeCell); break; case "edit-lazy-table": this.editLazyTable(l), this.contextMenu.hide(); break; case "fill-table": this.fillTable(l), this.contextMenu.hide(); break; } t && t.execute(); } editLazyTable(e) { const l = { url: e.getAttribute("data-lazy-url") || "", format: e.getAttribute("data-lazy-format") || "json", hasHeaders: e.getAttribute("data-lazy-headers") !== "false", delimiter: e.getAttribute("data-lazy-delimiter") || ",", tableId: e.id || "", isEdit: !0, onSave: (i) => { var c; i.url && e.setAttribute("data-lazy-url", i.url), i.format && e.setAttribute("data-lazy-format", i.format), i.delimiter && e.setAttribute("data-lazy-delimiter", i.delimiter), i.hasHeaders !== void 0 && e.setAttribute("data-lazy-headers", String(i.hasHeaders)), i.tableId && (e.id = i.tableId); const o = e.nextElementSibling; o && o.tagName === "SCRIPT" && o.remove(); const a = document.createElement("script"); a.type = "text/javascript", a.textContent = this.generateLazyScript(e.id, { url: e.getAttribute("data-lazy-url") || "", format: e.getAttribute("data-lazy-format") || "json", hasHeaders: e.getAttribute("data-lazy-headers") !== "false", delimiter: e.getAttribute("data-lazy-delimiter") || "," }), (c = e.parentNode) == null || c.insertBefore(a, e.nextSibling), setTimeout(() => { const r = document.createElement("script"); r.textContent = a.textContent, document.head.appendChild(r), document.head.removeChild(r); }, 100); } }; new f(this.editor, document.createRange(), l).show(); } fillTable(e) { new f(this.editor, document.createRange(), { isFillMode: !0, onSave: (t) => { this.loadAndFillTable(e, t); } }).show(); } async loadAndFillTable(e, l) { try { e.innerHTML = '<div class="table-row"><div class="table-cell" style="text-align:center;padding:20px;">Загрузка данных...</div></div>'; const t = await fetch(l.url); if (!t.ok) throw new Error(`HTTP ${t.status}`); let i; if (l.format === "json") i = await t.json(); else { const o = await t.text(); i = this.parseCSV(o, l.delimiter || ","); } this.renderTableData(e, i, l.format, l.hasHeaders); } catch (t) { e.innerHTML = `<div class="table-row"><div class="table-cell" style="text-align:center;padding:20px;color:#e74c3c;">Ошибка загрузки: ${t instanceof Error ? t.message : "Неизвестная ошибка"}</div></div>`; } } parseCSV(e, l) { return e.trim().split(` `).map((i) => { const o = []; let a = "", c = !1; for (let r = 0; r < i.length; r++) { const m = i[r]; m === '"' ? c = !c : m === l && !c ? (o.push(a.trim()), a = "") : a += m; } return o.push(a.trim()), o; }); } renderTableData(e, l, t, i) { e.innerHTML = "", t === "json" ? this.renderJSONData(e, l) : this.renderCSVData(e, l, i); } renderJSONData(e, l) { if (!Array.isArray(l) || !l.length) { e.innerHTML = '<div class="table-row"><div class="table-cell">Нет данных</div></div>'; return; } const t = Object.keys(l[0]), i = document.createElement("div"); i.className = "table-header-row", t.forEach((o) => { const a = document.createElement("div"); a.className = "table-header-cell", a.textContent = o, i.appendChild(a); }), e.appendChild(i), l.forEach((o) => { const a = document.createElement("div"); a.className = "table-row", t.forEach((c) => { const r = document.createElement("div"); r.className = "table-cell", r.textContent = o[c] || "", a.appendChild(r); }), e.appendChild(a); }); } renderCSVData(e, l, t) { l.forEach((i, o) => { const a = document.createElement("div"); a.className = o === 0 && t ? "table-header-row" : "table-row", i.forEach((c) => { const r = document.createElement("div"); r.className = o === 0 && t ? "table-header-cell" : "table-cell", r.textContent = c, a.appendChild(r); }), e.appendChild(a); }); } generateLazyScript(e, l) { return ` (function() { var table = document.getElementById('${e}'); if (!table) return; var url = table.getAttribute('data-lazy-url'); var format = table.getAttribute('data-lazy-format'); var delimiter = table.getAttribute('data-lazy-delimiter') || ','; var hasHeaders = table.getAttribute('data-lazy-headers') !== 'false'; function showLoading() { table.innerHTML = '<div class="table-row"><div class="table-cell" style="text-align:center;padding:20px;">Загрузка данных...</div></div>'; } function showError(msg) { table.innerHTML = '<div class="table-row"><div class="table-cell" style="text-align:center;padding:20px;color:#e74c3c;">Ошибка загрузки: ' + msg + '</div></div>'; } function parseCSV(csv, delimiter) { var rows = csv.trim().split('\\n'); return rows.map(function(row) { var result = [], current = '', inQuotes = false; for (var i = 0; i < row.length; i++) { var char = row[i]; if (char === '"') inQuotes = !inQuotes; else if (char === delimiter && !inQuotes) { result.push(current.trim()); current = ''; } else current += char; } result.push(current.trim()); return result; }); } function renderCSV(data, hasHeaders) { table.innerHTML = ''; data.forEach(function(row, rowIndex) { var rowDiv = document.createElement('div'); rowDiv.className = rowIndex === 0 && hasHeaders ? 'table-header-row' : 'table-row'; row.forEach(function(cellData) { var cell = document.createElement('div'); cell.className = rowIndex === 0 && hasHeaders ? 'table-header-cell' : 'table-cell'; cell.textContent = cellData; rowDiv.appendChild(cell); }); table.appendChild(rowDiv); }); } function renderJSON(data) { table.innerHTML = ''; if (!Array.isArray(data) || !data.length) { table.innerHTML = '<div class="table-row"><div class="table-cell">Нет данных</div></div>'; return; } var headers = Object.keys(data[0]); var headerRow = document.createElement('div'); headerRow.className = 'table-header-row'; headers.forEach(function(header) { var cell = document.createElement('div'); cell.className = 'table-header-cell'; cell.textContent = header; headerRow.appendChild(cell); }); table.appendChild(headerRow); data.forEach(function(row) { var rowDiv = document.createElement('div'); rowDiv.className = 'table-row'; headers.forEach(function(header) { var cell = document.createElement('div'); cell.className = 'table-cell'; cell.textContent = row[header] || ''; rowDiv.appendChild(cell); }); table.appendChild(rowDiv); }); } showLoading(); fetch(url).then(function(resp) { if (!resp.ok) throw new Error('HTTP ' + resp.status); return format === 'json' ? resp.json() : resp.text(); }).then(function(data) { if (format === 'json') renderJSON(data); else renderCSV(parseCSV(data, delimiter), hasHeaders); }).catch(function(e) { showError(e.message); }); })(); `; } show(e, l, t) { this.activeCell = e, this.contextMenu.show(e, l, t); } hide() { this.contextMenu.hide(); } destroy() { this.contextMenu.destroy(), this.popupManager.destroy(); } } export { Ne as TableContextMenu, G as exportIcon, w as formatIcon, K as importIcon, Y as lazyIcon, Z as mergeIcon, n as navigationIcons, _ as splitIcon };