Browse Source

Merge pull request #2166 from weseek/imprv/2235-revisionpath-controls

Imprv/2235 revisionpath controls
Yuki Takei 5 years ago
parent
commit
49639f5daf

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

@@ -8,6 +8,7 @@ import loggerFactory from '@alias/logger';
 import SearchPage from './components/SearchPage';
 import TagsList from './components/TagsList';
 import PageEditor from './components/PageEditor';
+import PagePathNavForEditor from './components/PageEditor/PagePathNavForEditor';
 // eslint-disable-next-line import/no-duplicates
 import OptionsSelector from './components/PageEditor/OptionsSelector';
 // eslint-disable-next-line import/no-duplicates
@@ -21,8 +22,6 @@ import PageTimeline from './components/PageTimeline';
 import CommentEditorLazyRenderer from './components/PageComment/CommentEditorLazyRenderer';
 import PageAttachment from './components/PageAttachment';
 import PageStatusAlert from './components/PageStatusAlert';
-import RevisionPath from './components/Page/RevisionPath';
-import TagLabels from './components/Page/TagLabels';
 import PagePathAutoComplete from './components/PagePathAutoComplete';
 import RecentCreated from './components/RecentCreated/RecentCreated';
 import MyDraftList from './components/MyDraftList/MyDraftList';
