Przeglądaj źródła

Merge branch 'master' into feat/enhanced-link-edit-modal-for-master-merge

yusuketk 5 lat temu
rodzic
commit
f8e955cf0d

+ 5 - 3
package.json

@@ -54,8 +54,9 @@
     "preserver:prod": "npm run migrate",
     "prestart": "npm run build:prod",
     "resource": "node bin/download-cdn-resources.js",
-    "translations": "rs-i18n -lan zh_CN -t",
-    "i18n-json-merge:noTran": "rs-i18n -lan zh_CN",
+    "i18n-json-merge:withTran": "rs-i18n -lan -- -t",
+    "i18n-json-merge:noTran": "rs-i18n -lan --",
+    "i18n-json-merge": "npm run i18n-json-merge:withTran --",
     "server:nolazy": "env-cmd -f config/env.dev.js node-dev --nolazy --inspect src/server/app.js",
     "server:dev": "env-cmd -f config/env.dev.js node-dev --inspect src/server/app.js",
     "server:prod:ci": "npm run server:prod -- --ci",
@@ -183,10 +184,11 @@
     "diff2html": "^3.1.2",
     "eazy-logger": "^3.0.2",
     "eslint": "^6.0.1",
-    "eslint-config-weseek": "^1.0.3",
+    "eslint-config-weseek": "^1.0.4",
     "eslint-plugin-import": "^2.18.0",
     "eslint-plugin-jest": "^23.0.3",
     "eslint-plugin-react": "^7.14.2",
+    "eslint-plugin-react-hooks": "^4.0.4",
     "file-loader": "^5.0.2",
     "handsontable": "=6.2.2",
     "hard-source-webpack-plugin": "^0.13.1",

+ 3 - 3
resource/locales/zh_CN/meta.json

@@ -1,4 +1,4 @@
 {
-  "id": "zh_CN",
-  "displayName": "简体中文"
-}
+	"id": "zh_CN",
+	"displayName": "简体中文"
+}

+ 24 - 0
resource/locales/zh_CN/translation.json

@@ -696,5 +696,29 @@
 		"Sign in error": "登录错误",
 		"Registration successful": "注册成功",
 		"Setup": "安装程序"
+	},
+	"message": {
+		"successfully_connected": "连接成功!",
+		"fail_to_save_access_token": "无法保存访问令牌。请再试一次。",
+		"fail_to_fetch_access_token": "无法获取访问令牌。请重新连接。",
+		"successfully_disconnected": "成功断开连接!",
+		"strategy_has_not_been_set_up": "{{strategy}尚未设置",
+		"maximum_number_of_users": "注册的用户数不能超过最大值。",
+		"database_error": "发生数据库服务器错误",
+		"sign_in_failure": "登录失败。",
+		"aws_sttings_required": "使用此功能所需的AWS设置。请询问管理员。",
+		"application_already_installed": "应用程序已安装。",
+		"email_address_could_not_be_used": "无法使用此电子邮件地址。(确保允许的电子邮件地址)",
+		"user_id_is_not_available.": "此用户ID不可用。",
+		"email_address_is_already_registered": "此电子邮件地址已注册。",
+		"can_not_register_maximum_number_of_users": "注册的用户数不能超过最大值。",
+		"failed_to_register": "注册失败。",
+		"successfully_created": "已成功创建用户{{username}。",
+		"can_not_activate_maximum_number_of_users": "无法激活超过最大用户数的用户。",
+		"failed_to_activate": "无法激活。",
+		"unable_to_use_this_user": "无法使用此用户。",
+		"complete_to_install1": "完成安装GROWI!请以管理员帐户登录。",
+		"complete_to_install2": "完成安装GROWI!请先检查此页上的每个设置。",
+		"failed_to_create_admin_user": "无法创建管理用户。{{errMessage}"
 	}
 }

+ 1 - 1
src/client/js/components/Navbar/SearchTop.jsx

