From 60d3bc3a5e1717bbbba53379c9c31622b423d317 Mon Sep 17 00:00:00 2001 From: KV-Tube Deployer Date: Sun, 11 Jan 2026 21:51:42 +0700 Subject: [PATCH] Fix subscribe button and library tab navigation --- .dockerignore | 12 + deploy_v2.ps1 | 27 +++ static/css/modules/layout.css | 25 +- static/css/modules/pages.css | 2 +- static/css/modules/watch.css | 105 ++++++++- static/js/main.js | 28 ++- templates/channel.html | 431 ++++++++++++++++++---------------- templates/layout.html | 6 - templates/my_videos.html | 277 +++++++++++++++++++--- templates/watch.html | 177 +++++++++++++- wsgi.py | 273 +++++++++++++++------ 11 files changed, 1034 insertions(+), 329 deletions(-) create mode 100644 .dockerignore create mode 100644 deploy_v2.ps1 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..83b1373 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +__pycache__ +.venv +.git +.env +*.mp4 +*.webm +*.mp3 +videos/ +data/ +temp/ +deployment_package/ +kvtube.db diff --git a/deploy_v2.ps1 b/deploy_v2.ps1 new file mode 100644 index 0000000..e461323 --- /dev/null +++ b/deploy_v2.ps1 @@ -0,0 +1,27 @@ +# deploy_v2.ps1 - Deploy KV-Tube v2.0 + +Write-Host "--- KV-Tube v2.0 Deployment ---" -ForegroundColor Cyan + +# 1. Check Git Remote +Write-Host "1. Pushing to Git..." -ForegroundColor Yellow +# Note: Ensure 'origin' is the correct writable remote, not a mirror. +git push -u origin main --tags + +if ($LASTEXITCODE -ne 0) { + Write-Host "Error: Git push failed. Verify that 'origin' is not a read-only mirror." -ForegroundColor Red + # Continue anyway to try Docker? +} + +# 2. Build Docker Image +Write-Host "2. Building Docker Image (linux/amd64)..." -ForegroundColor Yellow +# Requires Docker Desktop to be running +docker build --platform linux/amd64 -t kv-tube:v2.0 . + +if ($LASTEXITCODE -eq 0) { + Write-Host "Success! Docker image 'kv-tube:v2.0' built." -ForegroundColor Green +} else { + Write-Host "Error: Docker build failed. Is Docker Desktop running?" -ForegroundColor Red +} + +Write-Host "Done." -ForegroundColor Cyan +pause diff --git a/static/css/modules/layout.css b/static/css/modules/layout.css index 8cc9c33..20f1e81 100644 --- a/static/css/modules/layout.css +++ b/static/css/modules/layout.css @@ -120,16 +120,37 @@ .yt-sidebar.collapsed .yt-sidebar-item { flex-direction: column; - gap: 6px; + gap: 0; padding: 16px 0; margin: 0; border-radius: 0; justify-content: center; + align-items: center; text-align: center; } +/* Hide text labels in collapsed mode - icons only */ .yt-sidebar.collapsed .yt-sidebar-item span { - font-size: 10px; + display: none; +} + +/* Center icons in collapsed mode */ +.yt-sidebar.collapsed .yt-sidebar-item i { + font-size: 20px; + width: 100%; + text-align: center; +} + +/* Hide Saved, Subscriptions, dividers and titles in collapsed mode */ +.yt-sidebar.collapsed .yt-sidebar-title, +.yt-sidebar.collapsed .yt-sidebar-divider { + display: none; +} + +/* Hide Saved and Subscriptions globally (both full and collapsed sidebar) */ +.yt-sidebar a[data-category="saved"], +.yt-sidebar a[data-category="subscriptions"] { + display: none; } .yt-sidebar-divider { diff --git a/static/css/modules/pages.css b/static/css/modules/pages.css index 774890a..13966c5 100644 --- a/static/css/modules/pages.css +++ b/static/css/modules/pages.css @@ -1,9 +1,9 @@ /* ===== Watch Page ===== */ +/* Layout rules moved to watch.css - this is kept for compatibility */ .yt-watch-layout { display: grid; grid-template-columns: 1fr 400px; gap: 24px; - max-width: 1800px; } .yt-player-section { diff --git a/static/css/modules/watch.css b/static/css/modules/watch.css index 2db7839..dcb31c7 100644 --- a/static/css/modules/watch.css +++ b/static/css/modules/watch.css @@ -90,33 +90,118 @@ body { } /* ========== Watch Page Layout ========== */ -.yt-main { +/* Only apply these overrides when the watch layout is present */ +.yt-main:has(.yt-watch-layout) { padding: 0 !important; - margin-left: 240px; + /* Auto-collapse main content margin on watch page to match collapsed sidebar */ + margin-left: var(--yt-sidebar-mini) !important; } +/* Auto-collapse sidebar on watch page */ +.yt-sidebar:has(~ .yt-sidebar-overlay ~ .yt-main .yt-watch-layout), +body:has(.yt-watch-layout) .yt-sidebar { + width: var(--yt-sidebar-mini); +} + +/* Sidebar item styling for mini mode on watch page */ +body:has(.yt-watch-layout) .yt-sidebar .yt-sidebar-item { + flex-direction: column; + gap: 0; + padding: 16px 0; + margin: 0; + border-radius: 0; + justify-content: center; + align-items: center; + text-align: center; +} + +/* Hide text labels in mini mode - icons only */ +body:has(.yt-watch-layout) .yt-sidebar .yt-sidebar-item span { + display: none; +} + +/* Center the icons */ +body:has(.yt-watch-layout) .yt-sidebar .yt-sidebar-item i { + font-size: 20px; + width: 100%; + text-align: center; +} + +/* Hide Saved, Subscriptions, and dividers/titles on watch page */ +body:has(.yt-watch-layout) .yt-sidebar .yt-sidebar-title, +body:has(.yt-watch-layout) .yt-sidebar .yt-sidebar-divider, +body:has(.yt-watch-layout) .yt-sidebar a[data-category="saved"], +body:has(.yt-watch-layout) .yt-sidebar a[data-category="subscriptions"] { + display: none; +} + +/* Theater Mode (Default) - Full width video with sidebar below */ .yt-watch-layout { + display: flex; + flex-direction: column; + width: 100%; + padding: 8px 24px 24px; + box-sizing: border-box; +} + +/* Default Mode - 2 column layout */ +.yt-watch-layout.default-mode { display: grid; grid-template-columns: 1fr 400px; gap: 24px; max-width: 100%; - width: 100%; - padding: 24px; - margin: 0; - box-sizing: border-box; +} + +.yt-watch-layout.default-mode .yt-watch-sidebar { + position: sticky; + top: 80px; + align-self: start; + max-height: calc(100vh - 100px); +} + +/* Theater mode sidebar moves below */ +.yt-watch-layout:not(.default-mode) .yt-watch-sidebar { + margin-top: 24px; } .yt-watch-sidebar { display: flex; flex-direction: column; gap: 0; - position: sticky; - top: 80px; - align-self: start; - max-height: calc(100vh - 100px); overflow: visible; } +/* View Mode Button Styles */ +.view-mode-buttons { + display: flex; + gap: 8px; + margin-left: auto; +} + +.view-mode-btn { + width: 36px; + height: 36px; + border-radius: 50%; + border: none; + background: var(--yt-bg-secondary); + color: var(--yt-text-secondary); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; +} + +.view-mode-btn:hover { + background: var(--yt-bg-hover); + color: var(--yt-text-primary); +} + +.view-mode-btn.active { + background: linear-gradient(135deg, #cc0000 0%, #ff4444 100%); + color: white; +} + .yt-channel-avatar-lg { width: 40px; height: 40px; diff --git a/static/js/main.js b/static/js/main.js index 371bdc9..21f531c 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -245,7 +245,18 @@ async function switchCategory(category, btn) { return; } if (category === 'suggested') { - const response = await fetch('/api/suggested'); + // Build query params from localStorage history + const history = JSON.parse(localStorage.getItem('kv_history') || '[]'); + const titles = history.slice(0, 5).map(v => v.title).filter(Boolean).join(','); + const channels = history.slice(0, 3).map(v => v.uploader).filter(Boolean).join(','); + + let url = '/api/suggested'; + const params = new URLSearchParams(); + if (titles) params.append('titles', titles); + if (channels) params.append('channels', channels); + if (params.toString()) url += '?' + params.toString(); + + const response = await fetch(url); const data = await response.json(); displayResults(data, false); isLoading = false; @@ -296,7 +307,20 @@ async function loadTrending(reset = true) { const regionValue = window.currentRegion || 'vietnam'; // Add cache-buster for home page to ensure fresh content const cb = reset && currentCategory === 'all' ? `&_=${Date.now()}` : ''; - const response = await fetch(`/api/trending?category=${currentCategory}&page=${currentPage}&sort=${sortValue}®ion=${regionValue}${cb}`); + + // Include localStorage history for personalized suggestions on home page + let historyParams = ''; + if (currentCategory === 'all') { + const history = JSON.parse(localStorage.getItem('kv_history') || '[]'); + if (history.length > 0) { + const titles = history.slice(0, 5).map(v => v.title).filter(Boolean).join(','); + const channels = history.slice(0, 3).map(v => v.uploader).filter(Boolean).join(','); + if (titles) historyParams += `&history_titles=${encodeURIComponent(titles)}`; + if (channels) historyParams += `&history_channels=${encodeURIComponent(channels)}`; + } + } + + const response = await fetch(`/api/trending?category=${currentCategory}&page=${currentPage}&sort=${sortValue}®ion=${regionValue}${historyParams}${cb}`); const data = await response.json(); diff --git a/templates/channel.html b/templates/channel.html index 011a106..3a8fd52 100644 --- a/templates/channel.html +++ b/templates/channel.html @@ -22,10 +22,7 @@ %}@Loading...{% endif %}

