Add proper installation and uninstallation support from the Plugins page

This commit is contained in:
HighImKevin
2025-11-23 05:01:42 -05:00
parent 00020534d7
commit 5411065530
11 changed files with 1897 additions and 550 deletions
+28 -30
View File
@@ -10,14 +10,13 @@ I think it's also worth mentioning that before the 1.0 release, you can expect t
Note: The Slideshow at the top of the Home Screen in the video below is from the [MediaBar](https://github.com/IAmParadox27/jellyfin-plugin-media-bar) plugin by [IAmParadox27](https://github.com/IAmParadox27) and [MakD](https://github.com/MakD).
[![Home Screen Example](https://i.imgur.com/rCboFCB.jpeg)](https://streamable.com/ypnn6n)
[![Home Screen Example](https://i.imgur.com/rCboFCB.jpeg)](https://youtu.be/4ikW4CFSn3I)
## Table of Contents
- [Installation](#installation)
- [Prerequisites](#prerequisites)
- [Setup Instructions](#setup-instructions)
- [Alternate Installation Options](#alternate-installation-options)
- [Configuration](#configuration)
- [Features Overview](#features-overview)
- [Data Caching](#data-caching)
@@ -76,44 +75,29 @@ Note: The Slideshow at the top of the Home Screen in the video below is from the
### Setup Instructions
0a. **Install Prerequisites listed above**
0b. **If you are using JellyfinEnhanced, you MUST disable the "Watchlist" feature from the plugin settings**
0. **Install the Prerequisites listed above**
1. **Add a new script to your JS Injector Plugin**
2. **Copy the the code below and paste it into the new script:**
2. **Copy the the code below, paste it into the new script, save your changes and refresh the page**
```javascript
const script = document.createElement("script");
script.src = `https://ranaldsgift.github.io/KefinTweaks/injector.js?v=${new Date().getTime()}`;
script.src = `https://cdn.jsdelivr.net/gh/ranaldsgift/KefinTweaks/kefinTweaks-plugin.js`;
script.async = true;
document.head.appendChild(script);
```
3. **Save your changes**
If you are hosting the script yourself, replace the `https://cdn.jsdelivr.net/gh/ranaldsgift` with your hosted location. You should be able to include the scripts in your jelly-web folder and access it from `/web/KefinTweaks` if you like.
3. **Navigate to the Plugins page and click on KefinTweaks**
4. **Choose your preferred plugin version, or specify a custom source if you are self hosting the scripts**
5. **Press Install! KefinTweaks will automatically load without refreshing the page**
<div align="center">
<img src="pages/images/injector.png" alt="injector" style="max-width: 100%; height: auto;"/>
</div>
<img src="pages/images/plugin-install.gif" alt="injector" style="max-width: 100%; height: auto;"/>
</div>
<br/>
<hr>
### Alternate Installation Options
Some people may prefer to not include these scripts from a remote source and rather host them locally or at some other location and pull them from there. This is supported and easily achievable just follow these steps:
1. Download your prefered release, extract it and move the files to your preferred hosting location.
2. Modify the line below in your JS Injector script for KefinTweaks:
```script.src = `https://ranaldsgift.github.io/KefinTweaks/injector.js?v=${new Date().getTime()}`;```
- Replace the `https://ranaldsgift.github.io/KefinTweaks/injector.js` with the location you've chosen to host the scripts.
- Optionally remove the `?v=${new Date().getTime()}` if you prefer the script to not auto-refresh when you make local changes.
![KefinTweaks script include](https://i.imgur.com/n2h0eux.png)
3. Copy the contents of [kefintweaks-default-config.js](https://raw.githubusercontent.com/ranaldsgift/KefinTweaks/refs/heads/main/kefinTweaks-default-config.js) into JS Injector by adding a new script or updating the existing one.
![KefinTweaks-Config](https://i.imgur.com/OkoD2UJ.png)
- Ensure the name is "KefinTweaks-Config"
4. Update the "kefinTweaksRoot" and the "scriptRoot" values in the KefinTweaks-Config in JS Injector so that they reflect the locations you've chosen to host the scripts.
5. Save your changes!
Also, it's probably worth noting that you should be able to include the scripts in your jelly-web folder and access it from `/web/KefinTweaks` if you like.
## Configuration
KefinTweaks includes a comprehensive configuration interface accessible to administrators. The configuration UI allows you to customize all aspects of KefinTweaks without editing code files.
@@ -122,9 +106,9 @@ KefinTweaks includes a comprehensive configuration interface accessible to admin
The configuration interface can be accessed in multiple ways:
1. **From User Preferences**: Navigate to your User Preferences page and find the "Administration" section. Click the "Configure KefinTweaks" button.
2. **From Dashboard**: In the Dashboard side menu, find the "KefinTweaks" button under the Plugins section.
3. **From Left Navigation Menu**: A "Configure" link will appear in the left navigation menu beneath a KefinTweaks heading.
1. **From Dashboard**: In the Dashboard side menu, find the "KefinTweaks" button under the Plugins section.
2. **From Left Navigation Menu**: A "Configure" link will appear in the left navigation menu beneath a KefinTweaks heading.
3. **From User Preferences**: Navigate to your User Preferences page and find the "Administration" section. Click the "Configure KefinTweaks" button.
### Configuration Features
@@ -163,6 +147,19 @@ On first load, KefinTweaks automatically:
These tasks only run for administrator users and happen automatically in the background.
<hr>
## Uninstallation
1. **Navigate to the Plugins page and click on KefinTweaks**
2. **Press Uninstall and click to confirm**
<div align="center">
<img src="pages/images/plugin-uninstall.gif" alt="injector" style="max-width: 100%; height: auto;"/>
</div>
<br/>
<hr>
## Features Overview
KefinTweaks provides a modular system of enhancements that can be individually enabled or disabled based on your needs. Each script is designed to work independently while sharing common utilities and dependencies.
@@ -612,6 +609,7 @@ This README and the inline comments in kefinTweaks.js and the other scripts is t
<hr>
### Version History
- **v0.3.2**: Add proper installation and uninstallation support from the Plugins page. New feature: Playlist sorting.
- **v0.3.1**: New Home Screen Section feature: Spotlights! Improved Configuration UI.
- **v0.3.0**: KefinTweaks configuration for admins in the Jellyfin UI!
- **v0.2.4**: New Feature: Watchlist Export/Import
+51 -128
View File
@@ -6,7 +6,7 @@
'use strict';
console.log('[KefinTweaks Injector] Initializing...');
const versionNumber = "0.3.1";
const versionNumber = "0.3.2";
// Configuration: Start with defaults, then merge user config
// This allows new scripts to work out of the box without requiring config updates
@@ -198,8 +198,8 @@
name: 'playlist',
script: 'playlist.js',
css: null,
dependencies: ['cardBuilder', 'utils'],
description: 'Modifies playlist view page behavior to navigate to item details instead of playing, and adds play button to playlist items'
dependencies: ['cardBuilder', 'utils', 'modal'],
description: 'Modifies playlist view page behavior to navigate to item details instead of playing, adds play button to playlist items, and adds sorting functionality'
},
{
name: 'itemDetailsCollections',
@@ -227,25 +227,6 @@
// Use this for development
//const urlSuffix = '';
const urlSuffix = `?v=${new Date().getTime()}`;
// Fetch version date from GitHub releases API
async function fetchVersionDate(version) {
try {
const response = await fetch(`https://api.github.com/repos/ranaldsgift/KefinTweaks/releases/tags/v${version}`);
if (!response.ok) {
// Try latest release if tag doesn't exist
const latestResponse = await fetch(`https://api.github.com/repos/ranaldsgift/KefinTweaks/releases/latest`);
if (!latestResponse.ok) throw new Error('Failed to fetch release data');
const data = await latestResponse.json();
return new Date(data.published_at);
}
const data = await response.json();
return new Date(data.published_at);
} catch (error) {
console.warn('[KefinTweaks Injector] Failed to fetch version date from GitHub:', error);
return null;
}
}
// Inject dashboard drawer version badge CSS using dynamic version number
function injectVersionBadgeCSS() {
@@ -336,21 +317,32 @@
// Get the root path for scripts from configuration
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/';
const kefinTweaksRoot = window.KefinTweaksConfig?.kefinTweaksRoot;
// If kefinTweaksRoot is not set or empty, return null (scripts should not load)
if (!kefinTweaksRoot || kefinTweaksRoot === '') {
console.warn('[KefinTweaks Injector] kefinTweaksRoot is not configured');
return null;
}
return window.KefinTweaksConfig?.scriptRoot || 'https://ranaldsgift.github.io/KefinTweaks/scripts/';
// Ensure root ends with /
const root = kefinTweaksRoot.endsWith('/') ? kefinTweaksRoot : kefinTweaksRoot + '/';
// Return scripts path
return root + 'scripts/';
}
// Load a CSS file
function loadCSS(filename) {
return new Promise((resolve, reject) => {
const scriptRoot = getScriptRoot();
if (!scriptRoot) {
reject(new Error('kefinTweaksRoot is not configured'));
return;
}
// Check if CSS is already loaded (match by root and filename, ignore suffix)
const existingLink = document.querySelector(`link[href*="${getScriptRoot()}${filename}"]`);
const existingLink = document.querySelector(`link[href*="${scriptRoot}${filename}"]`);
if (existingLink) {
console.log(`[KefinTweaks Injector] CSS already loaded: ${filename}`);
resolve();
@@ -360,7 +352,7 @@
const link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = `${getScriptRoot()}${filename}${urlSuffix}`;
link.href = `${scriptRoot}${filename}${urlSuffix}`;
link.onload = () => {
console.log(`[KefinTweaks Injector] CSS loaded: ${filename}`);
@@ -379,8 +371,14 @@
// Load a JavaScript file
function loadScript(filename) {
return new Promise((resolve, reject) => {
const scriptRoot = getScriptRoot();
if (!scriptRoot) {
reject(new Error('kefinTweaksRoot is not configured'));
return;
}
// Check if script is already loaded
const existingScript = document.querySelector(`script[src*="${getScriptRoot()}${filename}"]`);
const existingScript = document.querySelector(`script[src*="${scriptRoot}${filename}"]`);
if (existingScript) {
console.log(`[KefinTweaks Injector] Script already loaded: ${filename}`);
resolve();
@@ -388,7 +386,7 @@
}
const script = document.createElement('script');
script.src = `${getScriptRoot()}${filename}${urlSuffix}`;
script.src = `${scriptRoot}${filename}${urlSuffix}`;
script.async = true;
script.onload = () => {
@@ -438,8 +436,13 @@
// Load a single script (assumes dependencies are already loaded)
async function loadScriptSync(scriptDef) {
const scriptRoot = getScriptRoot();
if (!scriptRoot) {
throw new Error('kefinTweaksRoot is not configured');
}
// Check if already loaded (match by root and filename, ignore suffix)
const isAlreadyLoaded = document.querySelector(`script[src*="${getScriptRoot()}${scriptDef.script}"]`);
const isAlreadyLoaded = document.querySelector(`script[src*="${scriptRoot}${scriptDef.script}"]`);
if (isAlreadyLoaded) {
console.log(`[KefinTweaks Injector] Script already loaded: ${scriptDef.name}`);
return;
@@ -460,6 +463,13 @@
async function initialize() {
console.log(`[KefinTweaks Injector] Starting KefinTweaks v${versionNumber} initialization...`);
// Check if kefinTweaksRoot is configured
const scriptRoot = getScriptRoot();
if (!scriptRoot) {
console.log('[KefinTweaks Injector] kefinTweaksRoot is not configured. Please configure KefinTweaks using the installer before scripts can be loaded.');
return;
}
// Validate configuration
if (!validateConfiguration()) {
console.error('[KefinTweaks Injector] Configuration validation failed. Aborting.');
@@ -713,96 +723,6 @@
}
}
// Ensure KefinTweaks-Config exists in JS Injector
async function ensureKefinTweaksConfig() {
try {
console.log('[KefinTweaks Startup] Ensuring KefinTweaks-Config exists in JS Injector...');
const pluginId = await findPlugin('JavaScript Injector') || await findPlugin('JS Injector');
if (!pluginId) {
console.warn('[KefinTweaks Startup] JavaScript Injector plugin not found');
return false;
}
const injectorConfig = await getPluginConfig(pluginId);
// Ensure CustomJavaScripts array exists
if (!injectorConfig.CustomJavaScripts) {
injectorConfig.CustomJavaScripts = [];
}
// Check if KefinTweaks-Config already exists
const existingScript = injectorConfig.CustomJavaScripts.find(
script => script.Name === 'KefinTweaks-Config'
);
if (existingScript) {
console.log('[KefinTweaks Startup] KefinTweaks-Config already exists in JS Injector');
return true;
}
// Load default config
let defaultConfig;
if (window.KefinTweaksDefaultConfig) {
defaultConfig = window.KefinTweaksDefaultConfig;
} else {
// Try to load it
try {
const defaultConfigUrl = window.KefinTweaksConfig?.kefinTweaksRoot
? `${window.KefinTweaksConfig.kefinTweaksRoot}kefinTweaks-default-config.js`
: 'https://ranaldsgift.github.io/KefinTweaks/kefinTweaks-default-config.js';
await new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = defaultConfigUrl;
script.onload = () => resolve();
script.onerror = () => reject(new Error('Failed to load default config'));
document.head.appendChild(script);
});
defaultConfig = window.KefinTweaksDefaultConfig;
} catch (error) {
console.error('[KefinTweaks Startup] Error loading default config:', error);
// Create minimal config
defaultConfig = {
kefinTweaksRoot: 'https://ranaldsgift.github.io/KefinTweaks/',
scriptRoot: 'https://ranaldsgift.github.io/KefinTweaks/scripts/',
scripts: {},
homeScreen: {},
exclusiveElsewhere: {},
search: {},
skins: [],
themes: [],
customMenuLinks: []
};
}
}
// Create the script content
const scriptContent = `// KefinTweaks Configuration
// This file is automatically generated by KefinTweaks Configuration UI
// Do not edit manually unless you know what you're doing
window.KefinTweaksConfig = ${JSON.stringify(defaultConfig, null, 2)};`;
// Add new script
injectorConfig.CustomJavaScripts.push({
Name: 'KefinTweaks-Config',
Script: scriptContent,
Enabled: true,
RequiresAuthentication: false
});
// Save the configuration
await savePluginConfig(pluginId, injectorConfig);
window.KefinTweaksConfig = defaultConfig;
console.log('[KefinTweaks Startup] KefinTweaks-Config added to JS Injector');
return true;
} catch (error) {
console.error('[KefinTweaks Startup] Error ensuring KefinTweaks-Config:', error);
return false;
}
}
// Ensure Watchlist tab exists in CustomTabs
async function ensureWatchlistTab() {
@@ -885,6 +805,12 @@ window.KefinTweaksConfig = ${JSON.stringify(defaultConfig, null, 2)};`;
async function startupTask() {
console.log('[KefinTweaks Startup] Starting startup task...');
// Check if kefinTweaksRoot is configured - startup tasks only run after installation
if (!window.KefinTweaksConfig?.kefinTweaksRoot || window.KefinTweaksConfig.kefinTweaksRoot === '') {
console.log('[KefinTweaks Startup] kefinTweaksRoot is not configured, skipping startup tasks');
return;
}
// Check if user is admin (wait up to 5s for login)
const isAdmin = await checkAdminWithTimeout(5000);
@@ -894,10 +820,7 @@ window.KefinTweaksConfig = ${JSON.stringify(defaultConfig, null, 2)};`;
}
try {
// Task 1: Ensure KefinTweaks-Config exists in JS Injector
await ensureKefinTweaksConfig();
// Task 2: Ensure Watchlist tab exists in CustomTabs
// Task: Ensure Watchlist tab exists in CustomTabs
await ensureWatchlistTab();
console.log('[KefinTweaks Startup] Startup task completed successfully');
+1 -2
View File
@@ -1,6 +1,5 @@
window.KefinTweaksDefaultConfig = {
"kefinTweaksRoot": "https://ranaldsgift.github.io/KefinTweaks/",
"scriptRoot": "https://ranaldsgift.github.io/KefinTweaks/scripts/",
"kefinTweaksRoot": "",
"scripts": {
"homeScreen": true,
"search": true,
File diff suppressed because it is too large Load Diff
-309
View File
@@ -1,309 +0,0 @@
// KefinTweaks - Central Configuration and Script Loader
// Copy this entire file into your Jellyfin JS injector plugin
// Modify the configuration below to enable/disable features and customize settings
//
// Full compatibility with Jellyfin 10.10.7
// Untested compatibility with Jellyfin 10.11+
//
// FEATURES OVERVIEW:
// =================
// For a detailed feature overview see README.md at https://github.com/ranaldsgift/KefinTweaks
//
// PREREQUISITES:
// ============
// - JS Injector plugin https://github.com/n00bcodr/Jellyfin-JavaScript-Injector
// - Custom Tabs plugin for watchlist functionality https://github.com/IAmParadox27/jellyfin-plugin-custom-tabs
// - JellyfinEnhanced plugin for Jellyseerr search functionality and ExclusiveElsewhere https://github.com/n00bcodr/Jellyfin-Enhanced
//
// INSTALLATION:
// ============
// 0a. Install required prerequisites listed above
// 0b. If you are using JellyfinEnhanced, you MUST disable the "Watchlist" feature from the plugin settings
// 1. Add a new script to your JS Injector plugin
// 2. Copy this entire file into the new script
// (Optional) Enable/disable features, or customize their configuration by editing the script below
// 3. Ensure the script is enabled
// 4. Save your changes
//
// CONFIGURATION GUIDE:
// ===================
// - Scripts: Enable/disable individual features
// - Home Screen: Configure custom sections and discovery settings
// - Exclusive Elsewhere: Only useful if you want to display an icon or logo instead of text
// - Search: Enable Jellyseerr integration adds an option to search for Jellyseerr results only
//
// Considerations for the home screen sections:
// - 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';
// ============================================================================
// CENTRAL CONFIGURATION
// ============================================================================
const KEFIN_TWEAKS_CONFIG = {
// ============================================================================
// SCRIPT ROOT CONFIGURATION
// ============================================================================
// Configure where scripts are loaded from
kefinTweaksRoot: 'https://ranaldsgift.github.io/KefinTweaks/',
scriptRoot: 'https://ranaldsgift.github.io/KefinTweaks/scripts/',
// ============================================================================
// SCRIPT ENABLE/DISABLE FLAGS
// ============================================================================
// Toggle individual features on/off. Dependencies are automatically managed.
scripts: {
// Major feature enhancements
homeScreen: true, // Custom home screen sections and discovery engine
search: true, // Enhanced search with better UI and Jellyseerr integration
// REQUIREMENTS: Watchlist requires Custom Tabs plugin https://github.com/IAmParadox27/jellyfin-plugin-custom-tabs
// If you are using JellyfinEnhanced, you MUST disable the "Watchlist" feature from the plugin settings
watchlist: true, // Complete watchlist management with progress tracking
// UI and navigation improvements
headerTabs: true, // Improved header tab functionality and behavior
customMenuLinks: true, // Load and add custom menu links from configuration
breadcrumbs: true, // Breadcrumb navigation for Movies, Series, Seasons, Episodes, Music
playlist: true, // Enhanced playlist view with better navigation
itemDetailsCollections: true, // Shows collections on item details pages (Included In section)
flattenSingleSeasonShows: true, // Flattens single-season shows to display episodes directly on series page
collections: true, // Collection sorting functionality
// Reviews and recommendations
// updoot: false, // This is unsupported for now but will be worked on in the future
// Branding and external integrations
// REQUIRES: ExclusiveElsewhere requires JellyfinEnhanced plugin https://github.com/n00bcodr/Jellyfin-Enhanced
exclusiveElsewhere: false, // Custom branding when items aren't available on streaming services
// Performance and stability improvements
backdropLeakFix: true, // Fixes memory leaks from backdrop images when tab isn't focused
dashboardButtonFix: true, // Proper back button behavior on dashboard
infiniteScroll: true, // Smooth infinite scrolling. Supports Movie and TV libraries. Music to be added in the future.
// Quality of life improvements
removeContinue: true, // Add "Remove from Continue Watching" functionality
subtitleSearch: true, // Search and download subtitles directly from video OSD
// UI customization
skinManager: true // Skin selection and management - adds skin dropdown to header and display preferences
},
// ============================================================================
// HOME SCREEN CONFIGURATION
// ============================================================================
// Configure custom home screen sections and discovery features
homeScreen: {
// NEW AND TRENDING SECTIONS
// Automatically curated sections showing latest content
enableNewAndTrending: true, // Master toggle for new and trending sections
enableNewMovies: true, // Show newest movies section
enableNewEpisodes: true, // Show newest episodes from last 7 days
enableTrending: false, // Show trending sections (currently stubs, order 32-33)
// DISCOVERY SECTIONS
// Intelligent recommendations based on your library content
// Includes:
// - [Genre] Movies (random genres with sufficient movie count)
// - Directed by [Director] (top directors from your library)
// - Written by [Writer] (top writers from your library)
// - Starring [Actor] (top actors from your library)
// - Because you watched [Movie] (similar content based on watch history)
// - Because you liked [Movie] (similar content based on favorites)
enableDiscovery: true, // Enables discovery sections on the home screen
// Discovery section behavior and filtering
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
// WATCHLIST SECTION
// Requires watchlist script to be enabled
enableWatchlist: true, // Show watchlist section on home screen
// SEASONAL SECTIONS
// Holiday-themed content sections
enableSeasonal: true, // Master toggle for seasonal sections
seasonalItemLimit: 16, // Maximum items to show in seasonal sections
// CUSTOM SECTIONS
// Add your own playlist/collection-based sections
customSections: [
// Add more custom sections here
/* Example:
{
// Optionally provide a name, otherwise the name of the playlist/collection will be used
name: 'Your Custom Section',
// The ID of the playlist/collection to display in the section, get this from Jellyfin playlist/collection URL
id: 'acc84f03475eb51602b13cde158d21ad',
// The maximum number of items to display in the section on the home screen
maxItems: 20,
// The order of the section for it to appear on the home screen
order: 5
},
*/
],
},
// ============================================================================
// EXCLUSIVE ELSEWHERE CONFIGURATION
// ============================================================================
// Configure exclusive elsewhere branding behavior
exclusiveElsewhere: {
hideServerName: false // Set to true to hide server name, useful when you want to only show a logo or icon via CSS
},
// ============================================================================
// SEARCH CONFIGURATION
// ============================================================================
// Configure search functionality
search: {
enableJellyseerr: false // Enable Jellyseerr integration for request functionality
},
// ============================================================================
// SKIN CONFIGURATION
// ============================================================================
// Configure additional skins available in the skin manager
// These will be merged with skins from userConfig.js
// Each skin's url field should be an array of objects with majorServerVersions and urls arrays
// This allows you to specify different CSS bundles for different server versions
// Each skin can also have colorSchemes array for color variations specific to that skin
// Each skin should have an author field indicating who created the skin
skins: [
// Add additional skins here that will be available to all users
// These are example skins - replace with your own or remove if not needed
/* Example of adding more skins:
{
name: 'Custom Theme',
author: 'username',
url: [
{
majorServerVersions: [10, 11],
urls: ['https://cdn.jsdelivr.net/gh/username/custom-theme.css']
}
],
colorSchemes: []
},
{
name: 'Multi-file Theme',
author: 'username',
url: [
{
majorServerVersions: [10, 11],
urls: [
'https://cdn.jsdelivr.net/gh/username/theme-base.css',
'https://cdn.jsdelivr.net/gh/username/theme-extensions.css'
]
}
],
colorSchemes: [
{
name: 'Dark Blue',
url: 'https://cdn.jsdelivr.net/gh/username/theme-dark-blue.css'
},
{
name: 'Light Green',
url: 'https://cdn.jsdelivr.net/gh/username/theme-light-green.css'
}
]
},
{
name: 'Version-Specific Theme',
author: 'username',
url: [
{
majorServerVersions: [10],
urls: ['https://cdn.jsdelivr.net/gh/username/theme-v10.css']
},
{
majorServerVersions: [11],
urls: [
'https://cdn.jsdelivr.net/gh/username/theme-v11.css',
'https://cdn.jsdelivr.net/gh/username/theme-v11-fixes.css'
]
}
],
colorSchemes: []
},
*/
],
// Default skin to use when no skin is selected
// This overrides the default selection from userConfig.js
defaultSkin: null, // Set to skin name (e.g., 'ElegantFin') or null to use userConfig default
// ============================================================================
// THEME CONFIGURATION
// ============================================================================
// Configure additional themes available in the theme manager
// These will be merged with themes from userConfig.js
// Each theme should have a name and URL pointing to a CSS file
themes: [
// Add additional themes here that will be available to all users
// These are example themes - replace with your own or remove if not needed
/* Example of adding more themes:
{
name: 'Custom Dark Theme',
url: 'https://cdn.jsdelivr.net/gh/username/custom-dark-theme.css'
},
{
name: 'Custom Light Theme',
url: 'https://cdn.jsdelivr.net/gh/username/custom-light-theme.css'
},
*/
],
// ============================================================================
// CUSTOM MENU LINKS CONFIGURATION
// ============================================================================
// Configure custom menu links to be added to the custom menu
customMenuLinks: [
// Add your custom menu links here
/* Example:
{
name: 'My Custom Link', // Display name for the menu item
icon: 'link', // Material icon name (optional, defaults to 'link')
url: '#/userpluginsettings.html?pageUrl=https://domain.com/custom-page', // URL to navigate to
openInNewTab: false // Whether to open in new tab (optional, defaults to false)
},
{
name: 'External Link',
icon: 'open_in_new',
url: 'https://example.com',
openInNewTab: true
},
*/
],
};
// ============================================================================
// CONFIGURATION EXPOSURE
// ============================================================================
// Make configuration available to individual scripts
window.KefinTweaksConfig = KEFIN_TWEAKS_CONFIG;
// ============================================================================
// SCRIPT LOADER INITIALIZATION
// ============================================================================
// Load the injector script which handles loading individual scripts based on configuration
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');
})();
+383 -59
View File
@@ -1,63 +1,387 @@
(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
window.KefinTweaksConfig = {
"kefinTweaksRoot": "",
"scripts": {
"homeScreen": true,
"search": true,
"watchlist": true,
"headerTabs": true,
"customMenuLinks": true,
"breadcrumbs": true,
"playlist": true,
"itemDetailsCollections": true,
"flattenSingleSeasonShows": true,
"collections": true,
"exclusiveElsewhere": false,
"backdropLeakFix": true,
"dashboardButtonFix": true,
"infiniteScroll": true,
"removeContinue": true,
"subtitleSearch": true,
"skinManager": true
},
"homeScreen": {
"defaultItemLimit": 16,
"defaultSortOrder": "Random",
"defaultCardFormat": "Poster",
"recentlyReleased": {
"enabled": true,
"order": 30,
"movies": {
"name": "Recently Released Movies",
"enabled": true,
"itemLimit": 16,
"sortOrder": "ReleaseDate",
"sortOrderDirection": "Descending",
"cardFormat": "Poster",
"order": 30
},
"episodes": {
"name": "Recently Aired Episodes",
"enabled": true,
"itemLimit": 16,
"sortOrder": "PremiereDate",
"sortOrderDirection": "Descending",
"cardFormat": "Backdrop",
"order": 31
}
},
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: [],
"trending": {
"name": "Trending",
"enabled": false,
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster",
"order": 32
},
exclusiveElsewhere: {
hideServerName: false
"popularTVNetworks": {
"name": "Popular TV Networks",
"enabled": true,
"minimumShowsForNetwork": 5,
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster",
"order": 61
},
search: {
enableJellyseerr: false
"watchlist": {
"name": "Watchlist",
"enabled": true,
"itemLimit": 16,
"sortOrder": "DateAdded",
"sortOrderDirection": "Descending",
"cardFormat": "Poster",
"order": 60
},
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');
})();
"watchAgain": {
"name": "Watch Again",
"enabled": true,
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster",
"order": 62
},
"upcoming": {
"name": "Upcoming",
"enabled": true,
"itemLimit": 48,
"cardFormat": "Thumb",
"order": 20
},
"imdbTop250": {
"name": "IMDb Top 250",
"enabled": true,
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster",
"order": 21
},
"seasonal": {
"enabled": true,
"defaultItemLimit": 16,
"defaultSortOrder": "Random",
"defaultCardFormat": "Poster",
"seasons": [
{
"id": "halloween",
"name": "Halloween",
"enabled": true,
"startDate": "10-01",
"endDate": "10-31",
"sections": [
{
"id": "halloween-tag",
"enabled": true,
"name": "Halloween Movies",
"type": "Tag",
"source": "halloween",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster",
"order": 50
},
{
"id": "halloween-horror",
"enabled": true,
"name": "Horror Genre",
"type": "Genre",
"source": "Horror",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster",
"order": 51
},
{
"id": "halloween-thriller",
"enabled": true,
"name": "Thriller Genre",
"type": "Genre",
"source": "Thriller",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster",
"order": 52
}
]
},
{
"id": "thanksgiving",
"name": "Thanksgiving",
"enabled": true,
"startDate": "11-20",
"endDate": "11-30",
"order": 50,
"sections": [
{
"id": "seasonal-2-section-0",
"enabled": true,
"name": "Thanksgiving Movies",
"type": "Tag",
"source": "thanksgiving",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster",
"order": 50,
"spotlight": true,
"discoveryEnabled": false,
"searchTerm": "",
"includeItemTypes": [
"Movie"
]
},
{
"id": "seasonal-2-section-1",
"enabled": true,
"name": "Thanksgiving Episodes",
"type": "Parent",
"source": "",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Thumb",
"order": 51,
"spotlight": false,
"discoveryEnabled": false,
"searchTerm": "thanksgiving",
"includeItemTypes": [
"Episode"
]
}
]
},
{
"id": "christmas",
"name": "Christmas",
"enabled": true,
"startDate": "12-01",
"endDate": "12-31",
"sections": [
{
"id": "christmas-genre",
"enabled": true,
"name": "Christmas Movies",
"type": "Genre",
"source": "Christmas",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster",
"order": 60
},
{
"id": "christmas-family",
"enabled": true,
"name": "Family Movies",
"type": "Genre",
"source": "Family",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster",
"order": 61
}
]
}
]
},
"discovery": {
"enabled": true,
"infiniteScroll": true,
"minPeopleAppearances": 10,
"minGenreMovieCount": 50,
"defaultItemLimit": 16,
"defaultSortOrder": "Random",
"defaultCardFormat": "Poster",
"sectionTypes": {
"spotlightGenre": {
"enabled": true,
"name": "Top Rated [Genre] Movies",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster"
},
"spotlightNetwork": {
"enabled": true,
"name": "Top Rated Shows from [Studio]",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster"
},
"genreMovies": {
"enabled": true,
"name": "[Genre] Movies",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster"
},
"studioShows": {
"enabled": true,
"name": "Shows from [Studio]",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster"
},
"collections": {
"enabled": true,
"name": "[Collection Name]",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster",
"minimumItems": 10
},
"becauseYouWatched": {
"enabled": true,
"name": "Because you watched [Movie]",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster"
},
"becauseYouLiked": {
"enabled": true,
"name": "Because you liked [Movie]",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster"
},
"starringTopActor": {
"enabled": true,
"name": "Starring [Actor]",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster"
},
"directedByTopDirector": {
"enabled": true,
"name": "Directed by [Director]",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster"
},
"writtenByTopWriter": {
"enabled": true,
"name": "Written by [Writer]",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster"
},
"becauseYouRecentlyWatched": {
"enabled": true,
"name": "Because you recently watched [Movie]",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster"
},
"starringActorRecentlyWatched": {
"enabled": true,
"name": "Starring [Actor] because you recently watched [Movie]",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster"
},
"directedByDirectorRecentlyWatched": {
"enabled": true,
"name": "Directed by [Director] because you recently watched [Movie]",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster"
},
"writtenByWriterRecentlyWatched": {
"enabled": true,
"name": "Written by [Writer] because you recently watched [Movie]",
"itemLimit": 16,
"sortOrder": "Random",
"sortOrderDirection": "Ascending",
"cardFormat": "Poster"
}
},
"spotlightDiscoveryChance": 0.5,
"spotlight": {
"autoPlay": true,
"interval": 5000,
"showDots": true,
"showNavButtons": true,
"showClearArt": true
},
"randomizeOrder": false
},
"customSections": []
},
"exclusiveElsewhere": {
"hideServerName": false
},
"search": {
"enableJellyseerr": false
},
"skins": [
{
"name": "Default",
"author": "Jellyfin",
"url": null,
"colorSchemes": [],
"enabled": true,
"hidden": true
}
],
"defaultSkin": null,
"themes": [],
"customMenuLinks": []
};
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

+21 -22
View File
@@ -45,8 +45,12 @@
return new Promise((resolve, reject) => {
try {
const defaultConfigUrl = window.KefinTweaksConfig?.kefinTweaksRoot
? `${window.KefinTweaksConfig.kefinTweaksRoot}kefinTweaks-default-config.js`
: 'https://ranaldsgift.github.io/KefinTweaks/kefinTweaks-default-config.js';
? `${window.KefinTweaksConfig.kefinTweaksRoot}kefinTweaks-default-config.js`;
if (!defaultConfigUrl) {
reject(new Error('No default config URL found'));
return;
}
// Check if already loaded
if (window.KefinTweaksDefaultConfig) {
@@ -115,9 +119,8 @@
const currentConfigSource = currentLoadedConfig || window.KefinTweaksCurrentConfig || window.KefinTweaksConfig || {};
const currentKefinRoot = currentConfigSource.kefinTweaksRoot || defaultConfig.kefinTweaksRoot;
const currentScriptRoot = currentConfigSource.scriptRoot || defaultConfig.scriptRoot;
defaultConfig.kefinTweaksRoot = currentKefinRoot;
defaultConfig.scriptRoot = currentScriptRoot;
// Note: scriptRoot is no longer used - scripts are loaded from kefinTweaksRoot + '/scripts/'
// Save defaults to JS Injector for future use
await saveConfigToJavaScriptInjector(defaultConfig);
@@ -132,8 +135,7 @@
console.error('[KefinTweaks Configuration] Error loading default config as fallback:', fallbackError);
// Return empty config structure as last resort
return {
kefinTweaksRoot: 'https://ranaldsgift.github.io/KefinTweaks/',
scriptRoot: 'https://ranaldsgift.github.io/KefinTweaks/scripts/',
kefinTweaksRoot: '',
scripts: {},
homeScreen: {},
exclusiveElsewhere: {},
@@ -361,10 +363,10 @@
const modal = window.ModalSystem.create({
id: 'scriptRootEditConfirmation',
title: 'Edit Script Root URLs',
title: 'Edit KefinTweaks Root URL',
content: `
<div class="listItemBodyText" style="margin-bottom: 1em; max-width: 600px;">
Editing the Root URLs is useful if you are hosting the scripts yourself or if you want to use a specific KefinTweaks version. There is no validation done for the URLs provided for these settings so please ensure they are valid. In any case, you can easily revert these settings.
Editing the Root URL is useful if you are hosting the scripts yourself or if you want to use a specific KefinTweaks version. Scripts will be loaded from this root URL + '/scripts/'. There is no validation done for the URL provided so please ensure it is valid. In any case, you can easily revert this setting.
</div>
`,
footer: `
@@ -417,8 +419,8 @@
<div class="listItem" style="border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 1em; margin-bottom: 1em;">
<div class="listItemContent" style="display: flex; justify-content: space-between; align-items: center;">
<div>
<h3 class="listItemBodyText" style="margin-bottom: 0.25em;">Script Root Configuration</h3>
<div class="listItemBodyText secondary">Configure where KefinTweaks scripts are loaded from</div>
<h3 class="listItemBodyText" style="margin-bottom: 0.25em;">KefinTweaks Root Configuration</h3>
<div class="listItemBodyText secondary">Configure where KefinTweaks scripts are loaded from. Scripts will be loaded from this root URL + '/scripts/'</div>
</div>
<button class="emby-button raised paper-icon-button-light" id="editScriptRootBtn">
<span class="material-icons" aria-hidden="true">edit</span>
@@ -429,17 +431,10 @@
<div class="listItem">
<div class="listItemContent">
<div class="listItemBodyText" style="margin-bottom: 0.5em;">KefinTweaks Root URL</div>
<input type="text" id="kefinTweaksRoot" class="fld emby-input" value="${config.kefinTweaksRoot || ''}" placeholder="https://ranaldsgift.github.io/KefinTweaks/" style="width: 100%; max-width: 600px;">
<div class="listItemBodyText secondary" style="margin-top: 0.5em; font-size: 0.9em;">Base URL for KefinTweaks resources</div>
<input type="text" id="kefinTweaksRoot" class="fld emby-input" value="${config.kefinTweaksRoot || ''}" placeholder="https://cdn.jsdelivr.net/gh/ranaldsgift/KefinTweaks@latest/" style="width: 100%; max-width: 600px;">
<div class="listItemBodyText secondary" style="margin-top: 0.5em; font-size: 0.9em;">Base URL for KefinTweaks resources. Scripts will be loaded from this URL + '/scripts/'</div>
</div>
</div>
<div class="listItem">
<div class="listItemContent">
<div class="listItemBodyText" style="margin-bottom: 0.5em;">Script Root URL</div>
<input type="text" id="scriptRoot" class="fld emby-input" value="${config.scriptRoot || ''}" placeholder="https://ranaldsgift.github.io/KefinTweaks/scripts/" style="width: 100%; max-width: 600px;">
<div class="listItemBodyText secondary" style="margin-top: 0.5em; font-size: 0.9em;">URL where individual script files are located</div>
</div>
</div>
</div>
</div>
@@ -4138,7 +4133,6 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
// Collect all form values
const config = {
kefinTweaksRoot: document.getElementById('kefinTweaksRoot')?.value || '',
scriptRoot: document.getElementById('scriptRoot')?.value || '',
scripts: {},
homeScreen: {},
exclusiveElsewhere: {},
@@ -4148,6 +4142,11 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
themes: [],
customMenuLinks: []
};
// Remove scriptRoot if it exists (legacy field, no longer used)
if (config.scriptRoot) {
delete config.scriptRoot;
}
// Collect script toggles
document.querySelectorAll('[data-script-key]').forEach(checkbox => {
@@ -4435,10 +4434,10 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
try {
const defaultConfig = await loadDefaultConfig();
// Retain script URLs from existing config
// Retain kefinTweaksRoot from existing config
const existingConfig = await getKefinTweaksConfig();
defaultConfig.kefinTweaksRoot = existingConfig.kefinTweaksRoot;
defaultConfig.scriptRoot = existingConfig.scriptRoot;
// Note: scriptRoot is no longer used - scripts are loaded from kefinTweaksRoot + '/scripts/'
await saveConfigToJavaScriptInjector(defaultConfig);