Преглед изворни кода

Merge pull request #214 from crowi/redirect-remover

Page remover, redirect remover
Sotaro KARASAWA пре 9 година
родитељ
комит
6c8ee251fe

+ 16 - 10
lib/models/attachment.js

@@ -108,16 +108,22 @@ module.exports = function(crowi) {
   attachmentSchema.statics.removeAttachmentsByPageId = function(pageId) {
     var Attachment = this;
 
-    // todo
-    return Promise.resolve();
-    //return new Promise(function(resolve, reject) {
-    //  // target find
-    //  Attachment.getListByPageId(pageId)
-    //  .then(function(attachments) {
-    //  }).then(function(done) {
-    //  }).catch(function(err) {
-    //  });
-    //});
+    return new Promise((resolve, reject) => {
+      Attachment.getListByPageId(pageId)
+      .then((attachments) => {
+        for (attachment of attachments) {
+          Attachment.removeAttachment(attachment).then((res) => {
+            // do nothing
+          }).catch((err) => {
+            debug('Attachment remove error', err);
+          });
+        }
+
+        resolve(attachments);
+      }).catch((err) => {
+        reject(err);
+      });
+    });
 
   };
 

+ 54 - 1
lib/models/page.js

@@ -539,6 +539,20 @@ module.exports = function(crowi) {
     });
   };
 
+  pageSchema.statics.findPageByRedirectTo = function(path) {
+    var Page = this;
+
+    return new Promise(function(resolve, reject) {
+      Page.findOne({redirectTo: path}, function(err, pageData) {
+        if (err || pageData === null) {
+          return reject(err);
+        }
+
+        return resolve(pageData);
+      });
+    });
+  };
+
   pageSchema.statics.findListByCreator = function(user, option, currentUser) {
     var Page = this;
     var User = crowi.model('User');
@@ -900,9 +914,11 @@ module.exports = function(crowi) {
         return Revision.removeRevisionsByPath(pageData.path);
       }).then(function(done) {
         return Page.removePageById(pageId);
+      }).then(function(done) {
+        return Page.removeRedirectOriginPageByPath(pageData.path);
       }).then(function(done) {
         pageEvent.emit('delete', pageData, user); // update as renamed page
-        resolve();
+        resolve(pageData);
       }).catch(reject);
     });
   };
@@ -922,6 +938,43 @@ module.exports = function(crowi) {
     });
   };
 
