nicolium: allow pinning/unpinning instances on mobile

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2026-03-18 19:05:42 +01:00
parent c9612b7ab2
commit b5c877e984
4 changed files with 37 additions and 72 deletions

View File

@ -1,61 +0,0 @@
import iconPushPinSlash from '@phosphor-icons/core/regular/push-pin-slash.svg';
import iconPushPin from '@phosphor-icons/core/regular/push-pin.svg';
import React from 'react';
import { useIntl, defineMessages } from 'react-intl';
import { changeSetting } from '@/actions/settings';
import Widget from '@/components/ui/widget';
import { useRemoteInstance } from '@/queries/instance/use-remote-instance';
import { useSettings } from '@/stores/settings';
const messages = defineMessages({
pinHost: { id: 'remote_instance.pin_host', defaultMessage: 'Pin {host}' },
unpinHost: { id: 'remote_instance.unpin_host', defaultMessage: 'Unpin {host}' },
});
interface IInstanceInfoPanel {
/** Hostname (domain) of the remote instance, eg "gleasonator.com" */
host: string;
}
/** Widget that displays information about a remote instance to users. */
const InstanceInfoPanel: React.FC<IInstanceInfoPanel> = ({ host }) => {
const intl = useIntl();
const settings = useSettings();
const remoteInstance = useRemoteInstance(host);
const pinnedHosts = settings.remote_timeline.pinnedHosts;
const isPinned = pinnedHosts.includes(host);
const pinHost = (host: string) => {
changeSetting(['remote_timeline', 'pinnedHosts'], [...pinnedHosts, host]);
};
const unpinHost = (host: string) => {
changeSetting(
['remote_timeline', 'pinnedHosts'],
pinnedHosts.filter((value) => value !== host),
);
};
const handlePinHost = () => {
if (!isPinned) {
pinHost(host);
} else {
unpinHost(host);
}
};
if (!remoteInstance) return null;
return (
<Widget
title={remoteInstance.host}
onActionClick={handlePinHost}
actionIcon={isPinned ? iconPushPinSlash : iconPushPin}
actionTitle={intl.formatMessage(isPinned ? messages.unpinHost : messages.pinHost, { host })}
/>
);
};
export { InstanceInfoPanel as default };

View File

@ -10,7 +10,6 @@ export const CryptoDonatePanel = lazy(
() => import('@/features/crypto-donate/components/crypto-donate-panel'),
);
export const GroupMediaPanel = lazy(() => import('@/components/panels/group-media-panel'));
export const InstanceInfoPanel = lazy(() => import('@/components/panels/instance-info-panel'));
export const InstanceModerationPanel = lazy(
() => import('@/components/panels/instance-moderation-panel'),
);

View File

@ -4,11 +4,7 @@ import React from 'react';
import LinkFooter from '@/components/navigation/link-footer';
import Layout from '@/components/ui/layout';
import { layouts } from '@/features/ui/router';
import {
PromoPanel,
InstanceInfoPanel,
InstanceModerationPanel,
} from '@/features/ui/util/async-components';
import { PromoPanel, InstanceModerationPanel } from '@/features/ui/util/async-components';
import { useOwnAccount } from '@/hooks/use-own-account';
import { useFederationRestrictionsDisclosed } from '@/utils/state';
@ -27,7 +23,6 @@ const RemoteInstanceLayout = () => {
<Layout.Aside>
<PromoPanel />
<InstanceInfoPanel host={instance} />
{(disclosed || account?.is_admin) && <InstanceModerationPanel host={instance} />}
<LinkFooter />
</Layout.Aside>

View File

@ -1,10 +1,13 @@
import iconChatCenteredText from '@phosphor-icons/core/regular/chat-centered-text.svg';
import iconDotsThreeVertical from '@phosphor-icons/core/regular/dots-three-vertical.svg';
import iconPushPinSlash from '@phosphor-icons/core/regular/push-pin-slash.svg';
import iconPushPin from '@phosphor-icons/core/regular/push-pin.svg';
import iconX from '@phosphor-icons/core/regular/x.svg';
import { useNavigate } from '@tanstack/react-router';
import React from 'react';
import React, { useMemo } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { changeSetting } from '@/actions/settings';
import { PublicTimelineColumn } from '@/columns/timeline';
import DropdownMenu from '@/components/dropdown-menu';
import { TimelinePicker } from '@/components/timeline-picker';
@ -17,24 +20,53 @@ import { useSettings } from '@/stores/settings';
const messages = defineMessages({
close: { id: 'remote_timeline.close', defaultMessage: 'Close remote timeline' },
pinHost: { id: 'remote_instance.pin_host', defaultMessage: 'Pin {host}' },
unpinHost: { id: 'remote_instance.unpin_host', defaultMessage: 'Unpin {host}' },
});
/** View statuses from a remote instance. */
const RemoteTimelinePage: React.FC = () => {
const { instance } = remoteTimelineRoute.useParams();
const items = useTimelineFiltersOptions('public');
const timelineFiltersOptions = useTimelineFiltersOptions('public');
const intl = useIntl();
const navigate = useNavigate();
const settings = useSettings();
const pinned = settings.remote_timeline.pinnedHosts.includes(instance);
const isPinned = settings.remote_timeline.pinnedHosts.includes(instance);
const handleCloseClick: React.MouseEventHandler = () => {
navigate({ to: '/timeline/fediverse' });
};
const handlePinHost = () => {
if (!isPinned) {
changeSetting(
['remote_timeline', 'pinnedHosts'],
[...settings.remote_timeline.pinnedHosts, instance],
);
} else {
changeSetting(
['remote_timeline', 'pinnedHosts'],
settings.remote_timeline.pinnedHosts.filter((value) => value !== instance),
);
}
};
const items = useMemo(
() => [
...timelineFiltersOptions,
null,
{
text: intl.formatMessage(isPinned ? messages.unpinHost : messages.pinHost, { instance }),
action: handlePinHost,
icon: isPinned ? iconPushPinSlash : iconPushPin,
},
],
[timelineFiltersOptions, isPinned, instance],
);
return (
<Column
label={instance}
@ -42,7 +74,7 @@ const RemoteTimelinePage: React.FC = () => {
truncateTitle={false}
action={<DropdownMenu items={items} src={iconDotsThreeVertical} />}
>
{!pinned && (
{!isPinned && (
<div className='mb-4 flex gap-2 px-2'>
<IconButton
className='text-gray-400 hover:text-gray-600'