v4: a playlist's edit form is a .form like the dump's, and its dumps keep the header's edges — the editor laid the cover beside its fields with display: contents, which on a phone left them a squeezed column, and drew its own inputs: the title and the slug both in bold large type, the description in another size, only the slug labelled, and a segmented control styled apart. It now stacks the shared fields, each under its label (Cover, Title with its counter, Link, Description, the visibility toggle), over the action row, as DumpEdit does, and takes the header's whole card. The dumps' drag handle and remove button took width from the cards, which sat inset from the header on both sides; they now sit in the page's side padding (--page-gutter, which .page-content now names), so the cards line up with the header at any width. Undo shows as a small btn-primary over the card it would bring back, instead of a third control beside it. CountedInput, VisibilitySegmented and the .playlist-edit-* rules had no other users and are gone. 669 frontend tests pass, and tsc, eslint, deno fmt, doc:lint and i18n:compile --strict are clean; checked in screenshots in all six styles at 390px, Kanagawa and Smooth at desktop widths too, Undo in Smooth and GeoCities.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
khannurien
2026-10-08 21:48:12 +00:00
parent 45f39a959b
commit cf89c18eb9
12 changed files with 313 additions and 435 deletions

File diff suppressed because one or more lines are too long

View File

@@ -120,13 +120,13 @@ msgstr "yours"
#. js-lingui-explicit-id
#: api/mail/digest.ts:221
#: src/pages/Settings.tsx:157
#: src/pages/Settings.tsx:154
msgid "digest.monthly"
msgstr "Monthly recap"
#. js-lingui-explicit-id
#: api/mail/digest.ts:221
#: src/pages/Settings.tsx:148
#: src/pages/Settings.tsx:145
msgid "digest.weekly"
msgstr "Weekly digest"
@@ -407,7 +407,7 @@ msgstr "{0}–{1} characters: letters, numbers, or underscores"
msgid "{label} ({count})"
msgstr "{label} ({count})"
#: src/pages/Notifications.tsx:502
#: src/pages/Notifications.tsx:497
msgid "{totalUnread, plural, one {# new} other {# new}}"
msgstr "{totalUnread, plural, one {# new} other {# new}}"
@@ -445,47 +445,47 @@ msgstr "+ Playlist"
#. placeholder {0}: d.commenterUsername
#. placeholder {1}: d.dumpTitle
#: src/pages/Notifications.tsx:235
#: src/pages/Notifications.tsx:237
msgid "<0>{0}</0> commented on <1>{1}</1>"
msgstr "<0>{0}</0> commented on <1>{1}</1>"
#. placeholder {0}: d.followerUsername
#. placeholder {1}: d.playlistTitle
#: src/pages/Notifications.tsx:195
#: src/pages/Notifications.tsx:197
msgid "<0>{0}</0> followed your playlist <1>{1}</1>"
msgstr "<0>{0}</0> followed your playlist <1>{1}</1>"
#. placeholder {0}: d.likerUsername
#. placeholder {1}: d.dumpTitle
#: src/pages/Notifications.tsx:245
#: src/pages/Notifications.tsx:247
msgid "<0>{0}</0> liked your comment on <1>{1}</1>"
msgstr "<0>{0}</0> liked your comment on <1>{1}</1>"
#. placeholder {0}: d.mentionerUsername
#: src/pages/Notifications.tsx:261
#: src/pages/Notifications.tsx:263
msgid "<0>{0}</0> mentioned you in <1>{where}</1>"
msgstr "<0>{0}</0> mentioned you in <1>{where}</1>"
#. placeholder {0}: d.dumperUsername
#. placeholder {1}: d.dumpTitle
#: src/pages/Notifications.tsx:205
#: src/pages/Notifications.tsx:207
msgid "<0>{0}</0> posted <1>{1}</1>"
msgstr "<0>{0}</0> posted <1>{1}</1>"
#. placeholder {0}: d.followerUsername
#: src/pages/Notifications.tsx:186
#: src/pages/Notifications.tsx:188
msgid "<0>{0}</0> started following you"
msgstr "<0>{0}</0> started following you"
#. placeholder {0}: d.voterUsername
#. placeholder {1}: d.dumpTitle
#: src/pages/Notifications.tsx:225
#: src/pages/Notifications.tsx:227
msgid "<0>{0}</0> upvoted <1>{1}</1>"
msgstr "<0>{0}</0> upvoted <1>{1}</1>"
#. placeholder {0}: d.dumpTitle
#. placeholder {1}: d.playlistTitle
#: src/pages/Notifications.tsx:215
#: src/pages/Notifications.tsx:217
msgid "<0>{0}</0> was added to <1>{1}</1>"
msgstr "<0>{0}</0> was added to <1>{1}</1>"
@@ -493,7 +493,7 @@ msgstr "<0>{0}</0> was added to <1>{1}</1>"
msgid "1 new message"
msgstr "1 new message"
#: src/pages/Notifications.tsx:256
#: src/pages/Notifications.tsx:258
msgid "a comment"
msgstr "a comment"
@@ -501,11 +501,11 @@ msgstr "a comment"
msgid "A link is required"
msgstr "A link is required"
#: src/pages/Notifications.tsx:259
#: src/pages/Notifications.tsx:261
msgid "a post"
msgstr "a post"
#: src/pages/Settings.tsx:86
#: src/pages/Settings.tsx:83
msgid "Account"
msgstr "Account"
@@ -554,7 +554,7 @@ msgstr "Already have an account? <0>Log in</0>"
msgid "An unexpected error occurred."
msgstr "An unexpected error occurred."
#: src/pages/Settings.tsx:102
#: src/pages/Settings.tsx:99
msgid "Appearance"
msgstr "Appearance"
@@ -569,7 +569,7 @@ msgstr "At least {0} characters"
msgid "At most {maxLength} characters."
msgstr "At most {maxLength} characters."
#: src/pages/Settings.tsx:44
#: src/pages/Settings.tsx:45
msgid "Auto"
msgstr "Auto"
@@ -589,7 +589,7 @@ msgstr "Can't connect to the live updates server. Upvotes and notifications may
#: src/components/comment/CommentForm.tsx:127
#: src/components/dump/create/DumpCreateModal.tsx:752
#: src/components/form/FormActions.tsx:42
#: src/components/playlist/PlaylistEditForm.tsx:218
#: src/components/playlist/PlaylistEditForm.tsx:194
#: src/components/profile/DescriptionEditor.tsx:97
#: src/components/profile/EmailEditor.tsx:83
#: src/components/shared/ConfirmModal.tsx:63
@@ -598,7 +598,7 @@ msgstr "Can't connect to the live updates server. Upvotes and notifications may
msgid "Cancel"
msgstr "Cancel"
#: src/components/playlist/PlaylistDumpList.tsx:118
#: src/components/playlist/PlaylistDumpList.tsx:120
msgid "Cancel removal"
msgstr "Cancel removal"
@@ -607,12 +607,12 @@ msgstr "Cancel removal"
msgid "Cancel reply"
msgstr "Cancel reply"
#: src/components/layout/AppHeader.tsx:163
#: src/components/layout/AppHeader.tsx:164
msgid "Cancel search"
msgstr "Cancel search"
#: src/components/category/CategorySelect.tsx:33
#: src/pages/Manage.tsx:33
#: src/pages/Manage.tsx:30
msgid "Categories"
msgstr "Categories"
@@ -633,7 +633,7 @@ msgstr "Change image"
msgid "Change password"
msgstr "Change password"
#: src/pages/Settings.tsx:97
#: src/pages/Settings.tsx:94
msgid "Change password…"
msgstr "Change password…"
@@ -642,14 +642,14 @@ msgid "Change skin tone"
msgstr "Change skin tone"
#: src/components/chat/ChatModal.tsx:716
#: src/components/layout/ChatButton.tsx:26
#: src/components/layout/ChatFab.tsx:53
#: src/components/layout/ChatButton.tsx:27
#: src/components/layout/ChatFab.tsx:54
#: src/components/layout/ChatFab.tsx:55
msgid "Chat"
msgstr "Chat"
#: src/components/layout/ChatButton.tsx:25
#: src/components/layout/ChatFab.tsx:52
#: src/components/layout/ChatButton.tsx:26
#: src/components/layout/ChatFab.tsx:53
msgid "Chat ({unreadChatCount} unread)"
msgstr "Chat ({unreadChatCount} unread)"
@@ -682,7 +682,7 @@ msgstr "Close without posting? The attached file won't be kept — everything yo
msgid "Collapse player"
msgstr "Collapse player"
#: src/pages/Settings.tsx:111
#: src/pages/Settings.tsx:108
msgid "Color scheme"
msgstr "Color scheme"
@@ -740,6 +740,10 @@ msgstr "Couldn't unsubscribe"
msgid "Couldn't update. Try again."
msgstr "Couldn't update. Try again."
#: src/components/playlist/PlaylistEditForm.tsx:133
msgid "Cover"
msgstr "Cover"
#: src/components/playlist/PlaylistCreateForm.tsx:112
msgid "Create"
msgstr "Create"
@@ -756,11 +760,11 @@ msgstr "Creating…"
msgid "Current password"
msgstr "Current password"
#: src/pages/Settings.tsx:46
#: src/pages/Settings.tsx:47
msgid "Dark"
msgstr "Dark"
#: src/pages/Settings.tsx:122
#: src/pages/Settings.tsx:119
msgid "Default tab"
msgstr "Default tab"
@@ -771,7 +775,7 @@ msgstr "Default tab"
#: src/components/chat/ChatMessageItem.tsx:214
#: src/components/comment/CommentNode.tsx:265
#: src/components/comment/CommentNode.tsx:271
#: src/components/playlist/PlaylistEditForm.tsx:214
#: src/components/playlist/PlaylistEditForm.tsx:190
#: src/components/shared/ConfirmModal.tsx:34
msgid "Delete"
msgstr "Delete"
@@ -822,8 +826,11 @@ msgstr "Delete this playlist? This cannot be undone."
msgid "deleted message"
msgstr "deleted message"
#: src/components/playlist/PlaylistEditForm.tsx:173
msgid "Description"
msgstr "Description"
#: src/components/playlist/PlaylistCreateForm.tsx:104
#: src/components/playlist/PlaylistEditForm.tsx:186
msgid "Description (optional)"
msgstr "Description (optional)"
@@ -861,7 +868,7 @@ msgstr "Drop a file here, or paste one"
msgid "Drop a replacement here"
msgstr "Drop a replacement here"
#: src/components/layout/AppHeader.tsx:202
#: src/components/layout/AppHeader.tsx:203
msgid "Dump"
msgstr "Dump"
@@ -884,7 +891,7 @@ msgstr "Dumps"
msgid "Dumps ({0})"
msgstr "Dumps ({0})"
#: src/pages/Notifications.tsx:301
#: src/pages/Notifications.tsx:303
msgid "Earlier"
msgstr "Earlier"
@@ -948,7 +955,7 @@ msgstr "Editing"
msgid "Editing {0}"
msgstr "Editing {0}"
#: src/pages/Settings.tsx:146
#: src/pages/Settings.tsx:143
msgid "Email"
msgstr "Email"
@@ -998,7 +1005,7 @@ msgstr "Failed to post comment"
msgid "Failed to post reply"
msgstr "Failed to post reply"
#: src/components/playlist/PlaylistEditForm.tsx:206
#: src/components/playlist/PlaylistEditForm.tsx:182
#: src/components/profile/DescriptionEditor.tsx:99
#: src/components/profile/EmailEditor.tsx:86
msgid "Failed to save"
@@ -1020,7 +1027,7 @@ msgstr "Failed to update avatar"
msgid "Failed to update role"
msgstr "Failed to update role"
#: src/pages/Settings.tsx:120
#: src/pages/Settings.tsx:117
msgid "Feeds"
msgstr "Feeds"
@@ -1055,7 +1062,7 @@ msgstr "Follow some users to see their dumps here."
#: src/components/feed/FeedTabBar.tsx:43
#: src/components/profile/ProfileFollowTab.tsx:163
#: src/pages/Settings.tsx:53
#: src/pages/Settings.tsx:54
#: src/pages/UserPublicProfile.tsx:161
msgid "Followed"
msgstr "Followed"
@@ -1090,7 +1097,7 @@ msgid "Go to login"
msgstr "Go to login"
#: src/components/feed/FeedTabBar.tsx:39
#: src/pages/Settings.tsx:50
#: src/pages/Settings.tsx:51
msgid "Hot"
msgstr "Hot"
@@ -1135,7 +1142,7 @@ msgid "Invitees"
msgstr "Invitees"
#: src/components/feed/FeedTabBar.tsx:41
#: src/pages/Settings.tsx:52
#: src/pages/Settings.tsx:53
msgid "Journal"
msgstr "Journal"
@@ -1147,7 +1154,7 @@ msgstr "Keep editing"
msgid "Letters, numbers and underscores only"
msgstr "Letters, numbers and underscores only"
#: src/pages/Settings.tsx:45
#: src/pages/Settings.tsx:46
msgid "Light"
msgstr "Light"
@@ -1168,7 +1175,7 @@ msgstr "Link or file"
msgid "Live updates are temporarily disconnected. Trying to reconnect…"
msgstr "Live updates are temporarily disconnected. Trying to reconnect…"
#: src/components/layout/AppHeader.tsx:237
#: src/components/layout/AppHeader.tsx:238
msgid "Live updates unavailable."
msgstr "Live updates unavailable."
@@ -1211,7 +1218,7 @@ msgstr "Loading profile…"
msgid "Loading…"
msgstr "Loading…"
#: src/components/layout/AppHeader.tsx:210
#: src/components/layout/AppHeader.tsx:211
#: src/pages/UserLogin.tsx:41
#: src/pages/UserLogin.tsx:67
#: src/pages/UserLogin.tsx:86
@@ -1245,8 +1252,8 @@ msgstr "Lowercase letters, numbers and single hyphens only"
#: src/components/layout/UserMenu.tsx:98
#: src/components/profile/ProfileHeader.tsx:180
#: src/pages/Manage.tsx:23
#: src/pages/Manage.tsx:30
#: src/pages/Manage.tsx:24
#: src/pages/Manage.tsx:29
#: src/pages/UserPublicProfile.tsx:164
msgid "Manage"
msgstr "Manage"
@@ -1270,7 +1277,7 @@ msgstr "Name"
#: src/components/chat/ChatModal.tsx:669
#: src/components/feed/FeedTabBar.tsx:40
#: src/pages/Settings.tsx:51
#: src/pages/Settings.tsx:52
msgid "New"
msgstr "New"
@@ -1355,26 +1362,26 @@ msgstr "Not following anyone yet."
#: src/components/profile/ProfileDumpsTab.tsx:53
#: src/components/profile/ProfileOverviewTab.tsx:47
#: src/pages/Notifications.tsx:516
#: src/pages/Notifications.tsx:511
msgid "Nothing here yet."
msgstr "Nothing here yet."
#: src/components/layout/NotificationBell.tsx:54
#: src/pages/Notifications.tsx:374
#: src/pages/Notifications.tsx:498
#: src/pages/Settings.tsx:131
#: src/components/layout/NotificationBell.tsx:55
#: src/pages/Notifications.tsx:376
#: src/pages/Notifications.tsx:494
#: src/pages/Settings.tsx:128
msgid "Notifications"
msgstr "Notifications"
#: src/components/layout/NotificationBell.tsx:53
#: src/components/layout/NotificationBell.tsx:54
msgid "Notifications ({unreadNotificationCount} unread)"
msgstr "Notifications ({unreadNotificationCount} unread)"
#: src/pages/Settings.tsx:58
#: src/pages/Settings.tsx:59
msgid "Off"
msgstr "Off"
#: src/pages/Settings.tsx:57
#: src/pages/Settings.tsx:58
msgid "On"
msgstr "On"
@@ -1407,7 +1414,7 @@ msgstr "Overview"
msgid "Page not found"
msgstr "Page not found"
#: src/pages/Settings.tsx:91
#: src/pages/Settings.tsx:88
#: src/pages/UserLogin.tsx:82
msgid "Password"
msgstr "Password"
@@ -1455,12 +1462,8 @@ msgstr "Picked up where you left off."
msgid "Play"
msgstr "Play"
#: src/components/playlist/PlaylistEditForm.tsx:162
msgid "Playlist title"
msgstr "Playlist title"
#: src/components/dump/create/StepIndicator.tsx:21
#: src/components/layout/AppHeader.tsx:183
#: src/components/layout/AppHeader.tsx:184
#: src/components/layout/UserMenu.tsx:81
#: src/components/profile/ProfileFollowTab.tsx:167
#: src/components/profile/ProfilePlaylistsTab.tsx:118
@@ -1496,7 +1499,7 @@ msgstr "Previous track"
msgid "private"
msgstr "private"
#: src/components/form/SegmentedField.tsx:65
#: src/components/form/SegmentedField.tsx:61
msgid "Private"
msgstr "Private"
@@ -1505,7 +1508,7 @@ msgstr "Private"
msgid "public"
msgstr "public"
#: src/components/form/SegmentedField.tsx:64
#: src/components/form/SegmentedField.tsx:60
msgid "Public"
msgstr "Public"
@@ -1543,7 +1546,7 @@ msgstr "Related"
msgid "Remove file"
msgstr "Remove file"
#: src/components/playlist/PlaylistDumpList.tsx:108
#: src/components/playlist/PlaylistDumpList.tsx:110
msgid "Remove from playlist"
msgstr "Remove from playlist"
@@ -1612,7 +1615,7 @@ msgstr "Role"
#: src/components/category/CategoryManager.tsx:216
#: src/components/chat/ChatEditBox.tsx:85
#: src/components/comment/CommentNode.tsx:216
#: src/components/playlist/PlaylistEditForm.tsx:225
#: src/components/playlist/PlaylistEditForm.tsx:200
#: src/components/profile/DescriptionEditor.tsx:89
#: src/components/profile/EmailEditor.tsx:75
#: src/pages/Dump.tsx:437
@@ -1621,7 +1624,7 @@ msgid "Save"
msgstr "Save"
#: src/components/comment/CommentNode.tsx:217
#: src/components/playlist/PlaylistEditForm.tsx:221
#: src/components/playlist/PlaylistEditForm.tsx:197
#: src/components/profile/ChangePasswordModal.tsx:119
#: src/components/profile/DescriptionEditor.tsx:86
#: src/components/profile/EmailEditor.tsx:72
@@ -1630,13 +1633,13 @@ msgstr "Save"
msgid "Saving…"
msgstr "Saving…"
#: src/components/layout/AppHeader.tsx:163
#: src/components/layout/SearchBar.tsx:82
#: src/components/layout/AppHeader.tsx:164
#: src/components/layout/SearchBar.tsx:83
#: src/pages/Search.tsx:69
msgid "Search"
msgstr "Search"
#: src/components/layout/SearchBar.tsx:78
#: src/components/layout/SearchBar.tsx:79
msgid "Search dumps, users, playlists…"
msgstr "Search dumps, users, playlists…"
@@ -1673,7 +1676,7 @@ msgstr "Send reset link"
msgid "Sending…"
msgstr "Sending…"
#: src/components/layout/AppHeader.tsx:199
#: src/components/layout/AppHeader.tsx:200
msgid "Server unreachable"
msgstr "Server unreachable"
@@ -1684,8 +1687,8 @@ msgstr "Set new password"
#: src/components/layout/UserMenu.tsx:89
#: src/components/profile/ProfileHeader.tsx:176
#: src/pages/Settings.tsx:69
#: src/pages/Settings.tsx:83
#: src/pages/Settings.tsx:70
#: src/pages/Settings.tsx:82
msgid "Settings"
msgstr "Settings"
@@ -1712,7 +1715,7 @@ msgstr "Some images couldn't be uploaded."
msgid "Something went wrong"
msgstr "Something went wrong"
#: src/pages/Settings.tsx:133
#: src/pages/Settings.tsx:130
msgid "Sounds"
msgstr "Sounds"
@@ -1720,11 +1723,11 @@ msgstr "Sounds"
msgid "Start fresh"
msgstr "Start fresh"
#: src/pages/Settings.tsx:104
#: src/pages/Settings.tsx:101
msgid "Style"
msgstr "Style"
#: src/components/layout/SearchBar.tsx:88
#: src/components/layout/SearchBar.tsx:89
msgid "Submit search"
msgstr "Submit search"
@@ -1732,7 +1735,7 @@ msgstr "Submit search"
msgid "That file is over {limit}."
msgstr "That file is over {limit}."
#: src/pages/Notifications.tsx:258
#: src/pages/Notifications.tsx:260
msgid "the chat"
msgstr "the chat"
@@ -1768,7 +1771,7 @@ msgstr "This page does not exist."
msgid "This reset link is missing or malformed."
msgstr "This reset link is missing or malformed."
#: src/pages/Settings.tsx:169
#: src/pages/Settings.tsx:166
msgid "This site doesn't send email, so no digest goes out."
msgstr "This site doesn't send email, so no digest goes out."
@@ -1782,6 +1785,7 @@ msgstr "Thumbnail"
#: src/components/dump/create/TitleField.tsx:45
#: src/components/playlist/PlaylistCreateForm.tsx:96
#: src/components/playlist/PlaylistEditForm.tsx:157
#: src/pages/DumpEdit.tsx:515
msgid "Title"
msgstr "Title"
@@ -1790,7 +1794,7 @@ msgstr "Title"
msgid "Title is required."
msgstr "Title is required."
#: src/pages/Notifications.tsx:299
#: src/pages/Notifications.tsx:301
#: src/utils/chat/time.ts:24
msgid "Today"
msgstr "Today"
@@ -1820,7 +1824,7 @@ msgstr "Turning off…"
msgid "Type a message…"
msgstr "Type a message…"
#: src/components/playlist/PlaylistDumpList.tsx:120
#: src/components/playlist/PlaylistDumpList.tsx:122
msgid "Undo"
msgstr "Undo"
@@ -1905,7 +1909,7 @@ msgstr "User menu"
#: src/components/profile/ProfileManageTab.tsx:61
#: src/components/profile/UsernameField.tsx:51
#: src/pages/Settings.tsx:88
#: src/pages/Settings.tsx:85
#: src/pages/UserLogin.tsx:75
#: src/pages/UserRegister.tsx:137
msgid "Username"
@@ -1955,7 +1959,7 @@ msgstr "Why?"
msgid "Write a reply…"
msgstr "Write a reply…"
#: src/pages/Notifications.tsx:300
#: src/pages/Notifications.tsx:302
#: src/utils/chat/time.ts:27
msgid "Yesterday"
msgstr "Yesterday"
@@ -1972,7 +1976,7 @@ msgstr "You won't get the monthly recap any more. You can turn it back on in you
msgid "You won't get the weekly digest any more. You can turn it back on in your settings."
msgstr "You won't get the weekly digest any more. You can turn it back on in your settings."
#: src/pages/Notifications.tsx:519
#: src/pages/Notifications.tsx:514
msgid "You'll be notified when someone follows your playlists, upvotes your dumps, or posts new content."
msgstr "You'll be notified when someone follows your playlists, upvotes your dumps, or posts new content."

