diff --git a/public/src/app.js b/public/src/app.js
index 4725ca9866..6928ab3197 100644
--- a/public/src/app.js
+++ b/public/src/app.js
@@ -569,6 +569,7 @@ var socket,
createHeaderTooltips();
ajaxify.variables.parse();
+ ajaxify.currentPage = tpl_url;
app.processPage();
ajaxify.widgets.render(tpl_url, url);
diff --git a/public/src/forum/category.js b/public/src/forum/category.js
index 8acd9543ce..e834156a3a 100644
--- a/public/src/forum/category.js
+++ b/public/src/forum/category.js
@@ -1,13 +1,13 @@
"use strict";
/* global define, config, templates, app, utils, ajaxify, socket, translator */
-define(['composer', 'forum/pagination', 'share', 'navigator'], function(composer, pagination, share, navigator) {
+define(['composer', 'forum/pagination', 'share', 'navigator', 'forum/categoryTools'], function(composer, pagination, share, navigator, categoryTools) {
var Category = {},
loadingMoreTopics = false;
$(window).on('action:ajaxify.start', function(ev, data) {
- if(data.url.indexOf('category') !== 0) {
+ if(data && data.url.indexOf('category') !== 0) {
navigator.hide();
}
});
@@ -24,11 +24,14 @@ define(['composer', 'forum/pagination', 'share', 'navigator'], function(composer
});
ajaxify.register_events([
- 'event:new_topic'
+ 'event:new_topic', 'event:topic_deleted', 'event:topic_restored', 'event:topic_locked',
+ 'event:topic_unlocked', 'event:topic_pinned', 'event:topic_unpinned', 'event:topic_moved'
]);
socket.on('event:new_topic', Category.onNewTopic);
+ categoryTools.init(cid);
+
enableInfiniteLoading();
if (!config.usePagination) {
diff --git a/public/src/forum/categoryTools.js b/public/src/forum/categoryTools.js
new file mode 100644
index 0000000000..34608f6d63
--- /dev/null
+++ b/public/src/forum/categoryTools.js
@@ -0,0 +1,168 @@
+
+'use strict';
+
+/* globals define, app, translator, socket, bootbox */
+
+
+define(['forum/topic/move', 'topicSelect'], function(move, topicSelect) {
+
+ var CategoryTools = {};
+
+ CategoryTools.init = function(cid) {
+ CategoryTools.cid = cid;
+
+ topicSelect.init(onTopicSelect);
+
+ $('.delete_thread').on('click', function(e) {
+ var tids = topicSelect.getSelectedTids();
+
+ if (tids.length) {
+ var command = isAny(isTopicDeleted, tids) ? 'restore' : 'delete';
+
+ translator.translate('[[topic:thread_tools.' + command + '_confirm]]', function(msg) {
+ bootbox.confirm(msg, function(confirm) {
+ if (!confirm) {
+ return;
+ }
+
+ socket.emit('topics.' + command, tids, onCommandComplete);
+ });
+ });
+ }
+
+ return false;
+ });
+
+ $('.lock_thread').on('click', function(e) {
+ var tids = topicSelect.getSelectedTids();
+ if (tids.length) {
+ socket.emit(isAny(isTopicLocked, tids) ? 'topics.unlock' : 'topics.lock', tids, onCommandComplete);
+ }
+
+ return false;
+ });
+
+ $('.pin_thread').on('click', function(e) {
+ var tids = topicSelect.getSelectedTids();
+ if (tids.length) {
+ socket.emit(isAny(isTopicPinned, tids) ? 'topics.unpin' : 'topics.pin', tids, onCommandComplete);
+ }
+ return false;
+ });
+
+ $('.markAsUnreadForAll').on('click', function() {
+ var tids = topicSelect.getSelectedTids();
+ if (tids.length) {
+ socket.emit('topics.markAsUnreadForAll', tids, function(err) {
+ if(err) {
+ return app.alertError(err.message);
+ }
+ app.alertSuccess('[[topic:markAsUnreadForAll.success]]');
+
+ onCommandComplete();
+ });
+ }
+
+ return false;
+ });
+
+ $('.move_thread').on('click', function() {
+ var tids = topicSelect.getSelectedTids();
+ console.log(tids);
+ if (tids.length) {
+ move.init(tids, cid, onCommandComplete);
+ }
+ return false;
+ });
+
+
+ socket.on('event:topic_deleted', setDeleteState);
+ socket.on('event:topic_restored', setDeleteState);
+ socket.on('event:topic_locked', setLockedState);
+ socket.on('event:topic_unlocked', setLockedState);
+ socket.on('event:topic_pinned', setPinnedState);
+ socket.on('event:topic_unpinned', setPinnedState);
+ socket.on('event:topic_moved', onTopicMoved);
+ };
+
+ function closeDropDown() {
+ $('.thread-tools.open').find('.dropdown-toggle').trigger('click');
+ }
+
+ function onCommandComplete(err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ closeDropDown();
+ topicSelect.unselectAll();
+ }
+
+ function onTopicSelect() {
+ var tids = topicSelect.getSelectedTids();
+ var isAnyDeleted = isAny(isTopicDeleted, tids);
+ var isAnyPinned = isAny(isTopicPinned, tids);
+ var isAnyLocked = isAny(isTopicLocked, tids);
+
+ translator.translate(' [[topic:thread_tools.' + (isAnyDeleted ? 'restore' : 'delete') + ']]', function(translated) {
+ $('.delete_thread span').html(translated);
+ });
+
+ translator.translate(' [[topic:thread_tools.' + (isAnyPinned ? 'unpin' : 'pin') + ']]', function(translated) {
+ $('.pin_thread').html(translated);
+ });
+
+ translator.translate(' [[topic:thread_tools.' + (isAnyLocked ? 'un': '') + 'lock]]', function(translated) {
+ $('.lock_thread').html(translated);
+ });
+ }
+
+ function isAny(method, tids) {
+ for(var i=0; i');
-
- moveTopic();
+ app.alertSuccess('[[topic:topic_move_success, ' + targetCategoryLabel + ']]');
+ if (typeof Move.onComplete === 'function') {
+ Move.onComplete();
}
- }
+ });
+ }
- function moveTopic() {
- socket.emit('topics.move', {
- tid: tid,
- cid: targetCid
- }, function(err) {
- modal.modal('hide');
- if(err) {
- return app.alertError(err.message);
- }
+ function renderCategories(categories) {
+ var categoriesEl = modal.find('.category-list'),
+ info;
- app.alertSuccess('[[topic:topic_move_success, ' + targetCategoryLabel + ']]');
- });
- }
-
- function renderCategories(categories) {
- var categoriesEl = modal.find('.category-list'),
- info;
-
- for (var x = 0; x < categories.length; ++x) {
- info = categories[x];
- $('')
- .css({background: info.bgColor, color: info.color || '#fff'})
- .addClass(info.disabled === '1' ? ' disabled' : '')
- .attr('data-cid', info.cid)
- .html(' ' + info.name)
- .appendTo(categoriesEl);
+ for (var x = 0; x < categories.length; ++x) {
+ info = categories[x];
+ if(parseInt(info.cid, 10) === parseInt(Move.currentCid, 10)) {
+ continue;
}
-
- $('#categories-loading').remove();
+ $('')
+ .css({background: info.bgColor, color: info.color || '#fff'})
+ .addClass(info.disabled === '1' ? ' disabled' : '')
+ .attr('data-cid', info.cid)
+ .html(' ' + info.name)
+ .appendTo(categoriesEl);
}
- };
+
+ $('#categories-loading').remove();
+ }
return Move;
});
diff --git a/public/src/forum/topic/threadTools.js b/public/src/forum/topic/threadTools.js
index 186bf682c6..ec03d81200 100644
--- a/public/src/forum/topic/threadTools.js
+++ b/public/src/forum/topic/threadTools.js
@@ -18,7 +18,7 @@ define(['forum/topic/fork', 'forum/topic/move'], function(fork, move) {
translator.translate('[[topic:thread_tools.' + command + '_confirm]]', function(msg) {
bootbox.confirm(msg, function(confirm) {
if (confirm) {
- socket.emit('topics.' + command, tid);
+ socket.emit('topics.' + command, [tid]);
}
});
});
@@ -27,18 +27,18 @@ define(['forum/topic/fork', 'forum/topic/move'], function(fork, move) {
});
$('.lock_thread').on('click', function(e) {
- socket.emit(threadState.locked !== '1' ? 'topics.lock' : 'topics.unlock', tid);
+ socket.emit(threadState.locked !== '1' ? 'topics.lock' : 'topics.unlock', [tid]);
return false;
});
$('.pin_thread').on('click', function(e) {
- socket.emit(threadState.pinned !== '1' ? 'topics.pin' : 'topics.unpin', tid);
+ socket.emit(threadState.pinned !== '1' ? 'topics.pin' : 'topics.unpin', [tid]);
return false;
});
$('.markAsUnreadForAll').on('click', function() {
var btn = $(this);
- socket.emit('topics.markAsUnreadForAll', tid, function(err) {
+ socket.emit('topics.markAsUnreadForAll', [tid], function(err) {
if(err) {
return app.alertError(err.message);
}
@@ -48,7 +48,10 @@ define(['forum/topic/fork', 'forum/topic/move'], function(fork, move) {
return false;
});
- move.init(tid);
+ $('.move_thread').on('click', function(e) {
+ move.init([tid], ajaxify.variables.get('category_id'));
+ return false;
+ });
fork.init();
}
diff --git a/public/src/forum/unread.js b/public/src/forum/unread.js
index cdfe192df3..d7b31b7eec 100644
--- a/public/src/forum/unread.js
+++ b/public/src/forum/unread.js
@@ -2,7 +2,7 @@
/* globals define, app, socket */
-define(['forum/recent'], function(recent) {
+define(['forum/recent', 'topicSelect'], function(recent, topicSelect) {
var Unread = {},
loadingMoreTopics = false;
@@ -16,14 +16,7 @@ define(['forum/recent'], function(recent) {
recent.watchForNewPosts();
$('#markSelectedRead').on('click', function() {
- function getSelectedTids() {
- var tids = [];
- $('#topics-container .category-item.selected').each(function() {
- tids.push($(this).attr('data-tid'));
- });
- return tids;
- }
- var tids = getSelectedTids();
+ var tids = topicSelect.getSelectedTids();
if(!tids.length) {
return;
}
@@ -77,14 +70,7 @@ define(['forum/recent'], function(recent) {
socket.emit('categories.get', onCategoriesLoaded);
- $('#topics-container').on('click', '.select', function() {
- var select = $(this);
- var isChecked = !select.hasClass('fa-square-o');
-
- select.toggleClass('fa-check-square-o', !isChecked);
- select.toggleClass('fa-square-o', isChecked);
- select.parents('.category-item').toggleClass('selected', !isChecked);
- });
+ topicSelect.init();
if ($("body").height() <= $(window).height() && $('#topics-container').children().length >= 20) {
$('#load-more-btn').show();
diff --git a/public/src/modules/topicSelect.js b/public/src/modules/topicSelect.js
new file mode 100644
index 0000000000..4fbbc38f12
--- /dev/null
+++ b/public/src/modules/topicSelect.js
@@ -0,0 +1,36 @@
+'use strict';
+
+/* globals define*/
+
+define(function() {
+ var TopicSelect = {};
+
+ TopicSelect.init = function(onSelect) {
+ $('#topics-container').on('click', '.select', function() {
+ var select = $(this);
+ var isChecked = !select.hasClass('fa-square-o');
+
+ select.toggleClass('fa-check-square-o', !isChecked);
+ select.toggleClass('fa-square-o', isChecked);
+ select.parents('.category-item').toggleClass('selected', !isChecked);
+ if (typeof onSelect === 'function') {
+ onSelect();
+ }
+ });
+ };
+
+ TopicSelect.getSelectedTids = function() {
+ var tids = [];
+ $('#topics-container .category-item.selected').each(function() {
+ tids.push($(this).attr('data-tid'));
+ });
+ return tids;
+ };
+
+ TopicSelect.unselectAll = function() {
+ $('#topics-container .category-item.selected').removeClass('selected');
+ $('#topics-container .select').toggleClass('fa-check-square-o', false).toggleClass('fa-square-o', true);
+ };
+
+ return TopicSelect;
+});
\ No newline at end of file
diff --git a/src/categories/activeusers.js b/src/categories/activeusers.js
index 418b891aa9..9b8e558aae 100644
--- a/src/categories/activeusers.js
+++ b/src/categories/activeusers.js
@@ -57,7 +57,7 @@ module.exports = function(Categories) {
db.getSortedSetRevRange('cid:' + cid + ':active_users', 0, 23, callback);
};
- Categories.moveActiveUsers = function(tid, oldCid, cid, callback) {
+ Categories.moveActiveUsers = function(tid, oldCid, cid) {
function updateUser(uid, timestamp) {
Categories.addActiveUser(cid, uid, timestamp);
Categories.isUserActiveIn(oldCid, uid, function(err, active) {
diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js
index 8f4b571e9e..33ee629811 100644
--- a/src/categories/recentreplies.js
+++ b/src/categories/recentreplies.js
@@ -22,25 +22,27 @@ module.exports = function(Categories) {
});
};
- Categories.moveRecentReplies = function(tid, oldCid, cid, callback) {
- function movePost(pid, callback) {
+ Categories.moveRecentReplies = function(tid, oldCid, cid) {
+ function movePost(pid, next) {
posts.getPostField(pid, 'timestamp', function(err, timestamp) {
if(err) {
- return callback(err);
+ return next(err);
}
db.sortedSetRemove('categories:recent_posts:cid:' + oldCid, pid);
db.sortedSetAdd('categories:recent_posts:cid:' + cid, timestamp, pid);
- callback();
+ next();
});
}
topics.getPids(tid, function(err, pids) {
- if(err) {
- return callback(err);
+ if(!err && pids) {
+ async.each(pids, movePost, function(err) {
+ if (err) {
+ winston.error(err.message);
+ }
+ });
}
-
- async.each(pids, movePost, callback);
});
};
};
diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js
index 17e2ef3684..4d183a1fb3 100644
--- a/src/socket.io/topics.js
+++ b/src/socket.io/topics.js
@@ -120,64 +120,72 @@ SocketTopics.markCategoryTopicsRead = function(socket, cid, callback) {
});
};
-SocketTopics.markAsUnreadForAll = function(socket, tid, callback) {
- topics.markAsUnreadForAll(tid, function(err) {
- if(err) {
- return callback(err);
- }
-
- db.sortedSetAdd('topics:recent', Date.now(), tid, function(err) {
- if(err) {
- return callback(err);
- }
- topics.pushUnreadCount();
- callback();
- });
- });
-};
-
-SocketTopics.delete = function(socket, tid, callback) {
- doTopicAction('delete', socket, tid, callback);
-};
-
-SocketTopics.restore = function(socket, tid, callback) {
- doTopicAction('restore', socket, tid, callback);
-};
-
-SocketTopics.lock = function(socket, tid, callback) {
- doTopicAction('lock', socket, tid, callback);
-};
-
-SocketTopics.unlock = function(socket, tid, callback) {
- doTopicAction('unlock', socket, tid, callback);
-};
-
-SocketTopics.pin = function(socket, tid, callback) {
- doTopicAction('pin', socket, tid, callback);
-};
-
-SocketTopics.unpin = function(socket, tid, callback) {
- doTopicAction('unpin', socket, tid, callback);
-};
-
-function doTopicAction(action, socket, tid, callback) {
- if(!tid) {
+SocketTopics.markAsUnreadForAll = function(socket, tids, callback) {
+ if(!Array.isArray(tids)) {
return callback(new Error('[[error:invalid-tid]]'));
}
- threadTools.privileges(tid, socket.uid, function(err, privileges) {
- if(err) {
- return callback(err);
- }
+ async.each(tids, function(tid, next) {
+ topics.markAsUnreadForAll(tid, function(err) {
+ if(err) {
+ return next(err);
+ }
- if(!privileges || !privileges.editable) {
- return callback(new Error('[[error:no-privileges]]'));
- }
+ db.sortedSetAdd('topics:recent', Date.now(), tid, function(err) {
+ if(err) {
+ return next(err);
+ }
+ topics.pushUnreadCount();
+ next();
+ });
+ });
+ }, callback);
+};
- if(threadTools[action]) {
- threadTools[action](tid, socket.uid, callback);
- }
- });
+SocketTopics.delete = function(socket, tids, callback) {
+ doTopicAction('delete', socket, tids, callback);
+};
+
+SocketTopics.restore = function(socket, tids, callback) {
+ doTopicAction('restore', socket, tids, callback);
+};
+
+SocketTopics.lock = function(socket, tids, callback) {
+ doTopicAction('lock', socket, tids, callback);
+};
+
+SocketTopics.unlock = function(socket, tids, callback) {
+ doTopicAction('unlock', socket, tids, callback);
+};
+
+SocketTopics.pin = function(socket, tids, callback) {
+ doTopicAction('pin', socket, tids, callback);
+};
+
+SocketTopics.unpin = function(socket, tids, callback) {
+ doTopicAction('unpin', socket, tids, callback);
+};
+
+function doTopicAction(action, socket, tids, callback) {
+ if(!tids) {
+ return callback(new Error('[[error:invalid-tid]]'));
+ }
+
+ async.each(tids, function(tid, next) {
+ threadTools.privileges(tid, socket.uid, function(err, privileges) {
+ if(err) {
+ return next(err);
+ }
+
+ if(!privileges || !privileges.editable) {
+ return next(new Error('[[error:no-privileges]]'));
+ }
+
+ if(typeof threadTools[action] === 'function') {
+ threadTools[action](tid, socket.uid, next);
+ }
+ });
+ }, callback);
}
SocketTopics.createTopicFromPosts = function(socket, data, callback) {
@@ -215,21 +223,45 @@ SocketTopics.movePost = function(socket, data, callback) {
};
SocketTopics.move = function(socket, data, callback) {
- if(!data || !data.tid || !data.cid) {
+ if(!data || !Array.isArray(data.tids) || !data.cid) {
return callback(new Error('[[error:invalid-data]]'));
}
- threadTools.move(data.tid, data.cid, function(err) {
- if(err) {
- return callback(err);
- }
+ async.each(data.tids, function(tid, next) {
+ var oldCid;
+ async.waterfall([
+ function(next) {
+ threadTools.privileges(tid, socket.uid, next);
+ },
+ function(privileges, next) {
+ if(!(privileges.admin || privileges.moderator)) {
+ return next(new Error('[[error:no-privileges]]'));
+ }
+ next();
+ },
+ function(next) {
+ topics.getTopicField(tid, 'cid', next);
+ },
+ function(cid, next) {
+ oldCid = cid;
+ threadTools.move(tid, data.cid, next);
+ }
+ ], function(err) {
+ if(err) {
+ return next(err);
+ }
- index.server.sockets.in('topic_' + data.tid).emit('event:topic_moved', {
- tid: data.tid
+ index.server.sockets.in('topic_' + tid).emit('event:topic_moved', {
+ tid: tid
+ });
+
+ index.server.sockets.in('category_' + oldCid).emit('event:topic_moved', {
+ tid: tid
+ });
+
+ next();
});
-
- callback(null);
- });
+ }, callback);
};
SocketTopics.followCheck = function(socket, tid, callback) {
diff --git a/src/threadTools.js b/src/threadTools.js
index d2de95bb3f..55bac851b4 100644
--- a/src/threadTools.js
+++ b/src/threadTools.js
@@ -63,15 +63,14 @@ var winston = require('winston'),
};
function toggleDelete(tid, uid, isDelete, callback) {
- topics.getTopicField(tid, 'deleted', function(err, deleted) {
+ topics.getTopicFields(tid, ['cid', 'deleted'], function(err, topicData) {
if(err) {
return callback(err);
}
- if (parseInt(deleted, 10) && isDelete) {
- return callback(new Error('[[error:topic-already-deleted]]'));
- } else if (!parseInt(deleted, 10) && !isDelete) {
- return callback(new Error('[[error:topic-already-restored]]'));
+ var alreadyDeletedOrRestored = (parseInt(topicData.deleted, 10) && isDelete) || (!parseInt(topicData.deleted, 10) && !isDelete);
+ if (alreadyDeletedOrRestored) {
+ return callback(null, {tid: tid});
}
topics[isDelete ? 'delete' : 'restore'](tid, function(err) {
@@ -88,7 +87,13 @@ var winston = require('winston'),
websockets.emitTopicPostStats();
websockets.in('topic_' + tid).emit(isDelete ? 'event:topic_deleted' : 'event:topic_restored', {
- tid: tid
+ tid: tid,
+ isDelete: isDelete
+ });
+
+ websockets.in('category_' + topicData.cid).emit(isDelete ? 'event:topic_deleted' : 'event:topic_restored', {
+ tid: tid,
+ isDelete: isDelete
});
callback(null, {
@@ -107,17 +112,30 @@ var winston = require('winston'),
};
function toggleLock(tid, uid, lock, callback) {
- topics.setTopicField(tid, 'locked', lock ? 1 : 0);
+ topics.getTopicField(tid, 'cid', function(err, cid) {
+ if (err) {
+ return callback(err);
+ }
- websockets.in('topic_' + tid).emit(lock ? 'event:topic_locked' : 'event:topic_unlocked', {
- tid: tid
- });
+ topics.setTopicField(tid, 'locked', lock ? 1 : 0);
- if (typeof callback === 'function') {
- callback(null, {
- tid: tid
+ websockets.in('topic_' + tid).emit(lock ? 'event:topic_locked' : 'event:topic_unlocked', {
+ tid: tid,
+ isLocked: lock
});
- }
+
+ websockets.in('category_' + cid).emit(lock ? 'event:topic_locked' : 'event:topic_unlocked', {
+ tid: tid,
+ isLocked: lock
+ });
+
+ if (typeof callback === 'function') {
+ callback(null, {
+ tid: tid,
+ isLocked: lock
+ });
+ }
+ });
}
ThreadTools.pin = function(tid, uid, callback) {
@@ -129,20 +147,33 @@ var winston = require('winston'),
};
function togglePin(tid, uid, pin, callback) {
- topics.setTopicField(tid, 'pinned', pin ? 1 : 0);
- topics.getTopicFields(tid, ['cid', 'lastposttime'], function(err, topicData) {
- db.sortedSetAdd('categories:' + topicData.cid + ':tid', pin ? Math.pow(2, 53) : topicData.lastposttime, tid);
- });
+ topics.getTopicField(tid, 'cid', function(err, cid) {
+ if (err) {
+ return callback(err);
+ }
- websockets.in('topic_' + tid).emit(pin ? 'event:topic_pinned' : 'event:topic_unpinned', {
- tid: tid
- });
-
- if (typeof callback === 'function') {
- callback(null, {
- tid: tid
+ topics.setTopicField(tid, 'pinned', pin ? 1 : 0);
+ topics.getTopicFields(tid, ['cid', 'lastposttime'], function(err, topicData) {
+ db.sortedSetAdd('categories:' + topicData.cid + ':tid', pin ? Math.pow(2, 53) : topicData.lastposttime, tid);
});
- }
+
+ websockets.in('topic_' + tid).emit(pin ? 'event:topic_pinned' : 'event:topic_unpinned', {
+ tid: tid,
+ isPinned: pin
+ });
+
+ websockets.in('category_' + cid).emit(pin ? 'event:topic_pinned' : 'event:topic_unpinned', {
+ tid: tid,
+ isPinned: pin
+ });
+
+ if (typeof callback === 'function') {
+ callback(null, {
+ tid: tid,
+ isPinned: pin
+ });
+ }
+ });
}
ThreadTools.move = function(tid, cid, callback) {
@@ -165,8 +196,6 @@ var winston = require('winston'),
}
var oldCid = topic.cid;
- topics.setTopicField(tid, 'cid', cid);
-
if(!parseInt(topic.deleted, 10)) {
categories.incrementCategoryFieldBy(oldCid, 'topic_count', -1);
categories.incrementCategoryFieldBy(cid, 'topic_count', 1);
@@ -174,7 +203,9 @@ var winston = require('winston'),
categories.moveActiveUsers(tid, oldCid, cid);
- categories.moveRecentReplies(tid, oldCid, cid, callback);
+ categories.moveRecentReplies(tid, oldCid, cid);
+
+ topics.setTopicField(tid, 'cid', cid, callback);
});
};