new functions
This commit is contained in:
+139
-15
@@ -17,9 +17,10 @@ export function startNoteEditor(adapter) {
|
||||
const modeToggle = document.querySelector("#mode-toggle"), passwordDialog = document.querySelector("#password-dialog"), identityDialog = document.querySelector("#identity-dialog");
|
||||
const accessLevel = document.querySelector("#access-level"), roomDetails = document.querySelector("#room-details"), roomUsers = document.querySelector("#room-users"), roomCount = document.querySelector("#room-count"), socketLatency = document.querySelector("#socket-latency"), chatMessages = document.querySelector("#chat-messages"), chatForm = document.querySelector("#chat-form"), chatInput = document.querySelector("#chat-input"), chatUnread = document.querySelector("#chat-unread"), mobileChatUnread = document.querySelector("#mobile-chat-unread");
|
||||
let unreadChat = 0;
|
||||
const compactToggle = document.querySelector("#compact-toggle"), publicTaskUpdates = document.querySelector("#public-task-updates"), unprotectPublicPage = document.querySelector("#unprotect-public-page"), participantBadges = document.querySelector("#participant-badges"), fontFamily = document.querySelector("#font-family"), fontSize = document.querySelector("#font-size"), currentUser = document.querySelector("#current-user"), userColorPicker = document.querySelector("#user-color-picker"), useGlobalColorButton = document.querySelector("#use-global-color");
|
||||
const compactToggle = document.querySelector("#compact-toggle"), authorshipColorsToggle = document.querySelector("#authorship-colors-toggle"), authorshipColorsLabel = document.querySelector("#authorship-colors-label"), saveEditorSettingsButton = document.querySelector("#save-editor-settings"), publicPageEnabled = document.querySelector("#public-page-enabled"), publicTaskUpdates = document.querySelector("#public-task-updates"), unprotectPublicPage = document.querySelector("#unprotect-public-page"), participantBadges = document.querySelector("#participant-badges"), fontFamily = document.querySelector("#font-family"), fontSize = document.querySelector("#font-size"), currentUser = document.querySelector("#current-user"), userColorPicker = document.querySelector("#user-color-picker"), useGlobalColorButton = document.querySelector("#use-global-color");
|
||||
const shareToken = new URLSearchParams(location.search).get("share"); if (shareToken) setAccessToken(adapter.access.kind, adapter.access.key, shareToken);
|
||||
let accessToken = shareToken || getAccessToken(adapter.access.kind, adapter.access.key), password = "", nickname = getNickname(), info, socket, saveTimer, applyingRemote = false, resourceUnlocked = false, uiState = readEditorState(), authorship = parseAuthorship("", "[]"), previousContent = "", globalColor = "", noteColor = "", presenceUsers = [], authorshipMode = ["full", "advanced"].includes(localStorage.getItem("rustpad:authorship-mode")) ? "full" : "simple";
|
||||
const notePreferenceKey = name => `rustpad:${name}:${adapter.access.kind}:${adapter.access.key}`;
|
||||
let accessToken = shareToken || getAccessToken(adapter.access.kind, adapter.access.key), password = "", nickname = getNickname(), info, socket, saveTimer, applyingRemote = false, resourceUnlocked = false, uiState = readEditorState(), authorship = parseAuthorship("", "[]"), previousContent = "", globalColor = "", noteColor = "", presenceUsers = [], authorshipMode = "simple", authorshipColorsEnabled = true;
|
||||
const compactLayoutQuery = window.matchMedia("(max-width: 1499px)");
|
||||
let compactView = uiState.view === "preview" ? "preview" : "edit";
|
||||
const lineToggle = document.querySelector("#line-numbers-toggle"), previewLineToggle = document.querySelector("#preview-line-numbers-toggle"); lineToggle.checked = localStorage.getItem("rustpad:line-numbers") !== "off";
|
||||
@@ -27,6 +28,12 @@ export function startNoteEditor(adapter) {
|
||||
compactToggle.checked = localStorage.getItem("rustpad:compact") !== "off";
|
||||
fontFamily.value = localStorage.getItem("rustpad:font-family") || "mono";
|
||||
fontSize.value = localStorage.getItem("rustpad:font-size") || "14";
|
||||
authorshipColorsToggle.checked = authorshipColorsEnabled;
|
||||
function updateAuthorshipControls() {
|
||||
authorshipColorsToggle.checked = authorshipColorsEnabled;
|
||||
authorshipColorsLabel.textContent = authorshipColorsEnabled ? "Colors on" : "Colors off";
|
||||
document.querySelectorAll("[data-authorship-mode]").forEach(button => button.classList.toggle("active", button.dataset.authorshipMode === authorshipMode));
|
||||
}
|
||||
function defaultColorFor(name) { let h = 0; for (const c of name || "?") h = (h * 31 + c.charCodeAt(0)) % 360; return `hsl(${h} 70% 62%)`; }
|
||||
function ownerParts(owner) { const raw = String(owner || ""); const split = raw.lastIndexOf("\u001f"); return split < 0 ? { name: raw, color: "" } : { name: raw.slice(0, split), color: raw.slice(split + 1) }; }
|
||||
function ownerName(owner) { return ownerParts(owner).name; }
|
||||
@@ -59,6 +66,10 @@ export function startNoteEditor(adapter) {
|
||||
} else {
|
||||
noteColor = readGuestColor();
|
||||
}
|
||||
authorshipMode = info.authorship_mode === "full" ? "full" : "simple";
|
||||
authorshipColorsEnabled = info.colors_enabled !== false;
|
||||
updateAuthorshipControls();
|
||||
if (saveEditorSettingsButton) saveEditorSettingsButton.disabled = !info.can_save_editor_settings;
|
||||
updateCurrentUser(); return info;
|
||||
}
|
||||
function updatePresence(users) { const entries = Array.isArray(users) ? users : []; presenceUsers = entries.map(entry => typeof entry === "string" ? { name: entry, color: "" } : entry || {}); roomCount.textContent = `${entries.length} ${entries.length === 1 ? "user" : "users"}`; roomUsers.replaceChildren(...presenceUsers.map(user => { const li = document.createElement("li"), dot = document.createElement("span"), label = document.createElement("span"); li.className = "room-user"; dot.className = "room-user__dot"; dot.style.setProperty("--owner", /^#[0-9a-f]{6}$/i.test(user.color || "") ? user.color : defaultColorFor(user.name)); label.textContent = user.name || "Guest"; li.title = label.textContent; li.append(dot, label); return li; })); if (!entries.length) { const li = document.createElement("li"); li.textContent = "No active users"; roomUsers.append(li); } renderGutter(); }
|
||||
@@ -95,12 +106,12 @@ export function startNoteEditor(adapter) {
|
||||
const lineCount = Math.max(1, (editor.value.match(/\n/g) || []).length + 1);
|
||||
const lines = Array.from({ length: lineCount });
|
||||
const owners = authorshipOwners(authorship);
|
||||
const showAuthorship = owners.length > 0;
|
||||
const showAuthorship = authorshipColorsEnabled && owners.length > 0;
|
||||
const authorsByLine = showAuthorship ? lineAuthors(editor.value, authorship) : [];
|
||||
const full = authorshipMode === "full";
|
||||
authorshipLayer.hidden = !showAuthorship;
|
||||
ownerLabels.hidden = !full || !showAuthorship;
|
||||
renderParticipantBadges(owners);
|
||||
renderParticipantBadges(authorshipColorsEnabled ? owners : []);
|
||||
document.querySelectorAll("[data-authorship-mode]").forEach(button => button.classList.toggle("active", button.dataset.authorshipMode === authorshipMode));
|
||||
editorWorkspace.dataset.authorshipMode = authorshipMode;
|
||||
const style = getComputedStyle(editor), lineHeight = parseFloat(style.lineHeight) || 29, paddingTop = parseFloat(style.paddingTop) || 24, paddingBottom = parseFloat(style.paddingBottom) || 24;
|
||||
@@ -146,10 +157,10 @@ export function startNoteEditor(adapter) {
|
||||
const title = current.getAttribute("title");
|
||||
return `}"` : ""})`;
|
||||
}
|
||||
if (tag === "br") return " ";
|
||||
if (tag === "br") return "\n";
|
||||
return body;
|
||||
};
|
||||
return [...node.childNodes].map(walk).join("").replace(/\n/g, " ").trim();
|
||||
return [...node.childNodes].map(walk).join("").replace(/\u00a0/g, " ");
|
||||
}
|
||||
|
||||
function previewCaretOffset(target) {
|
||||
@@ -219,11 +230,6 @@ export function startNoteEditor(adapter) {
|
||||
editorWorkspace.style.setProperty("--editor-font-size", `${fontSize.value}px`);
|
||||
document.body.classList.toggle("compact-editor", compactToggle.checked);
|
||||
document.body.classList.toggle("compact-note-layout", compactLayoutQuery.matches);
|
||||
document.querySelectorAll("[data-authorship-mode]").forEach(button => button.addEventListener("click", () => {
|
||||
authorshipMode = button.dataset.authorshipMode === "full" ? "full" : "simple";
|
||||
localStorage.setItem("rustpad:authorship-mode", authorshipMode);
|
||||
renderGutter();
|
||||
}));
|
||||
document.querySelectorAll("[data-view]").forEach(button => {
|
||||
const active = button.dataset.view === view;
|
||||
button.classList.toggle("active", active);
|
||||
@@ -274,7 +280,7 @@ export function startNoteEditor(adapter) {
|
||||
accessToken = shareToken || getAuthToken() || getAccessToken(adapter.access.kind, adapter.access.key);
|
||||
await loadNoteInfo();
|
||||
document.title = adapter.title(info);
|
||||
publicTaskUpdates.checked = Boolean(info.allow_public_task_updates); unprotectPublicPage.checked = Boolean(info.public_page_unprotected);
|
||||
publicPageEnabled.checked = Boolean(info.public_page_enabled); publicTaskUpdates.checked = Boolean(info.allow_public_task_updates); unprotectPublicPage.checked = Boolean(info.public_page_unprotected); updatePageControls();
|
||||
adapter.configureView?.(info);
|
||||
applyUi({ write: true, replace: true });
|
||||
updateCurrentUser();
|
||||
@@ -394,9 +400,127 @@ export function startNoteEditor(adapter) {
|
||||
mobileBubbleDrag.addEventListener("pointercancel", end);
|
||||
});
|
||||
window.addEventListener("resize", () => { if (mobileBubble?.style.left) placeMobileBubble(mobileBubble.getBoundingClientRect()); });
|
||||
window.addEventListener("popstate", () => { uiState = readEditorState(); applyUi(); }); window.addEventListener("rustpad:urlchange", updateAddressLabel); document.querySelector("#copy-link").addEventListener("click", async () => { try { await copyText(currentShareUrl(uiState)); toast("Link copied"); } catch (e) { toast(e.message); } }); document.querySelectorAll("[data-format]").forEach(b => b.addEventListener("click", () => { applyFormat(editor, b.dataset.format); b.closest("details")?.removeAttribute("open"); })); bindFormatShortcuts(editor); bindEmojiPicker({ editor, details: document.querySelector("#emoji-picker"), search: document.querySelector("#emoji-search"), categories: document.querySelector("#emoji-categories"), grid: document.querySelector("#emoji-grid"), empty: document.querySelector("#emoji-empty") }); document.querySelector("#shortcuts-button").addEventListener("click", () => document.querySelector("#shortcuts-dialog").showModal()); document.querySelector("#close-shortcuts").addEventListener("click", () => document.querySelector("#shortcuts-dialog").close()); preview.addEventListener("change", event => { const checkbox = event.target.closest(".task-checkbox"); if (!checkbox) return; const lineIndex = Number(checkbox.dataset.sourceLine) - 1; const lines = editor.value.split("\n"); if (lineIndex < 0 || lineIndex >= lines.length) return; lines[lineIndex] = lines[lineIndex].replace(/^(\s*[-*+]\s+\[)[ xX](\])/, `$1${checkbox.checked ? "x" : " "}$2`); editor.value = lines.join("\n"); editor.dispatchEvent(new Event("input", { bubbles: true })); }); preview.addEventListener("keydown", event => { const target = event.target.closest(".preview-editable"); if (!target) return; if (event.key === "Enter") { event.preventDefault(); target.blur(); return; } if (event.key === "ArrowUp" || event.key === "ArrowDown") { if (movePreviewCaret(target, event.key === "ArrowUp" ? -1 : 1)) event.preventDefault(); } }); preview.addEventListener("blur", event => { const target = event.target.closest(".preview-editable"); if (!target) return; const lineIndex = Number(target.dataset.sourceLine) - 1; if (lineIndex < 0) return; const lines = editor.value.split("\n"); const value = markdownFromPreview(target); let next; if (target.dataset.tableCell !== undefined) next = replaceTableCell(lines[lineIndex], Number(target.dataset.tableCell), value); else { const prefix = target.dataset.sourcePrefix || "", suffix = target.dataset.sourceSuffix || ""; next = prefix + value + suffix; } if (lines[lineIndex] === next) return; lines[lineIndex] = next; editor.value = lines.join("\n"); editor.setSelectionRange(editor.value.length, editor.value.length); editor.dispatchEvent(new Event("input", { bubbles: true })); }, { capture: true });
|
||||
const savePublicOptions = async () => adapter.publish(accessToken, publicTaskUpdates.checked, unprotectPublicPage.checked);
|
||||
publicTaskUpdates.addEventListener("change", async () => { publicTaskUpdates.disabled = true; try { await savePublicOptions(); toast(publicTaskUpdates.checked ? "Public task updates enabled" : "Public task updates disabled"); } catch (error) { publicTaskUpdates.checked = !publicTaskUpdates.checked; toast(error.message); } finally { publicTaskUpdates.disabled = false; } }); unprotectPublicPage.addEventListener("change", async () => { unprotectPublicPage.disabled = true; try { await savePublicOptions(); toast(unprotectPublicPage.checked ? "Published page is now unprotected" : "Published page protection enabled"); } catch (error) { unprotectPublicPage.checked = !unprotectPublicPage.checked; toast(error.message); } finally { unprotectPublicPage.disabled = false; } }); document.querySelector("#publish-page").addEventListener("click", async () => { try { const result = await savePublicOptions(); const url = new URL(result.url, location.origin).href; await copyText(url); toast("Page link copied"); window.open(url, "_blank", "noopener"); } catch (error) { toast(error.message); } });
|
||||
const cancelledPreviewEdits = new WeakSet();
|
||||
function commitPreviewEdit(target, { focusNextLine = false } = {}) {
|
||||
const lineIndex = Number(target.dataset.sourceLine) - 1;
|
||||
if (lineIndex < 0) return;
|
||||
const value = markdownFromPreview(target);
|
||||
const lines = editor.value.split("\n");
|
||||
if (target.dataset.rawSourceEdit === "true") {
|
||||
if (value === lines[lineIndex]) return;
|
||||
lines[lineIndex] = value.replace(/\n/g, "");
|
||||
editor.value = lines.join("\n");
|
||||
editor.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
return;
|
||||
}
|
||||
if (!focusNextLine && value === target.dataset.originalValue) return;
|
||||
if (focusNextLine) cancelledPreviewEdits.add(target);
|
||||
if (target.dataset.tableCell !== undefined) {
|
||||
lines[lineIndex] = replaceTableCell(lines[lineIndex], Number(target.dataset.tableCell), value.replace(/\n/g, " "));
|
||||
if (focusNextLine) lines.splice(lineIndex + 1, 0, "");
|
||||
} else {
|
||||
const prefix = target.dataset.sourcePrefix || "", suffix = target.dataset.sourceSuffix || "";
|
||||
const editedLines = value.split("\n");
|
||||
const replacements = editedLines.map((part, index) => `${index === 0 ? prefix : ""}${part}${index === editedLines.length - 1 ? suffix : ""}`);
|
||||
lines.splice(lineIndex, 1, ...replacements);
|
||||
}
|
||||
editor.value = lines.join("\n");
|
||||
editor.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
if (focusNextLine) {
|
||||
const nextLine = lineIndex + Math.max(2, value.split("\n").length);
|
||||
const next = preview.querySelector(`[data-source-line="${nextLine}"].preview-editable`);
|
||||
next?.focus();
|
||||
if (next) placePreviewCaret(next, 0);
|
||||
}
|
||||
}
|
||||
function editRawPreviewLine(target) {
|
||||
const lineIndex = Number(target.dataset.sourceLine) - 1;
|
||||
const lines = editor.value.split("\n");
|
||||
if (lineIndex < 0 || lineIndex >= lines.length) return;
|
||||
const caretOffset = Math.min(previewCaretOffset(target), lines[lineIndex].length);
|
||||
target.dataset.rawSourceEdit = "true";
|
||||
target.dataset.originalValue = lines[lineIndex];
|
||||
target.textContent = lines[lineIndex];
|
||||
target.classList.add("preview-editable--source");
|
||||
target.focus({ preventScroll: true });
|
||||
placePreviewCaret(target, caretOffset);
|
||||
}
|
||||
|
||||
function insertPreviewLineBreak(target) {
|
||||
const lineIndex = Number(target.dataset.sourceLine) - 1;
|
||||
if (lineIndex < 0) return;
|
||||
const lines = editor.value.split("\n");
|
||||
const value = markdownFromPreview(target);
|
||||
let insertedLineIndex;
|
||||
if (target.dataset.tableCell !== undefined) {
|
||||
lines[lineIndex] = replaceTableCell(lines[lineIndex], Number(target.dataset.tableCell), value.replace(/\n/g, " "));
|
||||
insertedLineIndex = lineIndex + 1;
|
||||
lines.splice(insertedLineIndex, 0, "");
|
||||
} else {
|
||||
const prefix = target.dataset.sourcePrefix || "", suffix = target.dataset.sourceSuffix || "";
|
||||
const editedLines = value.split("\n");
|
||||
const replacements = editedLines.map((part, index) => `${index === 0 ? prefix : ""}${part}${index === editedLines.length - 1 ? suffix : ""}`);
|
||||
insertedLineIndex = lineIndex + replacements.length;
|
||||
lines.splice(lineIndex, 1, ...replacements, "");
|
||||
}
|
||||
cancelledPreviewEdits.add(target);
|
||||
editor.value = lines.join("\n");
|
||||
editor.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
const sourceLine = insertedLineIndex + 1;
|
||||
const restoreFocus = () => {
|
||||
const next = preview.querySelector(`[data-source-line="${sourceLine}"].preview-editable`);
|
||||
if (!next) return;
|
||||
next.focus({ preventScroll: true });
|
||||
placePreviewCaret(next, 0);
|
||||
};
|
||||
restoreFocus();
|
||||
queueMicrotask(() => {
|
||||
const active = document.activeElement;
|
||||
if (!active || active === document.body || !preview.contains(active)) restoreFocus();
|
||||
});
|
||||
}
|
||||
document.querySelectorAll("[data-authorship-mode]").forEach(button => button.addEventListener("click", () => {
|
||||
authorshipMode = button.dataset.authorshipMode === "full" ? "full" : "simple";
|
||||
updateAuthorshipControls();
|
||||
renderGutter();
|
||||
}));
|
||||
authorshipColorsToggle?.addEventListener("change", () => {
|
||||
authorshipColorsEnabled = authorshipColorsToggle.checked;
|
||||
updateAuthorshipControls();
|
||||
renderGutter();
|
||||
});
|
||||
saveEditorSettingsButton?.addEventListener("click", async () => {
|
||||
if (!info?.can_save_editor_settings) return;
|
||||
saveEditorSettingsButton.disabled = true;
|
||||
try {
|
||||
await adapter.saveEditorSettings(sessionHeaders(), { authorship_mode: authorshipMode, colors_enabled: authorshipColorsEnabled });
|
||||
toast("Editor settings saved for everyone");
|
||||
} catch (error) {
|
||||
toast(error.message);
|
||||
} finally {
|
||||
saveEditorSettingsButton.disabled = !info?.can_save_editor_settings;
|
||||
}
|
||||
});
|
||||
window.addEventListener("popstate", () => { uiState = readEditorState(); applyUi(); }); window.addEventListener("rustpad:urlchange", updateAddressLabel); document.querySelector("#copy-link").addEventListener("click", async () => { try { await copyText(currentShareUrl(uiState)); toast("Link copied"); } catch (e) { toast(e.message); } }); document.querySelectorAll("[data-format]").forEach(b => b.addEventListener("click", () => { applyFormat(editor, b.dataset.format); b.closest("details")?.removeAttribute("open"); })); bindFormatShortcuts(editor); bindEmojiPicker({ editor, details: document.querySelector("#emoji-picker"), search: document.querySelector("#emoji-search"), categories: document.querySelector("#emoji-categories"), grid: document.querySelector("#emoji-grid"), empty: document.querySelector("#emoji-empty") }); document.querySelector("#shortcuts-button").addEventListener("click", () => document.querySelector("#shortcuts-dialog").showModal()); document.querySelector("#close-shortcuts").addEventListener("click", () => document.querySelector("#shortcuts-dialog").close()); preview.addEventListener("change", event => { const checkbox = event.target.closest(".task-checkbox"); if (!checkbox) return; const lineIndex = Number(checkbox.dataset.sourceLine) - 1; const lines = editor.value.split("\n"); if (lineIndex < 0 || lineIndex >= lines.length) return; lines[lineIndex] = lines[lineIndex].replace(/^(\s*[-*+]\s+\[)[ xX](\])/, `$1${checkbox.checked ? "x" : " "}$2`); editor.value = lines.join("\n"); editor.dispatchEvent(new Event("input", { bubbles: true })); }); preview.addEventListener("focusin", event => { const target = event.target.closest(".preview-editable"); if (!target) return; target.dataset.originalHtml = target.innerHTML; target.dataset.originalValue = markdownFromPreview(target); }); preview.addEventListener("beforeinput", event => { if (!event.target.closest(".preview-editable")) return; if (event.inputType === "insertParagraph" || event.inputType === "insertLineBreak") event.preventDefault(); }); preview.addEventListener("keydown", event => { const target = event.target.closest(".preview-editable"); if (!target) return; if (event.key === "Escape") { event.preventDefault(); event.stopPropagation(); if (target.dataset.rawSourceEdit === "true") { cancelledPreviewEdits.add(target); render(); } else editRawPreviewLine(target); return; } if (event.key === "Enter") { event.preventDefault(); event.stopPropagation(); if (event.altKey) insertPreviewLineBreak(target); else target.blur(); return; } if (event.key === "ArrowUp" || event.key === "ArrowDown") { if (movePreviewCaret(target, event.key === "ArrowUp" ? -1 : 1)) event.preventDefault(); } }); preview.addEventListener("blur", event => { const target = event.target.closest(".preview-editable"); if (!target) return; if (cancelledPreviewEdits.has(target)) { cancelledPreviewEdits.delete(target); return; } commitPreviewEdit(target); }, { capture: true });
|
||||
const publishPageButton = document.querySelector("#publish-page");
|
||||
function updatePageControls() {
|
||||
const enabled = publicPageEnabled.checked;
|
||||
publishPageButton.disabled = !enabled;
|
||||
publicTaskUpdates.disabled = !enabled;
|
||||
unprotectPublicPage.disabled = !enabled;
|
||||
}
|
||||
const savePublicOptions = async () => adapter.publish(accessToken, publicTaskUpdates.checked, unprotectPublicPage.checked, publicPageEnabled.checked);
|
||||
publicPageEnabled.addEventListener("change", async () => {
|
||||
const previous = !publicPageEnabled.checked;
|
||||
updatePageControls();
|
||||
publicPageEnabled.disabled = true;
|
||||
try { await savePublicOptions(); toast(publicPageEnabled.checked ? "Page enabled" : "Page disabled"); }
|
||||
catch (error) { publicPageEnabled.checked = previous; updatePageControls(); toast(error.message); }
|
||||
finally { publicPageEnabled.disabled = false; }
|
||||
});
|
||||
publicTaskUpdates.addEventListener("change", async () => { publicTaskUpdates.disabled = true; try { await savePublicOptions(); toast(publicTaskUpdates.checked ? "Public task updates enabled" : "Public task updates disabled"); } catch (error) { publicTaskUpdates.checked = !publicTaskUpdates.checked; toast(error.message); } finally { updatePageControls(); } });
|
||||
unprotectPublicPage.addEventListener("change", async () => { unprotectPublicPage.disabled = true; try { await savePublicOptions(); toast(unprotectPublicPage.checked ? "Published page is now unprotected" : "Published page protection enabled"); } catch (error) { unprotectPublicPage.checked = !unprotectPublicPage.checked; toast(error.message); } finally { updatePageControls(); } });
|
||||
publishPageButton.addEventListener("click", async () => { if (!publicPageEnabled.checked) return; try { const result = await savePublicOptions(); if (!result.url) throw new Error("Page is disabled"); const url = new URL(result.url, location.origin).href; await copyText(url); toast("Page link copied"); window.open(url, "_blank", "noopener"); } catch (error) { toast(error.message); } });
|
||||
roomDetails.addEventListener("toggle", () => { if (roomDetails.open) { clearUnread(); chatInput.focus(); if ("Notification" in window && Notification.permission === "default") Notification.requestPermission().catch(() => { }); } else { roomDetails.classList.remove("is-mobile-open"); } });
|
||||
document.addEventListener("visibilitychange", () => { if (!document.hidden && roomDetails.open) clearUnread(); });
|
||||
chatForm.addEventListener("submit", event => { event.preventDefault(); const text = chatInput.value.trim(); if (!text || !socket) return; socket.chat(text); chatInput.value = ""; chatInput.focus(); });
|
||||
|
||||
Reference in New Issue
Block a user