Merge pull request #329 from PR0M3TH3AN/codex/implement-collapsible-sidebar-on-desktop

Improve desktop sidebar collapse behavior
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2025-10-06 20:11:15 -04:00
commit d1d481555d
13 files changed
+645 -303

No files matched your search

+5
View File
@@ -70,6 +70,11 @@ Document the run in PR descriptions so QA can cross-reference results.
* **Probing:** Lightweight `HEAD`/`GET` requests should back `probeUrl()` so dead URLs can be hidden or flagged without blocking the UI.
* **Extensibility:** Future work (live streams, NIP-96 uploads, analytics) should preserve the URL-first strategy and magnet safety rules above.
* **Sidebar layering:** Keep the fixed sidebar below every modal and overlay. Respect the `--z-sidebar*` tokens so dialogs always render on top of the navigation.
* **Static asset cache busting:** The CDN aggressively caches `components/sidebar.html` and CSS/JS bundles. Update `config/asset-version.js` and keep the `?v=` query strings in `index.html` (and iframe form shells) in sync whenever you change sidebar markup, scripts, or styles. The fetch helpers in `js/index.js` already append `cache: "no-store"`; reuse them for new partials so desktop collapses ship immediately without waiting on manual cache clears.
* **View partial cache busting:** The `views/*.html` files are served from the same CDN tier and require the asset version suffix as well. `js/viewManager.js` now appends `?v=<ASSET_VERSION>` when loading views—keep that helper in place so layout or styling updates propagate on the first refresh.
* **Sidebar overflow styling:** `#sidebar` now owns vertical scrolling with `overflow-y: auto` while keeping `overflow-x: visible` so the gradient panel’s drop shadow can spill onto the page. The nested `.sidebar-scroll` wrapper must remain `overflow: visible`; do not reintroduce hidden overflow on any ancestor or the shadow will clip again.
* **Collapsed rail padding:** The collapsed rail keeps icons full-size by pairing reduced padding on `.sidebar__inner` and `.sidebar-panel` with the shared `--sidebar-control-size` (2.75 rem). Update the trio together so the 4 rem rail still fits the circular toggle/dropup buttons without squishing the gradients.
* **Collapsed nav width:** Collapsed links rely on `width: var(--sidebar-control-size)` plus `margin-inline: auto` to keep every glyph aligned with the circular toggle and footer trigger. Preserve that sizing or the icons will drift off the gradient panel.
---
@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<title>bitvid Whitelist Application Form</title>
<!-- Link to your main stylesheet -->
<link rel="stylesheet" href="../../css/style.css" />
<link rel="stylesheet" href="../../css/style.css?v=2024.10.22" />
<style>
/* Override for form page to match modal field styling */
@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<title>bitvid Bug Report Form</title>
<!-- Link to your main stylesheet -->
<link rel="stylesheet" href="../../css/style.css" />
<link rel="stylesheet" href="../../css/style.css?v=2024.10.22" />
<style>
/* Override for form page to match modal field styling */
@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<title>bitvid Content Appeals Form</title>
<!-- Link to your main stylesheet -->
<link rel="stylesheet" href="../../css/style.css" />
<link rel="stylesheet" href="../../css/style.css?v=2024.10.22" />
<style>
/* Override for form page to match modal field styling */
@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<title>bitvid General Feedback Form</title>
<!-- Link to your main stylesheet -->
<link rel="stylesheet" href="../../css/style.css" />
<link rel="stylesheet" href="../../css/style.css?v=2024.10.22" />
<style>
/* Override for form page to match modal field styling */
@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<title>bitvid Feature Request Form</title>
<!-- Link to your main stylesheet -->
<link rel="stylesheet" href="../../css/style.css" />
<link rel="stylesheet" href="../../css/style.css?v=2024.10.22" />
<style>
/* Override for form page to match modal field styling */
+116 -129
View File
@@ -5,25 +5,27 @@
-->
<aside
id="sidebar"
class="bg-gray-900 text-white transition-transform duration-300 ease-in-out fixed top-0 left-0 w-64 h-screen overflow-y-auto fade-in fade-in-delay-100"
class="fade-in fade-in-delay-100 sidebar-collapsed"
data-state="collapsed"
>
<div class="flex flex-col h-full">
<!-- Top Navigation Links -->
<div class="flex-1 overflow-y-auto px-4">
<div class="bg-gray-800 rounded-lg shadow-md mt-4 p-4">
<div class="hidden md:flex justify-end mb-2">
<div class="sidebar__inner">
<div class="sidebar-scroll">
<!-- Primary navigation -->
<section class="sidebar-panel sidebar-panel--primary">
<div class="sidebar-panel__header">
<button
id="sidebarCollapseToggle"
type="button"
class="flex items-center space-x-2 text-sm font-semibold text-gray-100"
class="sidebar-collapse-toggle"
aria-controls="sidebar"
aria-expanded="true"
aria-expanded="false"
aria-label="Expand sidebar"
title="Expand sidebar"
>
<span class="sr-only">Toggle sidebar</span>
<span class="sidebar-toggle-label">Collapse</span>
<svg
data-sidebar-chevron
class="w-4 h-4 transform transition-transform duration-300"
class="sidebar-collapse-toggle__icon sidebar-toggle-icon--rotated"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
@@ -36,144 +38,141 @@
</svg>
</button>
</div>
<nav class="flex flex-col space-y-2">
<a
href="#view=most-recent-videos"
class="flex items-center py-2 px-4 hover:bg-gray-700 rounded font-semibold"
>
<nav class="sidebar-nav" aria-label="Primary">
<a href="#view=most-recent-videos" class="sidebar-nav-link" title="Recent">
<img
src="assets/svg/recent-icon.svg"
alt="Recent"
class="w-6 h-6 mr-3 flex-shrink-0"
alt=""
aria-hidden="true"
class="sidebar-icon"
/>
<span class="sidebar-text">Recent</span>
</a>
<a
href="#view=explore"
class="flex items-center py-2 px-4 hover:bg-gray-700 rounded font-semibold"
>
<a href="#view=explore" class="sidebar-nav-link" title="Explore">
<img
src="assets/svg/explore-icon.svg"
alt="Explore"
class="w-6 h-6 mr-3 flex-shrink-0"
alt=""
aria-hidden="true"
class="sidebar-icon"
/>
<span class="sidebar-text">Explore</span>
</a>
<a
href="#view=history"
class="flex items-center py-2 px-4 hover:bg-gray-700 rounded font-semibold"
>
<a href="#view=history" class="sidebar-nav-link" title="History">
<img
src="assets/svg/history-icon.svg"
alt="History"
class="w-6 h-6 mr-3 flex-shrink-0"
alt=""
aria-hidden="true"
class="sidebar-icon"
/>
<span class="sidebar-text">History</span>
</a>
<a
id="subscriptionsLink"
href="#view=subscriptions"
class="hidden flex items-center py-2 px-4 hover:bg-gray-700 rounded font-semibold"
class="sidebar-nav-link hidden"
title="Subscriptions"
>
<img
src="assets/svg/subscriptions-icon.svg"
alt="Subscriptions"
class="w-6 h-6 mr-3 flex-shrink-0"
alt=""
aria-hidden="true"
class="sidebar-icon"
/>
<span class="sidebar-text">Subscriptions</span>
</a>
</nav>
</div>
</div>
</section>
<!-- Footer Links Toggle -->
<div class="md:mt-auto">
<div class="p-4">
<!-- Footer Links Toggle -->
<footer class="sidebar-footer" data-footer-state="collapsed">
<button
id="footerDropdownButton"
type="button"
class="flex items-center w-full py-2 px-4 bg-gray-800 hover:bg-gray-700 text-sm font-semibold text-gray-100 rounded shadow-md focus:outline-none transition-colors duration-200"
class="sidebar-dropup-trigger"
aria-expanded="false"
aria-controls="footerLinksContainer"
>
<!-- Arrow on the left side -->
<img
src="assets/svg/ellipsis.svg"
alt=""
aria-hidden="true"
class="sidebar-icon sidebar-dropup-trigger__glyph"
/>
<span id="footerDropdownText" class="sidebar-text">More</span>
<svg
id="footerDropdownIcon"
class="w-4 h-4 mr-2 transform transition-transform duration-300"
class="sidebar-dropup-trigger__chevron"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"
/>
<path d="M6 9l6 6 6-6" />
</svg>
<span id="footerDropdownText">More</span>
</button>
</div>
<!-- Footer Links Container -->
<div id="footerLinksContainer" class="hidden">
<hr class="border-gray-700" />
<div class="p-4">
<nav class="space-y-2 text-sm opacity-70">
<a
href="#view=about"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
>
<!-- Footer Links Container -->
<div
id="footerLinksContainer"
class="sidebar-dropup-panel"
aria-hidden="true"
data-state="collapsed"
>
<nav class="sidebar-dropup-panel__inner" aria-label="Secondary">
<a href="#view=about" class="sidebar-nav-link sidebar-nav-link--sub" title="About">
<img
src="assets/svg/about-icon.svg"
alt="About"
class="w-5 h-5 mr-3"
alt=""
aria-hidden="true"
class="sidebar-icon"
/>
<span class="sidebar-text">About</span>
</a>
<a
href="#view=community-guidelines"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
class="sidebar-nav-link sidebar-nav-link--sub"
title="Community Guidelines"
>
<img
src="assets/svg/guidelines-icon.svg"
alt="Guidelines"
class="w-5 h-5 mr-3"
alt=""
aria-hidden="true"
class="sidebar-icon"
/>
<span class="sidebar-text">Guidelines</span>
</a>
<a
href="#view=getting-started"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
class="sidebar-nav-link sidebar-nav-link--sub"
title="Getting Started"
>
<img
src="assets/svg/getting-started-icon.svg"
alt="Getting Started"
class="w-5 h-5 mr-3"
alt=""
aria-hidden="true"
class="sidebar-icon"
/>
<span class="sidebar-text">Getting Started</span>
</a>
<a
href="#view=roadmap"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
>
<a href="#view=roadmap" class="sidebar-nav-link sidebar-nav-link--sub" title="Roadmap">
<img
src="assets/svg/roadmap-icon.svg"
alt="Roadmap"
class="w-5 h-5 mr-3"
alt=""
aria-hidden="true"
class="sidebar-icon"
/>
<span class="sidebar-text">Roadmap</span>
</a>
<a
href="#view=blog"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
>
<a href="#view=blog" class="sidebar-nav-link sidebar-nav-link--sub" title="Blog">
<img
src="assets/svg/blog-icon.svg"
alt="Blog"
class="w-5 h-5 mr-3"
alt=""
aria-hidden="true"
class="sidebar-icon"
/>
<span class="sidebar-text">Blog</span>
</a>
@@ -181,12 +180,14 @@
href="https://primal.net/p/npub13yarr7j6vjqjjkahd63dmr27curypehx45ucue286ac7sft27y0srnpmpe"
target="_blank"
rel="noopener noreferrer"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
class="sidebar-nav-link sidebar-nav-link--sub"
title="Nostr"
>
<img
src="assets/svg/nostr-icon.svg"
alt="Nostr"
class="w-5 h-5 mr-3"
alt=""
aria-hidden="true"
class="sidebar-icon"
/>
<span class="sidebar-text">Nostr</span>
</a>
@@ -194,12 +195,14 @@
href="https://github.com/PR0M3TH3AN/bitvid"
target="_blank"
rel="noopener noreferrer"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
class="sidebar-nav-link sidebar-nav-link--sub"
title="GitHub"
>
<img
src="assets/svg/github-icon.svg"
alt="GitHub"
class="w-5 h-5 mr-3"
alt=""
aria-hidden="true"
class="sidebar-icon"
/>
<span class="sidebar-text">GitHub</span>
</a>
@@ -207,12 +210,14 @@
href="torrent/beacon.html"
target="_blank"
rel="noopener noreferrer"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
class="sidebar-nav-link sidebar-nav-link--sub"
title="βeacon"
>
<img
src="assets/svg/beacon-icon.svg"
alt="βeacon"
class="w-5 h-5 mr-3"
alt=""
aria-hidden="true"
class="sidebar-icon"
/>
<span class="sidebar-text">βeacon</span>
</a>
@@ -220,34 +225,32 @@
href="https://beta.bitvid.network/"
target="_blank"
rel="noopener noreferrer"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
class="sidebar-nav-link sidebar-nav-link--sub"
title="Beta"
>
<img
src="assets/svg/beta-icon.svg"
alt="Beta"
class="w-5 h-5 mr-3"
alt=""
aria-hidden="true"
class="sidebar-icon"
/>
<span class="sidebar-text">Beta</span>
</a>
<a
href="#view=links"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
>
<a href="#view=links" class="sidebar-nav-link sidebar-nav-link--sub" title="Links">
<img
src="assets/svg/links-icon.svg"
alt="Links"
class="w-5 h-5 mr-3"
alt=""
aria-hidden="true"
class="sidebar-icon"
/>
<span class="sidebar-text">Links</span>
</a>
<a
href="#view=ipns"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
>
<a href="#view=ipns" class="sidebar-nav-link sidebar-nav-link--sub" title="IPNS">
<img
src="assets/svg/ipns-icon.svg"
alt="IPNS"
class="w-5 h-5 mr-3"
alt=""
aria-hidden="true"
class="sidebar-icon"
/>
<span class="sidebar-text">IPNS</span>
</a>
@@ -255,36 +258,20 @@
href="https://bitvid.network/"
target="_blank"
rel="noopener noreferrer"
class="flex items-center hover:bg-gray-800 px-4 py-2 rounded"
class="sidebar-nav-link sidebar-nav-link--sub"
title="DNS"
>
<img src="assets/svg/dns-icon.svg" alt="DNS" class="w-5 h-5 mr-3" />
<img
src="assets/svg/dns-icon.svg"
alt=""
aria-hidden="true"
class="sidebar-icon"
/>
<span class="sidebar-text">DNS</span>
</a>
</nav>
</div>
</div>
</footer>
</div>
</div>
<script>
(function () {
var btn = document.getElementById("footerDropdownButton");
var footerLinks = document.getElementById("footerLinksContainer");
if (btn && footerLinks) {
btn.addEventListener("click", function () {
footerLinks.classList.toggle("hidden");
var isHidden = footerLinks.classList.contains("hidden");
btn.setAttribute("aria-expanded", String(!isHidden));
var icon = document.getElementById("footerDropdownIcon");
var textSpan = document.getElementById("footerDropdownText");
if (textSpan) {
textSpan.textContent = isHidden ? "More" : "Less";
}
if (icon) {
icon.classList.toggle("rotate-180", !isHidden);
}
});
}
})();
</script>
</aside>
+1
View File
@@ -0,0 +1 @@
export const ASSET_VERSION = "2024.10.22";
+360 -109
View File
@@ -1,10 +1,18 @@
:root {
--color-bg: #0f172a;
/*
Core palette tokens used across the application.
--color-page keeps the primary content surface neutral while --color-surface
powers the dark shells (sidebar, dialogs, cards).
*/
--color-page: #ffffff;
--color-surface: #0f172a;
--color-bg: var(--color-surface);
--color-card: #1e293b;
--color-primary: #fe0032;
--color-secondary: #ff93a5;
--color-card-critical: #3f0b13;
--color-text: #f8fafc;
--color-text-strong: #0f172a;
--color-muted: #94a3b8;
--shadow-sm: 0 0.0625rem 0.125rem 0 rgb(0 0 0 / 0.05);
--shadow-md: 0 0.25rem 0.375rem -0.0625rem rgb(0 0 0 / 0.1);
@@ -21,6 +29,10 @@
--z-modal-overlay: 70;
--z-modal-content: 90;
--z-modal-player: 120;
--sidebar-width-expanded: 16rem;
--sidebar-width-collapsed: 4rem;
--sidebar-width: var(--sidebar-width-expanded);
--sidebar-control-size: 2.75rem;
}
html {
@@ -60,14 +72,23 @@ html {
/* Core Styles */
body {
font-family: system-ui, -apple-system, sans-serif;
background-color: var(--color-bg);
color: var(--color-text);
background-color: var(--color-page);
color: var(--color-text-strong);
line-height: 1.5;
margin: 0;
padding: 0;
overflow-x: hidden; /* Disable horizontal scrolling */
}
#app {
/*
Keep the primary content surface aligned with the page background so the
sidebar styling does not bleed into the video grid.
*/
background-color: var(--color-page);
color: inherit;
}
header {
margin-bottom: 2rem;
padding: 1rem 0;
@@ -1448,22 +1469,271 @@ footer a:hover {
}
/* Sidebar default states */
#sidebarContainer {
position: relative;
overflow: visible;
}
#sidebar {
position: fixed;
top: 0;
left: 0;
width: 16rem; /* Tailwind's w-64 */
width: var(--sidebar-width);
min-width: var(--sidebar-width);
height: 100vh;
height: 100dvh;
background-color: #0f172a;
background-color: transparent;
color: var(--color-text);
overflow-x: visible;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
transition: transform 0.3s ease, box-shadow 0.3s ease;
overscroll-behavior: contain;
scrollbar-gutter: stable both-edges;
transition: width 200ms ease, transform 0.3s ease, box-shadow 0.3s ease;
/* Keep below modal stacks so dialogs overlay the drawer. */
z-index: var(--z-sidebar);
box-shadow: none;
}
#sidebar .sidebar__inner {
display: flex;
flex-direction: column;
height: 100%;
padding: 1.5rem 1.25rem 1.75rem;
position: relative;
width: 100%;
/*
Allow the gradient panel shadow to breathe outside the rail while a nested
scroll container handles overflow.
*/
overflow: visible;
}
/*
The scroll wrapper keeps nav + footer together while leaving overflow
visible so the gradient card shadow is never clipped.
*/
.sidebar-scroll {
display: flex;
flex-direction: column;
gap: 1.5rem;
width: 100%;
height: 100%;
min-height: 0;
flex: 1 1 auto;
overflow: visible;
}
.sidebar-panel {
background: linear-gradient(
160deg,
rgba(30, 41, 59, 0.92) 0%,
rgba(15, 23, 42, 0.92) 100%
);
border: 1px solid rgba(148, 163, 184, 0.18);
border-radius: 1rem;
box-shadow: 0 1.25rem 2.5rem rgba(15, 23, 42, 0.35);
padding: 1.25rem 1rem;
width: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 1.25rem;
}
.sidebar-panel--primary {
flex: 1 1 auto;
min-height: 0;
}
.sidebar-panel__header {
display: none;
justify-content: flex-end;
margin-bottom: 1rem;
}
.sidebar-collapse-toggle {
display: none;
align-items: center;
justify-content: center;
width: var(--sidebar-control-size);
height: var(--sidebar-control-size);
border-radius: 9999px;
border: 1px solid rgba(148, 163, 184, 0.2);
background: rgba(148, 163, 184, 0.08);
color: inherit;
cursor: pointer;
transition: background 180ms ease, color 180ms ease, border-color 180ms ease;
flex-shrink: 0;
box-sizing: border-box;
}
.sidebar-collapse-toggle:hover {
background: rgba(148, 163, 184, 0.18);
border-color: rgba(148, 163, 184, 0.35);
color: #f8fafc;
}
.sidebar-collapse-toggle:focus-visible {
outline: 0.125rem solid var(--color-secondary);
outline-offset: 0.25rem;
}
.sidebar-collapse-toggle__icon {
width: 1.125rem;
height: 1.125rem;
transition: transform 200ms ease;
}
.sidebar-nav {
display: flex;
flex-direction: column;
gap: 0.25rem;
opacity: 1;
transition: opacity 160ms ease;
width: 100%;
}
.sidebar-nav-link {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.625rem 0.875rem;
min-height: var(--sidebar-control-size);
border-radius: 0.875rem;
font-size: 0.95rem;
font-weight: 600;
color: rgba(226, 232, 240, 0.94);
text-decoration: none;
transition: background 160ms ease, color 160ms ease, transform 160ms ease;
width: 100%;
box-sizing: border-box;
}
.sidebar-nav-link:hover {
background: rgba(148, 163, 184, 0.18);
color: #f8fafc;
}
.sidebar-nav-link:focus-visible {
outline: 0.125rem solid var(--color-secondary);
outline-offset: 0.25rem;
}
.sidebar-nav-link--sub {
font-size: 0.85rem;
font-weight: 500;
color: rgba(226, 232, 240, 0.82);
padding-block: 0.55rem;
}
.sidebar-icon {
/* Lock icon sizing so the glyphs remain steady during collapse/expand. */
width: 1.5rem;
height: 1.5rem;
min-width: 1.5rem;
min-height: 1.5rem;
flex-shrink: 0;
display: block;
}
.sidebar-footer {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-top: auto;
position: relative;
z-index: 2;
}
.sidebar-dropup-trigger {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
width: 100%;
padding: 0.75rem 0.875rem;
min-height: var(--sidebar-control-size);
border-radius: 0.875rem;
border: 1px solid rgba(148, 163, 184, 0.18);
background: rgba(30, 41, 59, 0.85);
color: rgba(226, 232, 240, 0.9);
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: background 160ms ease, border-color 160ms ease, color 160ms ease;
box-sizing: border-box;
flex-shrink: 0;
}
.sidebar-dropup-trigger:hover {
background: rgba(51, 65, 85, 0.95);
border-color: rgba(148, 163, 184, 0.32);
color: #f8fafc;
}
.sidebar-dropup-trigger:focus-visible {
outline: 0.125rem solid var(--color-secondary);
outline-offset: 0.25rem;
}
.sidebar-dropup-trigger__glyph {
width: 1.25rem;
height: 1.25rem;
opacity: 0.9;
}
.sidebar-dropup-trigger__chevron {
width: 1.125rem;
height: 1.125rem;
transition: transform 180ms ease;
}
.sidebar-dropup-trigger__chevron.is-rotated,
.sidebar-dropup-trigger[aria-expanded="true"] .sidebar-dropup-trigger__chevron {
transform: rotate(180deg);
}
.sidebar-dropup-panel {
background: rgba(15, 23, 42, 0.88);
border: 1px solid rgba(148, 163, 184, 0.18);
border-radius: 0.875rem;
box-shadow: 0 1.5rem 3rem rgba(15, 23, 42, 0.4);
padding: 0.75rem 0.5rem;
position: absolute;
left: 0;
right: 0;
bottom: calc(100% + 0.75rem);
opacity: 0;
visibility: hidden;
pointer-events: none;
transform: translateY(0.5rem);
transition: opacity 160ms ease, transform 160ms ease, visibility 160ms ease;
z-index: 5;
}
.sidebar-dropup-panel[data-state="expanded"] {
opacity: 1;
visibility: visible;
pointer-events: auto;
transform: translateY(0);
}
.sidebar-dropup-panel__inner {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
#sidebar[data-footer-state="expanded"] .sidebar-nav {
opacity: 0;
pointer-events: none;
}
#sidebar[data-footer-state="collapsed"] .sidebar-nav {
opacity: 1;
}
#sidebar.sidebar-open {
box-shadow: 0 0.75rem 1.5rem rgba(15, 23, 42, 0.45);
}
@@ -1491,7 +1761,7 @@ body.sidebar-open #sidebarOverlay {
@media (max-width: 767px) {
#sidebar {
transform: translate3d(-100%, 0, 0);
width: min(16rem, calc(100vw - 3rem));
width: min(var(--sidebar-width-expanded), calc(100vw - 3rem));
}
/* When the sidebar-open class is added, slide the sidebar in */
#sidebar.sidebar-open {
@@ -1505,11 +1775,18 @@ body.sidebar-open #sidebarOverlay {
/* Desktop (min-width: 768px): Always show the sidebar */
@media (min-width: 768px) {
.sidebar-panel__header {
display: flex;
}
.sidebar-collapse-toggle {
display: inline-flex;
}
#sidebar {
transform: translateX(0) !important;
width: 16rem;
overflow-x: hidden;
transition: width 200ms ease, transform 0.3s ease, box-shadow 0.3s ease;
width: var(--sidebar-width);
overflow: visible;
}
#sidebar.sidebar-open {
@@ -1520,53 +1797,74 @@ body.sidebar-open #sidebarOverlay {
display: none;
}
body.sidebar-collapsed #sidebar,
#app {
margin-left: var(--sidebar-width);
transition: margin-left 200ms ease;
}
body.sidebar-collapsed,
#sidebar.sidebar-collapsed {
width: 4rem;
--sidebar-width: var(--sidebar-width-collapsed);
}
body.sidebar-expanded #sidebar,
body.sidebar-expanded,
#sidebar.sidebar-expanded {
width: 16rem;
--sidebar-width: var(--sidebar-width-expanded);
}
body.sidebar-collapsed #sidebar a,
#sidebar.sidebar-collapsed a {
body.sidebar-collapsed #sidebar .sidebar-nav-link,
#sidebar.sidebar-collapsed .sidebar-nav-link {
/*
Keep icons centered in the collapsed rail without resizing them.
*/
justify-content: center;
padding-inline: 1rem;
width: var(--sidebar-control-size);
padding: 0;
margin-inline: auto;
border-radius: 9999px;
}
body.sidebar-expanded #sidebar a,
#sidebar.sidebar-expanded a {
body.sidebar-expanded #sidebar .sidebar-nav-link,
#sidebar.sidebar-expanded .sidebar-nav-link {
justify-content: flex-start;
padding-inline: 1rem;
}
body.sidebar-collapsed #sidebar a > img,
body.sidebar-collapsed #sidebar a svg,
#sidebar.sidebar-collapsed a > img,
#sidebar.sidebar-collapsed a svg {
margin-right: 0;
margin-inline-end: 0;
}
body.sidebar-expanded #sidebar a > img,
body.sidebar-expanded #sidebar a svg,
#sidebar.sidebar-expanded a > img,
#sidebar.sidebar-expanded a svg {
margin-right: 0.75rem;
margin-inline-end: 0.75rem;
}
body.sidebar-collapsed #sidebar .sidebar-text,
body.sidebar-collapsed #sidebar .sidebar-toggle-label,
#sidebar.sidebar-collapsed .sidebar-text,
#sidebar.sidebar-collapsed .sidebar-toggle-label {
#sidebar.sidebar-collapsed .sidebar-text {
display: none;
}
#sidebar.sidebar-expanded .sidebar-toggle-label {
display: inline;
body.sidebar-collapsed #sidebar .sidebar__inner,
#sidebar.sidebar-collapsed .sidebar__inner {
/*
Keep enough breathing room for the panel shadow while widening the
interior track so var(--sidebar-control-size) controls stay perfectly
round.
*/
padding-inline: 0.25rem;
}
body.sidebar-collapsed #sidebar .sidebar-panel,
#sidebar.sidebar-collapsed .sidebar-panel {
/*
Mirror the outer adjustment so the gradient card retains its rounded
edges without squeezing the icon rail or clipping the control diameter.
*/
padding-inline: 0.375rem;
}
body.sidebar-collapsed #sidebar .sidebar-panel__header,
#sidebar.sidebar-collapsed .sidebar-panel__header {
justify-content: center;
}
body.sidebar-collapsed #sidebar .sidebar-collapse-toggle,
#sidebar.sidebar-collapsed .sidebar-collapse-toggle {
margin-inline: auto;
}
#sidebar .sidebar-toggle-icon--rotated {
transform: rotate(180deg);
}
body.sidebar-collapsed #sidebar .sidebar-toggle-icon--rotated,
@@ -1574,81 +1872,34 @@ body.sidebar-open #sidebarOverlay {
transform: rotate(0deg);
}
body.sidebar-expanded #sidebar .sidebar-toggle-icon--rotated,
#sidebar.sidebar-expanded .sidebar-toggle-icon--rotated {
transform: rotate(180deg);
body.sidebar-collapsed #sidebar .sidebar-dropup-trigger {
/*
Align the "More" control with the centered icon rail while keeping its
padding consistent so the glyph does not scale between states.
*/
justify-content: center;
width: var(--sidebar-control-size);
height: var(--sidebar-control-size);
padding: 0;
margin-inline: auto;
border-radius: 9999px;
}
body.sidebar-collapsed #app {
margin-left: 4rem !important;
body.sidebar-collapsed #sidebar .sidebar-dropup-trigger .sidebar-text,
#sidebar.sidebar-collapsed .sidebar-dropup-trigger .sidebar-text {
display: none;
}
body.sidebar-expanded #app {
margin-left: 16rem !important;
body.sidebar-collapsed #sidebar .sidebar-dropup-trigger__chevron {
display: none;
}
body.sidebar-collapsed #sidebar .sidebar-dropup-trigger__glyph,
#sidebar.sidebar-collapsed .sidebar-dropup-trigger__glyph {
margin: 0;
}
}
/* Example: customizing the border & background in the sidebar */
#sidebar hr {
border-color: rgba(255, 255, 255, 0.1);
}
/* Sidebar drop-up menu styling */
.sidebar-dropup {
display: flex;
flex-direction: column-reverse;
gap: 0.75rem;
}
.sidebar-dropup-panel {
background-color: #1f2937; /* gray-800 */
border: 0.0625rem solid rgba(148, 163, 184, 0.25);
border-radius: 0.75rem;
box-shadow: 0 1.125rem 2.1875rem rgba(15, 23, 42, 0.45);
max-height: 0;
opacity: 0;
overflow: hidden;
pointer-events: none;
transform: translateY(1rem);
transition: max-height 0.35s ease, opacity 0.25s ease, transform 0.35s ease;
}
.sidebar-dropup-panel__inner {
padding: 1.25rem;
}
.sidebar-dropup-panel--expanded {
max-height: 40rem;
opacity: 1;
pointer-events: auto;
transform: translateY(0);
}
.sidebar-dropup-trigger {
align-items: center;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.25), rgba(14, 116, 144, 0.35));
border: 0.0625rem solid rgba(148, 163, 184, 0.35);
border-radius: 9999px;
color: #f8fafc;
display: flex;
justify-content: space-between;
padding: 0.75rem 1.25rem;
transition: box-shadow 0.2s ease, transform 0.2s ease, border-color 0.2s ease;
width: 100%;
}
.sidebar-dropup-trigger:hover,
.sidebar-dropup-trigger:focus {
border-color: rgba(148, 163, 184, 0.6);
box-shadow: 0 0.75rem 1.875rem rgba(37, 99, 235, 0.25);
outline: none;
transform: translateY(-0.0625rem);
}
.sidebar-dropup-trigger__content {
align-items: center;
display: flex;
}
.edit-field-button {
align-items: center;
background-color: #1f2937;
+1 -1
View File
@@ -1 +1 @@
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media (min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@mediLine truncated
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.17 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media (min-width:640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width:768px){.\!container{max-width:768px!important}.container{max-width:768px}}@mediLine truncated
+24 -18
View File
@@ -45,12 +45,17 @@
<meta name="theme-color" content="#0f172a" />
<!-- Styles -->
<script src="js/tracking.js" defer></script>
<link href="css/tailwind.generated.css" rel="stylesheet" />
<link href="css/style.css" rel="stylesheet" />
<link href="css/markdown.css" rel="stylesheet" />
<!--
Cache busting: keep these query strings in sync with config/asset-version.js
so browsers pick up the latest sidebar markup and styling without
hard-refreshing around CDN caches.
-->
<script src="js/tracking.js?v=2024.10.22" defer></script>
<link href="css/tailwind.generated.css?v=2024.10.22" rel="stylesheet" />
<link href="css/style.css?v=2024.10.22" rel="stylesheet" />
<link href="css/markdown.css?v=2024.10.22" rel="stylesheet" />
</head>
<body class="bg-gray-100">
<body class="sidebar-collapsed">
<!--
SIDEBAR CONTAINER:
The <aside id="sidebar"> inside handles visibility.
@@ -64,8 +69,9 @@
<!--
MAIN CONTENT:
md:ml-64 ensures content is shifted on desktop so the pinned sidebar doesn't overlap.
On mobile we float the sidebar over the content and dim the background overlay.
Desktop spacing now follows the --sidebar-width custom property so the layout stays in
sync with the collapsible rail. On mobile we float the sidebar over the content and dim
the background overlay.
-->
<!--
`fade-in` is scrubbed by js/index.js after the first animation so that
@@ -73,7 +79,7 @@
elements that should animate exactly once on initial paint.
-->
<div id="app" class="md:ml-64 px-4 py-8 min-h-screen flex flex-col fade-in">
<div id="app" class="px-4 py-8 min-h-screen flex flex-col fade-in">
<!-- Header -->
<header class="mb-8 flex items-center w-full">
<!-- Mobile hamburger button (hidden on md+) -->
@@ -205,16 +211,16 @@
<!-- Additional Scripts -->
<!-- Bootstraps nostr-tools before other modules rely on nostrToolsReady -->
<script type="module" src="js/nostrToolsBootstrap.js"></script>
<script type="module" src="js/config.js"></script>
<script type="module" src="js/lists.js"></script>
<script type="module" src="js/accessControl.js"></script>
<script type="module" src="js/webtorrent.js"></script>
<script type="module" src="js/nostr.js"></script>
<script type="module" src="js/viewManager.js"></script>
<script type="module" src="js/bootstrap.js"></script>
<script type="module" src="js/disclaimer.js"></script>
<script type="module" src="js/index.js"></script>
<script type="module" src="js/nostrToolsBootstrap.js?v=2024.10.22"></script>
<script type="module" src="js/config.js?v=2024.10.22"></script>
<script type="module" src="js/lists.js?v=2024.10.22"></script>
<script type="module" src="js/accessControl.js?v=2024.10.22"></script>
<script type="module" src="js/webtorrent.js?v=2024.10.22"></script>
<script type="module" src="js/nostr.js?v=2024.10.22"></script>
<script type="module" src="js/viewManager.js?v=2024.10.22"></script>
<script type="module" src="js/bootstrap.js?v=2024.10.22"></script>
<script type="module" src="js/disclaimer.js?v=2024.10.22"></script>
<script type="module" src="js/index.js?v=2024.10.22"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
</body>
+122 -40
View File
@@ -1,6 +1,7 @@
// js/index.js
import { validateInstanceConfig } from "../config/validate-config.js";
import { ASSET_VERSION } from "../config/asset-version.js";
import "./bufferPolyfill.js";
import Application from "./app.js";
import { setApplication, setApplicationReady } from "./applicationContext.js";
@@ -102,13 +103,26 @@ document.addEventListener("animationend", handleFadeInAnimationComplete, true);
document.addEventListener("animationcancel", handleFadeInAnimationComplete, true);
// 1) Load modals (login, application, etc.)
const withAssetVersion = (url) => {
if (typeof url !== "string" || url.length === 0) {
return url;
}
const separator = url.includes("?") ? "&" : "?";
return `${url}${separator}v=${encodeURIComponent(ASSET_VERSION)}`;
};
const fetchPartial = async (url) => {
const response = await fetch(withAssetVersion(url), { cache: "no-store" });
if (!response.ok) {
throw new Error("Failed to load " + url);
}
return response.text();
};
async function loadModal(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("Failed to load " + url);
}
const html = await response.text();
const html = await fetchPartial(url);
// Remove analytics loader tags from modal partials to avoid duplicate pageview events.
const sanitizedHtml = html.replace(
/<script\b[^>]*src=["'][^"']*tracking\.js[^"']*["'][^>]*>\s*<\/script>/gi,
@@ -126,11 +140,7 @@ async function loadModal(url) {
// 2) Load sidebar
async function loadSidebar(url, containerId) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("Failed to load " + url);
}
const html = await response.text();
const html = await fetchPartial(url);
document.getElementById(containerId).innerHTML = html;
console.log(url, "loaded into", containerId);
} catch (err) {
@@ -141,11 +151,7 @@ async function loadSidebar(url, containerId) {
// 3) Load the disclaimer (now separate)
async function loadDisclaimer(url, containerId) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error("Failed to load " + url);
}
const html = await response.text();
const html = await fetchPartial(url);
document.getElementById(containerId).insertAdjacentHTML("beforeend", html);
console.log(url, "disclaimer loaded into", containerId);
} catch (err) {
@@ -170,24 +176,34 @@ async function bootstrapInterface() {
const sidebar = document.getElementById("sidebar");
const collapseToggle = document.getElementById("sidebarCollapseToggle");
if (sidebar && !sidebar.hasAttribute("data-footer-state")) {
sidebar.setAttribute("data-footer-state", "collapsed");
}
if (!collapseToggle) {
console.warn("Sidebar collapse toggle not found; skipping density controls.");
}
const collapseChevron = collapseToggle?.querySelector("[data-sidebar-chevron]");
const collapseLabel = collapseToggle?.querySelector(".sidebar-toggle-label");
const mobileMenuBtn = document.getElementById("mobileMenuBtn");
const sidebarOverlay = document.getElementById("sidebarOverlay");
const SIDEBAR_COLLAPSED_STORAGE_KEY = "sidebarCollapsed";
let isSidebarCollapsed = false;
const SIDEBAR_WIDTH_EXPANDED = "16rem";
const SIDEBAR_WIDTH_COLLAPSED = "4rem";
const DEFAULT_SIDEBAR_COLLAPSED = true;
let isSidebarCollapsed = DEFAULT_SIDEBAR_COLLAPSED;
let isFooterDropupExpanded = false;
let syncFooterDropupFn = null;
const readStoredSidebarCollapsed = () => {
try {
return localStorage.getItem(SIDEBAR_COLLAPSED_STORAGE_KEY) === "true";
const storedValue = localStorage.getItem(SIDEBAR_COLLAPSED_STORAGE_KEY);
if (storedValue === null) {
return DEFAULT_SIDEBAR_COLLAPSED;
}
return storedValue === "true";
} catch (error) {
console.warn("Unable to read sidebar collapse state from storage:", error);
return false;
return DEFAULT_SIDEBAR_COLLAPSED;
}
};
@@ -203,28 +219,47 @@ async function bootstrapInterface() {
};
const applySidebarDensity = (collapsed) => {
const targets = [sidebar, document.body].filter(
const widthTargets = [document.documentElement, document.body].filter(
(element) => element instanceof HTMLElement,
);
if (sidebar instanceof HTMLElement) {
widthTargets.push(sidebar);
}
const appShell = document.getElementById("app");
if (appShell instanceof HTMLElement) {
widthTargets.push(appShell);
}
const state = collapsed ? "collapsed" : "expanded";
const nextWidth = collapsed
? SIDEBAR_WIDTH_COLLAPSED
: SIDEBAR_WIDTH_EXPANDED;
targets.forEach((element) => {
element.classList.toggle("sidebar-collapsed", collapsed);
element.classList.toggle("sidebar-expanded", !collapsed);
element.setAttribute("data-state", state);
widthTargets.forEach((element) => {
element.style.setProperty("--sidebar-width", nextWidth);
});
[sidebar, document.body]
.filter((element) => element instanceof HTMLElement)
.forEach((element) => {
element.classList.toggle("sidebar-collapsed", collapsed);
element.classList.toggle("sidebar-expanded", !collapsed);
element.setAttribute("data-state", state);
});
if (collapseToggle) {
const actionLabel = collapsed ? "Expand sidebar" : "Collapse sidebar";
collapseToggle.setAttribute("aria-expanded", collapsed ? "false" : "true");
collapseToggle.setAttribute("data-state", state);
collapseToggle.setAttribute("aria-label", actionLabel);
collapseToggle.setAttribute("title", actionLabel);
}
if (collapseChevron instanceof HTMLElement) {
collapseChevron.classList.toggle("rotate-180", collapsed);
}
if (collapseLabel) {
collapseLabel.textContent = collapsed ? "Expand" : "Collapse";
if (
collapsed &&
isFooterDropupExpanded &&
typeof syncFooterDropupFn === "function"
) {
syncFooterDropupFn(false);
}
};
@@ -335,17 +370,64 @@ async function bootstrapInterface() {
}
const footerDropdownButton = document.getElementById("footerDropdownButton");
if (footerDropdownButton) {
footerDropdownButton.addEventListener("click", () => {
const footerLinksContainer = document.getElementById("footerLinksContainer");
if (!footerLinksContainer) return;
footerLinksContainer.classList.toggle("hidden");
if (footerLinksContainer.classList.contains("hidden")) {
footerDropdownButton.innerHTML = "More &#9660;";
} else {
footerDropdownButton.innerHTML = "Less &#9650;";
const footerLinksContainer = document.getElementById("footerLinksContainer");
const footerDropdownLabel = document.getElementById("footerDropdownText");
const footerDropdownIcon = document.getElementById("footerDropdownIcon");
if (footerDropdownButton && footerLinksContainer) {
const sidebarFooter = footerDropdownButton.closest(".sidebar-footer");
syncFooterDropupFn = (expanded) => {
const nextState = expanded ? "expanded" : "collapsed";
const actionLabel = expanded ? "Show fewer sidebar links" : "Show more sidebar links";
footerDropdownButton.setAttribute("aria-expanded", expanded ? "true" : "false");
footerDropdownButton.dataset.state = nextState;
footerDropdownButton.setAttribute("aria-label", actionLabel);
footerDropdownButton.setAttribute("title", actionLabel);
if (footerDropdownLabel) {
footerDropdownLabel.textContent = expanded ? "Less" : "More";
}
if (footerDropdownIcon) {
footerDropdownIcon.classList.toggle("is-rotated", expanded);
}
footerLinksContainer.classList.remove("hidden");
footerLinksContainer.setAttribute("aria-hidden", expanded ? "false" : "true");
footerLinksContainer.dataset.state = nextState;
if (sidebar) {
sidebar.setAttribute("data-footer-state", nextState);
}
if (sidebarFooter instanceof HTMLElement) {
sidebarFooter.dataset.footerState = nextState;
}
isFooterDropupExpanded = expanded;
};
footerDropdownButton.addEventListener("click", (event) => {
event.preventDefault();
const expanded = footerDropdownButton.getAttribute("aria-expanded") === "true";
if (isSidebarCollapsed && !expanded) {
const nextCollapsed = false;
isSidebarCollapsed = nextCollapsed;
applySidebarDensity(nextCollapsed);
persistSidebarCollapsed(nextCollapsed);
}
if (typeof syncFooterDropupFn === "function") {
syncFooterDropupFn(!expanded);
}
});
const initialExpanded = footerDropdownButton.getAttribute("aria-expanded") === "true";
if (typeof syncFooterDropupFn === "function") {
syncFooterDropupFn(initialExpanded);
}
}
try {
+11 -1
View File
@@ -2,9 +2,19 @@
import { initChannelProfileView } from "./channelProfile.js";
import { subscriptions } from "./subscriptions.js";
import { getApplication } from "./applicationContext.js";
import { ASSET_VERSION } from "../config/asset-version.js";
const TRACKING_SCRIPT_PATTERN = /(?:^|\/)tracking\.js(?:$|\?)/;
const withAssetVersion = (url) => {
if (typeof url !== "string" || url.length === 0) {
return url;
}
const separator = url.includes("?") ? "&" : "?";
return `${url}${separator}v=${encodeURIComponent(ASSET_VERSION)}`;
};
/**
* Load a partial view by URL into the #viewContainer.
*/
@@ -15,7 +25,7 @@ export async function loadView(viewUrl) {
app.prepareForViewLoad();
}
const res = await fetch(viewUrl);
const res = await fetch(withAssetVersion(viewUrl), { cache: "no-store" });
if (!res.ok) {
throw new Error(`Failed to load view: ${res.status}`);
}