all is working great
This commit is contained in:
parent
69f5df163a
commit
f850f40f78
@ -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'));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
</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 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>
|
||||||
@ -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>
|
||||||
@ -70,7 +71,7 @@
|
|||||||
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;
|
||||||
@ -113,7 +114,8 @@
|
|||||||
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;
|
||||||
}
|
}
|
||||||
@ -151,7 +153,9 @@
|
|||||||
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 {
|
||||||
@ -192,7 +196,10 @@
|
|||||||
.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
|
||||||
@ -205,7 +212,7 @@
|
|||||||
</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)
|
||||||
@ -229,20 +236,19 @@
|
|||||||
</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;
|
||||||
@ -252,19 +258,19 @@ function playVideo(card) {
|
|||||||
});
|
});
|
||||||
video.classList.add('active');
|
video.classList.add('active');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopVideo(card) {
|
function stopVideo(card) {
|
||||||
const video = card.querySelector('video');
|
const video = card.querySelector('video');
|
||||||
if (video) {
|
if (video) {
|
||||||
video.pause();
|
video.pause();
|
||||||
video.currentTime = 0;
|
video.currentTime = 0;
|
||||||
video.classList.remove('active');
|
video.classList.remove('active');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mobile touch support for hover-like behavior
|
// Mobile touch support for hover-like behavior
|
||||||
document.addEventListener('touchstart', function(e) {
|
document.addEventListener('touchstart', function(e) {
|
||||||
const card = e.target.closest('.yt-video-card');
|
const card = e.target.closest('.yt-video-card');
|
||||||
if (card) {
|
if (card) {
|
||||||
// Stop any other playing videos first
|
// Stop any other playing videos first
|
||||||
@ -275,46 +281,58 @@ document.addEventListener('touchstart', function(e) {
|
|||||||
});
|
});
|
||||||
playVideo(card);
|
playVideo(card);
|
||||||
}
|
}
|
||||||
}, { passive: true });
|
}, {
|
||||||
|
passive: true
|
||||||
|
});
|
||||||
|
|
||||||
document.addEventListener('touchend', function(e) {
|
document.addEventListener('touchend', function(e) {
|
||||||
const card = e.target.closest('.yt-video-card');
|
const card = e.target.closest('.yt-video-card');
|
||||||
if (card) {
|
if (card) {
|
||||||
stopVideo(card);
|
stopVideo(card);
|
||||||
}
|
}
|
||||||
}, { passive: true });
|
}, {
|
||||||
</script>
|
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 {
|
.yt-video-thumb {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.yt-video-info {
|
.yt-video-info {
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.yt-channel-icon {
|
.yt-channel-icon {
|
||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.yt-video-title {
|
.yt-video-title {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
.yt-channel-name, .yt-video-meta {
|
|
||||||
|
.yt-channel-name,
|
||||||
|
.yt-video-meta {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-info {
|
.search-info {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-info h2 {
|
.search-info h2 {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
@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">
|
||||||
@ -27,19 +27,21 @@
|
|||||||
<i class="bi bi-play-fill"></i> {{ number_format($video->view_count) }}
|
<i class="bi bi-play-fill"></i> {{ number_format($video->view_count) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@if($video->duration)
|
@if ($video->duration)
|
||||||
<span class="shorts-time">{{ gmdate('i:s', $video->duration) }}</span>
|
<span class="shorts-time">{{ gmdate('i:s', $video->duration) }}</span>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="shorts-details">
|
<div class="shorts-details">
|
||||||
<h3 class="shorts-title">{{ $video->title }}</h3>
|
<h3 class="shorts-title">{{ $video->title }}</h3>
|
||||||
<div class="shorts-channel-info">
|
<div class="shorts-channel-info">
|
||||||
@if($video->user)
|
@if ($video->user)
|
||||||
<div class="shorts-channel-row">
|
<div class="shorts-channel-row">
|
||||||
@if($video->user->avatar_url)
|
@if ($video->user->avatar_url)
|
||||||
<img src="{{ $video->user->avatar_url }}" class="shorts-avatar" alt="{{ $video->user->name }}">
|
<img src="{{ $video->user->avatar_url }}" class="shorts-avatar"
|
||||||
|
alt="{{ $video->user->name }}">
|
||||||
@else
|
@else
|
||||||
<div class="shorts-avatar-placeholder">{{ substr($video->user->name, 0, 1) }}</div>
|
<div class="shorts-avatar-placeholder">
|
||||||
|
{{ substr($video->user->name, 0, 1) }}</div>
|
||||||
@endif
|
@endif
|
||||||
<span class="shorts-channel-name">{{ $video->user->name }}</span>
|
<span class="shorts-channel-name">{{ $video->user->name }}</span>
|
||||||
</div>
|
</div>
|
||||||
@ -69,29 +71,24 @@
|
|||||||
@endforelse
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Pagination -->
|
|
||||||
@if($videos->hasPages())
|
|
||||||
<div class="shorts-pagination">
|
|
||||||
{{ $videos->links() }}
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
</div>
|
||||||
/* Shorts Page - YouTube Shorts Style */
|
</div>
|
||||||
.shorts-header {
|
|
||||||
|
<style>
|
||||||
|
/* Shorts Page - YouTube Shorts Style */
|
||||||
|
.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);
|
||||||
@ -101,95 +98,95 @@
|
|||||||
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);
|
||||||
@ -199,28 +196,29 @@
|
|||||||
-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,
|
||||||
|
.shorts-avatar-placeholder {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shorts-avatar-placeholder {
|
.shorts-avatar-placeholder {
|
||||||
background: #666;
|
background: #666;
|
||||||
color: white;
|
color: white;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -228,29 +226,29 @@
|
|||||||
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;
|
||||||
@ -259,25 +257,25 @@
|
|||||||
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;
|
||||||
@ -289,51 +287,51 @@
|
|||||||
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;
|
||||||
@ -347,7 +345,6 @@
|
|||||||
.shorts-brand {
|
.shorts-brand {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
|||||||
@ -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,15 +706,17 @@
|
|||||||
|
|
||||||
<!-- 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;">
|
||||||
@ -724,7 +728,8 @@
|
|||||||
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
|
||||||
|
class="yt-video-duration">{{ gmdate('i:s', $playlistVideo->duration) }}</span>
|
||||||
@endif
|
@endif
|
||||||
@if ($playlistVideo->is_shorts)
|
@if ($playlistVideo->is_shorts)
|
||||||
<span class="yt-shorts-badge"
|
<span class="yt-shorts-badge"
|
||||||
@ -733,7 +738,8 @@
|
|||||||
</span>
|
</span>
|
||||||
@endif
|
@endif
|
||||||
<!-- Playlist position indicator -->
|
<!-- 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;">
|
<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 }}
|
{{ $index + 1 }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -753,8 +759,9 @@
|
|||||||
@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
|
||||||
|
|||||||
@ -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,10 +490,10 @@
|
|||||||
</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>
|
||||||
@ -473,7 +514,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Next Video -->
|
<!-- Next Video -->
|
||||||
@if($nextVideo)
|
@if ($nextVideo)
|
||||||
<span class="playlist-nav-label">{{ Str::limit($nextVideo->title, 20) }}</span>
|
<span class="playlist-nav-label">{{ Str::limit($nextVideo->title, 20) }}</span>
|
||||||
<button class="playlist-nav-btn" onclick="goToNextVideo()" title="Next video">
|
<button class="playlist-nav-btn" onclick="goToNextVideo()" title="Next video">
|
||||||
<i class="bi bi-skip-forward-fill"></i>
|
<i class="bi bi-skip-forward-fill"></i>
|
||||||
@ -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,17 +748,51 @@
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
.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})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteComment(commentId) {
|
||||||
|
if (confirm('Are you sure you want to delete this comment?')) {
|
||||||
fetch(`/comments/${commentId}`, {
|
fetch(`/comments/${commentId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
@ -710,10 +802,16 @@
|
|||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
location.reload();
|
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() {
|
||||||
const commentTexts = document.querySelectorAll('.comment-body');
|
const commentTexts = document.querySelectorAll('.comment-body');
|
||||||
@ -752,15 +850,17 @@
|
|||||||
|
|
||||||
<!-- 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;">
|
||||||
@ -772,7 +872,8 @@
|
|||||||
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
|
||||||
|
class="yt-video-duration">{{ gmdate('i:s', $playlistVideo->duration) }}</span>
|
||||||
@endif
|
@endif
|
||||||
@if ($playlistVideo->is_shorts)
|
@if ($playlistVideo->is_shorts)
|
||||||
<span class="yt-shorts-badge"
|
<span class="yt-shorts-badge"
|
||||||
@ -781,7 +882,8 @@
|
|||||||
</span>
|
</span>
|
||||||
@endif
|
@endif
|
||||||
<!-- Playlist position indicator -->
|
<!-- 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;">
|
<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 }}
|
{{ $index + 1 }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -801,8 +903,9 @@
|
|||||||
@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
@ -5,7 +5,10 @@
|
|||||||
@section('extra_styles')
|
@section('extra_styles')
|
||||||
<style>
|
<style>
|
||||||
/* Video Section */
|
/* Video Section */
|
||||||
.yt-video-section { flex: 1; min-width: 0; }
|
.yt-video-section {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Video Player */
|
/* Video Player */
|
||||||
.video-container {
|
.video-container {
|
||||||
@ -28,11 +31,26 @@
|
|||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-container.portrait { aspect-ratio: 9/16; max-width: 50vh; }
|
.video-container.portrait {
|
||||||
.video-container.square { aspect-ratio: 1/1; max-width: 70vh; }
|
aspect-ratio: 9/16;
|
||||||
.video-container.ultrawide { aspect-ratio: 21/9; max-width: 100%; }
|
max-width: 50vh;
|
||||||
|
}
|
||||||
|
|
||||||
.video-container video { width: 100%; height: 100%; object-fit: contain; }
|
.video-container.square {
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
max-width: 70vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container.ultrawide {
|
||||||
|
aspect-ratio: 21/9;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-container video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
/* Playlist Navigation Controls */
|
/* Playlist Navigation Controls */
|
||||||
.playlist-controls {
|
.playlist-controls {
|
||||||
@ -52,7 +70,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.video-container:hover .playlist-controls,
|
.video-container:hover .playlist-controls,
|
||||||
.playlist-controls.visible { opacity: 1; }
|
.playlist-controls.visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.playlist-nav-btn {
|
.playlist-nav-btn {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@ -67,10 +87,22 @@
|
|||||||
transition: background 0.2s ease;
|
transition: background 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.playlist-nav-btn:hover { background: rgba(255, 255, 255, 0.2); }
|
.playlist-nav-btn:hover {
|
||||||
.playlist-nav-btn:disabled { opacity: 0.3; cursor: not-allowed; }
|
background: rgba(255, 255, 255, 0.2);
|
||||||
.playlist-nav-btn:disabled:hover { background: transparent; }
|
}
|
||||||
.playlist-nav-btn i { font-size: 20px; }
|
|
||||||
|
.playlist-nav-btn:disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlist-nav-btn:disabled:hover {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlist-nav-btn i {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.playlist-nav-label {
|
.playlist-nav-label {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@ -97,13 +129,25 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autoplay-switch { position: relative; width: 36px; height: 20px; }
|
.autoplay-switch {
|
||||||
.autoplay-switch input { opacity: 0; width: 0; height: 0; }
|
position: relative;
|
||||||
|
width: 36px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoplay-switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.autoplay-slider {
|
.autoplay-slider {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
top: 0; left: 0; right: 0; bottom: 0;
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
background-color: #666;
|
background-color: #666;
|
||||||
transition: 0.3s;
|
transition: 0.3s;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
@ -121,8 +165,13 @@
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autoplay-switch input:checked + .autoplay-slider { background-color: var(--brand-red); }
|
.autoplay-switch input:checked+.autoplay-slider {
|
||||||
.autoplay-switch input:checked + .autoplay-slider:before { transform: translateX(16px); }
|
background-color: var(--brand-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.autoplay-switch input:checked+.autoplay-slider:before {
|
||||||
|
transform: translateX(16px);
|
||||||
|
}
|
||||||
|
|
||||||
/* Keyboard hint */
|
/* Keyboard hint */
|
||||||
.keyboard-hint {
|
.keyboard-hint {
|
||||||
@ -154,7 +203,12 @@
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-stats-left { display: flex; align-items: center; gap: 16px; color: var(--text-secondary); }
|
.video-stats-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
.video-actions {
|
.video-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -176,9 +230,54 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.yt-action-btn:hover { background: var(--border-color); }
|
.yt-action-btn:hover {
|
||||||
|
background: var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
.yt-action-btn.liked { color: var(--brand-red); }
|
.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 {
|
||||||
|
color: var(--brand-red);
|
||||||
|
}
|
||||||
|
|
||||||
/* Channel Row */
|
/* Channel Row */
|
||||||
.channel-row {
|
.channel-row {
|
||||||
@ -195,7 +294,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.channel-avatar {
|
.channel-avatar {
|
||||||
width: 48px; height: 48px; border-radius: 50%; background: #555;
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-name {
|
.channel-name {
|
||||||
@ -256,9 +358,16 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-thumb img { width: 100%; height: 100%; object-fit: cover; }
|
.sidebar-thumb img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-info { flex: 1; min-width: 0; }
|
.sidebar-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.sidebar-title {
|
.sidebar-title {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@ -270,26 +379,45 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-meta { font-size: 12px; color: var(--text-secondary); }
|
.sidebar-meta {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
/* Responsive */
|
/* Responsive */
|
||||||
@media (max-width: 1300px) {
|
@media (max-width: 1300px) {
|
||||||
.yt-sidebar-container { width: 300px; }
|
.yt-sidebar-container {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 991px) {
|
@media (max-width: 991px) {
|
||||||
.yt-main { margin-left: 0; flex-direction: column; }
|
.yt-main {
|
||||||
.yt-sidebar-container { width: 100%; }
|
margin-left: 0;
|
||||||
.sidebar-video-card { flex-direction: column; }
|
flex-direction: column;
|
||||||
.sidebar-thumb { width: 100%; }
|
}
|
||||||
|
|
||||||
|
.yt-sidebar-container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-video-card {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-thumb {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.video-layout-container {
|
.video-layout-container {
|
||||||
flex-direction: column !important;
|
flex-direction: column !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.yt-video-section {
|
.yt-video-section {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
flex: none !important;
|
flex: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.yt-sidebar-container {
|
.yt-sidebar-container {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
@ -297,32 +425,49 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 576px) {
|
||||||
.video-stats-row { flex-direction: column; align-items: flex-start; }
|
.video-stats-row {
|
||||||
.video-actions { width: 100%; overflow-x: auto; justify-content: flex-start; }
|
flex-direction: column;
|
||||||
.yt-main { padding: 12px !important; }
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-actions {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yt-main {
|
||||||
|
padding: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
.video-container {
|
.video-container {
|
||||||
max-height: 50vh !important;
|
max-height: 50vh !important;
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-container video {
|
.video-container video {
|
||||||
object-fit: contain !important;
|
object-fit: contain !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-title {
|
.video-title {
|
||||||
font-size: 16px !important;
|
font-size: 16px !important;
|
||||||
margin: 12px 0 6px !important;
|
margin: 12px 0 6px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-row {
|
.channel-row {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start !important;
|
align-items: flex-start !important;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-info {
|
.channel-info {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subscribe-btn {
|
.subscribe-btn {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-description {
|
.video-description {
|
||||||
padding: 12px !important;
|
padding: 12px !important;
|
||||||
}
|
}
|
||||||
@ -338,16 +483,17 @@
|
|||||||
<!-- Video Section -->
|
<!-- Video Section -->
|
||||||
<div class="yt-video-section">
|
<div class="yt-video-section">
|
||||||
<!-- Video Player -->
|
<!-- Video Player -->
|
||||||
<div class="video-container @if($video->orientation === 'portrait') portrait @elseif($video->orientation === 'square') square @elseif($video->orientation === 'ultrawide') ultrawide @endif" id="videoContainer">
|
<div class="video-container @if ($video->orientation === 'portrait') portrait @elseif($video->orientation === 'square') square @elseif($video->orientation === 'ultrawide') ultrawide @endif"
|
||||||
|
id="videoContainer">
|
||||||
<video id="videoPlayer" controls playsinline preload="metadata" autoplay>
|
<video id="videoPlayer" controls playsinline preload="metadata" autoplay>
|
||||||
<source src="{{ route('videos.stream', $video->id) }}" type="video/mp4">
|
<source src="{{ route('videos.stream', $video->id) }}" type="video/mp4">
|
||||||
</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>
|
||||||
@ -368,7 +514,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Next Video -->
|
<!-- Next Video -->
|
||||||
@if($nextVideo)
|
@if ($nextVideo)
|
||||||
<span class="playlist-nav-label">{{ Str::limit($nextVideo->title, 20) }}</span>
|
<span class="playlist-nav-label">{{ Str::limit($nextVideo->title, 20) }}</span>
|
||||||
<button class="playlist-nav-btn" onclick="goToNextVideo()" title="Next video">
|
<button class="playlist-nav-btn" onclick="goToNextVideo()" title="Next video">
|
||||||
<i class="bi bi-skip-forward-fill"></i>
|
<i class="bi bi-skip-forward-fill"></i>
|
||||||
@ -401,28 +547,33 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Channel Row - All in one line -->
|
<!-- Channel Row - All in one line -->
|
||||||
<div class="channel-row" style="display: flex; align-items: center; justify-content: space-between; padding: 12px 0; flex-wrap: wrap; gap: 16px;">
|
<div class="channel-row"
|
||||||
|
style="display: flex; align-items: center; justify-content: space-between; padding: 12px 0; flex-wrap: wrap; gap: 16px;">
|
||||||
<div style="display: flex; align-items: center; gap: 12px;">
|
<div style="display: flex; align-items: center; gap: 12px;">
|
||||||
<a href="{{ route('channel', $video->user_id) }}" class="channel-info text-decoration-none" style="color: inherit; display: flex; align-items: center; gap: 12px;">
|
<a href="{{ route('channel', $video->user_id) }}" class="channel-info text-decoration-none"
|
||||||
@if($video->user)
|
style="color: inherit; display: flex; align-items: center; gap: 12px;">
|
||||||
<img src="{{ $video->user->avatar_url }}" class="channel-avatar" style="width: 40px; height: 40px; border-radius: 50%;" alt="{{ $video->user->name }}">
|
@if ($video->user)
|
||||||
|
<img src="{{ $video->user->avatar_url }}" class="channel-avatar"
|
||||||
|
style="width: 40px; height: 40px; border-radius: 50%;" alt="{{ $video->user->name }}">
|
||||||
@else
|
@else
|
||||||
<div class="channel-avatar" style="width: 40px; height: 40px; border-radius: 50%;"></div>
|
<div class="channel-avatar" style="width: 40px; height: 40px; border-radius: 50%;"></div>
|
||||||
@endif
|
@endif
|
||||||
<div>
|
<div>
|
||||||
<div class="channel-name" style="font-weight: 600;">{{ $video->user->name ?? 'Unknown' }}</div>
|
<div class="channel-name" style="font-weight: 600;">{{ $video->user->name ?? 'Unknown' }}</div>
|
||||||
<div class="channel-subs" style="font-size: 12px; color: var(--text-secondary);">{{ number_format($video->user->subscriber_count ?? 0) }} subscribers</div>
|
<div class="channel-subs" style="font-size: 12px; color: var(--text-secondary);">
|
||||||
|
{{ number_format($video->user->subscriber_count ?? 0) }} subscribers</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="video-actions" style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
<div class="video-actions" style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
||||||
@auth
|
@auth
|
||||||
@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
|
||||||
@ -430,10 +581,13 @@
|
|||||||
@endauth
|
@endauth
|
||||||
|
|
||||||
@auth
|
@auth
|
||||||
<form method="POST" action="{{ $video->isLikedBy(Auth::user()) ? route('videos.unlike', $video->id) : route('videos.like', $video->id) }}" class="d-inline">
|
<form method="POST"
|
||||||
|
action="{{ $video->isLikedBy(Auth::user()) ? route('videos.unlike', $video->id) : route('videos.like', $video->id) }}"
|
||||||
|
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 class="bi {{ $video->isLikedBy(Auth::user()) ? 'bi-hand-thumbs-up-fill' : 'bi-hand-thumbs-up' }}"></i>
|
<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' }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@ -443,23 +597,36 @@
|
|||||||
</a>
|
</a>
|
||||||
@endauth
|
@endauth
|
||||||
|
|
||||||
@if($video->isShareable())
|
@if ($video->isShareable())
|
||||||
<button class="yt-action-btn" onclick="openShareModal('{{ $video->share_url }}', '{{ addslashes($video->title) }}')">
|
<button class="action-btn"
|
||||||
|
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>
|
||||||
|
|
||||||
<!-- Description Box -->
|
<!-- Description Box -->
|
||||||
@if($video->description)
|
@if ($video->description)
|
||||||
@php
|
@php
|
||||||
$fullDescription = $video->description;
|
$fullDescription = $video->description;
|
||||||
$shortDescription = Str::limit($fullDescription, 200);
|
$shortDescription = Str::limit($fullDescription, 200);
|
||||||
$needsExpand = strlen($fullDescription) > 200;
|
$needsExpand = strlen($fullDescription) > 200;
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<div class="video-description-box" style="background: var(--bg-secondary); border-radius: 12px; padding: 12px; margin-bottom: 16px;">
|
<div class="video-description-box"
|
||||||
|
style="background: var(--bg-secondary); border-radius: 12px; padding: 12px; margin-bottom: 16px;">
|
||||||
<div class="description-stats" style="font-size: 14px; font-weight: 500; margin-bottom: 8px;">
|
<div class="description-stats" style="font-size: 14px; font-weight: 500; margin-bottom: 8px;">
|
||||||
<span>{{ number_format($video->view_count) }} views</span>
|
<span>{{ number_format($video->view_count) }} views</span>
|
||||||
<span>•</span>
|
<span>•</span>
|
||||||
@ -467,7 +634,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="border-bottom: 1px solid var(--border-color); margin: 8px 0;"></div>
|
<div style="border-bottom: 1px solid var(--border-color); margin: 8px 0;"></div>
|
||||||
<div class="description-content" id="descriptionContent">
|
<div class="description-content" id="descriptionContent">
|
||||||
@if($needsExpand)
|
@if ($needsExpand)
|
||||||
<div class="description-short" id="descShort">
|
<div class="description-short" id="descShort">
|
||||||
<span class="description-text">{!! Str::markdown($shortDescription) !!}</span>
|
<span class="description-text">{!! Str::markdown($shortDescription) !!}</span>
|
||||||
<span class="description-ellipsis" style="color: var(--text-secondary);">... </span>
|
<span class="description-ellipsis" style="color: var(--text-secondary);">... </span>
|
||||||
@ -475,7 +642,8 @@
|
|||||||
<div class="description-full" id="descFull" style="display: none;">
|
<div class="description-full" id="descFull" style="display: none;">
|
||||||
<span class="description-text">{!! Str::markdown($fullDescription) !!}</span>
|
<span class="description-text">{!! Str::markdown($fullDescription) !!}</span>
|
||||||
</div>
|
</div>
|
||||||
<button onclick="toggleDescription()" id="descToggleBtn" style="background: none; border: none; color: var(--text-primary); font-weight: 600; font-size: 14px; cursor: pointer; padding: 0; margin-top: 4px;">
|
<button onclick="toggleDescription()" id="descToggleBtn"
|
||||||
|
style="background: none; border: none; color: var(--text-primary); font-weight: 600; font-size: 14px; cursor: pointer; padding: 0; margin-top: 4px;">
|
||||||
Show more
|
Show more
|
||||||
</button>
|
</button>
|
||||||
@else
|
@else
|
||||||
@ -503,37 +671,53 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.video-description-box .description-text { font-size: 14px; line-height: 1.5; color: var(--text-primary); }
|
.video-description-box .description-text {
|
||||||
.video-description-box .description-text p { margin-bottom: 8px; }
|
font-size: 14px;
|
||||||
.video-description-box .description-text a { color: #3ea6ff; }
|
line-height: 1.5;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-description-box .description-text p {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-description-box .description-text a {
|
||||||
|
color: #3ea6ff;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<!-- Comment Section -->
|
<!-- Comment Section -->
|
||||||
<div class="comments-section" style="margin-top: 24px; padding-top: 16px; border-top: 1px solid var(--border-color);">
|
<div class="comments-section"
|
||||||
|
style="margin-top: 24px; padding-top: 16px; border-top: 1px solid var(--border-color);">
|
||||||
<h3 style="font-size: 18px; font-weight: 600; margin-bottom: 16px;">
|
<h3 style="font-size: 18px; font-weight: 600; margin-bottom: 16px;">
|
||||||
Comments <span style="color: var(--text-secondary); font-weight: 400;">({{ $video->comment_count }})</span>
|
Comments <span
|
||||||
|
style="color: var(--text-secondary); font-weight: 400;">({{ $video->comment_count }})</span>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
@auth
|
@auth
|
||||||
<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;" alt="{{ Auth::user()->name }}">
|
<img src="{{ Auth::user()->avatar_url }}" class="channel-avatar" style="width: 40px; height: 40px;"
|
||||||
<div style="flex: 1;">
|
alt="{{ Auth::user()->name }}">
|
||||||
<textarea
|
<div style="flex: 1; display: flex; align-items: center; gap: 8px;">
|
||||||
id="commentBody"
|
<textarea id="commentBody" class="form-control" placeholder="Add a comment... Use @ to mention someone"
|
||||||
class="form-control"
|
rows="1"
|
||||||
placeholder="Add a comment... Use @ to mention someone"
|
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>
|
||||||
rows="3"
|
<button type="button" class="action-btn"
|
||||||
style="background: var(--bg-secondary); border: 1px solid var(--border-color); color: var(--text-primary); border-radius: 8px; padding: 12px; width: 100%; resize: none;"
|
onclick="document.getElementById('commentBody').value = ''" style="flex-shrink: 0;">
|
||||||
></textarea>
|
<i class="bi bi-x-lg"></i>
|
||||||
<div style="display: flex; gap: 8px; margin-top: 8px; justify-content: flex-end;">
|
<span>Cancel</span>
|
||||||
<button type="button" class="yt-action-btn" onclick="document.getElementById('commentBody').value = ''">Cancel</button>
|
</button>
|
||||||
<button type="button" class="yt-action-btn" style="background: var(--brand-red); color: white;" onclick="submitComment({{ $video->id }})">Comment</button>
|
<button type="button" class="action-btn comment-btn"
|
||||||
</div>
|
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
|
||||||
<div style="margin-bottom: 24px; padding: 16px; background: var(--bg-secondary); border-radius: 8px; text-align: center;">
|
<div
|
||||||
|
style="margin-bottom: 24px; padding: 16px; background: var(--bg-secondary); border-radius: 8px; text-align: center;">
|
||||||
<a href="{{ route('login') }}" style="color: var(--brand-red);">Sign in</a> to comment
|
<a href="{{ route('login') }}" style="color: var(--brand-red);">Sign in</a> to comment
|
||||||
</div>
|
</div>
|
||||||
@endauth
|
@endauth
|
||||||
@ -542,7 +726,8 @@
|
|||||||
@forelse($video->comments()->whereNull('parent_id')->with('user', 'replies.user')->latest()->get() as $comment)
|
@forelse($video->comments()->whereNull('parent_id')->with('user', 'replies.user')->latest()->get() as $comment)
|
||||||
@include('videos.partials.comment', ['comment' => $comment])
|
@include('videos.partials.comment', ['comment' => $comment])
|
||||||
@empty
|
@empty
|
||||||
<p style="color: var(--text-secondary); text-align: center; padding: 20px;">No comments yet. Be the first to comment!</p>
|
<p style="color: var(--text-secondary); text-align: center; padding: 20px;">No comments yet. Be the
|
||||||
|
first to comment!</p>
|
||||||
@endforelse
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -558,20 +743,56 @@
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ body: body })
|
body: JSON.stringify({
|
||||||
|
body: body
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.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})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteComment(commentId) {
|
||||||
|
if (confirm('Are you sure you want to delete this comment?')) {
|
||||||
fetch(`/comments/${commentId}`, {
|
fetch(`/comments/${commentId}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
@ -581,15 +802,22 @@
|
|||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
location.reload();
|
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() {
|
||||||
const commentTexts = document.querySelectorAll('.comment-body');
|
const commentTexts = document.querySelectorAll('.comment-body');
|
||||||
commentTexts.forEach(text => {
|
commentTexts.forEach(text => {
|
||||||
const html = text.innerHTML.replace(/@(\w+)/g, '<span style="color: #3ea6ff; font-weight: 500;">@$1</span>');
|
const html = text.innerHTML.replace(/@(\w+)/g,
|
||||||
|
'<span style="color: #3ea6ff; font-weight: 500;">@$1</span>');
|
||||||
text.innerHTML = html;
|
text.innerHTML = html;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -605,7 +833,10 @@
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ body: body, parent_id: parentId })
|
body: JSON.stringify({
|
||||||
|
body: body,
|
||||||
|
parent_id: parentId
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
@ -619,33 +850,40 @@
|
|||||||
|
|
||||||
<!-- 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) }}" alt="{{ $playlistVideo->title }}">
|
<img src="{{ asset('storage/thumbnails/' . $playlistVideo->thumbnail) }}"
|
||||||
|
alt="{{ $playlistVideo->title }}">
|
||||||
@else
|
@else
|
||||||
<img src="https://picsum.photos/seed/{{ $playlistVideo->id }}/320/180" alt="{{ $playlistVideo->title }}">
|
<img src="https://picsum.photos/seed/{{ $playlistVideo->id }}/320/180"
|
||||||
|
alt="{{ $playlistVideo->title }}">
|
||||||
@endif
|
@endif
|
||||||
@if ($playlistVideo->duration)
|
@if ($playlistVideo->duration)
|
||||||
<span class="yt-video-duration">{{ gmdate('i:s', $playlistVideo->duration) }}</span>
|
<span
|
||||||
|
class="yt-video-duration">{{ gmdate('i:s', $playlistVideo->duration) }}</span>
|
||||||
@endif
|
@endif
|
||||||
@if ($playlistVideo->is_shorts)
|
@if ($playlistVideo->is_shorts)
|
||||||
<span class="yt-shorts-badge" style="position: absolute; top: 8px; left: 8px; font-size: 10px; padding: 2px 6px;">
|
<span class="yt-shorts-badge"
|
||||||
|
style="position: absolute; top: 8px; left: 8px; font-size: 10px; padding: 2px 6px;">
|
||||||
<i class="bi bi-collection-play-fill"></i> SHORTS
|
<i class="bi bi-collection-play-fill"></i> SHORTS
|
||||||
</span>
|
</span>
|
||||||
@endif
|
@endif
|
||||||
<!-- Playlist position indicator -->
|
<!-- 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;">
|
<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 }}
|
{{ $index + 1 }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -657,47 +895,55 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="sidebar-meta">
|
<div class="sidebar-meta">
|
||||||
<div>{{ $playlistVideo->user->name ?? 'Unknown' }}</div>
|
<div>{{ $playlistVideo->user->name ?? 'Unknown' }}</div>
|
||||||
<div>{{ number_format($playlistVideo->view_count) }} views • {{ $playlistVideo->created_at->diffForHumans() }}</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
|
||||||
@else
|
@else
|
||||||
<h3 style="font-size: 16px; font-weight: 500; margin-bottom: 12px;">Up Next</h3>
|
<h3 style="font-size: 16px; font-weight: 500; margin-bottom: 12px;">Up Next</h3>
|
||||||
@if($recommendedVideos && $recommendedVideos->count() > 0)
|
@if ($recommendedVideos && $recommendedVideos->count() > 0)
|
||||||
<div class="recommended-videos-list">
|
<div class="recommended-videos-list">
|
||||||
@foreach($recommendedVideos as $recVideo)
|
@foreach ($recommendedVideos as $recVideo)
|
||||||
<div class="sidebar-video-card" onclick="window.location.href='{{ route('videos.show', $recVideo->id) }}'">
|
<div class="sidebar-video-card"
|
||||||
|
onclick="window.location.href='{{ route('videos.show', $recVideo->id) }}'">
|
||||||
<div class="sidebar-thumb">
|
<div class="sidebar-thumb">
|
||||||
@if($recVideo->thumbnail)
|
@if ($recVideo->thumbnail)
|
||||||
<img src="{{ asset('storage/thumbnails/' . $recVideo->thumbnail) }}" alt="{{ $recVideo->title }}">
|
<img src="{{ asset('storage/thumbnails/' . $recVideo->thumbnail) }}"
|
||||||
|
alt="{{ $recVideo->title }}">
|
||||||
@else
|
@else
|
||||||
<img src="https://picsum.photos/seed/{{ $recVideo->id }}/320/180" alt="{{ $recVideo->title }}">
|
<img src="https://picsum.photos/seed/{{ $recVideo->id }}/320/180"
|
||||||
|
alt="{{ $recVideo->title }}">
|
||||||
@endif
|
@endif
|
||||||
@if($recVideo->duration)
|
@if ($recVideo->duration)
|
||||||
<span class="yt-video-duration">{{ gmdate('i:s', $recVideo->duration) }}</span>
|
<span class="yt-video-duration">{{ gmdate('i:s', $recVideo->duration) }}</span>
|
||||||
@endif
|
@endif
|
||||||
@if($recVideo->is_shorts)
|
@if ($recVideo->is_shorts)
|
||||||
<span class="yt-shorts-badge" style="position: absolute; top: 8px; left: 8px; font-size: 10px; padding: 2px 6px;">
|
<span class="yt-shorts-badge"
|
||||||
|
style="position: absolute; top: 8px; left: 8px; font-size: 10px; padding: 2px 6px;">
|
||||||
<i class="bi bi-collection-play-fill"></i> SHORTS
|
<i class="bi bi-collection-play-fill"></i> SHORTS
|
||||||
</span>
|
</span>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="sidebar-info">
|
<div class="sidebar-info">
|
||||||
<div class="sidebar-title">
|
<div class="sidebar-title">
|
||||||
<i class="bi {{ match($recVideo->type) { 'music' => 'bi-music-note', 'match' => 'bi-trophy', default => 'bi-film' } }}" style="color: #ef4444; margin-right: 4px; font-size: 12px;"></i>
|
<i class="bi {{ match ($recVideo->type) {'music' => 'bi-music-note','match' => 'bi-trophy',default => 'bi-film'} }}"
|
||||||
|
style="color: #ef4444; margin-right: 4px; font-size: 12px;"></i>
|
||||||
{{ Str::limit($recVideo->title, 60) }}
|
{{ Str::limit($recVideo->title, 60) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="sidebar-meta">
|
<div class="sidebar-meta">
|
||||||
<div>{{ $recVideo->user->name ?? 'Unknown' }}</div>
|
<div>{{ $recVideo->user->name ?? 'Unknown' }}</div>
|
||||||
<div>{{ number_format($recVideo->view_count) }} views • {{ $recVideo->created_at->diffForHumans() }}</div>
|
<div>{{ number_format($recVideo->view_count) }} views •
|
||||||
|
{{ $recVideo->created_at->diffForHumans() }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -714,7 +960,7 @@
|
|||||||
@include('layouts.partials.share-modal')
|
@include('layouts.partials.share-modal')
|
||||||
@include('layouts.partials.edit-video-modal')
|
@include('layouts.partials.edit-video-modal')
|
||||||
|
|
||||||
@if(Session::has('openEditModal') && Session::get('openEditModal'))
|
@if (Session::has('openEditModal') && Session::get('openEditModal'))
|
||||||
@auth
|
@auth
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
@ -741,7 +987,11 @@
|
|||||||
videoPlayer.volume = 0.5;
|
videoPlayer.volume = 0.5;
|
||||||
var playPromise = videoPlayer.play();
|
var playPromise = videoPlayer.play();
|
||||||
if (playPromise !== undefined) {
|
if (playPromise !== undefined) {
|
||||||
playPromise.then(function() { console.log('Video autoplayed'); }).catch(function(error) { console.log('Autoplay blocked'); });
|
playPromise.then(function() {
|
||||||
|
console.log('Video autoplayed');
|
||||||
|
}).catch(function(error) {
|
||||||
|
console.log('Autoplay blocked');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle video ended - autoplay next
|
// Handle video ended - autoplay next
|
||||||
@ -766,8 +1016,12 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Initially show for a few seconds
|
// Initially show for a few seconds
|
||||||
setTimeout(function() { playlistControls.classList.add('visible'); }, 2000);
|
setTimeout(function() {
|
||||||
setTimeout(function() { playlistControls.classList.remove('visible'); }, 6000);
|
playlistControls.classList.add('visible');
|
||||||
|
}, 2000);
|
||||||
|
setTimeout(function() {
|
||||||
|
playlistControls.classList.remove('visible');
|
||||||
|
}, 6000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Autoplay toggle handler
|
// Autoplay toggle handler
|
||||||
@ -787,18 +1041,29 @@
|
|||||||
|
|
||||||
// Keyboard navigation
|
// Keyboard navigation
|
||||||
document.addEventListener('keydown', function(e) {
|
document.addEventListener('keydown', function(e) {
|
||||||
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') { return; }
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
|
||||||
if (e.key === 'ArrowLeft') { e.preventDefault(); goToPreviousVideo(); }
|
return;
|
||||||
else if (e.key === 'ArrowRight') { e.preventDefault(); goToNextVideo(); }
|
}
|
||||||
|
if (e.key === 'ArrowLeft') {
|
||||||
|
e.preventDefault();
|
||||||
|
goToPreviousVideo();
|
||||||
|
} else if (e.key === 'ArrowRight') {
|
||||||
|
e.preventDefault();
|
||||||
|
goToNextVideo();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function goToPreviousVideo() {
|
function goToPreviousVideo() {
|
||||||
if (previousVideoUrl) { window.location.href = previousVideoUrl; }
|
if (previousVideoUrl) {
|
||||||
|
window.location.href = previousVideoUrl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToNextVideo() {
|
function goToNextVideo() {
|
||||||
if (nextVideoUrl) { window.location.href = nextVideoUrl; }
|
if (nextVideoUrl) {
|
||||||
|
window.location.href = nextVideoUrl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
45
resources/views/videos/types/music.blade.php-script-append
Normal file
45
resources/views/videos/types/music.blade.php-script-append
Normal file
@ -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>
|
||||||
Loading…
x
Reference in New Issue
Block a user