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",
|
"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",
|
"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",
|
"login-to-view": "🔒 Log in to view",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
|
"deleted": "Deleted",
|
||||||
"purge": "Purge",
|
"purge": "Purge",
|
||||||
"restore": "Restore",
|
"restore": "Restore",
|
||||||
|
"restored": "Restored",
|
||||||
"move": "Move",
|
"move": "Move",
|
||||||
"change-owner": "Change Owner",
|
"change-owner": "Change Owner",
|
||||||
"fork": "Fork",
|
"fork": "Fork",
|
||||||
@@ -31,8 +33,10 @@
|
|||||||
"share": "Share",
|
"share": "Share",
|
||||||
"tools": "Tools",
|
"tools": "Tools",
|
||||||
"locked": "Locked",
|
"locked": "Locked",
|
||||||
|
"unlocked": "Unlocked",
|
||||||
"pinned": "Pinned",
|
"pinned": "Pinned",
|
||||||
"pinned-with-expiry": "Pinned until %1",
|
"pinned-with-expiry": "Pinned until %1",
|
||||||
|
"unpinned": "Unpinned",
|
||||||
"moved": "Moved",
|
"moved": "Moved",
|
||||||
"moved-from": "Moved from %1",
|
"moved-from": "Moved from %1",
|
||||||
"copy-ip": "Copy IP",
|
"copy-ip": "Copy IP",
|
||||||
|
|||||||
@@ -242,6 +242,19 @@ get:
|
|||||||
flagId:
|
flagId:
|
||||||
type: number
|
type: number
|
||||||
description: The flag identifier, if this particular post has been flagged before
|
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:
|
category:
|
||||||
$ref: ../../components/schemas/CategoryObject.yaml#/CategoryObject
|
$ref: ../../components/schemas/CategoryObject.yaml#/CategoryObject
|
||||||
tagWhitelist:
|
tagWhitelist:
|
||||||
|
|||||||
@@ -271,9 +271,38 @@ define('forum/topic/posts', [
|
|||||||
posts.find('[component="post/content"] img:not(.not-responsive)').addClass('img-responsive');
|
posts.find('[component="post/content"] img:not(.not-responsive)').addClass('img-responsive');
|
||||||
Posts.addBlockquoteEllipses(posts);
|
Posts.addBlockquoteEllipses(posts);
|
||||||
hidePostToolsForDeletedPosts(posts);
|
hidePostToolsForDeletedPosts(posts);
|
||||||
|
addTopicEvents();
|
||||||
addNecroPostMessage();
|
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() {
|
function addNecroPostMessage() {
|
||||||
var necroThreshold = ajaxify.data.necroThreshold * 24 * 60 * 60 * 1000;
|
var necroThreshold = ajaxify.data.necroThreshold * 24 * 60 * 60 * 1000;
|
||||||
if (!necroThreshold || (config.topicPostSort !== 'newest_to_oldest' && config.topicPostSort !== 'oldest_to_newest')) {
|
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) {
|
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'];
|
var actionsToLog = ['delete', 'restore', 'purge'];
|
||||||
if (!actionsToLog.includes(action)) {
|
if (!actionsToLog.includes(action)) {
|
||||||
return;
|
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');
|
Topics.thumbs = require('./thumbs');
|
||||||
require('./bookmarks')(Topics);
|
require('./bookmarks')(Topics);
|
||||||
require('./merge')(Topics);
|
require('./merge')(Topics);
|
||||||
|
Topics.events = require('./events');
|
||||||
|
|
||||||
Topics.exists = async function (tid) {
|
Topics.exists = async function (tid) {
|
||||||
return await db.exists('topic:' + tid);
|
return await db.exists('topic:' + tid);
|
||||||
@@ -171,6 +172,7 @@ Topics.getTopicWithPosts = async function (topicData, set, uid, start, stop, rev
|
|||||||
merger,
|
merger,
|
||||||
related,
|
related,
|
||||||
thumbs,
|
thumbs,
|
||||||
|
events,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
getMainPostAndReplies(topicData, set, uid, start, stop, reverse),
|
getMainPostAndReplies(topicData, set, uid, start, stop, reverse),
|
||||||
categories.getCategoryData(topicData.cid),
|
categories.getCategoryData(topicData.cid),
|
||||||
@@ -183,11 +185,13 @@ Topics.getTopicWithPosts = async function (topicData, set, uid, start, stop, rev
|
|||||||
getMerger(topicData),
|
getMerger(topicData),
|
||||||
getRelated(topicData, uid),
|
getRelated(topicData, uid),
|
||||||
Topics.thumbs.get(topicData.tid),
|
Topics.thumbs.get(topicData.tid),
|
||||||
|
Topics.events.get(topicData.tid),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
topicData.thumbs = thumbs;
|
topicData.thumbs = thumbs;
|
||||||
restoreThumbValue(topicData);
|
restoreThumbValue(topicData);
|
||||||
topicData.posts = posts;
|
topicData.posts = posts;
|
||||||
|
topicData.events = events;
|
||||||
topicData.category = category;
|
topicData.category = category;
|
||||||
topicData.tagWhitelist = tagWhitelist[0];
|
topicData.tagWhitelist = tagWhitelist[0];
|
||||||
topicData.minTags = category.minTags;
|
topicData.minTags = category.minTags;
|
||||||
|
|||||||
Reference in New Issue
Block a user