all is working great

This commit is contained in:
ghassan committed 2026-03-15 04:06:35 +03:00
1 parent 69f5df163a
commit f850f40f78
11 files changed
+4609 -5282

No files matched your search

+3 -3
View File
@@ -23,7 +23,7 @@ class VideoController extends Controller
public function index() public function index()
{ {
$videos = Video::public()->latest()->paginate(12); $videos = Video::public()->latest()->get();
return view('videos.index', compact('videos')); return view('videos.index', compact('videos'));
} }
@@ -42,7 +42,7 @@ class VideoController extends Controller
->orWhere('description', 'like', "%{$query}%"); ->orWhere('description', 'like', "%{$query}%");
}) })
->latest() ->latest()
->paginate(12); ->get();
return view('videos.index', compact('videos', 'query')); return view('videos.index', compact('videos', 'query'));
} }
@@ -491,7 +491,7 @@ class VideoController extends Controller
->where('status', 'ready') ->where('status', 'ready')
->with('user') ->with('user')
->latest() ->latest()
->paginate(12); ->get();
return view('videos.shorts', compact('videos')); return view('videos.shorts', compact('videos'));
} }
+66 -11
View File
@@ -1,14 +1,41 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<style> <style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; } body {
.container { max-width: 600px; margin: 0 auto; padding: 20px; } font-family: Arial, sans-serif;
.header { background: #e61e1e; color: white; padding: 20px; text-align: center; } line-height: 1.6;
.content { padding: 20px; background: #f9f9f9; } color: #333;
.footer { text-align: center; padding: 20px; color: #666; font-size: 12px; } }
.container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
}
.header {
background: #e61e1e;
color: white;
padding: 20px;
text-align: center;
}
.content {
padding: 20px;
background: #f9f9f9;
}
.footer {
text-align: center;
padding: 20px;
color: #666;
font-size: 12px;
}
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<div class="header"> <div class="header">
@@ -19,16 +46,44 @@
<p>Hi {{ $userName }},</p> <p>Hi {{ $userName }},</p>
<p>Your video <strong>"{{ $video->title }}"</strong> has been uploaded successfully!</p> <p>Your video <strong>"{{ $video->title }}"</strong> has been uploaded successfully!</p>
<p>Your video is now being processed and will be available shortly.</p> <p>Your video is now being processed and will be available shortly.</p>
<p>Video Details:</p>
<ul> <p>Check out your uploaded video:</p>
<li>Size: {{ round($video->size / 1024 / 1024, 2) }} MB</li> <div style="margin: 20px 0; text-align: center;">
<li>Orientation: {{ $video->orientation }}</li> @if ($video->thumbnail)
</ul> <a href="{{ url('/videos/' . $video->id) }}">
<p><a href="{{ url('/videos/' . $video->id) }}" style="background: #e61e1e; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;">View Video</a></p> <img src="{{ asset('storage/thumbnails/' . $video->thumbnail) }}" alt="{{ $video->title }}"
style="max-width: 100%; height: auto; border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.3); display: block; margin: 0 auto;">
</a>
@else
<div
style="width: 100%; height: 200px; background: linear-gradient(45deg, #e61e1e, #ff4757); border-radius: 12px; display: flex; align-items: center; justify-content: center; margin: 0 auto; color: white; font-size: 18px; font-weight: bold; box-shadow: 0 8px 24px rgba(230,30,30,0.4);">
<i class="bi bi-play-circle-fill" style="font-size: 48px; margin-right: 16px;"></i>
{{ Str::limit($video->title, 30) }}
</div>
<p style="margin-top: 12px; color: #666; font-size: 14px;">Thumbnail generating...</p>
@endif
</div>
<div style="background: #f0f0f0; padding: 16px; border-radius: 8px; margin: 20px 0;">
<h4 style="margin-top: 0; color: #333;">Video Details:</h4>
<ul style="margin: 0; padding-left: 20px;">
<li><strong>Title:</strong> {{ $video->title }}</li>
<li><strong>Size:</strong> {{ round($video->size / 1024 / 1024, 2) }} MB</li>
<li><strong>Orientation:</strong> {{ $video->orientation ?? 'Horizontal' }}</li>
</ul>
</div>
<p style="text-align: center;">
<a href="{{ url('/videos/' . $video->id) }}"
style="background: #e61e1e; color: white; padding: 12px 24px; text-decoration: none; border-radius: 25px; font-weight: 600; font-size: 16px; display: inline-block; box-shadow: 0 4px 12px rgba(230,30,30,0.4);">▶️
Watch Video</a>
</p>
</div> </div>
<div class="footer"> <div class="footer">
<p>TAKEONE Video Platform</p> <p>TAKEONE Video Platform</p>
</div> </div>
</div> </div>
</body> </body>
</html> </html>
@@ -0,0 +1,29 @@
<div class="modal fade" id="confirmDeleteModal{{ $commentId }}" tabindex="-1" aria-labelledby="confirmDeleteModalLabel{{ $commentId }}" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content" style="background: var(--bg-secondary); border: 1px solid var(--border-color); border-radius: 12px;">
<div class="modal-header" style="border-bottom: 1px solid var(--border-color); padding: 20px;">
<h5 class="modal-title" id="confirmDeleteModalLabel{{ $commentId }}" style="color: var(--text-primary); font-weight: 600;">
<i class="bi bi-exclamation-triangle-fill" style="color: var(--brand-red); margin-right: 8px;"></i>
Delete Comment
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" style="padding: 20px;">
<p style="color: var(--text-primary); margin-bottom: 16px; line-height: 1.5;">
Are you sure you want to delete this comment? This action cannot be undone.
</p>
<div class="comment-preview" style="background: var(--bg-primary); border-radius: 8px; padding: 12px; font-size: 14px; line-height: 1.4;">
{{ Str::limit($body, 100) }}
</div>
</div>
<div class="modal-footer" style="border-top: 1px solid var(--border-color); padding: 16px 20px; gap: 8px;">
<button type="button" class="btn" style="background: var(--bg-primary); color: var(--text-primary); border: 1px solid var(--border-color); padding: 8px 16px; border-radius: 6px; font-weight: 500;" data-bs-dismiss="modal">
Cancel
</button>
<button type="button" class="btn" style="background: var(--brand-red); color: white; padding: 8px 16px; border-radius: 6px; font-weight: 500; border: none;" onclick="deleteCommentWithModal({{ $commentId }})">
Delete
</button>
</div>
</div>
</div>
</div>
+158 -140
View File
@@ -1,6 +1,7 @@
@extends('layouts.app') @extends('layouts.app')
@section('title', isset($query) ? 'Search: ' . $query . ' | ' . config('app.name') : 'Video Gallery | ' . config('app.name')) @section('title', isset($query) ? 'Search: ' . $query . ' | ' . config('app.name') : 'Video Gallery | ' .
config('app.name'))
@section('extra_styles') @section('extra_styles')
<style> <style>
@@ -11,28 +12,28 @@
background: var(--bg-secondary); background: var(--bg-secondary);
border-radius: 12px; border-radius: 12px;
} }
.search-info h2 { .search-info h2 {
font-size: 20px; font-size: 20px;
margin: 0; margin: 0;
} }
.search-info p { .search-info p {
color: var(--text-secondary); color: var(--text-secondary);
margin: 8px 0 0; margin: 8px 0 0;
} }
/* Video Grid */ /* Video Grid */
.yt-video-grid { .yt-video-grid {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
gap: 24px; gap: 24px;
} }
.yt-video-card { .yt-video-card {
cursor: pointer; cursor: pointer;
} }
.yt-video-thumb { .yt-video-thumb {
position: relative; position: relative;
aspect-ratio: 16/9; aspect-ratio: 16/9;
@@ -40,7 +41,7 @@
overflow: hidden; overflow: hidden;
background: #1a1a1a; background: #1a1a1a;
} }
.yt-video-thumb img { .yt-video-thumb img {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -49,7 +50,7 @@
top: 0; top: 0;
left: 0; left: 0;
} }
.yt-video-thumb video { .yt-video-thumb video {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -61,29 +62,29 @@
transition: opacity 0.3s ease; transition: opacity 0.3s ease;
background: #000; background: #000;
} }
.yt-video-thumb video.active { .yt-video-thumb video.active {
opacity: 1; opacity: 1;
} }
.yt-video-duration { .yt-video-duration {
position: absolute; position: absolute;
bottom: 8px; bottom: 8px;
right: 8px; right: 8px;
background: rgba(0,0,0,0.8); background: rgba(0, 0, 0, 0.8);
color: white; color: white;
padding: 3px 6px; padding: 3px 6px;
border-radius: 4px; border-radius: 4px;
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 500;
} }
.yt-video-info { .yt-video-info {
display: flex; display: flex;
margin-top: 12px; margin-top: 12px;
gap: 12px; gap: 12px;
} }
.yt-channel-icon { .yt-channel-icon {
width: 36px; width: 36px;
height: 36px; height: 36px;
@@ -91,11 +92,11 @@
background: #555; background: #555;
flex-shrink: 0; flex-shrink: 0;
} }
.yt-video-details { .yt-video-details {
flex: 1; flex: 1;
} }
.yt-video-title { .yt-video-title {
font-size: 16px; font-size: 16px;
font-weight: 500; font-weight: 500;
@@ -107,17 +108,18 @@
overflow: hidden; overflow: hidden;
line-height: 1.3; line-height: 1.3;
} }
.yt-video-title a { .yt-video-title a {
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
} }
.yt-channel-name, .yt-video-meta { .yt-channel-name,
.yt-video-meta {
color: var(--text-secondary); color: var(--text-secondary);
font-size: 14px; font-size: 14px;
} }
/* More button */ /* More button */
.yt-more-btn { .yt-more-btn {
background: transparent; background: transparent;
@@ -127,7 +129,7 @@
padding: 4px; padding: 4px;
border-radius: 50%; border-radius: 50%;
} }
.yt-more-dropdown { .yt-more-dropdown {
background: var(--bg-secondary); background: var(--bg-secondary);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
@@ -135,7 +137,7 @@
padding: 8px 0; padding: 8px 0;
min-width: 200px; min-width: 200px;
} }
.yt-more-dropdown-item { .yt-more-dropdown-item {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -150,30 +152,32 @@
text-align: left; text-align: left;
font-size: 14px; font-size: 14px;
} }
.yt-more-dropdown-item:hover { background: var(--border-color); } .yt-more-dropdown-item:hover {
background: var(--border-color);
}
/* Empty State */ /* Empty State */
.yt-empty { .yt-empty {
text-align: center; text-align: center;
padding: 80px 20px; padding: 80px 20px;
} }
.yt-empty-icon { .yt-empty-icon {
font-size: 80px; font-size: 80px;
color: var(--text-secondary); color: var(--text-secondary);
} }
.yt-empty-title { .yt-empty-title {
font-size: 24px; font-size: 24px;
margin: 20px 0 8px; margin: 20px 0 8px;
} }
.yt-empty-text { .yt-empty-text {
color: var(--text-secondary); color: var(--text-secondary);
margin-bottom: 20px; margin-bottom: 20px;
} }
/* Responsive */ /* Responsive */
@media (max-width: 1200px) { @media (max-width: 1200px) {
.yt-video-grid { .yt-video-grid {
@@ -181,18 +185,21 @@
gap: 20px; gap: 20px;
} }
} }
@media (max-width: 992px) { @media (max-width: 992px) {
.yt-video-grid { .yt-video-grid {
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
} }
} }
@media (max-width: 576px) { @media (max-width: 576px) {
.yt-video-grid { .yt-video-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
}
.yt-header-right .yt-icon-btn:not(:last-child) {
display: none;
} }
.yt-header-right .yt-icon-btn:not(:last-child) { display: none; }
} }
</style> </style>
@endsection @endsection
@@ -203,118 +210,129 @@
<h2>Search results for "{{ $query }}"</h2> <h2>Search results for "{{ $query }}"</h2>
<p>{{ $videos->total() }} videos found</p> <p>{{ $videos->total() }} videos found</p>
</div> </div>
@endif @endif
@if($videos->isEmpty()) @if ($videos->isEmpty())
<div class="yt-empty"> <div class="yt-empty">
<i class="bi bi-camera-video yt-empty-icon"></i> <i class="bi bi-camera-video yt-empty-icon"></i>
@isset($query) @isset($query)
<h2 class="yt-empty-title">No results found</h2> <h2 class="yt-empty-title">No results found</h2>
<p class="yt-empty-text">Try different keywords or browse all videos.</p> <p class="yt-empty-text">Try different keywords or browse all videos.</p>
@else @else
<h2 class="yt-empty-title">No videos yet</h2> <h2 class="yt-empty-title">No videos yet</h2>
<p class="yt-empty-text">Be the first to upload a video!</p> <p class="yt-empty-text">Be the first to upload a video!</p>
@endisset @endisset
@auth @auth
<a href="/videos/create" class="yt-upload-btn" style="display: inline-flex;"> <a href="/videos/create" class="yt-upload-btn" style="display: inline-flex;">
<i class="bi bi-plus-lg"></i> <i class="bi bi-plus-lg"></i>
Upload Video Upload Video
</a> </a>
@else @else
<a href="{{ route('login') }}" class="yt-upload-btn" style="display: inline-flex;"> <a href="{{ route('login') }}" class="yt-upload-btn" style="display: inline-flex;">
<i class="bi bi-box-arrow-in-right"></i> <i class="bi bi-box-arrow-in-right"></i>
Login to Upload Login to Upload
</a> </a>
@endauth @endauth
</div> </div>
@else @else
<div class="yt-video-grid"> <div class="yt-video-grid">
@foreach($videos as $video) @foreach ($videos as $video)
<x-video-card :video="$video" /> <x-video-card :video="$video" />
@endforeach @endforeach
</div> </div>
<div class="mt-4">{{ $videos->links() }}</div> @endif
@endif
@include('layouts.partials.share-modal')
@include('layouts.partials.share-modal') @endsection
@endsection
@section('scripts') @section('scripts')
<script> <script>
function playVideo(card) { function playVideo(card) {
const video = card.querySelector('video'); const video = card.querySelector('video');
if (video) { if (video) {
video.currentTime = 0; video.currentTime = 0;
video.volume = 0.10; // Set volume to 10% video.volume = 0.10; // Set volume to 10%
video.play().catch(function(e) { video.play().catch(function(e) {
// Auto-play may be blocked, ignore error // Auto-play may be blocked, ignore error
}); });
video.classList.add('active'); video.classList.add('active');
}
}
function stopVideo(card) {
const video = card.querySelector('video');
if (video) {
video.pause();
video.currentTime = 0;
video.classList.remove('active');
}
}
// Mobile touch support for hover-like behavior
document.addEventListener('touchstart', function(e) {
const card = e.target.closest('.yt-video-card');
if (card) {
// Stop any other playing videos first
document.querySelectorAll('.yt-video-card').forEach(function(otherCard) {
if (otherCard !== card) {
stopVideo(otherCard);
} }
}); }
playVideo(card);
}
}, { passive: true });
document.addEventListener('touchend', function(e) { function stopVideo(card) {
const card = e.target.closest('.yt-video-card'); const video = card.querySelector('video');
if (card) { if (video) {
stopVideo(card); video.pause();
} video.currentTime = 0;
}, { passive: true }); video.classList.remove('active');
</script> }
}
// Mobile touch support for hover-like behavior
document.addEventListener('touchstart', function(e) {
const card = e.target.closest('.yt-video-card');
if (card) {
// Stop any other playing videos first
document.querySelectorAll('.yt-video-card').forEach(function(otherCard) {
if (otherCard !== card) {
stopVideo(otherCard);
}
});
playVideo(card);
}
}, {
passive: true
});
document.addEventListener('touchend', function(e) {
const card = e.target.closest('.yt-video-card');
if (card) {
stopVideo(card);
}
}, {
passive: true
});
</script>
@endsection @endsection
<!-- Extra Mobile Styles --> <!-- Extra Mobile Styles -->
<style> <style>
@media (max-width: 400px) { @media (max-width: 400px) {
.yt-video-grid { .yt-video-grid {
gap: 12px; gap: 12px;
}
.yt-video-thumb {
border-radius: 8px;
}
.yt-video-info {
gap: 8px;
margin-top: 8px;
}
.yt-channel-icon {
width: 28px;
height: 28px;
}
.yt-video-title {
font-size: 13px;
}
.yt-channel-name, .yt-video-meta {
font-size: 11px;
}
.search-info {
padding: 12px;
}
.search-info h2 {
font-size: 16px;
}
} }
</style>
.yt-video-thumb {
border-radius: 8px;
}
.yt-video-info {
gap: 8px;
margin-top: 8px;
}
.yt-channel-icon {
width: 28px;
height: 28px;
}
.yt-video-title {
font-size: 13px;
}
.yt-channel-name,
.yt-video-meta {
font-size: 11px;
}
.search-info {
padding: 12px;
}
.search-info h2 {
font-size: 16px;
}
}
</style>
+303 -306
View File
@@ -3,351 +3,348 @@
@section('title', 'Shorts - ' . config('app.name')) @section('title', 'Shorts - ' . config('app.name'))
@section('content') @section('content')
<div class="yt-main"> <div class="yt-main">
<div class="yt-content"> <div class="yt-content">
<!-- Shorts Header --> <!-- Shorts Header -->
<div class="shorts-header"> <div class="shorts-header">
<div class="shorts-brand"> <div class="shorts-brand">
<div class="shorts-logo-icon"> <div class="shorts-logo-icon">
<i class="bi bi-play-fill"></i> <i class="bi bi-play-fill"></i>
</div>
<h1>Shorts</h1>
</div>
</div>
<!-- Shorts Grid - YouTube Style -->
<div class="shorts-container">
@forelse($videos as $video)
<div class="shorts-card">
<a href="{{ route('videos.show', $video->id) }}" class="shorts-link">
<div class="shorts-thumbnail-wrapper">
<img src="{{ $video->thumbnail_url }}" alt="{{ $video->title }}" class="shorts-thumb">
<div class="shorts-overlay">
<span class="shorts-views">
<i class="bi bi-play-fill"></i> {{ number_format($video->view_count) }}
</span>
</div>
@if($video->duration)
<span class="shorts-time">{{ gmdate('i:s', $video->duration) }}</span>
@endif
</div>
<div class="shorts-details">
<h3 class="shorts-title">{{ $video->title }}</h3>
<div class="shorts-channel-info">
@if($video->user)
<div class="shorts-channel-row">
@if($video->user->avatar_url)
<img src="{{ $video->user->avatar_url }}" class="shorts-avatar" alt="{{ $video->user->name }}">
@else
<div class="shorts-avatar-placeholder">{{ substr($video->user->name, 0, 1) }}</div>
@endif
<span class="shorts-channel-name">{{ $video->user->name }}</span>
</div>
@endif
<span class="shorts-ago">{{ $video->created_at->diffForHumans() }}</span>
</div>
</div>
</a>
</div>
@empty
<div class="shorts-empty-state">
<div class="shorts-empty-icon">
<i class="bi bi-collection-play"></i>
</div> </div>
<h2>No Shorts yet</h2> <h1>Shorts</h1>
<p>Be the first to upload a Short!</p>
@auth
<a href="{{ route('videos.create') }}" class="shorts-upload-btn">
<i class="bi bi-plus-lg"></i> Upload Short
</a>
@else
<a href="{{ route('login') }}" class="shorts-upload-btn">
<i class="bi bi-box-arrow-in-right"></i> Login to Upload
</a>
@endauth
</div> </div>
@endforelse </div>
</div>
<!-- Shorts Grid - YouTube Style -->
<div class="shorts-container">
@forelse($videos as $video)
<div class="shorts-card">
<a href="{{ route('videos.show', $video->id) }}" class="shorts-link">
<div class="shorts-thumbnail-wrapper">
<img src="{{ $video->thumbnail_url }}" alt="{{ $video->title }}" class="shorts-thumb">
<div class="shorts-overlay">
<span class="shorts-views">
<i class="bi bi-play-fill"></i> {{ number_format($video->view_count) }}
</span>
</div>
@if ($video->duration)
<span class="shorts-time">{{ gmdate('i:s', $video->duration) }}</span>
@endif
</div>
<div class="shorts-details">
<h3 class="shorts-title">{{ $video->title }}</h3>
<div class="shorts-channel-info">
@if ($video->user)
<div class="shorts-channel-row">
@if ($video->user->avatar_url)
<img src="{{ $video->user->avatar_url }}" class="shorts-avatar"
alt="{{ $video->user->name }}">
@else
<div class="shorts-avatar-placeholder">
{{ substr($video->user->name, 0, 1) }}</div>
@endif
<span class="shorts-channel-name">{{ $video->user->name }}</span>
</div>
@endif
<span class="shorts-ago">{{ $video->created_at->diffForHumans() }}</span>
</div>
</div>
</a>
</div>
@empty
<div class="shorts-empty-state">
<div class="shorts-empty-icon">
<i class="bi bi-collection-play"></i>
</div>
<h2>No Shorts yet</h2>
<p>Be the first to upload a Short!</p>
@auth
<a href="{{ route('videos.create') }}" class="shorts-upload-btn">
<i class="bi bi-plus-lg"></i> Upload Short
</a>
@else
<a href="{{ route('login') }}" class="shorts-upload-btn">
<i class="bi bi-box-arrow-in-right"></i> Login to Upload
</a>
@endauth
</div>
@endforelse
</div>
<!-- Pagination -->
@if($videos->hasPages())
<div class="shorts-pagination">
{{ $videos->links() }}
</div> </div>
@endif
</div> </div>
</div>
<style> <style>
/* Shorts Page - YouTube Shorts Style */ /* Shorts Page - YouTube Shorts Style */
.shorts-header { .shorts-header {
margin-bottom: 20px; margin-bottom: 20px;
padding: 12px 0; padding: 12px 0;
} }
.shorts-brand { .shorts-brand {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; gap: 12px;
} }
.shorts-logo-icon { .shorts-logo-icon {
width: 40px; width: 40px;
height: 40px; height: 40px;
background: linear-gradient(135deg, #ff0050, #ff6b6b); background: linear-gradient(135deg, #ff0050, #ff6b6b);
border-radius: 10px; border-radius: 10px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: white; color: white;
font-size: 20px; font-size: 20px;
} }
.shorts-brand h1 { .shorts-brand h1 {
font-size: 22px; font-size: 22px;
font-weight: 600; font-weight: 600;
color: var(--text-primary); color: var(--text-primary);
margin: 0; margin: 0;
} }
/* Shorts Grid - 4 columns on desktop */ /* Shorts Grid - 4 columns on desktop */
.shorts-container { .shorts-container {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
gap: 16px; gap: 16px;
} }
/* Shorts Card */ /* Shorts Card */
.shorts-card { .shorts-card {
background: transparent; background: transparent;
border-radius: 12px; border-radius: 12px;
overflow: hidden; overflow: hidden;
transition: transform 0.2s; transition: transform 0.2s;
} }
.shorts-card:hover { .shorts-card:hover {
transform: scale(1.02); transform: scale(1.02);
} }
.shorts-link { .shorts-link {
text-decoration: none; text-decoration: none;
color: inherit; color: inherit;
display: block; display: block;
} }
.shorts-thumbnail-wrapper { .shorts-thumbnail-wrapper {
position: relative; position: relative;
aspect-ratio: 9/16; aspect-ratio: 9/16;
border-radius: 12px; border-radius: 12px;
overflow: hidden; overflow: hidden;
background: #1a1a1a; background: #1a1a1a;
} }
.shorts-thumb { .shorts-thumb {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }
.shorts-overlay { .shorts-overlay {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
padding: 8px; padding: 8px;
background: linear-gradient(transparent, rgba(0,0,0,0.7)); background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
opacity: 0; opacity: 0;
transition: opacity 0.2s; transition: opacity 0.2s;
} }
.shorts-card:hover .shorts-overlay { .shorts-card:hover .shorts-overlay {
opacity: 1; opacity: 1;
} }
.shorts-views { .shorts-views {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 4px; gap: 4px;
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 500;
color: white; color: white;
} }
.shorts-time { .shorts-time {
position: absolute; position: absolute;
bottom: 8px; bottom: 8px;
right: 8px; right: 8px;
background: rgba(0,0,0,0.8); background: rgba(0, 0, 0, 0.8);
color: white; color: white;
padding: 2px 6px; padding: 2px 6px;
border-radius: 4px; border-radius: 4px;
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 500;
} }
.shorts-details { .shorts-details {
padding: 10px 4px; padding: 10px 4px;
} }
.shorts-title { .shorts-title {
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
color: var(--text-primary); color: var(--text-primary);
margin: 0 0 8px; margin: 0 0 8px;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
line-height: 1.3; line-height: 1.3;
} }
.shorts-channel-info { .shorts-channel-info {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 6px; gap: 6px;
} }
.shorts-channel-row { .shorts-channel-row {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
} }
.shorts-avatar, .shorts-avatar-placeholder { .shorts-avatar,
width: 24px; .shorts-avatar-placeholder {
height: 24px; width: 24px;
border-radius: 50%; height: 24px;
object-fit: cover; border-radius: 50%;
} object-fit: cover;
}
.shorts-avatar-placeholder { .shorts-avatar-placeholder {
background: #666; background: #666;
color: white; color: white;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 11px; font-size: 11px;
font-weight: 600; font-weight: 600;
} }
.shorts-channel-name { .shorts-channel-name {
font-size: 12px; font-size: 12px;
color: var(--text-secondary); color: var(--text-secondary);
} }
.shorts-ago { .shorts-ago {
font-size: 11px; font-size: 11px;
color: var(--text-secondary); color: var(--text-secondary);
opacity: 0.7; opacity: 0.7;
} }
/* Empty State */ /* Empty State */
.shorts-empty-state { .shorts-empty-state {
grid-column: 1 / -1; grid-column: 1 / -1;
text-align: center; text-align: center;
padding: 60px 20px; padding: 60px 20px;
background: var(--bg-secondary); background: var(--bg-secondary);
border-radius: 16px; border-radius: 16px;
} }
.shorts-empty-icon { .shorts-empty-icon {
width: 80px; width: 80px;
height: 80px; height: 80px;
margin: 0 auto 20px; margin: 0 auto 20px;
background: linear-gradient(135deg, #ff0050, #ff6b6b); background: linear-gradient(135deg, #ff0050, #ff6b6b);
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.shorts-empty-icon i { .shorts-empty-icon i {
font-size: 36px; font-size: 36px;
color: white; color: white;
} }
.shorts-empty-state h2 { .shorts-empty-state h2 {
font-size: 20px; font-size: 20px;
color: var(--text-primary); color: var(--text-primary);
margin: 0 0 8px; margin: 0 0 8px;
} }
.shorts-empty-state p { .shorts-empty-state p {
color: var(--text-secondary); color: var(--text-secondary);
margin: 0 0 20px; margin: 0 0 20px;
} }
.shorts-upload-btn { .shorts-upload-btn {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
padding: 10px 20px; padding: 10px 20px;
background: #ff0050; background: #ff0050;
color: white; color: white;
border-radius: 20px; border-radius: 20px;
text-decoration: none; text-decoration: none;
font-weight: 500; font-weight: 500;
font-size: 14px; font-size: 14px;
transition: background 0.2s; transition: background 0.2s;
} }
.shorts-upload-btn:hover { .shorts-upload-btn:hover {
background: #e60048; background: #e60048;
} }
/* Pagination */ /* Pagination */
.shorts-pagination { .shorts-pagination {
margin-top: 30px; margin-top: 30px;
display: flex; display: flex;
justify-content: center; justify-content: center;
} }
.shorts-pagination .pagination { .shorts-pagination .pagination {
gap: 4px; gap: 4px;
} }
.shorts-pagination .page-link { .shorts-pagination .page-link {
background: var(--bg-secondary); background: var(--bg-secondary);
border-color: var(--border-color); border-color: var(--border-color);
color: var(--text-primary); color: var(--text-primary);
padding: 6px 12px; padding: 6px 12px;
font-size: 13px; font-size: 13px;
} }
.shorts-pagination .page-item.active .page-link { .shorts-pagination .page-item.active .page-link {
background: #ff0050; background: #ff0050;
border-color: #ff0050; border-color: #ff0050;
} }
/* Responsive */ /* Responsive */
@media (max-width: 1200px) { @media (max-width: 1200px) {
.shorts-container { .shorts-container {
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
} }
} }
@media (max-width: 900px) { @media (max-width: 900px) {
.shorts-container { .shorts-container {
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 12px; gap: 12px;
} }
} }
@media (max-width: 600px) { @media (max-width: 600px) {
.shorts-container { .shorts-container {
grid-template-columns: 1fr; grid-template-columns: 1fr;
max-width: 320px; max-width: 320px;
margin: 0 auto; margin: 0 auto;
} }
.shorts-header { .shorts-header {
text-align: center; text-align: center;
} }
.shorts-brand { .shorts-brand {
justify-content: center; justify-content: center;
} }
} }
</style> </style>
@endsection @endsection
+48 -41
View File
@@ -492,7 +492,8 @@
</button> </button>
@auth @auth
<!-- Quick Watch Later Button --> <!-- Quick Watch Later Button -->
<form method="POST" action="{{ route('videos.watchLater', $video->id) }}" class="d-inline" style="display: inline;"> <form method="POST" action="{{ route('videos.watchLater', $video->id) }}" class="d-inline"
style="display: inline;">
@csrf @csrf
<button type="submit" class="yt-action-btn" title="Watch Later"> <button type="submit" class="yt-action-btn" title="Watch Later">
<i class="bi bi-clock"></i> <i class="bi bi-clock"></i>
@@ -613,7 +614,8 @@
<img src="{{ Auth::user()->avatar_url }}" class="channel-avatar" style="width: 40px; height: 40px;" <img src="{{ Auth::user()->avatar_url }}" class="channel-avatar" style="width: 40px; height: 40px;"
alt="{{ Auth::user()->name }}"> alt="{{ Auth::user()->name }}">
<div style="flex: 1;"> <div style="flex: 1;">
<textarea id="commentBody" class="form-control" placeholder="Add a comment... Use @ to mention someone" rows="3" <textarea id="commentBody" class="form-control" placeholder="Add a comment... Use @ to mention someone"
rows="3"
style="background: var(--bg-secondary); border: 1px solid var(--border-color); color: var(--text-primary); border-radius: 8px; padding: 12px; width: 100%; resize: none;"></textarea> style="background: var(--bg-secondary); border: 1px solid var(--border-color); color: var(--text-primary); border-radius: 8px; padding: 12px; width: 100%; resize: none;"></textarea>
<div style="display: flex; gap: 8px; margin-top: 8px; justify-content: flex-end;"> <div style="display: flex; gap: 8px; margin-top: 8px; justify-content: flex-end;">
<button type="button" class="yt-action-btn" <button type="button" class="yt-action-btn"
@@ -704,57 +706,62 @@
<!-- Sidebar - Up Next / Recommendations --> <!-- Sidebar - Up Next / Recommendations -->
<div class="yt-sidebar-container"> <div class="yt-sidebar-container">
@if($playlist && $playlistVideos && $playlistVideos->count() > 0) @if ($playlist && $playlistVideos && $playlistVideos->count() > 0)
<h3 style="font-size: 16px; font-weight: 500; margin-bottom: 12px;"> <h3 style="font-size: 16px; font-weight: 500; margin-bottom: 12px;">
<i class="bi bi-collection-play" style="margin-right: 8px;"></i> <i class="bi bi-collection-play" style="margin-right: 8px;"></i>
{{ $playlist->name }} {{ $playlist->name }}
<span style="font-weight: 400; color: var(--text-secondary); font-size: 14px;">({{ $playlistVideos->count() }} videos)</span> <span
style="font-weight: 400; color: var(--text-secondary); font-size: 14px;">({{ $playlistVideos->count() }}
videos)</span>
</h3> </h3>
<div class="recommended-videos-list"> <div class="recommended-videos-list">
@foreach($playlistVideos as $index => $playlistVideo) @foreach ($playlistVideos as $index => $playlistVideo)
@if($playlistVideo->id !== $video->id) @if ($playlistVideo->id !== $video->id)
<div class="sidebar-video-card{{ $playlistVideo->id === $video->id ? ' current-video' : '' }}" <div class="sidebar-video-card{{ $playlistVideo->id === $video->id ? ' current-video' : '' }}"
onclick="window.location.href='{{ route('videos.show', $playlistVideo->id) }}?playlist={{ $playlist->id }}'"> onclick="window.location.href='{{ route('videos.show', $playlistVideo->id) }}?playlist={{ $playlist->id }}'">
<div class="sidebar-thumb" style="position: relative;"> <div class="sidebar-thumb" style="position: relative;">
@if ($playlistVideo->thumbnail) @if ($playlistVideo->thumbnail)
<img src="{{ asset('storage/thumbnails/' . $playlistVideo->thumbnail) }}" <img src="{{ asset('storage/thumbnails/' . $playlistVideo->thumbnail) }}"
alt="{{ $playlistVideo->title }}"> alt="{{ $playlistVideo->title }}">
@else @else
<img src="https://picsum.photos/seed/{{ $playlistVideo->id }}/320/180" <img src="https://picsum.photos/seed/{{ $playlistVideo->id }}/320/180"
alt="{{ $playlistVideo->title }}"> alt="{{ $playlistVideo->title }}">
@endif @endif
@if ($playlistVideo->duration) @if ($playlistVideo->duration)
<span class="yt-video-duration">{{ gmdate('i:s', $playlistVideo->duration) }}</span> <span
@endif class="yt-video-duration">{{ gmdate('i:s', $playlistVideo->duration) }}</span>
@if ($playlistVideo->is_shorts) @endif
<span class="yt-shorts-badge" @if ($playlistVideo->is_shorts)
style="position: absolute; top: 8px; left: 8px; font-size: 10px; padding: 2px 6px;"> <span class="yt-shorts-badge"
<i class="bi bi-collection-play-fill"></i> SHORTS style="position: absolute; top: 8px; left: 8px; font-size: 10px; padding: 2px 6px;">
<i class="bi bi-collection-play-fill"></i> SHORTS
</span>
@endif
<!-- Playlist position indicator -->
<span
style="position: absolute; bottom: 4px; left: 4px; background: rgba(0,0,0,0.8); color: white; padding: 2px 6px; border-radius: 4px; font-size: 12px; font-weight: 500;">
{{ $index + 1 }}
</span> </span>
@endif
<!-- Playlist position indicator -->
<span style="position: absolute; bottom: 4px; left: 4px; background: rgba(0,0,0,0.8); color: white; padding: 2px 6px; border-radius: 4px; font-size: 12px; font-weight: 500;">
{{ $index + 1 }}
</span>
</div>
<div class="sidebar-info">
<div class="sidebar-title">
<i class="bi {{ match ($playlistVideo->type) {'music' => 'bi-music-note','match' => 'bi-trophy',default => 'bi-film'} }}"
style="color: #ef4444; margin-right: 4px; font-size: 12px;"></i>
{{ Str::limit($playlistVideo->title, 60) }}
</div> </div>
<div class="sidebar-meta"> <div class="sidebar-info">
<div>{{ $playlistVideo->user->name ?? 'Unknown' }}</div> <div class="sidebar-title">
<div>{{ number_format($playlistVideo->view_count) }} views • <i class="bi {{ match ($playlistVideo->type) {'music' => 'bi-music-note','match' => 'bi-trophy',default => 'bi-film'} }}"
{{ $playlistVideo->created_at->diffForHumans() }}</div> style="color: #ef4444; margin-right: 4px; font-size: 12px;"></i>
{{ Str::limit($playlistVideo->title, 60) }}
</div>
<div class="sidebar-meta">
<div>{{ $playlistVideo->user->name ?? 'Unknown' }}</div>
<div>{{ number_format($playlistVideo->view_count) }} views •
{{ $playlistVideo->created_at->diffForHumans() }}</div>
</div>
</div> </div>
</div> </div>
</div>
@endif @endif
@endforeach @endforeach
</div> </div>
@if($playlist->canEdit(Auth::user())) @if ($playlist->canEdit(Auth::user()))
<a href="{{ route('playlists.show', $playlist->id) }}" class="yt-action-btn" style="margin-top: 12px; display: inline-block;"> <a href="{{ route('playlists.show', $playlist->id) }}" class="yt-action-btn"
style="margin-top: 12px; display: inline-block;">
<i class="bi bi-pencil"></i> Edit Playlist <i class="bi bi-pencil"></i> Edit Playlist
</a> </a>
@endif @endif
+209 -108
View File
@@ -165,11 +165,11 @@
border-radius: 50%; border-radius: 50%;
} }
.autoplay-switch input:checked + .autoplay-slider { .autoplay-switch input:checked+.autoplay-slider {
background-color: var(--brand-red); background-color: var(--brand-red);
} }
.autoplay-switch input:checked + .autoplay-slider:before { .autoplay-switch input:checked+.autoplay-slider:before {
transform: translateX(16px); transform: translateX(16px);
} }
@@ -234,6 +234,47 @@
background: var(--border-color); background: var(--border-color);
} }
.action-btn,
.comments-section .action-btn {
border: none;
border-radius: 8px;
padding: 8px 14px;
font-size: 0.82rem;
font-weight: 500;
cursor: pointer;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
color: var(--text-primary);
display: flex;
align-items: center;
gap: 6px;
transition: all 0.2s ease;
}
.action-btn:hover,
.comments-section .action-btn:hover {
background: var(--border-color);
transform: translateY(-1px);
}
.action-btn:active,
.comments-section .action-btn:active {
transform: translateY(0);
}
.action-btn svg,
.action-btn i,
.comments-section .action-btn svg,
.comments-section .action-btn i {
flex-shrink: 0;
}
.action-btn.comment-btn {
background: var(--brand-red);
color: white;
border-color: var(--brand-red);
}
.yt-action-btn.liked { .yt-action-btn.liked {
color: var(--brand-red); color: var(--brand-red);
} }
@@ -449,44 +490,44 @@
</video> </video>
<!-- Playlist Navigation Controls (only show when in playlist) --> <!-- Playlist Navigation Controls (only show when in playlist) -->
@if($nextVideo || $previousVideo) @if ($nextVideo || $previousVideo)
<div class="playlist-controls" id="playlistControls"> <div class="playlist-controls" id="playlistControls">
<!-- Previous Video --> <!-- Previous Video -->
@if($previousVideo) @if ($previousVideo)
<button class="playlist-nav-btn" onclick="goToPreviousVideo()" title="Previous video"> <button class="playlist-nav-btn" onclick="goToPreviousVideo()" title="Previous video">
<i class="bi bi-skip-backward-fill"></i> <i class="bi bi-skip-backward-fill"></i>
</button> </button>
<span class="playlist-nav-label">{{ Str::limit($previousVideo->title, 20) }}</span> <span class="playlist-nav-label">{{ Str::limit($previousVideo->title, 20) }}</span>
@else @else
<button class="playlist-nav-btn" disabled title="No previous video"> <button class="playlist-nav-btn" disabled title="No previous video">
<i class="bi bi-skip-backward-fill"></i> <i class="bi bi-skip-backward-fill"></i>
</button> </button>
@endif @endif
<!-- Autoplay Toggle --> <!-- Autoplay Toggle -->
<div class="autoplay-toggle"> <div class="autoplay-toggle">
<label for="autoplayToggle">Autoplay</label> <label for="autoplayToggle">Autoplay</label>
<label class="autoplay-switch"> <label class="autoplay-switch">
<input type="checkbox" id="autoplayToggle" checked> <input type="checkbox" id="autoplayToggle" checked>
<span class="autoplay-slider"></span> <span class="autoplay-slider"></span>
</label> </label>
</div>
<!-- Next Video -->
@if ($nextVideo)
<span class="playlist-nav-label">{{ Str::limit($nextVideo->title, 20) }}</span>
<button class="playlist-nav-btn" onclick="goToNextVideo()" title="Next video">
<i class="bi bi-skip-forward-fill"></i>
</button>
@else
<button class="playlist-nav-btn" disabled title="No next video">
<i class="bi bi-skip-forward-fill"></i>
</button>
@endif
</div>
<div class="keyboard-hint">
<i class="bi bi-arrow-left"></i> <i class="bi bi-arrow-right"></i>
</div> </div>
<!-- Next Video -->
@if($nextVideo)
<span class="playlist-nav-label">{{ Str::limit($nextVideo->title, 20) }}</span>
<button class="playlist-nav-btn" onclick="goToNextVideo()" title="Next video">
<i class="bi bi-skip-forward-fill"></i>
</button>
@else
<button class="playlist-nav-btn" disabled title="No next video">
<i class="bi bi-skip-forward-fill"></i>
</button>
@endif
</div>
<div class="keyboard-hint">
<i class="bi bi-arrow-left"></i> <i class="bi bi-arrow-right"></i>
</div>
@endif @endif
</div> </div>
@@ -530,8 +571,9 @@
@if (Auth::id() !== $video->user_id) @if (Auth::id() !== $video->user_id)
<button class="subscribe-btn">Subscribe</button> <button class="subscribe-btn">Subscribe</button>
@else @else
<button class="yt-action-btn" onclick="openEditVideoModal({{ $video->id }})"> <button class="action-btn" onclick="openEditVideoModal({{ $video->id }})">
<i class="bi bi-pencil"></i> Edit <i class="bi bi-pencil"></i>
<span>Edit</span>
</button> </button>
@endif @endif
@else @else
@@ -543,7 +585,7 @@
action="{{ $video->isLikedBy(Auth::user()) ? route('videos.unlike', $video->id) : route('videos.like', $video->id) }}" action="{{ $video->isLikedBy(Auth::user()) ? route('videos.unlike', $video->id) : route('videos.like', $video->id) }}"
class="d-inline"> class="d-inline">
@csrf @csrf
<button type="submit" class="yt-action-btn {{ $video->isLikedBy(Auth::user()) ? 'liked' : '' }}"> <button type="submit" class="action-btn {{ $video->isLikedBy(Auth::user()) ? 'liked' : '' }}">
<i <i
class="bi {{ $video->isLikedBy(Auth::user()) ? 'bi-hand-thumbs-up-fill' : 'bi-hand-thumbs-up' }}"></i> class="bi {{ $video->isLikedBy(Auth::user()) ? 'bi-hand-thumbs-up-fill' : 'bi-hand-thumbs-up' }}"></i>
{{ $video->like_count > 0 ? number_format($video->like_count) : 'Like' }} {{ $video->like_count > 0 ? number_format($video->like_count) : 'Like' }}
@@ -556,11 +598,22 @@
@endauth @endauth
@if ($video->isShareable()) @if ($video->isShareable())
<button class="yt-action-btn" <button class="action-btn"
onclick="openShareModal('{{ $video->share_url }}', '{{ addslashes($video->title) }}')"> onclick="openShareModal('{{ $video->share_url }}', '{{ addslashes($video->title) }}')">
<i class="bi bi-share"></i> Share <i class="bi bi-share"></i> Share
</button> </button>
@endif @endif
<!-- Save to Playlist Button -->
<button class="action-btn" onclick="openAddToPlaylistModal({{ $video->id }})">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"></path>
</svg>
<span>Save</span>
</button>
</div> </div>
</div> </div>
@@ -646,15 +699,20 @@
<div class="comment-form" style="display: flex; gap: 12px; margin-bottom: 24px;"> <div class="comment-form" style="display: flex; gap: 12px; margin-bottom: 24px;">
<img src="{{ Auth::user()->avatar_url }}" class="channel-avatar" style="width: 40px; height: 40px;" <img src="{{ Auth::user()->avatar_url }}" class="channel-avatar" style="width: 40px; height: 40px;"
alt="{{ Auth::user()->name }}"> alt="{{ Auth::user()->name }}">
<div style="flex: 1;"> <div style="flex: 1; display: flex; align-items: center; gap: 8px;">
<textarea id="commentBody" class="form-control" placeholder="Add a comment... Use @ to mention someone" rows="3" <textarea id="commentBody" class="form-control" placeholder="Add a comment... Use @ to mention someone"
style="background: var(--bg-secondary); border: 1px solid var(--border-color); color: var(--text-primary); border-radius: 8px; padding: 12px; width: 100%; resize: none;"></textarea> rows="1"
<div style="display: flex; gap: 8px; margin-top: 8px; justify-content: flex-end;"> style="background: transparent; border: none; border-bottom: 2px solid var(--border-color); color: var(--text-primary); border-radius: 0; padding: 12px 0 8px 0; flex: 1; margin: 0; height: 40px; font-size: 14px; outline: none; overflow: hidden;"></textarea>
<button type="button" class="yt-action-btn" <button type="button" class="action-btn"
onclick="document.getElementById('commentBody').value = ''">Cancel</button> onclick="document.getElementById('commentBody').value = ''" style="flex-shrink: 0;">
<button type="button" class="yt-action-btn" style="background: var(--brand-red); color: white;" <i class="bi bi-x-lg"></i>
onclick="submitComment({{ $video->id }})">Comment</button> <span>Cancel</span>
</div> </button>
<button type="button" class="action-btn comment-btn"
onclick="submitComment({{ $video->id }})" style="flex-shrink: 0;">
<i class="bi bi-chat-dots"></i>
<span>Comment</span>
</button>
</div> </div>
</div> </div>
@else @else
@@ -690,29 +748,69 @@
}) })
}) })
.then(response => response.json()) .then(response => response.json())
.catch(error => {
console.error('Error:', error);
alert('Failed to post comment: ' + error);
})
.then(data => { .then(data => {
if (data.success) { if (data && data.success) {
document.getElementById('commentBody').value = ''; document.getElementById('commentBody').value = '';
location.reload(); addCommentToList(data.comment);
} else {
alert('Failed to post comment');
} }
}); });
} }
function deleteComment(commentId) { function addCommentToList(comment) {
if (!confirm('Are you sure you want to delete this comment?')) return; const commentsList = document.getElementById('commentsList');
const commentHtml = `
<div class="comment-item" style="display: flex; gap: 12px; margin-bottom: 16px;" id="comment-${comment.id}">
<img src="${comment.user.avatar_url}" class="channel-avatar" style="width: 36px; height: 36px; flex-shrink: 0;" alt="${comment.user.name}">
<div style="flex: 1;">
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px;">
<span style="font-weight: 600; font-size: 14px;">${comment.user.name}</span>
<span style="color: var(--text-secondary); font-size: 12px;">just now</span>
</div>
<div class="comment-body" style="font-size: 14px; line-height: 1.5; word-wrap: break-word;">
${comment.body.replace(/@(\\w+)/g, '<span style="color: #3ea6ff; font-weight: 500;">@$1</span>')}
</div>
<div style="display: flex; gap: 12px; margin-top: 8px;">
<button onclick="toggleReplyForm(${comment.id})" style="background: none; border: none; color: var(--text-secondary); font-size: 12px; font-weight: 600; cursor: pointer; padding: 0;">
Reply
</button>
</div>
</div>
</div>
`;
commentsList.insertAdjacentHTML('afterbegin', commentHtml);
const commentCount = document.querySelector('h3 span');
if (commentCount) {
const count = parseInt(commentCount.textContent.match(/\\((\\d+)\\)/)?.[2] || 0) + 1;
commentCount.textContent = `(${count})`;
}
}
fetch(`/comments/${commentId}`, { function deleteComment(commentId) {
method: 'DELETE', if (confirm('Are you sure you want to delete this comment?')) {
headers: { fetch(`/comments/${commentId}`, {
'X-CSRF-TOKEN': '{{ csrf_token() }}' method: 'DELETE',
} headers: {
}) 'X-CSRF-TOKEN': '{{ csrf_token() }}'
.then(response => response.json()) }
.then(data => { })
if (data.success) { .then(response => response.json())
location.reload(); .then(data => {
} if (data.success) {
}); document.getElementById('comment-' + commentId).remove();
const commentCount = document.querySelector('h3 span');
if (commentCount) {
const count = parseInt(commentCount.textContent.match(/\\((\\d+)\\)/)?.[2] || 0) - 1;
commentCount.textContent = `(${count})`;
}
}
});
}
} }
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
@@ -752,57 +850,62 @@
<!-- Sidebar - Up Next / Recommendations --> <!-- Sidebar - Up Next / Recommendations -->
<div class="yt-sidebar-container"> <div class="yt-sidebar-container">
@if($playlist && $playlistVideos && $playlistVideos->count() > 0) @if ($playlist && $playlistVideos && $playlistVideos->count() > 0)
<h3 style="font-size: 16px; font-weight: 500; margin-bottom: 12px;"> <h3 style="font-size: 16px; font-weight: 500; margin-bottom: 12px;">
<i class="bi bi-collection-play" style="margin-right: 8px;"></i> <i class="bi bi-collection-play" style="margin-right: 8px;"></i>
{{ $playlist->name }} {{ $playlist->name }}
<span style="font-weight: 400; color: var(--text-secondary); font-size: 14px;">({{ $playlistVideos->count() }} videos)</span> <span
style="font-weight: 400; color: var(--text-secondary); font-size: 14px;">({{ $playlistVideos->count() }}
videos)</span>
</h3> </h3>
<div class="recommended-videos-list"> <div class="recommended-videos-list">
@foreach($playlistVideos as $index => $playlistVideo) @foreach ($playlistVideos as $index => $playlistVideo)
@if($playlistVideo->id !== $video->id) @if ($playlistVideo->id !== $video->id)
<div class="sidebar-video-card{{ $playlistVideo->id === $video->id ? ' current-video' : '' }}" <div class="sidebar-video-card{{ $playlistVideo->id === $video->id ? ' current-video' : '' }}"
onclick="window.location.href='{{ route('videos.show', $playlistVideo->id) }}?playlist={{ $playlist->id }}'"> onclick="window.location.href='{{ route('videos.show', $playlistVideo->id) }}?playlist={{ $playlist->id }}'">
<div class="sidebar-thumb" style="position: relative;"> <div class="sidebar-thumb" style="position: relative;">
@if ($playlistVideo->thumbnail) @if ($playlistVideo->thumbnail)
<img src="{{ asset('storage/thumbnails/' . $playlistVideo->thumbnail) }}" <img src="{{ asset('storage/thumbnails/' . $playlistVideo->thumbnail) }}"
alt="{{ $playlistVideo->title }}"> alt="{{ $playlistVideo->title }}">
@else @else
<img src="https://picsum.photos/seed/{{ $playlistVideo->id }}/320/180" <img src="https://picsum.photos/seed/{{ $playlistVideo->id }}/320/180"
alt="{{ $playlistVideo->title }}"> alt="{{ $playlistVideo->title }}">
@endif @endif
@if ($playlistVideo->duration) @if ($playlistVideo->duration)
<span class="yt-video-duration">{{ gmdate('i:s', $playlistVideo->duration) }}</span> <span
@endif class="yt-video-duration">{{ gmdate('i:s', $playlistVideo->duration) }}</span>
@if ($playlistVideo->is_shorts) @endif
<span class="yt-shorts-badge" @if ($playlistVideo->is_shorts)
style="position: absolute; top: 8px; left: 8px; font-size: 10px; padding: 2px 6px;"> <span class="yt-shorts-badge"
<i class="bi bi-collection-play-fill"></i> SHORTS style="position: absolute; top: 8px; left: 8px; font-size: 10px; padding: 2px 6px;">
<i class="bi bi-collection-play-fill"></i> SHORTS
</span>
@endif
<!-- Playlist position indicator -->
<span
style="position: absolute; bottom: 4px; left: 4px; background: rgba(0,0,0,0.8); color: white; padding: 2px 6px; border-radius: 4px; font-size: 12px; font-weight: 500;">
{{ $index + 1 }}
</span> </span>
@endif
<!-- Playlist position indicator -->
<span style="position: absolute; bottom: 4px; left: 4px; background: rgba(0,0,0,0.8); color: white; padding: 2px 6px; border-radius: 4px; font-size: 12px; font-weight: 500;">
{{ $index + 1 }}
</span>
</div>
<div class="sidebar-info">
<div class="sidebar-title">
<i class="bi {{ match ($playlistVideo->type) {'music' => 'bi-music-note','match' => 'bi-trophy',default => 'bi-film'} }}"
style="color: #ef4444; margin-right: 4px; font-size: 12px;"></i>
{{ Str::limit($playlistVideo->title, 60) }}
</div> </div>
<div class="sidebar-meta"> <div class="sidebar-info">
<div>{{ $playlistVideo->user->name ?? 'Unknown' }}</div> <div class="sidebar-title">
<div>{{ number_format($playlistVideo->view_count) }} views • <i class="bi {{ match ($playlistVideo->type) {'music' => 'bi-music-note','match' => 'bi-trophy',default => 'bi-film'} }}"
{{ $playlistVideo->created_at->diffForHumans() }}</div> style="color: #ef4444; margin-right: 4px; font-size: 12px;"></i>
{{ Str::limit($playlistVideo->title, 60) }}
</div>
<div class="sidebar-meta">
<div>{{ $playlistVideo->user->name ?? 'Unknown' }}</div>
<div>{{ number_format($playlistVideo->view_count) }} views •
{{ $playlistVideo->created_at->diffForHumans() }}</div>
</div>
</div> </div>
</div> </div>
</div>
@endif @endif
@endforeach @endforeach
</div> </div>
@if($playlist->canEdit(Auth::user())) @if ($playlist->canEdit(Auth::user()))
<a href="{{ route('playlists.show', $playlist->id) }}" class="yt-action-btn" style="margin-top: 12px; display: inline-block;"> <a href="{{ route('playlists.show', $playlist->id) }}" class="yt-action-btn"
style="margin-top: 12px; display: inline-block;">
<i class="bi bi-pencil"></i> Edit Playlist <i class="bi bi-pencil"></i> Edit Playlist
</a> </a>
@endif @endif
@@ -938,11 +1041,9 @@
// Keyboard navigation // Keyboard navigation
document.addEventListener('keydown', function(e) { document.addEventListener('keydown', function(e) {
// Only handle if not typing in an input
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
return; return;
} }
if (e.key === 'ArrowLeft') { if (e.key === 'ArrowLeft') {
e.preventDefault(); e.preventDefault();
goToPreviousVideo(); goToPreviousVideo();
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,45 @@
<script>
function showDeleteCommentModal(commentId, commentBody) {
const modalId = 'confirmDeleteModal' + commentId;
const modalElement = document.getElementById(modalId);
if (modalElement) {
const modal = new bootstrap.Modal(modalElement);
modal.show();
window.currentDeleteCommentId = commentId;
}
}
function deleteCommentWithModal(commentId) {
fetch(`/comments/${commentId}`, {
method: 'DELETE',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
'Accept': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
document.getElementById('comment-' + commentId).remove();
const commentCount = document.querySelector('h3 span');
if (commentCount) {
const count = parseInt(commentCount.textContent.match(/\\((\\d+)\\)/)?.[2] || 0) - 1;
commentCount.textContent = `(${Math.max(0, count)})`;
}
// Close modal
const modalId = 'confirmDeleteModal' + commentId;
const modalElement = document.getElementById(modalId);
if (modalElement) {
const modal = bootstrap.Modal.getInstance(modalElement);
modal.hide();
}
} else {
alert('Failed to delete comment');
}
})
.catch(error => {
console.error('Error:', error);
alert('Failed to delete comment');
});
}
</script>