Skip to content

NodeBB Global Tchat : Forum with many users performance issues

Solved Configure
13 2 1.8k 1
  • Like you know I use glolbal chat plugin
    We had a massive influx of registrations and new members which means that they all integrate the chat room of the plugin

    We see this topic on github plugin page :

    https://github.com/NodeBB/nodebb-plugin-global-chat/issues/5

    I see maybe a solution here dating from 2019 : https://github.com/NodeBB/nodebb-plugin-global-chat/issues/5#issuecomment-492242438

    7bc1e6f9-542e-41d9-96aa-8a717d6bd538-image.png

    I then asked the question to the staff without an answer :

    Is this still relevant?
    Where in the file should I put the code?

    So Iโ€™m opening this topic to find out what you think about it with your expert eye ๐Ÿ˜‰

  • Like you know I use glolbal chat plugin
    We had a massive influx of registrations and new members which means that they all integrate the chat room of the plugin

    We see this topic on github plugin page :

    https://github.com/NodeBB/nodebb-plugin-global-chat/issues/5

    I see maybe a solution here dating from 2019 : https://github.com/NodeBB/nodebb-plugin-global-chat/issues/5#issuecomment-492242438

    7bc1e6f9-542e-41d9-96aa-8a717d6bd538-image.png

    I then asked the question to the staff without an answer :

    Is this still relevant?
    Where in the file should I put the code?

    So Iโ€™m opening this topic to find out what you think about it with your expert eye ๐Ÿ˜‰

    @DownPW Iโ€™d say that this was still relevant, and would solve the short term issue (in most cases) but if anything, youโ€™ll still experience slowness if you have say 600 users online.

    The right way to handle this in my view would be to pass the notification stream to a message queuing service (like RabbitMQ or Redis) and let that process in the back end rather than cause locking on the forum itself. Iโ€™m surprised that this is in fact the case as standard emails are being queued and arenโ€™t sent in real time.

    I personally donโ€™t use this plugin as you know, so Iโ€™d need to review the code first in order to understand itโ€™s structure.

  • @DownPW Iโ€™d say that this was still relevant, and would solve the short term issue (in most cases) but if anything, youโ€™ll still experience slowness if you have say 600 users online.

    The right way to handle this in my view would be to pass the notification stream to a message queuing service (like RabbitMQ or Redis) and let that process in the back end rather than cause locking on the forum itself. Iโ€™m surprised that this is in fact the case as standard emails are being queued and arenโ€™t sent in real time.

    I personally donโ€™t use this plugin as you know, so Iโ€™d need to review the code first in order to understand itโ€™s structure.

    @phenomlab said in NodeBB Global Tchat : Forum with many users performance issues:

    Iโ€™d say that this was still relevant, and would solve the short term issue (in most cases) but if anything, youโ€™ll still experience slowness if you have say 600 users online.
    The right way to handle this in my view would be to pass the notification stream to a message queuing service (like RabbitMQ or Redis) and let that process in the back end rather than cause locking on the forum itself. Iโ€™m surprised that this is in fact the case as standard emails are being queued and arenโ€™t sent in real time.
    I personally donโ€™t use this plugin as you know, so Iโ€™d need to review the code first in order to understand itโ€™s structure.

    Thank you for your analyse ๐Ÿ‘

    I would have liked to try the thing all the same, just to see the result

    600 users is better than 4000 ๐Ÿ˜‰

    Here is the content of the /src/messsagin/notification.js file

    I put it here because I donโ€™t see where to integrate the given code for test :

    'use strict';
    
    const winston = require('winston');
    
    const user = require('../user');
    const notifications = require('../notifications');
    const sockets = require('../socket.io');
    const plugins = require('../plugins');
    const meta = require('../meta');
    
    module.exports = function (Messaging) {
    	Messaging.notifyQueue = {}; // Only used to notify a user of a new chat message, see Messaging.notifyUser
    
    	Messaging.notifyUsersInRoom = async (fromUid, roomId, messageObj) => {
    		let uids = await Messaging.getUidsInRoom(roomId, 0, -1);
    		uids = await user.blocks.filterUids(fromUid, uids);
    
    		let data = {
    			roomId: roomId,
    			fromUid: fromUid,
    			message: messageObj,
    			uids: uids,
    		};
    		data = await plugins.hooks.fire('filter:messaging.notify', data);
    		if (!data || !data.uids || !data.uids.length) {
    			return;
    		}
    
    		uids = data.uids;
    		uids.forEach((uid) => {
    			data.self = parseInt(uid, 10) === parseInt(fromUid, 10) ? 1 : 0;
    			Messaging.pushUnreadCount(uid);
    			sockets.in(`uid_${uid}`).emit('event:chats.receive', data);
    		});
    		if (messageObj.system) {
    			return;
    		}
    		// Delayed notifications
    		let queueObj = Messaging.notifyQueue[`${fromUid}:${roomId}`];
    		if (queueObj) {
    			queueObj.message.content += `\n${messageObj.content}`;
    			clearTimeout(queueObj.timeout);
    		} else {
    			queueObj = {
    				message: messageObj,
    			};
    			Messaging.notifyQueue[`${fromUid}:${roomId}`] = queueObj;
    		}
    
    		queueObj.timeout = setTimeout(async () => {
    			try {
    				await sendNotifications(fromUid, uids, roomId, queueObj.message);
    			} catch (err) {
    				winston.error(`[messaging/notifications] Unabled to send notification\n${err.stack}`);
    			}
    		}, meta.config.notificationSendDelay * 1000);
    	};
    
    	async function sendNotifications(fromuid, uids, roomId, messageObj) {
    		const isOnline = await user.isOnline(uids);
    		uids = uids.filter((uid, index) => !isOnline[index] && parseInt(fromuid, 10) !== parseInt(uid, 10));
    		if (!uids.length) {
    			return;
    		}
    
    		const { displayname } = messageObj.fromUser;
    
    		const isGroupChat = await Messaging.isGroupChat(roomId);
    		const notification = await notifications.create({
    			type: isGroupChat ? 'new-group-chat' : 'new-chat',
    			subject: `email:notif.chat.subject, ${displayname}`,
    			bodyShort: `notifications:new_message_from, ${displayname}`,
    			bodyLong: messageObj.content,
    			nid: `chat_${fromuid}_${roomId}`,
    			from: fromuid,
    			path: `/chats/${messageObj.roomId}`,
    		});
    
    		delete Messaging.notifyQueue[`${fromuid}:${roomId}`];
    		notifications.push(notification, uids);
    	}
    };
    
  • @phenomlab said in NodeBB Global Tchat : Forum with many users performance issues:

    Iโ€™d say that this was still relevant, and would solve the short term issue (in most cases) but if anything, youโ€™ll still experience slowness if you have say 600 users online.
    The right way to handle this in my view would be to pass the notification stream to a message queuing service (like RabbitMQ or Redis) and let that process in the back end rather than cause locking on the forum itself. Iโ€™m surprised that this is in fact the case as standard emails are being queued and arenโ€™t sent in real time.
    I personally donโ€™t use this plugin as you know, so Iโ€™d need to review the code first in order to understand itโ€™s structure.

    Thank you for your analyse ๐Ÿ‘

    I would have liked to try the thing all the same, just to see the result

    600 users is better than 4000 ๐Ÿ˜‰

    Here is the content of the /src/messsagin/notification.js file

    I put it here because I donโ€™t see where to integrate the given code for test :

    'use strict';
    
    const winston = require('winston');
    
    const user = require('../user');
    const notifications = require('../notifications');
    const sockets = require('../socket.io');
    const plugins = require('../plugins');
    const meta = require('../meta');
    
    module.exports = function (Messaging) {
    	Messaging.notifyQueue = {}; // Only used to notify a user of a new chat message, see Messaging.notifyUser
    
    	Messaging.notifyUsersInRoom = async (fromUid, roomId, messageObj) => {
    		let uids = await Messaging.getUidsInRoom(roomId, 0, -1);
    		uids = await user.blocks.filterUids(fromUid, uids);
    
    		let data = {
    			roomId: roomId,
    			fromUid: fromUid,
    			message: messageObj,
    			uids: uids,
    		};
    		data = await plugins.hooks.fire('filter:messaging.notify', data);
    		if (!data || !data.uids || !data.uids.length) {
    			return;
    		}
    
    		uids = data.uids;
    		uids.forEach((uid) => {
    			data.self = parseInt(uid, 10) === parseInt(fromUid, 10) ? 1 : 0;
    			Messaging.pushUnreadCount(uid);
    			sockets.in(`uid_${uid}`).emit('event:chats.receive', data);
    		});
    		if (messageObj.system) {
    			return;
    		}
    		// Delayed notifications
    		let queueObj = Messaging.notifyQueue[`${fromUid}:${roomId}`];
    		if (queueObj) {
    			queueObj.message.content += `\n${messageObj.content}`;
    			clearTimeout(queueObj.timeout);
    		} else {
    			queueObj = {
    				message: messageObj,
    			};
    			Messaging.notifyQueue[`${fromUid}:${roomId}`] = queueObj;
    		}
    
    		queueObj.timeout = setTimeout(async () => {
    			try {
    				await sendNotifications(fromUid, uids, roomId, queueObj.message);
    			} catch (err) {
    				winston.error(`[messaging/notifications] Unabled to send notification\n${err.stack}`);
    			}
    		}, meta.config.notificationSendDelay * 1000);
    	};
    
    	async function sendNotifications(fromuid, uids, roomId, messageObj) {
    		const isOnline = await user.isOnline(uids);
    		uids = uids.filter((uid, index) => !isOnline[index] && parseInt(fromuid, 10) !== parseInt(uid, 10));
    		if (!uids.length) {
    			return;
    		}
    
    		const { displayname } = messageObj.fromUser;
    
    		const isGroupChat = await Messaging.isGroupChat(roomId);
    		const notification = await notifications.create({
    			type: isGroupChat ? 'new-group-chat' : 'new-chat',
    			subject: `email:notif.chat.subject, ${displayname}`,
    			bodyShort: `notifications:new_message_from, ${displayname}`,
    			bodyLong: messageObj.content,
    			nid: `chat_${fromuid}_${roomId}`,
    			from: fromuid,
    			path: `/chats/${messageObj.roomId}`,
    		});
    
    		delete Messaging.notifyQueue[`${fromuid}:${roomId}`];
    		notifications.push(notification, uids);
    	}
    };
    

    @phenomlab

    An idea for where to put this code before I open a thread an NodeBB communauty ?

  • @phenomlab

    An idea for where to put this code before I open a thread an NodeBB communauty ?

    @DownPW not specifically, no, as there is an existing function with the same name. The comma at the end of the revised function would indicate part of an existing array but Iโ€™m not entirely sure of where it should be placed - or if it should override the existing function altogether (which I donโ€™t think is the case).

  • @DownPW not specifically, no, as there is an existing function with the same name. The comma at the end of the revised function would indicate part of an existing array but Iโ€™m not entirely sure of where it should be placed - or if it should override the existing function altogether (which I donโ€™t think is the case).

    @phenomlab arf I hope i have an answers in nodeBB ๐Ÿ˜ž

    But itโ€™s an async function maybe here :

    345a9b2c-26cf-491d-8ffe-047afa529d61-image.png

  • @phenomlab arf I hope i have an answers in nodeBB ๐Ÿ˜ž

    But itโ€™s an async function maybe here :

    345a9b2c-26cf-491d-8ffe-047afa529d61-image.png

    @DownPW you can always experiment ๐Ÿ‘

  • @DownPW you can always experiment ๐Ÿ‘

    @phenomlab no luck ๐Ÿ˜ž

  • @DownPW what have you tried?

  • @DownPW what have you tried?

    @phenomlab lot of things ๐Ÿ˜‰

  • @phenomlab lot of things ๐Ÿ˜‰

    @DownPW can you provide some brief examples?

  • @DownPW can you provide some brief examples?

    @phenomlab .

    'use strict';
    
    const winston = require('winston');
    
    const user = require('../user');
    const notifications = require('../notifications');
    const sockets = require('../socket.io');
    const plugins = require('../plugins');
    const meta = require('../meta');
    
    module.exports = function (Messaging) {
    	Messaging.notifyQueue = {}; // Only used to notify a user of a new chat message, see Messaging.notifyUser
    	
    	Messaging.notifyUsersInRoom = async (fromUid, roomId, messageObj) => {
    		let uids = await Messaging.getUidsInRoom(roomId, 0, -1);
    		uids = await user.blocks.filterUids(fromUid, uids);
    
    		let data = {
    			roomId: roomId,
    			fromUid: fromUid,
    			message: messageObj,
    			uids: uids,
    		};
    		data = await plugins.hooks.fire('filter:messaging.notify', data);
    		if (!data || !data.uids || !data.uids.length) {
    			return;
    		}
    
    		uids = data.uids;
    		uids.forEach((uid) => {
    			data.self = parseInt(uid, 10) === parseInt(fromUid, 10) ? 1 : 0;
    			Messaging.pushUnreadCount(uid);
    			sockets.in(`uid_${uid}`).emit('event:chats.receive', data);
    		});
    		if (messageObj.system) {
    			return;
    		}
    		// Delayed notifications
    		let queueObj = Messaging.notifyQueue[`${fromUid}:${roomId}`];
    		if (queueObj) {
    			queueObj.message.content += `\n${messageObj.content}`;
    			clearTimeout(queueObj.timeout);
    		} else {
    			queueObj = {
    				message: messageObj,
    			};
    			Messaging.notifyQueue[`${fromUid}:${roomId}`] = queueObj;
    		}
    
    		queueObj.timeout = setTimeout(async () => {
    			try {
    				await sendNotifications(fromUid, uids, roomId, queueObj.message);
    			} catch (err) {
    				winston.error(`[messaging/notifications] Unabled to send notification\n${err.stack}`);
    			}
    		}, meta.config.notificationSendDelay * 1000);
    		
    		
               if (roomId != 11) { // 5 Is the ID of the ID of the global chat room.
                   Messaging.getUidsInRoom(roomId, 0, -1); // Proceed as normal.
                } else {
                    user.getUidsFromSet('users:online', 0, -1); // Only notify online users.
                }
    	};
    
    	async function sendNotifications(fromuid, uids, roomId, messageObj) {
    		const isOnline = await user.isOnline(uids);
    		uids = uids.filter((uid, index) => !isOnline[index] && parseInt(fromuid, 10) !== parseInt(uid, 10));
    		if (!uids.length) {
    			return;
    		}
    
    
    		const { displayname } = messageObj.fromUser;
    
    		const isGroupChat = await Messaging.isGroupChat(roomId);
    		const notification = await notifications.create({
    			type: isGroupChat ? 'new-group-chat' : 'new-chat',
    			subject: `email:notif.chat.subject, ${displayname}`,
    			bodyShort: `notifications:new_message_from, ${displayname}`,
    			bodyLong: messageObj.content,
    			nid: `chat_${fromuid}_${roomId}`,
    			from: fromuid,
    			path: `/chats/${messageObj.roomId}`,
    		});
    
    		delete Messaging.notifyQueue[`${fromuid}:${roomId}`];
    		notifications.push(notification, uids);
    	}
    	
    
    		
    };
    

    nodebb build is ok with this code but I donโ€™t see any diiference of latencies

  • @phenomlab .

    'use strict';
    
    const winston = require('winston');
    
    const user = require('../user');
    const notifications = require('../notifications');
    const sockets = require('../socket.io');
    const plugins = require('../plugins');
    const meta = require('../meta');
    
    module.exports = function (Messaging) {
    	Messaging.notifyQueue = {}; // Only used to notify a user of a new chat message, see Messaging.notifyUser
    	
    	Messaging.notifyUsersInRoom = async (fromUid, roomId, messageObj) => {
    		let uids = await Messaging.getUidsInRoom(roomId, 0, -1);
    		uids = await user.blocks.filterUids(fromUid, uids);
    
    		let data = {
    			roomId: roomId,
    			fromUid: fromUid,
    			message: messageObj,
    			uids: uids,
    		};
    		data = await plugins.hooks.fire('filter:messaging.notify', data);
    		if (!data || !data.uids || !data.uids.length) {
    			return;
    		}
    
    		uids = data.uids;
    		uids.forEach((uid) => {
    			data.self = parseInt(uid, 10) === parseInt(fromUid, 10) ? 1 : 0;
    			Messaging.pushUnreadCount(uid);
    			sockets.in(`uid_${uid}`).emit('event:chats.receive', data);
    		});
    		if (messageObj.system) {
    			return;
    		}
    		// Delayed notifications
    		let queueObj = Messaging.notifyQueue[`${fromUid}:${roomId}`];
    		if (queueObj) {
    			queueObj.message.content += `\n${messageObj.content}`;
    			clearTimeout(queueObj.timeout);
    		} else {
    			queueObj = {
    				message: messageObj,
    			};
    			Messaging.notifyQueue[`${fromUid}:${roomId}`] = queueObj;
    		}
    
    		queueObj.timeout = setTimeout(async () => {
    			try {
    				await sendNotifications(fromUid, uids, roomId, queueObj.message);
    			} catch (err) {
    				winston.error(`[messaging/notifications] Unabled to send notification\n${err.stack}`);
    			}
    		}, meta.config.notificationSendDelay * 1000);
    		
    		
               if (roomId != 11) { // 5 Is the ID of the ID of the global chat room.
                   Messaging.getUidsInRoom(roomId, 0, -1); // Proceed as normal.
                } else {
                    user.getUidsFromSet('users:online', 0, -1); // Only notify online users.
                }
    	};
    
    	async function sendNotifications(fromuid, uids, roomId, messageObj) {
    		const isOnline = await user.isOnline(uids);
    		uids = uids.filter((uid, index) => !isOnline[index] && parseInt(fromuid, 10) !== parseInt(uid, 10));
    		if (!uids.length) {
    			return;
    		}
    
    
    		const { displayname } = messageObj.fromUser;
    
    		const isGroupChat = await Messaging.isGroupChat(roomId);
    		const notification = await notifications.create({
    			type: isGroupChat ? 'new-group-chat' : 'new-chat',
    			subject: `email:notif.chat.subject, ${displayname}`,
    			bodyShort: `notifications:new_message_from, ${displayname}`,
    			bodyLong: messageObj.content,
    			nid: `chat_${fromuid}_${roomId}`,
    			from: fromuid,
    			path: `/chats/${messageObj.roomId}`,
    		});
    
    		delete Messaging.notifyQueue[`${fromuid}:${roomId}`];
    		notifications.push(notification, uids);
    	}
    	
    
    		
    };
    

    nodebb build is ok with this code but I donโ€™t see any diiference of latencies

    'use strict';
    
    const winston = require('winston');
    
    const user = require('../user');
    const notifications = require('../notifications');
    const sockets = require('../socket.io');
    const plugins = require('../plugins');
    const meta = require('../meta');
    
    module.exports = function (Messaging) {
    	Messaging.notifyQueue = {}; // Only used to notify a user of a new chat message, see Messaging.notifyUser
    	
    	Messaging.notifyUsersInRoom = async (fromUid, roomId, messageObj) => {
    		let uids = await Messaging.getUidsInRoom(roomId, 0, -1);
    		uids = await user.blocks.filterUids(fromUid, uids);
    
    		let data = {
    			roomId: roomId,
    			fromUid: fromUid,
    			message: messageObj,
    			uids: uids,
    		};
    		data = await plugins.hooks.fire('filter:messaging.notify', data);
    		if (!data || !data.uids || !data.uids.length) {
    			return;
    		}
    
    		uids = data.uids;
    		uids.forEach((uid) => {
    			data.self = parseInt(uid, 10) === parseInt(fromUid, 10) ? 1 : 0;
    			Messaging.pushUnreadCount(uid);
    			sockets.in(`uid_${uid}`).emit('event:chats.receive', data);
    		});
    		if (messageObj.system) {
    			return;
    		}
    		// Delayed notifications
    		let queueObj = Messaging.notifyQueue[`${fromUid}:${roomId}`];
    		if (queueObj) {
    			queueObj.message.content += `\n${messageObj.content}`;
    			clearTimeout(queueObj.timeout);
    		} else {
    			queueObj = {
    				message: messageObj,
    			};
    			Messaging.notifyQueue[`${fromUid}:${roomId}`] = queueObj;
    		}
    
    		queueObj.timeout = setTimeout(async () => {
    			try {
    				await sendNotifications(fromUid, uids, roomId, queueObj.message);
    			} catch (err) {
    				winston.error(`[messaging/notifications] Unabled to send notification\n${err.stack}`);
    			}
    		}, meta.config.notificationSendDelay * 1000);
    		
    	};
    
    	async function sendNotifications(fromuid, uids, roomId, messageObj) {
    		const isOnline = await user.isOnline(uids);
    		uids = uids.filter((uid, index) => !isOnline[index] && parseInt(fromuid, 10) !== parseInt(uid, 10));
    		if (!uids.length) {
    			return;
    		}
    		
    		if (roomId != 11) { // 5 Is the ID of the ID of the global chat room.
            Messaging.getUidsInRoom(roomId, 0, -1); // Proceed as normal.
            } else {
            user.getUidsFromSet('users:online', 0, -1); // Only notify online users.
            }
    
    
    		const { displayname } = messageObj.fromUser;
    
    		const isGroupChat = await Messaging.isGroupChat(roomId);
    		const notification = await notifications.create({
    			type: isGroupChat ? 'new-group-chat' : 'new-chat',
    			subject: `email:notif.chat.subject, ${displayname}`,
    			bodyShort: `notifications:new_message_from, ${displayname}`,
    			bodyLong: messageObj.content,
    			nid: `chat_${fromuid}_${roomId}`,
    			from: fromuid,
    			path: `/chats/${messageObj.roomId}`,
    		});
    
    		delete Messaging.notifyQueue[`${fromuid}:${roomId}`];
    		notifications.push(notification, uids);
    	}
    	
    
    		
    };
    
  • DownPWundefined DownPW has marked this topic as solved on

