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

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

yusuketk 5 лет назад
Родитель
Сommit
aca8a6e39c
68 измененных файлов с 506 добавлено и 715 удалено
  1. 1 2
      config/webpack.common.js
  2. 0 1
      package.json
  3. 4 0
      resource/locales/en-US/admin/admin.json
  4. 1 1
      resource/locales/en-US/translation.json
  5. 4 0
      resource/locales/ja/admin/admin.json
  6. 1 1
      resource/locales/ja/translation.json
  7. 47 40
      src/client/js/components/Admin/Customize/CustomizeThemeOptions.jsx
  8. 1 1
      src/client/js/components/Navbar/GrowiSubNavigationForUserPage.jsx
  9. 89 43
      src/client/js/components/Page/CopyDropdown.jsx
  10. 1 1
      src/client/js/components/Page/TagEditor.jsx
  11. 1 1
      src/client/js/components/PageEditor/Editor.jsx
  12. 2 2
      src/client/js/components/PageList/Page.jsx
  13. 3 3
      src/client/js/components/PageList/PagePathLabel.jsx
  14. 1 10
      src/client/js/components/PageTimeline.jsx
  15. 1 1
      src/client/js/components/SearchPage/SearchResult.jsx
  16. 1 1
      src/client/js/components/SearchTypeahead.jsx
  17. 9 8
      src/client/js/legacy/crowi.js
  18. 2 1
      src/client/js/services/PageContainer.js
  19. 0 2
      src/client/styles/scss/_navbar_kibela.scss
  20. 16 18
      src/client/styles/scss/_page_list.scss
  21. 0 2
      src/client/styles/scss/_staff_credit.scss
  22. 19 0
      src/client/styles/scss/molecules/copy-dropdown.scss
  23. 3 0
      src/client/styles/scss/style-app.scss
  24. 0 8
      src/client/styles/scss/theme/_apply-colors-kibela.scss
  25. 1 0
      src/client/styles/scss/theme/_apply-colors.scss
  26. 0 8
      src/client/styles/scss/theme/blue-night.scss
  27. 210 6
      src/client/styles/scss/theme/christmas.scss
  28. 0 2
      src/server/crowi/express-init.js
  29. 1 1
      src/server/models/config.js
  30. 2 2
      src/server/routes/apiv3/customize-setting.js
  31. 1 1
      src/server/routes/attachment.js
  32. 21 24
      src/server/routes/page.js
  33. 1 2
      src/server/util/swigFunctions.js
  34. 0 317
      src/server/views/admin/Users_reserve.html
  35. 1 2
      src/server/views/admin/markdown.html
  36. 0 3
      src/server/views/admin/search.html
  37. 0 7
      src/server/views/customlayout-selector/forbidden.html
  38. 0 7
      src/server/views/customlayout-selector/not_creatable.html
  39. 0 7
      src/server/views/customlayout-selector/not_found.html
  40. 0 7
      src/server/views/customlayout-selector/page.html
  41. 0 7
      src/server/views/customlayout-selector/page_list.html
  42. 0 7
      src/server/views/customlayout-selector/user_page.html
  43. 0 1
      src/server/views/layout-growi/page_list.html
  44. 0 1
      src/server/views/layout-kibela/page_list.html
  45. 1 1
      src/server/views/layout/layout.html
  46. 3 3
      src/server/views/modal/create_page.html
  47. 1 1
      src/server/views/modal/create_template.html
  48. 1 1
      src/server/views/modal/delete.html
  49. 1 1
      src/server/views/modal/duplicate.html
  50. 1 1
      src/server/views/modal/empty_trash.html
  51. 1 1
      src/server/views/modal/put_back.html
  52. 1 1
      src/server/views/modal/rename.html
  53. 1 1
      src/server/views/modal/shortcuts.html
  54. 0 50
      src/server/views/modal/unportalize.html
  55. 1 1
      src/server/views/page_presentation.html
  56. 1 1
      src/server/views/widget/alert_siteurl_undefined.html
  57. 2 2
      src/server/views/widget/forbidden_content.html
  58. 2 2
      src/server/views/widget/not_creatable_content.html
  59. 13 4
      src/server/views/widget/not_found_content.html
  60. 6 5
      src/server/views/widget/page_alerts.html
  61. 2 2
      src/server/views/widget/page_content.html
  62. 8 5
      src/server/views/widget/page_list.html
  63. 2 2
      src/server/views/widget/page_list_and_timeline.html
  64. 2 2
      src/server/views/widget/page_list_and_timeline_kibela.html
  65. 0 7
      src/server/views/widget/page_tabs.html
  66. 11 15
      src/server/views/widget/page_tabs_kibela.html
  67. 0 32
      src/server/views/widget/pager.html
  68. 0 15
      yarn.lock

+ 1 - 2
config/webpack.common.js

@@ -37,11 +37,10 @@ module.exports = (options) => {
       'styles/theme-nature':          './src/client/styles/scss/theme/nature.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-mono-blue':       './src/client/styles/scss/theme/mono-blue.scss',
       'styles/theme-future':          './src/client/styles/scss/theme/future.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-kibela':          './src/client/styles/scss/theme/kibela.scss',
       'styles/theme-halloween':       './src/client/styles/scss/theme/halloween.scss',
       'styles/theme-halloween':       './src/client/styles/scss/theme/halloween.scss',
+      'styles/theme-christmas':          './src/client/styles/scss/theme/christmas.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-island':      './src/client/styles/scss/theme/island.scss',
       'styles/theme-antarctic':      './src/client/styles/scss/theme/antarctic.scss',
       'styles/theme-antarctic':      './src/client/styles/scss/theme/antarctic.scss',
       'styles/theme-spring':         './src/client/styles/scss/theme/spring.scss',
       'styles/theme-spring':         './src/client/styles/scss/theme/spring.scss',

+ 0 - 1
package.json

@@ -98,7 +98,6 @@
     "express": "^4.16.1",
     "express": "^4.16.1",
     "express-bunyan-logger": "^1.3.3",
     "express-bunyan-logger": "^1.3.3",
     "express-form": "~0.12.0",
     "express-form": "~0.12.0",
-    "express-sanitizer": "^1.0.4",
     "express-session": "^1.16.1",
     "express-session": "^1.16.1",
     "express-validator": "^6.1.1",
     "express-validator": "^6.1.1",
     "express-webpack-assets": "^0.1.0",
     "express-webpack-assets": "^0.1.0",

+ 4 - 0
resource/locales/en-US/admin/admin.json

@@ -99,6 +99,10 @@
       "kibela_text2": "Show and post comments at the bottom of the page",
       "kibela_text2": "Show and post comments at the bottom of the page",
       "kibela_text3": "Affix Table-of-contents"
       "kibela_text3": "Affix Table-of-contents"
     },
     },
