Some checks failed
Docker Build & Push / build (push) Has been cancelled
- Added WebLLM service for client-side AI summarization and translation - Improved summary quality (5 sentences, 600 char limit) - Added Vietnamese character detection for proper language labels - Added Copy button for summary content - Key Points now extract conceptual ideas, not transcript excerpts - Removed mini player (scroll-to-minimize) feature - Fixed main.js null container error - Silent WebLLM loading (no overlay/toasts) - Added transcript service with yt-dlp
217 lines
No EOL
8.7 KiB
HTML
Executable file
217 lines
No EOL
8.7 KiB
HTML
Executable file
{% extends "layout.html" %}
|
|
|
|
{% block content %}
|
|
<script>
|
|
window.APP_CONFIG = {
|
|
page: '{{ page|default("home") }}',
|
|
channelId: '{{ channel_id|default("") }}',
|
|
query: '{{ query|default("") }}'
|
|
};
|
|
</script>
|
|
<!-- Filters & Categories -->
|
|
<div class="yt-filter-bar">
|
|
<div class="yt-categories" id="categoryList">
|
|
<!-- Pinned Categories -->
|
|
<button class="yt-chip" onclick="switchCategory('suggested', this)"><i class="fas fa-magic"></i>
|
|
Suggested</button>
|
|
<!-- Standard Categories -->
|
|
<button class="yt-chip" onclick="switchCategory('tech', this)">Tech</button>
|
|
<button class="yt-chip" onclick="switchCategory('music', this)">Music</button>
|
|
<button class="yt-chip" onclick="switchCategory('movies', this)">Movies</button>
|
|
<button class="yt-chip" onclick="switchCategory('news', this)">News</button>
|
|
<button class="yt-chip" onclick="switchCategory('trending', this)">Trending</button>
|
|
<button class="yt-chip" onclick="switchCategory('podcasts', this)">Podcasts</button>
|
|
<button class="yt-chip" onclick="switchCategory('live', this)">Live</button>
|
|
<button class="yt-chip" onclick="switchCategory('gaming', this)">Gaming</button>
|
|
<button class="yt-chip" onclick="switchCategory('sports', this)">Sports</button>
|
|
</div>
|
|
|
|
<div class="yt-filter-actions">
|
|
<div class="yt-dropdown">
|
|
<button class="yt-icon-btn" id="filterToggleBtn" onclick="toggleFilterMenu()">
|
|
<i class="fas fa-sliders-h"></i>
|
|
</button>
|
|
<div class="yt-dropdown-menu" id="filterMenu">
|
|
<div class="yt-menu-section">
|
|
<h4>Sort By</h4>
|
|
<button onclick="changeSort('day')">Today</button>
|
|
<button onclick="changeSort('week')">This Week</button>
|
|
<button onclick="changeSort('month')">This Month</button>
|
|
<button onclick="changeSort('3months')">Last 3 Months</button>
|
|
<button onclick="changeSort('year')">This Year</button>
|
|
</div>
|
|
<div class="yt-menu-section">
|
|
<h4>Region</h4>
|
|
<button onclick="changeRegion('vietnam')">Vietnam</button>
|
|
<button onclick="changeRegion('global')">Global</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<!-- Shorts Section -->
|
|
|
|
|
|
<!-- Videos Section -->
|
|
<div id="videosSection" class="yt-section">
|
|
<div class="yt-section-header" style="display:none;">
|
|
<h2><i class="fas fa-play-circle"></i> Videos</h2>
|
|
</div>
|
|
<div id="resultsArea" class="yt-video-grid">
|
|
<!-- Initial Skeleton State -->
|
|
<!-- Initial Skeleton State (12 items) -->
|
|
<div class="yt-video-card skeleton-card">
|
|
<div class="skeleton-thumb skeleton"></div>
|
|
<div class="skeleton-details">
|
|
<div class="skeleton-avatar skeleton"></div>
|
|
<div class="skeleton-text">
|
|
<div class="skeleton-title skeleton"></div>
|
|
<div class="skeleton-meta skeleton"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="yt-video-card skeleton-card">
|
|
<div class="skeleton-thumb skeleton"></div>
|
|
<div class="skeleton-details">
|
|
<div class="skeleton-avatar skeleton"></div>
|
|
<div class="skeleton-text">
|
|
<div class="skeleton-title skeleton"></div>
|
|
<div class="skeleton-meta skeleton"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="yt-video-card skeleton-card">
|
|
<div class="skeleton-thumb skeleton"></div>
|
|
<div class="skeleton-details">
|
|
<div class="skeleton-avatar skeleton"></div>
|
|
<div class="skeleton-text">
|
|
<div class="skeleton-title skeleton"></div>
|
|
<div class="skeleton-meta skeleton"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="yt-video-card skeleton-card">
|
|
<div class="skeleton-thumb skeleton"></div>
|
|
<div class="skeleton-details">
|
|
<div class="skeleton-avatar skeleton"></div>
|
|
<div class="skeleton-text">
|
|
<div class="skeleton-title skeleton"></div>
|
|
<div class="skeleton-meta skeleton"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="yt-video-card skeleton-card">
|
|
<div class="skeleton-thumb skeleton"></div>
|
|
<div class="skeleton-details">
|
|
<div class="skeleton-avatar skeleton"></div>
|
|
<div class="skeleton-text">
|
|
<div class="skeleton-title skeleton"></div>
|
|
<div class="skeleton-meta skeleton"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="yt-video-card skeleton-card">
|
|
<div class="skeleton-thumb skeleton"></div>
|
|
<div class="skeleton-details">
|
|
<div class="skeleton-avatar skeleton"></div>
|
|
<div class="skeleton-text">
|
|
<div class="skeleton-title skeleton"></div>
|
|
<div class="skeleton-meta skeleton"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="yt-video-card skeleton-card">
|
|
<div class="skeleton-thumb skeleton"></div>
|
|
<div class="skeleton-details">
|
|
<div class="skeleton-avatar skeleton"></div>
|
|
<div class="skeleton-text">
|
|
<div class="skeleton-title skeleton"></div>
|
|
<div class="skeleton-meta skeleton"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="yt-video-card skeleton-card">
|
|
<div class="skeleton-thumb skeleton"></div>
|
|
<div class="skeleton-details">
|
|
<div class="skeleton-avatar skeleton"></div>
|
|
<div class="skeleton-text">
|
|
<div class="skeleton-title skeleton"></div>
|
|
<div class="skeleton-meta skeleton"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Styles moved to CSS modules -->
|
|
|
|
<script>
|
|
// Global filter state
|
|
// Global filter state
|
|
var currentSort = 'month';
|
|
var currentRegion = 'vietnam';
|
|
|
|
function toggleFilterMenu() {
|
|
const menu = document.getElementById('filterMenu');
|
|
if (menu) menu.classList.toggle('show');
|
|
}
|
|
|
|
// Close menu when clicking outside - Prevent multiple listeners
|
|
if (!window.filterMenuListenerAttached) {
|
|
document.addEventListener('click', function (e) {
|
|
const menu = document.getElementById('filterMenu');
|
|
const btn = document.getElementById('filterToggleBtn');
|
|
// Only run if elements exist (we are on home page)
|
|
if (menu && btn && !menu.contains(e.target) && !btn.contains(e.target)) {
|
|
menu.classList.remove('show');
|
|
}
|
|
});
|
|
window.filterMenuListenerAttached = true;
|
|
}
|
|
|
|
function changeSort(sort) {
|
|
window.currentSort = sort;
|
|
// Global loadTrending from main.js will use this
|
|
loadTrending(true);
|
|
toggleFilterMenu();
|
|
}
|
|
|
|
function changeRegion(region) {
|
|
window.currentRegion = region;
|
|
loadTrending(true);
|
|
toggleFilterMenu();
|
|
}
|
|
|
|
// Helpers (if main.js not loaded yet or for standalone usage)
|
|
function escapeHtml(text) {
|
|
if (!text) return '';
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
function formatViews(views) {
|
|
if (!views) return '0';
|
|
const num = parseInt(views);
|
|
if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
|
|
if (num >= 1000) return (num / 1000).toFixed(0) + 'K';
|
|
return num.toLocaleString();
|
|
}
|
|
|
|
// Init Logic
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Pagination logic removed for infinite scroll
|
|
|
|
// Check URL params for category
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const category = urlParams.get('category');
|
|
if (category && typeof switchCategory === 'function') {
|
|
// Let main.js handle the switch, but we can set UI active state if needed
|
|
// switchCategory is in main.js
|
|
switchCategory(category);
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |