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

183 lines (182 loc) 6.95 kB
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 };