some changes

This commit is contained in:
Mateusz Gruszczyński
2026-07-30 23:57:35 +02:00
parent c5ca8e8d0b
commit 4edc511272
20 changed files with 686 additions and 65 deletions
+88 -3
View File
@@ -25,14 +25,14 @@ import { toast } from "@rustpad/toast";
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");
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");
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");
const shareToken = new URLSearchParams(location.search).get("share");
const notePreferenceKey = name => `rustpad:${name}:${location.pathname}`;
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, lastRevealedLineHash = "";
let editorSettingsSaveTimer, editorSettingsSaveInFlight = false, pendingPersonalSettingsSave = false, pendingAuthorshipSettingsSave = false;
let editorSettingsSaveTimer, editorSettingsSaveInFlight = false, pendingPersonalSettingsSave = false, pendingAuthorshipSettingsSave = false, connectionNoticeTimer = 0, connectionWasInterrupted = false;
const compactLayoutQuery = window.matchMedia("(max-width: 1499px)");
const singlePaneQuery = window.matchMedia("(max-width: 760px)");
let compactView = uiState.view === "preview" ? "preview" : "edit";
@@ -149,6 +149,38 @@ export function startNoteEditor(adapter) {
function clearUnread() { unreadChat = 0; chatUnread.hidden = true; chatUnread.textContent = ""; if (mobileChatUnread) { mobileChatUnread.hidden = true; mobileChatUnread.textContent = ""; } document.title = document.title.replace(/^● /, ""); }
function setStatus(kind, text) { const className = `status__dot${kind ? ` is-${kind}` : ""}`; document.querySelector("#status-dot").className = className; document.querySelector("#status-text").textContent = text; const mobileDot = document.querySelector("#mobile-status-dot"); const mobileText = document.querySelector("#mobile-status-text"); if (mobileDot) mobileDot.className = className; if (mobileText) mobileText.textContent = text; }
function showConnectionNotice(title, message, restored = false) {
clearTimeout(connectionNoticeTimer);
connectionNoticeTitle.textContent = title;
connectionNoticeMessage.textContent = message;
connectionNotice.hidden = false;
connectionNotice.classList.toggle("is-restored", restored);
requestAnimationFrame(() => connectionNotice.classList.add("is-visible"));
if (restored) connectionNoticeTimer = window.setTimeout(() => {
connectionNotice.classList.remove("is-visible", "is-restored");
connectionNoticeTimer = window.setTimeout(() => { connectionNotice.hidden = true; }, 220);
}, 1800);
}
function hideConnectionNotice() {
clearTimeout(connectionNoticeTimer);
connectionNotice.classList.remove("is-visible", "is-restored");
connectionNotice.hidden = true;
}
function handleSocketStatus(status, details = {}) {
if (status === "online") {
setStatus("online", "Connected");
if (connectionWasInterrupted || details.restored) showConnectionNotice("Connection restored", "Live editing is active again.", true);
connectionWasInterrupted = false;
return;
}
if (status === "reconnecting") {
connectionWasInterrupted = true;
setStatus("offline", "Reconnecting…");
showConnectionNotice("Connection interrupted", details.message || "Trying to reconnect automatically.");
return;
}
setStatus(null, "Connecting…");
}
function updateAddressLabel() { document.querySelector(adapter.addressSelector).textContent = `${location.pathname}${location.search}`; }
async function renderMermaid() { const nodes = preview.querySelectorAll(".mermaid"); if (!nodes.length) return; try { const { default: mermaid } = await import("https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs"); mermaid.initialize({ startOnLoad: false, theme: "dark", securityLevel: "strict" }); await mermaid.run({ nodes: [...nodes] }); } catch { nodes.forEach(n => n.insertAdjacentHTML("beforebegin", '<p class="error">Failed to load Mermaid.</p>')); } }
async function renderCodeHighlight() { const nodes = preview.querySelectorAll('pre code[class^="language-"]:not(.language-mermaid)'); if (!nodes.length) return; try { const hljs = await import("https://cdn.jsdelivr.net/npm/highlight.js@11.11.1/+esm"); nodes.forEach(node => { const lines = node.querySelectorAll(".code-line"); if (!lines.length) { hljs.default.highlightElement(node); return; } const language = [...node.classList].find(name => name.startsWith("language-"))?.slice(9); lines.forEach(line => { try { line.innerHTML = hljs.default.highlight(line.textContent, { language, ignoreIllegals: true }).value; } catch { line.innerHTML = hljs.default.highlightAuto(line.textContent).value; } }); node.classList.add("hljs"); }); } catch { } }
@@ -619,7 +651,51 @@ export function startNoteEditor(adapter) {
onFilesChanged: files => updateMarkdownFiles(files, { rerender: true }),
});
refreshFilesForAliases = () => loadFiles();
function connect() { socket?.stop(); socket = adapter.createSocket({ password, accessToken, nickname, color: currentUserColor() || null, sessionToken: null, guestId: getGuestId(), onStatus: s => setStatus(s === "online" ? "online" : s === "offline" ? "offline" : null, s === "online" ? "Connected" : s === "offline" ? "Reconnecting…" : "Connecting…"), onAuthenticated: m => { resourceUnlocked = true; if (passwordDialog.open) passwordDialog.close(); const readOnly = m.access_level === "read_only"; editor.readOnly = readOnly; accessLevel.textContent = readOnly ? "Access: read only" : "Access: full"; applyRemote(m.content, m.owner_map); if (!readOnly) editor.focus(); }, onDocument: m => { applyRemote(m.content, m.owner_map); document.querySelector("#save-state").textContent = `${m.author ? `${m.author} · ` : ""}${new Date(m.updated_at).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" })}`; }, onPresence: updatePresence, onLatency: updateLatency, onChat: appendChatMessage, onError: m => { const friendly = /read-only access/i.test(m) ? "This note is read only. Enter the password or ask the owner to grant write access." : m; document.querySelector("#password-error").textContent = friendly; if (/read-only access/i.test(m)) { toast(friendly); accessLevel.textContent = "Access: read only"; editor.readOnly = true; return; } if (/nickname|session|account/i.test(m)) { if (!identityDialog.open) identityDialog.showModal(); } else if (info?.protected && !passwordDialog.open) passwordDialog.showModal(); } }); socket.connect(); }
function connect() {
socket?.stop();
socket = adapter.createSocket({
password,
accessToken,
nickname,
color: currentUserColor() || null,
sessionToken: null,
guestId: getGuestId(),
onStatus: handleSocketStatus,
onAuthenticated: message => {
resourceUnlocked = true;
if (passwordDialog.open) passwordDialog.close();
const readOnly = message.access_level === "read_only";
editor.readOnly = readOnly;
accessLevel.textContent = readOnly ? "Access: read only" : "Access: full";
applyRemote(message.content, message.owner_map);
if (!readOnly) editor.focus();
},
onDocument: message => {
applyRemote(message.content, message.owner_map);
document.querySelector("#save-state").textContent = `${message.author ? `${message.author} · ` : ""}${new Date(message.updated_at).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" })}`;
},
onPresence: updatePresence,
onLatency: updateLatency,
onChat: appendChatMessage,
onError: message => {
hideConnectionNotice();
const friendly = /read-only access/i.test(message) ? "This note is read only. Enter the password or ask the owner to grant write access." : message;
document.querySelector("#password-error").textContent = friendly;
if (/read-only access/i.test(message)) {
toast(friendly);
accessLevel.textContent = "Access: read only";
editor.readOnly = true;
return;
}
if (/nickname|session|account/i.test(message)) {
if (!identityDialog.open) identityDialog.showModal();
} else if (info?.protected && !passwordDialog.open) {
passwordDialog.showModal();
}
},
});
socket.connect();
}
bindIdentityDialog({ dialog: identityDialog, onIdentity: async value => { nickname = value; accessToken = shareToken || getAccessToken(adapter.access.kind, adapter.access.key); identityDialog.close(); updateCurrentUser(); await loadNoteInfo(); if (info.protected && !accessToken && !getAuthToken()) passwordDialog.showModal(); else { loadFiles(); connect(); } } });
identityDialog.addEventListener("close", () => { if (!nickname) queueMicrotask(() => { if (!identityDialog.open) identityDialog.showModal(); }); });
async function showSystemNotFound() {
@@ -698,6 +774,15 @@ export function startNoteEditor(adapter) {
compactLayoutQuery.addEventListener("change", event => {
if (!event.matches) setHeaderMenuOpen(false);
});
const mobileEditorOptions = document.querySelector("#mobile-editor-options");
document.addEventListener("pointerdown", event => {
if (mobileEditorOptions?.open && !event.target.closest("#mobile-editor-options")) {
mobileEditorOptions.open = false;
}
}, { passive: true });
document.addEventListener("keydown", event => {
if (event.key === "Escape" && mobileEditorOptions?.open) mobileEditorOptions.open = false;
});
modeToggle.addEventListener("click", () => { uiState = { ...uiState, mode: uiState.mode === "markdown" ? "text" : "markdown" }; applyUi({ write: true }); });
lineToggle.addEventListener("change", () => { localStorage.setItem(notePreferenceKey("line-numbers"), lineToggle.checked ? "on" : "off"); syncMobileEditorControls(); renderGutter(); scheduleEditorSettingsSave({ personal: true }); });
previewLineToggle.addEventListener("change", () => { localStorage.setItem(notePreferenceKey("preview-line-numbers"), previewLineToggle.checked ? "on" : "off"); syncMobileEditorControls(); renderGutter(); alignPreviewLineNumbers(preview); scheduleEditorSettingsSave({ personal: true }); });