diff --git a/README.md b/README.md index 1d15bba..52a3f10 100644 --- a/README.md +++ b/README.md @@ -117,9 +117,11 @@ The features in KefinTweaks which use local data caching are listed below, along - ✅ [Watched History](https://features.jellyfin.org/posts/633/watched-history) - ✅ [On-Demand Subtitle Search in Video OSD](https://features.jellyfin.org/posts/3385/on-demand-subtitle-search) - ✅ [Add genres and recommendations to home screen](https://features.jellyfin.org/posts/3501/add-genres-and-recommendations-to-home-screen) +- ✅ [New or Improved Home Screen Sections](https://features.jellyfin.org/posts/1986/new-or-improved-home-secreen-sections) +- ✅ [Custom Carousel Options on Home Screen](https://features.jellyfin.org/posts/1439/custom-carousel-options-on-home-screen) - ✅ [Improved Playlist UX](https://features.jellyfin.org/posts/2823/playlist-moviesummary) - ✅ [Flattening TV Shows with 1 season](https://features.jellyfin.org/posts/8/flattening-tv-shows) [[2]](https://features.jellyfin.org/posts/3352/add-option-to-flatten-single-season-tv-shows-skip-season-level) -- ✅ [List all collections that an item belongs to on the details page](https://features.jellyfin.org/posts/540/list-all-collections-that-a-movie-belong-to-in-movie-details) [[2]](https://features.jellyfin.org/posts/3540/collection-data-present-on-content-within-the-collection) +- ✅ [List all collections that an item belongs to on the details page](https://features.jellyfin.org/posts/540/list-all-collections-that-a-movie-belong-to-in-movie-details) [[2]](https://features.jellyfin.org/posts/3540/collection-data-present-on-content-within-the-collection) [[3]](https://features.jellyfin.org/posts/3549/show-collections-on-movie-page) - ✅ [Custom Skins/Themes](https://features.jellyfin.org/posts/2509/themes-skins-or-clone-plex-layout-to-convert-the-rest-of-the-plex-and-emby-user-base-to-jellyfin) [[2]](https://features.jellyfin.org/posts/2616/theme-skins) - ✅ [TV Season Selection at the Series level](https://forum.jellyfin.org/t-tv-season-selection-when-browsing-seasons) @@ -183,6 +185,9 @@ Creates sections for movies and episodes released in the last 7 days (trending s - **Infinite Discovery Sections**: Load discovery sections based on items you've watched and favorited, as well as from genres or top people in your library +- **HSS Support**: +Full compatibility with [Home Screen Sections](https://github.com/IAmParadox27/jellyfin-plugin-home-sections) plugin by [IAmParadox27](https://github.com/IAmParadox27/) + - **Seasonal Content**: Seasonally-themed sections (Halloween, Christmas, etc.) @@ -269,6 +274,9 @@ Adds a card overlay button to remove items from Continue Watching for all resuma
+#### **Collection Sorting**: +Adds the ability to sort collection items on the Collection page. Supports sorting by Title, Release Date, Date Added, Community Rating, Critic Rating. + ### Navigation Improvements #### **Header Tab Enhancements**: @@ -413,6 +421,7 @@ Allow users to also customize various configuration options through the UI
### Version History +- **v0.2.3**: New Feature: Collection Sorting, general fixes - **v0.2.2**: Add support for skin server versions, fixes for HeaderTabs - **v0.2.1**: Caching improvements for Watchlist and Top People, Mobile UI fixes and Flatten TV Shows improvements - **v0.2.0**: New Features: Skin Manager, Collections on Details Page, Flatten TV Shows diff --git a/custom-menu.js b/custom-menu.js deleted file mode 100644 index 2455e48..0000000 --- a/custom-menu.js +++ /dev/null @@ -1,99 +0,0 @@ -// Custom Menu Script -// Removes target="_blank" from custom menu links to prevent opening in new tabs -// Ensures all custom menu links open in the same tab for better user experience - -(function() { - 'use strict'; - - // Common logging function - const LOG = (...args) => console.log('[CustomMenu]', ...args); - const WARN = (...args) => console.warn('[CustomMenu]', ...args); - const ERR = (...args) => console.error('[CustomMenu]', ...args); - - LOG('Script loaded - JS Injector mode'); - - function initMenuWatcher() { - const menuContainer = document.querySelector('.customMenuOptions'); - if (!menuContainer) return false; - - LOG('Custom menu container found, initializing watcher'); - - const cleanLinks = (node) => { - if (node.matches?.('a.navMenuOption[target="_blank"]')) { - LOG('Removing target="_blank" from menu link'); - node.removeAttribute('target'); - } - node.querySelectorAll?.('a.navMenuOption[target="_blank"]').forEach(link => { - LOG('Removing target="_blank" from nested menu link'); - link.removeAttribute('target'); - }); - }; - - // Clean existing links - cleanLinks(menuContainer); - - // Watch for new/changed nodes - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - mutation.addedNodes.forEach(node => cleanLinks(node)); - if (mutation.type === 'attributes' && mutation.target.matches('a.navMenuOption')) { - cleanLinks(mutation.target); - } - } - }); - - observer.observe(menuContainer, { - childList: true, - subtree: true, - attributes: true, - attributeFilter: ['target'] - }); - - LOG('Menu watcher initialized successfully'); - return true; - } - - // Watch for menu container to appear - const waitForMenu = () => { - const menuContainer = document.querySelector('.customMenuOptions'); - if (menuContainer) { - initMenuWatcher(); - return; - } - - LOG('Custom menu container not found, waiting for it to appear'); - - // Use MutationObserver to watch for the menu container - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - mutation.addedNodes.forEach(node => { - if (node.nodeType === Node.ELEMENT_NODE) { - // Check if the added node is the menu or contains it - if (node.matches?.('.customMenuOptions') || node.querySelector?.('.customMenuOptions')) { - LOG('Custom menu container detected, initializing watcher'); - observer.disconnect(); - initMenuWatcher(); - return; - } - } - }); - } - }); - - observer.observe(document.body, { - childList: true, - subtree: true - }); - - // Fallback timeout after 10 seconds - setTimeout(() => { - observer.disconnect(); - WARN('Menu not found after 10 seconds'); - }, 10000); - }; - - // Start watching - waitForMenu(); - - LOG('Custom menu functionality initialized'); -})(); diff --git a/injector.js b/injector.js index 65d66c2..96dbe8c 100644 --- a/injector.js +++ b/injector.js @@ -6,7 +6,7 @@ 'use strict'; console.log('[KefinTweaks Injector] Initializing...'); - const versionNumber = "0.2.2"; + const versionNumber = "0.2.3"; // Configuration: Start with defaults, then merge user config // This allows new scripts to work out of the box without requiring config updates @@ -310,8 +310,15 @@ } // Get the root path for scripts from configuration - function getScriptRoot() { - return window.KefinTweaksConfig?.scriptRoot || 'https://ranaldsgift.github.io/KefinTweaks/'; + function getScriptRoot() { + const configScriptRoot = window.KefinTweaksConfig?.scriptRoot; + + // Handle legacy configuration pointing to the root directory + if (configScriptRoot === 'https://ranaldsgift.github.io/KefinTweaks/') { + return 'https://ranaldsgift.github.io/KefinTweaks/scripts/'; + } + + return window.KefinTweaksConfig?.scriptRoot || 'https://ranaldsgift.github.io/KefinTweaks/scripts/'; } // Load a CSS file diff --git a/kefinTweaks.js b/kefinTweaks.js index 32eae30..eccc7da 100644 --- a/kefinTweaks.js +++ b/kefinTweaks.js @@ -33,10 +33,12 @@ // - Search: Enable Jellyseerr integration adds an option to search for Jellyseerr results only // // Considerations for the home screen sections: -// 1. Custom sections (order 1-29) - Your playlist-based sections -// 2. New and Trending sections (order 30-49) - Newest movies and episodes -// 3. Seasonal sections (order 50+) - Holiday-themed content -// 4. Discovery sections (order 200+) - Intelligent recommendations based on your library +// - Default Jellyfin sections (order 1-10) +// - New releases sections (order 30-49) - Newest movies and episodes +// - Seasonal sections (order 50-59) - Holiday-themed content +// - One-off sections: Watchlist, TV Networks, section (order 60-69) - Your watchlist +// - Discovery sections (order 1000+) - Intelligent recommendations based on your library +// - Custom sections can be placed anywhere in the order by using the order field in the configuration (function() { 'use strict'; @@ -50,7 +52,8 @@ // SCRIPT ROOT CONFIGURATION // ============================================================================ // Configure where scripts are loaded from - scriptRoot: 'https://ranaldsgift.github.io/KefinTweaks/', + kefinTweaksRoot: 'https://ranaldsgift.github.io/KefinTweaks/', + scriptRoot: 'https://ranaldsgift.github.io/KefinTweaks/scripts/', // ============================================================================ // SCRIPT ENABLE/DISABLE FLAGS @@ -117,7 +120,7 @@ enableDiscovery: true, // Enables discovery sections on the home screen // Discovery section behavior and filtering - enableInfiniteScroll: false, // Use infinite scroll vs Load More button + enableInfiniteScroll: true, // Use infinite scroll vs Load More button minPeopleAppearances: 10, // Minimum movie appearances for people to be featured minGenreMovieCount: 50, // Minimum movie count for genres to be included minimumShowsForNetwork: 5, // Minimum show count for TV networks to be featured @@ -297,7 +300,7 @@ // ============================================================================ // Load the injector script which handles loading individual scripts based on configuration const script = document.createElement("script"); - script.src = `${KEFIN_TWEAKS_CONFIG.scriptRoot}injector.js?v=${new Date().getTime()}`; + script.src = `${KEFIN_TWEAKS_CONFIG.kefinTweaksRoot}injector.js?v=${new Date().getTime()}`; script.async = true; document.head.appendChild(script); diff --git a/kefinTweaks.minimal.js b/kefinTweaks.minimal.js new file mode 100644 index 0000000..47c7fa5 --- /dev/null +++ b/kefinTweaks.minimal.js @@ -0,0 +1,63 @@ +(function() { + 'use strict'; + + const KEFIN_TWEAKS_CONFIG = { + kefinTweaksRoot: 'https://ranaldsgift.github.io/KefinTweaks/', + scriptRoot: 'https://ranaldsgift.github.io/KefinTweaks/scripts/', + + scripts: { + collections: true, + homeScreen: true, + search: true, + watchlist: true, + headerTabs: true, + customMenuLinks: true, + breadcrumbs: true, + playlist: true, + itemDetailsCollections: true, + flattenSingleSeasonShows: true, + exclusiveElsewhere: false, + backdropLeakFix: true, + dashboardButtonFix: true, + infiniteScroll: true, + removeContinue: true, + subtitleSearch: true, + skinManager: true + }, + homeScreen: { + enableNewAndTrending: true, + enableNewMovies: true, + enableNewEpisodes: true, + enableTrending: false, + enableDiscovery: true, + enableInfiniteScroll: true, + minPeopleAppearances: 10, + minGenreMovieCount: 50, + minimumShowsForNetwork: 5, + enableWatchlist: true, + enableSeasonal: true, + seasonalItemLimit: 16, + customSections: [], + }, + exclusiveElsewhere: { + hideServerName: false + }, + search: { + enableJellyseerr: false + }, + skins: [], + defaultSkin: null, + themes: [], + customMenuLinks: [], + }; + + window.KefinTweaksConfig = KEFIN_TWEAKS_CONFIG; + + const script = document.createElement("script"); + script.src = `${KEFIN_TWEAKS_CONFIG.kefinTweaksRoot}injector.js?v=${new Date().getTime()}`; + script.async = true; + document.head.appendChild(script); + + console.log('[KefinTweaks] Central configuration loaded. Available at window.KefinTweaksConfig'); +})(); + diff --git a/backdropLeakFix.js b/scripts/backdropLeakFix.js similarity index 100% rename from backdropLeakFix.js rename to scripts/backdropLeakFix.js diff --git a/breadcrumbNav.css b/scripts/breadcrumbNav.css similarity index 100% rename from breadcrumbNav.css rename to scripts/breadcrumbNav.css diff --git a/breadcrumbs.js b/scripts/breadcrumbs.js similarity index 100% rename from breadcrumbs.js rename to scripts/breadcrumbs.js diff --git a/cardBuilder.js b/scripts/cardBuilder.js similarity index 100% rename from cardBuilder.js rename to scripts/cardBuilder.js diff --git a/collections.js b/scripts/collections.js similarity index 99% rename from collections.js rename to scripts/collections.js index 35b178a..5298718 100644 --- a/collections.js +++ b/scripts/collections.js @@ -547,7 +547,7 @@ // Poll for section title (up to 5 seconds, every 500ms) const sectionTitle = await pollForElement(() => { return collectionItemsContainer.querySelector('.sectionTitle'); - }, 5000, 500); + }, 5000, 100); if (!sectionTitle) { WARN('Section title not found after polling for 5 seconds'); diff --git a/customMenuLinks.js b/scripts/customMenuLinks.js similarity index 100% rename from customMenuLinks.js rename to scripts/customMenuLinks.js diff --git a/dashboardButtonFix.js b/scripts/dashboardButtonFix.js similarity index 100% rename from dashboardButtonFix.js rename to scripts/dashboardButtonFix.js diff --git a/defaultSkin.css b/scripts/defaultSkin.css similarity index 92% rename from defaultSkin.css rename to scripts/defaultSkin.css index 481d065..e8873e5 100644 --- a/defaultSkin.css +++ b/scripts/defaultSkin.css @@ -31,4 +31,8 @@ div#seriesScheduleSection { .headerRight .headerAppearanceButton { display: none; } +} + +.itemBackdrop { + pointer-events: none; } \ No newline at end of file diff --git a/scripts/deviceManager.js b/scripts/deviceManager.js new file mode 100644 index 0000000..e36a7e5 --- /dev/null +++ b/scripts/deviceManager.js @@ -0,0 +1,392 @@ +// Device Manager +// Adds a "Remove Device" button to active device sessions on the dashboard page +// Allows users to remove devices via a custom endpoint + +(function() { + 'use strict'; + + // Logging functions + const LOG = (...args) => console.log('[DeviceManager]', ...args); + const WARN = (...args) => console.warn('[DeviceManager]', ...args); + const ERR = (...args) => console.error('[DeviceManager]', ...args); + + LOG('Initializing Device Manager'); + + // Polling interval references + let pollingInterval = null; + let continuousPollingInterval = null; + + // Wait for KefinTweaksUtils to become available + async function waitForUtils(maxWaitTime = 30000, checkInterval = 100) { + const startTime = Date.now(); + + while (Date.now() - startTime < maxWaitTime) { + if (window.KefinTweaksUtils && window.KefinTweaksUtils.onViewPage) { + LOG('KefinTweaksUtils is now available'); + return true; + } + + await new Promise(resolve => setTimeout(resolve, checkInterval)); + } + + ERR('KefinTweaksUtils not available after waiting. Device Manager requires utils.js'); + return false; + } + + // Initialize the device manager after utils are available + async function initializeDeviceManager() { + // Wait for utils to be available + const utilsAvailable = await waitForUtils(); + if (!utilsAvailable) { + return; + } + + // Register handler for dashboard page + window.KefinTweaksUtils.onViewPage((view, element) => { + // Check if we're on the dashboard page + const isDashboardPage = view === 'dashboard' || + view === 'dashboardPage' || + window.location.hash.includes('dashboard') || + document.querySelector('#dashboardPage:not(.hide)'); + + if (!isDashboardPage) { + // Not on dashboard, clear polling intervals + if (pollingInterval) { + clearInterval(pollingInterval); + pollingInterval = null; + } + if (continuousPollingInterval) { + clearInterval(continuousPollingInterval); + continuousPollingInterval = null; + } + return; + } + + LOG('Dashboard page detected, initializing device manager'); + + // Start polling for activeDevicesContainer + initializeDashboard(); + }, { + pages: ['dashboard', 'dashboardPage'] + }); + + LOG('Device Manager initialized'); + } + + // Check if ApiClient is available + function isApiClientAvailable() { + return window.ApiClient && + typeof window.ApiClient.accessToken === 'function' && + typeof window.ApiClient.serverAddress === 'function' && + typeof window.ApiClient.getSessions === 'function'; + } + + // Get session data by session ID + async function getSessionData(sessionId, sessions) { + try { + if (!isApiClientAvailable()) { + throw new Error('ApiClient not available or getSessions method not found'); + } + + if (!sessions || !Array.isArray(sessions)) { + throw new Error('Failed to get sessions or sessions is not an array'); + } + + LOG(`Retrieved ${sessions.length} sessions, filtering for session ID: ${sessionId}`); + + // Filter sessions to find the one matching our session ID + const sessionData = sessions.find(session => session.Id === sessionId); + + if (!sessionData) { + WARN(`Session with ID ${sessionId} not found in sessions list`); + return null; + } + + return sessionData; + } catch (error) { + ERR('Error fetching session data:', error); + return null; + } + } + + // Remove device via custom endpoint + async function removeDevice(deviceId) { + try { + if (!isApiClientAvailable()) { + throw new Error('ApiClient not available'); + } + + const token = window.ApiClient.accessToken(); + if (!token) { + throw new Error('Access token not available'); + } + + LOG(`Removing device: ${deviceId}`); + + const serverAddress = window.ApiClient.serverAddress(); + if (!serverAddress) { + throw new Error('Server address not available'); + } + + const response = await fetch(`${serverAddress}/Devices?Id=${encodeURIComponent(deviceId)}`, { + method: 'DELETE', + headers: { + 'Accept': 'application/json', + 'Authorization': `MediaBrowser Token="${token}"` + } + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + LOG('Device removed successfully'); + return true; + } catch (error) { + ERR('Error removing device:', error); + return false; + } + } + + // Create remove device button + function createRemoveDeviceButton(deviceId) { + const button = document.createElement('button'); + button.setAttribute('is', 'paper-icon-button-light'); + button.className = 'sessionCardButton btnSessionRemoveDevice paper-icon-button-light'; + button.setAttribute('title', 'Remove Device'); + + const iconSpan = document.createElement('span'); + iconSpan.className = 'material-icons'; + iconSpan.setAttribute('aria-hidden', 'true'); + iconSpan.textContent = 'delete'; + + button.appendChild(iconSpan); + + // Add click handler + button.addEventListener('click', async (e) => { + e.preventDefault(); + e.stopPropagation(); + + if (!confirm('Are you sure you want to remove this device?')) { + return; + } + + button.disabled = true; + const originalTitle = button.getAttribute('title'); + button.setAttribute('title', 'Removing...'); + + const success = await removeDevice(deviceId); + + if (success) { + // Remove the session container from the DOM + const sessionContainer = button.closest('.activeSession'); + if (sessionContainer) { + sessionContainer.style.transition = 'opacity 0.3s'; + sessionContainer.style.opacity = '0'; + setTimeout(() => { + sessionContainer.remove(); + }, 300); + } + + // Show success message if toaster is available + if (window.KefinTweaksToaster) { + window.KefinTweaksToaster.show('Device removed successfully', 'success'); + } + } else { + button.disabled = false; + button.setAttribute('title', originalTitle); + + // Show error message if toaster is available + if (window.KefinTweaksToaster) { + window.KefinTweaksToaster.show('Failed to remove device', 'error'); + } + } + }); + + return button; + } + + // Process a single session container + async function processSessionContainer(container, sessions) { + // Check if button already exists + const existingButton = container.querySelector('.btnSessionRemoveDevice'); + if (existingButton) { + LOG('Remove device button already exists for this session'); + return; + } + + // Get session ID from container ID + const containerId = container.id; + if (!containerId || !containerId.startsWith('session')) { + WARN('Container ID does not match expected format:', containerId); + return; + } + + const sessionId = containerId.substring(7); // Remove "session" prefix + if (!sessionId) { + WARN('Could not extract session ID from container ID:', containerId); + return; + } + + LOG(`Processing session container with ID: ${sessionId}`); + + // Find the sessionCardButtons container + const buttonsContainer = container.querySelector('.sessionCardButtons'); + if (!buttonsContainer) { + WARN('sessionCardButtons container not found'); + return; + } + + // Fetch session data to get device ID + const sessionData = await getSessionData(sessionId, sessions); + if (!sessionData || !sessionData.DeviceId) { + WARN('Could not get device ID from session data'); + return; + } + + const deviceId = sessionData.DeviceId; + LOG(`Found device ID: ${deviceId}`); + + // Create and add remove device button + const removeButton = createRemoveDeviceButton(deviceId); + buttonsContainer.appendChild(removeButton); + + LOG('Remove device button added successfully'); + } + + // Process all active session containers on the dashboard + async function processActiveSessions() { + const dashboardPage = document.querySelector('#dashboardPage:not(.hide)'); + if (!dashboardPage) { + return; + } + + const activeDevicesContainer = dashboardPage.querySelector('.activeDevices'); + if (!activeDevicesContainer) { + return; + } + + const activeSessions = activeDevicesContainer.querySelectorAll('.activeSession'); + LOG(`Found ${activeSessions.length} active session containers`); + + if (activeSessions.length === 0) { + return; + } + + let sessions = null; + + if (window.ApiClient && window.ApiClient.getSessions) { + sessions = await window.ApiClient.getSessions(); + } + + if (!sessions) { + WARN('Failed to get sessions'); + return; + } + + // Process each session container + for (const sessionContainer of activeSessions) { + await processSessionContainer(sessionContainer, sessions); + } + } + + // Poll for activeDevicesContainer and process sessions + function pollForActiveDevicesContainer(maxAttempts = 50, interval = 500) { + // Clear any existing intervals + if (pollingInterval) { + clearInterval(pollingInterval); + } + if (continuousPollingInterval) { + clearInterval(continuousPollingInterval); + } + + let attempts = 0; + + pollingInterval = setInterval(async () => { + attempts++; + + const dashboardPage = document.querySelector('#dashboardPage:not(.hide)'); + if (!dashboardPage) { + if (attempts >= maxAttempts) { + clearInterval(pollingInterval); + pollingInterval = null; + LOG('Dashboard page not found after max attempts'); + } + return; + } + + const activeDevicesContainer = dashboardPage.querySelector('.activeDevices'); + if (!activeDevicesContainer) { + if (attempts >= maxAttempts) { + clearInterval(pollingInterval); + pollingInterval = null; + LOG('Active devices container not found after max attempts'); + } + return; + } + + // Found the container, check for active sessions + clearInterval(pollingInterval); + pollingInterval = null; + LOG('Active devices container found, checking for sessions'); + + // Check if there are any active session containers + const activeSessions = activeDevicesContainer.querySelectorAll('.activeSession'); + + if (activeSessions.length > 0) { + LOG(`Found ${activeSessions.length} active session containers, processing and stopping polling`); + + // Process all existing sessions + await processActiveSessions(); + + // Stop polling once we've found and processed sessions + LOG('Sessions processed, polling stopped'); + return; + } + + // No sessions found yet, continue polling periodically + if (!continuousPollingInterval) { + continuousPollingInterval = setInterval(async () => { + const currentDashboardPage = document.querySelector('#dashboardPage:not(.hide)'); + if (!currentDashboardPage) { + // Dashboard page no longer visible, stop continuous polling + clearInterval(continuousPollingInterval); + continuousPollingInterval = null; + // Restart initial polling + pollForActiveDevicesContainer(); + return; + } + + const currentActiveDevicesContainer = currentDashboardPage.querySelector('.activeDevices'); + if (!currentActiveDevicesContainer) { + return; + } + + const currentActiveSessions = currentActiveDevicesContainer.querySelectorAll('.activeSession'); + if (currentActiveSessions.length > 0) { + LOG(`Found ${currentActiveSessions.length} active session containers, processing and stopping polling`); + + // Stop continuous polling + clearInterval(continuousPollingInterval); + continuousPollingInterval = null; + + // Process all sessions + await processActiveSessions(); + + LOG('Sessions processed, polling stopped'); + } + }, interval * 2); // Poll less frequently after initial find + } + }, interval); + } + + // Main initialization + function initializeDashboard() { + // Start polling for activeDevicesContainer + pollForActiveDevicesContainer(); + } + + // Start initialization + initializeDeviceManager(); +})(); + diff --git a/exclusiveElsewhere.js b/scripts/exclusiveElsewhere.js similarity index 100% rename from exclusiveElsewhere.js rename to scripts/exclusiveElsewhere.js diff --git a/flattenSingleSeasonShows.js b/scripts/flattenSingleSeasonShows.js similarity index 100% rename from flattenSingleSeasonShows.js rename to scripts/flattenSingleSeasonShows.js diff --git a/headerTabs.js b/scripts/headerTabs.js similarity index 100% rename from headerTabs.js rename to scripts/headerTabs.js diff --git a/homeScreen.css b/scripts/homeScreen.css similarity index 100% rename from homeScreen.css rename to scripts/homeScreen.css diff --git a/homeScreen.js b/scripts/homeScreen.js similarity index 100% rename from homeScreen.js rename to scripts/homeScreen.js diff --git a/indexedDBCache.js b/scripts/indexedDBCache.js similarity index 100% rename from indexedDBCache.js rename to scripts/indexedDBCache.js diff --git a/infiniteScroll.js b/scripts/infiniteScroll.js similarity index 100% rename from infiniteScroll.js rename to scripts/infiniteScroll.js diff --git a/itemDetailsCollections.js b/scripts/itemDetailsCollections.js similarity index 100% rename from itemDetailsCollections.js rename to scripts/itemDetailsCollections.js diff --git a/localStorageCache.js b/scripts/localStorageCache.js similarity index 100% rename from localStorageCache.js rename to scripts/localStorageCache.js diff --git a/modal.css b/scripts/modal.css similarity index 100% rename from modal.css rename to scripts/modal.css diff --git a/modal.js b/scripts/modal.js similarity index 100% rename from modal.js rename to scripts/modal.js diff --git a/playlist.js b/scripts/playlist.js similarity index 100% rename from playlist.js rename to scripts/playlist.js diff --git a/removeContinue.js b/scripts/removeContinue.js similarity index 100% rename from removeContinue.js rename to scripts/removeContinue.js diff --git a/search.css b/scripts/search.css similarity index 100% rename from search.css rename to scripts/search.css diff --git a/search.js b/scripts/search.js similarity index 100% rename from search.js rename to scripts/search.js diff --git a/skinManager.js b/scripts/skinManager.js similarity index 100% rename from skinManager.js rename to scripts/skinManager.js diff --git a/subtitleSearch.css b/scripts/subtitleSearch.css similarity index 100% rename from subtitleSearch.css rename to scripts/subtitleSearch.css diff --git a/subtitleSearch.js b/scripts/subtitleSearch.js similarity index 100% rename from subtitleSearch.js rename to scripts/subtitleSearch.js diff --git a/toaster.js b/scripts/toaster.js similarity index 100% rename from toaster.js rename to scripts/toaster.js diff --git a/updoot.js b/scripts/updoot.js similarity index 100% rename from updoot.js rename to scripts/updoot.js diff --git a/userConfig.js b/scripts/userConfig.js similarity index 100% rename from userConfig.js rename to scripts/userConfig.js diff --git a/utils.js b/scripts/utils.js similarity index 99% rename from utils.js rename to scripts/utils.js index 27b70b4..b4d8760 100644 --- a/utils.js +++ b/scripts/utils.js @@ -195,7 +195,6 @@ // Get the item promise once - will be shared across all handlers // The promise is cached/fetched by getCurrentItem(), ensuring only one API call - LOG('Getting current item for ID:', currentItemId); const itemPromise = getCurrentItem(); handlers.forEach((config) => { diff --git a/watchlist.css b/scripts/watchlist.css similarity index 100% rename from watchlist.css rename to scripts/watchlist.css diff --git a/watchlist.js b/scripts/watchlist.js similarity index 100% rename from watchlist.js rename to scripts/watchlist.js