move the ShareLink action to its own section at the bottom of the item menu

The button used to be cloned in next to "Copy Stream URL", or shoved at the top
of the menu when there was none, which put it in the middle of Jellyfin's own
command groups. Now there is a single injection path: append an actionsheetDivider
plus the action at the end of the sheet's scroller, so it reads as its own
section the way Jellyfin separates its groups.

It also stopped appearing on menus for things that are not media. The old code
resolved the item from the URL, so opening the "..." menu on a cast member inside
a movie page showed ShareLink and would have shared the movie behind it. The item
is now taken from the trigger that opened the menu (card, list row or the detail
page's own button, all of which we can walk up from) and its type is checked
against the server before anything is injected: movie, series, season, episode,
nothing else. The API refuses everything else too, so a hand-rolled request
cannot tag a person or a playlist either.

Clicking the action now dismisses the action sheet instead of leaving it stacked
under the dialog.
Cette révision appartient à :
Franciskid
2026-07-26 16:21:05 +02:00
Parent 36fc574d49
révision 35631eae46
5 fichiers modifiés avec 253 ajouts et 123 suppressions
+19 -3
Voir le fichier
@@ -12,6 +12,7 @@ using Jellyfin.Plugin.ShareLinks.Models;
using Jellyfin.Plugin.ShareLinks.Services;
using Jellyfin.Plugin.ShareLinks.Storage;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization;
@@ -202,10 +203,14 @@ public sealed class ShareLinksController : ControllerBase
return NotFound(new { error = "Item not found." });
}
if (item.IsFolder && item is not Series && item is not Season)
if (!IsShareableItem(item))
{
_logger.LogWarning("ShareLinks: create rejected, item {ItemId} \"{ItemName}\" is a folder or library, not shareable media.", itemId, item.Name);
return BadRequest(new { error = "Only a movie, episode, series or season can be shared, not a library or collection. Open the title's page and try again." });
_logger.LogWarning(
"ShareLinks: create rejected, item {ItemId} \"{ItemName}\" is a {ItemType}, which is not shareable media.",
itemId,
item.Name,
item.GetType().Name);
return BadRequest(new { error = "Only a movie, series, season or episode can be shared. Open the title's page and try again." });
}
try
@@ -412,6 +417,17 @@ setTimeout(function () { window.location.replace({{redirectUrlJson}}); }, 4000);
?? User.Identity?.Name;
}
/// <summary>
/// Only real video titles are shareable. Anything else - a person, studio,
/// genre, library, collection, playlist, music track or book - would either
/// give the guest nothing to play or pull unrelated items into the guest's tag
/// policy, so the API refuses it even when a client asks for it.
/// </summary>
private static bool IsShareableItem(BaseItem item)
{
return item is Movie or Series or Season or Episode;
}
private static bool IsExpired(ShareLinkRecord record)
{
return record.ExpiresAtUtc <= DateTimeOffset.UtcNow;