feat: add downvoteVisibility setting, closes #12698

This commit is contained in:
Barış Soner Uşaklı
2024-07-17 17:43:31 -04:00
parent cb4bf9dd53
commit 269fc06835
10 changed files with 79 additions and 24 deletions

View File

@@ -137,7 +137,8 @@
"sitemapTopics": 500, "sitemapTopics": 500,
"maintenanceMode": 0, "maintenanceMode": 0,
"maintenanceModeStatus": 503, "maintenanceModeStatus": 503,
"voteVisibility": "privileged", "upvoteVisibility": "all",
"downvoteVisibility": "privileged",
"maximumInvites": 0, "maximumInvites": 0,
"username:disableEdit": 0, "username:disableEdit": 0,
"email:disableEdit": 0, "email:disableEdit": 0,

View File

@@ -2,10 +2,14 @@
"reputation": "Reputation Settings", "reputation": "Reputation Settings",
"disable": "Disable Reputation System", "disable": "Disable Reputation System",
"disable-down-voting": "Disable Down Voting", "disable-down-voting": "Disable Down Voting",
"vote-visibility": "Vote visibility", "upvote-visibility": "Up Vote visibility",
"vote-visibility-all": "Everyone can see votes", "upvote-visibility-all": "Everyone can see up votes",
"vote-visibility-loggedin": "Only logged in users can see votes", "upvote-visibility-loggedin": "Only logged in users can see up votes",
"vote-visibility-privileged": "Only privileged users like admins & moderators can see 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", "thresholds": "Activity Thresholds",
"min-rep-upvote": "Minimum reputation to upvote posts", "min-rep-upvote": "Minimum reputation to upvote posts",
"upvotes-per-day": "Upvotes per day (set to 0 for unlimited upvotes)", "upvotes-per-day": "Upvotes per day (set to 0 for unlimited upvotes)",

View File

@@ -382,7 +382,9 @@ get:
type: number type: number
downvote:disabled: downvote:disabled:
type: number type: number
voteVisibility: upvoteVisibility:
type: string
downvoteVisibility:
type: string type: string
feeds:disableRSS: feeds:disableRSS:
type: number type: number

View File

@@ -28,6 +28,8 @@ get:
type: number type: number
downvoteCount: downvoteCount:
type: number type: number
showUpvotes:
type: boolean
showDownvotes: showDownvotes:
type: boolean type: boolean
upvoters: upvoters:

View File

@@ -9,17 +9,24 @@ define('forum/topic/votes', [
Votes.addVoteHandler = function () { Votes.addVoteHandler = function () {
_showTooltip = {}; _showTooltip = {};
if (canSeeVotes()) { if (canSeeUpVotes()) {
components.get('topic').on('mouseenter', '[data-pid] [component="post/vote-count"]', loadDataAndCreateTooltip); components.get('topic').on('mouseenter', '[data-pid] [component="post/vote-count"]', loadDataAndCreateTooltip);
components.get('topic').on('mouseleave', '[data-pid] [component="post/vote-count"]', destroyTooltip); components.get('topic').on('mouseleave', '[data-pid] [component="post/vote-count"]', destroyTooltip);
} }
}; };
function canSeeVotes() { function canSeeUpVotes() {
const { voteVisibility, privileges } = ajaxify.data; const { upvoteVisibility, privileges } = ajaxify.data;
return privileges.isAdminOrMod || return privileges.isAdminOrMod ||
voteVisibility === 'all' || upvoteVisibility === 'all' ||
(voteVisibility === 'loggedin' && config.loggedIn); (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() { function destroyTooltip() {

View File

@@ -314,12 +314,19 @@ postsAPI.getVoters = async function (caller, data) {
} }
const { pid } = data; const { pid } = data;
const cid = await posts.getCidByPid(pid); 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]]'); 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([ 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`) : [], showDownvotes ? db.getSetMembers(`pid:${data.pid}:downvote`) : [],
]); ]);
@@ -331,6 +338,7 @@ postsAPI.getVoters = async function (caller, data) {
return { return {
upvoteCount: upvoters.length, upvoteCount: upvoters.length,
downvoteCount: downvoters.length, downvoteCount: downvoters.length,
showUpvotes: showUpvotes,
showDownvotes: showDownvotes, showDownvotes: showDownvotes,
upvoters: upvoters, upvoters: upvoters,
downvoters: downvoters, downvoters: downvoters,
@@ -343,7 +351,7 @@ postsAPI.getUpvoters = async function (caller, data) {
} }
const { pid } = data; const { pid } = data;
const cid = await posts.getCidByPid(pid); 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]]'); 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); const isArray = Array.isArray(cids);
if (!isArray) { if (!isArray) {
cids = [cids]; cids = [cids];
@@ -389,8 +397,8 @@ async function canSeeVotes(uid, cids) {
( (
cidToAllowed[cid] && cidToAllowed[cid] &&
( (
meta.config.voteVisibility === 'all' || meta.config[type] === 'all' ||
(meta.config.voteVisibility === 'loggedin' && parseInt(uid, 10) > 0) (meta.config[type] === 'loggedin' && parseInt(uid, 10) > 0)
) )
) )
); );

View File

@@ -97,7 +97,8 @@ topicsController.get = async function getTopic(req, res, next) {
topicData.topicStaleDays = meta.config.topicStaleDays; topicData.topicStaleDays = meta.config.topicStaleDays;
topicData['reputation:disabled'] = meta.config['reputation:disabled']; topicData['reputation:disabled'] = meta.config['reputation:disabled'];
topicData['downvote:disabled'] = meta.config['downvote: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['feeds:disableRSS'] = meta.config['feeds:disableRSS'] || 0;
topicData['signatures:hideDuplicates'] = meta.config['signatures:hideDuplicates']; topicData['signatures:hideDuplicates'] = meta.config['signatures:hideDuplicates'];
topicData.bookmarkThreshold = meta.config.bookmarkThreshold; topicData.bookmarkThreshold = meta.config.bookmarkThreshold;

View File

@@ -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');
}
},
};