+  pageSchema.statics.removePageByPath = function(pagePath) {
+    var Page = this;
+
+    return Page.findPageByPath(redirectPath)
+      .then(function(pageData) {
+        return Page.removePageById(pageData.id);
+      });
+  };
+
+  /**
+   * remove the page that is redirecting to specified `pagePath` recursively
+   *  ex: when
+   *    '/page1' redirects to '/page2' and
+   *    '/page2' redirects to '/page3'
+   *    and given '/page3',
+   *    '/page1' and '/page2' will be removed
+   *
+   * @param {string} pagePath
+   */
+  pageSchema.statics.removeRedirectOriginPageByPath = function(pagePath) {
+    var Page = this;
+
+    return Page.findPageByRedirectTo(pagePath)
+      .then((redirectOriginPageData) => {
+        // remove
+        return Page.removePageById(redirectOriginPageData.id)
+          // remove recursive
+          .then(() => {
+            return Page.removeRedirectOriginPageByPath(redirectOriginPageData.path)
+          });
+      })
+      .catch((err) => {
+        // do nothing if origin page doesn't exist
+        return Promise.resolve();
+      })
+  };
+
   pageSchema.statics.rename = function(pageData, newPagePath, user, options) {
     var Page = this
       , Revision = crowi.model('Revision')

+ 1 - 0
lib/routes/index.js

@@ -103,6 +103,7 @@ module.exports = function(crowi, app) {
   app.post('/_api/pages.rename'       , accessTokenParser , loginRequired(crowi, app) , csrf, page.api.rename);
   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.unlink'       , loginRequired(crowi, app) , csrf, page.api.unlink); // (Avoid from API Token)
   app.get('/_api/comments.get'        , accessTokenParser , loginRequired(crowi, app) , comment.api.get);
   app.post('/_api/comments.add'       , form.comment, accessTokenParser , loginRequired(crowi, app) , csrf, comment.api.add);
   app.get( '/_api/bookmarks.get'      , accessTokenParser , loginRequired(crowi, app) , bookmark.api.get);

+ 39 - 1
lib/routes/page.js

@@ -199,7 +199,7 @@ module.exports = function(crowi, app) {
     }
 
     if (pageData.redirectTo) {
-      return res.redirect(encodeURI(pageData.redirectTo + '?renamed=' + pageData.path));
+      return res.redirect(encodeURI(pageData.redirectTo + '?redirectFrom=' + pageData.path));
     }
 
     var renderVars = {
@@ -726,10 +726,19 @@ module.exports = function(crowi, app) {
     var pageId = req.body.page_id;
     var previousRevision = req.body.revision_id || null;
 
+    // get completely flag
+    const isCompletely = (req.body.completely !== undefined);
+
     Page.findPageByIdAndGrantedUser(pageId, req.user)
     .then(function(pageData) {
       debug('Delete page', pageData._id, pageData.path);
 
+      if (isCompletely) {
+        return Page.completelyDeletePage(pageData, req.user);
+      }
+
+      // else
+
       if (!pageData.isUpdatable(previousRevision)) {
         throw new Error('Someone could update this page, so couldn\'t delete.');
       }
@@ -823,5 +832,34 @@ module.exports = function(crowi, app) {
     });
   };
 
+  /**
+   * @api {post} /pages.unlink Remove the redirecting page
+   * @apiName UnlinkPage
+   * @apiGroup Page
+   *
+   * @apiParam {String} page_id Page Id.
+   * @apiParam {String} revision_id
+   */
+  api.unlink = function(req, res){
+    var pageId = req.body.page_id;
+
+    Page.findPageByIdAndGrantedUser(pageId, req.user)
+    .then(function(pageData) {
+      debug('Unlink page', pageData._id, pageData.path);
+
+      return Page.removeRedirectOriginPageByPath(pageData.path)
+        .then(() => pageData);
+    }).then(function(data) {
+      debug('Redirect Page deleted', data.path);
+      var result = {};
+      result.page = data;
+
+      return res.json(ApiResponse.success(result));
+    }).catch(function(err) {
+      debug('Error occured while get setting', err, err.stack);
+      return res.json(ApiResponse.error('Failed to delete redirect page.'));
+    });
+  };
+
   return actions;
 };

+ 1 - 7
lib/views/modal/rename.html

@@ -20,13 +20,6 @@
                 <input type="text" class="form-control" name="new_path" id="newPageName" value="{{ page.path }}">
               </div>
             </div>
-            <div class="checkbox">
-               <label>
-                 <input name="create_redirect" value="1"  type="checkbox"> {{ t('Redirect') }}
-               </label>
-               <p class="help-block"> {{ t('modal_rename.help.redirect', page.path) }}
-               </p>
-            </div>
             {# <div class="checkbox"> #}
             {#    <label> #}
             {#      <input name="moveUnderTrees" value="1" type="checkbox"> 下層ページも全部移動する #}
@@ -37,6 +30,7 @@
         </div>
         <div class="modal-footer">
           <p><small class="pull-left" id="newPageNameCheck"></small></p>
+          <input type="hidden" name="create_redirect" value="1">
           <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() }}">

+ 57 - 13
lib/views/page.html

@@ -88,20 +88,37 @@
   {% else %}
 
   {% if page.isDeleted() %}
-  <div class="alert alert-danger">
-    <form role="form" class="pull-right" id="revert-delete-page-form" onsubmit="return false;">
-      <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="submit" class="btn btn-danger btn-inverse btn-sm" value="Put Back!">
-    </form>
-    <p>
+  <div class="alert alert-danger alert-trash">
+    <div>
+      <ul class="list-inline pull-right">
+        <li>
+          <form role="form" id="revert-delete-page-form" onsubmit="return false;">
+            <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() }}">
+            <button type="submit" class="btn btn-default btn-sm">
+              <i class="fa fa-undo" aria-hidden="true"></i>
+              Put Back
+            </button>
+          </form>
+        </li>
+        <li>
+          <form role="form" id="delete-page-form" onsubmit="return false;">
+            <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="completely" value="true">
+            <button type="submit" class="btn btn-danger btn-sm">
+              <i class="fa fa-times-circle" aria-hidden="true"></i>
+              Delete Completely
+            </button>
+          </form>
+        </li>
+      </ul>{# /.pull-right #}
       <i class="fa fa-trash-o" aria-hidden="true"></i>
       This page is in the trash.<br>
-    </p>
-    <p>
-    Deleted by <img src="{{ page.lastUpdateUser|picture }}" class="picture picture-sm picture-rounded"> {{ page.lastUpdateUser.name }} at {{ page.updatedAt|datetz('Y-m-d H:i:s') }}
-    </p>
+      Deleted by <img src="{{ page.lastUpdateUser|picture }}" class="picture picture-sm picture-rounded"> {{ page.lastUpdateUser.name }} at {{ page.updatedAt|datetz('Y-m-d H:i:s') }}
+    </div>
   </div>
   {% endif %}
 
@@ -140,10 +157,37 @@
 
   <div class="tab-content wiki-content">
   {% if req.query.renamed and not page.isDeleted() %}
+  <div class="alert alert-info alert-moved">
+    <span>
+      <strong>{{ t('Moved') }}: </strong> {{ t('page_page.notice.moved', req.query.renamed) }}
+    </span>
+  </div>
+  {% endif %}
+  {% if req.query.redirectFrom and not page.isDeleted() %}
+  <div class="alert alert-info alert-moved">
+    <div>
+      <form role="form" id="unlink-page-form" onsubmit="return false;">
+        <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() }}">
+        <button type="submit" class="btn btn-default btn-sm pull-right">
+          <i class="fa fa-unlink" aria-hidden="true"></i>
+          Unlink
+        </button>
+      </form>
+      <span>
+        <strong>{{ t('Moved') }}: </strong> {{ t('page_page.notice.moved', req.query.redirectFrom) }}
+      </span>
+    </div>
+  </div>
+  {% endif %}
+  {% if req.query.unlinked %}
   <div class="alert alert-info">
-    <strong>{{ t('Moved') }}: </strong> {{ t('page_page.notice.moved', req.query.renamed) }}
+    <strong>{{ t('Unlinked') }}: </strong> {{ t('page_page.notice.unlinked') }}
   </div>
   {% endif %}
+
+
   {% if not page.isLatestRevision() %}
   <div class="alert alert-warning">
     <strong>{{ t('Warning') }}: </strong> {{ t('page_page.notice.version') }} <i class="fa fa-magic"></i> <a href="{{ page.path }}">{{ t('Show latest') }}</a>

+ 2 - 0
locales/en-US/translation.json

@@ -4,6 +4,7 @@
   "Delete": "Delete",
   "Move": "Move",
   "Moved": "Moved",
+  "Unlinked": "Unlinked",
   "Like!": "Like!",
   "Seen by": "Seen by",
   "Cancel": "Cancel",
@@ -134,6 +135,7 @@
       "notice": {
           "version": "This is not the current version.",
           "moved": "This page was moved from <code>%s</code>",
+          "unlinked": "Redirect pages to this page have been deleted.",
           "restricted": "Access to this page is restricted"
       }
   },

