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
183 lines (182 loc) • 6.95 kB
JavaScript
var a = Object.defineProperty;
var l = (i, t, n) => t in i ? a(i, t, { enumerable: !0, configurable: !0, writable: !0, value: n }) : i[t] = n;
var e = (i, t, n) => l(i, typeof t != "symbol" ? t + "" : t, n);
import { PopupManager as c } from "../../core/ui/PopupManager.mjs";
import { createToolbarButton as d } from "../ToolbarPlugin/utils.mjs";
import h from "../../icons/collaboration.svg.mjs";
import { createContainer as u, createLink as p } from "../../utils/helpers.mjs";
function r(i = 8) {
const t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let n = "";
for (let o = 0; o < i; o++)
n += t.charAt(Math.floor(Math.random() * t.length));
return n;
}
class I {
constructor(t = {}) {
e(this, "name", "collaboration");
e(this, "hotkeys", [
{
keys: "Ctrl+Shift+C",
description: "Enable collaboration mode",
command: "collaboration",
icon: "👥"
}
]);
e(this, "editor", null);
e(this, "ws", null);
e(this, "docId", null);
e(this, "popup", null);
e(this, "options");
e(this, "toolbarButton", null);
e(this, "isExternalUpdate", !1);
// Флаг для отслеживания внешних изменений
e(this, "contentVersion", 0);
// Текущая версия контента
e(this, "lastContent", "");
// Последний известный контент
e(this, "unsubscribeFromContentChange", null);
// Функция для отписки от изменений
e(this, "status", "");
e(this, "userId", "");
e(this, "debouncedSendUpdate", this.debounce((t) => {
this.ws && this.docId && this.status === "Connected" && (this.ws.send(
JSON.stringify({
type: "update",
docId: this.docId,
userId: this.userId,
content: t,
version: this.contentVersion
})
), console.log("Content sent:", t));
}, 300));
this.options = {
serverUrl: "ws://localhost:8080",
autoStart: !0,
...t
};
}
initialize(t) {
const n = new URLSearchParams(window.location.search);
this.userId = n.get("userId") ?? r(), this.editor = t, this.popup = new c(this.editor, {
title: "Collaboration",
closeOnClickOutside: !1,
buttons: [
{
label: "Start Collaboration",
variant: "primary",
onClick: () => this.startCollaboration()
}
],
items: [
{
type: "custom",
id: "collaboration-content",
content: () => this.createCollaborationContent()
}
]
}), this.options.autoStart && this.setupCollaboration(), this.addToolbarButton(), this.editor.on("collaboration", () => {
var o;
(o = this.popup) == null || o.show();
});
}
addToolbarButton() {
var n;
const t = document.querySelector(".editor-toolbar");
t && (this.toolbarButton = d({
icon: h,
title: (n = this.editor) == null ? void 0 : n.t("Collaboration"),
onClick: () => {
var o;
return (o = this.popup) == null ? void 0 : o.show();
}
}), t.appendChild(this.toolbarButton));
}
updateConnectionStatus(t) {
if (this.status = t, !this.editor) return;
const n = this.editor.getInnerContainer().querySelector(".collaboration-status");
n && (n.textContent = this.editor.t("Collaboration") + ": " + t);
}
setupCollaboration() {
if (!this.editor || !this.options.serverUrl)
return;
const t = new URLSearchParams(window.location.search);
this.docId = t.get("docId") ?? null, this.docId && (this.ws = new WebSocket(this.options.serverUrl), this.ws.onopen = () => {
var n, o;
console.log("WebSocket connected"), this.updateConnectionStatus("Connected"), (o = this.ws) == null || o.send(
JSON.stringify({
type: "join",
docId: this.docId,
userId: this.userId,
content: (n = this.editor) == null ? void 0 : n.getHtml()
})
);
}, this.ws.onmessage = (n) => {
var s;
const o = JSON.parse(n.data);
if (o.type === "init" || o.type === "update") {
if (console.log("onmessage", o), o.userId === this.userId) {
console.log("Ignoring self update");
return;
}
if (!o.content || o.content === "") {
console.log("Ignoring empty version:", o.content);
return;
}
if (o.version && o.version < this.contentVersion) {
console.log("Ignoring older version:", o.version);
return;
}
if (this.normalizeHtml(o.content) === this.normalizeHtml(this.lastContent)) {
console.log("Content is the same, ignoring update");
return;
}
this.isExternalUpdate = !0, (s = this.editor) == null || s.setHtml(o.content), this.lastContent = o.content, this.contentVersion = o.version, this.isExternalUpdate = !1;
}
}, this.ws.onerror = (n) => {
console.error("WebSocket error:", n), this.updateConnectionStatus("Connection error");
}, this.ws.onclose = () => {
console.log("WebSocket disconnected. Reconnecting..."), this.updateConnectionStatus("Reconnecting..."), setTimeout(() => this.setupCollaboration(), 3e3);
}, this.unsubscribeFromContentChange = this.editor.subscribeToContentChange(
(n) => {
this.handleContentChange(n);
}
));
}
debounce(t, n) {
let o;
return (...s) => {
o && clearTimeout(o), o = setTimeout(() => {
t(...s);
}, n);
};
}
handleContentChange(t) {
if (!t || this.isExternalUpdate)
return;
const n = this.normalizeHtml(t), o = this.normalizeHtml(this.lastContent);
n !== o && (console.log("Content changed, scheduling update"), this.lastContent = t, this.contentVersion += 1, this.debouncedSendUpdate(t));
}
/**
* Нормализует HTML, удаляя лишние пробелы и переносы строк
* @param html Исходный HTML
* @returns Нормализованный HTML
*/
normalizeHtml(t) {
return t.replace(/\s+/g, " ").replace(/>\s+</g, "><").replace(/\s+</g, "<").replace(/>\s+/g, ">").trim();
}
createCollaborationContent() {
const t = u("p-4"), n = r(), o = `${window.location.origin}${window.location.pathname}?docId=${n}`, s = p(o, o, "_blank");
return t.appendChild(document.createTextNode("Share this link to collaborate: ")), t.appendChild(s), t;
}
startCollaboration() {
const t = r();
window.location.href = `${window.location.origin}${window.location.pathname}?docId=${t}}`;
}
destroy() {
this.ws && (this.ws.close(), this.ws = null), this.popup && (this.popup.destroy(), this.popup = null), this.unsubscribeFromContentChange && (this.unsubscribeFromContentChange(), this.unsubscribeFromContentChange = null), this.editor = null;
}
}
export {
I as CollaborationPlugin
};