View File

@@ -14,12 +14,20 @@
<input type="checkbox" class="form-check-input" id="downvote:disabled" data-field="downvote:disabled"> <input type="checkbox" class="form-check-input" id="downvote:disabled" data-field="downvote:disabled">
<label for="downvote:disabled" class="form-check-label">[[admin/settings/reputation:disable-down-voting]]</label> <label for="downvote:disabled" class="form-check-label">[[admin/settings/reputation:disable-down-voting]]</label>
</div> </div>
<div class="mb-3">
<label for="upvoteVisibility" class="form-check-label">[[admin/settings/reputation:upvote-visibility]]</label>
<select id="upvoteVisibility" data-field="upvoteVisibility" class="form-select">
<option value="all">[[admin/settings/reputation:upvote-visibility-all]]</option>
<option value="loggedin">[[admin/settings/reputation:upvote-visibility-loggedin]]</option>
<option value="privileged">[[admin/settings/reputation:upvote-visibility-privileged]]</option>
</select>
</div>
<div> <div>
<label for="voteVisibility" class="form-check-label">[[admin/settings/reputation:vote-visibility]]</label> <label for="downvoteVisibility" class="form-check-label">[[admin/settings/reputation:downvote-visibility]]</label>
<select id="voteVisibility" data-field="voteVisibility" class="form-select"> <select id="downvoteVisibility" data-field="downvoteVisibility" class="form-select">
<option value="all">[[admin/settings/reputation:vote-visibility-all]]</option> <option value="all">[[admin/settings/reputation:downvote-visibility-all]]</option>
<option value="loggedin">[[admin/settings/reputation:vote-visibility-loggedin]]</option> <option value="loggedin">[[admin/settings/reputation:downvote-visibility-loggedin]]</option>
<option value="privileged">[[admin/settings/reputation:vote-visibility-privileged]]</option> <option value="privileged">[[admin/settings/reputation:downvote-visibility-privileged]]</option>
</select> </select>
</div> </div>
</div> </div>

View File

@@ -1,9 +1,11 @@
{{{ if showUpvotes }}}
<div class="mb-3"> <div class="mb-3">
<h4>[[global:upvoters]] <small>({upvoteCount})</small></h4> <h4>[[global:upvoters]] <small>({upvoteCount})</small></h4>
{{{ each upvoters }}} {{{ each upvoters }}}
<a class="text-decoration-none" href="{config.relative_path}/user/{./userslug}">{buildAvatar(@value, "24px", true)}</a> <a class="text-decoration-none" href="{config.relative_path}/user/{./userslug}">{buildAvatar(@value, "24px", true)}</a>
{{{ end }}} {{{ end }}}
</div> </div>
{{{ end }}}
{{{ if showDownvotes }}} {{{ if showDownvotes }}}
<div> <div>
<h4>[[global:downvoters]] <small>({downvoteCount})</small></h4> <h4>[[global:downvoters]] <small>({downvoteCount})</small></h4>