diff --git a/kefinTweaks-default-config.js b/kefinTweaks-default-config.js
index f9defc3..9b00528 100644
--- a/kefinTweaks-default-config.js
+++ b/kefinTweaks-default-config.js
@@ -23,6 +23,7 @@ window.KefinTweaksDefaultConfig = {
"defaultItemLimit": 16,
"defaultSortOrder": "Random",
"defaultCardFormat": "Poster",
+ "recentlyAddedInLibrary": {},
"recentlyReleased": {
"enabled": true,
"order": 30,
diff --git a/kefinTweaks.minimal.js b/kefinTweaks.minimal.js
index 53dc9f3..5f8ca4b 100644
--- a/kefinTweaks.minimal.js
+++ b/kefinTweaks.minimal.js
@@ -23,6 +23,7 @@ window.KefinTweaksConfig = {
"defaultItemLimit": 16,
"defaultSortOrder": "Random",
"defaultCardFormat": "Poster",
+ "recentlyAddedInLibrary": {},
"recentlyReleased": {
"enabled": true,
"order": 30,
diff --git a/scripts/configuration.js b/scripts/configuration.js
index 8442798..9a3e504 100644
--- a/scripts/configuration.js
+++ b/scripts/configuration.js
@@ -432,7 +432,7 @@
// Build the configuration page content
- function buildConfigPageContent(config) {
+ function buildConfigPageContent(config, libraries = []) {
const scripts = config.scripts || {};
const homeScreen = config.homeScreen || {};
const exclusiveElsewhere = config.exclusiveElsewhere || {};
@@ -509,7 +509,7 @@
- ${buildHomeScreenConfig(homeScreen)}
+ ${buildHomeScreenConfig(homeScreen, libraries)}
@@ -1487,11 +1487,11 @@
} = options;
const nameValue = config.name ?? defaultName ?? '';
- const itemLimit = config.itemLimit ?? 16;
+ const itemLimit = config.itemLimit ?? options.defaultItemLimit ?? 16;
const sortOrder = config.sortOrder ?? 'Random';
const sortOrderDirection = config.sortOrderDirection ?? 'Ascending';
- const cardFormat = config.cardFormat ?? 'Poster';
- const order = config.order ?? 100;
+ const cardFormat = config.cardFormat ?? options.defaultCardFormat ?? 'Poster';
+ const order = config.order ?? options.defaultOrder ?? 100;
// IsPlayed logic
// If isPlayed is undefined or null, it means the filter is disabled.
@@ -1638,8 +1638,9 @@
}
// Build home screen configuration
- function buildHomeScreenConfig(homeScreen) {
+ function buildHomeScreenConfig(homeScreen, libraries = []) {
const recentlyReleased = homeScreen.recentlyReleased || {};
+ const recentlyAddedInLibrary = homeScreen.recentlyAddedInLibrary || {};
const trending = homeScreen.trending || {};
const popularTVNetworks = homeScreen.popularTVNetworks || {};
const watchlist = homeScreen.watchlist || {};
@@ -1707,7 +1708,63 @@
`;
}).join('');
+ // Build library sections HTML for "Recently Added in Library"
+ let recentlyAddedInLibraryHtml = '';
+ if (libraries && libraries.length > 0) {
+ recentlyAddedInLibraryHtml = libraries.map(library => {
+ const libConfig = recentlyAddedInLibrary[library.Id] || {};
+ const prefix = `homeScreen_recentlyAddedInLibrary_${library.Id}`;
+ const defaultName = `Recently Added in ${library.Name}`;
+
+ return `
+
+
+
+ chevron_right
+ ${library.Name}
+ (${libConfig.enabled !== false ? 'Enabled' : 'Disabled'}, Order: ${libConfig.order || 11})
+
+
+
+
+ ${buildJellyfinCheckbox(`${prefix}_enabled`, libConfig.enabled !== false, 'Enabled', { 'data-library-id': library.Id, 'class': 'recently-added-library-enabled' })}
+
+
+ ${createSectionConfiguration(prefix, libConfig, {
+ includeName: true,
+ defaultName: defaultName,
+ includeSortOrder: false,
+ includeOrder: true,
+ defaultOrder: 11,
+ includeItemLimit: true,
+ defaultItemLimit: 30,
+ defaultCardFormat: library.CollectionType === 'tvshows' ? 'Thumb' : 'Poster'
+ })}
+
+
+
+ `;
+ }).join('');
+ }
+
return `
+
+ ${libraries && libraries.length > 0 ? `
+
+
+ chevron_right
+ Recently Added in Library Sections
+
+
+
+
+ ${recentlyAddedInLibraryHtml}
+
+
+
+
+ ` : ''}
+
@@ -2036,6 +2093,29 @@
}
// Open configuration modal
+ // Fetch libraries (Movies and TV)
+ async function fetchLibraries() {
+ if (!window.ApiClient) return [];
+ try {
+ // Get user views (libraries)
+ const result = await window.ApiClient.getUserViews({}, window.ApiClient.getCurrentUserId());
+ if (result && result.Items) {
+ // Filter for movies and tvshows
+ return result.Items.filter(item =>
+ item.CollectionType === 'movies' || item.CollectionType === 'tvshows'
+ ).map(item => ({
+ Id: item.Id,
+ Name: item.Name,
+ CollectionType: item.CollectionType
+ }));
+ }
+ return [];
+ } catch (error) {
+ console.error('[KefinTweaks Configuration] Error fetching libraries:', error);
+ return [];
+ }
+ }
+
async function openConfigurationModal() {
console.log('[KefinTweaks Configuration] Opening configuration modal...');
@@ -2055,13 +2135,16 @@
currentLoadedConfig = config;
window.KefinTweaksCurrentConfig = config;
+ // Fetch libraries for configuration
+ const libraries = await fetchLibraries();
+
// Build the content (without title, as ModalSystem adds it)
const content = document.createElement('div');
content.className = 'modal-content-wrapper';
content.id = MODAL_ID;
content.innerHTML = `
- ${buildConfigPageContent(config)}
+ ${buildConfigPageContent(config, libraries)}
`;
@@ -5111,7 +5194,27 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
...getSectionConfig('homeScreen_recentlyReleased_episodes', { includeName: true, defaultName: 'Recently Aired Episodes', includeSortOrder: true, includeOrder: true, includeIsPlayed: true, includePremiereDays: true })
}
},
- trending: {
+ recentlyAddedInLibrary: (() => {
+ const libraryConfigs = {};
+ const libraryCheckboxes = modalInstance.dialogContent.querySelectorAll('.recently-added-library-enabled');
+ libraryCheckboxes.forEach(checkbox => {
+ const libraryId = checkbox.getAttribute('data-library-id');
+ if (libraryId) {
+ const prefix = `homeScreen_recentlyAddedInLibrary_${libraryId}`;
+ libraryConfigs[libraryId] = {
+ enabled: checkbox.checked !== false,
+ ...getSectionConfig(prefix, {
+ includeName: true,
+ defaultName: '',
+ includeSortOrder: false,
+ includeOrder: true
+ })
+ };
+ }
+ });
+ return libraryConfigs;
+ })(),
+ trending: {
enabled: document.getElementById('homeScreen_trending_enabled')?.checked === true,
...getSectionConfig('homeScreen_trending', { includeName: true, defaultName: 'Trending' })
},
diff --git a/scripts/homeScreen.js b/scripts/homeScreen.js
index c2bfb95..77b22e7 100644
--- a/scripts/homeScreen.js
+++ b/scripts/homeScreen.js
@@ -777,6 +777,9 @@
// Custom home sections configuration
const customHomeSections = homeScreenConfig.customSections || [];
+ // Recently Added in Library configuration
+ const recentlyAddedInLibraryConfig = homeScreenConfig.recentlyAddedInLibrary || {};
+
// Recently Released sections configuration
const recentlyReleasedConfig = homeScreenConfig.recentlyReleased || {};
const enableNewAndTrending = recentlyReleasedConfig.enabled !== false;
@@ -1429,6 +1432,127 @@
LOG(`Rendered ${successCount}/${sectionsToRender.length} new and trending sections`);
}
+ /**
+ * Renders Recently Added in Library sections
+ * @param {HTMLElement} container - Container to append sections to
+ */
+ async function renderAllRecentlyAddedInLibrarySections(container) {
+ if (!recentlyAddedInLibraryConfig || Object.keys(recentlyAddedInLibraryConfig).length === 0) {
+ return;
+ }
+
+ const libraryIds = Object.keys(recentlyAddedInLibraryConfig);
+ const sectionsToRender = [];
+
+ libraryIds.forEach(libraryId => {
+ const config = recentlyAddedInLibraryConfig[libraryId];
+ if (config && config.enabled !== false) {
+ // Check if already rendered
+ const sectionId = `recently-added-${libraryId}`;
+ if (!container.querySelector(`[data-custom-section-id="${sectionId}"]`)) {
+ sectionsToRender.push(renderRecentlyAddedInLibrarySection(libraryId, config, container));
+ }
+ }
+ });
+
+ if (sectionsToRender.length === 0) {
+ return;
+ }
+
+ const results = await Promise.all(sectionsToRender);
+ const successCount = results.filter(Boolean).length;
+ LOG(`Rendered ${successCount}/${sectionsToRender.length} recently added library sections`);
+ }
+
+ /**
+ * Renders a single Recently Added in Library section
+ * @param {string} libraryId - The library ID
+ * @param {Object} sectionConfig - Configuration for this section
+ * @param {HTMLElement} container - Container to append section to
+ */
+ async function renderRecentlyAddedInLibrarySection(libraryId, sectionConfig, container) {
+ if (sectionConfig.enabled === false) return false;
+
+ const userId = ApiClient.getCurrentUserId();
+ const itemLimit = sectionConfig.itemLimit || 30;
+
+ // Determine IncludeItemTypes and viewMoreUrl based on library type
+ let includeItemTypes = 'Movie,Episode'; // Default fallback
+ let viewMoreUrl = `#/list.html?parentId=${libraryId}&serverId=${ApiClient.serverId()}`; // Default fallback
+
+ try {
+ const library = await window.ApiClient.getItem(userId, libraryId);
+ if (library) {
+ if (library.CollectionType === 'movies') {
+ includeItemTypes = 'Movie';
+ viewMoreUrl = `#/movies.html?topParentId=${libraryId}&collectionType=movies&tab=1`;
+ } else if (library.CollectionType === 'tvshows') {
+ includeItemTypes = 'Episode';
+ viewMoreUrl = `#/tv.html?topParentId=${libraryId}&collectionType=tvshows&tab=1`;
+ }
+ }
+ } catch (e) {
+ WARN(`Failed to fetch library info for ${libraryId}`, e);
+ }
+
+ // Use Users/Items/Latest endpoint logic
+ // Since apiHelper might not have a dedicated getLatestItems, we construct query
+ const params = {
+ Limit: itemLimit,
+ ParentId: libraryId,
+ ImageTypeLimit: 1,
+ EnableImageTypes: 'Primary,Backdrop,Thumb',
+ IncludeItemTypes: includeItemTypes
+ };
+
+ const queryParams = Object.entries(params)
+ .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
+ .join('&');
+
+ const url = `${ApiClient.serverAddress()}/Users/${userId}/Items/Latest?${queryParams}`;
+
+ try {
+ const response = await fetch(url, {
+ headers: {
+ 'Authorization': `MediaBrowser Token="${ApiClient.accessToken()}"`
+ }
+ });
+
+ if (!response.ok) throw new Error(`Fetch failed: ${response.status}`);
+ const items = await response.json();
+
+ if (!items || items.length === 0) return false;
+
+ // Render section
+ if (!window.cardBuilder || !window.cardBuilder.renderCards) {
+ WARN("cardBuilder.renderCards not available");
+ return false;
+ }
+
+ const cardContainer = window.cardBuilder.renderCards(
+ items,
+ sectionConfig.name || `Recently Added`,
+ viewMoreUrl,
+ true, // overflowCard (use standard overflow style for horizontal scroll)
+ sectionConfig.cardFormat || 'Poster',
+ false, // sortOrder (already sorted)
+ 'Ascending'
+ );
+
+ const sectionId = `recently-added-${libraryId}`;
+ cardContainer.setAttribute('data-custom-section-id', sectionId);
+ cardContainer.setAttribute('data-custom-section-name', sectionConfig.name || `Recently Added`);
+ cardContainer.style.order = sectionConfig.order || 11;
+
+ container.appendChild(cardContainer);
+ return true;
+
+ } catch (err) {
+ ERR(`Error rendering recently added library section ${libraryId}:`, err);
+ return false;
+ }
+ }
+
/************ Seasonal Section Helpers ************/
/**
@@ -5810,6 +5934,11 @@
initPromises.push(renderAllCustomSections(homeSectionsContainer));
}
+ // Add Recently Added in Library sections
+ if (recentlyAddedInLibraryConfig && Object.keys(recentlyAddedInLibraryConfig).length > 0) {
+ initPromises.push(renderAllRecentlyAddedInLibrarySections(homeSectionsContainer));
+ }
+
// Add new and trending sections if enabled
if (enableNewAndTrending) {
initPromises.push(renderAllNewAndTrendingSections(homeSectionsContainer));