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
198 lines (197 loc) • 9.03 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 l = Object.defineProperty;
var a = (c, t, e) => t in c ? l(c, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : c[t] = e;
var s = (c, t, e) => a(c, typeof t != "symbol" ? t + "" : t, e);
/* empty css */
/* empty css */
import { BlockContextMenu as h } from "./components/BlockContextMenu.mjs";
import { BlockCommand as r } from "./commands/BlockCommand.mjs";
import { MergeBlocksCommand as d } from "./commands/MergeBlocksCommand.mjs";
import { createToolbarButton as k } from "../ToolbarPlugin/utils.mjs";
import u from "../../icons/block.svg.mjs";
import { Resizer as b } from "../../utils/Resizer.mjs";
class C {
constructor() {
s(this, "name", "block");
s(this, "hotkeys", [
{ keys: "Ctrl+Alt+N", description: "Insert block", command: "block", icon: "🧱" },
{ keys: "Ctrl+Alt+T", description: "Insert text block", command: "block-text", icon: "📝" },
{ keys: "Ctrl+Alt+C", description: "Insert container", command: "block-container", icon: "📦" }
]);
s(this, "editor", null);
s(this, "contextMenu", null);
s(this, "activeBlock", null);
s(this, "currentResizer", null);
s(this, "isProcessing", !1);
s(this, "toolbarButton", null);
}
initialize(t) {
this.contextMenu = new h(t), this.editor = t, this.addToolbarButton(), this.setupBlockEvents(), this.setupKeyboardEvents(), this.editor.on("block", () => {
this.insertBlock();
}), this.editor.on("block-text", () => {
this.insertTextBlock();
}), this.editor.on("block-container", () => {
this.insertContainerBlock();
});
}
addToolbarButton() {
var e, o;
const t = (e = this.editor) == null ? void 0 : e.getToolbar();
t && (this.toolbarButton = k({
icon: u,
title: ((o = this.editor) == null ? void 0 : o.t("Insert Block")) || "Insert Block",
onClick: () => this.insertBlock()
}), t.appendChild(this.toolbarButton));
}
setupBlockEvents() {
if (!this.editor) return;
const t = this.editor.getContainer();
this.handleBlockClick = this.handleBlockClick.bind(this), this.handleContextMenu = this.handleContextMenu.bind(this), this.handleBlockKeydown = this.handleBlockKeydown.bind(this), this.handleBlockBlur = this.handleBlockBlur.bind(this), this.handleBlockFocus = this.handleBlockFocus.bind(this), this.handleBlockMouseDown = this.handleBlockMouseDown.bind(this), t.addEventListener("click", this.handleBlockClick), t.addEventListener("contextmenu", this.handleContextMenu), t.addEventListener("keydown", this.handleBlockKeydown), t.addEventListener("blur", this.handleBlockBlur, !0), t.addEventListener("focus", this.handleBlockFocus, !0), t.addEventListener("mousedown", this.handleBlockMouseDown);
}
setupKeyboardEvents() {
this.editor && this.editor.on("keydown", (t) => {
if (t.key === "Enter" && !t.shiftKey) {
const e = t.target, o = e.closest(".editor-block");
o && e.classList.contains("block-content") && (t.preventDefault(), this.createNewBlockAfter(o));
}
if (t.key === "Tab") {
const e = t.target, o = e.closest(".editor-block");
if (o && e.classList.contains("block-content"))
if (t.preventDefault(), t.shiftKey) {
const n = o.previousElementSibling;
if (n && n.classList.contains("editor-block")) {
const i = n.querySelector(".block-content");
i && i.contentEditable === "true" && i.focus();
}
} else {
const n = o.nextElementSibling;
if (n && n.classList.contains("editor-block")) {
const i = n.querySelector(".block-content");
i && i.contentEditable === "true" && i.focus();
}
}
}
});
}
handleBlockClick(t) {
if (this.isProcessing) return;
const e = t.target.closest(".editor-block");
if (!e) {
this.deactivateBlock();
return;
}
if (this.activeBlock === e) {
const o = e.querySelector(".block-content");
e.getAttribute("data-block-type") === "text" && o && o.contentEditable !== "true" && (o.contentEditable = "true");
return;
}
this.deactivateBlock(), this.activateBlock(e), e.getAttribute("data-block-type") === "text" && this.createResizer(e);
}
handleContextMenu(t) {
var o;
const e = t.target.closest(".editor-block");
if (e instanceof HTMLElement) {
t.preventDefault();
const n = t.clientX, i = t.clientY;
(o = this.contextMenu) == null || o.show(e, n, i);
}
}
handleBlockKeydown(t) {
const e = t.target, o = e.closest(".editor-block");
o && (t.key === "Backspace" && e.classList.contains("block-content") && (e.textContent || "").length === 0 && (t.preventDefault(), this.mergeWithPreviousBlock(o)), t.key === "Delete" && e.classList.contains("block-content") && (e.textContent || "").length === 0 && (t.preventDefault(), this.mergeWithNextBlock(o)));
}
handleBlockBlur(t) {
var o;
const e = (o = this.editor) == null ? void 0 : o.getContainer();
e && !e.contains(t.relatedTarget) && setTimeout(() => {
this.autoMergeEmptyBlocks();
}, 100);
}
handleBlockFocus(t) {
const e = t.target, o = e.closest(".editor-block");
o && e.classList.contains("block-content") && this.activeBlock !== o && (this.deactivateBlock(), this.activateBlock(o));
}
handleBlockMouseDown(t) {
const e = t.target;
e.closest(".editor-block") && e.classList.contains("block-content") && e.contentEditable;
}
activateBlock(t) {
this.activeBlock = t, t.classList.add("active");
const e = t.querySelector(".block-content");
e && (t.getAttribute("data-block-type") === "text" ? e.contentEditable = "true" : e.contentEditable = "false");
}
deactivateBlock() {
this.activeBlock && this.activeBlock.classList.remove("active"), this.activeBlock = null, this.currentResizer && (this.currentResizer.destroy(), this.currentResizer = null);
}
createResizer(t) {
this.currentResizer && this.currentResizer.destroy(), this.currentResizer = new b(t, {
handleSize: 8,
handleColor: "#2563eb",
onResizeStart: () => {
var e;
(e = this.editor) == null || e.disableObserver(), this.isProcessing = !0;
},
onResize: (e, o) => {
t.style.width = e + "px", t.style.height = o + "px";
},
onResizeEnd: () => {
var e;
(e = this.editor) == null || e.enableObserver(), this.isProcessing = !1;
}
});
}
insertBlock() {
if (!this.editor) return;
new r(this.editor).execute();
}
insertTextBlock() {
if (!this.editor) return;
new r(this.editor).execute({ type: "text" });
}
insertContainerBlock() {
if (!this.editor) return;
new r(this.editor).execute({ type: "container" });
}
createNewBlockAfter(t) {
if (!this.editor) return;
new r(this.editor).execute({ type: "text" });
}
mergeWithPreviousBlock(t) {
const e = t.previousElementSibling;
e && e.classList.contains("editor-block") && this.mergeBlocks([e, t]);
}
mergeWithNextBlock(t) {
const e = t.nextElementSibling;
e && e.classList.contains("editor-block") && this.mergeBlocks([t, e]);
}
mergeBlocks(t) {
if (!this.editor || t.length < 2) return;
const e = new d(this.editor);
e.setData({ blocks: t }), e.execute();
}
autoMergeEmptyBlocks() {
if (!this.editor) return;
const e = this.editor.getContainer().querySelectorAll(".editor-block");
for (const o of e) {
const n = o.querySelector(".block-content");
if (n && (!n.textContent || n.textContent.trim() === "")) {
const i = o.nextElementSibling;
if (i && i.classList.contains("editor-block")) {
this.mergeBlocks([o, i]);
break;
}
}
}
}
destroy() {
var t, e, o;
if (this.editor) {
const n = this.editor.getContainer();
n.removeEventListener("click", this.handleBlockClick), n.removeEventListener("contextmenu", this.handleContextMenu), n.removeEventListener("keydown", this.handleBlockKeydown), n.removeEventListener("blur", this.handleBlockBlur, !0), n.removeEventListener("focus", this.handleBlockFocus, !0), n.removeEventListener("mousedown", this.handleBlockMouseDown);
}
(t = this.editor) == null || t.off("block"), (e = this.editor) == null || e.off("block-text"), (o = this.editor) == null || o.off("block-container"), this.currentResizer && (this.currentResizer.destroy(), this.currentResizer = null), this.contextMenu && (this.contextMenu.destroy(), this.contextMenu = null), this.deactivateBlock(), this.toolbarButton && (this.toolbarButton.remove(), this.toolbarButton = null), this.editor = null, this.activeBlock = null;
}
}
export {
C as BlockPlugin
};