+ 2 - 0
locales/ja/translation.json

@@ -4,6 +4,7 @@
   "Delete": "削除",
   "Move": "移動",
   "Moved": "移動しました",
+  "Unlinked": "リダイレクト削除",
   "Like!": "いいね!",
   "Seen by": "見た人",
   "Cancel": "キャンセル",
@@ -134,6 +135,7 @@
       "notice": {
           "version": "これは現在の版ではありません。",
           "moved": "このページは <code>%s</code> から移動しました。",
+          "unlinked": "このページへのリダイレクトは削除されました。",
           "restricted": "このページの閲覧は制限されています"
       }
   },

+ 16 - 0
resource/css/_page.scss

@@ -100,6 +100,22 @@
       .tab-content {
         margin-top: 30px;
       }
+
+      // alert component settings for trash page and moved page
+      // see: https://jsfiddle.net/me420sky/2/
+      .alert-trash, .alert-moved {
+        padding: 10px 15px;
+
+        span {
+          line-height: 25px;
+        }
+
+        >div:after {
+          clear: both;
+          content: '';
+          display: table;
+        }
+      }
     }
   } // }}}
 

+ 18 - 0
resource/js/crowi.js

@@ -320,6 +320,24 @@ $(function() {
 
     return false;
   });
+  $('#unlink-page-form').submit(function(e) {
+    $.ajax({
+      type: 'POST',
+      url: '/_api/pages.unlink',
+      data: $('#unlink-page-form').serialize(),
+      dataType: 'json'
+    }).done(function(res) {
+      if (!res.ok) {
+        $('#delete-errors').html('<i class="fa fa-times-circle"></i> ' + res.error);
+        $('#delete-errors').addClass('alert-danger');
+      } else {
+        var page = res.page;
+        top.location.href = page.path + '?unlinked=true';
+      }
+    });
+
+    return false;
+  });
 
   $('#create-portal-button').on('click', function(e) {
     $('.portal').removeClass('hide');