Procházet zdrojové kódy

Merge branch 'feat/article-area-renovation' into imprv/article-area-gw4067

# Conflicts:
#	src/client/js/components/Navbar/GrowiSubNavigation.jsx
#	src/client/js/components/Navbar/ThreeStrandedButton.jsx
白石誠 před 5 roky
rodič
revize
eb246c47c8
34 změnil soubory, kde provedl 180 přidání a 237 odebrání
  1. 2 2
      src/client/js/app.jsx
  2. 1 1
      src/client/js/components/CustomNavigation.jsx
  3. 0 2
      src/client/js/components/Hotkeys/Subscribers/EditPage.jsx
  4. 1 1
      src/client/js/components/MyDraftList/MyDraftList.jsx
  5. 3 1
      src/client/js/components/Navbar/GrowiSubNavigation.jsx
  6. 48 26
      src/client/js/components/Navbar/ThreeStrandedButton.jsx
  7. 8 0
      src/client/js/components/NotFoundPage.jsx
  8. 1 1
      src/client/js/components/Page/PageManagement.jsx
  9. 1 9
      src/client/js/components/Page/RevisionPathControls.jsx
  10. 2 2
      src/client/js/components/PageList.jsx
  11. 1 1
      src/client/js/components/PageTimeline.jsx
  12. 1 1
      src/client/js/components/RecentCreated/RecentCreated.jsx
  13. 1 0
      src/client/js/components/TopOfTableContents.jsx
  14. 5 5
      src/client/js/components/TrashPageList.jsx
  15. 2 2
      src/client/js/components/User/SeenUserList.jsx
  16. 12 98
      src/client/js/legacy/crowi.js
  17. 4 0
      src/client/js/services/NavigationContainer.js
  18. 57 0
      src/client/styles/scss/_mixins.scss
  19. 0 2
      src/client/styles/scss/_toc.scss
  20. 0 11
      src/client/styles/scss/style-app.scss
  21. 1 1
      src/client/styles/scss/theme/_apply-colors.scss
  22. 1 1
      src/client/styles/scss/theme/antarctic.scss
  23. 2 2
      src/client/styles/scss/theme/default.scss
  24. 1 1
      src/client/styles/scss/theme/future.scss
  25. 1 1
      src/client/styles/scss/theme/halloween.scss
  26. 1 1
      src/client/styles/scss/theme/island.scss
  27. 2 2
      src/client/styles/scss/theme/mono-blue.scss
  28. 1 1
      src/client/styles/scss/theme/nature.scss
  29. 1 1
      src/client/styles/scss/theme/spring.scss
  30. 1 1
      src/client/styles/scss/theme/wood.scss
  31. 13 1
      src/server/routes/apiv3/pages.js
  32. 3 1
      src/server/views/layout-growi/page_list.html
  33. 2 2
      src/server/views/widget/forbidden_content.html
  34. 0 56
      src/server/views/widget/page_tabs.html

+ 2 - 2
src/client/js/app.jsx

@@ -15,7 +15,7 @@ import PageComments from './components/PageComments';
 import PageTimeline from './components/PageTimeline';
 import CommentEditorLazyRenderer from './components/PageComment/CommentEditorLazyRenderer';
 import PageManagement from './components/Page/PageManagement';
-import TrashPage from './components/TrashPage';
+import TrashPageList from './components/TrashPageList';
 import TrashPageAlert from './components/Page/TrashPageAlert';
 import NotFoundPage from './components/NotFoundPage';
 import NotFoundAlert from './components/Page/NotFoundAlert';
@@ -76,7 +76,7 @@ Object.assign(componentMappings, {
 
   'trash-page-alert': <TrashPageAlert />,
 
-  'trash-page': <TrashPage />,
+  'trash-page-list': <TrashPageList />,
 
   'not-found-page': <NotFoundPage />,
 

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

@@ -63,7 +63,7 @@ const CustomNavigation = (props) => {
         })}
       </Nav>
       <hr className="my-0 grw-nav-slide-hr border-none" style={{ width: `${sliderWidth}%`, marginLeft: `${sliderMarginLeft}%` }} />
