diff --git a/src/apps/dashboard/routes/users/profile.tsx b/src/apps/dashboard/routes/users/profile.tsx
index 11cda31fcd..7ef62f8261 100644
--- a/src/apps/dashboard/routes/users/profile.tsx
+++ b/src/apps/dashboard/routes/users/profile.tsx
@@ -182,6 +182,7 @@ const UserEdit: FunctionComponent = () => {
(page.querySelector('.chkDisabled') as HTMLInputElement).checked = user.Policy.IsDisabled;
(page.querySelector('.chkIsHidden') as HTMLInputElement).checked = user.Policy.IsHidden;
(page.querySelector('.chkEnableCollectionManagement') as HTMLInputElement).checked = user.Policy.EnableCollectionManagement;
+ (page.querySelector('.chkEnableSubtitleManagement') as HTMLInputElement).checked = user.Policy.EnableSubtitleManagement;
(page.querySelector('.chkRemoteControlSharedDevices') as HTMLInputElement).checked = user.Policy.EnableSharedDeviceControl;
(page.querySelector('.chkEnableRemoteControlOtherUsers') as HTMLInputElement).checked = user.Policy.EnableRemoteControlOfOtherUsers;
(page.querySelector('.chkEnableDownloading') as HTMLInputElement).checked = user.Policy.EnableContentDownloading;
@@ -240,6 +241,7 @@ const UserEdit: FunctionComponent = () => {
user.Policy.EnableVideoPlaybackTranscoding = (page.querySelector('.chkEnableVideoPlaybackTranscoding') as HTMLInputElement).checked;
user.Policy.EnablePlaybackRemuxing = (page.querySelector('.chkEnableVideoPlaybackRemuxing') as HTMLInputElement).checked;
user.Policy.EnableCollectionManagement = (page.querySelector('.chkEnableCollectionManagement') as HTMLInputElement).checked;
+ user.Policy.EnableSubtitleManagement = (page.querySelector('.chkEnableSubtitleManagement') as HTMLInputElement).checked;
user.Policy.ForceRemoteSourceTranscoding = (page.querySelector('.chkForceRemoteSourceTranscoding') as HTMLInputElement).checked;
user.Policy.EnableContentDownloading = (page.querySelector('.chkEnableDownloading') as HTMLInputElement).checked;
user.Policy.EnableRemoteAccess = (page.querySelector('.chkRemoteAccess') as HTMLInputElement).checked;
@@ -392,6 +394,11 @@ const UserEdit: FunctionComponent = () => {
className='chkEnableCollectionManagement'
title='AllowCollectionManagement'
/>
+
{globalize.translate('HeaderFeatureAccess')}
diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js
index 6d1c3c24f5..ad2052a3e1 100644
--- a/src/components/itemContextMenu.js
+++ b/src/components/itemContextMenu.js
@@ -214,11 +214,7 @@ export function getCommands(options) {
});
}
- if (canEdit && item.MediaType === 'Video' && item.Type !== 'TvChannel' && item.Type !== 'Program'
- && item.LocationType !== 'Virtual'
- && !(item.Type === 'Recording' && item.Status !== 'Completed')
- && options.editSubtitles !== false
- ) {
+ if (itemHelper.canEditSubtitles(user, item) && options.editSubtitles !== false) {
commands.push({
name: globalize.translate('EditSubtitles'),
id: 'editsubtitles',
diff --git a/src/components/itemHelper.js b/src/components/itemHelper.js
index d763003fb9..498a8bab80 100644
--- a/src/components/itemHelper.js
+++ b/src/components/itemHelper.js
@@ -1,6 +1,10 @@
import { appHost } from './apphost';
import globalize from '../scripts/globalize';
import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
+import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
+import { LocationType } from '@jellyfin/sdk/lib/generated-client/models/location-type';
+import { RecordingStatus } from '@jellyfin/sdk/lib/generated-client/models/recording-status';
+import { MediaType } from '@jellyfin/sdk/lib/generated-client/models/media-type';
export function getDisplayName(item, options = {}) {
if (!item) {
@@ -155,6 +159,33 @@ export function canEditImages (user, item) {
return itemType !== 'Timer' && itemType !== 'SeriesTimer' && canEdit(user, item) && !isLocalItem(item);
}
+export function canEditSubtitles (user, item) {
+ if (item.MediaType !== MediaType.Video) {
+ return false;
+ }
+ const itemType = item.Type;
+ if (itemType === BaseItemKind.Recording && item.Status !== RecordingStatus.Completed) {
+ return false;
+ }
+ if (itemType === BaseItemKind.TvChannel
+ || itemType === BaseItemKind.Program
+ || itemType === 'Timer'
+ || itemType === 'SeriesTimer'
+ || itemType === BaseItemKind.UserRootFolder
+ || itemType === BaseItemKind.UserView
+ ) {
+ return false;
+ }
+ if (isLocalItem(item)) {
+ return false;
+ }
+ if (item.LocationType === LocationType.Virtual) {
+ return false;
+ }
+ return user.Policy.EnableSubtitleManagement
+ || user.Policy.IsAdministrator;
+}
+
export function canShare (item, user) {
if (item.Type === 'Program') {
return false;
@@ -300,6 +331,7 @@ export default {
canIdentify: canIdentify,
canEdit: canEdit,
canEditImages: canEditImages,
+ canEditSubtitles,
canShare: canShare,
enableDateAddedDisplay: enableDateAddedDisplay,
canMarkPlayed: canMarkPlayed,
diff --git a/src/strings/en-us.json b/src/strings/en-us.json
index 311dde071b..6d253c1f21 100644
--- a/src/strings/en-us.json
+++ b/src/strings/en-us.json
@@ -23,6 +23,7 @@
"AllLibraries": "All libraries",
"AllowedRemoteAddressesHelp": "Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. If left blank, all remote addresses will be allowed.",
"AllowCollectionManagement": "Allow this user to manage collections",
+ "AllowSubtitleManagement": "Allow this user to edit subtitles",
"AllowFfmpegThrottling": "Throttle Transcodes",
"AllowFfmpegThrottlingHelp": "When a transcode or remux gets far enough ahead from the current playback position, pause the process so it will consume less resources. This is most useful when watching without seeking often. Turn this off if you experience playback issues.",
"AllowSegmentDeletion": "Delete segments",