converted comment section and the cannel info and the action buttons to components
This commit is contained in:
1 parent
84fcbd84dc
commit
64eadfaf56
10 files changed
+1324
-11985
No files matched your search
@@ -601,215 +601,115 @@
|
||||
</style>
|
||||
@endif
|
||||
|
||||
<!-- Comments Section -->
|
||||
<div class="comments-section"
|
||||
style="margin-top: 24px; padding-top: 16: 1px solid var(--borderpx; border-top-color);">
|
||||
<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>
|
||||
</h3>
|
||||
@include('components.video-comments', ['video' => $video])
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@auth
|
||||
<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 }}">
|
||||
<div style="flex: 1;">
|
||||
<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>
|
||||
<div style="display: flex; gap: 8px; margin-top: 8px; justify-content: flex-end;">
|
||||
<button type="button" class="yt-action-btn"
|
||||
onclick="document.getElementById('commentBody').value = ''">Cancel</button>
|
||||
<button type="button" class="yt-action-btn"
|
||||
style="background: var(--brand-red); color: white;"
|
||||
onclick="submitComment({{ $video->id }})">Comment</button>
|
||||
<!-- Sidebar - Up Next / Recommendations -->
|
||||
<div class="yt-sidebar-container">
|
||||
@if ($playlist && $playlistVideos && $playlistVideos->count() > 0)
|
||||
<h3 style="font-size: 16px; font-weight: 500; margin-bottom: 12px;">
|
||||
<i class="bi bi-collection-play" style="margin-right: 8px;"></i>
|
||||
{{ $playlist->name }}
|
||||
<span
|
||||
style="font-weight: 400; color: var(--text-secondary); font-size: 14px;">({{ $playlistVideos->count() }}
|
||||
videos)</span>
|
||||
</h3>
|
||||
<div class="recommended-videos-list">
|
||||
@foreach ($playlistVideos as $index => $playlistVideo)
|
||||
@if ($playlistVideo->id !== $video->id)
|
||||
<div class="sidebar-video-card{{ $playlistVideo->id === $video->id ? ' current-video' : '' }}"
|
||||
onclick="window.location.href='{{ route('videos.show', $playlistVideo->id) }}?playlist={{ $playlist->id }}'">
|
||||
<div class="sidebar-thumb" style="position: relative;">
|
||||
@if ($playlistVideo->thumbnail)
|
||||
<img src="{{ asset('storage/thumbnails/' . $playlistVideo->thumbnail) }}"
|
||||
alt="{{ $playlistVideo->title }}">
|
||||
@else
|
||||
<img src="https://picsum.photos/seed/{{ $playlistVideo->id }}/320/180"
|
||||
alt="{{ $playlistVideo->title }}">
|
||||
@endif
|
||||
@if ($playlistVideo->duration)
|
||||
<span class="yt-video-duration">{{ gmdate('i:s', $playlistVideo->duration) }}</span>
|
||||
@endif
|
||||
@if ($playlistVideo->is_shorts)
|
||||
<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
|
||||
</span>
|
||||
@endif
|
||||
<!-- Playlist position indicator -->
|
||||
<span
|
||||
style="position: absolute; bottom: 4px; left: 4px; background: rgba(0,0,0,0.8); color: white; padding: 2px 6px; border-radius: 4px; font-size: 12px; font-weight: 500;">
|
||||
{{ $index + 1 }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="sidebar-info">
|
||||
<div class="sidebar-title">
|
||||
<i class="bi {{ match ($playlistVideo->type) {'music' => 'bi-music-note','match' => 'bi-trophy',default => 'bi-film'} }}"
|
||||
style="color: #ef4444; margin-right: 4px; font-size: 12px;"></i>
|
||||
{{ Str::limit($playlistVideo->title, 60) }}
|
||||
</div>
|
||||
<div class="sidebar-meta">
|
||||
<div>{{ $playlistVideo->user->name ?? 'Unknown' }}</div>
|
||||
<div>{{ number_format($playlistVideo->view_count) }} views •
|
||||
{{ $playlistVideo->created_at->diffForHumans() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<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
|
||||
</div>
|
||||
@endauth
|
||||
|
||||
<div id="commentsList">
|
||||
@forelse($video->comments()->whereNull('parent_id')->with('user', 'replies.user')->latest()->get() as $comment)
|
||||
@include('videos.partials.comment', ['comment' => $comment])
|
||||
@empty
|
||||
<p style="color: var(--text-secondary); text-align: center; padding: 20px;">No comments yet. Be the
|
||||
first to comment!</p>
|
||||
@endforelse
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function submitComment(videoId) {
|
||||
const body = document.getElementById('commentBody').value.trim();
|
||||
if (!body) return;
|
||||
fetch(`/videos/${videoId}/comments`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
body: body
|
||||
})
|
||||
}).then(r => r.json()).then(data => {
|
||||
if (data.success) {
|
||||
document.getElementById('commentBody').value = '';
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteComment(commentId) {
|
||||
if (!confirm('Delete this comment?')) return;
|
||||
fetch(`/comments/${commentId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
}
|
||||
})
|
||||
.then(r => r.json()).then(data => {
|
||||
if (data.success) location.reload();
|
||||
});
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('.comment-body').forEach(text => {
|
||||
text.innerHTML = text.innerHTML.replace(/@(\w+)/g,
|
||||
'<span style="color: #3ea6ff; font-weight: 500;">@$1</span>');
|
||||
});
|
||||
});
|
||||
|
||||
function submitReply(videoId, parentId) {
|
||||
const textarea = document.querySelector(`#replyForm${parentId} textarea`);
|
||||
const body = textarea.value.trim();
|
||||
if (!body) return;
|
||||
fetch(`/videos/${videoId}/comments`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
body: body,
|
||||
parent_id: parentId
|
||||
})
|
||||
}).then(r => r.json()).then(data => {
|
||||
if (data.success) location.reload();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar - Up Next / Recommendations -->
|
||||
<div class="yt-sidebar-container">
|
||||
@if ($playlist && $playlistVideos && $playlistVideos->count() > 0)
|
||||
<h3 style="font-size: 16px; font-weight: 500; margin-bottom: 12px;">
|
||||
<i class="bi bi-collection-play" style="margin-right: 8px;"></i>
|
||||
{{ $playlist->name }}
|
||||
<span
|
||||
style="font-weight: 400; color: var(--text-secondary); font-size: 14px;">({{ $playlistVideos->count() }}
|
||||
videos)</span>
|
||||
</h3>
|
||||
@if ($playlist->canEdit(Auth::user()))
|
||||
<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
|
||||
</a>
|
||||
@endif
|
||||
@else
|
||||
<h3 style="font-size: 16px; font-weight: 500; margin-bottom: 12px;">Up Next</h3>
|
||||
@if ($recommendedVideos && $recommendedVideos->count() > 0)
|
||||
<div class="recommended-videos-list">
|
||||
@foreach ($playlistVideos as $index => $playlistVideo)
|
||||
@if ($playlistVideo->id !== $video->id)
|
||||
<div class="sidebar-video-card{{ $playlistVideo->id === $video->id ? ' current-video' : '' }}"
|
||||
onclick="window.location.href='{{ route('videos.show', $playlistVideo->id) }}?playlist={{ $playlist->id }}'">
|
||||
<div class="sidebar-thumb" style="position: relative;">
|
||||
@if ($playlistVideo->thumbnail)
|
||||
<img src="{{ asset('storage/thumbnails/' . $playlistVideo->thumbnail) }}"
|
||||
alt="{{ $playlistVideo->title }}">
|
||||
@else
|
||||
<img src="https://picsum.photos/seed/{{ $playlistVideo->id }}/320/180"
|
||||
alt="{{ $playlistVideo->title }}">
|
||||
@endif
|
||||
@if ($playlistVideo->duration)
|
||||
<span
|
||||
class="yt-video-duration">{{ gmdate('i:s', $playlistVideo->duration) }}</span>
|
||||
@endif
|
||||
@if ($playlistVideo->is_shorts)
|
||||
<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
|
||||
</span>
|
||||
@endif
|
||||
<!-- Playlist position indicator -->
|
||||
<span
|
||||
style="position: absolute; bottom: 4px; left: 4px; background: rgba(0,0,0,0.8); color: white; padding: 2px 6px; border-radius: 4px; font-size: 12px; font-weight: 500;">
|
||||
{{ $index + 1 }}
|
||||
@foreach ($recommendedVideos as $recVideo)
|
||||
<div class="sidebar-video-card"
|
||||
onclick="window.location.href='{{ route('videos.show', $recVideo->id) }}'">
|
||||
<div class="sidebar-thumb">
|
||||
@if ($recVideo->thumbnail)
|
||||
<img src="{{ asset('storage/thumbnails/' . $recVideo->thumbnail) }}"
|
||||
alt="{{ $recVideo->title }}">
|
||||
@else
|
||||
<img src="https://picsum.photos/seed/{{ $recVideo->id }}/320/180"
|
||||
alt="{{ $recVideo->title }}">
|
||||
@endif
|
||||
@if ($recVideo->duration)
|
||||
<span class="yt-video-duration">{{ gmdate('i:s', $recVideo->duration) }}</span>
|
||||
@endif
|
||||
@if ($recVideo->is_shorts)
|
||||
<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
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="sidebar-info">
|
||||
<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>
|
||||
{{ Str::limit($recVideo->title, 60) }}
|
||||
</div>
|
||||
<div class="sidebar-info">
|
||||
<div class="sidebar-title">
|
||||
<i class="bi {{ match ($playlistVideo->type) {'music' => 'bi-music-note','match' => 'bi-trophy',default => 'bi-film'} }}"
|
||||
style="color: #ef4444; margin-right: 4px; font-size: 12px;"></i>
|
||||
{{ Str::limit($playlistVideo->title, 60) }}
|
||||
</div>
|
||||
<div class="sidebar-meta">
|
||||
<div>{{ $playlistVideo->user->name ?? 'Unknown' }}</div>
|
||||
<div>{{ number_format($playlistVideo->view_count) }} views •
|
||||
{{ $playlistVideo->created_at->diffForHumans() }}</div>
|
||||
</div>
|
||||
<div class="sidebar-meta">
|
||||
<div>{{ $recVideo->user->name ?? 'Unknown' }}</div>
|
||||
<div>{{ number_format($recVideo->view_count) }} views •
|
||||
{{ $recVideo->created_at->diffForHumans() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@if ($playlist->canEdit(Auth::user()))
|
||||
<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
|
||||
</a>
|
||||
@endif
|
||||
@else
|
||||
<h3 style="font-size: 16px; font-weight: 500; margin-bottom: 12px;">Up Next</h3>
|
||||
@if ($recommendedVideos && $recommendedVideos->count() > 0)
|
||||
<div class="recommended-videos-list">
|
||||
@foreach ($recommendedVideos as $recVideo)
|
||||
<div class="sidebar-video-card"
|
||||
onclick="window.location.href='{{ route('videos.show', $recVideo->id) }}'">
|
||||
<div class="sidebar-thumb">
|
||||
@if ($recVideo->thumbnail)
|
||||
<img src="{{ asset('storage/thumbnails/' . $recVideo->thumbnail) }}"
|
||||
alt="{{ $recVideo->title }}">
|
||||
@else
|
||||
<img src="https://picsum.photos/seed/{{ $recVideo->id }}/320/180"
|
||||
alt="{{ $recVideo->title }}">
|
||||
@endif
|
||||
@if ($recVideo->duration)
|
||||
<span class="yt-video-duration">{{ gmdate('i:s', $recVideo->duration) }}</span>
|
||||
@endif
|
||||
@if ($recVideo->is_shorts)
|
||||
<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
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="sidebar-info">
|
||||
<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>
|
||||
{{ Str::limit($recVideo->title, 60) }}
|
||||
</div>
|
||||
<div class="sidebar-meta">
|
||||
<div>{{ $recVideo->user->name ?? 'Unknown' }}</div>
|
||||
<div>{{ number_format($recVideo->view_count) }} views •
|
||||
{{ $recVideo->created_at->diffForHumans() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<div class="text-secondary">No recommendations available yet. Check back later!</div>
|
||||
@endif
|
||||
<div class="text-secondary">No recommendations available yet. Check back later!</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,9 +0,0 @@
|
||||
@extends('layouts.app')
|
||||
@section('title', ($video->title ?? 'Match Video') . ' | ' . config('app.name'))
|
||||
|
||||
@section('extra_styles')
|
||||
<style>
|
||||
/* ===== Video Section ===== */
|
||||
.yt-video-section {
|
||||
flex: 1;
|
||||
min
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -210,13 +210,81 @@
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.video-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.yt-action-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 20px;
|
||||
border: none;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.yt-action-btn:hover {
|
||||
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 {
|
||||
color: var(--brand-red);
|
||||
}
|
||||
|
||||
/* Channel Row */
|
||||
.channel-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 16px 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.channel-info {
|
||||
@@ -242,6 +310,18 @@
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.subscribe-btn {
|
||||
background: white;
|
||||
color: black;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 18px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Description */
|
||||
.video-description {
|
||||
background: var(--bg-secondary);
|
||||
@@ -352,16 +432,8 @@
|
||||
|
||||
.video-actions {
|
||||
width: 100%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.video-actions>.desktop-action {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-action-dropdown {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
overflow-x: auto;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.yt-main {
|
||||
@@ -474,7 +546,76 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<x-channel-row :video="$video" />
|
||||
<!-- 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 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;">
|
||||
@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
|
||||
<div class="channel-avatar" style="width: 40px; height: 40px; border-radius: 50%;"></div>
|
||||
@endif
|
||||
<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>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="video-actions" style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
||||
@auth
|
||||
@if (Auth::id() !== $video->user_id)
|
||||
<button class="subscribe-btn">Subscribe</button>
|
||||
@else
|
||||
<button class="action-btn" onclick="openEditVideoModal({{ $video->id }})">
|
||||
<i class="bi bi-pencil"></i>
|
||||
<span>Edit</span>
|
||||
</button>
|
||||
@endif
|
||||
@else
|
||||
<a href="{{ route('login') }}" class="subscribe-btn">Subscribe</a>
|
||||
@endauth
|
||||
|
||||
@auth
|
||||
<form method="POST"
|
||||
action="{{ $video->isLikedBy(Auth::user()) ? route('videos.unlike', $video->id) : route('videos.like', $video->id) }}"
|
||||
class="d-inline">
|
||||
@csrf
|
||||
<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>
|
||||
{{ $video->like_count > 0 ? number_format($video->like_count) : 'Like' }}
|
||||
</button>
|
||||
</form>
|
||||
@else
|
||||
<a href="{{ route('login') }}" class="yt-action-btn">
|
||||
<i class="bi bi-hand-thumbs-up"></i> Like
|
||||
</a>
|
||||
@endauth
|
||||
|
||||
@if ($video->isShareable())
|
||||
<button class="action-btn"
|
||||
onclick="openShareModal('{{ $video->share_url }}', '{{ addslashes($video->title) }}')">
|
||||
<i class="bi bi-share"></i> Share
|
||||
</button>
|
||||
@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>
|
||||
|
||||
<!-- Description Box -->
|
||||
@if ($video->description)
|
||||
@@ -546,45 +687,84 @@
|
||||
</style>
|
||||
@endif
|
||||
|
||||
<x-video-comments :video="$video" />
|
||||
<!-- Comment Section -->
|
||||
<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;">
|
||||
Comments <span
|
||||
style="color: var(--text-secondary); font-weight: 400;">({{ $video->comment_count }})</span>
|
||||
</h3>
|
||||
|
||||
@auth
|
||||
<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 }}">
|
||||
<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="1"
|
||||
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="action-btn"
|
||||
onclick="document.getElementById('commentBody').value = ''" style="flex-shrink: 0;">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
<span>Cancel</span>
|
||||
</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>
|
||||
@else
|
||||
<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
|
||||
</div>
|
||||
@endauth
|
||||
|
||||
<div id="commentsList">
|
||||
@forelse($video->comments()->whereNull('parent_id')->with('user', 'replies.user')->latest()->get() as $comment)
|
||||
@include('videos.partials.comment', ['comment' => $comment])
|
||||
@empty
|
||||
<p style="color: var(--text-secondary); text-align: center; padding: 20px;">No comments yet. Be the
|
||||
first to comment!</p>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script>
|
||||
function submitComment(videoId) {
|
||||
const body = document.getElementById('commentBody').value.trim();
|
||||
if (!body) return;
|
||||
|
||||
<script>
|
||||
function submitComment(videoId) {
|
||||
const body = document.getElementById('commentBody').value.trim();
|
||||
if (!body) return;
|
||||
|
||||
fetch(`/videos/${videoId}/comments`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
body: body
|
||||
fetch(`/videos/${videoId}/comments`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
body: body
|
||||
})
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Failed to post comment: ' + error);
|
||||
})
|
||||
.then(data => {
|
||||
if (data && data.success) {
|
||||
document.getElementById('commentBody').value = '';
|
||||
addCommentToList(data.comment);
|
||||
} else {
|
||||
alert('Failed to post comment');
|
||||
}
|
||||
});
|
||||
}
|
||||
.then(response => response.json())
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Failed to post comment: ' + error);
|
||||
})
|
||||
.then(data => {
|
||||
if (data && data.success) {
|
||||
document.getElementById('commentBody').value = '';
|
||||
addCommentToList(data.comment);
|
||||
} else {
|
||||
alert('Failed to post comment');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addCommentToList(comment) {
|
||||
const commentsList = document.getElementById('commentsList');
|
||||
const commentHtml = `
|
||||
function addCommentToList(comment) {
|
||||
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;">
|
||||
@@ -603,176 +783,177 @@
|
||||
</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})`;
|
||||
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}`, {
|
||||
method: 'DELETE',
|
||||
function deleteComment(commentId) {
|
||||
if (confirm('Are you sure you want to delete this comment?')) {
|
||||
fetch(`/comments/${commentId}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
}
|
||||
})
|
||||
.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 = `(${count})`;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const commentTexts = document.querySelectorAll('.comment-body');
|
||||
commentTexts.forEach(text => {
|
||||
const html = text.innerHTML.replace(/@(\w+)/g,
|
||||
'<span style="color: #3ea6ff; font-weight: 500;">@$1</span>');
|
||||
text.innerHTML = html;
|
||||
});
|
||||
});
|
||||
|
||||
function submitReply(videoId, parentId) {
|
||||
const textarea = document.querySelector(`#replyForm${parentId} textarea`);
|
||||
const body = textarea.value.trim();
|
||||
if (!body) return;
|
||||
|
||||
fetch(`/videos/${videoId}/comments`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
}
|
||||
},
|
||||
body: JSON.stringify({
|
||||
body: body,
|
||||
parent_id: parentId
|
||||
})
|
||||
})
|
||||
.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 = `(${count})`;
|
||||
}
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const commentTexts = document.querySelectorAll('.comment-body');
|
||||
commentTexts.forEach(text => {
|
||||
const html = text.innerHTML.replace(/@(\w+)/g,
|
||||
'<span style="color: #3ea6ff; font-weight: 500;">@$1</span>');
|
||||
text.innerHTML = html;
|
||||
});
|
||||
});
|
||||
|
||||
function submitReply(videoId, parentId) {
|
||||
const textarea = document.querySelector(`#replyForm${parentId} textarea`);
|
||||
const body = textarea.value.trim();
|
||||
if (!body) return;
|
||||
|
||||
fetch(`/videos/${videoId}/comments`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
body: body,
|
||||
parent_id: parentId
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar - Up Next / Recommendations -->
|
||||
<div class="yt-sidebar-container">
|
||||
@if ($playlist && $playlistVideos && $playlistVideos->count() > 0)
|
||||
<h3 style="font-size: 16px; font-weight: 500; margin-bottom: 12px;">
|
||||
<i class="bi bi-collection-play" style="margin-right: 8px;"></i>
|
||||
{{ $playlist->name }}
|
||||
<span
|
||||
style="font-weight: 400; color: var(--text-secondary); font-size: 14px;">({{ $playlistVideos->count() }}
|
||||
videos)</span>
|
||||
</h3>
|
||||
<div class="recommended-videos-list">
|
||||
@foreach ($playlistVideos as $index => $playlistVideo)
|
||||
@if ($playlistVideo->id !== $video->id)
|
||||
<div class="sidebar-video-card{{ $playlistVideo->id === $video->id ? ' current-video' : '' }}"
|
||||
onclick="window.location.href='{{ route('videos.show', $playlistVideo->id) }}?playlist={{ $playlist->id }}'">
|
||||
<div class="sidebar-thumb" style="position: relative;">
|
||||
@if ($playlistVideo->thumbnail)
|
||||
<img src="{{ asset('storage/thumbnails/' . $playlistVideo->thumbnail) }}"
|
||||
alt="{{ $playlistVideo->title }}">
|
||||
@else
|
||||
<img src="https://picsum.photos/seed/{{ $playlistVideo->id }}/320/180"
|
||||
alt="{{ $playlistVideo->title }}">
|
||||
@endif
|
||||
@if ($playlistVideo->duration)
|
||||
<span class="yt-video-duration">{{ gmdate('i:s', $playlistVideo->duration) }}</span>
|
||||
@endif
|
||||
@if ($playlistVideo->is_shorts)
|
||||
<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
|
||||
</span>
|
||||
@endif
|
||||
<!-- Playlist position indicator -->
|
||||
<span
|
||||
style="position: absolute; bottom: 4px; left: 4px; background: rgba(0,0,0,0.8); color: white; padding: 2px 6px; border-radius: 4px; font-size: 12px; font-weight: 500;">
|
||||
{{ $index + 1 }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="sidebar-info">
|
||||
<div class="sidebar-title">
|
||||
<i class="bi {{ match ($playlistVideo->type) {'music' => 'bi-music-note','match' => 'bi-trophy',default => 'bi-film'} }}"
|
||||
style="color: #ef4444; margin-right: 4px; font-size: 12px;"></i>
|
||||
{{ Str::limit($playlistVideo->title, 60) }}
|
||||
</div>
|
||||
<div class="sidebar-meta">
|
||||
<div>{{ $playlistVideo->user->name ?? 'Unknown' }}</div>
|
||||
<div>{{ number_format($playlistVideo->view_count) }} views •
|
||||
{{ $playlistVideo->created_at->diffForHumans() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
@if ($playlist->canEdit(Auth::user()))
|
||||
<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
|
||||
</a>
|
||||
@endif
|
||||
@else
|
||||
<h3 style="font-size: 16px; font-weight: 500; margin-bottom: 12px;">Up Next</h3>
|
||||
@if ($recommendedVideos && $recommendedVideos->count() > 0)
|
||||
<!-- Sidebar - Up Next / Recommendations -->
|
||||
<div class="yt-sidebar-container">
|
||||
@if ($playlist && $playlistVideos && $playlistVideos->count() > 0)
|
||||
<h3 style="font-size: 16px; font-weight: 500; margin-bottom: 12px;">
|
||||
<i class="bi bi-collection-play" style="margin-right: 8px;"></i>
|
||||
{{ $playlist->name }}
|
||||
<span
|
||||
style="font-weight: 400; color: var(--text-secondary); font-size: 14px;">({{ $playlistVideos->count() }}
|
||||
videos)</span>
|
||||
</h3>
|
||||
<div class="recommended-videos-list">
|
||||
@foreach ($recommendedVideos as $recVideo)
|
||||
<div class="sidebar-video-card"
|
||||
onclick="window.location.href='{{ route('videos.show', $recVideo->id) }}'">
|
||||
<div class="sidebar-thumb">
|
||||
@if ($recVideo->thumbnail)
|
||||
<img src="{{ asset('storage/thumbnails/' . $recVideo->thumbnail) }}"
|
||||
alt="{{ $recVideo->title }}">
|
||||
@else
|
||||
<img src="https://picsum.photos/seed/{{ $recVideo->id }}/320/180"
|
||||
alt="{{ $recVideo->title }}">
|
||||
@endif
|
||||
@if ($recVideo->duration)
|
||||
<span class="yt-video-duration">{{ gmdate('i:s', $recVideo->duration) }}</span>
|
||||
@endif
|
||||
@if ($recVideo->is_shorts)
|
||||
<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
|
||||
@foreach ($playlistVideos as $index => $playlistVideo)
|
||||
@if ($playlistVideo->id !== $video->id)
|
||||
<div class="sidebar-video-card{{ $playlistVideo->id === $video->id ? ' current-video' : '' }}"
|
||||
onclick="window.location.href='{{ route('videos.show', $playlistVideo->id) }}?playlist={{ $playlist->id }}'">
|
||||
<div class="sidebar-thumb" style="position: relative;">
|
||||
@if ($playlistVideo->thumbnail)
|
||||
<img src="{{ asset('storage/thumbnails/' . $playlistVideo->thumbnail) }}"
|
||||
alt="{{ $playlistVideo->title }}">
|
||||
@else
|
||||
<img src="https://picsum.photos/seed/{{ $playlistVideo->id }}/320/180"
|
||||
alt="{{ $playlistVideo->title }}">
|
||||
@endif
|
||||
@if ($playlistVideo->duration)
|
||||
<span
|
||||
class="yt-video-duration">{{ gmdate('i:s', $playlistVideo->duration) }}</span>
|
||||
@endif
|
||||
@if ($playlistVideo->is_shorts)
|
||||
<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
|
||||
</span>
|
||||
@endif
|
||||
<!-- Playlist position indicator -->
|
||||
<span
|
||||
style="position: absolute; bottom: 4px; left: 4px; background: rgba(0,0,0,0.8); color: white; padding: 2px 6px; border-radius: 4px; font-size: 12px; font-weight: 500;">
|
||||
{{ $index + 1 }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="sidebar-info">
|
||||
<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>
|
||||
{{ Str::limit($recVideo->title, 60) }}
|
||||
</div>
|
||||
<div class="sidebar-meta">
|
||||
<div>{{ $recVideo->user->name ?? 'Unknown' }}</div>
|
||||
<div>{{ number_format($recVideo->view_count) }} views •
|
||||
{{ $recVideo->created_at->diffForHumans() }}</div>
|
||||
<div class="sidebar-info">
|
||||
<div class="sidebar-title">
|
||||
<i class="bi {{ match ($playlistVideo->type) {'music' => 'bi-music-note','match' => 'bi-trophy',default => 'bi-film'} }}"
|
||||
style="color: #ef4444; margin-right: 4px; font-size: 12px;"></i>
|
||||
{{ Str::limit($playlistVideo->title, 60) }}
|
||||
</div>
|
||||
<div class="sidebar-meta">
|
||||
<div>{{ $playlistVideo->user->name ?? 'Unknown' }}</div>
|
||||
<div>{{ number_format($playlistVideo->view_count) }} views •
|
||||
{{ $playlistVideo->created_at->diffForHumans() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
@if ($playlist->canEdit(Auth::user()))
|
||||
<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
|
||||
</a>
|
||||
@endif
|
||||
@else
|
||||
<div class="text-secondary">No recommendations available yet. Check back later!</div>
|
||||
<h3 style="font-size: 16px; font-weight: 500; margin-bottom: 12px;">Up Next</h3>
|
||||
@if ($recommendedVideos && $recommendedVideos->count() > 0)
|
||||
<div class="recommended-videos-list">
|
||||
@foreach ($recommendedVideos as $recVideo)
|
||||
<div class="sidebar-video-card"
|
||||
onclick="window.location.href='{{ route('videos.show', $recVideo->id) }}'">
|
||||
<div class="sidebar-thumb">
|
||||
@if ($recVideo->thumbnail)
|
||||
<img src="{{ asset('storage/thumbnails/' . $recVideo->thumbnail) }}"
|
||||
alt="{{ $recVideo->title }}">
|
||||
@else
|
||||
<img src="https://picsum.photos/seed/{{ $recVideo->id }}/320/180"
|
||||
alt="{{ $recVideo->title }}">
|
||||
@endif
|
||||
@if ($recVideo->duration)
|
||||
<span class="yt-video-duration">{{ gmdate('i:s', $recVideo->duration) }}</span>
|
||||
@endif
|
||||
@if ($recVideo->is_shorts)
|
||||
<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
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="sidebar-info">
|
||||
<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>
|
||||
{{ Str::limit($recVideo->title, 60) }}
|
||||
</div>
|
||||
<div class="sidebar-meta">
|
||||
<div>{{ $recVideo->user->name ?? 'Unknown' }}</div>
|
||||
<div>{{ number_format($recVideo->view_count) }} views •
|
||||
{{ $recVideo->created_at->diffForHumans() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<div class="text-secondary">No recommendations available yet. Check back later!</div>
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
<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>
|
||||
Reference in new issue
Block a user