mirror of
https://github.com/MrUnknownDE/VRCX.git
synced 2026-05-05 22:36:05 +02:00
refactor global search
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
|||||||
avatarRequest,
|
avatarRequest,
|
||||||
favoriteRequest
|
favoriteRequest
|
||||||
} from '../../../api';
|
} from '../../../api';
|
||||||
|
import { removeAvatarFromCache } from '../../../coordinators/avatarCoordinator';
|
||||||
import {
|
import {
|
||||||
copyToClipboard,
|
copyToClipboard,
|
||||||
openExternalLink,
|
openExternalLink,
|
||||||
@@ -378,7 +379,7 @@ export function useAvatarDialogCommands(
|
|||||||
.deleteAvatar({ avatarId: id })
|
.deleteAvatar({ avatarId: id })
|
||||||
.then((args) => {
|
.then((args) => {
|
||||||
const { json } = args;
|
const { json } = args;
|
||||||
cachedAvatars.delete(json._id);
|
removeAvatarFromCache(json._id);
|
||||||
if (userDialog.value.id === json.authorId) {
|
if (userDialog.value.id === json.authorId) {
|
||||||
const map = new Map();
|
const map = new Map();
|
||||||
for (const ref of cachedAvatars.values()) {
|
for (const ref of cachedAvatars.values()) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
readFileAsBase64,
|
readFileAsBase64,
|
||||||
withUploadTimeout
|
withUploadTimeout
|
||||||
} from '../../../shared/utils/imageUpload';
|
} from '../../../shared/utils/imageUpload';
|
||||||
|
import { removeWorldFromCache } from '../../../coordinators/worldCoordinator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Composable for WorldDialog commands, prompt functions, and image upload.
|
* Composable for WorldDialog commands, prompt functions, and image upload.
|
||||||
@@ -534,7 +535,7 @@ export function useWorldDialogCommands(
|
|||||||
handler: (id) => {
|
handler: (id) => {
|
||||||
worldRequest.deleteWorld({ worldId: id }).then((args) => {
|
worldRequest.deleteWorld({ worldId: id }).then((args) => {
|
||||||
const { json } = args;
|
const { json } = args;
|
||||||
cachedWorlds.delete(json.id);
|
removeWorldFromCache(json.id);
|
||||||
if (worldDialog.value.ref.authorId === json.authorId) {
|
if (worldDialog.value.ref.authorId === json.authorId) {
|
||||||
const map = new Map();
|
const map = new Map();
|
||||||
for (const ref of cachedWorlds.values()) {
|
for (const ref of cachedWorlds.values()) {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { useAvatarProviderStore } from '../stores/avatarProvider';
|
|||||||
import { useAvatarStore } from '../stores/avatar';
|
import { useAvatarStore } from '../stores/avatar';
|
||||||
import { useFavoriteStore } from '../stores/favorite';
|
import { useFavoriteStore } from '../stores/favorite';
|
||||||
import { useModalStore } from '../stores/modal';
|
import { useModalStore } from '../stores/modal';
|
||||||
|
import { syncAvatarSearchIndex, removeAvatarSearchIndex } from './searchIndexCoordinator';
|
||||||
import { useUiStore } from '../stores/ui';
|
import { useUiStore } from '../stores/ui';
|
||||||
import { useUserStore } from '../stores/user';
|
import { useUserStore } from '../stores/user';
|
||||||
import { useVRCXUpdaterStore } from '../stores/vrcxUpdater';
|
import { useVRCXUpdaterStore } from '../stores/vrcxUpdater';
|
||||||
@@ -67,6 +68,7 @@ export function applyAvatar(json) {
|
|||||||
database.addAvatarToCache(avatarRef);
|
database.addAvatarToCache(avatarRef);
|
||||||
}
|
}
|
||||||
patchAvatarFromEvent(ref);
|
patchAvatarFromEvent(ref);
|
||||||
|
syncAvatarSearchIndex(ref);
|
||||||
return ref;
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -632,3 +634,12 @@ export async function preloadOwnAvatars() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
export function removeAvatarFromCache(id) {
|
||||||
|
const avatarStore = useAvatarStore();
|
||||||
|
avatarStore.cachedAvatars.delete(id);
|
||||||
|
removeAvatarSearchIndex(id);
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useFriendStore } from '../stores/friend';
|
|||||||
import { useGeneralSettingsStore } from '../stores/settings/general';
|
import { useGeneralSettingsStore } from '../stores/settings/general';
|
||||||
import { useUserStore } from '../stores/user';
|
import { useUserStore } from '../stores/user';
|
||||||
import { useWorldStore } from '../stores/world';
|
import { useWorldStore } from '../stores/world';
|
||||||
|
import { rebuildFavoriteSearchIndex } from './searchIndexCoordinator';
|
||||||
import { applyWorld } from './worldCoordinator';
|
import { applyWorld } from './worldCoordinator';
|
||||||
import { runUpdateFriendFlow } from './friendPresenceCoordinator';
|
import { runUpdateFriendFlow } from './friendPresenceCoordinator';
|
||||||
import { avatarRequest, favoriteRequest, queryRequest } from '../api';
|
import { avatarRequest, favoriteRequest, queryRequest } from '../api';
|
||||||
@@ -134,6 +135,7 @@ export function handleFavoriteAtDelete(ref) {
|
|||||||
avatarStore.setAvatarDialogIsFavorite(false);
|
avatarStore.setAvatarDialogIsFavorite(false);
|
||||||
}
|
}
|
||||||
favoriteStore.countFavoriteGroups();
|
favoriteStore.countFavoriteGroups();
|
||||||
|
rebuildFavoriteSearchIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -374,6 +376,7 @@ export async function applyFavorite(type, objectId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
rebuildFavoriteSearchIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Refresh flows ---
|
// --- Refresh flows ---
|
||||||
@@ -1284,5 +1287,8 @@ export function onLoginStateChanged(isLoggedIn) {
|
|||||||
friendStore.localFavoriteFriends.clear();
|
friendStore.localFavoriteFriends.clear();
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
initFavorites();
|
initFavorites();
|
||||||
|
} else {
|
||||||
|
rebuildFavoriteSearchIndex();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { database } from '../services/database';
|
|||||||
import { useFeedStore } from '../stores/feed';
|
import { useFeedStore } from '../stores/feed';
|
||||||
import { useFriendStore } from '../stores/friend';
|
import { useFriendStore } from '../stores/friend';
|
||||||
import { useNotificationStore } from '../stores/notification';
|
import { useNotificationStore } from '../stores/notification';
|
||||||
|
import { syncFriendSearchIndex } from './searchIndexCoordinator';
|
||||||
import { useSharedFeedStore } from '../stores/sharedFeed';
|
import { useSharedFeedStore } from '../stores/sharedFeed';
|
||||||
import { useUserStore } from '../stores/user';
|
import { useUserStore } from '../stores/user';
|
||||||
import { userRequest } from '../api';
|
import { userRequest } from '../api';
|
||||||
@@ -117,6 +118,7 @@ export async function runUpdateFriendDelayedCheckFlow(
|
|||||||
}
|
}
|
||||||
if (ref?.displayName) {
|
if (ref?.displayName) {
|
||||||
ctx.name = ref.displayName;
|
ctx.name = ref.displayName;
|
||||||
|
syncFriendSearchIndex(ctx);
|
||||||
}
|
}
|
||||||
ctx.isVIP = isVIP;
|
ctx.isVIP = isVIP;
|
||||||
}
|
}
|
||||||
@@ -205,6 +207,7 @@ export async function runUpdateFriendFlow(
|
|||||||
}
|
}
|
||||||
if (typeof ref !== 'undefined' && ctx.name !== ref.displayName) {
|
if (typeof ref !== 'undefined' && ctx.name !== ref.displayName) {
|
||||||
ctx.name = ref.displayName;
|
ctx.name = ref.displayName;
|
||||||
|
syncFriendSearchIndex(ctx);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -216,6 +219,7 @@ export async function runUpdateFriendFlow(
|
|||||||
ctx.isVIP = isVIP;
|
ctx.isVIP = isVIP;
|
||||||
if (typeof ref !== 'undefined') {
|
if (typeof ref !== 'undefined') {
|
||||||
ctx.name = ref.displayName;
|
ctx.name = ref.displayName;
|
||||||
|
syncFriendSearchIndex(ctx);
|
||||||
}
|
}
|
||||||
if (!watchState.isFriendsLoaded) {
|
if (!watchState.isFriendsLoaded) {
|
||||||
await runUpdateFriendDelayedCheckFlow(
|
await runUpdateFriendDelayedCheckFlow(
|
||||||
@@ -250,6 +254,7 @@ export async function runUpdateFriendFlow(
|
|||||||
ctx.isVIP = isVIP;
|
ctx.isVIP = isVIP;
|
||||||
if (typeof ref !== 'undefined') {
|
if (typeof ref !== 'undefined') {
|
||||||
ctx.name = ref.displayName;
|
ctx.name = ref.displayName;
|
||||||
|
syncFriendSearchIndex(ctx);
|
||||||
await runUpdateFriendDelayedCheckFlow(
|
await runUpdateFriendDelayedCheckFlow(
|
||||||
ctx,
|
ctx,
|
||||||
ctx.ref.state,
|
ctx.ref.state,
|
||||||
@@ -304,3 +309,4 @@ export async function runPendingOfflineTickFlow({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ import { useNotificationStore } from '../stores/notification';
|
|||||||
import { useSharedFeedStore } from '../stores/sharedFeed';
|
import { useSharedFeedStore } from '../stores/sharedFeed';
|
||||||
import { useUiStore } from '../stores/ui';
|
import { useUiStore } from '../stores/ui';
|
||||||
import { useUserStore } from '../stores/user';
|
import { useUserStore } from '../stores/user';
|
||||||
|
import {
|
||||||
|
removeFriendSearchIndex,
|
||||||
|
syncFriendSearchIndex
|
||||||
|
} from './searchIndexCoordinator';
|
||||||
import { watchState } from '../services/watchState';
|
import { watchState } from '../services/watchState';
|
||||||
|
|
||||||
import configRepository from '../services/config';
|
import configRepository from '../services/config';
|
||||||
@@ -44,6 +48,7 @@ export function handleFriendDelete(args) {
|
|||||||
D.isFriend = false;
|
D.isFriend = false;
|
||||||
runDeleteFriendshipFlow(args.params.userId);
|
runDeleteFriendshipFlow(args.params.userId);
|
||||||
friendStore.deleteFriend(args.params.userId);
|
friendStore.deleteFriend(args.params.userId);
|
||||||
|
removeFriendSearchIndex(args.params.userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,6 +58,10 @@ export function handleFriendAdd(args) {
|
|||||||
const friendStore = useFriendStore();
|
const friendStore = useFriendStore();
|
||||||
addFriendship(args.params.userId);
|
addFriendship(args.params.userId);
|
||||||
friendStore.addFriend(args.params.userId);
|
friendStore.addFriend(args.params.userId);
|
||||||
|
const ctx = friendStore.friends.get(args.params.userId);
|
||||||
|
if (ctx) {
|
||||||
|
syncFriendSearchIndex(ctx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -136,6 +145,10 @@ export function addFriendship(id) {
|
|||||||
state.friendNumber
|
state.friendNumber
|
||||||
);
|
);
|
||||||
friendStore.addFriend(id, ref.state);
|
friendStore.addFriend(id, ref.state);
|
||||||
|
const friendCtx = friendStore.friends.get(id);
|
||||||
|
if (friendCtx) {
|
||||||
|
syncFriendSearchIndex(friendCtx);
|
||||||
|
}
|
||||||
const friendLogHistory = {
|
const friendLogHistory = {
|
||||||
created_at: new Date().toJSON(),
|
created_at: new Date().toJSON(),
|
||||||
type: 'Friend',
|
type: 'Friend',
|
||||||
@@ -316,7 +329,16 @@ export function updateUserCurrentStatus(ref) {
|
|||||||
const appearanceSettingsStore = useAppearanceSettingsStore();
|
const appearanceSettingsStore = useAppearanceSettingsStore();
|
||||||
|
|
||||||
if (watchState.isFriendsLoaded) {
|
if (watchState.isFriendsLoaded) {
|
||||||
friendStore.refreshFriendsStatus(ref);
|
const { added, removed } = friendStore.refreshFriendsStatus(ref);
|
||||||
|
for (const id of added) {
|
||||||
|
const ctx = friendStore.friends.get(id);
|
||||||
|
if (ctx) {
|
||||||
|
syncFriendSearchIndex(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const id of removed) {
|
||||||
|
removeFriendSearchIndex(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
friendStore.updateOnlineFriendCounter();
|
friendStore.updateOnlineFriendCounter();
|
||||||
|
|
||||||
@@ -383,6 +405,7 @@ export function runDeleteFriendshipFlow(
|
|||||||
uiStore.notifyMenu('friend-log');
|
uiStore.notifyMenu('friend-log');
|
||||||
}
|
}
|
||||||
friendStore.deleteFriend(id);
|
friendStore.deleteFriend(id);
|
||||||
|
removeFriendSearchIndex(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { toast } from 'vue-sonner';
|
|||||||
|
|
||||||
import { AppDebug } from '../services/appConfig';
|
import { AppDebug } from '../services/appConfig';
|
||||||
import { migrateMemos } from './memoCoordinator';
|
import { migrateMemos } from './memoCoordinator';
|
||||||
|
import { syncFriendSearchIndex } from './searchIndexCoordinator';
|
||||||
import { reconnectWebSocket } from '../services/websocket';
|
import { reconnectWebSocket } from '../services/websocket';
|
||||||
import { useAuthStore } from '../stores/auth';
|
import { useAuthStore } from '../stores/auth';
|
||||||
import { useFriendStore } from '../stores/friend';
|
import { useFriendStore } from '../stores/friend';
|
||||||
@@ -56,6 +57,11 @@ export async function runInitFriendsListFlow(t) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bulk sync friends to search index after initial load
|
||||||
|
for (const ctx of friendStore.friends.values()) {
|
||||||
|
syncFriendSearchIndex(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
friendStore.tryApplyFriendOrder(); // once again
|
friendStore.tryApplyFriendOrder(); // once again
|
||||||
friendStore.getAllUserStats(); // joinCount, lastSeen, timeSpent
|
friendStore.getAllUserStats(); // joinCount, lastSeen, timeSpent
|
||||||
|
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ import { useNotificationStore } from '../stores/notification';
|
|||||||
import { useUiStore } from '../stores/ui';
|
import { useUiStore } from '../stores/ui';
|
||||||
import { useUserStore } from '../stores/user';
|
import { useUserStore } from '../stores/user';
|
||||||
import { useGroupStore } from '../stores/group';
|
import { useGroupStore } from '../stores/group';
|
||||||
|
import { syncGroupSearchIndex, removeGroupSearchIndex, clearGroupSearchIndex } from './searchIndexCoordinator';
|
||||||
import { watchState } from '../services/watchState';
|
import { watchState } from '../services/watchState';
|
||||||
|
|
||||||
import configRepository from '../services/config';
|
import configRepository from '../services/config';
|
||||||
|
|
||||||
import * as workerTimers from 'worker-timers';
|
import * as workerTimers from 'worker-timers';
|
||||||
|
|
||||||
// ─── Internal helpers (not exported) ─────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ref
|
* @param ref
|
||||||
@@ -48,7 +48,6 @@ function applyGroupLanguage(ref) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Core entity application ─────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -132,6 +131,9 @@ export function applyGroup(json) {
|
|||||||
D.ref = ref;
|
D.ref = ref;
|
||||||
}
|
}
|
||||||
patchGroupFromEvent(ref);
|
patchGroupFromEvent(ref);
|
||||||
|
if (groupStore.currentUserGroups.has(ref.id)) {
|
||||||
|
syncGroupSearchIndex(ref);
|
||||||
|
}
|
||||||
return ref;
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +180,6 @@ export function applyGroupMember(json) {
|
|||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Group change notifications ──────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -274,7 +275,6 @@ function groupRoleChange(ref, oldRoles, newRoles, oldRoleIds, newRoleIds) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Dialog flows ────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -461,7 +461,6 @@ export function getGroupDialogGroup(groupId, existingRef) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Group lifecycle flows ───────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -508,6 +507,7 @@ export function onGroupJoined(groupId) {
|
|||||||
name: '',
|
name: '',
|
||||||
iconUrl: ''
|
iconUrl: ''
|
||||||
});
|
});
|
||||||
|
syncGroupSearchIndex({ id: groupId, name: '', ownerId: '', iconUrl: '' });
|
||||||
groupRequest.getGroup({ groupId, includeRoles: true }).then((args) => {
|
groupRequest.getGroup({ groupId, includeRoles: true }).then((args) => {
|
||||||
applyGroup(args.json);
|
applyGroup(args.json);
|
||||||
saveCurrentUserGroups();
|
saveCurrentUserGroups();
|
||||||
@@ -539,6 +539,7 @@ export async function onGroupLeft(groupId) {
|
|||||||
}
|
}
|
||||||
if (groupStore.currentUserGroups.has(groupId)) {
|
if (groupStore.currentUserGroups.has(groupId)) {
|
||||||
groupStore.currentUserGroups.delete(groupId);
|
groupStore.currentUserGroups.delete(groupId);
|
||||||
|
removeGroupSearchIndex(groupId);
|
||||||
groupChange(ref, 'Left group');
|
groupChange(ref, 'Left group');
|
||||||
|
|
||||||
// delay to wait for json to be assigned to ref
|
// delay to wait for json to be assigned to ref
|
||||||
@@ -546,7 +547,6 @@ export async function onGroupLeft(groupId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── User group management ───────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -589,6 +589,7 @@ export async function loadCurrentUserGroups(userId, groups) {
|
|||||||
);
|
);
|
||||||
groupStore.cachedGroups.clear();
|
groupStore.cachedGroups.clear();
|
||||||
groupStore.currentUserGroups.clear();
|
groupStore.currentUserGroups.clear();
|
||||||
|
clearGroupSearchIndex();
|
||||||
for (const group of savedGroups) {
|
for (const group of savedGroups) {
|
||||||
const json = {
|
const json = {
|
||||||
id: group.id,
|
id: group.id,
|
||||||
@@ -602,6 +603,7 @@ export async function loadCurrentUserGroups(userId, groups) {
|
|||||||
};
|
};
|
||||||
const ref = applyGroup(json);
|
const ref = applyGroup(json);
|
||||||
groupStore.currentUserGroups.set(group.id, ref);
|
groupStore.currentUserGroups.set(group.id, ref);
|
||||||
|
syncGroupSearchIndex(ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groups) {
|
if (groups) {
|
||||||
@@ -620,6 +622,7 @@ export async function loadCurrentUserGroups(userId, groups) {
|
|||||||
});
|
});
|
||||||
const ref = applyGroup(args.json);
|
const ref = applyGroup(args.json);
|
||||||
groupStore.currentUserGroups.set(groupId, ref);
|
groupStore.currentUserGroups.set(groupId, ref);
|
||||||
|
syncGroupSearchIndex(ref);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
@@ -643,10 +646,12 @@ export async function getCurrentUserGroups() {
|
|||||||
});
|
});
|
||||||
handleGroupList(args);
|
handleGroupList(args);
|
||||||
groupStore.currentUserGroups.clear();
|
groupStore.currentUserGroups.clear();
|
||||||
|
clearGroupSearchIndex();
|
||||||
for (const group of args.json) {
|
for (const group of args.json) {
|
||||||
const ref = applyGroup(group);
|
const ref = applyGroup(group);
|
||||||
if (!groupStore.currentUserGroups.has(group.id)) {
|
if (!groupStore.currentUserGroups.has(group.id)) {
|
||||||
groupStore.currentUserGroups.set(group.id, ref);
|
groupStore.currentUserGroups.set(group.id, ref);
|
||||||
|
syncGroupSearchIndex(ref);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const args1 = await groupRequest.getGroupPermissions({
|
const args1 = await groupRequest.getGroupPermissions({
|
||||||
@@ -704,7 +709,6 @@ export async function updateInGameGroupOrder() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Group actions ───────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import { watch } from 'vue';
|
||||||
|
|
||||||
|
import { useSearchIndexStore } from '../stores/searchIndex';
|
||||||
|
import { watchState } from '../services/watchState';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} ctx
|
||||||
|
*/
|
||||||
|
export function syncFriendSearchIndex(ctx) {
|
||||||
|
useSearchIndexStore().syncFriend(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
export function removeFriendSearchIndex(id) {
|
||||||
|
useSearchIndexStore().removeFriend(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearFriendSearchIndex() {
|
||||||
|
useSearchIndexStore().clearFriends();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} ref
|
||||||
|
*/
|
||||||
|
export function syncAvatarSearchIndex(ref) {
|
||||||
|
useSearchIndexStore().upsertAvatar(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
export function removeAvatarSearchIndex(id) {
|
||||||
|
useSearchIndexStore().removeAvatar(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearAvatarSearchIndex() {
|
||||||
|
useSearchIndexStore().clearAvatars();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} ref
|
||||||
|
*/
|
||||||
|
export function syncWorldSearchIndex(ref) {
|
||||||
|
useSearchIndexStore().upsertWorld(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
export function removeWorldSearchIndex(id) {
|
||||||
|
useSearchIndexStore().removeWorld(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearWorldSearchIndex() {
|
||||||
|
useSearchIndexStore().clearWorlds();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} ref
|
||||||
|
*/
|
||||||
|
export function syncGroupSearchIndex(ref) {
|
||||||
|
useSearchIndexStore().upsertGroup(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
export function removeGroupSearchIndex(id) {
|
||||||
|
useSearchIndexStore().removeGroup(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearGroupSearchIndex() {
|
||||||
|
useSearchIndexStore().clearGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function rebuildFavoriteSearchIndex() {
|
||||||
|
useSearchIndexStore().rebuildFavoritesFromStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearFavoriteSearchIndex() {
|
||||||
|
useSearchIndexStore().clearFavorites();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a single login-state watcher that clears the entire search index
|
||||||
|
* on every login/logout transition, so individual stores don't need to.
|
||||||
|
*/
|
||||||
|
export function resetSearchIndexOnLogin() {
|
||||||
|
const searchIndexStore = useSearchIndexStore();
|
||||||
|
watch(
|
||||||
|
() => watchState.isLoggedIn,
|
||||||
|
() => {
|
||||||
|
searchIndexStore.clearFriends();
|
||||||
|
searchIndexStore.clearAvatars();
|
||||||
|
searchIndexStore.clearWorlds();
|
||||||
|
searchIndexStore.clearGroups();
|
||||||
|
searchIndexStore.clearFavorites();
|
||||||
|
},
|
||||||
|
{ flush: 'sync' }
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -53,6 +53,8 @@ import { useModerationStore } from '../stores/moderation';
|
|||||||
import { useNotificationStore } from '../stores/notification';
|
import { useNotificationStore } from '../stores/notification';
|
||||||
import { usePhotonStore } from '../stores/photon';
|
import { usePhotonStore } from '../stores/photon';
|
||||||
import { useSearchStore } from '../stores/search';
|
import { useSearchStore } from '../stores/search';
|
||||||
|
import { syncFriendSearchIndex } from './searchIndexCoordinator';
|
||||||
|
import { removeAvatarFromCache } from './avatarCoordinator';
|
||||||
import { useSharedFeedStore } from '../stores/sharedFeed';
|
import { useSharedFeedStore } from '../stores/sharedFeed';
|
||||||
import { useUiStore } from '../stores/ui';
|
import { useUiStore } from '../stores/ui';
|
||||||
import { useUserStore } from '../stores/user';
|
import { useUserStore } from '../stores/user';
|
||||||
@@ -181,6 +183,7 @@ export function applyUser(json) {
|
|||||||
if (friendCtx) {
|
if (friendCtx) {
|
||||||
friendCtx.ref = ref;
|
friendCtx.ref = ref;
|
||||||
friendCtx.name = ref.displayName;
|
friendCtx.name = ref.displayName;
|
||||||
|
syncFriendSearchIndex(friendCtx);
|
||||||
}
|
}
|
||||||
if (ref.id === currentUser.id) {
|
if (ref.id === currentUser.id) {
|
||||||
if (ref.status) {
|
if (ref.status) {
|
||||||
@@ -306,6 +309,7 @@ export function showUserDialog(userId) {
|
|||||||
} else {
|
} else {
|
||||||
ref.$nickName = '';
|
ref.$nickName = '';
|
||||||
}
|
}
|
||||||
|
syncFriendSearchIndex(ref);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -583,7 +587,7 @@ export async function refreshUserDialogAvatars(fileId) {
|
|||||||
};
|
};
|
||||||
for (const ref of avatarStore.cachedAvatars.values()) {
|
for (const ref of avatarStore.cachedAvatars.values()) {
|
||||||
if (ref.authorId === D.id) {
|
if (ref.authorId === D.id) {
|
||||||
avatarStore.cachedAvatars.delete(ref.id);
|
removeAvatarFromCache(ref.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const map = new Map();
|
const map = new Map();
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { removeAvatarFromCache } from './avatarCoordinator';
|
||||||
|
import { removeWorldFromCache } from './worldCoordinator';
|
||||||
import { useAvatarStore } from '../stores/avatar';
|
import { useAvatarStore } from '../stores/avatar';
|
||||||
import { useFavoriteStore } from '../stores/favorite';
|
import { useFavoriteStore } from '../stores/favorite';
|
||||||
import { useFriendStore } from '../stores/friend';
|
import { useFriendStore } from '../stores/friend';
|
||||||
@@ -9,6 +11,7 @@ import { useUserStore } from '../stores/user';
|
|||||||
import { useWorldStore } from '../stores/world';
|
import { useWorldStore } from '../stores/world';
|
||||||
import { failedGetRequests } from '../services/request';
|
import { failedGetRequests } from '../services/request';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears caches across multiple stores while preserving data that is
|
* Clears caches across multiple stores while preserving data that is
|
||||||
* still needed (friends, current user, favorites, active instances).
|
* still needed (friends, current user, favorites, active instances).
|
||||||
@@ -41,7 +44,7 @@ export function clearVRCXCache() {
|
|||||||
ref.authorId !== userStore.currentUser.id &&
|
ref.authorId !== userStore.currentUser.id &&
|
||||||
!favoriteStore.localWorldFavoritesList.includes(id)
|
!favoriteStore.localWorldFavoritesList.includes(id)
|
||||||
) {
|
) {
|
||||||
worldStore.cachedWorlds.delete(id);
|
removeWorldFromCache(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
avatarStore.cachedAvatars.forEach((ref, id) => {
|
avatarStore.cachedAvatars.forEach((ref, id) => {
|
||||||
@@ -51,7 +54,7 @@ export function clearVRCXCache() {
|
|||||||
!favoriteStore.localAvatarFavoritesList.includes(id) &&
|
!favoriteStore.localAvatarFavoritesList.includes(id) &&
|
||||||
!avatarStore.avatarHistory.includes(id)
|
!avatarStore.avatarHistory.includes(id)
|
||||||
) {
|
) {
|
||||||
avatarStore.cachedAvatars.delete(id);
|
removeAvatarFromCache(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
groupStore.cachedGroups.forEach((ref, id) => {
|
groupStore.cachedGroups.forEach((ref, id) => {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { applyFavorite } from './favoriteCoordinator';
|
|||||||
import { useFavoriteStore } from '../stores/favorite';
|
import { useFavoriteStore } from '../stores/favorite';
|
||||||
import { useInstanceStore } from '../stores/instance';
|
import { useInstanceStore } from '../stores/instance';
|
||||||
import { useLocationStore } from '../stores/location';
|
import { useLocationStore } from '../stores/location';
|
||||||
|
import { syncWorldSearchIndex, removeWorldSearchIndex } from './searchIndexCoordinator';
|
||||||
import { useUiStore } from '../stores/ui';
|
import { useUiStore } from '../stores/ui';
|
||||||
import { useUserStore } from '../stores/user';
|
import { useUserStore } from '../stores/user';
|
||||||
import { useWorldStore } from '../stores/world';
|
import { useWorldStore } from '../stores/world';
|
||||||
@@ -221,6 +222,7 @@ export function applyWorld(json) {
|
|||||||
database.addWorldToCache(ref);
|
database.addWorldToCache(ref);
|
||||||
}
|
}
|
||||||
patchWorldFromEvent(ref);
|
patchWorldFromEvent(ref);
|
||||||
|
syncWorldSearchIndex(ref);
|
||||||
return ref;
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,3 +249,12 @@ export async function preloadOwnWorlds() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
export function removeWorldFromCache(id) {
|
||||||
|
const worldStore = useWorldStore();
|
||||||
|
worldStore.cachedWorlds.delete(id);
|
||||||
|
removeWorldSearchIndex(id);
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,10 +5,7 @@ import { nextTick, reactive } from 'vue';
|
|||||||
const mocks = vi.hoisted(() => ({
|
const mocks = vi.hoisted(() => ({
|
||||||
workerInstances: [],
|
workerInstances: [],
|
||||||
friendStore: null,
|
friendStore: null,
|
||||||
favoriteStore: null,
|
searchIndexStore: null,
|
||||||
avatarStore: null,
|
|
||||||
worldStore: null,
|
|
||||||
groupStore: null,
|
|
||||||
userStore: null
|
userStore: null
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -29,17 +26,8 @@ vi.mock('../searchWorker.js?worker', () => ({
|
|||||||
vi.mock('../friend', () => ({
|
vi.mock('../friend', () => ({
|
||||||
useFriendStore: () => mocks.friendStore
|
useFriendStore: () => mocks.friendStore
|
||||||
}));
|
}));
|
||||||
vi.mock('../favorite', () => ({
|
vi.mock('../searchIndex', () => ({
|
||||||
useFavoriteStore: () => mocks.favoriteStore
|
useSearchIndexStore: () => mocks.searchIndexStore
|
||||||
}));
|
|
||||||
vi.mock('../avatar', () => ({
|
|
||||||
useAvatarStore: () => mocks.avatarStore
|
|
||||||
}));
|
|
||||||
vi.mock('../world', () => ({
|
|
||||||
useWorldStore: () => mocks.worldStore
|
|
||||||
}));
|
|
||||||
vi.mock('../group', () => ({
|
|
||||||
useGroupStore: () => mocks.groupStore
|
|
||||||
}));
|
}));
|
||||||
vi.mock('../user', () => ({
|
vi.mock('../user', () => ({
|
||||||
useUserStore: () => mocks.userStore
|
useUserStore: () => mocks.userStore
|
||||||
@@ -67,10 +55,30 @@ import { useGlobalSearchStore } from '../globalSearch';
|
|||||||
|
|
||||||
function setupStores() {
|
function setupStores() {
|
||||||
mocks.friendStore = reactive({ friends: new Map() });
|
mocks.friendStore = reactive({ friends: new Map() });
|
||||||
mocks.favoriteStore = reactive({ favoriteAvatars: [], favoriteWorlds: [] });
|
mocks.searchIndexStore = reactive({
|
||||||
mocks.avatarStore = reactive({ cachedAvatars: new Map() });
|
version: 0,
|
||||||
mocks.worldStore = reactive({ cachedWorlds: new Map() });
|
getSnapshot() {
|
||||||
mocks.groupStore = reactive({ currentUserGroups: new Map() });
|
const friendsList = [];
|
||||||
|
for (const ctx of (mocks.friendStore?.friends || new Map()).values()) {
|
||||||
|
if (typeof ctx.ref === 'undefined') continue;
|
||||||
|
friendsList.push({
|
||||||
|
id: ctx.id,
|
||||||
|
name: ctx.name,
|
||||||
|
memo: ctx.memo || '',
|
||||||
|
note: ctx.ref?.note || '',
|
||||||
|
imageUrl: ctx.ref?.currentAvatarThumbnailImageUrl || ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
friends: friendsList,
|
||||||
|
avatars: [],
|
||||||
|
worlds: [],
|
||||||
|
groups: [],
|
||||||
|
favAvatars: [],
|
||||||
|
favWorlds: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
mocks.userStore = reactive({ currentUser: { id: 'usr_me' } });
|
mocks.userStore = reactive({ currentUser: { id: 'usr_me' } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,4 +189,37 @@ describe('useGlobalSearchStore', () => {
|
|||||||
expect(lastMessage.type).toBe('search');
|
expect(lastMessage.type).toBe('search');
|
||||||
expect(lastMessage.payload.currentUserId).toBe('usr_other');
|
expect(lastMessage.payload.currentUserId).toBe('usr_other');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('re-dispatches search after index update when query is active', async () => {
|
||||||
|
vi.useFakeTimers();
|
||||||
|
const store = useGlobalSearchStore();
|
||||||
|
store.isOpen = true;
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
store.setQuery('ab');
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
const worker = mocks.workerInstances[0];
|
||||||
|
const callsBefore = worker.postMessage.mock.calls.length;
|
||||||
|
|
||||||
|
// Simulate searchIndex version bump (as if data arrived)
|
||||||
|
mocks.searchIndexStore.version++;
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
// Fast-forward the 200ms debounce
|
||||||
|
vi.advanceTimersByTime(200);
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
const newCalls = worker.postMessage.mock.calls.slice(callsBefore);
|
||||||
|
const types = newCalls.map((c) => c[0].type);
|
||||||
|
expect(types).toContain('updateIndex');
|
||||||
|
expect(types).toContain('search');
|
||||||
|
|
||||||
|
// updateIndex should come before search
|
||||||
|
const updateIdx = types.indexOf('updateIndex');
|
||||||
|
const searchIdx = types.lastIndexOf('search');
|
||||||
|
expect(updateIdx).toBeLessThan(searchIdx);
|
||||||
|
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
runPendingOfflineTickFlow,
|
runPendingOfflineTickFlow,
|
||||||
runUpdateFriendFlow
|
runUpdateFriendFlow
|
||||||
} from '../coordinators/friendPresenceCoordinator';
|
} from '../coordinators/friendPresenceCoordinator';
|
||||||
|
import { syncFriendSearchIndex } from '../coordinators/searchIndexCoordinator';
|
||||||
import {
|
import {
|
||||||
updateFriendship,
|
updateFriendship,
|
||||||
runUpdateFriendshipsFlow
|
runUpdateFriendshipsFlow
|
||||||
@@ -340,19 +341,24 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
for (id of ref.onlineFriends) {
|
for (id of ref.onlineFriends) {
|
||||||
map.set(id, 'online');
|
map.set(id, 'online');
|
||||||
}
|
}
|
||||||
|
const added = [];
|
||||||
|
const removed = [];
|
||||||
for (const friend of map) {
|
for (const friend of map) {
|
||||||
const [id, state_input] = friend;
|
const [id, state_input] = friend;
|
||||||
if (friends.has(id)) {
|
if (friends.has(id)) {
|
||||||
runUpdateFriendFlow(id, state_input);
|
runUpdateFriendFlow(id, state_input);
|
||||||
} else {
|
} else {
|
||||||
addFriend(id, state_input);
|
addFriend(id, state_input);
|
||||||
|
added.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (id of friends.keys()) {
|
for (id of friends.keys()) {
|
||||||
if (map.has(id) === false) {
|
if (map.has(id) === false) {
|
||||||
deleteFriend(id);
|
deleteFriend(id);
|
||||||
|
removed.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return { added, removed };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -389,6 +395,7 @@ export const useFriendStore = defineStore('Friend', () => {
|
|||||||
const array = memo.memo.split('\n');
|
const array = memo.memo.split('\n');
|
||||||
ctx.$nickName = array[0];
|
ctx.$nickName = array[0];
|
||||||
}
|
}
|
||||||
|
syncFriendSearchIndex(ctx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-111
@@ -1,11 +1,8 @@
|
|||||||
import { computed, effectScope, ref, watch } from 'vue';
|
import { computed, effectScope, ref, watch } from 'vue';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { useAvatarStore } from './avatar';
|
|
||||||
import { useFavoriteStore } from './favorite';
|
|
||||||
import { useFriendStore } from './friend';
|
import { useFriendStore } from './friend';
|
||||||
import { useGroupStore } from './group';
|
import { useSearchIndexStore } from './searchIndex';
|
||||||
import { useUserStore } from './user';
|
import { useUserStore } from './user';
|
||||||
import { useWorldStore } from './world';
|
|
||||||
import { showGroupDialog } from '../coordinators/groupCoordinator';
|
import { showGroupDialog } from '../coordinators/groupCoordinator';
|
||||||
import { showWorldDialog } from '../coordinators/worldCoordinator';
|
import { showWorldDialog } from '../coordinators/worldCoordinator';
|
||||||
import { showAvatarDialog } from '../coordinators/avatarCoordinator';
|
import { showAvatarDialog } from '../coordinators/avatarCoordinator';
|
||||||
@@ -15,11 +12,8 @@ import SearchWorker from './searchWorker.js?worker';
|
|||||||
|
|
||||||
export const useGlobalSearchStore = defineStore('GlobalSearch', () => {
|
export const useGlobalSearchStore = defineStore('GlobalSearch', () => {
|
||||||
const friendStore = useFriendStore();
|
const friendStore = useFriendStore();
|
||||||
const favoriteStore = useFavoriteStore();
|
|
||||||
const avatarStore = useAvatarStore();
|
|
||||||
const worldStore = useWorldStore();
|
|
||||||
const groupStore = useGroupStore();
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
const searchIndexStore = useSearchIndexStore();
|
||||||
|
|
||||||
const isOpen = ref(false);
|
const isOpen = ref(false);
|
||||||
const query = ref('');
|
const query = ref('');
|
||||||
@@ -73,81 +67,16 @@ export const useGlobalSearchStore = defineStore('GlobalSearch', () => {
|
|||||||
indexUpdateTimer = null;
|
indexUpdateTimer = null;
|
||||||
if (!isOpen.value) return;
|
if (!isOpen.value) return;
|
||||||
sendIndexUpdate();
|
sendIndexUpdate();
|
||||||
|
if (query.value && query.value.length >= 2) {
|
||||||
|
dispatchSearch();
|
||||||
|
}
|
||||||
}, 200);
|
}, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendIndexUpdate() {
|
function sendIndexUpdate() {
|
||||||
const w = getWorker();
|
const w = getWorker();
|
||||||
|
const payload = searchIndexStore.getSnapshot();
|
||||||
const friends = [];
|
w.postMessage({ type: 'updateIndex', payload });
|
||||||
for (const ctx of friendStore.friends.values()) {
|
|
||||||
if (typeof ctx.ref === 'undefined') continue;
|
|
||||||
friends.push({
|
|
||||||
id: ctx.id,
|
|
||||||
name: ctx.name,
|
|
||||||
memo: ctx.memo || '',
|
|
||||||
note: ctx.ref.note || '',
|
|
||||||
imageUrl: ctx.ref.currentAvatarThumbnailImageUrl
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const avatars = [];
|
|
||||||
for (const ref of avatarStore.cachedAvatars.values()) {
|
|
||||||
if (!ref || !ref.name) continue;
|
|
||||||
avatars.push({
|
|
||||||
id: ref.id,
|
|
||||||
name: ref.name,
|
|
||||||
authorId: ref.authorId,
|
|
||||||
imageUrl: ref.thumbnailImageUrl || ref.imageUrl
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const worlds = [];
|
|
||||||
for (const ref of worldStore.cachedWorlds.values()) {
|
|
||||||
if (!ref || !ref.name) continue;
|
|
||||||
worlds.push({
|
|
||||||
id: ref.id,
|
|
||||||
name: ref.name,
|
|
||||||
authorId: ref.authorId,
|
|
||||||
imageUrl: ref.thumbnailImageUrl || ref.imageUrl
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const groups = [];
|
|
||||||
for (const ref of groupStore.currentUserGroups.values()) {
|
|
||||||
if (!ref || !ref.name) continue;
|
|
||||||
groups.push({
|
|
||||||
id: ref.id,
|
|
||||||
name: ref.name,
|
|
||||||
ownerId: ref.ownerId,
|
|
||||||
imageUrl: ref.iconUrl || ref.bannerUrl
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const favAvatars = [];
|
|
||||||
for (const ctx of favoriteStore.favoriteAvatars) {
|
|
||||||
if (!ctx?.ref?.name) continue;
|
|
||||||
favAvatars.push({
|
|
||||||
id: ctx.ref.id,
|
|
||||||
name: ctx.ref.name,
|
|
||||||
imageUrl: ctx.ref.thumbnailImageUrl || ctx.ref.imageUrl
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const favWorlds = [];
|
|
||||||
for (const ctx of favoriteStore.favoriteWorlds) {
|
|
||||||
if (!ctx?.ref?.name) continue;
|
|
||||||
favWorlds.push({
|
|
||||||
id: ctx.ref.id,
|
|
||||||
name: ctx.ref.name,
|
|
||||||
imageUrl: ctx.ref.thumbnailImageUrl || ctx.ref.imageUrl
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
w.postMessage({
|
|
||||||
type: 'updateIndex',
|
|
||||||
payload: { friends, avatars, worlds, groups, favAvatars, favWorlds }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopIndexWatchers() {
|
function stopIndexWatchers() {
|
||||||
@@ -167,39 +96,8 @@ export const useGlobalSearchStore = defineStore('GlobalSearch', () => {
|
|||||||
indexWatchScope = effectScope();
|
indexWatchScope = effectScope();
|
||||||
indexWatchScope.run(() => {
|
indexWatchScope.run(() => {
|
||||||
watch(
|
watch(
|
||||||
() => friendStore.friends,
|
() => searchIndexStore.version,
|
||||||
() => scheduleIndexUpdate(),
|
() => scheduleIndexUpdate()
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => avatarStore.cachedAvatars,
|
|
||||||
() => scheduleIndexUpdate(),
|
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => worldStore.cachedWorlds,
|
|
||||||
() => scheduleIndexUpdate(),
|
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => groupStore.currentUserGroups,
|
|
||||||
() => scheduleIndexUpdate(),
|
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => favoriteStore.favoriteAvatars,
|
|
||||||
() => scheduleIndexUpdate(),
|
|
||||||
{ deep: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => favoriteStore.favoriteWorlds,
|
|
||||||
() => scheduleIndexUpdate(),
|
|
||||||
{ deep: true }
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,294 @@
|
|||||||
|
import { ref } from 'vue';
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { useFavoriteStore } from './favorite';
|
||||||
|
|
||||||
|
export const useSearchIndexStore = defineStore('SearchIndex', () => {
|
||||||
|
const friends = new Map();
|
||||||
|
const avatars = new Map();
|
||||||
|
const worlds = new Map();
|
||||||
|
const groups = new Map();
|
||||||
|
const favAvatars = new Map();
|
||||||
|
const favWorlds = new Map();
|
||||||
|
|
||||||
|
const version = ref(0);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync a friend context into the search index.
|
||||||
|
* Extracts only the fields needed for searching.
|
||||||
|
* @param {object} ctx - Friend context from friendStore.friends
|
||||||
|
*/
|
||||||
|
function syncFriend(ctx) {
|
||||||
|
if (!ctx || !ctx.id) return;
|
||||||
|
const entry = {
|
||||||
|
id: ctx.id,
|
||||||
|
name: ctx.name || '',
|
||||||
|
memo: ctx.memo || '',
|
||||||
|
note: ctx.ref?.note || '',
|
||||||
|
imageUrl: ctx.ref?.currentAvatarThumbnailImageUrl || ''
|
||||||
|
};
|
||||||
|
const existing = friends.get(ctx.id);
|
||||||
|
if (
|
||||||
|
existing &&
|
||||||
|
existing.name === entry.name &&
|
||||||
|
existing.memo === entry.memo &&
|
||||||
|
existing.note === entry.note &&
|
||||||
|
existing.imageUrl === entry.imageUrl
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
friends.set(ctx.id, entry);
|
||||||
|
version.value++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
function removeFriend(id) {
|
||||||
|
if (friends.delete(id)) {
|
||||||
|
version.value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFriends() {
|
||||||
|
if (friends.size > 0) {
|
||||||
|
friends.clear();
|
||||||
|
version.value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} ref - Avatar data object
|
||||||
|
*/
|
||||||
|
function upsertAvatar(ref) {
|
||||||
|
if (!ref || !ref.id) return;
|
||||||
|
const entry = {
|
||||||
|
id: ref.id,
|
||||||
|
name: ref.name || '',
|
||||||
|
authorId: ref.authorId || '',
|
||||||
|
imageUrl: ref.thumbnailImageUrl || ref.imageUrl || ''
|
||||||
|
};
|
||||||
|
const existing = avatars.get(ref.id);
|
||||||
|
if (
|
||||||
|
existing &&
|
||||||
|
existing.name === entry.name &&
|
||||||
|
existing.authorId === entry.authorId &&
|
||||||
|
existing.imageUrl === entry.imageUrl
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
avatars.set(ref.id, entry);
|
||||||
|
version.value++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
function removeAvatar(id) {
|
||||||
|
if (avatars.delete(id)) {
|
||||||
|
version.value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAvatars() {
|
||||||
|
if (avatars.size > 0) {
|
||||||
|
avatars.clear();
|
||||||
|
version.value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} ref - World data object
|
||||||
|
*/
|
||||||
|
function upsertWorld(ref) {
|
||||||
|
if (!ref || !ref.id) return;
|
||||||
|
const entry = {
|
||||||
|
id: ref.id,
|
||||||
|
name: ref.name || '',
|
||||||
|
authorId: ref.authorId || '',
|
||||||
|
imageUrl: ref.thumbnailImageUrl || ref.imageUrl || ''
|
||||||
|
};
|
||||||
|
const existing = worlds.get(ref.id);
|
||||||
|
if (
|
||||||
|
existing &&
|
||||||
|
existing.name === entry.name &&
|
||||||
|
existing.authorId === entry.authorId &&
|
||||||
|
existing.imageUrl === entry.imageUrl
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
worlds.set(ref.id, entry);
|
||||||
|
version.value++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
function removeWorld(id) {
|
||||||
|
if (worlds.delete(id)) {
|
||||||
|
version.value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearWorlds() {
|
||||||
|
if (worlds.size > 0) {
|
||||||
|
worlds.clear();
|
||||||
|
version.value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} ref - Group data object
|
||||||
|
*/
|
||||||
|
function upsertGroup(ref) {
|
||||||
|
if (!ref || !ref.id) return;
|
||||||
|
const entry = {
|
||||||
|
id: ref.id,
|
||||||
|
name: ref.name || '',
|
||||||
|
ownerId: ref.ownerId || '',
|
||||||
|
imageUrl: ref.iconUrl || ref.bannerUrl || ''
|
||||||
|
};
|
||||||
|
const existing = groups.get(ref.id);
|
||||||
|
if (
|
||||||
|
existing &&
|
||||||
|
existing.name === entry.name &&
|
||||||
|
existing.ownerId === entry.ownerId &&
|
||||||
|
existing.imageUrl === entry.imageUrl
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
groups.set(ref.id, entry);
|
||||||
|
version.value++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
function removeGroup(id) {
|
||||||
|
if (groups.delete(id)) {
|
||||||
|
version.value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearGroups() {
|
||||||
|
if (groups.size > 0) {
|
||||||
|
groups.clear();
|
||||||
|
version.value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function rebuildFavoritesFromStore() {
|
||||||
|
const favoriteStore = useFavoriteStore();
|
||||||
|
|
||||||
|
const newFavAvatars = new Map();
|
||||||
|
for (const ctx of favoriteStore.favoriteAvatars) {
|
||||||
|
if (!ctx?.ref?.name) continue;
|
||||||
|
newFavAvatars.set(ctx.ref.id, {
|
||||||
|
id: ctx.ref.id,
|
||||||
|
name: ctx.ref.name,
|
||||||
|
imageUrl: ctx.ref.thumbnailImageUrl || ctx.ref.imageUrl || ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newFavWorlds = new Map();
|
||||||
|
for (const ctx of favoriteStore.favoriteWorlds) {
|
||||||
|
if (!ctx?.ref?.name) continue;
|
||||||
|
newFavWorlds.set(ctx.ref.id, {
|
||||||
|
id: ctx.ref.id,
|
||||||
|
name: ctx.ref.name,
|
||||||
|
imageUrl: ctx.ref.thumbnailImageUrl || ctx.ref.imageUrl || ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let changed = false;
|
||||||
|
if (favAvatars.size !== newFavAvatars.size) {
|
||||||
|
changed = true;
|
||||||
|
} else {
|
||||||
|
for (const [id, entry] of newFavAvatars) {
|
||||||
|
const existing = favAvatars.get(id);
|
||||||
|
if (!existing || existing.name !== entry.name || existing.imageUrl !== entry.imageUrl) {
|
||||||
|
changed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (favWorlds.size !== newFavWorlds.size) {
|
||||||
|
changed = true;
|
||||||
|
} else if (!changed) {
|
||||||
|
for (const [id, entry] of newFavWorlds) {
|
||||||
|
const existing = favWorlds.get(id);
|
||||||
|
if (!existing || existing.name !== entry.name || existing.imageUrl !== entry.imageUrl) {
|
||||||
|
changed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
favAvatars.clear();
|
||||||
|
for (const [id, entry] of newFavAvatars) {
|
||||||
|
favAvatars.set(id, entry);
|
||||||
|
}
|
||||||
|
favWorlds.clear();
|
||||||
|
for (const [id, entry] of newFavWorlds) {
|
||||||
|
favWorlds.set(id, entry);
|
||||||
|
}
|
||||||
|
version.value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearFavorites() {
|
||||||
|
if (favAvatars.size > 0 || favWorlds.size > 0) {
|
||||||
|
favAvatars.clear();
|
||||||
|
favWorlds.clear();
|
||||||
|
version.value++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a snapshot from the internal index maps.
|
||||||
|
* Used by globalSearch to send data to the Worker.
|
||||||
|
* @returns {object} Plain object arrays ready for postMessage.
|
||||||
|
*/
|
||||||
|
function getSnapshot() {
|
||||||
|
return {
|
||||||
|
friends: Array.from(friends.values()),
|
||||||
|
avatars: Array.from(avatars.values()),
|
||||||
|
worlds: Array.from(worlds.values()),
|
||||||
|
groups: Array.from(groups.values()),
|
||||||
|
favAvatars: Array.from(favAvatars.values()),
|
||||||
|
favWorlds: Array.from(favWorlds.values())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
version,
|
||||||
|
|
||||||
|
syncFriend,
|
||||||
|
removeFriend,
|
||||||
|
clearFriends,
|
||||||
|
|
||||||
|
upsertAvatar,
|
||||||
|
removeAvatar,
|
||||||
|
clearAvatars,
|
||||||
|
|
||||||
|
upsertWorld,
|
||||||
|
removeWorld,
|
||||||
|
clearWorlds,
|
||||||
|
|
||||||
|
upsertGroup,
|
||||||
|
removeGroup,
|
||||||
|
clearGroups,
|
||||||
|
rebuildFavoritesFromStore,
|
||||||
|
clearFavorites,
|
||||||
|
|
||||||
|
getSnapshot
|
||||||
|
};
|
||||||
|
});
|
||||||
+77
-37
@@ -88,7 +88,6 @@ function removeWhitespace(a) {
|
|||||||
return a.replace(/\s/g, '');
|
return a.replace(/\s/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Locale-aware string search ──────────────────────────────────────
|
|
||||||
|
|
||||||
function localeIncludes(str, search, comparer) {
|
function localeIncludes(str, search, comparer) {
|
||||||
if (search === '') return true;
|
if (search === '') return true;
|
||||||
@@ -104,25 +103,20 @@ function localeIncludes(str, search, comparer) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function matchName(name, query, comparer) {
|
function matchName(name, cleanQuery, comparer, normalizedName) {
|
||||||
if (!name || !query) return false;
|
if (!name || !cleanQuery) return false;
|
||||||
const cleanQuery = removeWhitespace(query);
|
const cleanName = normalizedName || removeConfusables(name);
|
||||||
if (!cleanQuery) return false;
|
|
||||||
const cleanName = removeConfusables(name);
|
|
||||||
if (localeIncludes(cleanName, cleanQuery, comparer)) return true;
|
if (localeIncludes(cleanName, cleanQuery, comparer)) return true;
|
||||||
return localeIncludes(name, cleanQuery, comparer);
|
return localeIncludes(name, cleanQuery, comparer);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPrefixMatch(name, query, comparer) {
|
function isPrefixMatch(name, cleanQuery, comparer) {
|
||||||
if (!name || !query) return false;
|
if (!name || !cleanQuery) return false;
|
||||||
const cleanQuery = removeWhitespace(query);
|
|
||||||
if (!cleanQuery) return false;
|
|
||||||
return (
|
return (
|
||||||
comparer.compare(name.substring(0, cleanQuery.length), cleanQuery) === 0
|
comparer.compare(name.substring(0, cleanQuery.length), cleanQuery) === 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Index data (updated from main thread) ───────────────────────────
|
|
||||||
|
|
||||||
let indexedFriends = []; // { id, name, memo, note, imageUrl }
|
let indexedFriends = []; // { id, name, memo, note, imageUrl }
|
||||||
let indexedAvatars = []; // { id, name, authorId, imageUrl }
|
let indexedAvatars = []; // { id, name, authorId, imageUrl }
|
||||||
@@ -133,22 +127,54 @@ let indexedFavWorlds = []; // { id, name, imageUrl }
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the search index with fresh data snapshots.
|
* Update the search index with fresh data snapshots.
|
||||||
|
* Pre-computes normalized names to avoid per-search confusables overhead.
|
||||||
|
* @param payload
|
||||||
*/
|
*/
|
||||||
function updateIndex(payload) {
|
function updateIndex(payload) {
|
||||||
if (payload.friends) indexedFriends = payload.friends;
|
if (payload.friends) {
|
||||||
if (payload.avatars) indexedAvatars = payload.avatars;
|
indexedFriends = payload.friends;
|
||||||
if (payload.worlds) indexedWorlds = payload.worlds;
|
for (const f of indexedFriends) {
|
||||||
if (payload.groups) indexedGroups = payload.groups;
|
f._normalized = f.name ? removeConfusables(f.name) : '';
|
||||||
if (payload.favAvatars) indexedFavAvatars = payload.favAvatars;
|
}
|
||||||
if (payload.favWorlds) indexedFavWorlds = payload.favWorlds;
|
}
|
||||||
|
if (payload.avatars) {
|
||||||
|
indexedAvatars = payload.avatars;
|
||||||
|
for (const a of indexedAvatars) {
|
||||||
|
a._normalized = a.name ? removeConfusables(a.name) : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (payload.worlds) {
|
||||||
|
indexedWorlds = payload.worlds;
|
||||||
|
for (const w of indexedWorlds) {
|
||||||
|
w._normalized = w.name ? removeConfusables(w.name) : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (payload.groups) {
|
||||||
|
indexedGroups = payload.groups;
|
||||||
|
for (const g of indexedGroups) {
|
||||||
|
g._normalized = g.name ? removeConfusables(g.name) : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (payload.favAvatars) {
|
||||||
|
indexedFavAvatars = payload.favAvatars;
|
||||||
|
for (const a of indexedFavAvatars) {
|
||||||
|
a._normalized = a.name ? removeConfusables(a.name) : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (payload.favWorlds) {
|
||||||
|
indexedFavWorlds = payload.favWorlds;
|
||||||
|
for (const w of indexedFavWorlds) {
|
||||||
|
w._normalized = w.name ? removeConfusables(w.name) : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Search functions ────────────────────────────────────────────────
|
// ── Search functions ────────────────────────────────────────────────
|
||||||
|
|
||||||
function searchFriends(query, comparer, limit = 10) {
|
function searchFriends(query, cleanQuery, comparer, limit = 10) {
|
||||||
const results = [];
|
const results = [];
|
||||||
for (const ctx of indexedFriends) {
|
for (const ctx of indexedFriends) {
|
||||||
let match = matchName(ctx.name, query, comparer);
|
let match = matchName(ctx.name, cleanQuery, comparer, ctx._normalized);
|
||||||
let matchedField = match ? 'name' : null;
|
let matchedField = match ? 'name' : null;
|
||||||
if (!match && ctx.memo) {
|
if (!match && ctx.memo) {
|
||||||
match = localeIncludes(ctx.memo, query, comparer);
|
match = localeIncludes(ctx.memo, query, comparer);
|
||||||
@@ -170,19 +196,25 @@ function searchFriends(query, comparer, limit = 10) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Pre-compute prefix flags to avoid repeated Collator calls in sort
|
||||||
|
for (const r of results) {
|
||||||
|
r._isPrefix = isPrefixMatch(r.name, cleanQuery, comparer);
|
||||||
|
}
|
||||||
results.sort((a, b) => {
|
results.sort((a, b) => {
|
||||||
const aPrefix = isPrefixMatch(a.name, query, comparer);
|
if (a._isPrefix && !b._isPrefix) return -1;
|
||||||
const bPrefix = isPrefixMatch(b.name, query, comparer);
|
if (b._isPrefix && !a._isPrefix) return 1;
|
||||||
if (aPrefix && !bPrefix) return -1;
|
|
||||||
if (bPrefix && !aPrefix) return 1;
|
|
||||||
return comparer.compare(a.name, b.name);
|
return comparer.compare(a.name, b.name);
|
||||||
});
|
});
|
||||||
if (results.length > limit) results.length = limit;
|
if (results.length > limit) results.length = limit;
|
||||||
|
// Clean up internal sort field before returning
|
||||||
|
for (const r of results) {
|
||||||
|
delete r._isPrefix;
|
||||||
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchItems(
|
function searchItems(
|
||||||
query,
|
cleanQuery,
|
||||||
items,
|
items,
|
||||||
type,
|
type,
|
||||||
comparer,
|
comparer,
|
||||||
@@ -194,7 +226,7 @@ function searchItems(
|
|||||||
for (const ref of items) {
|
for (const ref of items) {
|
||||||
if (!ref || !ref.name) continue;
|
if (!ref || !ref.name) continue;
|
||||||
if (ownerId && ref[ownerKey] !== ownerId) continue;
|
if (ownerId && ref[ownerKey] !== ownerId) continue;
|
||||||
if (matchName(ref.name, query, comparer)) {
|
if (matchName(ref.name, cleanQuery, comparer, ref._normalized)) {
|
||||||
results.push({
|
results.push({
|
||||||
id: ref.id,
|
id: ref.id,
|
||||||
name: ref.name,
|
name: ref.name,
|
||||||
@@ -203,14 +235,20 @@ function searchItems(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Pre-compute prefix flags to avoid repeated Collator calls in sort
|
||||||
|
for (const r of results) {
|
||||||
|
r._isPrefix = isPrefixMatch(r.name, cleanQuery, comparer);
|
||||||
|
}
|
||||||
results.sort((a, b) => {
|
results.sort((a, b) => {
|
||||||
const aPrefix = isPrefixMatch(a.name, query, comparer);
|
if (a._isPrefix && !b._isPrefix) return -1;
|
||||||
const bPrefix = isPrefixMatch(b.name, query, comparer);
|
if (b._isPrefix && !a._isPrefix) return 1;
|
||||||
if (aPrefix && !bPrefix) return -1;
|
|
||||||
if (bPrefix && !aPrefix) return 1;
|
|
||||||
return comparer.compare(a.name, b.name);
|
return comparer.compare(a.name, b.name);
|
||||||
});
|
});
|
||||||
if (results.length > limit) results.length = limit;
|
if (results.length > limit) results.length = limit;
|
||||||
|
// Clean up internal sort field before returning
|
||||||
|
for (const r of results) {
|
||||||
|
delete r._isPrefix;
|
||||||
|
}
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,9 +277,12 @@ function handleSearch(payload) {
|
|||||||
sensitivity: 'base'
|
sensitivity: 'base'
|
||||||
});
|
});
|
||||||
|
|
||||||
const friends = searchFriends(query, comparer);
|
// Pre-compute cleaned query once for all name searches
|
||||||
|
const cleanQuery = removeWhitespace(query);
|
||||||
|
|
||||||
|
const friends = searchFriends(query, cleanQuery, comparer);
|
||||||
const ownAvatars = searchItems(
|
const ownAvatars = searchItems(
|
||||||
query,
|
cleanQuery,
|
||||||
indexedAvatars,
|
indexedAvatars,
|
||||||
'avatar',
|
'avatar',
|
||||||
comparer,
|
comparer,
|
||||||
@@ -249,7 +290,7 @@ function handleSearch(payload) {
|
|||||||
currentUserId
|
currentUserId
|
||||||
);
|
);
|
||||||
const favAvatars = searchItems(
|
const favAvatars = searchItems(
|
||||||
query,
|
cleanQuery,
|
||||||
indexedFavAvatars,
|
indexedFavAvatars,
|
||||||
'avatar',
|
'avatar',
|
||||||
comparer,
|
comparer,
|
||||||
@@ -257,7 +298,7 @@ function handleSearch(payload) {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
const ownWorlds = searchItems(
|
const ownWorlds = searchItems(
|
||||||
query,
|
cleanQuery,
|
||||||
indexedWorlds,
|
indexedWorlds,
|
||||||
'world',
|
'world',
|
||||||
comparer,
|
comparer,
|
||||||
@@ -265,7 +306,7 @@ function handleSearch(payload) {
|
|||||||
currentUserId
|
currentUserId
|
||||||
);
|
);
|
||||||
const favWorlds = searchItems(
|
const favWorlds = searchItems(
|
||||||
query,
|
cleanQuery,
|
||||||
indexedFavWorlds,
|
indexedFavWorlds,
|
||||||
'world',
|
'world',
|
||||||
comparer,
|
comparer,
|
||||||
@@ -273,7 +314,7 @@ function handleSearch(payload) {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
const ownGroups = searchItems(
|
const ownGroups = searchItems(
|
||||||
query,
|
cleanQuery,
|
||||||
indexedGroups,
|
indexedGroups,
|
||||||
'group',
|
'group',
|
||||||
comparer,
|
comparer,
|
||||||
@@ -281,7 +322,7 @@ function handleSearch(payload) {
|
|||||||
currentUserId
|
currentUserId
|
||||||
);
|
);
|
||||||
const joinedGroups = searchItems(
|
const joinedGroups = searchItems(
|
||||||
query,
|
cleanQuery,
|
||||||
indexedGroups,
|
indexedGroups,
|
||||||
'group',
|
'group',
|
||||||
comparer,
|
comparer,
|
||||||
@@ -314,7 +355,6 @@ function handleSearch(payload) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Message handler ─────────────────────────────────────────────────
|
|
||||||
|
|
||||||
self.addEventListener('message', (event) => {
|
self.addEventListener('message', (event) => {
|
||||||
const { type, payload } = event.data;
|
const { type, payload } = event.data;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { useAppearanceSettingsStore } from './settings/appearance';
|
|||||||
import { useFriendStore } from './friend';
|
import { useFriendStore } from './friend';
|
||||||
import { useInstanceStore } from './instance';
|
import { useInstanceStore } from './instance';
|
||||||
import { useLocationStore } from './location';
|
import { useLocationStore } from './location';
|
||||||
|
import { syncFriendSearchIndex } from '../coordinators/searchIndexCoordinator';
|
||||||
import { useUiStore } from './ui';
|
import { useUiStore } from './ui';
|
||||||
import { watchState } from '../services/watchState';
|
import { watchState } from '../services/watchState';
|
||||||
|
|
||||||
@@ -499,6 +500,10 @@ export const useUserStore = defineStore('User', () => {
|
|||||||
const user = users.get(note.userId);
|
const user = users.get(note.userId);
|
||||||
if (user) {
|
if (user) {
|
||||||
user.note = note.note;
|
user.note = note.note;
|
||||||
|
const friendCtx = friendStore.friends.get(note.userId);
|
||||||
|
if (friendCtx) {
|
||||||
|
syncFriendSearchIndex(friendCtx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
!state.lastDbNoteDate ||
|
!state.lastDbNoteDate ||
|
||||||
@@ -568,6 +573,10 @@ export const useUserStore = defineStore('User', () => {
|
|||||||
const user = users.get(note.targetUserId);
|
const user = users.get(note.targetUserId);
|
||||||
if (user) {
|
if (user) {
|
||||||
user.note = note.note;
|
user.note = note.note;
|
||||||
|
const friendCtx = friendStore.friends.get(note.targetUserId);
|
||||||
|
if (friendCtx) {
|
||||||
|
syncFriendSearchIndex(friendCtx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import { useUpdateLoopStore } from './updateLoop';
|
|||||||
import { useUserStore } from './user';
|
import { useUserStore } from './user';
|
||||||
import { useVrcStatusStore } from './vrcStatus';
|
import { useVrcStatusStore } from './vrcStatus';
|
||||||
import { clearVRCXCache } from '../coordinators/vrcxCoordinator';
|
import { clearVRCXCache } from '../coordinators/vrcxCoordinator';
|
||||||
|
import { resetSearchIndexOnLogin } from '../coordinators/searchIndexCoordinator';
|
||||||
import { watchState } from '../services/watchState';
|
import { watchState } from '../services/watchState';
|
||||||
|
|
||||||
import configRepository from '../services/config';
|
import configRepository from '../services/config';
|
||||||
@@ -177,6 +178,7 @@ export const useVrcxStore = defineStore('Vrcx', () => {
|
|||||||
refreshCustomScript();
|
refreshCustomScript();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetSearchIndexOnLogin();
|
||||||
init();
|
init();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import { useAvatarStore, useUserStore } from '../../../stores';
|
import { useAvatarStore, useUserStore } from '../../../stores';
|
||||||
|
import { removeAvatarFromCache } from '../../../coordinators/avatarCoordinator';
|
||||||
import { avatarRequest } from '../../../api';
|
import { avatarRequest } from '../../../api';
|
||||||
import { processBulk } from '../../../services/request';
|
import { processBulk } from '../../../services/request';
|
||||||
|
|
||||||
@@ -64,7 +65,7 @@
|
|||||||
loading.value = true;
|
loading.value = true;
|
||||||
for (const ref of cachedAvatars.values()) {
|
for (const ref of cachedAvatars.values()) {
|
||||||
if (ref.authorId === currentUser.value.id) {
|
if (ref.authorId === currentUser.value.id) {
|
||||||
cachedAvatars.delete(ref.id);
|
removeAvatarFromCache(ref.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const params = {
|
const params = {
|
||||||
|
|||||||
Reference in New Issue
Block a user