mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 02:55:58 +01:00 
			
		
		
		
	feat: #11850, chat msg search
This commit is contained in:
		| @@ -93,7 +93,7 @@ | ||||
|         "nconf": "0.12.0", | ||||
|         "nodebb-plugin-2factor": "7.1.3", | ||||
|         "nodebb-plugin-composer-default": "10.2.6", | ||||
|         "nodebb-plugin-dbsearch": "6.1.0", | ||||
|         "nodebb-plugin-dbsearch": "6.2.0", | ||||
|         "nodebb-plugin-emoji": "5.1.3", | ||||
|         "nodebb-plugin-emoji-android": "4.0.0", | ||||
|         "nodebb-plugin-markdown": "12.1.7", | ||||
| @@ -101,10 +101,10 @@ | ||||
|         "nodebb-plugin-ntfy": "1.1.0", | ||||
|         "nodebb-plugin-spam-be-gone": "2.1.1", | ||||
|         "nodebb-rewards-essentials": "0.2.3", | ||||
|         "nodebb-theme-harmony": "1.1.16", | ||||
|         "nodebb-theme-harmony": "1.1.17", | ||||
|         "nodebb-theme-lavender": "7.1.3", | ||||
|         "nodebb-theme-peace": "2.1.4", | ||||
|         "nodebb-theme-persona": "13.2.8", | ||||
|         "nodebb-theme-peace": "2.1.5", | ||||
|         "nodebb-theme-persona": "13.2.9", | ||||
|         "nodebb-widget-essentials": "7.0.13", | ||||
|         "nodemailer": "6.9.4", | ||||
|         "nprogress": "0.2.0", | ||||
|   | ||||
| @@ -9,6 +9,7 @@ define('forum/chats', [ | ||||
| 	'forum/chats/manage', | ||||
| 	'forum/chats/messages', | ||||
| 	'forum/chats/user-list', | ||||
| 	'forum/chats/message-search', | ||||
| 	'composer/autocomplete', | ||||
| 	'hooks', | ||||
| 	'bootbox', | ||||
| @@ -17,10 +18,9 @@ define('forum/chats', [ | ||||
| 	'api', | ||||
| 	'uploadHelpers', | ||||
| ], function ( | ||||
| 	components, mousetrap, | ||||
| 	recentChats, create, manage, messages, | ||||
| 	userList, autocomplete, hooks, bootbox, | ||||
| 	alerts, chatModule, api, uploadHelpers | ||||
| 	components, mousetrap, recentChats, create, | ||||
| 	manage, messages, userList, messageSearch, autocomplete, | ||||
| 	hooks, bootbox, alerts, chatModule, api, uploadHelpers | ||||
| ) { | ||||
| 	const Chats = { | ||||
| 		initialised: false, | ||||
| @@ -62,8 +62,8 @@ define('forum/chats', [ | ||||
| 		} | ||||
|  | ||||
| 		Chats.initialised = true; | ||||
| 		messages.scrollToBottom($('.expanded-chat ul.chat-content')); | ||||
| 		messages.wrapImagesInLinks($('.expanded-chat ul.chat-content')); | ||||
| 		messages.scrollToBottom($('[component="chat/message/content"]')); | ||||
| 		messages.wrapImagesInLinks($('[component="chat/message/content"]')); | ||||
| 		create.init(); | ||||
|  | ||||
| 		hooks.fire('action:chat.loaded', $('.chats-full')); | ||||
| @@ -80,8 +80,8 @@ define('forum/chats', [ | ||||
| 		Chats.addRenameHandler(roomId, chatControls.find('[data-action="rename"]')); | ||||
| 		Chats.addLeaveHandler(roomId, chatControls.find('[data-action="leave"]')); | ||||
| 		Chats.addDeleteHandler(roomId, chatControls.find('[data-action="delete"]')); | ||||
| 		Chats.addScrollHandler(roomId, ajaxify.data.uid, $('.chat-content')); | ||||
| 		Chats.addScrollBottomHandler($('.chat-content')); | ||||
| 		Chats.addScrollHandler(roomId, ajaxify.data.uid, $('[component="chat/message/content"]')); | ||||
| 		Chats.addScrollBottomHandler($('[component="chat/message/content"]')); | ||||
| 		Chats.addCharactersLeftHandler(mainWrapper); | ||||
| 		Chats.addTextareaResizeHandler(mainWrapper); | ||||
| 		Chats.addIPHandler(mainWrapper); | ||||
| @@ -98,6 +98,7 @@ define('forum/chats', [ | ||||
| 			Chats.switchChat(); | ||||
| 		}); | ||||
| 		userList.init(roomId, mainWrapper); | ||||
| 		messageSearch.init(roomId); | ||||
| 		Chats.addPublicRoomSortHandler(); | ||||
| 		Chats.addTooltipHandler(); | ||||
| 		Chats.addNotificationSettingHandler(); | ||||
| @@ -268,24 +269,24 @@ define('forum/chats', [ | ||||
| 		// https://stackoverflow.com/questions/454202/creating-a-textarea-with-auto-resize | ||||
| 		const textarea = parent.find('[component="chat/input"]'); | ||||
| 		textarea.on('input', function () { | ||||
| 			const isAtBottom = messages.isAtBottom(parent.find('.chat-content')); | ||||
| 			const isAtBottom = messages.isAtBottom(parent.find('[component="chat/message/content"]')); | ||||
| 			textarea.css({ height: 0 }); | ||||
| 			textarea.css({ height: messages.calcAutoTextAreaHeight(textarea) + 'px' }); | ||||
| 			if (isAtBottom) { | ||||
| 				messages.scrollToBottom(parent.find('.chat-content')); | ||||
| 				messages.scrollToBottom(parent.find('[component="chat/message/content"]')); | ||||
| 			} | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	Chats.addActionHandlers = function (element, roomId) { | ||||
| 		element.on('click', '[data-mid] [data-action]', function () { | ||||
| 			const messageId = $(this).parents('[data-mid]').attr('data-mid'); | ||||
| 			const msgEl = $(this).parents('[data-mid]'); | ||||
| 			const messageId = msgEl.attr('data-mid'); | ||||
| 			const action = this.getAttribute('data-action'); | ||||
|  | ||||
| 			switch (action) { | ||||
| 				case 'edit': { | ||||
| 					const inputEl = $('[data-roomid="' + roomId + '"] [component="chat/input"]'); | ||||
| 					messages.prepEdit(inputEl, messageId, roomId); | ||||
| 					messages.prepEdit(msgEl, messageId, roomId); | ||||
| 					break; | ||||
| 				} | ||||
| 				case 'delete': | ||||
| @@ -509,7 +510,7 @@ define('forum/chats', [ | ||||
| 				Chats.setActive(roomId); | ||||
| 				Chats.addEventListeners(); | ||||
| 				hooks.fire('action:chat.loaded', $('.chats-full')); | ||||
| 				messages.scrollToBottom(mainWrapper.find('.expanded-chat ul.chat-content')); | ||||
| 				messages.scrollToBottom(mainWrapper.find('[component="chat/message/content"]')); | ||||
| 				if (history.pushState) { | ||||
| 					history.pushState({ | ||||
| 						url: url, | ||||
| @@ -543,7 +544,7 @@ define('forum/chats', [ | ||||
| 				data.message.self = data.self; | ||||
| 				data.message.timestamp = Math.min(Date.now(), data.message.timestamp); | ||||
| 				data.message.timestampISO = utils.toISOString(data.message.timestamp); | ||||
| 				messages.appendChatMessage($('.expanded-chat .chat-content'), data.message); | ||||
| 				messages.appendChatMessage($('[component="chat/message/content"]'), data.message); | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
|   | ||||
| @@ -2,8 +2,8 @@ | ||||
|  | ||||
|  | ||||
| define('forum/chats/create', [ | ||||
| 	'components', 'api', 'alerts', 'forum/chats/search', | ||||
| ], function (components, api, alerts, search) { | ||||
| 	'components', 'api', 'alerts', 'forum/chats/user-search', | ||||
| ], function (components, api, alerts, userSearch) { | ||||
| 	const create = {}; | ||||
| 	create.init = function () { | ||||
| 		components.get('chat/create').on('click', handleCreate); | ||||
| @@ -65,7 +65,7 @@ define('forum/chats/create', [ | ||||
|  | ||||
| 		const chatRoomUsersList = modal.find('[component="chat/room/users"]'); | ||||
|  | ||||
| 		search.init({ | ||||
| 		userSearch.init({ | ||||
| 			onSelect: async function (user) { | ||||
| 				const html = await app.parseAndTranslate('modals/create-room', 'selectedUsers', { selectedUsers: [user] }); | ||||
| 				chatRoomUsersList.append(html); | ||||
|   | ||||
							
								
								
									
										82
									
								
								public/src/client/chats/message-search.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								public/src/client/chats/message-search.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| 'use strict'; | ||||
|  | ||||
|  | ||||
| define('forum/chats/message-search', [ | ||||
| 	'components', 'alerts', 'forum/chats/messages', | ||||
| ], function (components, alerts, messages) { | ||||
| 	const messageSearch = {}; | ||||
| 	let roomId = 0; | ||||
| 	let resultListEl; | ||||
| 	let chatContent; | ||||
| 	let clearEl; | ||||
|  | ||||
| 	messageSearch.init = function (_roomId) { | ||||
| 		roomId = _roomId; | ||||
| 		const searchInput = $('[component="chat/room/search"]'); | ||||
| 		searchInput.on('keyup', utils.debounce(doSearch, 250)) | ||||
| 			.on('focus', () => { | ||||
| 				if (searchInput.val()) { | ||||
| 					doSearch(); | ||||
| 				} | ||||
| 			}); | ||||
| 		resultListEl = $('[component="chat/message/search/results"]'); | ||||
| 		chatContent = $('[component="chat/message/content"]'); | ||||
| 		clearEl = $('[component="chat/room/search/clear"]'); | ||||
| 		$('[component="chat/input"]').on('focus', () => { | ||||
| 			resultListEl.addClass('hidden'); | ||||
| 			chatContent.removeClass('hidden'); | ||||
| 		}); | ||||
| 		clearEl.on('click', clearInputAndResults); | ||||
| 	}; | ||||
|  | ||||
| 	function clearInputAndResults() { | ||||
| 		components.get('chat/room/search').val(''); | ||||
| 		removeResults(); | ||||
| 		resultListEl.addClass('hidden'); | ||||
| 		chatContent.removeClass('hidden'); | ||||
| 		clearEl.addClass('hidden'); | ||||
| 	} | ||||
|  | ||||
| 	async function doSearch() { | ||||
| 		const query = components.get('chat/room/search').val(); | ||||
| 		if (!query) { | ||||
| 			return clearInputAndResults(); | ||||
| 		} | ||||
| 		if (query.length <= 2) { | ||||
| 			return; | ||||
| 		} | ||||
| 		clearEl.removeClass('hidden'); | ||||
| 		socket.emit('modules.chats.searchMessages', { | ||||
| 			content: query, | ||||
| 			roomId: roomId, | ||||
| 		}).then(displayResults) | ||||
| 			.catch(alerts.error); | ||||
| 	} | ||||
|  | ||||
| 	function removeResults() { | ||||
| 		resultListEl.children('[data-mid]').remove(); | ||||
| 	} | ||||
|  | ||||
| 	async function displayResults(data) { | ||||
| 		removeResults(); | ||||
|  | ||||
| 		if (!data.length) { | ||||
| 			resultListEl.removeClass('hidden'); | ||||
| 			chatContent.addClass('hidden'); | ||||
| 			return resultListEl.find('[component="chat/message/search/no-results"]').removeClass('hidden'); | ||||
| 		} | ||||
| 		resultListEl.find('[component="chat/message/search/no-results"]').addClass('hidden'); | ||||
|  | ||||
| 		const html = await app.parseAndTranslate('partials/chats/messages', { | ||||
| 			messages: data, | ||||
| 			isAdminOrGlobalMod: app.user.isAdmin || app.user.isGlobalMod, | ||||
| 		}); | ||||
|  | ||||
| 		resultListEl.append(html); | ||||
| 		messages.onMessagesAddedToDom(resultListEl.find('[component="chat/message"]')); | ||||
| 		chatContent.addClass('hidden'); | ||||
| 		resultListEl.removeClass('hidden'); | ||||
| 	} | ||||
|  | ||||
| 	return messageSearch; | ||||
| }); | ||||
| @@ -108,14 +108,15 @@ define('forum/chats/messages', [ | ||||
| 	messages.onMessagesAddedToDom = function (messageEls) { | ||||
| 		messageEls.find('.timeago').timeago(); | ||||
| 		messageEls.find('img:not(.not-responsive)').addClass('img-fluid'); | ||||
| 		messages.wrapImagesInLinks(messageEls.first().parent()); | ||||
| 		messageEls.find('img:not(.emoji)').each(function () { | ||||
| 			images.wrapImageInLink($(this)); | ||||
| 		}); | ||||
| 	}; | ||||
|  | ||||
| 	messages.parseMessage = function (data, callback) { | ||||
| 		const tplData = { | ||||
| 			messages: data, | ||||
| 			isAdminOrGlobalMod: app.user.isAdmin || app.user.isGlobalMod, | ||||
|  | ||||
| 		}; | ||||
| 		if (Array.isArray(data)) { | ||||
| 			app.parseAndTranslate('partials/chats/messages', tplData).then(callback); | ||||
| @@ -155,14 +156,14 @@ define('forum/chats/messages', [ | ||||
| 			.toggleClass('hidden', isAtBottom); | ||||
| 	}; | ||||
|  | ||||
| 	messages.prepEdit = async function (inputEl, mid, roomId) { | ||||
| 	messages.prepEdit = async function (msgEl, mid, roomId) { | ||||
| 		const raw = await socket.emit('modules.chats.getRaw', { mid: mid, roomId: roomId }); | ||||
| 		const editEl = await app.parseAndTranslate('partials/chats/edit-message', { | ||||
| 			rawContent: raw, | ||||
| 		}); | ||||
| 		const messageBody = $(`[data-roomid="${roomId}"] [data-mid="${mid}"] [component="chat/message/body"]`); | ||||
| 		const messageControls = $(`[data-roomid="${roomId}"] [data-mid="${mid}"] [component="chat/message/controls"]`); | ||||
| 		const chatContent = messageBody.parents('.chat-content'); | ||||
| 		const messageBody = msgEl.find(`[component="chat/message/body"]`); | ||||
| 		const messageControls = msgEl.find(`[component="chat/message/controls"]`); | ||||
| 		const chatContent = messageBody.parents('[component="chat/message/content"]'); | ||||
|  | ||||
| 		messageBody.addClass('hidden'); | ||||
| 		messageControls.addClass('hidden'); | ||||
| @@ -173,7 +174,7 @@ define('forum/chats/messages', [ | ||||
| 		textarea.focus().putCursorAtEnd(); | ||||
| 		autoresizeTextArea(textarea); | ||||
|  | ||||
| 		if (messages.isAtBottom(chatContent)) { | ||||
| 		if (chatContent.length && messages.isAtBottom(chatContent)) { | ||||
| 			messages.scrollToBottom(chatContent); | ||||
| 		} | ||||
|  | ||||
| @@ -212,7 +213,7 @@ define('forum/chats/messages', [ | ||||
| 		}); | ||||
|  | ||||
| 		hooks.fire('action:chat.prepEdit', { | ||||
| 			inputEl: inputEl, | ||||
| 			msgEl: msgEl, | ||||
| 			messageId: mid, | ||||
| 			roomId: roomId, | ||||
| 			editEl: editEl, | ||||
| @@ -236,10 +237,10 @@ define('forum/chats/messages', [ | ||||
| 			const self = parseInt(message.fromuid, 10) === parseInt(app.user.uid, 10); | ||||
| 			message.self = self ? 1 : 0; | ||||
| 			messages.parseMessage(message, function (html) { | ||||
| 				const body = components.get('chat/message', message.messageId); | ||||
| 				if (body.length) { | ||||
| 					body.replaceWith(html); | ||||
| 					messages.onMessagesAddedToDom(html); | ||||
| 				const msgEl = components.get('chat/message', message.mid); | ||||
| 				if (msgEl.length) { | ||||
| 					msgEl.replaceWith(html); | ||||
| 					messages.onMessagesAddedToDom(components.get('chat/message', message.mid)); | ||||
| 				} | ||||
| 			}); | ||||
| 		}); | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| 'use strict'; | ||||
| 
 | ||||
| 
 | ||||
| define('forum/chats/search', [ | ||||
| define('forum/chats/user-search', [ | ||||
| 	'components', 'api', 'alerts', | ||||
| ], function (components, api, alerts) { | ||||
| 	const search = {}; | ||||
| 	const userSearch = {}; | ||||
| 	let users = []; | ||||
| 
 | ||||
| 	search.init = function (options) { | ||||
| 	userSearch.init = function (options) { | ||||
| 		options = options || {}; | ||||
| 		users.length = 0; | ||||
| 		components.get('chat/search').on('keyup', utils.debounce(doSearch, 250)); | ||||
| @@ -65,5 +65,5 @@ define('forum/chats/search', [ | ||||
| 		chatsListEl.parent().toggleClass('show', true); | ||||
| 	} | ||||
| 
 | ||||
| 	return search; | ||||
| 	return userSearch; | ||||
| }); | ||||
| @@ -6,6 +6,7 @@ const db = require('../database'); | ||||
| const Messaging = require('../messaging'); | ||||
| const utils = require('../utils'); | ||||
| const user = require('../user'); | ||||
| const plugins = require('../plugins'); | ||||
| const privileges = require('../privileges'); | ||||
| const groups = require('../groups'); | ||||
|  | ||||
| @@ -213,4 +214,41 @@ SocketModules.chats.setNotificationSetting = async (socket, data) => { | ||||
| 	await Messaging.setUserNotificationSetting(socket.uid, data.roomId, data.value); | ||||
| }; | ||||
|  | ||||
| SocketModules.chats.searchMessages = async (socket, data) => { | ||||
| 	if (!data || !utils.isNumber(data.roomId) || !data.content) { | ||||
| 		throw new Error('[[error:invalid-data]]'); | ||||
| 	} | ||||
| 	const [roomData, inRoom] = await Promise.all([ | ||||
| 		Messaging.getRoomData(data.roomId), | ||||
| 		Messaging.isUserInRoom(socket.uid, data.roomId), | ||||
| 	]); | ||||
|  | ||||
| 	if (!roomData) { | ||||
| 		throw new Error('[[error:no-room]]'); | ||||
| 	} | ||||
| 	if (!inRoom) { | ||||
| 		throw new Error('[[error:no-privileges]]'); | ||||
| 	} | ||||
| 	const { ids } = await plugins.hooks.fire('filter:messaging.searchMessages', { | ||||
| 		content: data.content, | ||||
| 		roomId: [data.roomId], | ||||
| 		uid: [data.uid], | ||||
| 		matchWords: 'any', | ||||
| 		ids: [], | ||||
| 	}); | ||||
|  | ||||
| 	let userjoinTimestamp = 0; | ||||
| 	if (!roomData.public) { | ||||
| 		userjoinTimestamp = await db.sortedSetScore(`chat:room:${data.roomId}:uids`, socket.uid); | ||||
| 	} | ||||
| 	const messageData = await Messaging.getMessagesData(ids, socket.uid, data.roomId, false); | ||||
| 	messageData.forEach((msg) => { | ||||
| 		if (msg) { | ||||
| 			msg.newSet = true; | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	return messageData.filter(msg => msg && !msg.deleted && msg.timestamp > userjoinTimestamp); | ||||
| }; | ||||
|  | ||||
| require('../promisify')(SocketModules); | ||||
|   | ||||
| @@ -154,7 +154,7 @@ describe('Messaging Library', () => { | ||||
| 			const { body } = await callv3API('get', `/chats/${roomId}`, {}, 'foo'); | ||||
| 			const { messages } = body.response; | ||||
| 			assert.equal(messages.length, 2); | ||||
| 			assert.strictEqual(messages[0].system, true); | ||||
| 			assert.strictEqual(messages[0].system, 1); | ||||
| 			assert.strictEqual(messages[0].content, 'user-join'); | ||||
|  | ||||
| 			const { statusCode, body: body2 } = await callv3API('put', `/chats/${roomId}/messages/${messages[0].messageId}`, { | ||||
| @@ -233,7 +233,7 @@ describe('Messaging Library', () => { | ||||
| 			const { body } = await callv3API('get', `/chats/${roomId}`, {}, 'foo'); | ||||
| 			const { messages } = body.response; | ||||
| 			const message = messages.pop(); | ||||
| 			assert.strictEqual(message.system, true); | ||||
| 			assert.strictEqual(message.system, 1); | ||||
| 			assert.strictEqual(message.content, 'user-leave'); | ||||
| 		}); | ||||
|  | ||||
| @@ -244,12 +244,12 @@ describe('Messaging Library', () => { | ||||
|  | ||||
| 			assert.equal(messages.length, 4); | ||||
| 			let message = messages.pop(); | ||||
| 			assert.strictEqual(message.system, true); | ||||
| 			assert.strictEqual(message.system, 1); | ||||
| 			assert.strictEqual(message.content, 'user-leave'); | ||||
|  | ||||
| 			// The message before should still be a user-join | ||||
| 			message = messages.pop(); | ||||
| 			assert.strictEqual(message.system, true); | ||||
| 			assert.strictEqual(message.system, 1); | ||||
| 			assert.strictEqual(message.content, 'user-join'); | ||||
| 		}); | ||||
|  | ||||
| @@ -466,7 +466,7 @@ describe('Messaging Library', () => { | ||||
| 			const { messages } = body.response; | ||||
|  | ||||
| 			const message = messages.pop(); | ||||
| 			assert.strictEqual(message.system, true); | ||||
| 			assert.strictEqual(message.system, 1); | ||||
| 			assert.strictEqual(message.content, 'room-rename, new room name'); | ||||
| 		}); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user