Merge pull request #83 from micz/import_export_prompts
Import export prompts. Ready for version 1.2.0.
This commit is contained in:
commit
3371d80861
17 changed files with 583 additions and 236 deletions
|
|
@ -3,7 +3,12 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<h2>Version 1.2.0 - ??/07/2024</h2>
|
||||||
|
<ul>
|
||||||
|
<li>If no default language is set in the options, the language present in the text sent to ChatGPT will be used [<a href="https://github.com/micz/ThunderAI/issues/53">#53</a>].</li>
|
||||||
|
<li>Added the functionality to import and export custom prompts [<a href="https://github.com/micz/ThunderAI/issues/65">#65</a>].</li>
|
||||||
|
<li>Showing the currently used prompt name in the ChatGPT window [<a href="https://github.com/micz/ThunderAI/issues/20">#20</a>].</li>
|
||||||
|
</ul>
|
||||||
<h2>Version 1.1.4 - 28/06/2024</h2>
|
<h2>Version 1.1.4 - 28/06/2024</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Added an option to import text with formatting from ChatGPT, set to false by default [<a href="https://github.com/micz/ThunderAI/issues/70">#70</a>], [<a href="https://github.com/micz/ThunderAI/issues/77">#77</a>].</li>
|
<li>Added an option to import text with formatting from ChatGPT, set to false by default [<a href="https://github.com/micz/ThunderAI/issues/70">#70</a>], [<a href="https://github.com/micz/ThunderAI/issues/77">#77</a>].</li>
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,14 @@
|
||||||
"message": "Die Standard-Eingabeaufforderungen sind nicht bearbeitbar. Sie können sie deaktivieren, dann den Text der Eingabeaufforderung kopieren und in eine neue einfügen, um eine modifizierte Version zu erstellen.",
|
"message": "Die Standard-Eingabeaufforderungen sind nicht bearbeitbar. Sie können sie deaktivieren, dann den Text der Eingabeaufforderung kopieren und in eine neue einfügen, um eine modifizierte Version zu erstellen.",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
|
"customPrompts_managePrompts_info_default_2": {
|
||||||
|
"message": "Sie können die Prompts importieren und exportieren. Bestehende Prompts mit derselben ID werden überschrieben. Prompts mit neuen IDs werden hinzugefügt.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_managePrompts_info_default_3": {
|
||||||
|
"message": "Wenn alles korrekt ist, klicken Sie nach dem Import auf die Schaltfläche 'Alle speichern'.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
"customPrompts_start_saving": {
|
"customPrompts_start_saving": {
|
||||||
"message": "Eingabeaufforderungen werden gespeichert...",
|
"message": "Eingabeaufforderungen werden gespeichert...",
|
||||||
"description": ""
|
"description": ""
|
||||||
|
|
@ -342,5 +350,37 @@
|
||||||
"TranslateLink": {
|
"TranslateLink": {
|
||||||
"message": "Finde es heraus!",
|
"message": "Finde es heraus!",
|
||||||
"description": ""
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_ExportAll": {
|
||||||
|
"message": "Alle Prompts exportieren",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_Import": {
|
||||||
|
"message": "Neue Prompts importieren",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"importPrompts_confirmText": {
|
||||||
|
"message": "Sie sind dabei, neue Prompts zu importieren.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_start_import": {
|
||||||
|
"message": "Import von benutzerdefinierten Prompts startet...",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_import_completed": {
|
||||||
|
"message": "Import von benutzerdefinierten Prompts abgeschlossen! Sie müssen auf die Schaltfläche 'Alle speichern' klicken, um Ihre Änderungen zu speichern.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"importPrompts_invalidFile": {
|
||||||
|
"message": "Die Datei, die Sie importieren möchten, ist keine gültige Datei für benutzerdefinierte Prompts.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"importPrompts_invalidPrompts": {
|
||||||
|
"message": "Die Datei, die Sie importieren möchten, enthält keine gültigen Prompts.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"currently_used_prompt": {
|
||||||
|
"message": "Derzeit verwendeter Prompt-Name",
|
||||||
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -55,6 +55,14 @@
|
||||||
"message": "The default prompts are not editable. You can disable them, then copy the prompt text and paste it into a new one to create a modified version.",
|
"message": "The default prompts are not editable. You can disable them, then copy the prompt text and paste it into a new one to create a modified version.",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
|
"customPrompts_managePrompts_info_default_2": {
|
||||||
|
"message": "You can import and export the prompts. Existing prompts with the same ID will be overwritten. Prompts with new IDs will be added.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_managePrompts_info_default_3": {
|
||||||
|
"message": "If everything is correct, after the import click on the 'Save All' button.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
"customPrompts_start_saving": {
|
"customPrompts_start_saving": {
|
||||||
"message": "Saving prompts...",
|
"message": "Saving prompts...",
|
||||||
"description": ""
|
"description": ""
|
||||||
|
|
@ -342,5 +350,37 @@
|
||||||
"TranslateLink": {
|
"TranslateLink": {
|
||||||
"message": "Find out how!",
|
"message": "Find out how!",
|
||||||
"description": ""
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_ExportAll": {
|
||||||
|
"message": "Export all prompts",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_Import": {
|
||||||
|
"message": "Import new prompts",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"importPrompts_confirmText": {
|
||||||
|
"message": "You are about to import new prompts.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_start_import": {
|
||||||
|
"message": "Starting custom prompts import...",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_import_completed": {
|
||||||
|
"message": "Custom prompts import completed! You need to click on the 'Save All' button to save your changes.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"importPrompts_invalidFile": {
|
||||||
|
"message": "The file you're trying to import is not a valid custom prompts file.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"importPrompts_invalidPrompts": {
|
||||||
|
"message": "The file you're trying to import does not contain any valid prompts.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"currently_used_prompt": {
|
||||||
|
"message": "Currently used prompt name",
|
||||||
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"extensionDescription": {
|
"extensionDescription": {
|
||||||
"message": "Utilisez ChatGPT pour améliorer vos emails !",
|
"message": "Utilisez ChatGPT pour améliorer vos emails !",
|
||||||
"description": "Description de l'extension."
|
"description": "Description de l'extension."
|
||||||
},
|
},
|
||||||
"menu_title": {
|
"menu_title": {
|
||||||
"message": "IA",
|
"message": "IA",
|
||||||
|
|
@ -55,6 +55,14 @@
|
||||||
"message": "Les invites par défaut ne sont pas modifiables. Vous pouvez les désactiver, puis copier le texte de l'invite et le coller dans une nouvelle pour créer une version modifiée.",
|
"message": "Les invites par défaut ne sont pas modifiables. Vous pouvez les désactiver, puis copier le texte de l'invite et le coller dans une nouvelle pour créer une version modifiée.",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
|
"customPrompts_managePrompts_info_default_2": {
|
||||||
|
"message": "Vous pouvez importer et exporter les prompts. Les prompts existantes avec le même ID seront écrasées. Les prompts avec de nouveaux ID seront ajoutées.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_managePrompts_info_default_3": {
|
||||||
|
"message": "Si tout est correct, après l'importation, cliquez sur le bouton 'Tout enregistrer'.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
"customPrompts_start_saving": {
|
"customPrompts_start_saving": {
|
||||||
"message": "Enregistrement des prompts...",
|
"message": "Enregistrement des prompts...",
|
||||||
"description": ""
|
"description": ""
|
||||||
|
|
@ -342,5 +350,37 @@
|
||||||
"TranslateLink": {
|
"TranslateLink": {
|
||||||
"message": "Découvrez comment !",
|
"message": "Découvrez comment !",
|
||||||
"description": ""
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_ExportAll": {
|
||||||
|
"message": "Exporter toutes les prompts",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_Import": {
|
||||||
|
"message": "Importer de nouvelles prompts",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"importPrompts_confirmText": {
|
||||||
|
"message": "Vous êtes sur le point d'importer de nouvelles prompts.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_start_import": {
|
||||||
|
"message": "Début de l'importation des prompts personnalisées...",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_import_completed": {
|
||||||
|
"message": "Importation des prompts personnalisées terminée ! Vous devez cliquer sur le bouton 'Enregistrer tout' pour enregistrer vos modifications.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"importPrompts_invalidFile": {
|
||||||
|
"message": "Le fichier que vous essayez d'importer n'est pas un fichier de prompts personnalisées valide.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"importPrompts_invalidPrompts": {
|
||||||
|
"message": "Le fichier que vous essayez d'importer ne contient aucune prompt valide.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"currently_used_prompt": {
|
||||||
|
"message": "Nom du prompt actuellement utilisé",
|
||||||
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -51,6 +51,18 @@
|
||||||
"message": "Informazioni",
|
"message": "Informazioni",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
|
"customPrompts_managePrompts_info_default": {
|
||||||
|
"message": "I prompt predefiniti non sono modificabili. Puoi disattivarli, quindi copiare il testo del prompt e incollarlo in uno nuovo per crearne una versione modificata.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_managePrompts_info_default_2": {
|
||||||
|
"message": "Puoi importare ed esportare i prompt. I prompt esistenti con lo stesso ID verranno sovrascritti. I prompt con nuovi ID verranno aggiunti.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_managePrompts_info_default_3": {
|
||||||
|
"message": "Se tutto è corretto, dopo l'importazione fai clic sul pulsante 'Salva Tutto'.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
"customPrompts_start_saving": {
|
"customPrompts_start_saving": {
|
||||||
"message": "Salvataggio prompt in corso...",
|
"message": "Salvataggio prompt in corso...",
|
||||||
"description": ""
|
"description": ""
|
||||||
|
|
@ -67,10 +79,6 @@
|
||||||
"message": "Salvataggio prompt di base...",
|
"message": "Salvataggio prompt di base...",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"customPrompts_managePrompts_info_default": {
|
|
||||||
"message": "I prompt predefiniti non sono modificabili. Puoi disattivarli, quindi copiare il testo del prompt e incollarlo in uno nuovo per crearne una versione modificata.",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"customPrompts_saving_custom_prompts": {
|
"customPrompts_saving_custom_prompts": {
|
||||||
"message": "Salvataggio prompt personalizzati...",
|
"message": "Salvataggio prompt personalizzati...",
|
||||||
"description": ""
|
"description": ""
|
||||||
|
|
@ -207,6 +215,14 @@
|
||||||
"message": "Chiudi",
|
"message": "Chiudi",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
|
"chatgpt_textarea_not_found_error": {
|
||||||
|
"message": "C'è stato un errore nell'invio del prompt a ChatGPT, per favore riprova. Se il problema persiste, per favore <a href=\"https://micz.it/thunderbird-addon-thunderai/status/\">controlla lo stato del servizio</a>.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"chatgpt_sendbutton_not_found_error": {
|
||||||
|
"message": "Premi il pulsante per inviare il prompt a ChatGPT.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
"chatgpt_user_not_logged_in": {
|
"chatgpt_user_not_logged_in": {
|
||||||
"message": "Non sei connesso a ChatGPT. Effettua l'accesso con le tue credenziali, chiudi la finestra di ChatGPT e poi ripeti l'azione che avevi tentato. Rimarrai connesso in seguito.",
|
"message": "Non sei connesso a ChatGPT. Effettua l'accesso con le tue credenziali, chiudi la finestra di ChatGPT e poi ripeti l'azione che avevi tentato. Rimarrai connesso in seguito.",
|
||||||
"description": ""
|
"description": ""
|
||||||
|
|
@ -259,14 +275,6 @@
|
||||||
"message": "Larghezza",
|
"message": "Larghezza",
|
||||||
"description": ""
|
"description": ""
|
||||||
},
|
},
|
||||||
"chatgpt_textarea_not_found_error": {
|
|
||||||
"message": "C'è stato un errore nell'invio del prompt a ChatGPT, per favore riprova. Se il problema persiste, per favore <a href=\"https://micz.it/thunderbird-addon-thunderai/status/\">controlla lo stato del servizio</a>.",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"chatgpt_sendbutton_not_found_error": {
|
|
||||||
"message": "Premi il pulsante per inviare il prompt a ChatGPT.",
|
|
||||||
"description": ""
|
|
||||||
},
|
|
||||||
"prefs_OptionText_default_sign_name": {
|
"prefs_OptionText_default_sign_name": {
|
||||||
"message": "Nome da utilizzare per la firma",
|
"message": "Nome da utilizzare per la firma",
|
||||||
"description": ""
|
"description": ""
|
||||||
|
|
@ -342,5 +350,37 @@
|
||||||
"TranslateLink": {
|
"TranslateLink": {
|
||||||
"message": "Scopri come!",
|
"message": "Scopri come!",
|
||||||
"description": ""
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_ExportAll": {
|
||||||
|
"message": "Esporta tutti i prompt",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_Import": {
|
||||||
|
"message": "Importa i nuovi prompt",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"importPrompts_confirmText": {
|
||||||
|
"message": "Stai per importare nuovi prompt.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_start_import": {
|
||||||
|
"message": "Inizio importazione dei prompt personalizzati...",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"customPrompts_import_completed": {
|
||||||
|
"message": "Importazione dei prompt personalizzati completata! È necessario fare clic sul pulsante 'Salva Tutto' per salvare le modifiche.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"importPrompts_invalidFile": {
|
||||||
|
"message": "Il file che stai cercando di importare non è un file valido di prompt personalizzati.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"importPrompts_invalidPrompts": {
|
||||||
|
"message": "Il file che stai cercando di importare non contiene alcun prompt valido.",
|
||||||
|
"description": ""
|
||||||
|
},
|
||||||
|
"currently_used_prompt": {
|
||||||
|
"message": "Nome del prompt attualmente utilizzato",
|
||||||
|
"description": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -169,4 +169,15 @@ table .sort.desc {
|
||||||
|
|
||||||
#txtIdNew, .id_output{
|
#txtIdNew, .id_output{
|
||||||
text-transform: lowercase;
|
text-transform: lowercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
#import_export{
|
||||||
|
position: absolute;
|
||||||
|
right:10px;
|
||||||
|
top:10px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btnExportAll{
|
||||||
|
margin-bottom: 3px;
|
||||||
}
|
}
|
||||||
|
|
@ -8,9 +8,15 @@
|
||||||
<body>
|
<body>
|
||||||
<div>
|
<div>
|
||||||
<h1>__MSG_customPrompts_managePrompts__</h1>
|
<h1>__MSG_customPrompts_managePrompts__</h1>
|
||||||
<p>__MSG_customPrompts_managePrompts_info_default__
|
<p>__MSG_customPrompts_managePrompts_info_default__<br>
|
||||||
|
__MSG_customPrompts_managePrompts_info_default_2__<br>
|
||||||
|
__MSG_customPrompts_managePrompts_info_default_3__
|
||||||
<br><a href="https://micz.it/thunderbird-addon-thunderai/custom-prompts/">__MSG_customPrompts_managePrompts_help__</a></p>
|
<br><a href="https://micz.it/thunderbird-addon-thunderai/custom-prompts/">__MSG_customPrompts_managePrompts_help__</a></p>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="import_export">
|
||||||
|
<button id="btnExportAll">__MSG_customPrompts_ExportAll__</button><br>
|
||||||
|
<button id="btnImport">__MSG_customPrompts_Import__</button>
|
||||||
|
</div>
|
||||||
<div id="command_palette">
|
<div id="command_palette">
|
||||||
<button id="btnSaveAll" disabled>__MSG_customPrompts_btnSaveAll__</button>
|
<button id="btnSaveAll" disabled>__MSG_customPrompts_btnSaveAll__</button>
|
||||||
<span id="msgDisplay"></span>
|
<span id="msgDisplay"></span>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getPrompts, setDefaultPromptsProperties, setCustomPrompts } from "../js/mzta-prompts.js";
|
import { getPrompts, setDefaultPromptsProperties, setCustomPrompts, preparePromptsForExport, preparePromptsForImport } from "../js/mzta-prompts.js";
|
||||||
|
|
||||||
var promptsList = null;
|
var promptsList = null;
|
||||||
var somethingChanged = false;
|
var somethingChanged = false;
|
||||||
|
|
@ -26,91 +26,12 @@ var idnumMax = 0;
|
||||||
var msgTimeout = null;
|
var msgTimeout = null;
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
let options = {
|
|
||||||
valueNames: [ { data: ['idnum'] }, 'is_default', 'id', 'name', 'text', 'type', 'action', 'position_compose', 'position_display', { name: 'need_selected', attr: 'checked_val'}, { name: 'need_signature', attr: 'checked_val'}, { name: 'need_custom_text', attr: 'checked_val'}, { name: 'enabled', attr: 'checked_val'} ],
|
|
||||||
item: function(values) {
|
|
||||||
let type_output = '';
|
|
||||||
switch(String(values.type)){
|
|
||||||
case "0":
|
|
||||||
type_output = `__MSG_customPrompts_add_to_menu_always__`;
|
|
||||||
break;
|
|
||||||
case "1":
|
|
||||||
type_output = `__MSG_customPrompts_add_to_menu_reading__`;
|
|
||||||
break;
|
|
||||||
case "2":
|
|
||||||
type_output = `__MSG_customPrompts_add_to_menu_composing__`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let action_output = '';
|
|
||||||
switch(String(values.action)){
|
|
||||||
case "0":
|
|
||||||
action_output = `__MSG_customPrompts_close_button__`;
|
|
||||||
break;
|
|
||||||
case "1":
|
|
||||||
action_output = `__MSG_customPrompts_do_reply__`;
|
|
||||||
break;
|
|
||||||
case "2":
|
|
||||||
action_output = `__MSG_customPrompts_substitute_text__`;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
//console.log('>>>>>>>>>>>>> action_output: ' + JSON.stringify(action_output));
|
|
||||||
|
|
||||||
let output = `<tr ` + ((values.is_default == 1) ? 'class="is_default"':'') + `>
|
|
||||||
<td class="w08"><span class="id id_show"></span><input type="text" class="hiddendata id_output" value="` + values.id + `" /></td>
|
|
||||||
<td class="w08"><span class="name name_show"></span><input type="text" class="hiddendata name_output" value="` + values.name + `" /></td>
|
|
||||||
<td class="w40"><span class="text text_show"></span><textarea class="hiddendata text_output">` + values.text + `</textarea></td>
|
|
||||||
<td class="w08"><span class="type_show">` + type_output + `</span>
|
|
||||||
<select class="type_output hiddendata">
|
|
||||||
<option value="0"` + ((values.type == "0") ? ' selected':'') + `>__MSG_customPrompts_add_to_menu_always__</option>
|
|
||||||
<option value="1"` + ((values.type == "1") ? ' selected':'') + `>__MSG_customPrompts_add_to_menu_reading__</option>
|
|
||||||
<option value="2"` + ((values.type == "2") ? ' selected':'') + `>__MSG_customPrompts_add_to_menu_composing__</option>
|
|
||||||
</select>` +
|
|
||||||
`<span class="type hiddendata"></span>
|
|
||||||
</td>
|
|
||||||
<td class="w17">
|
|
||||||
Action: <span class="action_show">` + action_output + `</span>
|
|
||||||
<select class="action_output hiddendata">
|
|
||||||
<option value="0"` + ((values.action == "0") ? ' selected':'') + `>__MSG_customPrompts_close_button__</option>
|
|
||||||
<option value="1"` + ((values.action == "1") ? ' selected':'') + `>__MSG_customPrompts_do_reply__</option>
|
|
||||||
<option value="2"` + ((values.action == "2") ? ' selected':'') + `>__MSG_customPrompts_substitute_text__</option>
|
|
||||||
</select>` +
|
|
||||||
`<span class="action hiddendata"></span>
|
|
||||||
<br>
|
|
||||||
<input type="checkbox" class="need_selected" disabled> __MSG_customPrompts_form_label_need_selected__
|
|
||||||
<br>
|
|
||||||
<input type="checkbox" class="need_signature" disabled> __MSG_customPrompts_form_label_need_signature__
|
|
||||||
<br>
|
|
||||||
<input type="checkbox" class="need_custom_text" disabled> __MSG_customPrompts_form_label_need_custom_text__
|
|
||||||
<br>
|
|
||||||
<input type="checkbox" class="enabled input_mod"> __MSG_customPrompts_form_label_enabled__
|
|
||||||
<span class="is_default hiddendata"></span>
|
|
||||||
<span class="position_compose hiddendata"></span>
|
|
||||||
<span class="position_display hiddendata"></span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button class="btnEditItem"` + ((values.is_default == 1) ? ' disabled':'') + `>__MSG_customPrompts_btnEdit__</button>
|
|
||||||
<button class="btnCancelItem hiddendata"` + ((values.is_default == 1) ? ' disabled':'') + `>__MSG_customPrompts_btnCancel__</button>
|
|
||||||
<br><br>
|
|
||||||
<button class="btnConfirmItem hiddendata"` + ((values.is_default == 1) ? ' disabled':'') + `>__MSG_customPrompts_btnOK__</button>
|
|
||||||
<button class="btnDeleteItem"` + ((values.is_default == 1) ? ' disabled':'') + `>__MSG_customPrompts_btnDelete__</button>
|
|
||||||
</td>
|
|
||||||
</tr>`;
|
|
||||||
//console.log('>>>>>>>> values.name: ' + JSON.stringify(values.name));
|
|
||||||
positionMax_compose = Math.max(positionMax_compose, values.position_compose);
|
|
||||||
positionMax_display = Math.max(positionMax_display, values.position_display);
|
|
||||||
idnumMax = Math.max(idnumMax, values.idnum);
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let values = await getPrompts();
|
let values = await getPrompts();
|
||||||
|
|
||||||
//console.log('>>>>>>>>>>>>>>>> values: ' + JSON.stringify(values));
|
//console.log('>>>>>>>>>>>>>>>> values: ' + JSON.stringify(values));
|
||||||
|
|
||||||
promptsList = new List('all_prompts', options, values);
|
loadPromptsList(values);
|
||||||
|
|
||||||
checkSelectedBoxes();
|
|
||||||
|
|
||||||
const btnSaveAll = document.getElementById('btnSaveAll');
|
const btnSaveAll = document.getElementById('btnSaveAll');
|
||||||
btnSaveAll.disabled = true;
|
btnSaveAll.disabled = true;
|
||||||
|
|
@ -137,15 +58,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
}
|
}
|
||||||
btnNew.addEventListener('click', handleNewClick);
|
btnNew.addEventListener('click', handleNewClick);
|
||||||
|
|
||||||
// Enable save button on input change
|
|
||||||
function handleInputChange(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
setSomethingChanged();
|
|
||||||
}
|
|
||||||
document.querySelectorAll('.input_mod').forEach(element => {
|
|
||||||
element.addEventListener('change', handleInputChange);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Display selected value next to select input
|
// Display selected value next to select input
|
||||||
// function handleSelectChange(e) {
|
// function handleSelectChange(e) {
|
||||||
// e.preventDefault();
|
// e.preventDefault();
|
||||||
|
|
@ -157,123 +69,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// Log data ID number from item row and prepare for edit action
|
// Log data ID number from item row and prepare for edit action
|
||||||
function handleEditClick(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const tr = e.target.parentNode.parentNode;
|
|
||||||
//console.log('>>>>>>>> tr: ' + tr.getAttribute('data-idnum'));
|
|
||||||
e.target.style.display = 'none'; // Edit btn
|
|
||||||
tr.querySelector('.btnConfirmItem').style.display = 'inline'; // Save btn
|
|
||||||
tr.querySelector('.btnCancelItem').style.display = 'inline'; // Cancel btn
|
|
||||||
// tr.querySelector('.btnEditItem').style.display = 'none'; // Edit btn
|
|
||||||
tr.querySelector('.btnDeleteItem').style.display = 'none'; // Delete btn
|
|
||||||
showItemRowEditor(tr);
|
|
||||||
}
|
|
||||||
let btnEditItem_elements = document.querySelectorAll(".btnEditItem");
|
|
||||||
btnEditItem_elements.forEach(element => {
|
|
||||||
element.addEventListener('click', handleEditClick);
|
|
||||||
});
|
|
||||||
|
|
||||||
function showItemRowEditor(tr) {
|
|
||||||
tr.querySelector('.id_output').style.display = 'inline';
|
|
||||||
tr.querySelector('.id_show').style.display = 'none';
|
|
||||||
tr.querySelector('.name_output').style.display = 'inline';
|
|
||||||
tr.querySelector('.name_show').style.display = 'none';
|
|
||||||
tr.querySelector('.text_output').style.display = 'inline';
|
|
||||||
tr.querySelector('.text_show').style.display = 'none';
|
|
||||||
tr.querySelector('.type_output').style.display = 'inline';
|
|
||||||
tr.querySelector('.type_show').style.display = 'none';
|
|
||||||
tr.querySelector('.action_output').style.display = 'inline';
|
|
||||||
tr.querySelector('.action_show').style.display = 'none';
|
|
||||||
tr.querySelector('input.need_selected').disabled = false;
|
|
||||||
tr.querySelector('input.need_signature').disabled = false;
|
|
||||||
tr.querySelector('input.need_custom_text').disabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideItemRowEditor(tr) {
|
|
||||||
tr.querySelector('.id_output').style.display = 'none';
|
|
||||||
tr.querySelector('.id_show').style.display = 'inline';
|
|
||||||
tr.querySelector('.name_output').style.display = 'none';
|
|
||||||
tr.querySelector('.name_show').style.display = 'inline';
|
|
||||||
tr.querySelector('.text_output').style.display = 'none';
|
|
||||||
tr.querySelector('.text_show').style.display = 'inline';
|
|
||||||
tr.querySelector('.type_output').style.display = 'none';
|
|
||||||
tr.querySelector('.type_show').style.display = 'inline';
|
|
||||||
tr.querySelector('.action_output').style.display = 'none';
|
|
||||||
tr.querySelector('.action_show').style.display = 'inline';
|
|
||||||
tr.querySelector('input.need_selected').disabled = true;
|
|
||||||
tr.querySelector('input.need_signature').disabled = true;
|
|
||||||
tr.querySelector('input.need_custom_text').disabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Confirm and log deletion action
|
|
||||||
function handleDeleteClick(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const checkConfirm = window.confirm(browser.i18n.getMessage("customPrompts_btnDelete_confirmText"));
|
|
||||||
if (!checkConfirm) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tr = e.target.parentNode.parentNode;
|
|
||||||
//console.log('>>>>>>>> tr: ' + tr.getAttribute('data-idnum'));
|
|
||||||
promptsList.remove("id", tr.querySelector('span.id').innerText);
|
|
||||||
setSomethingChanged();
|
|
||||||
}
|
|
||||||
let btnDeleteItem_elements = document.querySelectorAll(".btnDeleteItem");
|
|
||||||
btnDeleteItem_elements.forEach(element => {
|
|
||||||
element.addEventListener('click', handleDeleteClick);
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleCancelClick(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const tr = e.target.parentNode.parentNode;
|
|
||||||
e.target.style.display = 'none'; // Cancel btn
|
|
||||||
tr.querySelector('.btnConfirmItem').style.display = 'none'; // Save btn
|
|
||||||
// tr.querySelector('.btnCancelItem').style.display = 'none'; // Cancel btn
|
|
||||||
tr.querySelector('.btnEditItem').style.display = 'inline'; // Edit btn
|
|
||||||
tr.querySelector('.btnDeleteItem').style.display = 'inline'; // Delete btn
|
|
||||||
hideItemRowEditor(tr);
|
|
||||||
}
|
|
||||||
let btnCancelItem_elements = document.querySelectorAll(".btnCancelItem");
|
|
||||||
btnCancelItem_elements.forEach(element => {
|
|
||||||
element.addEventListener('click', handleCancelClick);
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleConfirmClick(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
const tr = e.target.parentNode.parentNode;
|
|
||||||
e.target.style.display = 'none'; // Ok btn
|
|
||||||
// tr.querySelector('.btnConfirmItem').style.display = 'none'; // Ok btn
|
|
||||||
tr.querySelector('.btnCancelItem').style.display = 'none'; // Cancel btn
|
|
||||||
tr.querySelector('.btnEditItem').style.display = 'inline'; // Edit btn
|
|
||||||
tr.querySelector('.btnDeleteItem').style.display = 'inline'; // Delete btn
|
|
||||||
// Update item data
|
|
||||||
tr.querySelector('.id_show').innerText = String(tr.querySelector('.id_output').value).toLocaleLowerCase();
|
|
||||||
tr.querySelector('.name_show').innerText = tr.querySelector('.name_output').value;
|
|
||||||
tr.querySelector('.text_show').innerText = tr.querySelector('.text_output').value;
|
|
||||||
tr.querySelector('.type').innerText = tr.querySelector('.type_output').value;
|
|
||||||
tr.querySelector('.type_show').innerText = tr.querySelector('.type_output').selectedOptions[0].text;
|
|
||||||
tr.querySelector('.action').innerText = tr.querySelector('.action_output').value;
|
|
||||||
tr.querySelector('.action_show').innerText = tr.querySelector('.action_output').selectedOptions[0].text;
|
|
||||||
// the checkboxes update is handled directly by themselves
|
|
||||||
hideItemRowEditor(tr);
|
|
||||||
setSomethingChanged();
|
|
||||||
}
|
|
||||||
let btnConfirmItem_elements = document.querySelectorAll(".btnConfirmItem");
|
|
||||||
btnConfirmItem_elements.forEach(element => {
|
|
||||||
element.addEventListener('click', handleConfirmClick);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle checkbox changes and log new state
|
|
||||||
function handleCheckboxChange(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.target.setAttribute('checked_val', e.target.checked ? '1' : '0');
|
|
||||||
//console.log('>>>>>>>> checked_val: ' + e.target.getAttribute('checked_val'));
|
|
||||||
}
|
|
||||||
let checkbox_elements = document.querySelectorAll("input[type='checkbox']");
|
|
||||||
checkbox_elements.forEach(element => {
|
|
||||||
element.addEventListener('change', handleCheckboxChange);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle "type" select changes and log new state
|
// Handle "type" select changes and log new state
|
||||||
// function handleTypeSelectChange(e) {
|
// function handleTypeSelectChange(e) {
|
||||||
// e.preventDefault();
|
// e.preventDefault();
|
||||||
|
|
@ -367,8 +163,306 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Import Export
|
||||||
|
const btnExportAll = document.getElementById('btnExportAll');
|
||||||
|
btnExportAll.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
exportPrompts();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function exportPrompts() {
|
||||||
|
const manifest = browser.runtime.getManifest();
|
||||||
|
const addonVersion = manifest.version;
|
||||||
|
const outputPrompts = preparePromptsForExport(await getPrompts());
|
||||||
|
let outputObj = {id: 'thunderai-prompts', addon_version: addonVersion, prompts: outputPrompts};
|
||||||
|
const blob = new Blob([JSON.stringify(outputObj, null, 2)], {
|
||||||
|
type: "application/json",
|
||||||
|
});
|
||||||
|
const currentDate = new Date();
|
||||||
|
const time_stamp = `${currentDate.getFullYear()}${String(currentDate.getMonth() + 1).padStart(2, '0')}${String(currentDate.getDate()).padStart(2, '0')}${String(currentDate.getHours()).padStart(2, '0')}${String(currentDate.getMinutes()).padStart(2, '0')}${String(currentDate.getSeconds()).padStart(2, '0')}`;
|
||||||
|
messenger.downloads.download({
|
||||||
|
url: URL.createObjectURL(blob),
|
||||||
|
filename: `thunderai-prompts-${time_stamp}.json`,
|
||||||
|
saveAs: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const btnImport = document.getElementById('btnImport');
|
||||||
|
btnImport.addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
importPrompts();
|
||||||
|
});
|
||||||
|
|
||||||
|
function importPrompts() {
|
||||||
|
if(confirm(browser.i18n.getMessage("importPrompts_confirmText") + '\n' + browser.i18n.getMessage("customPrompts_managePrompts_info_default_2") + '\n' + browser.i18n.getMessage("customPrompts_managePrompts_info_default_3"))) {
|
||||||
|
//ask the user to choose a JSON file, and then read it, check if the serialized JSON is valid as generated from exportPrompts(), and if so, add it to the list
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'file';
|
||||||
|
input.accept = '.json';
|
||||||
|
input.click();
|
||||||
|
input.onchange = async () => {
|
||||||
|
setMessage(browser.i18n.getMessage('customPrompts_start_import'));
|
||||||
|
const file = input.files[0];
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = async () => {
|
||||||
|
const json = reader.result;
|
||||||
|
try {
|
||||||
|
const obj = JSON.parse(json);
|
||||||
|
if(obj.id !== 'thunderai-prompts') {
|
||||||
|
alert(browser.i18n.getMessage("importPrompts_invalidFile"));
|
||||||
|
setMessage(browser.i18n.getMessage('importPrompts_invalidFile'),'red');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// if(obj.addon_version !== manifest.version) {
|
||||||
|
// alert(browser.i18n.getMessage("importPrompts_invalidVersion"));
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
if(!Array.isArray(obj.prompts)) {
|
||||||
|
alert(browser.i18n.getMessage("importPrompts_invalidPrompts"));
|
||||||
|
setMessage(browser.i18n.getMessage('customPrompts_invalidPrompts'),'red');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//setCustomPrompts(obj.prompts);
|
||||||
|
promptsList.clear();
|
||||||
|
loadPromptsList(await preparePromptsForImport(obj.prompts));
|
||||||
|
setSomethingChanged();
|
||||||
|
i18n.updateDocument();
|
||||||
|
// browser.runtime.sendMessage({command: "reload_menus"});
|
||||||
|
setMessage(browser.i18n.getMessage('customPrompts_import_completed'), 'orange');
|
||||||
|
// msgTimeout = setTimeout(() => {
|
||||||
|
// clearMessage();
|
||||||
|
// }, 10000);
|
||||||
|
} catch(err) {
|
||||||
|
alert(browser.i18n.getMessage("importPrompts_invalidFile") + ' ' + err);
|
||||||
|
setMessage(browser.i18n.getMessage('importPrompts_invalidFile'),'red');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
}, { once: true });
|
}, { once: true });
|
||||||
|
|
||||||
|
//========= handling an item in a row
|
||||||
|
function handleEditClick(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const tr = e.target.parentNode.parentNode;
|
||||||
|
//console.log('>>>>>>>> tr: ' + tr.getAttribute('data-idnum'));
|
||||||
|
e.target.style.display = 'none'; // Edit btn
|
||||||
|
tr.querySelector('.btnConfirmItem').style.display = 'inline'; // Save btn
|
||||||
|
tr.querySelector('.btnCancelItem').style.display = 'inline'; // Cancel btn
|
||||||
|
// tr.querySelector('.btnEditItem').style.display = 'none'; // Edit btn
|
||||||
|
tr.querySelector('.btnDeleteItem').style.display = 'none'; // Delete btn
|
||||||
|
showItemRowEditor(tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showItemRowEditor(tr) {
|
||||||
|
tr.querySelector('.id_output').style.display = 'inline';
|
||||||
|
tr.querySelector('.id_show').style.display = 'none';
|
||||||
|
tr.querySelector('.name_output').style.display = 'inline';
|
||||||
|
tr.querySelector('.name_show').style.display = 'none';
|
||||||
|
tr.querySelector('.text_output').style.display = 'inline';
|
||||||
|
tr.querySelector('.text_show').style.display = 'none';
|
||||||
|
tr.querySelector('.type_output').style.display = 'inline';
|
||||||
|
tr.querySelector('.type_show').style.display = 'none';
|
||||||
|
tr.querySelector('.action_output').style.display = 'inline';
|
||||||
|
tr.querySelector('.action_show').style.display = 'none';
|
||||||
|
tr.querySelector('input.need_selected').disabled = false;
|
||||||
|
tr.querySelector('input.need_signature').disabled = false;
|
||||||
|
tr.querySelector('input.need_custom_text').disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideItemRowEditor(tr) {
|
||||||
|
tr.querySelector('.id_output').style.display = 'none';
|
||||||
|
tr.querySelector('.id_show').style.display = 'inline';
|
||||||
|
tr.querySelector('.name_output').style.display = 'none';
|
||||||
|
tr.querySelector('.name_show').style.display = 'inline';
|
||||||
|
tr.querySelector('.text_output').style.display = 'none';
|
||||||
|
tr.querySelector('.text_show').style.display = 'inline';
|
||||||
|
tr.querySelector('.type_output').style.display = 'none';
|
||||||
|
tr.querySelector('.type_show').style.display = 'inline';
|
||||||
|
tr.querySelector('.action_output').style.display = 'none';
|
||||||
|
tr.querySelector('.action_show').style.display = 'inline';
|
||||||
|
tr.querySelector('input.need_selected').disabled = true;
|
||||||
|
tr.querySelector('input.need_signature').disabled = true;
|
||||||
|
tr.querySelector('input.need_custom_text').disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm and log deletion action
|
||||||
|
function handleDeleteClick(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const checkConfirm = window.confirm(browser.i18n.getMessage("customPrompts_btnDelete_confirmText"));
|
||||||
|
if (!checkConfirm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tr = e.target.parentNode.parentNode;
|
||||||
|
//console.log('>>>>>>>> tr: ' + tr.getAttribute('data-idnum'));
|
||||||
|
promptsList.remove("id", tr.querySelector('span.id').innerText);
|
||||||
|
setSomethingChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCancelClick(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const tr = e.target.parentNode.parentNode;
|
||||||
|
e.target.style.display = 'none'; // Cancel btn
|
||||||
|
tr.querySelector('.btnConfirmItem').style.display = 'none'; // Save btn
|
||||||
|
// tr.querySelector('.btnCancelItem').style.display = 'none'; // Cancel btn
|
||||||
|
tr.querySelector('.btnEditItem').style.display = 'inline'; // Edit btn
|
||||||
|
tr.querySelector('.btnDeleteItem').style.display = 'inline'; // Delete btn
|
||||||
|
hideItemRowEditor(tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConfirmClick(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const tr = e.target.parentNode.parentNode;
|
||||||
|
e.target.style.display = 'none'; // Ok btn
|
||||||
|
// tr.querySelector('.btnConfirmItem').style.display = 'none'; // Ok btn
|
||||||
|
tr.querySelector('.btnCancelItem').style.display = 'none'; // Cancel btn
|
||||||
|
tr.querySelector('.btnEditItem').style.display = 'inline'; // Edit btn
|
||||||
|
tr.querySelector('.btnDeleteItem').style.display = 'inline'; // Delete btn
|
||||||
|
// Update item data
|
||||||
|
tr.querySelector('.id_show').innerText = String(tr.querySelector('.id_output').value).toLocaleLowerCase();
|
||||||
|
tr.querySelector('.name_show').innerText = tr.querySelector('.name_output').value;
|
||||||
|
tr.querySelector('.text_show').innerText = tr.querySelector('.text_output').value;
|
||||||
|
tr.querySelector('.type').innerText = tr.querySelector('.type_output').value;
|
||||||
|
tr.querySelector('.type_show').innerText = tr.querySelector('.type_output').selectedOptions[0].text;
|
||||||
|
tr.querySelector('.action').innerText = tr.querySelector('.action_output').value;
|
||||||
|
tr.querySelector('.action_show').innerText = tr.querySelector('.action_output').selectedOptions[0].text;
|
||||||
|
// the checkboxes update is handled directly by themselves
|
||||||
|
hideItemRowEditor(tr);
|
||||||
|
setSomethingChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle checkbox changes and log new state
|
||||||
|
function handleCheckboxChange(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.target.setAttribute('checked_val', e.target.checked ? '1' : '0');
|
||||||
|
//console.log('>>>>>>>> checked_val: ' + e.target.getAttribute('checked_val'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable save button on input change
|
||||||
|
function handleInputChange(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
setSomethingChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
//========= handling an item in a row - END
|
||||||
|
|
||||||
|
|
||||||
|
function loadPromptsList(values){
|
||||||
|
let options = {
|
||||||
|
valueNames: [ { data: ['idnum'] }, 'is_default', 'id', 'name', 'text', 'type', 'action', 'position_compose', 'position_display', { name: 'need_selected', attr: 'checked_val'}, { name: 'need_signature', attr: 'checked_val'}, { name: 'need_custom_text', attr: 'checked_val'}, { name: 'enabled', attr: 'checked_val'} ],
|
||||||
|
item: function(values) {
|
||||||
|
let type_output = '';
|
||||||
|
switch(String(values.type)){
|
||||||
|
case "0":
|
||||||
|
type_output = `__MSG_customPrompts_add_to_menu_always__`;
|
||||||
|
break;
|
||||||
|
case "1":
|
||||||
|
type_output = `__MSG_customPrompts_add_to_menu_reading__`;
|
||||||
|
break;
|
||||||
|
case "2":
|
||||||
|
type_output = `__MSG_customPrompts_add_to_menu_composing__`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let action_output = '';
|
||||||
|
switch(String(values.action)){
|
||||||
|
case "0":
|
||||||
|
action_output = `__MSG_customPrompts_close_button__`;
|
||||||
|
break;
|
||||||
|
case "1":
|
||||||
|
action_output = `__MSG_customPrompts_do_reply__`;
|
||||||
|
break;
|
||||||
|
case "2":
|
||||||
|
action_output = `__MSG_customPrompts_substitute_text__`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//console.log('>>>>>>>>>>>>> action_output: ' + JSON.stringify(action_output));
|
||||||
|
|
||||||
|
let output = `<tr ` + ((values.is_default == 1) ? 'class="is_default"':'') + `>
|
||||||
|
<td class="w08"><span class="id id_show"></span><input type="text" class="hiddendata id_output" value="` + values.id + `" /></td>
|
||||||
|
<td class="w08"><span class="name name_show"></span><input type="text" class="hiddendata name_output" value="` + values.name + `" /></td>
|
||||||
|
<td class="w40"><span class="text text_show"></span><textarea class="hiddendata text_output">` + values.text + `</textarea></td>
|
||||||
|
<td class="w08"><span class="type_show">` + type_output + `</span>
|
||||||
|
<select class="type_output hiddendata">
|
||||||
|
<option value="0"` + ((values.type == "0") ? ' selected':'') + `>__MSG_customPrompts_add_to_menu_always__</option>
|
||||||
|
<option value="1"` + ((values.type == "1") ? ' selected':'') + `>__MSG_customPrompts_add_to_menu_reading__</option>
|
||||||
|
<option value="2"` + ((values.type == "2") ? ' selected':'') + `>__MSG_customPrompts_add_to_menu_composing__</option>
|
||||||
|
</select>` +
|
||||||
|
`<span class="type hiddendata"></span>
|
||||||
|
</td>
|
||||||
|
<td class="w17">
|
||||||
|
Action: <span class="action_show">` + action_output + `</span>
|
||||||
|
<select class="action_output hiddendata">
|
||||||
|
<option value="0"` + ((values.action == "0") ? ' selected':'') + `>__MSG_customPrompts_close_button__</option>
|
||||||
|
<option value="1"` + ((values.action == "1") ? ' selected':'') + `>__MSG_customPrompts_do_reply__</option>
|
||||||
|
<option value="2"` + ((values.action == "2") ? ' selected':'') + `>__MSG_customPrompts_substitute_text__</option>
|
||||||
|
</select>` +
|
||||||
|
`<span class="action hiddendata"></span>
|
||||||
|
<br>
|
||||||
|
<input type="checkbox" class="need_selected" disabled> __MSG_customPrompts_form_label_need_selected__
|
||||||
|
<br>
|
||||||
|
<input type="checkbox" class="need_signature" disabled> __MSG_customPrompts_form_label_need_signature__
|
||||||
|
<br>
|
||||||
|
<input type="checkbox" class="need_custom_text" disabled> __MSG_customPrompts_form_label_need_custom_text__
|
||||||
|
<br>
|
||||||
|
<input type="checkbox" class="enabled input_mod"> __MSG_customPrompts_form_label_enabled__
|
||||||
|
<span class="is_default hiddendata"></span>
|
||||||
|
<span class="position_compose hiddendata"></span>
|
||||||
|
<span class="position_display hiddendata"></span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="btnEditItem"` + ((values.is_default == 1) ? ' disabled':'') + `>__MSG_customPrompts_btnEdit__</button>
|
||||||
|
<button class="btnCancelItem hiddendata"` + ((values.is_default == 1) ? ' disabled':'') + `>__MSG_customPrompts_btnCancel__</button>
|
||||||
|
<br><br>
|
||||||
|
<button class="btnConfirmItem hiddendata"` + ((values.is_default == 1) ? ' disabled':'') + `>__MSG_customPrompts_btnOK__</button>
|
||||||
|
<button class="btnDeleteItem"` + ((values.is_default == 1) ? ' disabled':'') + `>__MSG_customPrompts_btnDelete__</button>
|
||||||
|
</td>
|
||||||
|
</tr>`;
|
||||||
|
//console.log('>>>>>>>> values.name: ' + JSON.stringify(values.name));
|
||||||
|
positionMax_compose = Math.max(positionMax_compose, values.position_compose);
|
||||||
|
positionMax_display = Math.max(positionMax_display, values.position_display);
|
||||||
|
idnumMax = Math.max(idnumMax, values.idnum);
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
promptsList = new List('all_prompts', options, values);
|
||||||
|
|
||||||
|
checkSelectedBoxes();
|
||||||
|
let btnEditItem_elements = document.querySelectorAll(".btnEditItem");
|
||||||
|
btnEditItem_elements.forEach(element => {
|
||||||
|
element.addEventListener('click', handleEditClick);
|
||||||
|
});
|
||||||
|
|
||||||
|
let btnDeleteItem_elements = document.querySelectorAll(".btnDeleteItem");
|
||||||
|
btnDeleteItem_elements.forEach(element => {
|
||||||
|
element.addEventListener('click', handleDeleteClick);
|
||||||
|
});
|
||||||
|
|
||||||
|
let btnCancelItem_elements = document.querySelectorAll(".btnCancelItem");
|
||||||
|
btnCancelItem_elements.forEach(element => {
|
||||||
|
element.addEventListener('click', handleCancelClick);
|
||||||
|
});
|
||||||
|
|
||||||
|
let btnConfirmItem_elements = document.querySelectorAll(".btnConfirmItem");
|
||||||
|
btnConfirmItem_elements.forEach(element => {
|
||||||
|
element.addEventListener('click', handleConfirmClick);
|
||||||
|
});
|
||||||
|
|
||||||
|
let checkbox_elements = document.querySelectorAll("input[type='checkbox']");
|
||||||
|
checkbox_elements.forEach(element => {
|
||||||
|
element.addEventListener('change', handleCheckboxChange);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.input_mod').forEach(element => {
|
||||||
|
element.addEventListener('change', handleInputChange);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function checkFields() {
|
function checkFields() {
|
||||||
//console.log('>>>>>>>>>>>>> typeof promptsList: ' + typeof promptsList);
|
//console.log('>>>>>>>>>>>>> typeof promptsList: ' + typeof promptsList);
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,7 @@ function addCustomDiv(prompt_action,tabId,mailMessageId) {
|
||||||
style.textContent += "#mzta-custom_loading{height:50px;display:none;}";
|
style.textContent += "#mzta-custom_loading{height:50px;display:none;}";
|
||||||
style.textContent += "#mzta-custom_textarea{color:black;padding:1px;font-size:15px;width:100%;}";
|
style.textContent += "#mzta-custom_textarea{color:black;padding:1px;font-size:15px;width:100%;}";
|
||||||
style.textContent += "#mzta-custom_info{text-align:center;width:100%;padding-bottom:10px;font-size:15px;}";
|
style.textContent += "#mzta-custom_info{text-align:center;width:100%;padding-bottom:10px;font-size:15px;}";
|
||||||
|
style.textContent += "#mzta-prompt-name{font-size:13px;font-style:italic;color:#919191;position:fixed;bottom:75px;;left:0;padding-left:5px;}";
|
||||||
|
|
||||||
// Add <style> to the page's <head>
|
// Add <style> to the page's <head>
|
||||||
document.head.appendChild(style);
|
document.head.appendChild(style);
|
||||||
|
|
@ -179,6 +180,13 @@ function addCustomDiv(prompt_action,tabId,mailMessageId) {
|
||||||
force_go = true;
|
force_go = true;
|
||||||
};
|
};
|
||||||
modelWarnDiv.appendChild(btn_gpt35);
|
modelWarnDiv.appendChild(btn_gpt35);
|
||||||
|
|
||||||
|
//prompt name
|
||||||
|
var prompt_name_div = document.createElement('div');
|
||||||
|
prompt_name_div.id = 'mzta-prompt-name';
|
||||||
|
prompt_name_div.title= browser.i18n.getMessage("currently_used_prompt");
|
||||||
|
prompt_name_div.textContent = mztaPromptName;
|
||||||
|
fixedDiv.appendChild(prompt_name_div);
|
||||||
|
|
||||||
//status page
|
//status page
|
||||||
var status_page_div = document.createElement('div');
|
var status_page_div = document.createElement('div');
|
||||||
|
|
|
||||||
|
|
@ -93,14 +93,25 @@ export class mzta_Menus {
|
||||||
}
|
}
|
||||||
//open chatgpt window
|
//open chatgpt window
|
||||||
//console.log("Click menu item...");
|
//console.log("Click menu item...");
|
||||||
let prefs = await browser.storage.sync.get({default_chatgpt_lang: getLanguageDisplayName(browser.i18n.getUILanguage())});
|
let prefs = await browser.storage.sync.get({default_chatgpt_lang: ''});
|
||||||
let chatgpt_lang = prefs.default_chatgpt_lang;
|
let chatgpt_lang = prefs.default_chatgpt_lang;
|
||||||
|
//console.log(" >>>>>>>>>>>> chatgpt_lang: " + chatgpt_lang);
|
||||||
|
if(chatgpt_lang === ''){
|
||||||
|
chatgpt_lang = 'Reply in the language it is written.';
|
||||||
|
}else{
|
||||||
|
chatgpt_lang = browser.i18n.getMessage("prompt_lang") + " " + chatgpt_lang + ".";
|
||||||
|
}
|
||||||
|
|
||||||
var fullPrompt = curr_prompt.text + (String(curr_prompt.need_signature) == "1" ? " " + await this.getDefaultSignature():"") + " " + browser.i18n.getMessage("prompt_lang") + chatgpt_lang + ". \"" + body_text + "\" ";
|
var fullPrompt = curr_prompt.text + (String(curr_prompt.need_signature) == "1" ? " " + await this.getDefaultSignature():"") + " " + chatgpt_lang + " \"" + body_text + "\" ";
|
||||||
|
|
||||||
switch(curr_prompt.id){
|
switch(curr_prompt.id){
|
||||||
case 'prompt_translate_this':
|
case 'prompt_translate_this':
|
||||||
fullPrompt = curr_prompt.text + chatgpt_lang + ". \"" + body_text + "\" ";
|
let prefs2 = await browser.storage.sync.get({default_chatgpt_lang: getLanguageDisplayName(browser.i18n.getUILanguage())});
|
||||||
|
let chatgpt_lang2 = prefs2.default_chatgpt_lang;
|
||||||
|
if(chatgpt_lang2 === ''){
|
||||||
|
chatgpt_lang2 = getLanguageDisplayName(browser.i18n.getUILanguage());
|
||||||
|
}
|
||||||
|
fullPrompt = curr_prompt.text + chatgpt_lang2 + ". \"" + body_text + "\" ";
|
||||||
break;
|
break;
|
||||||
case 'prompt_reply':
|
case 'prompt_reply':
|
||||||
fullPrompt += "Do not add the subject line to the response."
|
fullPrompt += "Do not add the subject line to the response."
|
||||||
|
|
@ -112,7 +123,7 @@ export class mzta_Menus {
|
||||||
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
|
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
|
||||||
// add custom text if needed
|
// add custom text if needed
|
||||||
//browser.runtime.sendMessage({command: "chatgpt_open", prompt: fullPrompt, action: curr_prompt.action, tabId: tabs[0].id});
|
//browser.runtime.sendMessage({command: "chatgpt_open", prompt: fullPrompt, action: curr_prompt.action, tabId: tabs[0].id});
|
||||||
this.openChatGPT(fullPrompt, curr_prompt.action, tabs[0].id, curr_prompt.need_custom_text);
|
this.openChatGPT(fullPrompt, curr_prompt.action, tabs[0].id, curr_prompt.name, curr_prompt.need_custom_text);
|
||||||
};
|
};
|
||||||
this.rootMenu.push(curr_menu_entry);
|
this.rootMenu.push(curr_menu_entry);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,37 @@ export async function getPrompts(onlyEnabled = false){
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function preparePromptsForExport(prompts){
|
||||||
|
let output = [...prompts];
|
||||||
|
output.forEach(prompt => {
|
||||||
|
if(prompt.is_default == 1){
|
||||||
|
Object.keys(prompt).forEach(key => {
|
||||||
|
if(!['id', 'enabled', 'position_compose', 'position_display'].includes(key)){
|
||||||
|
delete prompt[key];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
delete prompt['idnum'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function preparePromptsForImport(prompts){
|
||||||
|
const output = await getPrompts();
|
||||||
|
prompts.forEach(prompt => {
|
||||||
|
if(output.some(p => p.id == prompt.id)){
|
||||||
|
Object.keys(prompt).forEach(key => {
|
||||||
|
output.find(p => p.id == prompt.id)[key] = prompt[key];
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
output.push(prompt);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
output.sort((a, b) => a.id.localeCompare(b.id));
|
||||||
|
//console.log(">>>>>>>>>>> preparePromptsForImport: " + JSON.stringify(output));
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
async function getDefaultPrompts_withProps() {
|
async function getDefaultPrompts_withProps() {
|
||||||
let prefs = await browser.storage.sync.get({_default_prompts_properties: null}); //production
|
let prefs = await browser.storage.sync.get({_default_prompts_properties: null}); //production
|
||||||
|
|
|
||||||
|
|
@ -134,4 +134,16 @@ const insertHtml = function (replyHtml, fullBody_string) {
|
||||||
});
|
});
|
||||||
fullBody.body.insertBefore(fragment, fullBody.body.firstChild);
|
fullBody.body.insertBefore(fragment, fullBody.body.firstChild);
|
||||||
return fullBody.body.innerHTML;
|
return fullBody.body.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function i18nConditionalGet(str) {
|
||||||
|
// if we are getting a string that starts with '__MSG_' and ends with '__' we return the translated string
|
||||||
|
// using the browser.i18n API
|
||||||
|
// else we return the original string
|
||||||
|
// Check if the string starts with '__MSG_' and ends with '__'
|
||||||
|
if (str.startsWith('__MSG_') && str.endsWith('__')) {
|
||||||
|
// Remove '__MSG_' from the beginning and '__' from the end
|
||||||
|
return browser.i18n.getMessage(str.substring(6, str.length - 2));
|
||||||
|
}
|
||||||
|
return str; // Return the original string if the conditions are not met
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "ThunderAI",
|
"name": "ThunderAI",
|
||||||
"description": "__MSG_extensionDescription__",
|
"description": "__MSG_extensionDescription__",
|
||||||
"version": "1.1.4",
|
"version": "1.2.0",
|
||||||
"author": "Mic (m@micz.it)",
|
"author": "Mic (m@micz.it)",
|
||||||
"homepage_url": "https://micz.it/thunderbird-addon-thunderai/",
|
"homepage_url": "https://micz.it/thunderbird-addon-thunderai/",
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
"tabs",
|
"tabs",
|
||||||
"activeTab",
|
"activeTab",
|
||||||
"accountsRead",
|
"accountsRead",
|
||||||
|
"downloads",
|
||||||
"<all_urls>"
|
"<all_urls>"
|
||||||
],
|
],
|
||||||
"background": {
|
"background": {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
import { mzta_script } from './js/mzta-chatgpt.js';
|
import { mzta_script } from './js/mzta-chatgpt.js';
|
||||||
import { prefs_default } from './options/mzta-options-default.js';
|
import { prefs_default } from './options/mzta-options-default.js';
|
||||||
import { mzta_Menus } from './js/mzta-menus.js';
|
import { mzta_Menus } from './js/mzta-menus.js';
|
||||||
import { getCurrentIdentity, getOriginalBody, replaceBody, setBody } from './js/mzta-utils.js';
|
import { getCurrentIdentity, getOriginalBody, replaceBody, setBody, i18nConditionalGet } from './js/mzta-utils.js';
|
||||||
|
|
||||||
var createdWindowID = null;
|
var createdWindowID = null;
|
||||||
var original_html = '';
|
var original_html = '';
|
||||||
|
|
@ -50,9 +50,9 @@ messenger.runtime.onMessage.addListener(async (message, sender, sendResponse) =>
|
||||||
// handler function.
|
// handler function.
|
||||||
if (message && message.hasOwnProperty("command")){
|
if (message && message.hasOwnProperty("command")){
|
||||||
switch (message.command) {
|
switch (message.command) {
|
||||||
case 'chatgpt_open':
|
// case 'chatgpt_open':
|
||||||
openChatGPT(message.prompt,message.action,message.tabId);
|
// openChatGPT(message.prompt,message.action,message.tabId);
|
||||||
return true;
|
// return true;
|
||||||
case 'chatgpt_close':
|
case 'chatgpt_close':
|
||||||
browser.windows.remove(createdWindowID).then(() => {
|
browser.windows.remove(createdWindowID).then(() => {
|
||||||
console.log("[ThunderAI] ChatGPT window closed successfully.");
|
console.log("[ThunderAI] ChatGPT window closed successfully.");
|
||||||
|
|
@ -136,7 +136,7 @@ messenger.runtime.onMessage.addListener(async (message, sender, sendResponse) =>
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
async function openChatGPT(promptText, action, curr_tabId, do_custom_text = 0) {
|
async function openChatGPT(promptText, action, curr_tabId, prompt_name = '', do_custom_text = 0) {
|
||||||
let prefs = await browser.storage.sync.get(prefs_default);
|
let prefs = await browser.storage.sync.get(prefs_default);
|
||||||
prefs = checkScreenDimensions(prefs);
|
prefs = checkScreenDimensions(prefs);
|
||||||
//console.log(">>>>>>>>>>>>>>>> prefs: " + JSON.stringify(prefs));
|
//console.log(">>>>>>>>>>>>>>>> prefs: " + JSON.stringify(prefs));
|
||||||
|
|
@ -181,6 +181,7 @@ async function openChatGPT(promptText, action, curr_tabId, do_custom_text = 0) {
|
||||||
let mztaForceCompletionTitle="`+ browser.i18n.getMessage("chatgpt_force_completion_title") +`";
|
let mztaForceCompletionTitle="`+ browser.i18n.getMessage("chatgpt_force_completion_title") +`";
|
||||||
let mztaDoCustomText=`+ do_custom_text +`;
|
let mztaDoCustomText=`+ do_custom_text +`;
|
||||||
let mztaKeepFormatting=`+ prefs.chatgpt_keep_formatting +`;
|
let mztaKeepFormatting=`+ prefs.chatgpt_keep_formatting +`;
|
||||||
|
let mztaPromptName="[`+ i18nConditionalGet(prompt_name) +`]";
|
||||||
`;
|
`;
|
||||||
|
|
||||||
browser.tabs.executeScript(createdTab.id, { code: pre_script + mzta_script, matchAboutBlank: false })
|
browser.tabs.executeScript(createdTab.id, { code: pre_script + mzta_script, matchAboutBlank: false })
|
||||||
|
|
|
||||||
|
|
@ -21,4 +21,5 @@ export const prefs_default = {
|
||||||
chatgpt_win_width: 700,
|
chatgpt_win_width: 700,
|
||||||
chatpgt_use_gpt4: false,
|
chatpgt_use_gpt4: false,
|
||||||
chatgpt_keep_formatting: false,
|
chatgpt_keep_formatting: false,
|
||||||
|
default_chatgpt_lang: '',
|
||||||
}
|
}
|
||||||
|
|
@ -61,7 +61,7 @@ function restoreOptions() {
|
||||||
break;
|
break;
|
||||||
case 'text':
|
case 'text':
|
||||||
let default_text_value = '';
|
let default_text_value = '';
|
||||||
if(element.id == 'default_chatgpt_lang') default_text_value = getLanguageDisplayName(browser.i18n.getUILanguage());
|
if(element.id == 'default_chatgpt_lang') default_text_value = prefs_default.default_chatgpt_lang;
|
||||||
element.value = result[element.id] || default_text_value;
|
element.value = result[element.id] || default_text_value;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,17 @@
|
||||||
<body>
|
<body>
|
||||||
<div id="miczBackPrefs"><a href="mzta-options.html">__MSG_backToOptionsText__</a></div>
|
<div id="miczBackPrefs"><a href="mzta-options.html">__MSG_backToOptionsText__</a></div>
|
||||||
<div id="miczRelNotes"><h1>ThunderAI Release Notes</h1>
|
<div id="miczRelNotes"><h1>ThunderAI Release Notes</h1>
|
||||||
|
<h2>Version 1.2.0 - ??/07/2024</h2>
|
||||||
|
<ul>
|
||||||
|
<li>If no default language is set in the options, the language present in the text sent to ChatGPT will be used [<a href="https://github.com/micz/ThunderAI/issues/53">#53</a>].</li>
|
||||||
|
<li>Added the functionality to import and export custom prompts [<a href="https://github.com/micz/ThunderAI/issues/65">#65</a>].</li>
|
||||||
|
<li>Showing the currently used prompt name in the ChatGPT window [<a href="https://github.com/micz/ThunderAI/issues/20">#20</a>].</li>
|
||||||
|
</ul>
|
||||||
<h2>Version 1.1.4 - 28/06/2024</h2>
|
<h2>Version 1.1.4 - 28/06/2024</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Added an option to import text with formatting from ChatGPT, set to false by default [<a href="https://github.com/micz/ThunderAI/issues/70">#70</a>], [<a href="https://github.com/micz/ThunderAI/issues/77">#77</a>].</li>
|
<li>Added an option to import text with formatting from ChatGPT, set to false by default [<a href="https://github.com/micz/ThunderAI/issues/70">#70</a>], [<a href="https://github.com/micz/ThunderAI/issues/77">#77</a>].</li>
|
||||||
<li>Improve the message warning to choose the right model [<a href="https://github.com/micz/ThunderAI/issues/76">#76</a>].</li>
|
<li>Improve the message warning to choose the right model [<a href="https://github.com/micz/ThunderAI/issues/76">#76</a>].</li>
|
||||||
<li>Added a description in the Custom Prompts page about default prompts [<a href="https://github.com/micz/ThunderAI/issues/74">#74</a>].</li>
|
<li>Added a description in the Custom Prompts page about default prompts [<a href="https://github.com/micz/ThunderAI/issues/74">#74</a>].</li>
|
||||||
<li>Added a link to the <a href="https://micz.it/thunderbird-addon-thunderai/translate/">translate page</a> in the options window [<a href="https://github.com/micz/ThunderAI/issues/68">#68</a>].</li>
|
<li>Added a link to the <a href="https://micz.it/thunderbird-addon-thunderai/translate/">translate page</a> in the options window [<a href="https://github.com/micz/ThunderAI/issues/68">#68</a>].</li>
|
||||||
</ul>
|
</ul>
|
||||||
<h2>Version 1.1.3 - 18/06/2024</h2>
|
<h2>Version 1.1.3 - 18/06/2024</h2>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue