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
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 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
};