From 8730eb0e71da6aee52fb906d9b5f1498d7534adb Mon Sep 17 00:00:00 2001 From: loner <2788892716@qq.com> Date: Sat, 22 Nov 2025 16:54:51 +0800 Subject: [PATCH] feat: Add emoji usage tracking and sorting by frequency --- .../Panels/Launcher/Plugins/EmojiPlugin.qml | 5 +- Services/Keyboard/EmojiService.qml | 129 +++++++++++++++--- 2 files changed, 110 insertions(+), 24 deletions(-) diff --git a/Modules/Panels/Launcher/Plugins/EmojiPlugin.qml b/Modules/Panels/Launcher/Plugins/EmojiPlugin.qml index 3ff7e5d83..f5fdea8e7 100644 --- a/Modules/Panels/Launcher/Plugins/EmojiPlugin.qml +++ b/Modules/Panels/Launcher/Plugins/EmojiPlugin.qml @@ -54,7 +54,6 @@ Item { } if (!EmojiService.loaded) { - Logger.d("EmojiPlugin", "Service not loaded yet, showing loading state"); return [ { "name": I18n.tr("plugins.emoji-loading"), @@ -66,10 +65,8 @@ Item { ]; } - Logger.d("EmojiPlugin", "Service loaded, processing query"); const query = searchText.slice(6).trim(); const emojis = EmojiService.search(query); - Logger.d("EmojiPlugin", `Found ${emojis.length} emojis for query: "${query}"`); return emojis.map(formatEmojiEntry); } @@ -96,4 +93,4 @@ Item { } }; } -} \ No newline at end of file +} diff --git a/Services/Keyboard/EmojiService.qml b/Services/Keyboard/EmojiService.qml index 872410b59..613e3dcac 100644 --- a/Services/Keyboard/EmojiService.qml +++ b/Services/Keyboard/EmojiService.qml @@ -9,13 +9,15 @@ import qs.Commons Singleton { id: root - // --- Public API --- - - // List of all loaded emojis after deduplication property var emojis: [] - // True when emojis are fully loaded property bool loaded: false + // Usage tracking for popular emojis + property var usageCounts: ({}) + + // File path for persisting usage data + readonly property string usageFilePath: Settings.cacheDir + "emoji_usage.json" + // Searches emojis based on query function search(query) { if (!loaded) { @@ -23,11 +25,12 @@ Singleton { } if (!query || query.trim() === "") { - return emojis.slice(0, 20); + // Return popular/recently used emojis, fallback to all emojis sorted by usage + return _getPopularEmojis(50); } const terms = query.toLowerCase().split(" ").filter(t => t); - return emojis.filter(emoji => { + const results = emojis.filter(emoji => { for (let term of terms) { const emojiMatch = emoji.emoji.toLowerCase().includes(term); const nameMatch = (emoji.name || "").toLowerCase().includes(term); @@ -40,16 +43,45 @@ Singleton { } return true; }); + + return results; } - // Copies emoji to clipboard - function copy(emojiChar) { + // Get popular emojis sorted by usage count + function _getPopularEmojis(limit) { + // Create array of emojis with their usage counts + const emojisWithUsage = emojis.map(emoji => { + return { + emoji: emoji, + usageCount: usageCounts[emoji.emoji] || 0 + }; + }); + + // Sort by usage count (descending), then by name + emojisWithUsage.sort((a, b) => { + if (b.usageCount !== a.usageCount) { + return b.usageCount - a.usageCount; + } + return (a.emoji.name || "").localeCompare(b.emoji.name || ""); + }); + + // Return the emoji objects limited by the specified count + return emojisWithUsage.slice(0, limit).map(item => item.emoji); + } + + // Record emoji usage + function recordUsage(emojiChar) { if (emojiChar) { - Quickshell.execDetached(["sh", "-c", `echo -n "${emojiChar}" | wl-copy`]); + const currentCount = usageCounts[emojiChar] || 0; + usageCounts[emojiChar] = currentCount + 1; + _saveUsageData(); } } - // --- Service Implementation --- + // Ensure usage file exists with default content + function _ensureUsageFileExists() { + Quickshell.execDetached(["sh", "-c", `mkdir -p "$(dirname "${root.usageFilePath}")" && echo '{}' > "${root.usageFilePath}"`]); + } // File paths readonly property string userEmojiPath: Settings.configDir + "emoji.json" @@ -62,7 +94,7 @@ Singleton { // Initialize on component completion Component.onCompleted: { - Logger.d("EmojiService", "Starting initialization..."); + _loadUsageData(); _loadEmojis(); } @@ -74,26 +106,21 @@ Singleton { watchChanges: false onLoaded: { - Logger.d("EmojiService", "User emoji file loaded"); try { const content = text(); if (content) { const parsed = JSON.parse(content); _userEmojiData = Array.isArray(parsed) ? parsed : []; - Logger.d("EmojiService", `Parsed ${_userEmojiData.length} user emojis`); } else { _userEmojiData = []; - Logger.d("EmojiService", "No user emoji content"); } } catch (e) { _userEmojiData = []; - Logger.w("EmojiService", "Failed to parse user emojis: " + e.message); } _onLoadComplete(); } onLoadFailed: function(error) { - Logger.d("EmojiService", "User emoji file load failed: " + error); _userEmojiData = []; _onLoadComplete(); } @@ -113,18 +140,15 @@ Singleton { _builtinEmojiData = Array.isArray(parsed) ? parsed : []; } else { _builtinEmojiData = []; - Logger.e("EmojiService", "Built-in emoji file is empty"); } } catch (e) { _builtinEmojiData = []; - Logger.e("EmojiService", "Failed to parse built-in emojis: " + e.message); } _onLoadComplete(); } onLoadFailed: function(error) { _builtinEmojiData = []; - Logger.e("EmojiService", "Failed to load built-in emojis: " + error); _onLoadComplete(); } } @@ -164,7 +188,72 @@ Singleton { emojis = Array.from(emojiMap.values()); loaded = true; + } - Logger.i("EmojiService", `Loaded ${emojis.length} unique emojis after deduplication (${_userEmojiData.length} user, ${_builtinEmojiData.length} built-in)`); + // FileView for usage data + FileView { + id: usageFile + path: root.usageFilePath + printErrors: false + watchChanges: false + + onLoaded: { + try { + const content = text(); + if (content && content.trim() !== "") { + const parsed = JSON.parse(content); + if (parsed && typeof parsed === 'object') { + root.usageCounts = parsed; + } else { + root.usageCounts = {}; + } + } else { + root.usageCounts = {}; + } + } catch (e) { + root.usageCounts = {}; + } + } + + onLoadFailed: function(error) { + root.usageCounts = {}; + Qt.callLater(_ensureUsageFileExists); + } } + + // Timer for debouncing usage data saves + Timer { + id: saveTimer + interval: 1000 + repeat: false + onTriggered: _doSaveUsageData() } + + // Load usage data + function _loadUsageData() { + usageFile.reload(); + } + + // Save usage data with debounce + function _saveUsageData() { + saveTimer.restart(); + } + + // Actually save usage data to file + function _doSaveUsageData() { + try { + const content = JSON.stringify(root.usageCounts); + Quickshell.execDetached(["sh", "-c", `mkdir -p "$(dirname "${root.usageFilePath}")" && echo '${content}' > "${root.usageFilePath}"`]); + } catch (e) { + Logger.e("EmojiService", "Failed to save usage data: " + e.message); + } + } + + // Copies emoji to clipboard + function copy(emojiChar) { + if (emojiChar) { + recordUsage(emojiChar); // Record usage before copying + Quickshell.execDetached(["sh", "-c", `echo -n "${emojiChar}" | wl-copy`]); + } + } +}