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

Merge branch 'master' into feat/ldap-auth

# Conflicts:
#	yarn.lock
Yuki Takei 8 лет назад
Родитель
Сommit
dcfddc6aec

+ 6 - 3
CHANGES.md

@@ -3,7 +3,10 @@ CHANGES
 
 
 ## 2.2.1-RC
 ## 2.2.1-RC
 
 
-* 
+* Feature: Duplicate page
+* Fix: Modal doesn't work with React v16
+* Support: Upgrade React to 16
+* Support: Upgrade outdated libs
 
 
 ## 2.2.0
 ## 2.2.0
 
 
@@ -22,8 +25,8 @@ CHANGES
 
 
 ## 2.1.0
 ## 2.1.0
 
 
-* Feat: Adopt Passport the authentication middleware
-* Feat: Selective batch deletion in search result page
+* Feature: Adopt Passport the authentication middleware
+* Feature: Selective batch deletion in search result page
 * Improvement: Ensure to be able to login with both of username or email
 * Improvement: Ensure to be able to login with both of username or email
 * Fix: The problem that couldn't update user data in /me
 * Fix: The problem that couldn't update user data in /me
 * Support: Upgrade outdated libs
 * Support: Upgrade outdated libs

+ 9 - 0
lib/locales/en-US/translation.json

@@ -2,6 +2,7 @@
   "Help": "Help",
   "Help": "Help",
   "Edit": "Edit",
   "Edit": "Edit",
   "Delete": "Delete",
   "Delete": "Delete",
+  "Duplicate": "Duplicate",
   "Move": "Move",
   "Move": "Move",
   "Moved": "Moved",
   "Moved": "Moved",
   "Unlinked": "Unlinked",
   "Unlinked": "Unlinked",
@@ -172,6 +173,14 @@
     }
     }
   },
   },
 
 
+  "modal_duplicate": {
+    "label": {
+      "Duplicate page": "Duplicate page",
+      "New page name": "New page name",
+      "Current page name": "Current page name"
+    }
+  },
+
   "modal_putBack": {
   "modal_putBack": {
     "label": {
     "label": {
       "Put Back Page": "Put Back Page",
       "Put Back Page": "Put Back Page",

+ 9 - 0
lib/locales/ja/translation.json

@@ -2,6 +2,7 @@
   "Help": "ヘルプ",
   "Help": "ヘルプ",
   "Edit": "編集",
   "Edit": "編集",
   "Delete": "削除",
   "Delete": "削除",
+  "Duplicate": "複製",
   "Move": "移動",
   "Move": "移動",
   "Moved": "移動しました",
   "Moved": "移動しました",
   "Unlinked": "リダイレクト削除",
   "Unlinked": "リダイレクト削除",
@@ -171,6 +172,14 @@
     }
     }
   },
   },
 
 
+  "modal_duplicate": {
+    "label": {
+      "Duplicate page": "ページを複製する",
+      "New page name": "複製後のページ名",
+      "Current page name": "現在のページ名"
+    }
+  },
+
   "modal_putBack": {
   "modal_putBack": {
     "label": {
     "label": {
       "Put Back Page": "ページを元に戻す",
       "Put Back Page": "ページを元に戻す",

+ 1 - 0
lib/routes/index.js

@@ -140,6 +140,7 @@ module.exports = function(crowi, app) {
   app.post('/_api/pages.remove'       , loginRequired(crowi, app) , csrf, page.api.remove); // (Avoid from API Token)
   app.post('/_api/pages.remove'       , loginRequired(crowi, app) , csrf, page.api.remove); // (Avoid from API Token)
   app.post('/_api/pages.revertRemove' , loginRequired(crowi, app) , csrf, page.api.revertRemove); // (Avoid from API Token)
   app.post('/_api/pages.revertRemove' , loginRequired(crowi, app) , csrf, page.api.revertRemove); // (Avoid from API Token)
   app.post('/_api/pages.unlink'       , loginRequired(crowi, app) , csrf, page.api.unlink); // (Avoid from API Token)
   app.post('/_api/pages.unlink'       , loginRequired(crowi, app) , csrf, page.api.unlink); // (Avoid from API Token)
+  app.post('/_api/pages.duplicate'    , accessTokenParser, loginRequired(crowi, app), csrf, page.api.duplicate);
   app.get('/_api/comments.get'        , accessTokenParser , loginRequired(crowi, app, false) , comment.api.get);
   app.get('/_api/comments.get'        , accessTokenParser , loginRequired(crowi, app, false) , comment.api.get);
   app.post('/_api/comments.add'       , form.comment, accessTokenParser , loginRequired(crowi, app) , csrf, comment.api.add);
   app.post('/_api/comments.add'       , form.comment, accessTokenParser , loginRequired(crowi, app) , csrf, comment.api.add);
   app.post('/_api/comments.remove'    , accessTokenParser , loginRequired(crowi, app) , csrf, comment.api.remove);
   app.post('/_api/comments.remove'    , accessTokenParser , loginRequired(crowi, app) , csrf, comment.api.remove);

+ 23 - 0
lib/routes/page.js

@@ -1142,6 +1142,29 @@ module.exports = function(crowi, app) {
     });
     });
   };
   };
 
 
