Просмотр исходного кода

Merge branch 'support/apply-bootstrap4' into support/reactify-login-page-stock

yusuketk 5 лет назад
Родитель
Сommit
7e4b8fd0f9
42 измененных файлов с 879 добавлено и 418 удалено
  1. 1 0
      babel.config.js
  2. 3 3
      config/webpack.common.js
  3. 1 0
      package.json
  4. 2 4
      src/client/js/app.jsx
  5. 5 5
      src/client/js/components/Admin/Customize/CustomizeFunctionSetting.jsx
  6. 51 30
      src/client/js/components/Navbar/GrowiSubNavigation.jsx
  7. 27 5
      src/client/js/components/Navbar/GrowiSubNavigationForUserPage.jsx
  8. 0 55
      src/client/js/components/Page/RevisionPath.jsx
  9. 47 0
      src/client/js/components/Page/RevisionPathControls.jsx
  10. 50 0
      src/client/js/components/PageEditor/PagePathNavForEditor.jsx
  11. 0 23
      src/client/js/legacy/crowi.js
  12. 28 19
      src/client/styles/scss/_on-edit.scss
  13. 0 4
      src/client/styles/scss/_page.scss
  14. 5 3
      src/client/styles/scss/_subnav.scss
  15. 1 0
      src/client/styles/scss/theme/_apply-colors.scss
  16. 189 6
      src/client/styles/scss/theme/mono-blue.scss
  17. 174 8
      src/client/styles/scss/theme/nature.scss
  18. 8 3
      src/client/styles/scss/theme/spring.scss
  19. 167 6
      src/client/styles/scss/theme/wood.scss
  20. 10 6
      src/lib/components/PagePathHierarchicalLink.jsx
  21. 2 0
      src/lib/models/devided-page-path.js
  22. 6 0
      src/lib/models/linked-page-path.js
  23. 38 0
      src/migrations/20200512005851-remove-behavior-type.js
  24. 0 3
      src/server/models/config.js
  25. 0 1
      src/server/routes/apiv3/customize-setting.js
  26. 17 95
      src/server/routes/page.js
  27. 0 2
      src/server/views/layout-growi/page.html
  28. 0 1
      src/server/views/layout-growi/page_list.html
  29. 0 2
      src/server/views/layout-growi/user_page.html
  30. 0 6
      src/server/views/layout-growi/widget/header.html
  31. 0 2
      src/server/views/layout-kibela/page.html
  32. 0 1
      src/server/views/layout-kibela/page_list.html
  33. 0 2
      src/server/views/layout-kibela/user_page.html
  34. 0 5
      src/server/views/layout-kibela/widget/header.html
  35. 2 2
      src/server/views/modal/create_page.html
  36. 0 81
      src/server/views/modal/what_is_portal.html
  37. 0 4
      src/server/views/widget/create_portal.html
  38. 2 0
      src/server/views/widget/not_found_tabs.html
  39. 0 3
      src/server/views/widget/page_content.html
  40. 1 4
      src/server/views/widget/page_tabs.html
  41. 27 24
      src/server/views/widget/page_tabs_kibela.html
  42. 15 0
      yarn.lock

+ 1 - 0
babel.config.js

@@ -26,6 +26,7 @@ module.exports = function(api) {
         },
       },
     ],
+    '@babel/plugin-proposal-optional-chaining',
     [
       '@babel/plugin-proposal-class-properties', { loose: true },
     ],

+ 3 - 3
config/webpack.common.js

@@ -34,13 +34,13 @@ module.exports = (options) => {
       'styles/style-presentation':    './src/client/styles/scss/style-presentation.scss',
       // themes
       'styles/theme-default':         './src/client/styles/scss/theme/default.scss',
-      // 'styles/theme-nature':          './src/client/styles/scss/theme/nature.scss',
-      // 'styles/theme-mono-blue':       './src/client/styles/scss/theme/mono-blue.scss',
+      'styles/theme-nature':          './src/client/styles/scss/theme/nature.scss',
+      'styles/theme-mono-blue':       './src/client/styles/scss/theme/mono-blue.scss',
       // 'styles/theme-future':          './src/client/styles/scss/theme/future.scss',
       // 'styles/theme-blue-night':      './src/client/styles/scss/theme/blue-night.scss',
       'styles/theme-kibela':          './src/client/styles/scss/theme/kibela.scss',
       'styles/theme-halloween':       './src/client/styles/scss/theme/halloween.scss',
-      // 'styles/theme-wood':          './src/client/styles/scss/theme/wood.scss',
+      'styles/theme-wood':          './src/client/styles/scss/theme/wood.scss',
       // 'styles/theme-christmas':          './src/client/styles/scss/theme/christmas.scss',
       // 'styles/theme-island':      './src/client/styles/scss/theme/island.scss',
       'styles/theme-antarctic':      './src/client/styles/scss/theme/antarctic.scss',

+ 1 - 0
package.json

@@ -159,6 +159,7 @@
     "@atlaskit/navigation-next": "^8.0.2",
     "@babel/core": "^7.4.5",
     "@babel/plugin-proposal-class-properties": "^7.8.3",
+    "@babel/plugin-proposal-optional-chaining": "^7.9.0",
     "@babel/polyfill": "^7.4.4",
     "@babel/preset-env": "^7.4.5",
     "@babel/preset-react": "^7.0.0",

+ 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 behaviorType={appContainer.config.behaviorType} pageId={pageContainer.state.pageId} pagePath={pageContainer.state.path} />,
-    'tag-label': <TagLabels />,
     'grw-subnav': <GrowiSubNavigation />,
     'grw-subnav-for-user-page': <GrowiSubNavigationForUserPage />,
   });

+ 5 - 5
src/client/js/components/Admin/Customize/CustomizeFunctionSetting.jsx

@@ -15,7 +15,7 @@ import AdminCustomizeContainer from '../../../services/AdminCustomizeContainer';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 import CustomizeFunctionOption from './CustomizeFunctionOption';
 
