From 1078662bcbb5a8038825eb2aa89c8252d8a9229d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Wed, 14 Jan 2026 21:22:52 +0100 Subject: [PATCH 01/12] pl-fe: use links instead of buttons when appropriate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/pl-fe/src/features/account/components/header.tsx | 6 +----- .../pl-fe/src/features/event/components/event-header.tsx | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/pl-fe/src/features/account/components/header.tsx b/packages/pl-fe/src/features/account/components/header.tsx index d651ccd84..d810308f8 100644 --- a/packages/pl-fe/src/features/account/components/header.tsx +++ b/packages/pl-fe/src/features/account/components/header.tsx @@ -279,10 +279,6 @@ const Header: React.FC = ({ account }) => { unblockDomain(domain); }; - const onProfileExternal = (url: string) => { - window.open(url, '_blank'); - }; - const onAddToList = () => { openModal('LIST_ADDER', { accountId: account.id, @@ -376,7 +372,7 @@ const Header: React.FC = ({ account }) => { menu.push({ text: intl.formatMessage(messages.profileExternal, { domain }), - action: () => onProfileExternal(account.url), + href: account.url, icon: require('@phosphor-icons/core/regular/arrow-square-out.svg'), }); } diff --git a/packages/pl-fe/src/features/event/components/event-header.tsx b/packages/pl-fe/src/features/event/components/event-header.tsx index e0642db79..368d3f67e 100644 --- a/packages/pl-fe/src/features/event/components/event-header.tsx +++ b/packages/pl-fe/src/features/event/components/event-header.tsx @@ -191,10 +191,6 @@ const EventHeader: React.FC = ({ status }) => { dispatch(initReport(ReportableEntities.STATUS, account, { status })); }; - const handleModerateStatus = () => { - window.open(`/pleroma/admin/#/statuses/${status.id}/`, '_blank'); - }; - const handleToggleStatusSensitivity = () => { dispatch(toggleStatusSensitivityModal(intl, status.id, status.sensitive)); }; @@ -349,7 +345,7 @@ const EventHeader: React.FC = ({ status }) => { if (isAdmin && features.pleromaAdminStatuses) { menu.push({ text: intl.formatMessage(messages.adminStatus), - action: handleModerateStatus, + href: `/pleroma/admin/#/statuses/${status.id}/`, icon: require('@phosphor-icons/core/regular/pencil-simple.svg'), }); } From 4c3e75b334843f827800c3af74c52f099cd7298e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Thu, 15 Jan 2026 18:49:50 +0100 Subject: [PATCH 02/12] support WS_DISABLED env value for pl-fe dev MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/pl-fe/vite.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/pl-fe/vite.config.ts b/packages/pl-fe/vite.config.ts index 96ae243c8..fbb3287f4 100644 --- a/packages/pl-fe/vite.config.ts +++ b/packages/pl-fe/vite.config.ts @@ -31,6 +31,7 @@ const config = defineConfig(({ command }) => ({ server: { port: Number(process.env.PORT ?? 7312), hmr: process.env.HMR_DISABLED === 'true' ? false : undefined, + ws: process.env.WS_DISABLED === 'true' ? false : undefined, }, plugins: [ checker({ typescript: true }), From a4aaacb31f733c30d8677d5be9c6de6d3b59d00a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Thu, 15 Jan 2026 18:52:02 +0100 Subject: [PATCH 03/12] pl-fe: error component fix, typo fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/pl-fe/src/components/site-error.tsx | 2 +- packages/pl-fe/vite.config.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/pl-fe/src/components/site-error.tsx b/packages/pl-fe/src/components/site-error.tsx index 02fed7fc7..3dbd01226 100644 --- a/packages/pl-fe/src/components/site-error.tsx +++ b/packages/pl-fe/src/components/site-error.tsx @@ -37,7 +37,7 @@ const SiteError: ErrorRouteComponent = ({ error, info }) => { const sentryEnabled = Boolean(sentryDsn); const isProduction = NODE_ENV === 'production'; - const errorText = String(error) + info?.componentStack; + const errorText = String(error) + (info?.componentStack || ''); const clearCookies: React.MouseEventHandler = (e) => { localStorage.clear(); diff --git a/packages/pl-fe/vite.config.ts b/packages/pl-fe/vite.config.ts index fbb3287f4..11ea2a2a2 100644 --- a/packages/pl-fe/vite.config.ts +++ b/packages/pl-fe/vite.config.ts @@ -65,7 +65,7 @@ const config = defineConfig(({ command }) => ({ manifest: { name: 'pl-fe', short_name: 'pl-fe', - description: 'Mastodon-compatible social media front-endx', + description: 'Mastodon-compatible social media front-end', icons: [ { src: '/instance/images/logo.png', From 4082cf47070a9ae926f64dd25c3f575d11e9608b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Thu, 15 Jan 2026 18:57:01 +0100 Subject: [PATCH 04/12] pl-fe: css workaround for Ladybird MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/pl-fe/src/styles/new/layout.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/pl-fe/src/styles/new/layout.scss b/packages/pl-fe/src/styles/new/layout.scss index 9be7f542e..383eb21eb 100644 --- a/packages/pl-fe/src/styles/new/layout.scss +++ b/packages/pl-fe/src/styles/new/layout.scss @@ -306,7 +306,8 @@ body { z-index: 1; &__content { - @apply mx-auto w-full max-w-3xl grow black:pt-0 sm:px-6 sm:pt-4 md:gap-4 md:px-8; + // FIXME: min-h-[100dvh] is a workaround for some Ladybird bug + @apply mx-auto w-full max-w-3xl grow black:pt-0 sm:px-6 sm:pt-4 md:gap-4 md:px-8 min-h-[100dvh]; &--full-width { @apply flex md:max-w-full; From cb00bf85cc00c0c66d40a8805e5685298274ba45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Fri, 16 Jan 2026 00:40:43 +0100 Subject: [PATCH 05/12] pl-fe: improve local users domain display with displayFqn === true MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/pl-fe/src/components/account.tsx | 5 ++--- .../ui/components/panels/user-panel.tsx | 8 +++----- packages/pl-fe/src/hooks/use-acct.ts | 20 +++++++++++++++++++ packages/pl-fe/src/layouts/profile-layout.tsx | 7 +++---- 4 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 packages/pl-fe/src/hooks/use-acct.ts diff --git a/packages/pl-fe/src/components/account.tsx b/packages/pl-fe/src/components/account.tsx index 607a3f076..7dfeb31de 100644 --- a/packages/pl-fe/src/components/account.tsx +++ b/packages/pl-fe/src/components/account.tsx @@ -14,10 +14,9 @@ import Text from 'pl-fe/components/ui/text'; import VerificationBadge from 'pl-fe/components/verification-badge'; import Emojify from 'pl-fe/features/emoji/emojify'; import ActionButton from 'pl-fe/features/ui/components/action-button'; +import { useAcct } from 'pl-fe/hooks/use-acct'; import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; import { useSettings } from 'pl-fe/stores/settings'; -import { getAcct } from 'pl-fe/utils/accounts'; -import { displayFqn } from 'pl-fe/utils/state'; import Badge from './badge'; import { ParsedContent } from './parsed-content'; @@ -148,7 +147,7 @@ const Account = ({ const [style, setStyle] = useState({}); const me = useAppSelector((state) => state.me); - const username = useAppSelector((state) => account ? getAcct(account, displayFqn(state)) : null); + const username = useAcct(account); const { disableUserProvidedMedia } = useSettings(); const handleAction = () => { diff --git a/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx b/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx index 1f89e7620..aaced57a1 100644 --- a/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx +++ b/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx @@ -11,11 +11,9 @@ import Stack from 'pl-fe/components/ui/stack'; import Text from 'pl-fe/components/ui/text'; import VerificationBadge from 'pl-fe/components/verification-badge'; import Emojify from 'pl-fe/features/emoji/emojify'; -import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; +import { useAcct } from 'pl-fe/hooks/use-acct'; import { useSettings } from 'pl-fe/stores/settings'; -import { getAcct } from 'pl-fe/utils/accounts'; import { shortNumberFormat } from 'pl-fe/utils/numbers'; -import { displayFqn } from 'pl-fe/utils/state'; const messages = defineMessages({ account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' }, @@ -32,7 +30,7 @@ const UserPanel: React.FC = ({ accountId, action, badges, domain }) const intl = useIntl(); const { demetricator, disableUserProvidedMedia } = useSettings(); const { account } = useAccount(accountId); - const fqn = useAppSelector((state) => displayFqn(state)); + const displayedAcct = useAcct(account); if (!account) return null; const acct = !account.acct.includes('@') && domain ? `${account.acct}@${domain}` : account.acct; @@ -96,7 +94,7 @@ const UserPanel: React.FC = ({ accountId, action, badges, domain }) - @{getAcct(account, fqn)} + @{displayedAcct} {account.locked && ( diff --git a/packages/pl-fe/src/hooks/use-acct.ts b/packages/pl-fe/src/hooks/use-acct.ts new file mode 100644 index 000000000..ce3b112ea --- /dev/null +++ b/packages/pl-fe/src/hooks/use-acct.ts @@ -0,0 +1,20 @@ +import { displayFqn } from 'pl-fe/utils/state'; + +import { useAppSelector } from './use-app-selector'; +import { useInstance } from './use-instance'; + +import type { Account } from 'pl-api'; + +const useAcct = (account?: Pick): string | undefined => { + const fqn = useAppSelector((state) => displayFqn(state)); + const instance = useInstance(); + + if (!account) return; + if (!fqn) return account.acct; + if (account.local === false) return account.fqn; + return `${account.acct}@${instance.domain}`; +}; + +export { + useAcct, +}; diff --git a/packages/pl-fe/src/layouts/profile-layout.tsx b/packages/pl-fe/src/layouts/profile-layout.tsx index b39cfaa94..1694b1d2b 100644 --- a/packages/pl-fe/src/layouts/profile-layout.tsx +++ b/packages/pl-fe/src/layouts/profile-layout.tsx @@ -19,10 +19,9 @@ import { PinnedAccountsPanel, AccountNotePanel, } from 'pl-fe/features/ui/util/async-components'; +import { useAcct } from 'pl-fe/hooks/use-acct'; import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; import { useFeatures } from 'pl-fe/hooks/use-features'; -import { usePlFeConfig } from 'pl-fe/hooks/use-pl-fe-config'; -import { getAcct } from 'pl-fe/utils/accounts'; /** Layout to display a user's profile. */ const ProfileLayout: React.FC = () => { @@ -33,7 +32,7 @@ const ProfileLayout: React.FC = () => { const me = useAppSelector(state => state.me); const features = useFeatures(); - const { displayFqn } = usePlFeConfig(); + const acct = useAcct(account); if (isUnauthorized) { localStorage.setItem('plfe:redirect_uri', location.href); @@ -101,7 +100,7 @@ const ProfileLayout: React.FC = () => { )} - +
From d49eb6e1697bb68722a8c25cee002e1b42d7de82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Fri, 16 Jan 2026 00:45:45 +0100 Subject: [PATCH 06/12] Revert "pl-fe: improve local users domain display with displayFqn === true" This reverts commit cb00bf85cc00c0c66d40a8805e5685298274ba45. --- packages/pl-fe/src/components/account.tsx | 5 +++-- .../ui/components/panels/user-panel.tsx | 8 +++++--- packages/pl-fe/src/hooks/use-acct.ts | 20 ------------------- packages/pl-fe/src/layouts/profile-layout.tsx | 7 ++++--- 4 files changed, 12 insertions(+), 28 deletions(-) delete mode 100644 packages/pl-fe/src/hooks/use-acct.ts diff --git a/packages/pl-fe/src/components/account.tsx b/packages/pl-fe/src/components/account.tsx index 7dfeb31de..607a3f076 100644 --- a/packages/pl-fe/src/components/account.tsx +++ b/packages/pl-fe/src/components/account.tsx @@ -14,9 +14,10 @@ import Text from 'pl-fe/components/ui/text'; import VerificationBadge from 'pl-fe/components/verification-badge'; import Emojify from 'pl-fe/features/emoji/emojify'; import ActionButton from 'pl-fe/features/ui/components/action-button'; -import { useAcct } from 'pl-fe/hooks/use-acct'; import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; import { useSettings } from 'pl-fe/stores/settings'; +import { getAcct } from 'pl-fe/utils/accounts'; +import { displayFqn } from 'pl-fe/utils/state'; import Badge from './badge'; import { ParsedContent } from './parsed-content'; @@ -147,7 +148,7 @@ const Account = ({ const [style, setStyle] = useState({}); const me = useAppSelector((state) => state.me); - const username = useAcct(account); + const username = useAppSelector((state) => account ? getAcct(account, displayFqn(state)) : null); const { disableUserProvidedMedia } = useSettings(); const handleAction = () => { diff --git a/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx b/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx index aaced57a1..1f89e7620 100644 --- a/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx +++ b/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx @@ -11,9 +11,11 @@ import Stack from 'pl-fe/components/ui/stack'; import Text from 'pl-fe/components/ui/text'; import VerificationBadge from 'pl-fe/components/verification-badge'; import Emojify from 'pl-fe/features/emoji/emojify'; -import { useAcct } from 'pl-fe/hooks/use-acct'; +import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; import { useSettings } from 'pl-fe/stores/settings'; +import { getAcct } from 'pl-fe/utils/accounts'; import { shortNumberFormat } from 'pl-fe/utils/numbers'; +import { displayFqn } from 'pl-fe/utils/state'; const messages = defineMessages({ account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' }, @@ -30,7 +32,7 @@ const UserPanel: React.FC = ({ accountId, action, badges, domain }) const intl = useIntl(); const { demetricator, disableUserProvidedMedia } = useSettings(); const { account } = useAccount(accountId); - const displayedAcct = useAcct(account); + const fqn = useAppSelector((state) => displayFqn(state)); if (!account) return null; const acct = !account.acct.includes('@') && domain ? `${account.acct}@${domain}` : account.acct; @@ -94,7 +96,7 @@ const UserPanel: React.FC = ({ accountId, action, badges, domain }) - @{displayedAcct} + @{getAcct(account, fqn)} {account.locked && ( diff --git a/packages/pl-fe/src/hooks/use-acct.ts b/packages/pl-fe/src/hooks/use-acct.ts deleted file mode 100644 index ce3b112ea..000000000 --- a/packages/pl-fe/src/hooks/use-acct.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { displayFqn } from 'pl-fe/utils/state'; - -import { useAppSelector } from './use-app-selector'; -import { useInstance } from './use-instance'; - -import type { Account } from 'pl-api'; - -const useAcct = (account?: Pick): string | undefined => { - const fqn = useAppSelector((state) => displayFqn(state)); - const instance = useInstance(); - - if (!account) return; - if (!fqn) return account.acct; - if (account.local === false) return account.fqn; - return `${account.acct}@${instance.domain}`; -}; - -export { - useAcct, -}; diff --git a/packages/pl-fe/src/layouts/profile-layout.tsx b/packages/pl-fe/src/layouts/profile-layout.tsx index 1694b1d2b..b39cfaa94 100644 --- a/packages/pl-fe/src/layouts/profile-layout.tsx +++ b/packages/pl-fe/src/layouts/profile-layout.tsx @@ -19,9 +19,10 @@ import { PinnedAccountsPanel, AccountNotePanel, } from 'pl-fe/features/ui/util/async-components'; -import { useAcct } from 'pl-fe/hooks/use-acct'; import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; import { useFeatures } from 'pl-fe/hooks/use-features'; +import { usePlFeConfig } from 'pl-fe/hooks/use-pl-fe-config'; +import { getAcct } from 'pl-fe/utils/accounts'; /** Layout to display a user's profile. */ const ProfileLayout: React.FC = () => { @@ -32,7 +33,7 @@ const ProfileLayout: React.FC = () => { const me = useAppSelector(state => state.me); const features = useFeatures(); - const acct = useAcct(account); + const { displayFqn } = usePlFeConfig(); if (isUnauthorized) { localStorage.setItem('plfe:redirect_uri', location.href); @@ -100,7 +101,7 @@ const ProfileLayout: React.FC = () => { )} - +
From 9b3eb090a7a125452f8e215bdb37ab667d946ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Fri, 16 Jan 2026 00:50:31 +0100 Subject: [PATCH 07/12] Reapply "pl-fe: improve local users domain display with displayFqn === true" This reverts commit d49eb6e1697bb68722a8c25cee002e1b42d7de82. --- packages/pl-fe/src/components/account.tsx | 5 ++--- .../ui/components/panels/user-panel.tsx | 8 +++----- packages/pl-fe/src/hooks/use-acct.ts | 20 +++++++++++++++++++ packages/pl-fe/src/layouts/profile-layout.tsx | 7 +++---- 4 files changed, 28 insertions(+), 12 deletions(-) create mode 100644 packages/pl-fe/src/hooks/use-acct.ts diff --git a/packages/pl-fe/src/components/account.tsx b/packages/pl-fe/src/components/account.tsx index 607a3f076..7dfeb31de 100644 --- a/packages/pl-fe/src/components/account.tsx +++ b/packages/pl-fe/src/components/account.tsx @@ -14,10 +14,9 @@ import Text from 'pl-fe/components/ui/text'; import VerificationBadge from 'pl-fe/components/verification-badge'; import Emojify from 'pl-fe/features/emoji/emojify'; import ActionButton from 'pl-fe/features/ui/components/action-button'; +import { useAcct } from 'pl-fe/hooks/use-acct'; import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; import { useSettings } from 'pl-fe/stores/settings'; -import { getAcct } from 'pl-fe/utils/accounts'; -import { displayFqn } from 'pl-fe/utils/state'; import Badge from './badge'; import { ParsedContent } from './parsed-content'; @@ -148,7 +147,7 @@ const Account = ({ const [style, setStyle] = useState({}); const me = useAppSelector((state) => state.me); - const username = useAppSelector((state) => account ? getAcct(account, displayFqn(state)) : null); + const username = useAcct(account); const { disableUserProvidedMedia } = useSettings(); const handleAction = () => { diff --git a/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx b/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx index 1f89e7620..aaced57a1 100644 --- a/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx +++ b/packages/pl-fe/src/features/ui/components/panels/user-panel.tsx @@ -11,11 +11,9 @@ import Stack from 'pl-fe/components/ui/stack'; import Text from 'pl-fe/components/ui/text'; import VerificationBadge from 'pl-fe/components/verification-badge'; import Emojify from 'pl-fe/features/emoji/emojify'; -import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; +import { useAcct } from 'pl-fe/hooks/use-acct'; import { useSettings } from 'pl-fe/stores/settings'; -import { getAcct } from 'pl-fe/utils/accounts'; import { shortNumberFormat } from 'pl-fe/utils/numbers'; -import { displayFqn } from 'pl-fe/utils/state'; const messages = defineMessages({ account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' }, @@ -32,7 +30,7 @@ const UserPanel: React.FC = ({ accountId, action, badges, domain }) const intl = useIntl(); const { demetricator, disableUserProvidedMedia } = useSettings(); const { account } = useAccount(accountId); - const fqn = useAppSelector((state) => displayFqn(state)); + const displayedAcct = useAcct(account); if (!account) return null; const acct = !account.acct.includes('@') && domain ? `${account.acct}@${domain}` : account.acct; @@ -96,7 +94,7 @@ const UserPanel: React.FC = ({ accountId, action, badges, domain }) - @{getAcct(account, fqn)} + @{displayedAcct} {account.locked && ( diff --git a/packages/pl-fe/src/hooks/use-acct.ts b/packages/pl-fe/src/hooks/use-acct.ts new file mode 100644 index 000000000..ce3b112ea --- /dev/null +++ b/packages/pl-fe/src/hooks/use-acct.ts @@ -0,0 +1,20 @@ +import { displayFqn } from 'pl-fe/utils/state'; + +import { useAppSelector } from './use-app-selector'; +import { useInstance } from './use-instance'; + +import type { Account } from 'pl-api'; + +const useAcct = (account?: Pick): string | undefined => { + const fqn = useAppSelector((state) => displayFqn(state)); + const instance = useInstance(); + + if (!account) return; + if (!fqn) return account.acct; + if (account.local === false) return account.fqn; + return `${account.acct}@${instance.domain}`; +}; + +export { + useAcct, +}; diff --git a/packages/pl-fe/src/layouts/profile-layout.tsx b/packages/pl-fe/src/layouts/profile-layout.tsx index b39cfaa94..1694b1d2b 100644 --- a/packages/pl-fe/src/layouts/profile-layout.tsx +++ b/packages/pl-fe/src/layouts/profile-layout.tsx @@ -19,10 +19,9 @@ import { PinnedAccountsPanel, AccountNotePanel, } from 'pl-fe/features/ui/util/async-components'; +import { useAcct } from 'pl-fe/hooks/use-acct'; import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; import { useFeatures } from 'pl-fe/hooks/use-features'; -import { usePlFeConfig } from 'pl-fe/hooks/use-pl-fe-config'; -import { getAcct } from 'pl-fe/utils/accounts'; /** Layout to display a user's profile. */ const ProfileLayout: React.FC = () => { @@ -33,7 +32,7 @@ const ProfileLayout: React.FC = () => { const me = useAppSelector(state => state.me); const features = useFeatures(); - const { displayFqn } = usePlFeConfig(); + const acct = useAcct(account); if (isUnauthorized) { localStorage.setItem('plfe:redirect_uri', location.href); @@ -101,7 +100,7 @@ const ProfileLayout: React.FC = () => { )} - +
From 6ff3cd1fd311011868c8d7fb7f90b818ae7f9349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Fri, 16 Jan 2026 00:54:09 +0100 Subject: [PATCH 08/12] pl-fe: maybe fix useAcct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/pl-fe/src/hooks/use-acct.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/pl-fe/src/hooks/use-acct.ts b/packages/pl-fe/src/hooks/use-acct.ts index ce3b112ea..7f05392df 100644 --- a/packages/pl-fe/src/hooks/use-acct.ts +++ b/packages/pl-fe/src/hooks/use-acct.ts @@ -1,18 +1,27 @@ +import { useMemo } from 'react'; + import { displayFqn } from 'pl-fe/utils/state'; import { useAppSelector } from './use-app-selector'; import { useInstance } from './use-instance'; +import { useOwnAccount } from './use-own-account'; import type { Account } from 'pl-api'; -const useAcct = (account?: Pick): string | undefined => { +const useAcct = (account?: Pick): string | undefined => { const fqn = useAppSelector((state) => displayFqn(state)); const instance = useInstance(); - if (!account) return; - if (!fqn) return account.acct; - if (account.local === false) return account.fqn; - return `${account.acct}@${instance.domain}`; + const localUrl = useOwnAccount().account?.url; + + return useMemo(() => { + if (!account) return; + if (!fqn) return account.acct; + const localHost = localUrl ? new URL(localUrl).host : null; + const otherHost = new URL(account.url).host; + if (account.local === false || (localHost && localHost !== otherHost)) return account.fqn; + return `${account.acct}@${instance.domain}`; + }, [account?.acct, fqn, instance.domain, localUrl]); }; export { From 63d226a2069f23ce0363dc3a2478c28d2cafe6fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Fri, 16 Jan 2026 01:02:03 +0100 Subject: [PATCH 09/12] pl-fe: further change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- .../features/ui/components/panels/profile-info-panel.tsx | 6 +++--- packages/pl-fe/src/hooks/use-acct.ts | 1 - packages/pl-fe/src/utils/accounts.ts | 5 ----- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/pl-fe/src/features/ui/components/panels/profile-info-panel.tsx b/packages/pl-fe/src/features/ui/components/panels/profile-info-panel.tsx index ee930ffdb..e11e97e23 100644 --- a/packages/pl-fe/src/features/ui/components/panels/profile-info-panel.tsx +++ b/packages/pl-fe/src/features/ui/components/panels/profile-info-panel.tsx @@ -12,8 +12,8 @@ import Icon from 'pl-fe/components/ui/icon'; import Stack from 'pl-fe/components/ui/stack'; import Text from 'pl-fe/components/ui/text'; import Emojify from 'pl-fe/features/emoji/emojify'; +import { useAcct } from 'pl-fe/hooks/use-acct'; import { useAppSelector } from 'pl-fe/hooks/use-app-selector'; -import { usePlFeConfig } from 'pl-fe/hooks/use-pl-fe-config'; import { accountScrobbleQueryOptions } from 'pl-fe/queries/accounts/account-scrobble'; import { capitalize } from 'pl-fe/utils/strings'; @@ -40,7 +40,7 @@ interface IProfileInfoPanel { /** User profile metadata, such as location, birthday, etc. */ const ProfileInfoPanel: React.FC = ({ account, username }) => { const intl = useIntl(); - const { displayFqn } = usePlFeConfig(); + const acct = useAcct(account); const me = useAppSelector(state => state.me); const ownAccount = account?.id === me; @@ -152,7 +152,7 @@ const ProfileInfoPanel: React.FC = ({ account, username }) => - @{displayFqn ? account.fqn : account.acct} + @{acct} {account.locked && ( diff --git a/packages/pl-fe/src/hooks/use-acct.ts b/packages/pl-fe/src/hooks/use-acct.ts index 7f05392df..521d81cf0 100644 --- a/packages/pl-fe/src/hooks/use-acct.ts +++ b/packages/pl-fe/src/hooks/use-acct.ts @@ -11,7 +11,6 @@ import type { Account } from 'pl-api'; const useAcct = (account?: Pick): string | undefined => { const fqn = useAppSelector((state) => displayFqn(state)); const instance = useInstance(); - const localUrl = useOwnAccount().account?.url; return useMemo(() => { diff --git a/packages/pl-fe/src/utils/accounts.ts b/packages/pl-fe/src/utils/accounts.ts index 7c0c72018..83e1a076d 100644 --- a/packages/pl-fe/src/utils/accounts.ts +++ b/packages/pl-fe/src/utils/accounts.ts @@ -22,12 +22,7 @@ const getBaseURL = (account: Pick): string => { } }; -const getAcct = (account: Pick, displayFqn: boolean): string => ( - displayFqn === true ? account.fqn : account.acct -); - export { getDomain, getBaseURL, - getAcct, }; From b97ce74ec2f827f2a1801f49bbc9e9d3dc9ca2a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Fri, 16 Jan 2026 01:56:43 +0100 Subject: [PATCH 10/12] pl-fe: try to fix media modal behavior after recent ports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- .../src/features/ui/components/modal-root.tsx | 1 - packages/pl-fe/src/modals/media-modal.tsx | 91 +++++++++++++------ .../pl-fe/src/styles/components/modal.scss | 20 ++++ 3 files changed, 84 insertions(+), 28 deletions(-) diff --git a/packages/pl-fe/src/features/ui/components/modal-root.tsx b/packages/pl-fe/src/features/ui/components/modal-root.tsx index bc92d1502..6fa0cd3c8 100644 --- a/packages/pl-fe/src/features/ui/components/modal-root.tsx +++ b/packages/pl-fe/src/features/ui/components/modal-root.tsx @@ -67,7 +67,6 @@ const ModalRoot: React.FC = () => { const index = modals.length - 1; const onClickClose = (type?: ModalType, all?: boolean) => { - console.log('Closing modal:', type, all); switch (type) { case 'COMPOSE': dispatch(cancelReplyCompose()); diff --git a/packages/pl-fe/src/modals/media-modal.tsx b/packages/pl-fe/src/modals/media-modal.tsx index 69f29970d..87e62a6de 100644 --- a/packages/pl-fe/src/modals/media-modal.tsx +++ b/packages/pl-fe/src/modals/media-modal.tsx @@ -1,8 +1,9 @@ +import { animated, useSpring } from '@react-spring/web'; import { Link } from '@tanstack/react-router'; +import { useDrag } from '@use-gesture/react'; import clsx from 'clsx'; -import React, { type RefCallback, useCallback, useEffect, useState } from 'react'; +import React, { type RefCallback, useCallback, useEffect, useMemo, useState } from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; -import ReactSwipeableViews from 'react-swipeable-views'; import { fetchStatusWithContext } from 'pl-fe/actions/statuses'; import ExtendedVideoPlayer from 'pl-fe/components/extended-video-player'; @@ -25,6 +26,8 @@ import { makeGetStatus } from 'pl-fe/selectors'; import type { MediaAttachment } from 'pl-api'; import type { BaseModalProps } from 'pl-fe/features/ui/components/modal-root'; +const MIN_SWIPE_DISTANCE = 400; + const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, expand: { id: 'lightbox.expand', defaultMessage: 'Expand' }, @@ -36,19 +39,6 @@ const messages = defineMessages({ download: { id: 'video.download', defaultMessage: 'Download file' }, }); -// you can't use 100vh, because the viewport height is taller -// than the visible part of the document in some mobile -// browsers when its address bar is visible. -// https://developers.google.com/web/updates/2016/12/url-bar-resizing -const swipeableViewsStyle: React.CSSProperties = { - width: '100%', - height: '100%', -}; - -const containerStyle: React.CSSProperties = { - alignItems: 'center', // center vertically -}; - interface MediaModalProps { media?: Array; statusId?: string; @@ -77,6 +67,32 @@ const MediaModal: React.FC = (props) => { const [navigationHidden, setNavigationHidden] = useState(false); const [isFullScreen, setIsFullScreen] = useState(!status); + const [wrapperStyles, api] = useSpring(() => ({ + x: `-${index * 100}%`, + })); + + const handleChangeIndex = useCallback( + (newIndex: number, animate = false) => { + if (newIndex < 0) { + newIndex = media.length + newIndex; + } else if (newIndex >= media.length) { + newIndex = newIndex % media.length; + } + setIndex(newIndex); + setZoomedIn(false); + if (animate) { + void api.start({ x: `calc(-${newIndex * 100}% + 0px)` }); + } + }, + [api, media.length], + ); + const handlePrevClick = useCallback(() => { + handleChangeIndex(index - 1, true); + }, [handleChangeIndex, index]); + const handleNextClick = useCallback(() => { + handleChangeIndex(index + 1, true); + }, [handleChangeIndex, index]); + const [viewportDimensions, setViewportDimensions] = useState<{ width: number; height: number; @@ -93,10 +109,6 @@ const MediaModal: React.FC = (props) => { const hasMultipleImages = media.length > 1; - const handleSwipe = (index: number) => setIndex(index % media.length); - const handleNextClick = () => setIndex((index + 1) % media.length); - const handlePrevClick = () => setIndex((media.length + index - 1) % media.length); - const navigationHiddenClassName = navigationHidden ? 'pointer-events-none opacity-0' : ''; const handleKeyDown = (e: KeyboardEvent) => { @@ -114,6 +126,30 @@ const MediaModal: React.FC = (props) => { } }; + const bind = useDrag( + ({ active, movement: [mx], direction: [xDir], cancel }) => { + // Disable swipe when zoomed in. + if (zoomedIn) { + return; + } + + // If dragging and swipe distance is enough, change the index. + if ( + active && + Math.abs(mx) > Math.min(window.innerWidth / 4, MIN_SWIPE_DISTANCE) + ) { + handleChangeIndex(index - xDir); + cancel(); + } + // Set the x position via calc to ensure proper centering regardless of screen size. + const x = active ? mx : 0; + void api.start({ + x: `calc(-${index * 100}% + ${x}px)`, + }); + }, + { pointer: { capture: false } }, + ); + const handleDownload = () => { const mediaItem = hasMultipleImages ? media[index as number] : media[0]; window.open(mediaItem?.url); @@ -133,7 +169,7 @@ const MediaModal: React.FC = (props) => { setZoomedIn((prev) => !prev); }, []); - const content = media.map((attachment, i) => { + const content = useMemo(() => media.map((attachment, i) => { let width: number | undefined, height: number | undefined; if (attachment.type === 'image' || attachment.type === 'gifv' || attachment.type === 'video') { width = (attachment.meta?.original?.width); @@ -209,7 +245,7 @@ const MediaModal: React.FC = (props) => { } return null; - }); + }), [media.length, index, zoomedIn, handleZoomClick]); // Load data. useEffect(() => { @@ -313,6 +349,7 @@ const MediaModal: React.FC = (props) => { {/* Height based on height of top/bottom bars */}
{hasMultipleImages && ( @@ -328,14 +365,14 @@ const MediaModal: React.FC = (props) => {
)} - onClose()} > {content} - + {hasMultipleImages && (
diff --git a/packages/pl-fe/src/styles/components/modal.scss b/packages/pl-fe/src/styles/components/modal.scss index 1d43bfaaa..ed0d9a701 100644 --- a/packages/pl-fe/src/styles/components/modal.scss +++ b/packages/pl-fe/src/styles/components/modal.scss @@ -1,4 +1,6 @@ .media-modal { + touch-action: pan-y; + .audio-player.detailed, .extended-video-player { @apply flex items-center justify-center; @@ -15,5 +17,23 @@ @apply max-w-full max-h-[80%]; } } + + &__closer { + display: flex; + position: absolute; + top: 0; + inset-inline-start: 0; + inset-inline-end: 0; + bottom: 0; + + > div { + flex-shrink: 0; + overflow: auto; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + } + } } From 048556b0a54eb4c7e055a792b9b97b4c31b497b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Fri, 16 Jan 2026 10:34:13 +0100 Subject: [PATCH 11/12] pl-api: Akkoma /api/v1/instance workaround MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/pl-api/lib/entities/instance.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/pl-api/lib/entities/instance.ts b/packages/pl-api/lib/entities/instance.ts index 13c836629..82db2793c 100644 --- a/packages/pl-api/lib/entities/instance.ts +++ b/packages/pl-api/lib/entities/instance.ts @@ -40,6 +40,13 @@ const instanceV1ToV2 = (data: any) => { ...instance } = v.parse(instanceV1Schema, data); + let domain = uri; + try { + domain = new URL(uri).host; + } catch { + // Ignore invalid URL + } + return { ...instance, account_domain: instance.account_domain || uri, @@ -74,7 +81,7 @@ const instanceV1ToV2 = (data: any) => { email: email, }, description: short_description || description, - domain: uri, + domain, pleroma: { ...pleroma, metadata: { From f87639e75a1a18abfe5213c0436b9826a803f3ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Fri, 16 Jan 2026 21:03:56 +0100 Subject: [PATCH 12/12] pl-api: handle version string for Egregoros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/pl-api/lib/entities/instance.ts | 15 +++++++-------- packages/pl-api/lib/features.ts | 11 ++++++++++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/pl-api/lib/entities/instance.ts b/packages/pl-api/lib/entities/instance.ts index 82db2793c..4945ba9c0 100644 --- a/packages/pl-api/lib/entities/instance.ts +++ b/packages/pl-api/lib/entities/instance.ts @@ -97,20 +97,19 @@ const instanceV1ToV2 = (data: any) => { }; }; +const software: Array<[string, string]> = [['takahe', 'Takahe'], ['neodb', 'NeoDB'], ['egregoros', 'Egregoros']]; + const fixVersion = (version: string) => { // Handle Mastodon release candidates if (new RegExp(/[0-9.]+rc[0-9]+/g).test(version)) { return version.split('rc').join('-rc'); } - // Set Takahē version to a Pleroma-like string - if (version.startsWith('takahe/')) { - return `0.0.0 (compatible; Takahe ${version.slice(7)})`; - } - - // Set NeoDB version to a Pleroma-like string - if (version.startsWith('neodb/')) { - return `0.0.0 (compatible; NeoDB ${version.slice(7)})`; + // Set Takahē and NeoDB versions to a Pleroma-like string + for (const [key, name] of software) { + if (version.startsWith(`${key}/`)) { + return `0.0.0 (compatible; ${name} ${version.slice(key.length + 1)})`; + } } const wordPressMatch = WORDPRESS_REGEX.exec(version); diff --git a/packages/pl-api/lib/features.ts b/packages/pl-api/lib/features.ts index 919e04a0c..b336cc23d 100644 --- a/packages/pl-api/lib/features.ts +++ b/packages/pl-api/lib/features.ts @@ -16,6 +16,14 @@ const any = (arr: Array): boolean => arr.some(Boolean); */ const DITTO = 'Ditto'; +/** + * Egregoros, a fediverse server pulled from the akashic records [TODO: fact check this claim ~nicole]. + * + * @category Software + * @see {@link https://github.com/egregoros-social/egregoros} + */ +const EGREGOROS = 'Egregoros'; + /** * Firefish, a fork of Misskey. Formerly known as Calckey. * @@ -164,7 +172,7 @@ const AKKOMA = 'Akkoma'; const GLITCH = 'glitch'; /** - * glitch-soc, fork of Mastodon that provides local posting and a wider range of content types. + * Hometown, fork of Mastodon that provides local posting and a wider range of content types. * * @category Software * @see {@link https://github.com/hometown-fork/hometown} @@ -1887,6 +1895,7 @@ const parseVersion = (version: string): Backend => { export { AKKOMA, DITTO, + EGREGOROS, FIREFISH, FRIENDICA, GOTOSOCIAL,