Răsfoiți Sursa

Merge branch 'master' into imprv/improve-link-edit-modal

yusuketk 5 ani în urmă
părinte
comite
05c34f0e4f
29 a modificat fișierele cu 371 adăugiri și 354 ștergeri
  1. 1 1
      .github/workflows/release-rc.yml
  2. 2 2
      .github/workflows/release.yml
  3. 1 0
      CHANGES.md
  4. 0 6
      src/client/js/app.jsx
  5. 9 6
      src/client/js/components/Admin/Users/UserMenu.jsx
  6. 16 35
      src/client/js/components/Navbar/GrowiSubNavigation.jsx
  7. 67 0
      src/client/js/components/Navbar/SubNavButtons.jsx
  8. 148 164
      src/client/js/components/Page/CopyDropdown.jsx
  9. 34 3
      src/client/js/components/Page/DisplaySwitcher.jsx
  10. 2 3
      src/client/js/components/Page/RenderTagLabels.jsx
  11. 0 34
      src/client/js/components/Page/RevisionPathControls.jsx
  12. 1 2
      src/client/js/components/Page/TagLabels.jsx
  13. 4 1
      src/client/js/components/PageAccessories.jsx
  14. 1 1
      src/client/js/components/PageAccessoriesModalControl.jsx
  15. 8 1
      src/client/js/components/ShareLink/ShareLinkList.jsx
  16. 9 0
      src/client/js/services/NavigationContainer.js
  17. 9 5
      src/client/styles/scss/_layout.scss
  18. 0 1
      src/client/styles/scss/_page-accessories-control.scss
  19. 2 2
      src/client/styles/scss/_user.scss
  20. 0 11
      src/client/styles/scss/theme/_apply-colors-dark.scss
  21. 0 11
      src/client/styles/scss/theme/_apply-colors-light.scss
  22. 2 12
      src/client/styles/scss/theme/_apply-colors.scss
  23. 21 0
      src/client/styles/scss/theme/_reboot-bootstrap-buttons.scss
  24. 12 0
      src/client/styles/scss/theme/_reboot-bootstrap-colors.scss
  25. 14 24
      src/client/styles/scss/theme/kibela.scss
  26. 2 2
      src/migrations/20200903080025-remove-timeline-type.js.js
  27. 2 6
      src/server/routes/apiv3/customize-setting.js
  28. 3 3
      src/server/views/layout-growi/user_page.html
  29. 1 18
      src/server/views/widget/page_content.html

+ 1 - 1
.github/workflows/release-rc.yml

@@ -35,7 +35,7 @@ jobs:
     - name: Get SemVer
     - name: Get SemVer
       run: |
       run: |
         semver=`npm run version --silent`
         semver=`npm run version --silent`
-        echo ::set-env name=SEMVER::$semver
+        echo "SEMVER=$semver" >> $GITHUB_ENV
 
 
     - name: Docker Tags by SemVer
     - name: Docker Tags by SemVer
       uses: weseek/ghaction-docker-tags-by-semver@v1.0.3
       uses: weseek/ghaction-docker-tags-by-semver@v1.0.3

+ 2 - 2
.github/workflows/release.yml

@@ -28,7 +28,7 @@ jobs:
         npm --no-git-tag-version version patch
         npm --no-git-tag-version version patch
         export RELEASE_VERSION=`npm run version --silent`
         export RELEASE_VERSION=`npm run version --silent`
         sh ./bin/github-actions/update-readme.sh
         sh ./bin/github-actions/update-readme.sh
-        echo ::set-env name=RELEASE_VERSION::$RELEASE_VERSION
+        echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_ENV
         echo ::set-output name=RELEASE_VERSION::$RELEASE_VERSION
         echo ::set-output name=RELEASE_VERSION::$RELEASE_VERSION
 
 
     - name: Checkout, Commit, Tag and Push
     - name: Checkout, Commit, Tag and Push
@@ -69,7 +69,7 @@ jobs:
     - name: Determine suffix
     - name: Determine suffix
       run: |
       run: |
         [[ ${{ matrix.flavor }} = "nocdn" ]] && suffix="-nocdn" || suffix=""
         [[ ${{ matrix.flavor }} = "nocdn" ]] && suffix="-nocdn" || suffix=""
-        echo ::set-env name=SUFFIX::$suffix
+        echo "SUFFIX=$suffix" >> $GITHUB_ENV
 
 
     - name: Set up Docker Buildx
     - name: Set up Docker Buildx
       uses: docker/setup-buildx-action@v1
       uses: docker/setup-buildx-action@v1

+ 1 - 0
CHANGES.md

@@ -9,6 +9,7 @@
 
 
 ### Updates
 ### Updates
 
 
+* Feature: File Upload Settings on admin pages
 * Improvement: Basic layout of page
 * Improvement: Basic layout of page
 * Support: Support MongoDB 4.0, 4.2 and 4.4
 * Support: Support MongoDB 4.0, 4.2 and 4.4
 * Support: Upgrade libs
 * Support: Upgrade libs

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

@@ -29,9 +29,6 @@ import MyDraftList from './components/MyDraftList/MyDraftList';
 import BookmarkIcon from './components/Icons/BookmarkIcon';
 import BookmarkIcon from './components/Icons/BookmarkIcon';
 import BookmarkList from './components/PageList/BookmarkList';
 import BookmarkList from './components/PageList/BookmarkList';
 import LikerList from './components/User/LikerList';
 import LikerList from './components/User/LikerList';
-import TableOfContents from './components/TableOfContents';
-import PageAccessories from './components/PageAccessories';
-import UserInfo from './components/User/UserInfo';
 import Fab from './components/Fab';
 import Fab from './components/Fab';
 import PersonalSettings from './components/Me/PersonalSettings';
 import PersonalSettings from './components/Me/PersonalSettings';
 import UserContentsLinks from './components/UserContentsLinks';
 import UserContentsLinks from './components/UserContentsLinks';
@@ -112,8 +109,6 @@ if (pageContainer.state.pageId != null) {
     'page-comments-list': <PageComments />,
     'page-comments-list': <PageComments />,
     'page-comment-write': <CommentEditorLazyRenderer />,
     'page-comment-write': <CommentEditorLazyRenderer />,
     'page-management': <PageManagement />,
     'page-management': <PageManagement />,
-    'page-accessories': <PageAccessories />,
-    'revision-toc': <TableOfContents />,
     'liker-list': <LikerList />,
     'liker-list': <LikerList />,
     'page-content-footer': <PageContentFooter />,
     'page-content-footer': <PageContentFooter />,
 
 
@@ -134,7 +129,6 @@ if (pageContainer.state.path != null) {
     'page': <Page />,
     'page': <Page />,
     'grw-subnav-container': <GrowiSubNavigation />,
     'grw-subnav-container': <GrowiSubNavigation />,
     'grw-subnav-switcher-container': <GrowiSubNavigationSwitcher />,
     'grw-subnav-switcher-container': <GrowiSubNavigationSwitcher />,
-    'user-info': <UserInfo pageUser={pageContainer.state.pageUser} />,
     'display-switcher': <DisplaySwitcher />,
     'display-switcher': <DisplaySwitcher />,
   });
   });
 }
 }

