v4: the playlist and dump edit forms end in one bar of actions, a panel of its own — the playlist editor drew rules between its fields and its actions, and on a phone its Delete, Cancel and Save wrapped into a staircase, then stacked (a0c8b1d), neither of which held together. Both forms are now built as DumpEdit was: surface panels 3px apart (.form-panel, from .dump-edit-panel), the last a bar (.form-panel--bar, from .dump-edit-footer) holding Delete on the left and Cancel and Save on the right, compact (btn-sm), on one line. The new DeleteButton says "Delete" and names what goes to assistive tech ("Delete playlist", "Delete dump"); in a bar too narrow for the three labels in every face and language (a container query, under 18rem), it shows 🗑 instead. While edited, the playlist's header drops its own card for the form's panels. The phone stacking rule is gone. An auto-sized editor also re-fits when its width changes with its text unchanged: opening the form makes the page tall enough for a scrollbar, which narrowed the description after it was measured, so it overflowed; and it hides its overflow below its max-height, where a scrollbar could only come of rounding, and drops the resize grip, which the next keystroke would undo. 669 frontend tests pass, and tsc, eslint, deno fmt, doc:lint and i18n:compile --strict are clean; checked in screenshots in Kanagawa dark (French, 385 and 360px), Smooth light at 1000px and DumpEdit in Smooth at 390px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
khannurien
2026-10-08 22:05:43 +00:00
parent b9281da9c9
commit 5acdf79c8d
9 changed files with 313 additions and 224 deletions

View File

