Просмотр исходного кода

fix non-auto fixable biome errors

Futa Arai 3 месяцев назад
Родитель
Сommit
e632f2486a

+ 25 - 28
apps/app/src/client/components/Bookmarks/BookmarkFolderItem.tsx

@@ -304,7 +304,6 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (
       bookmarkFolder.name,
       bookmarkFolderTreeMutation,
     ]);
-
   return (
     <div
       id={`grw-bookmark-folder-item-${folderId}`}
@@ -321,25 +320,8 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (
       >
         <li
           className="list-group-item list-group-item-action border-0 py-2 d-flex align-items-center rounded-1"
-          onClick={loadChildFolder}
           style={{ paddingLeft }}
         >
-          <div className="grw-triangle-container d-flex justify-content-center">
-            <button
-              type="button"
-              className={triangleBtnClassName(isOpen, childrenExists)}
-              onClick={loadChildFolder}
-            >
-              <div className="d-flex justify-content-center">
-                <span className="material-symbols-outlined fs-5">
-                  arrow_right
-                </span>
-              </div>
-            </button>
-          </div>
-          <div>
-            <FolderIcon isOpen={isOpen} />
-          </div>
           {isRenameAction ? (
             <div className="flex-fill">
               <BookmarkFolderNameInput
@@ -349,11 +331,25 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (
               />
             </div>
           ) : (
-            <>
+            <button
+              type="button"
+              className="d-flex align-items-center flex-fill border-0 bg-transparent p-0 text-start"
+              onClick={loadChildFolder}
+            >
+              <div className="grw-triangle-container d-flex justify-content-center">
+                <span className={triangleBtnClassName(isOpen, childrenExists)}>
+                  <span className="material-symbols-outlined fs-5">
+                    arrow_right
+                  </span>
+                </span>
+              </div>
+              <div>
+                <FolderIcon isOpen={isOpen} />
+              </div>
               <div className="grw-foldertree-title-anchor ps-1">
                 <p className="text-truncate m-auto">{name}</p>
               </div>
-            </>
+            </button>
           )}
           {isOperable && (
             <div className="grw-foldertree-control d-flex">
@@ -366,14 +362,15 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (
                     : undefined
                 }
               >
-                <div onClick={(e) => e.stopPropagation()}>
-                  <DropdownToggle
-                    color="transparent"
-                    className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover me-1"
-                  >
-                    <span className="material-symbols-outlined">more_vert</span>
-                  </DropdownToggle>
-                </div>
+                <DropdownToggle
+                  color="transparent"
+                  className="border-0 rounded btn-page-item-control p-0 grw-visible-on-hover me-1"
+                  onClick={(event) => {
+                    event.stopPropagation();
+                  }}
+                >
+                  <span className="material-symbols-outlined">more_vert</span>
+                </DropdownToggle>
               </BookmarkFolderItemControl>
               {/* Maximum folder hierarchy of 2 levels */}
               {!(bookmarkFolder.parent != null) && (

+ 12 - 0
apps/app/src/client/components/Bookmarks/BookmarkFolderMenu.tsx

@@ -130,6 +130,15 @@ export const BookmarkFolderMenu = (
     },
     [pageId, mutateCurrentUserBookmarks, mutateBookmarkFolders, mutatePageInfo],
   );
+  const onMenuItemKeyDownHandler = useCallback(
+    (itemId: string) => (event: React.KeyboardEvent<HTMLDivElement>) => {
+      if (event.key === 'Enter' || event.key === ' ') {
+        event.preventDefault();
+        onMenuItemClickHandler(event, itemId);
+      }
+    },
+    [onMenuItemClickHandler],
+  );
 
   const renderBookmarkMenuItem = () => {
     return (
@@ -152,6 +161,7 @@ export const BookmarkFolderMenu = (
                 tabIndex={0}
                 role="menuitem"
                 onClick={(e) => onMenuItemClickHandler(e, 'root')}
+                onKeyDown={onMenuItemKeyDownHandler('root')}
               >
                 <BookmarkFolderMenuItem
                   itemId="root"
@@ -167,6 +177,7 @@ export const BookmarkFolderMenu = (
                   tabIndex={0}
                   role="menuitem"
                   onClick={(e) => onMenuItemClickHandler(e, folder._id)}
+                  onKeyDown={onMenuItemKeyDownHandler(folder._id)}
                 >
                   <BookmarkFolderMenuItem
                     itemId={folder._id}
@@ -181,6 +192,7 @@ export const BookmarkFolderMenu = (
                       tabIndex={0}
                       role="menuitem"
                       onClick={(e) => onMenuItemClickHandler(e, child._id)}
+                      onKeyDown={onMenuItemKeyDownHandler(child._id)}
                     >
                       <BookmarkFolderMenuItem
                         itemId={child._id}

+ 3 - 9
apps/app/src/client/components/InAppNotification/InAppNotificationDropdown.tsx

@@ -65,19 +65,13 @@ export const InAppNotificationDropdown = (): JSX.Element => {
     setIsOpen(newIsOpenState);
   };
 
-  let badge;
-  if (
+  const badge =
     inAppNotificationUnreadStatusCount != null &&
-    inAppNotificationUnreadStatusCount > 0
-  ) {
-    badge = (
+    inAppNotificationUnreadStatusCount > 0 ? (
       <span className="badge rounded-pill bg-danger grw-notification-badge">
         {inAppNotificationUnreadStatusCount}
       </span>
-    );
-  } else {
-    badge = '';
-  }
+    ) : null;
 
   return (
     <Dropdown

+ 102 - 92
apps/app/src/client/components/InAppNotification/InAppNotificationPage.tsx

@@ -16,114 +16,124 @@ import CustomNavAndContents from '../CustomNavigation/CustomNavAndContents';
 import PaginationWrapper from '../PaginationWrapper';
 import InAppNotificationList from './InAppNotificationList';
 
-export const InAppNotificationPage: FC = () => {
+type InAppNotificationCategoryByStatusProps = {
+  status?: InAppNotificationStatuses;
+};
+
+const EmptyIcon: FC = () => {
+  return null;
+};
+
+const InAppNotificationCategoryByStatus: FC<
+  InAppNotificationCategoryByStatusProps
+> = ({ status }) => {
   const { t } = useTranslation('commons');
 
   const showPageLimitationXL = useAtomValue(showPageLimitationXLAtom);
-
   const limit = showPageLimitationXL != null ? showPageLimitationXL : 20;
 
-  const InAppNotificationCategoryByStatus = (
-    status?: InAppNotificationStatuses,
-  ) => {
-    const [activePage, setActivePage] = useState(1);
-    const offset = (activePage - 1) * limit;
-
-    let categoryStatus;
-
-    switch (status) {
-      case InAppNotificationStatuses.STATUS_UNOPENED:
-        categoryStatus = InAppNotificationStatuses.STATUS_UNOPENED;
-        break;
-      default:
-    }
-
-    const { data: notificationData, mutate: mutateNotificationData } =
-      useSWRxInAppNotifications(limit, offset, categoryStatus);
-    const { mutate: mutateAllNotificationData } = useSWRxInAppNotifications(
-      limit,
-      offset,
-      undefined,
-    );
-    const { mutate: mutateNotificationCount } =
-      useSWRxInAppNotificationStatus();
-
-    const setAllNotificationPageNumber = (selectedPageNumber): void => {
-      setActivePage(selectedPageNumber);
-    };
-
-    if (notificationData == null) {
-      return (
-        <div
-          className="wiki"
-          data-testid="grw-in-app-notification-page-spinner"
-        >
-          <div className="text-muted text-center">
-            <LoadingSpinner className="me-1 fs-3" />
-          </div>
-        </div>
-      );
-    }
-
-    const updateUnopendNotificationStatusesToOpened = async () => {
-      await apiv3Put('/in-app-notification/all-statuses-open');
-      // mutate notification statuses in 'UNREAD' Category
-      mutateNotificationData();
-      // mutate notification statuses in 'ALL' Category
-      mutateAllNotificationData();
-      mutateNotificationCount();
-    };
+  const [activePage, setActivePage] = useState(1);
+  const offset = (activePage - 1) * limit;
 
+  const categoryStatus =
+    status === InAppNotificationStatuses.STATUS_UNOPENED
+      ? InAppNotificationStatuses.STATUS_UNOPENED
+      : undefined;
+
+  const { data: notificationData, mutate: mutateNotificationData } =
+    useSWRxInAppNotifications(limit, offset, categoryStatus);
+  const { mutate: mutateAllNotificationData } = useSWRxInAppNotifications(
+    limit,
+    offset,
+    undefined,
+  );
+  const { mutate: mutateNotificationCount } = useSWRxInAppNotificationStatus();
+
+  const setAllNotificationPageNumber = (selectedPageNumber: number): void => {
+    setActivePage(selectedPageNumber);
+  };
+
+  if (notificationData == null) {
     return (
-      <>
-        {status === InAppNotificationStatuses.STATUS_UNOPENED &&
-          notificationData.totalDocs > 0 && (
-            <div className="mb-2 d-flex justify-content-end">
-              <button
-                type="button"
-                className="btn btn-outline-primary"
-                onClick={updateUnopendNotificationStatusesToOpened}
-              >
-                {t('in_app_notification.mark_all_as_read')}
-              </button>
-            </div>
-          )}
-        {notificationData != null && notificationData.docs.length === 0 ? (
-          // no items
-          t('in_app_notification.no_unread_messages')
-        ) : (
-          // render list-group
-          <InAppNotificationList inAppNotificationData={notificationData} />
-        )}
+      <div className="wiki" data-testid="grw-in-app-notification-page-spinner">
+        <div className="text-muted text-center">
+          <LoadingSpinner className="me-1 fs-3" />
+        </div>
+      </div>
+    );
+  }
+
+  const updateUnopendNotificationStatusesToOpened = async () => {
+    await apiv3Put('/in-app-notification/all-statuses-open');
+    // mutate notification statuses in 'UNREAD' Category
+    mutateNotificationData();
+    // mutate notification statuses in 'ALL' Category
+    mutateAllNotificationData();
+    mutateNotificationCount();
+  };
 
-        {notificationData.totalDocs > 0 && (
-          <div className="mt-4">
-            <PaginationWrapper
-              activePage={activePage}
-              changePage={setAllNotificationPageNumber}
-              totalItemsCount={notificationData.totalDocs}
-              pagingLimit={notificationData.limit}
-              align="center"
-              size="sm"
-            />
+  return (
+    <>
+      {status === InAppNotificationStatuses.STATUS_UNOPENED &&
+        notificationData.totalDocs > 0 && (
+          <div className="mb-2 d-flex justify-content-end">
+            <button
+              type="button"
+              className="btn btn-outline-primary"
+              onClick={updateUnopendNotificationStatusesToOpened}
+            >
+              {t('in_app_notification.mark_all_as_read')}
+            </button>
           </div>
         )}
-      </>
-    );
-  };
+      {notificationData != null && notificationData.docs.length === 0 ? (
+        // no items
+        t('in_app_notification.no_unread_messages')
+      ) : (
+        // render list-group
+        <InAppNotificationList inAppNotificationData={notificationData} />
+      )}
+
+      {notificationData.totalDocs > 0 && (
+        <div className="mt-4">
+          <PaginationWrapper
+            activePage={activePage}
+            changePage={setAllNotificationPageNumber}
+            totalItemsCount={notificationData.totalDocs}
+            pagingLimit={notificationData.limit}
+            align="center"
+            size="sm"
+          />
+        </div>
+      )}
+    </>
+  );
+};
+
+const InAppNotificationAllTabContent: FC = () => {
+  return <InAppNotificationCategoryByStatus />;
+};
+
+const InAppNotificationUnreadTabContent: FC = () => {
+  return (
+    <InAppNotificationCategoryByStatus
+      status={InAppNotificationStatuses.STATUS_UNOPENED}
+    />
+  );
+};
+
+export const InAppNotificationPage: FC = () => {
+  const { t } = useTranslation('commons');
 
   const navTabMapping = {
     user_infomation: {
-      Icon: () => <></>,
-      Content: () => InAppNotificationCategoryByStatus(),
+      Icon: EmptyIcon,
+      Content: InAppNotificationAllTabContent,
       i18n: t('in_app_notification.all'),
     },
     external_accounts: {
-      Icon: () => <></>,
-      Content: () =>
-        InAppNotificationCategoryByStatus(
-          InAppNotificationStatuses.STATUS_UNOPENED,
-        ),
+      Icon: EmptyIcon,
+      Content: InAppNotificationUnreadTabContent,
       i18n: t('in_app_notification.unopend'),
     },
   };

+ 2 - 2
apps/app/src/client/components/Me/AccessTokenScopeList.tsx

@@ -46,11 +46,11 @@ export const AccessTokenScopeList: React.FC<AccessTokenScopeListProps> = ({
               {showHr && <hr className="my-1" />}
               <div className="my-1 row">
                 <div className="col-md-5 ">
-                  <label
+                  <span
                     className={`form-check-label fw-bold indentation indentation-level-${level}`}
                   >
                     {scopeKey}
-                  </label>
+                  </span>
                 </div>
               </div>
 

+ 1 - 1
apps/app/src/client/components/Me/AccessTokenSettings.tsx

@@ -110,7 +110,7 @@ export const AccessTokenSettings = React.memo((): JSX.Element => {
         toastError(err);
       }
     },
-    [t, generateAccessToken, mutate, setIsFormOpen],
+    [t, generateAccessToken, mutate],
   );
 
   const deleteHandler = useCallback(

+ 5 - 5
apps/app/src/client/components/Me/BasicInfoSettings.tsx

@@ -118,9 +118,9 @@ export const BasicInfoSettings = (): JSX.Element => {
       </div>
 
       <div className="row mt-3">
-        <label className="text-start text-md-end col-md-3 col-form-label">
+        <span className="text-start text-md-end col-md-3 col-form-label">
           {t('Disclose E-mail')}
-        </label>
+        </span>
         <div className="col-md-6 my-auto">
           <div className="form-check form-check-inline me-4">
             <input
@@ -162,13 +162,13 @@ export const BasicInfoSettings = (): JSX.Element => {
       </div>
 
       <div className="row mt-3">
-        <label className="text-start text-md-end col-md-3 col-form-label">
+        <span className="text-start text-md-end col-md-3 col-form-label">
           {t('Language')}
-        </label>
+        </span>
         <div className="col-md-6 my-auto">
           {i18nConfig.locales.map((locale) => {
             if (i18n == null) {
-              return;
+              return null;
             }
             const fixedT = i18n.getFixedT(locale);
 

+ 2 - 7
apps/app/src/client/components/Me/ColorModeSettings.tsx

@@ -1,5 +1,5 @@
 import React, { type JSX, useCallback } from 'react';
-import { useTranslation } from 'react-i18next';
+import { Trans, useTranslation } from 'react-i18next';
 
 import { Themes, useNextThemes } from '~/stores-universal/use-next-themes';
 
@@ -81,12 +81,7 @@ export const ColorModeSettings = (): JSX.Element => {
         </div>
 
         <div className="mt-3 text-muted small">
-          {/* eslint-disable-next-line react/no-danger */}
-          <span
-            dangerouslySetInnerHTML={{
-              __html: t('color_mode_settings.description'),
-            }}
-          />
+          <Trans i18nKey="color_mode_settings.description" />
         </div>
       </div>
     </div>

+ 7 - 10
apps/app/src/client/components/Me/DisassociateModal.tsx

@@ -1,7 +1,7 @@
 import type React from 'react';
 import { useCallback } from 'react';
 import type { HasObjectId, IExternalAccount } from '@growi/core';
-import { useTranslation } from 'next-i18next';
+import { Trans, useTranslation } from 'next-i18next';
 import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
 
 import { toastError, toastSuccess } from '~/client/util/toastr';
@@ -65,15 +65,12 @@ const DisassociateModalSubstance = (
         {t('personal_settings.disassociate_external_account')}
       </ModalHeader>
       <ModalBody>
-        {/* eslint-disable-next-line react/no-danger */}
-        <p
-          dangerouslySetInnerHTML={{
-            __html: t('personal_settings.disassociate_external_account_desc', {
-              providerType,
-              accountId,
-            }),
-          }}
-        />
+        <p>
+          <Trans
+            i18nKey="personal_settings.disassociate_external_account_desc"
+            values={{ providerType, accountId }}
+          />
+        </p>
       </ModalBody>
       <ModalFooter>
         <button

+ 1 - 1
apps/app/src/client/components/Me/InAppNotificationSettings.tsx

@@ -74,7 +74,7 @@ const InAppNotificationSettings: FC = () => {
     } catch (err) {
       toastError(err);
     }
-  }, [subscribeRules, setSubscribeRules, t]);
+  }, [subscribeRules, t]);
 
   useEffect(() => {
     initializeInAppNotificationSettings();

+ 60 - 48
apps/app/src/client/components/Me/PersonalSettings.jsx

@@ -10,56 +10,82 @@ import OtherSettings from './OtherSettings';
 import PasswordSettings from './PasswordSettings';
 import UserSettings from './UserSettings';
 
+const UserInformationIcon = () => (
+  <span
+    data-testid="user-infomation-tab-button"
+    className="material-symbols-outlined"
+  >
+    person
+  </span>
+);
+
+const ExternalAccountsIcon = () => (
+  <span
+    data-testid="external-accounts-tab-button"
+    className="material-symbols-outlined"
+  >
+    ungroup
+  </span>
+);
+
+const PasswordSettingsIcon = () => (
+  <span
+    data-testid="password-settings-tab-button"
+    className="material-symbols-outlined"
+  >
+    password
+  </span>
+);
+
+const ApiSettingsIcon = () => (
+  <span
+    data-testid="api-settings-tab-button"
+    className="material-symbols-outlined"
+  >
+    api
+  </span>
+);
+
+const InAppNotificationSettingsIcon = () => (
+  <span
+    data-testid="in-app-notification-settings-tab-button"
+    className="material-symbols-outlined"
+  >
+    notifications
+  </span>
+);
+
+const OtherSettingsIcon = () => (
+  <span
+    data-testid="other-settings-tab-button"
+    className="material-symbols-outlined"
+  >
+    settings
+  </span>
+);
+
 const PersonalSettings = () => {
   const { t } = useTranslation();
 
   const navTabMapping = useMemo(() => {
     return {
       user_infomation: {
-        Icon: () => (
-          <span
-            data-testid="user-infomation-tab-button"
-            className="material-symbols-outlined"
-          >
-            person
-          </span>
-        ),
+        Icon: UserInformationIcon,
         Content: UserSettings,
         i18n: t('User Information'),
       },
       external_accounts: {
-        Icon: () => (
-          <span
-            data-testid="external-accounts-tab-button"
-            className="material-symbols-outlined"
-          >
-            ungroup
-          </span>
-        ),
+        Icon: ExternalAccountsIcon,
         Content: ExternalAccountLinkedMe,
         i18n: t('admin:user_management.external_accounts'),
       },
       password_settings: {
-        Icon: () => (
-          <span
-            data-testid="password-settings-tab-button"
-            className="material-symbols-outlined"
-          >
-            password
-          </span>
-        ),
+        Icon: PasswordSettingsIcon,
         Content: PasswordSettings,
         i18n: t('Password Settings'),
       },
       api_settings: {
-        Icon: () => (
-          <span
-            data-testid="api-settings-tab-button"
-            className="material-symbols-outlined"
-          >
-            api
-          </span>
-        ),
+        Icon: ApiSettingsIcon,
         Content: ApiSettings,
         i18n: t('API Settings'),
       },
@@ -69,26 +95,12 @@ const PersonalSettings = () => {
       //   i18n: t('editor_settings.editor_settings'),
       // },
       in_app_notification_settings: {
-        Icon: () => (
-          <span
-            data-testid="in-app-notification-settings-tab-button"
-            className="material-symbols-outlined"
-          >
-            notifications
-          </span>
-        ),
+        Icon: InAppNotificationSettingsIcon,
         Content: InAppNotificationSettings,
         i18n: t('in_app_notification_settings.in_app_notification_settings'),
       },
       other_settings: {
-        Icon: () => (
-          <span
-            data-testid="other-settings-tab-button"
-            className="material-symbols-outlined"
-          >
-            settings
-          </span>
-        ),
+        Icon: OtherSettingsIcon,
         Content: OtherSettings,
         i18n: t('Other Settings'),
       },

+ 7 - 4
apps/app/src/client/components/Me/ProfileImageSettings.tsx

@@ -136,6 +136,7 @@ const ProfileImageSettings = (): JSX.Element => {
               >
                 <img
                   src={GRAVATAR_DEFAULT}
+                  alt="Gravatar"
                   className="me-1"
                   data-vrt-blackout-profile
                 />{' '}
@@ -159,6 +160,7 @@ const ProfileImageSettings = (): JSX.Element => {
           </h5>
           <img
             src={generateGravatarSrc(currentUser.email)}
+            alt="Gravatar"
             className="rounded-pill"
             width="64"
             height="64"
@@ -187,13 +189,14 @@ const ProfileImageSettings = (): JSX.Element => {
             </div>
           </h5>
           <div className="row mt-3">
-            <label className="col-md-6 col-lg-4 col-form-label text-start">
+            <span className="col-md-6 col-lg-4 col-form-label text-start">
               {t('Current Image')}
-            </label>
+            </span>
             <div className="col-md-6 col-lg-8">
               <p className="mb-0">
                 <img
                   src={uploadedPictureSrc ?? DEFAULT_IMAGE}
+                  alt={t('Current Image')}
                   width="64"
                   height="64"
                   className="rounded-circle"
@@ -212,9 +215,9 @@ const ProfileImageSettings = (): JSX.Element => {
             </div>
           </div>
           <div className="row align-items-center mt-3 mt-md-5">
-            <label className="col-md-6 col-lg-4 col-form-label text-start mt-3 mt-md-0">
+            <span className="col-md-6 col-lg-4 col-form-label text-start mt-3 mt-md-0">
               {t('Upload new image')}
-            </label>
+            </span>
             <div className="col-md-6 col-lg-8">
               <input
                 type="file"

+ 5 - 1
apps/app/src/client/components/Me/UISettings.tsx

@@ -86,7 +86,11 @@ export const UISettings = (): JSX.Element => {
               <label
                 className="form-label form-check-label"
                 htmlFor="swSidebarMode"
-              ></label>
+              >
+                <span className="visually-hidden">
+                  {t('ui_settings.side_bar_mode.side_bar_mode_setting')}
+                </span>
+              </label>
             </div>
             <IconWithTooltip
               id="iwt-sidebar-dock"

+ 2 - 2
apps/app/src/client/components/PageTags/RenderTagLabels.tsx

@@ -15,14 +15,14 @@ const RenderTagLabels = React.memo((props: RenderTagLabelsProps) => {
   return (
     <SimpleBar className="grw-tag-simple-bar pe-1">
       {tags.map((tag) => (
-        <a
+        <button
           key={tag}
           type="button"
           className="grw-tag badge me-1 mb-1 text-truncate mw-100"
           onClick={() => setSearchKeyword(`tag:${tag}`)}
         >
           {tag}
-        </a>
+        </button>
       ))}
     </SimpleBar>
   );

+ 6 - 5
apps/app/src/client/components/ReactMarkdownComponents/Header.tsx

@@ -49,13 +49,14 @@ const EditLink = (props: EditLinkProps): JSX.Element => {
 
   return (
     <span className="revision-head-edit-button">
-      <a
-        href="#edit"
-        aria-disabled={isDisabled}
+      <button
+        type="button"
+        className="border-0 bg-transparent p-0"
+        disabled={isDisabled}
         onClick={() => setCaretLine(props.line)}
       >
         <span className="material-symbols-outlined">edit_square</span>
-      </a>
+      </button>
     </span>
   );
 };
@@ -120,7 +121,7 @@ export const Header = (props: HeaderProps): JSX.Element => {
     return () => {
       window.removeEventListener('hashchange', activeByHashWrapper);
     };
-  }, [activateByHash, router.events]);
+  }, [activateByHash]);
 
   // TODO: currentPageYjsData?.hasYdocsNewerThanLatestRevision === false make to hide the edit button when a Yjs draft exists
   // This is because the current conditional logic cannot handle cases where the draft is an empty string.

+ 14 - 2
apps/app/src/client/components/ReactMarkdownComponents/LightBox.tsx

@@ -10,7 +10,7 @@ type Props = DetailedHTMLProps<
 
 export const LightBox = (props: Props): JSX.Element => {
   const [toggler, setToggler] = useState(false);
-  const { alt, ...rest } = props;
+  const { alt, onClick: onImageClick, ...rest } = props;
 
   const lightboxPortal = useMemo(() => {
     return createPortal(
@@ -28,7 +28,19 @@ export const LightBox = (props: Props): JSX.Element => {
   return (
     <>
       {/* eslint-disable-next-line @next/next/no-img-element */}
-      <img alt={alt} {...rest} onClick={() => setToggler(!toggler)} />
+      <button
+        type="button"
+        className="border-0 bg-transparent p-0"
+        aria-label={alt ?? 'Open image'}
+        onClick={(event) => {
+          onImageClick?.(
+            event as unknown as React.MouseEvent<HTMLImageElement>,
+          );
+          setToggler((prev) => !prev);
+        }}
+      >
+        <img alt={alt} {...rest} />
+      </button>
 
       {lightboxPortal}
     </>

+ 3 - 3
apps/app/src/client/components/ReactMarkdownComponents/RichAttachment.tsx

@@ -106,13 +106,13 @@ export const RichAttachment = React.memo((props: RichAttachmentProps) => {
               </a>
 
               {showTrashButton && (
-                <a
-                  className="ml-2 text-danger attachment-delete d-share-link-none"
+                <button
                   type="button"
+                  className="ml-2 text-danger attachment-delete d-share-link-none"
                   onClick={onClickTrashButtonHandler}
                 >
                   <span className="material-symbols-outlined">delete</span>
-                </a>
+                </button>
               )}
             </div>
             <div className="d-flex align-items-center">