Merge pull request #1806 from NNTmux/2025-10-31-qlqw-eb87e

Refactor JavaScript functionality for improved performance and mainta…
This commit is contained in:
DariusIII
2025-10-31 16:44:48 +01:00
committed by GitHub
15 changed files with 977 additions and 203 deletions
+29 -3
View File
@@ -3,6 +3,7 @@ config.php
docker/.zsh_history
docker/composer-auth.json
docker/php-overrides.local.ini
docker-compose.yml
docker-compose.override.yml
.env
install.lock
@@ -20,7 +21,21 @@ test.php
/.idea/
/vendor/*
/storage/covers/*
# Ignore all files in covers directories but keep folder structure
# This includes symlinks - Git treats symlinks as files, so they're automatically ignored
/storage/covers/**/*
!/storage/covers/**/
!/storage/covers/**/.gitignore
!/storage/covers/**/no-cover.jpg
!/storage/covers/**/no-backdrop.jpg
/public/covers/**/*
!/public/covers/**/
!/public/covers/**/.gitignore
!/public/covers/**/no-cover.jpg
!/public/covers/**/no-backdrop.jpg
# Ignore symlinks in public/storage that point to storage/covers
/public/storage/covers
/storage/nzb/*
/resources/tmp/*
!/resources/tmp/unrar/
@@ -28,8 +43,8 @@ test.php
!/resources/tmp/imdb_cache/
!/public/assets/*
# Ignore Vite build manifest (regenerated locally)
/public/build/manifest.json
# Ignore Vite build output (regenerated locally)
/public/build/
# Logs
npm-debug.log*
@@ -44,3 +59,14 @@ node_modules/
.vscode-test
/.phpunit.cache
phpunit.xml.back
# IDE helper files (generated locally)
_ide_helper.php
# Lock files (generated locally, should not be committed)
*-lock
composer.lock
package-lock.json
yarn.lock
pnpm-lock.yaml
+32
View File
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="./bootstrap/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
<coverage>
<include>
<directory suffix=".php">Blacklight</directory>
<directory suffix=".php">app</directory>
</include>
<report>
<clover outputFile="tests/build/logs/clover.xml"/>
</report>
</coverage>
<testsuites>
<testsuite name="Install">
<directory suffix="Test.php">tests/Install</directory>
</testsuite>
<testsuite name="Unit">
<directory suffix="Test.php">tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">tests/Feature</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_ENV" value="testing"/>
<env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_DRIVER" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="QUEUE_DRIVER" value="sync"/>
<env name="MAIL_DRIVER" value="array"/>
</php>
<logging/>
</phpunit>
+24 -7
View File
@@ -4,17 +4,34 @@ import './bootstrap';
import '../css/csp-safe.css';
import './csp-safe.js';
// Theme initialization - must run early to prevent flash
// Theme initialization - optimized to prevent flash of unstyled content
(function() {
const themePreference = document.querySelector('meta[name="theme-preference"]')?.content || 'light';
'use strict';
// Use requestAnimationFrame for better performance
const applyTheme = function() {
const metaTheme = document.querySelector('meta[name="theme-preference"]');
const themePreference = metaTheme?.content || 'light';
if (themePreference === 'system') {
// Use OS preference
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
if (themePreference === 'system') {
// Use OS preference with proper media query listener
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
if (mediaQuery.matches) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
} else if (themePreference === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
} else if (themePreference === 'dark') {
document.documentElement.classList.add('dark');
};
// Apply theme immediately if DOM is ready, otherwise wait
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', applyTheme);
} else {
applyTheme();
}
})();
+875 -61
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -247,7 +247,7 @@
</a>
<a href="{{ url('admin/movie-edit?id=' . ($movie['imdbid'] ?? $movie->imdbid ?? '') . '&update=1') }}"
class="ml-auto px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700"
onclick="return confirm('This will fetch and update movie data from TMDB. Continue?')">
data-confirm="This will fetch and update movie data from TMDB. Continue?">
<i class="fa fa-refresh mr-2"></i>Update from TMDB
</a>
</div>
@@ -146,7 +146,7 @@
<a href="{{ url('/admin/release-delete/' . $release->guid) }}"
class="text-red-600 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300"
title="Delete release"
onclick="return confirm('Are you sure you want to delete this release?')">
data-confirm="Are you sure you want to delete this release?">
<i class="fa fa-trash"></i>
</a>
</div>
@@ -94,7 +94,7 @@
<a href="{{ url('admin/release-delete/' . $release->id) }}"
class="text-red-600 dark:text-red-400 hover:text-red-900 dark:hover:text-red-300"
title="Delete"
onclick="return confirm('Are you sure you want to delete this release?')">
data-confirm="Are you sure you want to delete this release?">
<i class="fa fa-trash"></i>
</a>
</div>
+5 -4
View File
@@ -278,7 +278,8 @@
<button type="button"
class="text-green-600 dark:text-green-400 hover:text-green-900 dark:hover:text-green-300 border-0 bg-transparent cursor-pointer p-0"
title="Verify User"
onclick="showVerifyModal(event, this.closest('form'))">
data-show-verify-modal
data-form-id="{{ $user->id }}">
<i class="fa fa-check-circle"></i>
</button>
</form>
@@ -330,7 +331,7 @@
<i class="fa fa-check-circle text-green-600 dark:text-green-400 mr-2"></i>
Verify User
</h3>
<button type="button" onclick="hideVerifyModal()" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<button type="button" data-close-verify-modal class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<i class="fas fa-times text-xl"></i>
</button>
</div>
@@ -342,12 +343,12 @@
</div>
<div class="px-6 py-4 bg-gray-50 dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 flex justify-end space-x-3">
<button type="button"
onclick="hideVerifyModal()"
data-close-verify-modal
class="px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition font-medium">
<i class="fas fa-times mr-2"></i>Cancel
</button>
<button type="button"
onclick="submitVerifyForm()"
data-submit-verify-form
class="px-4 py-2 bg-green-600 dark:bg-green-700 text-white rounded-lg hover:bg-green-700 dark:hover:bg-green-800 transition font-medium">
<i class="fa fa-check mr-2"></i>Verify
</button>
+3 -3
View File
@@ -279,7 +279,7 @@
<!-- Preview/Sample Image Modal -->
<div id="previewModal" class="hidden fixed inset-0 bg-black bg-opacity-75 items-center justify-center p-4" style="display: none; z-index: 9999 !important;">
<div class="relative max-w-4xl w-full">
<button type="button" onclick="closePreviewModal()" class="absolute top-4 right-4 text-white hover:text-gray-300 text-3xl font-bold z-10">
<button type="button" data-close-preview-modal class="absolute top-4 right-4 text-white hover:text-gray-300 text-3xl font-bold z-10">
<i class="fas fa-times"></i>
</button>
<div class="text-center mb-2">
@@ -299,7 +299,7 @@
<h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200 dark:text-gray-200 flex items-center">
<i class="fas fa-info-circle mr-2 text-blue-600 dark:text-blue-400 dark:text-blue-400"></i> Media Information
</h3>
<button type="button" onclick="closeMediainfoModal()" class="text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:text-gray-400 dark:hover:text-gray-300 text-2xl font-bold">
<button type="button" data-close-mediainfo-modal class="text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:text-gray-400 dark:hover:text-gray-300 text-2xl font-bold">
<i class="fas fa-times"></i>
</button>
</div>
@@ -319,7 +319,7 @@
<h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200 dark:text-gray-200 flex items-center">
<i class="fas fa-file-archive mr-2 text-green-600 dark:text-green-400"></i> File List
</h3>
<button type="button" onclick="closeFilelistModal()" class="text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:text-gray-400 dark:hover:text-gray-300 text-2xl font-bold">
<button type="button" data-close-filelist-modal class="text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:text-gray-400 dark:hover:text-gray-300 text-2xl font-bold">
<i class="fas fa-times"></i>
</button>
</div>
+1 -1
View File
@@ -348,7 +348,7 @@
<!-- Preview/Sample Image Modal -->
<div id="previewModal" class="hidden fixed inset-0 bg-black bg-opacity-75 items-center justify-center p-4">
<div class="relative max-w-4xl w-full">
<button type="button" onclick="closePreviewModal()" class="absolute top-4 right-4 text-white hover:text-gray-300 text-3xl font-bold z-10">
<button type="button" data-close-preview-modal class="absolute top-4 right-4 text-white hover:text-gray-300 text-3xl font-bold z-10">
<i class="fas fa-times"></i>
</button>
<div class="text-center mb-2">
+1 -1
View File
@@ -236,7 +236,7 @@
<div class="flex space-x-2">
<a href="{{ route('profileedit', ['action' => 'newapikey']) }}"
class="px-6 py-2 text-blue-700 bg-blue-100 rounded-lg hover:bg-blue-200 transition"
onclick="return confirm('Are you sure you want to generate a new API key?')">
data-confirm="Are you sure you want to generate a new API key?">
<i class="fas fa-key mr-2"></i>New API Key
</a>
<button type="submit" class="px-6 py-2 bg-blue-600 dark:bg-blue-700 text-white rounded-lg hover:bg-blue-700 dark:hover:bg-blue-800 transition">
+1 -1
View File
@@ -27,7 +27,7 @@
@if(!($isadmin ?? false) && !$publicview)
<a href="{{ url('profile_delete?id=' . $user->id) }}"
class="px-4 py-2 bg-red-600 dark:bg-red-700 text-white text-sm rounded hover:bg-red-700 dark:hover:bg-red-800 transition"
onclick="return confirm('Are you sure you want to delete your account? This action cannot be undone.')">
data-confirm="Are you sure you want to delete your account? This action cannot be undone.">
<i class="fa fa-trash mr-1"></i>Delete Account
</a>
@endif
+1 -77
View File
@@ -282,82 +282,6 @@
</div>
</div>
@push('scripts')
<script>
document.addEventListener('DOMContentLoaded', function() {
// Copy to clipboard functionality using modern Clipboard API
document.querySelectorAll('.copy-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
const targetId = this.getAttribute('data-copy-target');
const input = document.getElementById(targetId);
if (input) {
input.select();
input.setSelectionRange(0, 99999);
navigator.clipboard.writeText(input.value).then(() => {
// Visual feedback
const icon = this.querySelector('i');
icon.classList.remove('fa-copy');
icon.classList.add('fa-check');
this.classList.add('text-green-600');
setTimeout(() => {
icon.classList.remove('fa-check');
icon.classList.add('fa-copy');
this.classList.remove('text-green-600');
}, 2000);
}).catch(() => {
// Fallback for older browsers
document.execCommand('copy');
const icon = this.querySelector('i');
icon.classList.remove('fa-copy');
icon.classList.add('fa-check');
this.classList.add('text-green-600');
setTimeout(() => {
icon.classList.remove('fa-check');
icon.classList.add('fa-copy');
this.classList.remove('text-green-600');
}, 2000);
});
}
});
});
// API Token copy button
const copyApiTokenBtn = document.getElementById('copyApiToken');
if (copyApiTokenBtn) {
copyApiTokenBtn.addEventListener('click', function() {
const input = document.getElementById('apiTokenInput');
if (input) {
input.select();
input.setSelectionRange(0, 99999);
navigator.clipboard.writeText(input.value).then(() => {
// Visual feedback
const icon = this.querySelector('i');
icon.classList.remove('fa-copy');
icon.classList.add('fa-check');
this.classList.add('text-green-600');
setTimeout(() => {
icon.classList.remove('fa-check');
icon.classList.add('fa-copy');
this.classList.remove('text-green-600');
}, 2000);
}).catch(() => {
// Fallback for older browsers
document.execCommand('copy');
const icon = this.querySelector('i');
icon.classList.remove('fa-copy');
icon.classList.add('fa-check');
this.classList.add('text-green-600');
setTimeout(() => {
icon.classList.remove('fa-check');
icon.classList.add('fa-copy');
this.classList.remove('text-green-600');
}, 2000);
});
}
});
}
});
</script>
@endpush
{{-- Copy to clipboard functionality moved to csp-safe.js --}}
@endsection
+1 -1
View File
@@ -383,7 +383,7 @@
<!-- Preview/Sample Image Modal -->
<div id="previewModal" class="hidden fixed inset-0 bg-black bg-opacity-75 items-center justify-center p-4" style="display: none; z-index: 9999 !important;">
<div class="relative max-w-4xl w-full">
<button type="button" onclick="closePreviewModal()" class="absolute top-4 right-4 text-white hover:text-gray-300 text-3xl font-bold z-10">
<button type="button" data-close-preview-modal class="absolute top-4 right-4 text-white hover:text-gray-300 text-3xl font-bold z-10">
<i class="fas fa-times"></i>
</button>
<div class="text-center mb-2">
+1 -41
View File
@@ -286,47 +286,7 @@
</div>
</div>
<script>
function switchSeason(seasonNumber) {
// Hide all season content
document.querySelectorAll('.season-content').forEach(content => {
content.classList.add('hidden');
});
// Remove active styling from all tabs
document.querySelectorAll('.season-tab').forEach(tab => {
tab.classList.remove('border-blue-500', 'text-blue-600');
tab.classList.add('border-transparent', 'text-gray-500');
// Update badge styling
const badge = tab.querySelector('span');
if (badge) {
badge.classList.remove('bg-blue-100', 'text-blue-800');
badge.classList.add('bg-gray-100', 'text-gray-600');
}
});
// Show selected season content
const selectedContent = document.querySelector(`.season-content[data-season="${seasonNumber}"]`);
if (selectedContent) {
selectedContent.classList.remove('hidden');
}
// Add active styling to selected tab
const selectedTab = document.querySelector(`.season-tab[data-season="${seasonNumber}"]`);
if (selectedTab) {
selectedTab.classList.remove('border-transparent', 'text-gray-500');
selectedTab.classList.add('border-blue-500', 'text-blue-600');
// Update badge styling
const badge = selectedTab.querySelector('span');
if (badge) {
badge.classList.remove('bg-gray-100', 'text-gray-600');
badge.classList.add('bg-blue-100', 'text-blue-800');
}
}
}
</script>
{{-- Season switcher functionality moved to csp-safe.js --}}
@endforeach
</div>
</div>