diff --git a/install/package.json b/install/package.json
index 029693c979..852dab82b0 100644
--- a/install/package.json
+++ b/install/package.json
@@ -104,10 +104,10 @@
"nodebb-plugin-ntfy": "1.7.4",
"nodebb-plugin-spam-be-gone": "2.2.2",
"nodebb-rewards-essentials": "1.0.0",
- "nodebb-theme-harmony": "1.2.57",
+ "nodebb-theme-harmony": "1.2.58",
"nodebb-theme-lavender": "7.1.8",
"nodebb-theme-peace": "2.2.5",
- "nodebb-theme-persona": "13.3.20",
+ "nodebb-theme-persona": "13.3.21",
"nodebb-widget-essentials": "7.0.16",
"nodemailer": "6.9.13",
"nprogress": "0.2.0",
diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js
index 571361734f..f8d2ca8933 100644
--- a/public/src/client/topic/postTools.js
+++ b/public/src/client/topic/postTools.js
@@ -36,7 +36,7 @@ define('forum/topic/postTools', [
if (!container) {
return;
}
- $('[component="topic"]').on('show.bs.dropdown', '.moderator-tools', function () {
+ $('[component="topic"]').on('show.bs.dropdown', '[component="post/tools"]', function () {
const $this = $(this);
const dropdownMenu = $this.find('.dropdown-menu');
const { top } = this.getBoundingClientRect();
@@ -45,6 +45,10 @@ define('forum/topic/postTools', [
if (dropdownMenu.attr('data-loaded')) {
return;
}
+ dropdownMenu.html(helpers.generatePlaceholderWave([
+ 3, 5, 9, 7, 10, 'divider', 10,
+ ]));
+
const postEl = $this.parents('[data-pid]');
const pid = postEl.attr('data-pid');
const index = parseInt(postEl.attr('data-index'), 10);
diff --git a/public/src/client/topic/replies.js b/public/src/client/topic/replies.js
index a70862c119..0fb2da8a38 100644
--- a/public/src/client/topic/replies.js
+++ b/public/src/client/topic/replies.js
@@ -8,21 +8,22 @@ define('forum/topic/replies', ['forum/topic/posts', 'hooks', 'alerts', 'api'], f
const post = button.closest('[data-pid]');
const pid = post.data('pid');
const open = button.find('[component="post/replies/open"]');
- const loading = button.find('[component="post/replies/loading"]');
- const close = button.find('[component="post/replies/close"]');
- if (open.is(':not(.hidden)') && loading.is('.hidden')) {
- open.addClass('hidden');
- loading.removeClass('hidden');
+ if (open.attr('loading') !== '1' && open.attr('loaded') !== '1') {
+ open.attr('loading', '1')
+ .removeClass('fa-chevron-down')
+ .addClass('fa-spin fa-spinner');
+
api.get(`/posts/${pid}/replies`, {}, function (err, { replies }) {
const postData = replies;
- loading.addClass('hidden');
+ open.removeAttr('loading')
+ .attr('loaded', '1')
+ .removeClass('fa-spin fa-spinner')
+ .addClass('fa-chevron-up');
if (err) {
- open.removeClass('hidden');
return alerts.error(err);
}
- close.removeClass('hidden');
postData.forEach((post, index) => {
if (post) {
post.index = index;
@@ -50,10 +51,11 @@ define('forum/topic/replies', ['forum/topic/posts', 'hooks', 'alerts', 'api'], f
hooks.fire('action:posts.loaded', { posts: postData });
});
});
- } else if (close.is(':not(.hidden)')) {
- close.addClass('hidden');
- open.removeClass('hidden');
- loading.addClass('hidden');
+ } else if (open.attr('loaded') === '1') {
+ open.removeAttr('loaded')
+ .removeAttr('loading')
+ .removeClass('fa-spin fa-spinner fa-chevron-up')
+ .addClass('fa-chevron-down');
post.find('[component="post/replies"]').slideUp('fast', function () {
$(this).remove();
});
diff --git a/public/src/client/topic/threadTools.js b/public/src/client/topic/threadTools.js
index 60dfd8b766..a66b293a73 100644
--- a/public/src/client/topic/threadTools.js
+++ b/public/src/client/topic/threadTools.js
@@ -11,7 +11,8 @@ define('forum/topic/threadTools', [
'bootbox',
'alerts',
'bootstrap',
-], function (components, translator, handleBack, posts, api, hooks, bootbox, alerts, bootstrap) {
+ 'helpers',
+], function (components, translator, handleBack, posts, api, hooks, bootbox, alerts, bootstrap, helpers) {
const ThreadTools = {};
ThreadTools.init = function (tid, topicContainer) {
@@ -211,6 +212,7 @@ define('forum/topic/threadTools', [
if (dropdownMenu.attr('data-loaded')) {
return;
}
+ dropdownMenu.html(helpers.generatePlaceholderWave([8, 8, 8]));
const data = await socket.emit('topics.loadTopicTools', { tid: ajaxify.data.tid, cid: ajaxify.data.cid });
const html = await app.parseAndTranslate('partials/topic/topic-menu-list', data);
$(dropdownMenu).attr('data-loaded', 'true').html(html);
diff --git a/public/src/modules/helpers.common.js b/public/src/modules/helpers.common.js
index 6c17927918..c5533cf56b 100644
--- a/public/src/modules/helpers.common.js
+++ b/public/src/modules/helpers.common.js
@@ -25,12 +25,14 @@ module.exports = function (utils, Benchpress, relative_path) {
userAgentIcons,
buildAvatar,
increment,
+ generateWroteReplied,
generateRepliedTo,
generateWrote,
isoTimeToLocaleString,
shouldHideReplyContainer,
humanReadableNumber,
formattedNumber,
+ generatePlaceholderWave,
register,
__escape: identity,
};
@@ -295,34 +297,24 @@ module.exports = function (utils, Benchpress, relative_path) {
if (!userObj) {
userObj = this;
}
-
+ classNames = classNames || '';
const attributes = new Map([
- ['alt', userObj.username],
['title', userObj.username],
['data-uid', userObj.uid],
- ['loading', 'lazy'],
- ['aria-label', `[[aria:user-avatar-for, ${userObj.username}]]`],
+ ['class', `avatar ${classNames}${rounded ? ' avatar-rounded' : ''}`],
]);
const styles = [`--avatar-size: ${size};`];
const attr2String = attributes => Array.from(attributes).reduce((output, [prop, value]) => {
output += ` ${prop}="${value}"`;
return output;
}, '');
- classNames = classNames || '';
-
- attributes.set('class', `avatar ${classNames}${rounded ? ' avatar-rounded' : ''}`);
let output = '';
if (userObj.picture) {
- attributes.set('component', component || 'avatar/picture');
- output += '';
+ output += `
`;
}
-
- attributes.set('component', component || 'avatar/icon');
- styles.push('background-color: ' + userObj['icon:bgColor'] + ';');
- output += '' + userObj['icon:text'] + '';
-
+ output += `${userObj['icon:text']}`;
return output;
}
@@ -330,6 +322,13 @@ module.exports = function (utils, Benchpress, relative_path) {
return String(value + parseInt(inc, 10));
}
+ function generateWroteReplied(post, timeagoCutoff) {
+ if (post.toPid) {
+ return generateRepliedTo(post, timeagoCutoff);
+ }
+ return generateWrote(post, timeagoCutoff);
+ }
+
function generateRepliedTo(post, timeagoCutoff) {
const displayname = post.parent && post.parent.displayname ?
post.parent.displayname : '[[global:guest]]';
@@ -367,6 +366,21 @@ module.exports = function (utils, Benchpress, relative_path) {
return utils.addCommas(number);
}
+ function generatePlaceholderWave(items) {
+ const html = items.map((i) => {
+ if (i === 'divider') {
+ return '