@@ -427,7 +427,7 @@ msgstr "← Back"
#: src/pages/Dump.tsx:326 #: src/pages/Dump.tsx:326
#: src/pages/Dump.tsx:568 #: src/pages/Dump.tsx:568
#: src/pages/DumpEdit.tsx:231 #: src/pages/DumpEdit.tsx:232
msgid "← Back to all dumps" msgid "← Back to all dumps"
msgstr "← Back to all dumps" msgstr "← Back to all dumps"
@@ -589,12 +589,12 @@ msgstr "Can't connect to the live updates server. Upvotes and notifications may
#: src/components/comment/CommentForm.tsx:127 #: src/components/comment/CommentForm.tsx:127
#: src/components/dump/create/DumpCreateModal.tsx:752 #: src/components/dump/create/DumpCreateModal.tsx:752
#: src/components/form/FormActions.tsx:42 #: src/components/form/FormActions.tsx:42
#: src/components/playlist/PlaylistEditForm.tsx:194 #: src/components/playlist/PlaylistEditForm.tsx:199
#: src/components/profile/DescriptionEditor.tsx:97 #: src/components/profile/DescriptionEditor.tsx:97
#: src/components/profile/EmailEditor.tsx:83 #: src/components/profile/EmailEditor.tsx:83
#: src/components/shared/ConfirmModal.tsx:63 #: src/components/shared/ConfirmModal.tsx:63
#: src/pages/Dump.tsx:445 #: src/pages/Dump.tsx:445
#: src/pages/DumpEdit.tsx:570 #: src/pages/DumpEdit.tsx:565
msgid "Cancel" msgid "Cancel"
msgstr "Cancel" msgstr "Cancel"
@@ -607,7 +607,7 @@ msgstr "Cancel removal"
msgid "Cancel reply" msgid "Cancel reply"
msgstr "Cancel reply" msgstr "Cancel reply"
#: src/components/layout/AppHeader.tsx:164 #: src/components/layout/AppHeader.tsx:190
msgid "Cancel search" msgid "Cancel search"
msgstr "Cancel search" msgstr "Cancel search"
@@ -642,13 +642,13 @@ msgid "Change skin tone"
msgstr "Change skin tone" msgstr "Change skin tone"
#: src/components/chat/ChatModal.tsx:716 #: src/components/chat/ChatModal.tsx:716
#: src/components/layout/ChatButton.tsx:27 #: src/components/layout/ChatButton.tsx:30
#: src/components/layout/ChatFab.tsx:54 #: src/components/layout/ChatFab.tsx:54
#: src/components/layout/ChatFab.tsx:55 #: src/components/layout/ChatFab.tsx:55
msgid "Chat" msgid "Chat"
msgstr "Chat" msgstr "Chat"
#: src/components/layout/ChatButton.tsx:26 #: src/components/layout/ChatButton.tsx:29
#: src/components/layout/ChatFab.tsx:53 #: src/components/layout/ChatFab.tsx:53
msgid "Chat ({unreadChatCount} unread)" msgid "Chat ({unreadChatCount} unread)"
msgstr "Chat ({unreadChatCount} unread)" msgstr "Chat ({unreadChatCount} unread)"
@@ -712,7 +712,7 @@ msgid "Could not connect to the server. Check your connection and try again."
msgstr "Could not connect to the server. Check your connection and try again." msgstr "Could not connect to the server. Check your connection and try again."
#: src/components/profile/ProfilePlaylistsTab.tsx:126 #: src/components/profile/ProfilePlaylistsTab.tsx:126
#: src/pages/DumpEdit.tsx:558 #: src/pages/DumpEdit.tsx:559
msgid "Could not delete" msgid "Could not delete"
msgstr "Could not delete" msgstr "Could not delete"
@@ -720,7 +720,7 @@ msgstr "Could not delete"
msgid "Could not rename" msgid "Could not rename"
msgstr "Could not rename" msgstr "Could not rename"
#: src/pages/DumpEdit.tsx:511 #: src/pages/DumpEdit.tsx:512
msgid "Could not save" msgid "Could not save"
msgstr "Could not save" msgstr "Could not save"
@@ -740,7 +740,7 @@ msgstr "Couldn't unsubscribe"
msgid "Couldn't update. Try again." msgid "Couldn't update. Try again."
msgstr "Couldn't update. Try again." msgstr "Couldn't update. Try again."
#: src/components/playlist/PlaylistEditForm.tsx:133 #: src/components/playlist/PlaylistEditForm.tsx:135
msgid "Cover" msgid "Cover"
msgstr "Cover" msgstr "Cover"
@@ -775,7 +775,7 @@ msgstr "Default tab"
#: src/components/chat/ChatMessageItem.tsx:214 #: src/components/chat/ChatMessageItem.tsx:214
#: src/components/comment/CommentNode.tsx:265 #: src/components/comment/CommentNode.tsx:265
#: src/components/comment/CommentNode.tsx:271 #: src/components/comment/CommentNode.tsx:271
#: src/components/playlist/PlaylistEditForm.tsx:190 #: src/components/form/DeleteButton.tsx:36
#: src/components/shared/ConfirmModal.tsx:34 #: src/components/shared/ConfirmModal.tsx:34
msgid "Delete" msgid "Delete"
msgstr "Delete" msgstr "Delete"
@@ -789,19 +789,20 @@ msgstr "Delete category"
msgid "Delete category \"{0}\"? This cannot be undone." msgid "Delete category \"{0}\"? This cannot be undone."
msgstr "Delete category \"{0}\"? This cannot be undone." msgstr "Delete category \"{0}\"? This cannot be undone."
#: src/pages/DumpEdit.tsx:313 #: src/pages/DumpEdit.tsx:314
#: src/pages/DumpEdit.tsx:566 #: src/pages/DumpEdit.tsx:562
msgid "Delete dump" msgid "Delete dump"
msgstr "Delete dump" msgstr "Delete dump"
#. placeholder {0}: res.status #. placeholder {0}: res.status
#: src/pages/DumpEdit.tsx:192 #: src/pages/DumpEdit.tsx:193
msgid "Delete failed ({0})" msgid "Delete failed ({0})"
msgstr "Delete failed ({0})" msgstr "Delete failed ({0})"
#: src/components/playlist/PlaylistCard.tsx:142 #: src/components/playlist/PlaylistCard.tsx:142
#: src/components/playlist/PlaylistEditForm.tsx:190
#: src/components/profile/ProfilePlaylistsTab.tsx:156 #: src/components/profile/ProfilePlaylistsTab.tsx:156
#: src/pages/PlaylistDetail.tsx:136 #: src/pages/PlaylistDetail.tsx:141
msgid "Delete playlist" msgid "Delete playlist"
msgstr "Delete playlist" msgstr "Delete playlist"
@@ -809,7 +810,7 @@ msgstr "Delete playlist"
msgid "Delete this comment?" msgid "Delete this comment?"
msgstr "Delete this comment?" msgstr "Delete this comment?"
#: src/pages/DumpEdit.tsx:312 #: src/pages/DumpEdit.tsx:313
msgid "Delete this dump? This cannot be undone." msgid "Delete this dump? This cannot be undone."
msgstr "Delete this dump? This cannot be undone." msgstr "Delete this dump? This cannot be undone."
@@ -818,7 +819,7 @@ msgid "Delete this message?"
msgstr "Delete this message?" msgstr "Delete this message?"
#: src/components/profile/ProfilePlaylistsTab.tsx:155 #: src/components/profile/ProfilePlaylistsTab.tsx:155
#: src/pages/PlaylistDetail.tsx:135 #: src/pages/PlaylistDetail.tsx:140
msgid "Delete this playlist? This cannot be undone." msgid "Delete this playlist? This cannot be undone."
msgstr "Delete this playlist? This cannot be undone." msgstr "Delete this playlist? This cannot be undone."
@@ -826,7 +827,7 @@ msgstr "Delete this playlist? This cannot be undone."
msgid "deleted message" msgid "deleted message"
msgstr "deleted message" msgstr "deleted message"
#: src/components/playlist/PlaylistEditForm.tsx:173 #: src/components/playlist/PlaylistEditForm.tsx:175
msgid "Description" msgid "Description"
msgstr "Description" msgstr "Description"
@@ -864,11 +865,11 @@ msgstr "Drop a file here"
msgid "Drop a file here, or paste one" msgid "Drop a file here, or paste one"
msgstr "Drop a file here, or paste one" msgstr "Drop a file here, or paste one"
#: src/pages/DumpEdit.tsx:502 #: src/pages/DumpEdit.tsx:503
msgid "Drop a replacement here" msgid "Drop a replacement here"
msgstr "Drop a replacement here" msgstr "Drop a replacement here"
#: src/components/layout/AppHeader.tsx:203 #: src/components/layout/AppHeader.tsx:209
msgid "Dump" msgid "Dump"
msgstr "Dump" msgstr "Dump"
@@ -946,12 +947,12 @@ msgstr "edited {0}"
msgid "Edited {0}" msgid "Edited {0}"
msgstr "Edited {0}" msgstr "Edited {0}"
#: src/pages/DumpEdit.tsx:256 #: src/pages/DumpEdit.tsx:257
msgid "Editing" msgid "Editing"
msgstr "Editing" msgstr "Editing"
#. placeholder {0}: state.dump.title #. placeholder {0}: state.dump.title
#: src/pages/DumpEdit.tsx:79 #: src/pages/DumpEdit.tsx:80
msgid "Editing {0}" msgid "Editing {0}"
msgstr "Editing {0}" msgstr "Editing {0}"
@@ -1005,7 +1006,7 @@ msgstr "Failed to post comment"
msgid "Failed to post reply" msgid "Failed to post reply"
msgstr "Failed to post reply" msgstr "Failed to post reply"
#: src/components/playlist/PlaylistEditForm.tsx:182 #: src/components/playlist/PlaylistEditForm.tsx:184
#: src/components/profile/DescriptionEditor.tsx:99 #: src/components/profile/DescriptionEditor.tsx:99
#: src/components/profile/EmailEditor.tsx:86 #: src/components/profile/EmailEditor.tsx:86
msgid "Failed to save" msgid "Failed to save"
@@ -1113,8 +1114,8 @@ msgstr "image"
msgid "In collections" msgid "In collections"
msgstr "In collections" msgstr "In collections"
#: src/components/editor/TextEditor.tsx:544 #: src/components/editor/TextEditor.tsx:573
#: src/components/editor/TextEditor.tsx:546 #: src/components/editor/TextEditor.tsx:575
msgid "Insert emoji" msgid "Insert emoji"
msgstr "Insert emoji" msgstr "Insert emoji"
@@ -1175,7 +1176,7 @@ msgstr "Link or file"
msgid "Live updates are temporarily disconnected. Trying to reconnect…" msgid "Live updates are temporarily disconnected. Trying to reconnect…"
msgstr "Live updates are temporarily disconnected. Trying to reconnect…" msgstr "Live updates are temporarily disconnected. Trying to reconnect…"
#: src/components/layout/AppHeader.tsx:238 #: src/components/layout/AppHeader.tsx:242
msgid "Live updates unavailable." msgid "Live updates unavailable."
msgstr "Live updates unavailable." msgstr "Live updates unavailable."
@@ -1188,7 +1189,7 @@ msgid "Load older messages"
msgstr "Load older messages" msgstr "Load older messages"
#: src/pages/Dump.tsx:302 #: src/pages/Dump.tsx:302
#: src/pages/DumpEdit.tsx:207 #: src/pages/DumpEdit.tsx:208
msgid "Loading dump…" msgid "Loading dump…"
msgstr "Loading dump…" msgstr "Loading dump…"
@@ -1218,7 +1219,7 @@ msgstr "Loading profile…"
msgid "Loading…" msgid "Loading…"
msgstr "Loading…" msgstr "Loading…"
#: src/components/layout/AppHeader.tsx:211 #: src/components/layout/AppHeader.tsx:216
#: src/pages/UserLogin.tsx:41 #: src/pages/UserLogin.tsx:41
#: src/pages/UserLogin.tsx:67 #: src/pages/UserLogin.tsx:67
#: src/pages/UserLogin.tsx:86 #: src/pages/UserLogin.tsx:86
@@ -1366,14 +1367,14 @@ msgstr "Not following anyone yet."
msgid "Nothing here yet." msgid "Nothing here yet."
msgstr "Nothing here yet." msgstr "Nothing here yet."
#: src/components/layout/NotificationBell.tsx:55 #: src/components/layout/NotificationBell.tsx:57
#: src/pages/Notifications.tsx:376 #: src/pages/Notifications.tsx:376
#: src/pages/Notifications.tsx:494 #: src/pages/Notifications.tsx:494
#: src/pages/Settings.tsx:128 #: src/pages/Settings.tsx:128
msgid "Notifications" msgid "Notifications"
msgstr "Notifications" msgstr "Notifications"
#: src/components/layout/NotificationBell.tsx:54 #: src/components/layout/NotificationBell.tsx:56
msgid "Notifications ({unreadNotificationCount} unread)" msgid "Notifications ({unreadNotificationCount} unread)"
msgstr "Notifications ({unreadNotificationCount} unread)" msgstr "Notifications ({unreadNotificationCount} unread)"
@@ -1385,11 +1386,11 @@ msgstr "Off"
msgid "On" msgid "On"
msgstr "On" msgstr "On"
#: src/components/editor/TextEditor.tsx:359 #: src/components/editor/TextEditor.tsx:385
msgid "Only images can be dropped here." msgid "Only images can be dropped here."
msgstr "Only images can be dropped here." msgstr "Only images can be dropped here."
#: src/pages/DumpEdit.tsx:117 #: src/pages/DumpEdit.tsx:118
msgid "Only its poster or a moderator can edit this dump." msgid "Only its poster or a moderator can edit this dump."
msgstr "Only its poster or a moderator can edit this dump." msgstr "Only its poster or a moderator can edit this dump."
@@ -1463,7 +1464,7 @@ msgid "Play"
msgstr "Play" msgstr "Play"
#: src/components/dump/create/StepIndicator.tsx:21 #: src/components/dump/create/StepIndicator.tsx:21
#: src/components/layout/AppHeader.tsx:184 #: src/components/layout/AppHeader.tsx:178
#: src/components/layout/UserMenu.tsx:81 #: src/components/layout/UserMenu.tsx:81
#: src/components/profile/ProfileFollowTab.tsx:167 #: src/components/profile/ProfileFollowTab.tsx:167
#: src/components/profile/ProfilePlaylistsTab.tsx:118 #: src/components/profile/ProfilePlaylistsTab.tsx:118
@@ -1512,7 +1513,7 @@ msgstr "public"
msgid "Public" msgid "Public"
msgstr "Public" msgstr "Public"
#: src/pages/DumpEdit.tsx:289 #: src/pages/DumpEdit.tsx:290
msgid "Refresh metadata" msgid "Refresh metadata"
msgstr "Refresh metadata" msgstr "Refresh metadata"
@@ -1520,7 +1521,7 @@ msgstr "Refresh metadata"
msgid "Refreshing stream…" msgid "Refreshing stream…"
msgstr "Refreshing stream…" msgstr "Refreshing stream…"
#: src/pages/DumpEdit.tsx:288 #: src/pages/DumpEdit.tsx:289
msgid "Refreshing…" msgid "Refreshing…"
msgstr "Refreshing…" msgstr "Refreshing…"
@@ -1566,7 +1567,7 @@ msgstr "Rename"
msgid "Renaming…" msgid "Renaming…"
msgstr "Renaming…" msgstr "Renaming…"
#: src/pages/DumpEdit.tsx:494 #: src/pages/DumpEdit.tsx:495
msgid "Replace file" msgid "Replace file"
msgstr "Replace file" msgstr "Replace file"
@@ -1592,8 +1593,8 @@ msgstr "Reset failed"
msgid "Reset password" msgid "Reset password"
msgstr "Reset password" msgstr "Reset password"
#: src/pages/DumpEdit.tsx:470 #: src/pages/DumpEdit.tsx:471
#: src/pages/DumpEdit.tsx:525 #: src/pages/DumpEdit.tsx:526
msgid "Reset to default" msgid "Reset to default"
msgstr "Reset to default" msgstr "Reset to default"
@@ -1604,7 +1605,7 @@ msgstr "Reset to the suggested title"
#: src/components/chat/PendingItem.tsx:86 #: src/components/chat/PendingItem.tsx:86
#: src/pages/Dump.tsx:319 #: src/pages/Dump.tsx:319
#: src/pages/DumpEdit.tsx:224 #: src/pages/DumpEdit.tsx:225
msgid "Retry" msgid "Retry"
msgstr "Retry" msgstr "Retry"
@@ -1615,16 +1616,16 @@ msgstr "Role"
#: src/components/category/CategoryManager.tsx:216 #: src/components/category/CategoryManager.tsx:216
#: src/components/chat/ChatEditBox.tsx:85 #: src/components/chat/ChatEditBox.tsx:85
#: src/components/comment/CommentNode.tsx:216 #: src/components/comment/CommentNode.tsx:216
#: src/components/playlist/PlaylistEditForm.tsx:200 #: src/components/playlist/PlaylistEditForm.tsx:206
#: src/components/profile/DescriptionEditor.tsx:89 #: src/components/profile/DescriptionEditor.tsx:89
#: src/components/profile/EmailEditor.tsx:75 #: src/components/profile/EmailEditor.tsx:75
#: src/pages/Dump.tsx:437 #: src/pages/Dump.tsx:437
#: src/pages/DumpEdit.tsx:573 #: src/pages/DumpEdit.tsx:568
msgid "Save" msgid "Save"
msgstr "Save" msgstr "Save"
#: src/components/comment/CommentNode.tsx:217 #: src/components/comment/CommentNode.tsx:217
#: src/components/playlist/PlaylistEditForm.tsx:197 #: src/components/playlist/PlaylistEditForm.tsx:203
#: src/components/profile/ChangePasswordModal.tsx:119 #: src/components/profile/ChangePasswordModal.tsx:119
#: src/components/profile/DescriptionEditor.tsx:86 #: src/components/profile/DescriptionEditor.tsx:86
#: src/components/profile/EmailEditor.tsx:72 #: src/components/profile/EmailEditor.tsx:72
@@ -1633,7 +1634,7 @@ msgstr "Save"
msgid "Saving…" msgid "Saving…"
msgstr "Saving…" msgstr "Saving…"
#: src/components/layout/AppHeader.tsx:164 #: src/components/layout/AppHeader.tsx:190
#: src/components/layout/SearchBar.tsx:83 #: src/components/layout/SearchBar.tsx:83
#: src/pages/Search.tsx:69 #: src/pages/Search.tsx:69
msgid "Search" msgid "Search"
@@ -1676,7 +1677,7 @@ msgstr "Send reset link"
msgid "Sending…" msgid "Sending…"
msgstr "Sending…" msgstr "Sending…"
#: src/components/layout/AppHeader.tsx:200 #: src/components/layout/AppHeader.tsx:206
msgid "Server unreachable" msgid "Server unreachable"
msgstr "Server unreachable" msgstr "Server unreachable"
@@ -1704,14 +1705,14 @@ msgstr "Single use, valid for 7 days."
msgid "slug" msgid "slug"
msgstr "slug" msgstr "slug"
#: src/components/editor/TextEditor.tsx:317 #: src/components/editor/TextEditor.tsx:343
msgid "Some images couldn't be uploaded." msgid "Some images couldn't be uploaded."
msgstr "Some images couldn't be uploaded." msgstr "Some images couldn't be uploaded."
#: src/components/form/FormError.tsx:31 #: src/components/form/FormError.tsx:31
#: src/components/layout/PageError.tsx:29 #: src/components/layout/PageError.tsx:29
#: src/contexts/ws/WSProvider.tsx:729 #: src/contexts/ws/WSProvider.tsx:729
#: src/pages/PlaylistDetail.tsx:119 #: src/pages/PlaylistDetail.tsx:124
msgid "Something went wrong" msgid "Something went wrong"
msgstr "Something went wrong" msgstr "Something went wrong"
@@ -1743,7 +1744,7 @@ msgstr "the chat"
msgid "The dump couldn't be removed. Try again." msgid "The dump couldn't be removed. Try again."
msgstr "The dump couldn't be removed. Try again." msgstr "The dump couldn't be removed. Try again."
#: src/components/editor/TextEditor.tsx:318 #: src/components/editor/TextEditor.tsx:344
msgid "The image couldn't be uploaded." msgid "The image couldn't be uploaded."
msgstr "The image couldn't be uploaded." msgstr "The image couldn't be uploaded."
@@ -1779,14 +1780,14 @@ msgstr "This site doesn't send email, so no digest goes out."
msgid "This unsubscribe link is missing or malformed." msgid "This unsubscribe link is missing or malformed."
msgstr "This unsubscribe link is missing or malformed." msgstr "This unsubscribe link is missing or malformed."
#: src/pages/DumpEdit.tsx:454 #: src/pages/DumpEdit.tsx:455
msgid "Thumbnail" msgid "Thumbnail"
msgstr "Thumbnail" msgstr "Thumbnail"
#: src/components/dump/create/TitleField.tsx:45 #: src/components/dump/create/TitleField.tsx:45
#: src/components/playlist/PlaylistCreateForm.tsx:96 #: src/components/playlist/PlaylistCreateForm.tsx:96
#: src/components/playlist/PlaylistEditForm.tsx:157 #: src/components/playlist/PlaylistEditForm.tsx:159
#: src/pages/DumpEdit.tsx:515 #: src/pages/DumpEdit.tsx:516
msgid "Title" msgid "Title"
msgstr "Title" msgstr "Title"
@@ -1861,7 +1862,7 @@ msgstr "Upload failed"
msgid "Uploading {0}%" msgid "Uploading {0}%"
msgstr "Uploading {0}%" msgstr "Uploading {0}%"
#: src/components/editor/TextEditor.tsx:566 #: src/components/editor/TextEditor.tsx:595
msgid "Uploading image…" msgid "Uploading image…"
msgstr "Uploading image…" msgstr "Uploading image…"
@@ -1883,7 +1884,7 @@ msgstr "Upvoted"
msgid "Upvoted ({0})" msgid "Upvoted ({0})"
msgstr "Upvoted ({0})" msgstr "Upvoted ({0})"
#: src/pages/DumpEdit.tsx:486 #: src/pages/DumpEdit.tsx:487
msgid "URL" msgid "URL"
msgstr "URL" msgstr "URL"
@@ -1933,7 +1934,7 @@ msgid "Volume"
msgstr "Volume" msgstr "Volume"
#: src/components/dump/create/DumpCreateModal.tsx:712 #: src/components/dump/create/DumpCreateModal.tsx:712
#: src/pages/DumpEdit.tsx:543 #: src/pages/DumpEdit.tsx:544
msgid "What makes it worth it?" msgid "What makes it worth it?"
msgstr "What makes it worth it?" msgstr "What makes it worth it?"
@@ -1951,7 +1952,7 @@ msgid "Why & where"
msgstr "Why & where" msgstr "Why & where"
#: src/components/dump/create/DumpCreateModal.tsx:711 #: src/components/dump/create/DumpCreateModal.tsx:711
#: src/pages/DumpEdit.tsx:542 #: src/pages/DumpEdit.tsx:543
msgid "Why?" msgid "Why?"
msgstr "Why?" msgstr "Why?"