@@ -71,6 +70,7 @@ Object.assign(componentMappings, {
   'create-page-name-input': <PagePathAutoComplete crowi={appContainer} initializedPath={pageContainer.state.path} addTrailingSlash />,
 
   'page-editor': <PageEditor />,
+  'page-editor-path-nav': <PagePathNavForEditor />,
   'page-editor-options-selector': <OptionsSelector crowi={appContainer} />,
   'page-status-alert': <PageStatusAlert />,
   'save-page-controls': <SavePageControls />,
@@ -101,8 +101,6 @@ if (pageContainer.state.path != null) {
   Object.assign(componentMappings, {
     // eslint-disable-next-line quote-props
     'page': <Page />,
-    'revision-path': <RevisionPath pageId={pageContainer.state.pageId} pagePath={pageContainer.state.path} />,
-    'tag-label': <TagLabels />,
     'grw-subnav': <GrowiSubNavigation />,
     'grw-subnav-for-user-page': <GrowiSubNavigationForUserPage />,
   });

+ 39 - 21
src/client/js/components/Navbar/GrowiSubNavigation.jsx

@@ -12,7 +12,7 @@ import PagePathHierarchicalLink from '@commons/components/PagePathHierarchicalLi
 import { createSubscribedElement } from '../UnstatedUtils';
 import AppContainer from '../../services/AppContainer';
 
-import RevisionPath from '../Page/RevisionPath';
+import RevisionPathControls from '../Page/RevisionPathControls';
 import PageContainer from '../../services/PageContainer';
 import TagLabels from '../Page/TagLabels';
 import LikeButton from '../LikeButton';
@@ -21,6 +21,42 @@ import BookmarkButton from '../BookmarkButton';
 import PageCreator from './PageCreator';
 import RevisionAuthor from './RevisionAuthor';
 
+// eslint-disable-next-line react/prop-types
+const PagePathNav = ({ pageId, pagePath, isPageForbidden }) => {
+
+  const dPagePath = new DevidedPagePath(pagePath, false, true);
+
+  let formerLink;
+  let latterLink;
+
+  // when the path is root or first level
+  if (dPagePath.isRoot || dPagePath.isFormerRoot) {
+    const linkedPagePath = new LinkedPagePath(pagePath);
+    latterLink = <PagePathHierarchicalLink linkedPagePath={linkedPagePath} />;
+  }
+  // when the path is second level or deeper
+  else {
+    const linkedPagePathFormer = new LinkedPagePath(dPagePath.former);
+    const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter);
+    formerLink = <PagePathHierarchicalLink linkedPagePath={linkedPagePathFormer} />;
+    latterLink = <PagePathHierarchicalLink linkedPagePath={linkedPagePathLatter} basePath={dPagePath.former} />;
+  }
+
+  return (
+    <div className="grw-page-path-nav">
+      {formerLink}
+      <span className="d-flex align-items-center flex-wrap">
+        <h1 className="m-0">{latterLink}</h1>
+        <RevisionPathControls
+          pageId={pageId}
+          pagePath={pagePath}
+          isPageForbidden={isPageForbidden}
+        />
+      </span>
+    </div>
+  );
+};
+
 const GrowiSubNavigation = (props) => {
   const isPageForbidden = document.querySelector('#grw-subnav').getAttribute('data-is-forbidden-page') === 'true';
   const { appContainer, pageContainer } = props;
@@ -31,26 +67,11 @@ const GrowiSubNavigation = (props) => {
   const isPageNotFound = pageId == null;
   const isPageInTrash = isTrashPage(path);
 
-  const dPagePath = new DevidedPagePath(path, false, true);
-  const linkedPagePathFormer = new LinkedPagePath(dPagePath.former);
-  const renderFormerLink = () => (
-    <>
-      { !dPagePath.isRoot && <PagePathHierarchicalLink linkedPagePath={linkedPagePathFormer} /> }
-    </>
-  );
-
   // Display only the RevisionPath
   if (isPageNotFound || isPageForbidden) {
     return (
       <div className="px-3 py-3 grw-subnavbar">
-        { renderFormerLink() }
-        <h1 className="m-0">
-          <RevisionPath
-            pageId={pageId}
-            pagePath={pageContainer.state.path}
-            isPageForbidden={isPageForbidden}
-          />
-        </h1>
+        <PagePathNav pageId={pageId} pagePath={path} isPageForbidden={isPageForbidden} />
       </div>
     );
   }
@@ -72,10 +93,7 @@ const GrowiSubNavigation = (props) => {
 
       {/* Page Path */}
       <div>
-        { renderFormerLink() }
-        <h1 className="m-0">
-          <RevisionPath pageId={pageId} pagePath={pageContainer.state.path} />
-        </h1>
+        <PagePathNav pageId={pageId} pagePath={path} isPageForbidden={isPageForbidden} />
         { !isPageNotFound && !isPageForbidden && (
           <TagLabels />
         ) }

+ 27 - 5
src/client/js/components/Navbar/GrowiSubNavigationForUserPage.jsx

@@ -3,17 +3,41 @@ import PropTypes from 'prop-types';
 
 import { withTranslation } from 'react-i18next';
 
+import LinkedPagePath from '@commons/models/linked-page-path';
+import PagePathHierarchicalLink from '@commons/components/PagePathHierarchicalLink';
+
 import { createSubscribedElement } from '../UnstatedUtils';
 import AppContainer from '../../services/AppContainer';
-import RevisionPath from '../Page/RevisionPath';
 import PageContainer from '../../services/PageContainer';
+
+import RevisionPathControls from '../Page/RevisionPathControls';
 import BookmarkButton from '../BookmarkButton';
 import UserPicture from '../User/UserPicture';
 
+// eslint-disable-next-line react/prop-types
+const PagePathNav = ({ pageId, pagePath }) => {
+  const linkedPagePath = new LinkedPagePath(pagePath);
+  const latterLink = <PagePathHierarchicalLink linkedPagePath={linkedPagePath} />;
+
+  return (
+    <div className="grw-page-path-nav">
+      <span className="d-flex align-items-center flex-wrap">
+        <h4 className="grw-user-page-path">{latterLink}</h4>
+        <RevisionPathControls
+          pageId={pageId}
+          pagePath={pagePath}
+        />
+      </span>
+    </div>
+  );
+};
+
 const GrowiSubNavigationForUserPage = (props) => {
   const pageUser = JSON.parse(document.querySelector('#grw-subnav-for-user-page').getAttribute('data-page-user'));
   const { appContainer, pageContainer } = props;
-  const { pageId, isHeaderSticky, isSubnavCompact } = pageContainer.state;
+  const {
+    pageId, path, isHeaderSticky, isSubnavCompact,
+  } = pageContainer.state;
 
   const additionalClassNames = ['grw-subnavbar', 'grw-subnavbar-user-page'];
   const layoutType = appContainer.getConfig().layoutType;
@@ -32,9 +56,7 @@ const GrowiSubNavigationForUserPage = (props) => {
 
   return (
     <div className={`px-3 ${additionalClassNames.join(' ')}`}>
-      <h4 className="grw-user-page-path">
-        <RevisionPath pageId={pageId} pagePath={pageContainer.state.path} />
-      </h4>
+      <PagePathNav pageId={pageId} pagePath={path} />
 
       <div className="d-flex align-items-center justify-content-between">
 

+ 0 - 55
src/client/js/components/Page/RevisionPath.jsx

@@ -1,55 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { withTranslation } from 'react-i18next';
-
-import { isTrashPage } from '@commons/util/path-utils';
-import DevidedPagePath from '@commons/models/devided-page-path';
-import LinkedPagePath from '@commons/models/linked-page-path';
-import PagePathHierarchicalLink from '@commons/components/PagePathHierarchicalLink';
-
-import CopyDropdown from './CopyDropdown';
-
-const RevisionPath = (props) => {
-  // define styles
-  const buttonStyle = {
-    marginLeft: '0.5em',
-    padding: '0 2px',
-  };
-
-  const {
-    pagePath, pageId, isPageForbidden,
-  } = props;
-
-  const dPagePath = new DevidedPagePath(pagePath, false, true);
-  const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter);
-  const isInTrash = isTrashPage(pagePath);
-
-  return (
-    <>
-      <span className="d-flex align-items-center flex-wrap">
-        <PagePathHierarchicalLink linkedPagePath={linkedPagePathLatter} basePath={dPagePath.isRoot ? undefined : dPagePath.former} />
-        <CopyDropdown pagePath={props.pagePath} pageId={pageId} buttonStyle={buttonStyle} />
-        { !isInTrash && !isPageForbidden && (
-          <a href="#edit" className="d-block d-edit-none text-muted btn btn-secondary bg-transparent btn-edit border-0" style={buttonStyle}>
-            <i className="icon-note" />
-          </a>
-        ) }
-      </span>
-    </>
-  );
-};
-
-RevisionPath.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-
-  pagePath: PropTypes.string.isRequired,
-  pageId: PropTypes.string,
-  isPageForbidden: PropTypes.bool,
-};
-
-RevisionPath.defaultProps = {
-  isPageForbidden: false,
-};
-
-export default withTranslation()(RevisionPath);

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

@@ -0,0 +1,47 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { withTranslation } from 'react-i18next';
+
+import { isTrashPage } from '@commons/util/path-utils';
+
+import CopyDropdown from './CopyDropdown';
+
+const RevisionPathControls = (props) => {
+  // define styles
+  const buttonStyle = {
+    marginLeft: '0.5em',
+    padding: '0 2px',
+  };
+
+  const {
+    pagePath, pageId, isPageForbidden,
+  } = 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>
+      ) }
+    </>
+  );
+};
+
+RevisionPathControls.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+
+  pagePath: PropTypes.string.isRequired,
+  pageId: PropTypes.string,
+  isPageForbidden: PropTypes.bool,
+};
+
+RevisionPathControls.defaultProps = {
+  isPageForbidden: false,
+};
+
+export default withTranslation()(RevisionPathControls);

+ 50 - 0
src/client/js/components/PageEditor/PagePathNavForEditor.jsx

@@ -0,0 +1,50 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { withTranslation } from 'react-i18next';
+
+import LinkedPagePath from '@commons/models/linked-page-path';
+import PagePathHierarchicalLink from '@commons/components/PagePathHierarchicalLink';
+
+import { createSubscribedElement } from '../UnstatedUtils';
+import AppContainer from '../../services/AppContainer';
+import PageContainer from '../../services/PageContainer';
+
+import RevisionPathControls from '../Page/RevisionPathControls';
+import TagLabels from '../Page/TagLabels';
+
+const PagePathNavForEditor = (props) => {
+  const { pageId, path } = props.pageContainer.state;
+
+  const linkedPagePath = new LinkedPagePath(path);
+  const pagePathHierarchicalLink = <PagePathHierarchicalLink linkedPagePath={linkedPagePath} />;
+
+  return (
+    <div className="grw-page-path-nav-for-edit mt-1">
+      <span className="d-flex align-items-center flex-wrap">
+        <h3 className="mb-0 grw-page-path-link">{pagePathHierarchicalLink}</h3>
+        <RevisionPathControls
+          pageId={pageId}
+          pagePath={path}
+        />
+      </span>
+      <TagLabels />
+    </div>
+  );
+};
+
+/**
+ * Wrapper component for using unstated
+ */
+const PagePathNavForEditorWrapper = (props) => {
+  return createSubscribedElement(PagePathNavForEditor, props, [AppContainer, PageContainer]);
+};
+
+
+PagePathNavForEditor.propTypes = {
+  t: PropTypes.func.isRequired, //  i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
+};
+
+export default withTranslation()(PagePathNavForEditorWrapper);

+ 28 - 19
src/client/styles/scss/_on-edit.scss

@@ -24,6 +24,11 @@ body.on-edit {
     }
   }
 
+  // show
+  .d-edit-block {
+    display: block !important;
+  }
+
   // hide unnecessary elements
   header,
   footer,
@@ -63,11 +68,6 @@ body.on-edit {
     }
   }
 
