feat: add tools to recent/unread (#8477)

* feat: add tools to recent/unread

* fix: open api spec

* fix: more api spec
v1.18.x
Barış Soner Uşaklı 5 years ago committed by GitHub
parent 14eafcb6b8
commit 658dd03b03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3810,6 +3810,10 @@ paths:
type: number
canPost:
type: boolean
showSelect:
type: boolean
showTopicTools:
type: boolean
categories:
type: array
items:
@ -3871,6 +3875,8 @@ paths:
type: boolean
filter:
type: string
icon:
type: string
selectedFilter:
type: object
properties:
@ -3882,6 +3888,8 @@ paths:
type: boolean
filter:
type: string
icon:
type: string
terms:
type: array
items:
@ -3946,6 +3954,8 @@ paths:
properties:
showSelect:
type: boolean
showTopicTools:
type: boolean
nextStart:
type: number
topics:
@ -4199,6 +4209,8 @@ paths:
type: boolean
filter:
type: string
icon:
type: string
selectedFilter:
type: object
properties:
@ -4210,6 +4222,8 @@ paths:
type: boolean
filter:
type: string
icon:
type: string
- $ref: components/schemas/Pagination.yaml#/Pagination
- $ref: components/schemas/Breadcrumbs.yaml#/Breadcrumbs
- $ref: components/schemas/CommonProps.yaml#/CommonProps
@ -5492,6 +5506,10 @@ paths:
type: number
canPost:
type: boolean
showSelect:
type: boolean
showTopicTools:
type: boolean
categories:
type: array
items:
@ -5553,6 +5571,8 @@ paths:
type: boolean
filter:
type: string
icon:
type: string
selectedFilter:
type: object
properties:
@ -5564,6 +5584,8 @@ paths:
type: boolean
filter:
type: string
icon:
type: string
terms:
type: array
items:
@ -5620,6 +5642,10 @@ paths:
type: number
canPost:
type: boolean
showSelect:
type: boolean
showTopicTools:
type: boolean
categories:
type: array
items:
@ -5694,6 +5720,8 @@ paths:
type: boolean
filter:
type: string
icon:
type: string
selectedFilter:
type: object
properties:
@ -5705,6 +5733,8 @@ paths:
type: boolean
filter:
type: string
icon:
type: string
terms:
type: array
items:
@ -5817,6 +5847,8 @@ paths:
type: boolean
showSelect:
type: boolean
showTopicTools:
type: boolean
rssFeedUrl:
type: string
feeds:disableRSS:

@ -4,25 +4,17 @@ define('forum/category', [
'forum/infinitescroll',
'share',
'navigator',
'forum/category/tools',
'topicList',
'sort',
], function (infinitescroll, share, navigator, categoryTools, topicList, sort) {
], function (infinitescroll, share, navigator, topicList, sort) {
var Category = {};
$(window).on('action:ajaxify.start', function (ev, data) {
if (!String(data.url).startsWith('category/')) {
navigator.disable();
removeListeners();
}
});
function removeListeners() {
categoryTools.removeListeners();
topicList.removeListeners();
}
Category.init = function () {
var cid = ajaxify.data.cid;
@ -30,8 +22,6 @@ define('forum/category', [
share.addShareHandlers(ajaxify.data.name);
categoryTools.init(cid);
topicList.init('category', loadTopicsAfter);
sort.handleSort('categoryTopicSort', 'user.setCategorySort', 'category/' + ajaxify.data.slug);

@ -9,9 +9,7 @@ define('forum/category/tools', [
], function (topicSelect, components, translator) {
var CategoryTools = {};
CategoryTools.init = function (cid) {
CategoryTools.cid = cid;
CategoryTools.init = function () {
topicSelect.init(updateDropdownOptions);
handlePinnedTopicSort();
@ -36,7 +34,7 @@ define('forum/category/tools', [
if (!tids.length) {
return app.alertError('[[error:no-topics-selected]]');
}
socket.emit('topics.lock', { tids: tids, cid: CategoryTools.cid }, onCommandComplete);
socket.emit('topics.lock', { tids: tids }, onCommandComplete);
return false;
});
@ -45,7 +43,7 @@ define('forum/category/tools', [
if (!tids.length) {
return app.alertError('[[error:no-topics-selected]]');
}
socket.emit('topics.unlock', { tids: tids, cid: CategoryTools.cid }, onCommandComplete);
socket.emit('topics.unlock', { tids: tids }, onCommandComplete);
return false;
});
@ -54,7 +52,7 @@ define('forum/category/tools', [
if (!tids.length) {
return app.alertError('[[error:no-topics-selected]]');
}
socket.emit('topics.pin', { tids: tids, cid: CategoryTools.cid }, onCommandComplete);
socket.emit('topics.pin', { tids: tids }, onCommandComplete);
return false;
});
@ -63,7 +61,7 @@ define('forum/category/tools', [
if (!tids.length) {
return app.alertError('[[error:no-topics-selected]]');
}
socket.emit('topics.unpin', { tids: tids, cid: CategoryTools.cid }, onCommandComplete);
socket.emit('topics.unpin', { tids: tids }, onCommandComplete);
return false;
});
@ -92,13 +90,17 @@ define('forum/category/tools', [
if (!tids.length) {
return app.alertError('[[error:no-topics-selected]]');
}
move.init(tids, cid, onCommandComplete);
move.init(tids, null, onCommandComplete);
});
return false;
});
components.get('topic/move-all').on('click', function () {
var cid = ajaxify.data.cid;
if (!ajaxify.data.template.category) {
return app.alertError('[[error:invalid-data]]');
}
require(['forum/topic/move'], function (move) {
move.init(null, cid, function (err) {
if (err) {
@ -110,7 +112,7 @@ define('forum/category/tools', [
});
});
$('.category').on('click', '[component="topic/merge"]', function () {
components.get('topic/merge').on('click', function () {
require(['forum/topic/merge'], function (merge) {
merge.init();
});
@ -138,7 +140,7 @@ define('forum/category/tools', [
return;
}
socket.emit('topics.' + command, { tids: tids, cid: CategoryTools.cid }, onDeletePurgeComplete);
socket.emit('topics.' + command, { tids: tids }, onDeletePurgeComplete);
});
});
}
@ -259,7 +261,7 @@ define('forum/category/tools', [
return memo;
}, 0);
if (!ajaxify.data.privileges.isAdminOrMod || numPinned < 2) {
if ((!app.user.isAdmin && !app.user.isMod) || numPinned < 2) {
return;
}

@ -5,7 +5,8 @@ define('topicList', [
'handleBack',
'topicSelect',
'categorySearch',
], function (infinitescroll, handleBack, topicSelect, categorySearch) {
'forum/category/tools',
], function (infinitescroll, handleBack, topicSelect, categorySearch, categoryTools) {
var TopicList = {};
var templateName = '';
@ -24,6 +25,7 @@ define('topicList', [
$(window).on('action:ajaxify.start', function () {
TopicList.removeListeners();
categoryTools.removeListeners();
});
TopicList.init = function (template, cb) {
@ -32,6 +34,8 @@ define('topicList', [
templateName = template;
loadTopicsCallback = cb || loadTopicsAfter;
categoryTools.init();
TopicList.watchForNewPosts();
TopicList.handleCategorySelection();

@ -32,6 +32,9 @@ module.exports = function (Categories) {
'cid:' + cid + ':tids',
'cid:' + cid + ':tids:pinned',
'cid:' + cid + ':tids:posts',
'cid:' + cid + ':tids:votes',
'cid:' + cid + ':tids:lastposttime',
'cid:' + cid + ':recent_tids',
'cid:' + cid + ':pids',
'cid:' + cid + ':read_by_uid',
'cid:' + cid + ':uid:watch:state',

@ -95,6 +95,7 @@ categoryController.get = async function (req, res, next) {
categoryData.description = translator.escape(categoryData.description);
categoryData.privileges = userPrivileges;
categoryData.showSelect = userPrivileges.editable;
categoryData.showTopicTools = userPrivileges.editable;
categoryData.rssFeedUrl = nconf.get('url') + '/category/' + categoryData.cid + '.rss';
if (parseInt(req.uid, 10)) {
categories.markAsRead([cid], req.uid);

@ -72,21 +72,25 @@ helpers.buildFilters = function (url, filter, query) {
url: url + helpers.buildQueryString(query.cid, '', query.term),
selected: filter === '',
filter: '',
icon: 'fa-book',
}, {
name: '[[unread:new-topics]]',
url: url + helpers.buildQueryString(query.cid, 'new', query.term),
selected: filter === 'new',
filter: 'new',
icon: 'fa-clock-o',
}, {
name: '[[unread:watched-topics]]',
url: url + helpers.buildQueryString(query.cid, 'watched', query.term),
selected: filter === 'watched',
filter: 'watched',
icon: 'fa-bell-o',
}, {
name: '[[unread:unreplied-topics]]',
url: url + helpers.buildQueryString(query.cid, 'unreplied', query.term),
selected: filter === 'unreplied',
filter: 'unreplied',
icon: 'fa-reply',
}];
};

@ -37,11 +37,12 @@ recentController.getData = async function (req, url, sort) {
states.push(categories.watchStates.ignoring);
}
const [settings, categoryData, rssToken, canPost] = await Promise.all([
const [settings, categoryData, rssToken, canPost, isPrivileged] = await Promise.all([
user.getSettings(req.uid),
helpers.getCategoriesByStates(req.uid, cid, states),
user.auth.getFeedToken(req.uid),
canPostTopic(req.uid),
user.isPrivileged(req.uid),
]);
const start = Math.max(0, (page - 1) * settings.topicsPerPage);
@ -60,6 +61,8 @@ recentController.getData = async function (req, url, sort) {
});
data.canPost = canPost;
data.showSelect = isPrivileged;
data.showTopicTools = isPrivileged;
data.categories = categoryData.categories;
data.allCategoriesUrl = url + helpers.buildQueryString('', filter, '');
data.selectedCategory = categoryData.selectedCategory || null;

@ -22,9 +22,10 @@ unreadController.get = async function (req, res, next) {
if (!filterData.filters[filter]) {
return next();
}
const [watchedCategories, userSettings] = await Promise.all([
const [watchedCategories, userSettings, isPrivileged] = await Promise.all([
getWatchedCategories(req.uid, cid, filter),
user.getSettings(req.uid),
user.isPrivileged(req.uid),
]);
const page = parseInt(req.query.page, 10) || 1;
@ -48,7 +49,8 @@ unreadController.get = async function (req, res, next) {
req.query.page = Math.max(1, Math.min(data.pageCount, page));
return helpers.redirect(res, '/unread?' + querystring.stringify(req.query));
}
data.showSelect = isPrivileged;
data.showTopicTools = isPrivileged;
data.categories = watchedCategories.categories;
data.allCategoriesUrl = 'unread' + helpers.buildQueryString('', filter, '');
data.selectedCategory = watchedCategories.selectedCategory;

@ -191,9 +191,8 @@ SocketHelpers.rescindUpvoteNotification = async function (pid, fromuid) {
websockets.in('uid_' + uid).emit('event:notifications.updateCount', count);
};
SocketHelpers.emitToTopicAndCategory = function (event, data) {
websockets.in('topic_' + data.tid).emit(event, data);
websockets.in('category_' + data.cid).emit(event, data);
SocketHelpers.emitToTopicAndCategory = async function (event, data, uids) {
uids.forEach(toUid => websockets.in('uid_' + toUid).emit(event, data));
};
require('../promisify')(SocketHelpers);

@ -1,6 +1,7 @@
'use strict';
const async = require('async');
const user = require('../../user');
const topics = require('../../topics');
const categories = require('../../categories');
const privileges = require('../../privileges');
@ -12,6 +13,8 @@ module.exports = function (SocketTopics) {
throw new Error('[[error:invalid-data]]');
}
const uids = await user.getUidsFromSet('users:online', 0, -1);
await async.eachLimit(data.tids, 10, async function (tid) {
const canMove = await privileges.topics.isAdminOrMod(tid, socket.uid);
if (!canMove) {
@ -21,7 +24,8 @@ module.exports = function (SocketTopics) {
data.uid = socket.uid;
await topics.tools.move(tid, data);
socketHelpers.emitToTopicAndCategory('event:topic_moved', topicData);
const notifyUids = await privileges.categories.filterUids('topics:read', topicData.cid, uids);
socketHelpers.emitToTopicAndCategory('event:topic_moved', topicData, notifyUids);
if (!topicData.deleted) {
socketHelpers.sendNotificationToTopicOwner(tid, socket.uid, 'move', 'notifications:moved_your_topic');
}

@ -1,5 +1,6 @@
'use strict';
const user = require('../../user');
const topics = require('../../topics');
const events = require('../../events');
const privileges = require('../../privileges');
@ -65,17 +66,21 @@ module.exports = function (SocketTopics) {
throw new Error('[[error:no-privileges]]');
}
if (!data || !Array.isArray(data.tids) || !data.cid) {
if (!data || !Array.isArray(data.tids)) {
throw new Error('[[error:invalid-tid]]');
}
if (typeof topics.tools[action] !== 'function') {
return;
}
const uids = await user.getUidsFromSet('users:online', 0, -1);
await Promise.all(data.tids.map(async function (tid) {
const title = await topics.getTopicField(tid, 'title');
const data = await topics.tools[action](tid, socket.uid);
socketHelpers.emitToTopicAndCategory(event, data);
const notifyUids = await privileges.categories.filterUids('topics:read', data.cid, uids);
socketHelpers.emitToTopicAndCategory(event, data, notifyUids);
await logTopicAction(action, socket, tid, title);
}));
};

@ -48,7 +48,11 @@ module.exports = function (Topics) {
'cid:' + topicData.cid + ':tids',
'cid:' + topicData.cid + ':uid:' + topicData.uid + ':tids',
], timestamp, topicData.tid),
db.sortedSetAdd('cid:' + topicData.cid + ':tids:votes', 0, topicData.tid),
db.sortedSetsAdd([
'topics:views', 'topics:posts', 'topics:votes',
'cid:' + topicData.cid + ':tids:votes',
'cid:' + topicData.cid + ':tids:posts',
], 0, topicData.tid),
categories.updateRecentTid(topicData.cid, topicData.tid),
user.addTopicIdToUser(topicData.uid, topicData.tid, timestamp),
db.incrObjectField('category:' + topicData.cid, 'topic_count'),

@ -18,12 +18,6 @@ module.exports = function (Topics) {
deleterUid: uid,
deletedTimestamp: Date.now(),
}),
db.sortedSetsRemove([
'topics:recent',
'topics:posts',
'topics:views',
'topics:votes',
], tid),
removeTopicPidsFromCid(tid),
]);
};
@ -55,16 +49,11 @@ module.exports = function (Topics) {
}
Topics.restore = async function (tid) {
const topicData = await Topics.getTopicData(tid);
await Topics.deleteTopicFields(tid, [
'deleterUid', 'deletedTimestamp',
]);
await Promise.all([
Topics.setTopicField(tid, 'deleted', 0),
Topics.deleteTopicFields(tid, ['deleterUid', 'deletedTimestamp']),
Topics.updateRecent(tid, topicData.lastposttime),
db.sortedSetAddBulk([
['topics:posts', topicData.postcount, tid],
['topics:views', topicData.viewcount, tid],
['topics:votes', parseInt(topicData.votes, 10) || 0, tid],
]),
addTopicPidsToCid(tid),
]);
};

@ -60,9 +60,7 @@ module.exports = function (Topics) {
await db.sortedSetAdd('cid:' + topicData.cid + ':tids:lastposttime', lastposttime, tid);
if (!topicData.deleted) {
await Topics.updateRecent(tid, lastposttime);
}
await Topics.updateRecent(tid, lastposttime);
if (!topicData.pinned) {
await db.sortedSetAdd('cid:' + topicData.cid + ':tids', lastposttime, tid);

@ -0,0 +1,49 @@
'use strict';
const db = require('../../database');
const batch = require('../../batch');
module.exports = {
name: 'Re add deleted topics to topics:recent',
timestamp: Date.UTC(2018, 9, 11),
method: async function () {
const progress = this.progress;
await batch.processSortedSet('topics:tid', async function (tids) {
progress.incr(tids.length);
const topicData = await db.getObjectsFields(
tids.map(tid => 'topic:' + tid),
['tid', 'lastposttime', 'viewcount', 'postcount', 'upvotes', 'downvotes']
);
topicData.forEach((t) => {
if (t.hasOwnProperty('upvotes') && t.hasOwnProperty('downvotes')) {
t.votes = parseInt(t.upvotes, 10) - parseInt(t.downvotes, 10);
}
});
await db.sortedSetAdd('topics:recent',
topicData.map(t => t.lastposttime),
topicData.map(t => t.tid)
);
await db.sortedSetAdd('topics:views',
topicData.map(t => t.viewcount || 0),
topicData.map(t => t.tid)
);
await db.sortedSetAdd('topics:posts',
topicData.map(t => t.postcount || 0),
topicData.map(t => t.tid)
);
await db.sortedSetAdd('topics:votes',
topicData.map(t => t.votes || 0),
topicData.map(t => t.tid)
);
}, {
progress: progress,
batchSize: 500,
});
},
};
Loading…
Cancel
Save