From daaba0f006b9b8f639775c1e6ff32f25485adb95 Mon Sep 17 00:00:00 2001 From: "Tom (plebeius.eth)" Date: Mon, 3 Jun 2024 13:51:41 +0200 Subject: [PATCH] feat: add mod menu --- src/components/post/mod-menu/index.ts | 1 + .../post/mod-menu/mod-menu.module.css | 84 +++++++++ src/components/post/mod-menu/mod-menu.ts | 0 src/components/post/mod-menu/mod-menu.tsx | 175 ++++++++++++++++++ 4 files changed, 260 insertions(+) create mode 100644 src/components/post/mod-menu/index.ts delete mode 100644 src/components/post/mod-menu/mod-menu.ts diff --git a/src/components/post/mod-menu/index.ts b/src/components/post/mod-menu/index.ts new file mode 100644 index 00000000..cc8304d4 --- /dev/null +++ b/src/components/post/mod-menu/index.ts @@ -0,0 +1 @@ +export { default } from './mod-menu'; diff --git a/src/components/post/mod-menu/mod-menu.module.css b/src/components/post/mod-menu/mod-menu.module.css index e69de29b..634070df 100644 --- a/src/components/post/mod-menu/mod-menu.module.css +++ b/src/components/post/mod-menu/mod-menu.module.css @@ -0,0 +1,84 @@ +.checkbox { + display: inline-block; + padding: 3px 7px 3px 4px; + filter: var(--filter80); +} + +.button { + color: var(--text-info); + font-weight: bold; + text-decoration: none; + padding: 0 4px 0 4px; + cursor: pointer; +} + +.modal { + z-index: 7; + background-color: var(--challenge-modal-background-color); + padding: 1px 0; + padding-top: 2px; + color: var(--text); + border: var(--challenge-modal-border); + min-width: 200px; +} + +.modal input[type="checkbox"] { + margin-right: 3px; + outline: none; + cursor: pointer; + margin: 0 3px; + vertical-align: middle; + transform: translateY(-2px); +} + +.menuItem { + padding: 2px 3px 1px 3px; +} + +.menuItem label { + cursor: pointer; +} + +.menuReason { + padding-bottom: 2px; +} + +.menuItem input[type="text"] { + padding: 2px; + box-shadow: var(--box-shadow-input); + margin-top: 2px; + width: calc(100% - 6px); + margin-bottom: 2px; +} + +.menuItem input[type="text"], .menuItem input[type='number'] { + border: var(--post-form-field-input-border); + outline: none; +} + +.menuItem input[type="text"]:focus, .menuItem input[type='number']:focus { + border: var(--post-form-field-input-focus-border); +} + +.bottom { + clear: both; + display: block; +} + +.bottom button { + text-transform: capitalize; + cursor: pointer; + padding: 2px 6px 3px; + margin-left: 2px; + margin-bottom: 2px; +} + +.optional { + color: var(--text-info); +} + +@supports (-moz-appearance: none) { + .banInput { + width: 3.5em; + } +} \ No newline at end of file diff --git a/src/components/post/mod-menu/mod-menu.ts b/src/components/post/mod-menu/mod-menu.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/components/post/mod-menu/mod-menu.tsx b/src/components/post/mod-menu/mod-menu.tsx index e69de29b..62e1ebe0 100644 --- a/src/components/post/mod-menu/mod-menu.tsx +++ b/src/components/post/mod-menu/mod-menu.tsx @@ -0,0 +1,175 @@ +import { useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { autoUpdate, flip, FloatingFocusManager, offset, shift, useClick, useDismiss, useFloating, useId, useInteractions, useRole } from '@floating-ui/react'; +import { PublishCommentEditOptions, useComment, useEditedComment, usePublishCommentEdit } from '@plebbit/plebbit-react-hooks'; +import styles from './mod-menu.module.css'; +import { alertChallengeVerificationFailed } from '../../../lib/utils/challenge-utils'; +import useChallengesStore from '../../../stores/use-challenges-store'; +import _ from 'lodash'; + +const { addChallenge } = useChallengesStore.getState(); + +type ModMenuProps = { + cid: string; + isCommentAuthorMod?: boolean; +}; + +const ModMenu = ({ cid, isCommentAuthorMod }: ModMenuProps) => { + const { t } = useTranslation(); + + let post: any; + const comment = useComment({ commentCid: cid }); + const { editedComment } = useEditedComment({ comment }); + if (editedComment) { + post = editedComment; + } else if (comment) { + post = comment; + } + const isReply = post?.parentCid; + const [isModMenuOpen, setIsModMenuOpen] = useState(false); + + const defaultPublishOptions: PublishCommentEditOptions = { + removed: post?.removed, + locked: post?.locked, + spoiler: post?.spoiler, + pinned: post?.pinned, + commentAuthor: { banExpiresAt: post?.banExpiresAt }, + commentCid: post?.cid, + subplebbitAddress: post?.subplebbitAddress, + onChallenge: (...args: any) => addChallenge([...args, post]), + onChallengeVerification: alertChallengeVerificationFailed, + onError: (error: Error) => { + console.warn(error); + alert(error.message); + }, + }; + + const [publishCommentEditOptions, setPublishCommentEditOptions] = useState(defaultPublishOptions); + const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions); + + const [banDuration, setBanDuration] = useState(1); + + const daysToTimestampInSeconds = (days: number) => { + const now = new Date(); + now.setDate(now.getDate() + days); + return Math.floor(now.getTime() / 1000); + }; + + const onCheckbox = (e: React.ChangeEvent) => { + 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) => { + const days = parseInt(e.target.value, 10) || 1; + setBanDuration(days); + setPublishCommentEditOptions((state) => ({ + ...state, + commentAuthor: { ...state.commentAuthor, banExpiresAt: daysToTimestampInSeconds(days) }, + })); + }; + + const onReason = (e: React.ChangeEvent) => + setPublishCommentEditOptions((state) => ({ ...state, reason: e.target.value ? e.target.value : undefined })); + + const { refs, floatingStyles, context } = useFloating({ + placement: 'bottom-start', + open: isModMenuOpen, + onOpenChange: setIsModMenuOpen, + 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(); + + const handleSaveClick = async () => { + await publishCommentEdit(); + setIsModMenuOpen(false); + }; + + return ( + <> + + cid && setIsModMenuOpen(!isModMenuOpen)} checked={isModMenuOpen} /> + + {isModMenuOpen && ( + +
+
+
+ +
+ {!isReply && ( +
+ [ + + ] +
+ )} +
+ [ + + ] +
+
+ [ + + ] +
+ {!isCommentAuthorMod && ( +
+ [ + + ] +
+ )} +
+ {_.capitalize(t('reason'))}? ({t('optional')}) + +
+
+ +
+
+
+
+ )} + + ); +}; + +export default ModMenu;