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

Merge pull request #158 from crowi/create-page-api

Create page api
Sotaro KARASAWA 9 лет назад
Родитель
Сommit
9fbbc71d88

+ 2 - 0
lib/crowi/index.js

@@ -27,6 +27,8 @@ function Crowi (rootdir, env)
   this.resourceDir = path.join(this.rootDir, 'resource') + sep;
   this.resourceDir = path.join(this.rootDir, 'resource') + sep;
   this.viewsDir  = path.join(this.libDir, 'views') + sep;
   this.viewsDir  = path.join(this.libDir, 'views') + sep;
   this.mailDir   = path.join(this.viewsDir, 'mail') + sep;
   this.mailDir   = path.join(this.viewsDir, 'mail') + sep;
+  this.tmpDir    = path.join(this.rootDir, 'tmp') + sep;
+  this.cacheDir  = path.join(this.tmpDir, 'cache');
 
 
   this.assets    = {};
   this.assets    = {};
   try {
   try {

+ 38 - 0
lib/models/attachment.js

@@ -32,6 +32,24 @@ module.exports = function(crowi) {
     return fileUploader.generateUrl(this.filePath);
     return fileUploader.generateUrl(this.filePath);
   });
   });
 
 
+  attachmentSchema.statics.findById = function(id) {
+    var Attachment = this;
+
+    return new Promise(function(resolve, reject) {
+      Attachment.findOne({_id: id}, function(err, data) {
+        if (err) {
+          return reject(err);
+        }
+
+        if (data === null) {
+          return reject(new Error('Attachment not found'));
+        }
+        return resolve(data);
+      });
+
+    });
+  };
+
   attachmentSchema.statics.getListByPageId = function(id) {
   attachmentSchema.statics.getListByPageId = function(id) {
     var self = this;
     var self = this;
 
 
@@ -104,5 +122,25 @@ module.exports = function(crowi) {
 
 
   };
   };
 
 
+  attachmentSchema.statics.createCacheFileName = function(attachment) {
+    return crowi.cacheDir + 'attachment-' + attachment._id;
+  };
+
+  attachmentSchema.statics.findDeliveryFile = function(attachment) {
+    // find local
+    var fs = require('fs');
+    var deliveryFile = {
+      filename: '',
+      options: {
+        headers: {
+          'Content-Type': attachment.fileFormat,
+        },
+      },
+    };
+    var cacheFileName = this.createCacheFileName(attachment);
+    // とちゅう
+    return deliveryFile;
+  };
+
   return mongoose.model('Attachment', attachmentSchema);
   return mongoose.model('Attachment', attachmentSchema);
 };
 };

+ 5 - 0
lib/models/user.js

