From a27f8ad7f3cfa68f9fbf93dd40decda0ad61c11b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Gruszczy=C5=84ski?= Date: Mon, 27 Jul 2026 23:44:31 +0200 Subject: [PATCH] fix after change nickname --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/api/files.rs | 4 ---- src/app/mod.rs | 13 +++++++++++-- static/js/note-editor.js | 13 +++++++++++-- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 30337cc..8e4dc6d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2581,7 +2581,7 @@ dependencies = [ [[package]] name = "rustpad" -version = "0.1.10" +version = "0.1.11" dependencies = [ "argon2", "aws-config", diff --git a/Cargo.toml b/Cargo.toml index 4b9d36c..c085cfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustpad" -version = "0.1.10" +version = "0.1.11" edition = "2024" rust-version = "1.94" description = "Collaborative Markdown notepad built with Axum, WebSockets and SQLite, PostgreSQL and MySQL" diff --git a/src/api/files.rs b/src/api/files.rs index 9acfd99..a72f303 100644 --- a/src/api/files.rs +++ b/src/api/files.rs @@ -453,10 +453,6 @@ async fn serve_token_file( HeaderName::from_static("x-robots-tag"), HeaderValue::from_static("noindex, nofollow, noarchive, nosnippet"), ); - response.headers_mut().insert( - header::REFERRER_POLICY, - HeaderValue::from_static("no-referrer"), - ); response.headers_mut().insert( header::CACHE_CONTROL, HeaderValue::from_str(&format!( diff --git a/src/app/mod.rs b/src/app/mod.rs index 482466d..19295c5 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -200,10 +200,19 @@ pub fn router( } async fn add_non_asset_security_headers(request: Request, next: Next) -> Response { - let is_asset = request.uri().path().starts_with("/assets/"); + let path = request.uri().path(); + let is_asset = path.starts_with("/assets/"); + let is_file = path.starts_with("/f/"); let mut response = next.run(request).await; - if !is_asset { + if is_file { + let headers = response.headers_mut(); + headers.remove("x-frame-options"); + headers.remove("cross-origin-opener-policy"); + headers.remove("cross-origin-resource-policy"); + headers.remove("referrer-policy"); + headers.remove("permissions-policy"); + } else if !is_asset { let headers = response.headers_mut(); headers .entry("x-content-type-options") diff --git a/static/js/note-editor.js b/static/js/note-editor.js index 6816908..b5b6348 100644 --- a/static/js/note-editor.js +++ b/static/js/note-editor.js @@ -37,6 +37,15 @@ export function startNoteEditor(adapter) { function noteUserColor() { return noteColor || ""; } function currentUserColor() { return noteUserColor() || globalUserColor(); } function currentOwner() { const color = currentUserColor(); return color ? `${nickname}\u001f${color}` : nickname; } + function adoptCurrentOwnerAliases(model, contentLength) { + const color = currentUserColor(); + if (!getAuthToken() || !/^#[0-9a-f]{6}$/i.test(color)) return model; + const replacement = currentOwner(); + return replaceAuthorshipOwner(model, owner => { + const parts = ownerParts(owner); + return /^#[0-9a-f]{6}$/i.test(parts.color) && parts.color.toLowerCase() === color.toLowerCase(); + }, replacement, contentLength); + } function updateCurrentUser() { const color = currentUserColor() || defaultColorFor(nickname); const overridden = Boolean(noteUserColor()); currentUser.querySelector(".user-chip__name").textContent = nickname; currentUser.style.setProperty("--owner", color); currentUser.title = overridden ? "Note color override" : "Global profile color"; userColorPicker.value = /^#[0-9a-f]{6}$/i.test(color) ? color : "#7c6cff"; useGlobalColorButton.hidden = !overridden; } function toast(text) { const el = document.querySelector("#toast"); el.textContent = text; el.classList.add("visible"); setTimeout(() => el.classList.remove("visible"), 1800); } function sessionHeaders() { const token = accessToken || getAuthToken(); return token ? { Authorization: `Bearer ${token}` } : {}; } @@ -208,7 +217,7 @@ export function startNoteEditor(adapter) { if (write) writeEditorState(uiState, { replace }); updateAddressLabel(); } - function applyRemote(content, ownerMap) { if (content === editor.value && ownerMap == null) return; const previous = editor.value, start = editor.selectionStart, end = editor.selectionEnd, direction = editor.selectionDirection, scrollTop = editor.scrollTop, scrollLeft = editor.scrollLeft; const mapped = mapSelectionThroughEdit(previous, content, start, end); applyingRemote = true; editor.value = content; authorship = parseAuthorship(content, ownerMap); previousContent = content; editor.setSelectionRange(mapped.start, mapped.end, direction); editor.scrollTop = scrollTop; editor.scrollLeft = scrollLeft; applyingRemote = false; render(); editor.scrollTop = scrollTop; editor.scrollLeft = scrollLeft; authorshipLayer.scrollTop = scrollTop; authorshipLayer.scrollLeft = scrollLeft; } + function applyRemote(content, ownerMap) { if (content === editor.value && ownerMap == null) return; const previous = editor.value, start = editor.selectionStart, end = editor.selectionEnd, direction = editor.selectionDirection, scrollTop = editor.scrollTop, scrollLeft = editor.scrollLeft; const mapped = mapSelectionThroughEdit(previous, content, start, end); applyingRemote = true; editor.value = content; authorship = adoptCurrentOwnerAliases(parseAuthorship(content, ownerMap), content.length); previousContent = content; editor.setSelectionRange(mapped.start, mapped.end, direction); editor.scrollTop = scrollTop; editor.scrollLeft = scrollLeft; applyingRemote = false; render(); editor.scrollTop = scrollTop; editor.scrollLeft = scrollLeft; authorshipLayer.scrollTop = scrollTop; authorshipLayer.scrollLeft = scrollLeft; } const { loadFiles } = bindNoteFiles({ editor, toast, getAccessToken: () => accessToken, canDelete: () => Boolean(info?.can_delete_files), @@ -310,7 +319,7 @@ export function startNoteEditor(adapter) { if (socket) socket.update(editor.value, serializeAuthorship(authorship, editor.value.length)); toast("Global profile color restored"); }); - editor.addEventListener("keydown", continueIndentation); editor.addEventListener("scroll", () => { gutter.scrollTop = editor.scrollTop; authorshipLayer.scrollTop = editor.scrollTop; authorshipLayer.scrollLeft = editor.scrollLeft; renderGutter(); }); editor.addEventListener("input", () => { const nextContent = editor.value; authorship = replaceAuthorshipOwner(authorship, owner => ownerName(owner) === nickname, currentOwner(), previousContent.length); authorship = applyAuthorshipEdit(authorship, previousContent, nextContent, currentOwner()); previousContent = nextContent; render(); if (applyingRemote) return; clearTimeout(saveTimer); document.querySelector("#save-state").textContent = "Saving…"; saveTimer = setTimeout(() => socket?.update(editor.value, serializeAuthorship(authorship, editor.value.length)), 250); }); + editor.addEventListener("keydown", continueIndentation); editor.addEventListener("scroll", () => { gutter.scrollTop = editor.scrollTop; authorshipLayer.scrollTop = editor.scrollTop; authorshipLayer.scrollLeft = editor.scrollLeft; renderGutter(); }); editor.addEventListener("input", () => { const nextContent = editor.value; authorship = adoptCurrentOwnerAliases(authorship, previousContent.length); authorship = replaceAuthorshipOwner(authorship, owner => ownerName(owner) === nickname, currentOwner(), previousContent.length); authorship = applyAuthorshipEdit(authorship, previousContent, nextContent, currentOwner()); previousContent = nextContent; render(); if (applyingRemote) return; clearTimeout(saveTimer); document.querySelector("#save-state").textContent = "Saving…"; saveTimer = setTimeout(() => socket?.update(editor.value, serializeAuthorship(authorship, editor.value.length)), 250); }); document.querySelector("#password-form").addEventListener("submit", async e => { e.preventDefault(); try { password = document.querySelector("#open-password").value; const result = await adapter.requestAccess(password); accessToken = result.access_token; setAccessToken(adapter.access.kind, adapter.access.key, accessToken); password = ""; document.querySelector("#open-password").value = ""; document.querySelector("#password-error").textContent = ""; loadFiles(); connect(); } catch (error) { document.querySelector("#password-error").textContent = error.message; } }); const historyPanel = document.querySelector("#history-panel"); document.querySelector("#history-button").addEventListener("click", async () => { historyPanel.classList.add("open"); historyPanel.setAttribute("aria-hidden", "false"); document.body.classList.add("history-open"); const list = document.querySelector("#history-list"); list.innerHTML = '

Loading…

'; try { const revisions = await adapter.loadHistory(accessToken); list.innerHTML = revisions.length ? revisions.map((r, i) => { const snippet = escapeHtml(r.content.trim().split("\n").slice(0, 3).join(" · ").slice(0, 150) || "Empty note"); const author = r.author || "Unknown author"; return `
${escapeHtml(author)}

${snippet}

`; }).join("") : '

No history yet.

'; for (const r of revisions) { list.querySelector(`[data-preview="${r.id}"]`)?.addEventListener("click", () => { const el = list.querySelector(`#preview-${r.id}`); el.hidden = !el.hidden; el.textContent = r.content; }); list.querySelector(`[data-revision="${r.id}"]`)?.addEventListener("click", async () => { await adapter.restoreRevision(r.id, accessToken); toast("Version restored"); }); } } catch (e) { list.innerHTML = `

${escapeHtml(e.message)}

`; } }); document.querySelector("#close-history").addEventListener("click", () => { historyPanel.classList.remove("open"); historyPanel.setAttribute("aria-hidden", "true"); document.body.classList.remove("history-open"); });