diff --git a/client/components/Windows/SearchResults.vue b/client/components/Windows/SearchResults.vue index c29d51e9..bc0c153d 100644 --- a/client/components/Windows/SearchResults.vue +++ b/client/components/Windows/SearchResults.vue @@ -33,18 +33,19 @@
Searching… @@ -105,6 +106,7 @@ import type {ClientMessage} from "../../js/types"; import {useStore} from "../../js/store"; import {useRoute, useRouter} from "vue-router"; import {switchToChannel} from "../../js/router"; +import {SearchQuery} from "../../../server/plugins/messageStorage/types"; export default defineComponent({ name: "SearchResults", @@ -187,37 +189,44 @@ export default defineComponent({ const clearSearchState = () => { offset.value = 0; - store.commit("messageSearchInProgress", false); store.commit("messageSearchResults", null); + store.commit("messageSearchPendingQuery", null); }; const doSearch = () => { + if (!network.value || !channel.value) { + return; + } + clearSearchState(); // this is a new search, so we need to clear anything before that - socket.emit("search", { - networkUuid: network.value?.uuid, - channelName: channel.value?.name, + const query: SearchQuery = { + networkUuid: network.value.uuid, + channelName: channel.value.name, searchTerm: String(route.query.q || ""), offset: offset.value, - }); + }; + store.commit("messageSearchPendingQuery", query); + socket.emit("search", query); }; const onShowMoreClick = () => { - if (!chat.value) { + if (!chat.value || !network.value || !channel.value) { return; } offset.value += 100; - store.commit("messageSearchInProgress", true); oldScrollTop.value = chat.value.scrollTop; oldChatHeight.value = chat.value.scrollHeight; - socket.emit("search", { - networkUuid: network.value?.uuid, - channelName: channel.value?.name, + const query: SearchQuery = { + networkUuid: network.value.uuid, + channelName: channel.value.name, searchTerm: String(route.query.q || ""), offset: offset.value, - }); + }; + store.commit("messageSearchPendingQuery", query); + socket.emit("search", query); }; const jumpToBottom = async () => { diff --git a/client/js/socket-events/search.ts b/client/js/socket-events/search.ts index 7553e990..b83891e0 100644 --- a/client/js/socket-events/search.ts +++ b/client/js/socket-events/search.ts @@ -2,12 +2,27 @@ import socket from "../socket"; import {store} from "../store"; socket.on("search:results", (response) => { - store.commit("messageSearchInProgress", false); + const pendingQuery = store.state.messageSearchPendingQuery; + + if ( + !pendingQuery || + pendingQuery.channelName !== response.channelName || + pendingQuery.networkUuid !== response.networkUuid || + pendingQuery.offset !== response.offset || + pendingQuery.searchTerm !== response.searchTerm + ) { + // This is a response from a search that we are not interested in. + // The user may have entered a different search while one was still in flight. + // We can simply drop it on the floor. + return; + } + + store.commit("messageSearchPendingQuery", null); if (store.state.messageSearchResults) { store.commit("addMessageSearchResults", response); return; } - store.commit("messageSearchResults", response); + store.commit("messageSearchResults", {results: response.results}); }); diff --git a/client/js/store.ts b/client/js/store.ts index c7f724f9..d536389f 100644 --- a/client/js/store.ts +++ b/client/js/store.ts @@ -15,6 +15,7 @@ import type { import type {InjectionKey} from "vue"; import {SettingsState} from "./settings"; +import {SearchQuery} from "../../server/plugins/messageStorage/types"; const appName = document.title; @@ -85,7 +86,7 @@ export type State = { messageSearchResults: { results: ClientMessage[]; } | null; - messageSearchInProgress: boolean; + messageSearchPendingQuery: SearchQuery | null; searchEnabled: boolean; }; @@ -111,7 +112,7 @@ const state = () => versionDataExpired: false, serverHasSettings: false, messageSearchResults: null, - messageSearchInProgress: false, + messageSearchPendingQuery: null, searchEnabled: false, } as State); @@ -260,7 +261,7 @@ type Mutations = { versionStatus(state: State, payload: State["versionStatus"]): void; versionDataExpired(state: State, payload: State["versionDataExpired"]): void; serverHasSettings(state: State, value: State["serverHasSettings"]): void; - messageSearchInProgress(state: State, value: State["messageSearchInProgress"]): void; + messageSearchPendingQuery(state: State, value: State["messageSearchPendingQuery"]): void; messageSearchResults(state: State, value: State["messageSearchResults"]): void; addMessageSearchResults(state: State, value: NonNullable): void; }; @@ -338,8 +339,8 @@ const mutations: Mutations = { serverHasSettings(state, value) { state.serverHasSettings = value; }, - messageSearchInProgress(state, value) { - state.messageSearchInProgress = value; + messageSearchPendingQuery(state, value) { + state.messageSearchPendingQuery = value; }, messageSearchResults(state, value) { state.messageSearchResults = value; diff --git a/server/client.ts b/server/client.ts index cac35532..9612de19 100644 --- a/server/client.ts +++ b/server/client.ts @@ -17,7 +17,7 @@ import SqliteMessageStorage from "./plugins/messageStorage/sqlite"; import TextFileMessageStorage from "./plugins/messageStorage/text"; import Network, {IgnoreListItem, NetworkWithIrcFramework} from "./models/network"; import ClientManager from "./clientManager"; -import {MessageStorage, SearchQuery} from "./plugins/messageStorage/types"; +import {MessageStorage, SearchQuery, SearchResponse} from "./plugins/messageStorage/types"; type OrderItem = Chan["id"] | Network["uuid"]; type Order = OrderItem[]; @@ -618,15 +618,12 @@ class Client { } } - search(query: SearchQuery) { + async search(query: SearchQuery): Promise { if (!this.messageProvider?.isEnabled) { - return Promise.resolve({ + return { + ...query, results: [], - target: "", - networkUuid: "", - offset: 0, - searchTerm: query?.searchTerm, - }); + }; } return this.messageProvider.search(query); diff --git a/server/plugins/messageStorage/sqlite.ts b/server/plugins/messageStorage/sqlite.ts index fb7d9c3e..9c5ec985 100644 --- a/server/plugins/messageStorage/sqlite.ts +++ b/server/plugins/messageStorage/sqlite.ts @@ -259,15 +259,10 @@ class SqliteMessageStorage implements ISqliteMessageStorage { params.push(query.offset); const rows = await this.serialize_fetchall(select, ...params); - const response: SearchResponse = { - searchTerm: query.searchTerm, - target: query.channelName, - networkUuid: query.networkUuid, - offset: query.offset, + return { + ...query, results: parseSearchRowsToMessages(query.offset, rows).reverse(), }; - - return response; } canProvideMessages() { diff --git a/server/plugins/messageStorage/types.d.ts b/server/plugins/messageStorage/types.d.ts index 6a038822..68c6c271 100644 --- a/server/plugins/messageStorage/types.d.ts +++ b/server/plugins/messageStorage/types.d.ts @@ -29,12 +29,9 @@ export type SearchQuery = { offset: number; }; -export type SearchResponse = - | Omit & { - results: Message[]; - target: string; - offset: number; - }; +export type SearchResponse = SearchQuery & { + results: Message[]; +}; type SearchFunction = (query: SearchQuery) => Promise; diff --git a/server/server.ts b/server/server.ts index ee103997..a0a616a8 100644 --- a/server/server.ts +++ b/server/server.ts @@ -760,9 +760,8 @@ function initializeClient( }); socket.on("search", async (query) => { - await client.search(query).then((results) => { - socket.emit("search:results", results); - }); + const results = await client.search(query); + socket.emit("search:results", results); }); socket.on("mute:change", ({target, setMutedTo}) => { diff --git a/server/types/socket-events.d.ts b/server/types/socket-events.d.ts index ccfe1e37..3c7df130 100644 --- a/server/types/socket-events.d.ts +++ b/server/types/socket-events.d.ts @@ -107,7 +107,7 @@ interface ServerToClientEvents { token: string; }) => void; - "search:results": (response: {results: ClientMessage[]}) => void; + "search:results": (response: SearchResponse) => void; quit: (args: {network: string}) => void;