File diff suppressed because one or more lines are too long

View File

@@ -120,13 +120,13 @@ msgstr "à vous"
#. js-lingui-explicit-id
#: api/mail/digest.ts:221
#: src/pages/Settings.tsx:157
#: src/pages/Settings.tsx:154
msgid "digest.monthly"
msgstr "Récap mensuel"
#. js-lingui-explicit-id
#: api/mail/digest.ts:221
#: src/pages/Settings.tsx:148
#: src/pages/Settings.tsx:145
msgid "digest.weekly"
msgstr "Résumé hebdomadaire"
@@ -407,7 +407,7 @@ msgstr "{0}–{1} caractères : lettres, chiffres ou tirets bas"
msgid "{label} ({count})"
msgstr "{label} ({count})"
#: src/pages/Notifications.tsx:502
#: src/pages/Notifications.tsx:497
msgid "{totalUnread, plural, one {# new} other {# new}}"
msgstr "{totalUnread, plural, one {# nouvelle} other {# nouvelles}}"
@@ -445,47 +445,47 @@ msgstr "+ Collection"
#. placeholder {0}: d.commenterUsername
#. placeholder {1}: d.dumpTitle
#: src/pages/Notifications.tsx:235
#: src/pages/Notifications.tsx:237
msgid "<0>{0}</0> commented on <1>{1}</1>"
msgstr "<0>{0}</0> a commenté sur <1>{1}</1>"
#. placeholder {0}: d.followerUsername
#. placeholder {1}: d.playlistTitle
#: src/pages/Notifications.tsx:195
#: src/pages/Notifications.tsx:197
msgid "<0>{0}</0> followed your playlist <1>{1}</1>"
msgstr "<0>{0}</0> a suivi votre collection <1>{1}</1>"
#. placeholder {0}: d.likerUsername
#. placeholder {1}: d.dumpTitle
#: src/pages/Notifications.tsx:245
#: src/pages/Notifications.tsx:247
msgid "<0>{0}</0> liked your comment on <1>{1}</1>"
msgstr "<0>{0}</0> a aimé votre commentaire sur <1>{1}</1>"
#. placeholder {0}: d.mentionerUsername
#: src/pages/Notifications.tsx:261
#: src/pages/Notifications.tsx:263
msgid "<0>{0}</0> mentioned you in <1>{where}</1>"
msgstr "<0>{0}</0> vous a mentionné dans <1>{where}</1>"
#. placeholder {0}: d.dumperUsername
#. placeholder {1}: d.dumpTitle
#: src/pages/Notifications.tsx:205
#: src/pages/Notifications.tsx:207
msgid "<0>{0}</0> posted <1>{1}</1>"
msgstr "<0>{0}</0> a publié <1>{1}</1>"
#. placeholder {0}: d.followerUsername
#: src/pages/Notifications.tsx:186
#: src/pages/Notifications.tsx:188
msgid "<0>{0}</0> started following you"
msgstr "<0>{0}</0> a commencé à vous suivre"
#. placeholder {0}: d.voterUsername
#. placeholder {1}: d.dumpTitle
#: src/pages/Notifications.tsx:225
#: src/pages/Notifications.tsx:227
msgid "<0>{0}</0> upvoted <1>{1}</1>"
msgstr "<0>{0}</0> a voté pour <1>{1}</1>"
#. placeholder {0}: d.dumpTitle
#. placeholder {1}: d.playlistTitle
#: src/pages/Notifications.tsx:215
#: src/pages/Notifications.tsx:217
msgid "<0>{0}</0> was added to <1>{1}</1>"
msgstr "<0>{0}</0> a été ajouté à <1>{1}</1>"
@@ -493,7 +493,7 @@ msgstr "<0>{0}</0> a été ajouté à <1>{1}</1>"
msgid "1 new message"
msgstr "1 nouveau message"
#: src/pages/Notifications.tsx:256
#: src/pages/Notifications.tsx:258
msgid "a comment"
msgstr "un commentaire"
@@ -501,11 +501,11 @@ msgstr "un commentaire"
msgid "A link is required"
msgstr "Il faut un lien"
#: src/pages/Notifications.tsx:259
#: src/pages/Notifications.tsx:261
msgid "a post"
msgstr "une publication"
#: src/pages/Settings.tsx:86
#: src/pages/Settings.tsx:83
msgid "Account"
msgstr "Compte"
@@ -554,7 +554,7 @@ msgstr "Vous avez déjà un compte ? <0>Se connecter</0>"
msgid "An unexpected error occurred."
msgstr "Une erreur inattendue s'est produite."
#: src/pages/Settings.tsx:102
#: src/pages/Settings.tsx:99
msgid "Appearance"
msgstr "Apparence"
@@ -569,7 +569,7 @@ msgstr "Au moins {0} caractères"
msgid "At most {maxLength} characters."
msgstr "{maxLength} caractères au maximum."
#: src/pages/Settings.tsx:44
#: src/pages/Settings.tsx:45
msgid "Auto"
msgstr "Auto"
@@ -589,7 +589,7 @@ msgstr "Impossible de se connecter au serveur de mises à jour en direct. Les vo
#: src/components/comment/CommentForm.tsx:127
#: src/components/dump/create/DumpCreateModal.tsx:752
#: src/components/form/FormActions.tsx:42
#: src/components/playlist/PlaylistEditForm.tsx:218
#: src/components/playlist/PlaylistEditForm.tsx:194
#: src/components/profile/DescriptionEditor.tsx:97
#: src/components/profile/EmailEditor.tsx:83
#: src/components/shared/ConfirmModal.tsx:63
@@ -598,7 +598,7 @@ msgstr "Impossible de se connecter au serveur de mises à jour en direct. Les vo
msgid "Cancel"
msgstr "Annuler"
#: src/components/playlist/PlaylistDumpList.tsx:118
#: src/components/playlist/PlaylistDumpList.tsx:120
msgid "Cancel removal"
msgstr "Annuler la suppression"
@@ -607,12 +607,12 @@ msgstr "Annuler la suppression"
msgid "Cancel reply"
msgstr "Annuler la réponse"
#: src/components/layout/AppHeader.tsx:163
#: src/components/layout/AppHeader.tsx:164
msgid "Cancel search"
msgstr "Annuler la recherche"
#: src/components/category/CategorySelect.tsx:33
#: src/pages/Manage.tsx:33
#: src/pages/Manage.tsx:30
msgid "Categories"
msgstr "Catégories"
@@ -633,7 +633,7 @@ msgstr "Changer l'image"
msgid "Change password"
msgstr "Changer le mot de passe"
#: src/pages/Settings.tsx:97
#: src/pages/Settings.tsx:94
msgid "Change password…"
msgstr "Changer le mot de passe…"
@@ -642,14 +642,14 @@ msgid "Change skin tone"
msgstr "Changer la couleur de peau"
#: src/components/chat/ChatModal.tsx:716
#: src/components/layout/ChatButton.tsx:26
#: src/components/layout/ChatFab.tsx:53
#: src/components/layout/ChatButton.tsx:27
#: src/components/layout/ChatFab.tsx:54
#: src/components/layout/ChatFab.tsx:55
msgid "Chat"
msgstr "Tribune"
#: src/components/layout/ChatButton.tsx:25
#: src/components/layout/ChatFab.tsx:52
#: src/components/layout/ChatButton.tsx:26
#: src/components/layout/ChatFab.tsx:53
msgid "Chat ({unreadChatCount} unread)"
msgstr "{unreadChatCount} messages non lus"
@@ -682,7 +682,7 @@ msgstr "Fermer sans publier ? Le fichier joint sera perdu — ce que vous avez
msgid "Collapse player"
msgstr "Réduire le lecteur"
#: src/pages/Settings.tsx:111
#: src/pages/Settings.tsx:108
msgid "Color scheme"
msgstr "Thème de couleur"
@@ -740,6 +740,10 @@ msgstr "Désabonnement impossible"
msgid "Couldn't update. Try again."
msgstr "La mise à jour a échoué. Réessayez."
#: src/components/playlist/PlaylistEditForm.tsx:133
msgid "Cover"
msgstr "Couverture"
#: src/components/playlist/PlaylistCreateForm.tsx:112
msgid "Create"
msgstr "Créer"
@@ -756,11 +760,11 @@ msgstr "Création…"
msgid "Current password"
msgstr "Mot de passe actuel"
#: src/pages/Settings.tsx:46
#: src/pages/Settings.tsx:47
msgid "Dark"
msgstr "Sombre"
#: src/pages/Settings.tsx:122
#: src/pages/Settings.tsx:119
msgid "Default tab"
msgstr "Onglet par défaut"
@@ -771,7 +775,7 @@ msgstr "Onglet par défaut"
#: src/components/chat/ChatMessageItem.tsx:214
#: src/components/comment/CommentNode.tsx:265
#: src/components/comment/CommentNode.tsx:271
#: src/components/playlist/PlaylistEditForm.tsx:214
#: src/components/playlist/PlaylistEditForm.tsx:190
#: src/components/shared/ConfirmModal.tsx:34
msgid "Delete"
msgstr "Supprimer"
@@ -822,8 +826,11 @@ msgstr "Supprimer cette collection ? Cette action est irréversible."
msgid "deleted message"
msgstr "message supprimé"
#: src/components/playlist/PlaylistEditForm.tsx:173
msgid "Description"
msgstr "Description"
#: src/components/playlist/PlaylistCreateForm.tsx:104
#: src/components/playlist/PlaylistEditForm.tsx:186
msgid "Description (optional)"
msgstr "Description (facultatif)"
@@ -861,7 +868,7 @@ msgstr "Déposez un fichier ici, ou collez-en un"
msgid "Drop a replacement here"
msgstr "Déposez un fichier de remplacement ici"
#: src/components/layout/AppHeader.tsx:202
#: src/components/layout/AppHeader.tsx:203
msgid "Dump"
msgstr "Reco"
@@ -884,7 +891,7 @@ msgstr "Recos"
msgid "Dumps ({0})"
msgstr "Recos ({0})"
#: src/pages/Notifications.tsx:301
#: src/pages/Notifications.tsx:303
msgid "Earlier"
msgstr "Plus tôt"
@@ -948,7 +955,7 @@ msgstr "Modification"
msgid "Editing {0}"
msgstr "Édition de {0}"
#: src/pages/Settings.tsx:146
#: src/pages/Settings.tsx:143
msgid "Email"
msgstr "E-mail"
@@ -998,7 +1005,7 @@ msgstr "Impossible de publier le commentaire"
msgid "Failed to post reply"
msgstr "Impossible de publier la réponse"
#: src/components/playlist/PlaylistEditForm.tsx:206
#: src/components/playlist/PlaylistEditForm.tsx:182
#: src/components/profile/DescriptionEditor.tsx:99
#: src/components/profile/EmailEditor.tsx:86
msgid "Failed to save"
@@ -1020,7 +1027,7 @@ msgstr "Impossible de mettre à jour l'avatar"
msgid "Failed to update role"
msgstr "Erreur lors de la mise à jour du rôle"
#: src/pages/Settings.tsx:120
#: src/pages/Settings.tsx:117
msgid "Feeds"
msgstr "Flux"
@@ -1055,7 +1062,7 @@ msgstr "Suivez des utilisateurs pour voir leurs recos ici."
#: src/components/feed/FeedTabBar.tsx:43
#: src/components/profile/ProfileFollowTab.tsx:163
#: src/pages/Settings.tsx:53
#: src/pages/Settings.tsx:54
#: src/pages/UserPublicProfile.tsx:161
msgid "Followed"
msgstr "Suivi"
@@ -1090,7 +1097,7 @@ msgid "Go to login"
msgstr "Aller à la connexion"
#: src/components/feed/FeedTabBar.tsx:39
#: src/pages/Settings.tsx:50
#: src/pages/Settings.tsx:51
msgid "Hot"
msgstr "Tendances"
@@ -1135,7 +1142,7 @@ msgid "Invitees"
msgstr "Invités"
#: src/components/feed/FeedTabBar.tsx:41
#: src/pages/Settings.tsx:52
#: src/pages/Settings.tsx:53
msgid "Journal"
msgstr "Journal"
@@ -1147,7 +1154,7 @@ msgstr "Continuer l'édition"
msgid "Letters, numbers and underscores only"
msgstr "Lettres, chiffres et tirets bas seulement"
#: src/pages/Settings.tsx:45
#: src/pages/Settings.tsx:46
msgid "Light"
msgstr "Clair"
@@ -1168,7 +1175,7 @@ msgstr "Lien ou fichier"
msgid "Live updates are temporarily disconnected. Trying to reconnect…"
msgstr "Les mises à jour en direct sont temporairement interrompues. Tentative de reconnexion…"
#: src/components/layout/AppHeader.tsx:237
#: src/components/layout/AppHeader.tsx:238
msgid "Live updates unavailable."
msgstr "Mises à jour en direct indisponibles."
@@ -1211,7 +1218,7 @@ msgstr "Chargement du profil…"
msgid "Loading…"
msgstr "Chargement…"
#: src/components/layout/AppHeader.tsx:210
#: src/components/layout/AppHeader.tsx:211
#: src/pages/UserLogin.tsx:41
#: src/pages/UserLogin.tsx:67
#: src/pages/UserLogin.tsx:86
@@ -1245,8 +1252,8 @@ msgstr "Minuscules, chiffres et tirets simples seulement"
#: src/components/layout/UserMenu.tsx:98
#: src/components/profile/ProfileHeader.tsx:180
#: src/pages/Manage.tsx:23
#: src/pages/Manage.tsx:30
#: src/pages/Manage.tsx:24
#: src/pages/Manage.tsx:29
#: src/pages/UserPublicProfile.tsx:164
msgid "Manage"
msgstr "Administration"
@@ -1270,7 +1277,7 @@ msgstr "Nom"
#: src/components/chat/ChatModal.tsx:669
#: src/components/feed/FeedTabBar.tsx:40
#: src/pages/Settings.tsx:51
#: src/pages/Settings.tsx:52
msgid "New"
msgstr "Nouveau"
@@ -1355,26 +1362,26 @@ msgstr "Aucun abonnement pour le moment."
#: src/components/profile/ProfileDumpsTab.tsx:53
#: src/components/profile/ProfileOverviewTab.tsx:47
#: src/pages/Notifications.tsx:516
#: src/pages/Notifications.tsx:511
msgid "Nothing here yet."
msgstr "Rien ici pour l'instant."
#: src/components/layout/NotificationBell.tsx:54
#: src/pages/Notifications.tsx:374
#: src/pages/Notifications.tsx:498
#: src/pages/Settings.tsx:131
#: src/components/layout/NotificationBell.tsx:55
#: src/pages/Notifications.tsx:376
#: src/pages/Notifications.tsx:494
#: src/pages/Settings.tsx:128
msgid "Notifications"
msgstr "Notifications"
#: src/components/layout/NotificationBell.tsx:53
#: src/components/layout/NotificationBell.tsx:54
msgid "Notifications ({unreadNotificationCount} unread)"
msgstr "Notifications ({unreadNotificationCount} non lues)"
#: src/pages/Settings.tsx:58
#: src/pages/Settings.tsx:59
msgid "Off"
msgstr "Désactivés"
#: src/pages/Settings.tsx:57
#: src/pages/Settings.tsx:58
msgid "On"
msgstr "Activés"
@@ -1407,7 +1414,7 @@ msgstr "Aperçu"
msgid "Page not found"
msgstr "Page introuvable"
#: src/pages/Settings.tsx:91
#: src/pages/Settings.tsx:88
#: src/pages/UserLogin.tsx:82
msgid "Password"
msgstr "Mot de passe"
@@ -1455,12 +1462,8 @@ msgstr "Reprise de votre brouillon."
msgid "Play"
msgstr "Lecture"
#: src/components/playlist/PlaylistEditForm.tsx:162
msgid "Playlist title"
msgstr "Titre de la collection"
#: src/components/dump/create/StepIndicator.tsx:21
#: src/components/layout/AppHeader.tsx:183
#: src/components/layout/AppHeader.tsx:184
#: src/components/layout/UserMenu.tsx:81
#: src/components/profile/ProfileFollowTab.tsx:167
#: src/components/profile/ProfilePlaylistsTab.tsx:118
@@ -1496,7 +1499,7 @@ msgstr "Piste précédente"
msgid "private"
msgstr "privé"
#: src/components/form/SegmentedField.tsx:65
#: src/components/form/SegmentedField.tsx:61
msgid "Private"
msgstr "Privé"
@@ -1505,7 +1508,7 @@ msgstr "Privé"
msgid "public"
msgstr "public"
#: src/components/form/SegmentedField.tsx:64
#: src/components/form/SegmentedField.tsx:60
msgid "Public"
msgstr "Public"
@@ -1543,7 +1546,7 @@ msgstr "Connexe"
msgid "Remove file"
msgstr "Supprimer le fichier"
#: src/components/playlist/PlaylistDumpList.tsx:108
#: src/components/playlist/PlaylistDumpList.tsx:110
msgid "Remove from playlist"
msgstr "Retirer de la collection"
@@ -1612,7 +1615,7 @@ msgstr "Rôle"
#: src/components/category/CategoryManager.tsx:216
#: src/components/chat/ChatEditBox.tsx:85
#: src/components/comment/CommentNode.tsx:216
#: src/components/playlist/PlaylistEditForm.tsx:225
#: src/components/playlist/PlaylistEditForm.tsx:200
#: src/components/profile/DescriptionEditor.tsx:89
#: src/components/profile/EmailEditor.tsx:75
#: src/pages/Dump.tsx:437
@@ -1621,7 +1624,7 @@ msgid "Save"
msgstr "Enregistrer"
#: src/components/comment/CommentNode.tsx:217
#: src/components/playlist/PlaylistEditForm.tsx:221
#: src/components/playlist/PlaylistEditForm.tsx:197
#: src/components/profile/ChangePasswordModal.tsx:119
#: src/components/profile/DescriptionEditor.tsx:86
#: src/components/profile/EmailEditor.tsx:72
@@ -1630,13 +1633,13 @@ msgstr "Enregistrer"
msgid "Saving…"
msgstr "Enregistrement…"
#: src/components/layout/AppHeader.tsx:163
#: src/components/layout/SearchBar.tsx:82
#: src/components/layout/AppHeader.tsx:164
#: src/components/layout/SearchBar.tsx:83
#: src/pages/Search.tsx:69
msgid "Search"
msgstr "Rechercher"
#: src/components/layout/SearchBar.tsx:78
#: src/components/layout/SearchBar.tsx:79
msgid "Search dumps, users, playlists…"
msgstr "Rechercher des recos, utilisateurs, collections…"
@@ -1673,7 +1676,7 @@ msgstr "Envoyer le lien de réinitialisation"
msgid "Sending…"
msgstr "Envoi…"
#: src/components/layout/AppHeader.tsx:199
#: src/components/layout/AppHeader.tsx:200
msgid "Server unreachable"
msgstr "Serveur inaccessible"
@@ -1684,8 +1687,8 @@ msgstr "Définir un nouveau mot de passe"
#: src/components/layout/UserMenu.tsx:89
#: src/components/profile/ProfileHeader.tsx:176
#: src/pages/Settings.tsx:69
#: src/pages/Settings.tsx:83
#: src/pages/Settings.tsx:70
#: src/pages/Settings.tsx:82
msgid "Settings"
msgstr "Paramètres"
@@ -1712,7 +1715,7 @@ msgstr "Certaines images n'ont pas pu être envoyées."
msgid "Something went wrong"
msgstr "Une erreur est survenue"
#: src/pages/Settings.tsx:133
#: src/pages/Settings.tsx:130
msgid "Sounds"
msgstr "Sons"
@@ -1720,11 +1723,11 @@ msgstr "Sons"
msgid "Start fresh"
msgstr "Repartir de zéro"
#: src/pages/Settings.tsx:104
#: src/pages/Settings.tsx:101
msgid "Style"
msgstr "Style"
#: src/components/layout/SearchBar.tsx:88
#: src/components/layout/SearchBar.tsx:89
msgid "Submit search"
msgstr "Lancer la recherche"
@@ -1732,7 +1735,7 @@ msgstr "Lancer la recherche"
msgid "That file is over {limit}."
msgstr "Ce fichier dépasse {limit}."
#: src/pages/Notifications.tsx:258
#: src/pages/Notifications.tsx:260
msgid "the chat"
msgstr "la tribune"
@@ -1768,7 +1771,7 @@ msgstr "Rien à voir, circulez."
msgid "This reset link is missing or malformed."
msgstr "Ce lien de réinitialisation est absent ou malformé."
#: src/pages/Settings.tsx:169
#: src/pages/Settings.tsx:166
msgid "This site doesn't send email, so no digest goes out."
msgstr "Ce site n'envoie pas d'e-mails, aucun résumé ne part donc."
@@ -1782,6 +1785,7 @@ msgstr "Miniature"
#: src/components/dump/create/TitleField.tsx:45
#: src/components/playlist/PlaylistCreateForm.tsx:96
#: src/components/playlist/PlaylistEditForm.tsx:157
#: src/pages/DumpEdit.tsx:515
msgid "Title"
msgstr "Titre"
@@ -1790,7 +1794,7 @@ msgstr "Titre"
msgid "Title is required."
msgstr "Un titre est requis."
#: src/pages/Notifications.tsx:299
#: src/pages/Notifications.tsx:301
#: src/utils/chat/time.ts:24
msgid "Today"
msgstr "Aujourd'hui"
@@ -1820,7 +1824,7 @@ msgstr "Désactivation…"
msgid "Type a message…"
msgstr "Écrivez un message…"
#: src/components/playlist/PlaylistDumpList.tsx:120
#: src/components/playlist/PlaylistDumpList.tsx:122
msgid "Undo"
msgstr "Annuler"
@@ -1905,7 +1909,7 @@ msgstr "Menu utilisateur"
#: src/components/profile/ProfileManageTab.tsx:61
#: src/components/profile/UsernameField.tsx:51
#: src/pages/Settings.tsx:88
#: src/pages/Settings.tsx:85
#: src/pages/UserLogin.tsx:75
#: src/pages/UserRegister.tsx:137
msgid "Username"
@@ -1955,7 +1959,7 @@ msgstr "Pourquoi ?"
msgid "Write a reply…"
msgstr "Écrire une réponse…"
#: src/pages/Notifications.tsx:300
#: src/pages/Notifications.tsx:302
#: src/utils/chat/time.ts:27
msgid "Yesterday"
msgstr "Hier"
@@ -1972,7 +1976,7 @@ msgstr "Vous ne recevrez plus le récap mensuel. Vous pouvez le réactiver dans
msgid "You won't get the weekly digest any more. You can turn it back on in your settings."
msgstr "Vous ne recevrez plus le résumé hebdomadaire. Vous pouvez le réactiver dans vos paramètres."
#: src/pages/Notifications.tsx:519
#: src/pages/Notifications.tsx:514
msgid "You'll be notified when someone follows your playlists, upvotes your dumps, or posts new content."
msgstr "Vous serez notifié lorsque quelqu'un suit vos collections, vote pour vos recos ou publie du nouveau contenu."

