nicolium: remove flexsearch dependence, use fuzzysort for emojis

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2026-03-03 15:18:42 +01:00
parent 155647fd97
commit 9fb963df17
6 changed files with 36 additions and 65 deletions

View File

@ -87,7 +87,6 @@
"exifr": "^7.1.3",
"fast-average-color": "^9.5.0",
"fasttext.wasm.js": "^1.0.0",
"flexsearch": "^0.7.43",
"fuzzysort": "^3.1.0",
"graphemesplit": "^2.6.0",
"html-react-parser": "^5.2.17",

View File

@ -20,6 +20,7 @@ import {
useRelationshipQuery,
useUnblockAccountMutation,
} from '@/queries/accounts/use-relationship';
import { useCustomEmojis } from '@/queries/instance/use-custom-emojis';
import { useModalsActions } from '@/stores/modals';
import { textAtCursorMatchesToken } from '@/utils/suggestions';
@ -95,6 +96,7 @@ const ChatComposer = React.forwardRef<HTMLTextAreaElement | null, IChatComposer>
const { chat } = useChatContext();
const { data: relationship } = useRelationshipQuery(chat?.account.id);
const { mutate: unblockAccount } = useUnblockAccountMutation(chat?.account.id!);
const { data: customEmojis } = useCustomEmojis();
const isBlocked = relationship?.blocked_by && false;
const isBlocking = relationship?.blocking && false;
@ -130,7 +132,7 @@ const ChatComposer = React.forwardRef<HTMLTextAreaElement | null, IChatComposer>
);
if (token && tokenStart) {
const results = emojiSearch(token.replace(':', ''), { maxResults: 5 });
const results = emojiSearch(token.replace(':', ''), customEmojis, 5);
setSuggestions({
list: results,
token,

View File

@ -1,4 +1,4 @@
import FlexSearch from 'flexsearch';
import fuzzysort from 'fuzzysort';
import type { EmojiData } from './data';
import type { Emoji } from './index';
@ -6,79 +6,57 @@ import type { CustomEmoji } from 'pl-api';
let emojis: EmojiData['emojis'] = {};
const nativeData: Array<{ key: string; id: string }> = [];
let customData: Array<{ key: string; id: string }> = [];
import('./data')
.then((data) => {
emojis = data.emojis;
const sortedEmojis = Object.entries(emojis).toSorted((a, b) => a[0].localeCompare(b[0]));
for (const [key, emoji] of sortedEmojis) {
index.add('n' + key, `${emoji.id} ${emoji.name} ${emoji.keywords.join(' ')}`);
nativeData.push({
key: `${emoji.id} ${emoji.name} ${emoji.keywords.join(' ')}`,
id: 'n' + key,
});
}
})
.catch(() => {});
const index = new FlexSearch.Index({
tokenize: 'full',
optimize: true,
context: true,
});
interface searchOptions {
maxResults?: number;
custom?: CustomEmoji[];
}
const addCustomToPool = (customEmojis: CustomEmoji[]) => {
// @ts-expect-error
for (const key in index.register) {
if (key[0] === 'c') {
index.remove(key); // remove old custom emojis
}
}
let i = 0;
for (const emoji of customEmojis) {
index.add('c' + i++, emoji.shortcode);
}
customData = customEmojis.map((emoji, i) => ({
key: emoji.shortcode,
id: 'c' + i,
}));
};
// we can share an index by prefixing custom emojis with 'c' and native with 'n'
const search = (
str: string,
{ maxResults = 5 }: searchOptions = {},
custom_emojis?: Array<CustomEmoji>,
): Emoji[] =>
index
.search(str, maxResults)
.flatMap((id) => {
if (typeof id !== 'string') return;
const search = (query: string, customEmojis: Array<CustomEmoji> = [], limit = 5): Emoji[] => {
return fuzzysort
.go(query, [...nativeData, ...customData], { key: 'key', limit })
.map((result) => {
const { id } = result.obj;
if (id[0] === 'c' && custom_emojis) {
const index = Number(id.slice(1));
const custom = custom_emojis[index];
if (custom) {
return {
id: custom.shortcode,
colons: ':' + custom.shortcode + ':',
custom: true,
imageUrl: custom.static_url,
};
}
if (id[0] === 'c') {
const customEmoji = customEmojis[Number(id.slice(1))];
return {
id: customEmoji.shortcode,
colons: ':' + customEmoji.shortcode + ':',
custom: true,
imageUrl: customEmoji.static_url,
};
}
const skins = emojis[id.slice(1)]?.skins;
if (skins) {
const emojiData = emojis[id.slice(1)];
if (emojiData) {
return {
id: id.slice(1),
colons: ':' + id.slice(1) + ':',
unified: skins[0].unified,
native: skins[0].native,
unified: emojiData.skins[0].unified,
native: emojiData.skins[0].native,
};
}
})
.filter(Boolean) as Emoji[];
.filter(Boolean) as Array<Emoji>;
};
export { search as default, addCustomToPool };

View File

@ -32,7 +32,7 @@ const useComposeSuggestions = (token: string): Array<AutoSuggestion> => {
return useMemo((): Array<AutoSuggestion> => {
if (searchedType === 'emojis') {
return emojiSearch(token.replace(':', ''), { maxResults: 10 }, customEmojis);
return emojiSearch(token.replace(':', ''), customEmojis, 10);
}
if (searchedType === 'accounts') {

View File

@ -621,4 +621,4 @@ div:has(.⁂-background-shapes),
&--error svg {
@apply text-danger-600;
}
}
}

8
pnpm-lock.yaml generated
View File

@ -178,9 +178,6 @@ importers:
fasttext.wasm.js:
specifier: ^1.0.0
version: 1.0.0
flexsearch:
specifier: ^0.7.43
version: 0.7.43
fuzzysort:
specifier: ^3.1.0
version: 3.1.0
@ -3859,9 +3856,6 @@ packages:
flatted@3.3.3:
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
flexsearch@0.7.43:
resolution: {integrity: sha512-c5o/+Um8aqCSOXGcZoqZOm+NqtVwNsvVpWv6lfmSclU954O3wvQKxxK8zj74fPaSJbXpSLTs4PRhh+wnoCXnKg==}
for-each@0.3.5:
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
engines: {node: '>= 0.4'}
@ -10096,8 +10090,6 @@ snapshots:
flatted@3.3.3: {}
flexsearch@0.7.43: {}
for-each@0.3.5:
dependencies:
is-callable: 1.2.7