2025-03-08 16:08:13 +00:00
import {
App ,
Editor ,
EditorPosition ,
EditorSuggest ,
EditorSuggestContext ,
EditorSuggestTriggerInfo ,
MarkdownView ,
Notice ,
Plugin ,
PluginSettingTab ,
Setting
} from 'obsidian' ;
2025-03-09 15:20:25 +00:00
2025-03-08 16:08:13 +00:00
import init , { EmojiSearch } from './pkg/emoji_search_fixed.js' ;
2025-03-09 15:20:25 +00:00
import emojiDataEnglish from "./emoji_data_english.json" ;
import emojiDataRussian from "./emoji_data_russian.json" ;
2025-09-28 19:40:04 +00:00
import emojiDataSpanish from "./emoji_data_spanish.json" ;
2025-03-09 15:20:25 +00:00
import wasmDataUrl from "./pkg/emoji_search_bg.wasm" ;
2025-03-08 16:08:13 +00:00
// --- Type Definitions ---
2025-03-08 15:51:35 +00:00
interface EmojiSearchWasm {
new ( ) : EmojiSearch ;
}
interface InitOutput {
EmojiSearch : EmojiSearchWasm ;
}
interface EmojiSuggesterPluginSettings {
2025-09-28 19:40:04 +00:00
chosenLanguages : string [ ] ;
2025-03-08 15:51:35 +00:00
triggerChar : string ;
2025-03-09 08:47:39 +00:00
showKeywords : boolean ;
emojiPopularity : Record < string , number > ;
2025-03-10 11:46:55 +00:00
customEmojiMappings : Record < string , string > ;
2026-06-01 11:26:54 +00:00
lazyLoadWasm : boolean ;
2025-03-08 15:51:35 +00:00
}
const DEFAULT_SETTINGS : EmojiSuggesterPluginSettings = {
2025-09-28 19:40:04 +00:00
chosenLanguages : [ 'english' ] ,
2025-03-08 16:39:32 +00:00
triggerChar : ':' ,
2025-03-10 11:46:55 +00:00
showKeywords : true ,
emojiPopularity : { } ,
customEmojiMappings : { } ,
2026-06-01 11:26:54 +00:00
lazyLoadWasm : true ,
2025-03-08 16:08:13 +00:00
} ;
2025-09-28 19:40:04 +00:00
const AVAILABLE_LANGUAGES = {
english : 'English' ,
russian : 'Russian' ,
spanish : 'Spanish'
} ;
2025-03-08 16:39:32 +00:00
2025-03-08 16:08:13 +00:00
const RANDOM_EMOJIS = [ '😀' , '😂' , '🥰' , '😎' , '🤔' , '👍' , '🎉' , '✨' , '🔥' , '❤️' ] ;
2025-03-09 15:20:25 +00:00
async function loadEmojiData() {
// The JSON is already inlined as objects
2025-09-28 19:40:04 +00:00
return { english : emojiDataEnglish , russian : emojiDataRussian , spanish : emojiDataSpanish } ;
2025-03-08 15:51:35 +00:00
}
2025-03-08 16:08:13 +00:00
// --- Main Plugin Class ---
export default class EmojiSuggesterPlugin extends Plugin {
2025-03-08 15:51:35 +00:00
settings : EmojiSuggesterPluginSettings ;
private emojiSearch : EmojiSearch | null = null ;
async onload() {
await this . loadSettings ( ) ;
2026-06-01 11:26:54 +00:00
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' ) ;
}
}
2025-03-08 16:08:13 +00:00
// Add a settings tab for the plugin
2025-03-08 15:51:35 +00:00
this . addSettingTab ( new EmojiSuggesterSettingTab ( this . app , this ) ) ;
2026-06-01 11:26:54 +00:00
this . registerEditorSuggest ( new EmojiSuggester ( this . app , this , this . emojiSearch ) ) ;
2025-03-08 15:51:35 +00:00
2025-03-08 16:08:13 +00:00
// Create a ribbon icon with a tooltip
2025-03-24 11:50:29 +00:00
const ribbonIconEl = this . addRibbonIcon ( 'smile' , 'Emoji suggester' , ( ) = > {
2025-03-08 16:08:13 +00:00
new Notice ( ` Type " ${ this . settings . triggerChar } " followed by a keyword to suggest emojis ` ) ;
2025-03-08 15:51:35 +00:00
} ) ;
ribbonIconEl . addClass ( 'emoji-suggester-ribbon-class' ) ;
2025-03-08 16:08:13 +00:00
// Add command to insert a random emoji
2025-03-08 15:51:35 +00:00
this . addCommand ( {
id : 'insert-random-emoji' ,
name : 'Insert random emoji' ,
2025-03-08 16:08:13 +00:00
editorCallback : ( editor : Editor ) = > {
const randomEmoji = RANDOM_EMOJIS [ Math . floor ( Math . random ( ) * RANDOM_EMOJIS . length ) ] ;
2025-03-08 15:51:35 +00:00
editor . replaceSelection ( randomEmoji ) ;
}
} ) ;
2026-06-01 11:02:22 +00:00
}
2025-09-28 19:40:04 +00:00
2026-06-01 11:02:22 +00:00
async reinitializeEmojiSearch ( emojiSearch : EmojiSearch ) : Promise < void > {
2025-09-28 19:40:04 +00:00
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 ] ;
}
}
2026-06-01 11:02:22 +00:00
emojiSearch . initialize ( JSON . stringify ( filteredData ) ) ;
2025-09-28 19:40:04 +00:00
}
2025-03-08 15:51:35 +00:00
async loadWasmModule ( ) : Promise < InitOutput > {
try {
const wasmModule = await import ( './pkg/emoji_search_fixed.js' ) ;
2025-03-09 15:20:25 +00:00
const wasmResponse = await fetch ( wasmDataUrl as unknown as string ) ; ;
2025-03-08 15:51:35 +00:00
if ( ! wasmResponse . ok ) {
2025-03-09 15:20:25 +00:00
throw new Error ( ` Failed to fetch WASM from ${ wasmDataUrl } : ${ wasmResponse . statusText } ` ) ;
2025-03-08 15:51:35 +00:00
}
const wasmBytes = await wasmResponse . arrayBuffer ( ) ;
return await wasmModule . default ( wasmBytes ) ;
} catch ( error ) {
console . error ( 'Error loading WASM module:' , error ) ;
throw error ;
}
}
onunload() {
2025-03-09 13:07:16 +00:00
// freeing WASM resources
if ( this . emojiSearch ) {
this . emojiSearch . free ( ) ;
}
2025-03-08 15:51:35 +00:00
}
async loadSettings() {
2025-09-28 19:40:04 +00:00
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 } ;
2025-03-08 15:51:35 +00:00
}
async saveSettings() {
await this . saveData ( this . settings ) ;
2025-09-28 19:40:04 +00:00
// Reinitialize emoji search when settings change
if ( this . emojiSearch ) {
2026-06-01 11:02:22 +00:00
await this . reinitializeEmojiSearch ( this . emojiSearch ) ;
2025-09-28 19:40:04 +00:00
}
2025-03-08 15:51:35 +00:00
}
}
class EmojiSuggester extends EditorSuggest < string > {
2026-06-01 11:02:22 +00:00
private emojiSearch : EmojiSearch | null = null ;
2025-03-08 15:51:35 +00:00
private settings : EmojiSuggesterPluginSettings ;
2025-03-09 08:47:39 +00:00
private plugin : EmojiSuggesterPlugin ;
2026-06-01 11:02:22 +00:00
private wasmReady = false ;
private initPromise : Promise < void > | null = null ;
2025-03-08 15:51:35 +00:00
2026-06-01 11:26:54 +00:00
constructor ( app : App , plugin : EmojiSuggesterPlugin , emojiSearch : EmojiSearch | null = null ) {
2025-03-08 15:51:35 +00:00
super ( app ) ;
2025-03-09 08:47:39 +00:00
this . plugin = plugin ;
this . settings = plugin . settings ;
2026-06-01 11:26:54 +00:00
this . emojiSearch = emojiSearch ;
this . wasmReady = this . emojiSearch != null ;
2025-03-08 15:51:35 +00:00
}
2026-06-01 11:02:22 +00:00
private async ensureWasmReady ( ) : Promise < void > {
if ( this . wasmReady ) return ;
2026-06-01 11:26:54 +00:00
if ( ! this . plugin . settings . lazyLoadWasm ) return ;
2026-06-01 11:02:22 +00:00
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 ;
}
2025-03-08 16:08:13 +00:00
onTrigger ( cursor : EditorPosition , editor : Editor ) : EditorSuggestTriggerInfo | null {
2025-03-08 15:51:35 +00:00
const line = editor . getLine ( cursor . line ) ;
const subString = line . substring ( 0 , cursor . ch ) ;
const escapedTrigger = this . settings . triggerChar . replace ( /[.*+?^${}()|[\]\\]/g , '\\$&' ) ;
2025-09-28 19:40:04 +00:00
const triggerRegex = new RegExp ( ` ${ escapedTrigger } ([a-zA-Zа -яА-Яñáéíóúü][a-zA-Zа -яА-Яñáéíóúü ]*) $ ` ) ;
2025-03-08 15:51:35 +00:00
const match = subString . match ( triggerRegex ) ;
if ( ! match ) return null ;
return {
2025-03-08 16:08:13 +00:00
start : { line : cursor.line , ch : subString.lastIndexOf ( this . settings . triggerChar ) } ,
2025-03-08 15:51:35 +00:00
end : cursor ,
2025-03-08 16:08:13 +00:00
query : match [ 1 ] ? . trim ( ) || ''
2025-03-08 15:51:35 +00:00
} ;
}
2025-09-28 19:40:04 +00:00
2025-03-08 15:51:35 +00:00
getSuggestions ( context : EditorSuggestContext ) : string [ ] {
2026-06-01 11:02:22 +00:00
if ( ! this . wasmReady || ! this . emojiSearch ) {
this . ensureWasmReady ( ) ;
return [ ] ;
}
2025-03-08 16:08:13 +00:00
if ( ! context . query || ! this . emojiSearch ) return [ ] ;
2025-03-08 15:51:35 +00:00
try {
2025-09-28 19:40:04 +00:00
// 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 ) )
) ;
2025-03-08 15:51:35 +00:00
const emojiMap = new Map < string , string > ( ) ;
2025-03-10 11:46:55 +00:00
// Add emojis from WASM search results
2025-03-08 15:51:35 +00:00
for ( const [ keyword , emojis ] of results ) {
for ( const emoji of emojis ) {
if ( ! emojiMap . has ( emoji ) ) {
emojiMap . set ( emoji , keyword ) ;
}
}
}
2025-03-10 11:46:55 +00:00
// 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 ) ;
}
}
2025-03-09 08:47:39 +00:00
const emojisWithKeywords = Array . from ( emojiMap . entries ( ) ) ;
2025-03-10 11:46:55 +00:00
// Sort by popularity
2025-03-09 08:47:39 +00:00
emojisWithKeywords . sort ( ( a , b ) = > {
const popA = this . plugin . settings . emojiPopularity [ a [ 0 ] ] || 0 ;
const popB = this . plugin . settings . emojiPopularity [ b [ 0 ] ] || 0 ;
2025-03-10 11:46:55 +00:00
return popB - popA ;
2025-03-09 08:47:39 +00:00
} ) ;
2025-03-08 16:39:32 +00:00
if ( this . settings . showKeywords ) {
2025-03-09 08:47:39 +00:00
return emojisWithKeywords . map (
2025-03-08 16:39:32 +00:00
( [ emoji , keyword ] ) = > ` ${ emoji } ( ${ keyword } ) `
) ;
} else {
2025-03-09 08:47:39 +00:00
return emojisWithKeywords . map ( ( [ emoji , _ ] ) = > emoji ) ;
2025-03-08 16:39:32 +00:00
}
2025-03-08 15:51:35 +00:00
} catch ( error ) {
console . error ( 'Error in emoji search:' , error ) ;
return [ ] ;
}
}
2025-09-28 19:40:04 +00:00
2025-03-08 15:51:35 +00:00
renderSuggestion ( value : string , el : HTMLElement ) : void {
2025-03-10 10:42:00 +00:00
el . empty ( ) ;
2025-03-08 16:39:32 +00:00
if ( this . settings . showKeywords ) {
// Render emoji with keyword (vertical list)
const matches = value . match ( /^(.*) \((.*)\)$/ ) ;
if ( matches ) {
const [ , emoji , keyword ] = matches ;
const container = document . createElement ( 'div' ) ;
container . addClass ( 'emoji-suggestion-item' ) ;
const emojiSpan = document . createElement ( 'span' ) ;
emojiSpan . addClass ( 'emoji-suggestion-emoji' ) ;
emojiSpan . textContent = emoji ;
const keywordSpan = document . createElement ( 'span' ) ;
keywordSpan . addClass ( 'emoji-suggestion-keyword' ) ;
keywordSpan . textContent = keyword ;
container . appendChild ( emojiSpan ) ;
container . appendChild ( keywordSpan ) ;
el . appendChild ( container ) ;
} else {
el . setText ( value ) ;
}
} else {
2025-03-09 13:07:16 +00:00
// Render only the emoji
2025-03-08 15:51:35 +00:00
const emojiSpan = document . createElement ( 'span' ) ;
emojiSpan . addClass ( 'emoji-suggestion-emoji' ) ;
2025-03-08 16:39:32 +00:00
emojiSpan . textContent = value ;
el . appendChild ( emojiSpan ) ;
2025-03-08 15:51:35 +00:00
}
}
2025-03-08 16:39:32 +00:00
selectSuggestion ( value : string , evt : MouseEvent | KeyboardEvent ) : void {
// When only emojis are shown, the value is the emoji itself.
const emoji = this . settings . showKeywords ? value . split ( ' ' ) [ 0 ] : value ;
2025-03-08 15:51:35 +00:00
const { start , end } = this . context ! ;
this . context ! . editor . replaceRange ( emoji , start , end ) ;
2025-03-09 08:47:39 +00:00
this . updateEmojiPopularity ( emoji ) ;
2025-03-08 15:51:35 +00:00
}
2025-03-08 16:39:32 +00:00
2025-03-09 08:47:39 +00:00
private updateEmojiPopularity ( emoji : string ) : void {
const settings = this . plugin . settings ;
if ( ! settings . emojiPopularity [ emoji ] ) {
settings . emojiPopularity [ emoji ] = 0 ;
}
settings . emojiPopularity [ emoji ] ++ ;
this . plugin . saveSettings ( ) ;
}
2025-03-08 15:51:35 +00:00
}
class EmojiSuggesterSettingTab extends PluginSettingTab {
plugin : EmojiSuggesterPlugin ;
constructor ( app : App , plugin : EmojiSuggesterPlugin ) {
super ( app , plugin ) ;
this . plugin = plugin ;
}
display ( ) : void {
const { containerEl } = this ;
containerEl . empty ( ) ;
2025-03-24 12:02:22 +00:00
new Setting ( containerEl ) . setName ( 'General' ) . setHeading ( ) ;
2025-03-08 15:51:35 +00:00
2026-06-01 11:26:54 +00:00
new Setting ( containerEl )
. 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 . lazyLoadWasm = value ;
await this . plugin . saveSettings ( ) ;
new Notice ( 'Restart Obsidian for this change to take effect' ) ;
} ) ) ;
2025-03-08 15:51:35 +00:00
new Setting ( containerEl )
2025-09-28 19:40:04 +00:00
. 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 ( ) ;
} ) ;
}
2025-03-08 15:51:35 +00:00
new Setting ( containerEl )
2025-03-24 11:50:29 +00:00
. setName ( 'Trigger character' )
2025-03-08 15:51:35 +00:00
. setDesc ( 'Character that triggers the emoji suggestions' )
. addText ( text = > text
. setPlaceholder ( ':' )
. setValue ( this . plugin . settings . triggerChar )
. onChange ( async ( value ) = > {
if ( value ) {
this . plugin . settings . triggerChar = value ;
await this . plugin . saveSettings ( ) ;
}
} ) ) ;
2025-03-08 16:39:32 +00:00
new Setting ( containerEl )
. setName ( 'Show keywords in suggestions' )
. setDesc ( 'Toggle whether to display the keyword alongside the emoji' )
. addToggle ( toggle = > toggle
. setValue ( this . plugin . settings . showKeywords )
. onChange ( async ( value ) = > {
this . plugin . settings . showKeywords = value ;
await this . plugin . saveSettings ( ) ;
} ) ) ;
2025-03-10 11:46:55 +00:00
// Add section for custom emoji mappings
2025-03-24 12:02:22 +00:00
new Setting ( containerEl ) . setName ( 'Custom emoji mappings' ) . setHeading ( ) ;
2025-03-10 11:46:55 +00:00
// 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 )
2025-03-24 11:50:29 +00:00
. setName ( 'Reset custom emoji mappings' )
2025-03-10 11:46:55 +00:00
. 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' ) ;
} ) ) ;
2025-03-09 08:47:39 +00:00
new Setting ( containerEl )
2025-03-24 11:50:29 +00:00
. setName ( 'Reset emoji popularity' )
2025-03-09 08:47:39 +00:00
. setDesc ( 'Reset your emoji usage statistics' )
. addButton ( button = > button
. setButtonText ( 'Reset' )
. onClick ( async ( ) = > {
this . plugin . settings . emojiPopularity = { } ;
await this . plugin . saveSettings ( ) ;
new Notice ( 'Emoji popularity statistics have been reset' ) ;
// Reset the rendered top 10 list
const topEmojisEl = containerEl . querySelector ( '.emoji-popularity-stats' ) ;
if ( topEmojisEl ) {
topEmojisEl . removeChild ( topEmojisEl . querySelector ( '.emoji-top-list' ) ! ) ;
topEmojisEl . createEl ( 'p' , { text : 'No emojis used yet' } ) ;
}
} ) ) ;
// Display top 10 most used emojis
const topEmojisEl = containerEl . createDiv ( ) ;
topEmojisEl . addClass ( 'emoji-popularity-stats' ) ;
2025-03-24 11:50:29 +00:00
topEmojisEl . createEl ( 'h2' , { text : 'Your most used emojis' } ) ;
2025-03-08 16:39:32 +00:00
2025-03-09 08:47:39 +00:00
const popularityEntries = Object . entries ( this . plugin . settings . emojiPopularity ) ;
popularityEntries . sort ( ( a , b ) = > b [ 1 ] - a [ 1 ] ) ;
if ( popularityEntries . length === 0 ) {
topEmojisEl . createEl ( 'p' , { text : 'No emojis used yet' } ) ;
} else {
const topList = topEmojisEl . createEl ( 'div' ) ;
topList . addClass ( 'emoji-top-list' ) ;
// Take top 10 or less
const topEmojis = popularityEntries . slice ( 0 , 10 ) ;
for ( const [ emoji , count ] of topEmojis ) {
const item = topList . createEl ( 'div' ) ;
item . addClass ( 'emoji-top-item' ) ;
const emojiSpan = item . createEl ( 'span' ) ;
emojiSpan . addClass ( 'emoji-top-symbol' ) ;
emojiSpan . textContent = emoji ;
const countSpan = item . createEl ( 'span' ) ;
countSpan . addClass ( 'emoji-top-count' ) ;
countSpan . textContent = ` Used ${ count } time ${ count !== 1 ? 's' : '' } ` ;
}
2025-03-10 11:46:55 +00:00
}
}
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 } ` ) ;
} ) ;
2025-03-09 08:47:39 +00:00
}
2025-03-08 15:51:35 +00:00
}
2025-03-10 10:51:25 +00:00
}