Merge branch 'master' into develop

isekai-main
Barış Soner Uşaklı 1 year ago
commit cbc092be1e

@ -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) #### v3.3.0 (2023-08-16)
##### Chores ##### Chores

@ -2,7 +2,7 @@
"name": "nodebb", "name": "nodebb",
"license": "GPL-3.0", "license": "GPL-3.0",
"description": "NodeBB Forum", "description": "NodeBB Forum",
"version": "3.3.0", "version": "3.3.2",
"homepage": "https://www.nodebb.org", "homepage": "https://www.nodebb.org",
"repository": { "repository": {
"type": "git", "type": "git",

@ -2,8 +2,8 @@
define('admin/appearance/skins', [ define('admin/appearance/skins', [
'translator', 'alerts', 'settings', 'translator', 'alerts', 'settings', 'hooks',
], function (translator, alerts, settings) { ], function (translator, alerts, settings, hooks) {
const Skins = {}; const Skins = {};
Skins.init = function () { Skins.init = function () {
@ -11,9 +11,19 @@ define('admin/appearance/skins', [
$.ajax({ $.ajax({
method: 'get', method: 'get',
url: 'https://bootswatch.com/api/5.json', 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 () { $('#save-custom-skins').on('click', function () {
settings.save('custom-skins', $('.custom-skin-settings'), function () { settings.save('custom-skins', $('.custom-skin-settings'), function () {
alerts.success('[[admin/appearance/skins:save-custom-skins-success]]'); alerts.success('[[admin/appearance/skins:save-custom-skins-success]]');
@ -33,13 +43,12 @@ define('admin/appearance/skins', [
if (action && action === 'use') { if (action && action === 'use') {
const parentEl = target.parents('[data-theme]'); const parentEl = target.parents('[data-theme]');
const themeType = parentEl.attr('data-type');
const cssSrc = parentEl.attr('data-css'); const cssSrc = parentEl.attr('data-css');
const themeId = parentEl.attr('data-theme'); const themeId = parentEl.attr('data-theme');
const themeName = parentEl.attr('data-theme-name');
socket.emit('admin.themes.set', { socket.emit('admin.themes.set', {
type: themeType, type: 'bootswatch',
id: themeId, id: themeId,
src: cssSrc, src: cssSrc,
}, function (err) { }, function (err) {
@ -52,7 +61,7 @@ define('admin/appearance/skins', [
alert_id: 'admin:theme', alert_id: 'admin:theme',
type: 'info', type: 'info',
title: '[[admin/appearance/skins:skin-updated]]', 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, timeout: 5000,
}); });
}); });
@ -67,7 +76,7 @@ define('admin/appearance/skins', [
themes: bootswatch.themes.map(function (theme) { themes: bootswatch.themes.map(function (theme) {
return { return {
type: 'bootswatch', type: 'bootswatch',
id: theme.name, id: theme.name.toLowerCase(),
name: theme.name, name: theme.name,
description: theme.description, description: theme.description,
screenshot_url: theme.thumbnail, screenshot_url: theme.thumbnail,
@ -82,9 +91,7 @@ define('admin/appearance/skins', [
if (app.config.bootswatchSkin) { if (app.config.bootswatchSkin) {
const skin = app.config.bootswatchSkin; const skin = app.config.bootswatchSkin;
highlightSelectedTheme( highlightSelectedTheme(skin);
skin.charAt(0).toUpperCase() + skin.slice(1)
);
} }
}); });
}; };

@ -44,7 +44,7 @@ connection.getConnectionString = function (mongo) {
connection.getConnectionOptions = function (mongo) { connection.getConnectionOptions = function (mongo) {
mongo = mongo || nconf.get('mongo'); mongo = mongo || nconf.get('mongo');
const connOptions = { const connOptions = {
maxPoolSize: 10, maxPoolSize: 20,
minPoolSize: 3, minPoolSize: 3,
connectTimeoutMS: 90000, connectTimeoutMS: 90000,
}; };

@ -574,7 +574,7 @@ module.exports = function (module) {
if (processFn && processFn.constructor && processFn.constructor.name !== 'AsyncFunction') { if (processFn && processFn.constructor && processFn.constructor.name !== 'AsyncFunction') {
processFn = util.promisify(processFn); processFn = util.promisify(processFn);
} }
let iteration = 1;
while (!done) { while (!done) {
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
const item = await cursor.next(); const item = await cursor.next();
@ -585,12 +585,12 @@ module.exports = function (module) {
} }
if (ids.length >= options.batch || (done && ids.length !== 0)) { if (ids.length >= options.batch || (done && ids.length !== 0)) {
await processFn(ids); if (iteration > 1 && options.interval) {
ids.length = 0;
if (options.interval) {
await sleep(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, password: postgres.password,
database: postgres.database, database: postgres.database,
ssl: String(postgres.ssl) === 'true', ssl: String(postgres.ssl) === 'true',
max: 20,
connectionTimeoutMillis: 90000,
}; };
return _.merge(connOptions, postgres.options || {}); return _.merge(connOptions, postgres.options || {});

@ -677,7 +677,7 @@ SELECT z."value", z."score"
if (process && process.constructor && process.constructor.name !== 'AsyncFunction') { if (process && process.constructor && process.constructor.name !== 'AsyncFunction') {
process = util.promisify(process); process = util.promisify(process);
} }
let iteration = 1;
while (true) { while (true) {
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
let rows = await cursor.readAsync(batchSize); let rows = await cursor.readAsync(batchSize);
@ -692,14 +692,15 @@ SELECT z."value", z."score"
rows = rows.map(r => r.value); rows = rows.map(r => r.value);
} }
try { try {
if (iteration > 1 && options.interval) {
await sleep(options.interval);
}
await process(rows); await process(rows);
iteration += 1;
} catch (err) { } catch (err) {
await client.release(); await client.release();
throw err; 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 db = require('../../database');
const batch = require('../../batch'); const batch = require('../../batch');
@ -13,7 +14,7 @@ module.exports = {
progress.total = await db.sortedSetCard(`chat:rooms`); progress.total = await db.sortedSetCard(`chat:rooms`);
await batch.processSortedSet(`chat:rooms`, async (roomIds) => { await batch.processSortedSet(`chat:rooms`, async (roomIds) => {
progress.incr(roomIds.length); 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) => { await batch.processSortedSet(`chat:room:${roomId}:mids`, async (mids) => {
let messageData = await db.getObjects(mids.map(mid => `message:${mid}`)); let messageData = await db.getObjects(mids.map(mid => `message:${mid}`));
messageData.forEach((m, idx) => { messageData.forEach((m, idx) => {
@ -36,7 +37,7 @@ module.exports = {
}, { }, {
batch: 500, batch: 500,
}); });
})); }
}, { }, {
batch: 500, batch: 500,
}); });

@ -26,7 +26,7 @@ module.exports = {
}); });
await db.sortedSetAddBulk(bulkAdd); await db.sortedSetAddBulk(bulkAdd);
}, { }, {
batch: 500, batch: 100,
}); });
}, },
}; };

@ -1,12 +1,10 @@
'use strict'; /* eslint-disable no-await-in-loop */
const _ = require('lodash'); 'use strict';
const db = require('../../database'); const db = require('../../database');
const batch = require('../../batch'); const batch = require('../../batch');
module.exports = { module.exports = {
name: 'Update chat messages to add roomId field', name: 'Update chat messages to add roomId field',
timestamp: Date.UTC(2023, 6, 2), timestamp: Date.UTC(2023, 6, 2),
@ -18,47 +16,71 @@ module.exports = {
for (let i = 1; i <= nextChatRoomId; i++) { for (let i = 1; i <= nextChatRoomId; i++) {
allRoomIds.push(i); allRoomIds.push(i);
} }
progress.total = allRoomIds.length; progress.total = 0;
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) => { // calculate user count and set progress.total
const uids = arrayOfUids[index]; await batch.processArray(allRoomIds, async (roomIds) => {
const roomData = arrayOfRoomData[index]; const arrayOfRoomData = await db.getObjects(roomIds.map(roomId => `chat:room:${roomId}`));
if (!uids.length && !roomData) { await Promise.all(roomIds.map(async (roomId, idx) => {
return; const roomData = arrayOfRoomData[idx];
} if (roomData) {
if (roomData && roomData.owner && !uids.includes(String(roomData.owner))) { const userCount = await db.sortedSetCard(`chat:room:${roomId}:uids`);
uids.push(roomData.owner); progress.total += userCount;
} }
const userKeys = uids.map(uid => `uid:${uid}:chat:room:${roomId}:mids`); }));
const mids = await db.getSortedSetsMembers(userKeys); }, {
const uniqMids = _.uniq(_.flatten(mids)); batch: 500,
let messageData = await db.getObjects(uniqMids.map(mid => `message:${mid}`)); });
messageData.forEach((m, idx) => {
if (m) { await batch.processArray(allRoomIds, async (roomIds) => {
m.mid = parseInt(uniqMids[idx], 10); const arrayOfRoomData = await db.getObjects(roomIds.map(roomId => `chat:room:${roomId}`));
} for (const roomData of arrayOfRoomData) {
}); if (roomData) {
messageData = messageData.filter(Boolean); 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;
}
const bulkSet = messageData.map( let messageData = await db.getObjects(uniqMids.map(mid => `message:${mid}`));
msg => [`message:${msg.mid}`, { roomId: roomId }] messageData.forEach((m, idx) => {
); if (m) {
m.mid = parseInt(uniqMids[idx], 10);
}
});
messageData = messageData.filter(Boolean);
await db.setObjectBulk(bulkSet); const bulkSet = messageData.map(
await db.setObjectField(`chat:room:${roomId}`, 'userCount', uids.length); msg => [`message:${msg.mid}`, { roomId: roomId }]
await db.sortedSetAdd( );
`chat:room:${roomId}:mids`,
messageData.map(m => m.timestamp), await db.setObjectBulk(bulkSet);
messageData.map(m => m.mid), await db.sortedSetAdd(
); `chat:room:${roomId}:mids`,
await db.deleteAll(userKeys); 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, 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="d-flex justify-content-between align-items-center">
<div class=""> <div class="">
<strong>{custom-skin-name}</strong> <strong>{custom-skin-name}</strong>
</div> </div>
<div class=""> <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="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> <button type="button" data-type="remove" class="btn btn-sm btn-light"><i class="fa fa-trash-o text-danger"></i></button>
</div> </div>

@ -1,5 +1,5 @@
{{{ each themes }}} {{{ 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"> <div class="card h-100">
<img src="{./screenshot_url}" class="card-img-top"> <img src="{./screenshot_url}" class="card-img-top">
<div class="card-body"> <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 id="post-tooltip" class="card card-body shadow bg-body text-body z-1 position-absolute">
<div class="clearfix"> <div class="d-flex flex-column gap-2">
<div class="icon float-start"> <div class="d-flex gap-1 align-items-center">
<a href="{{{ if post.user.userslug }}}{config.relative_path}/user/{post.user.userslug}{{{ else }}}#{{{ end }}}"> <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} {buildAvatar(post.user, "24px", true, "", "user/picture")} {post.user.username}
</a> </a>
<span class="timeago text-xs" title="{post.timestampISO}"></span>
</div> </div>
<small class="float-end"> <div class="content">{post.content}</div>
<span class="timeago" title="{post.timestampISO}"></span>
</small>
</div> </div>
<div class="content">{post.content}</div>
</div> </div>

Loading…
Cancel
Save