diff --git a/src/posts/diffs.js b/src/posts/diffs.js index 38c4949f11..135139d4d6 100644 --- a/src/posts/diffs.js +++ b/src/posts/diffs.js @@ -1,121 +1,89 @@ 'use strict'; -var async = require('async'); -var validator = require('validator'); -var diff = require('diff'); +const validator = require('validator'); +const diff = require('diff'); -var db = require('../database'); -var meta = require('../meta'); -var plugins = require('../plugins'); -var translator = require('../translator'); +const db = require('../database'); +const meta = require('../meta'); +const plugins = require('../plugins'); +const translator = require('../translator'); -var Diffs = {}; -Diffs.exists = function (pid, callback) { - if (meta.config.enablePostHistory !== 1) { - return callback(null, 0); - } - - db.listLength('post:' + pid + ':diffs', function (err, numDiffs) { - return callback(err, !!numDiffs); - }); -}; - -Diffs.get = function (pid, since, callback) { - async.waterfall([ - function (next) { - Diffs.list(pid, next); - }, - function (timestamps, next) { - // Pass those made after `since`, and create keys - const keys = timestamps.filter(function (timestamp) { - return (parseInt(timestamp, 10) || 0) >= since; - }).map(function (timestamp) { - return 'diff:' + pid + '.' + timestamp; - }); - - db.getObjects(keys, next); - }, - ], callback); -}; - -Diffs.list = function (pid, callback) { - db.getListRange('post:' + pid + ':diffs', 0, -1, callback); -}; - -Diffs.save = function (pid, oldContent, newContent, callback) { - const now = Date.now(); - const patch = diff.createPatch('', newContent, oldContent); - async.parallel([ - async.apply(db.listPrepend.bind(db), 'post:' + pid + ':diffs', now), - async.apply(db.setObject.bind(db), 'diff:' + pid + '.' + now, { - pid: pid, - patch: patch, - }), - ], function (err) { - // No return arguments passed back - callback(err); - }); -}; - -Diffs.load = function (pid, since, uid, callback) { - var Posts = require('../posts'); - - // Retrieves all diffs made since `since` and replays them to reconstruct what the post looked like at `since` - since = parseInt(since, 10); - - if (isNaN(since) || since > Date.now()) { - return callback(new Error('[[error:invalid-data]]')); - } +module.exports = function (Posts) { + const Diffs = {}; + Posts.diffs = Diffs; + Diffs.exists = async function (pid) { + if (meta.config.enablePostHistory !== 1) { + return false; + } - async.parallel({ - post: async.apply(Posts.getPostSummaryByPids, [pid], uid, { - parse: false, - }), - diffs: async.apply(Posts.diffs.get, pid, since), - }, function (err, data) { - if (err) { - return callback(err); + const numDiffs = await db.listLength('post:' + pid + ':diffs'); + return !!numDiffs; + }; + + Diffs.get = async function (pid, since) { + const timestamps = await Diffs.list(pid); + // Pass those made after `since`, and create keys + const keys = timestamps.filter(t => (parseInt(t, 10) || 0) >= since) + .map(t => 'diff:' + pid + '.' + t); + return await db.getObjects(keys); + }; + + Diffs.list = async function (pid) { + return await db.getListRange('post:' + pid + ':diffs', 0, -1); + }; + + Diffs.save = async function (pid, oldContent, newContent) { + const now = Date.now(); + const patch = diff.createPatch('', newContent, oldContent); + await Promise.all([ + db.listPrepend('post:' + pid + ':diffs', now), + db.setObject('diff:' + pid + '.' + now, { + pid: pid, + patch: patch, + }), + ]); + }; + + Diffs.load = async function (pid, since, uid) { + // Retrieves all diffs made since `since` and replays them to reconstruct what the post looked like at `since` + since = parseInt(since, 10); + + if (isNaN(since) || since > Date.now()) { + throw new Error('[[error:invalid-data]]'); } + const [post, diffs] = await Promise.all([ + Posts.getPostSummaryByPids([pid], uid, { parse: false }), + Posts.diffs.get(pid, since), + ]); + const data = { + post: post, + diffs: diffs, + }; postDiffLoad(data); + const result = await plugins.fireHook('filter:parse.post', { postData: data.post }); + result.postData.content = translator.escape(result.postData.content); + return result.postData; + }; + + function postDiffLoad(data) { + data.post = data.post[0]; + data.post.content = validator.unescape(data.post.content); + + // Replace content with re-constructed content from that point in time + data.post.content = data.diffs.reduce(function (content, currentDiff) { + const result = diff.applyPatch(content, currentDiff.patch, { + fuzzFactor: 1, + }); - async.waterfall([ - function (next) { - plugins.fireHook('filter:parse.post', { postData: data.post }, next); - }, - function (data, next) { - data.postData.content = translator.escape(data.postData.content); - next(null, data.postData); - }, - ], callback); - }); -}; - -function postDiffLoad(data) { - data.post = data.post[0]; - data.post.content = validator.unescape(data.post.content); - - // Replace content with re-constructed content from that point in time - data.post.content = data.diffs.reduce(function (content, currentDiff) { - const result = diff.applyPatch(content, currentDiff.patch, { - fuzzFactor: 1, - }); - - return typeof result === 'string' ? result : content; - }, data.post.content); - - // Clear editor data (as it is outdated for this content) - delete data.post.edited; - data.post.editor = null; - - data.post.content = String(data.post.content || ''); -} + return typeof result === 'string' ? result : content; + }, data.post.content); -module.exports = function (Posts) { - Posts.diffs = {}; + // Clear editor data (as it is outdated for this content) + delete data.post.edited; + data.post.editor = null; - Object.keys(Diffs).forEach(function (property) { - Posts.diffs[property] = Diffs[property]; - }); + data.post.content = String(data.post.content || ''); + } }; diff --git a/src/posts/edit.js b/src/posts/edit.js index 38dd067c3f..f69a41b860 100644 --- a/src/posts/edit.js +++ b/src/posts/edit.js @@ -1,6 +1,5 @@ 'use strict'; -var async = require('async'); var validator = require('validator'); var _ = require('lodash'); @@ -19,161 +18,112 @@ module.exports = function (Posts) { require('./cache').del(pid); }); - Posts.edit = function (data, callback) { - var oldContent; // for diffing purposes - var postData; - var results; - - async.waterfall([ - function (next) { - privileges.posts.canEdit(data.pid, data.uid, next); - }, - function (canEdit, next) { - if (!canEdit.flag) { - return next(new Error(canEdit.message)); - } - Posts.getPostData(data.pid, next); - }, - function (_postData, next) { - if (!_postData) { - return next(new Error('[[error:no-post]]')); - } - - postData = _postData; - oldContent = postData.content; - postData.content = data.content; - postData.edited = Date.now(); - postData.editor = data.uid; - if (data.handle) { - postData.handle = data.handle; - } - plugins.fireHook('filter:post.edit', { req: data.req, post: postData, data: data, uid: data.uid }, next); - }, - function (result, next) { - postData = result.post; - - async.parallel({ - editor: function (next) { - user.getUserFields(data.uid, ['username', 'userslug'], next); - }, - topic: function (next) { - editMainPost(data, postData, next); - }, - }, next); - }, - function (_results, next) { - results = _results; - Posts.setPostFields(data.pid, postData, next); - }, - function (next) { - if (meta.config.enablePostHistory !== 1) { - return setImmediate(next); - } - - Posts.diffs.save(data.pid, oldContent, data.content, next); - }, - async.apply(Posts.uploads.sync, data.pid), - function (next) { - postData.cid = results.topic.cid; - postData.topic = results.topic; - plugins.fireHook('action:post.edit', { post: _.clone(postData), data: data, uid: data.uid }); - - require('./cache').del(String(postData.pid)); - pubsub.publish('post:edit', String(postData.pid)); - - Posts.parsePost(postData, next); - }, - function (postData, next) { - results.post = postData; - next(null, results); - }, - ], callback); + Posts.edit = async function (data) { + const canEdit = await privileges.posts.canEdit(data.pid, data.uid); + if (!canEdit.flag) { + throw new Error(canEdit.message); + } + let postData = await Posts.getPostData(data.pid); + if (!postData) { + throw new Error('[[error:no-post]]'); + } + + const oldContent = postData.content; // for diffing purposes + postData.content = data.content; + postData.edited = Date.now(); + postData.editor = data.uid; + if (data.handle) { + postData.handle = data.handle; + } + const result = await plugins.fireHook('filter:post.edit', { req: data.req, post: postData, data: data, uid: data.uid }); + postData = result.post; + + const [editor, topic] = await Promise.all([ + user.getUserFields(data.uid, ['username', 'userslug']), + editMainPost(data, postData), + ]); + + await Posts.setPostFields(data.pid, postData); + + if (meta.config.enablePostHistory === 1) { + await Posts.diffs.save(data.pid, oldContent, data.content); + } + await Posts.uploads.sync(data.pid); + + postData.cid = topic.cid; + postData.topic = topic; + plugins.fireHook('action:post.edit', { post: _.clone(postData), data: data, uid: data.uid }); + + require('./cache').del(String(postData.pid)); + pubsub.publish('post:edit', String(postData.pid)); + + postData = await Posts.parsePost(postData); + + return { + topic: topic, + editor: editor, + post: postData, + }; }; - function editMainPost(data, postData, callback) { - var tid = postData.tid; - var title = data.title ? data.title.trim() : ''; - - var topicData; - var results; - async.waterfall([ - function (next) { - async.parallel({ - topic: function (next) { - topics.getTopicFields(tid, ['cid', 'title', 'timestamp'], next); - }, - isMain: function (next) { - Posts.isMain(data.pid, next); - }, - }, next); - }, - function (_results, next) { - results = _results; - if (!results.isMain) { - return callback(null, { - tid: tid, - cid: results.topic.cid, - isMainPost: false, - renamed: false, - }); - } - - topicData = { - tid: tid, - cid: results.topic.cid, - uid: postData.uid, - mainPid: data.pid, - }; - - if (title) { - topicData.title = title; - topicData.slug = tid + '/' + (utils.slugify(title) || 'topic'); - } - - topicData.thumb = data.thumb || ''; - - data.tags = data.tags || []; - - if (!data.tags.length) { - return next(null, true); - } - - privileges.categories.can('topics:tag', topicData.cid, data.uid, next); - }, - function (canTag, next) { - if (!canTag) { - return next(new Error('[[error:no-privileges]]')); - } - - plugins.fireHook('filter:topic.edit', { req: data.req, topic: topicData, data: data }, next); - }, - function (results, next) { - db.setObject('topic:' + tid, results.topic, next); - }, - function (next) { - topics.updateTopicTags(tid, data.tags, next); - }, - function (next) { - topics.getTopicTagsObjects(tid, next); - }, - function (tags, next) { - topicData.tags = data.tags; - topicData.oldTitle = results.topic.title; - topicData.timestamp = results.topic.timestamp; - var renamed = translator.escape(validator.escape(String(title))) !== results.topic.title; - plugins.fireHook('action:topic.edit', { topic: topicData, uid: data.uid }); - next(null, { - tid: tid, - cid: topicData.cid, - uid: postData.uid, - title: validator.escape(String(title)), - oldTitle: results.topic.title, - slug: topicData.slug, - isMainPost: true, - renamed: renamed, - tags: tags, - }); - }, - ], callback); + async function editMainPost(data, postData) { + const tid = postData.tid; + const title = data.title ? data.title.trim() : ''; + + const [topicData, isMain] = await Promise.all([ + topics.getTopicFields(tid, ['cid', 'title', 'timestamp']), + Posts.isMain(data.pid), + ]); + + if (!isMain) { + return { + tid: tid, + cid: topicData.cid, + isMainPost: false, + renamed: false, + }; + } + + const newTopicData = { + tid: tid, + cid: topicData.cid, + uid: postData.uid, + mainPid: data.pid, + }; + if (title) { + newTopicData.title = title; + newTopicData.slug = tid + '/' + (utils.slugify(title) || 'topic'); + } + newTopicData.thumb = data.thumb || ''; + + data.tags = data.tags || []; + + if (data.tags.length) { + const canTag = await privileges.categories.can('topics:tag', topicData.cid, data.uid); + if (!canTag) { + throw new Error('[[error:no-privileges]]'); + } + } + const results = await plugins.fireHook('filter:topic.edit', { req: data.req, topic: newTopicData, data: data }); + await db.setObject('topic:' + tid, results.topic); + await topics.updateTopicTags(tid, data.tags); + const tags = await topics.getTopicTagsObjects(tid); + + topicData.tags = data.tags; + topicData.oldTitle = topicData.title; + topicData.timestamp = topicData.timestamp; + const renamed = translator.escape(validator.escape(String(title))) !== topicData.title; + plugins.fireHook('action:topic.edit', { topic: topicData, uid: data.uid }); + return { + tid: tid, + cid: topicData.cid, + uid: postData.uid, + title: validator.escape(String(title)), + oldTitle: topicData.title, + slug: topicData.slug, + isMainPost: true, + renamed: renamed, + tags: tags, + }; } };