- Subscribe for more -
-
- +
@@ -35,14 +32,29 @@
@@ -129,53 +141,58 @@ .yt-tabs { display: inline-flex; - gap: 0; + gap: 8px; background: var(--yt-bg-secondary); - padding: 4px; - border-radius: 24px; - position: relative; + padding: 6px; + border-radius: 16px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15); } .yt-tabs a { - font-size: 14px; + display: flex; + align-items: center; + gap: 8px; + padding: 12px 24px; + border-radius: 12px; + font-size: 0.95rem; font-weight: 600; color: var(--yt-text-secondary); text-decoration: none; - padding: 8px 24px; - border-radius: 20px; - border-bottom: none; - z-index: 1; - transition: color 0.2s; - position: relative; + transition: all 0.25s ease; + border: none; + background: transparent; } .yt-tabs a:hover { color: var(--yt-text-primary); + background: var(--yt-bg-hover); } .yt-tabs a.active { - color: var(--yt-bg-primary); - background: var(--yt-text-primary); - /* The "slider" is actually the active pill moving */ - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); + background: linear-gradient(135deg, #cc0000 0%, #ff4444 100%); + color: white; + box-shadow: 0 4px 12px rgba(204, 0, 0, 0.3); } .yt-sort-options { - display: flex; + display: inline-flex; gap: 8px; - justify-content: center; + background: var(--yt-bg-secondary); + padding: 6px; + border-radius: 16px; + box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15); } .yt-sort-options a { - padding: 6px 16px; - border-radius: 16px; + padding: 10px 20px; + border-radius: 12px; background: transparent; - border: 1px solid var(--yt-border); + border: none; color: var(--yt-text-secondary); text-decoration: none; - font-size: 13px; - font-weight: 500; - transition: all 0.2s; + font-size: 0.9rem; + font-weight: 600; + transition: all 0.25s ease; } .yt-sort-options a:hover { @@ -184,9 +201,9 @@ } .yt-sort-options a.active { - background: var(--yt-bg-secondary); - color: var(--yt-text-primary); - border-color: var(--yt-text-primary); + background: linear-gradient(135deg, #cc0000 0%, #ff4444 100%); + color: white; + box-shadow: 0 4px 12px rgba(204, 0, 0, 0.3); } /* Shorts Card Styling override for Channel Page grid */ @@ -245,133 +262,134 @@ {% endblock %} \ No newline at end of file diff --git a/templates/layout.html b/templates/layout.html index 748e076..7cc13c5 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -141,12 +141,6 @@ - -
-
-
-
diff --git a/templates/my_videos.html b/templates/my_videos.html index 65bc77b..4434cdc 100644 --- a/templates/my_videos.html +++ b/templates/my_videos.html @@ -1,25 +1,200 @@ {% extends "layout.html" %} {% block content %} -
-
-

My Library

-
- History - Saved - Subscriptions + + +
+
+

My Library

+ + - - + + +
+ +
@@ -28,32 +203,44 @@
-