View File

@@ -427,7 +427,7 @@ msgstr "← Retour"
#: src/pages/Dump.tsx:326 #: src/pages/Dump.tsx:326
#: src/pages/Dump.tsx:568 #: src/pages/Dump.tsx:568
#: src/pages/DumpEdit.tsx:231 #: src/pages/DumpEdit.tsx:232
msgid "← Back to all dumps" msgid "← Back to all dumps"
msgstr "← Retour à toutes les recos" msgstr "← Retour à toutes les recos"
@@ -589,12 +589,12 @@ msgstr "Impossible de se connecter au serveur de mises à jour en direct. Les vo
#: src/components/comment/CommentForm.tsx:127 #: src/components/comment/CommentForm.tsx:127
#: src/components/dump/create/DumpCreateModal.tsx:752 #: src/components/dump/create/DumpCreateModal.tsx:752
#: src/components/form/FormActions.tsx:42 #: src/components/form/FormActions.tsx:42
#: src/components/playlist/PlaylistEditForm.tsx:194 #: src/components/playlist/PlaylistEditForm.tsx:199
#: src/components/profile/DescriptionEditor.tsx:97 #: src/components/profile/DescriptionEditor.tsx:97
#: src/components/profile/EmailEditor.tsx:83 #: src/components/profile/EmailEditor.tsx:83
#: src/components/shared/ConfirmModal.tsx:63 #: src/components/shared/ConfirmModal.tsx:63
#: src/pages/Dump.tsx:445 #: src/pages/Dump.tsx:445
#: src/pages/DumpEdit.tsx:570 #: src/pages/DumpEdit.tsx:565
msgid "Cancel" msgid "Cancel"
msgstr "Annuler" msgstr "Annuler"
@@ -607,7 +607,7 @@ msgstr "Annuler la suppression"
msgid "Cancel reply" msgid "Cancel reply"
msgstr "Annuler la réponse" msgstr "Annuler la réponse"
#: src/components/layout/AppHeader.tsx:164 #: src/components/layout/AppHeader.tsx:190
msgid "Cancel search" msgid "Cancel search"
msgstr "Annuler la recherche" msgstr "Annuler la recherche"
@@ -642,13 +642,13 @@ msgid "Change skin tone"
msgstr "Changer la couleur de peau" msgstr "Changer la couleur de peau"
#: src/components/chat/ChatModal.tsx:716 #: src/components/chat/ChatModal.tsx:716
#: src/components/layout/ChatButton.tsx:27 #: src/components/layout/ChatButton.tsx:30
#: src/components/layout/ChatFab.tsx:54 #: src/components/layout/ChatFab.tsx:54
#: src/components/layout/ChatFab.tsx:55 #: src/components/layout/ChatFab.tsx:55
msgid "Chat" msgid "Chat"
msgstr "Tribune" msgstr "Tribune"
#: src/components/layout/ChatButton.tsx:26 #: src/components/layout/ChatButton.tsx:29
#: src/components/layout/ChatFab.tsx:53 #: src/components/layout/ChatFab.tsx:53
msgid "Chat ({unreadChatCount} unread)" msgid "Chat ({unreadChatCount} unread)"
msgstr "{unreadChatCount} messages non lus" msgstr "{unreadChatCount} messages non lus"
@@ -712,7 +712,7 @@ msgid "Could not connect to the server. Check your connection and try again."
msgstr "Impossible de joindre le serveur. Vérifiez votre connexion et réessayez." msgstr "Impossible de joindre le serveur. Vérifiez votre connexion et réessayez."
#: src/components/profile/ProfilePlaylistsTab.tsx:126 #: src/components/profile/ProfilePlaylistsTab.tsx:126
#: src/pages/DumpEdit.tsx:558 #: src/pages/DumpEdit.tsx:559
msgid "Could not delete" msgid "Could not delete"
msgstr "Suppression impossible" msgstr "Suppression impossible"
@@ -720,7 +720,7 @@ msgstr "Suppression impossible"
msgid "Could not rename" msgid "Could not rename"
msgstr "Renommage impossible" msgstr "Renommage impossible"
#: src/pages/DumpEdit.tsx:511 #: src/pages/DumpEdit.tsx:512
msgid "Could not save" msgid "Could not save"
msgstr "Sauvegarde impossible" msgstr "Sauvegarde impossible"
@@ -740,7 +740,7 @@ msgstr "Désabonnement impossible"
msgid "Couldn't update. Try again." msgid "Couldn't update. Try again."
msgstr "La mise à jour a échoué. Réessayez." msgstr "La mise à jour a échoué. Réessayez."
#: src/components/playlist/PlaylistEditForm.tsx:133 #: src/components/playlist/PlaylistEditForm.tsx:135
msgid "Cover" msgid "Cover"
msgstr "Couverture" msgstr "Couverture"
@@ -775,7 +775,7 @@ msgstr "Onglet par défaut"
#: src/components/chat/ChatMessageItem.tsx:214 #: src/components/chat/ChatMessageItem.tsx:214
#: src/components/comment/CommentNode.tsx:265 #: src/components/comment/CommentNode.tsx:265
#: src/components/comment/CommentNode.tsx:271 #: src/components/comment/CommentNode.tsx:271
#: src/components/playlist/PlaylistEditForm.tsx:190 #: src/components/form/DeleteButton.tsx:36
#: src/components/shared/ConfirmModal.tsx:34 #: src/components/shared/ConfirmModal.tsx:34
msgid "Delete" msgid "Delete"
msgstr "Supprimer" msgstr "Supprimer"
@@ -789,19 +789,20 @@ msgstr "Supprimer la catégorie"
msgid "Delete category \"{0}\"? This cannot be undone." msgid "Delete category \"{0}\"? This cannot be undone."
msgstr "Supprimer la catégorie \"{0}\" ? Cette action est irréversible." msgstr "Supprimer la catégorie \"{0}\" ? Cette action est irréversible."
#: src/pages/DumpEdit.tsx:313 #: src/pages/DumpEdit.tsx:314
#: src/pages/DumpEdit.tsx:566 #: src/pages/DumpEdit.tsx:562
msgid "Delete dump" msgid "Delete dump"
msgstr "Supprimer la reco" msgstr "Supprimer la reco"
#. placeholder {0}: res.status #. placeholder {0}: res.status
#: src/pages/DumpEdit.tsx:192 #: src/pages/DumpEdit.tsx:193
msgid "Delete failed ({0})" msgid "Delete failed ({0})"
msgstr "Échec de la suppression ({0})" msgstr "Échec de la suppression ({0})"
#: src/components/playlist/PlaylistCard.tsx:142 #: src/components/playlist/PlaylistCard.tsx:142
#: src/components/playlist/PlaylistEditForm.tsx:190
#: src/components/profile/ProfilePlaylistsTab.tsx:156 #: src/components/profile/ProfilePlaylistsTab.tsx:156
#: src/pages/PlaylistDetail.tsx:136 #: src/pages/PlaylistDetail.tsx:141
msgid "Delete playlist" msgid "Delete playlist"
msgstr "Supprimer la collection" msgstr "Supprimer la collection"
@@ -809,7 +810,7 @@ msgstr "Supprimer la collection"
msgid "Delete this comment?" msgid "Delete this comment?"
msgstr "Supprimer ce commentaire ?" msgstr "Supprimer ce commentaire ?"
#: src/pages/DumpEdit.tsx:312 #: src/pages/DumpEdit.tsx:313
msgid "Delete this dump? This cannot be undone." msgid "Delete this dump? This cannot be undone."
msgstr "Supprimer cette reco ? Cette action est irréversible." msgstr "Supprimer cette reco ? Cette action est irréversible."
@@ -818,7 +819,7 @@ msgid "Delete this message?"
msgstr "Supprimer ce message ?" msgstr "Supprimer ce message ?"
#: src/components/profile/ProfilePlaylistsTab.tsx:155 #: src/components/profile/ProfilePlaylistsTab.tsx:155
#: src/pages/PlaylistDetail.tsx:135 #: src/pages/PlaylistDetail.tsx:140
msgid "Delete this playlist? This cannot be undone." msgid "Delete this playlist? This cannot be undone."
msgstr "Supprimer cette collection ? Cette action est irréversible." msgstr "Supprimer cette collection ? Cette action est irréversible."
@@ -826,7 +827,7 @@ msgstr "Supprimer cette collection ? Cette action est irréversible."
msgid "deleted message" msgid "deleted message"
msgstr "message supprimé" msgstr "message supprimé"
#: src/components/playlist/PlaylistEditForm.tsx:173 #: src/components/playlist/PlaylistEditForm.tsx:175
msgid "Description" msgid "Description"
msgstr "Description" msgstr "Description"
@@ -864,11 +865,11 @@ msgstr "Déposez un fichier ici"
msgid "Drop a file here, or paste one" msgid "Drop a file here, or paste one"
msgstr "Déposez un fichier ici, ou collez-en un" msgstr "Déposez un fichier ici, ou collez-en un"
#: src/pages/DumpEdit.tsx:502 #: src/pages/DumpEdit.tsx:503
msgid "Drop a replacement here" msgid "Drop a replacement here"
msgstr "Déposez un fichier de remplacement ici" msgstr "Déposez un fichier de remplacement ici"
#: src/components/layout/AppHeader.tsx:203 #: src/components/layout/AppHeader.tsx:209
msgid "Dump" msgid "Dump"
msgstr "Reco" msgstr "Reco"
@@ -946,12 +947,12 @@ msgstr "modifié {0}"
msgid "Edited {0}" msgid "Edited {0}"
msgstr "Modifié le {0}" msgstr "Modifié le {0}"
#: src/pages/DumpEdit.tsx:256 #: src/pages/DumpEdit.tsx:257
msgid "Editing" msgid "Editing"
msgstr "Modification" msgstr "Modification"
#. placeholder {0}: state.dump.title #. placeholder {0}: state.dump.title
#: src/pages/DumpEdit.tsx:79 #: src/pages/DumpEdit.tsx:80
msgid "Editing {0}" msgid "Editing {0}"
msgstr "Édition de {0}" msgstr "Édition de {0}"
@@ -1005,7 +1006,7 @@ msgstr "Impossible de publier le commentaire"
msgid "Failed to post reply" msgid "Failed to post reply"
msgstr "Impossible de publier la réponse" msgstr "Impossible de publier la réponse"
#: src/components/playlist/PlaylistEditForm.tsx:182 #: src/components/playlist/PlaylistEditForm.tsx:184
#: src/components/profile/DescriptionEditor.tsx:99 #: src/components/profile/DescriptionEditor.tsx:99
#: src/components/profile/EmailEditor.tsx:86 #: src/components/profile/EmailEditor.tsx:86
msgid "Failed to save" msgid "Failed to save"
@@ -1113,8 +1114,8 @@ msgstr "image"
msgid "In collections" msgid "In collections"
msgstr "Dans les collections" msgstr "Dans les collections"
#: src/components/editor/TextEditor.tsx:544 #: src/components/editor/TextEditor.tsx:573
#: src/components/editor/TextEditor.tsx:546 #: src/components/editor/TextEditor.tsx:575
msgid "Insert emoji" msgid "Insert emoji"
msgstr "Insérer un emoji" msgstr "Insérer un emoji"
@@ -1175,7 +1176,7 @@ msgstr "Lien ou fichier"
msgid "Live updates are temporarily disconnected. Trying to reconnect…" msgid "Live updates are temporarily disconnected. Trying to reconnect…"
msgstr "Les mises à jour en direct sont temporairement interrompues. Tentative de reconnexion…" msgstr "Les mises à jour en direct sont temporairement interrompues. Tentative de reconnexion…"
#: src/components/layout/AppHeader.tsx:238 #: src/components/layout/AppHeader.tsx:242
msgid "Live updates unavailable." msgid "Live updates unavailable."
msgstr "Mises à jour en direct indisponibles." msgstr "Mises à jour en direct indisponibles."
@@ -1188,7 +1189,7 @@ msgid "Load older messages"
msgstr "Charger les messages plus anciens" msgstr "Charger les messages plus anciens"
#: src/pages/Dump.tsx:302 #: src/pages/Dump.tsx:302
#: src/pages/DumpEdit.tsx:207 #: src/pages/DumpEdit.tsx:208
msgid "Loading dump…" msgid "Loading dump…"
msgstr "Chargement de la reco…" msgstr "Chargement de la reco…"
@@ -1218,7 +1219,7 @@ msgstr "Chargement du profil…"
msgid "Loading…" msgid "Loading…"
msgstr "Chargement…" msgstr "Chargement…"
#: src/components/layout/AppHeader.tsx:211 #: src/components/layout/AppHeader.tsx:216
#: src/pages/UserLogin.tsx:41 #: src/pages/UserLogin.tsx:41
#: src/pages/UserLogin.tsx:67 #: src/pages/UserLogin.tsx:67
#: src/pages/UserLogin.tsx:86 #: src/pages/UserLogin.tsx:86
@@ -1366,14 +1367,14 @@ msgstr "Aucun abonnement pour le moment."
msgid "Nothing here yet." msgid "Nothing here yet."
msgstr "Rien ici pour l'instant." msgstr "Rien ici pour l'instant."
#: src/components/layout/NotificationBell.tsx:55 #: src/components/layout/NotificationBell.tsx:57
#: src/pages/Notifications.tsx:376 #: src/pages/Notifications.tsx:376
#: src/pages/Notifications.tsx:494 #: src/pages/Notifications.tsx:494
#: src/pages/Settings.tsx:128 #: src/pages/Settings.tsx:128
msgid "Notifications" msgid "Notifications"
msgstr "Notifications" msgstr "Notifications"
#: src/components/layout/NotificationBell.tsx:54 #: src/components/layout/NotificationBell.tsx:56
msgid "Notifications ({unreadNotificationCount} unread)" msgid "Notifications ({unreadNotificationCount} unread)"
msgstr "Notifications ({unreadNotificationCount} non lues)" msgstr "Notifications ({unreadNotificationCount} non lues)"
@@ -1385,11 +1386,11 @@ msgstr "Désactivés"
msgid "On" msgid "On"
msgstr "Activés" msgstr "Activés"
#: src/components/editor/TextEditor.tsx:359 #: src/components/editor/TextEditor.tsx:385
msgid "Only images can be dropped here." msgid "Only images can be dropped here."
msgstr "Seules les images peuvent être déposées ici." msgstr "Seules les images peuvent être déposées ici."
#: src/pages/DumpEdit.tsx:117 #: src/pages/DumpEdit.tsx:118
msgid "Only its poster or a moderator can edit this dump." msgid "Only its poster or a moderator can edit this dump."
msgstr "Seuls son auteur et les modérateurs peuvent modifier ce dump." msgstr "Seuls son auteur et les modérateurs peuvent modifier ce dump."
@@ -1463,7 +1464,7 @@ msgid "Play"
msgstr "Lecture" msgstr "Lecture"
#: src/components/dump/create/StepIndicator.tsx:21 #: src/components/dump/create/StepIndicator.tsx:21
#: src/components/layout/AppHeader.tsx:184 #: src/components/layout/AppHeader.tsx:178
#: src/components/layout/UserMenu.tsx:81 #: src/components/layout/UserMenu.tsx:81
#: src/components/profile/ProfileFollowTab.tsx:167 #: src/components/profile/ProfileFollowTab.tsx:167
#: src/components/profile/ProfilePlaylistsTab.tsx:118 #: src/components/profile/ProfilePlaylistsTab.tsx:118
@@ -1512,7 +1513,7 @@ msgstr "public"
msgid "Public" msgid "Public"
msgstr "Public" msgstr "Public"
#: src/pages/DumpEdit.tsx:289 #: src/pages/DumpEdit.tsx:290
msgid "Refresh metadata" msgid "Refresh metadata"
msgstr "Actualiser les métadonnées" msgstr "Actualiser les métadonnées"
@@ -1520,7 +1521,7 @@ msgstr "Actualiser les métadonnées"
msgid "Refreshing stream…" msgid "Refreshing stream…"
msgstr "Actualisation du flux…" msgstr "Actualisation du flux…"
#: src/pages/DumpEdit.tsx:288 #: src/pages/DumpEdit.tsx:289
msgid "Refreshing…" msgid "Refreshing…"
msgstr "Actualisation…" msgstr "Actualisation…"
@@ -1566,7 +1567,7 @@ msgstr "Renommer"
msgid "Renaming…" msgid "Renaming…"
msgstr "Renommage…" msgstr "Renommage…"
#: src/pages/DumpEdit.tsx:494 #: src/pages/DumpEdit.tsx:495
msgid "Replace file" msgid "Replace file"
msgstr "Remplacer le fichier" msgstr "Remplacer le fichier"
@@ -1592,8 +1593,8 @@ msgstr "Échec de la réinitialisation"
msgid "Reset password" msgid "Reset password"
msgstr "Réinitialiser le mot de passe" msgstr "Réinitialiser le mot de passe"
#: src/pages/DumpEdit.tsx:470 #: src/pages/DumpEdit.tsx:471
#: src/pages/DumpEdit.tsx:525 #: src/pages/DumpEdit.tsx:526
msgid "Reset to default" msgid "Reset to default"
msgstr "Réinitialiser par défaut" msgstr "Réinitialiser par défaut"
@@ -1604,7 +1605,7 @@ msgstr "Rétablir le titre suggéré"
#: src/components/chat/PendingItem.tsx:86 #: src/components/chat/PendingItem.tsx:86
#: src/pages/Dump.tsx:319 #: src/pages/Dump.tsx:319
#: src/pages/DumpEdit.tsx:224 #: src/pages/DumpEdit.tsx:225
msgid "Retry" msgid "Retry"
msgstr "Réessayer" msgstr "Réessayer"
@@ -1615,16 +1616,16 @@ msgstr "Rôle"
#: src/components/category/CategoryManager.tsx:216 #: src/components/category/CategoryManager.tsx:216
#: src/components/chat/ChatEditBox.tsx:85 #: src/components/chat/ChatEditBox.tsx:85
#: src/components/comment/CommentNode.tsx:216 #: src/components/comment/CommentNode.tsx:216
#: src/components/playlist/PlaylistEditForm.tsx:200 #: src/components/playlist/PlaylistEditForm.tsx:206
#: src/components/profile/DescriptionEditor.tsx:89 #: src/components/profile/DescriptionEditor.tsx:89
#: src/components/profile/EmailEditor.tsx:75 #: src/components/profile/EmailEditor.tsx:75
#: src/pages/Dump.tsx:437 #: src/pages/Dump.tsx:437
#: src/pages/DumpEdit.tsx:573 #: src/pages/DumpEdit.tsx:568
msgid "Save" msgid "Save"
msgstr "Enregistrer" msgstr "Enregistrer"
#: src/components/comment/CommentNode.tsx:217 #: src/components/comment/CommentNode.tsx:217
#: src/components/playlist/PlaylistEditForm.tsx:197 #: src/components/playlist/PlaylistEditForm.tsx:203
#: src/components/profile/ChangePasswordModal.tsx:119 #: src/components/profile/ChangePasswordModal.tsx:119
#: src/components/profile/DescriptionEditor.tsx:86 #: src/components/profile/DescriptionEditor.tsx:86
#: src/components/profile/EmailEditor.tsx:72 #: src/components/profile/EmailEditor.tsx:72
@@ -1633,7 +1634,7 @@ msgstr "Enregistrer"
msgid "Saving…" msgid "Saving…"
msgstr "Enregistrement…" msgstr "Enregistrement…"
#: src/components/layout/AppHeader.tsx:164 #: src/components/layout/AppHeader.tsx:190
#: src/components/layout/SearchBar.tsx:83 #: src/components/layout/SearchBar.tsx:83
#: src/pages/Search.tsx:69 #: src/pages/Search.tsx:69
msgid "Search" msgid "Search"
@@ -1676,7 +1677,7 @@ msgstr "Envoyer le lien de réinitialisation"
msgid "Sending…" msgid "Sending…"
msgstr "Envoi…" msgstr "Envoi…"
#: src/components/layout/AppHeader.tsx:200 #: src/components/layout/AppHeader.tsx:206
msgid "Server unreachable" msgid "Server unreachable"
msgstr "Serveur inaccessible" msgstr "Serveur inaccessible"
@@ -1704,14 +1705,14 @@ msgstr "Usage unique, valable 7 jours."
msgid "slug" msgid "slug"
msgstr "identifiant" msgstr "identifiant"
#: src/components/editor/TextEditor.tsx:317 #: src/components/editor/TextEditor.tsx:343
msgid "Some images couldn't be uploaded." msgid "Some images couldn't be uploaded."
msgstr "Certaines images n'ont pas pu être envoyées." msgstr "Certaines images n'ont pas pu être envoyées."
#: src/components/form/FormError.tsx:31 #: src/components/form/FormError.tsx:31
#: src/components/layout/PageError.tsx:29 #: src/components/layout/PageError.tsx:29
#: src/contexts/ws/WSProvider.tsx:729 #: src/contexts/ws/WSProvider.tsx:729
#: src/pages/PlaylistDetail.tsx:119 #: src/pages/PlaylistDetail.tsx:124
msgid "Something went wrong" msgid "Something went wrong"
msgstr "Une erreur est survenue" msgstr "Une erreur est survenue"
@@ -1743,7 +1744,7 @@ msgstr "la tribune"
msgid "The dump couldn't be removed. Try again." msgid "The dump couldn't be removed. Try again."
msgstr "Le dump n'a pas pu être retiré. Réessayez." msgstr "Le dump n'a pas pu être retiré. Réessayez."
#: src/components/editor/TextEditor.tsx:318 #: src/components/editor/TextEditor.tsx:344
msgid "The image couldn't be uploaded." msgid "The image couldn't be uploaded."
msgstr "L'image n'a pas pu être envoyée." msgstr "L'image n'a pas pu être envoyée."
@@ -1779,14 +1780,14 @@ msgstr "Ce site n'envoie pas d'e-mails, aucun résumé ne part donc."
msgid "This unsubscribe link is missing or malformed." msgid "This unsubscribe link is missing or malformed."
msgstr "Ce lien de désabonnement est absent ou mal formé." msgstr "Ce lien de désabonnement est absent ou mal formé."
#: src/pages/DumpEdit.tsx:454 #: src/pages/DumpEdit.tsx:455
msgid "Thumbnail" msgid "Thumbnail"
msgstr "Miniature" msgstr "Miniature"
#: src/components/dump/create/TitleField.tsx:45 #: src/components/dump/create/TitleField.tsx:45
#: src/components/playlist/PlaylistCreateForm.tsx:96 #: src/components/playlist/PlaylistCreateForm.tsx:96
#: src/components/playlist/PlaylistEditForm.tsx:157 #: src/components/playlist/PlaylistEditForm.tsx:159
#: src/pages/DumpEdit.tsx:515 #: src/pages/DumpEdit.tsx:516
msgid "Title" msgid "Title"
msgstr "Titre" msgstr "Titre"
@@ -1861,7 +1862,7 @@ msgstr "Envoi échoué"
msgid "Uploading {0}%" msgid "Uploading {0}%"
msgstr "Envoi {0} %" msgstr "Envoi {0} %"
#: src/components/editor/TextEditor.tsx:566 #: src/components/editor/TextEditor.tsx:595
msgid "Uploading image…" msgid "Uploading image…"
msgstr "Envoi de l'image…" msgstr "Envoi de l'image…"
@@ -1883,7 +1884,7 @@ msgstr "Voté"
msgid "Upvoted ({0})" msgid "Upvoted ({0})"
msgstr "Voté ({0})" msgstr "Voté ({0})"
#: src/pages/DumpEdit.tsx:486 #: src/pages/DumpEdit.tsx:487
msgid "URL" msgid "URL"
msgstr "URL" msgstr "URL"
@@ -1933,7 +1934,7 @@ msgid "Volume"
msgstr "Volume" msgstr "Volume"
#: src/components/dump/create/DumpCreateModal.tsx:712 #: src/components/dump/create/DumpCreateModal.tsx:712
#: src/pages/DumpEdit.tsx:543 #: src/pages/DumpEdit.tsx:544
msgid "What makes it worth it?" msgid "What makes it worth it?"
msgstr "Pourquoi on en voudrait ?" msgstr "Pourquoi on en voudrait ?"
@@ -1951,7 +1952,7 @@ msgid "Why & where"
msgstr "Pourquoi & où" msgstr "Pourquoi & où"
#: src/components/dump/create/DumpCreateModal.tsx:711 #: src/components/dump/create/DumpCreateModal.tsx:711
#: src/pages/DumpEdit.tsx:542 #: src/pages/DumpEdit.tsx:543
msgid "Why?" msgid "Why?"
msgstr "Pourquoi ?" msgstr "Pourquoi ?"

