Kaynağa Gözat

Merge pull request #191 from weseek/feat/duplicate-page

Feat/duplicate page
Yuki Takei 8 yıl önce
ebeveyn
işleme
3579bba077

+ 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

@@ -138,6 +138,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>

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