-  // show revision path
-  .grw-revision-path-for-edit {
-    display: block !important;
-  }
-
   /*****************
    * Expand Editor
    *****************/
@@ -93,26 +93,23 @@ body.on-edit {
       padding: 0; //    for crowi layout
       pointer-events: initial; // enable pointer-events
     }
+  }
 
-    h1#revision-path {
-      @include variable-font-size(20px);
+  .grw-page-path-nav-for-edit {
+    .grw-page-path-link {
+      font-size: 20px;
       line-height: 1em;
-
-      // nowrap even if the path is too long
-      .d-flex {
-        flex-wrap: nowrap;
-      }
-
-      .path-segment {
-        white-space: nowrap;
-      }
     }
-
-    .tag-labels.new-page {
-      display: block;
+    .separator {
+      margin-right: 0.1em;
+      margin-left: 0.1em;
     }
   }
 
+  .tag-labels {
+    line-height: 1em;
+  }
+
   .page-editor-footer {
     width: 100%;
     min-height: 40px;
@@ -136,6 +133,18 @@ body.on-edit {
     }
   }
 
+  /*********************
+   * Navigation styles
+   */
+  .nav:hover {
+    .btn-copy,
+    .btn-edit,
+    .btn-edit-tags {
+      // change button opacity
+      opacity: unset;
+    }
+  }
+
   &.builtin-editor {
     /*****************
     * Editor styles

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

@@ -24,10 +24,6 @@
       }
     }
   }
-
-  .tag-labels.new-page {
-    display: none;
-  }
 }
 
 .main .content-main .revision-history {

+ 5 - 3
src/client/styles/scss/_subnav.scss

@@ -77,9 +77,11 @@ header.grw-header {
     line-height: 1.1em;
   }
 
-  .separator {
-    margin-right: 0.2em;
-    margin-left: 0.2em;
+  .grw-page-path-nav {
+    .separator {
+      margin-right: 0.2em;
+      margin-left: 0.2em;
+    }
   }
 
   ul.authors {

+ 2 - 0
src/lib/models/devided-page-path.js

@@ -10,6 +10,7 @@ export default class DevidedPagePath {
   constructor(path, skipNormalize = false, evalDatePath = false) {
 
     this.isRoot = false;
+    this.isFormerRoot = false;
     this.former = null;
     this.latter = null;
 
@@ -35,6 +36,7 @@ export default class DevidedPagePath {
 
     const matchDefault = pagePath.match(PATTERN_DEFAULT);
     if (matchDefault != null) {
+      this.isFormerRoot = matchDefault[1] === '/';
       this.former = matchDefault[2];
       this.latter = matchDefault[3];
     }

+ 2 - 0
src/server/views/widget/not_found_tabs.html

@@ -14,5 +14,7 @@
       <i class="icon-note"></i> {{ t('Create') }}
     </a>
   </li>
+
+  <div id="page-editor-path-nav" class="d-none d-edit-block ml-2"></div>
   {% endif %}
 </ul>

+ 1 - 4
src/server/views/widget/page_tabs.html

@@ -37,10 +37,7 @@
     </li>
     {% endif %}
 
-    <div class="grw-revision-path-for-edit d-none ml-2">
-      <h4 id="revision-path" class="mb-0"></h4>
-      <div id="tag-label"></div>
-    </div>
+    <div id="page-editor-path-nav" class="d-none d-edit-block ml-2"></div>
   {% endif %}
 
   {#

+ 27 - 24
src/server/views/widget/page_tabs_kibela.html

@@ -11,30 +11,33 @@
   </li>
 
   {% if !isTrashPage() %}
-  <li class="nav-item nav-tab-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"></i> {{ t('Edit') }}
-    </a>
-  </li>
-  {% if isHackmdSetup() %}
-  <li class="nav-item 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-file-text-o"></i> {{ t('HackMD') }}
-    </a>
-  </li>
-  {% endif %}
+    <li class="nav-item nav-tab-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"></i> {{ t('Edit') }}
+      </a>
+    </li>
+    {% if isHackmdSetup() %}
+    <li class="nav-item 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-file-text-o"></i> {{ t('HackMD') }}
+      </a>
+    </li>
+    {% endif %}
+
+    <div id="page-editor-path-nav" class="d-none d-edit-block ml-2"></div>
+
   {% endif %}
 
   {#