Skip to content

Commit

Permalink
feat: closes #9684, allow event deletion
Browse files Browse the repository at this point in the history
fix: topic events appearing before necro messages
feat: add move topic event
feat: add ability to delete specific topic events via events.purge
  • Loading branch information
barisusakli committed Aug 10, 2021
1 parent 23dafa2 commit 358ad74
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 35 deletions.
2 changes: 2 additions & 0 deletions public/language/en-GB/topic.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"login-to-view": "🔒 Log in to view",
"edit": "Edit",
"delete": "Delete",
"delete-event": "Delete Event",
"purge": "Purge",
"restore": "Restore",
"move": "Move",
Expand All @@ -47,6 +48,7 @@
"unpinned-by": "Unpinned by",
"deleted-by": "Deleted by",
"restored-by": "Restored by",
"moved-from-by": "Moved from %1 by",
"queued-by": "Post queued for approval →",

"bookmark_instructions" : "Click here to return to the last read post in this thread.",
Expand Down
2 changes: 2 additions & 0 deletions public/openapi/write.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ paths:
$ref: 'write/topics/tid/thumbs/order.yaml'
/topics/{tid}/events:
$ref: 'write/topics/tid/events.yaml'
/topics/{tid}/events/{eventId}:
$ref: 'write/topics/tid/events/eventId.yaml'
/posts/{pid}:
$ref: 'write/posts/pid.yaml'
/posts/{pid}/state:
Expand Down
33 changes: 33 additions & 0 deletions public/openapi/write/topics/tid/events/eventId.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
delete:
tags:
- topics
summary: Delete a topic event
description: This operation deletes a single topic event from the topic
parameters:
- in: path
name: tid
schema:
type: string
required: true
description: a valid topic id
example: 1
- in: path
name: eventId
schema:
type: string
required: true
description: a valid topic event id
example: 1
responses:
'200':
description: Topic event successfully deleted
content:
application/json:
schema:
type: object
properties:
status:
$ref: ../../../../components/schemas/Status.yaml#/Status
response:
type: object
properties: {}
62 changes: 35 additions & 27 deletions public/src/client/topic/posts.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,7 @@ define('forum/topic/posts', [
posts.find('[component="post/content"] img:not(.not-responsive)').addClass('img-responsive');
Posts.addBlockquoteEllipses(posts);
hidePostToolsForDeletedPosts(posts);
Posts.addTopicEvents();
addNecroPostMessage();
addNecroPostMessage(Posts.addTopicEvents);
};

Posts.addTopicEvents = function (events) {
Expand Down Expand Up @@ -316,6 +315,7 @@ define('forum/topic/posts', [
}

return new Promise((resolve) => {
event.isAdminOrMod = ajaxify.data.privileges.isAdminOrMod;
app.parseAndTranslate('partials/topic/event', event, function (html) {
html = html.get(0);

Expand All @@ -333,14 +333,15 @@ define('forum/topic/posts', [
});
};

function addNecroPostMessage() {
function addNecroPostMessage(callback) {
var necroThreshold = ajaxify.data.necroThreshold * 24 * 60 * 60 * 1000;
if (!necroThreshold || (config.topicPostSort !== 'newest_to_oldest' && config.topicPostSort !== 'oldest_to_newest')) {
return;
return callback && callback();
}

$('[component="post"]').each(function () {
var post = $(this);
var postEls = $('[component="post"]').toArray();
Promise.all(postEls.map(function (post) {
post = $(post);
var prev = post.prev('[component="post"]');
if (post.is(':has(.necro-post)') || !prev.length) {
return;
Expand All @@ -350,27 +351,34 @@ define('forum/topic/posts', [
}

var diff = post.attr('data-timestamp') - prev.attr('data-timestamp');
if (Math.abs(diff) >= necroThreshold) {
var suffixAgo = $.timeago.settings.strings.suffixAgo;
var prefixAgo = $.timeago.settings.strings.prefixAgo;
var suffixFromNow = $.timeago.settings.strings.suffixFromNow;
var prefixFromNow = $.timeago.settings.strings.prefixFromNow;

$.timeago.settings.strings.suffixAgo = '';
$.timeago.settings.strings.prefixAgo = '';
$.timeago.settings.strings.suffixFromNow = '';
$.timeago.settings.strings.prefixFromNow = '';

var translationText = (diff > 0 ? '[[topic:timeago_later,' : '[[topic:timeago_earlier,') + $.timeago.inWords(diff) + ']]';

$.timeago.settings.strings.suffixAgo = suffixAgo;
$.timeago.settings.strings.prefixAgo = prefixAgo;
$.timeago.settings.strings.suffixFromNow = suffixFromNow;
$.timeago.settings.strings.prefixFromNow = prefixFromNow;
app.parseAndTranslate('partials/topic/necro-post', { text: translationText }, function (html) {
html.insertBefore(post);
});
}
return new Promise(function (resolve) {
if (Math.abs(diff) >= necroThreshold) {
var suffixAgo = $.timeago.settings.strings.suffixAgo;
var prefixAgo = $.timeago.settings.strings.prefixAgo;
var suffixFromNow = $.timeago.settings.strings.suffixFromNow;
var prefixFromNow = $.timeago.settings.strings.prefixFromNow;

$.timeago.settings.strings.suffixAgo = '';
$.timeago.settings.strings.prefixAgo = '';
$.timeago.settings.strings.suffixFromNow = '';
$.timeago.settings.strings.prefixFromNow = '';

var translationText = (diff > 0 ? '[[topic:timeago_later,' : '[[topic:timeago_earlier,') + $.timeago.inWords(diff) + ']]';

$.timeago.settings.strings.suffixAgo = suffixAgo;
$.timeago.settings.strings.prefixAgo = prefixAgo;
$.timeago.settings.strings.suffixFromNow = suffixFromNow;
$.timeago.settings.strings.prefixFromNow = prefixFromNow;
app.parseAndTranslate('partials/topic/necro-post', { text: translationText }, function (html) {
html.insertBefore(post);
resolve();
});
} else {
resolve();
}
});
})).then(function () {
callback && callback();
});
}

Expand Down
11 changes: 11 additions & 0 deletions public/src/client/topic/threadTools.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ define('forum/topic/threadTools', [
return false;
});

topicContainer.on('click', '[component="topic/event/delete"]', function () {
const eventId = $(this).attr('data-topic-event-id');
const eventEl = $(this).parents('[component="topic/event"]');
api.del(`/topics/${tid}/events/${eventId}`, {})
.then(function () {
eventEl.remove();
})
.catch(app.alertError);
return false;
});

// todo: should also use topicCommand, but no write api call exists for this yet
topicContainer.on('click', '[component="topic/mark-unread"]', function () {
socket.emit('topics.markUnread', tid, function (err) {
Expand Down
8 changes: 8 additions & 0 deletions src/controllers/write/topics.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,11 @@ Topics.getEvents = async (req, res) => {

helpers.formatApiResponse(200, res, await topics.events.get(req.params.tid));
};

Topics.deleteEvent = async (req, res) => {
if (!await privileges.topics.isAdminOrMod(req.params.tid, req.uid)) {
return helpers.formatApiResponse(403, res);
}
await topics.events.purge(req.params.tid, [req.params.eventId]);
helpers.formatApiResponse(200, res);
};
1 change: 1 addition & 0 deletions src/routes/write/topics.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ module.exports = function () {
setupApiRoute(router, 'put', '/:tid/thumbs/order', [...middlewares, middleware.checkRequired.bind(null, ['path', 'order'])], controllers.write.topics.reorderThumbs);

setupApiRoute(router, 'get', '/:tid/events', [middleware.assert.topic], controllers.write.topics.getEvents);
setupApiRoute(router, 'delete', '/:tid/events/:eventId', [middleware.assert.topic], controllers.write.topics.deleteEvent);

return router;
};
43 changes: 35 additions & 8 deletions src/topics/events.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
'use strict';

const _ = require('lodash');
const db = require('../database');
const user = require('../user');
const posts = require('../posts');
const categories = require('../categories');
const plugins = require('../plugins');

const Events = module.exports;
Expand Down Expand Up @@ -42,6 +44,10 @@ Events._types = {
icon: 'fa-trash-o',
text: '[[topic:restored-by]]',
},
move: {
icon: 'fa-arrow-circle-right',
// text: '[[topic:moved-from-by]]',
},
'post-queue': {
icon: 'fa-history',
text: '[[topic:queued-by]]',
Expand Down Expand Up @@ -83,6 +89,12 @@ async function getUserInfo(uids) {
return userMap;
}

async function getCategoryInfo(cids) {
const uniqCids = _.uniq(cids);
const catData = await categories.getCategoriesFields(uniqCids, ['name', 'slug', 'icon', 'color', 'bgColor']);
return _.zipObject(uniqCids, catData);
}

async function modifyEvent({ tid, uid, eventIds, timestamps, events }) {
// Add posts from post queue
const isPrivileged = await user.isPrivileged(uid);
Expand All @@ -98,7 +110,10 @@ async function modifyEvent({ tid, uid, eventIds, timestamps, events }) {
});
}

const users = await getUserInfo(events.map(event => event.uid).filter(Boolean));
const [users, fromCategories] = await Promise.all([
getUserInfo(events.map(event => event.uid).filter(Boolean)),
getCategoryInfo(events.map(event => event.fromCid).filter(Boolean)),
]);

// Remove events whose types no longer exist (e.g. plugin uninstalled)
events = events.filter(event => Events._types.hasOwnProperty(event.type));
Expand All @@ -111,6 +126,10 @@ async function modifyEvent({ tid, uid, eventIds, timestamps, events }) {
if (event.hasOwnProperty('uid')) {
event.user = users.get(event.uid === 'system' ? 'system' : parseInt(event.uid, 10));
}
if (event.hasOwnProperty('fromCid')) {
event.fromCategory = fromCategories[event.fromCid];
event.text = `[[topic:moved-from-by, ${event.fromCategory.name}]]`;
}

Object.assign(event, Events._types[event.type]);
});
Expand Down Expand Up @@ -149,11 +168,19 @@ Events.log = async (tid, payload) => {
return events;
};

Events.purge = async (tid) => {
// Should only be called on topic purge
const keys = [`topic:${tid}:events`];
const eventIds = await db.getSortedSetRange(keys[0], 0, -1);
keys.push(...eventIds.map(id => `topicEvent:${id}`));

await db.deleteAll(keys);
Events.purge = async (tid, eventIds = []) => {
if (eventIds.length) {
const isTopicEvent = await db.isSortedSetMembers(`topic:${tid}:events`, eventIds);
eventIds = eventIds.filter((id, index) => isTopicEvent[index]);
await Promise.all([
db.sortedSetRemove(`topic:${tid}:events`, eventIds),
db.deleteAll(eventIds.map(id => `topicEvent:${id}`)),
]);
} else {
const keys = [`topic:${tid}:events`];
const eventIds = await db.getSortedSetRange(keys[0], 0, -1);
keys.push(...eventIds.map(id => `topicEvent:${id}`));

await db.deleteAll(keys);
}
};
1 change: 1 addition & 0 deletions src/topics/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ module.exports = function (Topics) {
oldCid: oldCid,
}),
Topics.updateCategoryTagsCount([oldCid, cid], tags),
Topics.events.log(tid, { type: 'move', uid: data.uid, fromCid: oldCid }),
]);
const hookData = _.clone(data);
hookData.fromCid = oldCid;
Expand Down

1 comment on commit 358ad74

@julianlam
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I approve this commit.

Please sign in to comment.