-class CustomizeBehaviorSetting extends React.Component {
+class CustomizeFunctionSetting extends React.Component {
 
   constructor(props) {
     super(props);
@@ -173,14 +173,14 @@ class CustomizeBehaviorSetting extends React.Component {
 
 }
 
-const CustomizeBehaviorSettingWrapper = (props) => {
-  return createSubscribedElement(CustomizeBehaviorSetting, props, [AppContainer, AdminCustomizeContainer]);
+const CustomizeFunctionSettingWrapper = (props) => {
+  return createSubscribedElement(CustomizeFunctionSetting, props, [AppContainer, AdminCustomizeContainer]);
 };
 
-CustomizeBehaviorSetting.propTypes = {
+CustomizeFunctionSetting.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   adminCustomizeContainer: PropTypes.instanceOf(AdminCustomizeContainer).isRequired,
 };
 
-export default withTranslation()(CustomizeBehaviorSettingWrapper);
+export default withTranslation()(CustomizeFunctionSettingWrapper);

+ 51 - 30
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,27 +67,11 @@ const GrowiSubNavigation = (props) => {
   const isPageNotFound = pageId == null;
   const isPageInTrash = isTrashPage(path);
 
-  const dPagePath = new DevidedPagePath(pageContainer.state.path, false, true);
-  const linkedPagePathFormer = new LinkedPagePath(dPagePath.former);
-  const renderFormerLink = () => (
-    <>
-      { !dPagePath.isRoot && <PagePathHierarchicalLink linkedPagePath={linkedPagePathFormer} /> }
-    </>
-  );
-
   // Display only the RevisionPath
-  if (isPageNotFound || isPageForbidden || isPageInTrash) {
+  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}
-            isPageInTrash={isPageInTrash}
-          />
-        </h1>
+        <PagePathNav pageId={pageId} pagePath={path} isPageForbidden={isPageForbidden} />
       </div>
     );
   }
@@ -73,23 +93,24 @@ 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 />
         ) }
       </div>
 
       <div className="d-flex align-items-center">
-        {/* Header Button */}
-        <div className="mr-2">
-          <LikeButton pageId={pageId} isLiked={pageContainer.state.isLiked} />
-        </div>
-        <div>
-          <BookmarkButton pageId={pageId} crowi={appContainer} />
-        </div>
+        { !isPageInTrash && (
+          /* Header Button */
+          <div className="mr-2">
+            <LikeButton pageId={pageId} isLiked={pageContainer.state.isLiked} />
+          </div>
+        ) }
+        { !isPageInTrash && (
+          <div>
+            <BookmarkButton pageId={pageId} crowi={appContainer} />
+          </div>
+        ) }
 
         {/* Page Authors */}
         <ul className="authors text-nowrap d-none d-lg-block d-edit-none">

+ 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 behaviorType={appContainer.config.behaviorType} 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 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 {
-    pageId, isPageInTrash, isPageForbidden,
-  } = props;
-
-  const dPagePath = new DevidedPagePath(props.pagePath, false, true);
-  const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter);
-
-  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} />
-        { !isPageInTrash && !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,
-  isPageInTrash: PropTypes.bool,
-};
-
-RevisionPath.defaultProps = {
-  isPageForbidden: false,
-  isPageInTrash: 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);

+ 0 - 23
src/client/js/legacy/crowi.js

@@ -2,8 +2,6 @@
 
 import { pathUtils } from 'growi-commons';
 
-const entities = require('entities');
-const escapeStringRegexp = require('escape-string-regexp');
 require('jquery.cookie');
 
 require('./thirdparty-js/waves');
@@ -459,27 +457,6 @@ $(() => {
     window.location.hash = '#';
   });
 
