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

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

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

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

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

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

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