+  /**
+   * @api {post} /pages.duplicate Duplicate page
+   * @apiName DuplicatePage
+   * @apiGroup Page
+   *
+   * @apiParam {String} page_id Page Id.
+   * @apiParam {String} new_path
+   */
+  api.duplicate = function (req, res) {
+    var pageId = req.body.page_id;
+    var newPagePath = Page.normalizePath(req.body.new_path);
+    var page = {};
+
+    Page.findPageById(pageId)
+      .then(function (pageData) {
+        req.body.path = newPagePath;
+        req.body.body = pageData.revision.body;
+        req.body.grant = pageData.grant;
+
+        return api.create(req, res);
+      });
+  };
+
   /**
   /**
    * @api {post} /pages.unlink Remove the redirecting page
    * @api {post} /pages.unlink Remove the redirecting page
    * @apiName UnlinkPage
    * @apiName UnlinkPage

+ 36 - 0
lib/views/modal/duplicate.html

@@ -0,0 +1,36 @@
+  <div class="modal" id="duplicatePage">
+    <div class="modal-dialog">
+      <div class="modal-content">
+
+      <form role="form" id="duplicatePageForm" onsubmit="return false;">
+
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+          <h4 class="modal-title">{{ t('modal_duplicate.label.Duplicate page') }}</h4>
+        </div>
+        <div class="modal-body">
+            <div class="form-group">
+              <label for="">{{ t('modal_duplicate.label.Current page name') }}</label><br>
+              <code>{{ page.path }}</code>
+            </div>
+            <div class="form-group">
+              <label for="duplicatePageName">{{ t('modal_duplicate.label.New page name') }}</label><br>
+              <div class="input-group">
+                <span class="input-group-addon">{{ config.crowi['app:url'] }}</span>
+                <input type="text" class="form-control" name="new_path" id="duplicatePageName" value="{{ page.path }}">
+              </div>
+            </div>
+        </div>
+        <div class="modal-footer">
+          <p><small class="pull-left" id="duplicatePageNameCheck"></small></p>
+          <input type="hidden" name="_csrf" value="{{ csrf() }}">
+          <input type="hidden" name="path" value="{{ page.path }}">
+          <input type="hidden" name="page_id" value="{{ page._id.toString() }}">
+          <input type="hidden" name="revision_id" value="{{ page.revision._id.toString() }}">
+          <input type="submit" class="btn btn-primary" value="Duplicate page">
+        </div>
+
+      </form>
+      </div><!-- /.modal-content -->
+    </div><!-- /.modal-dialog -->
+  </div><!-- /.modal -->

+ 3 - 0
lib/views/page.html

@@ -120,6 +120,8 @@
       </a>
       </a>
       <ul class="dropdown-menu">
       <ul class="dropdown-menu">
        <li><a href="#" data-target="#renamePage" data-toggle="modal"><i class="fa fa-share"></i> {{ t('Move') }}</a></li>
        <li><a href="#" data-target="#renamePage" data-toggle="modal"><i class="fa fa-share"></i> {{ t('Move') }}</a></li>
+       <li class="divider"></li>
+       <li><a href="#" data-target="#duplicatePage" data-toggle="modal"><i class="fa fa-clone"></i> {{ t('Duplicate') }}</a></li>
        {% if isDeletablePage() %}
        {% if isDeletablePage() %}
        <li class="divider"></li>
        <li class="divider"></li>
        <li class=""><a href="#" data-target="#deletePage" data-toggle="modal"><i class="fa fa-trash-o text-danger"></i> {{ t('Delete') }}</a></li>
        <li class=""><a href="#" data-target="#deletePage" data-toggle="modal"><i class="fa fa-trash-o text-danger"></i> {{ t('Delete') }}</a></li>
@@ -265,6 +267,7 @@
 <div id="crowi-modals">
 <div id="crowi-modals">
   {% include 'modal/rename.html' %}
   {% include 'modal/rename.html' %}
   {% include 'modal/delete.html' %}
   {% include 'modal/delete.html' %}
+  {% include 'modal/duplicate.html' %}
   {% include 'modal/put_back.html' %}
   {% include 'modal/put_back.html' %}
   {% include 'modal/page_name_warning.html' %}
   {% include 'modal/page_name_warning.html' %}
 </div>
 </div>

+ 4 - 4
package.json

@@ -108,11 +108,11 @@
     "passport-local": "^1.0.0",
     "passport-local": "^1.0.0",
     "pino-clf": "^1.0.2",
     "pino-clf": "^1.0.2",
     "plantuml-encoder": "^1.2.4",
     "plantuml-encoder": "^1.2.4",
-    "react": "15.6.1",
-    "react-bootstrap": "^0.31.0",
+    "react": "^16.0.0",
+    "react-bootstrap": "^0.31.3",
     "react-bootstrap-typeahead": "^1.4.2",
     "react-bootstrap-typeahead": "^1.4.2",
     "react-clipboard.js": "^1.1.2",
     "react-clipboard.js": "^1.1.2",
-    "react-dom": "15.6.1",
+    "react-dom": "^16.0.0",
     "redis": "^2.7.1",
     "redis": "^2.7.1",
     "reveal.js": "^3.5.0",
     "reveal.js": "^3.5.0",
     "rimraf": "^2.6.1",
     "rimraf": "^2.6.1",
@@ -144,7 +144,7 @@
   "engines": {
   "engines": {
     "node": ">=6.11 <7",
     "node": ">=6.11 <7",
     "npm": ">=4.6 <5",
     "npm": ">=4.6 <5",
-    "yarn": "~1.1.0"
+    "yarn": "^1.1.0"
   },
   },
   "config": {
   "config": {
     "blanket": {
     "blanket": {

+ 0 - 2
resource/js/components/HeaderSearchBox/SearchForm.js

@@ -3,8 +3,6 @@ import { FormGroup, Button, InputGroup } from 'react-bootstrap';
 
 
 import { AsyncTypeahead } from 'react-bootstrap-typeahead';
 import { AsyncTypeahead } from 'react-bootstrap-typeahead';
 
 
-import axios from 'axios'
-
 import UserPicture from '../User/UserPicture';
 import UserPicture from '../User/UserPicture';
 import PageListMeta from '../PageList/PageListMeta';
 import PageListMeta from '../PageList/PageListMeta';
 import PagePath from '../PageList/PagePath';
 import PagePath from '../PageList/PagePath';

+ 39 - 0
resource/js/legacy/crowi.js

@@ -311,6 +311,45 @@ $(function() {
     return false;
     return false;
   });
   });
 
 
+  // duplicate
+  $('#duplicatePage').on('shown.bs.modal', function (e) {
+    $('#duplicatePageName').focus();
+  });
+  $('#duplicatePageForm, #unportalize-form').submit(function (e) {
+    // create name-value map
+    let nameValueMap = {};
+    $(this).serializeArray().forEach((obj) => {
+      nameValueMap[obj.name] = obj.value;
+    })
+
+    $.ajax({
+      type: 'POST',
+      url: '/_api/pages.duplicate',
+      data: $(this).serialize(),
+      dataType: 'json'
+    }).done(function (res) {
+      if (!res.ok) {
+        // if already exists
+        $('#duplicatePageNameCheck').html('<i class="fa fa-times-circle"></i> ' + res.error);
+        $('#duplicatePageNameCheck').addClass('alert-danger');
+        $('#linkToNewPage').html(`
+          <i class="fa fa-fw fa-arrow-right"></i><a href="${nameValueMap.new_path}">${nameValueMap.new_path}</a>
+        `);
+      } else {
+        var page = res.page;
+
+        $('#duplicatePageNameCheck').removeClass('alert-danger');
+        $('#duplicatePageNameCheck').html('<img src="/images/loading_s.gif"> Page duplicated! Redirecting to new page location.');
+
+        setTimeout(function () {
+          top.location.href = page.path + '?duplicated=' + pagePath;
+        }, 1000);
+      }
+    });
+
+    return false;
+  });
+
   // delete
   // delete
   $('#delete-page-form').submit(function(e) {
   $('#delete-page-form').submit(function(e) {
     $.ajax({
     $.ajax({

Разница между файлами не показана из-за своего большого размера
+ 181 - 292
yarn.lock


Некоторые файлы не были показаны из-за большого количества измененных файлов