Skip to content

Commit

Permalink
Merge pull request #1682 from FreeFeed/show-banned-comments
Browse files Browse the repository at this point in the history
Allow to "unlock" and preview comments from the banned users and with reply to the banned users
  • Loading branch information
davidmz authored May 21, 2024
2 parents d2b0f42 + 28afd09 commit 7d4c5e3
Show file tree
Hide file tree
Showing 13 changed files with 193 additions and 35 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.132.0] - Not released
### Added
- Ability to "unlock" and preview comments from the banned users and with reply
to the banned users.
### Fixed
- Fixed a bug where the first partial opening of comments ("show last N") would
open one extra comment.
Expand Down
29 changes: 29 additions & 0 deletions src/components/alternative-text.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import cn from 'classnames';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
import { Icon } from './fontawesome-icons';
import { ButtonLink } from './button-link';
import style from './alternative-text.module.scss';

export function AlternativeText({
status,
icon,
isError = false,
inComment = false,
close,
children,
}) {
return (
<div className={cn(style.box, inComment && style.inComment)}>
<div className={cn(style.status, isError && style.statusError)}>
<span className={style.icon}>
<Icon icon={icon} />
</span>
<span className={style.statusText}>{status}</span>
<ButtonLink onClick={close} aria-label="Close" className={style.closeIcon}>
<Icon icon={faTimes} />
</ButtonLink>
</div>
<div>{children}</div>
</div>
);
}
File renamed without changes.
5 changes: 3 additions & 2 deletions src/components/comment-likers.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import { initialAsyncState } from '../redux/async-helpers';
* Load and return all likers of the given comment
*
* @param {string} id
* @param {boolean} suppress do not load likes (when we know they aren't available)
*/
export function useCommentLikers(id) {
export function useCommentLikers(id, suppress = false) {
const dispatch = useDispatch();
useEffect(() => void dispatch(getCommentLikes(id)), [dispatch, id]);
useEffect(() => void (!suppress && dispatch(getCommentLikes(id))), [suppress, dispatch, id]);
return useSelector((state) => {
const { status = initialAsyncState, likes = [] } = state.commentLikes[id] || {};
return {
Expand Down
11 changes: 10 additions & 1 deletion src/components/post/post-comment-more-menu.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
faEdit,
faHeartBroken,
faLink,
faLockOpen,
faUserFriends,
} from '@fortawesome/free-solid-svg-icons';
import { faHeart, faClock, faTrashAlt } from '@fortawesome/free-regular-svg-icons';
Expand All @@ -35,6 +36,7 @@ export const PostCommentMoreMenu = forwardRef(function PostCommentMore(
doLike,
doUnlike,
doShowLikes,
doUnlock,
getBackwardIdx,
createdAt,
updatedAt,
Expand All @@ -46,7 +48,7 @@ export const PostCommentMoreMenu = forwardRef(function PostCommentMore(
},
menuRef,
) {
const { status, likers } = useCommentLikers(id);
const { status, likers } = useCommentLikers(id, isHidden);
const myUsername = useSelector((state) => state.user.username);
const bIdx = getBackwardIdx();
const arrows = bIdx <= 4 ? '^'.repeat(bIdx) : `^${bIdx}`;
Expand Down Expand Up @@ -113,6 +115,13 @@ export const PostCommentMoreMenu = forwardRef(function PostCommentMore(
!isHidden && (
<MenuItemTranslate key="translate" type="comment" id={id} doAndClose={doAndClose} />
),
doUnlock && (
<div key="unlock" className={styles.item}>
<ButtonLink onClick={doUnlock} className={styles.link}>
<Iconic icon={faLockOpen}>Show comment</Iconic>
</ButtonLink>
</div>
),
],
[
doDelete && authorUsername !== myUsername && (
Expand Down
23 changes: 21 additions & 2 deletions src/components/post/post-comment.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import classnames from 'classnames';
import { connect } from 'react-redux';

import { preventDefault, confirmFirst } from '../../utils';
import { READMORE_STYLE_COMPACT } from '../../utils/frontend-preferences-options';
import {
HIDDEN_AUTHOR_BANNED,
READMORE_STYLE_COMPACT,
} from '../../utils/frontend-preferences-options';
import { commentReadmoreConfig } from '../../utils/readmore-config';
import { defaultCommentState } from '../../redux/reducers/comment-edit';

Expand All @@ -22,6 +25,7 @@ import { Separated } from '../separated';
import { TranslatedText } from '../translated-text';
import { initialAsyncState } from '../../redux/async-helpers';
import { existingCommentURI, newCommentURI } from '../../services/drafts';
import { UnlockedHiddenComment } from '../unlocked-hidden-comment';
import { PostCommentMore } from './post-comment-more';
import { PostCommentPreview } from './post-comment-preview';
import { CommentProvider } from './post-comment-provider';
Expand All @@ -36,6 +40,7 @@ class PostComment extends Component {
previewSeqNumber: 0,
previewLeft: 0,
previewTop: 0,
unlockHidden: false,
};

scrollToComment = () => {
Expand Down Expand Up @@ -158,9 +163,13 @@ class PostComment extends Component {
canLike: !ownComment && !this.isHidden(),
canReply: !this.isHidden() && this.props.canAddComment,
canDelete: this.props.isEditable || this.props.isDeletable,
canUnlock: this.props.hideType === HIDDEN_AUTHOR_BANNED || this.props.isReplyToBanned,
};
}

unlockHidden = () => this.setState({ unlockHidden: true });
lockHidden = () => this.setState({ unlockHidden: false });

// We use this strange data structure because there can be more than one
// PostCommentMore element created in the comment (see Expandable/bonusInfo).
_moreMenuOpeners = [];
Expand All @@ -171,7 +180,7 @@ class PostComment extends Component {
onMoreMenuOpened = (moreMenuOpened) => this.setState({ moreMenuOpened });

commentTail() {
const { canLike, canReply, canDelete } = this.possibleActions();
const { canLike, canReply, canDelete, canUnlock } = this.possibleActions();
return (
<span
aria-label={
Expand Down Expand Up @@ -229,6 +238,7 @@ class PostComment extends Component {
doMention={canReply && this.mention}
doLike={canLike && !this.props.hasOwnLike && this.like}
doUnlike={canLike && this.props.hasOwnLike && this.unlike}
doUnlock={canUnlock && this.unlockHidden}
getBackwardIdx={this.backwardIdx}
createdAt={this.props.createdAt}
updatedAt={this.props.updatedAt}
Expand Down Expand Up @@ -266,6 +276,15 @@ class PostComment extends Component {
if (this.isHidden()) {
return (
<div className="comment-body">
{this.state.unlockHidden && (
<UnlockedHiddenComment
id={this.props.id}
userHover={this.props.authorHighlightHandlers}
arrowHover={this.arrowHoverHandlers}
arrowClick={this.arrowClick}
close={this.lockHidden}
/>
)}
<span className="comment-text">{this.hiddenBody()}</span>
{commentTail}
</div>
Expand Down
41 changes: 11 additions & 30 deletions src/components/translated-text.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import ISO6391 from 'iso-639-1';
import { useDispatch, useSelector } from 'react-redux';
import cn from 'classnames';
import { useCallback } from 'react';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
import { initialAsyncState } from '../redux/async-helpers';
import { resetTranslation } from '../redux/action-creators';
import { useServerValue } from './hooks/server-info';
import PieceOfText from './piece-of-text';
import { Icon } from './fontawesome-icons';
import { faTranslate } from './fontawesome-custom-icons';
import { ButtonLink } from './button-link';
import style from './translated-text.module.scss';
import { AlternativeText } from './alternative-text';

const selectTranslationService = (serverInfo) => serverInfo.textTranslation.serviceTitle;

Expand All @@ -27,29 +23,31 @@ export function TranslatedText({ type, id, userHover, arrowHover, arrowClick })
}
if (status.loading) {
return (
<Layout
<AlternativeText
icon={faTranslate}
status={`Translating using ${serviceTitle}...`}
inComment={type === 'comment'}
reset={reset}
close={reset}
/>
);
}
if (status.error) {
return (
<Layout
isError
<AlternativeText
icon={faTranslate}
status={`Translation error: ${status.errorText}`}
inComment={type === 'comment'}
reset={reset}
close={reset}
/>
);
}

return (
<Layout
<AlternativeText
icon={faTranslate}
status={`Translated from ${ISO6391.getName(result.detectedLang)} using ${serviceTitle}:`}
inComment={type === 'comment'}
reset={reset}
close={reset}
>
<PieceOfText
isExpanded
Expand All @@ -58,23 +56,6 @@ export function TranslatedText({ type, id, userHover, arrowHover, arrowClick })
arrowHover={arrowHover}
arrowClick={arrowClick}
/>
</Layout>
);
}

function Layout({ status, isError = false, inComment = false, reset, children }) {
return (
<div className={cn(style.box, inComment && style.inComment)}>
<div className={cn(style.status, isError && style.statusError)}>
<span className={style.icon}>
<Icon icon={faTranslate} />
</span>
<span className={style.statusText}>{status}</span>
<ButtonLink onClick={reset} aria-label="Close" className={style.closeIcon}>
<Icon icon={faTimes} />
</ButtonLink>
</div>
<div>{children}</div>
</div>
</AlternativeText>
);
}
87 changes: 87 additions & 0 deletions src/components/unlocked-hidden-comment.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { useDispatch, useSelector } from 'react-redux';
import { faLockOpen } from '@fortawesome/free-solid-svg-icons';
import { useEffect } from 'react';
import { COMMENT_VISIBLE, HIDDEN_AUTHOR_BANNED } from '../utils/frontend-preferences-options';
import { initialAsyncState } from '../redux/async-helpers';
import { unlockComment } from '../redux/action-creators';
import PieceOfText from './piece-of-text';
import { AlternativeText } from './alternative-text';
import UserName from './user-name';

export function UnlockedHiddenComment({ id, userHover, arrowHover, arrowClick, close }) {
const dispatch = useDispatch();
const comment = useSelector((state) => state.comments[id]);

const status = useSelector((store) => store.unlockedCommentStates[id] ?? initialAsyncState);
const unlockedComment = useSelector((store) => store.unlockedComments[id]);

useEffect(() => {
if (comment.hideType === HIDDEN_AUTHOR_BANNED && status.initial) {
dispatch(unlockComment(id));
}
}, [comment.hideType, dispatch, id, status.initial]);

let title;
if (comment.hideType === COMMENT_VISIBLE) {
title = `Comment with reply to blocked user:`;
} else if (comment.hideType === HIDDEN_AUTHOR_BANNED) {
if (status.loading) {
title = `Loading comment from a blocked user...`;
} else if (status.error) {
title = `Error loading comment: ${status.errorText}`;
} else {
title = `Comment from a blocked user:`;
}
}

let content = null;
if (comment.hideType === COMMENT_VISIBLE) {
content = (
<CommentContent
comment={comment}
userHover={userHover}
arrowHover={arrowHover}
arrowClick={arrowClick}
/>
);
} else if (comment.hideType === HIDDEN_AUTHOR_BANNED) {
content = (
<CommentContent
comment={unlockedComment}
userHover={userHover}
arrowHover={arrowHover}
arrowClick={arrowClick}
/>
);
}

return (
<AlternativeText icon={faLockOpen} status={title} inComment close={close}>
{content}
</AlternativeText>
);
}

function CommentContent({ comment, userHover, arrowHover, arrowClick }) {
const allUsers = useSelector((state) => state.users);
const author = allUsers[comment?.createdBy];
return (
comment && (
<>
<PieceOfText
isExpanded
text={comment.body}
userHover={userHover}
arrowHover={arrowHover}
arrowClick={arrowClick}
/>
{author && (
<>
{' '}
- <UserName user={author} />
</>
)}
</>
)
);
}
8 changes: 8 additions & 0 deletions src/redux/action-creators.js
Original file line number Diff line number Diff line change
Expand Up @@ -1387,3 +1387,11 @@ export function notifyOfAllComments(postId, enabled) {
payload: { postId, enabled },
};
}

export function unlockComment(id) {
return {
type: ActionTypes.UNLOCK_COMMENT,
apiRequest: Api.unlockComment,
payload: { id },
};
}
1 change: 1 addition & 0 deletions src/redux/action-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,4 @@ export const TRANSLATE_TEXT = 'TRANSLATE_TEXT';
export const GET_BACKLINKS = 'GET_BACKLINKS';
export const NOTIFY_OF_ALL_COMMENTS = 'NOTIFY_OF_ALL_COMMENTS';
export const SET_ORBIT = 'SET_ORBIT';
export const UNLOCK_COMMENT = 'UNLOCK_COMMENT';
2 changes: 2 additions & 0 deletions src/redux/reducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2181,3 +2181,5 @@ export const calendarMonthDays = fromResponse(
export { userStatsStatus, userStats } from './reducers/dynamic-user-stats';

export { translationStates, translationResults } from './reducers/translation';

export { unlockedCommentStates, unlockedComments } from './reducers/unlocked-comments';
14 changes: 14 additions & 0 deletions src/redux/reducers/unlocked-comments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { UNLOCK_COMMENT } from '../action-types';
import { asyncResultsMap, asyncStatesMap } from '../async-helpers';
import { setOnLocationChange } from './helpers';

const resetOnLocationChange = setOnLocationChange({});

export const unlockedCommentStates = asyncStatesMap(UNLOCK_COMMENT, {}, resetOnLocationChange);
export const unlockedComments = asyncResultsMap(
UNLOCK_COMMENT,
{
transformer: (action) => action.payload.comments,
},
resetOnLocationChange,
);
4 changes: 4 additions & 0 deletions src/services/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,10 @@ export function getCommentsByIds({ commentIds }) {
return fetch(`${apiRoot}/v2/comments/byIds`, postRequestOptions('POST', { commentIds }));
}

export function unlockComment({ id }) {
return fetch(`${apiRoot}/v2/comments/${id}?unlock-banned`, getRequestOptions());
}

export function translateText({ type, id, lang }) {
const part = type === 'post' ? 'posts' : 'comments';
const qs = lang ? `?lang=${lang}` : '';
Expand Down

0 comments on commit 7d4c5e3

Please sign in to comment.