diff --git a/CHANGELOG.md b/CHANGELOG.md index 63f0509b35..b66481805a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,141 @@ +#### v3.8.3 (2024-06-27) + +##### Chores + +* up themes (b15a5894) +* up harmony (3eb69c58) +* up harmony (b98333f3) +* up dbsearch (8a42db6f) +* use nodebb fork of spider-detector (3a1b39c9) +* up 2factor (142de2ca) +* incrementing version number - v3.8.2 (72d91251) +* update changelog for v3.8.2 (3854a434) +* incrementing version number - v3.8.1 (527326f7) +* incrementing version number - v3.8.0 (e228a6eb) +* incrementing version number - v3.7.5 (6882894d) +* incrementing version number - v3.7.4 (6678744c) +* incrementing version number - v3.7.3 (2d62b6f6) +* incrementing version number - v3.7.2 (cc257e7e) +* incrementing version number - v3.7.1 (712365a5) +* incrementing version number - v3.7.0 (9a6153d7) +* incrementing version number - v3.6.7 (86a17e38) +* incrementing version number - v3.6.6 (6604bf37) +* incrementing version number - v3.6.5 (6c653625) +* incrementing version number - v3.6.4 (83d131b4) +* incrementing version number - v3.6.3 (fc7d2bfd) +* incrementing version number - v3.6.2 (0f577a57) +* incrementing version number - v3.6.1 (f1a69468) +* incrementing version number - v3.6.0 (4cdf85f8) +* incrementing version number - v3.5.3 (ed0e8783) +* incrementing version number - v3.5.2 (52fbb2da) +* incrementing version number - v3.5.1 (4c543488) +* incrementing version number - v3.5.0 (d06fb4f0) +* incrementing version number - v3.4.3 (5c984250) +* incrementing version number - v3.4.2 (3f0dac38) +* incrementing version number - v3.4.1 (01e69574) +* incrementing version number - v3.4.0 (fd9247c5) +* incrementing version number - v3.3.9 (5805e770) +* incrementing version number - v3.3.8 (a5603565) +* incrementing version number - v3.3.7 (b26f1744) +* incrementing version number - v3.3.6 (7fb38792) +* incrementing version number - v3.3.4 (a67f84ea) +* incrementing version number - v3.3.3 (f94d239b) +* incrementing version number - v3.3.2 (ec9dac97) +* incrementing version number - v3.3.1 (151cc68f) +* incrementing version number - v3.3.0 (fc1ad70f) +* incrementing version number - v3.2.3 (b06d3e63) +* incrementing version number - v3.2.2 (758ecfcd) +* incrementing version number - v3.2.1 (20145074) +* incrementing version number - v3.2.0 (9ecac38e) +* incrementing version number - v3.1.7 (0b4e81ab) +* incrementing version number - v3.1.6 (b3a3b130) +* incrementing version number - v3.1.5 (ec19343a) +* incrementing version number - v3.1.4 (2452783c) +* incrementing version number - v3.1.3 (3b4e9d3f) +* incrementing version number - v3.1.2 (40fa3489) +* incrementing version number - v3.1.1 (40250733) +* incrementing version number - v3.1.0 (0cb386bd) +* incrementing version number - v3.0.1 (26f6ea49) +* incrementing version number - v3.0.0 (224e08cd) + +##### New Features + +* closes #12656, only send required meta/link tags on /api calls (64875b3f) +* show links in post queue (500e3342) +* show connetion count on /info (60b4bc66) +* make upload scores descending for ordering (3ec44d64) +* add isACP to config, closes #12623 (c51b772f) +* allow passing min,max to sortedSetsCardSum (70b4a0e2) +* add voters/upvoters v3 routes (1aaa6cbb) + +##### Bug Fixes + +* wront topic events showing up in topic (54b01395) +* dont show self in suggested topics (381bbb04) +* #12645, use titleRaw like reply button (3332480c) +* update follower/following counts after removing uid from zsets (6f6cfb1a) +* suggested topic tid (351ee71d) +* #12633, don't check post index (6e3b012b) +* recent chat pagination, closes #12637 (f7c9b7ae) +* info.tpl table (6f79d305) +* closes #12632, dont load data twice (66adfa29) +* don't error if file required too early (4430de8c) +* require of spider-detector (be86d8ef) +* return early for guests/spiders (8d56e097) +* dont show error alert when user user mouse overs votes (6bbe3d1c) +* dont load chat rooms for quests/spiders (ca4a7751) +* set uid in case its spider(-1), closes #12621 (9d74539a) +* dont add spiders to online_guests room (bcd4997d) +* dont autoconnect for spiders, closes #12620 (b6671d16) +* #12614 fix html markup for widget containers (56b5850a) + +##### Other Changes + +* remove unused winston (69ce3bf0) +* remove unused (adb0566f) +* fix semicolons (795a0daa) +* fix whitespace (b4db2f7f) + +##### Performance Improvements + +* cleanup sessions every 30 seconds (26feb2bb) +* change revokeSession to work with an array of sids (172bc249) +* dont make db call if posts is already loaded client side (461e95d8) +* if sigs disabled dont filter uids (73241bd5) +* cache isAdmin, isGlobalMod checks (fedfce7b) +* get rid of async call in user load for expiring bans (023d52a3) +* call getIconbackgrounds once (1dadd16f) +* get rid of more calls, fix other profile pages #12632 (5c6a853e) + +##### Refactors + +* sessionUUID (#12658) (d6c946cf) +* move delete call (65a91ea5) +* use array.some (85b329af) +* add placeholders on demand (8f486b1b) +* move getTopicData call to avoid db calls when its not in cache (252d0df7) +* remove exits check (74dd2844) +* move als requires, move autoLocale to api/page routes (3356022a) +* move requires down (8eccdb48) +* posts cache to get rid of require in functions (236ac323) +* closes #12629, allow passing arrays to meta.userOrGroupExists (bad15643) +* suggest topics, use strings for tids (8ded36f2) +* #12623, add filter:config.get.admin (2d86552b) +* add sanity checks to sio (e98f1848) + +##### Tests + +* dont track session for api/v3 (#12660) (61e5293a) +* move set (9108c900) +* spec (41f1cd69) +* spec (b148d0fc) +* fix path replace (9ecee782) +* update openapi spec (9af3a2da) +* fix tests (c2f63090) +* return empty array on db.exists([]); (1b283ccc) +* fix test only set for spiders (7c2a3a6e) +* fix post test (42230300) + #### v3.8.2 (2024-05-29) ##### Chores diff --git a/install/data/defaults.json b/install/data/defaults.json index 076a1435d0..a5921ab940 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -137,7 +137,8 @@ "sitemapTopics": 500, "maintenanceMode": 0, "maintenanceModeStatus": 503, - "voteVisibility": "privileged", + "upvoteVisibility": "all", + "downvoteVisibility": "privileged", "maximumInvites": 0, "username:disableEdit": 0, "email:disableEdit": 0, diff --git a/install/package.json b/install/package.json index b1dc0ba5db..9bf2e4e045 100644 --- a/install/package.json +++ b/install/package.json @@ -107,8 +107,8 @@ "nodebb-theme-harmony": "1.2.63", "nodebb-theme-lavender": "7.1.8", "nodebb-theme-peace": "2.2.6", - "nodebb-theme-persona": "13.3.24", - "nodebb-widget-essentials": "7.0.16", + "nodebb-theme-persona": "13.3.25", + "nodebb-widget-essentials": "7.0.18", "nodemailer": "6.9.13", "nprogress": "0.2.0", "passport": "0.7.0", diff --git a/public/language/en-GB/admin/settings/reputation.json b/public/language/en-GB/admin/settings/reputation.json index a78d0e3fd5..479069e3a4 100644 --- a/public/language/en-GB/admin/settings/reputation.json +++ b/public/language/en-GB/admin/settings/reputation.json @@ -2,10 +2,14 @@ "reputation": "Reputation Settings", "disable": "Disable Reputation System", "disable-down-voting": "Disable Down Voting", - "vote-visibility": "Vote visibility", - "vote-visibility-all": "Everyone can see votes", - "vote-visibility-loggedin": "Only logged in users can see votes", - "vote-visibility-privileged": "Only privileged users like admins & moderators can see votes", + "upvote-visibility": "Up Vote visibility", + "upvote-visibility-all": "Everyone can see up votes", + "upvote-visibility-loggedin": "Only logged in users can see up votes", + "upvote-visibility-privileged": "Only privileged users like admins & moderators can see up votes", + "downvote-visibility": "Down Vote visibility", + "downvote-visibility-all": "Everyone can see down votes", + "downvote-visibility-loggedin": "Only logged in users can see down votes", + "downvote-visibility-privileged": "Only privileged users like admins & moderators can see down votes", "thresholds": "Activity Thresholds", "min-rep-upvote": "Minimum reputation to upvote posts", "upvotes-per-day": "Upvotes per day (set to 0 for unlimited upvotes)", diff --git a/public/openapi/read/topic/topic_id.yaml b/public/openapi/read/topic/topic_id.yaml index 366ff32675..f0fde1b6c0 100644 --- a/public/openapi/read/topic/topic_id.yaml +++ b/public/openapi/read/topic/topic_id.yaml @@ -382,7 +382,9 @@ get: type: number downvote:disabled: type: number - voteVisibility: + upvoteVisibility: + type: string + downvoteVisibility: type: string feeds:disableRSS: type: number diff --git a/public/openapi/write/posts/pid/voters.yaml b/public/openapi/write/posts/pid/voters.yaml index 868b587c36..80647dd420 100644 --- a/public/openapi/write/posts/pid/voters.yaml +++ b/public/openapi/write/posts/pid/voters.yaml @@ -28,6 +28,8 @@ get: type: number downvoteCount: type: number + showUpvotes: + type: boolean showDownvotes: type: boolean upvoters: diff --git a/public/src/client/chats.js b/public/src/client/chats.js index abaae9f818..af18c73ec2 100644 --- a/public/src/client/chats.js +++ b/public/src/client/chats.js @@ -684,6 +684,31 @@ define('forum/chats', [ }; Chats.addSocketListeners = function () { + socket.on('event:new_notification', async function (notif) { + const { type, roomId } = notif; + if (ajaxify.data.template.chats && app.user.userslug && (type === 'new-chat' || type === 'new-group-chat')) { + const inRoom = parseInt(roomId, 10) === parseInt(ajaxify.data.roomId, 10); + if (inRoom) { + return; + } + const { rooms } = await api.get(`/chats`, { start: 0, perPage: 1 }); + const room = rooms.find(r => parseInt(r.roomId, 10) === parseInt(roomId, 10)); + if (room) { + const roomEl = chatNavWrapper.find(`[data-roomid="${roomId}"]`); + if (roomEl.length) { + updateTeaser(roomId, room.teaser); + } else { + const recentEl = components.get('chat/recent'); + const html = await app.parseAndTranslate('chats', 'rooms', { + rooms: [room], + showBottomHr: true, + }); + recentEl.prepend(html); + } + } + } + }); + socket.on('event:chats.receive', function (data) { if (chatModule.isFromBlockedUser(data.fromUid)) { return; @@ -697,9 +722,26 @@ define('forum/chats', [ data.message.timestamp = Math.min(Date.now(), data.message.timestamp); data.message.timestampISO = utils.toISOString(data.message.timestamp); messages.appendChatMessage($('[component="chat/message/content"]'), data.message); + + updateTeaser(data.roomId, { + content: utils.stripHTMLTags(utils.decodeHTMLEntities(data.message.content)), + user: data.message.fromUser, + timestampISO: data.message.timestampISO, + }); } }); + async function updateTeaser(roomId, teaser) { + const roomEl = $(`[data-roomid="${roomId}"]`); + if (roomEl.length) { + const html = await app.parseAndTranslate('partials/chats/room-teaser', { + teaser: teaser, + }); + roomEl.find('[component="chat/room/teaser"]').html(html[0].outerHTML); + roomEl.find('.timeago').timeago(); + } + } + socket.on('event:chats.public.unread', function (data) { if ( chatModule.isFromBlockedUser(data.fromUid) || diff --git a/public/src/client/topic.js b/public/src/client/topic.js index 3e7ffa7ee9..7e65cbeb4f 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -297,7 +297,7 @@ define('forum/topic', [ destroyed = true; } $(window).one('action:ajaxify.start', destroyTooltip); - $('[component="topic"]').on('mouseenter', '[component="post/parent"] a, [component="post/content"] a, [component="topic/event"] a', async function () { + $('[component="topic"]').on('mouseenter', '[component="post/parent"], [component="post/content"] a, [component="topic/event"] a', async function () { const link = $(this); destroyed = false; diff --git a/public/src/client/topic/votes.js b/public/src/client/topic/votes.js index d78d197f27..ae690de93a 100644 --- a/public/src/client/topic/votes.js +++ b/public/src/client/topic/votes.js @@ -9,17 +9,24 @@ define('forum/topic/votes', [ Votes.addVoteHandler = function () { _showTooltip = {}; - if (canSeeVotes()) { + if (canSeeUpVotes()) { components.get('topic').on('mouseenter', '[data-pid] [component="post/vote-count"]', loadDataAndCreateTooltip); components.get('topic').on('mouseleave', '[data-pid] [component="post/vote-count"]', destroyTooltip); } }; - function canSeeVotes() { - const { voteVisibility, privileges } = ajaxify.data; + function canSeeUpVotes() { + const { upvoteVisibility, privileges } = ajaxify.data; return privileges.isAdminOrMod || - voteVisibility === 'all' || - (voteVisibility === 'loggedin' && config.loggedIn); + upvoteVisibility === 'all' || + (upvoteVisibility === 'loggedin' && config.loggedIn); + } + + function canSeeVotes() { + const { upvoteVisibility, downvoteVisibility, privileges } = ajaxify.data; + return privileges.isAdminOrMod || + upvoteVisibility === 'all' || downvoteVisibility === 'all' || + ((upvoteVisibility === 'loggedin' || downvoteVisibility === 'loggedin') && config.loggedIn); } function destroyTooltip() { diff --git a/public/src/modules/categoryFilter.js b/public/src/modules/categoryFilter.js index 21dcd46b96..7c76182dd3 100644 --- a/public/src/modules/categoryFilter.js +++ b/public/src/modules/categoryFilter.js @@ -61,11 +61,12 @@ define('categoryFilter', ['categorySearch', 'api', 'hooks'], function (categoryS } }); - el.on('click', '[component="category/list"] [data-cid]', function () { + el.on('click', '[component="category/list"] [data-cid]', function (ev) { const listEl = el.find('[component="category/list"]'); const categoryEl = $(this); const link = categoryEl.find('a').attr('href'); if (link && link !== '#' && link.length) { + ev.stopPropagation(); return; } const cid = categoryEl.attr('data-cid'); diff --git a/src/api/chats.js b/src/api/chats.js index db07ac32f0..abd5c908f2 100644 --- a/src/api/chats.js +++ b/src/api/chats.js @@ -37,7 +37,7 @@ async function rateLimitExceeded(caller, field) { } chatsAPI.list = async (caller, { uid = caller.uid, start, stop, page, perPage } = {}) => { - if (!start && !stop && !page) { + if ((!utils.isNumber(start) || !utils.isNumber(stop)) && !utils.isNumber(page)) { throw new Error('[[error:invalid-data]]'); } diff --git a/src/api/posts.js b/src/api/posts.js index 2bf4a7c3d2..4e3917a008 100644 --- a/src/api/posts.js +++ b/src/api/posts.js @@ -314,12 +314,19 @@ postsAPI.getVoters = async function (caller, data) { } const { pid } = data; const cid = await posts.getCidByPid(pid); - if (!await canSeeVotes(caller.uid, cid)) { + const [canSeeUpvotes, canSeeDownvotes] = await Promise.all([ + canSeeVotes(caller.uid, cid, 'upvoteVisibility'), + canSeeVotes(caller.uid, cid, 'downvoteVisibility'), + ]); + + if (!canSeeUpvotes && !canSeeDownvotes) { throw new Error('[[error:no-privileges]]'); } - const showDownvotes = !meta.config['downvote:disabled']; + const repSystemDisabled = meta.config['reputation:disabled']; + const showUpvotes = canSeeUpvotes && !repSystemDisabled; + const showDownvotes = canSeeDownvotes && !meta.config['downvote:disabled'] && !repSystemDisabled; const [upvoteUids, downvoteUids] = await Promise.all([ - db.getSetMembers(`pid:${data.pid}:upvote`), + showUpvotes ? db.getSetMembers(`pid:${data.pid}:upvote`) : [], showDownvotes ? db.getSetMembers(`pid:${data.pid}:downvote`) : [], ]); @@ -331,6 +338,7 @@ postsAPI.getVoters = async function (caller, data) { return { upvoteCount: upvoters.length, downvoteCount: downvoters.length, + showUpvotes: showUpvotes, showDownvotes: showDownvotes, upvoters: upvoters, downvoters: downvoters, @@ -343,7 +351,7 @@ postsAPI.getUpvoters = async function (caller, data) { } const { pid } = data; const cid = await posts.getCidByPid(pid); - if (!await canSeeVotes(caller.uid, cid)) { + if (!await canSeeVotes(caller.uid, cid, 'upvoteVisibility')) { throw new Error('[[error:no-privileges]]'); } @@ -370,7 +378,7 @@ postsAPI.getUpvoters = async function (caller, data) { }; }; -async function canSeeVotes(uid, cids) { +async function canSeeVotes(uid, cids, type) { const isArray = Array.isArray(cids); if (!isArray) { cids = [cids]; @@ -389,8 +397,8 @@ async function canSeeVotes(uid, cids) { ( cidToAllowed[cid] && ( - meta.config.voteVisibility === 'all' || - (meta.config.voteVisibility === 'loggedin' && parseInt(uid, 10) > 0) + meta.config[type] === 'all' || + (meta.config[type] === 'loggedin' && parseInt(uid, 10) > 0) ) ) ); diff --git a/src/controllers/index.js b/src/controllers/index.js index 253df71a67..2cf50a7785 100644 --- a/src/controllers/index.js +++ b/src/controllers/index.js @@ -308,12 +308,14 @@ Controllers.manifest = async function (req, res) { if (meta.config['brand:maskableIcon']) { manifest.icons.push({ src: `${nconf.get('relative_path')}/assets/uploads/system/maskableicon-orig.png`, + sizes: '512x512', type: 'image/png', purpose: 'maskable', }); } else if (meta.config['brand:touchIcon']) { manifest.icons.push({ src: `${nconf.get('relative_path')}/assets/uploads/system/touchicon-orig.png`, + sizes: '512x512', type: 'image/png', purpose: 'maskable', }); diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 6a3eba85c5..d83ce8e602 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -97,7 +97,8 @@ topicsController.get = async function getTopic(req, res, next) { topicData.topicStaleDays = meta.config.topicStaleDays; topicData['reputation:disabled'] = meta.config['reputation:disabled']; topicData['downvote:disabled'] = meta.config['downvote:disabled']; - topicData.voteVisibility = meta.config.voteVisibility; + topicData.upvoteVisibility = meta.config.upvoteVisibility; + topicData.downvoteVisibility = meta.config.downvoteVisibility; topicData['feeds:disableRSS'] = meta.config['feeds:disableRSS'] || 0; topicData['signatures:hideDuplicates'] = meta.config['signatures:hideDuplicates']; topicData.bookmarkThreshold = meta.config.bookmarkThreshold; diff --git a/src/prestart.js b/src/prestart.js index 7a84a0a958..b09ef5d9bc 100644 --- a/src/prestart.js +++ b/src/prestart.js @@ -15,7 +15,7 @@ function setupWinston() { } const formats = []; - if (nconf.get('log-colorize') !== 'false') { + if (nconf.get('log-colorize') !== 'false' && nconf.get('log-colorize') !== false) { formats.push(winston.format.colorize()); } diff --git a/src/upgrades/3.8.4/downvote-visibility-config.js b/src/upgrades/3.8.4/downvote-visibility-config.js new file mode 100644 index 0000000000..d3ac559250 --- /dev/null +++ b/src/upgrades/3.8.4/downvote-visibility-config.js @@ -0,0 +1,20 @@ +/* eslint-disable no-await-in-loop */ + +'use strict'; + +const db = require('../../database'); + +module.exports = { + name: 'Add downvote visibility config field', + timestamp: Date.UTC(2024, 6, 17), + method: async function () { + const current = await db.getObjectField('config', 'voteVisibility'); + if (current) { + await db.setObject('config', { + upvoteVisibility: current, + downvoteVisibility: current, + }); + await db.deleteObjectField('config', 'voteVisibility'); + } + }, +}; diff --git a/src/views/admin/advanced/hooks.tpl b/src/views/admin/advanced/hooks.tpl index 357d3039f5..61aaa26ebc 100644 --- a/src/views/admin/advanced/hooks.tpl +++ b/src/views/admin/advanced/hooks.tpl @@ -1,28 +1,28 @@ -
{hooks.methods.method}
+ {hooks.methods.method}
-