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
163 lines (162 loc) • 6.66 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 p = Object.defineProperty;
var f = (h, t, e) => t in h ? p(h, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : h[t] = e;
var m = (h, t, e) => f(h, typeof t != "symbol" ? t + "" : t, e);
/* empty css */
/* empty css */
import { CommentManager as C } from "./services/CommentManager.mjs";
import { CommentMenu as g } from "./components/CommentMenu.mjs";
import { ErrorModal as M } from "./components/ErrorModal.mjs";
import { createToolbarButton as k } from "../ToolbarPlugin/utils.mjs";
import v from "../../icons/comment.svg.mjs";
import b from "../../icons/comment-marker.svg.mjs";
import { createContainer as y, createSpan as c } from "../../utils/helpers.mjs";
class S {
constructor() {
m(this, "name", "comments");
m(this, "hotkeys", [{ keys: "Ctrl+Alt+J", description: "Insert comment", command: "comment", icon: "💬" }]);
m(this, "editor", null);
m(this, "manager");
m(this, "menu", null);
m(this, "errorModal", null);
m(this, "tooltip");
m(this, "toolbarButton", null);
this.manager = new C(), this.tooltip = this.createTooltip();
}
initialize(t) {
this.errorModal = new M(t), this.menu = new g(t), this.editor = t, this.editor.getDOMContext().appendChild(this.tooltip), this.addToolbarButton(), this.setupEventListeners(), this.editor.on("comment", () => {
this.addComment();
});
}
createTooltip() {
const t = y("comment-tooltip");
return t.style.display = "none", t;
}
addToolbarButton() {
var e, o;
const t = (e = this.editor) == null ? void 0 : e.getToolbar();
t && (this.toolbarButton = k({
icon: v,
title: (o = this.editor) == null ? void 0 : o.t("Insert Comment"),
onClick: () => this.addComment()
}), t.appendChild(this.toolbarButton));
}
setupEventListeners() {
this.editor && (this.handleMouseOver = this.handleMouseOver.bind(this), this.handleMouseOut = this.handleMouseOut.bind(this), this.handleClick = this.handleClick.bind(this));
}
handleMouseOver(t) {
const e = t.target.closest(".comment-marker");
if (e instanceof HTMLElement) {
const o = e.getAttribute("data-comment-id");
o && this.showTooltip(e, o);
}
}
handleMouseOut(t) {
t.target.closest(".comment-marker") && this.hideTooltip();
}
handleClick(t) {
const e = t.target.closest(".comment-marker");
if (e instanceof HTMLElement) {
const o = e.getAttribute("data-comment-id");
o && this.editComment(o);
}
}
showTooltip(t, e) {
const o = this.manager.getComment(e);
if (!o) return;
this.tooltip.textContent = o.content, this.tooltip.style.display = "block";
const n = t.getBoundingClientRect();
this.tooltip.style.left = `${n.left + n.width / 2}px`, this.tooltip.style.top = `${n.bottom + 8}px`;
}
hideTooltip() {
this.tooltip.style.display = "none";
}
addComment() {
var o, n, l, r, s, i;
if (!this.editor) {
(o = this.errorModal) == null || o.show("Editor not initialized");
return;
}
const t = (n = this.editor.getTextFormatter()) == null ? void 0 : n.getSelection();
if (!t || !t.rangeCount) {
(l = this.errorModal) == null || l.show(this.editor.t("Please select some text to comment on"));
return;
}
const e = t.getRangeAt(0);
if (!this.isRangeInsideEditor(e)) {
(r = this.errorModal) == null || r.show(this.editor.t("Please select text inside the editor"));
return;
}
if (e.collapsed) {
(s = this.errorModal) == null || s.show(this.editor.t("Please select some text to comment on"));
return;
}
(i = this.menu) == null || i.show((a, d) => {
if (d === "save") {
const u = this.manager.createComment(a);
this.insertCommentMarker(u.id, e);
}
});
}
isRangeInsideEditor(t) {
var o;
const e = (o = this.editor) == null ? void 0 : o.getContainer();
return e ? e.contains(t.startContainer) && e.contains(t.endContainer) : !1;
}
editComment(t) {
var o;
const e = this.manager.getComment(t);
e && ((o = this.menu) == null || o.show(
(n, l) => {
var r;
if (l === "save")
this.manager.updateComment(t, n);
else if (l === "delete") {
this.manager.deleteComment(t);
const s = (r = this.editor) == null ? void 0 : r.getContainer().querySelector(`[data-comment-id="${t}"]`);
if (s) {
const i = s.previousElementSibling;
if (i != null && i.classList.contains("commented-text")) {
const a = i.parentNode;
if (a) {
for (; i.firstChild; )
a.insertBefore(i.firstChild, i);
i.remove(), s.remove();
}
}
}
}
},
e.content,
!0
));
}
insertCommentMarker(t, e) {
var o, n, l;
try {
const r = e.cloneContents(), s = c("commented-text");
s.appendChild(r);
const i = c("comment-marker");
i.setAttribute("data-comment-id", t), i.innerHTML = b;
const a = document.createDocumentFragment();
a.appendChild(s), a.appendChild(i), e.deleteContents(), e.insertNode(a), e.setStartAfter(i), e.setEndAfter(i);
const d = (o = this.editor.getTextFormatter()) == null ? void 0 : o.getSelection();
d && (d.removeAllRanges(), d.addRange(e));
} catch (r) {
console.error("Failed to insert comment:", r), (l = this.errorModal) == null || l.show(
((n = this.editor) == null ? void 0 : n.t("Failed to insert comment. Please try selecting a simpler text range.")) ?? ""
);
}
}
destroy() {
var t;
if (this.editor) {
const e = this.editor.getContainer();
e.removeEventListener("mouseover", this.handleMouseOver), e.removeEventListener("mouseout", this.handleMouseOut), e.removeEventListener("click", this.handleClick);
}
this.tooltip && this.tooltip.parentElement && this.tooltip.parentElement.removeChild(this.tooltip), this.toolbarButton && (this.toolbarButton.remove(), this.toolbarButton = null), this.menu && (this.menu.destroy(), this.menu = null), this.errorModal && (this.errorModal.destroy(), this.errorModal = null), (t = this.editor) == null || t.off("comment"), this.editor = null, this.menu = null, this.errorModal = null, this.tooltip = null, this.manager = null;
}
}
export {
S as CommentsPlugin
};