diff --git a/Jellyfin.Plugin.ShareLinks/Api/ShareLinksController.cs b/Jellyfin.Plugin.ShareLinks/Api/ShareLinksController.cs index 17b5aeb..1f475bb 100644 --- a/Jellyfin.Plugin.ShareLinks/Api/ShareLinksController.cs +++ b/Jellyfin.Plugin.ShareLinks/Api/ShareLinksController.cs @@ -91,6 +91,8 @@ public sealed class ShareLinkGuestStateDto public DateTimeOffset? ExpiresAtUtc { get; set; } public bool LockdownEnabled { get; set; } + + public string? HiddenSelectors { get; set; } } /// ShareLinks API surface. @@ -291,7 +293,8 @@ public sealed class ShareLinksController : ControllerBase AllowedItemId = match.ItemId, ShareId = match.Id, ExpiresAtUtc = match.ExpiresAtUtc, - LockdownEnabled = config.GuestModeLockdownEnabled + LockdownEnabled = config.GuestModeLockdownEnabled, + HiddenSelectors = config.GuestHiddenSelectors }); } } @@ -299,7 +302,8 @@ public sealed class ShareLinksController : ControllerBase return Ok(new ShareLinkGuestStateDto { IsGuest = false, - LockdownEnabled = config.GuestModeLockdownEnabled + LockdownEnabled = config.GuestModeLockdownEnabled, + HiddenSelectors = config.GuestHiddenSelectors }); } diff --git a/Jellyfin.Plugin.ShareLinks/Configuration/PluginConfiguration.cs b/Jellyfin.Plugin.ShareLinks/Configuration/PluginConfiguration.cs index dbc3799..d639ce5 100644 --- a/Jellyfin.Plugin.ShareLinks/Configuration/PluginConfiguration.cs +++ b/Jellyfin.Plugin.ShareLinks/Configuration/PluginConfiguration.cs @@ -41,4 +41,12 @@ public class PluginConfiguration : BasePluginConfiguration /// Gets or sets a value indicating whether guest-mode lockdown is enabled. public bool GuestModeLockdownEnabled { get; set; } = true; + + /// + /// Gets or sets a comma-separated list of CSS selectors that are hidden from guest + /// sessions in the web client. Used to suppress other plugins' injected UI (search + /// bars, floating buttons) so a guest only sees the shared title. Defaults to the + /// AI Search plugin's elements. + /// + public string GuestHiddenSelectors { get; set; } = ".ais-fab,.ais-overlay,#ais-styles"; } diff --git a/Jellyfin.Plugin.ShareLinks/Web/configPage.html b/Jellyfin.Plugin.ShareLinks/Web/configPage.html index af35a35..8a0808b 100644 --- a/Jellyfin.Plugin.ShareLinks/Web/configPage.html +++ b/Jellyfin.Plugin.ShareLinks/Web/configPage.html @@ -123,6 +123,11 @@ +
+ +
Elements hidden from guest sessions, e.g. other plugins' buttons. Default hides the AI Search UI.
+
+
@@ -207,6 +212,7 @@ page.querySelector('#MaxExpiryHours').value = cfg.MaxExpiryHours || 720; page.querySelector('#PublicBaseUrlOverride').value = cfg.PublicBaseUrlOverride || ''; page.querySelector('#GuestUsernamePrefix').value = cfg.GuestUsernamePrefix || 'share-'; + page.querySelector('#GuestHiddenSelectors').value = cfg.GuestHiddenSelectors != null ? cfg.GuestHiddenSelectors : '.ais-fab,.ais-overlay,#ais-styles'; page.querySelector('#AllowTranscoding').checked = cfg.AllowTranscoding !== false; page.querySelector('#AllowRemuxing').checked = cfg.AllowRemuxing !== false; page.querySelector('#CleanupIntervalMinutes').value = cfg.CleanupIntervalMinutes || 60; @@ -387,6 +393,7 @@ cfg.MaxExpiryHours = parseInt(page.querySelector('#MaxExpiryHours').value, 10) || 720; cfg.PublicBaseUrlOverride = page.querySelector('#PublicBaseUrlOverride').value.trim(); cfg.GuestUsernamePrefix = page.querySelector('#GuestUsernamePrefix').value.trim() || 'share-'; + cfg.GuestHiddenSelectors = page.querySelector('#GuestHiddenSelectors').value; cfg.AllowTranscoding = page.querySelector('#AllowTranscoding').checked; cfg.AllowRemuxing = page.querySelector('#AllowRemuxing').checked; cfg.CleanupIntervalMinutes = parseInt(page.querySelector('#CleanupIntervalMinutes').value, 10) || 60; diff --git a/Jellyfin.Plugin.ShareLinks/Web/sharelinks.js b/Jellyfin.Plugin.ShareLinks/Web/sharelinks.js index 12cb861..7dcc33f 100644 --- a/Jellyfin.Plugin.ShareLinks/Web/sharelinks.js +++ b/Jellyfin.Plugin.ShareLinks/Web/sharelinks.js @@ -2,7 +2,7 @@ var pluginId = '68540b76-ee74-436d-85ff-2abc884bbea6'; var copyLabel = 'Copy Stream URL'; var actionLabel = 'Create guest link'; - var clientVersion = '1.0.0-ui-modal-7'; + var clientVersion = '1.0.0-ui-modal-8'; var allowedItemStorageKey = 'sharelinks.allowedItemId'; var guestClassName = 'sharelinks-guest'; var hiddenAttr = 'data-sharelinks-hidden'; @@ -172,6 +172,7 @@ } ensureGuestStyle(); + ensurePluginHideStyle(context.hiddenSelectors); hideGuestControls(); // UX-only lockdown: the guest user's real access boundary is still the @@ -202,7 +203,8 @@ allowedItemId: extractAllowedItemId(state) || sessionStorage.getItem(allowedItemStorageKey) || null, username: username, prefix: prefix, - lockdownEnabled: lockdownEnabled + lockdownEnabled: lockdownEnabled, + hiddenSelectors: (state && (state.HiddenSelectors || state.hiddenSelectors)) || '' }; } @@ -227,10 +229,43 @@ var style = document.createElement('style'); style.id = 'ShareLinksGuestStyle'; style.textContent = 'body.' + guestClassName + ' [' + hiddenAttr + '="1"],' - + ' body.' + guestClassName + ' .headerBackButton,' + ' body.' + guestClassName + ' .headerHomeButton,' + ' body.' + guestClassName + ' .mainDrawerButton,' - + ' body.' + guestClassName + ' .headerSearchButton { display: none !important; }'; + + ' body.' + guestClassName + ' .headerSearchButton,' + + ' body.' + guestClassName + ' [data-action="addtoplaylist"],' + + ' body.' + guestClassName + ' [data-action="addtocollection"] { display: none !important; }' + + ' body.' + guestClassName + ' .card,' + + ' body.' + guestClassName + ' #castCollapsible a,' + + ' body.' + guestClassName + ' .detailsGroupItem a,' + + ' body.' + guestClassName + ' .genresGroup a,' + + ' body.' + guestClassName + ' .studiosGroup a,' + + ' body.' + guestClassName + ' .mediaInfoItem a { pointer-events: none !important; cursor: default !important; }'; + document.head.appendChild(style); + } + + function ensurePluginHideStyle(selectors) { + var value = String(selectors || '').split(',').map(function (part) { + return part.trim(); + }).filter(Boolean); + var existing = document.getElementById('ShareLinksPluginHideStyle'); + if (value.length === 0) { + if (existing) { + existing.remove(); + } + return; + } + + var css = value.join(', ') + ' { display: none !important; }'; + if (existing) { + if (existing.textContent !== css) { + existing.textContent = css; + } + return; + } + + var style = document.createElement('style'); + style.id = 'ShareLinksPluginHideStyle'; + style.textContent = css; document.head.appendChild(style); }