[MM-61641]: Ensure implicit list markup is avoided (#29787)

* MM-61641: Update post options to use semantic list elements

* MM-61641: Fix lint

* Apply suggestions from code review

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Harrison Healey <harrisonmhealey@gmail.com>
Этот коммит содержится в:
ayush-chauhan233
2025-01-28 21:07:18 +05:30
коммит произвёл GitHub
родитель 3b69d36643
Коммит a672aa5be5
4 изменённых файлов: 75 добавлений и 59 удалений

Просмотреть файл

@@ -59,7 +59,7 @@ type Props = {
};
const PostOptions = (props: Props): JSX.Element => {
const dotMenuRef = useRef<HTMLDivElement>(null);
const dotMenuRef = useRef<HTMLUListElement>(null);
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const [showDotMenu, setShowDotMenu] = useState(false);
@@ -120,12 +120,14 @@ const PostOptions = (props: Props): JSX.Element => {
let commentIcon;
if (showCommentIcon) {
commentIcon = (
<CommentIcon
handleCommentClick={props.handleCommentClick}
postId={post.id}
extraClass={commentIconExtraClass}
commentCount={props.collapsedThreadsEnabled ? 0 : props.replyCount}
/>
<li>
<CommentIcon
handleCommentClick={props.handleCommentClick}
postId={post.id}
extraClass={commentIconExtraClass}
commentCount={props.collapsedThreadsEnabled ? 0 : props.replyCount}
/>
</li>
);
}
@@ -152,38 +154,44 @@ const PostOptions = (props: Props): JSX.Element => {
let postReaction;
if (showReactionIcon) {
postReaction = (
<PostReaction
channelId={post.channel_id}
location={props.location}
postId={post.id}
teamId={props.teamId}
getDotMenuRef={getDotMenuRef}
showEmojiPicker={showEmojiPicker}
toggleEmojiPicker={toggleEmojiPicker}
/>
<li>
<PostReaction
channelId={post.channel_id}
location={props.location}
postId={post.id}
teamId={props.teamId}
getDotMenuRef={getDotMenuRef}
showEmojiPicker={showEmojiPicker}
toggleEmojiPicker={toggleEmojiPicker}
/>
</li>
);
}
let flagIcon: ReactNode = null;
if (!isMobileView && (!isEphemeral && !post.failed && !systemMessage)) {
flagIcon = (
<PostFlagIcon
location={props.location}
postId={post.id}
isFlagged={props.isFlagged}
/>
<li>
<PostFlagIcon
location={props.location}
postId={post.id}
isFlagged={props.isFlagged}
/>
</li>
);
}
// Action menus
const showActionsMenuIcon = props.shouldShowActionsMenu && (isMobileView || hoverLocal);
const actionsMenu = showActionsMenuIcon && (
<ActionsMenu
post={post}
location={props.location}
handleDropdownOpened={handleActionsMenuOpened}
isMenuOpen={showActionsMenu}
/>
<li>
<ActionsMenu
post={post}
location={props.location}
handleDropdownOpened={handleActionsMenuOpened}
isMenuOpen={showActionsMenu}
/>
</li>
);
let pluginItems: ReactNode = null;
@@ -193,10 +201,11 @@ const PostOptions = (props: Props): JSX.Element => {
if (item.component) {
const Component = item.component;
return (
<Component
post={props.post}
key={item.id}
/>
<li key={item.id}>
<Component
post={props.post}
/>
</li>
);
}
return null;
@@ -204,17 +213,19 @@ const PostOptions = (props: Props): JSX.Element => {
}
const dotMenu = (
<DotMenu
post={props.post}
location={props.location}
isFlagged={props.isFlagged}
handleDropdownOpened={handleDotMenuOpened}
handleCommentClick={props.handleCommentClick}
handleAddReactionClick={toggleEmojiPicker}
isReadOnly={isReadOnly || channelIsArchived}
isMenuOpen={showDotMenu}
enableEmojiPicker={props.enableEmojiPicker}
/>
<li>
<DotMenu
post={props.post}
location={props.location}
isFlagged={props.isFlagged}
handleDropdownOpened={handleDotMenuOpened}
handleCommentClick={props.handleCommentClick}
handleAddReactionClick={toggleEmojiPicker}
isReadOnly={isReadOnly || channelIsArchived}
isMenuOpen={showDotMenu}
enableEmojiPicker={props.enableEmojiPicker}
/>
</li>
);
// Build post options
@@ -235,10 +246,11 @@ const PostOptions = (props: Props): JSX.Element => {
} else if (props.location === Locations.SEARCH) {
const hasCRTFooter = props.collapsedThreadsEnabled && !post.root_id && (post.reply_count > 0 || post.is_following);
options = (
<div className='col__controls post-menu'>
<ul className='col__controls post-menu'>
{dotMenu}
{flagIcon}
{props.canReply && !hasCRTFooter &&
<li>
<CommentIcon
location={props.location}
handleCommentClick={props.handleCommentClick}
@@ -247,22 +259,25 @@ const PostOptions = (props: Props): JSX.Element => {
searchStyle={'search-item__comment'}
extraClass={props.replyCount ? 'icon--visible' : ''}
/>
</li>
}
<a
href='#'
onClick={props.handleJumpClick}
className='search-item__jump'
>
<FormattedMessage
id='search_item.jump'
defaultMessage='Jump'
/>
</a>
</div>
<li>
<a
href='#'
onClick={props.handleJumpClick}
className='search-item__jump'
>
<FormattedMessage
id='search_item.jump'
defaultMessage='Jump'
/>
</a>
</li>
</ul>
);
} else if (!props.isPostBeingEdited) {
options = (
<div
<ul
ref={dotMenuRef}
data-testid={`post-menu-${props.post.id}`}
className={classnames('col post-menu', {'post-menu--position': !hoverLocal && showCommentIcon})}
@@ -275,7 +290,7 @@ const PostOptions = (props: Props): JSX.Element => {
{actionsMenu}
{commentIcon}
{(collapsedThreadsEnabled || showRecentlyUsedReactions) && dotMenu}
</div>
</ul>
);
}

Просмотреть файл

@@ -31,7 +31,7 @@ export type Props = WrappedComponentProps & {
channelId?: string;
postId: string;
teamId: string;
getDotMenuRef: () => HTMLDivElement | null;
getDotMenuRef: () => HTMLUListElement | null;
location?: keyof typeof Locations;
showEmojiPicker: boolean;
toggleEmojiPicker: (e?: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;

Просмотреть файл

@@ -96,13 +96,13 @@ export default class PostRecentReactions extends React.PureComponent<Props, Stat
emoji={getEmojiName(emoji)}
isEmojiLarge={true}
>
<div>
<li>
<EmojiItem
emoji={emoji}
onItemClick={this.handleToggleEmoji}
order={n}
/>
</div>
</li>
</WithTooltip>
</ChannelPermissionGate>
),

Просмотреть файл

@@ -11,6 +11,7 @@
padding: 4px;
border: 1px solid transparent;
border-radius: 4px;
list-style: none;
white-space: normal;
}