Delay showing page content until JS has finished setting up page elements
That then allows the following:
Leave relative time to be rendered on the client
Leave collapsible lists to be rendered on the client
Which massively simplfies the backend templates which were error prone
This commit is contained in:
Svilen Markov 2024-05-16 20:41:50 +01:00
parent de965dace8
commit 36f8eac3e4
14 changed files with 170 additions and 108 deletions

View File

@ -57,6 +57,14 @@
font-size: var(--font-size-h4); font-size: var(--font-size-h4);
} }
.page-content, .page.content-ready .page-loading-container {
display: none;
}
.page.content-ready > .page-content {
display: block;
}
.page-column-full .size-title-dynamic { .page-column-full .size-title-dynamic {
font-size: var(--font-size-h3); font-size: var(--font-size-h3);
} }
@ -117,6 +125,14 @@
padding-top: var(--list-half-gap); padding-top: var(--list-half-gap);
} }
.list-collapsible:not(.list-collapsible-expanded) > .list-collapsible-item {
display: none;
}
.list-collapsible-item {
animation: listItemReveal 0.3s backwards;
}
@keyframes listItemReveal { @keyframes listItemReveal {
from { from {
opacity: 0; opacity: 0;
@ -124,56 +140,42 @@
} }
} }
.list-collapsible-item {
display: none;
animation: listItemReveal 0.3s backwards;
animation-delay: var(--animation-delay);
}
.list-collapsible-label { .list-collapsible-label {
display: flex; font: inherit;
align-items: center; border: 0;
gap: 1rem; cursor: pointer;
display: block;
width: 100%;
text-align: left;
color: var(--color-text-base);
text-transform: uppercase;
font-size: var(--font-size-h4);
padding: var(--widget-content-vertical-padding) 0; padding: var(--widget-content-vertical-padding) 0;
background: var(--color-widget-background); background: var(--color-widget-background);
} }
.list-collapsible-label:has(.list-collapsible-input:checked) { .list-collapsible-label-expanded {
position: sticky; position: sticky;
bottom: 0; bottom: 0;
} }
.list-collapsible:has(+ .list-collapsible-label > .list-collapsible-input:checked) .list-collapsible-item { .list-collapsible-label-icon {
display: block; display: inline-block;
margin-left: 1rem;
position: relative;
top: -.2rem;
} }
.list-collapsible-input { .list-collapsible-label-icon::before {
display: none;
}
.list-collapsible-label::before, .list-collapsible-label::after {
cursor: pointer;
display: block;
}
.list-collapsible-label::before {
content: 'SHOW MORE';
font-size: var(--font-size-h4);
}
.list-collapsible-label:has(.list-collapsible-input:checked)::before {
content: 'SHOW LESS';
}
.list-collapsible-label::after {
content: ''; content: '';
font-size: 0.8rem; font-size: 0.8rem;
transform: rotate(90deg); transform: rotate(90deg);
line-height: 1; line-height: 1;
display: inline-block;
transition: transform 0.3s; transition: transform 0.3s;
} }
.list-collapsible-label:has(.list-collapsible-input:checked)::after { .list-collapsible-label-expanded .list-collapsible-label-icon::before {
transform: rotate(-90deg); transform: rotate(-90deg);
} }
@ -1107,7 +1109,7 @@ body {
box-shadow: 0 calc(var(--spacing) * -1) 0 0 currentColor, 0 var(--spacing) 0 0 currentColor; box-shadow: 0 calc(var(--spacing) * -1) 0 0 currentColor, 0 var(--spacing) 0 0 currentColor;
} }
.list-collapsible-label:has(.list-collapsible-input:checked) { .list-collapsible-label-expanded {
bottom: var(--mobile-navigation-height); bottom: var(--mobile-navigation-height);
} }
} }

View File

