/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see .
*/
import {useLingui} from '@lingui/react/macro';
import {FlagCheckeredIcon, PushPinSlashIcon, SparkleIcon, XIcon} from '@phosphor-icons/react';
import {clsx} from 'clsx';
import {observer} from 'mobx-react-lite';
import React from 'react';
import * as ChannelPinActionCreators from '~/actions/ChannelPinsActionCreators';
import * as ModalActionCreators from '~/actions/ModalActionCreators';
import {modal} from '~/actions/ModalActionCreators';
import {ChannelTypes, MessagePreviewContext, Permissions} from '~/Constants';
import {Message} from '~/components/channel/Message';
import {LongPressable} from '~/components/LongPressable';
import {ConfirmModal} from '~/components/modals/ConfirmModal';
import FocusRing from '~/components/uikit/FocusRing/FocusRing';
import {MenuBottomSheet} from '~/components/uikit/MenuBottomSheet/MenuBottomSheet';
import {Scroller} from '~/components/uikit/Scroller';
import {Spinner} from '~/components/uikit/Spinner';
import type {ChannelRecord} from '~/records/ChannelRecord';
import type {MessageRecord} from '~/records/MessageRecord';
import ChannelPinsStore from '~/stores/ChannelPinsStore';
import ChannelStore from '~/stores/ChannelStore';
import MobileLayoutStore from '~/stores/MobileLayoutStore';
import PermissionStore from '~/stores/PermissionStore';
import ReadStateStore from '~/stores/ReadStateStore';
import {goToMessage} from '~/utils/MessageNavigator';
import previewStyles from './MessagePreview.module.css';
interface ChannelPinsContentProps {
channel: ChannelRecord;
onJump?: () => void;
}
export const ChannelPinsContent = observer(({channel, onJump}: ChannelPinsContentProps) => {
const {t} = useLingui();
const pinnedPins = ChannelPinsStore.getPins(channel.id);
const fetched = ChannelPinsStore.isFetched(channel.id);
const hasMore = ChannelPinsStore.getHasMore(channel.id);
const isLoading = ChannelPinsStore.getIsLoading(channel.id);
const isDMChannel = channel.type === ChannelTypes.DM || channel.type === ChannelTypes.GROUP_DM;
const canUnpin = isDMChannel || PermissionStore.can(Permissions.MANAGE_MESSAGES, channel);
const mobileLayout = MobileLayoutStore;
const [menuOpen, setMenuOpen] = React.useState(false);
const [selectedMessage, setSelectedMessage] = React.useState(null);
React.useEffect(() => {
if (!fetched && !isLoading) {
ChannelPinActionCreators.fetch(channel.id);
}
}, [fetched, isLoading, channel.id]);
React.useEffect(() => {
ReadStateStore.ackPins(channel.id);
}, [channel.id]);
const handleScroll = React.useCallback(
(event: React.UIEvent) => {
const target = event.currentTarget;
const scrollPercentage = (target.scrollTop + target.offsetHeight) / target.scrollHeight;
if (scrollPercentage > 0.8 && hasMore && !isLoading) {
ChannelPinActionCreators.loadMore(channel.id);
}
},
[channel.id, hasMore, isLoading],
);
const handleUnpin = (message: MessageRecord, event?: React.MouseEvent) => {
if (event?.shiftKey) {
ChannelPinActionCreators.unpin(message.channelId, message.id);
} else {
ModalActionCreators.push(
modal(() => (
ChannelPinActionCreators.unpin(message.channelId, message.id)}
/>
)),
);
}
setMenuOpen(false);
};
const renderUnpinButton = (message: MessageRecord) => {
if (!canUnpin) return null;
return (
);
};
const handleJump = (channelId: string, messageId: string) => {
goToMessage(channelId, messageId);
onJump?.();
};
const handleTap = (message: MessageRecord) => {
if (mobileLayout.enabled) {
handleJump(message.channelId, message.id);
}
};
const endStateDescription = channel.guildId
? t`Members with the "Pin Messages" permission can pin messages for everyone to see.`
: t`You can pin messages in this conversation for everyone to see.`;
if (pinnedPins.length === 0) {
return (
{t`No Pinned Messages`}
{t`Whenever someone pins a message, it'll appear here.`}