View File

@@ -164,8 +164,7 @@
.dump-post-header, .dump-post-header,
.dump-rich-content, .dump-rich-content,
.form-page-header, .form-page-header,
.dump-edit-panel, .form-panel,
.dump-edit-footer,
.playlist-detail-header, .playlist-detail-header,
.comment-node-inner { .comment-node-inner {
box-shadow: var(--panel-ring), var(--card-shadow); box-shadow: var(--panel-ring), var(--card-shadow);
@@ -3374,7 +3373,7 @@ body.has-player .chat-fab {
} }
.form-page--two-col .form-page-header, .form-page--two-col .form-page-header,
.dump-edit-form, .dump-edit-form,
.dump-edit-footer { .dump-edit-form > .form-panel--bar {
grid-column: 1 / -1; grid-column: 1 / -1;
} }
/* "Why?" soaks up whatever height the source column adds, so a tall /* "Why?" soaks up whatever height the source column adds, so a tall
@@ -3422,7 +3421,10 @@ body.has-player .chat-fab {
word-break: break-word; word-break: break-word;
} }
.dump-edit-panel { /* A form split into panels, a few pixels apart (as on the page's grid), in
place of rules between its parts: the fields, then a bar of actions, which
ends the card. */
.form-panel {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;
@@ -3435,18 +3437,35 @@ body.has-player .chat-fab {
overflow: hidden; overflow: hidden;
} }
.dump-edit-footer { .form-panel--bar {
padding: 0.875rem 1.25rem; padding: 0.875rem 1.25rem;
background: var(--color-surface);
border-radius: 0 0 var(--radius-xl) var(--radius-xl); border-radius: 0 0 var(--radius-xl) var(--radius-xl);
container: form-bar / inline-size;
} }
.dump-edit-footer .form-actions { .form-panel--bar .form-actions {
flex-wrap: nowrap;
padding-top: 0; padding-top: 0;
border-top: 0; border-top: 0;
margin-top: 0; margin-top: 0;
} }
/* Too narrow for Delete, Cancel and Save side by side in every face and
language, the bar shows Delete as its icon. */
.form-delete-icon {
display: none;
}
@container form-bar (max-width: 18rem) {
.form-delete-icon {
display: inline;
}
.form-delete-label {
display: none;
}
}
.dump-edit-refresh { .dump-edit-refresh {
margin-top: 0.75rem; margin-top: 0.75rem;
} }
@@ -3481,25 +3500,6 @@ body.has-player .chat-fab {
margin-left: auto; margin-left: auto;
} }
/* A destructive action, Cancel and Save don't fit a phone side by side in
every face, and wrapped they would make a staircase. On a phone they stack:
Cancel and Save across the top, Save taking the room left, the destructive
action the full width under them. */
@media (max-width: 512px) {
.form-actions:has(> .btn-danger) {
flex-direction: column-reverse;
align-items: stretch;
}
.form-actions:has(> .btn-danger) > .form-actions-right {
margin-left: 0;
}
.form-actions:has(> .btn-danger) > .form-actions-right > :last-child {
flex: 1;
}
}
/* Subtle text action. Resets the global <button> chrome so it looks the same /* Subtle text action. Resets the global <button> chrome so it looks the same
whether rendered as a <button> (modal/page forms) or a <Link> (DumpEdit). */ whether rendered as a <button> (modal/page forms) or a <Link> (DumpEdit). */
.form-cancel { .form-cancel {
@@ -5327,6 +5327,21 @@ body.has-player .chat-fab {
padding: 1.25rem; padding: 1.25rem;
} }
/* While edited, the header gives way to the form's own panels. */
.playlist-detail-header--editing {
padding: 0;
background: none;
box-shadow: none;
}
.playlist-edit-form {
gap: 3px;
}
.playlist-edit-form > .form-panel:first-child {
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
}
.playlist-detail-header-top { .playlist-detail-header-top {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;

View File

@@ -99,6 +99,20 @@ interface PendingInsert {
focus: boolean; focus: boolean;
} }
/**
* Sizes an `autoResize` textarea to its text. A border-box height counts the
* borders, which `scrollHeight` leaves out. Below its `max-height` the box
* holds all its text, so a scrollbar there could only come of sub-pixel
* rounding, and would narrow the text into more lines; overflow is hidden
* then, and left to the stylesheet once the box is capped.
*/
function fitHeight(el: HTMLTextAreaElement): void {
el.style.height = "auto";
const height = el.scrollHeight + el.offsetHeight - el.clientHeight;
el.style.height = `${height}px`;
el.style.overflowY = el.offsetHeight < height ? "" : "hidden";
}
/** /**
* Renders a textarea that completes `@mentions` and `:emoji`, opens an emoji * Renders a textarea that completes `@mentions` and `:emoji`, opens an emoji
* picker from its button, and counts characters near `maxLength`. A pasted or * picker from its button, and counts characters near `maxLength`. A pasted or
@@ -274,17 +288,25 @@ export const TextEditor: React.ForwardRefExoticComponent<
// Before paint, so that the first frame, and any layout effect measuring // Before paint, so that the first frame, and any layout effect measuring
// it (such as a height transition), sees the final size. // it (such as a height transition), sees the final size.
useLayoutEffect(() => { useLayoutEffect(() => {
if (!autoResize) return; if (autoResize && textareaRef.current) fitHeight(textareaRef.current);
const el = textareaRef.current;
if (!el) return;
el.style.height = "auto";
// A border-box height counts the borders, which scrollHeight leaves
// out; without them the text overflows by a few pixels and scrolls.
el.style.height = `${
el.scrollHeight + el.offsetHeight - el.clientHeight
}px`;
}, [value, autoResize]); }, [value, autoResize]);
// The text rewraps when the width changes with the value unchanged: when
// the page's scrollbar shows as the editor makes it taller, or the window
// narrows.
useEffect(() => {
const el = textareaRef.current;
if (!autoResize || !el) return;
let width = el.clientWidth;
const observer = new ResizeObserver(() => {
if (el.clientWidth === width) return;
width = el.clientWidth;
fitHeight(el);
});
observer.observe(el);
return () => observer.disconnect();
}, [autoResize]);
// The textarea is read-only while images upload, so the spot they go to // The textarea is read-only while images upload, so the spot they go to
// can't move. They go in once it is writable again. // can't move. They go in once it is writable again.
useLayoutEffect(() => { useLayoutEffect(() => {
@@ -534,6 +556,9 @@ export const TextEditor: React.ForwardRefExoticComponent<
rows={rows} rows={rows}
id={id} id={id}
className={className} className={className}
// Its height follows the text, so a grip to drag it would only
// be undone at the next keystroke.
style={autoResize ? { resize: "none" } : undefined}
/> />
<button <button
ref={emojiButtonRef} ref={emojiButtonRef}

View File

@@ -0,0 +1,40 @@
/**
* Renders the destructive action of an edit form's bar, in `DumpEdit` and
* `PlaylistEditForm`.
*
* @module
*/
import { Trans } from "@lingui/react/macro";
/** Passed to {@link DeleteButton} by an edit form. */
export interface DeleteButtonProps {
/** Its accessible name, which names what goes, such as "Delete dump". */
label: string;
/** Opens the page's confirmation, which deletes. */
onClick: () => void;
}
/**
* Renders a compact "Delete" button, at the start of the bar's
* `.form-actions`. A bar too narrow for it beside Cancel and Save shows its
* icon instead (`.form-delete-icon`).
*/
export function DeleteButton(
{ label, onClick }: DeleteButtonProps,
): React.JSX.Element {
return (
<button
type="button"
className="btn-danger btn-sm"
aria-label={label}
title={label}
onClick={onClick}
>
<span className="form-delete-icon" aria-hidden>🗑</span>
<span className="form-delete-label">
<Trans>Delete</Trans>
</span>
</button>
);
}

View File

@@ -26,6 +26,7 @@ import { ImagePicker } from "../shared/ImagePicker.tsx";
import { TextField } from "../form/TextField.tsx"; import { TextField } from "../form/TextField.tsx";
import { RichTextField } from "../form/RichTextField.tsx"; import { RichTextField } from "../form/RichTextField.tsx";
import { VisibilityToggle } from "../form/SegmentedField.tsx"; import { VisibilityToggle } from "../form/SegmentedField.tsx";
import { DeleteButton } from "../form/DeleteButton.tsx";
import { FormError } from "../form/FormError.tsx"; import { FormError } from "../form/FormError.tsx";
import { SubmitButton } from "../form/SubmitButton.tsx"; import { SubmitButton } from "../form/SubmitButton.tsx";
import { SlugField } from "../form/SlugField.tsx"; import { SlugField } from "../form/SlugField.tsx";
@@ -121,13 +122,14 @@ export function PlaylistEditForm(
return ( return (
<FormProvider {...form}> <FormProvider {...form}>
<form <form
className="form animate-in" className="form playlist-edit-form animate-in"
onSubmit={onSubmit} onSubmit={onSubmit}
onKeyDown={(e) => { onKeyDown={(e) => {
// The editor's popups take their own Escape and prevent it. // The editor's popups take their own Escape and prevent it.
if (e.key === "Escape" && !e.defaultPrevented) onCancel(); if (e.key === "Escape" && !e.defaultPrevented) onCancel();
}} }}
> >
<div className="form-panel">
<div className="form-field"> <div className="form-field">
<span className="form-label"> <span className="form-label">
<Trans>Cover</Trans> <Trans>Cover</Trans>
@@ -180,20 +182,24 @@ export function PlaylistEditForm(
<VisibilityToggle<EditValues> name="isPublic" /> <VisibilityToggle<EditValues> name="isPublic" />
<FormError title={t`Failed to save`} /> <FormError title={t`Failed to save`} />
</div>
<div className="form-panel form-panel--bar">
<div className="form-actions"> <div className="form-actions">
<DeleteButton
label={t`Delete playlist`}
onClick={onRequestDelete}
/>
<div className="form-actions-right">
<button <button
type="button" type="button"
className="btn-danger" className="form-cancel"
onClick={onRequestDelete} onClick={onCancel}
> >
<Trans>Delete</Trans>
</button>
<div className="form-actions-right">
<button type="button" className="form-cancel" onClick={onCancel}>
<Trans>Cancel</Trans> <Trans>Cancel</Trans>
</button> </button>
<SubmitButton <SubmitButton
className="btn-primary btn-sm"
pendingLabel={<Trans>Saving…</Trans>} pendingLabel={<Trans>Saving…</Trans>}
disabled={!title.trim()} disabled={!title.trim()}
> >
@@ -201,6 +207,7 @@ export function PlaylistEditForm(
</SubmitButton> </SubmitButton>
</div> </div>
</div> </div>
</div>
</form> </form>
</FormProvider> </FormProvider>
); );

View File

@@ -47,6 +47,7 @@ import { SubmitButton } from "../components/form/SubmitButton.tsx";
import { TextField } from "../components/form/TextField.tsx"; import { TextField } from "../components/form/TextField.tsx";
import { useApiForm } from "../hooks/form/useApiForm.ts"; import { useApiForm } from "../hooks/form/useApiForm.ts";
import { VisibilityToggle } from "../components/form/SegmentedField.tsx"; import { VisibilityToggle } from "../components/form/SegmentedField.tsx";
import { DeleteButton } from "../components/form/DeleteButton.tsx";
import { SlugField } from "../components/form/SlugField.tsx"; import { SlugField } from "../components/form/SlugField.tsx";
import { useCanonicalPath } from "../hooks/shared/useCanonicalPath.ts"; import { useCanonicalPath } from "../hooks/shared/useCanonicalPath.ts";
import { userPath } from "../../shared/paths.ts"; import { userPath } from "../../shared/paths.ts";
@@ -446,7 +447,7 @@ function DumpEditForm({
/* The source panel holds what was dumped, the details panel how it /* The source panel holds what was dumped, the details panel how it
is presented and who sees it. */ is presented and who sees it. */
} }
<div className="dump-edit-panel dump-edit-panel--source"> <div className="form-panel dump-edit-panel--source">
{preview} {preview}
<div className="form-field"> <div className="form-field">
@@ -507,7 +508,7 @@ function DumpEditForm({
)} )}
</div> </div>
<div className="dump-edit-panel dump-edit-panel--details"> <div className="form-panel dump-edit-panel--details">
<FormError title={t`Could not save`} /> <FormError title={t`Could not save`} />
<TextField<EditValues> <TextField<EditValues>
@@ -553,23 +554,17 @@ function DumpEditForm({
<VisibilityToggle<EditValues> name="isPublic" /> <VisibilityToggle<EditValues> name="isPublic" />
</div> </div>
<div className="dump-edit-footer"> <div className="form-panel form-panel--bar">
{deleteError && ( {deleteError && (
<ErrorCard title={t`Could not delete`} message={deleteError} /> <ErrorCard title={t`Could not delete`} message={deleteError} />
)} )}
<div className="form-actions"> <div className="form-actions">
<button <DeleteButton label={t`Delete dump`} onClick={onRequestDelete} />
type="button"
onClick={onRequestDelete}
className="btn-danger"
>
<Trans>Delete dump</Trans>
</button>
<div className="form-actions-right"> <div className="form-actions-right">
<Link to={dumpUrl(dump)} className="form-cancel"> <Link to={dumpUrl(dump)} className="form-cancel">
<Trans>Cancel</Trans> <Trans>Cancel</Trans>
</Link> </Link>
<SubmitButton> <SubmitButton className="btn-primary btn-sm">
<Trans>Save</Trans> <Trans>Save</Trans>
</SubmitButton> </SubmitButton>
</div> </div>

View File

@@ -90,7 +90,12 @@ export function PlaylistDetail(): React.JSX.Element {
return ( return (
<PageShell> <PageShell>
<div className="playlist-detail-header" ref={headerRef}> <div
className={`playlist-detail-header${
editOpen ? " playlist-detail-header--editing" : ""
}`}
ref={headerRef}
>
{editOpen {editOpen
? ( ? (
<PlaylistEditForm <PlaylistEditForm

View File

@@ -366,7 +366,7 @@ describe("PlaylistDetail", () => {
); );
const { user } = open(); const { user } = open();
await user.click(await screen.findByRole("button", { name: "Edit" })); await user.click(await screen.findByRole("button", { name: "Edit" }));
await user.click(screen.getByRole("button", { name: "Delete" })); await user.click(screen.getByRole("button", { name: "Delete playlist" }));
await user.click( await user.click(
within(screen.getByRole("alertdialog")).getByRole("button", { within(screen.getByRole("alertdialog")).getByRole("button", {
name: "Delete playlist", name: "Delete playlist",
@@ -379,7 +379,7 @@ describe("PlaylistDetail", () => {
route("DELETE /api/playlists/:id", () => fail("Forbidden", 403)); route("DELETE /api/playlists/:id", () => fail("Forbidden", 403));
const { user } = open(); const { user } = open();
await user.click(await screen.findByRole("button", { name: "Edit" })); await user.click(await screen.findByRole("button", { name: "Edit" }));
await user.click(screen.getByRole("button", { name: "Delete" })); await user.click(screen.getByRole("button", { name: "Delete playlist" }));
await user.click( await user.click(
within(screen.getByRole("alertdialog")).getByRole("button", { within(screen.getByRole("alertdialog")).getByRole("button", {
name: "Delete playlist", name: "Delete playlist",