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

fix non-auto fixable biome errors)

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

+ 28 - 19
apps/app/src/client/components/DescendantsPageListModal/DescendantsPageListModal.tsx

@@ -29,6 +29,29 @@ const PageTimeline = dynamic(
   { ssr: false },
 );
 
+const PageListTabIcon = (): React.JSX.Element => (
+  <span className="material-symbols-outlined">subject</span>
+);
+
+const PageListTabContent = (): React.JSX.Element | null => {
+  const status = useDescendantsPageListModalStatus();
+  const path = status?.path;
+
+  if (path == null) {
+    return null;
+  }
+
+  return <DescendantsPageList path={path} />;
+};
+
+const TimelineTabIcon = (): React.JSX.Element => (
+  <span data-testid="timeline-tab-button" className="material-symbols-outlined">
+    timeline
+  </span>
+);
+
+const TimelineTabContent = (): React.JSX.Element => <PageTimeline />;
+
 /**
  * DescendantsPageListModalSubstance - Presentation component (all logic here)
  */
@@ -62,33 +85,19 @@ const DescendantsPageListModalSubstance = ({
   const navTabMapping = useMemo(() => {
     return {
       pagelist: {
-        Icon: () => <span className="material-symbols-outlined">subject</span>,
-        Content: () => {
-          if (path == null) {
-            return <></>;
-          }
-          return <DescendantsPageList path={path} />;
-        },
+        Icon: PageListTabIcon,
+        Content: PageListTabContent,
         i18n: t('page_list'),
         isLinkEnabled: () => !isSharedUser,
       },
       timeline: {
-        Icon: () => (
-          <span
-            data-testid="timeline-tab-button"
-            className="material-symbols-outlined"
-          >
-            timeline
-          </span>
-        ),
-        Content: () => {
-          return <PageTimeline />;
-        },
+        Icon: TimelineTabIcon,
+        Content: TimelineTabContent,
         i18n: t('Timeline View'),
         isLinkEnabled: () => !isSharedUser,
       },
     };
-  }, [isSharedUser, path, t]);
+  }, [isSharedUser, t]);
 
   // Memoize event handlers
   const expandWindow = useCallback(() => {

+ 21 - 19
apps/app/src/client/components/LoginForm/LoginForm.tsx

@@ -147,10 +147,12 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
       if (errors == null || errors.length === 0) return <></>;
       return (
         <div className="alert alert-danger">
-          {errors.map((err) => {
+          {errors.map((err, index) => {
             // eslint-disable-next-line react/no-danger
             return (
               <small
+                key={`${err.code}-${index}`}
+                // biome-ignore lint/security/noDangerouslySetInnerHtml: rendered HTML from translations
                 dangerouslySetInnerHTML={{
                   __html: tWithOpt(err.message, err.args),
                 }}
@@ -170,7 +172,10 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
       return (
         <ul className="alert alert-danger">
           {errors.map((err, index) => (
-            <small className={index > 0 ? 'mt-1' : ''}>
+            <small
+              key={`${err.message}-${index}`}
+              className={index > 0 ? 'mt-1' : ''}
+            >
               {tWithOpt(err.message, err.args)}
             </small>
           ))}
@@ -200,6 +205,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
             <br />
             {/* eslint-disable-next-line react/no-danger */}
             <span
+              // biome-ignore lint/security/noDangerouslySetInnerHtml: rendered HTML from translations
               dangerouslySetInnerHTML={{
                 __html: t('login.set_env_var_for_logs'),
               }}
@@ -207,16 +213,13 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           </div>
         )}
 
-        <form role="form" onSubmit={handleLoginWithLocalSubmit} id="login-form">
+        <form onSubmit={handleLoginWithLocalSubmit} id="login-form">
           <div className="input-group">
             <label
               className="text-white opacity-75 d-flex align-items-center"
               htmlFor="tiUsernameForLogin"
             >
-              <span
-                className="material-symbols-outlined"
-                aria-label="Username or E-mail"
-              >
+              <span className="material-symbols-outlined" aria-hidden="true">
                 person
               </span>
             </label>
@@ -244,7 +247,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
               className="text-white opacity-75 d-flex align-items-center"
               htmlFor="tiPasswordForLogin"
             >
-              <span className="material-symbols-outlined" aria-label="Password">
+              <span className="material-symbols-outlined" aria-hidden="true">
                 lock
               </span>
             </label>
@@ -274,7 +277,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
                 ) : (
                   <span
                     className="material-symbols-outlined"
-                    aria-label="Login"
+                    aria-hidden="true"
                   >
                     login
                   </span>
@@ -299,7 +302,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
       <>
         <div className="mt-2">
           {enabledExternalAuthType.map((authType) => (
-            <ExternalAuthButton authType={authType} />
+            <ExternalAuthButton key={authType} authType={authType} />
           ))}
         </div>
       </>
@@ -392,8 +395,8 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
 
         {registerErrors != null && registerErrors.length > 0 && (
           <p className="alert alert-danger">
-            {registerErrors.map((err) => (
-              <span>
+            {registerErrors.map((err, index) => (
+              <span key={`${err.message}-${index}`}>
                 {tWithOpt(err.message, err.args)}
                 <br />
               </span>
@@ -412,7 +415,6 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
         )}
 
         <form
-          role="form"
           onSubmit={(e) => handleRegisterFormSubmit(e, registerAction)}
           id="register-form"
         >
@@ -537,15 +539,15 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
 
         <div className="row">
           <div className="text-end col-12 mb-5">
-            <a
-              href="#login"
+            <button
+              type="button"
               className="btn btn-sm btn-secondary btn-function col-10 col-sm-9 mx-auto py-1 d-flex"
               style={{ pointerEvents: isLoading ? 'none' : undefined }}
               onClick={switchForm}
             >
               <span className="material-symbols-outlined fs-5">login</span>
               <span className="flex-grow-1">{t('Sign in is here')}</span>
-            </a>
+            </button>
           </div>
         </div>
       </React.Fragment>
@@ -647,8 +649,8 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
                 {/* Sign up link */}
                 {isRegistrationEnabled && (
                   <div className="mt-2">
-                    <a
-                      href="#register"
+                    <button
+                      type="button"
                       className="btn btn-sm btn-secondary btn-function col-10 col-sm-9 mx-auto py-1 d-flex"
                       style={{ pointerEvents: isLoading ? 'none' : 'auto' }}
                       onClick={switchForm}
@@ -659,7 +661,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
                       <span className="flex-grow-1">
                         {t('Sign up is here')}
                       </span>
-                    </a>
+                    </button>
                   </div>
                 )}
               </div>

+ 1 - 1
apps/app/src/client/components/PageAttachment/DeleteAttachmentModal.tsx

@@ -74,7 +74,7 @@ const DeleteAttachmentModalSubstance = ({
 
     const content = attachment.fileFormat.match(/image\/.+/i) ? (
       // eslint-disable-next-line @next/next/no-img-element
-      <img src={attachment.filePathProxied} alt="deleting image" />
+      <img src={attachment.filePathProxied} alt="deleting attachment" />
     ) : (
       ''
     );

+ 9 - 6
apps/app/src/client/components/PageDeleteModal/PageDeleteModal.tsx

@@ -58,6 +58,7 @@ export const PageDeleteModal: FC = () => {
   const pageIds = useMemo(() => pages?.map((p) => p.data._id) ?? [], [pages]);
   const pagesLength = pages?.length ?? 0;
 
+  // biome-ignore lint/correctness/useExhaustiveDependencies: keep optimized deps
   const notOperatablePages: IPageToDeleteWithMeta[] = useMemo(
     () =>
       (pages ?? []).filter((p) => !isIPageInfoForEntityForDeleteModal(p.meta)),
@@ -74,6 +75,7 @@ export const PageDeleteModal: FC = () => {
   const { injectTo } = useSWRxPageInfoForList(notOperatablePageIds);
 
   // inject IPageInfo to operate
+  // biome-ignore lint/correctness/useExhaustiveDependencies: keep optimized deps
   const injectedPages = useMemo(
     () => {
       if (pages != null) {
@@ -101,6 +103,7 @@ export const PageDeleteModal: FC = () => {
   }, [injectedPages]);
 
   // Optimize deps: use page paths for trash detection
+  // biome-ignore lint/correctness/useExhaustiveDependencies: keep optimized deps
   const pagePaths = useMemo(
     () => pages?.map((p) => p.data?.path ?? '') ?? [],
     // Optimization: Use pageIds and pagesLength instead of pages array reference to avoid unnecessary re-computation
@@ -151,6 +154,7 @@ export const PageDeleteModal: FC = () => {
     setIsDeleteCompletely(!isDeleteCompletely);
   }, [forceDeleteCompletelyMode, isDeleteCompletely]);
 
+  // biome-ignore lint/correctness/useExhaustiveDependencies: keep optimized deps
   const deletePage = useCallback(
     async () => {
       if (pages == null) {
@@ -193,9 +197,9 @@ export const PageDeleteModal: FC = () => {
           setErrs([err]);
         }
       } else {
-      /*
-       * When single page
-       */
+        /*
+         * When single page
+         */
         try {
           const recursively = isDeleteRecursively === true ? true : undefined;
           const completely =
@@ -325,6 +329,7 @@ export const PageDeleteModal: FC = () => {
     );
   }, [isOpened, deleteMode, t]);
 
+  // biome-ignore lint/correctness/useExhaustiveDependencies: keep optimized deps
   const bodyContent = useMemo(() => {
     if (!isOpened) {
       return <></>;
@@ -352,9 +357,7 @@ export const PageDeleteModal: FC = () => {
     return (
       <>
         <div className="grw-scrollable-modal-body pb-1">
-          <label className="form-label">
-            {t('modal_delete.deleting_page')}:
-          </label>
+          <span className="form-label">{t('modal_delete.deleting_page')}:</span>
           <br />
           {/* Todo: change the way to show path on modal when too many pages are selected */}
           {pagePathsElements}

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

@@ -106,13 +106,7 @@ const PageDuplicateModalSubstance: React.FC = () => {
     if (isOpened && page != null && pageNameInput !== page.path) {
       checkExistPathsDebounce(page.path, pageNameInput);
     }
-  }, [
-    isOpened,
-    pageNameInput,
-    subordinatedPages,
-    checkExistPathsDebounce,
-    page,
-  ]);
+  }, [isOpened, pageNameInput, checkExistPathsDebounce, page]);
 
   const ppacInputChangeHandler = useCallback((value: string) => {
     setErrs(null);
@@ -200,9 +194,9 @@ const PageDuplicateModalSubstance: React.FC = () => {
     return (
       <>
         <div className="mt-3">
-          <label className="form-label">
+          <span className="form-label">
             {t('modal_duplicate.label.Current page name')}
-          </label>
+          </span>
           <br />
           <code>{path}</code>
         </div>

+ 128 - 118
apps/app/src/client/components/PageList/PageListItemL.tsx

@@ -240,134 +240,144 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (
   const hasBrowsingRights = canRenderESSnippet || canRenderRevisionSnippet;
 
   return (
-    <li
-      key={pageData._id}
-      className={`list-group-item d-flex align-items-center px-3 px-md-1 ${styleListGroupItem} ${styleActive}`}
-      data-testid="page-list-item-L"
-      onClick={clickHandler}
-    >
-      <div className="text-break w-100">
-        <div className="d-flex">
-          {/* checkbox */}
-          {onCheckboxChanged != null && (
-            <div className="d-flex align-items-center justify-content-center">
-              <Input
-                type="checkbox"
-                id={`cbSelect-${pageData._id}`}
-                data-testid="cb-select"
-                innerRef={inputRef}
-                onChange={(e) => {
-                  onCheckboxChanged(e.target.checked, pageData._id);
-                }}
-              />
-            </div>
-          )}
-
-          <div className="flex-grow-1 px-2 px-md-4">
-            <div className="d-flex justify-content-between">
-              {/* page path */}
-              <PagePathHierarchicalLink
-                linkedPagePath={linkedPagePathFormer}
-                linkedPagePathByHtml={linkedPagePathHighlightedFormer}
-              />
-              {showPageUpdatedTime && (
-                <span className="page-list-updated-at text-muted">
-                  Last update: {lastUpdateDate}
-                </span>
-              )}
-            </div>
-            <div className="d-flex align-items-center mb-1">
-              {/* Picture */}
-              <span className="me-2 d-none d-md-block">
-                <UserPicture user={pageData.lastUpdateUser} size="md" />
-              </span>
-              {/* page title */}
-              <Clamp lines={1}>
-                <span className="h5 mb-0">
-                  {/* Use permanent links to care for pages with the same name (Cannot use page path url) */}
-                  <span className="text-break">
-                    <Link
-                      legacyBehavior
-                      href={returnPathForURL(pageData.path, pageData._id)}
-                      prefetch={false}
-                    >
-                      {shouldDangerouslySetInnerHTMLForPaths ? (
-                        <a
-                          className="page-segment"
-                          // eslint-disable-next-line react/no-danger
-                          dangerouslySetInnerHTML={{
-                            __html: linkedPagePathHighlightedLatter.pathName,
-                          }}
-                        ></a>
-                      ) : (
-                        <a className="page-segment">
-                          {linkedPagePathHighlightedLatter.pathName}
-                        </a>
-                      )}
-                    </Link>
-                  </span>
-                </span>
-              </Clamp>
-
-              {/* page meta */}
-              <div className="d-none d-md-flex py-0 px-1 ms-2 text-nowrap">
-                <PageListMeta
-                  page={pageData}
-                  likerCount={likerCount}
-                  bookmarkCount={bookmarkCount}
-                  shouldSpaceOutIcon
+    <li key={pageData._id}>
+      <button
+        type="button"
+        className={`list-group-item d-flex align-items-center px-3 px-md-1 text-start w-100 ${styleListGroupItem} ${styleActive}`}
+        data-testid="page-list-item-L"
+        onClick={clickHandler}
+      >
+        <div className="text-break w-100">
+          <div className="d-flex">
+            {/* checkbox */}
+            {onCheckboxChanged != null && (
+              <div className="d-flex align-items-center justify-content-center">
+                <Input
+                  type="checkbox"
+                  id={`cbSelect-${pageData._id}`}
+                  data-testid="cb-select"
+                  innerRef={inputRef}
+                  onChange={(e) => {
+                    onCheckboxChanged(e.target.checked, pageData._id);
+                  }}
                 />
               </div>
-
-              {/* doropdown icon includes page control buttons */}
-              {hasBrowsingRights && (
-                <div className="ms-auto">
-                  <PageItemControl
-                    alignEnd
-                    pageId={pageData._id}
-                    pageInfo={
-                      isIPageInfoForListing(pageMeta) ? pageMeta : undefined
-                    }
-                    isEnableActions={isEnableActions}
-                    isReadOnlyUser={isReadOnlyUser}
-                    forceHideMenuItems={forceHideMenuItems}
-                    onClickBookmarkMenuItem={bookmarkMenuItemClickHandler}
-                    onClickRenameMenuItem={renameMenuItemClickHandler}
-                    onClickDuplicateMenuItem={duplicateMenuItemClickHandler}
-                    onClickDeleteMenuItem={deleteMenuItemClickHandler}
-                    onClickRevertMenuItem={revertMenuItemClickHandler}
+            )}
+
+            <div className="flex-grow-1 px-2 px-md-4">
+              <div className="d-flex justify-content-between">
+                {/* page path */}
+                <PagePathHierarchicalLink
+                  linkedPagePath={linkedPagePathFormer}
+                  linkedPagePathByHtml={linkedPagePathHighlightedFormer}
+                />
+                {showPageUpdatedTime && (
+                  <span className="page-list-updated-at text-muted">
+                    Last update: {lastUpdateDate}
+                  </span>
+                )}
+              </div>
+              <div className="d-flex align-items-center mb-1">
+                {/* Picture */}
+                <span className="me-2 d-none d-md-block">
+                  <UserPicture user={pageData.lastUpdateUser} size="md" />
+                </span>
+                {/* page title */}
+                <Clamp lines={1}>
+                  <span className="h5 mb-0">
+                    {/* Use permanent links to care for pages with the same name (Cannot use page path url) */}
+                    <span className="text-break">
+                      <Link
+                        legacyBehavior
+                        href={returnPathForURL(pageData.path, pageData._id)}
+                        prefetch={false}
+                      >
+                        {shouldDangerouslySetInnerHTMLForPaths ? (
+                          <a
+                            className="page-segment"
+                            href={returnPathForURL(pageData.path, pageData._id)}
+                            // eslint-disable-next-line react/no-danger
+                            // biome-ignore lint/security/noDangerouslySetInnerHtml: highlight markup is sanitized
+                            dangerouslySetInnerHTML={{
+                              __html: linkedPagePathHighlightedLatter.pathName,
+                            }}
+                          ></a>
+                        ) : (
+                          <a
+                            className="page-segment"
+                            href={returnPathForURL(pageData.path, pageData._id)}
+                          >
+                            {linkedPagePathHighlightedLatter.pathName}
+                          </a>
+                        )}
+                      </Link>
+                    </span>
+                  </span>
+                </Clamp>
+
+                {/* page meta */}
+                <div className="d-none d-md-flex py-0 px-1 ms-2 text-nowrap">
+                  <PageListMeta
+                    page={pageData}
+                    likerCount={likerCount}
+                    bookmarkCount={bookmarkCount}
+                    shouldSpaceOutIcon
                   />
                 </div>
-              )}
-            </div>
-            <div className="page-list-snippet py-1">
-              <Clamp lines={2}>
-                {elasticSearchResult != null &&
-                  elasticSearchResult.snippet != null && (
-                    // eslint-disable-next-line react/no-danger
-                    <div
-                      dangerouslySetInnerHTML={{
-                        __html: elasticSearchResult.snippet,
-                      }}
-                    ></div>
-                  )}
-                {revisionShortBody != null && (
-                  <div data-testid="revision-short-body-in-page-list-item-L">
-                    {revisionShortBody}
+
+                {/* doropdown icon includes page control buttons */}
+                {hasBrowsingRights && (
+                  <div className="ms-auto">
+                    <PageItemControl
+                      alignEnd
+                      pageId={pageData._id}
+                      pageInfo={
+                        isIPageInfoForListing(pageMeta) ? pageMeta : undefined
+                      }
+                      isEnableActions={isEnableActions}
+                      isReadOnlyUser={isReadOnlyUser}
+                      forceHideMenuItems={forceHideMenuItems}
+                      onClickBookmarkMenuItem={bookmarkMenuItemClickHandler}
+                      onClickRenameMenuItem={renameMenuItemClickHandler}
+                      onClickDuplicateMenuItem={duplicateMenuItemClickHandler}
+                      onClickDeleteMenuItem={deleteMenuItemClickHandler}
+                      onClickRevertMenuItem={revertMenuItemClickHandler}
+                    />
                   </div>
                 )}
-                {!hasBrowsingRights && (
-                  <>
-                    <span className="material-symbols-outlined p-1">error</span>
-                    {t('not_allowed_to_see_this_page')}
-                  </>
-                )}
-              </Clamp>
+              </div>
+              <div className="page-list-snippet py-1">
+                <Clamp lines={2}>
+                  {elasticSearchResult != null &&
+                    elasticSearchResult.snippet != null && (
+                      // eslint-disable-next-line react/no-danger
+                      <div
+                        // biome-ignore lint/security/noDangerouslySetInnerHtml: snippet markup is sanitized
+                        dangerouslySetInnerHTML={{
+                          __html: elasticSearchResult.snippet,
+                        }}
+                      ></div>
+                    )}
+                  {revisionShortBody != null && (
+                    <div data-testid="revision-short-body-in-page-list-item-L">
+                      {revisionShortBody}
+                    </div>
+                  )}
+                  {!hasBrowsingRights && (
+                    <>
+                      <span className="material-symbols-outlined p-1">
+                        error
+                      </span>
+                      {t('not_allowed_to_see_this_page')}
+                    </>
+                  )}
+                </Clamp>
+              </div>
             </div>
           </div>
         </div>
         {/* TODO: adjust snippet position */}
-      </div>
+      </button>
     </li>
   );
 };

+ 2 - 2
apps/app/src/client/components/PageManagement/ApiErrorMessage.jsx

@@ -55,12 +55,12 @@ const ApiErrorMessage = (props) => {
               <span className="material-symbols-outlined me-1">lightbulb</span>{' '}
               {t('page_api_error.outdated')}
             </strong>
-            <a className="btn-link" onClick={reload}>
+            <button type="button" className="btn-link" onClick={reload}>
               <span className="material-symbols-outlined">
                 keyboard_double_arrow_right
               </span>{' '}
               {t('Load latest')}
-            </a>
+            </button>
           </>
         );
       case 'invalid_path':

+ 3 - 1
apps/app/src/client/components/PagePathNavSticky/CollapsedParentsDropdown.tsx

@@ -48,7 +48,9 @@ export const CollapsedParentsDropdown = (props: Props): JSX.Element => {
         {ancestorPathAndPathNames.map((data) => (
           <DropdownItem key={data.path}>
             <Link href={data.path} legacyBehavior>
-              <a role="menuitem">{data.pathName}</a>
+              <a role="menuitem" href={data.path}>
+                {data.pathName}
+              </a>
             </Link>
           </DropdownItem>
         ))}

+ 2 - 2
apps/app/src/client/components/PagePathNavSticky/PagePathNavSticky.tsx

@@ -48,7 +48,7 @@ export const PagePathNavSticky = (
     setNavMaxWidth(
       pageControlsX - pagePathNavRef.current.getBoundingClientRect().x - 10,
     );
-  }, [pageControlsX, pagePathNavRef, sidebarWidth]);
+  }, [pageControlsX, sidebarWidth]);
 
   useEffect(() => {
     // wait for the end of the animation of the opening and closing of the sidebar
@@ -67,7 +67,7 @@ export const PagePathNavSticky = (
     return () => {
       clearTimeout(timeout);
     };
-  }, [pageControlsX, pagePathNavRef, sidebarMode]);
+  }, [pageControlsX, sidebarMode]);
 
   const latterLink = useMemo(() => {
     const dPagePath = new DevidedPagePath(pagePath, false, true);

+ 2 - 5
apps/app/src/client/components/PageRenameModal/PageRenameModal.tsx

@@ -211,7 +211,6 @@ const PageRenameModalSubstance: React.FC = () => {
   }, [
     isOpened,
     pageNameInput,
-    subordinatedPages,
     checkExistPathsDebounce,
     page,
     checkIsUsersHomepageDebounce,
@@ -259,9 +258,9 @@ const PageRenameModalSubstance: React.FC = () => {
     return (
       <>
         <div className="mb-3">
-          <label className="form-label w-100">
+          <span className="form-label w-100">
             {t('modal_rename.label.Current page name')}
-          </label>
+          </span>
           <code className="fs-6">{path}</code>
         </div>
         <div className="mb-3">
@@ -284,7 +283,6 @@ const PageRenameModalSubstance: React.FC = () => {
                   initializedPath={path}
                   onSubmit={rename}
                   onInputChange={ppacInputChangeHandler}
-                  autoFocus
                 />
               ) : (
                 <input
@@ -293,7 +291,6 @@ const PageRenameModalSubstance: React.FC = () => {
                   className="form-control"
                   onChange={(e) => inputChangeHandler(e.target.value)}
                   required
-                  autoFocus
                 />
               )}
             </form>