2024-07-03 15:35:45 +02:00
|
|
|
import { useState } from 'react';
|
2024-06-04 16:06:28 +02:00
|
|
|
import { Trans, useTranslation } from 'react-i18next';
|
2024-06-03 13:51:41 +02:00
|
|
|
import { autoUpdate, flip, FloatingFocusManager, offset, shift, useClick, useDismiss, useFloating, useId, useInteractions, useRole } from '@floating-ui/react';
|
2024-06-21 22:02:56 +02:00
|
|
|
import { Comment, PublishCommentEditOptions, usePublishCommentEdit } from '@plebbit/plebbit-react-hooks';
|
2024-06-03 21:32:00 +02:00
|
|
|
import styles from './edit-menu.module.css';
|
2024-06-11 15:49:26 +02:00
|
|
|
import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils';
|
|
|
|
|
import useChallengesStore from '../../stores/use-challenges-store';
|
2024-06-03 13:51:41 +02:00
|
|
|
import _ from 'lodash';
|
2024-06-11 15:49:26 +02:00
|
|
|
import useIsMobile from '../../hooks/use-is-mobile';
|
2024-06-03 13:51:41 +02:00
|
|
|
|
|
|
|
|
const { addChallenge } = useChallengesStore.getState();
|
|
|
|
|
|
2024-06-03 21:32:00 +02:00
|
|
|
type EditMenuProps = {
|
|
|
|
|
isAccountMod?: boolean;
|
|
|
|
|
isAccountCommentAuthor?: boolean;
|
2024-06-03 13:51:41 +02:00
|
|
|
isCommentAuthorMod?: boolean;
|
2024-06-21 22:02:56 +02:00
|
|
|
post: Comment;
|
2024-06-03 13:51:41 +02:00
|
|
|
};
|
|
|
|
|
|
2024-07-03 15:35:45 +02:00
|
|
|
const daysToTimestampInSeconds = (days: number) => {
|
|
|
|
|
const now = new Date();
|
|
|
|
|
now.setDate(now.getDate() + days);
|
|
|
|
|
return Math.floor(now.getTime() / 1000);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const timestampToDays = (timestamp: number) => {
|
|
|
|
|
const now = Math.floor(Date.now() / 1000);
|
|
|
|
|
return Math.max(1, Math.floor((timestamp - now) / (24 * 60 * 60)));
|
|
|
|
|
};
|
|
|
|
|
|
2024-06-21 22:02:56 +02:00
|
|
|
const EditMenu = ({ isAccountMod, isAccountCommentAuthor, isCommentAuthorMod, post }: EditMenuProps) => {
|
2024-06-03 13:51:41 +02:00
|
|
|
const { t } = useTranslation();
|
2024-06-04 16:13:20 +02:00
|
|
|
const isMobile = useIsMobile();
|
2024-07-03 15:35:45 +02:00
|
|
|
const { cid, commentAuthor, content, deleted, locked, parentCid, pinned, reason, removed, spoiler, subplebbitAddress } = post || {};
|
2024-06-04 10:30:01 +02:00
|
|
|
const isReply = parentCid;
|
2024-06-03 21:32:00 +02:00
|
|
|
const [isEditMenuOpen, setIsEditMenuOpen] = useState(false);
|
2024-06-04 10:30:01 +02:00
|
|
|
const [isContentEditorOpen, setIsContentEditorOpen] = useState(false);
|
|
|
|
|
|
2024-06-15 12:35:09 +02:00
|
|
|
const defaultPublishEditOptions: PublishCommentEditOptions = {
|
2024-07-03 15:35:45 +02:00
|
|
|
commentAuthor: isAccountMod && !isAccountCommentAuthor ? commentAuthor : undefined,
|
2024-06-21 22:02:56 +02:00
|
|
|
commentCid: cid,
|
2024-06-15 12:35:09 +02:00
|
|
|
content: isAccountCommentAuthor ? content : undefined,
|
|
|
|
|
deleted: isAccountCommentAuthor ? deleted : undefined,
|
|
|
|
|
locked: isAccountMod ? locked : undefined,
|
|
|
|
|
pinned: isAccountMod ? pinned : undefined,
|
2024-07-10 19:10:32 +02:00
|
|
|
reason,
|
2024-06-15 12:35:09 +02:00
|
|
|
removed: isAccountMod ? removed : undefined,
|
2024-06-04 10:30:01 +02:00
|
|
|
spoiler,
|
|
|
|
|
subplebbitAddress,
|
2024-06-03 13:51:41 +02:00
|
|
|
onChallenge: (...args: any) => addChallenge([...args, post]),
|
|
|
|
|
onChallengeVerification: alertChallengeVerificationFailed,
|
|
|
|
|
onError: (error: Error) => {
|
|
|
|
|
console.warn(error);
|
2024-06-15 12:35:09 +02:00
|
|
|
alert('Comment edit failed. ' + error.message);
|
2024-06-03 13:51:41 +02:00
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
2024-06-15 12:35:09 +02:00
|
|
|
const [publishCommentEditOptions, setPublishCommentEditOptions] = useState(defaultPublishEditOptions);
|
2024-06-03 13:51:41 +02:00
|
|
|
const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions);
|
|
|
|
|
|
2024-07-03 15:35:45 +02:00
|
|
|
const [banDuration, setBanDuration] = useState(() =>
|
|
|
|
|
publishCommentEditOptions.commentAuthor?.banExpiresAt ? timestampToDays(publishCommentEditOptions.commentAuthor.banExpiresAt) : 1,
|
|
|
|
|
);
|
2024-06-03 13:51:41 +02:00
|
|
|
|
|
|
|
|
const onCheckbox = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
|
|
const { id, checked } = e.target;
|
|
|
|
|
if (id === 'banUser') {
|
|
|
|
|
setPublishCommentEditOptions((state) => ({
|
|
|
|
|
...state,
|
|
|
|
|
commentAuthor: { ...state.commentAuthor, banExpiresAt: checked ? daysToTimestampInSeconds(banDuration) : undefined },
|
|
|
|
|
}));
|
|
|
|
|
} else {
|
|
|
|
|
setPublishCommentEditOptions((state) => ({ ...state, [id]: checked }));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onBanDurationChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
|
|
const days = parseInt(e.target.value, 10) || 1;
|
|
|
|
|
setBanDuration(days);
|
|
|
|
|
setPublishCommentEditOptions((state) => ({
|
|
|
|
|
...state,
|
|
|
|
|
commentAuthor: { ...state.commentAuthor, banExpiresAt: daysToTimestampInSeconds(days) },
|
|
|
|
|
}));
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-10 19:10:32 +02:00
|
|
|
const onReason = (e: React.ChangeEvent<HTMLInputElement>) => setPublishCommentEditOptions((state) => ({ ...state, reason: e.target.value }));
|
2024-06-03 13:51:41 +02:00
|
|
|
|
|
|
|
|
const { refs, floatingStyles, context } = useFloating({
|
|
|
|
|
placement: 'bottom-start',
|
2024-06-03 21:32:00 +02:00
|
|
|
open: isEditMenuOpen,
|
|
|
|
|
onOpenChange: setIsEditMenuOpen,
|
2024-06-03 13:51:41 +02:00
|
|
|
middleware: [offset(2), flip({ fallbackAxisSideDirection: 'end' }), shift()],
|
|
|
|
|
whileElementsMounted: autoUpdate,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const click = useClick(context);
|
|
|
|
|
const dismiss = useDismiss(context);
|
|
|
|
|
const role = useRole(context);
|
|
|
|
|
|
|
|
|
|
const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss, role]);
|
|
|
|
|
|
|
|
|
|
const headingId = useId();
|
|
|
|
|
|
2024-06-04 10:30:01 +02:00
|
|
|
const _publishCommentEdit = async () => {
|
|
|
|
|
try {
|
|
|
|
|
await publishCommentEdit();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (error instanceof Error) {
|
|
|
|
|
console.warn(error);
|
|
|
|
|
alert(error.message);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-06-03 21:32:00 +02:00
|
|
|
setIsEditMenuOpen(false);
|
2024-06-03 13:51:41 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
2024-06-21 22:02:56 +02:00
|
|
|
<span className={`${styles.checkbox} ${isReply && styles.replyCheckbox}`} ref={refs.setReference} {...(cid && getReferenceProps())}>
|
2024-06-30 15:38:34 +02:00
|
|
|
<input type='checkbox' onChange={() => setIsEditMenuOpen(cid && (isAccountCommentAuthor || isAccountMod) ? !isEditMenuOpen : false)} checked={isEditMenuOpen} />
|
2024-06-03 13:51:41 +02:00
|
|
|
</span>
|
2024-06-03 21:32:00 +02:00
|
|
|
{isEditMenuOpen && (isAccountCommentAuthor || isAccountMod) && (
|
2024-06-03 13:51:41 +02:00
|
|
|
<FloatingFocusManager context={context} modal={false}>
|
|
|
|
|
<div className={styles.modal} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
|
2024-06-03 21:32:00 +02:00
|
|
|
<div className={styles.editMenu}>
|
|
|
|
|
{isAccountCommentAuthor && (
|
|
|
|
|
<>
|
|
|
|
|
<div className={styles.menuItem}>
|
|
|
|
|
<label>
|
|
|
|
|
[
|
2024-06-12 10:46:14 +02:00
|
|
|
<input onChange={onCheckbox} checked={publishCommentEditOptions.deleted ?? false} type='checkbox' id='deleted' />
|
2024-06-21 22:12:59 +02:00
|
|
|
{_.capitalize(t('delete'))}?]
|
2024-06-03 21:32:00 +02:00
|
|
|
</label>
|
|
|
|
|
</div>
|
2024-06-04 10:30:01 +02:00
|
|
|
<div className={styles.menuItem}>
|
|
|
|
|
<label>
|
|
|
|
|
[
|
|
|
|
|
<input type='checkbox' onChange={() => setIsContentEditorOpen(!isContentEditorOpen)} checked={isContentEditorOpen} />
|
2024-06-21 22:12:59 +02:00
|
|
|
{_.capitalize(t('edit'))}?]
|
2024-06-04 10:30:01 +02:00
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
{isContentEditorOpen && (
|
|
|
|
|
<div>
|
|
|
|
|
<textarea
|
|
|
|
|
className={styles.editTextarea}
|
2024-06-12 10:46:14 +02:00
|
|
|
value={publishCommentEditOptions.content ?? ''}
|
2024-06-04 10:30:01 +02:00
|
|
|
onChange={(e) => setPublishCommentEditOptions((state) => ({ ...state, content: e.target.value }))}
|
2024-07-03 15:35:45 +02:00
|
|
|
autoFocus={true}
|
2024-06-04 10:30:01 +02:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2024-06-03 21:32:00 +02:00
|
|
|
</>
|
2024-06-03 13:51:41 +02:00
|
|
|
)}
|
2024-06-03 21:32:00 +02:00
|
|
|
{isAccountMod && (
|
|
|
|
|
<>
|
|
|
|
|
<div className={styles.menuItem}>
|
|
|
|
|
<label>
|
|
|
|
|
[
|
2024-07-03 15:35:45 +02:00
|
|
|
<input onChange={onCheckbox} checked={publishCommentEditOptions.removed} type='checkbox' id='removed' />
|
2024-06-11 15:21:54 +02:00
|
|
|
{_.capitalize(t('remove'))}?]
|
2024-06-03 21:32:00 +02:00
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
{!isReply && (
|
|
|
|
|
<div className={styles.menuItem}>
|
|
|
|
|
[
|
|
|
|
|
<label>
|
2024-07-03 15:35:45 +02:00
|
|
|
<input onChange={onCheckbox} checked={publishCommentEditOptions.locked} type='checkbox' id='locked' />
|
2024-06-03 21:32:00 +02:00
|
|
|
{_.capitalize(t('close_thread'))}?
|
|
|
|
|
</label>
|
|
|
|
|
]
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<div className={styles.menuItem}>
|
|
|
|
|
[
|
|
|
|
|
<label>
|
2024-07-03 15:35:45 +02:00
|
|
|
<input onChange={onCheckbox} checked={publishCommentEditOptions.spoiler} type='checkbox' id='spoiler' />
|
2024-06-03 21:32:00 +02:00
|
|
|
{_.capitalize(t('spoiler'))}?
|
|
|
|
|
</label>
|
|
|
|
|
]
|
|
|
|
|
</div>
|
|
|
|
|
<div className={styles.menuItem}>
|
|
|
|
|
[
|
|
|
|
|
<label>
|
2024-07-03 15:35:45 +02:00
|
|
|
<input onChange={onCheckbox} checked={publishCommentEditOptions.pinned} type='checkbox' id='pinned' />
|
2024-06-03 21:32:00 +02:00
|
|
|
{_.capitalize(t('sticky'))}?
|
|
|
|
|
</label>
|
|
|
|
|
]
|
|
|
|
|
</div>
|
|
|
|
|
{!isCommentAuthorMod && (
|
|
|
|
|
<div className={styles.menuItem}>
|
|
|
|
|
[
|
|
|
|
|
<label>
|
2024-07-03 15:35:45 +02:00
|
|
|
<input onChange={onCheckbox} checked={publishCommentEditOptions.commentAuthor?.banExpiresAt !== undefined} type='checkbox' id='banUser' />
|
2024-06-03 21:32:00 +02:00
|
|
|
<Trans
|
|
|
|
|
i18nKey='ban_user_for'
|
|
|
|
|
shouldUnescape={true}
|
|
|
|
|
components={{
|
2024-07-03 15:35:45 +02:00
|
|
|
1: <input className={styles.banInput} onChange={onBanDurationChange} type='number' min={1} max={100} value={banDuration} />,
|
2024-06-03 21:32:00 +02:00
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
?
|
|
|
|
|
</label>
|
|
|
|
|
]
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
2024-06-03 13:51:41 +02:00
|
|
|
)}
|
|
|
|
|
<div className={`${styles.menuItem} ${styles.menuReason}`}>
|
2024-06-04 10:30:01 +02:00
|
|
|
{_.capitalize(t('reason'))}? ({t('optional')})
|
2024-07-10 19:10:32 +02:00
|
|
|
<input type='text' onChange={onReason} value={publishCommentEditOptions.reason} size={14} />
|
2024-06-03 13:51:41 +02:00
|
|
|
</div>
|
|
|
|
|
<div className={styles.bottom}>
|
2024-06-04 16:13:20 +02:00
|
|
|
<button className={isMobile ? 'button' : ''} onClick={_publishCommentEdit}>
|
|
|
|
|
{t('save')}
|
|
|
|
|
</button>
|
2024-06-03 13:51:41 +02:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</FloatingFocusManager>
|
|
|
|
|
)}
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
2024-06-03 21:32:00 +02:00
|
|
|
export default EditMenu;
|