Did this solution help you?
Did you find the suggested solution useful? Support ๐Ÿ’— Sudonix with a coffee โ˜•
If your organisation needs deeper expertise around infrastructure, security, or technology leadership, learn more about Phenomlab Ltd. Many of the deeper technical guides behind Sudonix are published there.

Related Topics
  • Nodebb and emails

    Solved Configure nodebb
    27
    5 Votes
    27 Posts
    4k Views
    @Panda it will use SMTP. In all cases, I never use any plugin to send email. Iโ€™ll always send it raw.
  • Upgrade issues

    Solved Configure nodebb
    2
    1
    2 Votes
    2 Posts
    801 Views
    Use this code git fetch # Grab the latest code from the NodeBB repository git checkout v3.x git reset --hard origin/v3.x And you will have the latest version without specifying it https://docs.nodebb.org/configuring/upgrade/
  • Fixed background to nodebb forum

    Solved Configure nodebb
    25
    4 Votes
    25 Posts
    5k Views
    @Panda said in Fixed background to nodebb forum: Chatgpt told me the ::before method. Go figure
  • NodeBB: Favicon upload issue

    Solved Configure nodebb favicon
    12
    1
    3 Votes
    12 Posts
    2k Views
    @phenomlab I am on a Mac, so I used the โ€œOption + Command + Iโ€, and then performed the steps. It loaded my favicon! I checked on Firefox which I havenโ€™t used before, and it showed my favicon also! Thatโ€™s fantastic and thank you for the help!
  • NodeBB: The global Search option

    Solved Configure search nodebb
    5
    0 Votes
    5 Posts
    820 Views
    @mventures Yes, exactly. The other icon will restart NodeBB whilst the first icon I referenced will rebuild (recompile) it. The huge strength of NodeBB over Flarum (for example) is that the code is precompiled, and called once at boot. PHPโ€™s code has to repeatedly reload code from source making it much slower.
  • Gettin Erors NodeBB

    Solved Configure nodebb eror
    7
    0 Votes
    7 Posts
    1k Views
    @phenomlab no forum is working goods. there is no eror message since yestarday.
  • NodeBB metadata

    Solved Configure nodebb
    4
    2 Votes
    4 Posts
    1k Views
    @phenomlab said in NodeBB metadata: @jac Are you sure ? https://www.google.co.uk/search?q=site%3Astockportcounty.fans&sxsrf=AOaemvLwnaZL-PliU_2dBOg_Eo1pMVhBjg%3A1638982328139&source=hp&ei=uOKwYeatBcOsad3yp7AE&iflsig=ALs-wAMAAAAAYbDwyLBSDcG5XYoFCKwQFhgz94wTxOcV&ved=0ahUKEwjm6dX71NT0AhVDVhoKHV35CUYQ4dUDCAk&uact=5&oq=site%3Astockportcounty.fans&gs_lcp=Cgdnd3Mtd2l6EAM6BAgjECc6CwgAEIAEELEDEIMBOg4ILhCABBCxAxDHARCjAjoRCC4QgAQQsQMQgwEQxwEQowI6BQguEIAEOggIABCABBCxAzoFCAAQgAQ6CAguELEDEIMBOgsILhCABBDHARCvAToICC4QgAQQsQM6BQgAELEDOgsILhCABBDHARDRAzoLCAAQgAQQsQMQyQM6BQgAEJIDUABYySZg0CdoAHAAeACAAW2IAa0NkgEEMjMuMpgBAKABAQ&sclient=gws-wiz Fair enough ๐Ÿคช๐Ÿคช.
  • nodebb dropdown menu

    Solved Configure nodebb
    5
    1
    0 Votes
    5 Posts
    1k Views
    @phenomlab said in nodebb dropdown menu: @kurulumu-net You set it like the below example taken from this site [image: 1637939951821-aae36790-3257-4bb2-ad5a-0d744309876a-image.png] Which presents this [image: 1637939983445-77f47260-2941-4afe-9614-8e17dcfc8c19-image.png] Very interestingโ€ฆ I actually thought this wasnโ€™t possible, as I remember it being asked in the NodeBB forum. Is this something new thatโ€™s been implemented? Iโ€™ll 100% be doing that when Iโ€™m on the laptop over the weekend.