@@ -77,7 +77,7 @@ class SearchTop extends React.Component {
     const isReachable = config.isSearchServiceReachable;
 
     return (
-      <div className={`form-group mb-0 ${isReachable ? '' : 'has-error'}`}>
+      <div className={`form-group mb-0 d-print-none ${isReachable ? '' : 'has-error'}`}>
         <div className="input-group flex-nowrap">
           <div className="input-group-prepend">
             <button className="btn btn-secondary dropdown-toggle py-0" type="button" data-toggle="dropdown" aria-haspopup="true">

+ 1 - 1
src/client/js/components/PageDeleteModal.jsx

@@ -124,7 +124,7 @@ const PageDeleteModal = (props) => {
         {!isDeleteCompletelyModal && renderDeleteCompletelyForm()}
       </ModalBody>
       <ModalFooter>
-        <ApiErrorMessage errorCode={errorCode} errorMessage={errorMessage} linkPath={path} />
+        <ApiErrorMessage errorCode={errorCode} errorMessage={errorMessage} />
         <button type="button" className={`btn btn-${deleteIconAndKey[deleteMode].color}`} onClick={deleteButtonHandler}>
           <i className={`icon-${deleteIconAndKey[deleteMode].icon}`} aria-hidden="true"></i>
           { t(`modal_delete.delete_${deleteIconAndKey[deleteMode].translationKey}`) }

+ 1 - 1
src/client/js/components/PageDuplicateModal.jsx

@@ -99,7 +99,7 @@ const PageDuplicateModal = (props) => {
         </div>
       </ModalBody>
       <ModalFooter>
-        <ApiErrorMessage errorCode={errorCode} errorMessage={errorMessage} linkPath={path} />
+        <ApiErrorMessage errorCode={errorCode} errorMessage={errorMessage} targetPath={pageNameInput} />
         <button type="button" className="btn btn-primary" onClick={duplicate}>Duplicate page</button>
       </ModalFooter>
     </Modal>

+ 2 - 0
src/client/js/components/PageEditor.jsx

@@ -21,6 +21,8 @@ class PageEditor extends React.Component {
   constructor(props) {
     super(props);
 
+    this.previewElement = React.createRef();
+
     const config = this.props.appContainer.getConfig();
     const isUploadable = config.upload.image || config.upload.file;
     const isUploadableFile = config.upload.file;

+ 4 - 2
src/client/js/components/PageEditor/Preview.jsx

@@ -82,7 +82,9 @@ class Preview extends React.PureComponent {
             className="page-editor-preview-body"
             ref={(elm) => {
                 this.previewElement = elm;
-                this.props.inputRef(elm);
+                if (this.props.inputRef != null) {
+                  this.props.inputRef(elm);
+                }
               }}
             onScroll={(event) => {
                 if (this.props.onScroll != null) {
@@ -112,7 +114,7 @@ Preview.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 
   markdown: PropTypes.string,
-  inputRef: PropTypes.func.isRequired, // for getting div element
+  inputRef: PropTypes.func,
   isMathJaxEnabled: PropTypes.bool,
   renderMathJaxOnInit: PropTypes.bool,
   onScroll: PropTypes.func,

+ 3 - 3
src/client/js/components/PageManagement/ApiErrorMessage.jsx

@@ -5,7 +5,7 @@ import { withTranslation } from 'react-i18next';
 
 const ApiErrorMessage = (props) => {
   const {
-    t, errorCode, errorMessage, linkPath,
+    t, errorCode, errorMessage, targetPath,
   } = props;
 
   function reload() {
@@ -18,7 +18,7 @@ const ApiErrorMessage = (props) => {
         return (
           <>
             <strong><i className="icon-fw icon-ban"></i>{ t('page_api_error.already_exists') }</strong>
-            <small><a href={linkPath}>{linkPath} <i className="icon-login"></i></a></small>
+            <small><a href={targetPath}>{targetPath} <i className="icon-login"></i></a></small>
           </>
         );
       case 'notfound_or_forbidden':
@@ -77,7 +77,7 @@ ApiErrorMessage.propTypes = {
 
   errorCode:    PropTypes.string,
   errorMessage: PropTypes.string,
-  linkPath:     PropTypes.string,
+  targetPath:   PropTypes.string,
 };
 
 export default withTranslation()(ApiErrorMessage);

+ 1 - 1
src/client/js/components/PageRenameModal.jsx

@@ -150,7 +150,7 @@ const PageRenameModal = (props) => {
         </div>
       </ModalBody>
       <ModalFooter>
-        <ApiErrorMessage errorCode={errorCode} errorMessage={errorMessage} linkPath={path} />
+        <ApiErrorMessage errorCode={errorCode} errorMessage={errorMessage} targetPath={pageNameInput} />
         <button type="button" className="btn btn-primary" onClick={rename}>Rename</button>
       </ModalFooter>
     </Modal>

+ 1 - 1
src/client/js/components/PutbackPageModal.jsx

@@ -73,7 +73,7 @@ const PutBackPageModal = (props) => {
         </div>
       </ModalBody>
       <ModalFooter>
-        <ApiErrorMessage errorCode={errorCode} errorMessage={errorMessage} linkPath={path} />
+        <ApiErrorMessage errorCode={errorCode} errorMessage={errorMessage} />
         <button type="button" className="btn btn-info" onClick={putbackPageButtonHandler}>
           <i className="icon-action-undo mr-2" aria-hidden="true"></i> { t('Put Back') }
         </button>

+ 1 - 1
src/client/js/components/Sidebar.jsx

@@ -167,7 +167,7 @@ class Sidebar extends React.Component {
 
     return (
       <>
-        <div className={`grw-sidebar ${this.isDrawerMode ? 'grw-sidebar-drawer' : ''} ${isDrawerOpened ? 'open' : ''}`}>
+        <div className={`grw-sidebar d-print-none ${this.isDrawerMode ? 'grw-sidebar-drawer' : ''} ${isDrawerOpened ? 'open' : ''}`}>
           <ThemeProvider
             theme={theme => ({
               ...theme,

+ 6 - 6
src/client/js/components/StickyStretchableScroller.jsx

@@ -91,7 +91,7 @@ const StickyStretchableScroller = (props) => {
     if (contentsHeight < viewHeight) {
       $(scrollTargetSelector).slimScroll({ destroy: true });
     }
-  }, [contentsElemSelector, calcViewHeightFunc, calcContentsHeightFunc]);
+  }, [contentsElemSelector, calcViewHeightFunc, calcContentsHeightFunc, scrollTargetSelector]);
 
   const resetScrollbarDebounced = debounce(100, resetScrollbar);
 
@@ -99,7 +99,7 @@ const StickyStretchableScroller = (props) => {
   const stickyChangeHandler = useCallback((event) => {
     logger.debug('StickyEvents.CHANGE detected');
     resetScrollbar();
-  });
+  }, [resetScrollbar]);
 
   // setup effect by sticky event
   useEffect(() => {
@@ -118,7 +118,7 @@ const StickyStretchableScroller = (props) => {
     return () => {
       elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
     };
-  }, []);
+  }, [stickyElemSelector, stickyChangeHandler]);
 
   // setup effect by resizing event
   useEffect(() => {
@@ -132,19 +132,19 @@ const StickyStretchableScroller = (props) => {
     return () => {
       window.removeEventListener('resize', resizeHandler);
     };
-  }, []);
+  }, [resetScrollbarDebounced]);
 
   // setup effect by isScrollTop
   useEffect(() => {
     if (navigationContainer.state.isScrollTop) {
       resetScrollbar();
     }
-  }, [navigationContainer.state.isScrollTop]);
+  }, [navigationContainer.state.isScrollTop, resetScrollbar]);
 
   // setup effect by update props
   useEffect(() => {
     resetScrollbarDebounced();
-  });
+  }, [resetScrollbarDebounced]);
 
   return (
     <>

+ 1 - 1
src/client/js/components/TableOfContents.jsx

@@ -27,7 +27,7 @@ const TableOfContents = (props) => {
 
     // window height - revisionToc top - .system-version height
     return window.innerHeight - containerTop - 20;
-  });
+  }, []);
 
   const { tocHtml } = pageContainer.state;
 

+ 4 - 0
src/client/styles/scss/_layout.scss

@@ -75,6 +75,10 @@ body {
       }
     }
 
+    .row {
+      display: block !important;
+    }
+
     .revision-toc {
       float: none;
       max-width: 100%;

+ 3 - 4
src/server/models/page.js

@@ -1185,18 +1185,17 @@ module.exports = function(crowi) {
   /**
    * Delete Bookmarks, Attachments, Revisions, Pages and emit delete
    */
-  pageSchema.statics.completelyDeletePageRecursively = async function(pageData, user, options = {}) {
-    const path = pageData.path;
+  pageSchema.statics.completelyDeletePageRecursively = async function(pagePath, user, options = {}) {
 
     const findOpts = { includeRedirect: true, includeTrashed: true };
-    const result = await this.findListWithDescendants(path, user, findOpts);
+    const result = await this.findListWithDescendants(pagePath, user, findOpts);
     const pages = result.pages;
 
     await Promise.all(pages.map((page) => {
       return this.completelyDeletePage(page, user, options);
     }));
 
-    return pageData;
+    return pagePath;
   };
 
   pageSchema.statics.removeByPath = function(path) {

+ 1 - 0
src/server/routes/apiv3/docs.js

@@ -9,6 +9,7 @@ const router = express.Router();
 // paths to scan
 const APIS = [
   'src/server/routes/apiv3/**/*.js',
+  'src/server/models/**/*.js',
 ];
 
 module.exports = (crowi) => {

+ 1 - 3
src/server/routes/apiv3/pages.js

@@ -72,9 +72,7 @@ module.exports = (crowi) => {
   */
   router.delete('/empty-trash', loginRequired, adminRequired, csrf, async(req, res) => {
     try {
-      const pages = await Page.deleteMany({
-        path: { $in: /^\/trash/ },
-      });
+      const pages = await Page.completelyDeletePageRecursively('/trash', req.user);
       return res.apiv3({ pages });
     }
     catch (err) {

+ 1 - 1
src/server/routes/page.js

@@ -1245,7 +1245,7 @@ module.exports = function(crowi, app) {
           return res.json(ApiResponse.error('You can not delete completely', 'user_not_admin'));
         }
         if (isRecursively) {
-          page = await Page.completelyDeletePageRecursively(page, req.user, options);
+          await Page.completelyDeletePageRecursively(page.path, req.user, options);
         }
         else {
           page = await Page.completelyDeletePage(page, req.user, options);

+ 2 - 2
src/server/views/layout-kibela/base/layout.html

@@ -9,10 +9,10 @@
 {% block layout_main %}
 <div class="container-fluid p-0">
 
-  <div class="row body m-0 p-0">
+  <div class="row body m-0 p-0 d-print-block">
 
     <div id="main" class="main col-12 kibela-block round-corner {% if page %}{{ css.grant(page) }}{% endif %}{% block main_css_class %}{% endblock %}">
-      <div class="row grw-subnav d-edit-none">
+      <div class="row grw-subnav d-edit-none d-print-block">
         <div class="col-12 col-xl-9 col-lg-8 px-0 mx-0 bg-white kibela-border-top round-corner">
           {% block content_header %} {% endblock %}
         </div>

+ 1 - 1
src/server/views/layout/layout.html

@@ -79,7 +79,7 @@
 
   {% block head_warn_breaking_changes %}{% include '../widget/alert_breaking_changes.html' %}{% endblock %}
 
-  <div id="page-wrapper" class="page-wrapper d-flex">
+  <div id="page-wrapper" class="page-wrapper d-flex d-print-block">
     {# Sidebar #}
     <div id="grw-sidebar-wrapper"></div>
 

+ 1 - 1
src/server/views/widget/system-version.html

@@ -1,4 +1,4 @@
-<div class="system-version d-none d-md-block">
+<div class="system-version d-none d-md-block d-print-none">
   <span>
     <a href="https://growi.org">GROWI</a> {{ growiVersion() }}
   </span>

+ 9 - 4
yarn.lock

@@ -5474,10 +5474,10 @@ eslint-config-airbnb@^17.1.0:
     object.assign "^4.1.0"
     object.entries "^1.0.4"
 
-eslint-config-weseek@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/eslint-config-weseek/-/eslint-config-weseek-1.0.3.tgz#420f583371447def71af11a78baf39d65a3d9e4a"
-  integrity sha512-AXOuaZomA/h34EHMT+CmhU7TTDbsyqCl702yaY0PGt6wype/YWra9phTbPyHjuI+Uh8gh9eKX2tAhnruKK3Ivw==
+eslint-config-weseek@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/eslint-config-weseek/-/eslint-config-weseek-1.0.4.tgz#26f8dc5ce6e791f9eb379308f0fbac4902b25439"
+  integrity sha512-PkWrFlNDs5X+cyCDEZ6JFoHly0LQhDuPVbEiSxchguk9A8OvrUD+KBgIOWbQZSjI0YJV582zyGSTU5J1lwtpUw==
   dependencies:
     eslint-config-airbnb "^17.1.0"
 
@@ -5520,6 +5520,11 @@ eslint-plugin-jest@^23.0.3:
   dependencies:
     "@typescript-eslint/experimental-utils" "^2.5.0"
 
+eslint-plugin-react-hooks@^4.0.4:
+  version "4.0.4"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.4.tgz#aed33b4254a41b045818cacb047b81e6df27fa58"
+  integrity sha512-equAdEIsUETLFNCmmCkiCGq6rkSK5MoJhXFPFYeUebcjKgBmWWcgVOqZyQC8Bv1BwVCnTq9tBxgJFgAJTWoJtA==
+
 eslint-plugin-react@^7.14.2:
   version "7.14.2"
   resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.14.2.tgz#94c193cc77a899ac0ecbb2766fbef88685b7ecc1"