@ -21,7 +21,7 @@ function throttledDebounce(callback, maxDebounceTimes, debounceDelay) {
}; };
async function fetchPageContents (pageSlug) { async function fetchPageContent(pageSlug) {
// TODO: handle non 200 status codes/time outs // TODO: handle non 200 status codes/time outs
// TODO: add retries // TODO: add retries
const response = await fetch(`/api/pages/${pageSlug}/content/`); const response = await fetch(`/api/pages/${pageSlug}/content/`);
@ -33,6 +33,10 @@ async function fetchPageContents (pageSlug) {
function setupCarousels() { function setupCarousels() {
const carouselElements = document.getElementsByClassName("carousel-container"); const carouselElements = document.getElementsByClassName("carousel-container");
if (carouselElements.length == 0) {
return;
}
for (let i = 0; i < carouselElements.length; i++) { for (let i = 0; i < carouselElements.length; i++) {
const carousel = carouselElements[i]; const carousel = carouselElements[i];
carousel.classList.add("show-right-cutoff"); carousel.classList.add("show-right-cutoff");
@ -57,7 +61,7 @@ function setupCarousels() {
itemsContainer.addEventListener("scroll", determineSideCutoffsRateLimited); itemsContainer.addEventListener("scroll", determineSideCutoffsRateLimited);
document.addEventListener("resize", determineSideCutoffsRateLimited); document.addEventListener("resize", determineSideCutoffsRateLimited);
determineSideCutoffs(); setTimeout(determineSideCutoffs, 1);
} }
} }
@ -108,6 +112,8 @@ function setupDynamicRelativeTime() {
const updateInterval = 60 * 1000; const updateInterval = 60 * 1000;
let lastUpdateTime = Date.now(); let lastUpdateTime = Date.now();
updateRelativeTimeForElements(elements);
const updateElementsAndTimestamp = () => { const updateElementsAndTimestamp = () => {
updateRelativeTimeForElements(elements); updateRelativeTimeForElements(elements);
lastUpdateTime = Date.now(); lastUpdateTime = Date.now();
@ -154,6 +160,7 @@ function setupLazyImages() {
image.classList.add("finished-transition"); image.classList.add("finished-transition");
} }
setTimeout(() => {
for (let i = 0; i < images.length; i++) { for (let i = 0; i < images.length; i++) {
const image = images[i]; const image = images[i];
@ -168,21 +175,86 @@ function setupLazyImages() {
}); });
} }
} }
}, 5);
}
function setupCollapsibleLists() {
const collapsibleListElements = document.getElementsByClassName("list-collapsible");
if (collapsibleListElements.length == 0) {
return;
}
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", () => {
if (expanded) {
listElement.classList.remove("list-collapsible-expanded");
button.classList.remove("list-collapsible-label-expanded");
textNode.nodeValue = showMoreText;
} else {
listElement.classList.add("list-collapsible-expanded");
button.classList.add("list-collapsible-label-expanded");
textNode.nodeValue = showLessText;
}
expanded = !expanded;
});
listElement.after(button);
};
for (let i = 0; i < collapsibleListElements.length; i++) {
const listElement = collapsibleListElements[i];
if (listElement.dataset.collapseAfter === undefined) {
continue;
}
const collapseAfter = parseInt(listElement.dataset.collapseAfter);
if (listElement.children.length <= collapseAfter) {
continue;
}
attachExpandToggleButton(listElement);
for (let c = collapseAfter; c < listElement.children.length; c++) {
const child = listElement.children[c];
child.classList.add("list-collapsible-item");
child.style.animationDelay = ((c - collapseAfter) * 20).toString() + "ms";
}
}
} }
async function setupPage() { async function setupPage() {
const pageElement = document.getElementById("page"); const pageElement = document.getElementById("page");
const pageContents = await fetchPageContents(pageData.slug); const pageContentElement = document.getElementById("page-content");
const pageContent = await fetchPageContent(pageData.slug);
pageElement.innerHTML = pageContents; pageContentElement.innerHTML = pageContent;
setTimeout(() => { setTimeout(() => {
document.body.classList.add("animate-element-transition"); document.body.classList.add("animate-element-transition");
}, 200); }, 200);
setTimeout(setupLazyImages, 5); try {
setupLazyImages();
setupCarousels(); setupCarousels();
setupCollapsibleLists();
setupDynamicRelativeTime(); setupDynamicRelativeTime();
} finally {
pageElement.classList.add("content-ready");
}
} }
if (document.readyState === "loading") { if (document.readyState === "loading") {

View File

@ -1,14 +1,14 @@
{{ template "widget-base.html" . }} {{ template "widget-base.html" . }}
{{ define "widget-content" }} {{ define "widget-content" }}
<ul class="list list-gap-14 list-collapsible"> <ul class="list list-gap-14 list-collapsible" data-collapse-after="{{ .CollapseAfter }}">
{{ range $i, $post := .Posts }} {{ range .Posts }}
<li {{ if shouldCollapse $i $.CollapseAfter }}class="list-collapsible-item" style="--animation-delay: {{ itemAnimationDelay $i $.CollapseAfter }};"{{ end }}> <li>
<div class="forum-post-list-item thumbnail-container"> <div class="forum-post-list-item thumbnail-container">
{{ if $.ShowThumbnails }} {{ if $.ShowThumbnails }}
{{ if ne $post.ThumbnailUrl "" }} {{ if ne .ThumbnailUrl "" }}
<img class="forum-post-list-thumbnail thumbnail" src="{{ $post.ThumbnailUrl }}" alt="" loading="lazy"> <img class="forum-post-list-thumbnail thumbnail" src="{{ .ThumbnailUrl }}" alt="" loading="lazy">
{{ else if $post.HasTargetUrl }} {{ else if .HasTargetUrl }}
<svg class="forum-post-list-thumbnail hide-on-mobile" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="-9 -8 40 40" stroke-width="1.5" stroke="var(--color-text-subdue)"> <svg class="forum-post-list-thumbnail hide-on-mobile" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="-9 -8 40 40" stroke-width="1.5" stroke="var(--color-text-subdue)">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244" /> <path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244" />
</svg> </svg>
@ -19,13 +19,13 @@
{{ end }} {{ end }}
{{ end }} {{ end }}
<div class="grow"> <div class="grow">
<a href="{{ $post.DiscussionUrl }}" class="size-h3 color-primary-if-not-visited" target="_blank" rel="noreferrer">{{ .Title }}</a> <a href="{{ .DiscussionUrl }}" class="size-h3 color-primary-if-not-visited" target="_blank" rel="noreferrer">{{ .Title }}</a>
<ul class="list-horizontal-text"> <ul class="list-horizontal-text">
<li title="{{ $post.TimePosted | formatTime }}" {{ dynamicRelativeTimeAttrs $post.TimePosted }}>{{ $post.TimePosted | relativeTime }}</li> <li {{ dynamicRelativeTimeAttrs .TimePosted }}></li>
<li>{{ $post.Score | formatNumber }} points</li> <li>{{ .Score | formatNumber }} points</li>
<li>{{ $post.CommentCount | formatNumber }} comments</li> <li>{{ .CommentCount | formatNumber }} comments</li>
{{ if $post.HasTargetUrl }} {{ if .HasTargetUrl }}
<li class="shrink min-width-0"><a class="visited-indicator text-truncate block" href="{{ .TargetUrl }}" target="_blank" rel="noreferrer">{{ $post.TargetUrlDomain }}</a></li> <li class="shrink min-width-0"><a class="visited-indicator text-truncate block" href="{{ .TargetUrl }}" target="_blank" rel="noreferrer">{{ .TargetUrlDomain }}</a></li>
{{ end }} {{ end }}
</ul> </ul>
</div> </div>
@ -33,7 +33,4 @@
</li> </li>
{{ end }} {{ end }}
</ul> </ul>
{{ if gt (len .Posts) $.CollapseAfter }}
<label class="list-collapsible-label"><input type="checkbox" autocomplete="off" class="list-collapsible-input"></label>
{{ end }}
{{ end }} {{ end }}

View File

@ -50,6 +50,7 @@
<div class="content-bounds"> <div class="content-bounds">
<div class="page" id="page"> <div class="page" id="page">
<div class="page-content" id="page-content"></div>
<div class="page-loading-container"> <div class="page-loading-container">
<!-- TODO: add a bigger/better loading indicator --> <!-- TODO: add a bigger/better loading indicator -->
<div class="loading-icon"></div> <div class="loading-icon"></div>

View File

@ -20,7 +20,7 @@
{{ end }} {{ end }}
<a href="{{ .DiscussionUrl }}" title="{{ .Title }}" class="text-truncate-3-lines color-primary-if-not-visited margin-top-7 margin-bottom-auto" target="_blank" rel="noreferrer">{{ .Title }}</a> <a href="{{ .DiscussionUrl }}" title="{{ .Title }}" class="text-truncate-3-lines color-primary-if-not-visited margin-top-7 margin-bottom-auto" target="_blank" rel="noreferrer">{{ .Title }}</a>
<ul class="list-horizontal-text margin-top-7"> <ul class="list-horizontal-text margin-top-7">
<li title="{{ .TimePosted | formatTime }}" {{ dynamicRelativeTimeAttrs .TimePosted }}>{{ .TimePosted | relativeTime }}</li> <li {{ dynamicRelativeTimeAttrs .TimePosted }}></li>
<li>{{ .Score | formatNumber }} points</li> <li>{{ .Score | formatNumber }} points</li>
</ul> </ul>
</div> </div>

View File

@ -19,7 +19,7 @@
{{ end }} {{ end }}
<a href="{{ .DiscussionUrl }}" title="{{ .Title }}" class="text-truncate-3-lines color-primary-if-not-visited margin-top-7" target="_blank" rel="noreferrer">{{ .Title }}</a> <a href="{{ .DiscussionUrl }}" title="{{ .Title }}" class="text-truncate-3-lines color-primary-if-not-visited margin-top-7" target="_blank" rel="noreferrer">{{ .Title }}</a>
<ul class="list-horizontal-text margin-top-7"> <ul class="list-horizontal-text margin-top-7">
<li title="{{ .TimePosted | formatTime }}" {{ dynamicRelativeTimeAttrs .TimePosted }}>{{ .TimePosted | relativeTime }}</li> <li {{ dynamicRelativeTimeAttrs .TimePosted }}></li>
<li>{{ .Score | formatNumber }} points</li> <li>{{ .Score | formatNumber }} points</li>
</ul> </ul>
</div> </div>

View File

@ -6,7 +6,7 @@
<li {{ if shouldCollapse $i $.CollapseAfter }}class="list-collapsible-item" style="--animation-delay: {{ itemAnimationDelay $i $.CollapseAfter }};"{{ end }}> <li {{ if shouldCollapse $i $.CollapseAfter }}class="list-collapsible-item" style="--animation-delay: {{ itemAnimationDelay $i $.CollapseAfter }};"{{ end }}>
<a class="size-h4 block text-truncate color-primary-if-not-visited" href="{{ $release.NotesUrl }}" target="_blank" rel="noreferrer">{{ .Name }}</a> <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"> <ul class="list-horizontal-text">
<li title="{{ $release.TimeReleased | formatTime }}" {{ dynamicRelativeTimeAttrs $release.TimeReleased }}>{{ $release.TimeReleased | relativeTime }}</li> <li {{ dynamicRelativeTimeAttrs $release.TimeReleased }}></li>
<li>{{ $release.Version }}</li> <li>{{ $release.Version }}</li>
{{ if gt $release.Downvotes 3 }} {{ if gt $release.Downvotes 3 }}
<li>{{ $release.Downvotes | formatNumber }} ⚠</li> <li>{{ $release.Downvotes | formatNumber }} ⚠</li>

View File

@ -13,7 +13,7 @@
<div class="flex gap-7 size-h5 margin-top-3"> <div class="flex gap-7 size-h5 margin-top-3">
<ul class="list list-gap-2"> <ul class="list list-gap-2">
{{ range .RepositoryDetails.PullRequests }} {{ range .RepositoryDetails.PullRequests }}
<li title="{{ .CreatedAt | formatTime }}" {{ dynamicRelativeTimeAttrs .CreatedAt }}>{{ .CreatedAt | relativeTime }}</li> <li {{ dynamicRelativeTimeAttrs .CreatedAt }}></li>
{{ end }} {{ end }}
</ul> </ul>
<ul class="list list-gap-2 min-width-0"> <ul class="list list-gap-2 min-width-0">
@ -30,7 +30,7 @@
<div class="flex gap-7 size-h5 margin-top-3"> <div class="flex gap-7 size-h5 margin-top-3">
<ul class="list list-gap-2"> <ul class="list list-gap-2">
{{ range .RepositoryDetails.Issues }} {{ range .RepositoryDetails.Issues }}
<li title="{{ .CreatedAt | formatTime }}" {{ dynamicRelativeTimeAttrs .CreatedAt }}>{{ .CreatedAt | relativeTime }}</li> <li {{ dynamicRelativeTimeAttrs .CreatedAt }}></li>
{{ end }} {{ end }}
</ul> </ul>
<ul class="list list-gap-2 min-width-0"> <ul class="list list-gap-2 min-width-0">

View File

@ -17,7 +17,7 @@
<div class="rss-card-2-content padding-inline-widget"> <div class="rss-card-2-content padding-inline-widget">
<a href="{{ .Link }}" title="{{ .Title }}" class="block text-truncate color-primary-if-not-visited" target="_blank" rel="noreferrer">{{ .Title }}</a> <a href="{{ .Link }}" title="{{ .Title }}" class="block text-truncate color-primary-if-not-visited" target="_blank" rel="noreferrer">{{ .Title }}</a>
<ul class="list-horizontal-text flex-nowrap margin-top-5"> <ul class="list-horizontal-text flex-nowrap margin-top-5">
<li class="shrink-0" title="{{ .PublishedAt | formatTime }}" {{ dynamicRelativeTimeAttrs .PublishedAt }}>{{ .PublishedAt | relativeTime }}</li> <li class="shrink-0" {{ dynamicRelativeTimeAttrs .PublishedAt }}></li>
<li class="shrink min-width-0 text-truncate">{{ .ChannelName }}</li> <li class="shrink min-width-0 text-truncate">{{ .ChannelName }}</li>
</ul> </ul>
</div> </div>

View File

@ -17,7 +17,7 @@
<div class="margin-bottom-widget padding-inline-widget flex flex-column grow"> <div class="margin-bottom-widget padding-inline-widget flex flex-column grow">
<a href="{{ .Link }}" title="{{ .Title }}" class="text-truncate-3-lines color-primary-if-not-visited margin-top-10 margin-bottom-auto" target="_blank" rel="noreferrer">{{ .Title }}</a> <a href="{{ .Link }}" title="{{ .Title }}" class="text-truncate-3-lines color-primary-if-not-visited margin-top-10 margin-bottom-auto" target="_blank" rel="noreferrer">{{ .Title }}</a>
<ul class="list-horizontal-text flex-nowrap margin-top-7"> <ul class="list-horizontal-text flex-nowrap margin-top-7">
<li class="shrink-0" title="{{ .PublishedAt | formatTime }}" {{ dynamicRelativeTimeAttrs .PublishedAt }}>{{ .PublishedAt | relativeTime }}</li> <li class="shrink-0" {{ dynamicRelativeTimeAttrs .PublishedAt }}></li>
<li class="shrink min-width-0 text-truncate">{{ .ChannelName }}</li> <li class="shrink min-width-0 text-truncate">{{ .ChannelName }}</li>
</ul> </ul>
</div> </div>

View File

@ -1,12 +1,12 @@
{{ template "widget-base.html" . }} {{ template "widget-base.html" . }}
{{ define "widget-content" }} {{ define "widget-content" }}
<ul class="list list-gap-14 list-collapsible"> <ul class="list list-gap-14 list-collapsible" data-collapse-after="{{ .CollapseAfter }}">
{{ range $i, $item := .Items }} {{ range .Items }}
<li {{ if shouldCollapse $i $.CollapseAfter }}class="list-collapsible-item" style="--animation-delay: {{ itemAnimationDelay $i $.CollapseAfter }};"{{ end }}> <li>
<a class="size-title-dynamic color-primary-if-not-visited" href="{{ .Link }}" target="_blank" rel="noreferrer">{{ .Title }}</a> <a class="size-title-dynamic color-primary-if-not-visited" href="{{ .Link }}" target="_blank" rel="noreferrer">{{ .Title }}</a>
<ul class="list-horizontal-text"> <ul class="list-horizontal-text">
<li title="{{ $item.PublishedAt | formatTime }}" {{ dynamicRelativeTimeAttrs $item.PublishedAt }}>{{ .PublishedAt | relativeTime }}</li> <li {{ dynamicRelativeTimeAttrs .PublishedAt }}></li>
{{ if gt (len $.FeedRequests) 1 }} {{ if gt (len $.FeedRequests) 1 }}
<li><a href="{{ .ChannelURL }}" target="_blank" rel="noreferrer">{{ .ChannelName }}</a></li> <li><a href="{{ .ChannelURL }}" target="_blank" rel="noreferrer">{{ .ChannelName }}</a></li>
{{ end }} {{ end }}
@ -14,7 +14,4 @@
</li> </li>
{{ end }} {{ end }}
</ul> </ul>
{{ if gt (len .Items) $.CollapseAfter }}
<label class="list-collapsible-label"><input type="checkbox" autocomplete="off" class="list-collapsible-input"></label>
{{ end }}
{{ end }} {{ end }}

View File

@ -1,13 +1,13 @@
{{ template "widget-base.html" . }} {{ template "widget-base.html" . }}
{{ define "widget-content" }} {{ define "widget-content" }}
<ul class="list list-gap-14 list-collapsible"> <ul class="list list-gap-14 list-collapsible" data-collapse-after="{{ .CollapseAfter }}">
{{ range $i, $channel := .Channels }} {{ range .Channels }}
<li {{ if shouldCollapse $i $.CollapseAfter }}class="list-collapsible-item" style="--animation-delay: {{ itemAnimationDelay $i $.CollapseAfter }};"{{ end }}> <li>
<div class="{{ if $channel.IsLive }}twitch-channel-live {{ end }}flex gap-10 items-start thumbnail-container"> <div class="{{ if .IsLive }}twitch-channel-live {{ end }}flex gap-10 items-start thumbnail-container">
<div class="twitch-channel-avatar-container"> <div class="twitch-channel-avatar-container">
{{ if $channel.Exists }} {{ if .Exists }}
<img class="twitch-channel-avatar thumbnail" src="{{ $channel.AvatarUrl }}" alt="" loading="lazy"> <img class="twitch-channel-avatar thumbnail" src="{{ .AvatarUrl }}" alt="" loading="lazy">
{{ else }} {{ else }}
<svg class="twitch-channel-avatar thumbnail" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"> <svg class="twitch-channel-avatar thumbnail" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z" /> <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0ZM4.501 20.118a7.5 7.5 0 0 1 14.998 0A17.933 17.933 0 0 1 12 21.75c-2.676 0-5.216-.584-7.499-1.632Z" />
@ -15,13 +15,13 @@
{{ end }} {{ end }}
</div> </div>
<div class="shrink min-width-0"> <div class="shrink min-width-0">
<a href="https://twitch.tv/{{ $channel.Login }}" class="size-h3{{ if $channel.IsLive }} color-highlight{{ end }} block text-truncate" target="_blank" rel="noreferrer">{{ $channel.Name }}</a> <a href="https://twitch.tv/{{ .Login }}" class="size-h3{{ if .IsLive }} color-highlight{{ end }} block text-truncate" target="_blank" rel="noreferrer">{{ .Name }}</a>
{{ if $channel.Exists }} {{ if .Exists }}
{{ if $channel.IsLive }} {{ if .IsLive }}
<a class="text-truncate block" href="https://www.twitch.tv/directory/category/{{ $channel.CategorySlug }}" target="_blank" rel="noreferrer">{{ $channel.Category }}</a> <a class="text-truncate block" href="https://www.twitch.tv/directory/category/{{ .CategorySlug }}" target="_blank" rel="noreferrer">{{ .Category }}</a>
<ul class="list-horizontal-text"> <ul class="list-horizontal-text">
<li title="{{ $channel.LiveSince | formatTime }}" {{ dynamicRelativeTimeAttrs $channel.LiveSince }}>{{ $channel.LiveSince | relativeTime }}</li> <li {{ dynamicRelativeTimeAttrs .LiveSince }}></li>
<li>{{ $channel.ViewersCount | formatViewerCount }} viewers</li> <li>{{ .ViewersCount | formatViewerCount }} viewers</li>
</ul> </ul>
{{ else }} {{ else }}
<div>Offline</div> <div>Offline</div>
@ -34,7 +34,4 @@
</li> </li>
{{ end }} {{ end }}
</ul> </ul>
{{ if gt (len .Channels) $.CollapseAfter }}
<label class="list-collapsible-label"><input type="checkbox" autocomplete="off" class="list-collapsible-input"></label>
{{ end }}
{{ end }} {{ end }}

View File

@ -1,22 +1,21 @@
{{ template "widget-base.html" . }} {{ template "widget-base.html" . }}
{{ define "widget-content" }} {{ define "widget-content" }}
<ul class="list list-gap-14 list-collapsible"> <ul class="list list-gap-14 list-collapsible" data-collapse-after="{{ .CollapseAfter }}">
{{ range $i, $category := .Categories }} {{ range .Categories }}
{{ $shouldCollapseItem := shouldCollapse $i $.CollapseAfter }} <li class="twitch-category thumbnail-container">
<li class="twitch-category thumbnail-container{{ if $shouldCollapseItem }} list-collapsible-item{{ end }}" {{ if $shouldCollapseItem }}style="--animation-delay: {{ itemAnimationDelay $i $.CollapseAfter }};"{{ end }}>
<div class="flex gap-10 items-center"> <div class="flex gap-10 items-center">
<img class="twitch-category-thumbnail thumbnail" loading="lazy" src="{{ $category.AvatarUrl }}" alt=""> <img class="twitch-category-thumbnail thumbnail" loading="lazy" src="{{ .AvatarUrl }}" alt="">
<div class="shrink min-width-0"> <div class="shrink min-width-0">
<a class="size-h3 color-highlight text-truncate block" href="https://www.twitch.tv/directory/category/{{ $category.Slug }}" target="_blank" rel="noreferrer">{{ $category.Name }}</a> <a class="size-h3 color-highlight text-truncate block" href="https://www.twitch.tv/directory/category/{{ .Slug }}" target="_blank" rel="noreferrer">{{ .Name }}</a>
<ul class="list-horizontal-text"> <ul class="list-horizontal-text">
<li>{{ $category.ViewersCount | formatViewerCount }} viewers</li> <li>{{ .ViewersCount | formatViewerCount }} viewers</li>
{{ if $category.IsNew }} {{ if .IsNew }}
<li class="color-primary">NEW</li> <li class="color-primary">NEW</li>
{{ end }} {{ end }}
</ul> </ul>
<ul class="list-horizontal-text flex-nowrap"> <ul class="list-horizontal-text flex-nowrap">
{{ range $i, $tag := $category.Tags }} {{ range $i, $tag := .Tags }}
{{ if eq $i 0 }} {{ if eq $i 0 }}
<li class="shrink-0">{{ $tag.Name }}</li> <li class="shrink-0">{{ $tag.Name }}</li>
{{ else }} {{ else }}
@ -29,7 +28,4 @@
</li> </li>
{{ end }} {{ end }}
</ul> </ul>
{{ if gt (len .Categories) $.CollapseAfter }}
<label class="list-collapsible-label"><input type="checkbox" autocomplete="off" class="list-collapsible-input"></label>
{{ end }}
{{ end }} {{ end }}

View File

@ -3,7 +3,7 @@
<div class="margin-top-10 margin-bottom-widget flex flex-column grow padding-inline-widget"> <div class="margin-top-10 margin-bottom-widget flex flex-column grow padding-inline-widget">
<a class="video-title color-primary-if-not-visited" href="{{ .Url }}" target="_blank" rel="noreferrer" title="{{ .Title }}">{{ .Title }}</a> <a class="video-title color-primary-if-not-visited" href="{{ .Url }}" target="_blank" rel="noreferrer" title="{{ .Title }}">{{ .Title }}</a>
<ul class="list-horizontal-text flex-nowrap margin-top-7"> <ul class="list-horizontal-text flex-nowrap margin-top-7">
<li class="shrink-0" title="{{ .TimePosted | formatTime }}" {{ dynamicRelativeTimeAttrs .TimePosted }}>{{ .TimePosted | relativeTime }}</li> <li class="shrink-0" {{ dynamicRelativeTimeAttrs .TimePosted }}></li>
<li class="shrink min-width-0"> <li class="shrink min-width-0">
<a class="block text-truncate" href="{{ .AuthorUrl }}" target="_blank" rel="noreferrer">{{ .Author }}</a> <a class="block text-truncate" href="{{ .AuthorUrl }}" target="_blank" rel="noreferrer">{{ .Author }}</a>
</li> </li>