ghassan 0b2e95ea65 Add NAS file manager integration and all pending platform changes
- Installed p7h/nas-file-manager package via private VCS repo
- Published config/nas-file-manager.php with super_admin middleware restriction
- Added NAS env vars to .env.example
- Created admin/nas-storage page with connection info panel and file browser widget
- Added NAS Storage link to admin sidebar (super_admin only)
- Added SuperAdminController@nasStorage method and admin.nas-storage route
- Includes all accumulated branch changes: profile wall, 2FA, audit logs,
  settings panel, country/phone/timezone components, posts, slideshow,
  playlist shares, video downloads/shares, comment likes, notifications,
  social links, and more

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 13:24:32 +03:00

1106 lines
32 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@props(['video' => null, 'size' => 'medium'])
@php
$videoUrl = $video ? asset('storage/videos/' . $video->filename) : null;
$thumbnailUrl = $video && $video->thumbnail
? asset('storage/thumbnails/' . $video->thumbnail)
: ($video ? 'https://picsum.photos/seed/' . $video->id . '/640/360' : 'https://picsum.photos/seed/random/640/360');
$typeIcon = $video ? match($video->type) {
'music' => 'bi-music-note',
'match' => 'bi-trophy',
default => 'bi-film',
} : 'bi-film';
// Check if video is shorts
$isShorts = $video && $video->isShorts();
// Check if current user is the owner of the video
$isOwner = $video && auth()->check() && auth()->id() == $video->user_id;
// Size classes
$sizeClasses = match($size) {
'small' => 'yt-video-card-sm',
default => '',
};
@endphp
<div class="yt-video-card {{ $sizeClasses }}" data-video-url="{{ $videoUrl }}">
<a href="{{ $video ? route('videos.show', $video) : '#' }}">
<div class="yt-video-thumb" onmouseenter="playVideo(this)" onmouseleave="stopVideo(this)"
data-audio="{{ $video && $video->isAudioOnly() ? 'true' : 'false' }}">
<img src="{{ $thumbnailUrl }}" alt="{{ $video->title ?? 'Video' }}">
@if($videoUrl)
<video preload="none">
<source src="{{ $videoUrl }}" type="{{ $video->mime_type ?? 'video/mp4' }}">
</video>
@endif
{{-- Equalizer overlay shown when audio-only track is previewing --}}
<div class="audio-preview-overlay">
<div class="audio-eq">
<span></span><span></span><span></span><span></span><span></span>
</div>
</div>
@if($video && $video->duration)
<span class="yt-video-duration">{{ gmdate('i:s', $video->duration) }}</span>
@endif
@if($isShorts)
<span class="yt-shorts-badge">
<i class="bi bi-collection-play-fill"></i> SHORTS
</span>
@endif
@if($isOwner && $video->visibility === 'private')
<span class="yt-visibility-badge yt-visibility-private">
<i class="bi bi-lock-fill"></i> Private
</span>
@elseif($isOwner && $video->visibility === 'unlisted')
<span class="yt-visibility-badge yt-visibility-unlisted">
<i class="bi bi-link-45deg"></i> Unlisted
</span>
@endif
</div>
</a>
<div class="yt-video-info">
<a href="{{ $video && $video->user ? route('channel', $video->user->channel) : '#' }}"
class="yt-channel-icon" onclick="event.stopPropagation();">
@if($video && $video->user && $video->user->avatar_url)
<img src="{{ $video->user->avatar_url }}" alt="{{ $video->user->name }}" style="width: 100%; height: 100%; object-fit: cover; border-radius: 50%;">
@endif
</a>
<div class="yt-video-details">
<h3 class="yt-video-title">
<a href="{{ $video ? route('videos.show', $video) : '#' }}">
{{ $video->title ?? 'Untitled Video' }}
</a>
</h3>
@if($video && $video->user)
<a href="{{ route('channel', $video->user->channel) }}"
class="yt-channel-name" onclick="event.stopPropagation();">{{ $video->user->name }}</a>
@endif
@if($video)
<div class="yt-video-meta">
@if($video->type && $video->type !== 'generic')
<span class="yt-type-label yt-type-{{ $video->type }}">
<i class="bi {{ $typeIcon }}"></i>
{{ ucfirst($video->type === 'match' ? 'Sports' : $video->type) }}
</span>
&nbsp;·&nbsp;
@endif
{{ number_format($video->view_count) }} views · {{ $video->created_at->diffForHumans() }}
</div>
@endif
</div>
@if($video)
<div class="position-relative">
<button class="yt-more-btn" type="button" data-bs-toggle="dropdown" data-bs-auto-close="true" aria-expanded="false">
<i class="bi bi-three-dots-vertical"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end dropdown-menu-dark">
@if($isOwner)
<li>
<a class="dropdown-item" href="javascript:void(0)" onclick="openEditVideoModal('{{ $video->getRouteKey() }}')">
<i class="bi bi-pencil"></i> Edit
</a>
</li>
<li>
<button type="button" class="dropdown-item text-danger" onclick="showDeleteModal('{{ $video->getRouteKey() }}', {{ json_encode($video->title) }})">
<i class="bi bi-trash"></i> Delete
</button>
</li>
<li><hr class="dropdown-divider"></li>
@endif
<li>
<a class="dropdown-item" href="javascript:void(0)" onclick="addToQueue('{{ $video->getRouteKey() }}')">
<i class="bi bi-list-nested"></i> Add to queue
</a>
</li>
<li>
<a class="dropdown-item" href="javascript:void(0)" onclick="saveToWatchLater('{{ $video->getRouteKey() }}')">
<i class="bi bi-clock"></i> Save to Watch later
</a>
</li>
<li>
<a class="dropdown-item" href="javascript:void(0)" onclick="openPlaylistModal('{{ $video->getRouteKey() }}')">
<i class="bi bi-bookmark"></i> Save to playlist
</a>
</li>
@if($video->allow_download)
<li>
<a class="dropdown-item" href="{{ route('videos.download', $video) }}">
<i class="bi bi-download"></i> Download
</a>
</li>
@endif
@if($video->isShareable())
<li>
<a class="dropdown-item" href="javascript:void(0)" onclick="openShareModal('{{ $video->share_url }}', '{{ addslashes($video->title) }}', '{{ route('videos.recordShare', $video) }}')">
<i class="bi bi-share"></i> Share
</a>
</li>
@endif
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item" href="javascript:void(0)" onclick="notInterested('{{ $video->getRouteKey() }}')">
<i class="bi bi-dash-circle"></i> Not interested
</a>
</li>
<li>
<a class="dropdown-item" href="javascript:void(0)" onclick="dontRecommendChannel('{{ $video->user_id }}')">
<i class="bi bi-x-circle"></i> Don't recommend channel
</a>
</li>
<li>
<a class="dropdown-item" href="javascript:void(0)" onclick="reportVideo('{{ $video->getRouteKey() }}')">
<i class="bi bi-flag"></i> Report
</a>
</li>
</ul>
</div>
@endif
</div>
</div>
@php $vk = $video->getRouteKey() ?? ''; @endphp
<!-- Cute Edit Video Modal -->
<div class="modal fade" id="editVideoModal{{ $vk }}" tabindex="-1" aria-labelledby="editVideoModalLabel{{ $vk }}" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered cute-edit-modal">
<div class="modal-content cute-edit-content">
<div class="cute-edit-header">
<span class="cute-edit-icon">✏️</span>
<h5>Edit Video</h5>
<button type="button" class="btn-close-cute" onclick="closeEditVideoModal('{{ $vk }}')">×</button>
</div>
<div class="cute-edit-body">
<form id="edit-video-form-{{ $vk }}" enctype="multipart/form-data">
@csrf
@method('PUT')
<!-- Title -->
<div class="cute-form-group">
<label><i class="bi bi-card-heading"></i> Title</label>
<input type="text" name="title" id="edit-title-{{ $vk }}" class="cute-input" placeholder="Video title">
</div>
<!-- Description -->
<div class="cute-form-group">
<label><i class="bi bi-text-paragraph"></i> Description</label>
<textarea name="description" id="edit-description-{{ $vk }}" class="cute-textarea" rows="2" placeholder="Tell viewers about your video"></textarea>
</div>
<!-- Video Type -->
<div class="cute-form-group">
<label><i class="bi bi-collection-play"></i> Type</label>
<div class="cute-type-options">
<label class="cute-type-option active" data-type="generic">
<input type="radio" name="type" value="generic" checked>
<span>🎬 Generic</span>
</label>
<label class="cute-type-option" data-type="music">
<input type="radio" name="type" value="music">
<span>🎵 Music</span>
</label>
<label class="cute-type-option" data-type="match">
<input type="radio" name="type" value="match">
<span>🏆 Match</span>
</label>
</div>
</div>
<!-- Shorts Toggle -->
<div class="cute-form-group">
<label><i class="bi bi-lightning-charge-fill"></i> Shorts</label>
<label class="cute-shorts-toggle">
<input type="checkbox" name="is_shorts" id="edit-is-shorts-{{ $vk }}" value="1">
<span class="cute-shorts-slider"></span>
<span class="cute-shorts-label">Mark as Short</span>
</label>
</div>
<!-- Thumbnail -->
<div class="cute-form-group">
<label><i class="bi bi-image"></i> Thumbnail</label>
<div class="cute-thumbnail-upload" onclick="document.getElementById('edit-thumbnail-{{ $vk }}').click()">
<input type="file" name="thumbnail" id="edit-thumbnail-{{ $vk }}" accept="image/*" hidden>
<div class="cute-thumbnail-preview" id="thumbnail-preview-{{ $vk }}">
<i class="bi bi-camera"></i>
<span>Click to change</span>
</div>
</div>
</div>
<!-- Privacy -->
<div class="cute-form-group">
<label><i class="bi bi-shield-lock"></i> Privacy</label>
<div class="cute-privacy-options">
<label class="cute-privacy-option active" data-privacy="public">
<input type="radio" name="visibility" value="public" checked>
<span>🌐 Public</span>
</label>
<label class="cute-privacy-option" data-privacy="unlisted">
<input type="radio" name="visibility" value="unlisted">
<span>🔗 Unlisted</span>
</label>
<label class="cute-privacy-option" data-privacy="private">
<input type="radio" name="visibility" value="private">
<span>🔒 Private</span>
</label>
</div>
</div>
<!-- Status -->
<div class="cute-status" id="edit-status-{{ $vk }}"></div>
<!-- Buttons -->
<div class="cute-edit-actions">
<button type="button" class="cute-btn-cancel" onclick="closeEditVideoModal('{{ $vk }}')">Cancel</button>
<button type="submit" class="cute-btn-save">Save</button>
</div>
</form>
</div>
</div>
</div>
</div>
<style>
/* Base styles for video card */
.yt-video-card {
cursor: pointer;
}
.yt-video-card .yt-video-thumb {
position: relative;
aspect-ratio: 16/9;
border-radius: 12px;
overflow: hidden;
background: #1a1a1a;
}
.yt-video-card .yt-video-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
position: absolute;
top: 0;
left: 0;
transition: transform 0.2s ease;
}
.yt-video-card:hover .yt-video-thumb img {
transform: scale(1.03);
}
.yt-video-card .yt-video-thumb video {
width: 100%;
height: 100%;
object-fit: contain;
position: absolute;
top: 0;
left: 0;
opacity: 0;
transition: opacity 0.3s ease;
background: #000;
}
.yt-video-card .yt-video-thumb video.active {
opacity: 1;
}
/* Audio preview overlay (equalizer) */
.audio-preview-overlay {
position: absolute;
inset: 0;
display: flex;
align-items: flex-end;
justify-content: center;
padding-bottom: 14px;
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.yt-video-thumb.audio-playing .audio-preview-overlay {
opacity: 1;
}
.audio-eq {
display: flex;
align-items: flex-end;
gap: 3px;
height: 24px;
background: rgba(0,0,0,0.55);
padding: 4px 8px;
border-radius: 20px;
}
.audio-eq span {
display: block;
width: 3px;
border-radius: 2px;
background: #e61e1e;
animation: eq-bar 0.8s ease-in-out infinite;
}
.audio-eq span:nth-child(1) { height: 6px; animation-delay: 0s; }
.audio-eq span:nth-child(2) { height: 14px; animation-delay: 0.15s; }
.audio-eq span:nth-child(3) { height: 20px; animation-delay: 0.05s; }
.audio-eq span:nth-child(4) { height: 12px; animation-delay: 0.2s; }
.audio-eq span:nth-child(5) { height: 8px; animation-delay: 0.1s; }
@keyframes eq-bar {
0%, 100% { transform: scaleY(0.4); }
50% { transform: scaleY(1); }
}
.yt-video-card .yt-video-duration {
position: absolute;
bottom: 8px;
right: 8px;
background: rgba(0,0,0,0.8);
color: white;
padding: 3px 6px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
.yt-video-card .yt-shorts-badge {
position: absolute;
top: 8px;
left: 8px;
background: rgba(230, 30, 30, 0.9);
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 11px;
font-weight: 700;
display: flex;
align-items: center;
gap: 4px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.yt-video-card .yt-shorts-badge i {
font-size: 12px;
}
.yt-video-card .yt-visibility-badge {
position: absolute;
bottom: 8px;
left: 8px;
padding: 3px 7px;
border-radius: 4px;
font-size: 11px;
font-weight: 600;
display: flex;
align-items: center;
gap: 4px;
pointer-events: none;
}
.yt-video-card .yt-visibility-private {
background: rgba(220, 38, 38, 0.88);
color: #fff;
}
.yt-video-card .yt-visibility-unlisted {
background: rgba(30, 30, 30, 0.82);
color: #facc15;
border: 1px solid rgba(250, 204, 21, 0.4);
}
.yt-video-card .yt-video-info {
display: flex;
margin-top: 12px;
gap: 12px;
}
.yt-video-card .yt-channel-icon {
width: 36px;
height: 36px;
border-radius: 50%;
background: #555;
flex-shrink: 0;
overflow: hidden;
}
.yt-video-card .yt-video-details {
flex: 1;
min-width: 0;
}
.yt-video-card .yt-video-title {
font-size: 16px;
font-weight: 500;
color: #fff;
margin: 0 0 4px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 1.3;
}
.yt-video-card .yt-video-title a {
color: inherit;
text-decoration: none;
}
.yt-video-card .yt-channel-icon {
text-decoration: none;
display: block;
}
.yt-video-card .yt-channel-name,
.yt-video-card .yt-video-meta {
color: #aaa;
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.yt-video-card a.yt-channel-name {
display: block;
text-decoration: none;
}
.yt-video-card a.yt-channel-name:hover { color: #fff; }
/* Type label in meta row */
.yt-type-label {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.4px;
}
.yt-type-music { color: #c084fc; }
.yt-type-match { color: #60a5fa; }
/* More button — visible only on hover (touch devices always show it) */
.yt-video-card .yt-more-btn {
background: transparent;
border: none;
color: #fff;
cursor: pointer;
padding: 4px;
border-radius: 50%;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s, opacity 0.15s;
flex-shrink: 0;
}
@media (hover: hover) {
.yt-video-card .yt-more-btn { opacity: 0; }
.yt-video-card:hover .yt-more-btn,
.yt-video-card .yt-more-btn:focus,
.yt-video-card .yt-more-btn[aria-expanded="true"] { opacity: 1; }
}
.yt-video-card .yt-more-btn:hover {
background: #3f3f3f;
}
/* Dropdown menu styles - use Bootstrap defaults */
.yt-video-card .dropdown-menu-dark {
background: #282828;
border: 1px solid #3f3f3f;
border-radius: 12px;
padding: 8px 0;
min-width: 200px;
}
.yt-video-card .dropdown-item {
color: #fff;
padding: 10px 16px;
font-size: 14px;
display: flex;
align-items: center;
gap: 16px;
}
.yt-video-card .dropdown-item:hover {
background: #3f3f3f;
color: #fff;
}
.yt-video-card .dropdown-item.text-danger {
color: #ef4444 !important;
}
.yt-video-card .dropdown-item.text-danger:hover {
background: #3f3f3f;
color: #ef4444 !important;
}
.yt-video-card .dropdown-item i {
width: 20px;
text-align: center;
}
.yt-video-card .dropdown-divider {
border-color: #3f3f3f;
margin: 8px 0;
}
/* Small size styles */
.yt-video-card-sm .yt-video-thumb {
border-radius: 8px;
}
.yt-video-card-sm .yt-video-info {
margin-top: 8px;
gap: 8px;
}
.yt-video-card-sm .yt-channel-icon {
width: 28px;
height: 28px;
}
.yt-video-card-sm .yt-video-title {
font-size: 13px;
}
.yt-video-card-sm .yt-channel-name,
.yt-video-card-sm .yt-video-meta {
font-size: 12px;
}
.yt-video-card-sm .yt-more-btn {
width: 24px;
height: 24px;
}
/* Cute Edit Modal Styles */
.cute-edit-modal {
max-width: 380px;
margin: auto;
}
.cute-edit-content {
background: linear-gradient(145deg, #1f1f1f 0%, #2a2a2a 100%);
border: 1px solid #3a3a3a;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.cute-edit-header {
background: linear-gradient(135deg, #ff6b8a 0%, #ff8fa3 100%);
padding: 16px 20px;
display: flex;
align-items: center;
gap: 10px;
position: relative;
}
.cute-edit-icon {
font-size: 20px;
}
.cute-edit-header h5 {
margin: 0;
color: white;
font-size: 16px;
font-weight: 600;
}
.btn-close-cute {
position: absolute;
right: 12px;
background: rgba(255,255,255,0.2);
border: none;
color: white;
width: 26px;
height: 26px;
border-radius: 50%;
font-size: 18px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: background 0.2s;
}
.btn-close-cute:hover {
background: rgba(255,255,255,0.3);
}
.cute-edit-body {
padding: 20px;
}
.cute-form-group {
margin-bottom: 16px;
}
.cute-form-group label {
display: flex;
align-items: center;
gap: 6px;
color: #ccc;
font-size: 13px;
margin-bottom: 8px;
font-weight: 500;
}
.cute-form-group label i {
color: #ff6b8a;
font-size: 14px;
}
.cute-input, .cute-textarea {
width: 100%;
background: #151515;
border: 1px solid #333;
border-radius: 10px;
padding: 10px 12px;
color: #fff;
font-size: 13px;
transition: all 0.2s;
}
.cute-input:focus, .cute-textarea:focus {
outline: none;
border-color: #ff6b8a;
box-shadow: 0 0 0 3px rgba(255, 107, 138, 0.15);
}
.cute-input::placeholder, .cute-textarea::placeholder {
color: #555;
}
/* Type Options */
.cute-type-options, .cute-privacy-options {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.cute-type-option, .cute-privacy-option {
flex: 1;
min-width: 80px;
cursor: pointer;
}
.cute-type-option input, .cute-privacy-option input {
display: none;
}
.cute-type-option span, .cute-privacy-option span {
display: block;
padding: 8px 10px;
background: #151515;
border: 1px solid #333;
border-radius: 8px;
font-size: 12px;
color: #aaa;
text-align: center;
transition: all 0.2s;
}
.cute-type-option:hover span, .cute-privacy-option:hover span {
border-color: #555;
background: #1a1a1a;
}
.cute-type-option.active span, .cute-privacy-option.active span {
background: rgba(255, 107, 138, 0.15);
border-color: #ff6b8a;
color: #ff6b8a;
}
/* Shorts Toggle in Edit Modal */
.cute-shorts-toggle {
position: relative;
display: inline-flex;
align-items: center;
cursor: pointer;
margin-top: 8px;
}
.cute-shorts-toggle input {
opacity: 0;
width: 0;
height: 0;
}
.cute-shorts-slider {
position: relative;
width: 44px;
height: 24px;
background-color: #333;
border-radius: 12px;
transition: 0.3s;
margin-right: 10px;
}
.cute-shorts-slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
border-radius: 50%;
transition: 0.3s;
}
.cute-shorts-toggle input:checked + .cute-shorts-slider {
background-color: #e63030;
}
.cute-shorts-toggle input:checked + .cute-shorts-slider:before {
transform: translateX(20px);
}
.cute-shorts-label {
color: #aaa;
font-size: 12px;
}
/* Thumbnail Upload */
.cute-thumbnail-upload {
border: 2px dashed #444;
border-radius: 10px;
padding: 16px;
text-align: center;
cursor: pointer;
transition: all 0.2s;
background: #151515;
}
.cute-thumbnail-upload:hover {
border-color: #ff6b8a;
background: rgba(255, 107, 138, 0.05);
}
.cute-thumbnail-preview {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
color: #666;
}
.cute-thumbnail-preview i {
font-size: 24px;
color: #ff6b8a;
}
.cute-thumbnail-preview span {
font-size: 12px;
}
/* Status */
.cute-status {
padding: 10px;
border-radius: 8px;
font-size: 13px;
margin-bottom: 16px;
display: none;
}
.cute-status.success {
display: block;
background: rgba(34, 197, 94, 0.15);
color: #4ade80;
border: 1px solid rgba(34, 197, 94, 0.3);
}
.cute-status.error {
display: block;
background: rgba(239, 68, 68, 0.15);
color: #f87171;
border: 1px solid rgba(239, 68, 68, 0.3);
}
/* Actions */
.cute-edit-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
}
.cute-btn-cancel, .cute-btn-save {
padding: 10px 20px;
border-radius: 10px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border: none;
}
.cute-btn-cancel {
background: #333;
color: #aaa;
}
.cute-btn-cancel:hover {
background: #444;
color: #fff;
}
.cute-btn-save {
background: linear-gradient(135deg, #ff6b8a 0%, #ff8fa3 100%);
color: white;
box-shadow: 0 4px 15px rgba(255, 107, 138, 0.3);
}
.cute-btn-save:hover {
transform: translateY(-1px);
box-shadow: 0 6px 20px rgba(255, 107, 138, 0.4);
}
.cute-btn-save:disabled {
background: #444;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
@media (max-width: 420px) {
.cute-edit-modal {
max-width: 320px;
margin: 10px;
}
.cute-type-options, .cute-privacy-options {
flex-direction: column;
}
}
</style>
@once
<script>
// Global function to save to Watch Later
function saveToWatchLater(videoId) {
fetch(`/videos/${videoId}/watch-later`, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
showToast(data.message || 'Added to Watch Later', 'success');
}
})
.catch(error => console.error('Error:', error));
}
// Global function to open playlist modal
function openPlaylistModal(videoId) {
// Set the current video ID for the modal as global variable
window.currentVideoIdForModal = videoId;
// Close any open dropdown menus first
const activeDropdowns = document.querySelectorAll('.dropdown-menu.show');
activeDropdowns.forEach(function(dropdown) {
dropdown.classList.remove('show');
});
// Also close Bootstrap dropdowns by clicking the toggle
const dropdownToggles = document.querySelectorAll('.dropdown-toggle[aria-expanded="true"]');
dropdownToggles.forEach(function(toggle) {
toggle.click();
});
// Try to open the add to playlist modal
if (typeof openAddToPlaylistModal === 'function') {
openAddToPlaylistModal(videoId);
} else {
// Modal might not be loaded, try to find and show it directly
const modal = document.getElementById('addToPlaylistModal');
if (modal) {
modal.style.display = 'flex';
modal.style.opacity = '1';
} else {
// Fallback - redirect to login
window.location.href = '{{ route("login") }}?redirect=' + encodeURIComponent(window.location.href);
}
}
}
// Global function to add to queue
function addToQueue(videoId) {
showToast('Queue feature coming soon!', 'info');
}
function playVideo(element) {
const video = element.querySelector('video');
if (!video) return;
video.currentTime = 0;
const isAudio = element.dataset.audio === 'true';
video.volume = isAudio ? 0.4 : 0.10;
if (isAudio) {
// Keep thumbnail visible — just play audio, show equalizer
video.play().catch(function() {});
element.classList.add('audio-playing');
} else {
video.play().catch(function() {});
video.classList.add('active');
}
}
function stopVideo(element) {
const video = element.querySelector('video');
if (!video) return;
video.pause();
video.currentTime = 0;
video.classList.remove('active');
element.classList.remove('audio-playing');
}
// Edit Modal Functions
let currentEditVideoId = null;
function openEditVideoModal(videoId) {
currentEditVideoId = videoId;
const modalId = 'editVideoModal' + (videoId || '');
const modal = new bootstrap.Modal(document.getElementById(modalId));
modal.show();
// Fetch video data
fetch(`/videos/${videoId}/edit`, {
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
const video = data.video;
document.getElementById('edit-title-' + videoId).value = video.title || '';
document.getElementById('edit-description-' + videoId).value = video.description || '';
// Set type
const typeOptions = document.querySelectorAll('#' + modalId + ' .cute-type-option');
typeOptions.forEach(opt => {
opt.classList.remove('active');
if (opt.dataset.type === (video.type || 'generic')) {
opt.classList.add('active');
opt.querySelector('input').checked = true;
}
});
// Set privacy
const privacyOptions = document.querySelectorAll('#' + modalId + ' .cute-privacy-option');
privacyOptions.forEach(opt => {
opt.classList.remove('active');
if (opt.dataset.privacy === (video.visibility || 'public')) {
opt.classList.add('active');
opt.querySelector('input').checked = true;
}
});
// Set shorts toggle
const shortsCheckbox = document.getElementById('edit-is-shorts-' + videoId);
if (shortsCheckbox) {
shortsCheckbox.checked = video.is_shorts === true || video.is_shorts === 1 || video.is_shorts === '1';
}
// Clear status
const statusEl = document.getElementById('edit-status-' + videoId);
statusEl.className = 'cute-status';
statusEl.textContent = '';
}
})
.catch(error => {
console.error('Error:', error);
});
}
function closeEditVideoModal(videoId) {
const modalId = 'editVideoModal' + (videoId || '');
const modalEl = document.getElementById(modalId);
const modal = bootstrap.Modal.getInstance(modalEl);
if (modal) {
modal.hide();
}
}
// Type option click handlers
document.addEventListener('click', function(e) {
if (e.target.closest('.cute-type-option')) {
const option = e.target.closest('.cute-type-option');
const parent = option.parentElement;
parent.querySelectorAll('.cute-type-option').forEach(opt => opt.classList.remove('active'));
option.classList.add('active');
option.querySelector('input').checked = true;
}
if (e.target.closest('.cute-privacy-option')) {
const option = e.target.closest('.cute-privacy-option');
const parent = option.parentElement;
parent.querySelectorAll('.cute-privacy-option').forEach(opt => opt.classList.remove('active'));
option.classList.add('active');
option.querySelector('input').checked = true;
}
});
// Thumbnail preview
document.addEventListener('change', function(e) {
if (e.target.id && e.target.id.startsWith('edit-thumbnail-')) {
const file = e.target.files[0];
if (file) {
const videoId = e.target.id.replace('edit-thumbnail-', '');
const preview = document.getElementById('thumbnail-preview-' + videoId);
preview.innerHTML = `<span>${file.name}</span>`;
}
}
});
// Form submission
document.addEventListener('submit', function(e) {
const form = e.target;
if (form.id && form.id.startsWith('edit-video-form-')) {
e.preventDefault();
const videoId = form.id.replace('edit-video-form-', '');
const formData = new FormData(form);
const statusEl = document.getElementById('edit-status-' + videoId);
const submitBtn = form.querySelector('.cute-btn-save');
submitBtn.disabled = true;
submitBtn.textContent = 'Saving...';
fetch(`/videos/${videoId}`, {
method: 'POST',
body: formData,
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
statusEl.className = 'cute-status success';
statusEl.textContent = '✓ Saved successfully!';
setTimeout(() => {
closeEditVideoModal(videoId);
window.location.reload();
}, 1000);
} else {
throw new Error(data.message || 'Update failed');
}
})
.catch(error => {
statusEl.className = 'cute-status error';
statusEl.textContent = '✗ ' + error.message;
submitBtn.disabled = false;
submitBtn.textContent = 'Save';
});
}
});
</script>
@endonce