mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-01 19:46:01 +01:00
355 lines
11 KiB
JavaScript
355 lines
11 KiB
JavaScript
'use strict';
|
|
|
|
|
|
define('forum/topic/threadTools', [
|
|
'components',
|
|
'translator',
|
|
'handleBack',
|
|
'api',
|
|
], function (components, translator, handleBack, api) {
|
|
var ThreadTools = {};
|
|
|
|
ThreadTools.init = function (tid, topicContainer) {
|
|
renderMenu(topicContainer);
|
|
|
|
// function topicCommand(method, path, command, onComplete) {
|
|
topicContainer.on('click', '[component="topic/delete"]', function () {
|
|
topicCommand('del', '/state', 'delete');
|
|
return false;
|
|
});
|
|
|
|
topicContainer.on('click', '[component="topic/restore"]', function () {
|
|
topicCommand('put', '/state', 'restore');
|
|
return false;
|
|
});
|
|
|
|
topicContainer.on('click', '[component="topic/purge"]', function () {
|
|
topicCommand('del', '', 'purge');
|
|
return false;
|
|
});
|
|
|
|
topicContainer.on('click', '[component="topic/lock"]', function () {
|
|
topicCommand('put', '/lock', 'lock');
|
|
return false;
|
|
});
|
|
|
|
topicContainer.on('click', '[component="topic/unlock"]', function () {
|
|
topicCommand('del', '/lock', 'unlock');
|
|
return false;
|
|
});
|
|
|
|
topicContainer.on('click', '[component="topic/pin"]', function () {
|
|
topicCommand('put', '/pin', 'pin');
|
|
return false;
|
|
});
|
|
|
|
topicContainer.on('click', '[component="topic/unpin"]', function () {
|
|
topicCommand('del', '/pin', 'unpin');
|
|
return false;
|
|
});
|
|
|
|
// todo: should also use topicCommand, but no write api call exists for this yet
|
|
topicContainer.on('click', '[component="topic/mark-unread"]', function () {
|
|
socket.emit('topics.markUnread', tid, function (err) {
|
|
if (err) {
|
|
return app.alertError(err);
|
|
}
|
|
|
|
if (app.previousUrl && !app.previousUrl.match('^/topic')) {
|
|
ajaxify.go(app.previousUrl, function () {
|
|
handleBack.onBackClicked(true);
|
|
});
|
|
} else if (ajaxify.data.category) {
|
|
ajaxify.go('category/' + ajaxify.data.category.slug, handleBack.onBackClicked);
|
|
}
|
|
|
|
app.alertSuccess('[[topic:mark_unread.success]]');
|
|
});
|
|
return false;
|
|
});
|
|
|
|
topicContainer.on('click', '[component="topic/mark-unread-for-all"]', function () {
|
|
var btn = $(this);
|
|
socket.emit('topics.markAsUnreadForAll', [tid], function (err) {
|
|
if (err) {
|
|
return app.alertError(err.message);
|
|
}
|
|
app.alertSuccess('[[topic:markAsUnreadForAll.success]]');
|
|
btn.parents('.thread-tools.open').find('.dropdown-toggle').trigger('click');
|
|
});
|
|
return false;
|
|
});
|
|
|
|
topicContainer.on('click', '[component="topic/move"]', function () {
|
|
require(['forum/topic/move'], function (move) {
|
|
move.init([tid], ajaxify.data.cid);
|
|
});
|
|
return false;
|
|
});
|
|
|
|
topicContainer.on('click', '[component="topic/delete/posts"]', function () {
|
|
require(['forum/topic/delete-posts'], function (deletePosts) {
|
|
deletePosts.init();
|
|
});
|
|
});
|
|
|
|
topicContainer.on('click', '[component="topic/fork"]', function () {
|
|
require(['forum/topic/fork'], function (fork) {
|
|
fork.init();
|
|
});
|
|
});
|
|
|
|
topicContainer.on('click', '[component="topic/move-posts"]', function () {
|
|
require(['forum/topic/move-post'], function (movePosts) {
|
|
movePosts.init();
|
|
});
|
|
});
|
|
|
|
topicContainer.on('click', '[component="topic/following"]', function () {
|
|
changeWatching('follow');
|
|
});
|
|
topicContainer.on('click', '[component="topic/not-following"]', function () {
|
|
changeWatching('follow', 0);
|
|
});
|
|
topicContainer.on('click', '[component="topic/ignoring"]', function () {
|
|
changeWatching('ignore');
|
|
});
|
|
|
|
function changeWatching(type, state = 1) {
|
|
const method = state ? 'put' : 'del';
|
|
api[method](`/topics/${tid}/${type}`, {}, () => {
|
|
var message = '';
|
|
if (type === 'follow') {
|
|
message = state ? '[[topic:following_topic.message]]' : '[[topic:not_following_topic.message]]';
|
|
} else if (type === 'ignore') {
|
|
message = state ? '[[topic:ignoring_topic.message]]' : '[[topic:not_following_topic.message]]';
|
|
}
|
|
|
|
// From here on out, type changes to 'unfollow' if state is falsy
|
|
if (!state) {
|
|
type = 'unfollow';
|
|
}
|
|
|
|
setFollowState(type);
|
|
|
|
app.alert({
|
|
alert_id: 'follow_thread',
|
|
message: message,
|
|
type: 'success',
|
|
timeout: 5000,
|
|
});
|
|
|
|
$(window).trigger('action:topics.changeWatching', { tid: tid, type: type });
|
|
}, () => {
|
|
app.alert({
|
|
type: 'danger',
|
|
alert_id: 'topic_follow',
|
|
title: '[[global:please_log_in]]',
|
|
message: '[[topic:login_to_subscribe]]',
|
|
timeout: 5000,
|
|
});
|
|
});
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
function renderMenu(container) {
|
|
container.on('show.bs.dropdown', '.thread-tools', function () {
|
|
var $this = $(this);
|
|
var dropdownMenu = $this.find('.dropdown-menu');
|
|
if (dropdownMenu.html()) {
|
|
return;
|
|
}
|
|
|
|
socket.emit('topics.loadTopicTools', { tid: ajaxify.data.tid, cid: ajaxify.data.cid }, function (err, data) {
|
|
if (err) {
|
|
return app.alertError(err);
|
|
}
|
|
app.parseAndTranslate('partials/topic/topic-menu-list', data, function (html) {
|
|
dropdownMenu.html(html);
|
|
$(window).trigger('action:topic.tools.load', {
|
|
element: dropdownMenu,
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function topicCommand(method, path, command, onComplete) {
|
|
if (!onComplete) {
|
|
onComplete = function () {};
|
|
}
|
|
const tid = ajaxify.data.tid;
|
|
const body = {};
|
|
const execute = function (ok) {
|
|
if (ok) {
|
|
api[method](`/topics/${tid}${path}`, body)
|
|
.then(onComplete)
|
|
.catch(app.alertError);
|
|
}
|
|
};
|
|
|
|
switch (command) {
|
|
case 'delete':
|
|
case 'restore':
|
|
case 'purge':
|
|
bootbox.confirm(`[[topic:thread_tools.${command}_confirm]]`, execute);
|
|
break;
|
|
|
|
case 'pin':
|
|
ThreadTools.requestPinExpiry(body, execute.bind(null, true));
|
|
break;
|
|
|
|
default:
|
|
execute(true);
|
|
break;
|
|
}
|
|
}
|
|
|
|
ThreadTools.requestPinExpiry = function (body, onSuccess) {
|
|
app.parseAndTranslate('modals/set-pin-expiry', {}, function (html) {
|
|
const modal = bootbox.dialog({
|
|
title: '[[topic:thread_tools.pin]]',
|
|
message: html,
|
|
onEscape: true,
|
|
size: 'small',
|
|
buttons: {
|
|
cancel: {
|
|
label: '[[modules:bootbox.cancel]]',
|
|
className: 'btn-link',
|
|
},
|
|
save: {
|
|
label: '[[global:save]]',
|
|
className: 'btn-primary',
|
|
callback: function () {
|
|
const expiryEl = modal.get(0).querySelector('#expiry');
|
|
let expiry = expiryEl.value;
|
|
|
|
// No expiry set
|
|
if (expiry === '') {
|
|
return onSuccess();
|
|
}
|
|
|
|
// Expiration date set
|
|
expiry = new Date(expiry);
|
|
|
|
if (expiry && expiry.getTime() > Date.now()) {
|
|
body.expiry = expiry.getTime();
|
|
onSuccess();
|
|
} else {
|
|
app.alertError('[[error:invalid-date]]');
|
|
}
|
|
},
|
|
},
|
|
},
|
|
});
|
|
});
|
|
};
|
|
|
|
ThreadTools.setLockedState = function (data) {
|
|
var threadEl = components.get('topic');
|
|
if (parseInt(data.tid, 10) !== parseInt(threadEl.attr('data-tid'), 10)) {
|
|
return;
|
|
}
|
|
|
|
var isLocked = data.isLocked && !ajaxify.data.privileges.isAdminOrMod;
|
|
|
|
components.get('topic/lock').toggleClass('hidden', data.isLocked).parent().attr('hidden', data.isLocked ? '' : null);
|
|
components.get('topic/unlock').toggleClass('hidden', !data.isLocked).parent().attr('hidden', !data.isLocked ? '' : null);
|
|
|
|
var hideReply = !!((data.isLocked || ajaxify.data.deleted) && !ajaxify.data.privileges.isAdminOrMod);
|
|
|
|
components.get('topic/reply/container').toggleClass('hidden', hideReply);
|
|
components.get('topic/reply/locked').toggleClass('hidden', ajaxify.data.privileges.isAdminOrMod || !data.isLocked || ajaxify.data.deleted);
|
|
|
|
threadEl.find('[component="post"]:not(.deleted) [component="post/reply"], [component="post"]:not(.deleted) [component="post/quote"]').toggleClass('hidden', hideReply);
|
|
threadEl.find('[component="post/edit"], [component="post/delete"]').toggleClass('hidden', isLocked);
|
|
|
|
threadEl.find('[component="post"][data-uid="' + app.user.uid + '"].deleted [component="post/tools"]').toggleClass('hidden', isLocked);
|
|
|
|
$('.topic-header [component="topic/locked"]').toggleClass('hidden', !data.isLocked);
|
|
$('[component="post/tools"] .dropdown-menu').html('');
|
|
ajaxify.data.locked = data.isLocked;
|
|
};
|
|
|
|
ThreadTools.setDeleteState = function (data) {
|
|
var threadEl = components.get('topic');
|
|
if (parseInt(data.tid, 10) !== parseInt(threadEl.attr('data-tid'), 10)) {
|
|
return;
|
|
}
|
|
|
|
components.get('topic/delete').toggleClass('hidden', data.isDelete).parent().attr('hidden', data.isDelete ? '' : null);
|
|
components.get('topic/restore').toggleClass('hidden', !data.isDelete).parent().attr('hidden', !data.isDelete ? '' : null);
|
|
components.get('topic/purge').toggleClass('hidden', !data.isDelete).parent().attr('hidden', !data.isDelete ? '' : null);
|
|
components.get('topic/deleted/message').toggleClass('hidden', !data.isDelete);
|
|
|
|
if (data.isDelete) {
|
|
app.parseAndTranslate('partials/topic/deleted-message', {
|
|
deleter: data.user,
|
|
deleted: true,
|
|
deletedTimestampISO: utils.toISOString(Date.now()),
|
|
}, function (html) {
|
|
components.get('topic/deleted/message').replaceWith(html);
|
|
html.find('.timeago').timeago();
|
|
});
|
|
}
|
|
var hideReply = data.isDelete && !ajaxify.data.privileges.isAdminOrMod;
|
|
|
|
components.get('topic/reply/container').toggleClass('hidden', hideReply);
|
|
components.get('topic/reply/locked').toggleClass('hidden', ajaxify.data.privileges.isAdminOrMod || !ajaxify.data.locked || data.isDelete);
|
|
threadEl.find('[component="post"]:not(.deleted) [component="post/reply"], [component="post"]:not(.deleted) [component="post/quote"]').toggleClass('hidden', hideReply);
|
|
|
|
threadEl.toggleClass('deleted', data.isDelete);
|
|
ajaxify.data.deleted = data.isDelete ? 1 : 0;
|
|
};
|
|
|
|
|
|
ThreadTools.setPinnedState = function (data) {
|
|
var threadEl = components.get('topic');
|
|
if (parseInt(data.tid, 10) !== parseInt(threadEl.attr('data-tid'), 10)) {
|
|
return;
|
|
}
|
|
|
|
components.get('topic/pin').toggleClass('hidden', data.pinned).parent().attr('hidden', data.pinned ? '' : null);
|
|
components.get('topic/unpin').toggleClass('hidden', !data.pinned).parent().attr('hidden', !data.pinned ? '' : null);
|
|
var icon = $('.topic-header [component="topic/pinned"]');
|
|
icon.toggleClass('hidden', !data.pinned);
|
|
if (data.pinned) {
|
|
icon.translateAttr('title', data.pinExpiry && data.pinExpiryISO ?
|
|
'[[topic:pinned-with-expiry, ' + data.pinExpiryISO + ']]' :
|
|
'[[topic:pinned]]'
|
|
);
|
|
}
|
|
ajaxify.data.pinned = data.pinned;
|
|
};
|
|
|
|
function setFollowState(state) {
|
|
var titles = {
|
|
follow: '[[topic:watching]]',
|
|
unfollow: '[[topic:not-watching]]',
|
|
ignore: '[[topic:ignoring]]',
|
|
};
|
|
translator.translate(titles[state], function (translatedTitle) {
|
|
$('[component="topic/watch"] button')
|
|
.attr('title', translatedTitle)
|
|
.tooltip('fixTitle');
|
|
});
|
|
|
|
var menu = components.get('topic/following/menu');
|
|
menu.toggleClass('hidden', state !== 'follow');
|
|
components.get('topic/following/check').toggleClass('fa-check', state === 'follow');
|
|
|
|
menu = components.get('topic/not-following/menu');
|
|
menu.toggleClass('hidden', state !== 'unfollow');
|
|
components.get('topic/not-following/check').toggleClass('fa-check', state === 'unfollow');
|
|
|
|
menu = components.get('topic/ignoring/menu');
|
|
menu.toggleClass('hidden', state !== 'ignore');
|
|
components.get('topic/ignoring/check').toggleClass('fa-check', state === 'ignore');
|
|
}
|
|
|
|
|
|
return ThreadTools;
|
|
});
|