feat(topic-events): work in progress topic events logic and client-side implementation

v1.18.x
Julian Lam 4 years ago
parent faf5960373
commit ab2e1ecb40

@ -211,5 +211,7 @@
"no-connection": "There seems to be a problem with your internet connection",
"socket-reconnect-failed": "Unable to reach the server at this time. Click here to try again, or try again later",
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP"
"plugin-not-whitelisted": "Unable to install plugin – only plugins whitelisted by the NodeBB Package Manager can be installed via the ACP",
"topic-event-unrecognized": "Topic event '%1' unrecognized"
}

@ -22,8 +22,10 @@
"login-to-view": "🔒 Log in to view",
"edit": "Edit",
"delete": "Delete",
"deleted": "Deleted",
"purge": "Purge",
"restore": "Restore",
"restored": "Restored",
"move": "Move",
"change-owner": "Change Owner",
"fork": "Fork",
@ -31,8 +33,10 @@
"share": "Share",
"tools": "Tools",
"locked": "Locked",
"unlocked": "Unlocked",
"pinned": "Pinned",
"pinned-with-expiry": "Pinned until %1",
"unpinned": "Unpinned",
"moved": "Moved",
"moved-from": "Moved from %1",
"copy-ip": "Copy IP",

@ -242,6 +242,19 @@ get:
flagId:
type: number
description: The flag identifier, if this particular post has been flagged before
events:
type: array
items:
type: object
properties:
type:
type: string
id:
type: number
timestamp:
type: number
timestampISO:
type: string
category:
$ref: ../../components/schemas/CategoryObject.yaml#/CategoryObject
tagWhitelist:

@ -271,9 +271,38 @@ define('forum/topic/posts', [
posts.find('[component="post/content"] img:not(.not-responsive)').addClass('img-responsive');
Posts.addBlockquoteEllipses(posts);
hidePostToolsForDeletedPosts(posts);
addTopicEvents();
addNecroPostMessage();
};
function addTopicEvents() {
if (config.topicPostSort !== 'newest_to_oldest' && config.topicPostSort !== 'oldest_to_newest') {
return;
}
// TODO: Handle oldest_to_newest
const postTimestamps = ajaxify.data.posts.map(post => post.timestamp);
ajaxify.data.events.forEach((event) => {
const beforeIdx = postTimestamps.findIndex(timestamp => timestamp > event.timestamp);
let postEl;
if (beforeIdx > -1) {
postEl = document.querySelector(`[component="post"][data-pid="${ajaxify.data.posts[beforeIdx].pid}"]`);
}
app.parseAndTranslate('partials/topic/event', event, function (html) {
html = html.get(0);
if (postEl) {
document.querySelector('[component="topic"]').insertBefore(html, postEl);
} else {
document.querySelector('[component="topic"]').append(html);
}
$(html).find('.timeago').timeago();
});
});
}
function addNecroPostMessage() {
var necroThreshold = ajaxify.data.necroThreshold * 24 * 60 * 60 * 1000;
if (!necroThreshold || (config.topicPostSort !== 'newest_to_oldest' && config.topicPostSort !== 'oldest_to_newest')) {

@ -63,6 +63,12 @@ exports.doTopicAction = async function (action, event, caller, { tids }) {
};
async function logTopicAction(action, req, tid, title) {
// No 'purge' topic event (since topic is now gone)
if (action !== 'purge') {
await topics.events.log(tid, { type: action });
}
// Only log certain actions to system event log
var actionsToLog = ['delete', 'restore', 'purge'];
if (!actionsToLog.includes(action)) {
return;

@ -0,0 +1,90 @@
'use strict';
const db = require('../database');
const plugins = require('../plugins');
const Events = module.exports;
Events._types = {
pin: {
icon: 'fa-thumb-tack',
text: '[[topic:pinned]]',
},
pin_expiry: {
icon: 'fa-thumb-tack',
text: '[[topic:pinned-with-expiry]]',
},
unpin: {
icon: 'fa-thumb-tack',
text: '[[topic:unpinned]]',
},
lock: {
icon: 'fa-lock',
text: '[[topic:locked]]',
},
unlock: {
icon: 'fa-unlock',
text: '[[topic:unlocked]]',
},
delete: {
icon: 'fa-trash',
text: '[[topic:deleted]]',
},
restore: {
icon: 'fa-trash-o',
text: '[[topic:restored]]',
},
};
Events._ready = false;
Events.init = async () => {
if (!Events._ready) {
// Allow plugins to define additional topic event types
const { types } = await plugins.hooks.fire('filter:topicEvents.init', { types: Events._types });
Events._types = types;
Events._ready = true;
}
};
Events.get = async (tid) => {
await Events.init();
const topics = require('.');
if (!await topics.exists(tid)) {
throw new Error('[[error:no-topic]]');
}
const eventIds = await db.getSortedSetRangeWithScores(`topic:${tid}:events`, 0, -1);
const keys = eventIds.map(obj => `topicEvent:${obj.value}`);
const timestamps = eventIds.map(obj => obj.score);
const events = await db.getObjects(keys);
events.forEach((event, idx) => {
event.id = parseInt(eventIds[idx].value, 10);
event.timestamp = timestamps[idx];
event.timestampISO = new Date(timestamps[idx]).toISOString();
Object.assign(event, Events._types[event.type]);
});
return events;
};
Events.log = async (tid, payload) => {
await Events.init();
const topics = require('.');
const { type } = payload;
const now = Date.now();
if (!Events._types.hasOwnProperty(type)) {
throw new Error(`[[error:topic-event-unrecognized, ${type}]]`);
} else if (!await topics.exists(tid)) {
throw new Error('[[error:no-topic]]');
}
const eventId = await db.incrObjectField('global', 'nextTopicEventId');
await Promise.all([
db.setObject(`topicEvent:${eventId}`, payload),
db.sortedSetAdd(`topic:${tid}:events`, now, eventId),
]);
};

@ -33,6 +33,7 @@ require('./tools')(Topics);
Topics.thumbs = require('./thumbs');
require('./bookmarks')(Topics);
require('./merge')(Topics);
Topics.events = require('./events');
Topics.exists = async function (tid) {
return await db.exists('topic:' + tid);
@ -171,6 +172,7 @@ Topics.getTopicWithPosts = async function (topicData, set, uid, start, stop, rev
merger,
related,
thumbs,
events,
] = await Promise.all([
getMainPostAndReplies(topicData, set, uid, start, stop, reverse),
categories.getCategoryData(topicData.cid),
@ -183,11 +185,13 @@ Topics.getTopicWithPosts = async function (topicData, set, uid, start, stop, rev
getMerger(topicData),
getRelated(topicData, uid),
Topics.thumbs.get(topicData.tid),
Topics.events.get(topicData.tid),
]);
topicData.thumbs = thumbs;
restoreThumbValue(topicData);
topicData.posts = posts;
topicData.events = events;
topicData.category = category;
topicData.tagWhitelist = tagWhitelist[0];
topicData.minTags = category.minTags;

Loading…
Cancel
Save