+    "theme_desc": {
+      "light_and_dark": "Light and dark modes",
+      "unique": "Only one mode"
+    },
     "function": "Function",
     "function": "Function",
     "function_desc": "You can choose Valid/Invalid of the function",
     "function_desc": "You can choose Valid/Invalid of the function",
     "function_options": {
     "function_options": {

+ 1 - 1
resource/locales/en-US/translation.json

@@ -55,7 +55,6 @@
   "Share Link": "Share Link",
   "Share Link": "Share Link",
   "Markdown Link": "Markdown Link",
   "Markdown Link": "Markdown Link",
   "Create/Edit Template": "Create/Edit Template Page",
   "Create/Edit Template": "Create/Edit Template Page",
-  "Unportalize": "Unportalize",
   "Go to this version": "View this version",
   "Go to this version": "View this version",
   "View diff": "View diff",
   "View diff": "View diff",
   "No diff": "No diff",
   "No diff": "No diff",
@@ -200,6 +199,7 @@
   "copy_to_clipboard": {
   "copy_to_clipboard": {
     "Copy to clipboard": "Copy to clipboard",
     "Copy to clipboard": "Copy to clipboard",
     "Page path": "Page path",
     "Page path": "Page path",
+    "Page URL": "Page URL",
     "Parmanent link": "Parmanent link",
     "Parmanent link": "Parmanent link",
     "Page path and parmanent link": "Page path and parmanent link",
     "Page path and parmanent link": "Page path and parmanent link",
     "Markdown link": "Markdown link"
     "Markdown link": "Markdown link"

+ 4 - 0
resource/locales/ja/admin/admin.json

@@ -99,6 +99,10 @@
       "kibela_text2": "コメントはページの下部に表示されます。",
       "kibela_text2": "コメントはページの下部に表示されます。",
       "kibela_text3": "ページ情報は下部に表示されます。"
       "kibela_text3": "ページ情報は下部に表示されます。"
     },
     },
+    "theme_desc" : {
+      "light_and_dark": "Light/Dark モード選択あり",
+      "unique": "モード選択なし"
+    },
     "function": "機能",
     "function": "機能",
     "function_desc": "機能の有効/無効を選択できます。",
     "function_desc": "機能の有効/無効を選択できます。",
     "function_options": {
     "function_options": {

+ 1 - 1
resource/locales/ja/translation.json

@@ -55,7 +55,6 @@
   "Share Link": "共有用リンク",
   "Share Link": "共有用リンク",
   "Markdown Link": "Markdown形式のリンク",
   "Markdown Link": "Markdown形式のリンク",
   "Create/Edit Template": "テンプレートページの作成/編集",
   "Create/Edit Template": "テンプレートページの作成/編集",
-  "Unportalize": "ポータル解除",
   "Go to this version": "このバージョンを見る",
   "Go to this version": "このバージョンを見る",
   "View diff": "差分を表示",
   "View diff": "差分を表示",
   "No diff": "差分なし",
   "No diff": "差分なし",
@@ -199,6 +198,7 @@
   "copy_to_clipboard": {
   "copy_to_clipboard": {
     "Copy to clipboard": "クリップボードにコピー",
     "Copy to clipboard": "クリップボードにコピー",
     "Page path": "ページ名",
     "Page path": "ページ名",
+    "Page URL": "ページURL",
     "Parmanent link": "パーマリンク",
     "Parmanent link": "パーマリンク",
     "Page path and parmanent link": "ページ名とパーマリンク",
     "Page path and parmanent link": "ページ名とパーマリンク",
     "Markdown link": "マークダウン形式のリンク"
     "Markdown link": "マークダウン形式のリンク"

+ 47 - 40
src/client/js/components/Admin/Customize/CustomizeThemeOptions.jsx

@@ -1,5 +1,7 @@
 import React from 'react';
 import React from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+
 
 
 import { createSubscribedElement } from '../../UnstatedUtils';
 import { createSubscribedElement } from '../../UnstatedUtils';
 
 
@@ -10,15 +12,17 @@ import AdminCustomizeContainer from '../../../services/AdminCustomizeContainer';
 class CustomizeThemeOptions extends React.Component {
 class CustomizeThemeOptions extends React.Component {
 
 
   render() {
   render() {
-    const { adminCustomizeContainer } = this.props;
+    const { t, adminCustomizeContainer } = this.props;
     const { currentLayout, currentTheme } = adminCustomizeContainer.state;
     const { currentLayout, currentTheme } = adminCustomizeContainer.state;
 
 
-    const lightTheme = [{
+    const lightNDarkTheme = [{
       name: 'default', bg: '#ffffff', topbar: '#334455', theme: '#112744',
       name: 'default', bg: '#ffffff', topbar: '#334455', theme: '#112744',
-    }, {
-      name: 'nature', bg: '#f9fff3', topbar: '#118050', theme: '#460039',
     }, {
     }, {
       name: 'mono-blue', bg: '#F7FBFD', topbar: '#00587A', theme: '#00587A',
       name: 'mono-blue', bg: '#F7FBFD', topbar: '#00587A', theme: '#00587A',
+    }];
+
+    const uniqueTheme = [{
+      name: 'nature', bg: '#f9fff3', topbar: '#118050', theme: '#460039',
     }, {
     }, {
       name: 'wood', bg: '#fffefb', topbar: '#aaa45f', theme: '#dddebf',
       name: 'wood', bg: '#fffefb', topbar: '#aaa45f', theme: '#dddebf',
     }, {
     }, {
@@ -29,49 +33,51 @@ class CustomizeThemeOptions extends React.Component {
       name: 'antarctic', bg: '#ffffff', topbar: '#000080', theme: '#99cccc',
       name: 'antarctic', bg: '#ffffff', topbar: '#000080', theme: '#99cccc',
     }, {
     }, {
       name: 'spring', bg: '#fff5ee', topbar: '#ff69b4', theme: '#ffb6c1',
       name: 'spring', bg: '#fff5ee', topbar: '#ff69b4', theme: '#ffb6c1',
-    }];
-
-    const darkTheme = [{
-      name: 'future', bg: '#16282D', topbar: '#011414', theme: '#04B4AE',
     }, {
     }, {
-      name: 'blue-night', bg: '#061F2F', topbar: '#27343B', theme: '#0090C8',
+      name: 'future', bg: '#16282D', topbar: '#011414', theme: '#04B4AE',
     }, {
     }, {
       name: 'halloween', bg: '#030003', topbar: '#cc5d1f', theme: '#e9af2b',
       name: 'halloween', bg: '#030003', topbar: '#cc5d1f', theme: '#e9af2b',
     }];
     }];
 
 
     return (
     return (
       <div id="themeOptions" className={`${currentLayout === 'kibela' && 'disabled'}`}>
       <div id="themeOptions" className={`${currentLayout === 'kibela' && 'disabled'}`}>
-        {/* Light Themes  */}
-        <div className="d-flex flex-wrap">
-          {lightTheme.map((theme) => {
-            return (
-              <ThemeColorBox
-                key={theme.name}
-                isSelected={currentTheme === theme.name}
-                onSelected={() => adminCustomizeContainer.switchThemeType(theme.name)}
-                name={theme.name}
-                bg={theme.bg}
-                topbar={theme.topbar}
-                theme={theme.theme}
-              />
-            );
-          })}
+        {/* Light and Dark Themes */}
+        <div>
+          <h3>{t('admin:customize_setting.theme_desc.light_and_dark')}</h3>
+          <div className="d-flex flex-wrap">
+            {lightNDarkTheme.map((theme) => {
+              return (
+                <ThemeColorBox
+                  key={theme.name}
+                  isSelected={currentTheme === theme.name}
+                  onSelected={() => adminCustomizeContainer.switchThemeType(theme.name)}
+                  name={theme.name}
+                  bg={theme.bg}
+                  topbar={theme.topbar}
+                  theme={theme.theme}
+                />
+              );
+            })}
+          </div>
         </div>
         </div>
-        {/* Dark Themes  */}
-        <div className="d-flex mt-3">
-          {darkTheme.map((theme) => {
-            return (
-              <ThemeColorBox
-                key={theme.name}
-                isSelected={currentTheme === theme.name}
-                onSelected={() => adminCustomizeContainer.switchThemeType(theme.name)}
-                name={theme.name}
-                bg={theme.bg}
-                topbar={theme.topbar}
-                theme={theme.theme}
-              />
-            );
-          })}
+        {/* Unique Theme */}
+        <div>
+          <h3>{t('admin:customize_setting.theme_desc.unique')}</h3>
+          <div className="d-flex flex-wrap">
+            {uniqueTheme.map((theme) => {
+              return (
+                <ThemeColorBox
+                  key={theme.name}
+                  isSelected={currentTheme === theme.name}
+                  onSelected={() => adminCustomizeContainer.switchThemeType(theme.name)}
+                  name={theme.name}
+                  bg={theme.bg}
+                  topbar={theme.topbar}
+                  theme={theme.theme}
+                />
+              );
+            })}
+          </div>
         </div>
         </div>
       </div>
       </div>
     );
     );
@@ -84,8 +90,9 @@ const CustomizeThemeOptionsWrapper = (props) => {
 };
 };
 
 
 CustomizeThemeOptions.propTypes = {
 CustomizeThemeOptions.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   adminCustomizeContainer: PropTypes.instanceOf(AdminCustomizeContainer).isRequired,
   adminCustomizeContainer: PropTypes.instanceOf(AdminCustomizeContainer).isRequired,
 };
 };
 
 
-export default CustomizeThemeOptionsWrapper;
+export default withTranslation()(CustomizeThemeOptionsWrapper);

+ 1 - 1
src/client/js/components/Navbar/GrowiSubNavigationForUserPage.jsx

@@ -55,7 +55,7 @@ const GrowiSubNavigationForUserPage = (props) => {
   }
   }
 
 
   return (
   return (
-    <div className={`px-3 ${additionalClassNames.join(' ')}`}>
+    <div className={`px-3 py-3 ${additionalClassNames.join(' ')}`}>
       <PagePathNav pageId={pageId} pagePath={path} />
       <PagePathNav pageId={pageId} pagePath={path} />
 
 
       <div className="d-flex align-items-center justify-content-between">
       <div className="d-flex align-items-center justify-content-between">

+ 89 - 43
src/client/js/components/Page/CopyDropdown.jsx

@@ -15,9 +15,6 @@ class CopyDropdown extends React.Component {
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
 
 
-    // retrieve xss library from window
-    this.xss = window.xss;
-
     this.state = {
     this.state = {
       dropdownOpen: false,
       dropdownOpen: false,
       tooltipOpen: false,
       tooltipOpen: false,
@@ -25,7 +22,10 @@ class CopyDropdown extends React.Component {
 
 
     this.toggle = this.toggle.bind(this);
     this.toggle = this.toggle.bind(this);
     this.showToolTip = this.showToolTip.bind(this);
     this.showToolTip = this.showToolTip.bind(this);
-    this.generatePageUrl = this.generatePageUrl.bind(this);
+    this.generatePagePathWithParams = this.generatePagePathWithParams.bind(this);
+    this.generatePagePathUrl = this.generatePagePathUrl.bind(this);
+    this.generatePermalink = this.generatePermalink.bind(this);
+    this.generateMarkdownLink = this.generateMarkdownLink.bind(this);
   }
   }
 
 
   toggle() {
   toggle() {
@@ -39,25 +39,65 @@ class CopyDropdown extends React.Component {
     }, 1000);
     }, 1000);
   }
   }
 
 
-  generatePageUrl() {
-    return (this.props.pageId == null)
-      ? decodeURIComponent(window.location.pathname + window.location.search)
-      : `${window.location.origin}/${this.props.pageId}`;
+  generatePagePathWithParams() {
+    const { pagePath } = this.props;
+    const {
+      search, hash,
+    } = window.location;
+
+    return `${pagePath}${search}${hash}`;
+  }
+
+  generatePagePathUrl() {
+    const { origin } = window.location;
+    return `${origin}${this.generatePagePathWithParams()}`;
+  }
+
+  generatePermalink() {
+    const { pageId } = this.props;
+    const { location } = window;
+
+    if (pageId == null) {
+      return null;
+    }
+
+    const {
+      origin, search, hash,
+    } = location;
+    return `${origin}/${pageId}${search}${hash}`;
   }
   }
 
 
   generateMarkdownLink() {
   generateMarkdownLink() {
-    return;
+    const { pagePath } = this.props;
+    const {
+      search, hash,
+    } = window.location;
+
+    const label = `${pagePath}${search}${hash}`;
+    const permalink = this.generatePermalink();
+
+    return `[${label}](${permalink})`;
   }
   }
 
 
+  DropdownItemContents = ({ title, contents }) => (
+    <>
+      <div className="h6 mt-1 mb-2"><strong>{title}</strong></div>
+      <div className="card well mb-1 p-2">{contents}</div>
+    </>
+  );
+
   render() {
   render() {
-    const { t } = this.props;
+    const { t, pageId } = this.props;
+
+    const pagePathWithParams = this.generatePagePathWithParams();
+    const pagePathUrl = this.generatePagePathUrl();
+    const permalink = this.generatePermalink();
 
 
-    const safePagePath = this.xss.process(this.props.pagePath);
-    const url = this.generatePageUrl();
+    const { DropdownItemContents } = this;
 
 
     return (
     return (
       <>
       <>
-        <Dropdown id="copyPagePathDropdown" isOpen={this.state.dropdownOpen} toggle={this.toggle}>
+        <Dropdown id="copyPagePathDropdown" className="grw-copy-dropdown" isOpen={this.state.dropdownOpen} toggle={this.toggle}>
 
 
           <DropdownToggle
           <DropdownToggle
             caret
             caret
@@ -68,49 +108,55 @@ class CopyDropdown extends React.Component {
           </DropdownToggle>
           </DropdownToggle>
 
 
           <DropdownMenu>
           <DropdownMenu>
-            <h5 className="ml-3 my-0 text-muted">{ t('copy_to_clipboard.Copy to clipboard') }</h5>
-            <DropdownItem divider></DropdownItem>
+            <DropdownItem header className="px-3">{ t('copy_to_clipboard.Copy to clipboard') }</DropdownItem>
+
+            <DropdownItem divider className="my-0"></DropdownItem>
 
 
             {/* Page path */}
             {/* Page path */}
-            <CopyToClipboard text={this.props.pagePath} onCopy={this.showToolTip}>
-              <DropdownItem tag="a">
-                <div className="d-inline-flex flex-column">
-                  <h6 className="mt-1 mb-2"><strong>{ t('copy_to_clipboard.Page path') }</strong></h6>
-                  <span className="small">{safePagePath}</span>
-                </div>
+            <CopyToClipboard text={pagePathWithParams} onCopy={this.showToolTip}>
+              <DropdownItem className="px-3">
+                <DropdownItemContents title={t('copy_to_clipboard.Page path')} contents={pagePathWithParams} />
+              </DropdownItem>
+            </CopyToClipboard>
+
+            <DropdownItem divider className="my-0"></DropdownItem>
+
+            {/* Page path URL */}
+            <CopyToClipboard text={pagePathUrl} onCopy={this.showToolTip}>
+              <DropdownItem className="px-3">
+                <DropdownItemContents title={t('copy_to_clipboard.Page URL')} contents={pagePathUrl} />
               </DropdownItem>
               </DropdownItem>
             </CopyToClipboard>
             </CopyToClipboard>
+
+            <DropdownItem divider className="my-0"></DropdownItem>
+
             {/* Parmanent Link */}
             {/* Parmanent Link */}
-            { this.props.pageId && (
-              <CopyToClipboard text={url} onCopy={this.showToolTip}>
-                <DropdownItem tag="a">
-                  <div className="d-inline-flex flex-column">
-                    <h6 className="mt-1 mb-2"><strong>{ t('copy_to_clipboard.Parmanent link') }</strong></h6>
-                    <span className="small">{url}</span>
-                  </div>
+            { pageId && (
+              <CopyToClipboard text={permalink} onCopy={this.showToolTip}>
+                <DropdownItem className="px-3">
+                  <DropdownItemContents title={t('copy_to_clipboard.Parmanent link')} contents={permalink} />
                 </DropdownItem>
                 </DropdownItem>
               </CopyToClipboard>
               </CopyToClipboard>
             )}
             )}
+
+            <DropdownItem divider className="my-0"></DropdownItem>
+
             {/* Page path + Parmanent Link */}
             {/* Page path + Parmanent Link */}
-            { this.props.pageId && (
-              <CopyToClipboard text={`${this.props.pagePath}\n${url}`} onCopy={this.showToolTip}>
-                <DropdownItem tag="a">
-                  <div className="d-inline-flex flex-column">
-                    <h6 className="mt-1 mb-2"><strong>{ t('copy_to_clipboard.Page path and parmanent link') }</strong></h6>
-                    <span className="small mb-3">{safePagePath}</span>
-                    <span className="small">{url}</span>
-                  </div>
+            { pageId && (
+              <CopyToClipboard text={`${pagePathWithParams}\n${permalink}`} onCopy={this.showToolTip}>
+                <DropdownItem className="px-3">
+                  <DropdownItemContents title={t('copy_to_clipboard.Page path and parmanent link')} contents={<>{pagePathWithParams}<br />{permalink}</>} />
                 </DropdownItem>
                 </DropdownItem>
               </CopyToClipboard>
               </CopyToClipboard>
             )}
             )}
+
+            <DropdownItem divider className="my-0"></DropdownItem>
+
             {/* Markdown Link */}
             {/* Markdown Link */}
-            { this.props.pageId && (
-              <CopyToClipboard text={`[${this.props.pagePath}](${url})`} onCopy={this.showToolTip}>
-                <DropdownItem tag="a">
-                  <div className="d-inline-flex flex-column">
-                    <h6 className="mt-1 mb-2"><strong>{ t('copy_to_clipboard.Markdown link') }</strong></h6>
-                    <span className="small">{`[${safePagePath}](${url})`}</span>
-                  </div>
+            { pageId && (
+              <CopyToClipboard text={this.generateMarkdownLink()} onCopy={this.showToolTip}>
+                <DropdownItem className="px-3 text-wrap">
+                  <DropdownItemContents title={t('copy_to_clipboard.Markdown link')} contents={this.generateMarkdownLink()} isContentsWrap />
                 </DropdownItem>
                 </DropdownItem>
               </CopyToClipboard>
               </CopyToClipboard>
             )}
             )}

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

@@ -48,7 +48,7 @@ export default class TagEditor extends React.Component {
     return (
     return (
       <Modal isOpen={this.state.isOpenModal} toggle={this.closeModalHandler} id="edit-tag-modal">
       <Modal isOpen={this.state.isOpenModal} toggle={this.closeModalHandler} id="edit-tag-modal">
         <ModalHeader tag="h4" toggle={this.closeModalHandler} className="bg-primary text-light">
         <ModalHeader tag="h4" toggle={this.closeModalHandler} className="bg-primary text-light">
-          <span className="text-white">Edit Tags</span>
+          Edit Tags
         </ModalHeader>
         </ModalHeader>
         <ModalBody>
         <ModalBody>
           <TagsInput tags={this.state.tags} onTagsUpdated={this.onTagsUpdatedByTagsInput} />
           <TagsInput tags={this.state.tags} onTagsUpdated={this.onTagsUpdatedByTagsInput} />

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

@@ -261,7 +261,7 @@ export default class Editor extends AbstractEditor {
     return (
     return (
       <Modal isOpen={this.state.isCheatsheetModalShown} toggle={hideCheatsheetModal} className="modal-gfm-cheatsheet">
       <Modal isOpen={this.state.isCheatsheetModalShown} toggle={hideCheatsheetModal} className="modal-gfm-cheatsheet">
         <ModalHeader tag="h4" toggle={hideCheatsheetModal} className="bg-primary text-light">
         <ModalHeader tag="h4" toggle={hideCheatsheetModal} className="bg-primary text-light">
-          <span className="text-white"><i className="icon-fw icon-question" />Markdown Help</span>
+          <i className="icon-fw icon-question" />Markdown Help
         </ModalHeader>
         </ModalHeader>
         <ModalBody>
         <ModalBody>
           <Cheatsheet />
           <Cheatsheet />

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

@@ -12,8 +12,8 @@ export default class Page extends React.Component {
       page, noLink,
       page, noLink,
     } = this.props;
     } = this.props;
 
 
-    let pagePathElem = <PagePathLabel page={page} />;
-    if (!noLink != null) {
+    let pagePathElem = <PagePathLabel page={page} additionalClassNames={['mx-1']} />;
+    if (!noLink) {
       pagePathElem = <a className="text-break" href={page.path}>{pagePathElem}</a>;
       pagePathElem = <a className="text-break" href={page.path}>{pagePathElem}</a>;
     }
     }
 
 

+ 3 - 3
src/client/js/components/PageList/PagePathLabel.jsx

@@ -7,14 +7,14 @@ const PagePathLabel = (props) => {
 
 
   const dPagePath = new DevidedPagePath(props.page.path, false, true);
   const dPagePath = new DevidedPagePath(props.page.path, false, true);
 
 
-  let classNames = ['page-path'];
+  let classNames = [''];
   classNames = classNames.concat(props.additionalClassNames);
   classNames = classNames.concat(props.additionalClassNames);
 
 
   if (props.isLatterOnly) {
   if (props.isLatterOnly) {
     return <span className={classNames.join(' ')}>{dPagePath.latter}</span>;
     return <span className={classNames.join(' ')}>{dPagePath.latter}</span>;
   }
   }
 
 
-  const textElem = (dPagePath.former == null && dPagePath.latter == null)
+  const textElem = dPagePath.isRoot
     ? <><strong>/</strong></>
     ? <><strong>/</strong></>
     : <>{dPagePath.former}/<strong>{dPagePath.latter}</strong></>;
     : <>{dPagePath.former}/<strong>{dPagePath.latter}</strong></>;
 
 
@@ -24,7 +24,7 @@ const PagePathLabel = (props) => {
 PagePathLabel.propTypes = {
 PagePathLabel.propTypes = {
   page: PropTypes.object.isRequired,
   page: PropTypes.object.isRequired,
   isLatterOnly: PropTypes.bool,
   isLatterOnly: PropTypes.bool,
-  additionalClassNames: PropTypes.array,
+  additionalClassNames: PropTypes.arrayOf(PropTypes.string),
 };
 };
 
 
 PagePathLabel.defaultProps = {
 PagePathLabel.defaultProps = {

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

@@ -3,8 +3,6 @@ import PropTypes from 'prop-types';
 
 
 import { withTranslation } from 'react-i18next';
 import { withTranslation } from 'react-i18next';
 
 
-import * as entities from 'entities';
-
 import AppContainer from '../services/AppContainer';
 import AppContainer from '../services/AppContainer';
 import { createSubscribedElement } from './UnstatedUtils';
 import { createSubscribedElement } from './UnstatedUtils';
 
 
@@ -77,14 +75,7 @@ class PageTimeline extends React.Component {
       return null;
       return null;
     }
     }
 
 
-    let pages = JSON.parse(pageIdsElm.text);
-    // decode path
-    pages = pages.map((page) => {
-      page.path = decodeURIComponent(entities.decodeHTML(page.path));
-      return page;
-    });
-
-    return pages;
+    return JSON.parse(pageIdsElm.text);
   }
   }
 
 
   render() {
   render() {

+ 1 - 1
src/client/js/components/SearchPage/SearchResult.jsx

@@ -195,7 +195,7 @@ class SearchResult extends React.Component {
                 )
                 )
               }
               }
               <div className="page-list-option">
               <div className="page-list-option">
-                <a href={page.path}><i className="icon-login" /></a>
+                <button type="button" className="btn btn-link p-0" href={page.path}><i className="icon-login" /></button>
               </div>
               </div>
             </div>
             </div>
           </a>
           </a>

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

@@ -164,7 +164,7 @@ class SearchTypeahead extends React.Component {
     return (
     return (
       <span>
       <span>
         <UserPicture user={page.lastUpdateUser} size="sm" noLink />
         <UserPicture user={page.lastUpdateUser} size="sm" noLink />
-        <PagePathLabel page={page} />
+        <span className="ml-1"><PagePathLabel page={page} /></span>
         <PageListMeta page={page} />
         <PageListMeta page={page} />
       </span>
       </span>
     );
     );

+ 9 - 8
src/client/js/legacy/crowi.js

@@ -255,16 +255,17 @@ $(() => {
     if (name.match(/.+\/$/)) {
     if (name.match(/.+\/$/)) {
       name = name.substr(0, name.length - 1);
       name = name.substr(0, name.length - 1);
     }
     }
+    // TODO: remove by GW-2278
     window.location.href = `${pathUtils.encodePagePath(name)}#edit`;
     window.location.href = `${pathUtils.encodePagePath(name)}#edit`;
     return false;
     return false;
   });
   });
 
 
-  // rename/unportalize
-  $('#renamePage, #unportalize').on('shown.bs.modal', (e) => {
+  // rename
+  $('#renamePage').on('shown.bs.modal', (e) => {
     $('#renamePage #newPageName').focus();
     $('#renamePage #newPageName').focus();
-    $('#renamePage .msg, #unportalize .msg').hide();
+    $('#renamePage .msg').hide();
   });
   });
-  $('#renamePageForm, #unportalize-form').submit(function(e) {
+  $('#renamePageForm').submit(function(e) {
     // create name-value map
     // create name-value map
     const nameValueMap = {};
     const nameValueMap = {};
     $(this).serializeArray().forEach((obj) => {
     $(this).serializeArray().forEach((obj) => {
@@ -282,9 +283,9 @@ $(() => {
       // error
       // error
         if (!res.ok) {
         if (!res.ok) {
           const linkPath = pathUtils.normalizePath(nameValueMap.new_path);
           const linkPath = pathUtils.normalizePath(nameValueMap.new_path);
-          $('#renamePage .msg, #unportalize .msg').hide();
-          $(`#renamePage .msg-${res.code}, #unportalize .msg-${res.code}`).show();
-          $('#renamePage #linkToNewPage, #unportalize #linkToNewPage').html(`
+          $('#renamePage .msg').hide();
+          $(`#renamePage .msg-${res.code}`).show();
+          $('#renamePage #linkToNewPage').html(`
           <a href="${linkPath}">${linkPath} <i class="icon-login"></i></a>
           <a href="${linkPath}">${linkPath} <i class="icon-login"></i></a>
         `);
         `);
         }
         }
@@ -302,7 +303,7 @@ $(() => {
     $('#duplicatePage #duplicatePageName').focus();
     $('#duplicatePage #duplicatePageName').focus();
     $('#duplicatePage .msg').hide();
     $('#duplicatePage .msg').hide();
   });
   });
-  $('#duplicatePageForm, #unportalize-form').submit(function(e) {
+  $('#duplicatePageForm').submit(function(e) {
     // create name-value map
     // create name-value map
     const nameValueMap = {};
     const nameValueMap = {};
     $(this).serializeArray().forEach((obj) => {
     $(this).serializeArray().forEach((obj) => {

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

@@ -31,6 +31,7 @@ export default class PageContainer extends Container {
     }
     }
 
 
     const revisionId = mainContent.getAttribute('data-page-revision-id');
     const revisionId = mainContent.getAttribute('data-page-revision-id');
+    const path = decodeURI(mainContent.getAttribute('data-path'));
 
 
     this.state = {
     this.state = {
       // local page data
       // local page data
@@ -39,7 +40,7 @@ export default class PageContainer extends Container {
       revisionId,
       revisionId,
       revisionCreatedAt: +mainContent.getAttribute('data-page-revision-created'),
       revisionCreatedAt: +mainContent.getAttribute('data-page-revision-created'),
       revisionAuthor: JSON.parse(mainContent.getAttribute('data-page-revision-author')),
       revisionAuthor: JSON.parse(mainContent.getAttribute('data-page-revision-author')),
-      path: mainContent.getAttribute('data-path'),
+      path,
       tocHtml: '',
       tocHtml: '',
       isLiked: JSON.parse(mainContent.getAttribute('data-page-is-liked')),
       isLiked: JSON.parse(mainContent.getAttribute('data-page-is-liked')),
       seenUserIds: [],
       seenUserIds: [],

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

@@ -21,8 +21,6 @@
       }
       }
     }
     }
     .create-page {
     .create-page {
-      width: 8em;
-      text-align: center;
       background: #5584e1;
       background: #5584e1;
       border-radius: 0.35em;
       border-radius: 0.35em;
       &:hover {
       &:hover {

+ 16 - 18
src/client/styles/scss/_page_list.scss

@@ -1,4 +1,4 @@
-.page-list {
+body .page-list {
   .page-list-container {
   .page-list-container {
     font-size: 15px;
     font-size: 15px;
     line-height: 1.6em;
     line-height: 1.6em;
@@ -6,15 +6,12 @@
 
 
   .page-list-ul {
   .page-list-ul {
     padding-left: 0;
     padding-left: 0;
+    margin: 0;
 
 
     > li {
     > li {
+      margin: 0;
       list-style: none;
       list-style: none;
 
 
-      .picture {
-        width: 16px;
-        height: 16px;
-      }
-
       > a {
       > a {
         padding: 0px;
         padding: 0px;
         color: inherit;
         color: inherit;
@@ -22,22 +19,23 @@
         &:hover {
         &:hover {
           color: inherit;
           color: inherit;
         }
         }
+      }
+    }
 
 
-        span.page-path {
-          padding: 0 4px;
-        }
+    .picture {
+      width: 16px;
+      height: 16px;
+    }
 
 
-        > span.page-list-meta {
-          font-size: 0.9em;
+    .page-list-meta {
+      font-size: 0.9em;
 
 
-          > span {
-            margin-right: 0.3rem;
-          }
+      > span {
+        margin-right: 0.3rem;
+      }
 
 
-          i {
-            margin-right: 2px;
-          }
-        }
+      i {
+        margin-right: 2px;
       }
       }
     }
     }
 
 

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

@@ -5,8 +5,6 @@
   max-width: initial !important;
   max-width: initial !important;
   height: 80vh !important;
   height: 80vh !important;
 
 
-  font-family: 'Press Start 2P', $font-family-for-staff-credit;
-  color: white;
   // see https://css-tricks.com/old-timey-terminal-styling/
   // see https://css-tricks.com/old-timey-terminal-styling/
   @mixin old-timey-terminal-styling() {
   @mixin old-timey-terminal-styling() {
     text-shadow: 0 0 10px #c8c8c8;
     text-shadow: 0 0 10px #c8c8c8;

+ 19 - 0
src/client/styles/scss/molecules/copy-dropdown.scss

@@ -0,0 +1,19 @@
+.grw-copy-dropdown {
+  .dropdown-menu {
+    .dropdown-header {
+      margin-bottom: 0.5em;
+      font-size: 1.1em;
+    }
+
+    // unset active styles
+    .dropdown-item:active {
+      color: unset;
+      background-color: unset;
+    }
+
+    .well {
+      font-size: 0.6em;
+      word-break: break-all;
+    }
+  }
+}

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

@@ -20,6 +20,9 @@
 @import 'atoms/spinners';
 @import 'atoms/spinners';
 @import 'atoms/custom_control';
 @import 'atoms/custom_control';
 
 
+// molecules
+@import 'molecules/copy-dropdown';
+
 // growi component
 // growi component
 @import 'admin';
 @import 'admin';
 @import 'attachments';
 @import 'attachments';

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

@@ -125,14 +125,6 @@ body.kibela {
   /* Modal */
   /* Modal */
   .modal-content {
   .modal-content {
     background-color: $themelight;
     background-color: $themelight;
-
-    .modal-header.bg-primary {
-      color: white;
-
-      .close {
-        color: white;
-      }
-    }
   }
   }
 
 
   /* Inline Code */
   /* Inline Code */

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

@@ -158,6 +158,7 @@ $input-focus-color: $color-global;
   .modal-header {
   .modal-header {
     border-bottom-color: $border-color-theme;
     border-bottom-color: $border-color-theme;
     .close {
     .close {
+      color: $light;
       opacity: 0.5;
       opacity: 0.5;
       &:hover {
       &:hover {
         opacity: 0.9;
         opacity: 0.9;

+ 0 - 8
src/client/styles/scss/theme/blue-night.scss

@@ -1,8 +0,0 @@
-// import colors
-@import '../../agile-admin/inverse/colors/blue-night';
-
-// apply agile-admin theme
-@import '../../agile-admin/inverse/style';
-
-// override
-@import 'override-agileadmin';

+ 210 - 6
src/client/styles/scss/theme/christmas.scss

@@ -1,8 +1,212 @@
-// import colors
-@import '../../agile-admin/inverse/colors/christmas';
+@import '../variables';
+@import '../override-bootstrap-variables';
 
 
-// apply agile-admin theme
-@import '../../agile-admin/inverse/style';
+// == Define Bootstrap theme colors
+//
 
 
-// override
-@import 'override-agileadmin';
+// colors for overriding bootstrap $theme-colors
+// $secondary: #;
+// $info: #;
+// $success: #;
+// $warning: #;
+// $danger: #;
+// $light: #;
+// $dark: #;
+$themecolor: #b3000c;
+$themelight: white;
+
+$subthemecolor: #017e20;
+$bgcolor-navbar: $themecolor;
+$bgcolor-global: $themelight;
+$linktext: lighten(#0d5901, 5%);
+$linktext-hover: lighten($linktext, 12%);
+$sidebar-text: #ffffff;
+$fillcolor-logo-mark: lighten(desaturate($themecolor, 50%), 50%);
+$color-link-wiki: lighten($themecolor, 5%);
+$color-link-wiki-hover: lighten($color-link-wiki, 15%);
+$color-inline-code: darken($subthemecolor, 5%);
+$bgcolor-inline-code: lighten($subthemecolor, 70%);
+$active-nav-tabs-bgcolor: white;
+
+.growi:not(.login-page) {
+  // add background-image
+  #page-wrapper,
+  .page-editor-preview-container {
+    background-image: url('/images/themes/christmas/christmas.jpg');
+    background-attachment: fixed;
+    background-position: center center;
+    background-size: cover;
+  }
+}
+
+//== Light Mode
+//
+html[light],
+html[dark] {
+  // Background colors
+  $bgcolor-card: #f5f5f5;
+
+  // Font colors
+  $color-global: $subthemecolor;
+  $color-reversal: #eeeeee;
+  $color-link: lighten($color-global, 2%);
+  $color-link-hover: lighten($color-link, 20%);
+  $color-link-nabvar: $color-reversal;
+
+  // List Group colors
+  $color-list: $color-global;
+  $bgcolor-list: $themelight;
+  $color-list-active: $color-reversal;
+  $bgcolor-list-active: $themecolor;
+  $color-list-hover: $color-reversal;
+
+  // Logo colors
+  $bgcolor-logo: $themecolor;
+
+  // 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: $themecolor;
+  $color-sidebar-context: $color-reversal;
+  $bgcolor-sidebar-context: lighten($themecolor, 10%);
+
+  @import 'apply-colors';
+  @import 'apply-colors-light';
+
+  // change color of highlighted header in wiki (default: orange)
+  .wiki {
+    .code-line.revision-head.highlighted {
+      color: $themelight;
+      background-color: lighten($themecolor, 20%);
+
+      .icon-note,
+      .icon-link {
+        color: $themelight;
+      }
+    }
+  }
+
+  .sidebar {
+    background: $themecolor;
+  }
+
+  .rbt-menu {
+    background: $themelight;
+  }
+
+  #wrapper > #page-wrapper,
+  .page-editor-preview-container {
+    background-image: url('/images/themes/christmas/christmas.jpg');
+    background-attachment: fixed;
+    background-size: cover;
+  }
+
+  /*
+  * Tabs
+  */
+  body:not(.on-edit) .nav.nav-tabs {
+    > li.active > a {
+      background: linear-gradient(
+        rgba($active-nav-tabs-bgcolor, 0) 0%,
+        rgba($active-nav-tabs-bgcolor, 0) 90%,
+        $active-nav-tabs-bgcolor 100%
+      ); // overwrite only the bottom pixel
+    }
+  }
+
+  // login page
+  .nologin {
+    .input-group {
+      .input-group-addon {
+        background-color: rgba(lighten(black, 10%), 0.6);
+      }
+      .form-control {
+        background-color: rgba(lighten(black, 10%), 0.6);
+      }
+    }
+
+    &.login-page {
+      .login-header,
+      .login-dialog {
+        background-color: rgba(#ccc, 0.5);
+      }
+      .link-switch {
+        color: #bd3425;
+      }
+    }
+  }
+
+  /*
+  * Modal
+  */
+  .modal-dialog .modal-header.bg-primary {
+    background-image: url('/images/themes/christmas/christmas-navbar.jpg');
+    border-bottom: 2px solid $subthemecolor;
+  }
+
+  /*
+  * Panel
+  */
+  .panel {
+    &.panel-white,
+    &.panel-default {
+      border-color: $subthemecolor;
+      .panel-heading {
+        color: $dark;
+        background-color: $subthemecolor;
+        border-bottom: 1px solid $subthemecolor;
+      }
+    }
+  }
+
+  .panel.panel-primary {
+    border-color: #bd3425;
+    .panel-heading {
+      color: white;
+      background-color: $themecolor;
+      background-image: url('/images/themes/christmas/christmas-navbar.jpg');
+    }
+  }
+
+  .grw-navbar {
+    background-image: url('/images/themes/christmas/christmas-navbar.jpg');
+    border-bottom: 4px solid $subthemecolor;
+  }
+
+  .grw-subnav {
+    background-color: #ffffff;
+  }
+
+  .nav-tabs {
+    border-bottom-color: $themecolor;
+  }
+
+  .nav-link {
+    border-color: $themecolor;
+  }
+
+  .nav-tabs .nav-link.active {
+    background: none;
+    border-color: $themecolor;
+  }
+
+  .search-top .dropdown-toggle {
+    color: black;
+    background-color: hsla(0,0%,100%,.8);
+  }
+}

+ 0 - 2
src/server/crowi/express-init.js

@@ -10,7 +10,6 @@ module.exports = function(crowi, app) {
   const methodOverride = require('method-override');
   const methodOverride = require('method-override');
   const passport = require('passport');
   const passport = require('passport');
   const expressSession = require('express-session');
   const expressSession = require('express-session');
-  const sanitizer = require('express-sanitizer');
   const flash = require('connect-flash');
   const flash = require('connect-flash');
   const swig = require('swig-templates');
   const swig = require('swig-templates');
   const webpackAssets = require('express-webpack-assets');
   const webpackAssets = require('express-webpack-assets');
@@ -93,7 +92,6 @@ module.exports = function(crowi, app) {
   app.use(methodOverride());
   app.use(methodOverride());
   app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' }));
   app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' }));
   app.use(bodyParser.json({ limit: '50mb' }));
   app.use(bodyParser.json({ limit: '50mb' }));
-  app.use(sanitizer());
   app.use(cookieParser());
   app.use(cookieParser());
 
 
   // configure express-session
   // configure express-session

+ 1 - 1
src/server/models/config.js

@@ -107,7 +107,7 @@ module.exports = function(crowi) {
       'customize:highlightJsStyle' : 'github',
       'customize:highlightJsStyle' : 'github',
       'customize:highlightJsStyleBorder' : false,
       'customize:highlightJsStyleBorder' : false,
       'customize:theme' : 'default',
       'customize:theme' : 'default',
-      'customize:layout' : 'crowi',
+      'customize:layout' : 'growi',
       'customize:isEnabledTimeline' : true,
       'customize:isEnabledTimeline' : true,
       'customize:isSavedStatesOfTabChanges' : true,
       'customize:isSavedStatesOfTabChanges' : true,
       'customize:isEnabledAttachTitleHeader' : false,
       'customize:isEnabledAttachTitleHeader' : false,

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

@@ -90,13 +90,13 @@ module.exports = (crowi) => {
   const validator = {
   const validator = {
     themeAssetPath: [
     themeAssetPath: [
       query('themeName').isString().isIn([
       query('themeName').isString().isIn([
-        'default', 'nature', 'mono-blue', 'wood', 'island', 'christmas', 'antarctic', 'future', 'blue-night', 'halloween', 'spring',
+        'default', 'nature', 'mono-blue', 'wood', 'island', 'christmas', 'antarctic', 'future', 'halloween', 'spring',
       ]),
       ]),
     ],
     ],
     layoutTheme: [
     layoutTheme: [
       body('layoutType').isString().isIn(['growi', 'kibela']),
       body('layoutType').isString().isIn(['growi', 'kibela']),
       body('themeType').isString().isIn([
       body('themeType').isString().isIn([
-        'default', 'nature', 'mono-blue', 'wood', 'island', 'christmas', 'antarctic', 'future', 'blue-night', 'halloween', 'spring',
+        'default', 'nature', 'mono-blue', 'wood', 'island', 'christmas', 'antarctic', 'future', 'halloween', 'spring',
       ]),
       ]),
     ],
     ],
     function: [
     function: [

+ 1 - 1
src/server/routes/attachment.js

@@ -485,7 +485,7 @@ module.exports = function(crowi, app) {
    */
    */
   api.add = async function(req, res) {
   api.add = async function(req, res) {
     let pageId = req.body.page_id || null;
     let pageId = req.body.page_id || null;
-    const pagePath = decodeURIComponent(req.body.path) || null;
+    const pagePath = req.body.path || null;
     let pageCreated = false;
     let pageCreated = false;
 
 
     // check params
     // check params

+ 21 - 24
src/server/routes/page.js

@@ -146,7 +146,7 @@ module.exports = function(crowi, app) {
   const ApiResponse = require('../util/apiResponse');
   const ApiResponse = require('../util/apiResponse');
   const getToday = require('../util/getToday');
   const getToday = require('../util/getToday');
 
 
-  const { slackNotificationService } = crowi;
+  const { slackNotificationService, configManager } = crowi;
   const interceptorManager = crowi.getInterceptorManager();
   const interceptorManager = crowi.getInterceptorManager();
   const globalNotificationService = crowi.getGlobalNotificationService();
   const globalNotificationService = crowi.getGlobalNotificationService();
 
 
@@ -252,7 +252,6 @@ module.exports = function(crowi, app) {
 
 
   function addRendarVarsForPage(renderVars, page) {
   function addRendarVarsForPage(renderVars, page) {
     renderVars.page = page;
     renderVars.page = page;
-    renderVars.path = page.path;
     renderVars.revision = page.revision;
     renderVars.revision = page.revision;
     renderVars.author = page.revision.author;
     renderVars.author = page.revision.author;
     renderVars.pageIdOnHackmd = page.pageIdOnHackmd;
     renderVars.pageIdOnHackmd = page.pageIdOnHackmd;
@@ -298,7 +297,7 @@ module.exports = function(crowi, app) {
       seener_threshold: SEENER_THRESHOLD,
       seener_threshold: SEENER_THRESHOLD,
     };
     };
     renderVars.pager = generatePager(result.offset, result.limit, result.totalCount);
     renderVars.pager = generatePager(result.offset, result.limit, result.totalCount);
-    renderVars.pages = pathUtils.encodePagesPath(result.pages);
+    renderVars.pages = result.pages;
   }
   }
 
 
   function replacePlaceholdersOfTemplate(template, req) {
   function replacePlaceholdersOfTemplate(template, req) {
@@ -334,11 +333,12 @@ module.exports = function(crowi, app) {
     return res.render('page_presentation', renderVars);
     return res.render('page_presentation', renderVars);
   }
   }
 
 
-  async function showPageListForCrowiBehavior(req, res, next) {
-    const portalPath = pathUtils.addTrailingSlash(getPathFromRequest(req));
+  async function showTopPage(req, res, next) {
+    const portalPath = req.path;
     const revisionId = req.query.revision;
     const revisionId = req.query.revision;
+    const layoutName = configManager.getConfig('crowi', 'customize:layout');
 
 
-    const view = 'customlayout-selector/page_list';
+    const view = `layout-${layoutName}/page_list`;
     const renderVars = { path: portalPath };
     const renderVars = { path: portalPath };
 
 
     let portalPage = await Page.findByPathAndViewer(portalPath, req.user);
     let portalPage = await Page.findByPathAndViewer(portalPath, req.user);
@@ -362,6 +362,7 @@ module.exports = function(crowi, app) {
   async function showPageForGrowiBehavior(req, res, next) {
   async function showPageForGrowiBehavior(req, res, next) {
     const path = getPathFromRequest(req);
     const path = getPathFromRequest(req);
     const revisionId = req.query.revision;
     const revisionId = req.query.revision;
+    const layoutName = configManager.getConfig('crowi', 'customize:layout');
 
 
     let page = await Page.findByPathAndViewer(path, req.user);
     let page = await Page.findByPathAndViewer(path, req.user);
 
 
@@ -372,7 +373,7 @@ module.exports = function(crowi, app) {
     }
     }
     if (page.redirectTo) {
     if (page.redirectTo) {
       debug(`Redirect to '${page.redirectTo}'`);
       debug(`Redirect to '${page.redirectTo}'`);
-      return res.redirect(encodeURI(`${page.redirectTo}?redirectFrom=${pathUtils.encodePagePath(path)}`));
+      return res.redirect(`${encodeURI(page.redirectTo)}?redirectFrom=${encodeURIComponent(path)}`);
     }
     }
 
 
     logger.debug('Page is found when processing pageShowForGrowiBehavior', page._id, page.path);
     logger.debug('Page is found when processing pageShowForGrowiBehavior', page._id, page.path);
@@ -381,7 +382,7 @@ module.exports = function(crowi, app) {
     const offset = parseInt(req.query.offset) || 0;
     const offset = parseInt(req.query.offset) || 0;
     const renderVars = {};
     const renderVars = {};
 
 
-    let view = 'customlayout-selector/page';
+    let view = `layout-${layoutName}/page`;
 
 
     page.initLatestRevisionField(revisionId);
     page.initLatestRevisionField(revisionId);
 
 
@@ -395,7 +396,7 @@ module.exports = function(crowi, app) {
 
 
     if (isUserPage(page.path)) {
     if (isUserPage(page.path)) {
       // change template
       // change template
-      view = 'customlayout-selector/user_page';
+      view = `layout-${layoutName}/user_page`;
       await addRenderVarsForUserPage(renderVars, page, req.user);
       await addRenderVarsForUserPage(renderVars, page, req.user);
     }
     }
 
 
@@ -414,19 +415,15 @@ module.exports = function(crowi, app) {
   };
   };
 
 
   actions.showTopPage = function(req, res) {
   actions.showTopPage = function(req, res) {
-    return showPageListForCrowiBehavior(req, res);
+    return showTopPage(req, res);
   };
   };
 
 
   /**
   /**
-   * switch action by behaviorType
+   * Redirect to the page without trailing slash
    */
    */
-  /* eslint-disable no-else-return */
   actions.showPageWithEndOfSlash = function(req, res, next) {
   actions.showPageWithEndOfSlash = function(req, res, next) {
-    const path = getPathFromRequest(req); // end of slash should be omitted
-    // redirect and showPage action will be triggered
-    return res.redirect(path);
+    return res.redirect(pathUtils.removeTrailingSlash(req.path));
   };
   };
-  /* eslint-enable no-else-return */
 
 
   /**
   /**
    * switch action
    * switch action
@@ -476,18 +473,19 @@ module.exports = function(crowi, app) {
     const path = getPathFromRequest(req);
     const path = getPathFromRequest(req);
 
 
     const isCreatable = Page.isCreatableName(path);
     const isCreatable = Page.isCreatableName(path);
+    const layoutName = configManager.getConfig('crowi', 'customize:layout');
 
 
     let view;
     let view;
     const renderVars = { path };
     const renderVars = { path };
 
 
     if (!isCreatable) {
     if (!isCreatable) {
-      view = 'customlayout-selector/not_creatable';
+      view = `layout-${layoutName}/not_creatable`;
     }
     }
     else if (req.isForbidden) {
     else if (req.isForbidden) {
-      view = 'customlayout-selector/forbidden';
+      view = `layout-${layoutName}/forbidden`;
     }
     }
     else {
     else {
-      view = 'customlayout-selector/not_found';
+      view = `layout-${layoutName}/not_found`;
 
 
       // retrieve templates
       // retrieve templates
       if (req.user != null) {
       if (req.user != null) {
@@ -518,6 +516,7 @@ module.exports = function(crowi, app) {
   actions.deletedPageListShow = async function(req, res) {
   actions.deletedPageListShow = async function(req, res) {
     // normalizePath makes '/trash/' -> '/trash'
     // normalizePath makes '/trash/' -> '/trash'
     const path = pathUtils.normalizePath(`/trash${getPathFromRequest(req)}`);
     const path = pathUtils.normalizePath(`/trash${getPathFromRequest(req)}`);
+    const layoutName = configManager.getConfig('crowi', 'customize:layout');
 
 
     const limit = 50;
     const limit = 50;
     const offset = parseInt(req.query.offset) || 0;
     const offset = parseInt(req.query.offset) || 0;
@@ -541,8 +540,8 @@ module.exports = function(crowi, app) {
     }
     }
 
 
     renderVars.pager = generatePager(result.offset, result.limit, result.totalCount);
     renderVars.pager = generatePager(result.offset, result.limit, result.totalCount);
-    renderVars.pages = pathUtils.encodePagesPath(result.pages);
-    res.render('customlayout-selector/page_list', renderVars);
+    renderVars.pages = result.pages;
+    res.render(`layout-${layoutName}/page_list`, renderVars);
   };
   };
 
 
   /**
   /**
@@ -554,7 +553,7 @@ module.exports = function(crowi, app) {
     const page = await Page.findByIdAndViewer(id, req.user);
     const page = await Page.findByIdAndViewer(id, req.user);
 
 
     if (page != null) {
     if (page != null) {
-      return res.redirect(pathUtils.encodePagePath(page.path));
+      return res.redirect(encodeURI(page.path));
     }
     }
 
 
     return res.redirect('/');
     return res.redirect('/');
@@ -650,7 +649,6 @@ module.exports = function(crowi, app) {
         result.pages.pop();
         result.pages.pop();
       }
       }
 
 
-      result.pages = pathUtils.encodePagesPath(result.pages);
       return res.json(ApiResponse.success(result));
       return res.json(ApiResponse.success(result));
     }
     }
     catch (err) {
     catch (err) {
@@ -1613,7 +1611,6 @@ module.exports = function(crowi, app) {
 
 
     try {
     try {
       const result = await Page.findListByCreator(page.creator, req.user, queryOptions);
       const result = await Page.findListByCreator(page.creator, req.user, queryOptions);
-      result.pages = pathUtils.encodePagesPath(result.pages);
 
 
       return res.json(ApiResponse.success(result));
       return res.json(ApiResponse.success(result));
     }
     }

+ 1 - 2
src/server/util/swigFunctions.js

@@ -1,7 +1,6 @@
 module.exports = function(crowi, req, locals) {
 module.exports = function(crowi, req, locals) {
   const debug = require('debug')('growi:lib:swigFunctions');
   const debug = require('debug')('growi:lib:swigFunctions');
   const stringWidth = require('string-width');
   const stringWidth = require('string-width');
-  const entities = require('entities');
 
 
   const { pathUtils } = require('growi-commons');
   const { pathUtils } = require('growi-commons');
 
 
@@ -179,7 +178,7 @@ module.exports = function(crowi, req, locals) {
     return pages.map((page) => {
     return pages.map((page) => {
       return {
       return {
         id: page.id,
         id: page.id,
-        path: entities.encodeHTML(page.path),
+        path: page.path,
         revision: page.revision,
         revision: page.revision,
       };
       };
     });
     });

+ 0 - 317
src/server/views/admin/Users_reserve.html

@@ -1,317 +0,0 @@
-{% extends '../layout/admin.html' %}
-
-{% block html_title %}{{ customizeService.generateCustomTitle(t('User_Management')) }}{% endblock %}
-
-{% block content_header %}
-<h1 class="title">{{ t('User_Management') }}</h1>
-{% endblock %}
-
-{% block content_main %}
-<div class="content-main">
-  {% set smessage = req.flash('successMessage') %}
-  {% if smessage.length %}
-  <div class="alert alert-success">
-    {{ smessage }}
-  </div>
-  {% endif %}
-
-  {% set emessage = req.flash('errorMessage') %}
-  {% if emessage.length %}
-  <div class="alert alert-danger">
-    {{ emessage }}
-  </div>
-  {% endif %}
-
-  <div class="row">
-    <div class="col-md-3">
-      {% include './widget/menu.html' with {current: 'user'} %}
-    </div>
-
-    <div class="col-lg-9">
-      <p>
-        <button data-toggle="collapse" class="btn btn-default" href="#inviteUserForm" {% if isUserCountExceedsUpperLimit %}disabled{% endif %}>
-          {{ t("user_management.invite_users") }}
-        </button>
-        <a class="btn btn-default btn-outline" href="/admin/users/external-accounts">
-          <i class="icon-user-follow" aria-hidden="true"></i>
-          {{ t("user_management.external_account") }}
-        </a>
-      </p>
-      <form role="form" action="/admin/user/invite" method="post">
-        <div id="inviteUserForm" class="collapse">
-          <div class="form-group">
-            <label for="inviteForm[emailList]">{{ t('user_management.emails') }}</label>
-            <textarea class="form-control" name="inviteForm[emailList]" placeholder="{{ t('eg') }} user@growi.org"></textarea>
-          </div>
-          <div class="checkbox checkbox-info">
-            <input type="checkbox" id="inviteWithEmail" name="inviteForm[sendEmail]" checked>
-            <label for="inviteWithEmail">{{ t('user_management.invite_thru_email') }}</label>
-          </div>
-          <button type="submit" class="btn btn-primary">{{ t('user_management.invite') }}</button>
-        </div>
-        <input type="hidden" name="_csrf" value="{{ csrf() }}">
-      </form>
-
-      {% if isUserCountExceedsUpperLimit === true %}
-      <label>{{ t('user_management.cannot_invite_maximum_users') }}</label>
-      {% endif %}
-      {% if userUpperLimit !== 0 %}
-      <label>{{ t('user_management.current_users') }}{{ activeUsers }}</label>
-      {% endif %}
-
-      {% set createdUser = req.flash('createdUser') %}
-      {% if createdUser.length %}
-      <div class="modal fade" id="createdUserModal">
-        <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">{{ t('user_management.invited') }}</div>
-            </div>
-
-            <div class="modal-body">
-              <p>
-                {{ t('user_management.temporary_password') }}<br>
-                {{ t('user_management.password_never_seen') }}<span class="text-danger">{{ t('user_management.send_temporary_password') }}</span>
-              </p>
-
-              <pre>{% for cUser in createdUser %}{% if cUser.user %}{{ cUser.email }} {{ cUser.password }}<br>{% else %}{{ cUser.email }} 作成失敗<br>{% endif %}{% endfor %}</pre>
-            </div>
-
-          </div><!-- /.modal-content -->
-        </div><!-- /.modal-dialog -->
-      </div><!-- /.modal -->
-      {% endif %}
-
-      {# FIXME とりあえずクソ実装。React化はやくしたいなー(チラッチラッ #}
-      <div class="modal fade" id="admin-password-reset-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">{{ t('user_management.reset_password')}}</div>
-            </div>
-
-            <div class="modal-body">
-              <p>
-                {{ t('user_management.password_never_seen') }}<br>
-              <span class="text-danger">{{ t('user_management.send_new_password') }}</span>
-              </p>
-              <p>
-              {{ t('user_management.target_user') }}: <code id="admin-password-reset-user"></code>
-              </p>
-
-              <form method="post" id="admin-users-reset-password">
-                <input type="hidden" name="user_id" value="">
-                <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                <button type="submit" value="" class="btn btn-primary">
-                  {{ t('user_management.reset_password')}}
-                </button>
-              </form>
-
-            </div>
-
-          </div><!-- /.modal-content -->
-        </div>/.modal-dialog
-      </div>
-      <div class="modal fade" id="admin-password-reset-modal-done">
-        <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">{{ t('user_management.reset_password') }}</div>
-            </div>
-
-            <div class="modal-body">
-              <p class="alert alert-danger">Let the user know the new password below and strongly recommend to change another one immediately. </p>
-              <p>
-              Reset user: <code id="admin-password-reset-done-user"></code>
-              </p>
-              <p>
-              New password: <code id="admin-password-reset-done-password"></code>
-              </p>
-            </div>
-            <div class="modal-footer">
-              <button class="btn btn-primary" data-dismiss="modal">OK</button>
-            </div>
-          </div><!-- /.modal-content -->
-        </div><!-- /.modal-dialog -->
-      </div>
-
-      <h2>{{ t("User_Management") }}</h2>
-
-      <table class="table table-default table-bordered table-user-list">
-        <thead>
-          <tr>
-            <th width="100px">#</th>
-            <th>{{ t('status') }}</th>
-            <th><code>{{ t('User') }}</code></th>
-            <th>{{ t('Name') }}</th>
-            <th>{{ t('Email') }}</th>
-            <th width="100px">{{ t('Created') }}</th>
-            <th width="150px">{{ t('Last_Login') }}</th>
-            <th width="70px"></th>
-          </tr>
-        </thead>
-        <tbody>
-          {% for sUser in users %}
-          {% set sUserId = sUser._id.toString() %}
-          <tr>
-            <td>
-              <img src="{{ sUser|picture }}" class="picture rounded-circle" />
-              {% if sUser.admin %}
-              <span class="badge badge-dark label-admin">
-              {{ t('administrator') }}
-              </span>
-              {% endif %}
-            </td>
-            <td>
-              <span class="label {{ css.userStatus(sUser) }}">
-                {{ consts.userStatus[sUser.status] }}
-              </span>
-            </td>
-            <td>
-              <strong>{{ sUser.username }}</strong>
-            </td>
-            <td>{{ sUser.name }}</td>
-            <td>{{ sUser.email }}</td>
-            <td>{{ sUser.createdAt|date('Y-m-d', sUser.createdAt.getTimezoneOffset()) }}</td>
-            <td>
-              {% if sUser.lastLoginAt %}
-                {{ sUser.lastLoginAt|date('Y-m-d H:i', sUser.createdAt.getTimezoneOffset()) }}
-              {% endif %}
-            </td>
-            <td>
-              <div class="btn-group admin-user-menu">
-                <button type="button" class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown">
-                  <i class="icon-settings"></i> <span class="caret"></span>
-                </button>
-                <ul class="dropdown-menu" role="menu">
-                  <li class="dropdown-header">{{ t('user_management.edit_menu') }}</li>
-                  <li>
-                    <a href="#"
-                        data-user-id="{{ sUserId }}"
-                        data-user-email="{{ sUser.email }}"
-                        data-target="#admin-password-reset-modal"
-                        data-toggle="modal">
-                      <i class="icon-fw icon-key"></i>
-                      {{ t('user_management.reset_password') }}
-                    </a>
-                  </li>
-                  <li class="divider"></li>
-                  <li class="dropdown-header">{{ t('status') }}</li>
-
-                  {% if sUser.status == 1 %}
-                  <form id="form_activate_{{ sUserId }}" action="/admin/user/{{ sUserId }}/activate" method="post">
-                    <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                  </form>
-                  <li>
-                    <a href="javascript:form_activate_{{ sUserId }}.submit()">
-                      <i class="icon-fw icon-user-following"></i> {{ t('user_management.accept') }}
-                    </a>
-                  </li>
-                  {% endif  %}
-
-                  {% if sUser.status == 2 %}
-                  <form id="form_suspend_{{ sUserId }}" action="/admin/user/{{ sUserId }}/suspend" method="post">
-                    <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                  </form>
-                  <li>
-                    {% if sUser.username != user.username %}
-                    <a href="javascript:form_suspend_{{ sUserId }}.submit()">
-                      <i class="icon-fw icon-ban"></i>
-                      {{ t('user_management.deactivate_account') }}
-                    </a>
-                    {% else %}
-                    <a disabled>
-                      <i class="icon-fw icon-ban"></i>
-                      {{ t('user_management.deactivate_account') }}
-                    </a>
-                    <p class="alert alert-danger m-l-10 m-r-10 p-10">{{ t("user_management.your_own") }}</p>
-                    {% endif %}
-                  </li>
-                  {% endif %}
-
-                  {% if sUser.status == 3 %}
-                  <form id="form_activate_{{ sUserId }}" action="/admin/user/{{ sUserId }}/activate" method="post">
-                    <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                  </form>
-                  <form id="form_remove_{{ sUserId }}" action="/admin/user/{{ sUserId }}/remove" method="post">
-                    <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                  </form>
-                  <li>
-                    <a href="javascript:form_activate_{{ sUserId }}.submit()">
-                      <i class="icon-fw icon-action-redo"></i> {{ t('Undo') }}
-                    </a>
-                  </li>
-                  <li>
-                    {# label は同じだけど、こっちは論理削除 #}
-                    <a href="javascript:form_remove_{{ sUserId }}.submit()">
-                      <i class="icon-fw icon-fire text-danger"></i> {{ t('Delete') }}
-                    </a>
-                  </li>
-                  {% endif %}
-
-                  {% if sUser.status == 1 || sUser.status == 5 %}
-                  <form id="form_removeCompletely_{{ sUserId }}" action="/admin/user/{{ sUser._id.toString() }}/removeCompletely" method="post">
-                    <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                  </form>
-                  <li class="dropdown-button">
-                    {# label は同じだけど、こっちは物理削除 #}
-                    <a href="javascript:form_removeCompletely_{{ sUserId }}.submit()">
-                      <i class="icon-fw icon-fire text-danger"></i> {{ t('Delete') }}
-                    </a>
-                  </li>
-                  {% endif %}
-
-                  {% if sUser.status == 2 %} {# activated な人だけこのメニューを表示 #}
-                  <li class="divider"></li>
-                  <li class="dropdown-header">{{ t('user_management.administrator_menu') }}</li>
-
-                  {% if sUser.admin %}
-                  <form id="form_removeFromAdmin_{{ sUserId }}" action="/admin/user/{{ sUser._id.toString() }}/removeFromAdmin" method="post">
-                    <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                  </form>
-                  <li>
-                    {% if sUser.username != user.username %}
-                      <a href="javascript:form_removeFromAdmin_{{ sUserId }}.submit()">
-                        <i class="icon-fw icon-user-unfollow"></i> {{ t("user_management.remove_admin_access") }}
-                      </a>
-                    {% else %}
-                      <a disabled>
-                        <i class="icon-fw icon-user-unfollow"></i> {{ t("user_management.remove_admin_access") }}
-                      </a>
-                      <p class="alert alert-danger m-l-10 m-r-10 p-10">{{ t("user_management.cannot_remove") }}</p>
-                    {% endif %}
-                  </li>
-                  {% else %}
-                  <form id="form_makeAdmin_{{ sUserId }}" action="/admin/user/{{ sUser._id.toString() }}/makeAdmin" method="post">
-                    <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                  </form>
-                  <li>
-                    <a href="javascript:form_makeAdmin_{{ sUserId }}.submit()">
-                      <i class="icon-fw icon-magic-wand"></i> {{ t("user_management.give_admin_access") }}
-                    </a>
-                  </li>
-                  {% endif %}
-
-                  {% endif %}
-                </ul>
-              </div>
-            </td>
-          </tr>
-          {% endfor %}
-        </tbody>
-      </table>
-
-      {% include '../widget/pager.html' with {path: "/admin/users", pager: pager} %}
-
-    </div>
-  </div>
-</div>
-{% endblock content_main %}
-
-{% block content_footer %}
-{% endblock content_footer %} -->

+ 1 - 2
src/server/views/admin/markdown.html

@@ -1,7 +1,6 @@
 {% extends '../layout/admin.html' %}
 {% extends '../layout/admin.html' %}
 
 
-{% block html_title %}{{ customizeService.generateCustomTitle(t('Markdown settings')) }}
- · {{ path }}{% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle(t('Markdown settings')) }}{% endblock %}
 
 
 {% block content_header %}
 {% block content_header %}
 <h1 class="title">{{ t('Markdown Settings') }}</h1>
 <h1 class="title">{{ t('Markdown Settings') }}</h1>

+ 0 - 3
src/server/views/admin/search.html

@@ -13,9 +13,6 @@
       class="col-lg-9"
       class="col-lg-9"
       id ="admin-full-text-search-management"
       id ="admin-full-text-search-management"
     >
     >
-      <!-- Reactify Paginator start -->
-      <!-- {% include '../widget/pager.html' with {path: "/admin/search", pager: pager} %} -->
-      <!-- Reactify Paginator end -->
     </div>
     </div>
 </div>
 </div>
 
 

+ 0 - 7
src/server/views/customlayout-selector/forbidden.html

@@ -1,7 +0,0 @@
-{% if !getConfig('crowi', 'customize:layout') || 'crowi' === getConfig('crowi', 'customize:layout') %}
-  {% include '../layout-crowi/forbidden.html' %}
-{% elseif !getConfig('crowi', 'customize:layout') || 'kibela' === getConfig('crowi', 'customize:layout')%}
-  {% include '../layout-kibela/forbidden.html' %}
-{% else %}
-  {% include '../layout-growi/forbidden.html' %}
-{% endif %}

+ 0 - 7
src/server/views/customlayout-selector/not_creatable.html

@@ -1,7 +0,0 @@
-{% if !getConfig('crowi', 'customize:layout') || 'crowi' === getConfig('crowi', 'customize:layout') %}
-  {% include '../layout-crowi/not_creatable.html' %}
-{% elseif !getConfig('crowi', 'customize:layout') || 'kibela' === getConfig('crowi', 'customize:layout')%}
-  {% include '../layout-kibela/not_creatable.html' %}
-{% else %}
-  {% include '../layout-growi/not_creatable.html' %}
-{% endif %}

+ 0 - 7
src/server/views/customlayout-selector/not_found.html

@@ -1,7 +0,0 @@
-{% if !getConfig('crowi', 'customize:layout') || 'crowi' === getConfig('crowi', 'customize:layout') %}
-  {% include '../layout-crowi/not_found.html' %}
-{% elseif !getConfig('crowi', 'customize:layout') || 'kibela' === getConfig('crowi', 'customize:layout')%}
-  {% include '../layout-kibela/not_found.html' %}
-{% else %}
-  {% include '../layout-growi/not_found.html' %}
-{% endif %}

+ 0 - 7
src/server/views/customlayout-selector/page.html

@@ -1,7 +0,0 @@
-{% if !getConfig('crowi', 'customize:layout') || 'crowi' === getConfig('crowi', 'customize:layout') %}
-  {% include '../layout-crowi/page.html' %}
-{% elseif !getConfig('crowi', 'customize:layout') || 'kibela' === getConfig('crowi', 'customize:layout')%}
-  {% include '../layout-kibela/page.html' %}
-{% else %}
-  {% include '../layout-growi/page.html' %}
-{% endif %}

+ 0 - 7
src/server/views/customlayout-selector/page_list.html

@@ -1,7 +0,0 @@
-{% if !getConfig('crowi', 'customize:layout') || 'crowi' === getConfig('crowi', 'customize:layout') %}
-  {% include '../layout-crowi/page_list.html' %}
-{% elseif !getConfig('crowi', 'customize:layout') || 'kibela' === getConfig('crowi', 'customize:layout')%}
-  {% include '../layout-kibela/page_list.html' %}
-{% else %}
-  {% include '../layout-growi/page_list.html' %}
-{% endif %}

+ 0 - 7
src/server/views/customlayout-selector/user_page.html

@@ -1,7 +0,0 @@
-{% if !getConfig('crowi', 'customize:layout') || 'crowi' === getConfig('crowi', 'customize:layout') %}
-  {% include '../layout-crowi/user_page.html' %}
-{% elseif !getConfig('crowi', 'customize:layout') || 'kibela' === getConfig('crowi', 'customize:layout')%}
-  {% include '../layout-kibela/user_page.html' %}
-{% else %}
-  {% include '../layout-growi/user_page.html' %}
-{% endif %}

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

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

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

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

+ 1 - 1
src/server/views/layout/layout.html

@@ -108,7 +108,7 @@
 
 
       {% if user %}
       {% if user %}
       <li class="nav-item d-none d-md-block">
       <li class="nav-item d-none d-md-block">
-        <a class="nav-link create-page" href="#" data-target="#create-page" data-toggle="modal">
+        <a class="nav-link create-page px-4" href="#" data-target="#create-page" data-toggle="modal">
           <i class="icon-pencil mr-2"></i>
           <i class="icon-pencil mr-2"></i>
           <span>{{ t('New') }}</span>
           <span>{{ t('New') }}</span>
         </a>
         </a>

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

@@ -2,9 +2,9 @@
   <div class="modal-dialog modal-lg">
   <div class="modal-dialog modal-lg">
     <div class="modal-content">
     <div class="modal-content">
 
 
-      <div class="modal-header bg-primary">
-        <div class="modal-title text-white">{{ t('New Page') }}</div>
-        <button type="button" class="close text-white" data-dismiss="modal" aria-hidden="true">&times;</button>
+      <div class="modal-header bg-primary text-light">
+        <div class="modal-title">{{ t('New Page') }}</div>
+        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
       </div>
       </div>
 
 
       <div class="modal-body">
       <div class="modal-body">

+ 1 - 1
src/server/views/modal/create_template.html

@@ -4,7 +4,7 @@
   <div class="modal-dialog">
   <div class="modal-dialog">
     <div class="modal-content">
     <div class="modal-content">
 
 
-      <div class="modal-header bg-primary">
+      <div class="modal-header bg-primary text-light">
         <div class="modal-title">{{ t('template.modal_label.Create/Edit Template Page') }}</div>
         <div class="modal-title">{{ t('template.modal_label.Create/Edit Template Page') }}</div>
         <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
         <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
       </div>
       </div>

+ 1 - 1
src/server/views/modal/delete.html

@@ -4,7 +4,7 @@
 
 
       <form role="form" id="delete-page-form" onsubmit="return false;">
       <form role="form" id="delete-page-form" onsubmit="return false;">
 
 
-        <div class="modal-header {% if page.isDeleted() %}bg-danger{% else %}bg-primary{% endif %}">
+        <div class="modal-header text-light {% if page.isDeleted() %}bg-danger{% else %}bg-primary{% endif %}">
           <div class="modal-title">
           <div class="modal-title">
             {% if page.isDeleted() %}
             {% if page.isDeleted() %}
             <i class="icon-fw icon-fire"></i> {{ t('modal_delete.delete_completely') }}
             <i class="icon-fw icon-fire"></i> {{ t('modal_delete.delete_completely') }}

+ 1 - 1
src/server/views/modal/duplicate.html

@@ -4,7 +4,7 @@
 
 
       <form role="form" id="duplicatePageForm" onsubmit="return false;">
       <form role="form" id="duplicatePageForm" onsubmit="return false;">
 
 
-        <div class="modal-header bg-primary">
+        <div class="modal-header bg-primary text-light">
           <div class="modal-title">{{ t('modal_duplicate.label.Duplicate page') }}</div>
           <div class="modal-title">{{ t('modal_duplicate.label.Duplicate page') }}</div>
           <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
           <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
         </div>
         </div>

+ 1 - 1
src/server/views/modal/empty_trash.html

@@ -4,7 +4,7 @@
 
 
     <form role="form" id="empty-trash-form" onsubmit="return false;">
     <form role="form" id="empty-trash-form" onsubmit="return false;">
 
 
-      <div class="modal-header bg-danger">
+      <div class="modal-header bg-danger text-light">
         <div class="modal-title">
         <div class="modal-title">
           <i class="icon-fw icon-trash"></i>  {{ t('modal_empty.empty_the_trash') }}
           <i class="icon-fw icon-trash"></i>  {{ t('modal_empty.empty_the_trash') }}
         </div>
         </div>

+ 1 - 1
src/server/views/modal/put_back.html

@@ -4,7 +4,7 @@
 
 
       <form role="form" id="revert-delete-page-form" onsubmit="return false;">
       <form role="form" id="revert-delete-page-form" onsubmit="return false;">
 
 
-        <div class="modal-header bg-info">
+        <div class="modal-header bg-info text-light">
           <div class="modal-title"><i class="icon-action-undo"></i> {{ t('modal_putback.label.Put Back Page') }}</div>
           <div class="modal-title"><i class="icon-action-undo"></i> {{ t('modal_putback.label.Put Back Page') }}</div>
           <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
           <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
         </div>
         </div>

+ 1 - 1
src/server/views/modal/rename.html

@@ -4,7 +4,7 @@
 
 
       <form role="form" id="renamePageForm" onsubmit="return false;">
       <form role="form" id="renamePageForm" onsubmit="return false;">
 
 
-        <div class="modal-header bg-primary">
+        <div class="modal-header bg-primary text-light">
           <div class="modal-title">{{ t('modal_rename.label.Move/Rename page') }}</div>
           <div class="modal-title">{{ t('modal_rename.label.Move/Rename page') }}</div>
           <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
           <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
         </div>
         </div>

+ 1 - 1
src/server/views/modal/shortcuts.html

@@ -2,7 +2,7 @@
   <div class="modal-dialog modal-lg">
   <div class="modal-dialog modal-lg">
     <div class="modal-content">
     <div class="modal-content">
 
 
-      <div class="modal-header">
+      <div class="modal-header text-light">
         <div class="modal-title">{{ t('Shortcuts') }}</div>
         <div class="modal-title">{{ t('Shortcuts') }}</div>
         <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
         <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
       </div>
       </div>

+ 0 - 50
src/server/views/modal/unportalize.html

@@ -1,50 +0,0 @@
-{% if isTopPage() %}
-  {% set unportalizedPath = '/top-' + Date.now() %}
-{% else %}
-  {% set unportalizedPath = page.path|replace('(\/)$', '') %}
-{% endif %}
-  <div class="modal" id="unportalize">
-    <div class="modal-dialog">
-      <div class="modal-content">
-
-      <form role="form" id="unportalize-form" onsubmit="return false;">
-
-        <div class="modal-header bg-warning">
-          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
-          <div class="modal-title">ポータル化を解除する</div>
-        </div>
-        <div class="modal-body">
-          <ul>
-           <li>このポータル化を解除し、通常のページに戻します。</li>
-          </ul>
-            <div class="form-group">
-              <p>
-                <label for="">このページ</label><br><code>{{ page.path }}</code>
-              </p>
-              <p>
-                <label for="">解除後のページ</label><br><code>{{ unportalizedPath }}</code>
-              </p>
-              {% if isTopPage() %}
-              <p class="alert alert-info">
-              このポータルはトップページのポータルのため、特別なページに移動します。
-              </p>
-              {% endif %}
-            </div>
-        </div>
-        <div class="modal-footer">
-          <div class="d-flex justify-content-between">
-            {% include '../widget/modal/page-api-error-messages.html' %}
-            <div>
-              <input type="hidden" name="_csrf" value="{{ csrf() }}">
-              <input type="hidden" name="path" value="{{ page.path }}">
-              <input type="hidden" name="new_path" value="{{ unportalizedPath }}">
-              <input type="hidden" name="page_id" value="{{ page._id.toString() }}">
-              <input type="hidden" name="revision_id" value="{{ page.revision._id.toString() }}">
-              <button type="submit" class="btn btn-warning">Unportalize</button>
-            </div>
-        </div>
-
-      </form>
-      </div><!-- /.modal-content -->
-    </div><!-- /.modal-dialog -->
-  </div><!-- /.modal -->

+ 1 - 1
src/server/views/page_presentation.html

@@ -28,7 +28,7 @@
     <script src="{{ webpack_asset('js/legacy-presentation.js') }}" defer></script>
     <script src="{{ webpack_asset('js/legacy-presentation.js') }}" defer></script>
     <link rel="stylesheet" href="{{ webpack_asset('styles/style-presentation.css') }}">
     <link rel="stylesheet" href="{{ webpack_asset('styles/style-presentation.css') }}">
 
 
-    <title>{{ path|path2name }} | {{ path }}</title>
+    <title>{{ page.path | path2name | preventXss }} | {{ page.path | preventXss }}</title>
 
 
     {{ cdnStyleTagsByGroup('basis') }}
     {{ cdnStyleTagsByGroup('basis') }}
     {{ cdnHighlightJsStyleTag(getConfig('crowi', 'customize:highlightJsStyle')) }}
     {{ cdnHighlightJsStyleTag(getConfig('crowi', 'customize:highlightJsStyle')) }}

+ 1 - 1
src/server/views/widget/alert_siteurl_undefined.html

@@ -1,5 +1,5 @@
 {% if !getConfig('crowi', 'app:siteUrl') %}
 {% if !getConfig('crowi', 'app:siteUrl') %}
-<div class="alert alert-danger text-white mb-0 px-4 py-2">
+<div class="alert alert-danger mb-0 px-4 py-2">
   <i class="icon-exclamation"></i>
   <i class="icon-exclamation"></i>
   {{ t("security_setting.alert_siteUrl_is_not_set", { link:t('App Settings')}) }}
   {{ t("security_setting.alert_siteUrl_is_not_set", { link:t('App Settings')}) }}
 </div>
 </div>

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

@@ -8,7 +8,7 @@
 </div>
 </div>
 
 
 <div id="content-main" class="content-main page-list"
 <div id="content-main" class="content-main page-list"
-  data-path="{{ path | preventXss }}"
+  data-path="{{ encodeURI(path) }}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
   >
   >
 
 
@@ -33,7 +33,7 @@
     <div class="pt-2 active tab-pane page-list-container" id="revision-body">
     <div class="pt-2 active tab-pane page-list-container" id="revision-body">
       {% if pages.length == 0 %}
       {% if pages.length == 0 %}
         <div class="mt-2">
         <div class="mt-2">
-          There are no pages under <strong>{{ path }}</strong>.
+          There are no pages under <strong>{{ path | preventXss }}</strong>.
         </div>
         </div>
       {% endif  %}
       {% endif  %}
 
 

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

@@ -8,7 +8,7 @@
 </div>
 </div>
 
 
 <div id="content-main" class="content-main page-list"
 <div id="content-main" class="content-main page-list"
-  data-path="{{ path | preventXss }}"
+  data-path="{{ encodeURI(path) }}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
   >
   >
 
 
@@ -25,7 +25,7 @@
     <div class="pt-2 active tab-pane page-list-container" id="revision-body">
     <div class="pt-2 active tab-pane page-list-container" id="revision-body">
       {% if pages.length == 0 %}
       {% if pages.length == 0 %}
         <div class="mt-2">
         <div class="mt-2">
-          There are no pages under <strong>{{ path }}</strong>.
+          There are no pages under <strong>{{ path | preventXss }}</strong>.
         </div>
         </div>
       {% endif  %}
       {% endif  %}
 
 

+ 13 - 4
src/server/views/widget/not_found_content.html

@@ -8,7 +8,7 @@
 </div>
 </div>
 
 
 <div id="content-main" class="content-main page-list"
 <div id="content-main" class="content-main page-list"
-  data-path="{{ path | preventXss }}"
+  data-path="{{ encodeURI(path) }}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
   {% if templateTags %}
   {% if templateTags %}
     data-template-tags="{{ templateTags }}"
     data-template-tags="{{ templateTags }}"
@@ -18,22 +18,31 @@
   {% include 'not_found_tabs.html' %}
   {% include 'not_found_tabs.html' %}
 
 
   <div class="tab-content">
   <div class="tab-content">
+
+
+    {# TODO: should be removed and transplanted to PageContainer.initStateMarkdown ------ from here ------ #}
+
     {% if getConfig('crowi', 'customize:isEnabledAttachTitleHeader') %}
     {% if getConfig('crowi', 'customize:isEnabledAttachTitleHeader') %}
     {% if template %}
     {% if template %}
-    <script type="text/template" id="raw-text-original"># {{ path|path2name }}&NewLine;{{ template }}</script>
+    <script type="text/template" id="raw-text-original"># {{ path | path2name | preventXss }}&NewLine;{{ template }}</script>
     {% else %}
     {% else %}
-    <script type="text/template" id="raw-text-original"># {{ path|path2name }}</script>
+    <script type="text/template" id="raw-text-original"># {{ path | path2name | preventXss }}</script>
     {% endif %}
     {% endif %}
     {% else %}
     {% else %}
     {% if template %}
     {% if template %}
     <script type="text/template" id="raw-text-original">{{ template }}</script>
     <script type="text/template" id="raw-text-original">{{ template }}</script>
     {% endif %}
     {% endif %}
     {% endif %}
     {% endif %}
+
+    {# TODO: should be removed and transplanted to PageContainer.initStateMarkdown ------ to here ------ #}
+
+
+
     {# list view #}
     {# 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" id="revision-body">
       {% if pages.length == 0 %}
       {% if pages.length == 0 %}
         <div class="mt-2">
         <div class="mt-2">
-          There are no pages under <strong>{{ path }}</strong>.
+          There are no pages under <strong>{{ path | preventXss }}</strong>.
         </div>
         </div>
       {% endif  %}
       {% endif  %}
 
 

+ 6 - 5
src/server/views/widget/page_alerts.html

@@ -33,16 +33,17 @@
       <span>
       <span>
         {% set fromPath = req.query.renamed or req.query.redirectFrom %}
         {% set fromPath = req.query.renamed or req.query.redirectFrom %}
         {% if redirectFrom or req.query.redirectFrom %}
         {% if redirectFrom or req.query.redirectFrom %}
-          <strong>{{ t('Redirected') }}:</strong> {{ t('page_page.notice.redirected', req.sanitize(fromPath)) }}
+          <strong>{{ t('Redirected') }}:</strong> {{ t('page_page.notice.redirected', fromPath | preventXss) }}
         {% endif %}
         {% endif %}
         {% if req.query.renamed %}
         {% if req.query.renamed %}
-          <strong>{{ t('Moved') }}:</strong> {{ t('page_page.notice.moved', req.sanitize(fromPath)) }}
+          <strong>{{ t('Moved') }}:</strong> {{ t('page_page.notice.moved', fromPath | preventXss) }}
         {% endif %}
         {% endif %}
       </span>
       </span>
       {% if user and not page.isDeleted() %}
       {% if user and not page.isDeleted() %}
       <form role="form" id="unlink-page-form" onsubmit="return false;">
       <form role="form" id="unlink-page-form" onsubmit="return false;">
         <input type="hidden" name="_csrf" value="{{ csrf() }}">
         <input type="hidden" name="_csrf" value="{{ csrf() }}">
-        <input type="hidden" name="path" value="{{ path }}">
+        {# TODO: should be removed by GW-2283 #}
+        <input type="hidden" name="path" value="{{ page.path }}">
         <button type="submit" class="btn btn-outline-secondary btn-sm float-right">
         <button type="submit" class="btn btn-outline-secondary btn-sm float-right">
           <i class="ti-unlink" aria-hidden="true"></i>
           <i class="ti-unlink" aria-hidden="true"></i>
           Unlink
           Unlink
@@ -55,7 +56,7 @@
     {% if req.query.duplicated and not page.isDeleted() %}
     {% if req.query.duplicated and not page.isDeleted() %}
     <div class="alert alert-success py-3 px-4">
     <div class="alert alert-success py-3 px-4">
       <span>
       <span>
-        <strong>{{ t('Duplicated') }}: </strong> {{ t('page_page.notice.duplicated', req.sanitize(req.query.duplicated)) }}
+        <strong>{{ t('Duplicated') }}: </strong> {{ t('page_page.notice.duplicated', req.query.duplicated | preventXss) }}
       </span>
       </span>
     </div>
     </div>
     {% endif %}
     {% endif %}
@@ -69,7 +70,7 @@
     {% if page and not page.isLatestRevision() %}
     {% if page and not page.isLatestRevision() %}
     <div class="alert alert-warning">
     <div class="alert alert-warning">
       <strong>{{ t('Warning') }}: </strong> {{ t('page_page.notice.version') }}
       <strong>{{ t('Warning') }}: </strong> {{ t('page_page.notice.version') }}
-      <a href="{{ page.path }}"><i class="icon-fw icon-arrow-right-circle"></i>{{ t('Show latest') }}</a>
+      <a href="{{ encodeURI(page.path) }}"><i class="icon-fw icon-arrow-right-circle"></i>{{ t('Show latest') }}</a>
     </div>
     </div>
     {% endif %}
     {% endif %}
 
 

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

@@ -1,6 +1,6 @@
 {% if page %}
 {% if page %}
 <div id="content-main" class="content-main"
 <div id="content-main" class="content-main"
-  data-path="{{ path }}"
+  data-path="{{ encodeURI(page.path) }}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
   data-page-id="{% if page %}{{ page._id.toString() }}{% endif %}"
   data-page-id="{% if page %}{{ page._id.toString() }}{% endif %}"
   data-page-revision-id="{% if revision %}{{ revision._id.toString() }}{% endif %}"
   data-page-revision-id="{% if revision %}{{ revision._id.toString() }}{% endif %}"
@@ -18,7 +18,7 @@
   >
   >
 {% else %}
 {% else %}
 <div id="content-main" class="content-main"
 <div id="content-main" class="content-main"
-  data-path="{{ path }}"
+  data-path="{{ encodeURI(page.path) }}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
   data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
   data-slack-channels="{{ slack|default('') }}"
   data-slack-channels="{{ slack|default('') }}"
   >
   >

+ 8 - 5
src/server/views/widget/page_list.html

@@ -9,9 +9,8 @@
 
 
 <li>
 <li>
   <img src="{{ page.lastUpdateUser|picture }}" class="picture rounded-circle">
   <img src="{{ page.lastUpdateUser|picture }}" class="picture rounded-circle">
-  <a href="{{ page.path }}"
-    class="text-break"
-    data-path="{{ page.path }}">{{ decodeURIComponent(page.path) }}
+  <a href="{{ encodeURI(page.path) }}" class="text-break ml-1">
+    {{ page.path | preventXss }}
   </a>
   </a>
   <span class="page-list-meta">
   <span class="page-list-meta">
     {% if page.isTopPage() %}
     {% if page.isTopPage() %}
@@ -53,10 +52,14 @@
 {% if pager %}
 {% if pager %}
 <ul class="pagination">
 <ul class="pagination">
   {% if pager.prev !== null %}
   {% if pager.prev !== null %}
-    <li class="prev"><a href="{{ path }}?offset={{ pager.prev }}"><i class="fa fa-arrow-left"></i> Prev</a></li>
+    <li class="prev">
+      <a href="{{ encodeURI(path) }}?offset={{ pager.prev }}" class="btn btn-outline-secondary"><i class="icon-arrow-left"></i> Prev</a>
+    </li>
   {% endif %}
   {% endif %}
   {% if pager.next %}
   {% if pager.next %}
-    <li class="next"><a href="{{ path }}?offset={{ pager.next }}">Next <i class="fa fa-arrow-right"></i></a></li>
+    <li class="next">
+      <a href="{{ encodeURI(path) }}?offset={{ pager.next }}" class="btn btn-outline-secondary">Next <i class="icon-arrow-right"></i></a>
+    </li>
   {% endif %}
   {% endif %}
 </ul>
 </ul>
 {% endif %}
 {% endif %}

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

@@ -19,7 +19,7 @@
           {% if isTrashPage() %}
           {% if isTrashPage() %}
           No deleted pages.
           No deleted pages.
           {% else %}
           {% else %}
-          There are no pages under <strong>{{ path }}</strong>.
+          There are no pages under <strong>{{ path | preventXss }}</strong>.
           {% endif %}
           {% endif %}
         </div>
         </div>
       {% else %}
       {% else %}
@@ -30,7 +30,7 @@
     {# timeline view #}
     {# timeline view #}
     {% if getConfig('crowi', 'customize:isEnabledTimeline') %}
     {% if getConfig('crowi', 'customize:isEnabledTimeline') %}
       <div class="tab-pane mt-5" id="view-timeline">
       <div class="tab-pane mt-5" id="view-timeline">
-        <script type="text/template" id="page-timeline-data">{{ JSON.stringify(pagesDataForTimeline(pages)) }}</script>
+        <script type="text/template" id="page-timeline-data">{{ JSON.stringify(pagesDataForTimeline(pages)) | preventXss }}</script>
         {# render React Component PageTimeline #}
         {# render React Component PageTimeline #}
         <div id="page-timeline"></div>
         <div id="page-timeline"></div>
       </div>
       </div>

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

@@ -18,7 +18,7 @@
           {% if isTrashPage() %}
           {% if isTrashPage() %}
           No deleted pages.
           No deleted pages.
           {% else %}
           {% else %}
-          There are no pages under <strong>{{ path }}</strong>.
+          There are no pages under <strong>{{ path | preventXss }}</strong>.
           {% endif %}
           {% endif %}
         </div>
         </div>
       {% else %}
       {% else %}
@@ -29,7 +29,7 @@
     {# timeline view #}
     {# timeline view #}
     {% if getConfig('crowi', 'customize:isEnabledTimeline') %}
     {% if getConfig('crowi', 'customize:isEnabledTimeline') %}
       <div class="tab-pane mt-5" id="view-timeline">
       <div class="tab-pane mt-5" id="view-timeline">
-        <script type="text/template" id="page-timeline-data">{{ JSON.stringify(pagesDataForTimeline(pages)) }}</script>
+        <script type="text/template" id="page-timeline-data">{{ JSON.stringify(pagesDataForTimeline(pages)) | preventXss }}</script>
         {# render React Component PageTimeline #}
         {# render React Component PageTimeline #}
         <div id="page-timeline"></div>
         <div id="page-timeline"></div>
       </div>
       </div>

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

@@ -78,13 +78,6 @@
       </a>
       </a>
       <div class="dropdown-menu dropdown-menu-right">
       <div class="dropdown-menu dropdown-menu-right">
         <a class="dropdown-item" href="#" data-target="#create-template" data-toggle="modal"><i class="icon-fw icon-magic-wand"></i> {{ t('template.option_label.create/edit') }}</a>
         <a class="dropdown-item" href="#" data-target="#create-template" data-toggle="modal"><i class="icon-fw icon-magic-wand"></i> {{ t('template.option_label.create/edit') }}</a>
-        {% if ('/' !== path) %}
-          <div class="dropdown-divider"></div>
-          <a class="dropdown-item" href="#" data-target="#unportalize" data-toggle="modal"><i class="fa fa-share"></i> {{ t('Unportalize') }}</a>
-          {% if isDeletablePage() %}
-            <a class="dropdown-item" href="#" data-target="#deletePage" data-toggle="modal"><i class="icon-fw icon-fire text-danger"></i> {{ t('Delete') }}</a>
-          {% endif %}
-        {% endif %}
       </div>
       </div>
     </li>
     </li>
     {% else %}
     {% else %}

+ 11 - 15
src/server/views/widget/page_tabs_kibela.html

@@ -73,11 +73,9 @@
         <i class="icon-options-vertical"></i>
         <i class="icon-options-vertical"></i>
       </a>
       </a>
       <div class="dropdown-menu dropdown-menu-right">
       <div class="dropdown-menu dropdown-menu-right">
-        <a class="dropdown-item" href="#" data-target="#create-template" data-toggle="modal"><i class="icon-fw icon-magic-wand"></i> {{ t('template.option_label.create/edit') }}</a>
-        {% if ('/' !== path) %}
-        <div class="dropdown-divider"></div>
-        <a class="dropdown-item" href="#" data-target="#unportalize" data-toggle="modal"><i class="fa fa-share"></i> {{ t('Unportalize') }}</a></li>
-        {% endif %}
+        <a class="dropdown-item" href="#" data-target="#create-template" data-toggle="modal">
+          <i class="icon-fw icon-magic-wand"></i> {{ t('template.option_label.create/edit') }}
+        </a>
       </div>
       </div>
     </li>
     </li>
     {% else %}
     {% else %}
@@ -91,18 +89,16 @@
       >
       >
         <i class="icon-options-vertical"></i>
         <i class="icon-options-vertical"></i>
       </a>
       </a>
-      <ul class="dropdown-menu dropdown-menu-right">
-        <li class="dropdown-item"><a href="#" data-target="#renamePage" data-toggle="modal"><i class="icon-fw icon-action-redo"></i> {{ t('Move/Rename') }}</a></li>
-        <li class="dropdown-item"><a href="#" data-target="#duplicatePage" data-toggle="modal"><i class="icon-fw icon-docs"></i> {{ t('Duplicate') }}</a></li>
-        <li class="dropdown-divider"></li>
-        <li class="dropdown-item">
-          <a href="#" data-target="#create-template" data-toggle="modal"><i class="icon-fw icon-magic-wand"></i> {{ t('template.option_label.create/edit') }}</a>
-        </li>
+      <div class="dropdown-menu dropdown-menu-right">
+        <a class="dropdown-item" href="#" data-target="#renamePage" data-toggle="modal"><i class="icon-fw icon-action-redo"></i> {{ t('Move/Rename') }}</a>
+        <a class="dropdown-item" href="#" data-target="#duplicatePage" data-toggle="modal"><i class="icon-fw icon-docs"></i> {{ t('Duplicate') }}</a>
+        <div class="dropdown-divider"></div>
+        <a class="dropdown-item" href="#" data-target="#create-template" data-toggle="modal"><i class="icon-fw icon-magic-wand"></i> {{ t('template.option_label.create/edit') }}</a>
         {% if isDeletablePage() %}
         {% if isDeletablePage() %}
-        <li class="dropdown-divider"></li>
-        <li class="dropdown-item"><a href="#" data-target="#deletePage" data-toggle="modal"><i class="icon-fw icon-fire text-danger"></i> {{ t('Delete') }}</a></li>
+        <div class="dropdown-divider"></div>
+        <a class="dropdown-item" href="#" data-target="#deletePage" data-toggle="modal"><i class="icon-fw icon-fire text-danger"></i> {{ t('Delete') }}</a>
         {% endif %}
         {% endif %}
-      </ul>
+      </div>
     </li>
     </li>
     {% endif %}
     {% endif %}
   {% endif %}
   {% endif %}

+ 0 - 32
src/server/views/widget/pager.html

@@ -1,32 +0,0 @@
-<ul class="pagination">
-
-  <li {% if pager.page == 1 %}class="disabled"{% endif %}>
-    <a href="{{ path }}?page={{ pager.previous|default(1) }}">&laquo;</a>
-  </li>
-  {% if pager.previousDots %}
-    {% if pager.page !== 1 %}
-    <li>
-      <a href="{{ path }}?page=1">1</a>
-    </li>
-    {% endif %}
-  <li><a href="#">...</a></li>
-  {% endif  %}
-
-  {% for page in pager.pages %}
-  <li {% if pager.page == page %}class="active"{% endif %}>
-    <a href="{{ path }}?page={{ page }}">{{ page }}</a>
-  </li>
-  {% endfor %}
-
-  {% if pager.nextDots %}
-  <li><a href="#">...</a></li>
-    {% if pager.page !== pager.pagesCount %}
-    <li>
-      <a href="{{ path }}?page={{ pager.pagesCount }}">{{ pager.pagesCount }}</a>
-    </li>
-    {% endif %}
-  {% endif  %}
-  <li {% if pager.page == pager.pagesCount %}class="disabled"{% endif %}>
-    <a href="{{ path }}?page={{ pager.next|default(pager.pagesCount) }}">&raquo;</a>
-  </li>
-</ul>

+ 0 - 15
yarn.lock

@@ -5707,13 +5707,6 @@ express-form@~0.12.0:
     object-additions "^0.5.1"
     object-additions "^0.5.1"
     validator "^2.1.0"
     validator "^2.1.0"
 
 
-express-sanitizer@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/express-sanitizer/-/express-sanitizer-1.0.4.tgz#5331a12de6577582901a6581e91e38a8b99a6ee2"
-  dependencies:
-    sanitizer "0.1.3"
-    underscore "1.8.3"
-
 express-session@^1.16.1:
 express-session@^1.16.1:
   version "1.16.1"
   version "1.16.1"
   resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.16.1.tgz#251ff9776c59382301de6c8c33411af357ed439c"
   resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.16.1.tgz#251ff9776c59382301de6c8c33411af357ed439c"
@@ -12669,10 +12662,6 @@ sane@^4.0.3:
     minimist "^1.1.1"
     minimist "^1.1.1"
     walker "~1.0.5"
     walker "~1.0.5"
 
 
-sanitizer@0.1.3:
-  version "0.1.3"
-  resolved "https://registry.yarnpkg.com/sanitizer/-/sanitizer-0.1.3.tgz#d4f0af7475d9a7baf2a9e5a611718baa178a39e1"
-
 saslprep@^1.0.0:
 saslprep@^1.0.0:
   version "1.0.2"
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.2.tgz#da5ab936e6ea0bbae911ffec77534be370c9f52d"
   resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.2.tgz#da5ab936e6ea0bbae911ffec77534be370c9f52d"
@@ -14431,10 +14420,6 @@ ultron@~1.1.0:
   version "1.1.1"
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
   resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
 
 
-underscore@1.8.3:
-  version "1.8.3"
-  resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
-
 unherit@^1.0.4:
 unherit@^1.0.4:
   version "1.1.2"
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.2.tgz#14f1f397253ee4ec95cec167762e77df83678449"
   resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.2.tgz#14f1f397253ee4ec95cec167762e77df83678449"