mirror of
https://github.com/marsbatya/tg-emoji-search.git
synced 2026-07-22 12:20:25 +00:00
Compare commits
15 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2162038b6 | ||
|
|
28822f1ed2 | ||
|
|
73052d87bf | ||
|
|
e66b69886b | ||
|
|
9198cc2f14 | ||
|
|
de6407f615 | ||
|
|
17b8164a84 | ||
|
|
cf2f7e112b | ||
|
|
fa83477852 | ||
|
|
6ec21a16bd | ||
|
|
9f2a661b56 | ||
|
|
c56e4e943d | ||
|
|
1fb74fcc72 | ||
|
|
b04b97830b | ||
|
|
6572cbbfc3 |
14 changed files with 10782 additions and 5049 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -20,3 +20,9 @@ data.json
|
|||
|
||||
# Exclude macOS Finder (System Explorer) View States
|
||||
.DS_Store
|
||||
|
||||
# Exclude rust temp files
|
||||
target/*
|
||||
|
||||
# Exclude script helpers
|
||||
scripts/*
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
# TG Emoji Search Plugin
|
||||
|
||||

|
||||
<img src="https://github.com/user-attachments/assets/e059f517-78b4-4734-b34e-3a5b102acdce" alt="some logo" width="200"/>
|
||||
|
||||
## Simple demo
|
||||
<img src="https://github.com/user-attachments/assets/22517d21-6493-430e-bd03-3b2cc6f41f1e" alt="demo" width="600"/>
|
||||
|
||||
This plugin provides a fast and intuitive way to insert emojis into your Obsidian notes. By typing a trigger character (":" by default) followed by a keyword, you get an instant suggestion list of matching emojis, complete with optional keyword hints. The plugin also includes a command to insert a random emoji, plus it tracks your most used emojis.
|
||||
|
||||
|
|
@ -14,7 +17,8 @@ This plugin provides a fast and intuitive way to insert emojis into your Obsidia
|
|||
- Uses data inspired by Telegram’s emoji translations.
|
||||
- English keywords: ~4k, emojis: ~2k
|
||||
- Russian keywords: ~5k, emojis: ~2k
|
||||
- Total keywords: ~9k, emojis: ~2k
|
||||
- Spanish keywords: ~5k, emojis: ~2k
|
||||
- Total keywords: ~13k, emojis: ~2k
|
||||
|
||||
3. **Fast Performance with Rust + WebAssembly**
|
||||
- The core emoji search is implemented in Rust, compiled to WebAssembly for speed.
|
||||
|
|
@ -58,6 +62,7 @@ This plugin provides a fast and intuitive way to insert emojis into your Obsidia
|
|||
- Emoji keywords data is adapted from Telegram’s translations:
|
||||
- [Russian](https://translations.telegram.org/ru/emoji)
|
||||
- [English](https://translations.telegram.org/en/emoji)
|
||||
- [Spanish](https://translations.telegram.org/es/emoji)
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
|||
10178
emoji_data_russian.json
10178
emoji_data_russian.json
File diff suppressed because it is too large
Load diff
5068
emoji_data_spanish.json
Normal file
5068
emoji_data_spanish.json
Normal file
File diff suppressed because it is too large
Load diff
188
main.ts
188
main.ts
|
|
@ -15,6 +15,7 @@ import {
|
|||
import init, { EmojiSearch } from './pkg/emoji_search_fixed.js';
|
||||
import emojiDataEnglish from "./emoji_data_english.json";
|
||||
import emojiDataRussian from "./emoji_data_russian.json";
|
||||
import emojiDataSpanish from "./emoji_data_spanish.json";
|
||||
import wasmDataUrl from "./pkg/emoji_search_bg.wasm";
|
||||
|
||||
// --- Type Definitions ---
|
||||
|
|
@ -28,27 +29,34 @@ interface InitOutput {
|
|||
}
|
||||
|
||||
interface EmojiSuggesterPluginSettings {
|
||||
defaultLanguage: string;
|
||||
chosenLanguages: string[];
|
||||
triggerChar: string;
|
||||
showKeywords: boolean;
|
||||
emojiPopularity: Record<string, number>;
|
||||
customEmojiMappings: Record<string, string>;
|
||||
lazyLoadWasm: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: EmojiSuggesterPluginSettings = {
|
||||
defaultLanguage: 'english',
|
||||
chosenLanguages: ['english'],
|
||||
triggerChar: ':',
|
||||
showKeywords: true,
|
||||
emojiPopularity: {},
|
||||
customEmojiMappings: {},
|
||||
lazyLoadWasm: true,
|
||||
};
|
||||
|
||||
const AVAILABLE_LANGUAGES = {
|
||||
english: 'English',
|
||||
russian: 'Russian',
|
||||
spanish: 'Spanish'
|
||||
};
|
||||
|
||||
const RANDOM_EMOJIS = ['😀', '😂', '🥰', '😎', '🤔', '👍', '🎉', '✨', '🔥', '❤️'];
|
||||
|
||||
async function loadEmojiData() {
|
||||
// The JSON is already inlined as objects
|
||||
return { english: emojiDataEnglish, russian: emojiDataRussian };
|
||||
return { english: emojiDataEnglish, russian: emojiDataRussian, spanish: emojiDataSpanish };
|
||||
}
|
||||
|
||||
// --- Main Plugin Class ---
|
||||
|
|
@ -59,27 +67,22 @@ export default class EmojiSuggesterPlugin extends Plugin {
|
|||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
try {
|
||||
// Initialize WASM module using a binary fetch approach
|
||||
await this.loadWasmModule();
|
||||
|
||||
// Create instance of EmojiSearch and initialize with emoji data
|
||||
this.emojiSearch = new EmojiSearch();
|
||||
const emojiData = await loadEmojiData();
|
||||
this.emojiSearch.initialize(JSON.stringify(emojiData));
|
||||
console.log('WASM initialized successfully with emoji data');
|
||||
// Register editor suggestions using our custom suggester
|
||||
this.registerEditorSuggest(new EmojiSuggester(this.app, this, this.emojiSearch));
|
||||
|
||||
// Uncomment to have a success notice:
|
||||
// new Notice('Emoji Suggester plugin loaded successfully');
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize WASM:', error);
|
||||
new Notice('Failed to initialize Emoji suggester plugin');
|
||||
if (!this.settings.lazyLoadWasm) {
|
||||
try {
|
||||
await this.loadWasmModule();
|
||||
this.emojiSearch = new EmojiSearch();
|
||||
await this.reinitializeEmojiSearch(this.emojiSearch);
|
||||
console.log('Emoji suggester plugin initialized successfully');
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize WASM:', error);
|
||||
new Notice('Failed to initialize Emoji suggester plugin');
|
||||
}
|
||||
}
|
||||
|
||||
// Add a settings tab for the plugin
|
||||
this.addSettingTab(new EmojiSuggesterSettingTab(this.app, this));
|
||||
this.registerEditorSuggest(new EmojiSuggester(this.app, this, this.emojiSearch));
|
||||
|
||||
// Create a ribbon icon with a tooltip
|
||||
const ribbonIconEl = this.addRibbonIcon('smile', 'Emoji suggester', () => {
|
||||
|
|
@ -98,6 +101,17 @@ export default class EmojiSuggesterPlugin extends Plugin {
|
|||
});
|
||||
}
|
||||
|
||||
async reinitializeEmojiSearch(emojiSearch: EmojiSearch): Promise<void> {
|
||||
const allEmojiData = await loadEmojiData();
|
||||
const filteredData: Record<string, any> = {};
|
||||
for (const lang of this.settings.chosenLanguages) {
|
||||
if (allEmojiData[lang as keyof typeof allEmojiData]) {
|
||||
filteredData[lang] = allEmojiData[lang as keyof typeof allEmojiData];
|
||||
}
|
||||
}
|
||||
emojiSearch.initialize(JSON.stringify(filteredData));
|
||||
}
|
||||
|
||||
async loadWasmModule(): Promise<InitOutput> {
|
||||
try {
|
||||
const wasmModule = await import('./pkg/emoji_search_fixed.js');
|
||||
|
|
@ -123,31 +137,61 @@ export default class EmojiSuggesterPlugin extends Plugin {
|
|||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = { ...DEFAULT_SETTINGS, ...(await this.loadData() || {}) };
|
||||
const loadedSettings = await this.loadData() || {};
|
||||
|
||||
// Migration: convert old defaultLanguage to chosenLanguages
|
||||
if (loadedSettings.defaultLanguage && !loadedSettings.chosenLanguages) {
|
||||
loadedSettings.chosenLanguages = [loadedSettings.defaultLanguage];
|
||||
delete loadedSettings.defaultLanguage;
|
||||
}
|
||||
|
||||
this.settings = { ...DEFAULT_SETTINGS, ...loadedSettings };
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
// Reinitialize emoji search when settings change
|
||||
if (this.emojiSearch) {
|
||||
await this.reinitializeEmojiSearch(this.emojiSearch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EmojiSuggester extends EditorSuggest<string> {
|
||||
private emojiSearch: EmojiSearch;
|
||||
private emojiSearch: EmojiSearch | null = null;
|
||||
private settings: EmojiSuggesterPluginSettings;
|
||||
private plugin: EmojiSuggesterPlugin;
|
||||
private wasmReady = false;
|
||||
private initPromise: Promise<void> | null = null;
|
||||
|
||||
constructor(app: App, plugin: EmojiSuggesterPlugin, emojiSearch: EmojiSearch) {
|
||||
constructor(app: App, plugin: EmojiSuggesterPlugin, emojiSearch: EmojiSearch | null = null) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
this.emojiSearch = emojiSearch;
|
||||
this.settings = plugin.settings;
|
||||
this.emojiSearch = emojiSearch;
|
||||
this.wasmReady = this.emojiSearch != null;
|
||||
}
|
||||
|
||||
private async ensureWasmReady(): Promise<void> {
|
||||
if (this.wasmReady) return;
|
||||
if (!this.plugin.settings.lazyLoadWasm) return;
|
||||
if (this.initPromise) return this.initPromise;
|
||||
|
||||
this.initPromise = (async () => {
|
||||
await this.plugin.loadWasmModule();
|
||||
this.emojiSearch = new EmojiSearch();
|
||||
await this.plugin.reinitializeEmojiSearch(this.emojiSearch);
|
||||
console.log('WASM initialized successfully with emoji data');
|
||||
this.wasmReady = true;
|
||||
})();
|
||||
return this.initPromise;
|
||||
}
|
||||
|
||||
onTrigger(cursor: EditorPosition, editor: Editor): EditorSuggestTriggerInfo | null {
|
||||
const line = editor.getLine(cursor.line);
|
||||
const subString = line.substring(0, cursor.ch);
|
||||
const escapedTrigger = this.settings.triggerChar.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const triggerRegex = new RegExp(`${escapedTrigger}([a-zA-Zа-яА-Я][a-zA-Zа-яА-Я ]*)$`);
|
||||
const triggerRegex = new RegExp(`${escapedTrigger}([a-zA-Zа-яА-Яñáéíóúü][a-zA-Zа-яА-Яñáéíóúü ]*)$`);
|
||||
const match = subString.match(triggerRegex);
|
||||
if (!match) return null;
|
||||
return {
|
||||
|
|
@ -156,15 +200,34 @@ class EmojiSuggester extends EditorSuggest<string> {
|
|||
query: match[1]?.trim() || ''
|
||||
};
|
||||
}
|
||||
getSuggestions(context: EditorSuggestContext): string[] {
|
||||
if (!context.query || !this.emojiSearch) return [];
|
||||
const language = /[а-яА-Я]/.test(context.query)
|
||||
? 'russian'
|
||||
: this.settings.defaultLanguage;
|
||||
|
||||
getSuggestions(context: EditorSuggestContext): string[] {
|
||||
if (!this.wasmReady || !this.emojiSearch) {
|
||||
this.ensureWasmReady();
|
||||
return [];
|
||||
}
|
||||
if (!context.query || !this.emojiSearch) return [];
|
||||
try {
|
||||
// Get results from the WASM module
|
||||
const results: [string, string[]][] = JSON.parse(this.emojiSearch.search(context.query, language));
|
||||
// Search across all chosen languages
|
||||
// Detect language based on query characters and filter chosen languages
|
||||
const detectedLanguages = this.settings.chosenLanguages.filter(lang => {
|
||||
if (/[а-яА-Я]/.test(context.query)) {
|
||||
return lang === 'russian';
|
||||
} else if (/[ñáéíóúü¿¡]/.test(context.query)) {
|
||||
return lang === 'spanish';
|
||||
} else {
|
||||
// Default to non-Cyrillic languages for Latin script
|
||||
return lang !== 'russian';
|
||||
}
|
||||
});
|
||||
|
||||
// If no languages match the detected script, fall back to all chosen languages
|
||||
const languagesToSearch = detectedLanguages.length > 0 ? detectedLanguages : this.settings.chosenLanguages;
|
||||
|
||||
// Search only the filtered languages
|
||||
const results: [string, string[]][] = JSON.parse(
|
||||
this.emojiSearch.search_multiple(context.query, JSON.stringify(languagesToSearch))
|
||||
);
|
||||
const emojiMap = new Map<string, string>();
|
||||
|
||||
// Add emojis from WASM search results
|
||||
|
|
@ -205,6 +268,7 @@ class EmojiSuggester extends EditorSuggest<string> {
|
|||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
renderSuggestion(value: string, el: HTMLElement): void {
|
||||
el.empty();
|
||||
if (this.settings.showKeywords) {
|
||||
|
|
@ -272,17 +336,62 @@ class EmojiSuggesterSettingTab extends PluginSettingTab {
|
|||
new Setting(containerEl).setName('General').setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Default language')
|
||||
.setDesc('Choose the default language for emoji search')
|
||||
.addDropdown(dropdown => dropdown
|
||||
.addOption('english', 'English')
|
||||
.addOption('russian', 'Russian')
|
||||
.setValue(this.plugin.settings.defaultLanguage)
|
||||
.setName('Lazy load emoji engine')
|
||||
.setDesc('Load the emoji search engine on first use instead of at startup. Reduces Obsidian startup time but adds a small delay the first time you trigger suggestions.')
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(this.plugin.settings.lazyLoadWasm)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.defaultLanguage = value;
|
||||
this.plugin.settings.lazyLoadWasm = value;
|
||||
await this.plugin.saveSettings();
|
||||
new Notice('Restart Obsidian for this change to take effect');
|
||||
}));
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Chosen languages')
|
||||
.setDesc('Select which languages to enable for emoji search')
|
||||
.setClass('emoji-language-setting');
|
||||
|
||||
// Create checkboxes for each available language
|
||||
const languageContainer = containerEl.createDiv('language-checkbox-container');
|
||||
|
||||
for (const [langCode, langName] of Object.entries(AVAILABLE_LANGUAGES)) {
|
||||
const checkboxContainer = languageContainer.createDiv('language-checkbox-item');
|
||||
|
||||
const checkbox = checkboxContainer.createEl('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.id = `lang-${langCode}`;
|
||||
checkbox.checked = this.plugin.settings.chosenLanguages.includes(langCode);
|
||||
|
||||
const label = checkboxContainer.createEl('label');
|
||||
label.setAttribute('for', `lang-${langCode}`);
|
||||
label.textContent = langName;
|
||||
|
||||
checkbox.addEventListener('change', async (e) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (target.checked) {
|
||||
if (!this.plugin.settings.chosenLanguages.includes(langCode)) {
|
||||
this.plugin.settings.chosenLanguages.push(langCode);
|
||||
}
|
||||
} else {
|
||||
const index = this.plugin.settings.chosenLanguages.indexOf(langCode);
|
||||
if (index > -1) {
|
||||
this.plugin.settings.chosenLanguages.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure at least one language is selected
|
||||
if (this.plugin.settings.chosenLanguages.length === 0) {
|
||||
this.plugin.settings.chosenLanguages.push('english');
|
||||
// Update the english checkbox
|
||||
const englishCheckbox = containerEl.querySelector('#lang-english') as HTMLInputElement;
|
||||
if (englishCheckbox) englishCheckbox.checked = true;
|
||||
new Notice('At least one language must be selected. English has been re-enabled.');
|
||||
}
|
||||
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
}
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Trigger character')
|
||||
.setDesc('Character that triggers the emoji suggestions')
|
||||
|
|
@ -408,10 +517,7 @@ class EmojiSuggesterSettingTab extends PluginSettingTab {
|
|||
countSpan.addClass('emoji-top-count');
|
||||
countSpan.textContent = `Used ${count} time${count !== 1 ? 's' : ''}`;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
private renderCustomMappings(containerEl: HTMLElement): void {
|
||||
containerEl.empty();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "tg-emoji-search",
|
||||
"name": "TG Emoji Search",
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.1",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Lets you type out emojis like in Telegram.",
|
||||
"author": "MarsBatya",
|
||||
|
|
|
|||
10
pkg/emoji_search.d.ts
vendored
10
pkg/emoji_search.d.ts
vendored
|
|
@ -4,7 +4,12 @@ export class EmojiSearch {
|
|||
free(): void;
|
||||
constructor();
|
||||
initialize(emoji_data_json: string): void;
|
||||
update_language(language_code: string, language_data_json: string): void;
|
||||
remove_language(language_code: string): void;
|
||||
search(query: string, language: string): string;
|
||||
search_multiple(query: string, languages_json: string): string;
|
||||
get_languages(): string;
|
||||
get_stats(): string;
|
||||
}
|
||||
|
||||
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
|
|
@ -14,7 +19,12 @@ export interface InitOutput {
|
|||
readonly __wbg_emojisearch_free: (a: number, b: number) => void;
|
||||
readonly emojisearch_new: () => number;
|
||||
readonly emojisearch_initialize: (a: number, b: number, c: number) => [number, number];
|
||||
readonly emojisearch_update_language: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
||||
readonly emojisearch_remove_language: (a: number, b: number, c: number) => void;
|
||||
readonly emojisearch_search: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
||||
readonly emojisearch_search_multiple: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
||||
readonly emojisearch_get_languages: (a: number) => [number, number];
|
||||
readonly emojisearch_get_stats: (a: number) => [number, number];
|
||||
readonly __wbindgen_export_0: WebAssembly.Table;
|
||||
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
||||
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
|
|
|
|||
|
|
@ -114,6 +114,28 @@ export class EmojiSearch {
|
|||
throw takeFromExternrefTable0(ret[0]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} language_code
|
||||
* @param {string} language_data_json
|
||||
*/
|
||||
update_language(language_code, language_data_json) {
|
||||
const ptr0 = passStringToWasm0(language_code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ptr1 = passStringToWasm0(language_data_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.emojisearch_update_language(this.__wbg_ptr, ptr0, len0, ptr1, len1);
|
||||
if (ret[1]) {
|
||||
throw takeFromExternrefTable0(ret[0]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} language_code
|
||||
*/
|
||||
remove_language(language_code) {
|
||||
const ptr0 = passStringToWasm0(language_code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
wasm.emojisearch_remove_language(this.__wbg_ptr, ptr0, len0);
|
||||
}
|
||||
/**
|
||||
* @param {string} query
|
||||
* @param {string} language
|
||||
|
|
@ -135,6 +157,57 @@ export class EmojiSearch {
|
|||
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} query
|
||||
* @param {string} languages_json
|
||||
* @returns {string}
|
||||
*/
|
||||
search_multiple(query, languages_json) {
|
||||
let deferred3_0;
|
||||
let deferred3_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(query, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ptr1 = passStringToWasm0(languages_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.emojisearch_search_multiple(this.__wbg_ptr, ptr0, len0, ptr1, len1);
|
||||
deferred3_0 = ret[0];
|
||||
deferred3_1 = ret[1];
|
||||
return getStringFromWasm0(ret[0], ret[1]);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
get_languages() {
|
||||
let deferred1_0;
|
||||
let deferred1_1;
|
||||
try {
|
||||
const ret = wasm.emojisearch_get_languages(this.__wbg_ptr);
|
||||
deferred1_0 = ret[0];
|
||||
deferred1_1 = ret[1];
|
||||
return getStringFromWasm0(ret[0], ret[1]);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
get_stats() {
|
||||
let deferred1_0;
|
||||
let deferred1_1;
|
||||
try {
|
||||
const ret = wasm.emojisearch_get_stats(this.__wbg_ptr);
|
||||
deferred1_0 = ret[0];
|
||||
deferred1_1 = ret[1];
|
||||
return getStringFromWasm0(ret[0], ret[1]);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function __wbg_load(module, imports) {
|
||||
|
|
|
|||
Binary file not shown.
5
pkg/emoji_search_bg.wasm.d.ts
vendored
5
pkg/emoji_search_bg.wasm.d.ts
vendored
|
|
@ -4,7 +4,12 @@ export const memory: WebAssembly.Memory;
|
|||
export const __wbg_emojisearch_free: (a: number, b: number) => void;
|
||||
export const emojisearch_new: () => number;
|
||||
export const emojisearch_initialize: (a: number, b: number, c: number) => [number, number];
|
||||
export const emojisearch_update_language: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
||||
export const emojisearch_remove_language: (a: number, b: number, c: number) => void;
|
||||
export const emojisearch_search: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
||||
export const emojisearch_search_multiple: (a: number, b: number, c: number, d: number, e: number) => [number, number];
|
||||
export const emojisearch_get_languages: (a: number) => [number, number];
|
||||
export const emojisearch_get_stats: (a: number) => [number, number];
|
||||
export const __wbindgen_export_0: WebAssembly.Table;
|
||||
export const __wbindgen_malloc: (a: number, b: number) => number;
|
||||
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
||||
|
|
|
|||
|
|
@ -114,6 +114,28 @@ export class EmojiSearch {
|
|||
throw takeFromExternrefTable0(ret[0]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} language_code
|
||||
* @param {string} language_data_json
|
||||
*/
|
||||
update_language(language_code, language_data_json) {
|
||||
const ptr0 = passStringToWasm0(language_code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ptr1 = passStringToWasm0(language_data_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.emojisearch_update_language(this.__wbg_ptr, ptr0, len0, ptr1, len1);
|
||||
if (ret[1]) {
|
||||
throw takeFromExternrefTable0(ret[0]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} language_code
|
||||
*/
|
||||
remove_language(language_code) {
|
||||
const ptr0 = passStringToWasm0(language_code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
wasm.emojisearch_remove_language(this.__wbg_ptr, ptr0, len0);
|
||||
}
|
||||
/**
|
||||
* @param {string} query
|
||||
* @param {string} language
|
||||
|
|
@ -135,6 +157,57 @@ export class EmojiSearch {
|
|||
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {string} query
|
||||
* @param {string} languages_json
|
||||
* @returns {string}
|
||||
*/
|
||||
search_multiple(query, languages_json) {
|
||||
let deferred3_0;
|
||||
let deferred3_1;
|
||||
try {
|
||||
const ptr0 = passStringToWasm0(query, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ptr1 = passStringToWasm0(languages_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.emojisearch_search_multiple(this.__wbg_ptr, ptr0, len0, ptr1, len1);
|
||||
deferred3_0 = ret[0];
|
||||
deferred3_1 = ret[1];
|
||||
return getStringFromWasm0(ret[0], ret[1]);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
get_languages() {
|
||||
let deferred1_0;
|
||||
let deferred1_1;
|
||||
try {
|
||||
const ret = wasm.emojisearch_get_languages(this.__wbg_ptr);
|
||||
deferred1_0 = ret[0];
|
||||
deferred1_1 = ret[1];
|
||||
return getStringFromWasm0(ret[0], ret[1]);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
get_stats() {
|
||||
let deferred1_0;
|
||||
let deferred1_1;
|
||||
try {
|
||||
const ret = wasm.emojisearch_get_stats(this.__wbg_ptr);
|
||||
deferred1_0 = ret[0];
|
||||
deferred1_1 = ret[1];
|
||||
return getStringFromWasm0(ret[0], ret[1]);
|
||||
} finally {
|
||||
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function __wbg_load(module, imports) {
|
||||
|
|
@ -240,6 +313,9 @@ async function __wbg_init(module_or_path) {
|
|||
}
|
||||
|
||||
if (typeof module_or_path === 'undefined') {
|
||||
// instead of this:
|
||||
// module_or_path = new URL('emoji_search_bg.wasm', import.meta.url);
|
||||
// we use this:
|
||||
module_or_path = new URL('emoji_search_bg.wasm');
|
||||
}
|
||||
const imports = __wbg_get_imports();
|
||||
|
|
|
|||
|
|
@ -11,8 +11,5 @@
|
|||
"types": "emoji_search.d.ts",
|
||||
"sideEffects": [
|
||||
"./snippets/*"
|
||||
],
|
||||
"devDependencies": {
|
||||
"esbuild-plugin-copy": "^2.1.1"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
167
src/lib.rs
167
src/lib.rs
|
|
@ -1,12 +1,11 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
// Main structure to hold emoji data
|
||||
// Main structure to hold emoji data for multiple languages
|
||||
#[wasm_bindgen]
|
||||
pub struct EmojiSearch {
|
||||
english_keywords: HashMap<String, Vec<String>>,
|
||||
russian_keywords: HashMap<String, Vec<String>>,
|
||||
// Maps language code to keyword->emojis mapping
|
||||
language_keywords: HashMap<String, HashMap<String, Vec<String>>>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
|
|
@ -15,44 +14,36 @@ impl EmojiSearch {
|
|||
#[wasm_bindgen(constructor)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
english_keywords: HashMap::new(),
|
||||
russian_keywords: HashMap::new(),
|
||||
language_keywords: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize with emoji data
|
||||
// Initialize with emoji data - can be called multiple times to reinitialize
|
||||
#[wasm_bindgen]
|
||||
pub fn initialize(&mut self, emoji_data_json: &str) -> Result<(), JsValue> {
|
||||
// Clear existing data for reinitialization
|
||||
self.language_keywords.clear();
|
||||
|
||||
let emoji_data: serde_json::Value = serde_json::from_str(emoji_data_json)
|
||||
.map_err(|e| JsValue::from_str(&format!("Failed to parse JSON: {}", e)))?;
|
||||
|
||||
// Process the data
|
||||
// Process the data - each top-level key is a language code
|
||||
if let Some(obj) = emoji_data.as_object() {
|
||||
for (key, value) in obj {
|
||||
if key == "english" {
|
||||
if let Some(english_map) = value.as_object() {
|
||||
for (keyword, emojis) in english_map {
|
||||
if let Some(emoji_str) = emojis.as_str() {
|
||||
let emoji_vec: Vec<String> = emoji_str
|
||||
.split_whitespace()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
self.english_keywords.insert(keyword.to_lowercase(), emoji_vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if key == "russian" {
|
||||
if let Some(russian_map) = value.as_object() {
|
||||
for (keyword, emojis) in russian_map {
|
||||
if let Some(emoji_str) = emojis.as_str() {
|
||||
let emoji_vec: Vec<String> = emoji_str
|
||||
.split_whitespace()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
self.russian_keywords.insert(keyword.to_lowercase(), emoji_vec);
|
||||
}
|
||||
for (language_code, language_data) in obj {
|
||||
if let Some(language_map) = language_data.as_object() {
|
||||
let mut keyword_map = HashMap::new();
|
||||
|
||||
for (keyword, emojis) in language_map {
|
||||
if let Some(emoji_str) = emojis.as_str() {
|
||||
let emoji_vec: Vec<String> = emoji_str
|
||||
.split_whitespace()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
keyword_map.insert(keyword.to_lowercase(), emoji_vec);
|
||||
}
|
||||
}
|
||||
|
||||
self.language_keywords.insert(language_code.to_lowercase(), keyword_map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -60,14 +51,46 @@ impl EmojiSearch {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// Search for emojis
|
||||
// Add or update a specific language's keywords (useful for runtime updates)
|
||||
#[wasm_bindgen]
|
||||
pub fn update_language(&mut self, language_code: &str, language_data_json: &str) -> Result<(), JsValue> {
|
||||
let language_data: serde_json::Value = serde_json::from_str(language_data_json)
|
||||
.map_err(|e| JsValue::from_str(&format!("Failed to parse JSON: {}", e)))?;
|
||||
|
||||
if let Some(language_map) = language_data.as_object() {
|
||||
let mut keyword_map = HashMap::new();
|
||||
|
||||
for (keyword, emojis) in language_map {
|
||||
if let Some(emoji_str) = emojis.as_str() {
|
||||
let emoji_vec: Vec<String> = emoji_str
|
||||
.split_whitespace()
|
||||
.map(|s| s.to_string())
|
||||
.collect();
|
||||
keyword_map.insert(keyword.to_lowercase(), emoji_vec);
|
||||
}
|
||||
}
|
||||
|
||||
self.language_keywords.insert(language_code.to_lowercase(), keyword_map);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Remove a language entirely
|
||||
#[wasm_bindgen]
|
||||
pub fn remove_language(&mut self, language_code: &str) {
|
||||
self.language_keywords.remove(&language_code.to_lowercase());
|
||||
}
|
||||
|
||||
// Search for emojis in a specific language
|
||||
#[wasm_bindgen]
|
||||
pub fn search(&self, query: &str, language: &str) -> String {
|
||||
let query = query.to_lowercase();
|
||||
let map = if language == "russian" {
|
||||
&self.russian_keywords
|
||||
} else {
|
||||
&self.english_keywords
|
||||
let language = language.to_lowercase();
|
||||
|
||||
let map = match self.language_keywords.get(&language) {
|
||||
Some(map) => map,
|
||||
None => return "[]".to_string(), // Language not found
|
||||
};
|
||||
|
||||
let mut results: Vec<(String, Vec<String>)> = Vec::new();
|
||||
|
|
@ -91,4 +114,74 @@ impl EmojiSearch {
|
|||
"[]".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search across multiple languages (useful for multilingual users)
|
||||
#[wasm_bindgen]
|
||||
pub fn search_multiple(&self, query: &str, languages_json: &str) -> String {
|
||||
let query = query.to_lowercase();
|
||||
|
||||
// Parse the languages array
|
||||
let languages: Result<Vec<String>, _> = serde_json::from_str(languages_json);
|
||||
let languages = match languages {
|
||||
Ok(langs) => langs,
|
||||
Err(_) => return "[]".to_string(),
|
||||
};
|
||||
|
||||
let mut all_results: Vec<(String, Vec<String>)> = Vec::new();
|
||||
let mut seen_keywords = std::collections::HashSet::new();
|
||||
|
||||
for language in languages {
|
||||
let language = language.to_lowercase();
|
||||
if let Some(map) = self.language_keywords.get(&language) {
|
||||
// First look for exact matches
|
||||
if let Some(emojis) = map.get(&query) {
|
||||
if !seen_keywords.contains(&query) {
|
||||
all_results.push((query.clone(), emojis.clone()));
|
||||
seen_keywords.insert(query.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// Then look for prefix matches
|
||||
for (keyword, emojis) in map {
|
||||
if keyword.starts_with(&query) && keyword != &query && !seen_keywords.contains(keyword) {
|
||||
all_results.push((keyword.clone(), emojis.clone()));
|
||||
seen_keywords.insert(keyword.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert results to JSON
|
||||
if let Ok(json) = serde_json::to_string(&all_results) {
|
||||
json
|
||||
} else {
|
||||
"[]".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
// Get list of available languages
|
||||
#[wasm_bindgen]
|
||||
pub fn get_languages(&self) -> String {
|
||||
let languages: Vec<&String> = self.language_keywords.keys().collect();
|
||||
if let Ok(json) = serde_json::to_string(&languages) {
|
||||
json
|
||||
} else {
|
||||
"[]".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
// Get statistics about loaded data
|
||||
#[wasm_bindgen]
|
||||
pub fn get_stats(&self) -> String {
|
||||
let mut stats = HashMap::new();
|
||||
for (lang, keywords) in &self.language_keywords {
|
||||
stats.insert(lang, keywords.len());
|
||||
}
|
||||
|
||||
if let Ok(json) = serde_json::to_string(&stats) {
|
||||
json
|
||||
} else {
|
||||
"{}".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
42
styles.css
42
styles.css
|
|
@ -113,4 +113,46 @@
|
|||
margin-right: var(--emoji-margin-right);
|
||||
min-width: 2.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.emoji-language-setting {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.language-checkbox-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 6px;
|
||||
background: var(--background-secondary);
|
||||
}
|
||||
|
||||
.language-checkbox-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
|
||||
.language-checkbox-item input[type="checkbox"] {
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.language-checkbox-item label {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-normal);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.language-checkbox-item:hover {
|
||||
background: var(--background-modifier-hover);
|
||||
border-radius: 4px;
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin: 0 -0.5rem;
|
||||
}
|
||||
Loading…
Reference in a new issue