Przeglądaj źródła

Merge branch 'dev/4.0.x' into support/apply-bootstrap4

# Conflicts:
#	src/client/styles/scss/_staff_credit.scss
#	src/server/views/admin/Users_reserve.html
itizawa 5 lat temu
rodzic
commit
38bbba4c32
36 zmienionych plików z 88 dodań i 499 usunięć
  1. 0 1
      package.json
  2. 3 3
      src/client/js/components/Page/CopyDropdown.jsx
  3. 2 2
      src/client/js/components/PageList/Page.jsx
  4. 3 3
      src/client/js/components/PageList/PagePathLabel.jsx
  5. 1 10
      src/client/js/components/PageTimeline.jsx
  6. 1 1
      src/client/js/components/SearchPage/SearchResult.jsx
  7. 1 1
      src/client/js/components/SearchTypeahead.jsx
  8. 1 0
      src/client/js/legacy/crowi.js
  9. 2 1
      src/client/js/services/PageContainer.js
  10. 16 18
      src/client/styles/scss/_page_list.scss
  11. 0 2
      src/client/styles/scss/_staff_credit.scss
  12. 0 2
      src/server/crowi/express-init.js
  13. 1 1
      src/server/models/config.js
  14. 1 1
      src/server/routes/attachment.js
  15. 16 15
      src/server/routes/page.js
  16. 1 2
      src/server/util/swigFunctions.js
  17. 0 317
      src/server/views/admin/Users_reserve.html
  18. 1 2
      src/server/views/admin/markdown.html
  19. 0 3
      src/server/views/admin/search.html
  20. 0 7
      src/server/views/customlayout-selector/forbidden.html
  21. 0 7
      src/server/views/customlayout-selector/not_creatable.html
  22. 0 7
      src/server/views/customlayout-selector/not_found.html
  23. 0 7
      src/server/views/customlayout-selector/page.html
  24. 0 7
      src/server/views/customlayout-selector/page_list.html
  25. 0 7
      src/server/views/customlayout-selector/user_page.html
  26. 1 1
      src/server/views/page_presentation.html
  27. 2 2
      src/server/views/widget/forbidden_content.html
  28. 2 2
      src/server/views/widget/not_creatable_content.html
  29. 13 4
      src/server/views/widget/not_found_content.html
  30. 6 5
      src/server/views/widget/page_alerts.html
  31. 2 2
      src/server/views/widget/page_content.html
  32. 8 5
      src/server/views/widget/page_list.html
  33. 2 2
      src/server/views/widget/page_list_and_timeline.html
  34. 2 2
      src/server/views/widget/page_list_and_timeline_kibela.html
  35. 0 32
      src/server/views/widget/pager.html
  36. 0 15
      yarn.lock

+ 0 - 1
package.json

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

+ 3 - 3
src/client/js/components/Page/CopyDropdown.jsx

@@ -45,7 +45,7 @@ class CopyDropdown extends React.Component {
       search, hash,
     } = window.location;
 
