v3: added playlist moderation permission
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 43s
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 43s
This commit is contained in:
@@ -42,7 +42,7 @@ msgstr "{label} ({count})"
|
||||
msgid "{visibleCount, plural, one {# comment} other {# comments}}"
|
||||
msgstr "{visibleCount, plural, one {# comment} other {# comments}}"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:560
|
||||
#: src/pages/PlaylistDetail.tsx:561
|
||||
#: src/pages/UserPublicProfile.tsx:732
|
||||
msgid "← Back"
|
||||
msgstr "← Back"
|
||||
@@ -180,13 +180,13 @@ msgstr "Can't connect to the live updates server. Upvotes and notifications may
|
||||
#: src/components/form/FormActions.tsx:32
|
||||
#: src/pages/Dump.tsx:359
|
||||
#: src/pages/DumpEdit.tsx:433
|
||||
#: src/pages/PlaylistDetail.tsx:908
|
||||
#: src/pages/PlaylistDetail.tsx:911
|
||||
#: src/pages/UserPublicProfile.tsx:1637
|
||||
#: src/pages/UserPublicProfile.tsx:1707
|
||||
msgid "Cancel"
|
||||
msgstr "Cancel"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:733
|
||||
#: src/pages/PlaylistDetail.tsx:736
|
||||
msgid "Cancel removal"
|
||||
msgstr "Cancel removal"
|
||||
|
||||
@@ -278,7 +278,7 @@ msgstr "Default tab"
|
||||
#: src/components/CommentThread.tsx:376
|
||||
#: src/components/CommentThread.tsx:382
|
||||
#: src/components/ConfirmModal.tsx:16
|
||||
#: src/pages/PlaylistDetail.tsx:904
|
||||
#: src/pages/PlaylistDetail.tsx:907
|
||||
msgid "Delete"
|
||||
msgstr "Delete"
|
||||
|
||||
@@ -288,7 +288,7 @@ msgid "Delete dump"
|
||||
msgstr "Delete dump"
|
||||
|
||||
#: src/components/PlaylistCard.tsx:109
|
||||
#: src/pages/PlaylistDetail.tsx:746
|
||||
#: src/pages/PlaylistDetail.tsx:749
|
||||
#: src/pages/UserPlaylists.tsx:465
|
||||
msgid "Delete playlist"
|
||||
msgstr "Delete playlist"
|
||||
@@ -301,13 +301,13 @@ msgstr "Delete this comment?"
|
||||
msgid "Delete this dump? This cannot be undone."
|
||||
msgstr "Delete this dump? This cannot be undone."
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:745
|
||||
#: src/pages/PlaylistDetail.tsx:748
|
||||
#: src/pages/UserPlaylists.tsx:464
|
||||
msgid "Delete this playlist? This cannot be undone."
|
||||
msgstr "Delete this playlist? This cannot be undone."
|
||||
|
||||
#: src/components/PlaylistCreateForm.tsx:78
|
||||
#: src/pages/PlaylistDetail.tsx:865
|
||||
#: src/pages/PlaylistDetail.tsx:868
|
||||
msgid "Description (optional)"
|
||||
msgstr "Description (optional)"
|
||||
|
||||
@@ -353,7 +353,7 @@ msgstr "Earlier"
|
||||
|
||||
#: src/components/CommentThread.tsx:367
|
||||
#: src/pages/Dump.tsx:459
|
||||
#: src/pages/PlaylistDetail.tsx:613
|
||||
#: src/pages/PlaylistDetail.tsx:616
|
||||
msgid "Edit"
|
||||
msgstr "Edit"
|
||||
|
||||
@@ -366,7 +366,7 @@ msgstr "Edit title"
|
||||
#. placeholder {0}: relativeTime(playlist.updatedAt)
|
||||
#: src/components/CommentThread.tsx:317
|
||||
#: src/pages/Dump.tsx:412
|
||||
#: src/pages/PlaylistDetail.tsx:652
|
||||
#: src/pages/PlaylistDetail.tsx:655
|
||||
msgid "edited {0}"
|
||||
msgstr "edited {0}"
|
||||
|
||||
@@ -375,7 +375,7 @@ msgstr "edited {0}"
|
||||
#. placeholder {0}: playlist.updatedAt.toLocaleString()
|
||||
#: src/components/CommentThread.tsx:315
|
||||
#: src/pages/Dump.tsx:410
|
||||
#: src/pages/PlaylistDetail.tsx:649
|
||||
#: src/pages/PlaylistDetail.tsx:652
|
||||
msgid "Edited {0}"
|
||||
msgstr "Edited {0}"
|
||||
|
||||
@@ -424,7 +424,7 @@ msgstr "Failed to post comment"
|
||||
msgid "Failed to post reply"
|
||||
msgstr "Failed to post reply"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:896
|
||||
#: src/pages/PlaylistDetail.tsx:899
|
||||
#: src/pages/UserPublicProfile.tsx:1640
|
||||
#: src/pages/UserPublicProfile.tsx:1709
|
||||
msgid "Failed to save"
|
||||
@@ -595,7 +595,7 @@ msgstr "Loading dump…"
|
||||
msgid "Loading more…"
|
||||
msgstr "Loading more…"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:544
|
||||
#: src/pages/PlaylistDetail.tsx:545
|
||||
msgid "Loading playlist…"
|
||||
msgstr "Loading playlist…"
|
||||
|
||||
@@ -688,7 +688,7 @@ msgstr "New password"
|
||||
msgid "New playlist"
|
||||
msgstr "New playlist"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:668
|
||||
#: src/pages/PlaylistDetail.tsx:671
|
||||
msgid "No dumps in this playlist yet."
|
||||
msgstr "No dumps in this playlist yet."
|
||||
|
||||
@@ -785,7 +785,7 @@ msgstr "Password updated"
|
||||
msgid "Passwords do not match"
|
||||
msgstr "Passwords do not match"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:851
|
||||
#: src/pages/PlaylistDetail.tsx:854
|
||||
msgid "Playlist title"
|
||||
msgstr "Playlist title"
|
||||
|
||||
@@ -825,22 +825,22 @@ msgstr "Posting…"
|
||||
#: src/components/PlaylistCard.tsx:73
|
||||
#: src/components/PlaylistMembershipPanel.tsx:55
|
||||
#: src/pages/Dump.tsx:418
|
||||
#: src/pages/PlaylistDetail.tsx:632
|
||||
#: src/pages/PlaylistDetail.tsx:635
|
||||
msgid "private"
|
||||
msgstr "private"
|
||||
|
||||
#: src/components/form/SegmentedField.tsx:78
|
||||
#: src/pages/PlaylistDetail.tsx:890
|
||||
#: src/pages/PlaylistDetail.tsx:893
|
||||
msgid "Private"
|
||||
msgstr "Private"
|
||||
|
||||
#: src/components/PlaylistCard.tsx:72
|
||||
#: src/pages/PlaylistDetail.tsx:631
|
||||
#: src/pages/PlaylistDetail.tsx:634
|
||||
msgid "public"
|
||||
msgstr "public"
|
||||
|
||||
#: src/components/form/SegmentedField.tsx:77
|
||||
#: src/pages/PlaylistDetail.tsx:883
|
||||
#: src/pages/PlaylistDetail.tsx:886
|
||||
msgid "Public"
|
||||
msgstr "Public"
|
||||
|
||||
@@ -873,7 +873,7 @@ msgstr "Related"
|
||||
msgid "Remove file"
|
||||
msgstr "Remove file"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:723
|
||||
#: src/pages/PlaylistDetail.tsx:726
|
||||
msgid "Remove from playlist"
|
||||
msgstr "Remove from playlist"
|
||||
|
||||
@@ -918,7 +918,7 @@ msgstr "Role"
|
||||
#: src/components/CommentThread.tsx:328
|
||||
#: src/pages/Dump.tsx:351
|
||||
#: src/pages/DumpEdit.tsx:436
|
||||
#: src/pages/PlaylistDetail.tsx:915
|
||||
#: src/pages/PlaylistDetail.tsx:918
|
||||
#: src/pages/UserPublicProfile.tsx:1629
|
||||
#: src/pages/UserPublicProfile.tsx:1699
|
||||
msgid "Save"
|
||||
@@ -927,7 +927,7 @@ msgstr "Save"
|
||||
#: src/components/ChangePasswordModal.tsx:100
|
||||
#: src/components/CommentThread.tsx:329
|
||||
#: src/pages/Dump.tsx:350
|
||||
#: src/pages/PlaylistDetail.tsx:911
|
||||
#: src/pages/PlaylistDetail.tsx:914
|
||||
#: src/pages/ResetPassword.tsx:124
|
||||
#: src/pages/UserPublicProfile.tsx:1626
|
||||
#: src/pages/UserPublicProfile.tsx:1696
|
||||
@@ -1019,7 +1019,7 @@ msgstr "Title is required."
|
||||
msgid "Today"
|
||||
msgstr "Today"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:735
|
||||
#: src/pages/PlaylistDetail.tsx:738
|
||||
msgid "Undo"
|
||||
msgstr "Undo"
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -42,7 +42,7 @@ msgstr "{label} ({count})"
|
||||
msgid "{visibleCount, plural, one {# comment} other {# comments}}"
|
||||
msgstr "{visibleCount, plural, one {# commentaire} other {# commentaires}}"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:560
|
||||
#: src/pages/PlaylistDetail.tsx:561
|
||||
#: src/pages/UserPublicProfile.tsx:732
|
||||
msgid "← Back"
|
||||
msgstr "← Retour"
|
||||
@@ -180,13 +180,13 @@ msgstr "Impossible de se connecter au serveur de mises à jour en direct. Les vo
|
||||
#: src/components/form/FormActions.tsx:32
|
||||
#: src/pages/Dump.tsx:359
|
||||
#: src/pages/DumpEdit.tsx:433
|
||||
#: src/pages/PlaylistDetail.tsx:908
|
||||
#: src/pages/PlaylistDetail.tsx:911
|
||||
#: src/pages/UserPublicProfile.tsx:1637
|
||||
#: src/pages/UserPublicProfile.tsx:1707
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:733
|
||||
#: src/pages/PlaylistDetail.tsx:736
|
||||
msgid "Cancel removal"
|
||||
msgstr "Annuler la suppression"
|
||||
|
||||
@@ -278,7 +278,7 @@ msgstr "Onglet par défaut"
|
||||
#: src/components/CommentThread.tsx:376
|
||||
#: src/components/CommentThread.tsx:382
|
||||
#: src/components/ConfirmModal.tsx:16
|
||||
#: src/pages/PlaylistDetail.tsx:904
|
||||
#: src/pages/PlaylistDetail.tsx:907
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
@@ -288,7 +288,7 @@ msgid "Delete dump"
|
||||
msgstr "Supprimer la reco"
|
||||
|
||||
#: src/components/PlaylistCard.tsx:109
|
||||
#: src/pages/PlaylistDetail.tsx:746
|
||||
#: src/pages/PlaylistDetail.tsx:749
|
||||
#: src/pages/UserPlaylists.tsx:465
|
||||
msgid "Delete playlist"
|
||||
msgstr "Supprimer la collection"
|
||||
@@ -301,13 +301,13 @@ msgstr "Supprimer ce commentaire ?"
|
||||
msgid "Delete this dump? This cannot be undone."
|
||||
msgstr "Supprimer cette reco ? Cette action est irréversible."
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:745
|
||||
#: src/pages/PlaylistDetail.tsx:748
|
||||
#: src/pages/UserPlaylists.tsx:464
|
||||
msgid "Delete this playlist? This cannot be undone."
|
||||
msgstr "Supprimer cette collection ? Cette action est irréversible."
|
||||
|
||||
#: src/components/PlaylistCreateForm.tsx:78
|
||||
#: src/pages/PlaylistDetail.tsx:865
|
||||
#: src/pages/PlaylistDetail.tsx:868
|
||||
msgid "Description (optional)"
|
||||
msgstr "Description (facultatif)"
|
||||
|
||||
@@ -353,7 +353,7 @@ msgstr "Plus tôt"
|
||||
|
||||
#: src/components/CommentThread.tsx:367
|
||||
#: src/pages/Dump.tsx:459
|
||||
#: src/pages/PlaylistDetail.tsx:613
|
||||
#: src/pages/PlaylistDetail.tsx:616
|
||||
msgid "Edit"
|
||||
msgstr "Modifier"
|
||||
|
||||
@@ -366,7 +366,7 @@ msgstr "Modifier le titre"
|
||||
#. placeholder {0}: relativeTime(playlist.updatedAt)
|
||||
#: src/components/CommentThread.tsx:317
|
||||
#: src/pages/Dump.tsx:412
|
||||
#: src/pages/PlaylistDetail.tsx:652
|
||||
#: src/pages/PlaylistDetail.tsx:655
|
||||
msgid "edited {0}"
|
||||
msgstr "modifié {0}"
|
||||
|
||||
@@ -375,7 +375,7 @@ msgstr "modifié {0}"
|
||||
#. placeholder {0}: playlist.updatedAt.toLocaleString()
|
||||
#: src/components/CommentThread.tsx:315
|
||||
#: src/pages/Dump.tsx:410
|
||||
#: src/pages/PlaylistDetail.tsx:649
|
||||
#: src/pages/PlaylistDetail.tsx:652
|
||||
msgid "Edited {0}"
|
||||
msgstr "Modifié le {0}"
|
||||
|
||||
@@ -424,7 +424,7 @@ msgstr "Impossible de publier le commentaire"
|
||||
msgid "Failed to post reply"
|
||||
msgstr "Impossible de publier la réponse"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:896
|
||||
#: src/pages/PlaylistDetail.tsx:899
|
||||
#: src/pages/UserPublicProfile.tsx:1640
|
||||
#: src/pages/UserPublicProfile.tsx:1709
|
||||
msgid "Failed to save"
|
||||
@@ -595,7 +595,7 @@ msgstr "Chargement de la reco…"
|
||||
msgid "Loading more…"
|
||||
msgstr "Chargement…"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:544
|
||||
#: src/pages/PlaylistDetail.tsx:545
|
||||
msgid "Loading playlist…"
|
||||
msgstr "Chargement de la collection…"
|
||||
|
||||
@@ -688,7 +688,7 @@ msgstr "Nouveau mot de passe"
|
||||
msgid "New playlist"
|
||||
msgstr "Nouvelle collection"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:668
|
||||
#: src/pages/PlaylistDetail.tsx:671
|
||||
msgid "No dumps in this playlist yet."
|
||||
msgstr "Aucune reco dans cette collection pour l'instant."
|
||||
|
||||
@@ -785,7 +785,7 @@ msgstr "Mot de passe mis à jour"
|
||||
msgid "Passwords do not match"
|
||||
msgstr "Les mots de passe ne correspondent pas"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:851
|
||||
#: src/pages/PlaylistDetail.tsx:854
|
||||
msgid "Playlist title"
|
||||
msgstr "Titre de la collection"
|
||||
|
||||
@@ -825,22 +825,22 @@ msgstr "Publication…"
|
||||
#: src/components/PlaylistCard.tsx:73
|
||||
#: src/components/PlaylistMembershipPanel.tsx:55
|
||||
#: src/pages/Dump.tsx:418
|
||||
#: src/pages/PlaylistDetail.tsx:632
|
||||
#: src/pages/PlaylistDetail.tsx:635
|
||||
msgid "private"
|
||||
msgstr "privé"
|
||||
|
||||
#: src/components/form/SegmentedField.tsx:78
|
||||
#: src/pages/PlaylistDetail.tsx:890
|
||||
#: src/pages/PlaylistDetail.tsx:893
|
||||
msgid "Private"
|
||||
msgstr "Privé"
|
||||
|
||||
#: src/components/PlaylistCard.tsx:72
|
||||
#: src/pages/PlaylistDetail.tsx:631
|
||||
#: src/pages/PlaylistDetail.tsx:634
|
||||
msgid "public"
|
||||
msgstr "public"
|
||||
|
||||
#: src/components/form/SegmentedField.tsx:77
|
||||
#: src/pages/PlaylistDetail.tsx:883
|
||||
#: src/pages/PlaylistDetail.tsx:886
|
||||
msgid "Public"
|
||||
msgstr "Public"
|
||||
|
||||
@@ -873,7 +873,7 @@ msgstr "Connexe"
|
||||
msgid "Remove file"
|
||||
msgstr "Supprimer le fichier"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:723
|
||||
#: src/pages/PlaylistDetail.tsx:726
|
||||
msgid "Remove from playlist"
|
||||
msgstr "Retirer de la collection"
|
||||
|
||||
@@ -918,7 +918,7 @@ msgstr "Rôle"
|
||||
#: src/components/CommentThread.tsx:328
|
||||
#: src/pages/Dump.tsx:351
|
||||
#: src/pages/DumpEdit.tsx:436
|
||||
#: src/pages/PlaylistDetail.tsx:915
|
||||
#: src/pages/PlaylistDetail.tsx:918
|
||||
#: src/pages/UserPublicProfile.tsx:1629
|
||||
#: src/pages/UserPublicProfile.tsx:1699
|
||||
msgid "Save"
|
||||
@@ -927,7 +927,7 @@ msgstr "Enregistrer"
|
||||
#: src/components/ChangePasswordModal.tsx:100
|
||||
#: src/components/CommentThread.tsx:329
|
||||
#: src/pages/Dump.tsx:350
|
||||
#: src/pages/PlaylistDetail.tsx:911
|
||||
#: src/pages/PlaylistDetail.tsx:914
|
||||
#: src/pages/ResetPassword.tsx:124
|
||||
#: src/pages/UserPublicProfile.tsx:1626
|
||||
#: src/pages/UserPublicProfile.tsx:1696
|
||||
@@ -1019,7 +1019,7 @@ msgstr "Un titre est requis."
|
||||
msgid "Today"
|
||||
msgstr "Aujourd'hui"
|
||||
|
||||
#: src/pages/PlaylistDetail.tsx:735
|
||||
#: src/pages/PlaylistDetail.tsx:738
|
||||
msgid "Undo"
|
||||
msgstr "Annuler"
|
||||
|
||||
@@ -1068,7 +1068,7 @@ msgstr "Utilisateur"
|
||||
|
||||
#: src/pages/UserPublicProfile.tsx:1246
|
||||
msgid "User management"
|
||||
msgstr "Gestion des utilisateurs"
|
||||
msgstr "Gestion utilisateur"
|
||||
|
||||
#: src/components/UserMenu.tsx:37
|
||||
msgid "User menu"
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
deserializePlaylistWithDumps,
|
||||
} from "../model.ts";
|
||||
import { playlistUrl } from "../utils/urls.ts";
|
||||
import { can } from "../utils/permissions.ts";
|
||||
import { useAuth } from "../hooks/useAuth.ts";
|
||||
import { useWS } from "../hooks/useWS.ts";
|
||||
import { relativeTime } from "../utils/relativeTime.ts";
|
||||
@@ -566,6 +567,8 @@ export function PlaylistDetail() {
|
||||
|
||||
const { playlist } = state;
|
||||
const isOwner = !!user && user.id === playlist.userId;
|
||||
// Owners edit their own playlists; moderators may edit any (playlist:moderate).
|
||||
const canEdit = isOwner || can(user, "playlist:moderate");
|
||||
|
||||
// Active dumps in playlist order; fading dumps appended so they stay visible
|
||||
const activeDumps = playlist.dumps.filter((d) => activeDumpIds.has(d.id));
|
||||
@@ -604,7 +607,7 @@ export function PlaylistDetail() {
|
||||
isPublic={playlist.isPublic}
|
||||
/>
|
||||
)}
|
||||
{isOwner && (
|
||||
{canEdit && (
|
||||
<button
|
||||
type="button"
|
||||
className="playlist-edit-btn"
|
||||
@@ -671,7 +674,7 @@ export function PlaylistDetail() {
|
||||
: (
|
||||
<div
|
||||
className="playlist-dump-list"
|
||||
onDragOver={isOwner ? (e) => e.preventDefault() : undefined}
|
||||
onDragOver={canEdit ? (e) => e.preventDefault() : undefined}
|
||||
>
|
||||
{visibleDumps.map((dump) => {
|
||||
const isActive = activeDumpIds.has(dump.id);
|
||||
@@ -692,16 +695,16 @@ export function PlaylistDetail() {
|
||||
? " playlist-dump-item--drag-over"
|
||||
: ""
|
||||
}`}
|
||||
draggable={isOwner && isActive}
|
||||
onDragStart={isOwner && isActive
|
||||
draggable={canEdit && isActive}
|
||||
onDragStart={canEdit && isActive
|
||||
? () => handleDragStart(activeIndex)
|
||||
: undefined}
|
||||
onDragOver={isOwner && isActive
|
||||
onDragOver={canEdit && isActive
|
||||
? (e) => handleDragOver(e, activeIndex)
|
||||
: undefined}
|
||||
onDragEnd={isOwner ? handleDragEnd : undefined}
|
||||
onDragEnd={canEdit ? handleDragEnd : undefined}
|
||||
>
|
||||
{isOwner && isActive && (
|
||||
{canEdit && isActive && (
|
||||
<span className="drag-handle" aria-hidden>⠿</span>
|
||||
)}
|
||||
<DumpCard
|
||||
@@ -714,7 +717,7 @@ export function PlaylistDetail() {
|
||||
className={cardCls}
|
||||
isOwner={!!user && user.id === dump.userId}
|
||||
/>
|
||||
{isOwner && (isActive
|
||||
{canEdit && (isActive
|
||||
? (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import type { PublicUser, Role } from "../model.ts";
|
||||
|
||||
// Mirror of api/lib/permissions.ts. Keep the two in sync.
|
||||
export type Permission = "dump:moderate" | "comment:moderate" | "user:manage";
|
||||
export type Permission =
|
||||
| "dump:moderate"
|
||||
| "comment:moderate"
|
||||
| "playlist:moderate"
|
||||
| "user:manage";
|
||||
|
||||
const ALL_PERMISSIONS: readonly Permission[] = [
|
||||
"dump:moderate",
|
||||
"comment:moderate",
|
||||
"playlist:moderate",
|
||||
"user:manage",
|
||||
];
|
||||
|
||||
const ROLE_PERMISSIONS: Record<Role, readonly Permission[]> = {
|
||||
admin: ALL_PERMISSIONS,
|
||||
moderator: ["dump:moderate", "comment:moderate"],
|
||||
moderator: ["dump:moderate", "comment:moderate", "playlist:moderate"],
|
||||
user: [],
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user