Merge pull request #140 from micz/shortcut_try2
Merging the new Dynamic Menu
This commit is contained in:
commit
a35dd23401
16 changed files with 710 additions and 8 deletions
|
|
@ -11,6 +11,8 @@
|
|||
<li><i>[OpenAI Comp API]</i> Added a new integration method to use a local LLM via an API compatible with OpenAI's API specifications [<a href="https://github.com/micz/ThunderAI/issues/126">#126</a>].</li>
|
||||
<li>Custom Prompts storage space incremented to 5MB, using <i>storage.local</i>. Added also the total occupied space at the bottom of the Custom Prompts page [<a href="https://github.com/micz/ThunderAI/issues/129">#129</a>].</li>
|
||||
<li><i>[ChatGPT Web]</i> Updated the information in the option page.</li>
|
||||
<li>A new dynamic menu for selecting prompts has been added. In addition to clicking the ThunderAI button, you can now use the CTRL+ALT+A keyboard shortcut [<a href="https://github.com/micz/ThunderAI/issues/130">#130</a>].</li>
|
||||
<li>Added an option to order alphabetically the prompts in the menu.</li>
|
||||
<li>...</li>
|
||||
</ul>
|
||||
<h2>Version 2.1.4 - 11/09/2024</h2>
|
||||
|
|
|
|||
|
|
@ -56,5 +56,6 @@ Are you using this addon in your Thunderbird?
|
|||
<ul><li><a href="https://github.com/KudoAI/chatgpt.js">chatgpt.js</a> for providing methods to interact with the ChatGTP frontend.</li>
|
||||
<li><a href="https://github.com/ali-raheem/Aify">Aify</a> for inspiration.</li>
|
||||
<li><a href="https://github.com/boxabirds">Julian Harris</a> for his project <a href="https://github.com/boxabirds/chatgpt-frontend-nobuild">chatgpt-frontend-nobuild</a>, that has been used as a starting point for the API Web Interface.</li>
|
||||
<li><a href="https://loading.io">loading.io</a> for the dynamic menu loading SVG.</li>
|
||||
</ul>
|
||||
<i>The specific references are described in the corresponding source files.</i>
|
||||
|
|
|
|||
|
|
@ -327,6 +327,14 @@
|
|||
"message": "To use this integration, you need to set up a local server compatible with the OpenAI API, like LM Studio. Once the server is running, enter its address in the designated field within the application. To ensure proper communication between ThunderAI and the local server remember to set the CORS settings correctly.",
|
||||
"description": ""
|
||||
},
|
||||
"prefsInfoDesc_5": {
|
||||
"message": "Remember that you can open the ThunderAI menu using the keyboard shortcut CTRL+ALT+A.",
|
||||
"description": ""
|
||||
},
|
||||
"prefsInfoDesc_6": {
|
||||
"message": "You can change the shortcut clicking on the cogwheel icon at the top right of this page and choosing \"Manage Extension Shortcut\".",
|
||||
"description": ""
|
||||
},
|
||||
"prefsDonation_1": {
|
||||
"message": "Do you like this addon?",
|
||||
"description": ""
|
||||
|
|
@ -542,5 +550,25 @@
|
|||
"StorageSpace": {
|
||||
"message": "Total storage space occupied",
|
||||
"description": ""
|
||||
},
|
||||
"SearchPrompt": {
|
||||
"message": "Search prompts",
|
||||
"description": ""
|
||||
},
|
||||
"prefs_OptionText_dynamic_menu_force_enter": {
|
||||
"message": "Menu: immediate prompt send",
|
||||
"description": ""
|
||||
},
|
||||
"prefs_OptionText_dynamic_menu_force_enter_info": {
|
||||
"message": "If checked, using the keyboard shortcut CTRL+ALT+A will automatically send the hightlighted prompt from the menu. Otherwise, the prompt name will be displayed to the user, requiring another press of the Enter key to send it.",
|
||||
"description": ""
|
||||
},
|
||||
"prefs_OptionText_dynamic_menu_order_alphabet": {
|
||||
"message": "Menu: order alphabetically",
|
||||
"description": ""
|
||||
},
|
||||
"prefs_OptionText_dynamic_menu_order_alphabet_info": {
|
||||
"message": "If checked, the prompts in the menu will be ordered alphabetically.",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
// Some original methods are derived from https://github.com/ali-raheem/Aify/blob/cfadf52f576b7be3720b5b73af7c8d3129c054da/plugin/html/actions.js
|
||||
|
||||
import { getPrompts } from './mzta-prompts.js';
|
||||
import { getLanguageDisplayName, getMenuContextCompose, getMenuContextDisplay } from './mzta-utils.js'
|
||||
import { getLanguageDisplayName, getMenuContextCompose, getMenuContextDisplay, i18nConditionalGet } from './mzta-utils.js'
|
||||
|
||||
export class mzta_Menus {
|
||||
|
||||
|
|
@ -33,6 +33,10 @@ export class mzta_Menus {
|
|||
//{ id: 'ItemC', act: (info, tab) => { console.log('ItemC', info, tab, info.menuItemId); alert('ItemC') } },
|
||||
];
|
||||
|
||||
shortcutMenu = [
|
||||
//{ id: 'ItemD', label: 'LabelD' },
|
||||
];
|
||||
|
||||
constructor(openChatGPT) {
|
||||
this.menu_context_compose = getMenuContextCompose();
|
||||
this.menu_context_display = getMenuContextDisplay();
|
||||
|
|
@ -45,6 +49,7 @@ export class mzta_Menus {
|
|||
async initialize() {
|
||||
this.allPrompts = [];
|
||||
this.rootMenu = [];
|
||||
this.shortcutMenu = [];
|
||||
this.menu_listeners = {};
|
||||
this.allPrompts = await getPrompts(true);
|
||||
this.allPrompts.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
|
@ -55,7 +60,7 @@ export class mzta_Menus {
|
|||
|
||||
async reload(){
|
||||
await browser.menus.removeAll().catch(error => {
|
||||
console.error("[ThunderAI] ERROR removing the menus: ", error);
|
||||
console.error("[ThunderAI] ERROR removing the menus: ", error);
|
||||
});
|
||||
this.removeClickListener();
|
||||
this.loadMenus();
|
||||
|
|
@ -140,10 +145,23 @@ export class mzta_Menus {
|
|||
}
|
||||
}
|
||||
|
||||
loadShortcutMenu() {
|
||||
this.shortcutMenu = [];
|
||||
this.allPrompts.forEach((prompt) => {
|
||||
this.addShortcutMenu(prompt);
|
||||
});
|
||||
}
|
||||
|
||||
addShortcutMenu(prompt) {
|
||||
let curr_menu_entry = {id: prompt.id, label: i18nConditionalGet(prompt.name), type: prompt.type};
|
||||
this.shortcutMenu.push(curr_menu_entry);
|
||||
}
|
||||
|
||||
async loadMenus() {
|
||||
await this.initialize();
|
||||
await this.addMenu(this.rootMenu);
|
||||
this.addClickListener();
|
||||
this.loadShortcutMenu();
|
||||
}
|
||||
|
||||
listener(info, tab) {
|
||||
|
|
@ -219,4 +237,23 @@ export class mzta_Menus {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
async executeMenuAction(id) {
|
||||
// Retrieve the action callback from the menu listeners using the provided ID
|
||||
const action = this.menu_listeners[id];
|
||||
|
||||
if (action) {
|
||||
try {
|
||||
// Execute the action callback
|
||||
await action();
|
||||
} catch (error) {
|
||||
// Log any errors that occur during execution
|
||||
console.error(`Error executing action for menu item ${id}:`, error);
|
||||
}
|
||||
} else {
|
||||
// Warn if no action is found for the provided ID
|
||||
console.warn(`No action found for menu item ID: ${id}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
32
js/mzta-store.js
Normal file
32
js/mzta-store.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
|
||||
* Copyright (C) 2024 Mic (m@micz.it)
|
||||
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
export const taStore = {
|
||||
|
||||
async setSessionData (key, value) {
|
||||
let obj = {};
|
||||
obj[key] = value;
|
||||
await browser.storage.session.set(obj);
|
||||
},
|
||||
|
||||
async getSessionData (key) {
|
||||
let output = await browser.storage.session.get(key);
|
||||
return output[key];
|
||||
},
|
||||
};
|
||||
65
js/tb115_segmenter/tb115_loader.js
Normal file
65
js/tb115_segmenter/tb115_loader.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
|
||||
* Copyright (C) 2024 Mic (m@micz.it)
|
||||
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// Firefox 115 and, consequently, Thunderbird 115 are not compatible with Intl.Segmenter.
|
||||
// ChatGPT is using Intl.Segmenter, so we have to do something about it or it won't work...
|
||||
|
||||
console.log("[ThunderAI] Intl.Segmenter: " + Intl.Segmenter);
|
||||
|
||||
(function() {
|
||||
if('Segmenter' in Intl){
|
||||
console.log('[ThunderAI] TB128+ detected. Intl.Segmenter is already supported.');
|
||||
return;
|
||||
}
|
||||
// Creates a <script> element to inject polyfill.js into the page context
|
||||
const script = document.createElement('script');
|
||||
script.src = browser.runtime.getURL('js/tb115_segmenter/tb115_polyfill.js');
|
||||
script.onload = function() {
|
||||
// Remove the script once loaded for cleanup
|
||||
this.remove();
|
||||
};
|
||||
script.onerror = function() {
|
||||
console.error('[ThunderAI] Error loading Intl.Segmenter polyfill.');
|
||||
};
|
||||
(document.head || document.documentElement).appendChild(script);
|
||||
})();
|
||||
|
||||
|
||||
|
||||
async function isThunderbird128OrGreater(){
|
||||
try {
|
||||
const info = await browser.runtime.getBrowserInfo();
|
||||
const version = info.version;
|
||||
return compareThunderbirdVersions(version, '128.0') >= 0;
|
||||
} catch (error) {
|
||||
console.error('[ThunderAI] Error retrieving browser information:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function compareThunderbirdVersions(v1, v2) {
|
||||
const v1parts = v1.split('.').map(Number);
|
||||
const v2parts = v2.split('.').map(Number);
|
||||
|
||||
for (let i = 0; i < Math.max(v1parts.length, v2parts.length); i++) {
|
||||
const v1part = v1parts[i] || 0;
|
||||
const v2part = v2parts[i] || 0;
|
||||
if (v1part > v2part) return 1;
|
||||
if (v1part < v2part) return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
48
js/tb115_segmenter/tb115_polyfill.js
Normal file
48
js/tb115_segmenter/tb115_polyfill.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
|
||||
* Copyright (C) 2024 Mic (m@micz.it)
|
||||
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// Firefox 115 and, consequently, Thunderbird 115 are not compatible with Intl.Segmenter.
|
||||
// ChatGPT is using Intl.Segmenter, so we have to do something about it or it won't work...
|
||||
|
||||
(function() {
|
||||
if (!('Segmenter' in Intl)) {
|
||||
class Segmenter {
|
||||
constructor(locale = 'en', options = { granularity: 'grapheme' }) {
|
||||
this.locale = locale;
|
||||
this.granularity = options.granularity || 'grapheme';
|
||||
}
|
||||
|
||||
segment(text) {
|
||||
switch (this.granularity) {
|
||||
case 'word':
|
||||
return text.split(/\s+/).filter(word => word.length > 0).map(word => ({ segment: word }));
|
||||
case 'grapheme':
|
||||
return Array.from(text).map(char => ({ segment: char }));
|
||||
case 'sentence':
|
||||
return text.split(/(?<=[.!?])\s+/).map(sentence => ({ segment: sentence }));
|
||||
default:
|
||||
return [{ segment: text }];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Intl.Segmenter = Segmenter;
|
||||
console.log('Intl.Segmenter polyfill correctly loaded.');
|
||||
} else {
|
||||
console.log('Intl.Segmenter is already supported.');
|
||||
}
|
||||
})();
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
"manifest_version": 2,
|
||||
"name": "ThunderAI",
|
||||
"description": "__MSG_extensionDescription__",
|
||||
"version": "2.2.0pre2",
|
||||
"version": "2.2.0pre3",
|
||||
"author": "Mic (m@micz.it)",
|
||||
"homepage_url": "https://micz.it/thunderbird-addon-thunderai/",
|
||||
"browser_specific_settings": {
|
||||
|
|
@ -14,12 +14,12 @@
|
|||
"message_display_action": {
|
||||
"default_title": "__MSG_menu_title__",
|
||||
"default_icon": "images/icon-32px.png",
|
||||
"type": "menu"
|
||||
"default_popup": "popup/mzta-popup.html"
|
||||
},
|
||||
"compose_action": {
|
||||
"default_title": "__MSG_menu_title__",
|
||||
"default_icon": "images/icon-32px.png",
|
||||
"type":"menu"
|
||||
"default_popup": "popup/mzta-popup.html"
|
||||
},
|
||||
"permissions": [
|
||||
"compose",
|
||||
|
|
@ -44,5 +44,18 @@
|
|||
"default_locale": "en",
|
||||
"options_ui": {
|
||||
"page": "options/mzta-options.html"
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"_thunderai__do_action": {
|
||||
"description": "Opens the ThunderAI prompts menu"
|
||||
}
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": ["https://*.chatgpt.com/*"],
|
||||
"js": ["js/tb115_segmenter/tb115_loader.js"],
|
||||
"all_frames": true,
|
||||
"run_at": "document_start"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { mzta_script } from './js/mzta-chatgpt.js';
|
|||
import { prefs_default } from './options/mzta-options-default.js';
|
||||
import { mzta_Menus } from './js/mzta-menus.js';
|
||||
import { taLogger } from './js/mzta-logger.js';
|
||||
import { taStore } from './js/mzta-store.js';
|
||||
import { getCurrentIdentity, getOriginalBody, replaceBody, setBody, i18nConditionalGet, generateCallID, migrateCustomPromptsStorage, migrateDefaultPromptsPropStorage } from './js/mzta-utils.js';
|
||||
|
||||
await migrateCustomPromptsStorage();
|
||||
|
|
@ -65,6 +66,61 @@ browser.contentScripts.register({
|
|||
runAt: "document_idle"
|
||||
});
|
||||
|
||||
let ThunderAI_Shortcut = "Ctrl+Alt+A";
|
||||
|
||||
// Shortcut
|
||||
messenger.commands.update({
|
||||
name: "_thunderai__do_action",
|
||||
shortcut: ThunderAI_Shortcut
|
||||
}).then(() => {
|
||||
taLog.log('Shortcut [' + ThunderAI_Shortcut + '] registered successfully!');
|
||||
}).catch((error) => {
|
||||
taLog.error('Error registering shortcut [' + ThunderAI_Shortcut + ']: ' + error);
|
||||
});
|
||||
|
||||
// Listen for shortcut command
|
||||
messenger.commands.onCommand.addListener((command, tab) => {
|
||||
if (command === "_thunderai__do_action") {
|
||||
handleShortcut(tab);
|
||||
}
|
||||
});
|
||||
|
||||
async function handleShortcut(tab) {
|
||||
taLog.log("Shortcut triggered!");
|
||||
if(!["mail", "messageCompose","messageDisplay"].includes(tab.type)){
|
||||
return;
|
||||
}
|
||||
switch (tab.type) {
|
||||
case "mail":
|
||||
case "messageDisplay":
|
||||
browser.messageDisplayAction.openPopup();
|
||||
break;
|
||||
case "messageCompose":
|
||||
browser.composeAction.openPopup();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async function preparePopupMenu(tab) {
|
||||
await taStore.setSessionData("lastShortcutTabId", tab.id);
|
||||
await taStore.setSessionData("lastShortcutTabType", tab.type);
|
||||
await taStore.setSessionData("lastShortcutPromptsData", menus.shortcutMenu);
|
||||
console.log(">>>>>>>> menus.shortcutMenu: " + JSON.stringify(menus.shortcutMenu));
|
||||
switch (tab.type) {
|
||||
case "mail":
|
||||
case "messageDisplay":
|
||||
taStore.setSessionData("lastShortcutFiltering", 1);
|
||||
break;
|
||||
case "messageCompose":
|
||||
taStore.setSessionData("lastShortcutFiltering", 2);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
messenger.runtime.onMessage.addListener(async (message, sender, sendResponse) => {
|
||||
// Check what type of message we have received and invoke the appropriate
|
||||
// handler function.
|
||||
|
|
@ -142,10 +198,25 @@ messenger.runtime.onMessage.addListener(async (message, sender, sendResponse) =>
|
|||
modified_html = await getOriginalBody(message.tabId);
|
||||
await setBody(message.tabId, original_html);
|
||||
await setBody(message.tabId, modified_html);
|
||||
return true;
|
||||
break;
|
||||
case 'reload_menus':
|
||||
await menus.reload();
|
||||
taLog.log("[ThunderAI] Reloaded menus");
|
||||
return true;
|
||||
break;
|
||||
case 'shortcut_do_prompt':
|
||||
taLog.log("Executing shortcut, promptId: " + message.promptId);
|
||||
menus.executeMenuAction(message.promptId);
|
||||
return true;
|
||||
break;
|
||||
case 'popup_menu_ready':
|
||||
let tabs = await browser.tabs.query({ active: true, currentWindow: true });
|
||||
if(tabs.length == 0){
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
preparePopupMenu(tabs[0]);
|
||||
return Promise.resolve(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -29,4 +29,6 @@ export const prefs_default = {
|
|||
openai_comp_host: '', // For OpenAI Compatible API as LM-Studio
|
||||
openai_comp_model: '',
|
||||
openai_comp_chat_name: 'OpenAI Comp',
|
||||
dynamic_menu_force_enter: false,
|
||||
dynamic_menu_order_alphabet: true,
|
||||
}
|
||||
|
|
@ -59,6 +59,28 @@
|
|||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label>
|
||||
<span>__MSG_prefs_OptionText_dynamic_menu_order_alphabet__</span>
|
||||
</label></td>
|
||||
<td>
|
||||
<label>
|
||||
<input type="checkbox" id="dynamic_menu_order_alphabet" name="dynamic_menu_order_alphabet" class="option-input" />
|
||||
<span>__MSG_prefs_OptionText_dynamic_menu_order_alphabet_info__</span>
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label>
|
||||
<span>__MSG_prefs_OptionText_dynamic_menu_force_enter__</span>
|
||||
</label></td>
|
||||
<td>
|
||||
<label>
|
||||
<input type="checkbox" id="dynamic_menu_force_enter" name="dynamic_menu_force_enter" class="option-input" />
|
||||
<span>__MSG_prefs_OptionText_dynamic_menu_force_enter_info__</span>
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label>
|
||||
|
|
@ -183,10 +205,14 @@
|
|||
</div>
|
||||
<div id="miczDescription">
|
||||
<h1>__MSG_prefsInfoTitle__</h1>
|
||||
<p><span class="conntype_chatgpt_web">__MSG_prefsInfoDesc_1__<br></span>
|
||||
<p>
|
||||
<span class="conntype_chatgpt_web">__MSG_prefsInfoDesc_1__<br></span>
|
||||
<span class="conntype_chatgpt_api">__MSG_prefsInfoDesc_2__<br></span>
|
||||
<span class="conntype_ollama_api">__MSG_prefsInfoDesc_3__<br></span>
|
||||
<span class="conntype_openai_comp_api">__MSG_prefsInfoDesc_4__</span></p>
|
||||
<span class="conntype_openai_comp_api">__MSG_prefsInfoDesc_4__</span>
|
||||
<br><span>__MSG_prefsInfoDesc_5__
|
||||
<br>__MSG_prefsInfoDesc_6__</span>
|
||||
</p>
|
||||
</div>
|
||||
<div id="miczTranslate">__MSG_TranslateText__ <a href="https://micz.it/thunderbird-addon-thunderai/translate/">__MSG_TranslateLink__</a></div>
|
||||
<div id="miczDonation">__MSG_prefsDonation_1__<br/><a href="http://micz.it/thunderbird-addon-thunderai/donate/">__MSG_prefsDonation_2__</a></div>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
<li><i>[OpenAI Comp API]</i> Added a new integration method to use a local LLM via an API compatible with OpenAI's API specifications [<a href="https://github.com/micz/ThunderAI/issues/126">#126</a>].</li>
|
||||
<li>Custom Prompts storage space incremented to 5MB, using <pre>storage.local</pre>. Added also the total occupied space at the bottom of the Custom Prompts page [<a href="https://github.com/micz/ThunderAI/issues/129">#129</a>].</li>
|
||||
<li><i>[ChatGPT Web]</i> Updated the information in the option page.</li>
|
||||
<li>A new dynamic menu for selecting prompts has been added. In addition to clicking the ThunderAI button, you can now use the CTRL+ALT+A keyboard shortcut [<a href="https://github.com/micz/ThunderAI/issues/130">#130</a>].</li>
|
||||
<li>Added an option to order alphabetically the prompts in the menu.</li>
|
||||
<li>...</li>
|
||||
</ul>
|
||||
<h2>Version 2.1.4 - 11/09/2024</h2>
|
||||
|
|
|
|||
10
popup/mzta-loading.svg
Normal file
10
popup/mzta-loading.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" style="shape-rendering: auto; display: block; background: transparent;" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink"><g><circle r="25" fill="#408ee0" cy="50" cx="25">
|
||||
<animate begin="-0.5s" values="25;75;25" keyTimes="0;0.5;1" dur="1s" repeatCount="indefinite" attributeName="cx"></animate>
|
||||
</circle>
|
||||
<circle r="25" fill="#89bff8" cy="50" cx="75">
|
||||
<animate begin="0s" values="25;75;25" keyTimes="0;0.5;1" dur="1s" repeatCount="indefinite" attributeName="cx"></animate>
|
||||
</circle>
|
||||
<circle r="25" fill="#408ee0" cy="50" cx="25">
|
||||
<animate begin="-0.5s" values="25;75;25" keyTimes="0;0.5;1" dur="1s" repeatCount="indefinite" attributeName="cx"></animate>
|
||||
<animate repeatCount="indefinite" dur="1s" keyTimes="0;0.499;0.5;1" calcMode="discrete" values="0;0;1;1" attributeName="fill-opacity"></animate>
|
||||
</circle><g></g></g><!-- [ldio] generated by https://loading.io --></svg>
|
||||
|
After Width: | Height: | Size: 990 B |
93
popup/mzta-popup.css
Normal file
93
popup/mzta-popup.css
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
body {
|
||||
background-color: #FAFAFA;
|
||||
font-size: 14px;
|
||||
padding: 0;
|
||||
margin:3px;
|
||||
}
|
||||
|
||||
#mzta_search_banner{
|
||||
background-color: #FAFAFA;
|
||||
color:black;
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
width: 10em;
|
||||
max-height: 13em;
|
||||
min-height: 1em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#mzta_search_input{
|
||||
width: 12em;
|
||||
position: fixed;
|
||||
top:0;
|
||||
left:0;
|
||||
width: -moz-available;
|
||||
z-index: 1001;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#_spacer_div{
|
||||
width: 100%;
|
||||
height: 2.1em;
|
||||
}
|
||||
|
||||
#mzta_autocomplete-items {
|
||||
border: none;
|
||||
z-index: 1000;
|
||||
background-color: #FAFAFA;
|
||||
width: 10em;
|
||||
overflow-y: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mzta_autocomplete-item {
|
||||
padding: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mzta_autocomplete-item:hover {
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
|
||||
.mzta_autocomplete-item-active {
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
|
||||
body{
|
||||
background-color: #2c2c2c;
|
||||
}
|
||||
|
||||
#mzta_search_input {
|
||||
background-color: #2c2c2c;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
#mzta_search_banner{
|
||||
background-color: #2c2c2c;
|
||||
color:#ffffff;
|
||||
}
|
||||
|
||||
#mzta_autocomplete-items {
|
||||
background-color: #2c2c2c;
|
||||
}
|
||||
|
||||
.mzta_autocomplete-item {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.mzta_autocomplete-item:hover {
|
||||
background-color: #444444;
|
||||
}
|
||||
|
||||
.mzta_autocomplete-item-active {
|
||||
background-color: #444444;
|
||||
}
|
||||
}
|
||||
20
popup/mzta-popup.html
Normal file
20
popup/mzta-popup.html
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="stylesheet" href="mzta-popup.css">
|
||||
<title>ThunderAI Prompts</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="mzta_search_banner">
|
||||
<div id="_spacer_div"> </div>
|
||||
<input type="text" id="mzta_search_input" placeholder="__MSG_SearchPrompt__">
|
||||
<div id="mzta_autocomplete-items-loading"><img src="mzta-loading.svg" title="__MSG_Loading__"></div>
|
||||
<div id="mzta_autocomplete-items" style="display: none;"></div>
|
||||
</div>
|
||||
<script src="mzta-popup.js" type="module"></script>
|
||||
<script src="../js/mzta-i18n.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
252
popup/mzta-popup.js
Normal file
252
popup/mzta-popup.js
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
/*
|
||||
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
|
||||
* Copyright (C) 2024 Mic (m@micz.it)
|
||||
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { taStore } from "../js/mzta-store.js";
|
||||
import { taLogger } from "../js/mzta-logger.js";
|
||||
|
||||
let menuSendImmediately = false;
|
||||
let taLog = console;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
let prefs = await browser.storage.sync.get({do_debug: false, dynamic_menu_force_enter: false});
|
||||
taLog = new taLogger("mzta-popup",prefs.do_debug);
|
||||
let reponse = await browser.runtime.sendMessage({command: "popup_menu_ready"});
|
||||
taLog.log("Preparing data to load the popup menu: " + JSON.stringify(reponse));
|
||||
let tabId = await taStore.getSessionData("lastShortcutTabId");
|
||||
let tabType = await taStore.getSessionData("lastShortcutTabType");
|
||||
let filtering = await taStore.getSessionData("lastShortcutFiltering");
|
||||
let _prompts_data = await taStore.getSessionData("lastShortcutPromptsData");
|
||||
taLog.log("_prompts_data: " + JSON.stringify(_prompts_data));
|
||||
let active_prompts = filterPromptsForTab(_prompts_data, filtering);
|
||||
taLog.log("active_prompts: " + JSON.stringify(active_prompts));
|
||||
menuSendImmediately = prefs.dynamic_menu_force_enter;
|
||||
searchPrompt(active_prompts, tabId, tabType);
|
||||
i18n.updateDocument();
|
||||
}, { once: true });
|
||||
|
||||
async function searchPrompt(allPrompts, tabId, tabType){
|
||||
taLog.log("tabType: " + tabType);
|
||||
|
||||
let prefs_order = await browser.storage.sync.get({dynamic_menu_order_alphabet: true});
|
||||
|
||||
if(prefs_order.dynamic_menu_order_alphabet){
|
||||
allPrompts.sort((a, b) => a.label.localeCompare(b.label));
|
||||
}
|
||||
|
||||
// console.log(">>>>>>>>> allPrompts: " + JSON.stringify(allPrompts));
|
||||
|
||||
let input = document.getElementById('mzta_search_input');
|
||||
let autocompleteList = document.getElementById('mzta_autocomplete-items');
|
||||
let autocompleteListLoading = document.getElementById('mzta_autocomplete-items-loading');
|
||||
let _spacer_div = document.getElementById('_spacer_div');
|
||||
let banner = document.getElementById('mzta_search_banner');
|
||||
|
||||
// Initialize variables to track focus and selection
|
||||
let currentFocus = -1; // Tracks the currently highlighted item
|
||||
let selectedId = null; // Tracks the ID of the selected item
|
||||
|
||||
// Function to filter and display autocomplete suggestions
|
||||
input.addEventListener('input', function() {
|
||||
const query = this.value.trim().toLowerCase();
|
||||
// console.log(">>>>>>>>>>>> query: " + query);
|
||||
autocompleteList.innerHTML = ''; // Clear previous suggestions
|
||||
currentFocus = -1; // Reset the highlighted index
|
||||
selectedId = null; // Reset the selected ID since input has changed
|
||||
|
||||
// Uncomment the following lines if you want to hide suggestions when input is empty
|
||||
/*
|
||||
if (query === '') {
|
||||
autocompleteList.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
// Filter data based on the query
|
||||
const filteredData = allPrompts.filter(item =>
|
||||
item.label.toLowerCase().includes(query)
|
||||
);
|
||||
taLog.log("filteredData: " + JSON.stringify(filteredData));
|
||||
|
||||
if (filteredData.length === 0) {
|
||||
autocompleteList.style.display = 'none';
|
||||
_spacer_div.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepend numbers to the first 10 items
|
||||
Array.from(filteredData).slice(0, 10).forEach((item, index) => {
|
||||
const number = (index < 9) ? (index + 1).toString() : '0';
|
||||
// Check if the number is already prepended to avoid duplication
|
||||
if (!item.numberPrepended) {
|
||||
item.label = `${number}. ${item.label}`;
|
||||
item.numberPrepended = 'true'; // Mark as prepended
|
||||
}
|
||||
});
|
||||
|
||||
// console.log(">>>>>>>>>>>>> filteredData: " + JSON.stringify(filteredData));
|
||||
|
||||
// Create a div for each filtered result
|
||||
filteredData.forEach(item => {
|
||||
const itemDiv = document.createElement('div');
|
||||
itemDiv.classList.add('mzta_autocomplete-item');
|
||||
itemDiv.textContent = item.label;
|
||||
itemDiv.setAttribute('data-id', item.id);
|
||||
|
||||
// Add a mousedown event to select the item
|
||||
itemDiv.addEventListener('mousedown', function(e) { // Use mousedown instead of click
|
||||
e.preventDefault(); // Prevents the input from losing focus
|
||||
input.value = item.label;
|
||||
selectedId = item.id; // Store the selected item's ID
|
||||
taLog.log('mousedown selectedId:', selectedId);
|
||||
autocompleteList.style.display = 'none';
|
||||
_spacer_div.style.display = 'none';
|
||||
sendPrompt(selectedId, tabId);
|
||||
});
|
||||
|
||||
// Add a select_prompt event to select the item
|
||||
itemDiv.addEventListener('select_prompt', function(e) { // Use select_prompt instead of click
|
||||
e.preventDefault(); // Prevents the input from losing focus
|
||||
input.value = item.label;
|
||||
selectedId = item.id; // Store the selected item's ID
|
||||
// console.log('>>>>>>>>>>>>> select_prompt selectedId:', selectedId);
|
||||
autocompleteList.style.display = 'none';
|
||||
_spacer_div.style.display = 'none';
|
||||
if(menuSendImmediately){
|
||||
sendPrompt(selectedId, tabId);
|
||||
}
|
||||
});
|
||||
|
||||
autocompleteList.appendChild(itemDiv);
|
||||
});
|
||||
|
||||
autocompleteListLoading.style.display = 'none';
|
||||
autocompleteList.style.display = 'block';
|
||||
_spacer_div.style.display = 'block';
|
||||
});
|
||||
|
||||
// Add a keydown event listener to handle arrow navigation and selection
|
||||
input.addEventListener('keydown', function (e) {
|
||||
|
||||
const items = autocompleteList.getElementsByClassName('mzta_autocomplete-item');
|
||||
if ((autocompleteList.style.display === 'none' || items.length === 0)
|
||||
&& (e.key !== 'Enter')
|
||||
&& !['1','2','3','4','5','6','7','8','9','0'].includes(e.key))
|
||||
{
|
||||
return; // Do nothing if the autocomplete list is not visible
|
||||
}
|
||||
|
||||
// Handle number key presses (1-9,0) to select the corresponding item directly
|
||||
if (['1','2','3','4','5','6','7','8','9','0'].includes(e.key)) {
|
||||
// Map '1' to index 0, '2' to 1, ..., '9' to 8, '0' to 9
|
||||
const numIndex = (e.key === '0') ? 9 : parseInt(e.key, 10) - 1;
|
||||
if (items[numIndex]) {
|
||||
e.preventDefault(); // Prevent any default behavior
|
||||
// Dispatch a select_prompt event to simulate a click/select action
|
||||
items[numIndex].dispatchEvent(new Event('select_prompt'));
|
||||
return; // Exit after handling the number key
|
||||
}
|
||||
}
|
||||
|
||||
if (e.key === 'ArrowDown') {
|
||||
// Navigate down the list
|
||||
currentFocus++;
|
||||
if (currentFocus >= items.length) currentFocus = 0; // Wrap to the first item
|
||||
addActive(items);
|
||||
e.preventDefault(); // Prevent cursor from moving to the end
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
// Navigate up the list
|
||||
currentFocus--;
|
||||
if (currentFocus < 0) currentFocus = items.length - 1; // Wrap to the last item
|
||||
addActive(items);
|
||||
e.preventDefault(); // Prevent cursor from moving to the start
|
||||
} else if (e.key === 'Enter') {
|
||||
// console.log(">>>>>>>>>>>>>> keydown == enter selectedId: " + selectedId);
|
||||
if (selectedId) {
|
||||
// If an item is already selected, call sendPrompt with the selected ID
|
||||
e.preventDefault();
|
||||
sendPrompt(selectedId, tabId); // Call your sendPrompt function
|
||||
//banner.remove(); // Remove the banner after sending the prompt
|
||||
} else {
|
||||
// If no item is selected yet, select the highlighted item
|
||||
// Select the highlighted item, or the first item if none is highlighted
|
||||
e.preventDefault(); // Prevent form submission if inside a form
|
||||
if (currentFocus > -1) {
|
||||
if (items[currentFocus]) {
|
||||
items[currentFocus].dispatchEvent(new Event('select_prompt')); // Trigger the select_prompt event
|
||||
}
|
||||
} else if (items.length > 0) {
|
||||
// If no item is highlighted, select the first item
|
||||
items[0].dispatchEvent(new Event('select_prompt'));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Function to add the "active" class to the current item
|
||||
function addActive(items) {
|
||||
removeActive(items); // Remove the "active" class from all items
|
||||
if (currentFocus >= items.length) currentFocus = 0;
|
||||
if (currentFocus < 0) currentFocus = items.length - 1;
|
||||
items[currentFocus].classList.add('mzta_autocomplete-item-active'); // Add "active" class to the current item
|
||||
// Ensure the active item is visible within the scrollable list
|
||||
items[currentFocus].scrollIntoView({
|
||||
behavior: 'auto', // You can change to 'smooth' if you prefer smooth scrolling
|
||||
block: 'nearest', // Align the item to the nearest edge of the visible area
|
||||
});
|
||||
}
|
||||
|
||||
// Function to remove the "active" class from all items
|
||||
function removeActive(items) {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
items[i].classList.remove('mzta_autocomplete-item-active');
|
||||
}
|
||||
}
|
||||
|
||||
document.body.insertBefore(banner, document.body.firstChild);
|
||||
setTimeout(() => {
|
||||
input.dispatchEvent(new InputEvent('input', { bubbles: true }));
|
||||
input.focus();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
|
||||
function sendPrompt(prompt_id, tabId){
|
||||
taLog.log("sendPrompt: " + prompt_id);
|
||||
browser.runtime.sendMessage({command: "shortcut_do_prompt", tabId: tabId, promptId: prompt_id});
|
||||
}
|
||||
|
||||
function filterPromptsForTab(prompts_data, filtering){
|
||||
// If filtering is 0, return the original array without any filters (btw it should not happen)
|
||||
if (filtering === 0) {
|
||||
return prompts_data;
|
||||
}
|
||||
|
||||
// Define the types to include based on the value of filtering
|
||||
let allowedTypes;
|
||||
if (filtering === 1) {
|
||||
allowedTypes = ["0", "1"];
|
||||
} else if (filtering === 2) {
|
||||
allowedTypes = ["0", "2"];
|
||||
} else {
|
||||
// If filtering has an unexpected value, return the original data
|
||||
return prompts_data;
|
||||
}
|
||||
|
||||
// Filter the array based on the allowed types
|
||||
return prompts_data.filter(prompt => allowedTypes.includes(prompt.type));
|
||||
}
|
||||
Loading…
Reference in a new issue