Jelajahi Sumber

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

itizawa 5 tahun lalu
induk
melakukan
3f446cd491

+ 1 - 0
src/client/js/app.jsx

@@ -89,6 +89,7 @@ Object.assign(componentMappings, {
 
   'not-found-alert': <NotFoundAlert
     onPageCreateClicked={navigationContainer.setEditorMode}
+    isGuestUserMode={appContainer.currentUser == null}
     isHidden={pageContainer.state.isForbidden || pageContainer.state.isNotCreatable || pageContainer.state.isTrashPage}
   />,
 

+ 29 - 6
src/client/js/components/Admin/Notification/NotificationSetting.jsx

@@ -1,8 +1,9 @@
-import React, { useMemo } from 'react';
+import React, { useMemo, useState } from 'react';
 import PropTypes from 'prop-types';
 
 import loggerFactory from '@alias/logger';
 
+import { TabContent, TabPane } from 'reactstrap';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import { toastError } from '../../../util/apiNotification';
 import toArrayIfNot from '../../../../../lib/util/toArrayIfNot';
@@ -10,7 +11,7 @@ import { withLoadingSppiner } from '../../SuspenseUtils';
 
 import AdminNotificationContainer from '../../../services/AdminNotificationContainer';
 
-import CustomNavigation from '../../CustomNavigation';
+import { CustomNav } from '../../CustomNavigation';
 
 import SlackAppConfiguration from './SlackAppConfiguration';
 import UserTriggerNotification from './UserTriggerNotification';
@@ -21,6 +22,15 @@ const logger = loggerFactory('growi:NotificationSetting');
 let retrieveErrors = null;
 function NotificationSetting(props) {
   const { adminNotificationContainer } = props;
+
+  const [activeTab, setActiveTab] = useState('slack_configuration');
+  const [activeComponents, setActiveComponents] = useState(new Set(['slack_configuration']));
+
+  const switchActiveTab = (selectedTab) => {
+    setActiveTab(selectedTab);
+    setActiveComponents(activeComponents.add(selectedTab));
+  };
+
   if (adminNotificationContainer.state.webhookUrl === adminNotificationContainer.dummyWebhookUrl) {
     throw (async() => {
       try {
@@ -44,26 +54,39 @@ function NotificationSetting(props) {
     return {
       slack_configuration: {
         Icon: () => <i className="icon-settings" />,
-        Content: SlackAppConfiguration,
         i18n: 'Slack configuration',
         index: 0,
       },
       user_trigger_notification: {
         Icon: () => <i className="icon-settings" />,
-        Content: UserTriggerNotification,
         i18n: 'User trigger notification',
         index: 1,
       },
       global_notification: {
         Icon: () => <i className="icon-settings" />,
-        Content: GlobalNotification,
         i18n: 'Global notification',
         index: 2,
       },
     };
   }, []);
 
-  return <CustomNavigation navTabMapping={navTabMapping} />;
+  return (
+    <>
+      <CustomNav activeTab={activeTab} navTabMapping={navTabMapping} onNavSelected={switchActiveTab} hideBorderBottom />
+
+      <TabContent activeTab={activeTab} className="p-5">
+        <TabPane tabId="slack_configuration">
+          {activeComponents.has('slack_configuration') && <SlackAppConfiguration />}
+        </TabPane>
+        <TabPane tabId="user_trigger_notification">
+          {activeComponents.has('user_trigger_notification') && <UserTriggerNotification />}
+        </TabPane>
+        <TabPane tabId="global_notification">
+          {activeComponents.has('global_notification') && <GlobalNotification />}
+        </TabPane>
+      </TabContent>
+    </>
+  );
 }
 
 const NotificationSettingWithUnstatedContainer = withUnstatedContainers(withLoadingSppiner(NotificationSetting), [AdminNotificationContainer]);

+ 42 - 12
src/client/js/components/Admin/Security/SecurityManagementContents.jsx

@@ -1,7 +1,9 @@
-import React, { Fragment, useMemo } from 'react';
+import React, { Fragment, useMemo, useState } from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 
+import { TabContent, TabPane } from 'reactstrap';
+
 import LdapSecuritySetting from './LdapSecuritySetting';
 import LocalSecuritySetting from './LocalSecuritySetting';
 import SamlSecuritySetting from './SamlSecuritySetting';
@@ -14,64 +16,63 @@ import TwitterSecuritySetting from './TwitterSecuritySetting';
 import FacebookSecuritySetting from './FacebookSecuritySetting';
 import ShareLinkSetting from './ShareLinkSetting';
 
-import CustomNavigation from '../../CustomNavigation';
+import { CustomNav } from '../../CustomNavigation';
 
 function SecurityManagementContents(props) {
   const { t } = props;
 
+  const [activeTab, setActiveTab] = useState('passport_local');
+  const [activeComponents, setActiveComponents] = useState(new Set(['passport_local']));
+
+  const switchActiveTab = (selectedTab) => {
+    setActiveTab(selectedTab);
+    setActiveComponents(activeComponents.add(selectedTab));
+  };
+
   const navTabMapping = useMemo(() => {
     return {
       passport_local: {
         Icon: () => <i className="fa fa-users" />,
-        Content: LocalSecuritySetting,
         i18n: 'ID/Pass',
         index: 0,
       },
       passport_ldap: {
         Icon: () => <i className="fa fa-sitemap" />,
-        Content: LdapSecuritySetting,
         i18n: 'LDAP',
         index: 1,
       },
       passport_saml: {
         Icon: () => <i className="fa fa-key" />,
-        Content: SamlSecuritySetting,
         i18n: 'SAML',
         index: 2,
       },
       passport_oidc: {
         Icon: () => <i className="fa fa-key" />,
-        Content: OidcSecuritySetting,
         i18n: 'OIDC',
         index: 3,
       },
       passport_basic: {
         Icon: () => <i className="fa fa-lock" />,
-        Content: BasicSecuritySetting,
         i18n: 'BASIC',
         index: 4,
       },
       passport_google: {
         Icon: () => <i className="fa fa-google" />,
-        Content: GoogleSecuritySetting,
         i18n: 'Google',
         index: 5,
       },
       passport_github: {
         Icon: () => <i className="fa fa-github" />,
-        Content: GitHubSecuritySetting,
         i18n: 'GitHub',
         index: 6,
       },
       passport_twitter: {
         Icon: () => <i className="fa fa-twitter" />,
-        Content: TwitterSecuritySetting,
         i18n: 'Twitter',
         index: 7,
       },
       passport_facebook: {
         Icon: () => <i className="fa fa-facebook" />,
-        Content: FacebookSecuritySetting,
         i18n: '(TBD) Facebook',
         index: 8,
       },
@@ -103,7 +104,36 @@ function SecurityManagementContents(props) {
 
       <div className="auth-mechanism-configurations">
         <h2 className="border-bottom">{t('security_setting.Authentication mechanism settings')}</h2>
-        <CustomNavigation navTabMapping={navTabMapping} />
+        <CustomNav activeTab={activeTab} navTabMapping={navTabMapping} onNavSelected={switchActiveTab} hideBorderBottom />
+        <TabContent activeTab={activeTab} className="p-5">
+          <TabPane tabId="passport_local">
+            {activeComponents.has('passport_local') && <LocalSecuritySetting />}
+          </TabPane>
+          <TabPane tabId="passport_ldap">
+            {activeComponents.has('passport_ldap') && <LdapSecuritySetting />}
+          </TabPane>
+          <TabPane tabId="passport_saml">
+            {activeComponents.has('passport_saml') && <SamlSecuritySetting />}
+          </TabPane>
+          <TabPane tabId="passport_oidc">
+            {activeComponents.has('passport_oidc') && <OidcSecuritySetting />}
+          </TabPane>
+          <TabPane tabId="passport_basic">
+            {activeComponents.has('passport_basic') && <BasicSecuritySetting />}
+          </TabPane>
+          <TabPane tabId="passport_google">
+            {activeComponents.has('passport_google') && <GoogleSecuritySetting />}
+          </TabPane>
+          <TabPane tabId="passport_github">
+            {activeComponents.has('passport_github') && <GitHubSecuritySetting />}
+          </TabPane>
+          <TabPane tabId="passport_twitter">
+            {activeComponents.has('passport_twitter') && <TwitterSecuritySetting />}
+          </TabPane>
+          <TabPane tabId="passport_facebook">
+            {activeComponents.has('passport_facebook') && <FacebookSecuritySetting />}
+          </TabPane>
+        </TabContent>
       </div>
     </Fragment>
   );

+ 2 - 2
src/client/js/components/Admin/UserGroup/UserGroupDeleteModal.jsx

@@ -100,8 +100,8 @@ class UserGroupDeleteModal extends React.Component {
     const { t } = this.props;
 
     const optoins = this.availableOptions.map((opt) => {
-      const dataContent = `<i class="icon icon-fw ${opt.iconClass} ${opt.styleClass}"></i> <span class="action-name ${opt.styleClass}">${t(opt.label)}</span>`;
-      return <option key={opt.id} value={opt.actionForPages} data-content={dataContent}>{t(opt.label)}</option>;
+      const dataContent = `<i class="icon icon-fw ${opt.iconClass} ${opt.styleClass}"></i> <span class="action-name ${opt.styleClass}">${opt.label}</span>`;
+      return <option key={opt.id} value={opt.actionForPages} data-content={dataContent}>{opt.label}</option>;
     });
 
     return (

+ 18 - 0
src/client/js/components/Drawio.jsx

@@ -1,6 +1,8 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 
+import { debounce } from 'throttle-debounce';
+
 import { withTranslation } from 'react-i18next';
 
 import AppContainer from '../services/AppContainer';
@@ -26,6 +28,9 @@ class Drawio extends React.Component {
     this.drawioContent = this.props.drawioContent;
 
     this.onEdit = this.onEdit.bind(this);
+
+    // create debounced method for rendering Drawio
+    this.renderDrawioWithDebounce = debounce(200, this.renderDrawio);
   }
 
   onEdit() {
@@ -35,6 +40,16 @@ class Drawio extends React.Component {
   }
 
   componentDidMount() {
+    const DrawioViewer = window.GraphViewer;
+    if (DrawioViewer != null) {
+      this.renderDrawio();
+    }
+    else {
+      this.renderDrawioWithDebounce();
+    }
+  }
+
+  renderDrawio() {
     const DrawioViewer = window.GraphViewer;
     if (DrawioViewer != null) {
       const mxgraphs = this.drawioContainer.getElementsByClassName('mxgraph');
@@ -48,6 +63,9 @@ class Drawio extends React.Component {
         }
       }
     }
+    else {
+      this.renderDrawioWithDebounce();
+    }
   }
 
   renderContents() {

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

@@ -38,9 +38,7 @@ class ApiSettings extends React.Component {
     return (
       <React.Fragment>
 
-        <div className="container-fluid my-4">
-          <h2 className="border-bottom">{ t('API Token Settings') }</h2>
-        </div>
+        <h2 className="border-bottom my-4">{ t('API Token Settings') }</h2>
 
         <div className="row mb-3">
           <label htmlFor="apiToken" className="col-md-3 text-md-right">{t('Current API Token')}</label>

+ 7 - 9
src/client/js/components/Me/ExternalAccountLinkedMe.jsx

@@ -67,15 +67,13 @@ class ExternalAccountLinkedMe extends React.Component {
 
     return (
       <Fragment>
-        <div className="container-fluid my-4">
-          <h2 className="border-bottom">
-            <button type="button" className="btn btn-outline-secondary btn-sm pull-right" onClick={this.openAssociateModal}>
-              <i className="icon-plus" aria-hidden="true" />
-            Add
-            </button>
-            { t('admin:user_management.external_accounts') }
-          </h2>
-        </div>
+        <h2 className="border-bottom my-4">
+          <button type="button" className="btn btn-outline-secondary btn-sm pull-right" onClick={this.openAssociateModal}>
+            <i className="icon-plus" aria-hidden="true" />
+          Add
+          </button>
+          { t('admin:user_management.external_accounts') }
+        </h2>
 
         <table className="table table-bordered table-user-list">
           <thead>

+ 3 - 5
src/client/js/components/Me/PasswordSettings.jsx

@@ -90,11 +90,9 @@ class PasswordSettings extends React.Component {
           <div className="alert alert-warning">{ t('personal_settings.password_is_not_set') }</div>
         ) }
 
-        <div className="container-fluid my-4">
-          {(this.state.isPasswordSet)
-            ? <h2 className="border-bottom">{t('personal_settings.update_password')}</h2>
-          : <h2 className="border-bottom">{t('personal_settings.set_new_password')}</h2>}
-        </div>
+        {(this.state.isPasswordSet)
+          ? <h2 className="border-bottom my-4">{t('personal_settings.update_password')}</h2>
+        : <h2 className="border-bottom my-4">{t('personal_settings.set_new_password')}</h2>}
         {(this.state.isPasswordSet)
         && (
           <div className="row mb-3">

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

@@ -43,7 +43,7 @@ const PersonalSettings = (props) => {
 
 
   return (
-    <CustomNavigation navTabMapping={navTabMapping} />
+    <CustomNavigation navTabMapping={navTabMapping} tabContentClasses={['px-0']} />
   );
 
 };

+ 6 - 8
src/client/js/components/Me/UserSettings.jsx

@@ -13,16 +13,14 @@ class UserSettings extends React.Component {
 
     return (
       <Fragment>
-        <div className="container-fluid my-4">
-          <h2 className="border-bottom">{t('Basic Info')}</h2>
+        <div className="mb-5">
+          <h2 className="border-bottom my-4">{t('Basic Info')}</h2>
+          <BasicInfoSettings />
         </div>
-        <BasicInfoSettings />
-
-        <div className="container-fluid my-4">
-          <h2 className="border-bottom">{t('Set Profile Image')}</h2>
+        <div className="mb-5">
+          <h2 className="border-bottom my-4">{t('Set Profile Image')}</h2>
+          <ProfileImageSettings />
         </div>
-        <ProfileImageSettings />
-
       </Fragment>
     );
   }

+ 22 - 13
src/client/js/components/Page/CopyDropdown.jsx

@@ -16,25 +16,24 @@ class CopyDropdown extends React.Component {
     super(props);
 
     this.state = {
-      dropdownOpen: false,
       tooltipOpen: false,
       isParamsAppended: true,
+      pagePathWithParams: '',
+      pagePathUrl: '',
+      permalink: '',
+      markdownLink: '',
     };
 
     this.id = (Math.random() * 1000).toString();
 
-    this.toggle = this.toggle.bind(this);
     this.showToolTip = this.showToolTip.bind(this);
+    this.generateItemContents = this.generateItemContents.bind(this);
     this.generatePagePathWithParams = this.generatePagePathWithParams.bind(this);
     this.generatePagePathUrl = this.generatePagePathUrl.bind(this);
     this.generatePermalink = this.generatePermalink.bind(this);
     this.generateMarkdownLink = this.generateMarkdownLink.bind(this);
   }
 
-  toggle() {
-    this.setState({ dropdownOpen: !this.state.dropdownOpen });
-  }
-
   showToolTip() {
     this.setState({ tooltipOpen: true });
     setTimeout(() => {
@@ -64,6 +63,17 @@ class CopyDropdown extends React.Component {
     return str.replace(/ /g, '%20').replace(/\u3000/g, '%E3%80%80');
   }
 
+  generateItemContents() {
+    const pagePathWithParams = this.generatePagePathWithParams();
+    const pagePathUrl = this.generatePagePathUrl();
+    const permalink = this.generatePermalink();
+    const markdownLink = this.generateMarkdownLink();
+
+    this.setState({
+      pagePathWithParams, pagePathUrl, permalink, markdownLink,
+    });
+  }
+
   generatePagePathWithParams() {
     const { pagePath } = this.props;
     return decodeURI(`${pagePath}${this.uriParams}`);
@@ -108,11 +118,9 @@ class CopyDropdown extends React.Component {
     const {
       t, pageId, isShareLinkMode,
     } = this.props;
-    const { isParamsAppended } = this.state;
-
-    const pagePathWithParams = this.generatePagePathWithParams();
-    const pagePathUrl = this.generatePagePathUrl();
-    const permalink = this.generatePermalink();
+    const {
+      isParamsAppended, pagePathWithParams, pagePathUrl, permalink, markdownLink,
+    } = this.state;
 
     const copyTarget = isShareLinkMode ? `copyShareLink${pageId}` : 'copyPagePathDropdown';
     const dropdownToggleStyle = isShareLinkMode ? 'btn btn-secondary' : 'd-block text-muted bg-transparent btn-copy border-0';
@@ -128,6 +136,7 @@ class CopyDropdown extends React.Component {
             caret
             className={dropdownToggleStyle}
             style={this.props.buttonStyle}
+            onClick={this.generateItemContents}
           >
             { isShareLinkMode ? (
               <>Copy Link</>
@@ -195,9 +204,9 @@ class CopyDropdown extends React.Component {
 
             {/* Markdown Link */}
             { pageId && (
-              <CopyToClipboard text={this.generateMarkdownLink()} onCopy={this.showToolTip}>
+              <CopyToClipboard text={markdownLink} onCopy={this.showToolTip}>
                 <DropdownItem className="px-3 text-wrap">
-                  <DropdownItemContents title={t('copy_to_clipboard.Markdown link')} contents={this.generateMarkdownLink()} isContentsWrap />
+                  <DropdownItemContents title={t('copy_to_clipboard.Markdown link')} contents={markdownLink} isContentsWrap />
                 </DropdownItem>
               </CopyToClipboard>
             )}

+ 20 - 4
src/client/js/components/Page/NotFoundAlert.jsx

@@ -1,9 +1,11 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import { UncontrolledTooltip } from 'reactstrap';
+
 
 const NotFoundAlert = (props) => {
-  const { t, isHidden } = props;
+  const { t, isHidden, isGuestUserMode } = props;
   function clickHandler(viewType) {
     if (props.onPageCreateClicked === null) {
       return;
@@ -15,9 +17,12 @@ const NotFoundAlert = (props) => {
     return null;
   }
 
+
   return (
     <div className="border border-info p-3">
-      <div className="col-md-12 p-0">
+      <div
+        className="col-md-12 p-0"
+      >
         <h2 className="text-info lead">
           <i className="icon-info pr-2 font-weight-bold" aria-hidden="true"></i>
           {t('not_found_page.page_not_exist_alert')}
@@ -26,10 +31,20 @@ const NotFoundAlert = (props) => {
           type="button"
           className="m-1 pl-3 pr-3 btn bg-info text-white"
           onClick={() => { clickHandler('edit') }}
+          disabled={isGuestUserMode}
         >
-          <i className="icon-note icon-fw" />
-          {t('not_found_page.Create Page')}
+          <div id="create-page-btn-wrapper-for-tooltip">
+            <i className="icon-note icon-fw" />
+            {t('not_found_page.Create Page')}
+          </div>
         </button>
+
+
+        {isGuestUserMode && (
+        <UncontrolledTooltip placement="bottom" target="create-page-btn-wrapper-for-tooltip" fade={false}>
+          {t('Not available for guest')}
+        </UncontrolledTooltip>
+      )}
       </div>
     </div>
   );
@@ -40,6 +55,7 @@ NotFoundAlert.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   onPageCreateClicked: PropTypes.func,
   isHidden: PropTypes.bool.isRequired,
+  isGuestUserMode: PropTypes.bool.isRequired,
 };
 
 export default withTranslation()(NotFoundAlert);

+ 1 - 1
src/client/js/components/PageEditor/EditorNavbarBottom.jsx

@@ -115,7 +115,7 @@ const EditorNavbarBottom = (props) => {
         <Collapse isOpen={isExpanded}>
           <div className="px-2"> {/* set padding for border-top */}
             <div className={`navbar navbar-expand border-top px-0 ${additionalClasses.join(' ')}`}>
-              <form className="form-inline ml-auto">
+              <form className="form-inline ml-md-auto">
                 <OptionsSelector />
               </form>
             </div>

+ 3 - 3
src/client/js/components/PageEditor/OptionsSelector.jsx

@@ -247,9 +247,9 @@ class OptionsSelector extends React.Component {
   render() {
     return (
       <div className="d-flex flex-row">
-        <span className="ml-3">{this.renderThemeSelector()}</span>
-        <span className="ml-4">{this.renderKeymapModeSelector()}</span>
-        <span className="ml-4">{this.renderConfigurationDropdown()}</span>
+        <span className="ml-sm-3">{this.renderThemeSelector()}</span>
+        <span className="ml-2 ml-sm-4">{this.renderKeymapModeSelector()}</span>
+        <span className="ml-2 ml-sm-4">{this.renderConfigurationDropdown()}</span>
       </div>
     );
   }

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

@@ -67,7 +67,7 @@ class RecentCreated extends React.Component {
 
     return (
       <div className="page-list-container-create">
-        <ul className="page-list-ul page-list-ul-flat">
+        <ul className="page-list-ul page-list-ul-flat mb-3">
           {pageList}
         </ul>
         <PaginationWrapper

+ 5 - 2
src/client/js/services/PageContainer.js

@@ -104,8 +104,11 @@ export default class PageContainer extends Container {
     this.initStateMarkdown();
     this.checkAndUpdateImageUrlCached(this.state.likerUsers);
 
-    // skip if shared page or new page
-    if (this.state.shareLinkId == null && this.state.pageId != null) {
+    const { currentUser } = this.appContainer;
+    // see https://dev.growi.org/5fabddf8bbeb1a0048bcb9e9
+    const isAbleToGetAttachedInformationAboutPages = this.state.pageId != null || !(currentUser == null && this.state.isSharedPage);
+
+    if (isAbleToGetAttachedInformationAboutPages) {
       this.retrieveSeenUsers();
       this.retrieveLikeInfo();
       this.retrieveBookmarkInfo();

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

@@ -1,9 +1,5 @@
 .user-settings-page {
   .title {
-    padding: 0.5rem 15px;
-
-    line-height: 1em;
-
     @include variable-font-size(28px);
     line-height: 1.1em;
   }

+ 19 - 3
src/client/styles/scss/theme/_apply-colors-dark.scss

@@ -14,15 +14,16 @@ $bgcolor-table-hover: lighten($bgcolor-table, 7.5%) !default;
 $bgcolor-sidebar-list-group: $bgcolor-list !default;
 $color-tags: #949494 !default;
 $bgcolor-tags: $dark !default;
-$border-color-toc: $gray-500 !default;
+$border-color-global: $gray-500 !default;
+$border-color-toc: $border-color-global !default;
 
 // override bootstrap variables
-$border-color: $gray-500;
 $table-dark-color: $color-table;
 $table-dark-bg: $bgcolor-table;
 $table-dark-border-color: $border-color-table;
 $table-dark-hover-color: $color-table-hover;
 $table-dark-hover-bg: $bgcolor-table-hover;
+$border-color: $border-color-global;
 
 @import 'reboot-bootstrap-border-colors';
 @import 'reboot-bootstrap-tables';
@@ -38,6 +39,7 @@ select.form-control,
 textarea.form-control {
   color: lighten($color-global, 30%);
   background-color: darken($bgcolor-global, 5%);
+  border-color: $border-color-global;
   &:focus {
     background-color: $bgcolor-global;
   }
@@ -65,7 +67,7 @@ textarea.form-control {
 }
 
 .input-group input {
-  border-color: $secondary;
+  border-color: $border-color-global;
 }
 
 /*
@@ -354,6 +356,13 @@ ul.pagination {
   }
 }
 
+/*
+ * admin settings
+ */
+.admin-setting-header {
+  border-color: $border-color-global;
+}
+
 /*
 * grw-side-contents
 */
@@ -367,6 +376,13 @@ ul.pagination {
   }
 }
 
+/*
+ * modal
+ */
+.grw-modal-head {
+  border-color: $border-color-global;
+}
+
 /*
  * GROWI user page
  */

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

@@ -14,7 +14,8 @@ $bgcolor-table-hover: rgba(black, 0.075) !default;
 $bgcolor-sidebar-list-group: $bgcolor-list !default;
 $color-tags: $gray-500 !default;
 $bgcolor-tags: $gray-200 !default;
-$border-color-toc: $gray-300 !default;
+$border-color-global: $gray-300 !default;
+$border-color-toc: $border-color-global !default;
 
 // override bootstrap variables
 $table-color: $color-table;
@@ -22,6 +23,7 @@ $table-bg: $bgcolor-table;
 $table-border-color: $border-color-table;
 $table-hover-color: $color-table-hover;
 $table-hover-bg: $bgcolor-table-hover;
+$border-color: $border-color-global;
 
 @import 'reboot-bootstrap-border-colors';
 @import 'reboot-bootstrap-tables';
@@ -314,6 +316,13 @@ $table-hover-bg: $bgcolor-table-hover;
   border-color: $border-color;
 }
 
+/*
+ * modal
+ */
+.grw-modal-head {
+  border-color: $border-color-global;
+}
+
 /*
  * GROWI user page
  */

+ 1 - 0
src/client/styles/scss/theme/mixins/_list-group.scss

@@ -3,6 +3,7 @@
     .list-group-item {
       color: $color;
       background-color: $bgcolor;
+      border-color: $border-color-global;
 
       &.list-group-item-action {
         &:hover {

+ 4 - 0
src/client/styles/scss/theme/spring.scss

@@ -94,6 +94,10 @@ html[dark] {
   @import 'apply-colors-light';
 
   //Button
+  // Outline buttons are applyed the accent color to this spring theme cuz the primary is too light and it looks like unable to click them.
+  .btn.btn-outline-primary {
+    @include button-outline-variant($accentcolor, $accentcolor, lighten($accentcolor, 20%), $accentcolor);
+  }
   .btn-group.grw-three-stranded-button {
     .btn.btn-outline-primary {
       @include three-stranded-button(darken($primary, 50%), lighten($primary, 5%), lighten($primary, 10%));

+ 4 - 3
src/server/routes/apiv3/bookmarks.js

@@ -3,7 +3,7 @@ const loggerFactory = require('@alias/logger');
 const logger = loggerFactory('growi:routes:apiv3:bookmarks'); // eslint-disable-line no-unused-vars
 
 const express = require('express');
-const { body, query } = require('express-validator');
+const { body, query, param } = require('express-validator');
 
 const router = express.Router();
 
@@ -176,12 +176,13 @@ module.exports = (crowi) => {
    *                schema:
    *                  $ref: '#/components/schemas/Bookmark'
    */
-  validator.myBookmarkList = [
+  validator.userBookmarkList = [
+    param('userId').isMongoId().withMessage('userId is required'),
     query('page').isInt({ min: 1 }),
     query('limit').if(value => value != null).isInt({ max: 300 }).withMessage('You should set less than 300 or not to set limit.'),
   ];
 
-  router.get('/:userId', accessTokenParser, loginRequiredStrictly, validator.myBookmarkList, apiV3FormValidator, async(req, res) => {
+  router.get('/:userId', accessTokenParser, loginRequired, validator.userBookmarkList, apiV3FormValidator, async(req, res) => {
     const { userId } = req.params;
     const page = req.query.page;
     const limit = parseInt(req.query.limit) || await crowi.configManager.getConfig('crowi', 'customize:showPageLimitationM') || 30;

+ 1 - 1
src/server/views/me/index.html

@@ -15,7 +15,7 @@
 
 <div id="main" class="main">
   <div id="content-main" class="content-main container-lg">
-    <div class="content-main" id="personal-setting"></div>
+    <div id="personal-setting"></div>
   </div>
 </div>
 {% endblock %}

+ 3 - 2
src/server/views/widget/headers/drawio.html

@@ -24,8 +24,9 @@
       DrawioViewer.useResizeSensor = false;
       DrawioViewer.prototype.checkVisibleState = false;
 
-      // initialize
-      DrawioViewer.processElements();
+      // Set responsive option.
+      // refs: https://github.com/jgraph/drawio/blob/v13.9.1/src/main/webapp/js/diagramly/GraphViewer.js#L89-L95
+      DrawioViewer.prototype.responsive = true;
     }
   };
 </script>

+ 3 - 0
src/server/views/widget/not_found_content.html

@@ -1,6 +1,9 @@
 <div id="content-main" class="content-main page-list"
   data-path="{{ encodeURI(path) }}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
+  data-page-grant="{{ grant }}"
+  data-page-grant-group="{{ grantedGroupId }}"
+  data-page-grant-group-name="{{ grantedGroupName }}"
   {% if templateTags %}
     data-template-tags="{{ templateTags }}"
   {% endif %}