mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-05-07 16:46:24 +02:00
Merge branch 'master' into develop
This commit is contained in:
62
CHANGELOG.md
62
CHANGELOG.md
@@ -1,3 +1,65 @@
|
||||
#### v3.3.2 (2023-08-18)
|
||||
|
||||
##### Chores
|
||||
|
||||
* incrementing version number - v3.3.1 (151cc68f)
|
||||
* update changelog for v3.3.1 (6f961f9c)
|
||||
* 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)
|
||||
|
||||
##### Bug Fixes
|
||||
|
||||
* upgrade script (c02f1d70)
|
||||
|
||||
#### v3.3.1 (2023-08-18)
|
||||
|
||||
##### Chores
|
||||
|
||||
* up themes (62231baa)
|
||||
* incrementing version number - v3.3.0 (fc1ad70f)
|
||||
* update changelog for v3.3.0 (46f7405d)
|
||||
* 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
|
||||
|
||||
* #11930, ability to set custom skins as default (db07ab15)
|
||||
|
||||
##### Bug Fixes
|
||||
|
||||
* some more upgrade script fixes (f23b0b5b)
|
||||
* #11906, userData.sso — don't serve deauthUrl or non-associated url if caller uid is not same as target uid (19e047e2)
|
||||
* include latin-ext subset of fonts in admin styles (#11918) (556a1c48)
|
||||
|
||||
##### Other Changes
|
||||
|
||||
* fix lint (d1949cee)
|
||||
|
||||
#### v3.3.0 (2023-08-16)
|
||||
|
||||
##### Chores
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "nodebb",
|
||||
"license": "GPL-3.0",
|
||||
"description": "NodeBB Forum",
|
||||
"version": "3.3.0",
|
||||
"version": "3.3.2",
|
||||
"homepage": "https://www.nodebb.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
|
||||
define('admin/appearance/skins', [
|
||||
'translator', 'alerts', 'settings',
|
||||
], function (translator, alerts, settings) {
|
||||
'translator', 'alerts', 'settings', 'hooks',
|
||||
], function (translator, alerts, settings, hooks) {
|
||||
const Skins = {};
|
||||
|
||||
Skins.init = function () {
|
||||
@@ -11,9 +11,19 @@ define('admin/appearance/skins', [
|
||||
$.ajax({
|
||||
method: 'get',
|
||||
url: 'https://bootswatch.com/api/5.json',
|
||||
}).done(Skins.render);
|
||||
}).done((bsData) => {
|
||||
hooks.on('action:settings.sorted-list.loaded', (data) => {
|
||||
if (data.hash === 'custom-skins') {
|
||||
// lower case all custom-skin ids after load
|
||||
$('.custom-skin-settings [data-type="list"] [data-theme]').each((i, el) => {
|
||||
$(el).attr('data-theme', $(el).attr('data-theme').toLowerCase());
|
||||
});
|
||||
Skins.render(bsData);
|
||||
}
|
||||
});
|
||||
settings.load('custom-skins', $('.custom-skin-settings'));
|
||||
});
|
||||
|
||||
settings.load('custom-skins', $('.custom-skin-settings'));
|
||||
$('#save-custom-skins').on('click', function () {
|
||||
settings.save('custom-skins', $('.custom-skin-settings'), function () {
|
||||
alerts.success('[[admin/appearance/skins:save-custom-skins-success]]');
|
||||
@@ -33,13 +43,12 @@ define('admin/appearance/skins', [
|
||||
|
||||
if (action && action === 'use') {
|
||||
const parentEl = target.parents('[data-theme]');
|
||||
const themeType = parentEl.attr('data-type');
|
||||
const cssSrc = parentEl.attr('data-css');
|
||||
const themeId = parentEl.attr('data-theme');
|
||||
|
||||
const themeName = parentEl.attr('data-theme-name');
|
||||
|
||||
socket.emit('admin.themes.set', {
|
||||
type: themeType,
|
||||
type: 'bootswatch',
|
||||
id: themeId,
|
||||
src: cssSrc,
|
||||
}, function (err) {
|
||||
@@ -52,7 +61,7 @@ define('admin/appearance/skins', [
|
||||
alert_id: 'admin:theme',
|
||||
type: 'info',
|
||||
title: '[[admin/appearance/skins:skin-updated]]',
|
||||
message: themeId ? ('[[admin/appearance/skins:applied-success, ' + themeId + ']]') : '[[admin/appearance/skins:revert-success]]',
|
||||
message: themeId ? ('[[admin/appearance/skins:applied-success, ' + themeName + ']]') : '[[admin/appearance/skins:revert-success]]',
|
||||
timeout: 5000,
|
||||
});
|
||||
});
|
||||
@@ -67,7 +76,7 @@ define('admin/appearance/skins', [
|
||||
themes: bootswatch.themes.map(function (theme) {
|
||||
return {
|
||||
type: 'bootswatch',
|
||||
id: theme.name,
|
||||
id: theme.name.toLowerCase(),
|
||||
name: theme.name,
|
||||
description: theme.description,
|
||||
screenshot_url: theme.thumbnail,
|
||||
@@ -82,9 +91,7 @@ define('admin/appearance/skins', [
|
||||
|
||||
if (app.config.bootswatchSkin) {
|
||||
const skin = app.config.bootswatchSkin;
|
||||
highlightSelectedTheme(
|
||||
skin.charAt(0).toUpperCase() + skin.slice(1)
|
||||
);
|
||||
highlightSelectedTheme(skin);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -44,7 +44,7 @@ connection.getConnectionString = function (mongo) {
|
||||
connection.getConnectionOptions = function (mongo) {
|
||||
mongo = mongo || nconf.get('mongo');
|
||||
const connOptions = {
|
||||
maxPoolSize: 10,
|
||||
maxPoolSize: 20,
|
||||
minPoolSize: 3,
|
||||
connectTimeoutMS: 90000,
|
||||
};
|
||||
|
||||
@@ -574,7 +574,7 @@ module.exports = function (module) {
|
||||
if (processFn && processFn.constructor && processFn.constructor.name !== 'AsyncFunction') {
|
||||
processFn = util.promisify(processFn);
|
||||
}
|
||||
|
||||
let iteration = 1;
|
||||
while (!done) {
|
||||
/* eslint-disable no-await-in-loop */
|
||||
const item = await cursor.next();
|
||||
@@ -585,12 +585,12 @@ module.exports = function (module) {
|
||||
}
|
||||
|
||||
if (ids.length >= options.batch || (done && ids.length !== 0)) {
|
||||
await processFn(ids);
|
||||
|
||||
ids.length = 0;
|
||||
if (options.interval) {
|
||||
if (iteration > 1 && options.interval) {
|
||||
await sleep(options.interval);
|
||||
}
|
||||
await processFn(ids);
|
||||
iteration += 1;
|
||||
ids.length = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -28,6 +28,8 @@ connection.getConnectionOptions = function (postgres) {
|
||||
password: postgres.password,
|
||||
database: postgres.database,
|
||||
ssl: String(postgres.ssl) === 'true',
|
||||
max: 20,
|
||||
connectionTimeoutMillis: 90000,
|
||||
};
|
||||
|
||||
return _.merge(connOptions, postgres.options || {});
|
||||
|
||||
@@ -677,7 +677,7 @@ SELECT z."value", z."score"
|
||||
if (process && process.constructor && process.constructor.name !== 'AsyncFunction') {
|
||||
process = util.promisify(process);
|
||||
}
|
||||
|
||||
let iteration = 1;
|
||||
while (true) {
|
||||
/* eslint-disable no-await-in-loop */
|
||||
let rows = await cursor.readAsync(batchSize);
|
||||
@@ -692,14 +692,15 @@ SELECT z."value", z."score"
|
||||
rows = rows.map(r => r.value);
|
||||
}
|
||||
try {
|
||||
if (iteration > 1 && options.interval) {
|
||||
await sleep(options.interval);
|
||||
}
|
||||
await process(rows);
|
||||
iteration += 1;
|
||||
} catch (err) {
|
||||
await client.release();
|
||||
throw err;
|
||||
}
|
||||
if (options.interval) {
|
||||
await sleep(options.interval);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
/* eslint-disable no-await-in-loop */
|
||||
|
||||
'use strict';
|
||||
|
||||
const db = require('../../database');
|
||||
const batch = require('../../batch');
|
||||
@@ -13,7 +14,7 @@ module.exports = {
|
||||
progress.total = await db.sortedSetCard(`chat:rooms`);
|
||||
await batch.processSortedSet(`chat:rooms`, async (roomIds) => {
|
||||
progress.incr(roomIds.length);
|
||||
await Promise.all(roomIds.map(async (roomId) => {
|
||||
for (const roomId of roomIds) {
|
||||
await batch.processSortedSet(`chat:room:${roomId}:mids`, async (mids) => {
|
||||
let messageData = await db.getObjects(mids.map(mid => `message:${mid}`));
|
||||
messageData.forEach((m, idx) => {
|
||||
@@ -36,7 +37,7 @@ module.exports = {
|
||||
}, {
|
||||
batch: 500,
|
||||
});
|
||||
}));
|
||||
}
|
||||
}, {
|
||||
batch: 500,
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ module.exports = {
|
||||
});
|
||||
await db.sortedSetAddBulk(bulkAdd);
|
||||
}, {
|
||||
batch: 500,
|
||||
batch: 100,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
|
||||
'use strict';
|
||||
|
||||
|
||||
const _ = require('lodash');
|
||||
|
||||
const db = require('../../database');
|
||||
const batch = require('../../batch');
|
||||
|
||||
|
||||
module.exports = {
|
||||
name: 'Update chat messages to add roomId field',
|
||||
timestamp: Date.UTC(2023, 6, 2),
|
||||
@@ -18,49 +16,73 @@ module.exports = {
|
||||
for (let i = 1; i <= nextChatRoomId; i++) {
|
||||
allRoomIds.push(i);
|
||||
}
|
||||
progress.total = allRoomIds.length;
|
||||
progress.total = 0;
|
||||
|
||||
// calculate user count and set progress.total
|
||||
await batch.processArray(allRoomIds, async (roomIds) => {
|
||||
progress.incr(roomIds.length);
|
||||
const [arrayOfUids, arrayOfRoomData] = await Promise.all([
|
||||
db.getSortedSetsMembers(roomIds.map(roomId => `chat:room:${roomId}:uids`)),
|
||||
db.getObjects(roomIds.map(roomId => `chat:room:${roomId}`)),
|
||||
]);
|
||||
|
||||
await Promise.all(roomIds.map(async (roomId, index) => {
|
||||
const uids = arrayOfUids[index];
|
||||
const roomData = arrayOfRoomData[index];
|
||||
if (!uids.length && !roomData) {
|
||||
return;
|
||||
const arrayOfRoomData = await db.getObjects(roomIds.map(roomId => `chat:room:${roomId}`));
|
||||
await Promise.all(roomIds.map(async (roomId, idx) => {
|
||||
const roomData = arrayOfRoomData[idx];
|
||||
if (roomData) {
|
||||
const userCount = await db.sortedSetCard(`chat:room:${roomId}:uids`);
|
||||
progress.total += userCount;
|
||||
}
|
||||
if (roomData && roomData.owner && !uids.includes(String(roomData.owner))) {
|
||||
uids.push(roomData.owner);
|
||||
}
|
||||
const userKeys = uids.map(uid => `uid:${uid}:chat:room:${roomId}:mids`);
|
||||
const mids = await db.getSortedSetsMembers(userKeys);
|
||||
const uniqMids = _.uniq(_.flatten(mids));
|
||||
let messageData = await db.getObjects(uniqMids.map(mid => `message:${mid}`));
|
||||
messageData.forEach((m, idx) => {
|
||||
if (m) {
|
||||
m.mid = parseInt(uniqMids[idx], 10);
|
||||
}
|
||||
});
|
||||
messageData = messageData.filter(Boolean);
|
||||
|
||||
const bulkSet = messageData.map(
|
||||
msg => [`message:${msg.mid}`, { roomId: roomId }]
|
||||
);
|
||||
|
||||
await db.setObjectBulk(bulkSet);
|
||||
await db.setObjectField(`chat:room:${roomId}`, 'userCount', uids.length);
|
||||
await db.sortedSetAdd(
|
||||
`chat:room:${roomId}:mids`,
|
||||
messageData.map(m => m.timestamp),
|
||||
messageData.map(m => m.mid),
|
||||
);
|
||||
await db.deleteAll(userKeys);
|
||||
}));
|
||||
}, {
|
||||
batch: 500,
|
||||
});
|
||||
|
||||
await batch.processArray(allRoomIds, async (roomIds) => {
|
||||
const arrayOfRoomData = await db.getObjects(roomIds.map(roomId => `chat:room:${roomId}`));
|
||||
for (const roomData of arrayOfRoomData) {
|
||||
if (roomData) {
|
||||
const midsSeen = {};
|
||||
const { roomId } = roomData;
|
||||
await batch.processSortedSet(`chat:room:${roomId}:uids`, async (uids) => {
|
||||
for (const uid of uids) {
|
||||
await batch.processSortedSet(`uid:${uid}:chat:room:${roomId}:mids`, async (mids) => {
|
||||
const uniqMids = mids.filter(mid => !midsSeen.hasOwnProperty(mid));
|
||||
if (!uniqMids.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let messageData = await db.getObjects(uniqMids.map(mid => `message:${mid}`));
|
||||
messageData.forEach((m, idx) => {
|
||||
if (m) {
|
||||
m.mid = parseInt(uniqMids[idx], 10);
|
||||
}
|
||||
});
|
||||
messageData = messageData.filter(Boolean);
|
||||
|
||||
const bulkSet = messageData.map(
|
||||
msg => [`message:${msg.mid}`, { roomId: roomId }]
|
||||
);
|
||||
|
||||
await db.setObjectBulk(bulkSet);
|
||||
await db.sortedSetAdd(
|
||||
`chat:room:${roomId}:mids`,
|
||||
messageData.map(m => m.timestamp),
|
||||
messageData.map(m => m.mid),
|
||||
);
|
||||
uniqMids.forEach((mid) => {
|
||||
midsSeen[mid] = 1;
|
||||
});
|
||||
}, {
|
||||
batch: 500,
|
||||
});
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await db.deleteAll(`uid:${uid}:chat:room:${roomId}:mids`);
|
||||
}
|
||||
progress.incr(uids.length);
|
||||
}, {
|
||||
batch: 500,
|
||||
});
|
||||
const userCount = await db.sortedSetCard(`chat:room:${roomId}:uids`);
|
||||
await db.setObjectField(`chat:room:${roomId}`, 'userCount', userCount);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
batch: 500,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<li data-type="item" class="list-group-item">
|
||||
<li data-type="item" class="list-group-item" data-theme-name="{custom-skin-name}" data-theme="{custom-skin-name}">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="">
|
||||
<strong>{custom-skin-name}</strong>
|
||||
</div>
|
||||
<div class="">
|
||||
<button type="button" data-action="use" class="btn btn-sm btn-primary">[[admin/appearance/skins:select-skin]]</button>
|
||||
<button type="button" data-type="edit" class="btn btn-sm btn-light"><i class="fa fa-edit text-primary"></i></button>
|
||||
<button type="button" data-type="remove" class="btn btn-sm btn-light"><i class="fa fa-trash-o text-danger"></i></button>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{{ each themes }}}
|
||||
<div class="col-lg-4 col-md-6 col-12 mb-4" data-type="{./type}" data-theme="{./id}"{{{ if ./css }}} data-css="{./css}" {{{ end }}}>
|
||||
<div class="col-lg-4 col-md-6 col-12 mb-4" data-type="{./type}" data-theme-name="{./name}" data-theme="{./id}"{{{ if ./css }}} data-css="{./css}" {{{ end }}}>
|
||||
<div class="card h-100">
|
||||
<img src="{./screenshot_url}" class="card-img-top">
|
||||
<div class="card-body">
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
<div id="post-tooltip" class="card card-body shadow text-bg-light" style="position:absolute; z-index: 1;">
|
||||
<div class="clearfix">
|
||||
<div class="icon float-start">
|
||||
<div id="post-tooltip" class="card card-body shadow bg-body text-body z-1 position-absolute">
|
||||
<div class="d-flex flex-column gap-2">
|
||||
<div class="d-flex gap-1 align-items-center">
|
||||
<a href="{{{ if post.user.userslug }}}{config.relative_path}/user/{post.user.userslug}{{{ else }}}#{{{ end }}}">
|
||||
{buildAvatar(post.user, "24px", true, "", "user/picture")} {post.user.username}
|
||||
</a>
|
||||
<span class="timeago text-xs" title="{post.timestampISO}"></span>
|
||||
</div>
|
||||
<small class="float-end">
|
||||
<span class="timeago" title="{post.timestampISO}"></span>
|
||||
</small>
|
||||
<div class="content">{post.content}</div>
|
||||
</div>
|
||||
<div class="content">{post.content}</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user