From 3bfe886a962609320820c732015e13f992870fff Mon Sep 17 00:00:00 2001 From: CaffeineFueled Date: Mon, 2 Mar 2026 22:28:08 +0100 Subject: [PATCH 1/5] FIX typo in README - man.... --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 81d6258..01a3b26 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Simple **live collaboration notepad** with websockets and FastAPI. [Issue tracker](https://git.uphillsecurity.com/cf7/aukpad/issues) | `Libera Chat #aukpad` - Status: Beta - expect minor changes. -- Instance/Demo: [aukpad.com](https://aufkpad.com/) +- Instance/Demo: [aukpad.com](https://aukpad.com/) - Inspired by: - [Rustpad](https://github.com/ekzhang/rustpad) From 6f07a180c99ece2bb05c7973beff00b1b201ea6b Mon Sep 17 00:00:00 2001 From: CaffeineFueled Date: Sat, 7 Mar 2026 09:48:42 +0100 Subject: [PATCH 2/5] feat: ADD darkmode #16 --- app.py | 107 +++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 88 insertions(+), 19 deletions(-) diff --git a/app.py b/app.py index 0f8a261..a52638f 100644 --- a/app.py +++ b/app.py @@ -134,46 +134,69 @@ HTML = """ aukpad +

System Information

From 7c5218dba268fbc3e66900c5d847371f23e70636 Mon Sep 17 00:00:00 2001 From: CaffeineFueled Date: Thu, 12 Mar 2026 20:43:02 +0100 Subject: [PATCH 3/5] ux: FIX cursor updates to prevent cursor from jumping around while working together #21 --- app.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/app.py b/app.py index a52638f..0d84bb4 100644 --- a/app.py +++ b/app.py @@ -399,9 +399,10 @@ function connect(){ } } else if (msg.type === "update" && msg.ver > ver && msg.clientId !== clientId) { const {selectionStart:s, selectionEnd:e} = ta; + const oldText = ta.value; ta.value = msg.text; ver = msg.ver; updateGutter(); - ta.selectionStart = Math.min(s, ta.value.length); - ta.selectionEnd = Math.min(e, ta.value.length); + ta.selectionStart = adjustCursor(oldText, msg.text, s); + ta.selectionEnd = adjustCursor(oldText, msg.text, e); } else if (msg.type === "peers_changed") { const el = $("#peers"); el.textContent = msg.count; @@ -439,6 +440,25 @@ async function copyToClipboard() { } } +// Adjust cursor position after a remote text update. +// Finds the single changed region (common prefix + suffix), +// then shifts the cursor accordingly: +// - change is after cursor → no movement +// - change is before cursor → shift by length delta +// - cursor was inside the changed region → place at end of new content +function adjustCursor(oldText, newText, pos) { + let start = 0; + const minLen = Math.min(oldText.length, newText.length); + while (start < minLen && oldText[start] === newText[start]) start++; + if (pos <= start) return pos; // change is entirely after cursor + let oldEnd = oldText.length, newEnd = newText.length; + while (oldEnd > start && newEnd > start && oldText[oldEnd - 1] === newText[newEnd - 1]) { + oldEnd--; newEnd--; + } + if (pos >= oldEnd) return pos + (newEnd - oldEnd); // change is before cursor + return newEnd; // cursor was inside changed region +} + connect(); // Handle Tab key to insert 4 spaces instead of navigation From 99e33f9277c551e67cc6a271f591c11b0f4d1e3a Mon Sep 17 00:00:00 2001 From: CaffeineFueled Date: Thu, 12 Mar 2026 21:29:59 +0100 Subject: [PATCH 4/5] ux: CHANGE buttons at the top, rearrange, replace with icons, add descriptions --- app.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/app.py b/app.py index 0d84bb4..cff4fe2 100644 --- a/app.py +++ b/app.py @@ -140,6 +140,7 @@ HTML = """ --text: #111; --border: #ddd; --btn-bg: #fff; + --btn-hover: #f0f0f0; --gutter-bg: #f8fafc; --gutter-border: #eee; --gutter-color: #9ca3af; @@ -151,6 +152,7 @@ HTML = """ --text: #F0F0F0; --border: #3a3b45; --btn-bg: #2a2b33; + --btn-hover: #35363f; --gutter-bg: #1e1f28; --gutter-border: #2e2f3a; --gutter-color: #6b7280; @@ -162,8 +164,9 @@ HTML = """ body { font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji","Segoe UI Emoji"; max-width: 1000px; margin: 0 auto; padding: 1rem; display: flex; flex-direction: column; height: 100vh; box-sizing: border-box; } header { display:flex; justify-content:space-between; align-items:center; margin-bottom: .5rem; flex-shrink: 0; } - a,button { padding:.35rem .6rem; text-decoration:none; border:1px solid var(--border); border-radius:4px; background:var(--btn-bg); color:var(--text); cursor:pointer; } - #newpad { background:#000; color:#fff; border:1px solid #000; font-weight:bold; } + a,button { padding:.35rem 0; text-decoration:none; border:1px solid var(--border); border-radius:4px; background:var(--btn-bg); color:var(--text); cursor:pointer; font-size:.9rem; font-weight:bold; min-width:2rem; text-align:center; display:inline-block; } + a:hover,button:hover { background:var(--btn-hover); } + #lock-btn { font-weight:normal; } #status { font-size:.9rem; opacity:.7; margin-left:.5rem; } #status::before { content: "●"; margin-right: .3rem; color: #ef4444; } #status.connected::before { color: #22c55e; } @@ -175,8 +178,6 @@ HTML = """ user-select:none; min-width: 3ch; white-space: pre; height: 100%; overflow: hidden; } #t { padding:.5rem .75rem; width:100%; height: 100%; resize: none; border:0; outline:0; overflow:auto; white-space: pre; background:var(--bg); color:var(--text); } - #newpad { margin-left:.5rem; } - #info { margin-left:.5rem; font-size: 0.8rem; } pre { margin: 0; } /* Password protection */ #lock-btn.locked { background:#dbeafe; border-color:#3b82f6; } @@ -206,11 +207,11 @@ HTML = """ disconnected
- - - New pad - Info - + + + + + +
@@ -246,7 +247,7 @@ const rand = () => Math.random().toString(36).slice(2, 6); // 4 chars // Theme function applyTheme(dark) { document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light'); - $('#theme-btn').textContent = dark ? 'Light' : 'Dark'; + $('#theme-btn').textContent = dark ? '☀' : '☾'; } function toggleTheme() { const isDark = document.documentElement.getAttribute('data-theme') === 'dark'; @@ -328,11 +329,11 @@ function removePassword() { function updateLockBtn() { const btn = $("#lock-btn"); if (isProtected) { - btn.textContent = "Locked"; + btn.textContent = "🔒︎"; btn.classList.add("locked"); btn.title = "Password protected – click to manage"; } else { - btn.textContent = "Lock"; + btn.textContent = "🔒︎"; btn.classList.remove("locked"); btn.title = "No password – click to set one"; } @@ -427,7 +428,7 @@ async function copyToClipboard() { await navigator.clipboard.writeText(ta.value); const btn = $("#copy"); const original = btn.textContent; - btn.textContent = "Copied!"; + btn.textContent = "✓"; setTimeout(() => { btn.textContent = original; }, 1500); } catch (err) { // Fallback for older browsers @@ -435,7 +436,7 @@ async function copyToClipboard() { document.execCommand('copy'); const btn = $("#copy"); const original = btn.textContent; - btn.textContent = "Copied!"; + btn.textContent = "✓"; setTimeout(() => { btn.textContent = original; }, 1500); } } From c60381c59aa3a3c8d2a4f11fa14f4a5ef64bc766 Mon Sep 17 00:00:00 2001 From: CaffeineFueled Date: Thu, 19 Mar 2026 21:50:55 +0100 Subject: [PATCH 5/5] feat: CHANGE allow ?pw= authentication in the UI as well like the /raw http endpoint #18 --- app.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index cff4fe2..33b2c84 100644 --- a/app.py +++ b/app.py @@ -274,6 +274,7 @@ $("#padname").textContent = "/"+docId+"/"; let ws, ver = 0, clientId = Math.random().toString(36).slice(2), debounce; let isProtected = false, isAuthed = false; +const urlPw = new URLSearchParams(location.search).get("pw") || ""; // --- Line numbers --- const ta = $("#t"); @@ -382,7 +383,11 @@ function connect(){ updateLockBtn(); if (msg.peers !== undefined) { const el = $("#peers"); el.textContent = msg.peers; el.style.display = "inline"; } if (isProtected && !isAuthed) { - showOverlay(); + if (urlPw) { + ws.send(JSON.stringify({type: "auth", password: urlPw})); + } else { + showOverlay(); + } } else { isAuthed = true; hideOverlay(); @@ -394,6 +399,7 @@ function connect(){ // Real init with content follows immediately from server } else if (msg.type === "error") { if (!isAuthed) { + showOverlay(); const errEl = $("#auth-error"); errEl.textContent = msg.message; errEl.style.display = "block";