Przeglądaj źródła

Merge branch 'master' into feat/3176-grid-edit-modal-for-master-merge

itizawa 5 lat temu
rodzic
commit
e50ea88bfd

+ 15 - 0
.github/workflows/release-rc.yml

@@ -45,6 +45,21 @@ jobs:
         semver: ${{ env.SEMVER }}
         publish: true
 
+    - name: Login to GitHub Container Registry
+      uses: docker/login-action@v1
+      with:
+        registry: ghcr.io
+        username: wsmoogle
+        password: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
+
+    - name: Docker Tags by SemVer in Github Container Registry
+      uses: weseek/ghaction-docker-tags-by-semver@v1.0.3
+      with:
+        source: growi
+        target: ghcr.io/weseek/growi
+        semver: ${{ env.SEMVER }}
+        publish: true
+
     - name: Check whether workspace is clean
       run: |
         STATUS=`git status --porcelain`

+ 24 - 7
.github/workflows/release.yml

@@ -97,13 +97,6 @@ jobs:
         additional-tags: 'latest'
         publish: true
 
-    - name: Slack Notification
-      uses: weseek/ghaction-release-slack-notification@master
-      with:
-        channel: '#general'
-        url: ${{ secrets.SLACK_WEBHOOK_URL }}
-        created_tag: 'v${{ needs.github-release.outputs.RELEASE_VERSION }}${{ env.SUFFIX }}'
-
     - name: Update Docker Hub Description
       uses: peter-evans/dockerhub-description@v2
       with:
@@ -112,6 +105,30 @@ jobs:
         repository: weseek/growi
         readme-filepath: ./docker/README.md
 
+    - name: Login to GitHub Container Registry
+      uses: docker/login-action@v1
+      with:
+        registry: ghcr.io
+        username: wsmoogle
+        password: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
+
+    - name: Docker Tags by SemVer in Github Container Registry
+      uses: weseek/ghaction-docker-tags-by-semver@v1.0.3
+      with:
+        source: growi${{ env.SUFFIX }}
+        target: ghcr.io/weseek/growi
+        semver: ${{ needs.github-release.outputs.RELEASE_VERSION }}
+        suffix: ${{ env.SUFFIX }}
+        additional-tags: 'latest'
+        publish: true
+
+    - name: Slack Notification
+      uses: weseek/ghaction-release-slack-notification@master
+      with:
+        channel: '#general'
+        url: ${{ secrets.SLACK_WEBHOOK_URL }}
+        created_tag: 'v${{ needs.github-release.outputs.RELEASE_VERSION }}${{ env.SUFFIX }}'
+
     - name: Check whether workspace is clean
       run: |
         STATUS=`git status --porcelain`

+ 1 - 0
resource/locales/en_US/translation.json