@@ -258,6 +258,11 @@ module.exports = function(crowi) {
   };
   };
 
 
   userSchema.statics.filterToPublicFields = function(user) {
   userSchema.statics.filterToPublicFields = function(user) {
+    debug('User is', typeof user, user);
+    if (typeof user !== 'object' || !user._id) {
+      return user;
+    }
+
     var filteredUser = {};
     var filteredUser = {};
     var fields = USER_PUBLIC_FIELDS.split(' ');
     var fields = USER_PUBLIC_FIELDS.split(' ');
     for (var i = 0; i < fields.length; i++) {
     for (var i = 0; i < fields.length; i++) {

+ 54 - 23
lib/routes/attachment.js

@@ -9,31 +9,60 @@ module.exports = function(crowi, app) {
     , config = crowi.getConfig()
     , config = crowi.getConfig()
     , fs = require('fs')
     , fs = require('fs')
     , fileUploader = require('../util/fileUploader')(crowi, app)
     , fileUploader = require('../util/fileUploader')(crowi, app)
+    , ApiResponse = require('../util/apiResponse')
     , actions = {}
     , actions = {}
     , api = {};
     , api = {};
 
 
   actions.api = api;
   actions.api = api;
 
 
+  api.redirector = function(req, res){
+    var id = req.params.id;
+
+    Attachment.findById(id)
+    .then(function(data) {
+
+      // TODO: file delivery plugin for cdn
+      var deliveryFile = Attachment.findDeliveryFile(data);
+      return res.sendFile(deliveryFile.filename, deliveryFile.options);
+    }).catch(function(err) {
+
+      // not found
+      return res.sendFile(crowi.publicDir + '/images/file-not-found.png');
+    });
+  };
+
+  /**
+   * @api {get} /attachments.list Get attachments of the page
+   * @apiName ListAttachments
+   * @apiGroup Attachment
+   *
+   * @apiParam {String} page_id
+   */
   api.list = function(req, res){
   api.list = function(req, res){
-    var id = req.params.pageId;
+    var id = req.query.page_id || null;
+    if (!id) {
+      return res.json(ApiResponse.error('Parameters page_id is required.'));
+    }
 
 
     Attachment.getListByPageId(id)
     Attachment.getListByPageId(id)
     .then(function(attachments) {
     .then(function(attachments) {
-      res.json({
-        status: true,
-        data: {
-          attachments: attachments
-        }
-      });
+      return res.json(ApiResponse.success({
+        attachments: attachments
+      }));
     });
     });
   };
   };
 
 
   /**
   /**
+   * @api {post} /attachments.add Add attachment to the page
+   * @apiName AddAttachments
+   * @apiGroup Attachment
    *
    *
+   * @apiParam {String} page_id
+   * @apiParam {File} file
    */
    */
   api.add = function(req, res){
   api.add = function(req, res){
-    var id = req.params.pageId,
-      path = decodeURIComponent(req.body.path),
+    var id = req.body.page_id || 0,
+      path = decodeURIComponent(req.body.path) || null,
       pageCreated = false,
       pageCreated = false,
       page = {};
       page = {};
 
 
@@ -42,14 +71,14 @@ module.exports = function(crowi, app) {
     var tmpFile = req.files.file || null;
     var tmpFile = req.files.file || null;
     debug('Uploaded tmpFile: ', tmpFile);
     debug('Uploaded tmpFile: ', tmpFile);
     if (!tmpFile) {
     if (!tmpFile) {
-      return res.json({
-        status: false,
-        message: 'File error.'
-      });
+      return res.json(ApiResponse.error('File error.'));
     }
     }
 
 
     new Promise(function(resolve, reject) {
     new Promise(function(resolve, reject) {
       if (id == 0) {
       if (id == 0) {
+        if (path === null) {
+          throw new Error('path required if page_id is not specified.');
+        }
         debug('Create page before file upload');
         debug('Create page before file upload');
         Page.create(path, '# '  + path, req.user, {grant: Page.GRANT_OWNER})
         Page.create(path, '# '  + path, req.user, {grant: Page.GRANT_OWNER})
           .then(function(page) {
           .then(function(page) {
@@ -80,22 +109,21 @@ module.exports = function(crowi, app) {
           return Attachment.create(id, req.user, filePath, originalName, fileName, fileType, fileSize);
           return Attachment.create(id, req.user, filePath, originalName, fileName, fileType, fileSize);
         }).then(function(data) {
         }).then(function(data) {
           var imageUrl = fileUploader.generateUrl(data.filePath);
           var imageUrl = fileUploader.generateUrl(data.filePath);
-          return res.json({
-            status: true,
+          var result = {
+            page: page.toObject(),
+            attachment: data.toObject(),
             filename: imageUrl,
             filename: imageUrl,
-            attachment: data,
-            page: page,
             pageCreated: pageCreated,
             pageCreated: pageCreated,
-            message: 'Successfully uploaded.',
-          });
+          };
+
+          result.page.creator = User.filterToPublicFields(result.page.creator);
+          result.attachment.creator = User.filterToPublicFields(result.attachment.creator);
+          return res.json(ApiResponse.success(result));
         }).catch(function (err) {
         }).catch(function (err) {
           debug('Error on saving attachment data', err);
           debug('Error on saving attachment data', err);
           // @TODO
           // @TODO
           // Remove from S3
           // Remove from S3
-          return res.json({
-            status: false,
-            message: 'Error while uploading.',
-          });
+          return res.json(ApiResponse.error('Error while uploading.'));
         }).finally(function() {
         }).finally(function() {
           fs.unlink(tmpPath, function (err) {
           fs.unlink(tmpPath, function (err) {
             if (err) {
             if (err) {
@@ -104,6 +132,9 @@ module.exports = function(crowi, app) {
           });
           });
         })
         })
       ;
       ;
+    }).catch(function(err) {
+      debug('Attachement upload error', err);
+      return res.json(ApiResponse.error('Error.'));
     });
     });
   };
   };
 
 

+ 23 - 21
lib/routes/index.js

@@ -14,7 +14,7 @@ module.exports = function(crowi, app) {
     , revision  = require('./revision')(crowi, app)
     , revision  = require('./revision')(crowi, app)
     , search    = require('./search')(crowi, app)
     , search    = require('./search')(crowi, app)
     , loginRequired = middleware.loginRequired
     , loginRequired = middleware.loginRequired
-    , accessTokenParser = middleware.accessTokenParser
+    , accessTokenParser = middleware.accessTokenParser(crowi, app)
     , csrf      = middleware.csrfVerify(crowi, app)
     , csrf      = middleware.csrfVerify(crowi, app)
     ;
     ;
 
 
@@ -79,39 +79,41 @@ module.exports = function(crowi, app) {
 
 
   app.get( '/:id([0-9a-z]{24})'       , loginRequired(crowi, app) , page.api.redirector);
   app.get( '/:id([0-9a-z]{24})'       , loginRequired(crowi, app) , page.api.redirector);
   app.get( '/_r/:id([0-9a-z]{24})'    , loginRequired(crowi, app) , page.api.redirector); // alias
   app.get( '/_r/:id([0-9a-z]{24})'    , loginRequired(crowi, app) , page.api.redirector); // alias
+  app.get( '/files/:id([0-9a-z]{24})' , loginRequired(crowi, app) , attachment.api.redirector);
 
 
   app.get( '/_search'                 , loginRequired(crowi, app) , search.searchPage);
   app.get( '/_search'                 , loginRequired(crowi, app) , search.searchPage);
-  app.get( '/_api/search'             , accessTokenParser(crowi, app) , loginRequired(crowi, app) , search.api.search);
+  app.get( '/_api/search'             , accessTokenParser , loginRequired(crowi, app) , search.api.search);
 
 
   app.get( '/_api/check_username'     , user.api.checkUsername);
   app.get( '/_api/check_username'     , user.api.checkUsername);
   app.post('/_api/me/picture/upload'  , loginRequired(crowi, app) , me.api.uploadPicture);
   app.post('/_api/me/picture/upload'  , loginRequired(crowi, app) , me.api.uploadPicture);
   app.get( '/_api/user/bookmarks'     , loginRequired(crowi, app) , user.api.bookmarks);
   app.get( '/_api/user/bookmarks'     , loginRequired(crowi, app) , user.api.bookmarks);
-  app.get( '/_api/attachment/page/:pageId', loginRequired(crowi, app) , attachment.api.list);
-  app.post('/_api/attachment/page/:pageId', loginRequired(crowi, app) , attachment.api.add);
-  app.post('/_api/attachment/:id/remove',loginRequired(crowi, app), attachment.api.remove);
 
 
   app.get( '/user/:username([^/]+)/bookmarks'      , loginRequired(crowi, app) , page.userBookmarkList);
   app.get( '/user/:username([^/]+)/bookmarks'      , loginRequired(crowi, app) , page.userBookmarkList);
   app.get( '/user/:username([^/]+)/recent-create'  , loginRequired(crowi, app) , page.userRecentCreatedList);
   app.get( '/user/:username([^/]+)/recent-create'  , loginRequired(crowi, app) , page.userRecentCreatedList);
 
 
   // HTTP RPC Styled API (に徐々に移行していいこうと思う)
   // HTTP RPC Styled API (に徐々に移行していいこうと思う)
-  app.get('/_api/users.list'          , accessTokenParser(crowi, app) , loginRequired(crowi, app) , user.api.list);
-  app.post('/_api/pages.create'        , accessTokenParser(crowi, app) , loginRequired(crowi, app) , csrf, page.api.create);
-  app.get('/_api/pages.get'           , accessTokenParser(crowi, app) , loginRequired(crowi, app) , page.api.get);
-  app.get('/_api/pages.updatePost'    , accessTokenParser(crowi, app) , loginRequired(crowi, app) , page.api.getUpdatePost);
-  app.post('/_api/pages.seen'         , accessTokenParser(crowi, app) , loginRequired(crowi, app) , page.api.seen);
-  app.post('/_api/pages.rename'       , accessTokenParser(crowi, app) , loginRequired(crowi, app) , csrf, page.api.rename);
+  app.get('/_api/users.list'          , accessTokenParser , loginRequired(crowi, app) , user.api.list);
+  app.post('/_api/pages.create'       , accessTokenParser , loginRequired(crowi, app) , csrf, page.api.create);
+  app.post('/_api/pages.update'       , accessTokenParser , loginRequired(crowi, app) , csrf, page.api.update);
+  app.get('/_api/pages.get'           , accessTokenParser , loginRequired(crowi, app) , page.api.get);
+  app.get('/_api/pages.updatePost'    , accessTokenParser , loginRequired(crowi, app) , page.api.getUpdatePost);
+  app.post('/_api/pages.seen'         , accessTokenParser , loginRequired(crowi, app) , page.api.seen);
+  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.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.get('/_api/comments.get'        , accessTokenParser(crowi, app) , loginRequired(crowi, app) , comment.api.get);
-  app.post('/_api/comments.add'       , form.comment, accessTokenParser(crowi, app) , loginRequired(crowi, app) , csrf, comment.api.add);
-  app.get( '/_api/bookmarks.get'      , accessTokenParser(crowi, app) , loginRequired(crowi, app) , bookmark.api.get);
-  app.post('/_api/bookmarks.add'      , accessTokenParser(crowi, app) , loginRequired(crowi, app) , csrf, bookmark.api.add);
-  app.post('/_api/bookmarks.remove'   , accessTokenParser(crowi, app) , loginRequired(crowi, app) , csrf, bookmark.api.remove);
-  app.post('/_api/likes.add'          , accessTokenParser(crowi, app) , loginRequired(crowi, app) , csrf, page.api.like);
-  app.post('/_api/likes.remove'       , accessTokenParser(crowi, app) , loginRequired(crowi, app) , csrf, page.api.unlike);
-
-  app.get( '/_api/revisions.get'      , accessTokenParser(crowi, app) , loginRequired(crowi, app) , revision.api.get);
-  app.get( '/_api/revisions.list'     , accessTokenParser(crowi, app) , loginRequired(crowi, app) ,revision.api.list);
+  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);
+  app.post('/_api/bookmarks.add'      , accessTokenParser , loginRequired(crowi, app) , csrf, bookmark.api.add);
+  app.post('/_api/bookmarks.remove'   , accessTokenParser , loginRequired(crowi, app) , csrf, bookmark.api.remove);
+  app.post('/_api/likes.add'          , accessTokenParser , loginRequired(crowi, app) , csrf, page.api.like);
+  app.post('/_api/likes.remove'       , accessTokenParser , loginRequired(crowi, app) , csrf, page.api.unlike);
+  app.get( '/_api/attachments.list'   , accessTokenParser , loginRequired(crowi, app) , attachment.api.list);
+  app.post('/_api/attachments.add'    , accessTokenParser , loginRequired(crowi, app) , csrf, attachment.api.add);
+  app.post('/_api/attachments.remove' , accessTokenParser , loginRequired(crowi, app) , csrf, attachment.api.remove);
+
+  app.get( '/_api/revisions.get'      , accessTokenParser , loginRequired(crowi, app) , revision.api.get);
+  app.get( '/_api/revisions.list'     , accessTokenParser , loginRequired(crowi, app) ,revision.api.list);
 
 
   //app.get('/_api/revision/:id'     , user.useUserData()         , revision.api.get);
   //app.get('/_api/revision/:id'     , user.useUserData()         , revision.api.get);
   //app.get('/_api/r/:revisionId'    , user.useUserData()         , page.api.get);
   //app.get('/_api/r/:revisionId'    , user.useUserData()         , page.api.get);

+ 86 - 2
lib/routes/page.js

@@ -12,6 +12,14 @@ module.exports = function(crowi, app) {
 
 
     , actions = {};
     , actions = {};
 
 
+  // register page events
+
+  var pageEvent = crowi.event('page');
+  pageEvent.on('update', function(page, user) {
+    crowi.getIo().sockets.emit('page edited', {page, user});
+  });
+
+
   function getPathFromRequest(req) {
   function getPathFromRequest(req) {
     var path = '/' + (req.params[0] || '');
     var path = '/' + (req.params[0] || '');
     return path.replace(/\.md$/, '');
     return path.replace(/\.md$/, '');
@@ -395,7 +403,6 @@ module.exports = function(crowi, app) {
         throw new Error('Data not found');
         throw new Error('Data not found');
       }
       }
       // TODO: move to events
       // TODO: move to events
-      crowi.getIo().sockets.emit('page edited', {page: data, user: req.user});
       if (notify.slack) {
       if (notify.slack) {
         if (notify.slack.on && notify.slack.channel) {
         if (notify.slack.on && notify.slack.channel) {
           data.updateSlackChannel(notify.slack.channel).then(function(){}).catch(function(){});
           data.updateSlackChannel(notify.slack.channel).then(function(){}).catch(function(){});
@@ -526,9 +533,86 @@ module.exports = function(crowi, app) {
    *
    *
    * @apiParam {String} body
    * @apiParam {String} body
    * @apiParam {String} path
    * @apiParam {String} path
-   * @apiParam {String} revision_id
+   * @apiParam {String} grant
    */
    */
   api.create = function(req, res){
   api.create = function(req, res){
+    var body = req.body.body || null;
+    var pagePath = req.body.path || null;
+    var grant = req.body.grant || null;
+
+    if (body === null || pagePath === null) {
+      return res.json(ApiResponse.error('Parameters body and path are required.'));
+    }
+
+    var ignoreNotFound = true;
+    Page.findPage(pagePath, req.user, null, ignoreNotFound)
+    .then(function(data) {
+      if (data !== null) {
+        throw new Error('Page exists');
+      }
+
+      return Page.create(pagePath, body, req.user, {grant: grant});
+    }).then(function(data) {
+      if (!data) {
+        throw new Error('Failed to create page.');
+      }
+      var result = { page: data.toObject() };
+
+      result.page.lastUpdateUser = User.filterToPublicFields(data.lastUpdateUser);
+      result.page.creator = User.filterToPublicFields(data.creator);
+      return res.json(ApiResponse.success(result));
+    }).catch(function(err) {
+      return res.json(ApiResponse.error(err));
+    });;
+
+  };
+
+  /**
+   * @api {post} /pages.update Update page
+   * @apiName UpdatePage
+   * @apiGroup Page
+   *
+   * @apiParam {String} body
+   * @apiParam {String} page_id
+   * @apiParam {String} revision_id
+   * @apiParam {String} grant
+   *
+   * In the case of the page exists:
+   * - If revision_id is specified => update the page,
+   * - If revision_id is not specified => force update by the new contents.
+   */
+  api.update = function(req, res){
+    var pageBody = req.body.body || null;
+    var pageId = req.body.page_id || null;
+    var revisionId = req.body.revision_id || null;
+    var grant = req.body.grant || null;
+
+    if (pageId === null || pageBody === null) {
+      return res.json(ApiResponse.error('page_id and body are required.'));
+    }
+
+    Page.findPageByIdAndGrantedUser(pageId, req.user)
+    .then(function(pageData) {
+      if (pageData && revisionId !== null && !pageData.isUpdatable(revisionId)) {
+        throw new Error('Revision error.');
+      };
+
+      var grantOption = {grant: pageData.grant};
+      if (grant !== null) {
+        grantOption.grant = grant;
+      }
+      return Page.updatePage(pageData, pageBody, req.user, grantOption);
+    }).then(function(pageData) {
+      var result = {
+        page: pageData.toObject(),
+      };
+      result.page.lastUpdateUser = User.filterToPublicFields(result.page.lastUpdateUser);
+      return res.json(ApiResponse.success(result));
+    }).catch(function(err) {
+      debug('error on _api/pages.update', err);
+      return res.json(ApiResponse.error(err));
+    });
+
   };
   };
 
 
   /**
   /**

+ 4 - 0
lib/util/middlewares.js

@@ -33,7 +33,9 @@ exports.csrfVerify = function(crowi, app) {
     var token = req.body._csrf || req.query._csrf || null;
     var token = req.body._csrf || req.query._csrf || null;
     var csrfKey = (req.session && req.session.id) || 'anon';
     var csrfKey = (req.session && req.session.id) || 'anon';
 
 
+    debug('req.skipCsrfVerify', req.skipCsrfVerify);
     if (req.skipCsrfVerify) {
     if (req.skipCsrfVerify) {
+      debug('csrf verify skipped');
       return next();
       return next();
     }
     }
 
 
@@ -193,10 +195,12 @@ exports.accessTokenParser = function(crowi, app) {
 
 
     var User = crowi.model('User')
     var User = crowi.model('User')
 
 
+    debug('accessToken is', accessToken);
     User.findUserByApiToken(accessToken)
     User.findUserByApiToken(accessToken)
     .then(function(userData) {
     .then(function(userData) {
       req.user = userData;
       req.user = userData;
       req.skipCsrfVerify = true;
       req.skipCsrfVerify = true;
+      debug('Access token parsed: skipCsrfVerify');
 
 
       next();
       next();
     }).catch(function(err) {
     }).catch(function(err) {

+ 1 - 1
lib/views/_form.html

@@ -49,7 +49,7 @@
           {% endfor %}
           {% endfor %}
         </select>
         </select>
         {% endif %}
         {% endif %}
-        <input type="hidden" name="_csrf" value="{{ csrf() }}">
+        <input type="hidden" id="edit-form-csrf" name="_csrf" value="{{ csrf() }}">
         <input type="submit" class="btn btn-primary" id="edit-form-submit" value="ページを更新" />
         <input type="submit" class="btn btn-primary" id="edit-form-submit" value="ページを更新" />
       </div>
       </div>
     </div>
     </div>

+ 1 - 1
lib/views/page.html

@@ -26,8 +26,8 @@
             class="like-button btn btn-default btn-sm {% if page.isLiked(user) %}active{% endif %}"
             class="like-button btn btn-default btn-sm {% if page.isLiked(user) %}active{% endif %}"
         ><i class="fa fa-thumbs-o-up"></i></button>
         ><i class="fa fa-thumbs-o-up"></i></button>
       </div>
       </div>
+      {% endif %}
     </div>
     </div>
-    {% endif %}
   </header>
   </header>
   {% else %}
   {% else %}
   {# trash/* #}
   {# trash/* #}

+ 27 - 17
lib/views/page_list.html

@@ -14,24 +14,34 @@
 
 
 <div class="header-wrap">
 <div class="header-wrap">
   <header class="portal-header {% if page %}has-page{% endif %}">
   <header class="portal-header {% if page %}has-page{% endif %}">
-    {% if page %}
-      <a href="#" title="Bookmark" class="bookmark-link" id="bookmark-button" data-csrftoken="{{ csrf() }}" data-bookmarked="0"><i class="fa fa-star-o"></i></a>
-
-    {% endif %}
-    <h1 class="title">
-      <span class="" id="revision-path">{{ path|insertSpaceToEachSlashes }}</span>
-      {% if searchConfigured() && !isTopPage() && !isTrashPage() %}
-      <form class="input-group search-input-group hidden-xs hidden-sm" data-toggle="tooltip" data-placement="bottom" title="{{ path }} 以下から検索" id="search-listpage-form">
-        <input type="text" class="search-listpage-input form-control" data-path="{{ path }}" id="search-listpage-input">
-        <span class="input-group-btn search-listpage-submit-group">
-          <button type="submit" class="btn btn-default" id="search-listpage-submit">
-            <i class="fa fa-search"></i>
-          </button>
-        </span>
-        <a class="search-listpage-clear" id="search-listpage-clear"><i class="fa fa-times-circle"></i></a>
-      </form>
+    <div class="flex-title-line">
+      <h1 class="title flex-item-title">
+        <span id="revision-path">{{ path|insertSpaceToEachSlashes }}</span>
+        {% if searchConfigured() && !isTopPage() && !isTrashPage() %}
+        <form class="input-group search-input-group hidden-xs hidden-sm" data-toggle="tooltip" data-placement="bottom" title="{{ path }} 以下から検索" id="search-listpage-form">
+          <input type="text" class="search-listpage-input form-control" data-path="{{ path }}" id="search-listpage-input">
+          <span class="input-group-btn search-listpage-submit-group">
+            <button type="submit" class="btn btn-default" id="search-listpage-submit">
+              <i class="fa fa-search"></i>
+            </button>
+          </span>
+          <a class="search-listpage-clear" id="search-listpage-clear"><i class="fa fa-times-circle"></i></a>
+        </form>
+        {% endif %}
+      </h1>
+      {% if page %}
+      <div class="flex-item-action">
+        <a href="#" title="Bookmark" class="bookmark-link" id="bookmark-button" data-csrftoken="{{ csrf() }}" data-bookmarked="0"><i class="fa fa-star-o"></i></a>
+      </div>
+      <div class="flex-item-action visible-xs visible-sm">
+        <button
+            data-csrftoken="{{ csrf() }}"
+            data-liked="{% if page.isLiked(user) %}1{% else %}0{% endif %}"
+            class="like-button btn btn-default btn-sm {% if page.isLiked(user) %}active{% endif %}"
+        ><i class="fa fa-thumbs-o-up"></i></button>
+      </div>
       {% endif %}
       {% endif %}
-    </h1>
+    </div>
   </header>
   </header>
 </div>
 </div>
 
 

BIN
public/images/file-not-found.png


+ 8 - 4
resource/js/crowi-form.js

@@ -409,11 +409,14 @@ $(function() {
 
 
   var $inputForm = $('form.uploadable textarea#form-body');
   var $inputForm = $('form.uploadable textarea#form-body');
   if ($inputForm.length > 0) {
   if ($inputForm.length > 0) {
+    var csrfToken = $('form.uploadable input#edit-form-csrf').val();
     var pageId = $('#content-main').data('page-id') || 0;
     var pageId = $('#content-main').data('page-id') || 0;
     var attachmentOption = {
     var attachmentOption = {
-      uploadUrl: '/_api/attachment/page/' + pageId,
+      uploadUrl: '/_api/attachments.add',
       extraParams: {
       extraParams: {
-        path: location.pathname
+        path: location.pathname,
+        page_id: pageId,
+        _csrf: csrfToken
       },
       },
       progressText: '(Uploading file...)',
       progressText: '(Uploading file...)',
       urlText: "\n![file]({filename})\n"
       urlText: "\n![file]({filename})\n"
@@ -421,8 +424,9 @@ $(function() {
 
 
     attachmentOption.onFileUploadResponse = function(res) {
     attachmentOption.onFileUploadResponse = function(res) {
       var result = JSON.parse(res.response);
       var result = JSON.parse(res.response);
+      console.log(result);
 
 
-      if (result.status && result.pageCreated) {
+      if (result.ok && result.pageCreated) {
         var page = result.page,
         var page = result.page,
             pageId = page._id;
             pageId = page._id;
 
 
@@ -431,7 +435,7 @@ $(function() {
 
 
         unbindInlineAttachment($inputForm);
         unbindInlineAttachment($inputForm);
 
 
-        attachmentOption.uploadUrl = '/_api/attachment/page/' + pageId,
+        attachmentOption.extraParams.page_id = pageId;
         bindInlineAttachment($inputForm, attachmentOption);
         bindInlineAttachment($inputForm, attachmentOption);
       }
       }
       return true;
       return true;

+ 6 - 2
resource/js/crowi.js

@@ -559,8 +559,12 @@ $(function() {
 
 
     // attachment
     // attachment
     var $pageAttachmentList = $('.page-attachments ul');
     var $pageAttachmentList = $('.page-attachments ul');
-    $.get('/_api/attachment/page/' + pageId, function(res) {
-      var attachments = res.data.attachments;
+    $.get('/_api/attachments.list', {page_id: pageId}, function(res) {
+      if (!res.ok) {
+        return ;
+      }
+
+      var attachments = res.attachments;
       if (attachments.length > 0) {
       if (attachments.length > 0) {
         $.each(attachments, function(i, file) {
         $.each(attachments, function(i, file) {
           $pageAttachmentList.append(
           $pageAttachmentList.append(

+ 1 - 0
tmp/cache/.gitignore

@@ -0,0 +1 @@
+*