theseus/quicklinks.html
Silent Mode 08ecd093d2 Theseus 0.3.67: Opera-style quick-links strip on the left edge
New thin vertical column (44 px) on the left side of every page, Opera-style.
Click an icon to open its web app in a new tab; if a tab is already open on
that host, we focus it instead of stacking another one. Hidden in HTML
fullscreen so a video still fills the window; toggle via Settings › General ›
Quick links › Show the strip.

Defaults ship X, Telegram and WhatsApp. The Settings › General › Quick links
section lists the current entries with a Remove button each and a Title + URL
+ Add row that auto-prefixes https:// and auto-fills the title from the
hostname when empty. Edits write the whole settings.quickLinks array; the
strip view and the window layout react through settings-set, so no restart is
needed.

Settings › General › Updates panel also now runs standalone (no longer gated
by anything in the shared cfg.get().then() init), so a thrown exception in an
unrelated feature can't leave it stuck on "Loading…" any more — the version
line reads immediately and Check for Updates stays functional.
2026-10-02 23:49:39 +02:00

82 lines
3.7 KiB
HTML

<!doctype html>
<html>
<head><meta charset="utf-8">
<style>
:root {
color-scheme: light dark;
--bg:#0e131c; --ink:#e7eaf1; --mut:#8b98a9; --line2:rgba(255,255,255,.13);
--acid:#d6ff3d;
}
@media (prefers-color-scheme: light) {
:root { --bg:#eceff3; --ink:#1a1f28; --mut:#5c6676; --line2:rgba(0,0,0,.10); --acid:#0AC18E; }
}
html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; overflow: hidden;
user-select: none; }
.strip { display: flex; flex-direction: column; align-items: center;
padding: 10px 0; gap: 4px; height: 100%; box-sizing: border-box;
border-right: 1px solid var(--line2); }
.icon { width: 36px; height: 36px; border-radius: 8px; display: grid; place-items: center;
cursor: pointer; background: transparent; border: none; padding: 0; color: var(--mut);
position: relative; }
.icon:hover { background: var(--line2); color: var(--ink); }
.icon:active { transform: scale(0.94); }
.icon svg { width: 20px; height: 20px; display: block; }
.icon img { width: 22px; height: 22px; display: block; border-radius: 4px; }
.icon .tt { position: absolute; left: 44px; top: 50%; transform: translateY(-50%);
background: var(--bg); color: var(--ink); padding: 4px 8px; border-radius: 6px;
border: 1px solid var(--line2); white-space: nowrap; font-size: 12px;
pointer-events: none; opacity: 0; transition: opacity .1s; z-index: 10; }
.icon:hover .tt { opacity: 1; transition-delay: .4s; }
.sep { width: 24px; height: 1px; background: var(--line2); margin: 6px 0; flex: none; }
.spacer { flex: 1 1 auto; }
/* Add button — small plus to add a custom link. Prompts for URL via main. */
.add { color: var(--mut); font: 500 20px/1 system-ui, sans-serif; }
.add:hover { color: var(--acid); }
</style></head>
<body>
<div class="strip" id="strip"></div>
<script>
const Q = window.quicklinks;
const strip = document.getElementById("strip");
// The links come from main (saved in settings.quickLinks). Each entry is
// { id, url, title, icon } where icon is either a letter, an emoji, or a
// data: URL for a bundled brand SVG.
function render(links) {
strip.innerHTML = "";
for (const L of (links || [])) {
const b = document.createElement("button");
b.className = "icon";
b.title = L.title || L.url;
// Icon: a data URL (img) or a short text badge.
if (L.icon && /^(data:|https?:|file:)/.test(L.icon)) {
const img = document.createElement("img");
img.src = L.icon; img.alt = "";
img.onerror = () => { b.textContent = (L.title || "?").slice(0, 1).toUpperCase(); };
b.appendChild(img);
} else if (L.svg) {
b.innerHTML = L.svg;
} else {
b.textContent = (L.title || "?").slice(0, 1).toUpperCase();
}
const tt = document.createElement("span");
tt.className = "tt"; tt.textContent = L.title || L.url;
b.appendChild(tt);
b.onclick = () => Q && Q.open(L.id);
strip.appendChild(b);
}
// Spacer pushes the + to the bottom.
const sp = document.createElement("div"); sp.className = "spacer";
strip.appendChild(sp);
const add = document.createElement("button");
add.className = "icon add"; add.textContent = "+"; add.title = "Add link";
const addTt = document.createElement("span"); addTt.className = "tt"; addTt.textContent = "Add link";
add.appendChild(addTt);
add.onclick = () => Q && Q.add();
strip.appendChild(add);
}
Q && Q.getLinks && Q.getLinks().then(render).catch(() => render([]));
Q && Q.onLinks && Q.onLinks(render);
</script>
</body>
</html>