add the feature of adding custom mappings

This commit is contained in:
MarsBatya 2025-03-10 16:46:55 +05:00
parent f9e834eb0f
commit 107e4b2c26
3 changed files with 211 additions and 23 deletions

View file

@ -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 languages keywords to use.
- **Trigger Character**: Change the character that initiates the emoji search (default is `:`).
- **Show Keywords in Suggestions**: Toggle the display of each emojis keyword in the suggestion list.

125
main.ts
View file

@ -32,13 +32,15 @@ interface EmojiSuggesterPluginSettings {
triggerChar: string;
showKeywords: boolean;
emojiPopularity: Record<string, number>;
customEmojiMappings: Record<string, string>;
}
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<string> {
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<string> {
: 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<string, string>();
// 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<string> {
}
}
// 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<string> {
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}`);
});
}
}
}

View file

@ -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;
}