uxui:CHANGE icons, peers indicator and responsive design - I fucking hate frontend with a passion
This commit is contained in:
parent
aae1f5f924
commit
dba602e698
1 changed files with 99 additions and 33 deletions
132
app.py
132
app.py
|
|
@ -188,6 +188,7 @@ def record_auth_failure(client_ip: str):
|
|||
|
||||
HTML = """<!doctype html>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>aukpad</title>
|
||||
<style>
|
||||
:root {
|
||||
|
|
@ -232,8 +233,11 @@ HTML = """<!doctype html>
|
|||
* { box-sizing: border-box; }
|
||||
html, body { height: 100%; margin: 0; padding: 0; background-color: var(--bg); color: var(--text); }
|
||||
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; }
|
||||
max-width: 1000px; margin: 0 auto; padding: 1rem; display: flex; flex-direction: column;
|
||||
height: 100vh; height: 100dvh; /* dvh excludes iOS Safari's dynamic toolbar; 100vh is the fallback */
|
||||
box-sizing: border-box; }
|
||||
header { display:flex; justify-content:space-between; align-items:center; gap:.5rem; margin-bottom: .5rem; flex-shrink: 0; }
|
||||
#padname { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
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); }
|
||||
/* Icon buttons: inline SVG so glyphs render the same on every platform */
|
||||
|
|
@ -241,13 +245,25 @@ HTML = """<!doctype html>
|
|||
#lang { height:2rem; max-width:7.5rem; padding:0 .35rem; border:1px solid var(--border); border-radius:4px;
|
||||
background:var(--btn-bg); color:var(--text); font-size:.8rem; font-family:inherit; cursor:pointer; }
|
||||
#lang:disabled { opacity:.5; cursor:default; }
|
||||
/* Three groups: pad content | new pad | app UI. .25rem flex gap + .75rem = 1rem,
|
||||
i.e. half of the 2rem icon box. */
|
||||
#newpad, #theme-btn { margin-left:.75rem; }
|
||||
.ic { width:1.05rem; height:1.05rem; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; pointer-events:none; }
|
||||
#status { font-size:.9rem; opacity:.7; margin-left:.5rem; }
|
||||
/* Flex row so pad name, status and peer badge share one centre line. Baseline
|
||||
alignment cannot do this: #peers is an inline-flex box, whose baseline comes
|
||||
from its own first item rather than from the text beside it. */
|
||||
#meta { display:flex; align-items:center; gap:.5rem; min-width:0; }
|
||||
#status { font-size:.9rem; opacity:.7; white-space:nowrap; }
|
||||
#status::before { content:""; display:inline-block; width:.55em; height:.55em; border-radius:50%; margin-right:.35rem; background:#ef4444; }
|
||||
#status.connected::before { background:#22c55e; }
|
||||
#peers { font-size:.95rem; font-weight:bold; margin-left:.5rem; margin-right:.3rem; color:#22c55e; display:none; }
|
||||
#wrap { display:grid; grid-template-columns: max-content 1fr; border:1px solid var(--border); border-radius:4px; overflow:hidden;
|
||||
flex: 1; }
|
||||
/* Reads as a button to match the header, but it is a status badge: no hover, no pointer. */
|
||||
/* Bare status readout, no chrome. #meta's align-items:center keeps it on the
|
||||
header centre line, so no vertical-align is needed. */
|
||||
#peers { display:none; align-items:center; gap:.3rem; flex-shrink:0;
|
||||
color:#22c55e; font-size:.9rem; font-weight:700; line-height:1.4; }
|
||||
#peers .ic-sm { width:1.05rem; height:1.05rem; } /* scoped: the notice icon stays .9rem */
|
||||
#wrap { display:grid; grid-template-columns: max-content 1fr; grid-template-rows: 1fr auto;
|
||||
border:1px solid var(--border); border-radius:4px; overflow:hidden; flex: 1; }
|
||||
#gutter, #t, #hl { font: 14px/var(--line-h) ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
|
||||
#gutter { padding:.5rem .75rem; text-align:right; color:var(--gutter-color); background:var(--gutter-bg); border-right:1px solid var(--gutter-border);
|
||||
user-select:none; min-width: 3ch; white-space: pre; height: 100%; overflow: hidden; }
|
||||
|
|
@ -265,17 +281,19 @@ HTML = """<!doctype html>
|
|||
#wrap.hl #t::placeholder { color:var(--gutter-color); }
|
||||
#wrap.hl #t::selection { background:rgba(59,130,246,.35); }
|
||||
pre { margin: 0; }
|
||||
/* Dismissed with the checkbox hack so it needs no JS. Trade-off: dismissal lasts
|
||||
for the page view only, since there is no script to persist it. */
|
||||
#notice-dismiss { position:absolute; width:0; height:0; opacity:0; }
|
||||
#notice { flex-shrink:0; margin:.5rem 0 0; display:flex; gap:.75rem; align-items:flex-start;
|
||||
font-size:.75rem; line-height:1.45; color:var(--gutter-color); }
|
||||
#notice strong { color:var(--hl-attr); font-weight:600; }
|
||||
#notice label { margin-left:auto; flex-shrink:0; cursor:pointer; padding:0 .2rem;
|
||||
font-size:1rem; line-height:1.1; user-select:none; }
|
||||
#notice label:hover { color:var(--text); }
|
||||
#notice-dismiss:focus-visible + #notice label { outline:2px solid var(--hl-attr); border-radius:3px; }
|
||||
#notice-dismiss:checked + #notice { display:none; }
|
||||
/* Footer bar of the editor rather than loose text under it: it sits inside #wrap,
|
||||
so it inherits the box's border and rounded corners. */
|
||||
#notice { grid-column: 1 / -1; position:relative; display:flex; gap:.5rem;
|
||||
align-items:center; justify-content:center; text-align:center;
|
||||
margin:0; padding:.4rem 2rem; border-top:1px solid var(--border);
|
||||
background:var(--gutter-bg); font-size:.75rem; line-height:1.5;
|
||||
color:var(--text); cursor:pointer; }
|
||||
.ic-sm { width:.9rem; height:.9rem; flex-shrink:0; }
|
||||
/* Reset the global a,button rule: this is a bare glyph, not a control.
|
||||
Absolute so it stays at the right edge without offsetting the centred text. */
|
||||
#notice button { all:unset; position:absolute; right:.5rem; top:50%; transform:translateY(-50%);
|
||||
cursor:pointer; padding:0 .2rem; font-size:1rem; line-height:1.1; }
|
||||
#notice button:focus-visible { outline:2px solid currentColor; border-radius:3px; }
|
||||
/* highlight.js tokens, mapped onto the theme variables above so dark/light just works */
|
||||
.hljs-comment, .hljs-quote { color:var(--hl-comment); font-style:italic; }
|
||||
.hljs-keyword, .hljs-selector-tag, .hljs-literal, .hljs-type, .hljs-doctag { color:var(--hl-keyword); }
|
||||
|
|
@ -290,7 +308,8 @@ HTML = """<!doctype html>
|
|||
#lock-btn.locked { background:#dbeafe; border-color:#3b82f6; }
|
||||
[data-theme="dark"] #lock-btn.locked { background:#1e3a5f; border-color:#3b82f6; }
|
||||
#pw-panel { display:none; position:absolute; top:2.5rem; right:0; background:var(--panel-bg); border:1px solid var(--border);
|
||||
border-radius:6px; padding:.75rem; box-shadow:0 4px 12px rgba(0,0,0,.1); z-index:10; min-width:230px; }
|
||||
border-radius:6px; padding:.75rem; box-shadow:0 4px 12px rgba(0,0,0,.1); z-index:10;
|
||||
min-width:230px; max-width:calc(100vw - 2rem); }
|
||||
#pw-panel.open { display:block; }
|
||||
#pw-panel label { font-size:.8rem; font-weight:bold; display:block; margin-bottom:.4rem; }
|
||||
#pw-panel input { width:100%; padding:.35rem .5rem; border:1px solid var(--border); border-radius:4px;
|
||||
|
|
@ -301,13 +320,41 @@ HTML = """<!doctype html>
|
|||
#pw-overlay { display:none; position:fixed; inset:0; background:var(--overlay-bg); z-index:100;
|
||||
align-items:center; justify-content:center; }
|
||||
#pw-overlay.open { display:flex; }
|
||||
#pw-box { background:var(--panel-bg); border-radius:8px; padding:1.5rem; width:300px; }
|
||||
#pw-box { background:var(--panel-bg); border-radius:8px; padding:1.5rem; width:300px; max-width:calc(100vw - 2rem); }
|
||||
#pw-box h2 { margin:0 0 .75rem; font-size:1rem; }
|
||||
#auth-input { width:100%; padding:.45rem .6rem; border:1px solid var(--border); border-radius:4px;
|
||||
font-size:1rem; margin-bottom:.5rem; font-family:inherit; display:block; background:var(--btn-bg); color:var(--text); }
|
||||
#auth-error { color:#ef4444; font-size:.85rem; margin-bottom:.5rem; display:none; }
|
||||
#auth-submit { width:100%; padding:.45rem; background:#000; color:#fff; border:none;
|
||||
border-radius:4px; font-size:.95rem; cursor:pointer; }
|
||||
|
||||
/* --- Narrow screens: keep the header on one row by shedding text, not controls. --- */
|
||||
@media (max-width: 640px) {
|
||||
body { padding:.5rem; }
|
||||
/* Hide the word but keep the dot. It is a ::before on #status itself, sized in em,
|
||||
so it must be re-declared in rem to survive font-size:0. Doing it this way avoids
|
||||
wrapping the label in a child element, which would break the four places where
|
||||
JS assigns to $("#status").textContent. */
|
||||
#status { font-size:0; }
|
||||
#status::before { width:.55rem; height:.55rem; margin-right:0; }
|
||||
#lang { max-width:5.5rem; }
|
||||
#newpad, #theme-btn { margin-left:.4rem; }
|
||||
#notice { padding:.4rem 1.75rem; }
|
||||
}
|
||||
|
||||
/* Below ~450px the controls (5 buttons + dropdown = 281px) leave the pad name too
|
||||
little room to be legible - it degrades to "/..". Hide it rather than show a stub:
|
||||
it is not interactive and the phone's URL bar already displays it. */
|
||||
@media (max-width: 450px) {
|
||||
#padname { display:none; }
|
||||
}
|
||||
|
||||
/* --- Touch devices: 16px fields, because iOS Safari force-zooms into anything
|
||||
smaller on focus and never zooms back out. Button sizes are unchanged. --- */
|
||||
@media (pointer: coarse) {
|
||||
#gutter, #t, #hl { font-size:16px; } /* all three, or the overlay desyncs */
|
||||
#pw-panel input { font-size:1rem; }
|
||||
}
|
||||
</style>
|
||||
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false"><defs>
|
||||
<symbol id="i-copy" viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></symbol>
|
||||
|
|
@ -316,17 +363,21 @@ HTML = """<!doctype html>
|
|||
<symbol id="i-lock" viewBox="0 0 24 24"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></symbol>
|
||||
<symbol id="i-moon" viewBox="0 0 24 24"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></symbol>
|
||||
<symbol id="i-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M6.3 17.7l-1.4 1.4M19.1 4.9l-1.4 1.4"/></symbol>
|
||||
<symbol id="i-user" viewBox="0 0 24 24"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></symbol>
|
||||
<symbol id="i-info" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></symbol>
|
||||
</defs></svg>
|
||||
<header>
|
||||
<div>
|
||||
<strong id="padname"></strong><span id="peers"></span><span id="status">disconnected</span>
|
||||
<div id="meta">
|
||||
<strong id="padname"></strong><span id="status">disconnected</span><span id="peers"><svg class="ic ic-sm" aria-hidden="true"><use href="#i-user"/></svg><span id="peer-count"></span></span>
|
||||
</div>
|
||||
<div style="position:relative; display:flex; align-items:center; gap:.25rem;">
|
||||
<!-- Acts on this pad's content -->
|
||||
<select id="lang" title="Syntax highlighting for this pad" aria-label="Syntax highlighting" disabled></select>
|
||||
<button id="copy" class="ibtn" onclick="copyToClipboard()" title="Copy to clipboard" aria-label="Copy to clipboard"><svg class="ic"><use href="#i-copy"/></svg></button>
|
||||
<a id="newpad" class="ibtn" href="/" target="_blank" title="Opens a new pad in a new tab" aria-label="New pad"><svg class="ic"><use href="#i-plus"/></svg></a>
|
||||
<button id="lock-btn" class="ibtn" onclick="togglePwPanel()" title="No password – click to set one"><svg class="ic"><use href="#i-lock"/></svg></button>
|
||||
<!-- Leaves this pad -->
|
||||
<a id="newpad" class="ibtn" href="/" target="_blank" title="Opens a new pad in a new tab" aria-label="New pad"><svg class="ic"><use href="#i-plus"/></svg></a>
|
||||
<!-- App-level UI -->
|
||||
<button id="theme-btn" class="ibtn" onclick="toggleTheme()" title="Toggle dark/light mode" aria-label="Toggle dark/light mode"><svg class="ic"><use href="#i-moon"/></svg></button>
|
||||
<a id="info" class="ibtn" href="/system/info" title="System info" aria-label="System info"><svg class="ic"><use href="#i-info"/></svg></a>
|
||||
<div id="pw-panel">
|
||||
|
|
@ -348,14 +399,13 @@ HTML = """<!doctype html>
|
|||
<textarea id="t" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off"
|
||||
placeholder="Start typing…"></textarea>
|
||||
</div>
|
||||
<p id="notice" onclick="hideNotice()" title="Click to dismiss">
|
||||
<svg class="ic ic-sm" aria-hidden="true"><use href="#i-info"/></svg>
|
||||
<span>Content is deleted after __RETENTION_HOURS__ hours of inactivity and can be accessed
|
||||
by the server and anyone with the link and optional password.</span>
|
||||
<button type="button" aria-label="Dismiss notice">×</button>
|
||||
</p>
|
||||
</div>
|
||||
<input type="checkbox" id="notice-dismiss">
|
||||
<p id="notice">
|
||||
<span><strong>Please Note:</strong> no client-side encryption — the server and anyone with the
|
||||
link and password can access the content. Content is deleted after __RETENTION_HOURS__ hours
|
||||
of inactivity.</span>
|
||||
<label for="notice-dismiss" title="Dismiss" aria-label="Dismiss notice">×</label>
|
||||
</p>
|
||||
|
||||
<div id="pw-overlay">
|
||||
<div id="pw-box">
|
||||
|
|
@ -400,6 +450,16 @@ function toggleTheme() {
|
|||
});
|
||||
})();
|
||||
|
||||
// Bottom notice: click anywhere on it to dismiss, remembered across pads and reloads.
|
||||
function hideNotice() {
|
||||
$("#notice").style.display = "none";
|
||||
try { localStorage.setItem("notice", "off"); } catch (e) {}
|
||||
}
|
||||
(function() {
|
||||
try { if (localStorage.getItem("notice") === "off") $("#notice").style.display = "none"; }
|
||||
catch (e) {} // private mode / storage disabled: just leave the notice up
|
||||
})();
|
||||
|
||||
// Derive docId from path; redirect root to random
|
||||
let docId = decodeURIComponent(location.pathname.replace(/(^\\/|\\/$)/g, ""));
|
||||
if (!docId) { location.replace("/" + rand() + "/"); }
|
||||
|
|
@ -587,6 +647,13 @@ function submitAuth() {
|
|||
}
|
||||
}
|
||||
|
||||
function setPeers(n) {
|
||||
const el = $("#peers");
|
||||
$("#peer-count").textContent = n; // never el.textContent: it would wipe the <svg>
|
||||
el.title = n + (n === 1 ? " peer" : " peers") + " connected";
|
||||
el.style.display = "inline-flex";
|
||||
}
|
||||
|
||||
// --- WS connect + sync ---
|
||||
function connect(){
|
||||
isAuthed = false;
|
||||
|
|
@ -603,7 +670,7 @@ function connect(){
|
|||
if (msg.type === "init") {
|
||||
isProtected = !!msg.protected;
|
||||
updateLockBtn();
|
||||
if (msg.peers !== undefined) { const el = $("#peers"); el.textContent = msg.peers; el.style.display = "inline"; }
|
||||
if (msg.peers !== undefined) setPeers(msg.peers);
|
||||
if (isProtected && !isAuthed) {
|
||||
if (urlPw) {
|
||||
ws.send(JSON.stringify({type: "auth", password: urlPw}));
|
||||
|
|
@ -648,9 +715,7 @@ function connect(){
|
|||
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;
|
||||
el.style.display = "inline";
|
||||
setPeers(msg.count);
|
||||
} else if (msg.type === "lang_changed") {
|
||||
applyLang(msg.lang || "");
|
||||
} else if (msg.type === "protected_changed") {
|
||||
|
|
@ -803,6 +868,7 @@ def get_system_info():
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>aukpad - System Info</title>
|
||||
<style>
|
||||
:root {{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue