search: ignore searchResults if it isn't the active query
Prior to this, the search is still racy but one tends to notice this only when the DB is large or network is involved. The user can initiate a search, get bored, navigate to another chan issue a different search. Now however, the results of the first search come back in and hilarity ensues as we are now confused with the state. To avoid this, keep track of the last search done and any result that comes in that isn't equal to the active query is garbage and can be dropped.
This commit is contained in:
parent
8b1a4f72fa
commit
0ebc3a574c
@ -33,18 +33,19 @@
|
|||||||
<button
|
<button
|
||||||
ref="loadMoreButton"
|
ref="loadMoreButton"
|
||||||
:disabled="
|
:disabled="
|
||||||
store.state.messageSearchInProgress || !store.state.isConnected
|
!!store.state.messageSearchPendingQuery ||
|
||||||
|
!store.state.isConnected
|
||||||
"
|
"
|
||||||
class="btn"
|
class="btn"
|
||||||
@click="onShowMoreClick"
|
@click="onShowMoreClick"
|
||||||
>
|
>
|
||||||
<span v-if="store.state.messageSearchInProgress">Loading…</span>
|
<span v-if="store.state.messageSearchPendingQuery">Loading…</span>
|
||||||
<span v-else>Show older messages</span>
|
<span v-else>Show older messages</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="store.state.messageSearchInProgress && !offset"
|
v-if="store.state.messageSearchPendingQuery && !offset"
|
||||||
class="search-status"
|
class="search-status"
|
||||||
>
|
>
|
||||||
Searching…
|
Searching…
|
||||||
@ -105,6 +106,7 @@ import type {ClientMessage} from "../../js/types";
|
|||||||
import {useStore} from "../../js/store";
|
import {useStore} from "../../js/store";
|
||||||
import {useRoute, useRouter} from "vue-router";
|
import {useRoute, useRouter} from "vue-router";
|
||||||
import {switchToChannel} from "../../js/router";
|
import {switchToChannel} from "../../js/router";
|
||||||
|
import {SearchQuery} from "../../../server/plugins/messageStorage/types";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "SearchResults",
|
name: "SearchResults",
|
||||||
@ -187,37 +189,44 @@ export default defineComponent({
|
|||||||
|
|
||||||
const clearSearchState = () => {
|
const clearSearchState = () => {
|
||||||
offset.value = 0;
|
offset.value = 0;
|
||||||
store.commit("messageSearchInProgress", false);
|
|
||||||
store.commit("messageSearchResults", null);
|
store.commit("messageSearchResults", null);
|
||||||
|
store.commit("messageSearchPendingQuery", null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const doSearch = () => {
|
const doSearch = () => {
|
||||||
|
if (!network.value || !channel.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
clearSearchState(); // this is a new search, so we need to clear anything before that
|
clearSearchState(); // this is a new search, so we need to clear anything before that
|
||||||
socket.emit("search", {
|
const query: SearchQuery = {
|
||||||
networkUuid: network.value?.uuid,
|
networkUuid: network.value.uuid,
|
||||||
channelName: channel.value?.name,
|
channelName: channel.value.name,
|
||||||
searchTerm: String(route.query.q || ""),
|
searchTerm: String(route.query.q || ""),
|
||||||
offset: offset.value,
|
offset: offset.value,
|
||||||
});
|
};
|
||||||
|
store.commit("messageSearchPendingQuery", query);
|
||||||
|
socket.emit("search", query);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onShowMoreClick = () => {
|
const onShowMoreClick = () => {
|
||||||
if (!chat.value) {
|
if (!chat.value || !network.value || !channel.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
offset.value += 100;
|
offset.value += 100;
|
||||||
store.commit("messageSearchInProgress", true);
|
|
||||||
|
|
||||||
oldScrollTop.value = chat.value.scrollTop;
|
oldScrollTop.value = chat.value.scrollTop;
|
||||||
oldChatHeight.value = chat.value.scrollHeight;
|
oldChatHeight.value = chat.value.scrollHeight;
|
||||||
|
|
||||||
socket.emit("search", {
|
const query: SearchQuery = {
|
||||||
networkUuid: network.value?.uuid,
|
networkUuid: network.value.uuid,
|
||||||
channelName: channel.value?.name,
|
channelName: channel.value.name,
|
||||||
searchTerm: String(route.query.q || ""),
|
searchTerm: String(route.query.q || ""),
|
||||||
offset: offset.value,
|
offset: offset.value,
|
||||||
});
|
};
|
||||||
|
store.commit("messageSearchPendingQuery", query);
|
||||||
|
socket.emit("search", query);
|
||||||
};
|
};
|
||||||
|
|
||||||
const jumpToBottom = async () => {
|
const jumpToBottom = async () => {
|
||||||
|
@ -2,12 +2,27 @@ import socket from "../socket";
|
|||||||
import {store} from "../store";
|
import {store} from "../store";
|
||||||
|
|
||||||
socket.on("search:results", (response) => {
|
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) {
|
if (store.state.messageSearchResults) {
|
||||||
store.commit("addMessageSearchResults", response);
|
store.commit("addMessageSearchResults", response);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
store.commit("messageSearchResults", response);
|
store.commit("messageSearchResults", {results: response.results});
|
||||||
});
|
});
|
||||||
|
@ -15,6 +15,7 @@ import type {
|
|||||||
import type {InjectionKey} from "vue";
|
import type {InjectionKey} from "vue";
|
||||||
|
|
||||||
import {SettingsState} from "./settings";
|
import {SettingsState} from "./settings";
|
||||||
|
import {SearchQuery} from "../../server/plugins/messageStorage/types";
|
||||||
|
|
||||||
const appName = document.title;
|
const appName = document.title;
|
||||||
|
|
||||||
@ -85,7 +86,7 @@ export type State = {
|
|||||||
messageSearchResults: {
|
messageSearchResults: {
|
||||||
results: ClientMessage[];
|
results: ClientMessage[];
|
||||||
} | null;
|
} | null;
|
||||||
messageSearchInProgress: boolean;
|
messageSearchPendingQuery: SearchQuery | null;
|
||||||
searchEnabled: boolean;
|
searchEnabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -111,7 +112,7 @@ const state = () =>
|
|||||||
versionDataExpired: false,
|
versionDataExpired: false,
|
||||||
serverHasSettings: false,
|
serverHasSettings: false,
|
||||||
messageSearchResults: null,
|
messageSearchResults: null,
|
||||||
messageSearchInProgress: false,
|
messageSearchPendingQuery: null,
|
||||||
searchEnabled: false,
|
searchEnabled: false,
|
||||||
} as State);
|
} as State);
|
||||||
|
|
||||||
@ -260,7 +261,7 @@ type Mutations = {
|
|||||||
versionStatus(state: State, payload: State["versionStatus"]): void;
|
versionStatus(state: State, payload: State["versionStatus"]): void;
|
||||||
versionDataExpired(state: State, payload: State["versionDataExpired"]): void;
|
versionDataExpired(state: State, payload: State["versionDataExpired"]): void;
|
||||||
serverHasSettings(state: State, value: State["serverHasSettings"]): 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;
|
messageSearchResults(state: State, value: State["messageSearchResults"]): void;
|
||||||
addMessageSearchResults(state: State, value: NonNullable<State["messageSearchResults"]>): void;
|
addMessageSearchResults(state: State, value: NonNullable<State["messageSearchResults"]>): void;
|
||||||
};
|
};
|
||||||
@ -338,8 +339,8 @@ const mutations: Mutations = {
|
|||||||
serverHasSettings(state, value) {
|
serverHasSettings(state, value) {
|
||||||
state.serverHasSettings = value;
|
state.serverHasSettings = value;
|
||||||
},
|
},
|
||||||
messageSearchInProgress(state, value) {
|
messageSearchPendingQuery(state, value) {
|
||||||
state.messageSearchInProgress = value;
|
state.messageSearchPendingQuery = value;
|
||||||
},
|
},
|
||||||
messageSearchResults(state, value) {
|
messageSearchResults(state, value) {
|
||||||
state.messageSearchResults = value;
|
state.messageSearchResults = value;
|
||||||
|
@ -17,7 +17,7 @@ import SqliteMessageStorage from "./plugins/messageStorage/sqlite";
|
|||||||
import TextFileMessageStorage from "./plugins/messageStorage/text";
|
import TextFileMessageStorage from "./plugins/messageStorage/text";
|
||||||
import Network, {IgnoreListItem, NetworkWithIrcFramework} from "./models/network";
|
import Network, {IgnoreListItem, NetworkWithIrcFramework} from "./models/network";
|
||||||
import ClientManager from "./clientManager";
|
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 OrderItem = Chan["id"] | Network["uuid"];
|
||||||
type Order = OrderItem[];
|
type Order = OrderItem[];
|
||||||
@ -618,15 +618,12 @@ class Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
search(query: SearchQuery) {
|
async search(query: SearchQuery): Promise<SearchResponse> {
|
||||||
if (!this.messageProvider?.isEnabled) {
|
if (!this.messageProvider?.isEnabled) {
|
||||||
return Promise.resolve({
|
return {
|
||||||
|
...query,
|
||||||
results: [],
|
results: [],
|
||||||
target: "",
|
};
|
||||||
networkUuid: "",
|
|
||||||
offset: 0,
|
|
||||||
searchTerm: query?.searchTerm,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.messageProvider.search(query);
|
return this.messageProvider.search(query);
|
||||||
|
@ -259,15 +259,10 @@ class SqliteMessageStorage implements ISqliteMessageStorage {
|
|||||||
params.push(query.offset);
|
params.push(query.offset);
|
||||||
|
|
||||||
const rows = await this.serialize_fetchall(select, ...params);
|
const rows = await this.serialize_fetchall(select, ...params);
|
||||||
const response: SearchResponse = {
|
return {
|
||||||
searchTerm: query.searchTerm,
|
...query,
|
||||||
target: query.channelName,
|
|
||||||
networkUuid: query.networkUuid,
|
|
||||||
offset: query.offset,
|
|
||||||
results: parseSearchRowsToMessages(query.offset, rows).reverse(),
|
results: parseSearchRowsToMessages(query.offset, rows).reverse(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
canProvideMessages() {
|
canProvideMessages() {
|
||||||
|
5
server/plugins/messageStorage/types.d.ts
vendored
5
server/plugins/messageStorage/types.d.ts
vendored
@ -29,11 +29,8 @@ export type SearchQuery = {
|
|||||||
offset: number;
|
offset: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SearchResponse =
|
export type SearchResponse = SearchQuery & {
|
||||||
| Omit<SearchQuery, "channelName" | "offset"> & {
|
|
||||||
results: Message[];
|
results: Message[];
|
||||||
target: string;
|
|
||||||
offset: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type SearchFunction = (query: SearchQuery) => Promise<SearchResponse>;
|
type SearchFunction = (query: SearchQuery) => Promise<SearchResponse>;
|
||||||
|
@ -760,10 +760,9 @@ function initializeClient(
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on("search", async (query) => {
|
socket.on("search", async (query) => {
|
||||||
await client.search(query).then((results) => {
|
const results = await client.search(query);
|
||||||
socket.emit("search:results", results);
|
socket.emit("search:results", results);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("mute:change", ({target, setMutedTo}) => {
|
socket.on("mute:change", ({target, setMutedTo}) => {
|
||||||
const networkAndChan = client.find(target);
|
const networkAndChan = client.find(target);
|
||||||
|
2
server/types/socket-events.d.ts
vendored
2
server/types/socket-events.d.ts
vendored
@ -107,7 +107,7 @@ interface ServerToClientEvents {
|
|||||||
token: string;
|
token: string;
|
||||||
}) => void;
|
}) => void;
|
||||||
|
|
||||||
"search:results": (response: {results: ClientMessage[]}) => void;
|
"search:results": (response: SearchResponse) => void;
|
||||||
|
|
||||||
quit: (args: {network: string}) => void;
|
quit: (args: {network: string}) => void;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user