latest update
This commit is contained in:
parent
3b09f4baed
commit
84fcbd84dc
File diff suppressed because it is too large
Load Diff
@ -18,6 +18,7 @@ class MatchEventController extends Controller
|
|||||||
$request->validate([
|
$request->validate([
|
||||||
'round_number' => 'required|integer|min:1',
|
'round_number' => 'required|integer|min:1',
|
||||||
'name' => 'nullable|string|max:50',
|
'name' => 'nullable|string|max:50',
|
||||||
|
'start_time_seconds' => 'nullable|integer|min:0',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Check if user owns the video
|
// Check if user owns the video
|
||||||
@ -29,6 +30,7 @@ class MatchEventController extends Controller
|
|||||||
'video_id' => $video->id,
|
'video_id' => $video->id,
|
||||||
'round_number' => $request->round_number,
|
'round_number' => $request->round_number,
|
||||||
'name' => $request->name ?? 'ROUND '.$request->round_number,
|
'name' => $request->name ?? 'ROUND '.$request->round_number,
|
||||||
|
'start_time_seconds' => $request->start_time_seconds,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@ -41,7 +43,9 @@ class MatchEventController extends Controller
|
|||||||
public function updateRound(Request $request, MatchRound $round)
|
public function updateRound(Request $request, MatchRound $round)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
|
'round_number' => 'sometimes|integer|min:1',
|
||||||
'name' => 'required|string|max:50',
|
'name' => 'required|string|max:50',
|
||||||
|
'start_time_seconds' => 'nullable|integer|min:0',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Check if user owns the video
|
// Check if user owns the video
|
||||||
@ -50,7 +54,9 @@ class MatchEventController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$round->update([
|
$round->update([
|
||||||
|
'round_number' => $request->round_number ?? $round->round_number,
|
||||||
'name' => $request->name,
|
'name' => $request->name,
|
||||||
|
'start_time_seconds' => $request->start_time_seconds,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@ -93,16 +99,25 @@ class MatchEventController extends Controller
|
|||||||
return response()->json(['success' => false, 'message' => 'Unauthorized'], 403);
|
return response()->json(['success' => false, 'message' => 'Unauthorized'], 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate scores
|
// Get ALL previous points in this round (ordered by timestamp)
|
||||||
$previousPoints = MatchPoint::where('video_id', $video->id)
|
$previousPoints = MatchPoint::where('match_round_id', $request->round_id)
|
||||||
->where('match_round_id', $request->round_id)
|
|
||||||
->where('timestamp_seconds', '<', $request->timestamp_seconds)
|
->where('timestamp_seconds', '<', $request->timestamp_seconds)
|
||||||
->orderBy('timestamp_seconds', 'desc')
|
->orderBy('timestamp_seconds', 'asc')
|
||||||
->first();
|
->pluck('points', 'competitor')
|
||||||
|
->toArray();
|
||||||
|
|
||||||
$scoreBlue = $previousPoints ? $previousPoints->score_blue : 0;
|
// Calculate cumulative scores by summing each point value
|
||||||
$scoreRed = $previousPoints ? $previousPoints->score_red : 0;
|
$scoreBlue = 0;
|
||||||
|
$scoreRed = 0;
|
||||||
|
|
||||||
|
if (isset($previousPoints['blue'])) {
|
||||||
|
$scoreBlue += $previousPoints['blue'];
|
||||||
|
}
|
||||||
|
if (isset($previousPoints['red'])) {
|
||||||
|
$scoreRed += $previousPoints['red'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add current point
|
||||||
if ($request->competitor === 'blue') {
|
if ($request->competitor === 'blue') {
|
||||||
$scoreBlue += $request->points;
|
$scoreBlue += $request->points;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -18,7 +18,7 @@ class VideoController extends Controller
|
|||||||
{
|
{
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->middleware('auth')->except(['index', 'show', 'search', 'stream']);
|
$this->middleware('auth')->except(['index', 'show', 'search', 'stream', 'trending', 'shorts']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function index()
|
public function index()
|
||||||
|
|||||||
@ -15,6 +15,11 @@ class MatchRound extends Model
|
|||||||
'video_id',
|
'video_id',
|
||||||
'round_number',
|
'round_number',
|
||||||
'name',
|
'name',
|
||||||
|
'start_time_seconds',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'start_time_seconds' => 'integer',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function video(): BelongsTo
|
public function video(): BelongsTo
|
||||||
|
|||||||
@ -57,7 +57,7 @@ return [
|
|||||||
|
|
||||||
'url' => env('APP_URL', 'http://localhost'),
|
'url' => env('APP_URL', 'http://localhost'),
|
||||||
|
|
||||||
'asset_url' => env('ASSET_URL'),
|
'asset_url' => env('APP_URL'),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@ -70,7 +70,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'timezone' => 'UTC',
|
'timezone' => 'Asia/Bahrain',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('match_rounds', function (Blueprint $table) {
|
||||||
|
if (! Schema::hasColumn('match_rounds', 'start_time_seconds')) {
|
||||||
|
$table->integer('start_time_seconds')->nullable()->after('name');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('match_rounds', function (Blueprint $table) {
|
||||||
|
if (Schema::hasColumn('match_rounds', 'start_time_seconds')) {
|
||||||
|
$table->dropColumn('start_time_seconds');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
66
resources/views/components/channel-row.blade.php
Normal file
66
resources/views/components/channel-row.blade.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<div class="channel-row">
|
||||||
|
<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" alt="{{ $video->user->name }}">
|
||||||
|
@else
|
||||||
|
<div class="channel-avatar"></div>
|
||||||
|
@endif
|
||||||
|
<div>
|
||||||
|
<div class="channel-name">{{ $video->user->name ?? 'Unknown' }}</div>
|
||||||
|
<div class="channel-subs">{{ number_format($video->user->subscriber_count ?? 0) }} subscribers</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<x-video-actions :video="$video" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Channel Row */
|
||||||
|
.channel-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 16px 0;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-avatar {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-subs {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.channel-row {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start !important;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-info {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
@props(['video'])
|
||||||
200
resources/views/components/video-actions.blade.php
Normal file
200
resources/views/components/video-actions.blade.php
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
@props(['video'])
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.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;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn:hover {
|
||||||
|
background: var(--border-color);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn svg,
|
||||||
|
.action-btn i {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.comment-btn {
|
||||||
|
background: var(--brand-red);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--brand-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn.liked {
|
||||||
|
color: var(--brand-red) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-action-dropdown .dropdown-item.liked {
|
||||||
|
color: var(--brand-red) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-action-dropdown {
|
||||||
|
display: none;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-action-dropdown .dropdown-menu {
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
min-width: 200px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 6px 0;
|
||||||
|
z-index: 1200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-action-dropdown .dropdown-item {
|
||||||
|
color: var(--text-primary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-action-dropdown .dropdown-item:hover {
|
||||||
|
background: var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 576px) {
|
||||||
|
.video-actions>.desktop-action {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-action-dropdown {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="video-actions" style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap; overflow: visible;">
|
||||||
|
@auth
|
||||||
|
@if (Auth::id() === $video->user_id)
|
||||||
|
<button class="action-btn desktop-action" onclick="openEditVideoModal({{ $video->id }})">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
<span>Edit</span>
|
||||||
|
</button>
|
||||||
|
@elseif (Auth::id() !== $video->user_id)
|
||||||
|
<button class="action-btn desktop-action"><i class="bi bi-bell"></i><span>Subscribe</span></button>
|
||||||
|
@endif
|
||||||
|
@else
|
||||||
|
<button onclick="window.location.href='{{ route('login') }}'" class="action-btn desktop-action"><i
|
||||||
|
class="bi bi-bell"></i><span>Subscribe</span></button>
|
||||||
|
@endauth
|
||||||
|
|
||||||
|
@auth
|
||||||
|
<form method="POST"
|
||||||
|
action="{{ $video->isLikedBy(Auth::user()) ? route('videos.unlike', $video->id) : route('videos.like', $video->id) }}"
|
||||||
|
class="d-inline desktop-action">
|
||||||
|
@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
|
||||||
|
<button onclick="window.location.href='{{ route('login') }}'" class="action-btn desktop-action">
|
||||||
|
<i class="bi bi-hand-thumbs-up"></i>
|
||||||
|
{{ $video->like_count > 0 ? number_format($video->like_count) : 'Like' }}
|
||||||
|
</button>
|
||||||
|
@endauth
|
||||||
|
|
||||||
|
@if ($video->isShareable())
|
||||||
|
<button class="action-btn desktop-action"
|
||||||
|
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 desktop-action" 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 class="dropdown mobile-action-dropdown">
|
||||||
|
<button class="action-btn dropdown-toggle" type="button" id="dropdownMenuLinkMusic{{ $video->id }}"
|
||||||
|
data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="bi bi-lightning-charge-fill"></i>
|
||||||
|
<span>Action</span>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu" aria-labelledby="dropdownMenuLinkMusic{{ $video->id }}">
|
||||||
|
@auth
|
||||||
|
@if (Auth::id() !== $video->user_id)
|
||||||
|
<button type="button" class="dropdown-item">
|
||||||
|
<i class="bi bi-bell"></i> Subscribe
|
||||||
|
</button>
|
||||||
|
@else
|
||||||
|
<button type="button" class="dropdown-item" onclick="openEditVideoModal({{ $video->id }})">
|
||||||
|
<i class="bi bi-pencil"></i> Edit
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
@else
|
||||||
|
<button class="dropdown-item" onclick="window.location.href='{{ route('login') }}'">
|
||||||
|
<i class="bi bi-bell"></i> Subscribe
|
||||||
|
</button>
|
||||||
|
@endauth
|
||||||
|
|
||||||
|
@auth
|
||||||
|
<form method="POST"
|
||||||
|
action="{{ $video->isLikedBy(Auth::user()) ? route('videos.unlike', $video->id) : route('videos.like', $video->id) }}"
|
||||||
|
class="d-inline w-100">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="dropdown-item {{ $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
|
||||||
|
<button class="dropdown-item" onclick="window.location.href='{{ route('login') }}'">
|
||||||
|
<i class="bi bi-hand-thumbs-up"></i>
|
||||||
|
{{ $video->like_count > 0 ? number_format($video->like_count) : 'Like' }}
|
||||||
|
</button>
|
||||||
|
@endauth
|
||||||
|
|
||||||
|
@if ($video->isShareable())
|
||||||
|
<button class="dropdown-item"
|
||||||
|
onclick="openShareModal('{{ $video->share_url }}', '{{ addslashes($video->title) }}')">
|
||||||
|
<i class="bi bi-share"></i> Share
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<button class="dropdown-item" onclick="openAddToPlaylistModal({{ $video->id }})">
|
||||||
|
<i class="bi bi-bookmark"></i> Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
150
resources/views/components/video-comments.blade.php
Normal file
150
resources/views/components/video-comments.blade.php
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
<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{{ $video->id }}" 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 comment-btn" onclick="submitComment({{ $video->id }})"
|
||||||
|
style="flex-shrink: 0;">
|
||||||
|
<span>Send</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{{ $video->id }}">
|
||||||
|
@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>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function submitComment(videoId) {
|
||||||
|
const body = document.getElementById('commentBody' + videoId).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(response => response.json())
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('Failed to post comment: ' + error);
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if (data & amp; & amp; data.success) {
|
||||||
|
document.getElementById('commentBody' + videoId).value = '';
|
||||||
|
addCommentToList(data.comment, videoId);
|
||||||
|
} else {
|
||||||
|
alert('Failed to post comment');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCommentToList(comment, videoId) {
|
||||||
|
const commentsList = document.getElementById('commentsList' + videoId);
|
||||||
|
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 = commentsList.closest('.comments-section').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',
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
document.getElementById('comment-' + commentId).remove();
|
||||||
|
// Update comment count for all comment sections (simplified)
|
||||||
|
document.querySelectorAll('.comments-section h3 span').forEach(span => {
|
||||||
|
const count = parseInt(span.textContent.match(/\\((\\d+)\\)/)?.[2] || 0) - 1;
|
||||||
|
span.textContent = `(${Math.max(0, 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) {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,46 +1,69 @@
|
|||||||
<div class="comment-item" style="display: flex; gap: 12px; margin-bottom: 16px;" id="comment-{{ $comment->id }}">
|
<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 }}">
|
<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="flex: 1;">
|
||||||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px;">
|
<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="font-weight: 600; font-size: 14px;">{{ $comment->user->name }}</span>
|
||||||
<span style="color: var(--text-secondary); font-size: 12px;">{{ $comment->created_at->diffForHumans() }}</span>
|
<span
|
||||||
|
style="color: var(--text-secondary); font-size: 12px;">{{ $comment->created_at->diffForHumans() }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="comment-body" style="font-size: 14px; line-height: 1.5; word-wrap: break-word;">
|
<div class="comment-body" style="font-size: 14px; line-height: 1.5; word-wrap: break-word;">
|
||||||
{{ $comment->body }}
|
{{ $comment->body }}
|
||||||
</div>
|
</div>
|
||||||
|
@auth
|
||||||
|
@if (Auth::id() === $comment->user_id)
|
||||||
|
<div id="commentEditWrap{{ $comment->id }}" style="display:none; margin-top:8px;">
|
||||||
|
<textarea id="commentEditInput{{ $comment->id }}" class="form-control" rows="3"
|
||||||
|
style="background: var(--bg-secondary); border: 1px solid var(--border-color); color: var(--text-primary); border-radius: 8px; padding: 8px; width: 100%; resize: vertical; font-size: 14px;">{{ $comment->body }}</textarea>
|
||||||
|
<div style="display:flex; gap:8px; justify-content:flex-end; margin-top:8px;">
|
||||||
|
<button type="button" class="yt-action-btn" style="font-size: 12px; padding: 6px 12px;"
|
||||||
|
onclick="cancelEditComment({{ $comment->id }})">Cancel</button>
|
||||||
|
<button type="button" class="yt-action-btn"
|
||||||
|
style="background: var(--brand-red); color: white; font-size: 12px; padding: 6px 12px;"
|
||||||
|
onclick="saveEditComment({{ $comment->id }})">Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@endauth
|
||||||
<div style="display: flex; gap: 12px; margin-top: 8px;">
|
<div style="display: flex; gap: 12px; margin-top: 8px;">
|
||||||
@auth
|
@auth
|
||||||
<button onclick="toggleReplyForm({{ $comment->id }})" style="background: none; border: none; color: var(--text-secondary); font-size: 12px; font-weight: 600; cursor: pointer; padding: 0;">
|
<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
|
Reply
|
||||||
</button>
|
</button>
|
||||||
@if(Auth::id() === $comment->user_id)
|
@if (Auth::id() === $comment->user_id)
|
||||||
<button onclick="deleteComment({{ $comment->id }})" style="background: none; border: none; color: var(--text-secondary); font-size: 12px; font-weight: 600; cursor: pointer; padding: 0;">
|
<button onclick="startEditComment({{ $comment->id }})"
|
||||||
|
style="background: none; border: none; color: var(--text-secondary); font-size: 12px; font-weight: 600; cursor: pointer; padding: 0;">
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
<button onclick="deleteComment({{ $comment->id }})"
|
||||||
|
style="background: none; border: none; color: var(--text-secondary); font-size: 12px; font-weight: 600; cursor: pointer; padding: 0;">
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
@endif
|
@endif
|
||||||
@endauth
|
@endauth
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Reply Form -->
|
<!-- Reply Form -->
|
||||||
<div id="replyForm{{ $comment->id }}" style="display: none; margin-top: 12px;">
|
<div id="replyForm{{ $comment->id }}" style="display: none; margin-top: 12px;">
|
||||||
<div style="display: flex; gap: 8px;">
|
<div style="display: flex; gap: 8px;">
|
||||||
<textarea
|
<textarea class="form-control" placeholder="Write a reply..." rows="2"
|
||||||
class="form-control"
|
style="background: var(--bg-secondary); border: 1px solid var(--border-color); color: var(--text-primary); border-radius: 8px; padding: 8px; width: 100%; resize: none; font-size: 14px;"></textarea>
|
||||||
placeholder="Write a reply..."
|
|
||||||
rows="2"
|
|
||||||
style="background: var(--bg-secondary); border: 1px solid var(--border-color); color: var(--text-primary); border-radius: 8px; padding: 8px; width: 100%; resize: none; font-size: 14px;"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
</div>
|
||||||
<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" style="font-size: 12px; padding: 6px 12px;" onclick="toggleReplyForm({{ $comment->id }})">Cancel</button>
|
<button type="button" class="yt-action-btn" style="font-size: 12px; padding: 6px 12px;"
|
||||||
<button type="button" class="yt-action-btn" style="background: var(--brand-red); color: white; font-size: 12px; padding: 6px 12px;" onclick="submitReply({{ $video->id ?? $comment->video_id }}, {{ $comment->id }})">Reply</button>
|
onclick="toggleReplyForm({{ $comment->id }})">Cancel</button>
|
||||||
|
<button type="button" class="yt-action-btn"
|
||||||
|
style="background: var(--brand-red); color: white; font-size: 12px; padding: 6px 12px;"
|
||||||
|
onclick="submitReply({{ $video->id ?? $comment->video_id }}, {{ $comment->id }})">Reply</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Replies -->
|
<!-- Replies -->
|
||||||
@if($comment->replies && $comment->replies->count() > 0)
|
@if ($comment->replies && $comment->replies->count() > 0)
|
||||||
<div style="margin-left: 24px; margin-top: 12px; border-left: 2px solid var(--border-color); padding-left: 12px;">
|
<div
|
||||||
@foreach($comment->replies as $reply)
|
style="margin-left: 24px; margin-top: 12px; border-left: 2px solid var(--border-color); padding-left: 12px;">
|
||||||
|
@foreach ($comment->replies as $reply)
|
||||||
@include('videos.partials.comment', ['comment' => $reply, 'video' => $video ?? null])
|
@include('videos.partials.comment', ['comment' => $reply, 'video' => $video ?? null])
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@ -49,8 +72,64 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function toggleReplyForm(commentId) {
|
function toggleReplyForm(commentId) {
|
||||||
const form = document.getElementById('replyForm' + commentId);
|
const form = document.getElementById('replyForm' + commentId);
|
||||||
form.style.display = form.style.display === 'none' ? 'block' : 'none';
|
form.style.display = form.style.display === 'none' ? 'block' : 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function startEditComment(commentId) {
|
||||||
|
const wrap = document.getElementById('commentEditWrap' + commentId);
|
||||||
|
const body = document.querySelector('#comment-' + commentId + ' .comment-body');
|
||||||
|
const input = document.getElementById('commentEditInput' + commentId);
|
||||||
|
if (!wrap || !body || !input) return;
|
||||||
|
|
||||||
|
input.value = body.textContent.trim();
|
||||||
|
wrap.style.display = 'block';
|
||||||
|
body.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelEditComment(commentId) {
|
||||||
|
const wrap = document.getElementById('commentEditWrap' + commentId);
|
||||||
|
const body = document.querySelector('#comment-' + commentId + ' .comment-body');
|
||||||
|
if (wrap) wrap.style.display = 'none';
|
||||||
|
if (body) body.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveEditComment(commentId) {
|
||||||
|
const input = document.getElementById('commentEditInput' + commentId);
|
||||||
|
const bodyEl = document.querySelector('#comment-' + commentId + ' .comment-body');
|
||||||
|
const wrap = document.getElementById('commentEditWrap' + commentId);
|
||||||
|
if (!input || !bodyEl || !wrap) return;
|
||||||
|
|
||||||
|
const body = input.value.trim();
|
||||||
|
if (!body) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/comments/${commentId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
body
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
if (res.ok) {
|
||||||
|
bodyEl.textContent = data.body || body;
|
||||||
|
bodyEl.dataset.timeEnhanced = '';
|
||||||
|
if (typeof enhanceCommentBodyWithTimeBadges === 'function') {
|
||||||
|
enhanceCommentBodyWithTimeBadges(document.getElementById('comment-' + commentId));
|
||||||
|
}
|
||||||
|
wrap.style.display = 'none';
|
||||||
|
bodyEl.style.display = 'block';
|
||||||
|
} else {
|
||||||
|
alert(data.error || 'Failed to update comment');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
alert('Failed to update comment: ' + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
1116
resources/views/videos/types/backup/generic.blade.php
Normal file
1116
resources/views/videos/types/backup/generic.blade.php
Normal file
File diff suppressed because it is too large
Load Diff
4184
resources/views/videos/types/backup/match.blade.php
Normal file
4184
resources/views/videos/types/backup/match.blade.php
Normal file
File diff suppressed because it is too large
Load Diff
1069
resources/views/videos/types/backup/music.blade.php
Normal file
1069
resources/views/videos/types/backup/music.blade.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -210,75 +210,6 @@
|
|||||||
color: var(--text-secondary);
|
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 */
|
||||||
.channel-row {
|
.channel-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -310,18 +241,6 @@
|
|||||||
color: var(--text-secondary);
|
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 */
|
/* Description */
|
||||||
.video-description {
|
.video-description {
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
@ -546,76 +465,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Channel Row - All in one line -->
|
<x-channel-row :video="$video" />
|
||||||
<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 -->
|
<!-- Description Box -->
|
||||||
@if ($video->description)
|
@if ($video->description)
|
||||||
@ -700,16 +550,15 @@
|
|||||||
<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; display: flex; align-items: center; gap: 8px;">
|
<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"
|
<textarea id="commentBody" class="form-control" placeholder="Add a comment... Use @ to mention someone" rows="1"
|
||||||
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>
|
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"
|
<button type="button" class="action-btn"
|
||||||
onclick="document.getElementById('commentBody').value = ''" style="flex-shrink: 0;">
|
onclick="document.getElementById('commentBody').value = ''" style="flex-shrink: 0;">
|
||||||
<i class="bi bi-x-lg"></i>
|
<i class="bi bi-x-lg"></i>
|
||||||
<span>Cancel</span>
|
<span>Cancel</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="action-btn comment-btn"
|
<button type="button" class="action-btn comment-btn" onclick="submitComment({{ $video->id }})"
|
||||||
onclick="submitComment({{ $video->id }})" style="flex-shrink: 0;">
|
style="flex-shrink: 0;">
|
||||||
<i class="bi bi-chat-dots"></i>
|
<i class="bi bi-chat-dots"></i>
|
||||||
<span>Comment</span>
|
<span>Comment</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
3515
resources/views/videos/types/match.blade copy.php
Normal file
3515
resources/views/videos/types/match.blade copy.php
Normal file
File diff suppressed because it is too large
Load Diff
9
resources/views/videos/types/match.blade.backup.php
Normal file
9
resources/views/videos/types/match.blade.backup.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
@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
3615
resources/views/videos/types/match.blade.php.bak_20260316_025638
Normal file
3615
resources/views/videos/types/match.blade.php.bak_20260316_025638
Normal file
File diff suppressed because it is too large
Load Diff
3682
resources/views/videos/types/match.blade.pre_coach_review_backup.php
Normal file
3682
resources/views/videos/types/match.blade.pre_coach_review_backup.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -210,81 +210,13 @@
|
|||||||
color: var(--text-secondary);
|
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 */
|
||||||
.channel-row {
|
.channel-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 16px 0;
|
padding: 16px 0;
|
||||||
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-info {
|
.channel-info {
|
||||||
@ -310,18 +242,6 @@
|
|||||||
color: var(--text-secondary);
|
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 */
|
/* Description */
|
||||||
.video-description {
|
.video-description {
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
@ -432,8 +352,16 @@
|
|||||||
|
|
||||||
.video-actions {
|
.video-actions {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow-x: auto;
|
justify-content: flex-end;
|
||||||
justify-content: flex-start;
|
}
|
||||||
|
|
||||||
|
.video-actions>.desktop-action {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-action-dropdown {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.yt-main {
|
.yt-main {
|
||||||
@ -546,76 +474,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Channel Row - All in one line -->
|
<x-channel-row :video="$video" />
|
||||||
<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 -->
|
<!-- Description Box -->
|
||||||
@if ($video->description)
|
@if ($video->description)
|
||||||
@ -687,84 +546,45 @@
|
|||||||
</style>
|
</style>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<!-- Comment Section -->
|
<x-video-comments :video="$video" />
|
||||||
<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>
|
|
||||||
|
|
||||||
<script>
|
</div>
|
||||||
function submitComment(videoId) {
|
|
||||||
const body = document.getElementById('commentBody').value.trim();
|
|
||||||
if (!body) return;
|
|
||||||
|
|
||||||
fetch(`/videos/${videoId}/comments`, {
|
<script>
|
||||||
method: 'POST',
|
function submitComment(videoId) {
|
||||||
headers: {
|
const body = document.getElementById('commentBody').value.trim();
|
||||||
'Content-Type': 'application/json',
|
if (!body) return;
|
||||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
|
||||||
},
|
fetch(`/videos/${videoId}/comments`, {
|
||||||
body: JSON.stringify({
|
method: 'POST',
|
||||||
body: body
|
headers: {
|
||||||
})
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
body: body
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
})
|
||||||
.catch(error => {
|
.then(response => response.json())
|
||||||
console.error('Error:', error);
|
.catch(error => {
|
||||||
alert('Failed to post comment: ' + error);
|
console.error('Error:', error);
|
||||||
})
|
alert('Failed to post comment: ' + error);
|
||||||
.then(data => {
|
})
|
||||||
if (data && data.success) {
|
.then(data => {
|
||||||
document.getElementById('commentBody').value = '';
|
if (data && data.success) {
|
||||||
addCommentToList(data.comment);
|
document.getElementById('commentBody').value = '';
|
||||||
} else {
|
addCommentToList(data.comment);
|
||||||
alert('Failed to post comment');
|
} else {
|
||||||
}
|
alert('Failed to post comment');
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function addCommentToList(comment) {
|
function addCommentToList(comment) {
|
||||||
const commentsList = document.getElementById('commentsList');
|
const commentsList = document.getElementById('commentsList');
|
||||||
const commentHtml = `
|
const commentHtml = `
|
||||||
<div class="comment-item" style="display: flex; gap: 12px; margin-bottom: 16px;" id="comment-${comment.id}">
|
<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}">
|
<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="flex: 1;">
|
||||||
@ -783,177 +603,176 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
commentsList.insertAdjacentHTML('afterbegin', commentHtml);
|
commentsList.insertAdjacentHTML('afterbegin', commentHtml);
|
||||||
const commentCount = document.querySelector('h3 span');
|
const commentCount = document.querySelector('h3 span');
|
||||||
if (commentCount) {
|
if (commentCount) {
|
||||||
const count = parseInt(commentCount.textContent.match(/\\((\\d+)\\)/)?.[2] || 0) + 1;
|
const count = parseInt(commentCount.textContent.match(/\\((\\d+)\\)/)?.[2] || 0) + 1;
|
||||||
commentCount.textContent = `(${count})`;
|
commentCount.textContent = `(${count})`;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function deleteComment(commentId) {
|
function deleteComment(commentId) {
|
||||||
if (confirm('Are you sure you want to delete this comment?')) {
|
if (confirm('Are you sure you want to delete this comment?')) {
|
||||||
fetch(`/comments/${commentId}`, {
|
fetch(`/comments/${commentId}`, {
|
||||||
method: 'DELETE',
|
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: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
},
|
}
|
||||||
body: JSON.stringify({
|
|
||||||
body: body,
|
|
||||||
parent_id: parentId
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.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})`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
}
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Sidebar - Up Next / Recommendations -->
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
<div class="yt-sidebar-container">
|
const commentTexts = document.querySelectorAll('.comment-body');
|
||||||
@if ($playlist && $playlistVideos && $playlistVideos->count() > 0)
|
commentTexts.forEach(text => {
|
||||||
<h3 style="font-size: 16px; font-weight: 500; margin-bottom: 12px;">
|
const html = text.innerHTML.replace(/@(\w+)/g,
|
||||||
<i class="bi bi-collection-play" style="margin-right: 8px;"></i>
|
'<span style="color: #3ea6ff; font-weight: 500;">@$1</span>');
|
||||||
{{ $playlist->name }}
|
text.innerHTML = html;
|
||||||
<span
|
});
|
||||||
style="font-weight: 400; color: var(--text-secondary); font-size: 14px;">({{ $playlistVideos->count() }}
|
});
|
||||||
videos)</span>
|
|
||||||
</h3>
|
function submitReply(videoId, parentId) {
|
||||||
<div class="recommended-videos-list">
|
const textarea = document.querySelector(`#replyForm${parentId} textarea`);
|
||||||
@foreach ($playlistVideos as $index => $playlistVideo)
|
const body = textarea.value.trim();
|
||||||
@if ($playlistVideo->id !== $video->id)
|
if (!body) return;
|
||||||
<div class="sidebar-video-card{{ $playlistVideo->id === $video->id ? ' current-video' : '' }}"
|
|
||||||
onclick="window.location.href='{{ route('videos.show', $playlistVideo->id) }}?playlist={{ $playlist->id }}'">
|
fetch(`/videos/${videoId}/comments`, {
|
||||||
<div class="sidebar-thumb" style="position: relative;">
|
method: 'POST',
|
||||||
@if ($playlistVideo->thumbnail)
|
headers: {
|
||||||
<img src="{{ asset('storage/thumbnails/' . $playlistVideo->thumbnail) }}"
|
'Content-Type': 'application/json',
|
||||||
alt="{{ $playlistVideo->title }}">
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
@else
|
},
|
||||||
<img src="https://picsum.photos/seed/{{ $playlistVideo->id }}/320/180"
|
body: JSON.stringify({
|
||||||
alt="{{ $playlistVideo->title }}">
|
body: body,
|
||||||
@endif
|
parent_id: parentId
|
||||||
@if ($playlistVideo->duration)
|
})
|
||||||
<span
|
})
|
||||||
class="yt-video-duration">{{ gmdate('i:s', $playlistVideo->duration) }}</span>
|
.then(response => response.json())
|
||||||
@endif
|
.then(data => {
|
||||||
@if ($playlistVideo->is_shorts)
|
if (data.success) {
|
||||||
<span class="yt-shorts-badge"
|
location.reload();
|
||||||
style="position: absolute; top: 8px; left: 8px; font-size: 10px; padding: 2px 6px;">
|
}
|
||||||
<i class="bi bi-collection-play-fill"></i> SHORTS
|
});
|
||||||
</span>
|
}
|
||||||
@endif
|
</script>
|
||||||
<!-- Playlist position indicator -->
|
</div>
|
||||||
<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;">
|
<!-- Sidebar - Up Next / Recommendations -->
|
||||||
{{ $index + 1 }}
|
<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>
|
</span>
|
||||||
|
@endif
|
||||||
|
<!-- Playlist position indicator -->
|
||||||
|
<span
|
||||||
|
style="position: absolute; bottom: 4px; left: 4px; background: rgba(0,0,0,0.8); color: white; padding: 2px 6px; border-radius: 4px; font-size: 12px; font-weight: 500;">
|
||||||
|
{{ $index + 1 }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar-info">
|
||||||
|
<div class="sidebar-title">
|
||||||
|
<i class="bi {{ match ($playlistVideo->type) {'music' => 'bi-music-note','match' => 'bi-trophy',default => 'bi-film'} }}"
|
||||||
|
style="color: #ef4444; margin-right: 4px; font-size: 12px;"></i>
|
||||||
|
{{ Str::limit($playlistVideo->title, 60) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="sidebar-info">
|
<div class="sidebar-meta">
|
||||||
<div class="sidebar-title">
|
<div>{{ $playlistVideo->user->name ?? 'Unknown' }}</div>
|
||||||
<i class="bi {{ match ($playlistVideo->type) {'music' => 'bi-music-note','match' => 'bi-trophy',default => 'bi-film'} }}"
|
<div>{{ number_format($playlistVideo->view_count) }} views •
|
||||||
style="color: #ef4444; margin-right: 4px; font-size: 12px;"></i>
|
{{ $playlistVideo->created_at->diffForHumans() }}</div>
|
||||||
{{ Str::limit($playlistVideo->title, 60) }}
|
|
||||||
</div>
|
|
||||||
<div class="sidebar-meta">
|
|
||||||
<div>{{ $playlistVideo->user->name ?? 'Unknown' }}</div>
|
|
||||||
<div>{{ number_format($playlistVideo->view_count) }} views •
|
|
||||||
{{ $playlistVideo->created_at->diffForHumans() }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
</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)
|
||||||
|
<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
|
@endforeach
|
||||||
</div>
|
</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
|
@else
|
||||||
<h3 style="font-size: 16px; font-weight: 500; margin-bottom: 12px;">Up Next</h3>
|
<div class="text-secondary">No recommendations available yet. Check back later!</div>
|
||||||
@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>
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -133,5 +133,3 @@ Route::delete('/points/{point}', [MatchEventController::class, 'destroyPoint'])-
|
|||||||
Route::post('/videos/{video}/reviews', [MatchEventController::class, 'storeReview'])->name('match.storeReview');
|
Route::post('/videos/{video}/reviews', [MatchEventController::class, 'storeReview'])->name('match.storeReview');
|
||||||
Route::put('/reviews/{review}', [MatchEventController::class, 'updateReview'])->name('match.updateReview');
|
Route::put('/reviews/{review}', [MatchEventController::class, 'updateReview'])->name('match.updateReview');
|
||||||
Route::delete('/reviews/{review}', [MatchEventController::class, 'destroyReview'])->name('match.destroyReview');
|
Route::delete('/reviews/{review}', [MatchEventController::class, 'destroyReview'])->name('match.destroyReview');
|
||||||
|
|
||||||
// });
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user