diff --git a/configuration/homeScreen-configuration.js b/configuration/homeScreen-configuration.js
index 378c23b..3164378 100644
--- a/configuration/homeScreen-configuration.js
+++ b/configuration/homeScreen-configuration.js
@@ -348,6 +348,11 @@
*/
async function verifyRecentlyAddedInLibraryConfig(config) {
try {
+ if (!window.ApiClient || !window.ApiClient._loggedIn) {
+ WARN('User not logged in, skipping recently-added verification');
+ return config;
+ }
+
if (!window.dataHelper || !window.dataHelper.getLibraries) {
WARN('dataHelper.getLibraries not available, skipping recently-added verification');
return config;
@@ -2179,7 +2184,7 @@
${buildSelect(`query-${queryIndex}-parentItemType`, [
- { value: '', label: 'Select a Parent...' },
+ { value: '', label: 'Select a Parent... (optional)' },
{ value: 'Collection', label: 'Collection' },
{ value: 'Playlist', label: 'Playlist' },
{ value: 'Generic Parent', label: 'Generic Parent' }
diff --git a/injector.js b/injector.js
index 4cfc1c8..d50c966 100644
--- a/injector.js
+++ b/injector.js
@@ -19,37 +19,42 @@
return "Pending Install"; // Fallback
}
- // Match version patterns: @commitHash, @latest, @main, or @v0.3.3
- // Note: check for hash first to avoid partial numeric matches (e.g. hash starting with digits)
- const versionMatch = root.match(/@([a-f0-9]{7,}|latest|main|v?[\d.]+)/i);
-
- if (versionMatch) {
- const version = versionMatch[1];
+ // Match version patterns: @commitHash, @latest, @main, @experimental, or @v0.3.3
+ // Note: check for hash first to avoid partial numeric matches (e.g. hash starting with digits)
+ const versionMatch = root.match(/@([a-f0-9]{7,}|latest|main|experimental|v?[\d.]+)/i);
- // Handle version tags (v0.3.3 -> 0.3.3)
- if (version.startsWith('v') && /^\d+\.\d+\.\d+/.test(version.substring(1))) {
- return version.substring(1); // Remove 'v' prefix
+ if (versionMatch) {
+ const version = versionMatch[1];
+
+ // Handle version tags (v0.3.3 -> 0.3.3)
+ if (version.startsWith('v') && /^\d+\.\d+\.\d+/.test(version.substring(1))) {
+ return version.substring(1); // Remove 'v' prefix
+ }
+
+ // Handle @main -> "development"
+ if (version.toLowerCase() === 'main') {
+ return 'development';
+ }
+
+ // Handle @experimental -> "experimental"
+ if (version.toLowerCase() === 'experimental') {
+ return 'experimental';
+ }
+
+ // Handle @latest -> "latest"
+ if (version.toLowerCase() === 'latest') {
+ return 'latest';
+ }
+
+ // Handle commit hash -> show as "Dev (#hash)"
+ if (/^[a-f0-9]{7,}$/i.test(version)) {
+ return `Dev (#${version.substring(0, 7)})`;
+ }
+
+ // Return as-is for other patterns
+ return version;
}
- // Handle @main -> "Dev"
- if (version.toLowerCase() === 'main') {
- return 'development';
- }
-
- // Handle @latest -> "latest"
- if (version.toLowerCase() === 'latest') {
- return 'latest';
- }
-
- // Handle commit hash -> show as "Dev (#hash)"
- if (/^[a-f0-9]{7,}$/i.test(version)) {
- return `Dev (#${version.substring(0, 7)})`;
- }
-
- // Return as-is for other patterns
- return version;
- }
-
// No version found in URL, this is a custom install
return "Custom Install";
}
@@ -75,6 +80,8 @@
seriesInfo: true, // Add series and season information to details pages
collections: true, // Collection sorting functionality
skinManager: true, // Skin selection and management
+ thumbnailScrubber: false, // Trickplay thumbnail scrub on hover (bottom 20px of video cards)
+
// Note: Core functionality scripts (utils, cardBuilder, localStorageCache, modal)
// are automatically enabled when needed by other scripts
@@ -285,7 +292,7 @@
name: 'homeScreen',
script: 'homeScreen3.js',
css: 'homeScreen.css',
- dependencies: ['cardBuilder', 'localStorageCache', 'utils', 'homeScreenConfig2', 'homeScreen-configuration', 'peopleCache', 'studiosCache', 'moviesCache', 'indexedDBCache', 'homeScreenConfigCommunity', 'dataHelper', 'apiHelper', 'websocketHelper', 'homeScreen-migration', 'homeScreen-user-configuration'],
+ dependencies: ['cardBuilder', 'localStorageCache', 'utils', 'homeScreenConfig2', 'homeScreen-configuration', 'peopleCache', 'studiosCache', 'moviesCache', 'indexedDBCache', 'homeScreenConfigCommunity', 'dataHelper', 'apiHelper', 'homeScreen-migration', 'homeScreen-user-configuration'],
priority: true, // Load immediately after dependencies to reduce UI disruption
description: 'Adds custom home screen sections'
},
@@ -331,6 +338,13 @@
dependencies: [],
description: 'Fixes the dashboard button to redirect to the home page when the back button is clicked and there is no history to go back to'
},
+ {
+ name: 'thumbnailScrubber',
+ script: 'thumbnailScrubber.js',
+ css: null,
+ dependencies: [],
+ description: 'Shows trickplay thumbnail preview when hovering in the bottom 20px of a video card for 2+ seconds'
+ },
{
name: 'infiniteScroll',
script: 'infiniteScroll.js',
@@ -509,11 +523,12 @@
return true;
}
- // Resolve @latest or @main to actual version/commit hash
+ // Resolve @latest, @main, or @experimental to actual version/commit hash (so user always loads latest from that ref)
async function resolveRootVersion(root) {
- // Check if root contains @latest or @main
+ // Check if root contains @latest, @main, or @experimental
const latestMatch = root.match(/@latest(\/|$)/);
const mainMatch = root.match(/@main(\/|$)/);
+ const experimentalMatch = root.match(/@experimental(\/|$)/);
if (latestMatch) {
// Fetch the latest release version number
@@ -556,6 +571,26 @@
console.warn('[KefinTweaks Injector] Error fetching commit hash:', error);
return root;
}
+ } else if (experimentalMatch) {
+ // Fetch the latest commit hash from the experimental branch
+ try {
+ const response = await fetch('https://api.github.com/repos/ranaldsgift/KefinTweaks/commits/experimental');
+ if (response.ok) {
+ const commit = await response.json();
+ if (commit && commit.sha) {
+ return root.replace('@experimental', `@${commit.sha}`);
+ } else {
+ console.warn('[KefinTweaks Injector] Could not fetch experimental commit hash, using @experimental');
+ return root;
+ }
+ } else {
+ console.warn('[KefinTweaks Injector] Failed to fetch experimental commit hash, using @experimental');
+ return root;
+ }
+ } catch (error) {
+ console.warn('[KefinTweaks Injector] Error fetching experimental commit hash:', error);
+ return root;
+ }
}
// No resolution needed
@@ -575,7 +610,7 @@
// Ensure root ends with /
let root = kefinTweaksRoot.endsWith('/') ? kefinTweaksRoot : kefinTweaksRoot + '/';
- // Resolve @latest or @main if needed (with caching)
+ // Resolve @latest, @main, or @experimental if needed (with caching)
if (resolvedRootCache === null) {
root = await resolveRootVersion(root);
resolvedRootCache = root;
diff --git a/kefinTweaks-plugin.js b/kefinTweaks-plugin.js
index e971e34..725c9db 100644
--- a/kefinTweaks-plugin.js
+++ b/kefinTweaks-plugin.js
@@ -1329,6 +1329,9 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
const maxRetries = 100; // 10 seconds (100 * 100ms)
try {
+ // Check if there is a valid config with a root url already, and load the injector if so
+ checkAndLoadInjector();
+
// Check if ApiClient is available and user is logged in
if (!window.ApiClient || !window.ApiClient._loggedIn) {
if (retryCount < maxRetries) {
@@ -1342,9 +1345,6 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
}
}
- // Check if there is a valid config with a root url already, and load the injector if so
- checkAndLoadInjector();
-
// Check if user is admin
const userIsAdmin = await isAdmin();
if (!userIsAdmin) {
diff --git a/scripts/userHelper.js b/scripts/userHelper.js
index 376e857..c0981ee 100644
--- a/scripts/userHelper.js
+++ b/scripts/userHelper.js
@@ -13,6 +13,28 @@
throw new Error('ApiClient is not available');
}
}
+
+ function ensureLoggedIn() {
+ if (typeof ApiClient === 'undefined' || !ApiClient) {
+ throw new Error('ApiClient is not available');
+ }
+ if (!ApiClient._loggedIn) {
+ throw new Error('User is not logged in');
+ }
+ }
+
+ // Run this on a separate thread to avoid blocking the main thread with requestIdleCallback
+ async function waitForLogin() {
+ return new Promise((resolve) => {
+ requestIdleCallback(() => {
+ if (window.ApiClient && window.ApiClient._loggedIn) {
+ resolve(true);
+ } else {
+ setTimeout(() => waitForLogin(), 100);
+ }
+ });
+ });
+ }
/**
* API Helper functions for Jellyfin operations
@@ -20,6 +42,7 @@
const userHelper = {
getUserDisplayPreferences: async function() {
ensureApiClient();
+ ensureLoggedIn();
let cachedDisplayPreferences = null;
@@ -63,13 +86,16 @@
return { promise: userDisplayPreferencesPromise, cached: cachedDisplayPreferences };
},
useEpisodeImages: async function() {
- const { promise, cached } = await this.getUserDisplayPreferences();
- const userDisplayPreferences = await promise;
- if (!userDisplayPreferences) {
- LOG('No user display preferences found');
+ try {
+ ensureApiClient();
+ ensureLoggedIn();
+ const { promise, cached } = await this.getUserDisplayPreferences();
+ const userDisplayPreferences = await promise;
+ return userDisplayPreferences.CustomPrefs?.useEpisodeImagesInNextUpAndResume === 'true';
+ } catch (error) {
+ WARN('Failed to load user display preferences for useEpisodeImages:', error);
return false;
}
- return userDisplayPreferences.CustomPrefs?.useEpisodeImagesInNextUpAndResume === 'true';
},
updateCustomPrefsByKey: async function(key, value) {
const { promise, cached } = await this.getUserDisplayPreferences();
@@ -96,7 +122,8 @@
body: JSON.stringify(prefs)
});
return response.ok;
- }
+ },
+ waitForLogin: waitForLogin
};
// TODO -- Update this whenever the user display preferences are updated
diff --git a/scripts/watchlist.js b/scripts/watchlist.js
index e59acfe..049d7ee 100644
--- a/scripts/watchlist.js
+++ b/scripts/watchlist.js
@@ -4616,11 +4616,13 @@ In the Custom Tabs plugin, add a new tab with the following HTML content:
}
// Initialize monitoring to remove watched items from watchlist (playback + manual)
- function initializePlaybackMonitoring() {
+ async function initializePlaybackMonitoring() {
if (playbackMonitorInitialized) {
return;
}
+ await window.userHelper.waitForLogin();
+
const maxRetries = 10;
let retries = 0;
// Use WebSocket to listen for UserDataChanged messages instead of intercepting fetch