tokens and more
This commit is contained in:
@@ -26,7 +26,7 @@ import { getTheme } from "@rustpad/theme";
|
||||
export function startNoteEditor(adapter) {
|
||||
const editor = document.querySelector("#editor"), preview = document.querySelector("#preview"), editorWorkspace = document.querySelector("#editor-workspace"), gutter = document.querySelector("#line-gutter"), ownerLabels = document.querySelector("#owner-labels"), authorshipLayer = document.querySelector("#authorship-layer");
|
||||
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"), connectionNotice = document.querySelector("#connection-notice"), connectionNoticeTitle = document.querySelector("#connection-notice-title"), connectionNoticeMessage = document.querySelector("#connection-notice-message");
|
||||
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"), mobileConnectionDetails = document.querySelector("#mobile-connection-details"), 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"), connectionNotice = document.querySelector("#connection-notice"), connectionNoticeTitle = document.querySelector("#connection-notice-title"), connectionNoticeMessage = document.querySelector("#connection-notice-message");
|
||||
let unreadChat = 0;
|
||||
const compactToggle = document.querySelector("#compact-toggle"), lineLinksToggle = document.querySelector("#line-links-toggle"), authorshipColorsToggle = document.querySelector("#authorship-colors-toggle"), authorshipColorsLabel = document.querySelector("#authorship-colors-label"), 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"), mobileColorPicker = document.querySelector("#mobile-color-picker"), useGlobalColorButton = document.querySelector("#use-global-color");
|
||||
const mobileFontFamily = document.querySelector("#mobile-font-family"), mobileFontSize = document.querySelector("#mobile-font-size"), mobileLineToggle = document.querySelector("#mobile-line-numbers-toggle"), mobilePreviewLineToggle = document.querySelector("#mobile-preview-line-numbers-toggle"), mobileCompactToggle = document.querySelector("#mobile-compact-toggle"), mobileLineLinksToggle = document.querySelector("#mobile-line-links-toggle");
|
||||
@@ -216,7 +216,65 @@ export function startNoteEditor(adapter) {
|
||||
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(); }
|
||||
function updateLatency(ms) { socketLatency.textContent = Number.isFinite(ms) ? `${ms} ms` : "— ms"; }
|
||||
function updateLatency(ms) {
|
||||
const text = Number.isFinite(ms) ? `${ms} ms` : "— ms";
|
||||
socketLatency.textContent = text;
|
||||
const mobileLatency = document.querySelector("#mobile-socket-latency");
|
||||
if (mobileLatency) mobileLatency.textContent = text;
|
||||
}
|
||||
function setDiagnosticField(name, value) {
|
||||
document.querySelectorAll(`[data-connection-diagnostic="${name}"]`).forEach(node => { node.textContent = value; });
|
||||
}
|
||||
function formatDiagnosticDuration(milliseconds) {
|
||||
const seconds = Math.max(0, Math.floor(Number(milliseconds || 0) / 1000));
|
||||
if (seconds < 60) return `${seconds}s`;
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
if (minutes < 60) return `${minutes}m ${seconds % 60}s`;
|
||||
const hours = Math.floor(minutes / 60);
|
||||
return `${hours}h ${minutes % 60}m`;
|
||||
}
|
||||
function renderConnectionDiagnostics(snapshot = {}) {
|
||||
const server = snapshot.server || {};
|
||||
const runtime = snapshot.runtime || {};
|
||||
const latency = runtime.latency || {};
|
||||
const client = server.client || {};
|
||||
const quality = latency.quality || (runtime.state === "open" ? "measuring" : runtime.state || "waiting");
|
||||
const qualityLabel = quality.charAt(0).toUpperCase() + quality.slice(1);
|
||||
setDiagnosticField("quality", qualityLabel);
|
||||
setDiagnosticField("latency", Number.isFinite(latency.current)
|
||||
? `${latency.current} ms · avg ${latency.average} ms · ${latency.minimum}–${latency.maximum} ms`
|
||||
: "Waiting for heartbeat");
|
||||
setDiagnosticField("jitter", Number.isFinite(latency.jitter) ? `${latency.jitter} ms` : "—");
|
||||
setDiagnosticField("uptime", runtime.authenticated_at
|
||||
? formatDiagnosticDuration(runtime.uptime_ms)
|
||||
: runtime.last_connection_uptime_ms ? `last ${formatDiagnosticDuration(runtime.last_connection_uptime_ms)}` : "—");
|
||||
setDiagnosticField("reconnects", `${runtime.total_reconnects || 0}${runtime.reconnect_attempt ? ` · attempt ${runtime.reconnect_attempt}` : ""}`);
|
||||
const scheme = client.request_scheme ? `${client.request_scheme.toUpperCase()} / ` : "";
|
||||
setDiagnosticField("transport", `${scheme}${server.transport || "WebSocket"}`);
|
||||
setDiagnosticField("heartbeat", server.heartbeat_interval_ms
|
||||
? `${Math.round(server.heartbeat_interval_ms / 1000)}s ping · ${Math.round(server.heartbeat_timeout_ms / 1000)}s timeout`
|
||||
: "Waiting for server policy");
|
||||
const network = runtime.network || {};
|
||||
const networkParts = [network.effective_type || client.effective_type];
|
||||
if (Number.isFinite(network.downlink_mbps ?? client.downlink_mbps)) networkParts.push(`${network.downlink_mbps ?? client.downlink_mbps} Mb/s`);
|
||||
if (Number.isFinite(network.rtt_ms ?? client.network_rtt_ms)) networkParts.push(`system RTT ${Math.round(network.rtt_ms ?? client.network_rtt_ms)} ms`);
|
||||
if ((network.save_data ?? client.save_data) === true) networkParts.push("data saver");
|
||||
setDiagnosticField("network", networkParts.filter(Boolean).join(" · ") || (runtime.online === false ? "Offline" : "Not exposed by browser"));
|
||||
const clientParts = [client.platform, client.timezone, client.language || client.accept_language, client.id ? `id ${client.id}` : null, client.user_agent];
|
||||
setDiagnosticField("client", clientParts.filter(Boolean).join(" · ") || "Waiting for server data");
|
||||
setDiagnosticField("server", server.server_version ? `RustPad ${server.server_version} · connection ${server.connection_id}` : "Waiting for server data");
|
||||
const lastEvent = runtime.last_close
|
||||
? `Closed ${runtime.last_close.code}${runtime.last_close.reason ? `: ${runtime.last_close.reason}` : ""}`
|
||||
: runtime.last_message_at ? `Message ${new Date(runtime.last_message_at).toLocaleTimeString()}` : "No messages yet";
|
||||
const traffic = `${formatBytes(runtime.bytes_received)} received · ${formatBytes(runtime.bytes_sent)} sent`;
|
||||
const buffered = runtime.buffered_amount ? ` · ${formatBytes(runtime.buffered_amount)} buffered` : "";
|
||||
setDiagnosticField("last-event", `${lastEvent} · ${runtime.visibility || document.visibilityState} · ${traffic}${buffered}`);
|
||||
for (const details of [document.querySelector("#connection-details"), document.querySelector("#mobile-connection-details")]) {
|
||||
if (!details) continue;
|
||||
details.classList.remove("is-quality-excellent", "is-quality-good", "is-quality-degraded", "is-quality-poor");
|
||||
if (["excellent", "good", "degraded", "poor"].includes(quality)) details.classList.add(`is-quality-${quality}`);
|
||||
}
|
||||
}
|
||||
function appendLinkifiedText(container, value) { const text = String(value || ""); const urlPattern = /https?:\/\/[^\s<>{}\[\]"'`]+/gi; let index = 0; for (const match of text.matchAll(urlPattern)) { const start = match.index ?? 0; if (start > index) container.append(document.createTextNode(text.slice(index, start))); let raw = match[0], trail = ""; while (/[),.!?:;]$/.test(raw)) { trail = raw.slice(-1) + trail; raw = raw.slice(0, -1); } try { const url = new URL(raw); if (url.protocol === "http:" || url.protocol === "https:") { const link = document.createElement("a"); link.href = url.href; link.textContent = raw; link.target = "_blank"; link.rel = "noopener noreferrer"; container.append(link); } else container.append(document.createTextNode(raw)); } catch { container.append(document.createTextNode(raw)); } if (trail) container.append(document.createTextNode(trail)); index = start + match[0].length; } if (index < text.length) container.append(document.createTextNode(text.slice(index))); }
|
||||
function appendChatMessage(message) { const empty = chatMessages.querySelector(".chat-empty"); empty?.remove(); const row = document.createElement("p"); row.className = "chat-message"; const author = document.createElement("strong"); author.textContent = message.sender; const text = document.createElement("span"); appendLinkifiedText(text, message.text); row.append(author, text); chatMessages.append(row); while (chatMessages.children.length > 100) chatMessages.firstElementChild.remove(); chatMessages.scrollTop = chatMessages.scrollHeight; if (message.sender !== nickname && !roomDetails.open) { unreadChat++; chatUnread.hidden = false; chatUnread.textContent = unreadChat > 99 ? "99+" : String(unreadChat); if (mobileChatUnread) { mobileChatUnread.hidden = false; mobileChatUnread.textContent = chatUnread.textContent; } const oldTitle = document.title; if (!document.title.startsWith("● ")) document.title = `● ${oldTitle}`; if (document.hidden && Notification.permission === "granted") new Notification(`${message.sender} wrote in RustPad`, { body: message.text.slice(0, 160), tag: "rustpad-room-chat" }); } }
|
||||
function clearUnread() { unreadChat = 0; chatUnread.hidden = true; chatUnread.textContent = ""; if (mobileChatUnread) { mobileChatUnread.hidden = true; mobileChatUnread.textContent = ""; } document.title = document.title.replace(/^● /, ""); }
|
||||
@@ -774,6 +832,7 @@ export function startNoteEditor(adapter) {
|
||||
},
|
||||
onPresence: updatePresence,
|
||||
onLatency: updateLatency,
|
||||
onDiagnostics: renderConnectionDiagnostics,
|
||||
onChat: appendChatMessage,
|
||||
onError: message => {
|
||||
hideConnectionNotice();
|
||||
@@ -1281,10 +1340,13 @@ export function startNoteEditor(adapter) {
|
||||
pageSettings?.querySelector("summary")?.setAttribute("title", enabled ? "Published page enabled" : "Published page disabled");
|
||||
}
|
||||
document.addEventListener("pointerdown", event => {
|
||||
if (pageSettings?.open && !event.target.closest(".page-settings")) pageSettings.open = false;
|
||||
const target = event.target instanceof Element ? event.target : null;
|
||||
if (pageSettings?.open && !target?.closest(".page-settings")) pageSettings.open = false;
|
||||
if (mobileConnectionDetails?.open && (!target || !mobileConnectionDetails.contains(target))) mobileConnectionDetails.open = false;
|
||||
}, { passive: true });
|
||||
document.addEventListener("keydown", event => {
|
||||
if (event.key === "Escape" && pageSettings?.open) pageSettings.open = false;
|
||||
if (event.key === "Escape" && mobileConnectionDetails?.open) mobileConnectionDetails.open = false;
|
||||
});
|
||||
const savePublicOptions = async () => adapter.publish(accessToken, publicTaskUpdates.checked, unprotectPublicPage.checked, publicPageEnabled.checked);
|
||||
publicPageEnabled.addEventListener("change", async () => {
|
||||
|
||||
Reference in New Issue
Block a user