mirror of
https://github.com/svemagie/indiekit-endpoint-youtube.git
synced 2026-04-02 07:44:57 +02:00
refactor: align to upstream @indiekit/frontend patterns
- Extract 177 lines inline CSS to assets/styles.css - Create intermediate layout (views/layouts/youtube.njk) - Use section() macro for dashboard sections - Rename CSS prefix from yt-* to youtube-* - Replace inline style for LIVE duration badge with CSS class - Use upstream CSS custom properties without fallbacks - Keep animation CSS (@keyframes youtube-pulse) - Add assets to package.json files array - Bump version to 1.2.2 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -42,3 +42,7 @@ YouTube Data API has a 10,000 units/day default quota:
|
||||
- `search.list`: 100 units (used only for full live status check with `?full=true`)
|
||||
|
||||
The plugin uses the playlist method by default for quota efficiency.
|
||||
|
||||
## Workspace Context
|
||||
|
||||
This plugin is part of the Indiekit development workspace at `/home/rick/code/indiekit-dev/`. See the workspace CLAUDE.md for the full repository map. It is deployed via `indiekit-cloudron/` and listed in its `indiekit.config.js` plugins array.
|
||||
|
||||
214
assets/styles.css
Normal file
214
assets/styles.css
Normal file
@@ -0,0 +1,214 @@
|
||||
/* YouTube endpoint styles */
|
||||
|
||||
/* Channel header */
|
||||
.youtube-channel {
|
||||
align-items: center;
|
||||
background: var(--color-offset);
|
||||
border-radius: var(--radius-m);
|
||||
display: flex;
|
||||
gap: var(--space-m);
|
||||
margin-block-end: var(--space-l);
|
||||
padding: var(--space-m);
|
||||
}
|
||||
|
||||
.youtube-channel__avatar {
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
height: 64px;
|
||||
object-fit: cover;
|
||||
width: 64px;
|
||||
}
|
||||
|
||||
.youtube-channel__info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.youtube-channel__name {
|
||||
font-size: var(--step-1);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin: 0 0 var(--space-3xs) 0;
|
||||
}
|
||||
|
||||
.youtube-channel__stats {
|
||||
color: var(--color-text-secondary);
|
||||
display: flex;
|
||||
font-size: var(--step--1);
|
||||
gap: var(--space-m);
|
||||
}
|
||||
|
||||
/* Live status badge */
|
||||
.youtube-live-badge {
|
||||
align-items: center;
|
||||
border-radius: 2rem;
|
||||
display: inline-flex;
|
||||
font-size: var(--step--2);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
gap: var(--space-xs);
|
||||
padding: var(--space-3xs) var(--space-s);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.youtube-live-badge--live {
|
||||
animation: youtube-pulse 2s infinite;
|
||||
background: #ff0000;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.youtube-live-badge--upcoming {
|
||||
background: #065fd4;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.youtube-live-badge--offline {
|
||||
background: var(--color-offset);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
@keyframes youtube-pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
}
|
||||
|
||||
.youtube-live-dot {
|
||||
background: currentColor;
|
||||
border-radius: 50%;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
/* Live stream card */
|
||||
.youtube-live-stream {
|
||||
background: linear-gradient(135deg, #ff000010, #ff000005);
|
||||
border: 1px solid #ff000030;
|
||||
border-radius: var(--radius-m);
|
||||
display: flex;
|
||||
gap: var(--space-m);
|
||||
margin-block-end: var(--space-l);
|
||||
padding: var(--space-m);
|
||||
}
|
||||
|
||||
.youtube-live-stream__thumb {
|
||||
border-radius: var(--radius-s);
|
||||
flex-shrink: 0;
|
||||
height: 90px;
|
||||
object-fit: cover;
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.youtube-live-stream__info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.youtube-live-stream__title {
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin: 0 0 var(--space-xs) 0;
|
||||
}
|
||||
|
||||
.youtube-live-stream__title a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.youtube-live-stream__title a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Video grid */
|
||||
.youtube-video-grid {
|
||||
display: grid;
|
||||
gap: var(--space-m);
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.youtube-video {
|
||||
background: var(--color-offset);
|
||||
border-radius: var(--radius-m);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.youtube-video__thumb-wrapper {
|
||||
aspect-ratio: 16/9;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.youtube-video__thumb {
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.youtube-video__duration {
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
border-radius: var(--radius-s);
|
||||
bottom: var(--space-2xs);
|
||||
color: white;
|
||||
font-size: var(--step--2);
|
||||
font-weight: var(--font-weight-medium);
|
||||
padding: var(--space-3xs) var(--space-2xs);
|
||||
position: absolute;
|
||||
right: var(--space-2xs);
|
||||
}
|
||||
|
||||
.youtube-video__duration--live {
|
||||
background: #ff0000;
|
||||
}
|
||||
|
||||
.youtube-video__info {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
padding: var(--space-s);
|
||||
}
|
||||
|
||||
.youtube-video__title {
|
||||
display: -webkit-box;
|
||||
font-size: var(--step--1);
|
||||
font-weight: var(--font-weight-medium);
|
||||
margin: 0 0 var(--space-xs) 0;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.youtube-video__title a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.youtube-video__title a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.youtube-video__meta {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: var(--step--2);
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
/* Multi-channel separator */
|
||||
.youtube-channel-section + .youtube-channel-section {
|
||||
border-block-start: 1px solid var(--color-border);
|
||||
margin-block-start: var(--space-xl);
|
||||
padding-block-start: var(--space-xl);
|
||||
}
|
||||
|
||||
/* Public link banner */
|
||||
.youtube-public-link {
|
||||
align-items: center;
|
||||
background: var(--color-offset);
|
||||
border-radius: var(--radius-m);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-block-start: var(--space-xl);
|
||||
padding: var(--space-m);
|
||||
}
|
||||
|
||||
.youtube-public-link p {
|
||||
color: var(--color-text-secondary);
|
||||
margin: 0;
|
||||
}
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@rmdes/indiekit-endpoint-youtube",
|
||||
"version": "1.0.0",
|
||||
"version": "1.2.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@rmdes/indiekit-endpoint-youtube",
|
||||
"version": "1.0.0",
|
||||
"version": "1.2.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@indiekit/error": "^1.0.0-beta.25",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@rmdes/indiekit-endpoint-youtube",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"description": "YouTube channel endpoint for Indiekit. Display latest videos and live status from any YouTube channel.",
|
||||
"keywords": [
|
||||
"indiekit",
|
||||
@@ -33,6 +33,7 @@
|
||||
".": "./index.js"
|
||||
},
|
||||
"files": [
|
||||
"assets",
|
||||
"includes",
|
||||
"lib",
|
||||
"locales",
|
||||
|
||||
6
views/layouts/youtube.njk
Normal file
6
views/layouts/youtube.njk
Normal file
@@ -0,0 +1,6 @@
|
||||
{% extends "document.njk" %}
|
||||
|
||||
{% block content %}
|
||||
<link rel="stylesheet" href="/assets/@rmdes-indiekit-endpoint-youtube/styles.css">
|
||||
{% block youtube %}{% endblock %}
|
||||
{% endblock %}
|
||||
@@ -1,218 +1,43 @@
|
||||
{% extends "document.njk" %}
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
.yt-section { margin-bottom: 2rem; }
|
||||
.yt-section h2 { margin-bottom: 1rem; }
|
||||
|
||||
/* Channel header */
|
||||
.yt-channel {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
background: var(--color-offset, #f5f5f5);
|
||||
border-radius: 0.5rem;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.yt-channel__avatar {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.yt-channel__info { flex: 1; }
|
||||
.yt-channel__name {
|
||||
font-weight: 600;
|
||||
font-size: 1.125rem;
|
||||
margin: 0 0 0.25rem 0;
|
||||
}
|
||||
.yt-channel__stats {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-secondary, #666);
|
||||
}
|
||||
|
||||
/* Live status badge */
|
||||
.yt-live-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 2rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.yt-live-badge--live {
|
||||
background: #ff0000;
|
||||
color: white;
|
||||
animation: yt-pulse 2s infinite;
|
||||
}
|
||||
.yt-live-badge--upcoming {
|
||||
background: #065fd4;
|
||||
color: white;
|
||||
}
|
||||
.yt-live-badge--offline {
|
||||
background: var(--color-offset, #e5e5e5);
|
||||
color: var(--color-text-secondary, #666);
|
||||
}
|
||||
@keyframes yt-pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.7; }
|
||||
}
|
||||
.yt-live-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
/* Live stream card */
|
||||
.yt-live-stream {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
background: linear-gradient(135deg, #ff000010, #ff000005);
|
||||
border: 1px solid #ff000030;
|
||||
border-radius: 0.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.yt-live-stream__thumb {
|
||||
width: 160px;
|
||||
height: 90px;
|
||||
object-fit: cover;
|
||||
border-radius: 0.25rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.yt-live-stream__info { flex: 1; }
|
||||
.yt-live-stream__title {
|
||||
font-weight: 600;
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
.yt-live-stream__title a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.yt-live-stream__title a:hover { text-decoration: underline; }
|
||||
|
||||
/* Video grid */
|
||||
.yt-video-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.yt-video {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--color-offset, #f5f5f5);
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
.yt-video__thumb-wrapper {
|
||||
position: relative;
|
||||
aspect-ratio: 16/9;
|
||||
}
|
||||
.yt-video__thumb {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.yt-video__duration {
|
||||
position: absolute;
|
||||
bottom: 0.5rem;
|
||||
right: 0.5rem;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
.yt-video__info {
|
||||
padding: 0.75rem;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.yt-video__title {
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
margin: 0 0 0.5rem 0;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.yt-video__title a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
.yt-video__title a:hover { text-decoration: underline; }
|
||||
.yt-video__meta {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-text-secondary, #666);
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
/* Public link banner */
|
||||
.yt-public-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1rem;
|
||||
background: var(--color-offset, #f5f5f5);
|
||||
border-radius: 0.5rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
.yt-public-link p {
|
||||
margin: 0;
|
||||
color: var(--color-text-secondary, #666);
|
||||
}
|
||||
</style>
|
||||
{% extends "layouts/youtube.njk" %}
|
||||
|
||||
{% block youtube %}
|
||||
{% if error %}
|
||||
{{ prose({ text: error.message }) }}
|
||||
{% elif isMultiChannel and channelsData %}
|
||||
{# Multi-channel mode: show all channels #}
|
||||
{% for chData in channelsData %}
|
||||
<div class="yt-channel-section" style="{% if not loop.first %}margin-top: 3rem; padding-top: 2rem; border-top: 1px solid var(--color-border, #e5e5e5);{% endif %}">
|
||||
<div class="youtube-channel-section">
|
||||
{# Channel Header #}
|
||||
{% if chData.error %}
|
||||
<div class="yt-channel" style="border: 1px solid #ff6b6b;">
|
||||
<div class="yt-channel__info">
|
||||
<h2 class="yt-channel__name">{{ chData.name }}</h2>
|
||||
<div class="youtube-channel" style="border: 1px solid #ff6b6b;">
|
||||
<div class="youtube-channel__info">
|
||||
<h2 class="youtube-channel__name">{{ chData.name }}</h2>
|
||||
<p style="color: #ff6b6b; margin: 0;">Error: {{ chData.error }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% elif chData.channel %}
|
||||
<div class="yt-channel">
|
||||
<div class="youtube-channel">
|
||||
{% if chData.channel.thumbnail %}
|
||||
<img src="{{ chData.channel.thumbnail }}" alt="" class="yt-channel__avatar">
|
||||
<img src="{{ chData.channel.thumbnail }}" alt="" class="youtube-channel__avatar">
|
||||
{% endif %}
|
||||
<div class="yt-channel__info">
|
||||
<h2 class="yt-channel__name">{{ chData.channel.title }}</h2>
|
||||
<div class="yt-channel__stats">
|
||||
<div class="youtube-channel__info">
|
||||
<h2 class="youtube-channel__name">{{ chData.channel.title }}</h2>
|
||||
<div class="youtube-channel__stats">
|
||||
<span>{{ chData.channel.subscriberCount }} {{ __("youtube.subscribers") }}</span>
|
||||
<span>{{ chData.channel.videoCount }} videos</span>
|
||||
</div>
|
||||
</div>
|
||||
{% if chData.isLive %}
|
||||
<span class="yt-live-badge yt-live-badge--live">
|
||||
<span class="yt-live-dot"></span>
|
||||
<span class="youtube-live-badge youtube-live-badge--live">
|
||||
<span class="youtube-live-dot"></span>
|
||||
{{ __("youtube.live") }}
|
||||
</span>
|
||||
{% elif chData.isUpcoming %}
|
||||
<span class="yt-live-badge yt-live-badge--upcoming">
|
||||
<span class="youtube-live-badge youtube-live-badge--upcoming">
|
||||
{{ __("youtube.upcoming") }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="yt-live-badge yt-live-badge--offline">
|
||||
<span class="youtube-live-badge youtube-live-badge--offline">
|
||||
{{ __("youtube.offline") }}
|
||||
</span>
|
||||
{% endif %}
|
||||
@@ -220,14 +45,13 @@
|
||||
|
||||
{# Live Stream (if live) #}
|
||||
{% if chData.liveStatus and (chData.liveStatus.isLive or chData.liveStatus.isUpcoming) %}
|
||||
<section class="yt-section">
|
||||
<h3>{% if chData.liveStatus.isLive %}{{ __("youtube.live") }}{% else %}{{ __("youtube.upcoming") }}{% endif %}</h3>
|
||||
<div class="yt-live-stream">
|
||||
{% call section({ title: chData.liveStatus.isLive and __("youtube.live") or __("youtube.upcoming") }) %}
|
||||
<div class="youtube-live-stream">
|
||||
{% if chData.liveStatus.thumbnail %}
|
||||
<img src="{{ chData.liveStatus.thumbnail }}" alt="" class="yt-live-stream__thumb">
|
||||
<img src="{{ chData.liveStatus.thumbnail }}" alt="" class="youtube-live-stream__thumb">
|
||||
{% endif %}
|
||||
<div class="yt-live-stream__info">
|
||||
<h4 class="yt-live-stream__title">
|
||||
<div class="youtube-live-stream__info">
|
||||
<h4 class="youtube-live-stream__title">
|
||||
<a href="https://www.youtube.com/watch?v={{ chData.liveStatus.videoId }}" target="_blank" rel="noopener">
|
||||
{{ chData.liveStatus.title }}
|
||||
</a>
|
||||
@@ -239,31 +63,30 @@
|
||||
}) }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endcall %}
|
||||
{% endif %}
|
||||
|
||||
{# Latest Videos #}
|
||||
<section class="yt-section">
|
||||
<h3>{{ __("youtube.videos") }}</h3>
|
||||
{% call section({ title: __("youtube.videos") }) %}
|
||||
{% if chData.videos and chData.videos.length > 0 %}
|
||||
<ul class="yt-video-grid">
|
||||
<ul class="youtube-video-grid">
|
||||
{% for video in chData.videos %}
|
||||
<li class="yt-video">
|
||||
<div class="yt-video__thumb-wrapper">
|
||||
<li class="youtube-video">
|
||||
<div class="youtube-video__thumb-wrapper">
|
||||
<a href="{{ video.url }}" target="_blank" rel="noopener">
|
||||
<img src="{{ video.thumbnail }}" alt="" class="yt-video__thumb" loading="lazy">
|
||||
<img src="{{ video.thumbnail }}" alt="" class="youtube-video__thumb" loading="lazy">
|
||||
</a>
|
||||
{% if video.durationFormatted and not video.isLive %}
|
||||
<span class="yt-video__duration">{{ video.durationFormatted }}</span>
|
||||
<span class="youtube-video__duration">{{ video.durationFormatted }}</span>
|
||||
{% elif video.isLive %}
|
||||
<span class="yt-video__duration" style="background:#ff0000">LIVE</span>
|
||||
<span class="youtube-video__duration youtube-video__duration--live">LIVE</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="yt-video__info">
|
||||
<h4 class="yt-video__title">
|
||||
<div class="youtube-video__info">
|
||||
<h4 class="youtube-video__title">
|
||||
<a href="{{ video.url }}" target="_blank" rel="noopener">{{ video.title }}</a>
|
||||
</h4>
|
||||
<div class="yt-video__meta">
|
||||
<div class="youtube-video__meta">
|
||||
{{ video.viewCount }} {{ __("youtube.views") }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -273,10 +96,10 @@
|
||||
{% else %}
|
||||
{{ prose({ text: __("youtube.noVideos") }) }}
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endcall %}
|
||||
|
||||
{# Link to YouTube channel #}
|
||||
<div class="yt-public-link" style="margin-top: 1rem;">
|
||||
<div class="youtube-public-link" style="margin-top: var(--space-m);">
|
||||
<p>{{ __("youtube.widget.description") }}</p>
|
||||
{{ button({
|
||||
href: "https://www.youtube.com/channel/" + chData.channel.id,
|
||||
@@ -291,28 +114,28 @@
|
||||
{# Single channel mode (backward compatible) #}
|
||||
{# Channel Header #}
|
||||
{% if channel %}
|
||||
<div class="yt-channel">
|
||||
<div class="youtube-channel">
|
||||
{% if channel.thumbnail %}
|
||||
<img src="{{ channel.thumbnail }}" alt="" class="yt-channel__avatar">
|
||||
<img src="{{ channel.thumbnail }}" alt="" class="youtube-channel__avatar">
|
||||
{% endif %}
|
||||
<div class="yt-channel__info">
|
||||
<h2 class="yt-channel__name">{{ channel.title }}</h2>
|
||||
<div class="yt-channel__stats">
|
||||
<div class="youtube-channel__info">
|
||||
<h2 class="youtube-channel__name">{{ channel.title }}</h2>
|
||||
<div class="youtube-channel__stats">
|
||||
<span>{{ channel.subscriberCount }} {{ __("youtube.subscribers") }}</span>
|
||||
<span>{{ channel.videoCount }} videos</span>
|
||||
</div>
|
||||
</div>
|
||||
{% if isLive %}
|
||||
<span class="yt-live-badge yt-live-badge--live">
|
||||
<span class="yt-live-dot"></span>
|
||||
<span class="youtube-live-badge youtube-live-badge--live">
|
||||
<span class="youtube-live-dot"></span>
|
||||
{{ __("youtube.live") }}
|
||||
</span>
|
||||
{% elif isUpcoming %}
|
||||
<span class="yt-live-badge yt-live-badge--upcoming">
|
||||
<span class="youtube-live-badge youtube-live-badge--upcoming">
|
||||
{{ __("youtube.upcoming") }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="yt-live-badge yt-live-badge--offline">
|
||||
<span class="youtube-live-badge youtube-live-badge--offline">
|
||||
{{ __("youtube.offline") }}
|
||||
</span>
|
||||
{% endif %}
|
||||
@@ -321,14 +144,13 @@
|
||||
|
||||
{# Live Stream (if live) #}
|
||||
{% if liveStatus and (liveStatus.isLive or liveStatus.isUpcoming) %}
|
||||
<section class="yt-section">
|
||||
<h2>{% if liveStatus.isLive %}{{ __("youtube.live") }}{% else %}{{ __("youtube.upcoming") }}{% endif %}</h2>
|
||||
<div class="yt-live-stream">
|
||||
{% call section({ title: liveStatus.isLive and __("youtube.live") or __("youtube.upcoming") }) %}
|
||||
<div class="youtube-live-stream">
|
||||
{% if liveStatus.thumbnail %}
|
||||
<img src="{{ liveStatus.thumbnail }}" alt="" class="yt-live-stream__thumb">
|
||||
<img src="{{ liveStatus.thumbnail }}" alt="" class="youtube-live-stream__thumb">
|
||||
{% endif %}
|
||||
<div class="yt-live-stream__info">
|
||||
<h3 class="yt-live-stream__title">
|
||||
<div class="youtube-live-stream__info">
|
||||
<h3 class="youtube-live-stream__title">
|
||||
<a href="https://www.youtube.com/watch?v={{ liveStatus.videoId }}" target="_blank" rel="noopener">
|
||||
{{ liveStatus.title }}
|
||||
</a>
|
||||
@@ -340,31 +162,30 @@
|
||||
}) }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endcall %}
|
||||
{% endif %}
|
||||
|
||||
{# Latest Videos #}
|
||||
<section class="yt-section">
|
||||
<h2>{{ __("youtube.videos") }}</h2>
|
||||
{% call section({ title: __("youtube.videos") }) %}
|
||||
{% if videos and videos.length > 0 %}
|
||||
<ul class="yt-video-grid">
|
||||
<ul class="youtube-video-grid">
|
||||
{% for video in videos %}
|
||||
<li class="yt-video">
|
||||
<div class="yt-video__thumb-wrapper">
|
||||
<li class="youtube-video">
|
||||
<div class="youtube-video__thumb-wrapper">
|
||||
<a href="{{ video.url }}" target="_blank" rel="noopener">
|
||||
<img src="{{ video.thumbnail }}" alt="" class="yt-video__thumb" loading="lazy">
|
||||
<img src="{{ video.thumbnail }}" alt="" class="youtube-video__thumb" loading="lazy">
|
||||
</a>
|
||||
{% if video.durationFormatted and not video.isLive %}
|
||||
<span class="yt-video__duration">{{ video.durationFormatted }}</span>
|
||||
<span class="youtube-video__duration">{{ video.durationFormatted }}</span>
|
||||
{% elif video.isLive %}
|
||||
<span class="yt-video__duration" style="background:#ff0000">LIVE</span>
|
||||
<span class="youtube-video__duration youtube-video__duration--live">LIVE</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="yt-video__info">
|
||||
<h3 class="yt-video__title">
|
||||
<div class="youtube-video__info">
|
||||
<h3 class="youtube-video__title">
|
||||
<a href="{{ video.url }}" target="_blank" rel="noopener">{{ video.title }}</a>
|
||||
</h3>
|
||||
<div class="yt-video__meta">
|
||||
<div class="youtube-video__meta">
|
||||
{{ video.viewCount }} {{ __("youtube.views") }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -374,11 +195,11 @@
|
||||
{% else %}
|
||||
{{ prose({ text: __("youtube.noVideos") }) }}
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endcall %}
|
||||
|
||||
{# Link to YouTube channel #}
|
||||
{% if channel %}
|
||||
<div class="yt-public-link">
|
||||
<div class="youtube-public-link">
|
||||
<p>{{ __("youtube.widget.description") }}</p>
|
||||
{{ button({
|
||||
href: "https://www.youtube.com/channel/" + channel.id,
|
||||
|
||||
Reference in New Issue
Block a user