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.
82 lines
3.7 KiB
HTML
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>
|