v3: lots of small UI tweaks and fixes
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 46s

This commit is contained in:
khannurien
2026-04-11 08:35:16 +00:00
parent 362967472c
commit b822f861ed
22 changed files with 673 additions and 500 deletions

View File

@@ -1288,6 +1288,36 @@ body.has-player .fab-new {
gap: 1.5rem;
}
.profile-info {
position: relative;
min-width: 0;
}
.profile-info-scroll {
overflow-x: auto;
scrollbar-width: none;
padding-bottom: 0.25rem;
padding-right: 0.25rem;
}
.profile-info-scroll::-webkit-scrollbar {
display: none;
}
/* Fade hint only at narrow viewports where the info column can actually overflow */
@media (max-width: 600px) {
.profile-info::after {
content: "";
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 2rem;
background: linear-gradient(to right, transparent, var(--color-bg));
pointer-events: none;
}
}
.profile-tabs-scroller {
margin-top: 0.5rem;
padding-bottom: 0.5rem;
@@ -1854,6 +1884,33 @@ body.has-player .fab-new {
.app-header-center {
display: flex;
}
.app-header-center .search-bar-btn {
display: none;
}
}
/* Below-header search row — small viewports only */
.header-search-below {
display: flex;
align-items: center;
padding: 0.5rem 1rem;
background: var(--color-surface);
border-bottom: 2px solid var(--color-border);
}
.header-search-below .search-bar {
flex: 1;
}
.header-search-below .search-bar-input {
max-width: 100%;
}
@media (min-width: 860px) {
.header-search-below {
display: none;
}
}
/* When the search bar is expanded, immediately clear the rest of the center —
@@ -1917,12 +1974,32 @@ body.has-player .fab-new {
.app-header-nav button {
padding: 0.35rem 0.85rem;
font-size: 0.95rem;
font-family: inherit;
line-height: inherit;
}
.app-header-nav .btn-primary {
padding: 0.35rem 1rem;
}
/* Ghost search button — always visible except when the center search bar is shown */
.nav-search-btn {
display: flex;
align-items: center;
padding: 0.35rem;
background: transparent;
border: none;
color: inherit;
text-decoration: none;
font-size: 1rem;
cursor: pointer;
border-radius: 6px;
}
.nav-search-btn:hover {
background: var(--color-surface);
}
/* Text links — visible only at wide viewports */
.nav-links {
display: none;
@@ -2204,6 +2281,7 @@ body.has-player .fab-new {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.25em;
white-space: nowrap;
cursor: pointer;
border-radius: 8px;
@@ -2360,7 +2438,7 @@ body.has-player .fab-new {
/* Right-edge gradient: absolutely positioned over the scroll container,
always at the viewport-right edge regardless of scroll position */
.feed-sort-scroller::after {
content: '';
content: "";
position: absolute;
top: 0;
right: 0;
@@ -2387,6 +2465,7 @@ body.has-player .fab-new {
overflow-x: auto;
scrollbar-width: none;
padding-bottom: 0.3rem;
padding-right: 0.25rem;
min-width: 0;
}
@@ -2525,7 +2604,6 @@ body.has-player .fab-new {
order: 0;
}
/* Left column: preview + vote stacked with fixed gap, independent of body height */
.dump-card-left {
display: flex;
@@ -3052,7 +3130,6 @@ body.has-player .fab-new {
opacity: 1;
}
.playlist-detail-content {
flex: 1;
min-width: 0;
@@ -3907,6 +3984,7 @@ body.has-player .fab-new {
border: none;
cursor: pointer;
font-size: 0.95rem;
font-weight: 600;
padding: 0.35rem 0.85rem;
border-radius: 8px;
transition: background 0.15s;
@@ -4398,12 +4476,9 @@ body.has-player .fab-new {
}
.search-tabs {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 1rem 1.25rem 0;
width: 100%;
max-width: 860px;
padding: 1rem 1.25rem 0;
box-sizing: border-box;
}

View File

@@ -62,6 +62,9 @@ const ResetPassword = lazy(() =>
default: m.ResetPassword,
}))
);
const NotFound = lazy(() =>
import("./pages/NotFound.tsx").then((m) => ({ default: m.NotFound }))
);
function AppRoutes() {
return (
@@ -111,6 +114,7 @@ function AppRoutes() {
</RestrictedLoggedIn>
}
/>
<Route path="*" element={<NotFound />} />
</Routes>
</Suspense>
);

View File

@@ -1,11 +1,12 @@
import { lazy, type ReactNode, Suspense, useState } from "react";
import { Link, useNavigate } from "react-router";
import { Link, useLocation, useNavigate } from "react-router";
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { useAuth } from "../hooks/useAuth.ts";
import { useWS } from "../hooks/useWS.ts";
import { NotificationBell } from "./NotificationBell.tsx";
import { UserMenu } from "./UserMenu.tsx";
import { SearchBar } from "./SearchBar.tsx";
const DumpCreateModal = lazy(() =>
import("./DumpCreateModal.tsx").then((m) => ({ default: m.DumpCreateModal }))
@@ -21,21 +22,51 @@ export function AppHeader(
const { user } = useAuth();
const { wsStatus, wsErrorMessage } = useWS();
const navigate = useNavigate();
const location = useLocation();
const isSearchPage = location.pathname === "/search";
const [searchExpanded, setSearchExpanded] = useState(isSearchPage);
const [createModalOpen, setCreateModalOpen] = useState(!!initialDumpUrl);
function handleSearchToggle() {
if (searchExpanded) {
if (isSearchPage) {
navigate(-1);
} else {
setSearchExpanded(false);
}
} else {
setSearchExpanded(true);
}
}
return (
<>
<header
className={`app-header${centerSlot ? " app-header--has-center" : ""}`}
>
<header className="app-header app-header--has-center">
<Link to="/?tab=hot" className="app-header-brand">
🚚<span className="app-header-brand-name">{" "}{document.querySelector<HTMLMetaElement>('meta[name="site-name"]')
?.content ?? "gerbeur"}</span>
🚚<span className="app-header-brand-name">
{" "}
{document.querySelector<HTMLMetaElement>('meta[name="site-name"]')
?.content ?? "gerbeur"}
</span>
</Link>
{centerSlot && <div className="app-header-center">{centerSlot}</div>}
<div className="app-header-center">
{centerSlot}
<SearchBar
expanded={searchExpanded}
onExpandedChange={setSearchExpanded}
/>
</div>
<nav className="app-header-nav">
<button
type="button"
className="nav-search-btn"
aria-label={searchExpanded ? t`Cancel search` : t`Search`}
onClick={handleSearchToggle}
>
{searchExpanded ? "✕" : "🔍"}
</button>
{user
? (
<>
@@ -68,7 +99,9 @@ export function AppHeader(
disabled={disableNew}
title={disableNew ? t`Server unreachable` : undefined}
>
+<span className="btn-new-label"><Trans> Dump</Trans></span>
+<span className="btn-new-label">
<Trans>Dump</Trans>
</span>
</button>
</>
)
@@ -82,6 +115,15 @@ export function AppHeader(
</nav>
</header>
{searchExpanded && (
<div className="header-search-below">
<SearchBar
expanded={searchExpanded}
onExpandedChange={setSearchExpanded}
/>
</div>
)}
{wsStatus === "disconnected" && wsErrorMessage && (
<div className="app-header-status" role="alert">
<strong>

View File

@@ -106,7 +106,6 @@ export function DumpCard(
)}
</div>
</div>
</div>
</li>
);

View File

@@ -2,6 +2,7 @@ import { useLocation, useNavigate } from "react-router";
import { Trans } from "@lingui/react/macro";
import { useAuth } from "../hooks/useAuth.ts";
import { type FeedTab, VALID_TABS } from "../config/feedTabs.ts";
import { TabBar } from "./TabBar.tsx";
export function FeedTabBar() {
const location = useLocation();
@@ -11,44 +12,20 @@ export function FeedTabBar() {
const rawTab = new URLSearchParams(location.search).get("tab") ?? "hot";
const tab: FeedTab = VALID_TABS.has(rawTab) ? (rawTab as FeedTab) : "hot";
function setTab(t: FeedTab) {
navigate(`/?tab=${t}`, { replace: true });
}
const tabs = [
{ key: "hot" as FeedTab, label: <Trans>Hot</Trans> },
{ key: "new" as FeedTab, label: <Trans>New</Trans> },
{ key: "journal" as FeedTab, label: <Trans>Journal</Trans> },
...(user
? [{ key: "followed" as FeedTab, label: <Trans>Followed</Trans> }]
: []),
];
return (
<div className="feed-sort-scroller">
<div className="feed-sort">
<button
type="button"
className={`feed-sort-btn${tab === "hot" ? " active" : ""}`}
onClick={() => setTab("hot")}
>
<Trans>Hot</Trans>
</button>
<button
type="button"
className={`feed-sort-btn${tab === "new" ? " active" : ""}`}
onClick={() => setTab("new")}
>
<Trans>New</Trans>
</button>
<button
type="button"
className={`feed-sort-btn${tab === "journal" ? " active" : ""}`}
onClick={() => setTab("journal")}
>
<Trans>Journal</Trans>
</button>
{user && (
<button
type="button"
className={`feed-sort-btn${tab === "followed" ? " active" : ""}`}
onClick={() => setTab("followed")}
>
<Trans>Followed</Trans>
</button>
)}
</div>
</div>
<TabBar
tabs={tabs}
activeTab={tab}
onChange={(t) => navigate(`/?tab=${t}`, { replace: true })}
/>
);
}

View File

@@ -1,5 +1,6 @@
import { useContext } from "react";
import { Link, useNavigate } from "react-router";
import { Plural, Trans } from "@lingui/react/macro";
import type { Dump } from "../model.ts";
import { API_URL } from "../config/api.ts";
import { relativeTime } from "../utils/relativeTime.ts";
@@ -73,12 +74,18 @@ export function JournalCard(
</time>
</Tooltip>
{dump.commentCount > 0 && (
<span>
{dump.commentCount} {dump.commentCount === 1 ? "comment" : "comments"}
<span className="dump-card-comment-count">
<Plural
value={dump.commentCount}
one="# comment"
other="# comments"
/>
</span>
)}
{dump.isPrivate && isOwner && (
<span className="dump-card-private-badge">private</span>
<span className="dump-card-private-badge">
<Trans>private</Trans>
</span>
)}
</div>
);

View File

@@ -27,7 +27,13 @@ export function NewPlaylistForm(
className={toggleClassName}
onClick={() => setOpen(true)}
>
{toggleLabel ?? <>+<span className="btn-new-label"><Trans> New playlist</Trans></span></>}
{toggleLabel ?? (
<>
+<span className="btn-new-label">
<Trans>New playlist</Trans>
</span>
</>
)}
</button>
{open && (

View File

@@ -1,6 +1,5 @@
import { type ReactNode } from "react";
import { AppHeader } from "./AppHeader.tsx";
import { SearchBar } from "./SearchBar.tsx";
interface PageShellProps {
children: ReactNode;
@@ -14,7 +13,7 @@ export function PageShell(
) {
return (
<div className="page-shell">
<AppHeader centerSlot={centerSlot ?? <SearchBar />} />
<AppHeader centerSlot={centerSlot} />
<main
className={`page-content${centered ? " page-content--centered" : ""}`}
>

View File

@@ -4,22 +4,35 @@ import { t } from "@lingui/core/macro";
interface SearchBarProps {
collapsible?: boolean;
expanded?: boolean;
onExpandedChange?: (v: boolean) => void;
}
export function SearchBar({ collapsible = false }: SearchBarProps) {
const [value, setValue] = useState(
() => new URLSearchParams(location.search).get("q") ?? "",
);
const [expanded, setExpanded] = useState(!collapsible);
export function SearchBar(
{ collapsible = false, expanded: expandedProp, onExpandedChange }:
SearchBarProps,
) {
const isControlled = expandedProp !== undefined;
const [expandedState, setExpandedState] = useState(!collapsible);
const expanded = isControlled ? expandedProp! : expandedState;
const inputRef = useRef<HTMLInputElement>(null);
const navigate = useNavigate();
const [value, setValue] = useState(
() => new URLSearchParams(location.search).get("q") ?? "",
);
function setExpanded(v: boolean) {
if (!isControlled) setExpandedState(v);
onExpandedChange?.(v);
}
useEffect(() => {
if (collapsible && expanded) inputRef.current?.focus();
}, [expanded, collapsible]);
if ((collapsible || isControlled) && expanded) inputRef.current?.focus();
}, [expanded, collapsible, isControlled]);
function handleIconClick() {
if (!collapsible) return;
if (!collapsible && !isControlled) return;
if (expanded) {
setExpanded(false);
setValue("");
@@ -33,14 +46,14 @@ export function SearchBar({ collapsible = false }: SearchBarProps) {
const q = value.trim();
if (!q) return;
navigate(`/search?q=${encodeURIComponent(q)}&tab=dumps`);
if (collapsible) {
if (collapsible || isControlled) {
setExpanded(false);
setValue("");
}
}
function handleKeyDown(e: React.KeyboardEvent) {
if (e.key === "Escape" && collapsible) {
if (e.key === "Escape" && (collapsible || isControlled)) {
setExpanded(false);
setValue("");
}
@@ -48,9 +61,9 @@ export function SearchBar({ collapsible = false }: SearchBarProps) {
return (
<form
className={`search-bar${collapsible ? " search-bar--collapsible" : ""}${
expanded ? " search-bar--expanded" : ""
}`}
className={`search-bar${
collapsible || isControlled ? " search-bar--collapsible" : ""
}${expanded ? " search-bar--expanded" : ""}`}
onSubmit={handleSubmit}
role="search"
>
@@ -66,10 +79,12 @@ export function SearchBar({ collapsible = false }: SearchBarProps) {
tabIndex={expanded ? 0 : -1}
/>
<button
type={expanded && !collapsible ? "submit" : "button"}
type={expanded ? "submit" : "button"}
className="search-bar-btn"
aria-label={expanded ? t`Submit search` : t`Open search`}
onClick={collapsible ? handleIconClick : undefined}
onClick={!expanded && (collapsible || isControlled)
? handleIconClick
: undefined}
>
🔍
</button>

35
src/components/TabBar.tsx Normal file
View File

@@ -0,0 +1,35 @@
import { type ReactNode } from "react";
interface Tab<T extends string> {
key: T;
label: ReactNode;
}
interface TabBarProps<T extends string> {
tabs: Tab<T>[];
activeTab: T;
onChange: (key: T) => void;
className?: string;
innerClassName?: string;
}
export function TabBar<T extends string>(
{ tabs, activeTab, onChange, className, innerClassName }: TabBarProps<T>,
) {
return (
<div className={`feed-sort-scroller${className ? ` ${className}` : ""}`}>
<div className={`feed-sort${innerClassName ? ` ${innerClassName}` : ""}`}>
{tabs.map(({ key, label }) => (
<button
key={key}
type="button"
className={`feed-sort-btn${activeTab === key ? " active" : ""}`}
onClick={() => onChange(key)}
>
{label}
</button>
))}
</div>
</div>
);
}

File diff suppressed because one or more lines are too long

View File

@@ -13,25 +13,13 @@ msgstr ""
"Language-Team: \n"
"Plural-Forms: \n"
#: src/components/AppHeader.tsx:71
msgid " Dump"
msgstr " Dump"
#: src/pages/UserDumps.tsx:114
#: src/pages/UserPublicProfile.tsx:1332
msgid " New dump"
msgstr " New dump"
#: src/components/NewPlaylistForm.tsx:30
msgid " New playlist"
msgstr " New playlist"
#: src/components/CommentThread.tsx:176
msgid "[deleted]"
msgstr "[deleted]"
#. placeholder {0}: dump.commentCount
#: src/components/DumpCard.tsx:95
#: src/components/JournalCard.tsx:78
msgid "{0, plural, one {# comment} other {# comments}}"
msgstr "{0, plural, one {# comment} other {# comments}}"
@@ -83,14 +71,10 @@ msgstr "← Back to all dumps"
msgid "← Back to profile"
msgstr "← Back to profile"
#: src/pages/UserPublicProfile.tsx:100
#: src/pages/UserPublicProfile.tsx:101
msgid "+ Invite someone"
msgstr "+ Invite someone"
#: src/components/AppHeader.tsx:71
msgid "+ New"
msgstr "+ New"
#: src/pages/UserDumps.tsx:114
#: src/pages/UserPublicProfile.tsx:1332
msgid "+ New dump"
@@ -152,7 +136,7 @@ msgstr "a comment"
msgid "a post"
msgstr "a post"
#: src/pages/UserPublicProfile.tsx:1217
#: src/pages/UserPublicProfile.tsx:1193
msgid "Account"
msgstr "Account"
@@ -160,7 +144,7 @@ msgstr "Account"
msgid "Add a comment…"
msgstr "Add a comment…"
#: src/pages/UserPublicProfile.tsx:859
#: src/pages/UserPublicProfile.tsx:860
msgid "Add email…"
msgstr "Add email…"
@@ -181,7 +165,7 @@ msgstr "All {0, plural, one {# upvoted dump} other {# upvoted dumps}} loaded."
msgid "Already have an account? <0>Log in</0>"
msgstr "Already have an account? <0>Log in</0>"
#: src/pages/UserPublicProfile.tsx:1236
#: src/pages/UserPublicProfile.tsx:1212
msgid "Appearance"
msgstr "Appearance"
@@ -191,7 +175,7 @@ msgstr "Appearance"
msgid "At least {0} characters"
msgstr "At least {0} characters"
#: src/pages/UserPublicProfile.tsx:1270
#: src/pages/UserPublicProfile.tsx:1246
msgid "Auto"
msgstr "Auto"
@@ -214,8 +198,8 @@ msgstr "Can't connect to the live updates server. Upvotes and notifications may
#: src/components/PlaylistCreateForm.tsx:112
#: src/pages/DumpEdit.tsx:299
#: src/pages/PlaylistDetail.tsx:680
#: src/pages/UserPublicProfile.tsx:841
#: src/pages/UserPublicProfile.tsx:919
#: src/pages/UserPublicProfile.tsx:842
#: src/pages/UserPublicProfile.tsx:921
msgid "Cancel"
msgstr "Cancel"
@@ -223,6 +207,10 @@ msgstr "Cancel"
msgid "Cancel removal"
msgstr "Cancel removal"
#: src/components/AppHeader.tsx:62
msgid "Cancel search"
msgstr "Cancel search"
#: src/pages/UserPublicProfile.tsx:772
msgid "Change avatar"
msgstr "Change avatar"
@@ -232,7 +220,7 @@ msgstr "Change avatar"
msgid "Change password"
msgstr "Change password"
#: src/pages/UserPublicProfile.tsx:1229
#: src/pages/UserPublicProfile.tsx:1205
msgid "Change password…"
msgstr "Change password…"
@@ -245,7 +233,7 @@ msgstr "Checking invite…"
msgid "Close"
msgstr "Close"
#: src/pages/UserPublicProfile.tsx:1262
#: src/pages/UserPublicProfile.tsx:1238
msgid "Color scheme"
msgstr "Color scheme"
@@ -254,11 +242,11 @@ msgstr "Color scheme"
msgid "Confirm new password"
msgstr "Confirm new password"
#: src/pages/UserPublicProfile.tsx:91
#: src/pages/UserPublicProfile.tsx:92
msgid "Copied!"
msgstr "Copied!"
#: src/pages/UserPublicProfile.tsx:91
#: src/pages/UserPublicProfile.tsx:92
msgid "Copy"
msgstr "Copy"
@@ -299,7 +287,7 @@ msgstr "Creating…"
msgid "Current password"
msgstr "Current password"
#: src/pages/UserPublicProfile.tsx:1284
#: src/pages/UserPublicProfile.tsx:1260
msgid "Dark"
msgstr "Dark"
@@ -351,6 +339,10 @@ msgstr "Drop a file here"
msgid "Drop a replacement here"
msgstr "Drop a replacement here"
#: src/components/AppHeader.tsx:99
msgid "Dump"
msgstr "Dump"
#: src/components/DumpCreateModal.tsx:427
msgid "Dump it"
msgstr "Dump it"
@@ -361,13 +353,13 @@ msgstr "Dumped!"
#: src/pages/Search.tsx:172
#: src/pages/UserDumps.tsx:107
#: src/pages/UserPublicProfile.tsx:968
#: src/pages/UserPublicProfile.tsx:965
msgid "Dumps"
msgstr "Dumps"
#. placeholder {0}: dumps.items.length
#. placeholder {1}: dumps.hasMore ? "+" : ""
#: src/pages/UserPublicProfile.tsx:1006
#: src/pages/UserPublicProfile.tsx:982
msgid "Dumps ({0}{1})"
msgstr "Dumps ({0}{1})"
@@ -407,7 +399,7 @@ msgstr "Editing"
msgid "Email address"
msgstr "Email address"
#: src/pages/Search.tsx:207
#: src/pages/Search.tsx:198
msgid "Enter a query to search."
msgstr "Enter a query to search."
@@ -420,20 +412,20 @@ msgstr "Failed to change password"
msgid "Failed to create playlist"
msgstr "Failed to create playlist"
#: src/pages/UserPublicProfile.tsx:72
#: src/pages/UserPublicProfile.tsx:75
#: src/pages/UserPublicProfile.tsx:103
#: src/pages/UserPublicProfile.tsx:73
#: src/pages/UserPublicProfile.tsx:76
#: src/pages/UserPublicProfile.tsx:104
msgid "Failed to generate invite"
msgstr "Failed to generate invite"
#: src/pages/index/FollowedFeed.tsx:81
#: src/pages/index/FollowedFeed.tsx:82
#: src/pages/index/HotFeed.tsx:36
#: src/pages/index/JournalFeed.tsx:48
#: src/pages/index/NewFeed.tsx:36
#: src/pages/Notifications.tsx:342
#: src/pages/UserPublicProfile.tsx:1108
#: src/pages/UserPublicProfile.tsx:1150
#: src/pages/UserPublicProfile.tsx:1195
#: src/pages/UserPublicProfile.tsx:1084
#: src/pages/UserPublicProfile.tsx:1126
#: src/pages/UserPublicProfile.tsx:1171
msgid "Failed to load"
msgstr "Failed to load"
@@ -452,8 +444,8 @@ msgstr "Failed to post reply"
#: src/pages/PlaylistDetail.tsx:789
#: src/pages/UserPublicProfile.tsx:680
#: src/pages/UserPublicProfile.tsx:718
#: src/pages/UserPublicProfile.tsx:845
#: src/pages/UserPublicProfile.tsx:922
#: src/pages/UserPublicProfile.tsx:846
#: src/pages/UserPublicProfile.tsx:924
msgid "Failed to save"
msgstr "Failed to save"
@@ -461,7 +453,7 @@ msgstr "Failed to save"
msgid "Failed to save edit"
msgstr "Failed to save edit"
#: src/pages/UserPublicProfile.tsx:868
#: src/pages/UserPublicProfile.tsx:869
msgid "Failed to update avatar"
msgstr "Failed to update avatar"
@@ -495,16 +487,16 @@ msgstr "Follow {targetUsername}"
msgid "Follow playlist"
msgstr "Follow playlist"
#: src/pages/index/FollowedFeed.tsx:371
#: src/pages/index/FollowedFeed.tsx:365
msgid "Follow some public playlists to see their dumps here."
msgstr "Follow some public playlists to see their dumps here."
#: src/pages/index/FollowedFeed.tsx:357
#: src/pages/index/FollowedFeed.tsx:351
msgid "Follow some users to see their dumps here."
msgstr "Follow some users to see their dumps here."
#: src/components/FeedTabBar.tsx:48
#: src/pages/UserPublicProfile.tsx:982
#: src/components/FeedTabBar.tsx:19
#: src/pages/UserPublicProfile.tsx:967
msgid "Followed"
msgstr "Followed"
@@ -514,13 +506,13 @@ msgstr "Followed"
msgid "Followed ({0}{1})"
msgstr "Followed ({0}{1})"
#: src/pages/UserPublicProfile.tsx:1139
#: src/pages/UserPublicProfile.tsx:1115
msgid "Followed playlists"
msgstr "Followed playlists"
#: src/components/FollowButton.tsx:37
#: src/components/FollowButton.tsx:64
#: src/pages/UserPublicProfile.tsx:1097
#: src/pages/UserPublicProfile.tsx:1073
msgid "Following"
msgstr "Following"
@@ -528,19 +520,23 @@ msgstr "Following"
msgid "Forgot password?"
msgstr "Forgot password?"
#: src/pages/index/FollowedFeed.tsx:337
#: src/pages/index/FollowedFeed.tsx:334
msgid "From people"
msgstr "From people"
#: src/pages/index/FollowedFeed.tsx:344
#: src/pages/index/FollowedFeed.tsx:335
msgid "From playlists"
msgstr "From playlists"
#: src/pages/NotFound.tsx:13
msgid "Go home"
msgstr "Go home"
#: src/pages/ResetPassword.tsx:66
msgid "Go to login"
msgstr "Go to login"
#: src/components/FeedTabBar.tsx:26
#: src/components/FeedTabBar.tsx:16
msgid "Hot"
msgstr "Hot"
@@ -556,16 +552,16 @@ msgstr "Invalid invite"
msgid "Invalid link"
msgstr "Invalid link"
#: src/pages/UserPublicProfile.tsx:790
#: src/pages/UserPublicProfile.tsx:791
msgid "invited by"
msgstr "invited by"
#: src/pages/UserPublicProfile.tsx:989
#: src/pages/UserPublicProfile.tsx:1184
#: src/pages/UserPublicProfile.tsx:968
#: src/pages/UserPublicProfile.tsx:1160
msgid "Invitees"
msgstr "Invitees"
#: src/components/FeedTabBar.tsx:40
#: src/components/FeedTabBar.tsx:18
msgid "Journal"
msgstr "Journal"
@@ -573,7 +569,7 @@ msgstr "Journal"
msgid "just now"
msgstr "just now"
#: src/pages/UserPublicProfile.tsx:1277
#: src/pages/UserPublicProfile.tsx:1253
msgid "Light"
msgstr "Light"
@@ -581,7 +577,7 @@ msgstr "Light"
msgid "Live updates are temporarily disconnected. Trying to reconnect…"
msgstr "Live updates are temporarily disconnected. Trying to reconnect…"
#: src/components/AppHeader.tsx:88
#: src/components/AppHeader.tsx:125
msgid "Live updates unavailable."
msgstr "Live updates unavailable."
@@ -594,11 +590,11 @@ msgstr "Load more"
msgid "Loading dump…"
msgstr "Loading dump…"
#: src/pages/index/FollowedFeed.tsx:109
#: src/pages/index/FollowedFeed.tsx:110
#: src/pages/index/HotFeed.tsx:64
#: src/pages/index/JournalFeed.tsx:77
#: src/pages/index/NewFeed.tsx:64
#: src/pages/Search.tsx:244
#: src/pages/Search.tsx:235
#: src/pages/UserDumps.tsx:93
#: src/pages/UserPlaylists.tsx:417
#: src/pages/UserPlaylists.tsx:452
@@ -616,7 +612,7 @@ msgstr "Loading profile…"
#: src/components/PlaylistMembershipPanel.tsx:28
#: src/components/TextEditor.tsx:289
#: src/pages/index/FollowedFeed.tsx:76
#: src/pages/index/FollowedFeed.tsx:77
#: src/pages/index/HotFeed.tsx:32
#: src/pages/index/JournalFeed.tsx:44
#: src/pages/index/NewFeed.tsx:32
@@ -624,21 +620,21 @@ msgstr "Loading profile…"
#: src/pages/Notifications.tsx:414
#: src/pages/UserDumps.tsx:51
#: src/pages/UserPlaylists.tsx:342
#: src/pages/UserPublicProfile.tsx:1102
#: src/pages/UserPublicProfile.tsx:1144
#: src/pages/UserPublicProfile.tsx:1189
#: src/pages/UserPublicProfile.tsx:1078
#: src/pages/UserPublicProfile.tsx:1120
#: src/pages/UserPublicProfile.tsx:1165
#: src/pages/UserUpvoted.tsx:123
msgid "Loading…"
msgstr "Loading…"
#: src/components/AppHeader.tsx:78
#: src/components/AppHeader.tsx:106
#: src/pages/UserLogin.tsx:87
#: src/pages/UserLogin.tsx:117
msgid "Log in"
msgstr "Log in"
#: src/pages/UserPublicProfile.tsx:749
#: src/pages/UserPublicProfile.tsx:882
#: src/pages/UserPublicProfile.tsx:883
msgid "Log out"
msgstr "Log out"
@@ -658,11 +654,13 @@ msgstr "Max 50 MB"
msgid "new"
msgstr "new"
#: src/components/FeedTabBar.tsx:33
#: src/components/FeedTabBar.tsx:17
msgid "New"
msgstr "New"
#: src/components/DumpCreateModal.tsx:277
#: src/pages/UserDumps.tsx:114
#: src/pages/UserPublicProfile.tsx:1308
msgid "New dump"
msgstr "New dump"
@@ -671,6 +669,7 @@ msgstr "New dump"
msgid "New password"
msgstr "New password"
#: src/components/NewPlaylistForm.tsx:30
#: src/components/NewPlaylistForm.tsx:34
msgid "New playlist"
msgstr "New playlist"
@@ -679,7 +678,7 @@ msgstr "New playlist"
msgid "No dumps in this playlist yet."
msgstr "No dumps in this playlist yet."
#: src/pages/Search.tsx:224
#: src/pages/Search.tsx:215
msgid "No dumps match \"{q}\"."
msgstr "No dumps match \"{q}\"."
@@ -694,36 +693,36 @@ msgid "No emoji found."
msgstr "No emoji found."
#: src/pages/UserPlaylists.tsx:439
#: src/pages/UserPublicProfile.tsx:1157
#: src/pages/UserPublicProfile.tsx:1133
msgid "No followed playlists yet."
msgstr "No followed playlists yet."
#: src/pages/UserPublicProfile.tsx:1202
#: src/pages/UserPublicProfile.tsx:1178
msgid "No invitees yet."
msgstr "No invitees yet."
#: src/pages/Search.tsx:283
#: src/pages/Search.tsx:274
msgid "No playlists match \"{q}\"."
msgstr "No playlists match \"{q}\"."
#: src/components/PlaylistMembershipPanel.tsx:34
#: src/pages/UserPlaylists.tsx:397
#: src/pages/UserPublicProfile.tsx:1068
#: src/pages/UserPublicProfile.tsx:1044
msgid "No playlists yet."
msgstr "No playlists yet."
#: src/pages/Search.tsx:257
#: src/pages/Search.tsx:248
msgid "No users match \"{q}\"."
msgstr "No users match \"{q}\"."
#: src/pages/UserPublicProfile.tsx:1115
#: src/pages/UserPublicProfile.tsx:1091
msgid "Not following anyone yet."
msgstr "Not following anyone yet."
#: src/pages/Notifications.tsx:349
#: src/pages/UserDumps.tsx:123
#: src/pages/UserPublicProfile.tsx:1342
#: src/pages/UserPublicProfile.tsx:1465
#: src/pages/UserPublicProfile.tsx:1318
#: src/pages/UserPublicProfile.tsx:1441
#: src/pages/UserUpvoted.tsx:195
msgid "Nothing here yet."
msgstr "Nothing here yet."
@@ -737,7 +736,7 @@ msgstr "Notifications"
msgid "Notifications ({unreadNotificationCount} unread)"
msgstr "Notifications ({unreadNotificationCount} unread)"
#: src/components/SearchBar.tsx:71
#: src/components/SearchBar.tsx:83
msgid "Open search"
msgstr "Open search"
@@ -746,7 +745,7 @@ msgid "or <0>browse files</0>"
msgstr "or <0>browse files</0>"
#: src/pages/UserLogin.tsx:106
#: src/pages/UserPublicProfile.tsx:1222
#: src/pages/UserPublicProfile.tsx:1198
msgid "Password"
msgstr "Password"
@@ -768,17 +767,17 @@ msgstr "Password updated"
msgid "Passwords do not match"
msgstr "Passwords do not match"
#: src/components/AppHeader.tsx:54
#: src/components/AppHeader.tsx:82
#: src/components/UserMenu.tsx:62
#: src/pages/Search.tsx:175
#: src/pages/UserPlaylists.tsx:368
#: src/pages/UserPublicProfile.tsx:975
#: src/pages/UserPublicProfile.tsx:966
msgid "Playlists"
msgstr "Playlists"
#. placeholder {0}: playlists.items.length
#. placeholder {1}: playlists.hasMore ? "+" : ""
#: src/pages/UserPublicProfile.tsx:1037
#: src/pages/UserPublicProfile.tsx:1013
msgid "Playlists ({0}{1})"
msgstr "Playlists ({0}{1})"
@@ -800,6 +799,7 @@ msgid "Posting…"
msgstr "Posting…"
#: src/components/DumpCard.tsx:104
#: src/components/JournalCard.tsx:87
#: src/components/PlaylistCard.tsx:73
#: src/components/PlaylistMembershipPanel.tsx:55
#: src/pages/Dump.tsx:287
@@ -879,8 +879,8 @@ msgstr "Retry"
#: src/components/CommentThread.tsx:270
#: src/pages/DumpEdit.tsx:306
#: src/pages/PlaylistDetail.tsx:673
#: src/pages/UserPublicProfile.tsx:833
#: src/pages/UserPublicProfile.tsx:911
#: src/pages/UserPublicProfile.tsx:834
#: src/pages/UserPublicProfile.tsx:913
msgid "Save"
msgstr "Save"
@@ -888,24 +888,25 @@ msgstr "Save"
#: src/components/CommentThread.tsx:269
#: src/pages/PlaylistDetail.tsx:673
#: src/pages/ResetPassword.tsx:152
#: src/pages/UserPublicProfile.tsx:832
#: src/pages/UserPublicProfile.tsx:911
#: src/pages/UserPublicProfile.tsx:833
#: src/pages/UserPublicProfile.tsx:913
msgid "Saving…"
msgstr "Saving…"
#: src/components/SearchBar.tsx:65
#: src/components/AppHeader.tsx:62
#: src/components/SearchBar.tsx:77
msgid "Search"
msgstr "Search"
#: src/components/SearchBar.tsx:61
#: src/components/SearchBar.tsx:73
msgid "Search dumps, users, playlists…"
msgstr "Search dumps, users, playlists…"
#: src/pages/Search.tsx:218
#: src/pages/Search.tsx:209
msgid "Search failed"
msgstr "Search failed"
#: src/pages/Search.tsx:213
#: src/pages/Search.tsx:204
msgid "Searching…"
msgstr "Searching…"
@@ -917,7 +918,7 @@ msgstr "Send reset link"
msgid "Sending…"
msgstr "Sending…"
#: src/components/AppHeader.tsx:69
#: src/components/AppHeader.tsx:97
msgid "Server unreachable"
msgstr "Server unreachable"
@@ -926,7 +927,7 @@ msgstr "Server unreachable"
msgid "Set new password"
msgstr "Set new password"
#: src/pages/UserPublicProfile.tsx:997
#: src/pages/UserPublicProfile.tsx:970
msgid "Settings"
msgstr "Settings"
@@ -934,11 +935,11 @@ msgstr "Settings"
msgid "Something went wrong"
msgstr "Something went wrong"
#: src/pages/UserPublicProfile.tsx:1241
#: src/pages/UserPublicProfile.tsx:1217
msgid "Style"
msgstr "Style"
#: src/components/SearchBar.tsx:71
#: src/components/SearchBar.tsx:83
msgid "Submit search"
msgstr "Submit search"
@@ -950,6 +951,10 @@ msgstr "This invite link is missing, expired, or already used."
msgid "This is a mirage."
msgstr "This is a mirage."
#: src/pages/NotFound.tsx:10
msgid "This page does not exist."
msgstr "This page does not exist."
#: src/pages/ResetPassword.tsx:37
msgid "This reset link is missing or malformed."
msgstr "This reset link is missing or malformed."
@@ -993,7 +998,7 @@ msgstr "Upvoted"
#. placeholder {0}: votes.items.length
#. placeholder {1}: votes.hasMore ? "+" : ""
#: src/pages/UserPublicProfile.tsx:1017
#: src/pages/UserPublicProfile.tsx:993
msgid "Upvoted ({0}{1})"
msgstr "Upvoted ({0}{1})"
@@ -1019,11 +1024,11 @@ msgstr "Username"
msgid "Users"
msgstr "Users"
#: src/pages/UserPublicProfile.tsx:1087
#: src/pages/UserPublicProfile.tsx:1130
#: src/pages/UserPublicProfile.tsx:1172
#: src/pages/UserPublicProfile.tsx:1363
#: src/pages/UserPublicProfile.tsx:1495
#: src/pages/UserPublicProfile.tsx:1063
#: src/pages/UserPublicProfile.tsx:1106
#: src/pages/UserPublicProfile.tsx:1148
#: src/pages/UserPublicProfile.tsx:1339
#: src/pages/UserPublicProfile.tsx:1471
msgid "View all →"
msgstr "View all →"
@@ -1036,8 +1041,8 @@ msgstr "View dump →"
msgid "What makes it worth it?"
msgstr "What makes it worth it?"
#: src/pages/UserPublicProfile.tsx:899
#: src/pages/UserPublicProfile.tsx:948
#: src/pages/UserPublicProfile.tsx:901
#: src/pages/UserPublicProfile.tsx:950
msgid "Who am I?"
msgstr "Who am I?"
@@ -1058,11 +1063,11 @@ msgstr "Yesterday"
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."
#: src/pages/index/FollowedFeed.tsx:114
#: src/pages/index/FollowedFeed.tsx:115
#: src/pages/index/HotFeed.tsx:69
#: src/pages/index/JournalFeed.tsx:82
#: src/pages/index/NewFeed.tsx:69
#: src/pages/Search.tsx:249
#: src/pages/Search.tsx:240
#: src/pages/UserDumps.tsx:98
#: src/pages/UserPlaylists.tsx:422
#: src/pages/UserPlaylists.tsx:457

File diff suppressed because one or more lines are too long

View File

@@ -13,25 +13,13 @@ msgstr ""
"Last-Translator: \n"
"Language-Team: \n"
#: src/components/AppHeader.tsx:71
msgid " Dump"
msgstr " Reco"
#: src/pages/UserDumps.tsx:114
#: src/pages/UserPublicProfile.tsx:1332
msgid " New dump"
msgstr " Nouvelle reco"
#: src/components/NewPlaylistForm.tsx:30
msgid " New playlist"
msgstr " Nouvelle collection"
#: src/components/CommentThread.tsx:176
msgid "[deleted]"
msgstr "[supprimé]"
#. placeholder {0}: dump.commentCount
#: src/components/DumpCard.tsx:95
#: src/components/JournalCard.tsx:78
msgid "{0, plural, one {# comment} other {# comments}}"
msgstr "{0, plural, one {# commentaire} other {# commentaires}}"
@@ -83,14 +71,10 @@ msgstr "← Retour à toutes les recos"
msgid "← Back to profile"
msgstr "← Retour au profil"
#: src/pages/UserPublicProfile.tsx:100
#: src/pages/UserPublicProfile.tsx:101
msgid "+ Invite someone"
msgstr "+ Inviter quelqu'un"
#: src/components/AppHeader.tsx:71
msgid "+ New"
msgstr "+ Nouveau"
#: src/pages/UserDumps.tsx:114
#: src/pages/UserPublicProfile.tsx:1332
msgid "+ New dump"
@@ -152,7 +136,7 @@ msgstr "un commentaire"
msgid "a post"
msgstr "une publication"
#: src/pages/UserPublicProfile.tsx:1217
#: src/pages/UserPublicProfile.tsx:1193
msgid "Account"
msgstr "Compte"
@@ -160,7 +144,7 @@ msgstr "Compte"
msgid "Add a comment…"
msgstr "Ajouter un commentaire…"
#: src/pages/UserPublicProfile.tsx:859
#: src/pages/UserPublicProfile.tsx:860
msgid "Add email…"
msgstr "Ajouter un e-mail…"
@@ -181,7 +165,7 @@ msgstr "Toutes les {0, plural, one {# reco votée} other {# recos votées}} char
msgid "Already have an account? <0>Log in</0>"
msgstr "Vous avez déjà un compte ? <0>Se connecter</0>"
#: src/pages/UserPublicProfile.tsx:1236
#: src/pages/UserPublicProfile.tsx:1212
msgid "Appearance"
msgstr "Apparence"
@@ -191,7 +175,7 @@ msgstr "Apparence"
msgid "At least {0} characters"
msgstr "Au moins {0} caractères"
#: src/pages/UserPublicProfile.tsx:1270
#: src/pages/UserPublicProfile.tsx:1246
msgid "Auto"
msgstr "Auto"
@@ -214,8 +198,8 @@ msgstr "Impossible de se connecter au serveur de mises à jour en direct. Les vo
#: src/components/PlaylistCreateForm.tsx:112
#: src/pages/DumpEdit.tsx:299
#: src/pages/PlaylistDetail.tsx:680
#: src/pages/UserPublicProfile.tsx:841
#: src/pages/UserPublicProfile.tsx:919
#: src/pages/UserPublicProfile.tsx:842
#: src/pages/UserPublicProfile.tsx:921
msgid "Cancel"
msgstr "Annuler"
@@ -223,6 +207,10 @@ msgstr "Annuler"
msgid "Cancel removal"
msgstr "Annuler la suppression"
#: src/components/AppHeader.tsx:62
msgid "Cancel search"
msgstr "Annuler la recherche"
#: src/pages/UserPublicProfile.tsx:772
msgid "Change avatar"
msgstr "Changer l'avatar"
@@ -232,7 +220,7 @@ msgstr "Changer l'avatar"
msgid "Change password"
msgstr "Changer le mot de passe"
#: src/pages/UserPublicProfile.tsx:1229
#: src/pages/UserPublicProfile.tsx:1205
msgid "Change password…"
msgstr "Changer le mot de passe…"
@@ -245,7 +233,7 @@ msgstr "Vérification de l'invitation…"
msgid "Close"
msgstr "Fermer"
#: src/pages/UserPublicProfile.tsx:1262
#: src/pages/UserPublicProfile.tsx:1238
msgid "Color scheme"
msgstr "Thème de couleur"
@@ -254,11 +242,11 @@ msgstr "Thème de couleur"
msgid "Confirm new password"
msgstr "Confirmer le nouveau mot de passe"
#: src/pages/UserPublicProfile.tsx:91
#: src/pages/UserPublicProfile.tsx:92
msgid "Copied!"
msgstr "Copié !"
#: src/pages/UserPublicProfile.tsx:91
#: src/pages/UserPublicProfile.tsx:92
msgid "Copy"
msgstr "Copier"
@@ -299,7 +287,7 @@ msgstr "Création…"
msgid "Current password"
msgstr "Mot de passe actuel"
#: src/pages/UserPublicProfile.tsx:1284
#: src/pages/UserPublicProfile.tsx:1260
msgid "Dark"
msgstr "Sombre"
@@ -351,6 +339,10 @@ msgstr "Déposez un fichier ici"
msgid "Drop a replacement here"
msgstr "Déposez un fichier de remplacement ici"
#: src/components/AppHeader.tsx:99
msgid "Dump"
msgstr "Reco"
#: src/components/DumpCreateModal.tsx:427
msgid "Dump it"
msgstr "Recommander"
@@ -361,13 +353,13 @@ msgstr "Recommandé !"
#: src/pages/Search.tsx:172
#: src/pages/UserDumps.tsx:107
#: src/pages/UserPublicProfile.tsx:968
#: src/pages/UserPublicProfile.tsx:965
msgid "Dumps"
msgstr "Recos"
#. placeholder {0}: dumps.items.length
#. placeholder {1}: dumps.hasMore ? "+" : ""
#: src/pages/UserPublicProfile.tsx:1006
#: src/pages/UserPublicProfile.tsx:982
msgid "Dumps ({0}{1})"
msgstr "Recos ({0}{1})"
@@ -407,7 +399,7 @@ msgstr "Modification"
msgid "Email address"
msgstr "Adresse e-mail"
#: src/pages/Search.tsx:207
#: src/pages/Search.tsx:198
msgid "Enter a query to search."
msgstr "Saisissez une recherche."
@@ -420,20 +412,20 @@ msgstr "Impossible de changer le mot de passe"
msgid "Failed to create playlist"
msgstr "Impossible de créer la collection"
#: src/pages/UserPublicProfile.tsx:72
#: src/pages/UserPublicProfile.tsx:75
#: src/pages/UserPublicProfile.tsx:103
#: src/pages/UserPublicProfile.tsx:73
#: src/pages/UserPublicProfile.tsx:76
#: src/pages/UserPublicProfile.tsx:104
msgid "Failed to generate invite"
msgstr "Impossible de générer une invitation"
#: src/pages/index/FollowedFeed.tsx:81
#: src/pages/index/FollowedFeed.tsx:82
#: src/pages/index/HotFeed.tsx:36
#: src/pages/index/JournalFeed.tsx:48
#: src/pages/index/NewFeed.tsx:36
#: src/pages/Notifications.tsx:342
#: src/pages/UserPublicProfile.tsx:1108
#: src/pages/UserPublicProfile.tsx:1150
#: src/pages/UserPublicProfile.tsx:1195
#: src/pages/UserPublicProfile.tsx:1084
#: src/pages/UserPublicProfile.tsx:1126
#: src/pages/UserPublicProfile.tsx:1171
msgid "Failed to load"
msgstr "Chargement échoué"
@@ -452,8 +444,8 @@ msgstr "Impossible de publier la réponse"
#: src/pages/PlaylistDetail.tsx:789
#: src/pages/UserPublicProfile.tsx:680
#: src/pages/UserPublicProfile.tsx:718
#: src/pages/UserPublicProfile.tsx:845
#: src/pages/UserPublicProfile.tsx:922
#: src/pages/UserPublicProfile.tsx:846
#: src/pages/UserPublicProfile.tsx:924
msgid "Failed to save"
msgstr "Enregistrement échoué"
@@ -461,7 +453,7 @@ msgstr "Enregistrement échoué"
msgid "Failed to save edit"
msgstr "Impossible d'enregistrer la modification"
#: src/pages/UserPublicProfile.tsx:868
#: src/pages/UserPublicProfile.tsx:869
msgid "Failed to update avatar"
msgstr "Impossible de mettre à jour l'avatar"
@@ -495,16 +487,16 @@ msgstr "Suivre {targetUsername}"
msgid "Follow playlist"
msgstr "Suivre la collection"
#: src/pages/index/FollowedFeed.tsx:371
#: src/pages/index/FollowedFeed.tsx:365
msgid "Follow some public playlists to see their dumps here."
msgstr "Suivez des collections publiques pour voir leurs recos ici."
#: src/pages/index/FollowedFeed.tsx:357
#: src/pages/index/FollowedFeed.tsx:351
msgid "Follow some users to see their dumps here."
msgstr "Suivez des utilisateurs pour voir leurs recos ici."
#: src/components/FeedTabBar.tsx:48
#: src/pages/UserPublicProfile.tsx:982
#: src/components/FeedTabBar.tsx:19
#: src/pages/UserPublicProfile.tsx:967
msgid "Followed"
msgstr "Suivi"
@@ -514,13 +506,13 @@ msgstr "Suivi"
msgid "Followed ({0}{1})"
msgstr "Suivies ({0}{1})"
#: src/pages/UserPublicProfile.tsx:1139
#: src/pages/UserPublicProfile.tsx:1115
msgid "Followed playlists"
msgstr "Collections suivies"
#: src/components/FollowButton.tsx:37
#: src/components/FollowButton.tsx:64
#: src/pages/UserPublicProfile.tsx:1097
#: src/pages/UserPublicProfile.tsx:1073
msgid "Following"
msgstr "Abonné"
@@ -528,19 +520,23 @@ msgstr "Abonné"
msgid "Forgot password?"
msgstr "Mot de passe oublié ?"
#: src/pages/index/FollowedFeed.tsx:337
#: src/pages/index/FollowedFeed.tsx:334
msgid "From people"
msgstr "De personnes"
#: src/pages/index/FollowedFeed.tsx:344
#: src/pages/index/FollowedFeed.tsx:335
msgid "From playlists"
msgstr "De collections"
#: src/pages/NotFound.tsx:13
msgid "Go home"
msgstr "Accueil"
#: src/pages/ResetPassword.tsx:66
msgid "Go to login"
msgstr "Aller à la connexion"
#: src/components/FeedTabBar.tsx:26
#: src/components/FeedTabBar.tsx:16
msgid "Hot"
msgstr "Tendances"
@@ -556,16 +552,16 @@ msgstr "Invitation invalide"
msgid "Invalid link"
msgstr "Lien invalide"
#: src/pages/UserPublicProfile.tsx:790
#: src/pages/UserPublicProfile.tsx:791
msgid "invited by"
msgstr "invité par"
#: src/pages/UserPublicProfile.tsx:989
#: src/pages/UserPublicProfile.tsx:1184
#: src/pages/UserPublicProfile.tsx:968
#: src/pages/UserPublicProfile.tsx:1160
msgid "Invitees"
msgstr "Invités"
#: src/components/FeedTabBar.tsx:40
#: src/components/FeedTabBar.tsx:18
msgid "Journal"
msgstr "Journal"
@@ -573,7 +569,7 @@ msgstr "Journal"
msgid "just now"
msgstr "à l'instant"
#: src/pages/UserPublicProfile.tsx:1277
#: src/pages/UserPublicProfile.tsx:1253
msgid "Light"
msgstr "Clair"
@@ -581,7 +577,7 @@ msgstr "Clair"
msgid "Live updates are temporarily disconnected. Trying to reconnect…"
msgstr "Les mises à jour en direct sont temporairement interrompues. Tentative de reconnexion…"
#: src/components/AppHeader.tsx:88
#: src/components/AppHeader.tsx:125
msgid "Live updates unavailable."
msgstr "Mises à jour en direct indisponibles."
@@ -594,11 +590,11 @@ msgstr "Charger plus"
msgid "Loading dump…"
msgstr "Chargement de la reco…"
#: src/pages/index/FollowedFeed.tsx:109
#: src/pages/index/FollowedFeed.tsx:110
#: src/pages/index/HotFeed.tsx:64
#: src/pages/index/JournalFeed.tsx:77
#: src/pages/index/NewFeed.tsx:64
#: src/pages/Search.tsx:244
#: src/pages/Search.tsx:235
#: src/pages/UserDumps.tsx:93
#: src/pages/UserPlaylists.tsx:417
#: src/pages/UserPlaylists.tsx:452
@@ -616,7 +612,7 @@ msgstr "Chargement du profil…"
#: src/components/PlaylistMembershipPanel.tsx:28
#: src/components/TextEditor.tsx:289
#: src/pages/index/FollowedFeed.tsx:76
#: src/pages/index/FollowedFeed.tsx:77
#: src/pages/index/HotFeed.tsx:32
#: src/pages/index/JournalFeed.tsx:44
#: src/pages/index/NewFeed.tsx:32
@@ -624,21 +620,21 @@ msgstr "Chargement du profil…"
#: src/pages/Notifications.tsx:414
#: src/pages/UserDumps.tsx:51
#: src/pages/UserPlaylists.tsx:342
#: src/pages/UserPublicProfile.tsx:1102
#: src/pages/UserPublicProfile.tsx:1144
#: src/pages/UserPublicProfile.tsx:1189
#: src/pages/UserPublicProfile.tsx:1078
#: src/pages/UserPublicProfile.tsx:1120
#: src/pages/UserPublicProfile.tsx:1165
#: src/pages/UserUpvoted.tsx:123
msgid "Loading…"
msgstr "Chargement…"
#: src/components/AppHeader.tsx:78
#: src/components/AppHeader.tsx:106
#: src/pages/UserLogin.tsx:87
#: src/pages/UserLogin.tsx:117
msgid "Log in"
msgstr "Se connecter"
#: src/pages/UserPublicProfile.tsx:749
#: src/pages/UserPublicProfile.tsx:882
#: src/pages/UserPublicProfile.tsx:883
msgid "Log out"
msgstr "Se déconnecter"
@@ -658,11 +654,13 @@ msgstr "Max 50 Mo"
msgid "new"
msgstr "nouveau"
#: src/components/FeedTabBar.tsx:33
#: src/components/FeedTabBar.tsx:17
msgid "New"
msgstr "Nouveau"
#: src/components/DumpCreateModal.tsx:277
#: src/pages/UserDumps.tsx:114
#: src/pages/UserPublicProfile.tsx:1308
msgid "New dump"
msgstr "Nouvelle reco"
@@ -671,6 +669,7 @@ msgstr "Nouvelle reco"
msgid "New password"
msgstr "Nouveau mot de passe"
#: src/components/NewPlaylistForm.tsx:30
#: src/components/NewPlaylistForm.tsx:34
msgid "New playlist"
msgstr "Nouvelle collection"
@@ -679,7 +678,7 @@ msgstr "Nouvelle collection"
msgid "No dumps in this playlist yet."
msgstr "Aucune reco dans cette collection pour l'instant."
#: src/pages/Search.tsx:224
#: src/pages/Search.tsx:215
msgid "No dumps match \"{q}\"."
msgstr "Aucune reco ne correspond à « {q} »."
@@ -694,36 +693,36 @@ msgid "No emoji found."
msgstr "Aucun emoji trouvé."
#: src/pages/UserPlaylists.tsx:439
#: src/pages/UserPublicProfile.tsx:1157
#: src/pages/UserPublicProfile.tsx:1133
msgid "No followed playlists yet."
msgstr "Pas encore de collections suivies."
#: src/pages/UserPublicProfile.tsx:1202
#: src/pages/UserPublicProfile.tsx:1178
msgid "No invitees yet."
msgstr "Aucun invité pour le moment."
#: src/pages/Search.tsx:283
#: src/pages/Search.tsx:274
msgid "No playlists match \"{q}\"."
msgstr "Aucune collection ne correspond à « {q} »."
#: src/components/PlaylistMembershipPanel.tsx:34
#: src/pages/UserPlaylists.tsx:397
#: src/pages/UserPublicProfile.tsx:1068
#: src/pages/UserPublicProfile.tsx:1044
msgid "No playlists yet."
msgstr "Pas encore de collections."
#: src/pages/Search.tsx:257
#: src/pages/Search.tsx:248
msgid "No users match \"{q}\"."
msgstr "Aucun utilisateur ne correspond à « {q} »."
#: src/pages/UserPublicProfile.tsx:1115
#: src/pages/UserPublicProfile.tsx:1091
msgid "Not following anyone yet."
msgstr "Aucun abonnement pour le moment."
#: src/pages/Notifications.tsx:349
#: src/pages/UserDumps.tsx:123
#: src/pages/UserPublicProfile.tsx:1342
#: src/pages/UserPublicProfile.tsx:1465
#: src/pages/UserPublicProfile.tsx:1318
#: src/pages/UserPublicProfile.tsx:1441
#: src/pages/UserUpvoted.tsx:195
msgid "Nothing here yet."
msgstr "Rien ici pour l'instant."
@@ -737,7 +736,7 @@ msgstr "Notifications"
msgid "Notifications ({unreadNotificationCount} unread)"
msgstr "Notifications ({unreadNotificationCount} non lues)"
#: src/components/SearchBar.tsx:71
#: src/components/SearchBar.tsx:83
msgid "Open search"
msgstr "Ouvrir la recherche"
@@ -746,7 +745,7 @@ msgid "or <0>browse files</0>"
msgstr "ou <0>parcourir les fichiers</0>"
#: src/pages/UserLogin.tsx:106
#: src/pages/UserPublicProfile.tsx:1222
#: src/pages/UserPublicProfile.tsx:1198
msgid "Password"
msgstr "Mot de passe"
@@ -768,17 +767,17 @@ msgstr "Mot de passe mis à jour"
msgid "Passwords do not match"
msgstr "Les mots de passe ne correspondent pas"
#: src/components/AppHeader.tsx:54
#: src/components/AppHeader.tsx:82
#: src/components/UserMenu.tsx:62
#: src/pages/Search.tsx:175
#: src/pages/UserPlaylists.tsx:368
#: src/pages/UserPublicProfile.tsx:975
#: src/pages/UserPublicProfile.tsx:966
msgid "Playlists"
msgstr "Collections"
#. placeholder {0}: playlists.items.length
#. placeholder {1}: playlists.hasMore ? "+" : ""
#: src/pages/UserPublicProfile.tsx:1037
#: src/pages/UserPublicProfile.tsx:1013
msgid "Playlists ({0}{1})"
msgstr "Collections ({0}{1})"
@@ -800,6 +799,7 @@ msgid "Posting…"
msgstr "Publication…"
#: src/components/DumpCard.tsx:104
#: src/components/JournalCard.tsx:87
#: src/components/PlaylistCard.tsx:73
#: src/components/PlaylistMembershipPanel.tsx:55
#: src/pages/Dump.tsx:287
@@ -879,8 +879,8 @@ msgstr "Réessayer"
#: src/components/CommentThread.tsx:270
#: src/pages/DumpEdit.tsx:306
#: src/pages/PlaylistDetail.tsx:673
#: src/pages/UserPublicProfile.tsx:833
#: src/pages/UserPublicProfile.tsx:911
#: src/pages/UserPublicProfile.tsx:834
#: src/pages/UserPublicProfile.tsx:913
msgid "Save"
msgstr "Enregistrer"
@@ -888,24 +888,25 @@ msgstr "Enregistrer"
#: src/components/CommentThread.tsx:269
#: src/pages/PlaylistDetail.tsx:673
#: src/pages/ResetPassword.tsx:152
#: src/pages/UserPublicProfile.tsx:832
#: src/pages/UserPublicProfile.tsx:911
#: src/pages/UserPublicProfile.tsx:833
#: src/pages/UserPublicProfile.tsx:913
msgid "Saving…"
msgstr "Enregistrement…"
#: src/components/SearchBar.tsx:65
#: src/components/AppHeader.tsx:62
#: src/components/SearchBar.tsx:77
msgid "Search"
msgstr "Rechercher"
#: src/components/SearchBar.tsx:61
#: src/components/SearchBar.tsx:73
msgid "Search dumps, users, playlists…"
msgstr "Rechercher des recos, utilisateurs, collections…"
#: src/pages/Search.tsx:218
#: src/pages/Search.tsx:209
msgid "Search failed"
msgstr "Recherche échouée"
#: src/pages/Search.tsx:213
#: src/pages/Search.tsx:204
msgid "Searching…"
msgstr "Recherche…"
@@ -917,7 +918,7 @@ msgstr "Envoyer le lien de réinitialisation"
msgid "Sending…"
msgstr "Envoi…"
#: src/components/AppHeader.tsx:69
#: src/components/AppHeader.tsx:97
msgid "Server unreachable"
msgstr "Serveur inaccessible"
@@ -926,7 +927,7 @@ msgstr "Serveur inaccessible"
msgid "Set new password"
msgstr "Définir un nouveau mot de passe"
#: src/pages/UserPublicProfile.tsx:997
#: src/pages/UserPublicProfile.tsx:970
msgid "Settings"
msgstr "Paramètres"
@@ -934,11 +935,11 @@ msgstr "Paramètres"
msgid "Something went wrong"
msgstr "Une erreur est survenue"
#: src/pages/UserPublicProfile.tsx:1241
#: src/pages/UserPublicProfile.tsx:1217
msgid "Style"
msgstr "Style"
#: src/components/SearchBar.tsx:71
#: src/components/SearchBar.tsx:83
msgid "Submit search"
msgstr "Lancer la recherche"
@@ -950,6 +951,10 @@ msgstr "Ce lien d'invitation est manquant, expiré ou déjà utilisé."
msgid "This is a mirage."
msgstr "C'est un mirage."
#: src/pages/NotFound.tsx:10
msgid "This page does not exist."
msgstr "Rien à voir, circulez."
#: src/pages/ResetPassword.tsx:37
msgid "This reset link is missing or malformed."
msgstr "Ce lien de réinitialisation est absent ou malformé."
@@ -993,7 +998,7 @@ msgstr "Voté"
#. placeholder {0}: votes.items.length
#. placeholder {1}: votes.hasMore ? "+" : ""
#: src/pages/UserPublicProfile.tsx:1017
#: src/pages/UserPublicProfile.tsx:993
msgid "Upvoted ({0}{1})"
msgstr "Votés ({0}{1})"
@@ -1019,11 +1024,11 @@ msgstr "Nom d'utilisateur"
msgid "Users"
msgstr "Utilisateurs"
#: src/pages/UserPublicProfile.tsx:1087
#: src/pages/UserPublicProfile.tsx:1130
#: src/pages/UserPublicProfile.tsx:1172
#: src/pages/UserPublicProfile.tsx:1363
#: src/pages/UserPublicProfile.tsx:1495
#: src/pages/UserPublicProfile.tsx:1063
#: src/pages/UserPublicProfile.tsx:1106
#: src/pages/UserPublicProfile.tsx:1148
#: src/pages/UserPublicProfile.tsx:1339
#: src/pages/UserPublicProfile.tsx:1471
msgid "View all →"
msgstr "Tout voir →"
@@ -1036,8 +1041,8 @@ msgstr "Voir la reco →"
msgid "What makes it worth it?"
msgstr "Pourquoi on en voudrait ?"
#: src/pages/UserPublicProfile.tsx:899
#: src/pages/UserPublicProfile.tsx:948
#: src/pages/UserPublicProfile.tsx:901
#: src/pages/UserPublicProfile.tsx:950
msgid "Who am I?"
msgstr "Qui suis-je ?"
@@ -1058,11 +1063,11 @@ msgstr "Hier"
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."
#: src/pages/index/FollowedFeed.tsx:114
#: src/pages/index/FollowedFeed.tsx:115
#: src/pages/index/HotFeed.tsx:69
#: src/pages/index/JournalFeed.tsx:82
#: src/pages/index/NewFeed.tsx:69
#: src/pages/Search.tsx:249
#: src/pages/Search.tsx:240
#: src/pages/UserDumps.tsx:98
#: src/pages/UserPlaylists.tsx:422
#: src/pages/UserPlaylists.tsx:457

View File

@@ -252,42 +252,42 @@ export function Dump() {
)}
</div>
<div className="dump-header-right">
<h1 className="dump-title">{dump.title}</h1>
<div className="dump-op">
<Avatar
userId={dump.userId}
username={op?.username ?? "?"}
hasAvatar={!!op?.avatarMime}
size={22}
/>
{op
? (
<Link to={`/users/${op.username}`} className="dump-op-link">
{op.username}
</Link>
)
: <span className="dump-op-link"></span>}
<Tooltip text={dump.createdAt.toLocaleString()}>
<time
className="dump-card-date"
dateTime={dump.createdAt.toISOString()}
>
{relativeTime(dump.createdAt)}
</time>
</Tooltip>
{dump.updatedAt && (
<Tooltip text={t`Edited ${dump.updatedAt.toLocaleString()}`}>
<span className="dump-edited-label">
<Trans>edited {relativeTime(dump.updatedAt)}</Trans>
</span>
<h1 className="dump-title">{dump.title}</h1>
<div className="dump-op">
<Avatar
userId={dump.userId}
username={op?.username ?? "?"}
hasAvatar={!!op?.avatarMime}
size={22}
/>
{op
? (
<Link to={`/users/${op.username}`} className="dump-op-link">
{op.username}
</Link>
)
: <span className="dump-op-link"></span>}
<Tooltip text={dump.createdAt.toLocaleString()}>
<time
className="dump-card-date"
dateTime={dump.createdAt.toISOString()}
>
{relativeTime(dump.createdAt)}
</time>
</Tooltip>
)}
{dump.isPrivate && (
<span className="dump-card-private-badge">
<Trans>private</Trans>
</span>
)}
</div>
{dump.updatedAt && (
<Tooltip text={t`Edited ${dump.updatedAt.toLocaleString()}`}>
<span className="dump-edited-label">
<Trans>edited {relativeTime(dump.updatedAt)}</Trans>
</span>
</Tooltip>
)}
{dump.isPrivate && (
<span className="dump-card-private-badge">
<Trans>private</Trans>
</span>
)}
</div>
</div>
</div>

View File

@@ -9,7 +9,6 @@ import {
import { useLocation } from "react-router";
import { AppHeader } from "../components/AppHeader.tsx";
import { SearchBar } from "../components/SearchBar.tsx";
import { PresenceRow } from "../components/PresenceRow.tsx";
import { FeedTabBar } from "../components/FeedTabBar.tsx";
import { type FeedTab, VALID_TABS } from "../config/feedTabs.ts";
@@ -249,7 +248,6 @@ export function Index() {
<div className="header-center-slot">
<PresenceRow />
<FeedTabBar />
<SearchBar collapsible />
</div>
}
disableNew={dumpsState.status === "error"}

17
src/pages/NotFound.tsx Normal file
View File

@@ -0,0 +1,17 @@
import { Link } from "react-router";
import { Trans } from "@lingui/react/macro";
import { PageShell } from "../components/PageShell.tsx";
export function NotFound() {
return (
<PageShell centered>
<h1>404</h1>
<p>
<Trans>This page does not exist.</Trans>
</p>
<Link to="/" className="btn-primary">
<Trans>Go home</Trans>
</Link>
</PageShell>
);
}

View File

@@ -3,7 +3,7 @@ import { Link, useSearchParams } from "react-router";
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { AppHeader } from "../components/AppHeader.tsx";
import { SearchBar } from "../components/SearchBar.tsx";
import { TabBar } from "../components/TabBar.tsx";
import { DumpCard } from "../components/DumpCard.tsx";
import { PlaylistCard } from "../components/PlaylistCard.tsx";
import { ErrorCard } from "../components/ErrorCard.tsx";
@@ -178,28 +178,26 @@ export function Search() {
return (
<div className="page-shell">
<AppHeader centerSlot={<SearchBar />} />
<AppHeader />
<main className="search-page">
{q && (
<div className="search-tabs">
{(["dumps", "users", "playlists"] as Tab[]).map((tabKey) => (
<button
key={tabKey}
type="button"
className={`feed-sort-btn${tab === tabKey ? " active" : ""}`}
onClick={() => setTab(tabKey)}
>
{tabLabel(
tabKey,
tabKey === "dumps"
? dumpCount
: tabKey === "users"
? userCount
: playlistCount,
)}
</button>
))}
</div>
<TabBar
tabs={(["dumps", "users", "playlists"] as Tab[]).map((key) => ({
key,
label: tabLabel(
key,
key === "dumps"
? dumpCount
: key === "users"
? userCount
: playlistCount,
),
}))}
activeTab={tab}
onChange={setTab}
className="search-tabs"
innerClassName="search-tabs-inner"
/>
)}
{state.status === "idle" && (

View File

@@ -111,7 +111,9 @@ export function UserDumps() {
className="new-playlist-toggle"
onClick={() => setCreateModalOpen(true)}
>
+<span className="btn-new-label"><Trans> New dump</Trans></span>
+<span className="btn-new-label">
<Trans>New dump</Trans>
</span>
</button>
)}
/>

View File

@@ -53,6 +53,7 @@ import { friendlyFetchError } from "../utils/apiError.ts";
import { TextEditor } from "../components/TextEditor.tsx";
import { Markdown } from "../components/Markdown.tsx";
import { ChangePasswordModal } from "../components/ChangePasswordModal.tsx";
import { TabBar } from "../components/TabBar.tsx";
function InviteButton() {
const { authFetch } = useAuth();
@@ -145,6 +146,7 @@ type InviteTreeState =
| { status: "loaded"; entries: InviteTreeEntry[] };
type InviteTreeNode = InviteTreeEntry & { children: InviteTreeNode[] };
type ProfileTab = "dumps" | "playlists" | "followed" | "invitees" | "settings";
function buildInviteTree(
entries: InviteTreeEntry[],
@@ -282,9 +284,7 @@ export function UserPublicProfile() {
const [emailError, setEmailError] = useState<string | null>(null);
const prevMyVotesRef = useRef<Set<string> | null>(null);
const [tab, setTab] = useState<
"dumps" | "playlists" | "followed" | "invitees" | "settings"
>("dumps");
const [tab, setTab] = useState<ProfileTab>("dumps");
const [changePasswordOpen, setChangePasswordOpen] = useState(false);
const [followedState, setFollowedState] = useState<FollowedState>(null);
const [inviteTreeState, setInviteTreeState] = useState<InviteTreeState>(null);
@@ -782,107 +782,112 @@ export function UserPublicProfile() {
</label>
)}
</div>
<div>
<h1 className="profile-username">{profileUser.username}</h1>
{profileUser.invitedByUsername
? (
<p className="profile-invited-by">
<Trans>invited by</Trans>{" "}
<Link
to={`/users/${profileUser.invitedByUsername}`}
className="profile-invited-by-link"
>
@{profileUser.invitedByUsername}
</Link>
</p>
)
: (
<p className="profile-invited-by profile-invited-by--founding">
O.G.
</p>
)}
{isOwnProfile && (
emailEditing
<div className="profile-info">
<div className="profile-info-scroll">
<h1 className="profile-username">{profileUser.username}</h1>
{profileUser.invitedByUsername
? (
<form
className="profile-email-editor"
onSubmit={(e) => {
e.preventDefault();
handleEmailSave();
}}
>
<input
type="email"
className="profile-email-input"
value={emailDraft}
onChange={(e) => setEmailDraft(e.currentTarget.value)}
onKeyDown={(e) => {
if (e.key === "Escape") setEmailEditing(false);
}}
disabled={emailSaving}
autoFocus
/>
<div className="profile-email-actions">
<button
type="submit"
className="profile-email-btn profile-email-btn--save"
disabled={emailSaving || !emailDraft.trim()}
>
{emailSaving
? <Trans>Saving</Trans>
: <Trans>Save</Trans>}
</button>
<button
type="button"
className="profile-email-btn profile-email-btn--cancel"
onClick={() => setEmailEditing(false)}
disabled={emailSaving}
>
<Trans>Cancel</Trans>
</button>
</div>
{emailError && (
<ErrorCard title={t`Failed to save`} message={emailError} />
)}
</form>
)
: (
<p
className="profile-email-display"
onClick={() => {
setEmailDraft(me?.email ?? "");
setEmailError(null);
setEmailEditing(true);
}}
title="Edit email"
>
{me?.email ?? t`Add email…`}
<span className="profile-description-edit-btn" aria-hidden>
</span>
<p className="profile-invited-by">
<Trans>invited by</Trans>{" "}
<Link
to={`/users/${profileUser.invitedByUsername}`}
className="profile-invited-by-link"
>
@{profileUser.invitedByUsername}
</Link>
</p>
)
)}
{avatarError && (
<ErrorCard
title={t`Failed to update avatar`}
message={avatarError}
/>
)}
{!isOwnProfile && (
<FollowUserButton
targetUserId={profileUser.id}
targetUsername={profileUser.username}
/>
)}
{isOwnProfile && (
<div className="profile-own-actions">
<InviteButton />
<button type="button" className="btn-border" onClick={logout}>
<Trans>Log out</Trans>
</button>
</div>
)}
: (
<p className="profile-invited-by profile-invited-by--founding">
O.G.
</p>
)}
{isOwnProfile && (
emailEditing
? (
<form
className="profile-email-editor"
onSubmit={(e) => {
e.preventDefault();
handleEmailSave();
}}
>
<input
type="email"
className="profile-email-input"
value={emailDraft}
onChange={(e) => setEmailDraft(e.currentTarget.value)}
onKeyDown={(e) => {
if (e.key === "Escape") setEmailEditing(false);
}}
disabled={emailSaving}
autoFocus
/>
<div className="profile-email-actions">
<button
type="submit"
className="profile-email-btn profile-email-btn--save"
disabled={emailSaving || !emailDraft.trim()}
>
{emailSaving
? <Trans>Saving</Trans>
: <Trans>Save</Trans>}
</button>
<button
type="button"
className="profile-email-btn profile-email-btn--cancel"
onClick={() => setEmailEditing(false)}
disabled={emailSaving}
>
<Trans>Cancel</Trans>
</button>
</div>
{emailError && (
<ErrorCard
title={t`Failed to save`}
message={emailError}
/>
)}
</form>
)
: (
<p
className="profile-email-display"
onClick={() => {
setEmailDraft(me?.email ?? "");
setEmailError(null);
setEmailEditing(true);
}}
title="Edit email"
>
{me?.email ?? t`Add email…`}
<span className="profile-description-edit-btn" aria-hidden>
</span>
</p>
)
)}
{avatarError && (
<ErrorCard
title={t`Failed to update avatar`}
message={avatarError}
/>
)}
{!isOwnProfile && (
<FollowUserButton
targetUserId={profileUser.id}
targetUsername={profileUser.username}
/>
)}
{isOwnProfile && (
<div className="profile-own-actions">
<InviteButton />
<button type="button" className="btn-border" onClick={logout}>
<Trans>Log out</Trans>
</button>
</div>
)}
</div>
</div>
</div>
@@ -958,47 +963,21 @@ export function UserPublicProfile() {
</div>
)}
<div className="feed-sort-scroller profile-tabs-scroller">
<div className="profile-tabs feed-sort">
<button
type="button"
className={`feed-sort-btn${tab === "dumps" ? " active" : ""}`}
onClick={() => setTab("dumps")}
>
<Trans>Dumps</Trans>
</button>
<button
type="button"
className={`feed-sort-btn${tab === "playlists" ? " active" : ""}`}
onClick={() => setTab("playlists")}
>
<Trans>Playlists</Trans>
</button>
<button
type="button"
className={`feed-sort-btn${tab === "followed" ? " active" : ""}`}
onClick={() => setTab("followed")}
>
<Trans>Followed</Trans>
</button>
<button
type="button"
className={`feed-sort-btn${tab === "invitees" ? " active" : ""}`}
onClick={() => setTab("invitees")}
>
<Trans>Invitees</Trans>
</button>
{isOwnProfile && (
<button
type="button"
className={`feed-sort-btn${tab === "settings" ? " active" : ""}`}
onClick={() => setTab("settings")}
>
<Trans>Settings</Trans>
</button>
)}
</div>
</div>
<TabBar
tabs={[
{ key: "dumps", label: <Trans>Dumps</Trans> },
{ key: "playlists", label: <Trans>Playlists</Trans> },
{ key: "followed", label: <Trans>Followed</Trans> },
{ key: "invitees", label: <Trans>Invitees</Trans> },
...(isOwnProfile
? [{ key: "settings" as const, label: <Trans>Settings</Trans> }]
: []),
]}
activeTab={tab}
onChange={(key) => setTab(key)}
className="profile-tabs-scroller"
innerClassName="profile-tabs"
/>
{tab === "dumps" && (
<div className="profile-columns">
@@ -1329,7 +1308,9 @@ function DumpList(
className="new-playlist-toggle"
onClick={() => setCreateModalOpen(true)}
>
+<span className="btn-new-label"><Trans> New dump</Trans></span>
+<span className="btn-new-label">
<Trans>New dump</Trans>
</span>
</button>
)}
</div>

View File

@@ -1,4 +1,5 @@
import { useCallback, useEffect, useState } from "react";
import { TabBar } from "../../components/TabBar.tsx";
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { DumpCard } from "../../components/DumpCard.tsx";
@@ -328,22 +329,15 @@ export function FollowedFeed({
return (
<div className="followed-feed">
<div className="feed-sort followed-sub-nav">
<button
type="button"
className={`feed-sort-btn${section === "users" ? " active" : ""}`}
onClick={() => setSection("users")}
>
<Trans>From people</Trans>
</button>
<button
type="button"
className={`feed-sort-btn${section === "playlists" ? " active" : ""}`}
onClick={() => setSection("playlists")}
>
<Trans>From playlists</Trans>
</button>
</div>
<TabBar
tabs={[
{ key: "users", label: <Trans>From people</Trans> },
{ key: "playlists", label: <Trans>From playlists</Trans> },
]}
activeTab={section}
onChange={setSection}
innerClassName="followed-sub-nav"
/>
{section === "users" && (
<FollowedSubFeed

View File

@@ -174,13 +174,19 @@
[data-style="brutalist"] .audio-player-btn,
[data-style="brutalist"] .audio-player-vol-btn,
[data-style="brutalist"] .rich-content-thumbnail-btn,
[data-style="brutalist"] .user-menu-trigger {
[data-style="brutalist"] .user-menu-trigger,
[data-style="brutalist"] .nav-search-btn,
[data-style="brutalist"] .auth-link-btn {
border: none;
box-shadow: none;
}
[data-style="brutalist"] .user-menu-trigger:hover:not(:disabled),
[data-style="brutalist"] .user-menu-trigger:active {
[data-style="brutalist"] .user-menu-trigger:active,
[data-style="brutalist"] .nav-search-btn:hover:not(:disabled),
[data-style="brutalist"] .nav-search-btn:active,
[data-style="brutalist"] .auth-link-btn:hover:not(:disabled),
[data-style="brutalist"] .auth-link-btn:active {
transform: none;
box-shadow: none;
}