mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-03-21 20:02:03 +01:00
* chore: up deps * chore: up composer * fix(deps): bump 2factor to v7 * chore: up harmony * chore: up harmony * fix: missing await * feat: allow middlewares to pass in template values via res.locals * feat: buildAccountData middleware automatically added ot all account routes * fix: properly allow values in res.locals.templateValues to be added to the template data * refactor: user/blocks * refactor(accounts): categories and consent * feat: automatically 404 if exposeUid or exposeGroupName come up empty * refactor: remove calls to getUserDataByUserSlug for most account routes, since it is populated via middleware now * fix: allow exposeUid and exposeGroupName to work with slugs with mixed capitalization * fix: move reputation removal check to accountHelpers method * test: skip i18n tests if ref branch when present is not develop * fix(deps): bump theme versions * fix(deps): bump ntfy and 2factor * chore: up harmony * fix: add missing return * fix: #11191, only focus on search input on md environments and up * feat: allow file uploads on mobile chat closes https://github.com/NodeBB/NodeBB/issues/11217 * chore: up themes * chore: add lang string * fix(deps): bump ntfy to 1.0.15 * refactor: use new if/each syntax * chore: up composer * fix: regression from user helper refactor * chore: up harmony * chore: up composer * chore: up harmony * chore: up harmony * chore: up harmony * chore: fix composer version * feat: add increment helper * chore: up harmony * fix: #11228 no timestamps in future ⌛ * chore: up harmony * check config.theme as well fire action:posts.loaded after processing dom * chore: up harmony * chore: up harmony * chore: up harmony * chore: up themes * chore: up harmony * remove extra class * refactor: move these to core from harmony * chore: up widgets * chore: up widgets * height auto * fix: closes #11238 * dont focus inputs, annoying on mobile * fix: dont focus twice, only focus on chat input on desktop dont wrap widget footer in row * chore: up harmony * chore: up harmony * update chat window * chore: up themes * fix cache buster for skins * chat fixes * chore: up harmony * chore: up composer * refactor: change hook logs to debug * fix: scroll to post right after adding to dom * fix: hash scrolling and highlighting correct post * test: re-enable read API schema tests * fix: add back schema changes for179faa2270andc3920ccb10* fix: schema changes from488f0978a4* fix: schema changes forf4cf482a87* fix: schema update forbe6bbabd0e* fix: schema changes for69c96078ea* fix: schema changes ford1364c3130* fix: schema changes for84ff1152f7* fix: schema changes forb860c2605c* fix: schema changes for23cb67a112* fix: schema changes forb916e42f40* fix: schema change fora9bbb586fc* fix: schema changes for4b738c8cd3* fix: schema changes for58b5781cea* fix: schema changes for794bf01b21* fix: schema changes for80ea12c1c1,e368feef51, and52ead114be* fix: composer-default object in config? * fix: schema changes for9acdc6808cand0930934200* fix: schema changes forc0a52924f1* fix: schema change foraba420a3f3, move loggedInUser to optional props * fix: schema changes for8c67031609* fix: schema changes for27e53b42f3* fix: schema changes for2835966518* fix: breaking test for email confirmation API call * fix: schema changes for refactored search page * fix: schema changes for user object * fix: schema changes for9f531f957e* fix: schema changes forc4042c70deand23175110a2* fix: schema changes for9b3616b103* fix: schema changes for5afd5de07d* fix: schema change for1d7baf1217* fix: schema changes for57bfb37c55andbe6bbabd0e* fix: schema changes for6e86b4afa2and3efad2e13band68f66223e7* fix: allowing optional qs prop in pagination keys (not sure why this didn't break before) * fix: re-login on email change * fix: schema changes forc926358d73* fix: schema changes for388a8270c9* fix: schema change for2658bcc821* fix: no need to call account middlewares for chats routes * fix: schema changes for71743affc3* fix: final schema changes * test: support for anyOf and oneOf * fix: check thumb * dont scroll to top on back press * remove group log * fix: add top margin to merged and deleted alerts * chore: up widgets * fix: improve fix-lists mixin * chore: up harmony/composer * feat: allow hiding quicksearch results during search * dont record searches made by composer * chore: up 54 * chore: up spam be gone * feat: add prev/next page and page count into mobile paginator * chore: up harmony * chore: up harmony * use old style for IS * fix: hide entire toolbar row if no posts or not singlePost * fix: updated messaging for post-queue template, #11206 * fix: btn-sm on post queue back button * fix: bump harmony, closes #11206 * fix: remove unused alert module import * fix: bump harmony * fix: bump harmony * chore: up harmony * refactor: IS scrolltop * fix: update users:search-user-for-chat source string * feat: support for mark-read toggle on chats dropdown and recent chats list * feat: api v3 calls to mark chat read/unread * feat: send event:chats.mark socket event on mark read or unread * refactor: allow frontend to mark chats as unread, use new API v3 routes instead of socket calls, better frontend event handling * docs: openapi schema updates for chat marking * fix: allow unread state toggling in chats dropdown too * fix: issue where repeated openings of the chats dropdown would continually add events for mark-read/unread * fix: debug log * refactor: move userSearch filter to a module * feat(routes): allow remounting /categories (#11230) * feat: send flags count to frontend on flags list page * refactor: filter form client-side js to extract out some logic * fix: applyFilters to not take any arguments, update selectedCids in updateButton instead of onHidden * fix: use userFilter module for assignee, reporterId, targetUid * fix(openapi): schema changes for updated flags page * fix: dont allow adding duplicates to userFilter * use same var * remove log * fix: closes #11282 * feat: lang key for x-topics * chore: up harmony * chore: up emoji * chore: up harmony * fix: update userFilter to allow new option `selectedBlock` * fix: wrong block name passed to userFilter * fix: https://github.com/NodeBB/NodeBB/issues/11283 * fix: chats, allow multiple dropdowns like in harmony * chore: up harmony * refactor: flag note adding/editing, closes #11285 * fix: remove old prepareEdit logic * chore: add caveat about hacky code block in userFilter module * fix: placeholders for userFilter module * refactor: navigator so it works with multiple thumbs/navigators * chore: up harmony * fix: closes #11287, destroy quick reply autocomplete on navigation * fix: filter disabled categories on user categories page count * chore: up harmony * docs: update openapi spec to include info about passing in timestamps for topic creation, removing timestamp as valid request param for topic replying * fix: send back null values on ACP search dashboard for startDate and endDate if not expicitly passed in, fix tests * fix: tweak table order in ACP dash searches * fix: only invoke navigator click drag on left mouse button * feat: add back unread indicator to navigator * clear bookmark on mark unread * fix: navigator crash on ajaxify * better thumb top calculation * fix: reset user bookmark when topic is marked unread * Revert "fix: reset user bookmark when topic is marked unread" This reverts commit9bcd85c2c6. * fix: update unread indicator on scroll, add unread count * chore: bump harmony * fix: crash on navigator unread update when backing out of a topic * fix: closes #11183 * fix: update topics:recent zset when rescheduling a topic * fix: dupe quote button, increase delay, hide immediately on empty selection * fix: navigator not showing up on first load * refactor: remove glance assorted fixes to navigator dont reduce remaning count if user scrolls down and up quickly only call topic.navigatorCallback when index changes * more sanity checks for bookmark dont allow setting bookmark higher than topic postcount * closes #11218, 🚋 * Revert "fix: update topics:recent zset when rescheduling a topic" This reverts commit737973cca9. * fix: #11306, show proper error if queued post doesn't exist was showing no-privileges if someone else accepted the post * https://github.com/NodeBB/NodeBB/issues/11307 dont use li * chore: up harmony * chore: bump version string * fix: copy paste fail * feat: closes #7382, tag filtering add client side support for filtering by tags on /category, /recent and /unread * chore: up harmony * chore: up harmony * Revert "fix: add back req.query fallback for backwards compatibility" [breaking] This reverts commitcf6cc2c454. This commit is no longer required as passing in a CSRF token via query parameter is no longer supported as of NodeBB v3.x This is a breaking change. * fix: pass csrf token in form data, re: NodeBB/NodeBB#11309 * chore: up deps * fix: tests, use x-csrf-token query param removed * test: fix csrf_token * lint: remove unused * feat: add itemprop="image" to avatar helper * fix: get chat upload button in chat modal * breaking: remove deprecated socket.io methods * test: update messaging tests to not use sockets * fix: parent post links * fix: prevent post tooltip if mouse leaves before data/tpl is loaded * chore: up harmony * chore: up harmony * chore: up harmony * chore: up harmony * fix: nested replies indices * fix(deps): bump 2factor * feat: add loggedIn user to all api routes * chore: up themes * refactor: audit admin v3 write api routes as per #11321 * refactor: audit category v3 write api routes as per #11321 [breaking] docs: fix open api spec for #11321 * refactor: audit chat v3 write api routes as per #11321 * refactor: audit files v3 write api routes as per #11321 * refactor: audit flags v3 write api routes as per #11321 * refactor: audit posts v3 write api routes as per #11321 * refactor: audit topics v3 write api routes as per #11321 * refactor: audit users v3 write api routes as per #11321 * fix: lang string * remove min height * fix: empty topic/labels taking up space * fix: tag filtering when changing filter to watched topics or changing popular time limit to month * chore: up harmony * fix: closes #11354, show no post error if queued post already accepted/rejected * test: #11354 * test: #11354 * fix(deps): bump 2factor * fix: #11357 clear cache on thumb remove * fix: thumb remove on windows, closes #11357 * test: openapi for thumbs * test: fix openapi --------- Co-authored-by: Julian Lam <julian@nodebb.org> Co-authored-by: Opliko <opliko.reg@protonmail.com>
544 lines
16 KiB
JavaScript
544 lines
16 KiB
JavaScript
'use strict';
|
|
|
|
|
|
define('forum/topic/postTools', [
|
|
'share',
|
|
'navigator',
|
|
'components',
|
|
'translator',
|
|
'forum/topic/votes',
|
|
'api',
|
|
'bootbox',
|
|
'alerts',
|
|
'hooks',
|
|
], function (share, navigator, components, translator, votes, api, bootbox, alerts, hooks) {
|
|
const PostTools = {};
|
|
|
|
let staleReplyAnyway = false;
|
|
|
|
PostTools.init = function (tid) {
|
|
staleReplyAnyway = false;
|
|
|
|
renderMenu();
|
|
|
|
addPostHandlers(tid);
|
|
|
|
share.addShareHandlers(ajaxify.data.titleRaw);
|
|
|
|
votes.addVoteHandler();
|
|
|
|
PostTools.updatePostCount(ajaxify.data.postcount);
|
|
};
|
|
|
|
function renderMenu() {
|
|
const container = document.querySelector('[component="topic"]');
|
|
if (!container) {
|
|
return;
|
|
}
|
|
$('[component="topic"]').on('show.bs.dropdown', '.moderator-tools', function () {
|
|
const $this = $(this);
|
|
const dropdownMenu = $this.find('.dropdown-menu');
|
|
if (dropdownMenu.attr('data-loaded')) {
|
|
return;
|
|
}
|
|
const postEl = $this.parents('[data-pid]');
|
|
const pid = postEl.attr('data-pid');
|
|
const index = parseInt(postEl.attr('data-index'), 10);
|
|
|
|
socket.emit('posts.loadPostTools', { pid: pid, cid: ajaxify.data.cid }, async (err, data) => {
|
|
if (err) {
|
|
return alerts.error(err);
|
|
}
|
|
data.posts.display_move_tools = data.posts.display_move_tools && index !== 0;
|
|
|
|
const html = await app.parseAndTranslate('partials/topic/post-menu-list', data);
|
|
const clipboard = require('clipboard');
|
|
|
|
dropdownMenu.attr('data-loaded', 'true').html(html);
|
|
|
|
new clipboard('[data-clipboard-text]');
|
|
|
|
hooks.fire('action:post.tools.load', {
|
|
element: dropdownMenu,
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
PostTools.toggle = function (pid, isDeleted) {
|
|
const postEl = components.get('post', 'pid', pid);
|
|
|
|
postEl.find('[component="post/quote"], [component="post/bookmark"], [component="post/reply"], [component="post/flag"], [component="user/chat"]')
|
|
.toggleClass('hidden', isDeleted);
|
|
|
|
postEl.find('[component="post/delete"]').toggleClass('hidden', isDeleted).parent().attr('hidden', isDeleted ? '' : null);
|
|
postEl.find('[component="post/restore"]').toggleClass('hidden', !isDeleted).parent().attr('hidden', !isDeleted ? '' : null);
|
|
postEl.find('[component="post/purge"]').toggleClass('hidden', !isDeleted).parent().attr('hidden', !isDeleted ? '' : null);
|
|
|
|
PostTools.removeMenu(postEl);
|
|
};
|
|
|
|
PostTools.removeMenu = function (postEl) {
|
|
postEl.find('[component="post/tools"] .dropdown-menu')
|
|
.removeAttr('data-loaded').html('');
|
|
};
|
|
|
|
PostTools.updatePostCount = function (postCount) {
|
|
const postCountEl = components.get('topic/post-count');
|
|
postCountEl.html(postCount).attr('title', postCount);
|
|
utils.makeNumbersHumanReadable(postCountEl);
|
|
navigator.setCount(postCount);
|
|
};
|
|
|
|
function addPostHandlers(tid) {
|
|
const postContainer = components.get('topic');
|
|
|
|
handleSelectionTooltip();
|
|
|
|
postContainer.on('click', '[component="post/quote"]', function () {
|
|
onQuoteClicked($(this), tid);
|
|
});
|
|
|
|
postContainer.on('click', '[component="post/reply"]', function () {
|
|
onReplyClicked($(this), tid);
|
|
});
|
|
|
|
$('.topic').on('click', '[component="topic/reply"]', function (e) {
|
|
e.preventDefault();
|
|
onReplyClicked($(this), tid);
|
|
});
|
|
|
|
$('.topic').on('click', '[component="topic/reply-as-topic"]', function () {
|
|
translator.translate('[[topic:link_back, ' + ajaxify.data.titleRaw + ', ' + config.relative_path + '/topic/' + ajaxify.data.slug + ']]', function (body) {
|
|
hooks.fire('action:composer.topic.new', {
|
|
cid: ajaxify.data.cid,
|
|
body: body,
|
|
});
|
|
});
|
|
});
|
|
|
|
postContainer.on('click', '[component="post/bookmark"]', function () {
|
|
return bookmarkPost($(this), getData($(this), 'data-pid'));
|
|
});
|
|
|
|
postContainer.on('click', '[component="post/upvote"]', function () {
|
|
return votes.toggleVote($(this), '.upvoted', 1);
|
|
});
|
|
|
|
postContainer.on('click', '[component="post/downvote"]', function () {
|
|
return votes.toggleVote($(this), '.downvoted', -1);
|
|
});
|
|
|
|
postContainer.on('click', '[component="post/vote-count"]', function () {
|
|
votes.showVotes(getData($(this), 'data-pid'));
|
|
});
|
|
|
|
postContainer.on('click', '[component="post/flag"]', function () {
|
|
const pid = getData($(this), 'data-pid');
|
|
require(['flags'], function (flags) {
|
|
flags.showFlagModal({
|
|
type: 'post',
|
|
id: pid,
|
|
});
|
|
});
|
|
});
|
|
|
|
postContainer.on('click', '[component="post/flagUser"]', function () {
|
|
const uid = getData($(this), 'data-uid');
|
|
require(['flags'], function (flags) {
|
|
flags.showFlagModal({
|
|
type: 'user',
|
|
id: uid,
|
|
});
|
|
});
|
|
});
|
|
|
|
postContainer.on('click', '[component="post/flagResolve"]', function () {
|
|
const flagId = $(this).attr('data-flagId');
|
|
require(['flags'], function (flags) {
|
|
flags.resolve(flagId);
|
|
});
|
|
});
|
|
|
|
postContainer.on('click', '[component="post/edit"]', function () {
|
|
const btn = $(this);
|
|
|
|
const timestamp = parseInt(getData(btn, 'data-timestamp'), 10);
|
|
const postEditDuration = parseInt(ajaxify.data.postEditDuration, 10);
|
|
|
|
if (checkDuration(postEditDuration, timestamp, 'post-edit-duration-expired')) {
|
|
hooks.fire('action:composer.post.edit', {
|
|
pid: getData(btn, 'data-pid'),
|
|
});
|
|
}
|
|
});
|
|
|
|
if (config.enablePostHistory && ajaxify.data.privileges['posts:history']) {
|
|
postContainer.on('click', '[component="post/view-history"], [component="post/edit-indicator"]', function () {
|
|
const btn = $(this);
|
|
require(['forum/topic/diffs'], function (diffs) {
|
|
diffs.open(getData(btn, 'data-pid'));
|
|
});
|
|
});
|
|
}
|
|
|
|
postContainer.on('click', '[component="post/delete"]', function () {
|
|
const btn = $(this);
|
|
const timestamp = parseInt(getData(btn, 'data-timestamp'), 10);
|
|
const postDeleteDuration = parseInt(ajaxify.data.postDeleteDuration, 10);
|
|
if (checkDuration(postDeleteDuration, timestamp, 'post-delete-duration-expired')) {
|
|
togglePostDelete($(this));
|
|
}
|
|
});
|
|
|
|
function checkDuration(duration, postTimestamp, languageKey) {
|
|
if (!ajaxify.data.privileges.isAdminOrMod && duration && Date.now() - postTimestamp > duration * 1000) {
|
|
const numDays = Math.floor(duration / 86400);
|
|
const numHours = Math.floor((duration % 86400) / 3600);
|
|
const numMinutes = Math.floor(((duration % 86400) % 3600) / 60);
|
|
const numSeconds = ((duration % 86400) % 3600) % 60;
|
|
let msg = '[[error:' + languageKey + ', ' + duration + ']]';
|
|
if (numDays) {
|
|
if (numHours) {
|
|
msg = '[[error:' + languageKey + '-days-hours, ' + numDays + ', ' + numHours + ']]';
|
|
} else {
|
|
msg = '[[error:' + languageKey + '-days, ' + numDays + ']]';
|
|
}
|
|
} else if (numHours) {
|
|
if (numMinutes) {
|
|
msg = '[[error:' + languageKey + '-hours-minutes, ' + numHours + ', ' + numMinutes + ']]';
|
|
} else {
|
|
msg = '[[error:' + languageKey + '-hours, ' + numHours + ']]';
|
|
}
|
|
} else if (numMinutes) {
|
|
if (numSeconds) {
|
|
msg = '[[error:' + languageKey + '-minutes-seconds, ' + numMinutes + ', ' + numSeconds + ']]';
|
|
} else {
|
|
msg = '[[error:' + languageKey + '-minutes, ' + numMinutes + ']]';
|
|
}
|
|
}
|
|
alerts.error(msg);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
postContainer.on('click', '[component="post/restore"]', function () {
|
|
togglePostDelete($(this));
|
|
});
|
|
|
|
postContainer.on('click', '[component="post/purge"]', function () {
|
|
purgePost($(this));
|
|
});
|
|
|
|
postContainer.on('click', '[component="post/move"]', function () {
|
|
const btn = $(this);
|
|
require(['forum/topic/move-post'], function (movePost) {
|
|
movePost.init(btn.parents('[data-pid]'));
|
|
});
|
|
});
|
|
|
|
postContainer.on('click', '[component="post/change-owner"]', function () {
|
|
const btn = $(this);
|
|
require(['forum/topic/change-owner'], function (changeOwner) {
|
|
changeOwner.init(btn.parents('[data-pid]'));
|
|
});
|
|
});
|
|
|
|
postContainer.on('click', '[component="post/ban-ip"]', function () {
|
|
const ip = $(this).attr('data-ip');
|
|
socket.emit('blacklist.addRule', ip, function (err) {
|
|
if (err) {
|
|
return alerts.error(err);
|
|
}
|
|
alerts.success('[[admin/manage/blacklist:ban-ip]]');
|
|
});
|
|
});
|
|
|
|
postContainer.on('click', '[component="post/chat"]', function () {
|
|
openChat($(this));
|
|
});
|
|
}
|
|
|
|
async function onReplyClicked(button, tid) {
|
|
const selectedNode = await getSelectedNode();
|
|
|
|
showStaleWarning(async function () {
|
|
let username = await getUserSlug(button);
|
|
if (getData(button, 'data-uid') === '0' || !getData(button, 'data-userslug')) {
|
|
username = '';
|
|
}
|
|
|
|
const toPid = button.is('[component="post/reply"]') ? getData(button, 'data-pid') : null;
|
|
const isQuoteToPid = !toPid || !selectedNode.pid || toPid === selectedNode.pid;
|
|
|
|
if (selectedNode.text && isQuoteToPid) {
|
|
username = username || selectedNode.username;
|
|
hooks.fire('action:composer.addQuote', {
|
|
tid: tid,
|
|
pid: toPid,
|
|
topicName: ajaxify.data.titleRaw,
|
|
username: username,
|
|
text: selectedNode.text,
|
|
selectedPid: selectedNode.pid,
|
|
});
|
|
} else {
|
|
hooks.fire('action:composer.post.new', {
|
|
tid: tid,
|
|
pid: toPid,
|
|
topicName: ajaxify.data.titleRaw,
|
|
text: username ? username + ' ' : ($('[component="topic/quickreply/text"]').val() || ''),
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
async function onQuoteClicked(button, tid) {
|
|
const selectedNode = await getSelectedNode();
|
|
|
|
showStaleWarning(async function () {
|
|
const username = await getUserSlug(button);
|
|
const toPid = getData(button, 'data-pid');
|
|
|
|
function quote(text) {
|
|
hooks.fire('action:composer.addQuote', {
|
|
tid: tid,
|
|
pid: toPid,
|
|
username: username,
|
|
topicName: ajaxify.data.titleRaw,
|
|
text: text,
|
|
});
|
|
}
|
|
|
|
if (selectedNode.text && toPid && toPid === selectedNode.pid) {
|
|
return quote(selectedNode.text);
|
|
}
|
|
socket.emit('posts.getRawPost', toPid, function (err, post) {
|
|
if (err) {
|
|
return alerts.error(err);
|
|
}
|
|
|
|
quote(post);
|
|
});
|
|
});
|
|
}
|
|
|
|
async function getSelectedNode() {
|
|
let selectedText = '';
|
|
let selectedPid;
|
|
let username = '';
|
|
const selection = window.getSelection ? window.getSelection() : document.selection.createRange();
|
|
const postContents = $('[component="post"] [component="post/content"]');
|
|
let content;
|
|
postContents.each(function (index, el) {
|
|
if (selection && selection.containsNode && el && selection.containsNode(el, true)) {
|
|
content = el;
|
|
}
|
|
});
|
|
|
|
if (content && selection) {
|
|
selectedText = selection.toString();
|
|
const postEl = $(content).parents('[component="post"]');
|
|
selectedPid = postEl.attr('data-pid');
|
|
username = await getUserSlug($(content));
|
|
}
|
|
return { text: selectedText, pid: selectedPid, username: username };
|
|
}
|
|
|
|
function bookmarkPost(button, pid) {
|
|
const method = button.attr('data-bookmarked') === 'false' ? 'put' : 'del';
|
|
|
|
api[method](`/posts/${pid}/bookmark`, undefined, function (err) {
|
|
if (err) {
|
|
return alerts.error(err);
|
|
}
|
|
const type = method === 'put' ? 'bookmark' : 'unbookmark';
|
|
hooks.fire(`action:post.${type}`, { pid: pid });
|
|
});
|
|
return false;
|
|
}
|
|
|
|
function getData(button, data) {
|
|
return button.parents('[data-pid]').attr(data);
|
|
}
|
|
|
|
function getUserSlug(button) {
|
|
return new Promise((resolve) => {
|
|
let slug = '';
|
|
if (button.attr('component') === 'topic/reply') {
|
|
resolve(slug);
|
|
return;
|
|
}
|
|
const post = button.parents('[data-pid]');
|
|
if (post.length) {
|
|
require(['slugify'], function (slugify) {
|
|
slug = slugify(post.attr('data-username'), true);
|
|
if (!slug) {
|
|
if (post.attr('data-uid') !== '0') {
|
|
slug = '[[global:former_user]]';
|
|
} else {
|
|
slug = '[[global:guest]]';
|
|
}
|
|
}
|
|
if (slug && slug !== '[[global:former_user]]' && slug !== '[[global:guest]]') {
|
|
slug = '@' + slug;
|
|
}
|
|
resolve(slug);
|
|
});
|
|
return;
|
|
}
|
|
|
|
resolve(slug);
|
|
});
|
|
}
|
|
|
|
function togglePostDelete(button) {
|
|
const pid = getData(button, 'data-pid');
|
|
const postEl = components.get('post', 'pid', pid);
|
|
const action = !postEl.hasClass('deleted') ? 'delete' : 'restore';
|
|
|
|
postAction(action, pid);
|
|
}
|
|
|
|
function purgePost(button) {
|
|
postAction('purge', getData(button, 'data-pid'));
|
|
}
|
|
|
|
async function postAction(action, pid) {
|
|
({ action } = await hooks.fire(`static:post.${action}`, { action, pid }));
|
|
if (!action) {
|
|
return;
|
|
}
|
|
|
|
bootbox.confirm('[[topic:post_' + action + '_confirm]]', function (confirm) {
|
|
if (!confirm) {
|
|
return;
|
|
}
|
|
|
|
const route = action === 'purge' ? '' : '/state';
|
|
const method = action === 'restore' ? 'put' : 'del';
|
|
api[method](`/posts/${pid}${route}`).catch(alerts.error);
|
|
});
|
|
}
|
|
|
|
function openChat(button) {
|
|
const post = button.parents('[data-pid]');
|
|
require(['chat'], function (chat) {
|
|
chat.newChat(post.attr('data-uid'));
|
|
});
|
|
button.parents('.btn-group').find('.dropdown-toggle').click();
|
|
return false;
|
|
}
|
|
|
|
function showStaleWarning(callback) {
|
|
const staleThreshold = Math.min(Date.now() - (1000 * 60 * 60 * 24 * ajaxify.data.topicStaleDays), 8640000000000000);
|
|
if (staleReplyAnyway || ajaxify.data.lastposttime >= staleThreshold) {
|
|
return callback();
|
|
}
|
|
|
|
const warning = bootbox.dialog({
|
|
title: '[[topic:stale.title]]',
|
|
message: '[[topic:stale.warning]]',
|
|
buttons: {
|
|
reply: {
|
|
label: '[[topic:stale.reply_anyway]]',
|
|
className: 'btn-link',
|
|
callback: function () {
|
|
staleReplyAnyway = true;
|
|
callback();
|
|
},
|
|
},
|
|
create: {
|
|
label: '[[topic:stale.create]]',
|
|
className: 'btn-primary',
|
|
callback: function () {
|
|
translator.translate('[[topic:link_back, ' + ajaxify.data.title + ', ' + config.relative_path + '/topic/' + ajaxify.data.slug + ']]', function (body) {
|
|
hooks.fire('action:composer.topic.new', {
|
|
cid: ajaxify.data.cid,
|
|
body: body,
|
|
fromStaleTopic: true,
|
|
});
|
|
});
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
warning.modal();
|
|
}
|
|
|
|
const selectionChangeFn = utils.debounce(selectionChange, 250);
|
|
|
|
function handleSelectionTooltip() {
|
|
if (!ajaxify.data.privileges['topics:reply']) {
|
|
return;
|
|
}
|
|
|
|
hooks.onPage('action:posts.loaded', delayedTooltip);
|
|
$(document).off('selectionchange');
|
|
$(document).on('selectionchange', function () {
|
|
const selectionEmpty = window.getSelection().toString() === '';
|
|
if (selectionEmpty) {
|
|
$('[component="selection/tooltip"]').addClass('hidden');
|
|
}
|
|
});
|
|
$(document).on('selectionchange', selectionChangeFn);
|
|
}
|
|
|
|
function selectionChange() {
|
|
const selectionEmpty = window.getSelection().toString() === '';
|
|
if (!selectionEmpty) {
|
|
delayedTooltip();
|
|
}
|
|
}
|
|
|
|
async function delayedTooltip() {
|
|
let selectionTooltip = $('[component="selection/tooltip"]');
|
|
selectionTooltip.addClass('hidden');
|
|
if (selectionTooltip.attr('data-ajaxify') === '1') {
|
|
selectionTooltip.remove();
|
|
return;
|
|
}
|
|
|
|
const selection = window.getSelection();
|
|
if (selection.focusNode && selection.type === 'Range' && ajaxify.data.template.topic) {
|
|
const focusNode = $(selection.focusNode);
|
|
const anchorNode = $(selection.anchorNode);
|
|
const firstPid = anchorNode.parents('[data-pid]').attr('data-pid');
|
|
const lastPid = focusNode.parents('[data-pid]').attr('data-pid');
|
|
if (firstPid !== lastPid || !focusNode.parents('[component="post/content"]').length || !anchorNode.parents('[component="post/content"]').length) {
|
|
return;
|
|
}
|
|
const postEl = focusNode.parents('[data-pid]');
|
|
const selectionRange = selection.getRangeAt(0);
|
|
if (!postEl.length || selectionRange.collapsed) {
|
|
return;
|
|
}
|
|
const rects = selectionRange.getClientRects();
|
|
const lastRect = rects[rects.length - 1];
|
|
|
|
if (!selectionTooltip.length) {
|
|
selectionTooltip = await app.parseAndTranslate('partials/topic/selection-tooltip', ajaxify.data);
|
|
$('[component="selection/tooltip"]').remove();
|
|
selectionTooltip.addClass('hidden').appendTo('body');
|
|
}
|
|
selectionTooltip.off('click').on('click', '[component="selection/tooltip/quote"]', function () {
|
|
selectionTooltip.addClass('hidden');
|
|
onQuoteClicked(postEl.find('[component="post/quote"]'), ajaxify.data.tid);
|
|
});
|
|
selectionTooltip.removeClass('hidden');
|
|
$(window).one('action:ajaxify.start', function () {
|
|
selectionTooltip.attr('data-ajaxify', 1).addClass('hidden');
|
|
$(document).off('selectionchange', selectionChangeFn);
|
|
});
|
|
const tooltipWidth = selectionTooltip.outerWidth(true);
|
|
selectionTooltip.css({
|
|
top: lastRect.bottom + $(window).scrollTop(),
|
|
left: tooltipWidth > lastRect.width ? lastRect.left : lastRect.left + lastRect.width - tooltipWidth,
|
|
});
|
|
}
|
|
}
|
|
|
|
return PostTools;
|
|
});
|