View File

@@ -265,9 +265,7 @@
Each rule below adds only its own size / weight / width deltas. */
.dump-title-input,
.profile-email-input,
.username-field-input,
.playlist-edit-input,
.playlist-edit-textarea {
.username-field-input {
border: var(--border-width) solid var(--color-border);
border-radius: var(--radius-lg);
background: var(--color-surface-sunken);
@@ -279,9 +277,7 @@
.dump-title-input:focus,
.profile-email-input:focus,
.username-field-input:focus,
.playlist-edit-input:focus,
.playlist-edit-textarea:focus {
.username-field-input:focus {
border-color: var(--color-accent);
}
@@ -2711,7 +2707,8 @@ body.has-player .chat-fab {
width: 100%;
max-width: 860px;
margin: 0 auto;
padding: 2rem 1.25rem 0;
--page-gutter: 1.25rem;
padding: 2rem var(--page-gutter) 0;
box-sizing: border-box;
animation: page-enter 0.2s ease both;
display: flex;
@@ -5308,11 +5305,6 @@ body.has-player .chat-fab {
flex-wrap: wrap;
}
.playlist-detail-title-row .playlist-edit-input {
flex: 1;
min-width: 0;
}
.playlist-detail-title {
margin: 0;
font-size: var(--text-2xl);
@@ -5370,41 +5362,12 @@ body.has-player .chat-fab {
var(--dur-base);
}
.playlist-edit-btn:hover,
.playlist-cancel-btn:hover {
.playlist-edit-btn:hover {
opacity: 1;
border-color: var(--color-accent);
color: var(--color-accent);
}
.playlist-edit-input,
.playlist-edit-textarea {
width: 100%;
font-size: var(--text-md);
padding: 0.4rem 0.65rem;
}
.playlist-edit-textarea {
resize: none;
overflow: hidden;
line-height: 1.5;
}
.playlist-edit-input {
font-size: var(--text-lg);
font-weight: 700;
}
/* Inline edit form: stack the fields with a little more breathing room than
the tight display layout, and let the action row sit at the bottom. */
.playlist-edit-content {
gap: 0.7rem;
}
.playlist-detail-meta .playlist-edit-toggle {
opacity: 1;
}
/* ── Playlist dump list ── */
.playlist-dump-list {
display: flex;
@@ -5413,31 +5376,55 @@ body.has-player .chat-fab {
}
.playlist-dump-item {
display: flex;
display: grid;
grid-template-columns: minmax(0, 1fr);
align-items: center;
gap: 0.5rem;
padding: 0 0.25rem;
transition: background var(--dur-base);
border-radius: var(--radius-xl);
}
/* For those who may edit the playlist, each card has a drag handle on its
left and a remove button on its right. They sit in the page's side
padding, so the cards keep the header's edges. Undo shows over the card it
would bring back, dimmed during its cooldown. */
.playlist-dump-list--editable .playlist-dump-item {
grid-template-columns: var(--page-gutter) minmax(0, 1fr) var(--page-gutter);
margin-inline: calc(-1 * var(--page-gutter));
}
.playlist-dump-item .dump-card {
min-width: 0;
}
.playlist-dump-list--editable .playlist-dump-item .dump-card {
grid-area: 1 / 2;
}
.playlist-dump-item--drag-over {
background: color-mix(in srgb, var(--color-accent) 12%, transparent);
outline: 2px dashed var(--color-accent);
}
.playlist-dump-item .dump-card {
flex: 1;
min-width: 0;
.drag-handle {
grid-column: 1;
justify-self: center;
cursor: grab;
opacity: 0.35;
font-size: var(--text-lg);
user-select: none;
}
.drag-handle:active {
cursor: grabbing;
}
/* ── Playlist dump item action buttons ── */
.playlist-remove-btn {
flex-shrink: 0;
grid-column: 3;
align-self: stretch;
padding: 0;
color: var(--color-text);
opacity: 0.4;
font-size: var(--text-sm);
padding: 0.25rem 0.35rem;
}
.playlist-remove-btn:hover {
@@ -5445,32 +5432,12 @@ body.has-player .chat-fab {
color: var(--color-danger);
}
.playlist-cancel-btn {
flex-shrink: 0;
background: none;
border: 1px solid var(--color-border-subtle);
cursor: pointer;
color: var(--color-text);
opacity: 0.7;
font-size: var(--text-xs);
padding: 0.2rem 0.5rem;
border-radius: var(--radius-sm);
transition: opacity var(--dur-base), border-color var(--dur-base), color
var(--dur-base);
white-space: nowrap;
}
.drag-handle {
cursor: grab;
opacity: 0.35;
font-size: var(--text-lg);
flex-shrink: 0;
user-select: none;
padding: 0 0.25rem;
}
.drag-handle:active {
cursor: grabbing;
.playlist-undo-btn {
grid-area: 1 / 2;
justify-self: center;
/* Over the card, which is positioned. */
position: relative;
z-index: 1;
}
/* ── Add to playlist button (dump detail) ── */

View File

@@ -6,11 +6,7 @@
*/
import { Trans } from "@lingui/react/macro";
import {
Segmented,
type SegmentedOption,
type SegmentedProps,
} from "../shared/Segmented.tsx";
import { Segmented, type SegmentedOption } from "../shared/Segmented.tsx";
import {
Controller,
type FieldValues,
@@ -92,14 +88,3 @@ export function VisibilityToggle<T extends FieldValues>({
/>
);
}
/**
* Renders a Public/Private {@link Segmented} outside `SegmentedField`, for
* `PlaylistEditForm`, which binds it with its own `Controller` and styles it
* apart.
*/
export function VisibilitySegmented(
props: Omit<SegmentedProps<boolean>, "options">,
): React.JSX.Element {
return <Segmented options={VISIBILITY_OPTIONS} {...props} />;
}

View File

@@ -74,7 +74,9 @@ export function PlaylistDumpList(
let activeIndex = 0;
return (
<div
className="playlist-dump-list"
className={`playlist-dump-list${
canDrag ? " playlist-dump-list--editable" : ""
}`}
onDragOver={canDrag ? (e) => e.preventDefault() : undefined}
>
{dumps.map((dump) => {
@@ -113,7 +115,7 @@ export function PlaylistDumpList(
{phase === "cooldown" && onUndoRemove && (
<button
type="button"
className="playlist-cancel-btn"
className="btn-primary btn-sm playlist-undo-btn"
onClick={() => onUndoRemove(dump.id)}
aria-label={t`Cancel removal`}
>

View File

@@ -22,10 +22,10 @@ import { playlistImageUrl } from "../../utils/shared/urls.ts";
import { expectOk } from "../../utils/shared/api.ts";
import { useAuth } from "../../hooks/auth/useAuth.ts";
import { useApiForm } from "../../hooks/form/useApiForm.ts";
import { CountedInput } from "../shared/CountedInput.tsx";
import { ImagePicker } from "../shared/ImagePicker.tsx";
import { TextEditor } from "../editor/TextEditor.tsx";
import { VisibilitySegmented } from "../form/SegmentedField.tsx";
import { TextField } from "../form/TextField.tsx";
import { RichTextField } from "../form/RichTextField.tsx";
import { VisibilityToggle } from "../form/SegmentedField.tsx";
import { FormError } from "../form/FormError.tsx";
import { SubmitButton } from "../form/SubmitButton.tsx";
import { SlugField } from "../form/SlugField.tsx";
@@ -75,7 +75,6 @@ export function PlaylistEditForm(
image: null,
},
});
const description = form.watch("description");
const title = form.watch("title");
const onSubmit = form.submit(
@@ -121,110 +120,85 @@ export function PlaylistEditForm(
return (
<FormProvider {...form}>
{
/* `display: contents` keeps the header's layout, the cover beside the
fields, around a real <form>, which Enter submits. */
}
<form style={{ display: "contents" }} onSubmit={onSubmit}>
<Controller
name="image"
control={form.control}
render={({ field }) => (
<ImagePicker
src={imagePreview ??
(playlist.imageMime
? playlistImageUrl(playlist, mediaToken)
: null)}
alt="Cover"
size={72}
className="animate-in"
onChange={(file) => {
field.onChange(file);
setImagePreview(URL.createObjectURL(file));
}}
/>
)}
<form
className="form animate-in"
onSubmit={onSubmit}
onKeyDown={(e) => {
// The editor's popups take their own Escape and prevent it.
if (e.key === "Escape" && !e.defaultPrevented) onCancel();
}}
>
<div className="form-field">
<span className="form-label">
<Trans>Cover</Trans>
</span>
<Controller
name="image"
control={form.control}
render={({ field }) => (
<ImagePicker
src={imagePreview ??
(playlist.imageMime
? playlistImageUrl(playlist, mediaToken)
: null)}
alt="Cover"
size={72}
onChange={(file) => {
field.onChange(file);
setImagePreview(URL.createObjectURL(file));
}}
/>
)}
/>
</div>
<TextField<EditValues>
name="title"
label={t`Title`}
autoFocus
rules={{ required: true }}
maxLength={VALIDATION.PLAYLIST_TITLE_MAX}
showCount
/>
<div className="playlist-detail-content playlist-edit-content animate-in">
<Controller
name="title"
control={form.control}
render={({ field }) => (
<CountedInput
className="playlist-edit-input"
value={field.value}
onChange={field.onChange}
onKeyDown={(e) => {
if (e.key === "Escape") onCancel();
}}
autoFocus
placeholder={t`Playlist title`}
maxLength={VALIDATION.PLAYLIST_TITLE_MAX}
/>
)}
/>
<SlugField<EditValues>
name="slug"
titleName="title"
prefix={`${userPath(playlist.ownerUsername, "playlists")}/`}
saved={playlist}
/>
<SlugField<EditValues>
name="slug"
titleName="title"
prefix={`${userPath(playlist.ownerUsername, "playlists")}/`}
saved={playlist}
inputClassName="playlist-edit-input"
/>
<RichTextField<EditValues>
name="description"
label={t`Description`}
rows={2}
autoResize
maxLength={VALIDATION.PLAYLIST_DESCRIPTION_MAX}
onSubmit={() => void onSubmit()}
/>
<Controller
name="description"
control={form.control}
render={({ field }) => (
<TextEditor
className="playlist-edit-textarea"
value={field.value}
onChange={field.onChange}
onSubmit={() => void onSubmit()}
onCancel={onCancel}
placeholder={t`Description (optional)`}
autoResize
rows={2}
maxLength={VALIDATION.PLAYLIST_DESCRIPTION_MAX}
/>
)}
/>
<VisibilityToggle<EditValues> name="isPublic" />
<Controller
name="isPublic"
control={form.control}
render={({ field }) => (
<VisibilitySegmented
className="playlist-edit-toggle"
value={field.value}
onChange={field.onChange}
/>
)}
/>
<FormError title={t`Failed to save`} />
<FormError title={t`Failed to save`} />
<div className="form-actions">
<button
type="button"
className="btn-danger"
onClick={onRequestDelete}
>
<Trans>Delete</Trans>
<div className="form-actions">
<button
type="button"
className="btn-danger"
onClick={onRequestDelete}
>
<Trans>Delete</Trans>
</button>
<div className="form-actions-right">
<button type="button" className="form-cancel" onClick={onCancel}>
<Trans>Cancel</Trans>
</button>
<div className="form-actions-right">
<button type="button" className="form-cancel" onClick={onCancel}>
<Trans>Cancel</Trans>
</button>
<SubmitButton
pendingLabel={<Trans>Saving…</Trans>}
disabled={!title.trim() ||
description.length > VALIDATION.PLAYLIST_DESCRIPTION_MAX}
>
<Trans>Save</Trans>
</SubmitButton>
</div>
<SubmitButton
pendingLabel={<Trans>Saving…</Trans>}
disabled={!title.trim()}
>
<Trans>Save</Trans>
</SubmitButton>
</div>
</div>
</form>

View File

@@ -1,39 +0,0 @@
/**
* Renders a controlled text input with a character counter. Unlike `TextField`,
* it takes any input prop, for an inline editor such as a playlist's title.
*
* @module
*/
import { charCountClass } from "../../utils/shared/charCount.ts";
/**
* Passed to {@link CountedInput} by its caller, which holds the value. The rest
* goes to the `<input>`.
*/
export interface CountedInputProps
extends
Omit<React.InputHTMLAttributes<HTMLInputElement>, "maxLength" | "value"> {
value: string;
/** Stops the typing at this length, and is the counter's limit. */
maxLength: number;
}
/**
* Renders the input and an `n / maxLength` counter, which changes colour as the
* value nears its limit (`charCountClass`).
*/
export function CountedInput(
{ value, maxLength, ...rest }: CountedInputProps,
): React.JSX.Element {
const countClass = charCountClass(value.length, maxLength);
return (
<div className="input-with-count">
<input value={value} maxLength={maxLength} {...rest} />
<span className={`text-editor-count${countClass}`}>
{value.length} / {maxLength}
</span>
</div>
);
}

View File

@@ -91,28 +91,28 @@ export function PlaylistDetail(): React.JSX.Element {
return (
<PageShell>
<div className="playlist-detail-header" ref={headerRef}>
<div className="playlist-detail-header-top">
{editOpen
? (
<PlaylistEditForm
playlist={playlist}
onCancel={() => setEditOpen(false)}
onRequestDelete={() => setConfirmDelete(true)}
onSaved={(updated) => {
setEditOpen(false);
// Shown at once rather than left to the socket's echo,
// which may never come (disconnected).
applyDetails(updated);
}}
/>
)
: (
{editOpen
? (
<PlaylistEditForm
playlist={playlist}
onCancel={() => setEditOpen(false)}
onRequestDelete={() => setConfirmDelete(true)}
onSaved={(updated) => {
setEditOpen(false);
// Shown at once rather than left to the socket's echo,
// which may never come (disconnected).
applyDetails(updated);
}}
/>
)
: (
<div className="playlist-detail-header-top">
<PlaylistHeader
playlist={playlist}
onEdit={canEdit ? () => setEditOpen(true) : undefined}
/>
)}
</div>
</div>
)}
</div>
{actionError && (

View File

@@ -1,19 +0,0 @@
import { describe, expect, test } from "vitest";
import { screen } from "@testing-library/react";
import { CountedInput } from "../../../components/shared/CountedInput.tsx";
import { renderWithContexts } from "../../render.tsx";
describe("CountedInput", () => {
test("CountedInput counts toward its limit", () => {
renderWithContexts(
<CountedInput
value={"x".repeat(90)}
maxLength={100}
onChange={() => {}}
/>,
);
const count = screen.getByText("90 / 100");
expect(count).toHaveClass("text-editor-count--warn");
expect(screen.getByRole("textbox")).toHaveAttribute("maxlength", "100");
});
});

View File

@@ -130,7 +130,7 @@ describe("PlaylistDetail", () => {
);
const { user } = open();
await user.click(await screen.findByRole("button", { name: "Edit" }));
const box = screen.getByPlaceholderText("Description (optional)");
const box = screen.getByRole("textbox", { name: "Description" });
await user.clear(box);
await user.type(box, "Better tunes");
await user.click(screen.getByRole("button", { name: "Save" }));
@@ -157,7 +157,7 @@ describe("PlaylistDetail", () => {
);
const { user } = open();
await user.click(await screen.findByRole("button", { name: "Edit" }));
const title = screen.getByPlaceholderText("Playlist title");
const title = screen.getByRole("textbox", { name: "Title" });
await user.clear(title);
await user.type(title, "Re-mix!");
const link = screen.getByRole("textbox", { name: "Link" });
@@ -239,7 +239,7 @@ describe("PlaylistDetail", () => {
test("Escape leaves the editor", async () => {
const { user } = open();
await user.click(await screen.findByRole("button", { name: "Edit" }));
await user.type(screen.getByPlaceholderText("Playlist title"), "{Escape}");
await user.type(screen.getByRole("textbox", { name: "Title" }), "{Escape}");
expect(screen.getByRole("heading", { name: "Mix" })).toBeInTheDocument();
});