-      <TabContent activeTab={activeTab} className="p-5">
+      <TabContent activeTab={activeTab} className="p-4">
         {Object.entries(props.navTabMapping).map(([key, value]) => {
           return (
             <TabPane key={key} tabId={key}>

+ 0 - 2
src/client/js/components/Hotkeys/Subscribers/EditPage.jsx

@@ -9,8 +9,6 @@ const EditPage = (props) => {
     if (document.getElementsByClassName('modal in').length > 0) {
       return;
     }
-    // show editor
-    $('a[data-toggle="tab"][href="#edit"]').tab('show');
 
     // remove this
     props.onDeleteRender(this);

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

@@ -134,7 +134,7 @@ class MyDraftList extends React.Component {
     const totalCount = this.state.totalDrafts;
 
     return (
-      <div className="page-list-container-create">
+      <div>
 
         { totalCount === 0
           && <span>No drafts yet.</span>

+ 3 - 1
src/client/js/components/Navbar/GrowiSubNavigation.jsx

@@ -145,6 +145,7 @@ const GrowiSubNavigation = (props) => {
     isForbidden: isPageForbidden, pageUser, isCreatable,
   } = pageContainer.state;
 
+  const { currentUser } = appContainer;
   const isPageNotFound = pageId == null;
   const isUserPage = pageUser != null;
   const isPageInTrash = isTrashPage(path);
@@ -195,7 +196,8 @@ const GrowiSubNavigation = (props) => {
             { !isPageNotFound && !isPageForbidden && <PageManagement /> }
           </div>
           <div className="mt-2">
-            { !isCreatable && <ThreeStrandedButton onThreeStrandedButtonClicked={onThreeStrandedButtonClicked} editorMode={editorMode} />}
+            { !isCreatable && !isPageInTrash
+            && <ThreeStrandedButton onThreeStrandedButtonClicked={onThreeStrandedButtonClicked} isBtnDisabled={currentUser == null} editorMode={editorMode} />}
           </div>
         </div>
 

+ 48 - 26
src/client/js/components/Navbar/ThreeStrandedButton.jsx

@@ -1,11 +1,16 @@
 import React, { useState } from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import { UncontrolledTooltip } from 'reactstrap';
 
 const ThreeStrandedButton = (props) => {
-  const { t, editorMode } = props;
+  const { t, editorMode, isBtnDisabled } = props;
   const [btnActive, setBtnActive] = useState(editorMode);
+
   function threeStrandedButtonClickedHandler(viewType) {
+    if (isBtnDisabled) {
+      return;
+    }
     if (props.onThreeStrandedButtonClicked != null) {
       props.onThreeStrandedButtonClicked(viewType);
     }
@@ -13,32 +18,44 @@ const ThreeStrandedButton = (props) => {
   }
 
   return (
-    <div className="btn-group grw-three-stranded-button" role="group " aria-label="three-stranded-button">
-      <button
-        type="button"
-        className={`btn btn-outline-primary view-button ${btnActive === 'view' && 'active'}`}
-        onClick={() => { threeStrandedButtonClickedHandler('view') }}
-      >
-        <i className="icon-control-play icon-fw" />
-        { t('view') }
-      </button>
-      <button
-        type="button"
-        className={`btn btn-outline-primary edit-button ${btnActive === 'edit' && 'active'}`}
-        onClick={(e) => { threeStrandedButtonClickedHandler('edit') }}
+    <>
+      <div
+        className="btn-group grw-three-stranded-button"
+        role="group"
+        aria-label="three-stranded-button"
+        id="grw-three-stranded-button"
       >
-        <i className="icon-note icon-fw" />
-        { t('Edit') }
-      </button>
-      <button
-        type="button"
-        className={`btn btn-outline-primary hackmd-button ${btnActive === 'hackmd' && 'active'}`}
-        onClick={() => { threeStrandedButtonClickedHandler('hackmd') }}
-      >
-        <i className="fa fa-fw fa-file-text-o" />
-        { t('hackmd.hack_md') }
-      </button>
-    </div>
+        <button
+          type="button"
+          className={`btn btn-outline-primary view-button ${btnActive === 'view' && 'active'} ${isBtnDisabled && 'disabled'}`}
+          onClick={() => { threeStrandedButtonClickedHandler('view') }}
+        >
+          <i className="icon-control-play icon-fw grw-three-stranded-button-icon" />
+          { t('view') }
+        </button>
+        <button
+          type="button"
+          className={`btn btn-outline-primary edit-button ${btnActive === 'edit' && 'active'} ${isBtnDisabled && 'disabled'}`}
+          onClick={() => { threeStrandedButtonClickedHandler('edit') }}
+        >
+          <i className="icon-note icon-fw grw-three-stranded-button-icon" />
+          { t('Edit') }
+        </button>
+        <button
+          type="button"
+          className={`btn btn-outline-primary hackmd-button ${btnActive === 'hackmd' && 'active'} ${isBtnDisabled && 'disabled'}`}
+          onClick={() => { threeStrandedButtonClickedHandler('hackmd') }}
+        >
+          <i className="fa fa-fw fa-file-text-o grw-three-stranded-button-icon" />
+          { t('hackmd.hack_md') }
+        </button>
+      </div>
+      {isBtnDisabled && (
+        <UncontrolledTooltip placement="top" target="grw-three-stranded-button" fade={false}>
+          {t('Not available for guest')}
+        </UncontrolledTooltip>
+      )}
+    </>
   );
 
 };
@@ -47,6 +64,11 @@ ThreeStrandedButton.propTypes = {
   t: PropTypes.func.isRequired, //  i18next
   editorMode: PropTypes.string.isRequired,
   onThreeStrandedButtonClicked: PropTypes.func,
+  isBtnDisabled: PropTypes.bool,
+};
+
+ThreeStrandedButton.defaultProps = {
+  isBtnDisabled: false,
 };
 
 export default withTranslation()(ThreeStrandedButton);

+ 8 - 0
src/client/js/components/NotFoundPage.jsx

@@ -2,8 +2,10 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 import PageListIcon from './Icons/PageListIcon';
+import TimeLineIcon from './Icons/TimeLineIcon';
 import CustomNavigation from './CustomNavigation';
 import PageList from './PageList';
+import PageTimeline from './PageTimeline';
 
 
 const NotFoundPage = (props) => {
@@ -16,6 +18,12 @@ const NotFoundPage = (props) => {
       tabContent: <PageList />,
       index: 0,
     },
+    timeLine: {
+      icon: <TimeLineIcon />,
+      i18n: t('Timeline View'),
+      tabContent: <PageTimeline />,
+      index: 1,
+    },
   };
 
   return (

+ 1 - 1
src/client/js/components/Page/PageManagement.jsx

@@ -194,7 +194,7 @@ const PageManagement = (props) => {
         >
           <i className="icon-options"></i>
         </button>
-        <UncontrolledTooltip placement="top" target="icon-options-guest-tltips">
+        <UncontrolledTooltip placement="top" target="icon-options-guest-tltips" fade={false}>
           {t('Not available for guest')}
         </UncontrolledTooltip>
       </>

+ 1 - 9
src/client/js/components/Page/RevisionPathControls.jsx

@@ -3,8 +3,6 @@ import PropTypes from 'prop-types';
 
 import { withTranslation } from 'react-i18next';
 
-import { isTrashPage } from '@commons/util/path-utils';
-
 import CopyDropdown from './CopyDropdown';
 
 const RevisionPathControls = (props) => {
@@ -15,19 +13,13 @@ const RevisionPathControls = (props) => {
   };
 
   const {
-    pagePath, pageId, isPageForbidden,
+    pagePath, pageId,
   } = props;
 
-  const isPageInTrash = isTrashPage(pagePath);
 
   return (
     <>
       <CopyDropdown pagePath={pagePath} pageId={pageId} buttonStyle={buttonStyle} />
-      { !isPageInTrash && !isPageForbidden && (
-        <a href="#edit" className="d-edit-none text-muted btn btn-secondary bg-transparent btn-edit border-0" style={buttonStyle}>
-          <i className="icon-note" />
-        </a>
-      ) }
     </>
   );
 };

+ 2 - 2
src/client/js/components/PageList.jsx

@@ -67,8 +67,8 @@ const PageList = (props) => {
   }
 
   return (
-    <div className="page-list-container-create">
-      <ul className="page-list-ul page-list-ul-flat ml-n4">
+    <div className="page-list">
+      <ul className="page-list-ul page-list-ul-flat">
         {pageList}
       </ul>
       <PaginationWrapper

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

@@ -62,7 +62,7 @@ class PageTimeline extends React.Component {
 
   render() {
     const { pages } = this.state;
-    if (pages == null) {
+    if (pages == null || pages.length === 0) {
       return <React.Fragment></React.Fragment>;
     }
 

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

@@ -69,7 +69,7 @@ class RecentCreated extends React.Component {
     const pageList = this.generatePageList(this.state.pages);
 
     return (
-      <div className="page-list-container-create">
+      <div>
         <ul className="page-list-ul page-list-ul-flat mb-3">
           {pageList}
         </ul>

+ 1 - 0
src/client/js/components/TopOfTableContents.jsx

@@ -76,6 +76,7 @@ const TopOfTableContents = (props) => {
           id="seen-user-list"
           data-user-ids-str="{{ page.seenUsers|slice(-15)|default([])|reverse|join(',') }}"
           data-sum-of-seen-users="{{ page.seenUsers.length|default(0) }}"
+          className="grw-seen-user-list ml-1 pl-1"
         >
         </div>
       </div>

+ 5 - 5
src/client/js/components/TrashPage.jsx → src/client/js/components/TrashPageList.jsx

@@ -3,17 +3,17 @@ import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 import PageListIcon from './Icons/PageListIcon';
 import CustomNavigation from './CustomNavigation';
+import PageList from './PageList';
 
 
-const TrashPage = (props) => {
+const TrashPageList = (props) => {
   const { t } = props;
 
   const navTabMapping = {
     pagelist: {
       icon: <PageListIcon />,
       i18n: t('page_list'),
-      // [TODO: show trash page list by gw4064]
-      tabContent: t('Trash page list'),
+      tabContent: <PageList />,
       index: 0,
     },
   };
@@ -25,8 +25,8 @@ const TrashPage = (props) => {
   );
 };
 
-TrashPage.propTypes = {
+TrashPageList.propTypes = {
   t: PropTypes.func.isRequired, //  i18next
 };
 
-export default withTranslation()(TrashPage);
+export default withTranslation()(TrashPageList);

+ 2 - 2
src/client/js/components/User/SeenUserList.jsx

@@ -20,7 +20,7 @@ const SeenUserList = (props) => {
   const toggle = () => setPopoverOpen(!popoverOpen);
   const { pageContainer } = props;
   return (
-    <div className="grw-seen-user-list pl-2 ml-2">
+    <>
       <Button id="po-seen-user" color="link" className="px-2">
         <span className="mr-1 footstamp-icon"><FootstampIcon /></span>
         <span className="seen-user-count">{pageContainer.state.countOfSeenUsers}</span>
@@ -32,7 +32,7 @@ const SeenUserList = (props) => {
           </div>
         </PopoverBody>
       </Popover>
-    </div>
+    </>
   );
 };
 

+ 12 - 98
src/client/js/legacy/crowi.js

@@ -15,6 +15,9 @@ window.Crowi = Crowi;
  * @param {number} line
  */
 Crowi.setCaretLineData = function(line) {
+  const { appContainer } = window;
+  const navigationContainer = appContainer.getContainer('NavigationContainer');
+  navigationContainer.setEditorMode('edit');
   const pageEditorDom = document.querySelector('#page-editor');
   pageEditorDom.setAttribute('data-caret-line', line);
 };
@@ -152,15 +155,11 @@ Crowi.highlightSelectedSection = function(hash) {
 };
 
 $(() => {
-  const appContainer = window.appContainer;
-  const config = appContainer.getConfig();
-
   const pageId = $('#content-main').data('page-id');
   // const revisionId = $('#content-main').data('page-revision-id');
   // const revisionCreatedAt = $('#content-main').data('page-revision-created');
   // const currentUser = $('#content-main').data('current-user');
   const isSeen = $('#content-main').data('page-is-seen');
-  const isSavedStatesOfTabChanges = config.isSavedStatesOfTabChanges;
 
   $('[data-toggle="popover"]').popover();
   $('[data-toggle="tooltip"]').tooltip();
@@ -198,78 +197,6 @@ $(() => {
       });
     }
   } // end if pageId
-
-  // TODO clean code after GW-3605
-  // tab changing handling
-  $('a[href="#revision-body"]').on('show.bs.tab', () => {
-    const navigationContainer = appContainer.getContainer('NavigationContainer');
-    navigationContainer.setEditorMode(null);
-  });
-  $('a[href="#edit"]').on('show.bs.tab', () => {
-    const navigationContainer = appContainer.getContainer('NavigationContainer');
-    navigationContainer.setEditorMode('builtin');
-    $('body').addClass('on-edit');
-    $('body').addClass('builtin-editor');
-  });
-  $('a[href="#edit"]').on('hide.bs.tab', () => {
-    $('body').removeClass('on-edit');
-    $('body').removeClass('builtin-editor');
-  });
-  $('a[href="#hackmd"]').on('show.bs.tab', () => {
-    const navigationContainer = appContainer.getContainer('NavigationContainer');
-    navigationContainer.setEditorMode('hackmd');
-    $('body').addClass('on-edit');
-    $('body').addClass('hackmd');
-  });
-
-  $('a[href="#hackmd"]').on('hide.bs.tab', () => {
-    $('body').removeClass('on-edit');
-    $('body').removeClass('hackmd');
-  });
-
-  // hash handling
-  if (isSavedStatesOfTabChanges) {
-    $('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', () => {
-      window.location.hash = '#revision-history';
-      window.history.replaceState('', 'History', '#revision-history');
-    });
-    $('a[data-toggle="tab"][href="#edit"]').on('show.bs.tab', () => {
-      window.location.hash = '#edit';
-      window.history.replaceState('', 'Edit', '#edit');
-    });
-    $('a[data-toggle="tab"][href="#hackmd"]').on('show.bs.tab', () => {
-      window.location.hash = '#hackmd';
-      window.history.replaceState('', 'HackMD', '#hackmd');
-    });
-    $('a[data-toggle="tab"][href="#revision-body"]').on('show.bs.tab', () => {
-      // couln't solve https://github.com/weseek/crowi-plus/issues/119 completely -- 2017.07.03 Yuki Takei
-      window.location.hash = '#';
-      window.history.replaceState('', '', window.location.href);
-    });
-  }
-  else {
-    $('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', () => {
-      window.history.replaceState('', 'History', '#revision-history');
-    });
-    $('a[data-toggle="tab"][href="#edit"]').on('show.bs.tab', () => {
-      window.history.replaceState('', 'Edit', '#edit');
-    });
-    $('a[data-toggle="tab"][href="#hackmd"]').on('show.bs.tab', () => {
-      window.history.replaceState('', 'HackMD', '#hackmd');
-    });
-    $('a[data-toggle="tab"][href="#revision-body"]').on('show.bs.tab', () => {
-      window.history.replaceState('', '', window.location.href.replace(window.location.hash, ''));
-    });
-    // replace all href="#edit" link behaviors
-    $(document).on('click', 'a[href="#edit"]', () => {
-      window.location.replace('#edit');
-    });
-  }
-
-  // focus to editor when 'shown.bs.tab' event fired
-  $('a[href="#edit"]').on('shown.bs.tab', (e) => {
-    Crowi.setCaretLineAndFocusToEditor();
-  });
 });
 
 window.addEventListener('load', (e) => {
@@ -284,25 +211,14 @@ window.addEventListener('load', (e) => {
   if (window.location.hash) {
     const navigationContainer = appContainer.getContainer('NavigationContainer');
 
-    if ((window.location.hash === '#edit' || window.location.hash === '#edit-form') && $('.tab-pane#edit').length > 0) {
-      navigationContainer.setEditorMode('builtin');
-
-      $('a[data-toggle="tab"][href="#edit"]').tab('show');
-      $('body').addClass('on-edit');
-      $('body').addClass('builtin-editor');
+    if (window.location.hash === '#edit') {
+      navigationContainer.setEditorMode('edit');
 
       // focus
       Crowi.setCaretLineAndFocusToEditor();
     }
-    else if (window.location.hash === '#hackmd' && $('.tab-pane#hackmd').length > 0) {
+    else if (window.location.hash === '#hackmd') {
       navigationContainer.setEditorMode('hackmd');
-
-      $('a[data-toggle="tab"][href="#hackmd"]').tab('show');
-      $('body').addClass('on-edit');
-      $('body').addClass('hackmd');
-    }
-    else if (window.location.hash === '#revision-history' && $('.tab-pane#revision-history').length > 0) {
-      $('a[data-toggle="tab"][href="#revision-history"]').tab('show');
     }
   }
 });
@@ -354,22 +270,20 @@ window.addEventListener('hashchange', (e) => {
   Crowi.unhighlightSelectedSection(Crowi.findHashFromUrl(e.oldURL));
   Crowi.highlightSelectedSection(Crowi.findHashFromUrl(e.newURL));
   Crowi.modifyScrollTop();
+  const { appContainer } = window;
+  const navigationContainer = appContainer.getContainer('NavigationContainer');
+
 
   // hash on page
   if (window.location.hash) {
     if (window.location.hash === '#edit') {
-      $('a[data-toggle="tab"][href="#edit"]').tab('show');
+      navigationContainer.setEditorMode('edit');
+      Crowi.setCaretLineAndFocusToEditor();
     }
     else if (window.location.hash === '#hackmd') {
-      $('a[data-toggle="tab"][href="#hackmd"]').tab('show');
-    }
-    else if (window.location.hash === '#revision-history') {
-      $('a[data-toggle="tab"][href="#revision-history"]').tab('show');
+      navigationContainer.setEditorMode('hackmd');
     }
   }
-  else {
-    $('a[data-toggle="tab"][href="#revision-body"]').tab('show');
-  }
 });
 
 // adjust min-height of page for print temporarily

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

@@ -91,17 +91,21 @@ export default class NavigationContainer extends Container {
       $('body').removeClass('on-edit');
       $('body').removeClass('builtin-editor');
       $('body').removeClass('hackmd');
+      window.history.replaceState(null, '', window.location.pathname);
     }
 
     if (editorMode === 'edit') {
       $('body').addClass('on-edit');
       $('body').addClass('builtin-editor');
+      window.location.hash = '#edit';
     }
 
     if (editorMode === 'hackmd') {
       $('body').addClass('on-edit');
       $('body').addClass('hackmd');
       $('body').removeClass('builtin-editor');
+      window.location.hash = '#hackmd';
+
     }
 
     this.updateDrawerMode({ ...this.state, editorMode }); // generate newest state object

+ 57 - 0
src/client/styles/scss/_mixins.scss

@@ -221,14 +221,71 @@
   transition-duration: 300ms;
 }
 
+@mixin border-vertical($beforeOrAfter, $borderColor, $borderLength, $zIndex: initial, $isBtnGroup: false) {
+  position: relative;
+  @if $isBtnGroup {
+    &:not(:first-child) {
+      margin-left: 0;
+      border-left: none;
+    }
+    &:not(:last-child) {
+      border-right: none;
+    }
+  }
+  &:not(:first-child) {
+    &::#{$beforeOrAfter} {
+      position: absolute;
+      top: calc((100% - #{$borderLength}) / 2);
+      left: 0;
+      z-index: $zIndex;
+      width: 100%;
+      height: $borderLength;
+      margin-left: -0.5px;
+      content: '';
+      border-left: 1px solid $borderColor;
+      transition: border-color 0.15s ease-in-out;
+    }
+  }
+}
+
 @mixin three-stranded-button($textColor, $borderColor, $bgColorHoverAndActive, $bgColor: white) {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  width: 70px;
+  padding-right: 0;
+  padding-left: 0;
   color: $textColor;
+  white-space: nowrap;
   background-color: $bgColor;
   border-color: $borderColor;
+
+  @include border-vertical('before', $borderColor, 70%, 1, true);
+
+  &.view-button,
+  &.edit-button {
+    .grw-three-stranded-button-icon {
+      margin-right: -0.25rem;
+    }
+  }
+  &.hackmd-button {
+    font-size: 12px;
+    letter-spacing: -0.6px;
+
+    .grw-three-stranded-button-icon {
+      margin-right: -0.1rem;
+    }
+  }
   &:hover,
   &:active {
     color: $textColor;
     background-color: $bgColorHoverAndActive;
     border-color: $borderColor;
+    &::after {
+      border-color: $bgColorHoverAndActive;
+    }
+  }
+  &:focus {
+    box-shadow: none;
   }
 }

+ 0 - 2
src/client/styles/scss/_toc.scss

@@ -13,8 +13,6 @@
     font-weight: bolder;
   }
   .grw-seen-user-list {
-    border-left: 1px solid;
-
     .btn {
       white-space: nowrap;
     }

+ 0 - 11
src/client/styles/scss/style-app.scss

@@ -69,22 +69,11 @@
   cursor: not-allowed;
 }
 
-.view-button {
-  min-width: 74px;
-}
-
-.edit-button {
-  min-width: 71px;
-}
 // TODO: reactify and replace with `grw-not-available-for-guest`
 .edit-button.edit-button-disabled {
   cursor: not-allowed;
 }
 
-.hackmd-button {
-  min-width: 97px;
-}
-
 .grw-not-available-for-guest {
   cursor: not-allowed !important;
 }

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

@@ -274,7 +274,7 @@ pre:not(.hljs):not(.CodeMirror-line) {
     fill: $color-link;
   }
   .grw-seen-user-list {
-    border-color: $bordercolor-toc;
+    @include border-vertical('before', $bordercolor-toc, 70%);
 
     .btn {
       color: $color-seen-user;

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

@@ -113,7 +113,7 @@ html[dark] {
   @import 'apply-colors-light';
 
   //Button
-  .grw-three-stranded-button {
+  .btn-group.grw-three-stranded-button {
     .btn.btn-outline-primary {
       @include three-stranded-button(darken($primary, 10%), lighten($primary, 55%), lighten($primary, 60%));
     }

+ 2 - 2
src/client/styles/scss/theme/default.scss

@@ -105,7 +105,7 @@ html[light] {
   @import 'apply-colors-light';
 
   // Button
-  .grw-three-stranded-button {
+  .btn-group.grw-three-stranded-button {
     .btn.btn-outline-primary {
       @include three-stranded-button($primary, lighten($primary, 65%), lighten($primary, 70%));
     }
@@ -208,7 +208,7 @@ html[dark] {
   @import 'apply-colors-dark';
 
   //Button
-  .grw-three-stranded-button {
+  .btn-group.grw-three-stranded-button {
     .btn.btn-outline-primary {
       @include three-stranded-button(lighten($primary, 30%), lighten($primary, 20%), $primary, darken($primary, 20%));
     }

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

@@ -90,7 +90,7 @@ html[dark] {
   @import 'apply-colors-dark';
 
   //Button
-  .grw-three-stranded-button {
+  .btn-group.grw-three-stranded-button {
     .btn.btn-outline-primary {
       @include three-stranded-button(lighten($primary, 10%), $primary, darken($primary, 10%), darken($primary, 20%));
     }

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

@@ -108,7 +108,7 @@ html[dark] {
   @import 'apply-colors-dark';
 
   //Button
-  .grw-three-stranded-button {
+  .btn-group.grw-three-stranded-button {
     .btn.btn-outline-primary {
       @include three-stranded-button(lighten($primary, 35%), $primary, lighten($primary, 5%), darken($primary, 20%));
     }

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

@@ -110,7 +110,7 @@ html[dark] {
   }
 
   // Button
-  .grw-three-stranded-button {
+  .btn-group.grw-three-stranded-button {
     .btn.btn-outline-primary {
       @include three-stranded-button(darken($primary, 50%), lighten($primary, 5%), darken($primary, 5%));
     }

+ 2 - 2
src/client/styles/scss/theme/mono-blue.scss

@@ -89,7 +89,7 @@ html[light] {
     }
   }
   // Button
-  .grw-three-stranded-button {
+  .btn-group.grw-three-stranded-button {
     .btn.btn-outline-primary {
       @include three-stranded-button($primary, lighten($primary, 65%), lighten($primary, 70%));
     }
@@ -200,7 +200,7 @@ html[dark] {
   }
 
   // Button
-  .grw-three-stranded-button {
+  .btn-group.grw-three-stranded-button {
     .btn.btn-outline-primary {
       @include three-stranded-button(lighten($primary, 30%), $primary, darken($primary, 10%), darken($primary, 20%));
     }

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

@@ -112,7 +112,7 @@ html[dark] {
   }
 
   // Button
-  .grw-three-stranded-button {
+  .btn-group.grw-three-stranded-button {
     .btn.btn-outline-primary {
       @include three-stranded-button($bgcolor-navbar, lighten($bgcolor-navbar, 65%), lighten($bgcolor-navbar, 70%));
     }

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

@@ -94,7 +94,7 @@ html[dark] {
   @import 'apply-colors-light';
 
   //Button
-  .grw-three-stranded-button {
+  .btn-group.grw-three-stranded-button {
     .btn.btn-outline-primary {
       @include three-stranded-button(darken($primary, 50%), lighten($primary, 5%), lighten($primary, 10%));
     }

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

@@ -162,7 +162,7 @@ html[dark] {
   }
 
   // Button
-  .grw-three-stranded-button {
+  .btn-group.grw-three-stranded-button {
     .btn.btn-outline-primary {
       @include three-stranded-button(darken($primary, 30%), lighten($primary, 15%), lighten($primary, 25%));
     }

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

@@ -88,7 +88,19 @@ module.exports = (crowi) => {
     const { path } = req.query;
     const limit = +req.query.limit || 30;
     const offset = +req.query.offset || 0;
-    const queryOptions = { offset, limit };
+    const { isTrashPage } = require('@commons/util/path-utils');
+
+    let includeTrashed = false;
+
+    if (isTrashPage(path)) {
+      includeTrashed = true;
+    }
+
+    const queryOptions = {
+      offset,
+      limit,
+      includeTrashed,
+    };
 
     try {
       const result = await Page.findListWithDescendants(path, req.user, queryOptions);

+ 3 - 1
src/server/views/layout-growi/page_list.html

@@ -26,7 +26,9 @@
 
 
 {% block content_main_after %}
-  <div id="trash-page"></div>
+  {% if isTrashPage() %}
+    <div id="trash-page-list"></div>
+  {% endif %}
   {% if page %}
     {% include '../widget/page_attachments.html' %}
   {% endif %}

+ 2 - 2
src/server/views/widget/forbidden_content.html

@@ -22,7 +22,7 @@
 
   <ul class="nav nav-tabs d-print-none" role="tablist">
     <li class="nav-item grw-nav-main-left-tab">
-      <a class="nav-link active" role="tab" href="#revision-body" data-toggle="tab">
+      <a class="nav-link active">
         <i class="icon-notebook"></i> List
       </a>
     </li>
@@ -30,7 +30,7 @@
 
   <div class="tab-content">
     {# list view #}
-    <div class="pt-2 active tab-pane page-list-container" id="revision-body">
+    <div class="pt-2 active tab-pane page-list-container">
       {% if pages.length == 0 %}
         <div class="mt-2">
           There are no pages under <strong>{{ path | preventXss }}</strong>.

+ 0 - 56
src/server/views/widget/page_tabs.html

@@ -1,56 +0,0 @@
-{% if page %}
-<ul class="nav nav-tabs d-print-none" role="tablist">
-
-  {#
-    Left Tabs
-  #}
-  <li class="nav-item grw-main-nav-item-left">
-    <a class="nav-link active" href="#revision-body" role="tab" data-toggle="tab">
-      <i class="icon-control-play icon-fw"></i><span class="d-none d-md-inline">View</span>
-    </a>
-  </li>
-
-  {% if !isTrashPage() %}
-    <li class="nav-item grw-main-nav-item-left grw-nav-item-edit">
-      <a
-        {% if user %} href="#edit" data-toggle="tab" class="nav-link edit-button" {% endif %}
-        {% if not user %}
-          class="nav-link edit-button edit-button-disabled"
-          data-toggle="tooltip" data-placement="top" data-container="body" title="{{ t('Not available for guest') }}"
-        {% endif %}
-      >
-        <i class="icon-note icon-fw"></i><span class="d-none d-md-inline">{{ t('Edit') }}</span>
-      </a>
-    </li>
-
-    {% if isHackmdSetup() %}
-    <li class="nav-item grw-main-nav-item-left grw-nav-tab-hackmd">
-      <a
-        {% if user %} href="#hackmd" data-toggle="tab" class="nav-link edit-button" {% endif %}
-        {% if not user %}
-          class="nav-link edit-button edit-button-disabled"
-          data-toggle="tooltip" data-placement="top" data-container="body" title="{{ t('Not available for guest') }}"
-        {% endif %}
-      >
-        <i class="fa fa-fw fa-file-text-o"></i><span class="d-none d-md-inline">{{ t('HackMD') }}</span>
-      </a>
-    </li>
-    {% endif %}
-
-    <div id="page-editor-path-nav" class="d-none d-edit-sm-block ml-2"></div>
-  {% endif %}
-
-  {#
-    Right Tabs
-  #}
-
-  {# to place right side #}
-  <div class="mr-auto"></div>
-
-  <!-- icon-options -->
-  {% if !isTrashPage() %}
-    <li id="page-management" class="nav-item dropdown d-edit-none"></li>
-  {% endif %}
-</ul>
-
-{% endif %}