Merge remote-tracking branch 'origin/develop' into activitypub

This commit is contained in:
Julian Lam
2024-12-04 15:24:26 -05:00
184 changed files with 1203 additions and 875 deletions

View File

@@ -29,7 +29,7 @@ editController.get = async function (req, res, next) {
const [canUseSignature, canManageUsers, customUserFields] = await Promise.all([
privileges.global.can('signature', req.uid),
privileges.admin.can('admin:users', req.uid),
accountHelpers.getCustomUserFields(userData),
accountHelpers.getCustomUserFields(req.uid, userData),
]);
userData.customUserFields = customUserFields;

View File

@@ -139,7 +139,7 @@ helpers.getUserDataByUserSlug = async function (userslug, callerUID, query = {})
return hookData.userData;
};
helpers.getCustomUserFields = async function (userData) {
helpers.getCustomUserFields = async function (callerUID, userData) {
// Remote users' fields are serialized in hash
if (!utils.isNumber(userData.uid)) {
const customFields = await user.getUserField(userData.uid, 'customFields');
@@ -165,9 +165,22 @@ helpers.getCustomUserFields = async function (userData) {
const keys = await db.getSortedSetRange('user-custom-fields', 0, -1);
const allFields = (await db.getObjects(keys.map(k => `user-custom-field:${k}`))).filter(Boolean);
const isSelf = String(callerUID) === String(userData.uid);
const [isAdmin, isModOfAny] = await Promise.all([
privileges.users.isAdministrator(callerUID),
user.isModeratorOfAnyCategory(callerUID),
]);
const fields = allFields.filter((field) => {
const visibilityCheck = isAdmin || isModOfAny || isSelf || field.visibility === 'all' ||
(
field.visibility === 'loggedin' &&
String(callerUID) !== '0' &&
String(callerUID) !== '-1'
);
const minRep = field['min:rep'] || 0;
return userData.reputation >= minRep || meta.config['reputation:disabled'];
const repCheck = userData.reputation >= minRep || meta.config['reputation:disabled'];
return visibilityCheck && repCheck;
});
fields.forEach((f) => {

View File

@@ -27,7 +27,7 @@ profileController.get = async function (req, res, next) {
const [latestPosts, bestPosts, customUserFields] = await Promise.all([
getLatestPosts(req.uid, userData),
getBestPosts(req.uid, userData),
accountHelpers.getCustomUserFields(userData),
accountHelpers.getCustomUserFields(req.uid, userData),
posts.parseSignature(userData, req.uid),
]);
userData.customUserFields = customUserFields;

View File

@@ -310,6 +310,7 @@ usersController.customFields = async function (req, res) {
field.selectOptionsFormatted = field['select-options'].trim().split('\n').join(', ');
}
field['min:rep'] = field['min:rep'] || 0;
field.visibility = field.visibility || 'all';
});
res.render('admin/manage/users/custom-fields', { fields: fields });
};

View File

@@ -110,12 +110,16 @@ async function resizeImage(fileObj) {
await image.resizeImage({
path: fileObj.path,
target: file.appendToFileName(fileObj.path, '-resized'),
target: meta.config.resizeImageKeepOriginal ?
file.appendToFileName(fileObj.path, '-resized') :
fileObj.path,
width: meta.config.resizeImageWidth,
quality: meta.config.resizeImageQuality,
});
// Return the resized version to the composer/postData
fileObj.url = file.appendToFileName(fileObj.url, '-resized');
if (meta.config.resizeImageKeepOriginal) {
fileObj.url = file.appendToFileName(fileObj.url, '-resized');
}
return fileObj;
}

View File

@@ -39,9 +39,9 @@ redisModule.init = async function (opts) {
redisModule.createSessionStore = async function (options) {
const meta = require('../meta');
const sessionStore = require('connect-redis').default;
const { RedisStore } = require('connect-redis');
const client = await connection.connect(options);
const store = new sessionStore({
const store = new RedisStore({
client: client,
ttl: meta.getSessionTTLSeconds(),
});

View File

@@ -122,7 +122,6 @@ image.stripEXIF = async function (path) {
};
image.checkDimensions = async function (path) {
const meta = require('./meta');
const result = await image.size(path);
if (result.width > meta.config.rejectImageWidth || result.height > meta.config.rejectImageHeight) {

View File

@@ -234,7 +234,7 @@ Messaging.getRecentChats = async (callerUid, uid, start, stop) => {
Messaging.generateUsernames = function (room, excludeUid) {
const users = room.users.filter(u => u && parseInt(u.uid, 10) !== excludeUid);
const usernames = users.map(u => u.username);
const usernames = users.map(u => u.displayname);
if (users.length > 3) {
return translator.compile(
'modules:chat.usernames-and-x-others',
@@ -248,8 +248,8 @@ Messaging.generateUsernames = function (room, excludeUid) {
Messaging.generateChatWithMessage = async function (room, callerUid, userLang) {
const users = room.users.filter(u => u && parseInt(u.uid, 10) !== callerUid);
const usernames = users.map(u => (utils.isNumber(u.uid) ?
`<a href="${relative_path}/uid/${u.uid}">${u.username}</a>` :
`<a href="${relative_path}/user/${u.username}">${u.username}</a>`));
`<a href="${relative_path}/uid/${u.uid}">${u.displayname}</a>` :
`<a href="${relative_path}/user/${u.username}">${u.displayname}</a>`));
let compiled = '';
if (!users.length) {
return '[[modules:chat.no-users-in-room]]';

View File

@@ -0,0 +1,13 @@
/* eslint-disable no-await-in-loop */
'use strict';
const db = require('../../database');
module.exports = {
name: 'Add setting for keeping original image after resize',
timestamp: Date.UTC(2024, 11, 2),
method: async function () {
await db.setObjectField('config', 'resizeImageKeepOriginal', 1);
},
};

View File

@@ -21,13 +21,14 @@
<th class="text-muted">[[admin/manage/user-custom-fields:key]]</th>
<th class="text-muted">[[admin/manage/user-custom-fields:name]]</th>
<th class="text-muted">[[admin/manage/user-custom-fields:type]]</th>
<th class="text-muted">[[admin/manage/user-custom-fields:visibility]]</th>
<th class="text-muted text-end">[[admin/manage/user-custom-fields:min-rep]]</th>
<th></th>
</tr>
</thead>
<tbody>
{{{ each fields }}}
<tr data-key="{./key}" data-name="{./name}" data-icon="{./icon}" data-type="{./type}" data-min-rep="{./min:rep}" data-select-options="{./select-options}" class="align-middle">
<tr data-key="{./key}" data-name="{./name}" data-icon="{./icon}" data-type="{./type}" data-min-rep="{./min:rep}" data-select-options="{./select-options}" data-visibility="{./visibility}" class="align-middle">
<td style="width: 32px;">
<a href="#" component="sort/handle" class="btn btn-light btn-sm d-none d-md-block ui-sortable-handle" style="cursor:grab;"><i class="fa fa-arrows-up-down text-muted"></i></a>
</td>
@@ -41,6 +42,9 @@
</div>
{{{ end }}}
</td>
<td>
{./visibility}
</td>
<td class="text-end">
{./min:rep}
</td>

View File

@@ -29,6 +29,15 @@
</div>
</div>
<div class="mb-3">
<label class="form-label">[[admin/manage/user-custom-fields:visibility]]</label>
<select name="visibility" class="form-select">
<option value="all">[[admin/manage/user-custom-fields:visibility-all]]</option>
<option value="loggedin">[[admin/manage/user-custom-fields:visibility-loggedin]]</option>
<option value="privileged">[[admin/manage/user-custom-fields:visibility-privileged]]</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">[[admin/manage/user-custom-fields:minimum-reputation]]</label>
<input class="form-control" type="number" name="min:rep" value="{./min:rep}" placeholder="0">

View File

@@ -15,7 +15,7 @@
<label for="downvote:disabled" class="form-check-label">[[admin/settings/reputation:disable-down-voting]]</label>
</div>
<div class="mb-3">
<label for="upvoteVisibility" class="form-check-label">[[admin/settings/reputation:upvote-visibility]]</label>
<label for="upvoteVisibility" class="form-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>
@@ -23,7 +23,7 @@
</select>
</div>
<div>
<label for="downvoteVisibility" class="form-check-label">[[admin/settings/reputation:downvote-visibility]]</label>
<label for="downvoteVisibility" class="form-label">[[admin/settings/reputation:downvote-visibility]]</label>
<select id="downvoteVisibility" data-field="downvoteVisibility" class="form-select">
<option value="all">[[admin/settings/reputation:downvote-visibility-all]]</option>
<option value="loggedin">[[admin/settings/reputation:downvote-visibility-loggedin]]</option>

View File

@@ -46,6 +46,11 @@
</div>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="resizeImageKeepOriginal" data-field="resizeImageKeepOriginal">
<label for="resizeImageKeepOriginal" class="form-check-label">[[admin/settings/uploads:resize-image-keep-original]]</label>
</div>
<div class="mb-3">
<label class="form-label" for="resizeImageQuality">[[admin/settings/uploads:resize-image-quality]]</label>
<input id="resizeImageQuality" type="text" class="form-control" value="60" data-field="resizeImageQuality" placeholder="60">

View File

@@ -9,7 +9,7 @@
<table role="presentation" cellspacing="0" cellpadding="0" border="0" width="100%">
<tr>
<td style="padding: 40px 40px 6px 40px; font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol; font-size: 15px; line-height: 20px; color: #555555;">
<h1 style="margin: 0; font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol; font-size: 24px; line-height: 27px; color: #333333; font-weight: normal;">[[email:greeting-with-name, {username}]]</h1>
<h1 style="margin: 0; font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol; font-size: 24px; line-height: 27px; color: #333333; font-weight: normal;">[[email:greeting-with-name, {displayname}]]</h1>
</td>
</tr>
<tr>
@@ -75,7 +75,7 @@
<td style="padding: 6px 16px; font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol; width: 32px; vertical-align: middle;">{function.renderDigestAvatar}</td>
<td style="padding: 6px 16px; font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol; line-height: 16px; color: #333333;">
<p style="margin: 0;"><a style="text-decoration:none !important; text-decoration:none; color: #333333;" href="{url}/topic/{topTopics.slug}"><strong>{topTopics.title}</strong></a></p>
<p style="margin: 0; font-size: 12px;"><a style="text-decoration:none !important; text-decoration:none; color: #aaaaaa; line-height: 16px;" href="{url}/uid/{topTopics.teaser.user.uid}"><strong>{topTopics.teaser.user.username}</strong></a></p>
<p style="margin: 0; font-size: 12px;"><a style="text-decoration:none !important; text-decoration:none; color: #aaaaaa; line-height: 16px;" href="{url}/uid/{topTopics.teaser.user.uid}"><strong>{topTopics.teaser.user.displayname}</strong></a></p>
</td>
</tr>
<tr>
@@ -110,7 +110,7 @@
<td style="padding: 6px 16px; font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol; width: 32px; vertical-align: middle;">{function.renderDigestAvatar}</td>
<td style="padding: 6px 16px; font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol; line-height: 16px; color: #333333;">
<p style="margin: 0;"><a style="text-decoration:none !important; text-decoration:none; color: #333333;" href="{url}/topic/{popularTopics.slug}"><strong>{popularTopics.title}</strong></a></p>
<p style="margin: 0; font-size: 12px;"><a style="text-decoration:none !important; text-decoration:none; color: #aaaaaa; line-height: 16px;" href="{url}/uid/{popularTopics.teaser.user.uid}"><strong>{popularTopics.teaser.user.username}</strong></a></p>
<p style="margin: 0; font-size: 12px;"><a style="text-decoration:none !important; text-decoration:none; color: #aaaaaa; line-height: 16px;" href="{url}/uid/{popularTopics.teaser.user.uid}"><strong>{popularTopics.teaser.user.displayname}</strong></a></p>
</td>
</tr>
<tr>
@@ -145,7 +145,7 @@
<td style="padding: 6px 16px; font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol; width: 32px; vertical-align: middle;">{function.renderDigestAvatar}</td>
<td style="padding: 6px 16px; font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol; line-height: 16px; color: #333333;">
<p style="margin: 0;"><a style="text-decoration:none !important; text-decoration:none; color: #333333;" href="{url}/topic/{recent.slug}"><strong>{recent.title}</strong></a></p>
<p style="margin: 0; font-size: 12px;"><a style="text-decoration:none !important; text-decoration:none; color: #aaaaaa; line-height: 16px;" href="{url}/uid/{recent.teaser.user.uid}"><strong>{recent.teaser.user.username}</strong></a></p>
<p style="margin: 0; font-size: 12px;"><a style="text-decoration:none !important; text-decoration:none; color: #aaaaaa; line-height: 16px;" href="{url}/uid/{recent.teaser.user.uid}"><strong>{recent.teaser.user.displayname}</strong></a></p>
</td>
</tr>
<tr>

View File

@@ -1,4 +1,4 @@
<li component="chat/message" class="chat-message mx-2 pe-2 {{{ if messages.deleted }}} deleted{{{ end }}} {{{ if messages.pinned}}} pinned{{{ end }}} {{{ if messages.newSet }}}border-top pt-3{{{ end }}}" data-mid="{messages.messageId}" data-uid="{messages.fromuid}" data-index="{messages.index}" data-self="{messages.self}" data-break="{messages.newSet}" data-timestamp="{messages.timestamp}" data-username="{messages.fromUser.username}">
<li component="chat/message" class="chat-message mx-2 pe-2 {{{ if messages.deleted }}} deleted{{{ end }}} {{{ if messages.pinned}}} pinned{{{ end }}} {{{ if messages.newSet }}}border-top pt-3{{{ end }}}" data-mid="{messages.messageId}" data-uid="{messages.fromuid}" data-index="{messages.index}" data-self="{messages.self}" data-break="{messages.newSet}" data-timestamp="{messages.timestamp}" data-username="{messages.fromUser.username}" data-displayname="{messages.fromUser.displayname}">
{{{ if messages.parent }}}
<!-- IMPORT partials/chats/parent.tpl -->

View File

@@ -2,7 +2,7 @@
{{{ if ./teaser }}}
<div class="teaser-content text-sm line-clamp-3 text-break">
{buildAvatar(./teaser.user, "14px", true, "align-middle")}
<strong class="text-xs fw-semibold teaser-username">{./teaser.user.username}:</strong>
<strong class="text-xs fw-semibold teaser-username">{./teaser.user.displayname}:</strong>
{./teaser.content}
</div>
<div class="teaser-timestamp text-muted text-xs">{{{ if ./teaser.timeagoLong }}}{./teaser.timeagoLong}{{{ else }}}<span class="timeago" title="{./teaser.timestampISO}"></span>{{{ end }}}</div>

View File

@@ -3,7 +3,7 @@
<a data-index="{./index}" data-uid="{./uid}" class="btn btn-ghost btn-sm d-flex ff-secondary d-flex justify-content-start align-items-center gap-2 {{{ if ./online }}}online{{{ end}}}" href="{config.relative_path}/uid/{./uid}">
<div>{buildAvatar(users, "24px", true)}</div>
<div class="d-flex gap-1 flex-grow-1 text-nowrap text-truncate">
<span component="chat/user/list/username" class="text-truncate">{./username}</span>
<span component="chat/user/list/username" class="text-truncate">{./displayname}</span>
{{{ if ./isOwner }}}<span><i class="fa fa-star text-warning" data-bs-toggle="tooltip" title="[[modules:chat.owner]]"></i></span>{{{ end }}}
</div>
</a>