From e86d0378d70c7d4cb7044c2102096641f695d207 Mon Sep 17 00:00:00 2001 From: HighImKevin Date: Wed, 21 Oct 2026 07:41:58 -0400 Subject: [PATCH] Ensure Recently-Added library sections are treated like default kefin sections including the Restore Defaults button --- configuration/homeScreen-configuration.js | 127 ++++++++++++++-------- configuration/homeScreenAdvancedEditor.js | 19 +++- 2 files changed, 95 insertions(+), 51 deletions(-) diff --git a/configuration/homeScreen-configuration.js b/configuration/homeScreen-configuration.js index df8baf3..ed53d68 100644 --- a/configuration/homeScreen-configuration.js +++ b/configuration/homeScreen-configuration.js @@ -816,6 +816,78 @@ }; } + const RECENTLY_ADDED_POSTER_TYPES = ['movies', 'tvshows']; + + /** + * Canonical Recently Added section for one library. Same object created during library sync. + * @param {Object} library + * @param {boolean} isJellyfin12 + * @returns {Object} + */ + function buildRecentlyAddedSection(library, isJellyfin12) { + let viewMoreUrl = null; + if (library.CollectionType === 'movies') { + viewMoreUrl = `#/movies.html?topParentId=${library.Id}&collectionType=movies&tab=1`; + } else if (library.CollectionType === 'tvshows') { + viewMoreUrl = `#/tv.html?topParentId=${library.Id}&collectionType=tvshows&tab=1`; + } else if (library.CollectionType === 'music') { + viewMoreUrl = `#/music.html?topParentId=${library.Id}&collectionType=music&tab=1`; + } else if (isJellyfin12) { + if (library.CollectionType === 'homevideos') { + viewMoreUrl = `#/homevideos?topParentId=${library.Id}&collectionType=homevideos&tab=1`; + } else if (library.CollectionType === 'books') { + viewMoreUrl = `#/books?topParentId=${library.Id}&collectionType=books&tab=1`; + } else if (library.CollectionType === 'musicvideos') { + viewMoreUrl = `#/musicvideos?topParentId=${library.Id}&collectionType=musicvideos&tab=1`; + } else { + viewMoreUrl = `#/mixed?topParentId=${library.Id}&collectionType=mixed&tab=1`; + } + } else { + viewMoreUrl = `#/list.html?parentId=${library.Id}&collectionType=${library.CollectionType}&tab=1`; + } + + return { + id: `recently-added-${library.Id}`, + name: `Recently Added ${library.Name}`, + viewMoreUrl: viewMoreUrl, + jellyfinId: 'latestmedia', + userConfigurable: true, + ttl: 0, + enabled: false, + order: 61, + cardFormat: RECENTLY_ADDED_POSTER_TYPES.includes(library.CollectionType) ? 'Poster' : 'Thumb', + type: 'home', + queries: [{ + path: '/Items/Latest', + queryOptions: { + ParentId: library.Id, + Fields: 'PrimaryImageAspectRatio,Path', + Limit: 16, + ImageTypeLimit: 1, + EnableImageTypes: 'Primary,Backdrop,Thumb', + } + }] + }; + } + + /** + * Resolve a saved recently-added-* id back to the library template. + * @param {string} sectionId + * @returns {Promise} + */ + async function buildRecentlyAddedDefaultSection(sectionId) { + const prefix = 'recently-added-'; + if (!sectionId || !String(sectionId).startsWith(prefix)) return null; + if (!window.dataHelper?.getLibraries) return null; + const libId = String(sectionId).slice(prefix.length); + if (!libId) return null; + const libraries = await window.dataHelper.getLibraries(); + const library = (libraries || []).find(l => l?.Id === libId); + if (!library) return null; + const isJellyfin12 = await window.KefinTweaks?.getJellyfinMajorVersion?.(); + return buildRecentlyAddedSection(library, isJellyfin12); + } + /** * Verify and sync library-based section groups (Recently Added, Popular Genres, etc.) with actual Jellyfin libraries * @param {Object} config - Current configuration object @@ -852,53 +924,7 @@ { idPrefix: 'recently-added-', groupId: 'home-recently-added', - groupName: 'Recently Added', - sectionTemplate: { - enabled: false, - order: 61, - cardFormat: 'Poster', - buildSection: function (library) { - let viewMoreUrl = null; - if (library.CollectionType === 'movies') { - viewMoreUrl = `#/movies.html?topParentId=${library.Id}&collectionType=movies&tab=1`; - } else if (library.CollectionType === 'tvshows') { - viewMoreUrl = `#/tv.html?topParentId=${library.Id}&collectionType=tvshows&tab=1`; - } else if (library.CollectionType === 'music') { - viewMoreUrl = `#/music.html?topParentId=${library.Id}&collectionType=music&tab=1`; - } else if (isJellyfin12) { - if (library.CollectionType === 'homevideos') { - viewMoreUrl = `#/homevideos?topParentId=${library.Id}&collectionType=homevideos&tab=1`; - } else if (library.CollectionType === 'books') { - viewMoreUrl = `#/books?topParentId=${library.Id}&collectionType=books&tab=1`; - } else if (library.CollectionType === 'musicvideos') { - viewMoreUrl = `#/musicvideos?topParentId=${library.Id}&collectionType=musicvideos&tab=1`; - } else { - viewMoreUrl = `#/mixed?topParentId=${library.Id}&collectionType=mixed&tab=1`; - } - } else { - viewMoreUrl = `#/list.html?parentId=${library.Id}&collectionType=${library.CollectionType}&tab=1`; - } - - return { - id: `recently-added-${library.Id}`, - name: `Recently Added ${library.Name}`, - viewMoreUrl: viewMoreUrl, - jellyfinId: 'latestmedia', - userConfigurable: true, - ttl: 0, - queries: [{ - path: '/Items/Latest', - queryOptions: { - ParentId: library.Id, - Fields: 'PrimaryImageAspectRatio,Path', - Limit: 16, - ImageTypeLimit: 1, - EnableImageTypes: 'Primary,Backdrop,Thumb', - } - }] - }; - } - } + groupName: 'Recently Added' }, { idPrefix: 'popular-genres-', @@ -971,6 +997,10 @@ group.sections = groupSections.filter(s => !sectionsToRemoveIds.has(s.id)); librariesToAdd.forEach(library => { + if (template.idPrefix === 'recently-added-') { + group.sections.push(buildRecentlyAddedSection(library, isJellyfin12)); + return; + } const base = template.sectionTemplate.buildSection(library); const newSection = { ...template.sectionTemplate, @@ -991,7 +1021,7 @@ const library = librariesById.get(libId); if (!library) continue; - const expectedUrl = template.sectionTemplate.buildSection(library)?.viewMoreUrl; + const expectedUrl = buildRecentlyAddedSection(library, isJellyfin12)?.viewMoreUrl; const query = section.queries?.[0]; if (query) { if (!query.queryOptions) query.queryOptions = {}; @@ -6322,6 +6352,7 @@ window.KefinTweaksConfig = ${JSON.stringify(configToBackup, null, 2)};`; window.KefinHomeScreen.findSectionInAllGroups = findSectionInAllGroups; window.KefinHomeScreen.ensureKefinTweaksDefaultSections = ensureKefinTweaksDefaultSections; window.KefinHomeScreen.collectCustomDiscoverySections = collectCustomDiscoverySections; + window.KefinHomeScreen.buildRecentlyAddedDefaultSection = buildRecentlyAddedDefaultSection; LOG('Home Screen Configuration UI loaded'); diff --git a/configuration/homeScreenAdvancedEditor.js b/configuration/homeScreenAdvancedEditor.js index f46b635..975bbb0 100644 --- a/configuration/homeScreenAdvancedEditor.js +++ b/configuration/homeScreenAdvancedEditor.js @@ -3951,10 +3951,15 @@ return null; } + function isRecentlyAddedDefaultSection(section) { + return String(section?.id || '').startsWith('recently-added-'); + } + function isKefinDefaultSection(section, context) { if (context?.isCustomSection === true) return false; const id = section?.id; if (!id) return false; + if (isRecentlyAddedDefaultSection(section)) return true; return getCanonicalDefaultSection(id) != null; } @@ -4693,12 +4698,20 @@ showSectionImportPasteModal(apply); } - function handleHeaderRestoreDefaults(modalInstance) { + async function handleHeaderRestoreDefaults(modalInstance) { const state = modalInstance?._hsaeState; const dialog = modalInstance?.dialogContainer || modalInstance?.dialogContent; if (!state || !dialog) return; const sectionId = state.section?.id; - const canonical = getCanonicalDefaultSection(sectionId); + let canonical = getCanonicalDefaultSection(sectionId); + if (!canonical && isRecentlyAddedDefaultSection(state.section)) { + try { + canonical = await window.KefinHomeScreen?.buildRecentlyAddedDefaultSection?.(sectionId); + } catch (error) { + console.warn('[KefinTweaks] Failed to build Recently Added default:', error); + canonical = null; + } + } if (!canonical) { showHsaeToast('No default found for this section'); syncHeaderActions(modalInstance); @@ -4789,7 +4802,7 @@ if (exportBtn) exportBtn.hidden = isDefault; if (restoreBtn) { restoreBtn.hidden = !isDefault; - if (isDefault && !getCanonicalDefaultSection(section.id)) { + if (isDefault && !getCanonicalDefaultSection(section.id) && !isRecentlyAddedDefaultSection(section)) { restoreBtn.hidden = true; } }