-    return decodeURI(`${pagePath}${search}${hash}`);
+    return `${pagePath}${search}${hash}`;
   }
 
   generatePagePathUrl() {
@@ -64,7 +64,7 @@ class CopyDropdown extends React.Component {
     const {
       origin, search, hash,
     } = location;
-    return decodeURI(`${origin}/${pageId}${search}${hash}`);
+    return `${origin}/${pageId}${search}${hash}`;
   }
 
   generateMarkdownLink() {
@@ -76,7 +76,7 @@ class CopyDropdown extends React.Component {
     const label = `${pagePath}${search}${hash}`;
     const permalink = this.generatePermalink();
 
-    return decodeURI(`[${label}](${permalink})`);
+    return `[${label}](${permalink})`;
   }
 
   DropdownItemContents = ({ title, contents }) => (

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

@@ -12,8 +12,8 @@ export default class Page extends React.Component {
       page, noLink,
     } = 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>;
     }
 

+ 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);
 
-  let classNames = ['page-path'];
+  let classNames = [''];
   classNames = classNames.concat(props.additionalClassNames);
 
   if (props.isLatterOnly) {
     return <span className={classNames.join(' ')}>{dPagePath.latter}</span>;
   }
 
-  const textElem = (dPagePath.former == null && dPagePath.latter == null)
+  const textElem = dPagePath.isRoot
     ? <><strong>/</strong></>
     : <>{dPagePath.former}/<strong>{dPagePath.latter}</strong></>;
 
@@ -24,7 +24,7 @@ const PagePathLabel = (props) => {
 PagePathLabel.propTypes = {
   page: PropTypes.object.isRequired,
   isLatterOnly: PropTypes.bool,
-  additionalClassNames: PropTypes.array,
+  additionalClassNames: PropTypes.arrayOf(PropTypes.string),
 };
 
 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 * as entities from 'entities';
-
 import AppContainer from '../services/AppContainer';
 import { createSubscribedElement } from './UnstatedUtils';
 
@@ -77,14 +75,7 @@ class PageTimeline extends React.Component {
       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() {

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

@@ -195,7 +195,7 @@ class SearchResult extends React.Component {
                 )
               }
               <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>
           </a>

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

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

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

@@ -255,6 +255,7 @@ $(() => {
     if (name.match(/.+\/$/)) {
       name = name.substr(0, name.length - 1);
     }
+    // TODO: remove by GW-2278
     window.location.href = `${pathUtils.encodePagePath(name)}#edit`;
     return false;
   });

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

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

@@ -1,4 +1,4 @@
-.page-list {
+body .page-list {
   .page-list-container {
     font-size: 15px;
     line-height: 1.6em;
@@ -6,15 +6,12 @@
 
   .page-list-ul {
     padding-left: 0;
+    margin: 0;
 
     > li {
+      margin: 0;
       list-style: none;
 
-      .picture {
-        width: 16px;
-        height: 16px;
-      }
-
       > a {
         padding: 0px;
         color: inherit;
@@ -22,22 +19,23 @@
         &:hover {
           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;
   height: 80vh !important;
 
-  font-family: 'Press Start 2P', $font-family-for-staff-credit;
-  color: white;
   // see https://css-tricks.com/old-timey-terminal-styling/
   @mixin old-timey-terminal-styling() {
     text-shadow: 0 0 10px #c8c8c8;

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

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

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

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

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

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

+ 16 - 15
src/server/routes/page.js

@@ -146,7 +146,7 @@ module.exports = function(crowi, app) {
   const ApiResponse = require('../util/apiResponse');
   const getToday = require('../util/getToday');
 
-  const { slackNotificationService } = crowi;
+  const { slackNotificationService, configManager } = crowi;
   const interceptorManager = crowi.getInterceptorManager();
   const globalNotificationService = crowi.getGlobalNotificationService();
 
@@ -252,7 +252,6 @@ module.exports = function(crowi, app) {
 
   function addRendarVarsForPage(renderVars, page) {
     renderVars.page = page;
-    renderVars.path = page.path;
     renderVars.revision = page.revision;
     renderVars.author = page.revision.author;
     renderVars.pageIdOnHackmd = page.pageIdOnHackmd;
@@ -298,7 +297,7 @@ module.exports = function(crowi, app) {
       seener_threshold: SEENER_THRESHOLD,
     };
     renderVars.pager = generatePager(result.offset, result.limit, result.totalCount);
-    renderVars.pages = pathUtils.encodePagesPath(result.pages);
+    renderVars.pages = result.pages;
   }
 
   function replacePlaceholdersOfTemplate(template, req) {
@@ -337,8 +336,9 @@ module.exports = function(crowi, app) {
   async function showTopPage(req, res, next) {
     const portalPath = req.path;
     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 };
 
     let portalPage = await Page.findByPathAndViewer(portalPath, req.user);
@@ -362,6 +362,7 @@ module.exports = function(crowi, app) {
   async function showPageForGrowiBehavior(req, res, next) {
     const path = getPathFromRequest(req);
     const revisionId = req.query.revision;
+    const layoutName = configManager.getConfig('crowi', 'customize:layout');
 
     let page = await Page.findByPathAndViewer(path, req.user);
 
@@ -372,7 +373,7 @@ module.exports = function(crowi, app) {
     }
     if (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);
@@ -381,7 +382,7 @@ module.exports = function(crowi, app) {
     const offset = parseInt(req.query.offset) || 0;
     const renderVars = {};
 
-    let view = 'customlayout-selector/page';
+    let view = `layout-${layoutName}/page`;
 
     page.initLatestRevisionField(revisionId);
 
@@ -395,7 +396,7 @@ module.exports = function(crowi, app) {
 
     if (isUserPage(page.path)) {
       // change template
-      view = 'customlayout-selector/user_page';
+      view = `layout-${layoutName}/user_page`;
       await addRenderVarsForUserPage(renderVars, page, req.user);
     }
 
@@ -472,18 +473,19 @@ module.exports = function(crowi, app) {
     const path = getPathFromRequest(req);
 
     const isCreatable = Page.isCreatableName(path);
+    const layoutName = configManager.getConfig('crowi', 'customize:layout');
 
     let view;
     const renderVars = { path };
 
     if (!isCreatable) {
-      view = 'customlayout-selector/not_creatable';
+      view = `layout-${layoutName}/not_creatable`;
     }
     else if (req.isForbidden) {
-      view = 'customlayout-selector/forbidden';
+      view = `layout-${layoutName}/forbidden`;
     }
     else {
-      view = 'customlayout-selector/not_found';
+      view = `layout-${layoutName}/not_found`;
 
       // retrieve templates
       if (req.user != null) {
@@ -514,6 +516,7 @@ module.exports = function(crowi, app) {
   actions.deletedPageListShow = async function(req, res) {
     // normalizePath makes '/trash/' -> '/trash'
     const path = pathUtils.normalizePath(`/trash${getPathFromRequest(req)}`);
+    const layoutName = configManager.getConfig('crowi', 'customize:layout');
 
     const limit = 50;
     const offset = parseInt(req.query.offset) || 0;
@@ -537,8 +540,8 @@ module.exports = function(crowi, app) {
     }
 
     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);
   };
 
   /**
@@ -550,7 +553,7 @@ module.exports = function(crowi, app) {
     const page = await Page.findByIdAndViewer(id, req.user);
 
     if (page != null) {
-      return res.redirect(pathUtils.encodePagePath(page.path));
+      return res.redirect(encodeURI(page.path));
     }
 
     return res.redirect('/');
@@ -646,7 +649,6 @@ module.exports = function(crowi, app) {
         result.pages.pop();
       }
 
-      result.pages = pathUtils.encodePagesPath(result.pages);
       return res.json(ApiResponse.success(result));
     }
     catch (err) {
@@ -1609,7 +1611,6 @@ module.exports = function(crowi, app) {
 
     try {
       const result = await Page.findListByCreator(page.creator, req.user, queryOptions);
-      result.pages = pathUtils.encodePagesPath(result.pages);
 
       return res.json(ApiResponse.success(result));
     }

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

@@ -1,7 +1,6 @@
 module.exports = function(crowi, req, locals) {
   const debug = require('debug')('growi:lib:swigFunctions');
   const stringWidth = require('string-width');
-  const entities = require('entities');
 
   const { pathUtils } = require('growi-commons');
 
@@ -179,7 +178,7 @@ module.exports = function(crowi, req, locals) {
     return pages.map((page) => {
       return {
         id: page.id,
-        path: entities.encodeHTML(page.path),
+        path: page.path,
         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 text-light">
-              <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 text-light">
-              <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 text-light">
-              <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' %}
 
-{% block html_title %}{{ customizeService.generateCustomTitle(t('Markdown settings')) }}
- · {{ path }}{% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle(t('Markdown settings')) }}{% endblock %}
 
 {% block content_header %}
 <h1 class="title">{{ t('Markdown Settings') }}</h1>

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

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

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

@@ -28,7 +28,7 @@
     <script src="{{ webpack_asset('js/legacy-presentation.js') }}" defer></script>
     <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') }}
     {{ cdnHighlightJsStyleTag(getConfig('crowi', 'customize:highlightJsStyle')) }}

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

@@ -8,7 +8,7 @@
 </div>
 
 <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 %}"
   >
 
@@ -33,7 +33,7 @@
     <div class="pt-2 active tab-pane page-list-container" id="revision-body">
       {% if pages.length == 0 %}
         <div class="mt-2">
-          There are no pages under <strong>{{ path }}</strong>.
+          There are no pages under <strong>{{ path | preventXss }}</strong>.
         </div>
       {% endif  %}
 

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

@@ -8,7 +8,7 @@
 </div>
 
 <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 %}"
   >
 
@@ -25,7 +25,7 @@
     <div class="pt-2 active tab-pane page-list-container" id="revision-body">
       {% if pages.length == 0 %}
         <div class="mt-2">
-          There are no pages under <strong>{{ path }}</strong>.
+          There are no pages under <strong>{{ path | preventXss }}</strong>.
         </div>
       {% endif  %}
 

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

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

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

@@ -33,16 +33,17 @@
       <span>
         {% set fromPath = req.query.renamed 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 %}
         {% 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 %}
       </span>
       {% if user and not page.isDeleted() %}
       <form role="form" id="unlink-page-form" onsubmit="return false;">
         <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">
           <i class="ti-unlink" aria-hidden="true"></i>
           Unlink
@@ -55,7 +56,7 @@
     {% if req.query.duplicated and not page.isDeleted() %}
     <div class="alert alert-success py-3 px-4">
       <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>
     </div>
     {% endif %}
@@ -69,7 +70,7 @@
     {% if page and not page.isLatestRevision() %}
     <div class="alert alert-warning">
       <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>
     {% endif %}
 

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

@@ -1,6 +1,6 @@
 {% if page %}
 <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-page-id="{% if page %}{{ page._id.toString() }}{% endif %}"
   data-page-revision-id="{% if revision %}{{ revision._id.toString() }}{% endif %}"
@@ -18,7 +18,7 @@
   >
 {% else %}
 <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-slack-channels="{{ slack|default('') }}"
   >

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

@@ -9,9 +9,8 @@
 
 <li>
   <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>
   <span class="page-list-meta">
     {% if page.isTopPage() %}
@@ -53,10 +52,14 @@
 {% if pager %}
 <ul class="pagination">
   {% 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 %}
   {% 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 %}
 </ul>
 {% endif %}

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

@@ -19,7 +19,7 @@
           {% if isTrashPage() %}
           No deleted pages.
           {% else %}
-          There are no pages under <strong>{{ path }}</strong>.
+          There are no pages under <strong>{{ path | preventXss }}</strong>.
           {% endif %}
         </div>
       {% else %}
@@ -30,7 +30,7 @@
     {# timeline view #}
     {% if getConfig('crowi', 'customize:isEnabledTimeline') %}
       <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 #}
         <div id="page-timeline"></div>
       </div>

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

@@ -18,7 +18,7 @@
           {% if isTrashPage() %}
           No deleted pages.
           {% else %}
-          There are no pages under <strong>{{ path }}</strong>.
+          There are no pages under <strong>{{ path | preventXss }}</strong>.
           {% endif %}
         </div>
       {% else %}
@@ -29,7 +29,7 @@
     {# timeline view #}
     {% if getConfig('crowi', 'customize:isEnabledTimeline') %}
       <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 #}
         <div id="page-timeline"></div>
       </div>

+ 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"
     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:
   version "1.16.1"
   resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.16.1.tgz#251ff9776c59382301de6c8c33411af357ed439c"
@@ -12664,10 +12657,6 @@ sane@^4.0.3:
     minimist "^1.1.1"
     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:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.2.tgz#da5ab936e6ea0bbae911ffec77534be370c9f52d"
@@ -14426,10 +14415,6 @@ ultron@~1.1.0:
   version "1.1.1"
   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:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.2.tgz#14f1f397253ee4ec95cec167762e77df83678449"