mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +01:00
feat(topic-events): work in progress topic events logic and client-side implementation
This commit is contained in:
@@ -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;
|
||||
|
||||
90
src/topics/events.js
Normal file
90
src/topics/events.js
Normal file
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user