diff --git a/README.md b/README.md index 3acf5d9..e2d5481 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,11 @@ This plugin provides a fast and intuitive way to insert emojis into your Obsidia - The core emoji search is implemented in Rust, compiled to WebAssembly for speed. - TypeScript provides type safety and works seamlessly with the Obsidian plugin API. -4. **Customizable Settings** +4. **Custom emoji mappings** + - Feel free to add or delete your own keyword, when you're not satisfied with existing ones + - You can also reset these mappings easily + +5. **Customizable Settings** - **Default Language**: Choose which language’s keywords to use. - **Trigger Character**: Change the character that initiates the emoji search (default is `:`). - **Show Keywords in Suggestions**: Toggle the display of each emoji’s keyword in the suggestion list. diff --git a/main.ts b/main.ts index 3bef21c..b7e764a 100644 --- a/main.ts +++ b/main.ts @@ -32,13 +32,15 @@ interface EmojiSuggesterPluginSettings { triggerChar: string; showKeywords: boolean; emojiPopularity: Record; + customEmojiMappings: Record; } const DEFAULT_SETTINGS: EmojiSuggesterPluginSettings = { defaultLanguage: 'english', triggerChar: ':', - showKeywords: true, // default to showing keywords - emojiPopularity: {}, // empty ranking + showKeywords: true, + emojiPopularity: {}, + customEmojiMappings: {}, }; @@ -154,7 +156,6 @@ class EmojiSuggester extends EditorSuggest { query: match[1]?.trim() || '' }; } - getSuggestions(context: EditorSuggestContext): string[] { if (!context.query || !this.emojiSearch) return []; const language = /[а-яА-Я]/.test(context.query) @@ -162,8 +163,11 @@ class EmojiSuggester extends EditorSuggest { : this.settings.defaultLanguage; try { + // Get results from the WASM module const results: [string, string[]][] = JSON.parse(this.emojiSearch.search(context.query, language)); const emojiMap = new Map(); + + // Add emojis from WASM search results for (const [keyword, emojis] of results) { for (const emoji of emojis) { if (!emojiMap.has(emoji)) { @@ -172,12 +176,21 @@ class EmojiSuggester extends EditorSuggest { } } + // Add custom emoji mappings that match the query + const query = context.query.toLowerCase(); + for (const [keyword, emoji] of Object.entries(this.plugin.settings.customEmojiMappings)) { + if (keyword.toLowerCase().includes(query)) { + emojiMap.set(emoji, keyword); + } + } + const emojisWithKeywords = Array.from(emojiMap.entries()); + // Sort by popularity emojisWithKeywords.sort((a, b) => { const popA = this.plugin.settings.emojiPopularity[a[0]] || 0; const popB = this.plugin.settings.emojiPopularity[b[0]] || 0; - return popB - popA; // Sort in descending order + return popB - popA; }); if (this.settings.showKeywords) { @@ -192,7 +205,6 @@ class EmojiSuggester extends EditorSuggest { return []; } } - renderSuggestion(value: string, el: HTMLElement): void { el.empty(); if (this.settings.showKeywords) { @@ -257,7 +269,7 @@ class EmojiSuggesterSettingTab extends PluginSettingTab { display(): void { const { containerEl } = this; containerEl.empty(); - containerEl.createEl('h2', { text: 'Emoji Suggester Settings' }); + containerEl.createEl('h2', { text: 'Advanced' }); new Setting(containerEl) .setName('Default Language') @@ -292,6 +304,64 @@ class EmojiSuggesterSettingTab extends PluginSettingTab { this.plugin.settings.showKeywords = value; await this.plugin.saveSettings(); })); + + // Add section for custom emoji mappings + containerEl.createEl('h2', { text: 'Custom Emoji Mappings' }); + + // Display existing custom mappings + const customMappingsContainer = containerEl.createDiv('custom-emoji-mappings-container'); + this.renderCustomMappings(customMappingsContainer); + + // Add new mapping controls + const newMappingContainer = containerEl.createDiv('new-emoji-mapping-container'); + + const keywordInput = newMappingContainer.createEl('input'); + keywordInput.type = 'text'; + keywordInput.placeholder = 'Keyword'; + keywordInput.classList.add('custom-emoji-keyword-input'); + + const emojiInput = newMappingContainer.createEl('input'); + emojiInput.type = 'text'; + emojiInput.placeholder = 'Emoji'; + emojiInput.classList.add('custom-emoji-input'); + + const addButton = newMappingContainer.createEl('button'); + addButton.setText('Add'); + addButton.classList.add('custom-emoji-add-button'); + addButton.addEventListener('click', async () => { + const keyword = keywordInput.value.trim(); + const emoji = emojiInput.value.trim(); + + if (keyword && emoji) { + this.plugin.settings.customEmojiMappings[keyword] = emoji; + await this.plugin.saveSettings(); + + // Clear inputs + keywordInput.value = ''; + emojiInput.value = ''; + + // Refresh the mappings display + this.renderCustomMappings(customMappingsContainer); + new Notice(`Added custom mapping: ${keyword} → ${emoji}`); + } else { + new Notice('Both keyword and emoji are required'); + } + }); + + // Reset Custom Mappings button + new Setting(containerEl) + .setName('Reset Custom Emoji Mappings') + .setDesc('Remove all your custom emoji mappings') + .addButton(button => button + .setButtonText('Reset') + .onClick(async () => { + this.plugin.settings.customEmojiMappings = {}; + await this.plugin.saveSettings(); + this.renderCustomMappings(customMappingsContainer); + new Notice('Custom emoji mappings have been reset'); + })); + + new Setting(containerEl) .setName('Reset Emoji Popularity') .setDesc('Reset your emoji usage statistics') @@ -313,7 +383,7 @@ class EmojiSuggesterSettingTab extends PluginSettingTab { // Display top 10 most used emojis const topEmojisEl = containerEl.createDiv(); topEmojisEl.addClass('emoji-popularity-stats'); - topEmojisEl.createEl('h3', { text: 'Your Most Used Emojis' }); + topEmojisEl.createEl('h2', { text: 'Your Most Used Emojis' }); const popularityEntries = Object.entries(this.plugin.settings.emojiPopularity); popularityEntries.sort((a, b) => b[1] - a[1]); @@ -338,6 +408,47 @@ class EmojiSuggesterSettingTab extends PluginSettingTab { countSpan.addClass('emoji-top-count'); countSpan.textContent = `Used ${count} time${count !== 1 ? 's' : ''}`; } + + + } + + } + private renderCustomMappings(containerEl: HTMLElement): void { + containerEl.empty(); + + const mappings = this.plugin.settings.customEmojiMappings; + const entries = Object.entries(mappings); + + if (entries.length === 0) { + containerEl.createEl('p', { text: 'No custom emoji mappings yet' }); + return; + } + + const table = containerEl.createEl('table'); + table.classList.add('custom-emoji-table'); + + // Create header row + const headerRow = table.createEl('tr'); + headerRow.createEl('th', { text: 'Keyword' }); + headerRow.createEl('th', { text: 'Emoji' }); + headerRow.createEl('th', { text: 'Action' }); + + // Create rows for each mapping + for (const [keyword, emoji] of entries) { + const row = table.createEl('tr'); + row.createEl('td', { text: keyword }); + row.createEl('td', { text: emoji }); + + const actionCell = row.createEl('td'); + const deleteButton = actionCell.createEl('button'); + deleteButton.setText('Delete'); + deleteButton.classList.add('custom-emoji-delete-button'); + deleteButton.addEventListener('click', async () => { + delete this.plugin.settings.customEmojiMappings[keyword]; + await this.plugin.saveSettings(); + this.renderCustomMappings(containerEl); + new Notice(`Removed custom mapping: ${keyword}`); + }); } } } \ No newline at end of file diff --git a/styles.css b/styles.css index 5d29089..2d1c974 100644 --- a/styles.css +++ b/styles.css @@ -1,43 +1,116 @@ /* Emoji Suggester Plugin Styles */ -/* Suggestion items styling */ +/* Common variables */ +:root { + --emoji-font-size: 1.5em; + --emoji-margin-right: 0.625rem; + --standard-spacing: 0.625rem; + --container-margin: 1rem; + --border-color: var(--background-modifier-border); +} + +/* === Suggestion Popup Styles === */ .emoji-suggestion-item { display: flex; align-items: center; + padding: 0.25rem 0; } .emoji-suggestion-emoji { - font-size: 1.5em; - margin-right: 10px; + font-size: var(--emoji-font-size); + margin-right: var(--emoji-margin-right); +} + +.emoji-suggestion-emoji:only-child { + margin: 0 var(--standard-spacing); } .emoji-suggestion-keyword { opacity: 0.7; } -/* Settings tab styling */ +/* === Settings Tab Styles === */ + + +/* Custom Emoji Mappings Section */ +.custom-emoji-mappings-container { + margin-bottom: var(--container-margin); + max-height: 12.5rem; + overflow-y: auto; + border: 1px solid var(--border-color); + border-radius: 0.25rem; +} + +.custom-emoji-table { + width: 100%; + border-collapse: collapse; +} + +.custom-emoji-table th, +.custom-emoji-table td { + padding: 0.5rem; + text-align: left; + border-bottom: 1px solid var(--border-color); +} + +/* New Emoji Mapping Controls */ +.new-emoji-mapping-container { + display: flex; + gap: 0.5rem; + margin-bottom: var(--container-margin); + align-items: center; +} + +.custom-emoji-keyword-input, +.custom-emoji-input { + flex: 1; + padding: 0.5rem; + border: 1px solid var(--border-color); + border-radius: 0.25rem; +} + +.custom-emoji-add-button, +.custom-emoji-delete-button { + padding: 0.4rem 0.6rem; + cursor: pointer; + background-color: var(--interactive-accent); + color: var(--text-on-accent); + border: none; + border-radius: 0.25rem; + transition: background-color 0.2s ease; +} + +.custom-emoji-add-button:hover, +.custom-emoji-delete-button:hover { + background-color: var(--interactive-accent-hover); +} + +.custom-emoji-delete-button { + background-color: var(--text-error); +} + +.custom-emoji-delete-button:hover { + background-color: var(--text-error-hover); +} + +/* Emoji Popularity Section */ .emoji-popularity-stats { - margin-top: 20px; + margin-top: var(--container-margin); } .emoji-top-list { - margin-top: 10px; + margin-top: var(--standard-spacing); } .emoji-top-item { display: flex; align-items: center; - padding: 5px 0; + padding: 0.3125rem 0; } .emoji-top-symbol { - font-size: 1.5em; - margin-right: 10px; - min-width: 40px; + font-size: var(--emoji-font-size); + margin-right: var(--emoji-margin-right); + min-width: 2.5rem; text-align: center; -} - -/* When showing only emoji without keywords */ -.emoji-suggestion-emoji:only-child { - margin: 0 10px; } \ No newline at end of file