@@ -194,6 +194,7 @@
     }
   },
   "page_me_apitoken": {
+    "api_token": "API Token",
     "notice": {
       "apitoken_issued": "API token is not issued.",
       "update_token1": "You can update to generate a new API token.",

+ 1 - 0
resource/locales/ja_JP/translation.json

@@ -197,6 +197,7 @@
     }
   },
   "page_me_apitoken": {
+    "api_token": "API Token",
     "notice": {
       "apitoken_issued": "API Token が設定されていません。",
       "update_token1": "API Token を更新すると、自動的に新しい Token が生成されます。",

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

@@ -196,6 +196,7 @@
 		}
 	},
 	"page_me_apitoken": {
+    "api_token": "API Token",
 		"notice": {
 			"apitoken_issued": "API token 未发布。",
 			"update_token1": "您可以更新以生成新的API令牌。",

+ 9 - 1
src/client/js/components/CustomNavigation.jsx

@@ -12,7 +12,9 @@ export const CustomNav = (props) => {
   const [sliderWidth, setSliderWidth] = useState(0);
   const [sliderMarginLeft, setSliderMarginLeft] = useState(0);
 
-  const { activeTab, navTabMapping, onNavSelected } = props;
+  const {
+    activeTab, navTabMapping, onNavSelected, hideBorderBottom,
+  } = props;
 
   const navTabRefs = useMemo(() => {
     const obj = {};
@@ -87,6 +89,7 @@ export const CustomNav = (props) => {
         </Nav>
       </div>
       <hr className="my-0 grw-nav-slide-hr border-none" style={{ width: `${sliderWidth}%`, marginLeft: `${sliderMarginLeft}%` }} />
+      { !hideBorderBottom && <hr className="my-0 border-top-0 border-bottom" /> }
     </div>
   );
 
@@ -96,6 +99,11 @@ CustomNav.propTypes = {
   activeTab: PropTypes.string.isRequired,
   navTabMapping: PropTypes.object.isRequired,
   onNavSelected: PropTypes.func,
+  hideBorderBottom: PropTypes.bool,
+};
+
+CustomNav.defaultProps = {
+  hideBorderBottom: false,
 };
 
 

+ 12 - 4
src/client/js/components/Fab.jsx

@@ -17,13 +17,17 @@ const Fab = (props) => {
   const { currentUser } = appContainer;
 
   const [animateClasses, setAnimateClasses] = useState('invisible');
+  const [buttonClasses, setButtonClasses] = useState('');
 
 
   const stickyChangeHandler = useCallback((event) => {
     logger.debug('StickyEvents.CHANGE detected');
 
-    const classes = event.detail.isSticky ? 'animated fadeInUp faster' : 'animated fadeOut faster';
-    setAnimateClasses(classes);
+    const newAnimateClasses = event.detail.isSticky ? 'animated fadeInUp faster' : 'animated fadeOut faster';
+    const newButtonClasses = event.detail.isSticky ? '' : 'disabled grw-pointer-events-none';
+
+    setAnimateClasses(newAnimateClasses);
+    setButtonClasses(newButtonClasses);
   }, []);
 
   // setup effect by sticky event
@@ -47,7 +51,7 @@ const Fab = (props) => {
         <div className={`rounded-circle position-absolute ${animateClasses}`} style={{ bottom: '2.3rem', right: '4rem' }}>
           <button
             type="button"
-            className="btn btn-lg btn-create-page btn-primary rounded-circle p-0 waves-effect waves-light"
+            className={`btn btn-lg btn-create-page btn-primary rounded-circle p-0 waves-effect waves-light ${buttonClasses}`}
             onClick={navigationContainer.openPageCreateModal}
           >
             <CreatePageIcon />
@@ -61,7 +65,11 @@ const Fab = (props) => {
     <div className="grw-fab d-none d-md-block">
       {currentUser != null && renderPageCreateButton()}
       <div className={`rounded-circle position-absolute ${animateClasses}`} style={{ bottom: 0, right: 0 }}>
-        <button type="button" className="btn btn-light btn-scroll-to-top rounded-circle p-0" onClick={() => navigationContainer.smoothScrollIntoView()}>
+        <button
+          type="button"
+          className={`btn btn-light btn-scroll-to-top rounded-circle p-0 ${buttonClasses}`}
+          onClick={() => navigationContainer.smoothScrollIntoView()}
+        >
           <ReturnTopIcon />
         </button>
       </div>

+ 0 - 5
src/client/js/components/Icons/LooockIcon.jsx

@@ -1,5 +0,0 @@
-import React from 'react';
-
-const LockIcon = () => <i className="icon-fw icon-lock"></i>;
-
-export default LockIcon;

+ 0 - 5
src/client/js/components/Icons/PaperPlaneIcon.jsx

@@ -1,5 +0,0 @@
-import React from 'react';
-
-const PaperPlaneIcon = () => <i className="icon-fw icon-paper-plane"></i>;
-
-export default PaperPlaneIcon;

+ 0 - 5
src/client/js/components/Icons/ShareAltIcon.jsx

@@ -1,5 +0,0 @@
-import React from 'react';
-
-const ShareAltIcon = () => <i className="icon-fw icon-share-alt"></i>;
-
-export default ShareAltIcon;

+ 0 - 5
src/client/js/components/Icons/UserIcon.jsx

@@ -1,5 +0,0 @@
-import React from 'react';
-
-const UserIcon = () => <i className="icon-fw icon-user"></i>;
-
-export default UserIcon;

+ 1 - 1
src/client/js/components/Me/ApiSettings.jsx

@@ -25,7 +25,7 @@ class ApiSettings extends React.Component {
       await appContainer.apiv3Put('/personal-setting/api-token');
 
       await personalContainer.retrievePersonalData();
-      toastSuccess(t('toaster.update_successed', { target: t('personal_settings.update_password') }));
+      toastSuccess(t('toaster.update_successed', { target: t('page_me_apitoken.api_token') }));
     }
     catch (err) {
       toastError(err);

+ 1 - 1
src/client/js/components/Me/PasswordSettings.jsx

@@ -55,7 +55,7 @@ class PasswordSettings extends React.Component {
       });
       this.setState({ oldPassword: '', newPassword: '', newPasswordConfirm: '' });
       await personalContainer.retrievePersonalData();
-      toastSuccess(t('toaster.update_successed', { target: t('personal_settings.update_password') }));
+      toastSuccess(t('toaster.update_successed', { target: t('Password') }));
     }
     catch (err) {
       toastError(err);

+ 14 - 19
src/client/js/components/Me/PersonalSettings.jsx

@@ -1,5 +1,5 @@
 
-import React from 'react';
+import React, { useMemo } from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 import CustomNavigation from '../CustomNavigation';
@@ -8,50 +8,45 @@ import PasswordSettings from './PasswordSettings';
 import ExternalAccountLinkedMe from './ExternalAccountLinkedMe';
 import ApiSettings from './ApiSettings';
 
-import UserIcon from '../Icons/UserIcon';
-import ShareAltIcon from '../Icons/ShareAltIcon';
-import LockIcon from '../Icons/LooockIcon';
-import PaperPlaneIcon from '../Icons/PaperPlaneIcon';
+const PersonalSettings = (props) => {
 
-class PersonalSettings extends React.Component {
+  const { t } = props;
 
-  render() {
-    const { t } = this.props;
-
-    const navTabMapping = {
+  const navTabMapping = useMemo(() => {
+    return {
       user_infomation: {
-        Icon: UserIcon,
+        Icon: () => <i className="icon-fw icon-user"></i>,
         Content: UserSettings,
         i18n: t('User Information'),
         index: 0,
       },
       external_accounts: {
-        Icon: ShareAltIcon,
+        Icon: () => <i className="icon-fw icon-share-alt"></i>,
         Content: ExternalAccountLinkedMe,
         i18n: t('admin:user_management.external_accounts'),
         index: 1,
       },
       password_settings: {
-        Icon: LockIcon,
+        Icon: () => <i className="icon-fw icon-lock"></i>,
         Content: PasswordSettings,
         i18n: t('Password Settings'),
         index: 2,
       },
       api_settings: {
-        Icon: PaperPlaneIcon,
+        Icon: () => <i className="icon-fw icon-paper-plane"></i>,
         Content: ApiSettings,
         i18n: t('API Settings'),
         index: 3,
       },
     };
+  }, [t]);
 
 
-    return (
-      <CustomNavigation navTabMapping={navTabMapping} />
-    );
-  }
+  return (
+    <CustomNavigation navTabMapping={navTabMapping} />
+  );
 
-}
+};
 
 PersonalSettings.propTypes = {
   t: PropTypes.func.isRequired, // i18next

+ 18 - 15
src/client/js/components/NotFoundPage.jsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useMemo } from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 import PageListIcon from './Icons/PageListIcon';
@@ -10,20 +10,23 @@ import PageTimeline from './PageTimeline';
 const NotFoundPage = (props) => {
   const { t } = props;
 
-  const navTabMapping = {
-    pagelist: {
-      Icon: PageListIcon,
-      Content: PageList,
-      i18n: t('page_list'),
-      index: 0,
-    },
-    timeLine: {
-      Icon: TimeLineIcon,
-      Content: PageTimeline,
-      i18n: t('Timeline View'),
-      index: 1,
-    },
-  };
+  const navTabMapping = useMemo(() => {
+    return {
+      pagelist: {
+        Icon: PageListIcon,
+        Content: PageList,
+        i18n: t('page_list'),
+        index: 0,
+      },
+      timeLine: {
+        Icon: TimeLineIcon,
+        Content: PageTimeline,
+        i18n: t('Timeline View'),
+        index: 1,
+      },
+    };
+  }, [t]);
+
 
   return (
     <div className="mt-5 d-edit-none">

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

@@ -94,7 +94,7 @@ const PageAccessoriesModal = (props) => {
     <React.Fragment>
       <Modal size="xl" isOpen={props.isOpen} toggle={closeModalHandler} className={`grw-page-accessories-modal ${isWindowExpanded && 'grw-modal-expanded'} `}>
         <ModalHeader className="p-0" toggle={closeModalHandler} close={buttons}>
-          <CustomNav activeTab={activeTab} navTabMapping={navTabMapping} onNavSelected={switchActiveTab} />
+          <CustomNav activeTab={activeTab} navTabMapping={navTabMapping} onNavSelected={switchActiveTab} hideBorderBottom />
         </ModalHeader>
         <ModalBody className="overflow-auto grw-modal-body-style p-0">
           {/* Do not use CustomTabContent because of performance problem:

+ 1 - 1
src/client/js/components/ShareLink/ShareLinkForm.jsx

@@ -22,7 +22,7 @@ class ShareLinkForm extends React.Component {
       numberOfDays: '7',
       description: '',
       customExpirationDate: dateFnsFormat(new Date(), 'yyyy-MM-dd'),
-      customExpirationTime: dateFnsFormat(new Date(), 'hh:mm'),
+      customExpirationTime: dateFnsFormat(new Date(), 'HH:mm'),
     };
 
     this.handleChangeExpirationType = this.handleChangeExpirationType.bind(this);

+ 11 - 9
src/client/js/components/TrashPageList.jsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useMemo } from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 import PageListIcon from './Icons/PageListIcon';
@@ -9,14 +9,16 @@ import PageList from './PageList';
 const TrashPageList = (props) => {
   const { t } = props;
 
-  const navTabMapping = {
-    pagelist: {
-      Icon: PageListIcon,
-      Content: PageList,
-      i18n: t('page_list'),
-      index: 0,
-    },
-  };
+  const navTabMapping = useMemo(() => {
+    return {
+      pagelist: {
+        Icon: PageListIcon,
+        Content: PageList,
+        i18n: t('page_list'),
+        index: 0,
+      },
+    };
+  }, [t]);
 
   return (
     <div className="mt-5 d-edit-none">

+ 3 - 1
src/client/js/legacy/crowi.js

@@ -156,12 +156,14 @@ Crowi.highlightSelectedSection = function(hash) {
 
 window.addEventListener('load', (e) => {
   const { appContainer } = window;
+  const pageContainer = appContainer.getContainer('PageContainer');
+  const { isEditable } = pageContainer;
 
   // hash on page
   if (window.location.hash) {
     const navigationContainer = appContainer.getContainer('NavigationContainer');
 
-    if (window.location.hash === '#edit') {
+    if (window.location.hash === '#edit' && isEditable) {
       navigationContainer.setEditorMode('edit');
 
       // focus

+ 4 - 0
src/client/js/services/NavigationContainer.js

@@ -176,6 +176,10 @@ export default class NavigationContainer extends Container {
   }
 
   openPageCreateModal() {
+    if (this.appContainer.currentUser == null) {
+      logger.warn('Please login or signup to create a new page.');
+      return;
+    }
     this.setState({ isPageCreateModalShown: true });
   }
 

+ 13 - 0
src/client/js/services/PageContainer.js

@@ -139,6 +139,19 @@ export default class PageContainer extends Container {
     return 'PageContainer';
   }
 
+
+  get isEditable() {
+    const { currentUser } = this.appContainer;
+    const {
+      isPageExist, isPageForbidden, isNotCreatable, isTrashPage,
+    } = this.state;
+
+    if (isPageExist && (currentUser != null) && !isPageForbidden && !isNotCreatable && !isTrashPage) {
+      return true;
+    }
+    return false;
+  }
+
   /**
    * initialize state for markdown data
    */

+ 9 - 0
src/client/styles/scss/atoms/_buttons.scss

@@ -91,3 +91,12 @@
   background-color: transparent;
   transition: 0.3s;
 }
+
+// define disabled button w/o pointer-events, see _override-bootstrap.scss
+.btn.disabled,
+.btn[disabled],
+fieldset[disabled] .btn {
+  &.grw-pointer-events-none {
+    pointer-events: none;
+  }
+}

+ 2 - 1
src/client/styles/scss/theme/_apply-colors.scss

@@ -138,7 +138,8 @@ ul.pagination {
     button.page-link {
       color: color-yiq($primary);
       background-color: $primary;
-      &:hover {
+      &:hover,
+      &:focus {
         color: color-yiq($primary);
         background-color: $primary;
       }

+ 1 - 1
src/client/styles/scss/theme/halloween.scss

@@ -38,7 +38,7 @@ html[dark] {
   // Background colors
   $bgcolor-global: #050000;
   $bgcolor-inline-code: #1f1f22; //optional
-  $bgcolor-card: $gray-50;
+  $bgcolor-card: $bgcolor-global;
 
   // Font colors
   $color-global: #e9af2b;

+ 3 - 3
src/server/views/layout-growi/base/layout.html

@@ -14,10 +14,10 @@
       {% block content_header %}
         <div id="grw-subnav-container"></div>
       {% endblock %}
-      <div id="grw-subnav-switcher-container" class="d-edit-none"></div>
-      <div id="grw-subnav-sticky-trigger" class="sticky-top"></div>
-      <div id="grw-fav-sticky-trigger" class="sticky-top"></div>
     </header>
+    <div id="grw-subnav-switcher-container" class="d-edit-none"></div>
+    <div id="grw-subnav-sticky-trigger" class="sticky-top"></div>
+    <div id="grw-fav-sticky-trigger" class="sticky-top"></div>
   {% endblock %}
 
   <div class="flex-grow-1">

+ 1 - 1
src/server/views/widget/not_creatable_content.html

@@ -1,4 +1,4 @@
-<div class="row not-found-message-row mb-4">
+<div class="row not-found-message-row">
   <div class="col-md-12">
     <h2 class="text-muted">
       <i class="icon-ban" aria-hidden="true"></i>