diff --git a/Emby/Emby.Plugins.Moonfin/Pages/moonfin.js b/Emby/Emby.Plugins.Moonfin/Pages/moonfin.js
index c4472b9..d2ec179 100644
--- a/Emby/Emby.Plugins.Moonfin/Pages/moonfin.js
+++ b/Emby/Emby.Plugins.Moonfin/Pages/moonfin.js
@@ -3,6 +3,10 @@ define(['baseView', 'loading', 'emby-input', 'emby-button', 'emby-checkbox', 'em
var PluginUniqueId = '9a1b2c3d-4e5f-6789-abcd-ef0123456789';
+ function esc(str) {
+ return String(str || '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');
+ }
+
var RATING_SOURCES = [
{ id: 'tomatoes', label: 'Rotten Tomatoes' },
{ id: 'tomatoes_audience', label: 'RT Audience Score' },
@@ -162,6 +166,65 @@ define(['baseView', 'loading', 'emby-input', 'emby-button', 'emby-checkbox', 'em
return isNaN(parsed) ? null : parsed;
}
+ function setNullableRangeInput(view, selectorId, value, unit) {
+ var select = view.querySelector(selectorId + '_Set');
+ var range = view.querySelector(selectorId);
+ var display = view.querySelector(selectorId + '_Val');
+ if (!range) return;
+
+ unit = unit || '';
+
+ if (value == null || value === '') {
+ if (select) select.value = 'unset';
+ range.disabled = true;
+ if (display) display.textContent = '--';
+ } else {
+ if (select) select.value = 'set';
+ range.disabled = false;
+ range.value = String(value);
+ if (display) display.textContent = String(value) + unit;
+ }
+ }
+
+ function getNullableRangeInput(view, selectorId) {
+ var select = view.querySelector(selectorId + '_Set');
+ var range = view.querySelector(selectorId);
+ if (!range) return null;
+ if (select && select.value === 'unset') return null;
+ if (range.disabled) return null;
+ var parsed = parseInt(range.value, 10);
+ return isNaN(parsed) ? null : parsed;
+ }
+
+ function bindNullableRangeInput(view, selectorId, unit) {
+ var select = view.querySelector(selectorId + '_Set');
+ var range = view.querySelector(selectorId);
+ var display = view.querySelector(selectorId + '_Val');
+ if (!range || !select) return;
+
+ unit = unit || '';
+
+ if (!select.dataset.rangeBound) {
+ select.dataset.rangeBound = 'true';
+ select.addEventListener('change', function() {
+ if (select.value === 'unset') {
+ range.disabled = true;
+ if (display) display.textContent = '--';
+ } else {
+ range.disabled = false;
+ if (display) display.textContent = range.value + unit;
+ }
+ });
+ }
+
+ if (!range.dataset.rangeBound) {
+ range.dataset.rangeBound = 'true';
+ range.addEventListener('input', function() {
+ if (display) display.textContent = range.value + unit;
+ });
+ }
+ }
+
// ── Tabbed navigation ───────────────────────────────────────────────────
function buildDefaultSettingsSubTabs(section) {
@@ -494,6 +557,8 @@ define(['baseView', 'loading', 'emby-input', 'emby-button', 'emby-checkbox', 'em
});
}
+
+
function loadAdminGenrePicker(view, selectedIds) {
var picker = view.querySelector('#DefaultGenrePicker');
if (!picker) return;
@@ -578,6 +643,217 @@ define(['baseView', 'loading', 'emby-input', 'emby-button', 'emby-checkbox', 'em
return result.length > 0 ? result : null;
}
+ // Same ids and declaration order as DetailButton in Core, so an untouched
+ // arrangement here matches what a fresh client shows.
+ var DETAIL_BUTTONS = [
+ { id: 'seerrRequest', label: 'Request' },
+ { id: 'seerrRequest4k', label: 'Request 4K' },
+ { id: 'shuffle', label: 'Shuffle' },
+ { id: 'restart', label: 'Restart', canHide: false },
+ { id: 'playOffline', label: 'Play Offline', canHide: false },
+ { id: 'audio', label: 'Audio' },
+ { id: 'subtitles', label: 'Subtitles' },
+ { id: 'version', label: 'Version' },
+ { id: 'cast', label: 'Cast' },
+ { id: 'trailer', label: 'Trailer' },
+ { id: 'watchWithGroup', label: 'Watch with group' },
+ { id: 'watched', label: 'Watched' },
+ { id: 'favorite', label: 'Favorite' },
+ { id: 'playlist', label: 'Playlist' },
+ { id: 'download', label: 'Download' },
+ { id: 'deleteFiles', label: 'Delete files' },
+ { id: 'goToSeries', label: 'Go to series' },
+ { id: 'seerrWatchlist', label: 'Watchlist' },
+ { id: 'seerrReportIssue', label: 'Report Issue' },
+ { id: 'seerrManage', label: 'Manage Requests' },
+ { id: 'admin', label: 'Admin' }
+ ];
+
+ function loadDetailButtonsPicker(view, order, hidden) {
+ var container = view.querySelector('#DefaultDetailButtonsList');
+ if (!container) return;
+ var hiddenSet = {};
+ if (hidden && Array.isArray(hidden)) {
+ hidden.forEach(function (id) { hiddenSet[id] = true; });
+ }
+
+ var ordered = [];
+ var addedSet = {};
+
+ function entryFor(btn) {
+ var hideable = btn.canHide !== false;
+ return {
+ id: btn.id,
+ label: btn.label,
+ hideable: hideable,
+ checked: hideable ? !hiddenSet[btn.id] : true
+ };
+ }
+
+ if (order && Array.isArray(order) && order.length > 0) {
+ order.forEach(function (id) {
+ var btn = DETAIL_BUTTONS.find(function (b) { return b.id === id; });
+ if (btn) {
+ ordered.push(entryFor(btn));
+ addedSet[btn.id] = true;
+ }
+ });
+ }
+
+ DETAIL_BUTTONS.forEach(function (btn) {
+ if (!addedSet[btn.id]) {
+ ordered.push(entryFor(btn));
+ }
+ });
+
+ container.innerHTML = '';
+ ordered.forEach(function (item) {
+ var row = document.createElement('div');
+ row.className = 'detailButtonItem';
+ row.dataset.id = item.id;
+ row.style.cssText = 'display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:4px;margin-bottom:2px;background:rgba(128,128,128,0.03);';
+ row.innerHTML =
+ (item.hideable
+ ? ' '
+ : '✓ ') +
+ '' + esc(item.label) + ' ' +
+ '↑ ' +
+ '↓ ';
+ container.appendChild(row);
+ });
+
+ if (!container.dataset.hasListener) {
+ container.dataset.hasListener = 'true';
+ container.addEventListener('click', function (e) {
+ var btn = e.target.closest('.detailButtonMoveBtn');
+ if (!btn) return;
+ var row = btn.closest('.detailButtonItem');
+ if (!row) return;
+ if (btn.dataset.dir === 'up' && row.previousElementSibling) {
+ container.insertBefore(row, row.previousElementSibling);
+ } else if (btn.dataset.dir === 'down' && row.nextElementSibling) {
+ container.insertBefore(row.nextElementSibling, row);
+ }
+ });
+ }
+ }
+
+ function getDetailButtonsValue(view) {
+ var items = view.querySelectorAll('#DefaultDetailButtonsList .detailButtonItem');
+ var order = [];
+ var hidden = [];
+ items.forEach(function (item) {
+ var cb = item.querySelector('input[type=checkbox]');
+ var id = item.dataset.id;
+ order.push(id);
+ if (cb && !cb.checked) {
+ hidden.push(id);
+ }
+ });
+ return {
+ order: order.length > 0 ? order : null,
+ hidden: hidden.length > 0 ? hidden : null
+ };
+ }
+
+ var SEERR_DISCOVERY_ROWS = [
+ { id: 'recent_requests', label: 'Recent Requests' },
+ { id: 'watchlist', label: 'Your Watchlist' },
+ { id: 'recently_added', label: 'Recently Added' },
+ { id: 'trending', label: 'Trending' },
+ { id: 'popular_movies', label: 'Popular Movies' },
+ { id: 'movie_genres', label: 'Movie Genres' },
+ { id: 'upcoming_movies', label: 'Upcoming Movies' },
+ { id: 'studios', label: 'Studios' },
+ { id: 'popular_series', label: 'Popular Series' },
+ { id: 'series_genres', label: 'Series Genres' },
+ { id: 'upcoming_series', label: 'Upcoming Series' },
+ { id: 'networks', label: 'Networks' }
+ ];
+
+ function loadSeerrDiscoveryPicker(view, order, hidden) {
+ var container = view.querySelector('#DefaultSeerrDiscoveryList');
+ if (!container) return;
+ var hiddenSet = {};
+ if (hidden && Array.isArray(hidden)) {
+ hidden.forEach(function (id) { hiddenSet[id] = true; });
+ }
+
+ var ordered = [];
+ var addedSet = {};
+
+ if (order && Array.isArray(order) && order.length > 0) {
+ order.forEach(function (id) {
+ var row = SEERR_DISCOVERY_ROWS.find(function (r) { return r.id === id; });
+ if (row) {
+ ordered.push({ id: row.id, label: row.label, checked: !hiddenSet[row.id] });
+ addedSet[row.id] = true;
+ }
+ });
+ }
+
+ SEERR_DISCOVERY_ROWS.forEach(function (row) {
+ if (!addedSet[row.id]) {
+ ordered.push({ id: row.id, label: row.label, checked: !hiddenSet[row.id] });
+ }
+ });
+
+ container.innerHTML = '';
+ ordered.forEach(function (item) {
+ var row = document.createElement('div');
+ row.className = 'seerrDiscoveryItem';
+ row.dataset.id = item.id;
+ row.style.cssText = 'display:flex;align-items:center;gap:8px;padding:5px 8px;border-radius:4px;margin-bottom:2px;background:rgba(128,128,128,0.03);';
+ row.innerHTML =
+ ' ' +
+ '' + esc(item.label) + ' ' +
+ '↑ ' +
+ '↓ ';
+ container.appendChild(row);
+ });
+
+ if (!container.dataset.hasListener) {
+ container.dataset.hasListener = 'true';
+ container.addEventListener('click', function (e) {
+ var btn = e.target.closest('.seerrMoveBtn');
+ if (!btn) return;
+ var row = btn.closest('.seerrDiscoveryItem');
+ if (!row) return;
+ if (btn.dataset.dir === 'up' && row.previousElementSibling) {
+ container.insertBefore(row, row.previousElementSibling);
+ } else if (btn.dataset.dir === 'down' && row.nextElementSibling) {
+ container.insertBefore(row.nextElementSibling, row);
+ }
+ });
+ }
+ }
+
+ // Clients carry Seerr rows as seerrRows.rowOrder, which lists the visible rows in
+ // order and treats every row it leaves out as switched off.
+ function getSeerrDiscoveryRowOrder(view) {
+ var items = view.querySelectorAll('#DefaultSeerrDiscoveryList .seerrDiscoveryItem');
+ var rowOrder = [];
+ items.forEach(function (item) {
+ var cb = item.querySelector('input[type=checkbox]');
+ if (!cb || cb.checked) {
+ rowOrder.push(item.dataset.id);
+ }
+ });
+ return rowOrder.length > 0 ? rowOrder : null;
+ }
+
+ // Turns a stored rowOrder back into the picker's order plus hidden pair.
+ function seerrRowsToPicker(seerrRows) {
+ var rowOrder = seerrRows && Array.isArray(seerrRows.rowOrder) ? seerrRows.rowOrder : null;
+ if (!rowOrder || rowOrder.length === 0) return { order: null, hidden: null };
+ var visible = {};
+ rowOrder.forEach(function (id) { visible[id] = true; });
+ var hidden = SEERR_DISCOVERY_ROWS
+ .filter(function (row) { return !visible[row.id]; })
+ .map(function (row) { return row.id; });
+ return { order: rowOrder, hidden: hidden.length > 0 ? hidden : null };
+ }
+
// ── Home layout builder ─────────────────────────────────────────────────
var HOME_SECTION_DEFINITIONS = [
@@ -1417,27 +1693,66 @@ define(['baseView', 'loading', 'emby-input', 'emby-button', 'emby-checkbox', 'em
initGameCores(view, view.__moonfinState);
var defaults = camelKeysDeep(config.DefaultUserSettings) || {};
+ setSelectValue(view, '#DefaultInterfaceStyle', defaults.interfaceStyle, 'Configured style');
setSelectValue(view, '#DefaultVisualTheme', defaults.visualTheme, 'Configured theme');
setSelectValue(view, '#DefaultDetailScreenStyle', defaults.detailScreenStyle, 'Configured style');
setNullableBoolSelect(view, '#DefaultDetailExpandedTabs', defaults.detailExpandedTabs);
+ setNullableBoolSelect(view, '#DefaultDetailShowTechnicalDetails', defaults.detailShowTechnicalDetails);
+ setSelectValue(view, '#DefaultRecommendationSystemSource', defaults.recommendationSystemSource, 'Configured source');
+ setNullableBoolSelect(view, '#DefaultRecommendationsApplyParentalRatingCap', defaults.recommendationsApplyParentalRatingCap);
+ // One picker stands in for all three form factors.
+ loadDetailButtonsPicker(view,
+ defaults.detailButtonOrderTv || defaults.detailButtonOrderMobile || defaults.detailButtonOrderDesktop || null,
+ defaults.hiddenDetailButtonsTv || defaults.hiddenDetailButtonsMobile || defaults.hiddenDetailButtonsDesktop || null);
setSelectValue(view, '#DefaultFocusColor', defaults.focusColor, 'Configured color');
+ setSelectValue(view, '#DefaultClockBehavior', defaults.clockBehavior, 'Configured clock');
+ setNullableBoolSelect(view, '#DefaultUse24HourClock', defaults.use24HourClock);
+ setSelectValue(view, '#DefaultDesktopUiScale', defaults.desktopUiScale, 'Configured scale');
+ setNullableBoolSelect(view, '#DefaultBackdropEnabled', defaults.backdropEnabled);
+ bindNullableRangeInput(view, '#DefaultBrowsingBlur');
+ setNullableRangeInput(view, '#DefaultBrowsingBlur', defaults.browsingBlur);
+ bindNullableRangeInput(view, '#DefaultDetailsScreenBlur');
+ setNullableRangeInput(view, '#DefaultDetailsScreenBlur', defaults.detailsScreenBlur);
+ setNullableBoolSelect(view, '#DefaultThemeMusicEnabled', defaults.themeMusicEnabled);
+ setNullableBoolSelect(view, '#DefaultThemeMusicOnHomeRows', defaults.themeMusicOnHomeRows);
+ setNullableBoolSelect(view, '#DefaultThemeMusicLoop', defaults.themeMusicLoop);
+ bindNullableRangeInput(view, '#DefaultThemeMusicVolume', '%');
+ setNullableRangeInput(view, '#DefaultThemeMusicVolume', defaults.themeMusicVolume, '%');
setSelectValue(view, '#DefaultWatchedIndicator', defaults.watchedIndicator, 'Configured mode');
setNullableBoolSelect(view, '#DefaultCardFocusExpansion', defaults.cardFocusExpansion);
setSelectValue(view, '#DefaultScreensaverMode', defaults.screensaverMode, 'Configured mode');
setSelectValue(view, '#DefaultNavbarPosition', defaults.navbarPosition, 'Configured position');
setSelectValue(view, '#DefaultNavbarColor', defaults.navbarColor, 'Configured color');
- setNullableIntInput(view, '#DefaultNavbarOpacity', defaults.navbarOpacity);
+ bindNullableRangeInput(view, '#DefaultNavbarOpacity', '%');
+ setNullableRangeInput(view, '#DefaultNavbarOpacity', defaults.navbarOpacity, '%');
+ setNullableBoolSelect(view, '#DefaultNavbarAlwaysExpanded', defaults.navbarAlwaysExpanded);
+ setNullableBoolSelect(view, '#DefaultEnableFolderView', defaults.enableFolderView);
+ setNullableBoolSelect(view, '#DefaultShowSeerrButton', defaults.showSeerrButton);
setSelectValue(view, '#DefaultMediaBarSourceType', defaults.mediaBarSourceType, 'Configured source');
+ loadAdminGenrePicker(view, defaults.mediaBarExcludedGenres || []);
setSelectValue(view, '#DefaultMediaBarMode', defaults.mediaBarMode, 'Configured mode');
+ setSelectValue(view, '#DefaultMediaBarContentType', defaults.mediaBarContentType, 'Configured content type');
+ setSelectValue(view, '#DefaultMediaBarItemCount', defaults.mediaBarItemCount, 'Configured item count');
+ setNullableBoolSelect(view, '#DefaultMediaBarAutoAdvance', defaults.mediaBarAutoAdvance);
+ setSelectValue(view, '#DefaultMediaBarIntervalMs', defaults.mediaBarIntervalMs, 'Configured interval');
+ setNullableBoolSelect(view, '#DefaultMediaBarTrailerPreview', defaults.mediaBarTrailerPreview);
setNullableBoolSelect(view, '#DefaultMediaBarTrailerAudio', defaults.mediaBarTrailerAudio);
+ setNullableBoolSelect(view, '#DefaultMediaBarTrailerCaptions', defaults.mediaBarTrailerCaptions);
+ setNullableBoolSelect(view, '#DefaultEpisodePreviewEnabled', defaults.episodePreviewEnabled);
+ setNullableBoolSelect(view, '#DefaultPreviewAudioEnabled', defaults.previewAudioEnabled);
+ setSelectValue(view, '#DefaultSeasonalSurprise', defaults.seasonalSurprise, 'Configured surprise');
setSelectValue(view, '#DefaultHomeRowsStyle', defaults.homeRowsStyle, 'Configured style');
setNullableBoolSelect(view, '#DefaultFullScreenRows', defaults.fullScreenRows);
- setNullableBoolSelect(view, '#DefaultUseDetailedSubHeadings', defaults.useDetailedSubHeadings);
- setSelectValue(view, '#DefaultHomeImageTypeContinueWatching', defaults.homeImageTypeContinueWatching, 'Configured image type');
+ setNullableBoolSelect(view, '#DefaultHomeRowInfoOverlay', defaults.homeRowInfoOverlay);
+ bindNullableRangeInput(view, '#DefaultClassicHomeRowsPadding', 'px');
+ setNullableRangeInput(view, '#DefaultClassicHomeRowsPadding', defaults.classicHomeRowsPadding, 'px');
+ bindNullableRangeInput(view, '#DefaultModernHomeRowsPadding', 'px');
+ setNullableRangeInput(view, '#DefaultModernHomeRowsPadding', defaults.modernHomeRowsPadding, 'px');
setSelectValue(view, '#DefaultPosterSize', defaults.posterSize, 'Configured size');
+ setSelectValue(view, '#DefaultHomeImageTypeContinueWatching', defaults.homeImageTypeContinueWatching, 'Configured image type');
setNullableBoolSelect(view, '#DefaultDisplayFavoritesRows', defaults.displayFavoritesRows);
setNullableBoolSelect(view, '#DefaultDisplayCollectionsRows', defaults.displayCollectionsRows);
setNullableBoolSelect(view, '#DefaultDisplayGenresRows', defaults.displayGenresRows);
@@ -1449,25 +1764,30 @@ define(['baseView', 'loading', 'emby-input', 'emby-button', 'emby-checkbox', 'em
setSelectValue(view, '#DefaultGenresRowItemFilter', defaults.genresRowItemFilter, 'Configured filter');
setNullableBoolSelect(view, '#DefaultHomeImageUseSeriesImage', defaults.homeImageUseSeriesImage);
loadHomeSectionsEditor(view, defaults.homeSections || null, defaults.homeRowOrder || null);
- view.querySelector('#DefaultMergeContinueWatchingNextUp').checked = !!defaults.mergeContinueWatchingNextUp;
- setNullableIntInput(view, '#DefaultNextUpMaxDays', defaults.nextUpMaxDays);
+ setNullableBoolSelect(view, '#DefaultMergeContinueWatchingNextUp', defaults.mergeContinueWatchingNextUp);
+ setSelectValue(view, '#DefaultNextUpMaxDays', defaults.nextUpMaxDays, 'Configured max days');
- view.querySelector('#DefaultShowShuffleButton').checked = !!defaults.showShuffleButton;
- view.querySelector('#DefaultShowGenresButton').checked = !!defaults.showGenresButton;
- view.querySelector('#DefaultShowFavoritesButton').checked = !!defaults.showFavoritesButton;
+ setNullableBoolSelect(view, '#DefaultEnableMultiServerLibraries', defaults.enableMultiServerLibraries);
+ setNullableBoolSelect(view, '#DefaultMergeRecentRowsByType', defaults.mergeRecentRowsByType);
+ setNullableBoolSelect(view, '#DefaultGroupItemsIntoCollections', defaults.groupItemsIntoCollections);
+ setNullableBoolSelect(view, '#DefaultShowMediaDetailsOnLibraryPage', defaults.showMediaDetailsOnLibraryPage);
+ setNullableBoolSelect(view, '#DefaultUseDetailedSubHeadings', defaults.useDetailedSubHeadings);
+ setNullableBoolSelect(view, '#DefaultHideBackdropsInLibraries', defaults.hideBackdropsInLibraries);
+
+ setNullableBoolSelect(view, '#DefaultShowShuffleButton', defaults.showShuffleButton);
+ setNullableBoolSelect(view, '#DefaultShowGenresButton', defaults.showGenresButton);
+ setNullableBoolSelect(view, '#DefaultShowFavoritesButton', defaults.showFavoritesButton);
view.querySelector('#DefaultShowCastButton').checked = !!defaults.showCastButton;
view.querySelector('#DefaultShowSyncPlayButton').checked = !!defaults.showSyncPlayButton;
- view.querySelector('#DefaultShowLibrariesInToolbar').checked = !!defaults.showLibrariesInToolbar;
-
- setNullableBoolSelect(view, '#DefaultMediaBarTrailerPreview', defaults.mediaBarTrailerPreview);
- setNullableBoolSelect(view, '#DefaultEpisodePreviewEnabled', defaults.episodePreviewEnabled);
- setNullableBoolSelect(view, '#DefaultPreviewAudioEnabled', defaults.previewAudioEnabled);
+ setNullableBoolSelect(view, '#DefaultShowLibrariesInToolbar', defaults.showLibrariesInToolbar);
view.querySelector('#DefaultMdblistEnabled').checked = !!defaults.mdblistEnabled;
view.querySelector('#DefaultTmdbEpisodeRatingsEnabled').checked = !!defaults.tmdbEpisodeRatingsEnabled;
setNullableBoolSelect(view, '#DefaultMdblistShowRatingBadges', defaults.mdblistShowRatingBadges);
loadRatingSourcesPicker(view, defaults.mdblistRatingSources || null);
setNullableBoolSelect(view, '#DefaultSeerrBlockNsfw', defaults.seerrBlockNsfw);
+ var seerrPicker = seerrRowsToPicker(defaults.seerrRows);
+ loadSeerrDiscoveryPicker(view, seerrPicker.order, seerrPicker.hidden);
var sourceSelect = view.querySelector('#DefaultMediaBarSourceType');
var collectionPickerSection = view.querySelector('#DefaultCollectionPickerSection');
@@ -1485,7 +1805,6 @@ define(['baseView', 'loading', 'emby-input', 'emby-button', 'emby-checkbox', 'em
sourceSelect.addEventListener('change', togglePickerVisibility);
}
togglePickerVisibility();
- loadAdminGenrePicker(view, defaults.mediaBarExcludedGenres || []);
loading.hide();
}).catch(reportConfigError);
@@ -1516,34 +1835,70 @@ define(['baseView', 'loading', 'emby-input', 'emby-button', 'emby-checkbox', 'em
.map(function (cb) { return cb.getAttribute('data-id'); });
var d = camelKeysDeep(config.DefaultUserSettings) || {};
+ d.interfaceStyle = view.querySelector('#DefaultInterfaceStyle').value || null;
d.visualTheme = view.querySelector('#DefaultVisualTheme').value || null;
d.detailScreenStyle = view.querySelector('#DefaultDetailScreenStyle').value || null;
d.detailExpandedTabs = getNullableBoolSelect(view, '#DefaultDetailExpandedTabs');
+ d.detailShowTechnicalDetails = getNullableBoolSelect(view, '#DefaultDetailShowTechnicalDetails');
+ d.recommendationSystemSource = view.querySelector('#DefaultRecommendationSystemSource').value || null;
+ d.recommendationsApplyParentalRatingCap = getNullableBoolSelect(view, '#DefaultRecommendationsApplyParentalRatingCap');
+ var btnVal = getDetailButtonsValue(view);
+ // Clients read a different key per form factor, so one arrangement covers all three.
+ d.detailButtonOrderTv = btnVal.order;
+ d.detailButtonOrderMobile = btnVal.order;
+ d.detailButtonOrderDesktop = btnVal.order;
+ d.hiddenDetailButtonsTv = btnVal.hidden;
+ d.hiddenDetailButtonsMobile = btnVal.hidden;
+ d.hiddenDetailButtonsDesktop = btnVal.hidden;
d.focusColor = view.querySelector('#DefaultFocusColor').value || null;
+ d.clockBehavior = view.querySelector('#DefaultClockBehavior').value || null;
+ d.use24HourClock = getNullableBoolSelect(view, '#DefaultUse24HourClock');
+ d.desktopUiScale = view.querySelector('#DefaultDesktopUiScale').value || null;
+ d.backdropEnabled = getNullableBoolSelect(view, '#DefaultBackdropEnabled');
+ d.browsingBlur = getNullableRangeInput(view, '#DefaultBrowsingBlur');
+ d.detailsScreenBlur = getNullableRangeInput(view, '#DefaultDetailsScreenBlur');
+ d.themeMusicEnabled = getNullableBoolSelect(view, '#DefaultThemeMusicEnabled');
+ d.themeMusicOnHomeRows = getNullableBoolSelect(view, '#DefaultThemeMusicOnHomeRows');
+ d.themeMusicLoop = getNullableBoolSelect(view, '#DefaultThemeMusicLoop');
+ d.themeMusicVolume = getNullableRangeInput(view, '#DefaultThemeMusicVolume');
d.watchedIndicator = view.querySelector('#DefaultWatchedIndicator').value || null;
d.cardFocusExpansion = getNullableBoolSelect(view, '#DefaultCardFocusExpansion');
d.screensaverMode = view.querySelector('#DefaultScreensaverMode').value || null;
d.navbarPosition = view.querySelector('#DefaultNavbarPosition').value || null;
d.navbarColor = view.querySelector('#DefaultNavbarColor').value || null;
- d.navbarOpacity = getNullableIntInput(view, '#DefaultNavbarOpacity');
+ d.navbarOpacity = getNullableRangeInput(view, '#DefaultNavbarOpacity');
+ d.navbarAlwaysExpanded = getNullableBoolSelect(view, '#DefaultNavbarAlwaysExpanded');
+ d.enableFolderView = getNullableBoolSelect(view, '#DefaultEnableFolderView');
+ d.showSeerrButton = getNullableBoolSelect(view, '#DefaultShowSeerrButton');
d.mediaBarSourceType = view.querySelector('#DefaultMediaBarSourceType').value || null;
+ var genreIds = Array.prototype.slice.call(view.querySelectorAll('.adminGenreCb:checked')).map(function (cb) { return cb.dataset.id; });
+ d.mediaBarExcludedGenres = genreIds.length > 0 ? genreIds : null;
d.mediaBarMode = view.querySelector('#DefaultMediaBarMode').value || null;
+ d.mediaBarContentType = view.querySelector('#DefaultMediaBarContentType').value || null;
+ d.mediaBarItemCount = getNullableIntInput(view, '#DefaultMediaBarItemCount');
+ d.mediaBarAutoAdvance = getNullableBoolSelect(view, '#DefaultMediaBarAutoAdvance');
+ d.mediaBarIntervalMs = getNullableIntInput(view, '#DefaultMediaBarIntervalMs');
+ d.mediaBarTrailerPreview = getNullableBoolSelect(view, '#DefaultMediaBarTrailerPreview');
d.mediaBarTrailerAudio = getNullableBoolSelect(view, '#DefaultMediaBarTrailerAudio');
+ d.mediaBarTrailerCaptions = getNullableBoolSelect(view, '#DefaultMediaBarTrailerCaptions');
+ d.episodePreviewEnabled = getNullableBoolSelect(view, '#DefaultEpisodePreviewEnabled');
+ d.previewAudioEnabled = getNullableBoolSelect(view, '#DefaultPreviewAudioEnabled');
+ d.seasonalSurprise = view.querySelector('#DefaultSeasonalSurprise').value || null;
var collectionIds = Array.prototype.slice.call(view.querySelectorAll('.adminCollectionCb:checked')).map(function (cb) { return cb.dataset.id; });
d.mediaBarCollectionIds = collectionIds.length > 0 ? collectionIds : null;
var libraryIds = Array.prototype.slice.call(view.querySelectorAll('.adminLibraryCb:checked')).map(function (cb) { return cb.dataset.id; });
d.mediaBarLibraryIds = libraryIds.length > 0 ? libraryIds : null;
- var genreIds = Array.prototype.slice.call(view.querySelectorAll('.adminGenreCb:checked')).map(function (cb) { return cb.dataset.id; });
- d.mediaBarExcludedGenres = genreIds.length > 0 ? genreIds : null;
d.homeRowsStyle = view.querySelector('#DefaultHomeRowsStyle').value || null;
d.fullScreenRows = getNullableBoolSelect(view, '#DefaultFullScreenRows');
- d.useDetailedSubHeadings = getNullableBoolSelect(view, '#DefaultUseDetailedSubHeadings');
- d.homeImageTypeContinueWatching = view.querySelector('#DefaultHomeImageTypeContinueWatching').value || null;
+ d.homeRowInfoOverlay = getNullableBoolSelect(view, '#DefaultHomeRowInfoOverlay');
+ d.classicHomeRowsPadding = getNullableIntInput(view, '#DefaultClassicHomeRowsPadding');
+ d.modernHomeRowsPadding = getNullableIntInput(view, '#DefaultModernHomeRowsPadding');
d.posterSize = view.querySelector('#DefaultPosterSize').value || null;
+ d.homeImageTypeContinueWatching = view.querySelector('#DefaultHomeImageTypeContinueWatching').value || null;
d.displayFavoritesRows = getNullableBoolSelect(view, '#DefaultDisplayFavoritesRows');
d.displayCollectionsRows = getNullableBoolSelect(view, '#DefaultDisplayCollectionsRows');
d.displayGenresRows = getNullableBoolSelect(view, '#DefaultDisplayGenresRows');
@@ -1557,15 +1912,22 @@ define(['baseView', 'loading', 'emby-input', 'emby-button', 'emby-checkbox', 'em
var homeSections = getHomeSectionsValue(view);
d.homeSections = homeSections;
d.homeRowOrder = getHomeRowOrderValue(view, homeSections);
- d.mergeContinueWatchingNextUp = view.querySelector('#DefaultMergeContinueWatchingNextUp').checked;
+ d.mergeContinueWatchingNextUp = getNullableBoolSelect(view, '#DefaultMergeContinueWatchingNextUp');
d.nextUpMaxDays = getNullableIntInput(view, '#DefaultNextUpMaxDays');
- d.showShuffleButton = view.querySelector('#DefaultShowShuffleButton').checked;
- d.showGenresButton = view.querySelector('#DefaultShowGenresButton').checked;
- d.showFavoritesButton = view.querySelector('#DefaultShowFavoritesButton').checked;
+ d.enableMultiServerLibraries = getNullableBoolSelect(view, '#DefaultEnableMultiServerLibraries');
+ d.mergeRecentRowsByType = getNullableBoolSelect(view, '#DefaultMergeRecentRowsByType');
+ d.groupItemsIntoCollections = getNullableBoolSelect(view, '#DefaultGroupItemsIntoCollections');
+ d.showMediaDetailsOnLibraryPage = getNullableBoolSelect(view, '#DefaultShowMediaDetailsOnLibraryPage');
+ d.useDetailedSubHeadings = getNullableBoolSelect(view, '#DefaultUseDetailedSubHeadings');
+ d.hideBackdropsInLibraries = getNullableBoolSelect(view, '#DefaultHideBackdropsInLibraries');
+
+ d.showShuffleButton = getNullableBoolSelect(view, '#DefaultShowShuffleButton');
+ d.showGenresButton = getNullableBoolSelect(view, '#DefaultShowGenresButton');
+ d.showFavoritesButton = getNullableBoolSelect(view, '#DefaultShowFavoritesButton');
d.showCastButton = view.querySelector('#DefaultShowCastButton').checked;
d.showSyncPlayButton = view.querySelector('#DefaultShowSyncPlayButton').checked;
- d.showLibrariesInToolbar = view.querySelector('#DefaultShowLibrariesInToolbar').checked;
+ d.showLibrariesInToolbar = getNullableBoolSelect(view, '#DefaultShowLibrariesInToolbar');
d.mediaBarTrailerPreview = getNullableBoolSelect(view, '#DefaultMediaBarTrailerPreview');
d.episodePreviewEnabled = getNullableBoolSelect(view, '#DefaultEpisodePreviewEnabled');
@@ -1576,6 +1938,10 @@ define(['baseView', 'loading', 'emby-input', 'emby-button', 'emby-checkbox', 'em
d.mdblistShowRatingBadges = getNullableBoolSelect(view, '#DefaultMdblistShowRatingBadges');
d.mdblistRatingSources = getRatingSourcesValue(view);
d.seerrBlockNsfw = getNullableBoolSelect(view, '#DefaultSeerrBlockNsfw');
+ // seerrRows carries more than the ordering, so merge rather than replace.
+ var seerrRows = Object.assign({}, d.seerrRows || {});
+ seerrRows.rowOrder = getSeerrDiscoveryRowOrder(view);
+ d.seerrRows = seerrRows;
config.DefaultUserSettings = d;
@@ -1679,12 +2045,39 @@ define(['baseView', 'loading', 'emby-input', 'emby-button', 'emby-checkbox', 'em
});
}
+ function initializeDefaultsSubtabs(view) {
+ var subnav = view.querySelector('.defaultsSubnavBar');
+ if (!subnav || subnav.dataset.bound) return;
+ subnav.dataset.bound = 'true';
+
+ subnav.addEventListener('click', function (e) {
+ var btn = e.target.closest('.defaultsSubtabBtn');
+ if (!btn) return;
+ var subtab = btn.dataset.subtab;
+ if (!subtab) return;
+
+ var buttons = subnav.querySelectorAll('.defaultsSubtabBtn');
+ buttons.forEach(function (b) { b.classList.remove('active'); });
+ btn.classList.add('active');
+
+ var panels = view.querySelectorAll('.defaultsSubtabPanel');
+ panels.forEach(function (p) {
+ if (p.dataset.subtab === subtab) {
+ p.classList.add('active');
+ } else {
+ p.classList.remove('active');
+ }
+ });
+ });
+ }
+
function bindOnce(view) {
if (view.__moonfinBound) return;
view.__moonfinBound = true;
view.__moonfinState = { timer: null };
initializeAdminTabs(view);
+ initializeDefaultsSubtabs(view);
var form = view.querySelector('#MoonfinConfigForm');
if (form) {
diff --git a/Jellyfin/backend/Models/MoonfinSettingsProfile.cs b/Jellyfin/backend/Models/MoonfinSettingsProfile.cs
index 5683a7f..50fdae0 100644
--- a/Jellyfin/backend/Models/MoonfinSettingsProfile.cs
+++ b/Jellyfin/backend/Models/MoonfinSettingsProfile.cs
@@ -77,6 +77,9 @@ public class MoonfinSettingsProfile
[JsonPropertyName("navbarAlwaysExpanded")]
public bool? NavbarAlwaysExpanded { get; set; }
+ [JsonPropertyName("mediaBarTrailerCaptions")]
+ public bool? MediaBarTrailerCaptions { get; set; }
+
[JsonPropertyName("focusColor")]
public string? FocusColor { get; set; }
diff --git a/Jellyfin/backend/Pages/configPage.html b/Jellyfin/backend/Pages/configPage.html
index fee6663..10d11e5 100644
--- a/Jellyfin/backend/Pages/configPage.html
+++ b/Jellyfin/backend/Pages/configPage.html
@@ -21,6 +21,49 @@
max-width: none;
}
+ #MoonfinConfigPage .defaultsSubnavBar {
+ display: flex;
+ gap: 8px;
+ flex-wrap: wrap;
+ margin-top: 1em;
+ margin-bottom: 1.5em;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+ padding-bottom: 12px;
+ }
+
+ #MoonfinConfigPage .defaultsSubtabBtn {
+ background: rgba(255, 255, 255, 0.06);
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ color: rgba(255, 255, 255, 0.75);
+ border-radius: 20px;
+ padding: 6px 16px;
+ font-size: 0.9em;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ }
+
+ #MoonfinConfigPage .defaultsSubtabBtn:hover {
+ background: rgba(255, 255, 255, 0.12);
+ color: #fff;
+ }
+
+ #MoonfinConfigPage .defaultsSubtabBtn.active {
+ background: #00a4dc;
+ border-color: #00a4dc;
+ color: #fff;
+ font-weight: 600;
+ box-shadow: 0 2px 8px rgba(0, 164, 220, 0.4);
+ }
+
+ #MoonfinConfigPage .defaultsSubtabPanel {
+ display: none;
+ }
+
+ #MoonfinConfigPage .defaultsSubtabPanel.active {
+ display: block;
+ }
+
#MoonfinConfigPage .moonfinAdminShell {
display: flex;
gap: 18px;
@@ -667,479 +710,696 @@
Default User Settings
Not set means "do not force this value" so users keep their own choice.
- Appearance
-
-
-
-
-
- Browsing Blur
-
-
-
- Detail Screen Blur
-
-
-
- Enable Theme Music
-
- Not set (user decides)
- Yes
- No
-
-
-
- Watched Indicator
-
- Not set (user decides)
- Always
- Hide Unwatched
- Episodes Only
- Never
-
-
-
- Scale Focused Cards
-
- Not set (user decides)
- Yes
- No
-
-
-
- Screensaver Mode
-
- Not set (user decides)
- Off
- Logo
- Library
-
-
- Navigation
-
- Default Navbar Position
-
- Not set (user decides)
- Top
- Left (Sidebar)
-
-
-
- Navigation Bar Accent Color
-
- Not set (user decides)
- White
- Black
- Gray
- Dark Blue
- Purple
- Teal
- Navy
- Charcoal
- Brown
- Dark Red
- Dark Green
- Slate
- Indigo
- Moonfin Cyan
- Neon Pulse Magenta
-
-
-