浏览代码

Refactor, again

Make collapsible components reusable
Add ability to collapse video rows in videos-grid style
Lose sanity while dealing with all the intricacies
Svilen Markov 1 年之前
父节点
当前提交
b0018c3f06

+ 27 - 12
internal/assets/static/main.css

@@ -125,22 +125,22 @@
     padding-top: var(--list-half-gap);
 }
 
-.list-collapsible:not(.list-collapsible-expanded) > .list-collapsible-item {
+.collapsible-container:not(.container-expanded) > .collapsible-item {
     display: none;
 }
 
-.list-collapsible-item {
-    animation: listItemReveal 0.3s backwards;
+.collapsible-item {
+    animation: collapsibleItemReveal .25s backwards;
 }
 
-@keyframes listItemReveal {
+@keyframes collapsibleItemReveal {
     from {
         opacity: 0;
         transform: translateY(10px);
     }
 }
 
-.list-collapsible-label {
+.expand-toggle-button {
     font: inherit;
     border: 0;
     cursor: pointer;
@@ -154,19 +154,20 @@
     background: var(--color-widget-background);
 }
 
-.list-collapsible-label-expanded {
+.expand-toggle-button.container-expanded {
     position: sticky;
-    bottom: 0;
+    /* -1px to hide 1px gap on chrome */
+    bottom: -1px;
 }
 
-.list-collapsible-label-icon {
+.expand-toggle-button-icon {
     display: inline-block;
     margin-left: 1rem;
     position: relative;
     top: -.2rem;
 }
 
-.list-collapsible-label-icon::before {
+.expand-toggle-button-icon::before {
     content: '';
     font-size: 0.8rem;
     transform: rotate(90deg);
@@ -175,14 +176,25 @@
     transition: transform 0.3s;
 }
 
-.list-collapsible-label-expanded .list-collapsible-label-icon::before {
+.expand-toggle-button.container-expanded .expand-toggle-button-icon::before {
     transform: rotate(-90deg);
 }
 
-.widget-content:has(.list-collapsible-label:last-child) {
+.widget-content:has(.expand-toggle-button:last-child) {
     padding-bottom: 0;
 }
 
+.cards-grid.collapsible-container + .expand-toggle-button {
+    text-align: center;
+    margin-top: 0.5rem;
+    background-color: var(--color-background);
+}
+
+/* required to prevent collapsed lazy images from being loaded while the container is being setup */
+.collapsible-container:not(.ready) img[loading=lazy] {
+    display: none;
+}
+
 ::selection {
     background-color: hsl(var(--bghs), calc(var(--scheme) (var(--scheme) var(--bgl) + 20%)));
     color: var(--color-text-highlight);
@@ -270,6 +282,7 @@ body {
     gap: var(--widget-gap);
     margin: var(--widget-gap) 0;
     animation: pageColumnsEntrance .3s cubic-bezier(0.25, 1, 0.5, 1) backwards;
+    animation-delay: 3ms;
 }
 
 @keyframes pageColumnsEntrance {
@@ -1109,8 +1122,10 @@ body {
         box-shadow: 0 calc(var(--spacing) * -1) 0 0 currentColor, 0 var(--spacing) 0 0 currentColor;
     }
 
-    .list-collapsible-label-expanded {
+    .expand-toggle-button.container-expanded {
         bottom: var(--mobile-navigation-height);
+        /* hides content that peeks through the rounded borders of the mobile navigation */
+        box-shadow: 0 var(--border-radius) 0 0 var(--color-background);
     }
 }
 

+ 131 - 46
internal/assets/static/main.js

@@ -178,74 +178,158 @@ function setupLazyImages() {
     }, 5);
 }
 
-function setupCollapsibleLists() {
-    const collapsibleListElements = document.getElementsByClassName("list-collapsible");
-
-    if (collapsibleListElements.length == 0) {
-        return;
-    }
-
+function attachExpandToggleButton(collapsibleContainer) {
     const showMoreText = "Show more";
     const showLessText = "Show less";
 
-    const attachExpandToggleButton = (listElement) => {
-        let expanded = false;
-        const button = document.createElement("button");
-        const arrowElement = document.createElement("span");
-        arrowElement.classList.add("list-collapsible-label-icon");
-        const textNode = document.createTextNode(showMoreText);
-        button.classList.add("list-collapsible-label");
-        button.append(textNode, arrowElement);
-        button.addEventListener("click", () => {
-            expanded = !expanded;
-
-            if (expanded) {
-                listElement.classList.add("list-collapsible-expanded");
-                button.classList.add("list-collapsible-label-expanded");
-                textNode.nodeValue = showLessText;
-                return;
-            }
+    let expanded = false;
+    const button = document.createElement("button");
+    const icon = document.createElement("span");
+    icon.classList.add("expand-toggle-button-icon");
+    const textNode = document.createTextNode(showMoreText);
+    button.classList.add("expand-toggle-button");
+    button.append(textNode, icon);
+    button.addEventListener("click", () => {
+        expanded = !expanded;
+
+        if (expanded) {
+            collapsibleContainer.classList.add("container-expanded");
+            button.classList.add("container-expanded");
+            textNode.nodeValue = showLessText;
+            return;
+        }
 
-            const topBefore = button.getClientRects()[0].top;
+        const topBefore = button.getClientRects()[0].top;
 
-            listElement.classList.remove("list-collapsible-expanded");
-            button.classList.remove("list-collapsible-label-expanded");
-            textNode.nodeValue = showMoreText;
+        collapsibleContainer.classList.remove("container-expanded");
+        button.classList.remove("container-expanded");
+        textNode.nodeValue = showMoreText;
 
-            const topAfter = button.getClientRects()[0].top;
+        const topAfter = button.getClientRects()[0].top;
 
-            if (topAfter > 0)
-                return;
+        if (topAfter > 0)
+            return;
 
-            window.scrollBy({
-                top: topAfter - topBefore,
-                behavior: "instant"
-            });
+        window.scrollBy({
+            top: topAfter - topBefore,
+            behavior: "instant"
         });
+    });
 
-        listElement.after(button);
-    };
+    collapsibleContainer.after(button);
+
+    return button;
+};
 
-    for (let i = 0; i < collapsibleListElements.length; i++) {
-        const listElement = collapsibleListElements[i];
 
-        if (listElement.dataset.collapseAfter === undefined) {
+function setupCollapsibleLists() {
+    const collapsibleLists = document.querySelectorAll(".list.collapsible-container");
+
+    if (collapsibleLists.length == 0) {
+        return;
+    }
+
+    for (let i = 0; i < collapsibleLists.length; i++) {
+        const list = collapsibleLists[i];
+
+        if (list.dataset.collapseAfter === undefined) {
             continue;
         }
 
-        const collapseAfter = parseInt(listElement.dataset.collapseAfter);
+        const collapseAfter = parseInt(list.dataset.collapseAfter);
+
+        if (collapseAfter == -1) {
+            continue;
+        }
 
-        if (listElement.children.length <= collapseAfter) {
+        if (list.children.length <= collapseAfter) {
             continue;
         }
 
-        attachExpandToggleButton(listElement);
+        attachExpandToggleButton(list);
 
-        for (let c = collapseAfter; c < listElement.children.length; c++) {
-            const child = listElement.children[c];
-            child.classList.add("list-collapsible-item");
+        for (let c = collapseAfter; c < list.children.length; c++) {
+            const child = list.children[c];
+            child.classList.add("collapsible-item");
             child.style.animationDelay = ((c - collapseAfter) * 20).toString() + "ms";
         }
+
+        list.classList.add("ready");
+    }
+}
+
+function setupCollapsibleGrids() {
+    const collapsibleGridElements = document.querySelectorAll(".cards-grid.collapsible-container");
+
+    if (collapsibleGridElements.length == 0) {
+        return;
+    }
+
+    for (let i = 0; i < collapsibleGridElements.length; i++) {
+        const gridElement = collapsibleGridElements[i];
+
+        if (gridElement.dataset.collapseAfterRows === undefined) {
+            continue;
+        }
+
+        const collapseAfterRows = parseInt(gridElement.dataset.collapseAfterRows);
+
+        if (collapseAfterRows == -1) {
+            continue;
+        }
+
+        const getCardsPerRow = () => {
+            return parseInt(getComputedStyle(gridElement).getPropertyValue('--cards-per-row'));
+        };
+
+        const button = attachExpandToggleButton(gridElement);
+
+        let cardsPerRow = 2;
+
+        const resolveCollapsibleItems = () => {
+            const hideItemsAfterIndex = cardsPerRow * collapseAfterRows;
+
+            if (hideItemsAfterIndex >= gridElement.children.length) {
+                button.style.display = "none";
+            } else {
+                button.style.removeProperty("display");
+            }
+
+            let row = 0;
+
+            for (let i = 0; i < gridElement.children.length; i++) {
+                const child = gridElement.children[i];
+
+                if (i >= hideItemsAfterIndex) {
+                    child.classList.add("collapsible-item");
+                    child.style.animationDelay = (row * 40).toString() + "ms";
+
+                    if (i % cardsPerRow + 1 == cardsPerRow) {
+                        row++;
+                    }
+                } else {
+                    child.classList.remove("collapsible-item");
+                    child.style.removeProperty("animation-delay");
+                }
+            }
+        };
+
+        setTimeout(() => {
+            cardsPerRow = getCardsPerRow();
+            resolveCollapsibleItems();
+            gridElement.classList.add("ready");
+        }, 1);
+
+        window.addEventListener("resize", () => {
+            const newCardsPerRow = getCardsPerRow();
+
+            if (cardsPerRow == newCardsPerRow) {
+                return;
+            }
+
+            cardsPerRow = newCardsPerRow;
+            resolveCollapsibleItems();
+        });
     }
 }
 
@@ -264,6 +348,7 @@ async function setupPage() {
         setupLazyImages();
         setupCarousels();
         setupCollapsibleLists();
+        setupCollapsibleGrids();
         setupDynamicRelativeTime();
     } finally {
         pageElement.classList.add("content-ready");

+ 1 - 1
internal/assets/templates/forum-posts.html

@@ -1,7 +1,7 @@
 {{ template "widget-base.html" . }}
 
 {{ define "widget-content" }}
-<ul class="list list-gap-14 list-collapsible" data-collapse-after="{{ .CollapseAfter }}">
+<ul class="list list-gap-14 collapsible-container" data-collapse-after="{{ .CollapseAfter }}">
     {{ range .Posts }}
     <li>
         <div class="forum-post-list-item thumbnail-container">

+ 2 - 5
internal/assets/templates/releases.html

@@ -1,9 +1,9 @@
 {{ template "widget-base.html" . }}
 
 {{ define "widget-content" }}
-<ul class="list list-gap-10 list-collapsible">
+<ul class="list list-gap-10 collapsible-container" data-collapse-after="{{ .CollapseAfter }}">
     {{ range $i, $release := .Releases }}
-    <li {{ if shouldCollapse $i $.CollapseAfter }}class="list-collapsible-item" style="--animation-delay: {{ itemAnimationDelay $i $.CollapseAfter }};"{{ end }}>
+    <li>
         <a class="size-h4 block text-truncate color-primary-if-not-visited" href="{{ $release.NotesUrl }}" target="_blank" rel="noreferrer">{{ .Name }}</a>
         <ul class="list-horizontal-text">
             <li {{ dynamicRelativeTimeAttrs $release.TimeReleased }}></li>
@@ -15,7 +15,4 @@
     </li>
     {{ end }}
 </ul>
-{{ if gt (len .Releases) $.CollapseAfter }}
-<label class="list-collapsible-label"><input type="checkbox" autocomplete="off" class="list-collapsible-input"></label>
-{{ end }}
 {{ end }}

+ 1 - 1
internal/assets/templates/rss-list.html

@@ -1,7 +1,7 @@
 {{ template "widget-base.html" . }}
 
 {{ define "widget-content" }}
-<ul class="list list-gap-14 list-collapsible" data-collapse-after="{{ .CollapseAfter }}">
+<ul class="list list-gap-14 collapsible-container" data-collapse-after="{{ .CollapseAfter }}">
     {{ range .Items }}
     <li>
         <a class="size-title-dynamic color-primary-if-not-visited" href="{{ .Link }}" target="_blank" rel="noreferrer">{{ .Title }}</a>

+ 1 - 1
internal/assets/templates/twitch-channels.html

@@ -1,7 +1,7 @@
 {{ template "widget-base.html" . }}
 
 {{ define "widget-content" }}
-<ul class="list list-gap-14 list-collapsible" data-collapse-after="{{ .CollapseAfter }}">
+<ul class="list list-gap-14 collapsible-container" data-collapse-after="{{ .CollapseAfter }}">
     {{ range .Channels }}
     <li>
         <div class="{{ if .IsLive }}twitch-channel-live {{ end }}flex gap-10 items-start thumbnail-container">

+ 1 - 1
internal/assets/templates/twitch-games-list.html

@@ -1,7 +1,7 @@
 {{ template "widget-base.html" . }}
 
 {{ define "widget-content" }}
-<ul class="list list-gap-14 list-collapsible" data-collapse-after="{{ .CollapseAfter }}">
+<ul class="list list-gap-14 collapsible-container" data-collapse-after="{{ .CollapseAfter }}">
     {{ range .Categories }}
     <li class="twitch-category thumbnail-container">
         <div class="flex gap-10 items-center">

+ 1 - 1
internal/assets/templates/videos-grid.html

@@ -3,7 +3,7 @@
 {{ define "widget-content-classes" }}widget-content-frameless{{ end }}
 
 {{ define "widget-content" }}
-<div class="cards-grid">
+<div class="cards-grid collapsible-container" data-collapse-after-rows="{{ .CollapseAfterRows }}">
     {{ range .Videos }}
     <div class="card widget-content-frame thumbnail-container">
         {{ template "video-card-contents" . }}

+ 11 - 6
internal/widget/videos.go

@@ -10,12 +10,13 @@ import (
 )
 
 type Videos struct {
-	widgetBase       `yaml:",inline"`
-	Videos           feed.Videos `yaml:"-"`
-	VideoUrlTemplate string      `yaml:"video-url-template"`
-	Style            string      `yaml:"style"`
-	Channels         []string    `yaml:"channels"`
-	Limit            int         `yaml:"limit"`
+	widgetBase        `yaml:",inline"`
+	Videos            feed.Videos `yaml:"-"`
+	VideoUrlTemplate  string      `yaml:"video-url-template"`
+	Style             string      `yaml:"style"`
+	CollapseAfterRows int         `yaml:"collapse-after-rows"`
+	Channels          []string    `yaml:"channels"`
+	Limit             int         `yaml:"limit"`
 }
 
 func (widget *Videos) Initialize() error {
@@ -25,6 +26,10 @@ func (widget *Videos) Initialize() error {
 		widget.Limit = 25
 	}
 
+	if widget.CollapseAfterRows == 0 || widget.CollapseAfterRows < -1 {
+		widget.CollapseAfterRows = 4
+	}
+
 	return nil
 }