+ 9 - 6
src/client/js/components/Admin/Users/UserMenu.jsx

@@ -1,6 +1,9 @@
 import React, { Fragment } from 'react';
 import React, { Fragment } from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 import { withTranslation } from 'react-i18next';
+import {
+  UncontrolledDropdown, DropdownToggle, DropdownMenu,
+} from 'reactstrap';
 
 
 import StatusActivateButton from './StatusActivateButton';
 import StatusActivateButton from './StatusActivateButton';
 import StatusSuspendedButton from './StatusSuspendedButton';
 import StatusSuspendedButton from './StatusSuspendedButton';
@@ -80,16 +83,16 @@ class UserMenu extends React.Component {
 
 
     return (
     return (
       <Fragment>
       <Fragment>
-        <div className="btn-group admin-user-menu position-absolute" role="group">
-          <button id="userMenu" type="button" className="btn btn-outline-secondary btn-sm dropdown-toggle" data-toggle="dropdown">
+        <UncontrolledDropdown id="userMenu" size="sm">
+          <DropdownToggle caret color="secondary" outline>
             <i className="icon-settings"></i>
             <i className="icon-settings"></i>
-          </button>
-          <div className="dropdown-menu" aria-labelledby="userMenu">
+          </DropdownToggle>
+          <DropdownMenu positionFixed>
             {this.renderEditMenu()}
             {this.renderEditMenu()}
             {user.status !== 4 && this.renderStatusMenu()}
             {user.status !== 4 && this.renderStatusMenu()}
             {user.status === 2 && this.renderAdminMenu()}
             {user.status === 2 && this.renderAdminMenu()}
-          </div>
-        </div>
+          </DropdownMenu>
+        </UncontrolledDropdown>
       </Fragment>
       </Fragment>
     );
     );
   }
   }

+ 16 - 35
src/client/js/components/Navbar/GrowiSubNavigation.jsx

@@ -12,21 +12,17 @@ import AppContainer from '../../services/AppContainer';
 import NavigationContainer from '../../services/NavigationContainer';
 import NavigationContainer from '../../services/NavigationContainer';
 import PageContainer from '../../services/PageContainer';
 import PageContainer from '../../services/PageContainer';
 
 
-import RevisionPathControls from '../Page/RevisionPathControls';
+import CopyDropdown from '../Page/CopyDropdown';
 import TagLabels from '../Page/TagLabels';
 import TagLabels from '../Page/TagLabels';
-import LikeButton from '../LikeButton';
-import BookmarkButton from '../BookmarkButton';
+import SubnavButtons from './SubNavButtons';
 import PageEditorModeManager from './PageEditorModeManager';
 import PageEditorModeManager from './PageEditorModeManager';
 
 
 import AuthorInfo from './AuthorInfo';
 import AuthorInfo from './AuthorInfo';
 import DrawerToggler from './DrawerToggler';
 import DrawerToggler from './DrawerToggler';
 
 
-import PageManagement from '../Page/PageManagement';
-
-
 const PagePathNav = ({
 const PagePathNav = ({
   // eslint-disable-next-line react/prop-types
   // eslint-disable-next-line react/prop-types
-  pageId, pagePath, isEditorMode,
+  pageId, pagePath, isEditorMode, isCompactMode,
 }) => {
 }) => {
 
 
   const dPagePath = new DevidedPagePath(pagePath, false, true);
   const dPagePath = new DevidedPagePath(pagePath, false, true);
@@ -47,43 +43,29 @@ const PagePathNav = ({
     latterLink = <PagePathHierarchicalLink linkedPagePath={linkedPagePathLatter} basePath={dPagePath.former} />;
     latterLink = <PagePathHierarchicalLink linkedPagePath={linkedPagePathLatter} basePath={dPagePath.former} />;
   }
   }
 
 
+  const copyDropdownId = `copydropdown${isCompactMode ? '-subnav-compact' : ''}-${pageId}`;
+  const copyDropdownToggleClassName = 'd-block text-muted bg-transparent btn-copy border-0';
+
   return (
   return (
     <div className="grw-page-path-nav">
     <div className="grw-page-path-nav">
       {formerLink}
       {formerLink}
       <span className="d-flex align-items-center">
       <span className="d-flex align-items-center">
         <h1 className="m-0">{latterLink}</h1>
         <h1 className="m-0">{latterLink}</h1>
         <div className="mx-2">
         <div className="mx-2">
-          <RevisionPathControls
+          <CopyDropdown
             pageId={pageId}
             pageId={pageId}
             pagePath={pagePath}
             pagePath={pagePath}
-          />
+            dropdownToggleId={copyDropdownId}
+            dropdownToggleClassName={copyDropdownToggleClassName}
+          >
+            <i className="ti-clipboard"></i>
+          </CopyDropdown>
         </div>
         </div>
       </span>
       </span>
     </div>
     </div>
   );
   );
 };
 };
 
 