-  /*
-   * wrap short path with <strong></strong>
-   */
-  $('#view-list .page-list-ul-flat .page-list-link').each(function() {
-    const $link = $(this);
-    /* eslint-disable-next-line no-unused-vars */
-    const text = $link.text();
-    let path = decodeURIComponent($link.data('path'));
-    const shortPath = decodeURIComponent($link.data('short-path')); // convert to string
-
-    if (path == null || shortPath == null) {
-      // continue
-      return;
-    }
-
-    path = entities.encodeHTML(path);
-    const pattern = `${escapeStringRegexp(entities.encodeHTML(shortPath))}(/)?$`;
-
-    $link.html(path.replace(new RegExp(pattern), `<strong>${shortPath}$1</strong>`));
-  });
-
   if (pageId) {
     // for Crowi Template LangProcessor
     $('.template-create-button', $('#revision-body')).on('click', function() {

+ 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 {

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

@@ -7,6 +7,7 @@ $body-bg: $bgcolor-global;
 $body-color: $color-global;
 $link-color: $color-link;
 $link-hover-color: $color-link-hover;
+$input-focus-color: $color-global;
 
 @import '~bootstrap/scss/functions';
 @import '~bootstrap/scss/variables';

+ 189 - 6
src/client/styles/scss/theme/mono-blue.scss

@@ -1,8 +1,191 @@
-// import colors
-@import '../../agile-admin/inverse/colors/mono-blue';
+@import '../variables';
+@import '../override-bootstrap-variables';
 
-// apply agile-admin theme
-@import '../../agile-admin/inverse/style';
 
-// override
-@import 'override-agileadmin';
+
+html[light] {
+  // Theme colors
+  $themecolor: #00587a;
+  $themelight: #f7fbfd;
+  $accentcolor: #16617d;
+  $subthemecolor: #186718;
+
+  $primary: $themecolor;
+
+  // Background colors
+  $bgcolor-global: $themelight;
+  $bgcolor-navbar: $themecolor;
+  $bgcolor-inline-code: lighten($subthemecolor, 70%);
+  $bgcolor-card: darken($themelight, 5%);
+
+  // Font colors
+  $color-global: $themecolor;
+  $color-reversal: #eee;
+  $color-link: lighten($primary, 5%);
+  $color-link-hover: lighten($color-link, 12%);
+  $color-link-wiki: lighten($primary, 20%);
+  $color-link-wiki-hover: lighten($color-link-wiki, 20%);
+  $color-link-nabvar: $color-reversal;
+  $color-inline-code: $subthemecolor;
+  $color-search: #c0d6df;
+
+
+  // List Group colors
+  $color-list: $color-global;
+  $bgcolor-list: transparent;
+  $color-list-active: $color-reversal;
+  $bgcolor-list-active: $primary;
+  $color-list-hover: $color-reversal;
+
+  // Logo colors
+  $bgcolor-logo: $themecolor;
+  $fillcolor-logo-mark: lighten(desaturate($bgcolor-navbar, 30%), 20%);
+
+  // Icon colors
+  $color-editor-icons: $color-global;
+
+  // Border colors
+  $border-color-theme: #ccc;
+
+  // Dropdown colors
+  $bgcolor-dropdown-link-active: $primary;
+  $color-dropdown-link-active: $color-reversal;
+
+  // alert
+  $color-alert: $color-reversal;
+
+  // badge
+  $color-badge: $color-reversal;
+
+  // Sidebar
+  $bgcolor-sidebar: $bgcolor-navbar;
+  $color-sidebar-context: $color-reversal;
+  $bgcolor-sidebar-context: lighten($bgcolor-sidebar, 10%);
+
+
+  @import 'apply-colors';
+  @import 'apply-colors-light';
+
+  // Navs {
+  .nav-tabs {
+    border-bottom: $border-color-theme 1px solid;
+    .nav-link {
+      &:hover {
+        border-color: lighten($border-color-theme, 10%);
+        border-bottom: none;
+      }
+      &.active {
+        background-color: transparent;
+      }
+    }
+  }
+
+  // Search Top
+  .search-top {
+    .input-group-prepend .dropdown-toggle {
+      color: $themecolor;
+      background-color: $color-search;
+      &:hover {
+        background-color: darken($color-search, 10%);
+      }
+    }
+  }
+}
+
+html[dark] {
+  // Theme colors
+  $themecolor: #0090c8;
+  $themedark: #061f2f;
+  $accentcolor: #16617d;
+  $subthemecolor: #c1f1f0;
+
+  $primary: $themecolor;
+  $dark: #031018;
+
+  // Background colors
+  $bgcolor-global: $themedark;
+  $bgcolor-navbar: #27343b;
+  $bgcolor-inline-code: #0a121b;
+  $bgcolor-card: darken($themedark, 5%);
+
+  // Font colors
+  $color-global: #d3d4d4;
+  $color-reversal: #eee;
+  $color-link: #97d1f0;
+  $color-link-hover: darken($color-link, 12%);
+  $color-link-wiki: lighten($primary, 20%);
+  $color-link-wiki-hover: lighten($color-link-wiki, 20%);
+  $color-link-nabvar: $color-reversal;
+  $color-inline-code: $subthemecolor;
+  $color-search: #000102;
+
+
+  // List Group colors
+  $color-list: $color-global;
+  $bgcolor-list: transparent;
+  $color-list-active: $color-reversal;
+  $bgcolor-list-active: $primary;
+  $color-list-hover: $color-reversal;
+
+  // Logo colors
+  $bgcolor-logo: #13191c;
+  $fillcolor-logo-mark: lighten(desaturate($bgcolor-navbar, 10%), 15%);
+  // $fillcolor-logo-mark: #4e5a60;
+
+  // Icon colors
+  $color-editor-icons: $color-global;
+
+  // Border colors
+  $border-color-theme: #146aa0;
+
+  // Dropdown colors
+  $bgcolor-dropdown-link-active: $primary;
+  $color-dropdown-link-active: $color-reversal;
+
+  // alert
+  $color-alert: $color-reversal;
+
+  // badge
+  $color-badge: $color-reversal;
+
+  // Sidebar
+  $bgcolor-sidebar: $bgcolor-navbar;
+  $color-sidebar-context: $color-reversal;
+  $bgcolor-sidebar-context: lighten($bgcolor-sidebar, 10%);
+
+
+  @import 'apply-colors';
+  @import 'apply-colors-dark';
+
+  // Navs {
+  .nav-tabs {
+    border-bottom: $border-color-theme 1px solid;
+    .nav-link {
+      &:hover {
+        border-color: lighten($border-color-theme, 10%);
+        border-bottom: none;
+      }
+      &.active {
+        color: $color-link;
+        background-color: transparent;
+        border-color: $border-color-theme;
+      }
+    }
+  }
+
+  // Search Top
+  .search-top {
+    .input-group-prepend .dropdown-toggle {
+      background-color: $color-search;
+      border-color: $color-search;
+      &:hover {
+        background-color: darken($color-search, 10%);
+      }
+    }
+  }
+
+  // Table
+  .table {
+    color: white;
+  }
+}

+ 174 - 8
src/client/styles/scss/theme/nature.scss

@@ -1,8 +1,174 @@
-// import colors
-@import '../../agile-admin/inverse/colors/nature';
-
-// apply agile-admin theme
-@import '../../agile-admin/inverse/style';
-
-// override
-@import 'override-agileadmin';
+@import '../variables';
+@import '../override-bootstrap-variables';
+
+// == Define Bootstrap theme colors
+//
+
+// colors for overriding bootstrap $theme-colors
+// $secondary: #;
+// $info: #;
+// $success: #;
+// $warning: #;
+// $danger: #;
+// $light: #;
+// $dark: #;
+
+.growi:not(.login-page) {
+  // add background-image
+  #page-wrapper,
+  .page-editor-preview-container {
+    background-attachment: fixed;
+    background-position: center center;
+    background-size: cover;
+  }
+}
+
+.growi.login-page {
+  #page-wrapper {
+    background-attachment: fixed;
+    background-position: center center;
+    background-size: cover;
+  }
+}
+
+$themecolor: #118050;
+$themelight: #fefffd;
+
+//== Light Mode
+//
+html[light],
+html[dark] {
+  $bgcolor-theme: #460039;
+
+  $bgcolor-navbar: #118050;
+  $bgcolor-global: #fefffd;
+
+  $color-header: #46694e;
+  $color-global: #333333;
+  $linktext: lighten($bgcolor-theme, 5%);
+  $linktext-hover: lighten($linktext, 12%);
+  $sidebar-text: #5c7253;
+
+  $primary: $bgcolor-theme;
+
+  $fillcolor-logo-mark: lighten(desaturate($bgcolor-navbar, 30%), 20%);
+  $color-link-wiki: lighten($bgcolor-theme, 20%);
+  $color-link-wiki-hover: lighten($color-link-wiki, 20%);
+
+  // Background colors
+  $bgcolor-global: $themelight;
+  $bgcolor-navbar: $themecolor;
+  $bgcolor-inline-code: #f9f2f4;
+  $bgcolor-card: #f5f5f5;
+
+  // Font colors
+  $color-global: $bgcolor-theme;
+  $color-reversal: #eeeeee;
+  $color-link: lighten($color-global, 20%);
+  $color-link-hover: lighten($color-link, 20%);
+  $color-link-wiki: lighten($primary, 20%);
+  $color-link-wiki-hover: lighten($color-link-wiki, 20%);
+  $color-link-nabvar: $color-reversal;
+  $color-inline-code: #c7254e;
+
+  // List Group colors
+  $color-list: $color-global;
+  $bgcolor-list: $bgcolor-global;
+  $color-list-active: $color-reversal;
+  $bgcolor-list-active: $primary;
+  $color-list-hover: $color-reversal;
+
+  // Logo colors
+  $bgcolor-logo: $bgcolor-navbar;
+  $fillcolor-logo-mark: lighten(desaturate($bgcolor-inline-code, 10%), 15%);
+
+  // Icon colors
+  $color-editor-icons: $color-global;
+
+  // Border colors
+  $border-color-theme: #ccc;
+
+  // Dropdown colors
+  $bgcolor-dropdown-link-active: $growi-blue;
+  $color-dropdown-link-active: $color-reversal;
+  $color-dropdown-link-hover: $color-global;
+
+  // alert
+  $color-alert: $color-reversal;
+
+  // badge
+  $color-badge: $color-reversal;
+
+  // Sidebar
+  $bgcolor-sidebar: $bgcolor-navbar;
+  $color-sidebar-context: $color-reversal;
+  $bgcolor-sidebar-context: lighten($bgcolor-navbar, 10%);
+
+  @import 'apply-colors';
+  @import 'apply-colors-light';
+
+  .table {
+    background-color: $themelight;
+  }
+}
+
+//== Dark Mode
+//
+// html[dark] {
+//   $primary: #d65a31;
+
+//   $basecolor: #222831;
+
+//   // Background colors
+//   $bgcolor-global: $basecolor;
+//   $bgcolor-navbar: #151515;
+//   $bgcolor-inline-code: darken($basecolor, 5%);
+//   $bgcolor-card: darken($basecolor, 5%);
+
+//   // Font colors
+//   $color-global: #eeeeee;
+//   $color-reversal: #333333;
+//   // $color-header: desaturate($primary, 20%);
+//   $color-link: $primary;
+//   $color-link-hover: lighten($color-link, 10%);
+//   $color-link-wiki: lighten($basecolor, 50%);
+//   $color-link-wiki-hover: darken($color-link-wiki, 5%);
+//   $color-link-nabvar: $color-global;
+//   $color-inline-code: #c7254e;
+
+//   // List Group colors
+//   $color-list: $color-global;
+//   $bgcolor-list: $bgcolor-global;
+//   $color-list-active: $color-reversal;
+//   $bgcolor-list-active: $primary;
+//   $color-list-hover: $color-reversal;
+
+//   // Logo colors
+//   $bgcolor-logo: $bgcolor-navbar;
+//   $fillcolor-logo-mark: #444;
+
+//   // Icon colors
+//   $color-editor-icons: darken($accentcolor, 15%);
+
+//   // Border colors
+//   $border-color-theme: black; // former: `$navbar-border: #ccc;`
+
+//   // Dropdown colors
+//   $bgcolor-dropdown-link-active: $primary;
+//   $color-dropdown-link-active: $color-global;
+//   $color-dropdown-link-hover: $color-reversal;
+
+//   // alert
+//   $color-alert: $color-global;
+
+//   // badge
+//   $color-badge: $color-global;
+
+//   // Sidebar
+//   $bgcolor-sidebar: $bgcolor-navbar;
+//   $color-sidebar-context: $color-global;
+//   $bgcolor-sidebar-context: lighten($bgcolor-navbar, 5%);
+
+//   @import 'apply-colors';
+//   @import 'apply-colors-dark';
+// }

+ 8 - 3
src/client/styles/scss/theme/spring.scss

@@ -35,6 +35,8 @@
 
 $themecolor: #ffb8c6;
 $themelight: #fff0f5;
+$subthemecolor: #67a856;
+$third-main-color: antiquewhite;
 $accentcolor: #e08dbc;
 
 .grw-navbar {
@@ -48,7 +50,7 @@ html[dark] {
   $primary: $themecolor;
 
   // Background colors
-  $bgcolor-global: $themelight;
+  $bgcolor-global: white;
   $bgcolor-navbar: $themecolor;
   $bgcolor-inline-code: #f9f2f4;
   $bgcolor-card: #f5f5f5;
@@ -59,7 +61,7 @@ html[dark] {
   // $color-header: #2b2b2b;
   $color-link: lighten($color-global, 20%);
   $color-link-hover: lighten($color-link, 20%);
-  $color-link-wiki: lighten($primary, 20%);
+  $color-link-wiki: $subthemecolor;
   $color-link-wiki-hover: lighten($color-link-wiki, 20%);
   $color-link-nabvar: $color-reversal;
   $color-inline-code: #c7254e;
@@ -101,7 +103,10 @@ html[dark] {
   @import 'apply-colors-light';
 
   .table {
-    background-color: $themelight;
+    background-color: $bgcolor-global;
+  }
+  .card-timeline > .card-header {
+    background-color: $third-main-color;
   }
 }
 

+ 167 - 6
src/client/styles/scss/theme/wood.scss

@@ -1,8 +1,169 @@
-// import colors
-@import '../../agile-admin/inverse/colors/wood';
+@import '../variables';
+@import '../override-bootstrap-variables';
 
-// apply agile-admin theme
-@import '../../agile-admin/inverse/style';
+// == Define Bootstrap theme colors
+//
+// colors for overriding bootstrap $theme-colors
+// $secondary: #;
+// $success: #;
+// $warning: #;
+// $danger: #;
+// $light: #;
+// $dark: #;
 
-// override
-@import 'override-agileadmin';
+.growi:not(.login-page) {
+
+  // add background-image
+  #page-wrapper,
+  .page-editor-preview-container {
+    background-image: url('/images/themes/wood/wood.jpg');
+    background-attachment: fixed;
+    background-position: center center;
+    background-size: cover;
+  }
+}
+
+.growi.login-page {
+  #page-wrapper {
+    background-image: url('/images/themes/wood/wood.jpg');
+    background-attachment: fixed;
+    background-position: center center;
+    background-size: cover;
+  }
+}
+
+$themecolor: #aaa45f;
+$themelight: #f5f3ee;
+$accentcolor: #577254;
+
+//== Light Mode
+//
+html[light],
+html[dark] {
+  $primary: $themecolor;
+
+  // Background colors
+  $bgcolor-global: $themelight;
+  $bgcolor-navbar: $themecolor;
+  $bgcolor-inline-code: darken($themecolor, 20%);
+  $bgcolor-card: #f5f5f5;
+
+  // Font colors
+  $color-global: black;
+  $color-reversal: #fffffc;
+  // $color-header: #2b2b2b;
+  $color-link: lighten($color-global, 20%);
+  $color-link-hover: lighten($color-link, 20%);
+  $color-link-wiki: lighten($themecolor, 5%);
+  $color-link-wiki-hover: lighten($color-link-wiki, 15%);
+  $color-link-nabvar: $color-reversal;
+  $color-inline-code: lighten($accentcolor, 70%);
+
+  // List Group colors
+  $color-list: $color-global;
+  $bgcolor-list: $bgcolor-global;
+  $color-list-active: $color-reversal;
+  $bgcolor-list-active: $primary;
+  $color-list-hover: $color-reversal;
+
+  // Logo colors
+  $bgcolor-logo: darken($themecolor, 10%);
+  $fillcolor-logo-mark: lighten(desaturate($themecolor, 50%), 50%); // Icon colors
+  $color-editor-icons: $color-global;
+
+  // Border colors
+  $border-color-theme: #ccc; // former: `$navbar-border: #ccc;`
+
+  // Dropdown colors
+  $bgcolor-dropdown-link-active: $growi-blue;
+  $color-dropdown-link-active: $color-reversal;
+  $color-dropdown-link-hover: $color-global;
+
+  // alert
+  $color-alert: $color-reversal;
+
+  // badge
+  $color-badge: $color-reversal;
+
+  // Sidebar
+  $bgcolor-sidebar: $bgcolor-navbar;
+  $color-sidebar-context: $color-reversal;
+  $bgcolor-sidebar-context: lighten($bgcolor-navbar, 10%);
+
+  // portal
+  $info: lighten($themecolor, 10%);
+
+  @import 'apply-colors';
+  @import 'apply-colors-light';
+
+  .table {
+    background-color: $themelight;
+  }
+
+  .grw-navbar {
+    background-image: url('/images/themes/wood/wood-navbar.jpg');
+    border-bottom: $accentcolor 4px solid;
+
+  }
+}
+
+//== Dark Mode
+//
+// html[dark] {
+//   $primary: #d65a31;
+
+//   $basecolor: #222831;
+
+//   // Background colors
+//   $bgcolor-global: $basecolor;
+//   $bgcolor-navbar: #151515;
+//   $bgcolor-inline-code: darken($basecolor, 5%);
+//   $bgcolor-card: darken($basecolor, 5%);
+
+//   // Font colors
+//   $color-global: #eeeeee;
+//   $color-reversal: #333333;
+//   // $color-header: desaturate($primary, 20%);
+//   $color-link: $primary;
+//   $color-link-hover: lighten($color-link, 10%);
+//   $color-link-wiki: lighten($basecolor, 50%);
+//   $color-link-wiki-hover: darken($color-link-wiki, 5%);
+//   $color-link-nabvar: $color-global;
+//   $color-inline-code: #c7254e;
+
+//   // List Group colors
+//   $color-list: $color-global;
+//   $bgcolor-list: $bgcolor-global;
+//   $color-list-active: $color-reversal;
+//   $bgcolor-list-active: $primary;
+//   $color-list-hover: $color-reversal;
+
+//   // Logo colors
+//   $bgcolor-logo: $bgcolor-navbar;
+//   $fillcolor-logo-mark: #444;
+
+//   // Icon colors
+//   $color-editor-icons: darken($accentcolor, 15%);
+
+//   // Border colors
+//   $border-color-theme: black; // former: `$navbar-border: #ccc;`
+
+//   // Dropdown colors
+//   $bgcolor-dropdown-link-active: $primary;
+//   $color-dropdown-link-active: $color-global;
+//   $color-dropdown-link-hover: $color-reversal;
+
+//   // alert
+//   $color-alert: $color-global;
+
+//   // badge
+//   $color-badge: $color-global;
+
+//   // Sidebar
+//   $bgcolor-sidebar: $bgcolor-navbar;
+//   $color-sidebar-context: $color-global;
+//   $bgcolor-sidebar-context: lighten($bgcolor-navbar, 5%);
+
+//   @import 'apply-colors';
+//   @import 'apply-colors-dark';
+// }

+ 10 - 6
src/lib/components/PagePathHierarchicalLink.jsx

@@ -7,7 +7,7 @@ import LinkedPagePath from '../models/linked-page-path';
 
 
 const PagePathHierarchicalLink = (props) => {
-  const { linkedPagePath, basePath } = props;
+  const { linkedPagePath, basePath, isInTrash } = props;
 
   // render root element
   if (linkedPagePath.isRoot) {
@@ -15,7 +15,7 @@ const PagePathHierarchicalLink = (props) => {
       return null;
     }
 
-    return props.isPageInTrash
+    return isInTrash
       ? (
         <>
           <span className="path-segment">
@@ -37,15 +37,20 @@ const PagePathHierarchicalLink = (props) => {
   }
 
   const isParentExists = linkedPagePath.parent != null;
-  const isParentRoot = isParentExists && linkedPagePath.parent.isRoot;
+  const isParentRoot = linkedPagePath.parent?.isRoot;
   const isSeparatorRequired = isParentExists && !isParentRoot;
+  const isParentInTrash = isInTrash || linkedPagePath.isInTrash;
 
   const href = encodeURI(urljoin(basePath || '/', linkedPagePath.href));
 
   return (
     <>
       { isParentExists && (
-        <PagePathHierarchicalLink linkedPagePath={linkedPagePath.parent} basePath={basePath} />
+        <PagePathHierarchicalLink
+          linkedPagePath={linkedPagePath.parent}
+          basePath={basePath}
+          isInTrash={isParentInTrash}
+        />
       ) }
       { isSeparatorRequired && (
         <span className="separator">/</span>
@@ -59,8 +64,7 @@ const PagePathHierarchicalLink = (props) => {
 PagePathHierarchicalLink.propTypes = {
   linkedPagePath: PropTypes.instanceOf(LinkedPagePath).isRequired,
   basePath: PropTypes.string,
-
-  isPageInTrash: PropTypes.bool, // TODO: omit
+  isInTrash: PropTypes.bool,
 };
 
 export default PagePathHierarchicalLink;

+ 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];
     }

+ 6 - 0
src/lib/models/linked-page-path.js

@@ -1,4 +1,5 @@
 import { pathUtils } from 'growi-commons';
+import { isTrashPage } from '@commons/util/path-utils';
 
 import DevidedPagePath from './devided-page-path';
 
@@ -11,6 +12,7 @@ export default class LinkedPagePath {
 
     const pagePath = new DevidedPagePath(path, skipNormalize);
 
+    this.path = path;
     this.pathName = pagePath.latter;
     this.isRoot = pagePath.isRoot;
     this.parent = pagePath.isRoot
@@ -27,4 +29,8 @@ export default class LinkedPagePath {
     return pathUtils.normalizePath(`${this.parent.href}/${this.pathName}`);
   }
 
+  get isInTrash() {
+    return isTrashPage(this.path);
+  }
+
 }

+ 38 - 0
src/migrations/20200512005851-remove-behavior-type.js

@@ -0,0 +1,38 @@
+require('module-alias/register');
+const logger = require('@alias/logger')('growi:migrate:remove-behavior-type');
+
+const mongoose = require('mongoose');
+const config = require('@root/config/migrate');
+
+const { getModelSafely } = require('@commons/util/mongoose-utils');
+
+module.exports = {
+  async up(db, client) {
+    logger.info('Apply migration');
+    mongoose.connect(config.mongoUri, config.mongodb.options);
+
+    const Config = getModelSafely('Config') || require('@server/models/config')();
+
+    await Config.findOneAndDelete({ key: 'customize:behavior' }); // remove behavior
+
+    logger.info('Migration has successfully applied');
+  },
+
+  async down(db, client) {
+    // do not rollback
+    logger.info('Rollback migration');
+    mongoose.connect(config.mongoUri, config.mongodb.options);
+
+    const Config = getModelSafely('Config') || require('@server/models/config')();
+
+    const insertConfig = new Config({
+      ns: 'crowi',
+      key: 'customize:behavior',
+      value: JSON.stringify('growi'),
+    });
+
+    await insertConfig.save();
+
+    logger.info('Migration has been successfully rollbacked');
+  },
+};

+ 0 - 3
src/server/models/config.js

@@ -22,7 +22,6 @@ module.exports = function(crowi) {
     // overwrite
     config['app:installed'] = true;
     config['app:fileUpload'] = true;
-    config['customize:behavior'] = 'growi';
     config['customize:layout'] = 'growi';
     config['customize:isSavedStatesOfTabChanges'] = false;
 
@@ -108,7 +107,6 @@ module.exports = function(crowi) {
       'customize:highlightJsStyle' : 'github',
       'customize:highlightJsStyleBorder' : false,
       'customize:theme' : 'default',
-      'customize:behavior' : 'crowi',
       'customize:layout' : 'crowi',
       'customize:isEnabledTimeline' : true,
       'customize:isSavedStatesOfTabChanges' : true,
@@ -190,7 +188,6 @@ module.exports = function(crowi) {
         file: crowi.fileUploadService.getFileUploadEnabled(),
       },
       registrationWhiteList: crowi.configManager.getConfig('crowi', 'security:registrationWhiteList'),
-      behaviorType: crowi.configManager.getConfig('crowi', 'customize:behavior'),
       layoutType: crowi.configManager.getConfig('crowi', 'customize:layout'),
       themeType: crowi.configManager.getConfig('crowi', 'customize:theme'),
       isEnabledLinebreaks: crowi.configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'),

+ 0 - 1
src/server/routes/apiv3/customize-setting.js

@@ -152,7 +152,6 @@ module.exports = (crowi) => {
     const customizeParams = {
       layoutType: await crowi.configManager.getConfig('crowi', 'customize:layout'),
       themeType: await crowi.configManager.getConfig('crowi', 'customize:theme'),
-      behaviorType: await crowi.configManager.getConfig('crowi', 'customize:behavior'),
       isEnabledTimeline: await crowi.configManager.getConfig('crowi', 'customize:isEnabledTimeline'),
       isSavedStatesOfTabChanges: await crowi.configManager.getConfig('crowi', 'customize:isSavedStatesOfTabChanges'),
       isEnabledAttachTitleHeader: await crowi.configManager.getConfig('crowi', 'customize:isEnabledAttachTitleHeader'),

+ 17 - 95
src/server/routes/page.js

@@ -146,16 +146,12 @@ module.exports = function(crowi, app) {
   const ApiResponse = require('../util/apiResponse');
   const getToday = require('../util/getToday');
 
-  const { configManager, slackNotificationService } = crowi;
+  const { slackNotificationService } = crowi;
   const interceptorManager = crowi.getInterceptorManager();
   const globalNotificationService = crowi.getGlobalNotificationService();
 
   const actions = {};
 
-  const PORTAL_STATUS_NOT_EXISTS = 0;
-  const PORTAL_STATUS_EXISTS = 1;
-  const PORTAL_STATUS_FORBIDDEN = 2;
-
   // register page events
 
   const pageEvent = crowi.event('page');
@@ -342,27 +338,17 @@ module.exports = function(crowi, app) {
     const portalPath = pathUtils.addTrailingSlash(getPathFromRequest(req));
     const revisionId = req.query.revision;
 
-    // check whether this page has portal page
-    const portalPageStatus = await getPortalPageState(portalPath, req.user);
-
-    let view = 'customlayout-selector/page_list';
+    const view = 'customlayout-selector/page_list';
     const renderVars = { path: portalPath };
 
-    if (portalPageStatus === PORTAL_STATUS_FORBIDDEN) {
-      // inject to req
-      req.isForbidden = true;
-      view = 'customlayout-selector/forbidden';
-    }
-    else if (portalPageStatus === PORTAL_STATUS_EXISTS) {
-      let portalPage = await Page.findByPathAndViewer(portalPath, req.user);
-      portalPage.initLatestRevisionField(revisionId);
+    let portalPage = await Page.findByPathAndViewer(portalPath, req.user);
+    portalPage.initLatestRevisionField(revisionId);
 
-      // populate
-      portalPage = await portalPage.populateDataToShowRevision();
+    // populate
+    portalPage = await portalPage.populateDataToShowRevision();
 
-      addRendarVarsForPage(renderVars, portalPage);
-      await addRenderVarsForSlack(renderVars, portalPage);
-    }
+    addRendarVarsForPage(renderVars, portalPage);
+    await addRenderVarsForSlack(renderVars, portalPage);
 
     const limit = 50;
     const offset = parseInt(req.query.offset) || 0;
@@ -427,25 +413,6 @@ module.exports = function(crowi, app) {
     return channels;
   };
 
-  /**
-   *
-   * @param {string} path
-   * @param {User} user
-   * @returns {number} PORTAL_STATUS_NOT_EXISTS(0) or PORTAL_STATUS_EXISTS(1) or PORTAL_STATUS_FORBIDDEN(2)
-   */
-  async function getPortalPageState(path, user) {
-    const portalPath = Page.addSlashOfEnd(path);
-    const page = await Page.findByPathAndViewer(portalPath, user);
-
-    if (page == null) {
-      // check the page is forbidden or just does not exist.
-      const isForbidden = await Page.count({ path: portalPath }) > 0;
-      return isForbidden ? PORTAL_STATUS_FORBIDDEN : PORTAL_STATUS_NOT_EXISTS;
-    }
-    return PORTAL_STATUS_EXISTS;
-  }
-
-
   actions.showTopPage = function(req, res) {
     return showPageListForCrowiBehavior(req, res);
   };
@@ -455,16 +422,9 @@ module.exports = function(crowi, app) {
    */
   /* eslint-disable no-else-return */
   actions.showPageWithEndOfSlash = function(req, res, next) {
-    const behaviorType = configManager.getConfig('crowi', 'customize:behavior');
-
-    if (behaviorType === 'crowi') {
-      return showPageListForCrowiBehavior(req, res, next);
-    }
-    else {
-      const path = getPathFromRequest(req); // end of slash should be omitted
-      // redirect and showPage action will be triggered
-      return res.redirect(path);
-    }
+    const path = getPathFromRequest(req); // end of slash should be omitted
+    // redirect and showPage action will be triggered
+    return res.redirect(path);
   };
   /* eslint-enable no-else-return */
 
@@ -478,20 +438,6 @@ module.exports = function(crowi, app) {
     if (req.query.presentation) {
       return showPageForPresentation(req, res, next);
     }
-
-    const behaviorType = configManager.getConfig('crowi', 'customize:behavior');
-
-    // check whether this page has portal page
-    if (behaviorType === 'crowi') {
-      const portalPagePath = pathUtils.addTrailingSlash(getPathFromRequest(req));
-      const hasPortalPage = await Page.count({ path: portalPagePath }) > 0;
-
-      if (hasPortalPage) {
-        logger.debug('The portal page is found', portalPagePath);
-        return res.redirect(encodeURI(`${portalPagePath}?redirectFrom=${pathUtils.encodePagePath(req.path)}`));
-      }
-    }
-
     // delegate to showPageForGrowiBehavior
     return showPageForGrowiBehavior(req, res, next);
   };
@@ -501,16 +447,8 @@ module.exports = function(crowi, app) {
    */
   /* eslint-disable no-else-return */
   actions.trashPageListShowWrapper = function(req, res) {
-    const behaviorType = configManager.getConfig('crowi', 'customize:behavior');
-
-    if (behaviorType === 'crowi') {
-      // Crowi behavior for '/trash/*'
-      return actions.deletedPageListShow(req, res);
-    }
-    else {
-      // redirect to '/trash'
-      return res.redirect('/trash');
-    }
+    // redirect to '/trash'
+    return res.redirect('/trash');
   };
   /* eslint-enable no-else-return */
 
@@ -519,16 +457,8 @@ module.exports = function(crowi, app) {
    */
   /* eslint-disable no-else-return */
   actions.trashPageShowWrapper = function(req, res) {
-    const behaviorType = configManager.getConfig('crowi', 'customize:behavior');
-
-    if (behaviorType === 'crowi') {
-      // redirect to '/trash/'
-      return res.redirect('/trash/');
-    }
-    else {
-      // Crowi behavior for '/trash/*'
-      return actions.deletedPageListShow(req, res);
-    }
+    // Crowi behavior for '/trash/*'
+    return actions.deletedPageListShow(req, res);
   };
   /* eslint-enable no-else-return */
 
@@ -537,16 +467,8 @@ module.exports = function(crowi, app) {
    */
   /* eslint-disable no-else-return */
   actions.deletedPageListShowWrapper = function(req, res) {
-    const behaviorType = configManager.getConfig('crowi', 'customize:behavior');
-
-    if (behaviorType === 'crowi') {
-      // Crowi behavior for '/trash/*'
-      return actions.deletedPageListShow(req, res);
-    }
-    else {
-      const path = `/trash${getPathFromRequest(req)}`;
-      return res.redirect(path);
-    }
+    const path = `/trash${getPathFromRequest(req)}`;
+    return res.redirect(path);
   };
   /* eslint-enable no-else-return */
 

+ 0 - 2
src/server/views/layout-growi/page.html

@@ -34,13 +34,11 @@
 
   </div>
 
-  {% if 'growi' === getConfig('crowi', 'customize:behavior') || 'crowi-plus' === getConfig('crowi', 'customize:behavior') %}
   <div class="row page-list d-edit-none d-print-none mt-5">
     <div class="col-md-10">
       {% include '../widget/page_list_and_timeline.html' %}
     </div>
   </div>
-  {% endif %}
 {% endblock %}
 
 

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

@@ -55,7 +55,6 @@
   </div>
   <div id="crowi-modals">
     {% include '../widget/page_modals.html' %}
-    {% include '../modal/what_is_portal.html' %}
     {% include '../modal/unportalize.html' %}
   </div>
 {% endblock %}

+ 0 - 2
src/server/views/layout-growi/user_page.html

@@ -60,13 +60,11 @@
 
   </div>
 
-  {% if 'growi' === getConfig('crowi', 'customize:behavior') || 'crowi-plus' === getConfig('crowi', 'customize:behavior') %}
   <div class="row page-list d-edit-none d-print-none mt-5">
     <div class="col-md-10">
       {% include '../widget/page_list_and_timeline.html' %}
     </div>
   </div>
-  {% endif %}
 
 {% endblock %}
 

+ 0 - 6
src/server/views/layout-growi/widget/header.html

@@ -1,7 +1 @@
 <div id="grw-subnav" class="grw-subnav d-edit-none" data-is-forbidden-page="{{ forbidden }}"></div>
-
-{% if not page and not forbidden and ('/' === path or 'crowi' === getConfig('crowi', 'customize:behavior')) and not isUserPageList(path) and !isTrashPage() %}
-  {% if '/' === path.slice(-1) %}
-    {% include '../../widget/create_portal.html' %}
-  {% endif %}
-{% endif %}

+ 0 - 2
src/server/views/layout-kibela/page.html

@@ -32,13 +32,11 @@
 
 </div>
 
-  {% if 'growi' === getConfig('crowi', 'customize:behavior') || 'crowi-plus' === getConfig('crowi', 'customize:behavior') %}
   <div class="row page-list grw-pt-10px my-5 round-corner d-edit-none">
     <div class="col-md-10">
       {% include '../widget/page_list_and_timeline.html' %}
     </div>
   </div>
-  {% endif %}
 {% endblock %}
 
 

+ 0 - 1
src/server/views/layout-kibela/page_list.html

@@ -53,7 +53,6 @@
 </div>
 <div id="crowi-modals">
   {% include '../widget/page_modals.html' %}
-    {% include '../modal/what_is_portal.html' %}
   {% include '../modal/unportalize.html' %}
 </div>
 {% endblock %}

+ 0 - 2
src/server/views/layout-kibela/user_page.html

@@ -51,13 +51,11 @@
 
   </div>
 
-  {% if 'growi' === getConfig('crowi', 'customize:behavior') || 'crowi-plus' === getConfig('crowi', 'customize:behavior') %}
   <div class="row page-list mt-5 d-edit-none">
     <div class="col-12">
       {% include '../widget/page_list_and_timeline_kibela.html' %}
     </div>
   </div>
-  {% endif %}
 
 {% endblock %}
 

+ 0 - 5
src/server/views/layout-kibela/widget/header.html

@@ -1,7 +1,2 @@
 <div id="grw-subnav" class="grw-subnav" data-is-forbidden-page="{{ forbidden }}"></div>
 
-{% if not page and ('/' === path or 'crowi' === getConfig('crowi', 'customize:behavior')) and not isUserPageList(path) and !isTrashPage() %}
-  {% if '/' === path.slice(-1) %}
-    {% include '../../widget/create_portal.html' %}
-  {% endif %}
-{% endif %}

+ 2 - 2
src/server/views/modal/create_page.html

@@ -62,10 +62,10 @@
                   </a>
                   <div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
                     <button class="dropdown-item" type="button" data-template-type="children">
-                      {{ t('template.children.label') }} (_template) <small class="text-muted">- {{ t('template.children.desc') }}</small>
+                      {{ t('template.children.label') }} (_template)<br class="d-block d-md-none" /><small class="text-muted text-wrap">- {{ t('template.children.desc') }}</small>
                     </button>
                     <button class="dropdown-item" type="button" data-template-type="decentants">
-                      {{ t('template.decendants.label') }} (__template) <small class="text-muted">- {{ t('template.decendants.desc') }}</small>
+                      {{ t('template.decendants.label') }} (__template) <br class="d-block d-md-none" /><small class="text-muted">- {{ t('template.decendants.desc') }}</small>
                     </button>
                   </div>
                 </div>

+ 0 - 81
src/server/views/modal/what_is_portal.html

@@ -1,81 +0,0 @@
-<div class="modal" id="help-portal">
-  <div class="modal-dialog">
-    <div class="modal-content">
-
-      <div class="modal-header">
-        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-        <div class="modal-title">What is Portal?</div>
-      </div>
-
-      <div class="modal-body">
-        <h4>Portal とは</h4>
-        <br>
-        <ul>
-          <li>すべての、スラッシュ <code>/</code> で終わるページは、その階層の一覧ページとなります。</li>
-          <li>Portal 機能を使うと、その一覧ページに対して、任意の編集コンテンツを配置することができるようになります (つまり、一般的なページと同様に、編集したコンテンツを作成でき、その内容は常にページ一覧の上部に表示されるようになります)</li>
-        </ul>
-        <br>
-
-        <hr>
-
-        <h4>想定される使われ方</h4>
-        <br>
-        <p>
-        例えば、以下のようなページの階層があったとします。
-        </p>
-        <ul>
-          <li><code>/projects</code>
-            <ul>
-              <li><code>/projects/homepage-renewal</code>
-                <ul>
-                  <li><code>/projects/homepage-renewal/...</code></li>
-                </ul>
-              </li>
-              <li><code>/projects/...</code></li>
-            </ul>
-          </li>
-        </ul>
-
-        <p>
-        こういったケースでは、<code>/projects/homepage-renewal</code> には homepage-renewal プロジェクトについてのイントロや各ページへのリンク、関係者の紹介など、homepage-renewal に関する情報を掲載しておきたいと思うはずです。
-        </p>
-        <p>
-        Poral機能を使うと、こうしたときに、<code>/projects/homepage-renewal/</code> この <strong>"一覧ページ" を、ページ化することができ、そこに、通常のページと同じように Markdown で編集したコンテンツを配置することができるようになります</strong>。
-        </p>
-
-        <p>
-        まさにそのプロジェクトのポータルページを用意したい場合などに活用してください。
-        </p>
-
-        </div>
-
-      </div>
-
-    </div><!-- /.modal-content -->
-  </div><!-- /.modal-dialog -->
-</div><!-- /.modal -->
-
-<div class="modal fade portal-warning-modal" id="portal-warning-modal">
-  <div class="modal-dialog">
-    <div class="modal-content">
-
-      <div class="modal-header">
-        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-        <div class="modal-title">ポータルに関するヒント</div>
-      </div>
-      <div class="modal-body">
-
-        <strong>Warning!</strong><br>
-
-        <p>既に <strong><a href="{{ path|removeTrailingSlash }}">{{ path|removeTrailingSlash }}</a></strong> のページが存在します。</p>
-
-        <p>
-          <a href="{{ path|removeTrailingSlash }}">{{ path|removeTrailingSlash }}</a> をポータル化するには、
-          <a href="{{ path|removeTrailingSlash }}">{{ path|removeTrailingSlash }}</a> に移動し、「ページを移動」させてください。<br>
-          <a href="{{ path|removeTrailingSlash }}">{{ path|removeTrailingSlash }}</a> とは別に、このページ(<code>{{ path }}</code>)にポータルを作成する場合、このまま編集を続けて作成してください。
-        </p>
-
-      </div>
-    </div>
-  </div>
-</div>

+ 0 - 4
src/server/views/widget/create_portal.html

@@ -1,4 +0,0 @@
-<div class="portal-form-button d-edit-none">
-  <a class="btn btn-primary" id="create-portal-button" href="#edit" data-toggle="tab" {% if not user %}disabled{% endif %}>Create Portal</a>
-  <p class="form-text text-muted"><a href="#" data-target="#help-portal" data-toggle="modal"><i class="icon-question"></i> What is Portal?</a></p>
-</div>

+ 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>

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

@@ -45,9 +45,6 @@
         </div>
         <div id="page" class="mt-4"></div>
       </div>
-    {% elseif 'crowi' === getConfig('crowi', 'customize:behavior') %}
-      <div class="tab-pane active" id="cancel-creating-portal">
-      </div>
     {% endif %}
 
     {% if !isTrashPage() %}

+ 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 %}
 
   {#

+ 15 - 0
yarn.lock

@@ -688,6 +688,14 @@
     "@babel/helper-plugin-utils" "^7.0.0"
     "@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
 
+"@babel/plugin-proposal-optional-chaining@^7.9.0":
+  version "7.9.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.9.0.tgz#31db16b154c39d6b8a645292472b98394c292a58"
+  integrity sha512-NDn5tu3tcv4W30jNhmc2hyD5c56G6cXx4TesJubhxrJeCvuuMpttxr0OnNCqbZGhFjLrg+NIhxxC+BK5F6yS3w==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.8.3"
+    "@babel/plugin-syntax-optional-chaining" "^7.8.0"
+
 "@babel/plugin-proposal-unicode-property-regex@^7.4.4":
   version "7.4.4"
   resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz#501ffd9826c0b91da22690720722ac7cb1ca9c78"
@@ -739,6 +747,13 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.0.0"
 
+"@babel/plugin-syntax-optional-chaining@^7.8.0":
+  version "7.8.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a"
+  integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.8.0"
+
 "@babel/plugin-transform-arrow-functions@^7.2.0":
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz#9aeafbe4d6ffc6563bf8f8372091628f00779550"