Browse Source

Merge branch 'master' into feat/119760

Shun Miyazawa 2 years ago
parent
commit
9de5a5ac7b

+ 10 - 10
apps/app/src/components/PageEditor.tsx

@@ -132,8 +132,6 @@ const PageEditor = React.memo((): JSX.Element => {
   const markdownToSave = useRef<string>(initialValue);
   const [markdownToPreview, setMarkdownToPreview] = useState<string>(initialValue);
 
-  const [isPageCreatedWithAttachmentUpload, setIsPageCreatedWithAttachmentUpload] = useState(false);
-
   const { data: socket } = useGlobalSocket();
 
   const { mutate: mutateIsConflict } = useIsConflict();
@@ -327,7 +325,6 @@ const PageEditor = React.memo((): JSX.Element => {
       // Not using 'mutateGrant' to inherit the grant of the parent page
       if (res.pageCreated) {
         logger.info('Page is created', res.page._id);
-        setIsPageCreatedWithAttachmentUpload(true);
         globalEmitter.emit('resetInitializedHackMdStatus');
         mutateIsLatestRevision(true);
         await mutateCurrentPageId(res.page._id);
@@ -522,14 +519,17 @@ const PageEditor = React.memo((): JSX.Element => {
 
   // when transitioning to a different page, if the initialValue is the same,
   // UnControlled CodeMirror value does not reset, so explicitly set the value to initialValue
-  // Also, if an attachment is uploaded and a new page is created,
-  // "useCurrentPagePath" changes, but no page transition is made, so nothing is done.
+  const onRouterChangeComplete = useCallback(() => {
+    editorRef.current?.setValue(initialValue);
+    editorRef.current?.setCaretLine(0);
+  }, [initialValue]);
+
   useEffect(() => {
-    if (currentPagePath != null && !isPageCreatedWithAttachmentUpload) {
-      editorRef.current?.setValue(initialValue);
-    }
-    setIsPageCreatedWithAttachmentUpload(false);
-  }, [currentPagePath, initialValue, isPageCreatedWithAttachmentUpload]);
+    router.events.on('routeChangeComplete', onRouterChangeComplete);
+    return () => {
+      router.events.off('routeChangeComplete', onRouterChangeComplete);
+    };
+  }, [onRouterChangeComplete, router.events]);
 
   if (!isEditable) {
     return <></>;

+ 20 - 4
apps/app/src/components/SearchPage.tsx

@@ -92,7 +92,10 @@ SearchResultListHead.displayName = 'SearchResultListHead';
 export const SearchPage = (): JSX.Element => {
   const { t } = useTranslation();
   const { data: showPageLimitationL } = useShowPageLimitationL();
+
+  // routerRef solve the problem of infinite redrawing that occurs with routers
   const router = useRouter();
+  const routerRef = useRef(router);
 
   // parse URL Query
   const queries = router.query.q;
@@ -165,10 +168,10 @@ export const SearchPage = (): JSX.Element => {
 
   const initialSearchConditions: Partial<ISearchConditions> = useMemo(() => {
     return {
-      keyword: initQ,
+      keyword,
       limit: INITIAL_PAGIONG_SIZE,
     };
-  }, [initQ]);
+  }, [keyword]);
 
   // for bulk deletion
   const deleteAllButtonClickedHandler = usePageDeleteModalForBulkDeletion(data, searchPageBaseRef, () => mutate());
@@ -177,8 +180,21 @@ export const SearchPage = (): JSX.Element => {
   useEffect(() => {
     const newUrl = new URL('/_search', 'http://example.com');
     newUrl.searchParams.append('q', keyword);
-    window.history.pushState('', `Search - ${keyword}`, `${newUrl.pathname}${newUrl.search}`);
-  }, [keyword]);
+    routerRef.current.push(`${newUrl.pathname}${newUrl.search}`, '', { shallow: true });
+  }, [keyword, routerRef]);
+
+  // browser back and forward
+  useEffect(() => {
+    routerRef.current.beforePopState(({ url }) => {
+      const newUrl = new URL(url, 'https://exmple.com');
+      const newKeyword = newUrl.searchParams.get('q');
+      if (newKeyword != null) {
+        setKeyword(newKeyword);
+      }
+      return true;
+    });
+  }, [setKeyword, routerRef]);
+
   const hitsCount = data?.meta.hitsCount;
 
   const allControl = useMemo(() => {

+ 14 - 5
apps/app/src/components/SearchPage/SearchPageBase.tsx

@@ -43,8 +43,13 @@ type Props = {
   searchPager: React.ReactNode,
 }
 
+
+const SearchResultContent = dynamic(() => import('./SearchResultContent').then(mod => mod.SearchResultContent), {
+  ssr: false,
+  loading: () => <></>,
+});
 const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll & IReturnSelectedPageIds, Props> = (props:Props, ref) => {
-  const SearchResultContent = dynamic(import('./SearchResultContent').then(mod => mod.SearchResultContent), { ssr: false });
+
   const {
     pages,
     searchingKeyword,
@@ -62,6 +67,7 @@ const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll & IReturn
 
   const [selectedPageIdsByCheckboxes] = useState<Set<string>>(new Set());
   // const [allPageIds] = useState<Set<string>>(new Set());
+
   const [selectedPageWithMeta, setSelectedPageWithMeta] = useState<IPageWithSearchMeta | undefined>();
 
   // publish selectAll()
@@ -108,10 +114,13 @@ const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll & IReturn
 
   // select first item on load
   useEffect(() => {
-    if (selectedPageWithMeta == null && pages != null && pages.length > 0) {
+    if ((pages == null || pages.length === 0)) {
+      setSelectedPageWithMeta(undefined);
+    }
+    else if ((pages != null && pages.length > 0)) {
       setSelectedPageWithMeta(pages[0]);
     }
-  }, [pages, selectedPageWithMeta]);
+  }, [pages, setSelectedPageWithMeta]);
 
   // reset selectedPageIdsByCheckboxes
   useEffect(() => {
@@ -189,7 +198,7 @@ const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll & IReturn
                       pages={pages}
                       selectedPageId={selectedPageWithMeta?.data._id}
                       forceHideMenuItems={forceHideMenuItems}
-                      onPageSelected={page => setSelectedPageWithMeta(page)}
+                      onPageSelected={page => (setSelectedPageWithMeta(page)) }
                       onCheckboxChanged={checkboxChangedHandler}
                     />
                   </div>
@@ -205,7 +214,7 @@ const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll & IReturn
         </div>
 
         <div className="mw-0 flex-grow-1 flex-basis-0 d-none d-lg-block search-result-content">
-          { selectedPageWithMeta != null && (
+          {pages != null && pages.length !== 0 && selectedPageWithMeta != null && (
             <SearchResultContent
               pageWithMeta={selectedPageWithMeta}
               highlightKeywords={highlightKeywords}

+ 11 - 6
apps/app/src/stores/editor.tsx

@@ -1,13 +1,13 @@
 import { useCallback } from 'react';
 
 import { Nullable, withUtils, type SWRResponseWithUtils } from '@growi/core';
-import { SWRResponse } from 'swr';
+import useSWR, { type SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
 import { apiGet } from '~/client/util/apiv1-client';
 import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
-import { IEditorSettings } from '~/interfaces/editor-settings';
-import { SlackChannels } from '~/interfaces/user-trigger-notification';
+import type { IEditorSettings } from '~/interfaces/editor-settings';
+import type { SlackChannels } from '~/interfaces/user-trigger-notification';
 
 import {
   useCurrentUser, useDefaultIndentSize, useIsGuestUser, useIsReadOnlyUser,
@@ -41,7 +41,9 @@ export const useEditorSettings = (): SWRResponseWithUtils<EditorSettingsOperatio
 
   const swrResult = useSWRImmutable(
     (isGuestUser || isReadOnlyUser) ? null : ['/personal-setting/editor-settings', currentUser?.username],
-    ([endpoint]) => apiv3Get(endpoint).then(result => result.data),
+    ([endpoint]) => {
+      return apiv3Get(endpoint).then(result => result.data);
+    },
     {
       // use: [localStorageMiddleware], // store to localStorage for initialization fastly
       // fallbackData: undefined,
@@ -78,10 +80,13 @@ export const useCurrentIndentSize = (): SWRResponse<number, Error> => {
 */
 export const useSWRxSlackChannels = (currentPagePath: Nullable<string>): SWRResponse<string[], Error> => {
   const shouldFetch: boolean = currentPagePath != null;
-  return useSWRImmutable(
+  return useSWR(
     shouldFetch ? ['/pages.updatePost', currentPagePath] : null,
     ([endpoint, path]) => apiGet(endpoint, { path }).then((response: SlackChannels) => response.updatePost),
-    { fallbackData: [''] },
+    {
+      revalidateOnFocus: false,
+      fallbackData: [''],
+    },
   );
 };
 

+ 15 - 22
apps/app/test/cypress/e2e/20-basic-features/20-basic-features--access-to-page.cy.ts

@@ -1,3 +1,16 @@
+function openEditor() {
+  cy.get('#grw-page-editor-mode-manager').as('pageEditorModeManager').should('be.visible');
+  cy.waitUntil(() => {
+    // do
+    cy.get('@pageEditorModeManager').within(() => {
+      cy.get('button:nth-child(2)').click();
+    });
+    // until
+    return cy.get('.layout-root').then($elem => $elem.hasClass('editing'));
+  })
+  cy.get('.CodeMirror').should('be.visible');
+}
+
 context('Access to page', () => {
   const ssPrefix = 'access-to-page-';
 
@@ -72,17 +85,7 @@ context('Access to page', () => {
     const body1 = 'hello';
     cy.visit('/Sandbox/testForUseEditingMarkdown');
 
-    cy.get('#grw-page-editor-mode-manager').as('pageEditorModeManager').should('be.visible');
-    cy.waitUntil(() => {
-      // do
-      cy.get('@pageEditorModeManager').within(() => {
-        cy.get('button:nth-child(2)').click();
-      });
-      // until
-      return cy.get('.layout-root').then($elem => $elem.hasClass('editing'));
-    })
-
-    cy.get('.grw-editor-navbar-bottom').should('be.visible');
+    openEditor();
 
     // check edited contents after save
     cy.get('.CodeMirror').type(body1);
@@ -100,17 +103,7 @@ context('Access to page', () => {
 
     cy.visit('/Sandbox/testForUseEditingMarkdown');
 
-    cy.get('#grw-page-editor-mode-manager').as('pageEditorModeManager').should('be.visible');
-    cy.waitUntil(() => {
-      // do
-      cy.get('@pageEditorModeManager').within(() => {
-        cy.get('button:nth-child(2)').click();
-      });
-      // until
-      return cy.get('.layout-root').then($elem => $elem.hasClass('editing'));
-    })
-
-    cy.get('.grw-editor-navbar-bottom').should('be.visible');
+    openEditor();
 
     // check editing contents with shortcut key
     cy.get('.CodeMirror-line').children().first().invoke('text').then((text) => {

+ 9 - 2
apps/app/test/cypress/e2e/23-editor/23-editor--with-navigation.cy.ts

@@ -24,7 +24,11 @@ context('Editor while uploading to a new page', () => {
     });
   });
 
-  // for https://redmine.weseek.co.jp/issues/122040
+  /**
+   * for the issues:
+   * @see https://redmine.weseek.co.jp/issues/122040
+   * @see https://redmine.weseek.co.jp/issues/124281
+   */
   it('should not be cleared and should prevent GrantSelector from modified', { scrollBehavior: false }, () => {
     cy.visit('/Sandbox/for-122040');
 
@@ -84,7 +88,10 @@ context.skip('Editor while navigation', () => {
     });
   });
 
-  // for https://redmine.weseek.co.jp/issues/115285
+  /**
+   * for the issue:
+   * @see https://redmine.weseek.co.jp/issues/115285
+   */
   it('Successfully updating the page body', { scrollBehavior: false }, () => {
     const page1Path = '/Sandbox/for-115285/page1';
     const page2Path = '/Sandbox/for-115285/page2';