-
-/* eslint-enable react/prop-types */
-
-/* eslint-disable react/prop-types */
-const PageReactionButtons = ({ appContainer, pageContainer }) => {
-
-  return (
-    <>
-      {pageContainer.isAbleToShowLikeButton && (
-        <span className="mr-2">
-          <LikeButton />
-        </span>
-      )}
-      <span>
-        <BookmarkButton />
-      </span>
-    </>
-  );
-};
-/* eslint-enable react/prop-types */
-
 const GrowiSubNavigation = (props) => {
 const GrowiSubNavigation = (props) => {
   const {
   const {
     appContainer, navigationContainer, pageContainer, isCompactMode,
     appContainer, navigationContainer, pageContainer, isCompactMode,
@@ -119,19 +101,18 @@ const GrowiSubNavigation = (props) => {
               <TagLabels editorMode={editorMode} />
               <TagLabels editorMode={editorMode} />
             </div>
             </div>
           ) }
           ) }
-          <PagePathNav pageId={pageId} pagePath={path} isEditorMode={isEditorMode} />
+          <PagePathNav pageId={pageId} pagePath={path} isEditorMode={isEditorMode} isCompactMode={isCompactMode} />
         </div>
         </div>
       </div>
       </div>
 
 
       {/* Right side */}
       {/* Right side */}
       <div className="d-flex">
       <div className="d-flex">
 
 
-        <div className={`d-flex ${isEditorMode ? 'align-items-center' : 'flex-column align-items-end'}`}>
+        <div className="d-flex flex-column align-items-end">
           <div className="d-flex">
           <div className="d-flex">
-            { pageContainer.isAbleToShowPageReactionButtons && <PageReactionButtons appContainer={appContainer} pageContainer={pageContainer} /> }
-            { pageContainer.isAbleToShowPageManagement && <PageManagement isCompactMode={isCompactMode} /> }
+            <SubnavButtons isCompactMode={isCompactMode} />
           </div>
           </div>
-          <div className={`${isEditorMode ? 'ml-2' : 'mt-2'}`}>
+          <div className="mt-2">
             {pageContainer.isAbleToShowPageEditorModeManager && (
             {pageContainer.isAbleToShowPageEditorModeManager && (
               <PageEditorModeManager
               <PageEditorModeManager
                 onPageEditorModeButtonClicked={onPageEditorModeButtonClicked}
                 onPageEditorModeButtonClicked={onPageEditorModeButtonClicked}

+ 67 - 0
src/client/js/components/Navbar/SubNavButtons.jsx

@@ -0,0 +1,67 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import AppContainer from '../../services/AppContainer';
+import NavigationContainer from '../../services/NavigationContainer';
+import PageContainer from '../../services/PageContainer';
+import { withUnstatedContainers } from '../UnstatedUtils';
+
+import BookmarkButton from '../BookmarkButton';
+import LikeButton from '../LikeButton';
+import PageManagement from '../Page/PageManagement';
+
+const SubnavButtons = (props) => {
+  const {
+    appContainer, navigationContainer, pageContainer, isCompactMode,
+  } = props;
+
+  /* eslint-enable react/prop-types */
+
+  /* eslint-disable react/prop-types */
+  const PageReactionButtons = ({ pageContainer }) => {
+
+    return (
+      <>
+        {pageContainer.isAbleToShowLikeButton && (
+          <span>
+            <LikeButton />
+          </span>
+        )}
+        <span>
+          <BookmarkButton />
+        </span>
+
+      </>
+    );
+  };
+  /* eslint-enable react/prop-types */
+
+  const { editorMode } = navigationContainer.state;
+  const isViewMode = editorMode === 'view';
+
+  return (
+    <>
+      {isViewMode && (
+      <>
+        { pageContainer.isAbleToShowPageReactionButtons && <PageReactionButtons appContainer={appContainer} pageContainer={pageContainer} /> }
+        { pageContainer.isAbleToShowPageManagement && <PageManagement isCompactMode={isCompactMode} /> }
+      </>
+      )}
+    </>
+  );
+};
+
+/**
+ * Wrapper component for using unstated
+ */
+const SubnavButtonsWrapper = withUnstatedContainers(SubnavButtons, [AppContainer, NavigationContainer, PageContainer]);
+
+
+SubnavButtons.propTypes = {
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  navigationContainer: PropTypes.instanceOf(NavigationContainer).isRequired,
+  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
+
+  isCompactMode: PropTypes.bool,
+};
+
+export default SubnavButtonsWrapper;

+ 148 - 164
src/client/js/components/Page/CopyDropdown.jsx

@@ -1,92 +1,70 @@
-import React from 'react';
+import React, {
+  useState, useMemo, useCallback,
+} from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
 import { withTranslation } from 'react-i18next';
 import { withTranslation } from 'react-i18next';
 
 
 import {
 import {
-  UncontrolledDropdown, DropdownToggle, DropdownMenu, DropdownItem,
+  Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
   Tooltip,
   Tooltip,
 } from 'reactstrap';
 } from 'reactstrap';
 
 
 import { CopyToClipboard } from 'react-copy-to-clipboard';
 import { CopyToClipboard } from 'react-copy-to-clipboard';
 
 
-class CopyDropdown extends React.Component {
-
-  constructor(props) {
-    super(props);
+function encodeSpaces(str) {
+  if (str == null) {
+    return null;
+  }
 
 
-    this.state = {
-      tooltipOpen: false,
-      isParamsAppended: true,
-      pagePathWithParams: '',
-      pagePathUrl: '',
-      permalink: '',
-      markdownLink: '',
-    };
+  // Encode SPACE and IDEOGRAPHIC SPACE
+  return str.replace(/ /g, '%20').replace(/\u3000/g, '%E3%80%80');
+}
 
 
-    this.id = (Math.random() * 1000).toString();
 
 
-    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);
-  }
+/* eslint-disable react/prop-types */
+const DropdownItemContents = ({ title, contents }) => (
+  <>
+    <div className="h6 mt-1 mb-2"><strong>{title}</strong></div>
+    <div className="card well mb-1 p-2">{contents}</div>
+  </>
+);
+/* eslint-enable react/prop-types */
 
 
-  showToolTip() {
-    this.setState({ tooltipOpen: true });
-    setTimeout(() => {
-      this.setState({ tooltipOpen: false });
-    }, 1000);
-  }
 
 
-  get uriParams() {
-    const { isParamsAppended } = this.state;
+const CopyDropdown = (props) => {
+  const [dropdownOpen, setDropdownOpen] = useState(false);
+  const [tooltipOpen, setTooltipOpen] = useState(false);
+  const [isParamsAppended, setParamsAppended] = useState(!props.isShareLinkMode);
 
 
-    if (!isParamsAppended) {
+  /*
+   * functions to construct labels and URLs
+   */
+  const getUriParams = useCallback(() => {
+    if (!isParamsAppended || !dropdownOpen) {
       return '';
       return '';
     }
     }
 
 
     const {
     const {
       search, hash,
       search, hash,
     } = window.location;
     } = window.location;
-    return `${search}${hash}`;
-  }
-
-  encodeSpaces(str) {
-    if (str == null) {
-      return null;
-    }
 
 
-    // Encode SPACE and IDEOGRAPHIC SPACE
-    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,
-    });
-  }
+    return `${search}${hash}`;
+  }, [isParamsAppended, dropdownOpen]);
 
 
-  generatePagePathWithParams() {
-    const { pagePath } = this.props;
-    return decodeURI(`${pagePath}${this.uriParams}`);
-  }
+  const pagePathWithParams = useMemo(() => {
+    const { pagePath } = props;
+    return decodeURI(`${pagePath}${getUriParams()}`);
+  }, [props, getUriParams]);
 
 
-  generatePagePathUrl() {
+  const pagePathUrl = useMemo(() => {
     const { origin } = window.location;
     const { origin } = window.location;
-    return `${origin}${this.encodeSpaces(this.generatePagePathWithParams())}`;
-  }
+    return `${origin}${encodeSpaces(pagePathWithParams)}`;
+  }, [pagePathWithParams]);
 
 
-  generatePermalink() {
+  const permalink = useMemo(() => {
     const { origin } = window.location;
     const { origin } = window.location;
-    const { pageId, isShareLinkMode } = this.props;
+    const { pageId, isShareLinkMode } = props;
 
 
     if (pageId == null) {
     if (pageId == null) {
       return null;
       return null;
@@ -95,140 +73,146 @@ class CopyDropdown extends React.Component {
       return decodeURI(`${origin}/share/${pageId}`);
       return decodeURI(`${origin}/share/${pageId}`);
     }
     }
 
 
-    return this.encodeSpaces(decodeURI(`${origin}/${pageId}${this.uriParams}`));
-  }
+    return encodeSpaces(decodeURI(`${origin}/${pageId}${getUriParams()}`));
+  }, [props, getUriParams]);
 
 
-  generateMarkdownLink() {
-    const { pagePath } = this.props;
+  const markdownLink = useMemo(() => {
+    const { pagePath } = props;
 
 
-    const label = decodeURI(`${pagePath}${this.uriParams}`);
-    const permalink = this.generatePermalink();
+    const label = decodeURI(`${pagePath}${getUriParams()}`);
+    // const permalink = generatePermalink();
 
 
     return `[${label}](${permalink})`;
     return `[${label}](${permalink})`;
-  }
+  }, [props, getUriParams, permalink]);
 
 
-  DropdownItemContents = ({ title, contents }) => (
-    <>
-      <div className="h6 mt-1 mb-2"><strong>{title}</strong></div>
-      <div className="card well mb-1 p-2">{contents}</div>
-    </>
-  );
 
 
-  render() {
-    const {
-      t, pageId, isShareLinkMode,
-    } = this.props;
-    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';
-
-    const { id, DropdownItemContents } = this;
-
-    const customSwitchForParamsId = `customSwitchForParams_${id}`;
-
-    return (
-      <>
-        <UncontrolledDropdown id={copyTarget} className="grw-copy-dropdown">
-          <DropdownToggle
-            caret
-            className={dropdownToggleStyle}
-            style={this.props.buttonStyle}
-            onClick={this.generateItemContents}
-          >
-            { isShareLinkMode ? (
-              <>Copy Link</>
-            ) : (<i className="ti-clipboard"></i>)}
-          </DropdownToggle>
-
-          <DropdownMenu positionFixed modifiers={{ preventOverflow: { boundariesElement: null } }}>
-
-            <div className="d-flex align-items-center justify-content-between">
-              <DropdownItem header className="px-3">
-                { t('copy_to_clipboard.Copy to clipboard') }
-              </DropdownItem>
+  /**
+   * control
+   */
+  const toggleDropdown = useCallback(() => {
+    setDropdownOpen(!dropdownOpen);
+  }, [dropdownOpen]);
+
+  const toggleAppendParams = useCallback(() => {
+    setParamsAppended(!isParamsAppended);
+  }, [isParamsAppended]);
+
+  const showToolTip = useCallback(() => {
+    setTooltipOpen(true);
+    setTimeout(() => {
+      setTooltipOpen(false);
+    }, 1000);
+  }, []);
+
+
+  /*
+   * render
+   */
+  const {
+    t, dropdownToggleId, pageId, dropdownToggleClassName, children, isShareLinkMode,
+  } = props;
+
+  const customSwitchForParamsId = `customSwitchForParams_${dropdownToggleId}`;
+
+  return (
+    <>
+      <Dropdown className="grw-copy-dropdown" isOpen={dropdownOpen} toggle={toggleDropdown}>
+        <DropdownToggle
+          caret
+          className={dropdownToggleClassName}
+        >
+          <span id={dropdownToggleId}>{children}</span>
+        </DropdownToggle>
+
+        <DropdownMenu positionFixed modifiers={{ preventOverflow: { boundariesElement: null } }}>
+
+          <div className="d-flex align-items-center justify-content-between">
+            <DropdownItem header className="px-3">
+              { t('copy_to_clipboard.Copy to clipboard') }
+            </DropdownItem>
+            { !isShareLinkMode && (
               <div className="px-3 custom-control custom-switch custom-switch-sm">
               <div className="px-3 custom-control custom-switch custom-switch-sm">
                 <input
                 <input
                   type="checkbox"
                   type="checkbox"
                   id={customSwitchForParamsId}
                   id={customSwitchForParamsId}
                   className="custom-control-input"
                   className="custom-control-input"
                   checked={isParamsAppended}
                   checked={isParamsAppended}
-                  onChange={e => this.setState({ isParamsAppended: !isParamsAppended })}
+                  onChange={toggleAppendParams}
                 />
                 />
                 <label className="custom-control-label small" htmlFor={customSwitchForParamsId}>Append params</label>
                 <label className="custom-control-label small" htmlFor={customSwitchForParamsId}>Append params</label>
               </div>
               </div>
-            </div>
+            ) }
+          </div>
+
+          <DropdownItem divider className="my-0"></DropdownItem>
+
+          {/* Page path */}
+          <CopyToClipboard text={pagePathWithParams} onCopy={showToolTip}>
+            <DropdownItem className="px-3">
+              <DropdownItemContents title={t('copy_to_clipboard.Page path')} contents={pagePathWithParams} />
+            </DropdownItem>
+          </CopyToClipboard>
+
+          <DropdownItem divider className="my-0"></DropdownItem>
+
+          {/* Page path URL */}
+          <CopyToClipboard text={pagePathUrl} onCopy={showToolTip}>
+            <DropdownItem className="px-3">
+              <DropdownItemContents title={t('copy_to_clipboard.Page URL')} contents={pagePathUrl} />
+            </DropdownItem>
+          </CopyToClipboard>
+          <DropdownItem divider className="my-0"></DropdownItem>
+
+          {/* Permanent Link */}
+          { pageId && (
+            <CopyToClipboard text={permalink} onCopy={showToolTip}>
+              <DropdownItem className="px-3">
+                <DropdownItemContents title={t('copy_to_clipboard.Permanent link')} contents={permalink} />
+              </DropdownItem>
+            </CopyToClipboard>
+          )}
 
 
-            <DropdownItem divider className="my-0"></DropdownItem>
+          <DropdownItem divider className="my-0"></DropdownItem>
 
 
-            {/* Page path */}
-            <CopyToClipboard text={pagePathWithParams} onCopy={this.showToolTip}>
+          {/* Page path + Permanent Link */}
+          { pageId && (
+            <CopyToClipboard text={`${pagePathWithParams}\n${permalink}`} onCopy={showToolTip}>
               <DropdownItem className="px-3">
               <DropdownItem className="px-3">
-                <DropdownItemContents title={t('copy_to_clipboard.Page path')} contents={pagePathWithParams} />
+                <DropdownItemContents title={t('copy_to_clipboard.Page path and permanent link')} contents={<>{pagePathWithParams}<br />{permalink}</>} />
               </DropdownItem>
               </DropdownItem>
             </CopyToClipboard>
             </CopyToClipboard>
+          )}
 
 
-            <DropdownItem divider className="my-0"></DropdownItem>
+          <DropdownItem divider className="my-0"></DropdownItem>
 
 
-            {/* Page path URL */}
-            <CopyToClipboard text={pagePathUrl} onCopy={this.showToolTip}>
-              <DropdownItem className="px-3">
-                <DropdownItemContents title={t('copy_to_clipboard.Page URL')} contents={pagePathUrl} />
+          {/* Markdown Link */}
+          { pageId && (
+            <CopyToClipboard text={markdownLink} onCopy={showToolTip}>
+              <DropdownItem className="px-3 text-wrap">
+                <DropdownItemContents title={t('copy_to_clipboard.Markdown link')} contents={markdownLink} isContentsWrap />
               </DropdownItem>
               </DropdownItem>
             </CopyToClipboard>
             </CopyToClipboard>
-            <DropdownItem divider className="my-0"></DropdownItem>
-
-            {/* Permanent Link */}
-            { pageId && (
-              <CopyToClipboard text={permalink} onCopy={this.showToolTip}>
-                <DropdownItem className="px-3">
-                  <DropdownItemContents title={t('copy_to_clipboard.Permanent link')} contents={permalink} />
-                </DropdownItem>
-              </CopyToClipboard>
-            )}
-
-            <DropdownItem divider className="my-0"></DropdownItem>
-
-            {/* Page path + Permanent Link */}
-            { pageId && (
-              <CopyToClipboard text={`${pagePathWithParams}\n${permalink}`} onCopy={this.showToolTip}>
-                <DropdownItem className="px-3">
-                  <DropdownItemContents title={t('copy_to_clipboard.Page path and permanent link')} contents={<>{pagePathWithParams}<br />{permalink}</>} />
-                </DropdownItem>
-              </CopyToClipboard>
-            )}
-
-            <DropdownItem divider className="my-0"></DropdownItem>
-
-            {/* Markdown Link */}
-            { pageId && (
-              <CopyToClipboard text={markdownLink} onCopy={this.showToolTip}>
-                <DropdownItem className="px-3 text-wrap">
-                  <DropdownItemContents title={t('copy_to_clipboard.Markdown link')} contents={markdownLink} isContentsWrap />
-                </DropdownItem>
-              </CopyToClipboard>
-            )}
-          </DropdownMenu>
-
-        </UncontrolledDropdown>
-
-        <Tooltip placement="bottom" isOpen={this.state.tooltipOpen} target={copyTarget} fade={false}>
-          copied!
-        </Tooltip>
-      </>
-    );
-  }
+          )}
+        </DropdownMenu>
 
 
-}
+      </Dropdown>
+
+      <Tooltip placement="bottom" isOpen={tooltipOpen} target={dropdownToggleId} fade={false}>
+        copied!
+      </Tooltip>
+    </>
+  );
+};
 
 
 CopyDropdown.propTypes = {
 CopyDropdown.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   t: PropTypes.func.isRequired, // i18next
 
 
+  children: PropTypes.node.isRequired,
+  dropdownToggleId: PropTypes.string.isRequired,
   pagePath: PropTypes.string.isRequired,
   pagePath: PropTypes.string.isRequired,
+
   pageId: PropTypes.string,
   pageId: PropTypes.string,
-  buttonStyle: PropTypes.object,
+  dropdownToggleClassName: PropTypes.string,
   isShareLinkMode: PropTypes.bool,
   isShareLinkMode: PropTypes.bool,
 };
 };
 
 

+ 34 - 3
src/client/js/components/Page/DisplaySwitcher.jsx

@@ -3,21 +3,51 @@ import { TabContent, TabPane } from 'reactstrap';
 import propTypes from 'prop-types';
 import propTypes from 'prop-types';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import NavigationContainer from '../../services/NavigationContainer';
 import NavigationContainer from '../../services/NavigationContainer';
+import PageContainer from '../../services/PageContainer';
 import Editor from '../PageEditor';
 import Editor from '../PageEditor';
 import Page from '../Page';
 import Page from '../Page';
+import UserInfo from '../User/UserInfo';
+import TableOfContents from '../TableOfContents';
+import UserContentsLinks from '../UserContentsLinks';
+import PageAccessories from '../PageAccessories';
 import PageEditorByHackmd from '../PageEditorByHackmd';
 import PageEditorByHackmd from '../PageEditorByHackmd';
 import EditorNavbarBottom from '../PageEditor/EditorNavbarBottom';
 import EditorNavbarBottom from '../PageEditor/EditorNavbarBottom';
 
 
 
 
 const DisplaySwitcher = (props) => {
 const DisplaySwitcher = (props) => {
-  const { navigationContainer } = props;
+  const {
+    navigationContainer, pageContainer,
+  } = props;
   const { editorMode } = navigationContainer.state;
   const { editorMode } = navigationContainer.state;
+  const { pageUser } = pageContainer.state;
 
 
   return (
   return (
     <>
     <>
       <TabContent activeTab={editorMode}>
       <TabContent activeTab={editorMode}>
         <TabPane tabId="view">
         <TabPane tabId="view">
-          <Page />
+          <div className="d-flex flex-column flex-lg-row-reverse">
+
+            <div className="grw-side-contents-container">
+              <div className="grw-side-contents-sticky-container">
+                <div className="border-bottom pb-1">
+                  <PageAccessories />
+                </div>
+
+                <div className="d-none d-lg-block">
+                  <div id="revision-toc" className="revision-toc">
+                    <TableOfContents />
+                  </div>
+                  {pageUser && <UserContentsLinks />}
+                </div>
+              </div>
+            </div>
+
+            <div className="flex-grow-1 flex-basis-0 mw-0">
+              {pageUser && <UserInfo pageUser={pageUser} />}
+              <Page />
+            </div>
+
+          </div>
         </TabPane>
         </TabPane>
         <TabPane tabId="edit">
         <TabPane tabId="edit">
           <div id="page-editor">
           <div id="page-editor">
@@ -37,7 +67,8 @@ const DisplaySwitcher = (props) => {
 
 
 DisplaySwitcher.propTypes = {
 DisplaySwitcher.propTypes = {
   navigationContainer: propTypes.instanceOf(NavigationContainer).isRequired,
   navigationContainer: propTypes.instanceOf(NavigationContainer).isRequired,
+  pageContainer: propTypes.instanceOf(PageContainer).isRequired,
 };
 };
 
 
 
 
-export default withUnstatedContainers(DisplaySwitcher, [NavigationContainer]);
+export default withUnstatedContainers(DisplaySwitcher, [NavigationContainer, PageContainer]);

+ 2 - 3
src/client/js/components/Page/RenderTagLabels.jsx

@@ -6,7 +6,7 @@ import { UncontrolledTooltip } from 'reactstrap';
 
 
 const RenderTagLabels = React.memo((props) => {
 const RenderTagLabels = React.memo((props) => {
   const {
   const {
-    t, tags, pageId, isGuestUser,
+    t, tags, isGuestUser,
   } = props;
   } = props;
 
 
   function openEditorHandler() {
   function openEditorHandler() {
@@ -25,7 +25,7 @@ const RenderTagLabels = React.memo((props) => {
 
 
   const tagElements = tags.map((tag) => {
   const tagElements = tags.map((tag) => {
     return (
     return (
-      <a key={`${pageId}_${tag}`} href={`/_search?q=tag:${tag}`} className="grw-tag-label badge badge-secondary mr-2">
+      <a key={tag} href={`/_search?q=tag:${tag}`} className="grw-tag-label badge badge-secondary mr-2">
         {tag}
         {tag}
       </a>
       </a>
     );
     );
@@ -60,7 +60,6 @@ RenderTagLabels.propTypes = {
   tags: PropTypes.array,
   tags: PropTypes.array,
   openEditorModal: PropTypes.func,
   openEditorModal: PropTypes.func,
   isGuestUser: PropTypes.bool.isRequired,
   isGuestUser: PropTypes.bool.isRequired,
-  pageId: PropTypes.string.isRequired,
 };
 };
 
 
 export default withTranslation()(RenderTagLabels);
 export default withTranslation()(RenderTagLabels);

+ 0 - 34
src/client/js/components/Page/RevisionPathControls.jsx

@@ -1,34 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { withTranslation } from 'react-i18next';
-
-import CopyDropdown from './CopyDropdown';
-
-const RevisionPathControls = (props) => {
-  // define styles
-  const buttonStyle = {
-    marginLeft: '0.5em',
-    padding: '0 2px',
-  };
-
-  const {
-    pagePath, pageId,
-  } = props;
-
-
-  return (
-    <>
-      <CopyDropdown pagePath={pagePath} pageId={pageId} buttonStyle={buttonStyle} />
-    </>
-  );
-};
-
-RevisionPathControls.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-
-  pagePath: PropTypes.string.isRequired,
-  pageId: PropTypes.string,
-};
-
-export default withTranslation()(RevisionPathControls);

+ 1 - 2
src/client/js/components/Page/TagLabels.jsx

@@ -74,7 +74,7 @@ class TagLabels extends React.Component {
 
 
   render() {
   render() {
     const tags = this.getTagData();
     const tags = this.getTagData();
-    const { appContainer, pageContainer } = this.props;
+    const { appContainer } = this.props;
 
 
     return (
     return (
       <>
       <>
@@ -85,7 +85,6 @@ class TagLabels extends React.Component {
             <RenderTagLabels
             <RenderTagLabels
               tags={tags}
               tags={tags}
               openEditorModal={this.openEditorModal}
               openEditorModal={this.openEditorModal}
-              pageId={pageContainer.state.pageId}
               isGuestUser={appContainer.isGuestUser}
               isGuestUser={appContainer.isGuestUser}
             />
             />
           </Suspense>
           </Suspense>

+ 4 - 1
src/client/js/components/PageAccessories.jsx

@@ -14,7 +14,10 @@ const PageAccessories = (props) => {
 
 
   return (
   return (
     <>
     <>
-      <PageAccessoriesModalControl isGuestUser={isGuestUser} isSharedUser={isSharedUser} />
+      <PageAccessoriesModalControl
+        isGuestUser={isGuestUser}
+        isSharedUser={isSharedUser}
+      />
       <PageAccessoriesModal
       <PageAccessoriesModal
         isGuestUser={isGuestUser}
         isGuestUser={isGuestUser}
         isSharedUser={isSharedUser}
         isSharedUser={isSharedUser}

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

@@ -51,7 +51,7 @@ const PageAccessoriesModalControl = (props) => {
   }, [isGuestUser, isSharedUser]);
   }, [isGuestUser, isSharedUser]);
 
 
   return (
   return (
-    <div className="grw-page-accessories-control d-flex align-items-center justify-content-between pb-1">
+    <div className="grw-page-accessories-control d-flex flex-nowrap align-items-center justify-content-end justify-content-lg-between">
       {accessoriesBtnList.map((accessory) => {
       {accessoriesBtnList.map((accessory) => {
         return (
         return (
           <Fragment key={accessory.name}>
           <Fragment key={accessory.name}>

+ 8 - 1
src/client/js/components/ShareLink/ShareLinkList.jsx

@@ -28,7 +28,14 @@ const ShareLinkList = (props) => {
             <td>
             <td>
               <div className="d-flex">
               <div className="d-flex">
                 <span className="mr-auto my-auto">{shareLink._id}</span>
                 <span className="mr-auto my-auto">{shareLink._id}</span>
-                <CopyDropdown isShareLinkMode pagePath={shareLink.relatedPage.path} pageId={shareLink._id} />
+                <CopyDropdown
+                  pagePath={shareLink.relatedPage.path}
+                  dropdownToggleId={`copydropdown-${shareLink._id}`}
+                  pageId={shareLink._id}
+                  isShareLinkMode
+                >
+                  Copy Link
+                </CopyDropdown>
               </div>
               </div>
             </td>
             </td>
             {props.isAdmin && <td><a href={shareLink.relatedPage.path}>{shareLink.relatedPage.path}</a></td>}
             {props.isAdmin && <td><a href={shareLink.relatedPage.path}>{shareLink.relatedPage.path}</a></td>}

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

@@ -52,6 +52,9 @@ export default class NavigationContainer extends Container {
     return 'NavigationContainer';
     return 'NavigationContainer';
   }
   }
 
 
+  getPageContainer() {
+    return this.appContainer.getContainer('PageContainer');
+  }
 
 
   initDeviceSize() {
   initDeviceSize() {
     const mdOrAvobeHandler = async(mql) => {
     const mdOrAvobeHandler = async(mql) => {
@@ -89,12 +92,18 @@ export default class NavigationContainer extends Container {
   }
   }
 
 
   setEditorMode(editorMode) {
   setEditorMode(editorMode) {
+    const { isNotCreatable } = this.getPageContainer().state;
 
 
     if (this.appContainer.currentUser == null) {
     if (this.appContainer.currentUser == null) {
       logger.warn('Please login or signup to edit the page or use hackmd.');
       logger.warn('Please login or signup to edit the page or use hackmd.');
       return;
       return;
     }
     }
 
 
+    if (isNotCreatable) {
+      logger.warn('This page could not edit.');
+      return;
+    }
+
     this.setState({ editorMode });
     this.setState({ editorMode });
     if (editorMode === 'view') {
     if (editorMode === 'view') {
       $('body').removeClass('on-edit');
       $('body').removeClass('on-edit');

+ 9 - 5
src/client/styles/scss/_layout.scss

@@ -31,21 +31,25 @@ body {
 .main {
 .main {
   margin-top: 1rem;
   margin-top: 1rem;
 
 
-  @include media-breakpoint-up(md) {
-    margin-top: 3rem;
+  @include media-breakpoint-up(lg) {
+    margin-top: 2rem;
   }
   }
 }
 }
 
 
 .grw-side-contents-container {
 .grw-side-contents-container {
-  margin-left: 30px;
+  margin-bottom: 1rem;
+
+  @include media-breakpoint-up(lg) {
+    width: 250px;
+    min-width: 250px;
+    margin-left: 30px;
+  }
 }
 }
 
 
 .grw-side-contents-sticky-container {
 .grw-side-contents-sticky-container {
   position: sticky;
   position: sticky;
   // growisubnavigation + grw-navbar-boder
   // growisubnavigation + grw-navbar-boder
   top: calc(100px + 4px);
   top: calc(100px + 4px);
-  width: 250px;
-  min-width: 250px;
   margin-top: 5px;
   margin-top: 5px;
 }
 }
 
 

+ 0 - 1
src/client/styles/scss/_page-accessories-control.scss

@@ -1,5 +1,4 @@
 .grw-page-accessories-control {
 .grw-page-accessories-control {
-  flex-wrap: wrap;
   line-height: 1.25;
   line-height: 1.25;
   border-bottom: 1px solid transparent;
   border-bottom: 1px solid transparent;
 
 

+ 2 - 2
src/client/styles/scss/_user.scss

@@ -44,8 +44,8 @@ $easeInOutCubic: cubic-bezier(0.65, 0, 0.35, 1);
   }
   }
 }
 }
 
 
-.user-page {
-  .grw-user-page-header {
+.user-page-footer {
+  .grw-user-page-list-m {
     svg {
     svg {
       width: 35px;
       width: 35px;
       height: 35px;
       height: 35px;

+ 0 - 11
src/client/styles/scss/theme/_apply-colors-dark.scss

@@ -382,14 +382,3 @@ ul.pagination {
 .grw-modal-head {
 .grw-modal-head {
   border-color: $border-color-global;
   border-color: $border-color-global;
 }
 }
-
-/*
- * GROWI user page
- */
-.grw-page-list-m {
-  .grw-page-list-title-m {
-    svg {
-      fill: $color-global;
-    }
-  }
-}

+ 0 - 11
src/client/styles/scss/theme/_apply-colors-light.scss

@@ -322,14 +322,3 @@ $border-color: $border-color-global;
 .grw-modal-head {
 .grw-modal-head {
   border-color: $border-color-global;
   border-color: $border-color-global;
 }
 }
-
-/*
- * GROWI user page
- */
-.grw-page-list-m {
-  .grw-page-list-title-m {
-    svg {
-      fill: $color-global;
-    }
-  }
-}

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

@@ -35,6 +35,7 @@ $nav-tabs-link-active-border-color: $bordercolor-nav-tabs-active;
 @import 'mixins/list-group';
 @import 'mixins/list-group';
 @import 'mixins/page-editor-mode-manager';
 @import 'mixins/page-editor-mode-manager';
 @import 'mixins/tables'; // comment out and use _reboot-bootstrap-tables instead -- 2020.05.28 Yuki Takei
 @import 'mixins/tables'; // comment out and use _reboot-bootstrap-tables instead -- 2020.05.28 Yuki Takei
+@import 'reboot-bootstrap-buttons';
 @import 'reboot-bootstrap-colors';
 @import 'reboot-bootstrap-colors';
 @import 'reboot-bootstrap-theme-colors';
 @import 'reboot-bootstrap-theme-colors';
 @import 'reboot-bootstrap-nav';
 @import 'reboot-bootstrap-nav';
@@ -70,15 +71,6 @@ pre:not(.hljs):not(.CodeMirror-line) {
   }
   }
 }
 }
 
 
-// Link buttons
-.btn-link {
-  color: $link-color;
-
-  @include hover {
-    color: $link-hover-color;
-  }
-}
-
 // Dropdown
 // Dropdown
 .dropdown-menu {
 .dropdown-menu {
   color: $color-global;
   color: $color-global;
@@ -102,6 +94,7 @@ pre:not(.hljs):not(.CodeMirror-line) {
 
 
   &:active,
   &:active,
   &.active,
   &.active,
+  &:active:hover,
   &.active:hover {
   &.active:hover {
     color: $color-dropdown-link-active;
     color: $color-dropdown-link-active;
     background-color: $bgcolor-dropdown-link-active;
     background-color: $bgcolor-dropdown-link-active;
@@ -312,9 +305,6 @@ ul.pagination {
 }
 }
 
 
 .grw-page-accessories-control {
 .grw-page-accessories-control {
-  .grw-btn-page-accessories {
-    fill: $color-link;
-  }
   .grw-seen-user-info {
   .grw-seen-user-info {
     .btn {
     .btn {
       color: $color-seen-user;
       color: $color-seen-user;

+ 21 - 0
src/client/styles/scss/theme/_reboot-bootstrap-buttons.scss

@@ -0,0 +1,21 @@
+.btn-link {
+  color: $link-color;
+  svg {
+    fill: $link-color;
+  }
+
+  @include hover() {
+    color: $link-hover-color;
+    svg {
+      fill: $link-hover-color;
+    }
+  }
+
+  &:disabled,
+  &.disabled {
+    color: $btn-link-disabled-color;
+    svg {
+      fill: $btn-link-disabled-color;
+    }
+  }
+}

+ 12 - 0
src/client/styles/scss/theme/_reboot-bootstrap-colors.scss

@@ -23,6 +23,10 @@ body {
   color: $body-color;
   color: $body-color;
   // text-align: left; // 3
   // text-align: left; // 3
   background-color: $body-bg; // 2
   background-color: $body-bg; // 2
+
+  svg {
+    fill: $body-color;
+  }
 }
 }
 
 
 // Links
 // Links
@@ -32,9 +36,17 @@ a {
   text-decoration: $link-decoration;
   text-decoration: $link-decoration;
   background-color: transparent; // Remove the gray background on active links in IE 10.
   background-color: transparent; // Remove the gray background on active links in IE 10.
 
 
+  svg {
+    fill: $link-color;
+  }
+
   @include hover() {
   @include hover() {
     color: $link-hover-color;
     color: $link-hover-color;
     text-decoration: $link-hover-decoration;
     text-decoration: $link-hover-decoration;
+
+    svg {
+      fill: $link-hover-color;
+    }
   }
   }
 }
 }
 
 

+ 14 - 24
src/client/styles/scss/theme/kibela.scss

@@ -6,36 +6,26 @@ $themelight: #f4f5f6;
 $subthemecolor: rgb(88, 130, 250);
 $subthemecolor: rgb(88, 130, 250);
 $lightthemecolor: rgba(181, 203, 247, 0.61);
 $lightthemecolor: rgba(181, 203, 247, 0.61);
 
 
-// change width only for pages with articles
-.growi:not(.on-edit):not(.admin-page):not(.user-settings-page) {
-  // layout
-  header,
-  #main {
-    max-width: 1024px;
-    margin: auto;
+.main {
+  .container,
+  .container-sm,
+  .container-md,
+  .container-lg,
+  .container-fluid {
+    padding-top: 30px;
+    padding-bottom: 30px;
+    background-color: white;
+    border-radius: 0.35em;
   }
   }
-  header {
-    margin-top: 30px;
-    margin-bottom: 42px;
-    background-color: $gray-100;
-  }
-}
-
-.grw-subnav {
-  padding: 20px 30px;
-  border-radius: 0.35em;
 }
 }
 
 
-.grw-page-content-container {
-  padding-top: 10px;
-  background-color: #fff;
+.user-page-footer {
+  margin-top: 3rem;
+  margin-bottom: 3rem;
+  background-color: white;
   border-radius: 0.35em;
   border-radius: 0.35em;
 }
 }
 
 
-.page-content-footer {
-  margin-top: 30px;
-}
-
 // Light Mode
 // Light Mode
 html[light],
 html[light],
 html[dark] {
 html[dark] {

+ 2 - 2
src/migrations/20200903080025-remove-timeline-type.js.js

@@ -13,7 +13,7 @@ module.exports = {
 
 
     const Config = getModelSafely('Config') || require('@server/models/config')();
     const Config = getModelSafely('Config') || require('@server/models/config')();
 
 
-    await Config.findOneAndDelete({ key: 'customize:timeline' }); // remove timeline
+    await Config.findOneAndDelete({ key: 'customize:isEnabledTimeline' }); // remove timeline
 
 
     logger.info('Migration has successfully applied');
     logger.info('Migration has successfully applied');
   },
   },
@@ -27,7 +27,7 @@ module.exports = {
 
 
     const insertConfig = new Config({
     const insertConfig = new Config({
       ns: 'crowi',
       ns: 'crowi',
-      key: 'customize:timeline',
+      key: 'customize:isEnabledTimeline',
       value: true,
       value: true,
     });
     });
 
 

+ 2 - 6
src/server/routes/apiv3/customize-setting.js

@@ -88,14 +88,10 @@ module.exports = (crowi) => {
 
 
   const validator = {
   const validator = {
     themeAssetPath: [
     themeAssetPath: [
-      query('themeName').isString().isIn([
-        'default', 'nature', 'mono-blue', 'wood', 'island', 'christmas', 'antarctic', 'future', 'halloween', 'spring',
-      ]),
+      query('themeName').isString(),
     ],
     ],
     theme: [
     theme: [
-      body('themeType').isString().isIn([
-        'default', 'nature', 'mono-blue', 'wood', 'island', 'christmas', 'antarctic', 'future', 'halloween', 'spring', 'kibela',
-      ]),
+      body('themeType').isString(),
     ],
     ],
     function: [
     function: [
       body('isEnabledTimeline').isBoolean(),
       body('isEnabledTimeline').isBoolean(),

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

@@ -12,9 +12,9 @@
   {% include 'widget/comments.html' %}
   {% include 'widget/comments.html' %}
 
 
   {% if page %}
   {% if page %}
-    <div class="container-lg">
+    <div class="container-lg user-page-footer py-5">
 
 
-      <div class="grw-page-list-m mt-5 pb-5 d-edit-none">
+      <div class="grw-user-page-list-m d-edit-none">
         <h2 class="grw-user-page-header border-bottom pb-2 mb-3" id="bookmarks-list">
         <h2 class="grw-user-page-header border-bottom pb-2 mb-3" id="bookmarks-list">
           <i id="user-bookmark-icon"></i>
           <i id="user-bookmark-icon"></i>
           Bookmarks
           Bookmarks
@@ -25,7 +25,7 @@
         </div>
         </div>
       </div>
       </div>
 
 
-      <div class="grw-page-list-m mt-5 pb-5 d-edit-none">
+      <div class="grw-user-page-list-m mt-5 d-edit-none">
         <h2 class="grw-user-page-header border-bottom pb-2 mb-3" id="recently-created-list">
         <h2 class="grw-user-page-header border-bottom pb-2 mb-3" id="recently-created-list">
           <i id="recent-created-icon"></i>
           <i id="recent-created-icon"></i>
           Recently Created
           Recently Created

+ 1 - 18
src/server/views/widget/page_content.html

@@ -40,32 +40,15 @@
   >
   >
 {% endif %}
 {% endif %}
 
 
-<div class="flex-grow-1">
+<div class="flex-grow-1 flex-basis-0 mw-0">
   {% include 'page_alerts.html' %}
   {% include 'page_alerts.html' %}
 
 
-  {% if pageUser %}
-    <div class="user-info" id="user-info"></div>
-  {% endif %}
-
   <div id="display-switcher">
   <div id="display-switcher">
     <script type="text/template" id="raw-text-original">{{ revision.body.toString() | encodeHTML }}</script>
     <script type="text/template" id="raw-text-original">{{ revision.body.toString() | encodeHTML }}</script>
   </div>
   </div>
   <div id="page-editor-navbar-bottom-container" class="d-none d-edit-block"></div>
   <div id="page-editor-navbar-bottom-container" class="d-none d-edit-block"></div>
 </div>
 </div>
 
 
-{% if revision %}
-<div class="d-none d-lg-block d-edit-none grw-side-contents-container">
-  <div class="grw-side-contents-sticky-container">
-    <div id="page-accessories" class="page-accessories"></div>
-    <div id="revision-toc" class="revision-toc sps sps--abv" data-sps-offset="123"></div>
-    {% if pageUser %}
-      <div id="grw-user-contents-links"></div>
-    {% endif %}
-  </div>
-</div>
-{% endif %}
-
-
 <div id="grw-page-status-alert-container"></div>
 <div id="grw-page-status-alert-container"></div>
 
 
 </div>
 </div>