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

Merge branch 'master' into fix/show-alert-when-appsiteurl-is-not-set

Taichi Masuyama 3 лет назад
Родитель
Сommit
5b62dd584a

+ 11 - 0
packages/app/public/static/locales/en_US/commons.json

@@ -0,0 +1,11 @@
+{
+  "meta": {
+    "display_name": "English"
+  },
+  "toaster": {
+    "create_succeeded": "Succeeded to create {{target}}",
+    "create_failed": "Failed to create {{target}}",
+    "update_successed": "Succeeded to update {{target}}",
+    "update_failed": "Failed to update {{target}}"
+  }
+}

+ 0 - 4
packages/app/public/static/locales/en_US/translation.json

@@ -523,10 +523,6 @@
     "page_not_found_in_preview": "\"{{path}}\" is not a GROWI page."
   },
   "toaster": {
-    "create_succeeded": "Succeeded to create {{target}}",
-    "create_failed": "Failed to create {{target}}",
-    "update_successed": "Succeeded to update {{target}}",
-    "update_failed": "Failed to update {{target}}",
     "file_upload_succeeded": "File upload succeeded.",
     "file_upload_failed": "File upload failed.",
     "initialize_successed": "Succeeded to initialize {{target}}",

+ 11 - 0
packages/app/public/static/locales/ja_JP/commons.json

@@ -0,0 +1,11 @@
+{
+  "meta": {
+    "display_name": "日本語"
+  },
+  "toaster": {
+    "create_succeeded": "新しい{{target}}が作成されました",
+    "create_failed": "{{target}}の作成に失敗しました",
+    "update_successed": "{{target}}を更新しました",
+    "update_failed": "{{target}}の更新に失敗しました"
+  }
+}

+ 0 - 4
packages/app/public/static/locales/ja_JP/translation.json

@@ -514,10 +514,6 @@
     "page_not_found_in_preview": "\"{{path}}\" というページはありません。"
   },
   "toaster": {
-    "create_succeeded": "新しい{{target}}が作成されました",
-    "create_failed": "{{target}}の作成に失敗しました",
-    "update_successed": "{{target}}を更新しました",
-    "update_failed": "{{target}}の更新に失敗しました",
     "file_upload_succeeded": "ファイルをアップロードしました",
     "file_upload_failed": "ファイルのアップロードに失敗しました",
     "initialize_successed": "{{target}}を初期化しました",

+ 11 - 0
packages/app/public/static/locales/zh_CN/commons.json

@@ -0,0 +1,11 @@
+{
+  "meta": {
+    "display_name": "简体中文"
+  },
+  "toaster": {
+    "create_succeeded": "Succeeded to create {{target}}",
+    "create_failed": "Failed to create {{target}}",
+    "update_successed": "Succeeded to update {{target}}",
+    "update_failed": "Failed to update {{target}}"
+  }
+}

+ 0 - 4
packages/app/public/static/locales/zh_CN/translation.json

@@ -496,10 +496,6 @@
     "page_not_found_in_preview": "\"{{path}}\" is not a GROWI page."
   },
 	"toaster": {
-    "create_succeeded": "Succeeded to create {{target}}",
-    "create_failed": "Failed to create {{target}}",
-		"update_successed": "Succeeded to update {{target}}",
-    "update_failed": "Failed to update {{target}}",
     "file_upload_succeeded": "文件上传成功",
     "file_upload_failed": "文件上传失败",
     "initialize_successed": "Succeeded to initialize {{target}}",

+ 1 - 0
packages/app/src/components/Hotkeys/Subscribers/SwitchToMirrorMode.jsx

@@ -1,4 +1,5 @@
 import React, { useEffect } from 'react';
+
 import PropTypes from 'prop-types';
 
 const SwitchToMirrorMode = (props) => {

+ 6 - 0
packages/app/src/components/Layout/AdminLayout.tsx

@@ -8,6 +8,9 @@ import { RawLayout } from './RawLayout';
 
 import styles from './Admin.module.scss';
 
+
+const HotkeysManager = dynamic(() => import('../Hotkeys/HotkeysManager'), { ssr: false });
+
 const AdminNotFoundPage = dynamic(() => import('../Admin/NotFoundPage').then(mod => mod.AdminNotFoundPage), { ssr: false });
 
 
@@ -54,6 +57,9 @@ const AdminLayout = ({
 
         <SystemVersion />
       </div>
+
+      <HotkeysManager />
+
     </RawLayout>
   );
 };

+ 1 - 1
packages/app/src/components/Layout/RawLayout.tsx

@@ -21,7 +21,7 @@ type Props = {
 }
 
 export const RawLayout = ({ children, title, className }: Props): JSX.Element => {
-  const classNames: string[] = ['wrapper'];
+  const classNames: string[] = ['layout-root'];
   if (className != null) {
     classNames.push(className);
   }

+ 11 - 13
packages/app/src/components/Page/DisplaySwitcher.tsx

@@ -3,8 +3,8 @@ import React, { useMemo } from 'react';
 import { pagePathUtils } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
+import { Link } from 'react-scroll';
 
-// import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
 import {
   useCurrentPagePath, useIsSharedUser, useIsEditable, useShareLinkId, useIsNotFound,
 } from '~/stores/context';
@@ -79,18 +79,18 @@ const PageView = React.memo((): JSX.Element => {
             </div>
 
             {/* Comments */}
-            {/* { getCommentListDom != null && !isTopPagePath && ( */}
             { !isTopPagePath && (
               <div className={`mt-2 grw-page-accessories-control ${styles['grw-page-accessories-control']}`}>
-                <button
-                  type="button"
-                  className="btn btn-block btn-outline-secondary grw-btn-page-accessories rounded-pill d-flex justify-content-between align-items-center"
-                  // onClick={() => smoothScrollIntoView(getCommentListDom, WIKI_HEADER_LINK)}
-                >
-                  <i className="icon-fw icon-bubbles grw-page-accessories-control-icon"></i>
-                  <span>Comments</span>
-                  <CountBadge count={currentPage?.commentCount} />
-                </button>
+                <Link to={'page-comments'} smooth="easeOutQuart" offset={-100} duration={800}>
+                  <button
+                    type="button"
+                    className="btn btn-block btn-outline-secondary grw-btn-page-accessories rounded-pill d-flex justify-content-between align-items-center"
+                  >
+                    <i className="icon-fw icon-bubbles grw-page-accessories-control-icon"></i>
+                    <span>Comments</span>
+                    <CountBadge count={currentPage?.commentCount} />
+                  </button>
+                </Link>
               </div>
             ) }
 
@@ -109,8 +109,6 @@ PageView.displayName = 'PageView';
 
 
 const DisplaySwitcher = React.memo((): JSX.Element => {
-  // get element for smoothScroll
-  // const getCommentListDom = useMemo(() => { return document.getElementById('page-comments-list') }, []);
 
   const { data: isEditable } = useIsEditable();
 

+ 1 - 1
packages/app/src/components/PageComment.tsx

@@ -152,7 +152,7 @@ export const PageComment: FC<PageCommentProps> = memo((props:PageCommentProps):
 
   return (
     <>
-      <div className={`${styles['page-comment-styles']} page-comments-row comment-list`}>
+      <div id="page-comments" className={`${styles['page-comment-styles']} page-comments-row comment-list`}>
         <div className="container-lg">
           <div className="page-comments">
             <h2 className={commentTitleClasses}><i className="icon-fw icon-bubbles"></i>Comments</h2>

+ 9 - 18
packages/app/src/pages/[[...path]].page.tsx

@@ -39,7 +39,9 @@ import type { UserUISettingsModel } from '~/server/models/user-ui-settings';
 import { useSWRxCurrentPage, useSWRxIsGrantNormalized, useSWRxPageInfo } from '~/stores/page';
 import { useRedirectFrom } from '~/stores/page-redirect';
 import {
-  usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth, useSelectedGrant,
+  EditorMode,
+  useEditorMode, useSelectedGrant,
+  usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth,
 } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 
@@ -230,8 +232,6 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   useIsUploadableFile(props.editorConfig.upload.isUploadableFile);
   useIsUploadableImage(props.editorConfig.upload.isUploadableImage);
 
-  // const { data: editorMode } = useEditorMode();
-
   const { pageWithMeta, userUISettings } = props;
 
   const pageId = pageWithMeta?.data._id;
@@ -246,13 +246,13 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
 
   useSWRxCurrentPage(undefined, pageWithMeta?.data ?? null); // store initial data
   useEditingMarkdown(pageWithMeta?.data.revision?.body ?? '');
-
-  const { data: layoutSetting } = useLayoutSetting({ isContainerFluid: props.isContainerFluid });
   const { data: dataPageInfo } = useSWRxPageInfo(pageId);
-
   const { data: grantData } = useSWRxIsGrantNormalized(pageId);
   const { mutate: mutateSelectedGrant } = useSelectedGrant();
 
+  const { data: layoutSetting } = useLayoutSetting({ isContainerFluid: props.isContainerFluid });
+  const { getClassNamesByEditorMode } = useEditorMode();
+
   const shouldRenderPutbackPageModal = pageWithMeta != null
     ? _isTrashPage(pageWithMeta.data.path)
     : false;
@@ -271,17 +271,9 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   }, [props.currentPathname, router]);
 
   const classNames: string[] = [];
-  // switch (editorMode) {
-  //   case EditorMode.Editor:
-  //     classNames.push('on-edit', 'builtin-editor');
-  //     break;
-  //   case EditorMode.HackMD:
-  //     classNames.push('on-edit', 'hackmd');
-  //     break;
-  // }
-  // if (page == null) {
-  //   classNames.push('not-found-page');
-  // }
+
+  const isSidebar = pagePath === '/Sidebar';
+  classNames.push(...getClassNamesByEditorMode(isSidebar));
 
   const isTopPagePath = isTopPage(pageWithMeta?.data.path ?? '');
 
@@ -301,7 +293,6 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
         {renderHighlightJsStyleTag(props.highlightJsStyle)}
         */}
       </Head>
-      {/* <BasicLayout title={useCustomTitle(props, t('GROWI'))} className={classNames.join(' ')}> */}
       <BasicLayout title={useCustomTitle(props, 'GROWI')} className={classNames.join(' ')} expandContainer={isContainerFluid}>
         <div className="h-100 d-flex flex-column justify-content-between">
           <header className="py-0 position-relative">

+ 33 - 43
packages/app/src/stores/ui.tsx

@@ -1,4 +1,4 @@
-import { RefObject, useEffect } from 'react';
+import { RefObject, useCallback, useEffect } from 'react';
 
 import {
   isClient, isServer, pagePathUtils, Nullable, PageGrant,
@@ -72,29 +72,21 @@ export const useIsMobile = (): SWRResponse<boolean, Error> => {
   return useStaticSWR<boolean, Error>(key, undefined, configuration);
 };
 
-const updateBodyClassesByEditorMode = (newEditorMode: EditorMode, isSidebar = false) => {
-  const bodyElement = document.getElementsByTagName('body')[0];
-  if (bodyElement == null) {
-    logger.warn('The body tag was not successfully obtained');
-    return;
-  }
-  switch (newEditorMode) {
-    case EditorMode.View:
-      bodyElement.classList.remove('on-edit', 'builtin-editor', 'hackmd', 'editing-sidebar');
-      break;
+const getClassNamesByEditorMode = (editorMode: EditorMode | undefined, isSidebar = false): string[] => {
+  const classNames: string[] = [];
+  switch (editorMode) {
     case EditorMode.Editor:
-      bodyElement.classList.add('on-edit', 'builtin-editor');
-      bodyElement.classList.remove('hackmd');
-      // editing /Sidebar
+      classNames.push('editing', 'builtin-editor');
       if (isSidebar) {
-        bodyElement.classList.add('editing-sidebar');
+        classNames.push('editing-sidebar');
       }
       break;
     case EditorMode.HackMD:
-      bodyElement.classList.add('on-edit', 'hackmd');
-      bodyElement.classList.remove('builtin-editor', 'editing-sidebar');
+      classNames.push('editing', 'hackmd');
       break;
   }
+
+  return classNames;
 };
 
 const updateHashByEditorMode = (newEditorMode: EditorMode) => {
@@ -130,8 +122,11 @@ export const determineEditorModeByHash = (): EditorMode => {
   }
 };
 
-let isEditorModeLoaded = false;
-export const useEditorMode = (): SWRResponse<EditorMode, Error> => {
+type EditorModeUtils = {
+  getClassNamesByEditorMode: (isEditingSidebar: boolean) => string[],
+}
+
+export const useEditorMode = (): SWRResponseWithUtils<EditorModeUtils, EditorMode> => {
   const { data: _isEditable } = useIsEditable();
 
   const editorModeByHash = determineEditorModeByHash();
@@ -140,36 +135,31 @@ export const useEditorMode = (): SWRResponse<EditorMode, Error> => {
   const isEditable = !isLoading && _isEditable;
   const initialData = isEditable ? editorModeByHash : EditorMode.View;
 
-  const { data: currentPagePath } = useCurrentPagePath();
-  const isSidebar = currentPagePath === '/Sidebar';
-
-  const swrResponse = useSWRImmutable(
+  const swrResponse = useSWRImmutable<EditorMode>(
     isLoading ? null : ['editorMode', isEditable],
     null,
     { fallbackData: initialData },
   );
 
-  // initial updating
-  if (!isEditorModeLoaded && !isLoading && swrResponse.data != null) {
-    if (isEditable) {
-      updateBodyClassesByEditorMode(swrResponse.data, isSidebar);
+  // construct overriding mutate method
+  const mutateOriginal = swrResponse.mutate;
+  const mutate = useCallback((editorMode: EditorMode, shouldRevalidate?: boolean) => {
+    if (!isEditable) {
+      return Promise.resolve(EditorMode.View); // fixed if not editable
     }
-    isEditorModeLoaded = true;
-  }
-
-  return {
-    ...swrResponse,
-
-    // overwrite mutate
-    mutate: (editorMode: EditorMode, shouldRevalidate?: boolean) => {
-      if (!isEditable) {
-        return Promise.resolve(EditorMode.View); // fixed if not editable
-      }
-      updateBodyClassesByEditorMode(editorMode, isSidebar);
-      updateHashByEditorMode(editorMode);
-      return swrResponse.mutate(editorMode, shouldRevalidate);
-    },
-  };
+    updateHashByEditorMode(editorMode);
+    return mutateOriginal(editorMode, shouldRevalidate);
+  }, [isEditable, mutateOriginal]);
+
+  // construct getClassNamesByEditorMode method
+  const getClassNames = useCallback((isEditingSidebar: boolean) => {
+    return getClassNamesByEditorMode(swrResponse.data, isEditingSidebar);
+  }, [swrResponse.data]);
+
+  return Object.assign(swrResponse, {
+    mutate,
+    getClassNamesByEditorMode: getClassNames,
+  });
 };
 
 export const useIsDeviceSmallerThanMd = (): SWRResponse<boolean, Error> => {

+ 4 - 4
packages/app/src/styles/_on-edit.scss → packages/app/src/styles/_editor.scss

@@ -4,7 +4,7 @@
 @import 'sidebar-wiki';
 
 // global imported
-body.on-edit {
+.layout-root.editing {
   overflow-y: hidden !important;
 
   .grw-navbar {
@@ -279,14 +279,14 @@ body.on-edit {
   }
 }
 
-body.on-edit {
-  .wrapper:not(.growi-layout-fluid) .page-editor-preview-body {
+.layout-root.editing {
+  &:not(.growi-layout-fluid) .page-editor-preview-body {
     .wiki {
       max-width: 980px;
       margin: 0 auto;
     }
   }
-  .wrapper.growi-layout-fluid .page-editor-preview-body {
+  &.growi-layout-fluid .page-editor-preview-body {
     .wiki {
       margin: 0 auto;
     }

+ 2 - 2
packages/app/src/styles/_layout.scss

@@ -6,11 +6,11 @@ body {
   overscroll-behavior-y: none;
 }
 
-.wrapper:not(.growi-layout-fluid) .grw-container-convertible {
+.layout-root:not(.growi-layout-fluid) .grw-container-convertible {
   @extend .container-lg;
 }
 
-.wrapper.growi-layout-fluid .grw-container-convertible {
+.layout-root.growi-layout-fluid .grw-container-convertible {
   @extend .container-fluid;
 }
 

+ 1 - 1
packages/app/src/styles/_old-ios.scss

@@ -1,4 +1,4 @@
-html[old-ios] body:not(.on-edit) {
+html[old-ios] .layout-root:not(.editing) {
   .grw-navbar {
     position: initial !important;
     top: 0 !important;

+ 1 - 1
packages/app/src/styles/_variables.scss

@@ -35,7 +35,7 @@ $grw-logo-width: $grw-sidebar-nav-width;
 $grw-logomark-width: 36px;
 
 // fix tab width to 95 pixels
-// see also '_on-edit.scss'
+// see also '_editor.scss'
 $grw-nav-main-left-tab-width: 95px;
 $grw-nav-main-left-tab-width-mobile: 50px;
 $grw-nav-main-tab-height: 42px;

+ 2 - 6
packages/app/src/styles/style-next.scss

@@ -45,20 +45,16 @@
 // @import 'comment_growi';
 // @import 'create-page';
 // @import 'draft';
-// @import 'editor-attachment';
-// @import 'editor-navbar';
-// @import 'page-content-footer';
+@import 'editor';
 // @import 'handsontable';
 @import 'layout';
 // @import 'login';
 // @import 'me';
-// @import 'mirror_mode';
+@import 'mirror_mode';
 @import 'modal';
 // @import 'navbar';
 // @import 'old-ios';
-@import 'on-edit';
 // @import 'page-duplicate-modal';
-
 // @import 'page-path';
 // @import 'page-tree';
 // @import 'page';

+ 1 - 1
packages/app/src/styles/theme/_apply-colors-light.scss

@@ -252,7 +252,7 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
 }
 
 /*
- * GROWI on-edit
+ * GROWI Editor
  */
 .grw-editor-navbar-bottom {
   background-color: $gray-50;

+ 2 - 2
packages/app/src/styles/theme/_apply-colors.scss

@@ -503,9 +503,9 @@ ul.pagination {
 }
 
 /*
- * GROWI on-edit
+ * GROWI Editor
  */
-body.on-edit {
+.layout-root.editing {
   .main {
     background-color: darken($bgcolor-global, 2%);
 

+ 2 - 2
packages/app/test/cypress/integration/20-basic-features/use-tools.spec.ts

@@ -54,7 +54,7 @@ context('Modal for page operation', () => {
     });
     cy.getByTestid('page-editor').should('be.visible');
     cy.getByTestid('save-page-btn').click();
-    cy.get('body').should('not.have.class', 'on-edit');
+    cy.get('.layout-root').should('not.have.class', 'editing');
 
     cy.getByTestid('grw-contextual-sub-nav').should('be.visible');
     cy.screenshot(`${ssPrefix}create-today-page`);
@@ -72,7 +72,7 @@ context('Modal for page operation', () => {
     });
     cy.getByTestid('page-editor').should('be.visible');
     cy.getByTestid('save-page-btn').click();
-    cy.get('body').should('not.have.class', 'on-edit');
+    cy.get('layout-root').should('not.have.class', 'editing');
 
     cy.getByTestid('grw-contextual-sub-nav').should('be.visible');
     cy.screenshot(`${ssPrefix}create-page-under-specific-page`);

+ 1 - 1
packages/app/test/cypress/integration/50-sidebar/access-to-side-bar.spec.ts

@@ -60,7 +60,7 @@ context('Access to sidebar', () => {
     cy.get('.CodeMirror textarea').type(content, {force: true});
     cy.screenshot(`${ssPrefix}custom-sidebar-2-custom-sidebar-editor`);
     cy.getByTestid('save-page-btn').click();
-    cy.get('body').should('not.have.class', 'on-edit');
+    cy.get('layout-root').should('not.have.class', 'editing');
 
     // What to do when UserUISettings is not saved in time
     cy.getByTestid('grw-sidebar-nav-primary-custom-sidebar').then(($el) => {