working on #156. saving and loading custom placeholders in the dedicated page

This commit is contained in:
Mic 2025-06-15 23:27:00 +02:00
parent bed5c65de0
commit 1b5dea9253
4 changed files with 1095 additions and 0 deletions

View file

@ -1575,5 +1575,21 @@
"customDataPH_Import": {
"message": "Import new data placeholders",
"description": ""
},
"customDataPH_form_label_Text": {
"message": "Data Placeholder Text",
"description": ""
},
"customDataPH_saving_custom": {
"message": "Saving custom data placeholders...",
"description": ""
},
"customDataPH_saved": {
"message": "Custom data placeholders saved!",
"description": ""
},
"customDataPH_btnAddNewCommit": {
"message": "Add the Data Placeholder",
"description": ""
}
}

View file

@ -0,0 +1,360 @@
@media (prefers-color-scheme: light) {
table th {
background-color: #ffffff;
}
#command_palette{
background-color: #ffffff;
}
table.prompts_list tr:nth-child(even) {
background-color: #e9e9e9;
}
table.prompts_list tr:nth-child(odd) {
background-color: #f3f3f3;
}
}
table {
border-collapse: collapse;
border-spacing: 0;
border: 1px solid #ccc;
}
thead {
position: sticky;
top: 26px;
z-index: 90;
}
#command_palette{
position: sticky;
top: 0;
padding-top: 1px;
height: 26px;
width: 100%;
z-index: 90;
}
#btnNew{
float: right;
}
#btnSaveAll{
float: left;
}
#msgDisplay{
padding-left: 40px;
}
table tr {
border-bottom: 1px solid #ccc;
}
table.prompts_list th {
padding: 8px;
text-align: left;
}
table td {
border: 1px solid #ddd;
padding: 8px;
}
table td.w08{
width: 8vw;
}
table td.w17{
width: 17vw;
}
table td.w19{
width: 19vw;
}
table td.w25{
width: 25vw;
}
table td.w40{
width: 40vw;
}
table.prompts_list td.properties{
white-space: nowrap;
}
table .sort {
background-image: url(../images/listjs-arrow-sort-inactive.png);
background-repeat: no-repeat;
background-position: right center;
padding-right: 14px;
cursor: pointer;
cursor: hand;
}
table .sort.asc {
background-image: url(../images/listjs-arrow-sort-active-down.png);
}
table .sort.desc {
background-image: url(../images/listjs-arrow-sort-active-up.png);
}
tbody.list tr{
vertical-align: top;
}
.hiddendata{
display: none;
}
#formNew{
display: none;
width: 100%;
padding-bottom: 7px;
}
.text_output{
width: 98%;
height: 9em;
}
.go_right_float{
float: right;
}
.go_right{
width: 100%;
text-align: right;
}
.width_all{
width: 100%;
}
.small_label{
font-size: small;
font-style: italic;
}
.field_title{
font-weight: bold;
}
.field_title_s{
font-weight: bold;
font-size: small;
}
.field_title_us{
font-weight: bold;
font-size: smaller;
}
label, .id_show, .text_show, .name_show, .type_show, .action_show{
font-size: small;
}
#txtIdNew, .id_output{
text-transform: lowercase;
}
#import_export{
position: absolute;
right:10px;
top:10px;
text-align: right;
}
#btnExportAll{
margin-bottom: 3px;
}
.storage_space{
font-size: 0.8em;
font-weight: normal;
font-style: italic;
color: rgb(41, 41, 41);
text-align: right;
margin-right: 1px;
}
.page_title{
margin-bottom: 0px;
}
.autocomplete-container {
position: relative;
}
.autocomplete-list {
position: absolute;
top: 100%;
left: 0;
right: 0;
background-color: white;
border: 1px solid #ccc;
z-index: 1000;
max-height: 200px;
overflow-y: auto;
padding: 0;
margin: 0;
list-style: none;
font-size: small;
}
.autocomplete-list li {
padding: 8px;
cursor: pointer;
}
.autocomplete-list li:hover {
background-color: #f0f0f0;
}
.autocomplete-list li.active {
background-color: #ddd;
}
.hidden {
display: none;
}
.need_custom_text_span, .need_selected_span{
padding-right: 2px;
}
#chatgpt_web_additional_info_toggle td{
padding:0px 5px;
font-style: italic;
}
#chatgpt_web_additional_info, #chatgpt_web_additional_info_toggle, .chatgpt_web_additional_info, .chatgpt_web_additional_info_toggle, .chatgpt_web_additional_info_show{
background-color: rgb(255, 209, 183);
display:none;
}
.chatgpt_web_models_list_table{
background-color: rgb(255, 209, 183);
border:0;
}
.chatgpt_web_models_list_table *{
border:0;
}
.chatgpt_web_models_list_table td{
padding:2px;
}
#chatgpt_web_additional_info_toggle, .chatgpt_web_additional_info_toggle{
cursor: pointer;
}
#chatgpt_web_additional_info_toggle:hover, .chatgpt_web_additional_info_toggle:hover{
background-color: rgb(255, 189, 153);
text-decoration: underline;
}
#chatgpt_web_additional_info_toggle td{
text-align: center;
}
#chatgpt_web_additional_info td{
vertical-align: top;
}
.chatgpt_web_additional_info_toggle{
width: -moz-available;
text-align: center;
margin-top: 10px;
}
.chatgpt_web_additional_info_show{
flex-direction: column;
justify-content: space-between;
height: 100%;
padding: 3px;
margin-top: 5px;
}
.chatgpt_web_additional_info_row{
display: flex;
}
.chatgpt_web_additional_info_row span{
margin-right: 0.4em;
}
input.input_additional[type="text"]{
width: -moz-available;
}
.small_info{
font-size:smaller;
}
.conntype_chatgpt_web_option{
cursor: pointer;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #1C1B22;
color: rgb(251, 251, 254);
}
a:link { color: #409EFF; }
a:visited { color: #409EFF; }
a:hover { color: #66B1FF; }
a:active { color: #66B1FF; }
table {
background-color: #1C1B22;
border: 1px solid #ccc;
}
table.prompts_list th {
background-color: #4c4e58;
border: 1px solid rgb(251, 251, 254);
color: rgb(194, 194, 194);
}
#command_palette{
background-color: inherit;
}
table.prompts_list tr:nth-child(even) {
background-color: #2E2F36;
}
.storage_space{
color: rgb(182, 182, 182);
}
.autocomplete-list {
background-color: #2E2F36;
border: 1px solid #2E2F36;
}
.autocomplete-list li:hover {
background-color: #4c4e58;
}
.autocomplete-list li.active {
background-color: #4c4e58;
}
#chatgpt_web_additional_info, #chatgpt_web_additional_info_toggle, .chatgpt_web_additional_info, .chatgpt_web_additional_info_toggle, .chatgpt_web_additional_info_show, .chatgpt_web_models_list_table{
background-color: rgb(39, 11, 0);
color: rgb(182, 182, 182);
}
#chatgpt_web_additional_info_toggle:hover, .chatgpt_web_additional_info_toggle:hover{
background-color: rgb(88, 25, 0);
}
}

View file

@ -0,0 +1,91 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ThunderAI - __MSG_customDataPH_manageDataPH__</title>
<link rel="stylesheet" href="mzta-custom-dataplaceholders.css">
<link rel="icon" href="../../images/icon-16px.png">
</head>
<body>
<div>
<h1 class="page_title">__MSG_customDataPH_manageDataPH__</h1>
<p>__MSG_customDataPH_manageDataPH_info_default__<br>
__MSG_customPrompts_managePrompts_info_default_3__
<br>__MSG_prefs_OptionText_btnManagePrompts_infoline__ <a href="https://micz.it/thunderbird-addon-thunderai/data-placeholders/">__MSG_more_info_string__</a></p>
</div>
<div id="import_export">
<button id="btnExportAll">__MSG_customDataPH_ExportAll__</button><br>
<button id="btnImport">__MSG_customDataPH_Import__</button>
</div>
<div id="command_palette">
<button id="btnSaveAll" disabled>__MSG_btnSaveAll_string__</button>
<span id="msgDisplay"></span>
<button id="btnNew">__MSG_btnNew_string__</button>
</div>
<div id="formNew">
<table class="width_all">
<tr>
<td class="w19">
<label for="txtIdNew" class="field_title">*__MSG_customPrompts_form_label_ID__:</label>
<br>
<i>thunderai_custom_</i><input type="text" id="txtIdNew" name="id" class="input_new" tabindex="1">
<br><span class="small_label">[__MSG_customPrompts_form_label_ID_rules__]</span>
</td>
<td rowspan="2" class="w25">
<label for="txtTextNew" class="field_title">*__MSG_customDataPH_form_label_Text__:</label>
<br>
<div class="autocomplete-container">
<textarea id="txtTextNew" name="text" class="input_new text_output editor" tabindex="3"></textarea>
<ul class="autocomplete-list hidden"></ul>
</div>
</td>
<td rowspan="2" class="w19">
<label for="selectTypeNew" class="field_title">__MSG_customPrompts_add_to_menu__:</label>
<br>
<select id="selectTypeNew" name="type" tabindex="4" class="type_output">
<option value="0">__MSG_customPrompts_add_to_menu_always__</option>
<option value="1">__MSG_customPrompts_add_to_menu_reading__</option>
<option value="2">__MSG_customPrompts_add_to_menu_composing__</option>
</select>
</td>
<td rowspan="2" class="w19">
<label class="field_title">__MSG_customPrompts_Properties__:</label>
<br>
<span class="need_selected_span"><input type="checkbox" id="checkboxNeedSelectedNew" name="need_selected" class="need_selected_new" value="1" tabindex="6"> <label for="checkboxNeedSelectedNew">__MSG_customPrompts_form_label_need_selected__</label></span>
<br>
<span class="need_custom_text_span"><input type="checkbox" id="checkboxNeedCustomTextNew" name="need_custom_text" class="need_custom_text_new" value="1" tabindex="8"> <label for="checkboxNeedCustomTextNew">__MSG_customPrompts_form_label_need_custom_text__</label></span>
</td>
</tr>
<tr>
<td class="w19">
<label for="txtNameNew" class="field_title">*__MSG_customPrompts_form_label_Name__:</label>
<br>
<input type="text" id="txtNameNew" name="name" class="input_new" tabindex="2">
</td>
</tr>
</table>
<div class="go_right_float">* __MSG_customPrompts_form_required_fields__</div>
<br>
<div class="go_right"><button id="btnAddNew" disabled>__MSG_customDataPH_btnAddNewCommit__</button></div>
</div>
<div id="all_custom_dataplaceholders">
<table class="prompts_list">
<thead>
<tr>
<th class="sort" data-sort="id">__MSG_customPrompts_form_label_ID__</th>
<th class="sort" data-sort="name">__MSG_customPrompts_form_label_Name__</th>
<th class="sort" data-sort="text">__MSG_customDataPH_form_label_Text__</th>
<th>__MSG_customPrompts_Properties__</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody class="list">
</tbody>
</table>
</div>
<div class="storage_space">__MSG_StorageSpace__: <span id="storage_space"></span></div>
<script src="../_lib/list.js"></script>
<script src="mzta-custom-dataplaceholders.js" type="module"></script>
<script src="../../js/mzta-i18n.js"></script>
</body>
</html>

View file

@ -0,0 +1,628 @@
/*
* ThunderAI [https://micz.it/thunderbird-addon-thunderai/]
* Copyright (C) 2024 - 2025 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 { prefs_default } from "../../options/mzta-options-default.js";
import { getLocalStorageUsedSpace, sanitizeHtml, isThunderbird128OrGreater } from "../../js/mzta-utils.js";
import { taLogger } from "../../js/mzta-logger.js";
import { getPlaceholders, setCustomPlaceholders, getCustomPlaceholders, prepareCustomDataPHsForExport, prepareCustomDataPHsForImport, placeholdersUtils } from "../../js/mzta-placeholders.js";
import { textareaAutocomplete } from "../../js/mzta-placeholders-autocomplete.js";
let prefs = null;
var customDataPHsList = null;
var somethingChanged = false;
var idnumMax = 0;
var msgTimeout = null;
let taLog = null;
let autocompleteSuggestions = [];
document.addEventListener('DOMContentLoaded', async () => {
prefs = await browser.storage.sync.get({ do_debug: prefs_default.do_debug });
taLog = new taLogger("mzta-custom-dataplaceholders", prefs.do_debug);
setStorageSpace();
let values = await getCustomPlaceholders();
//console.log('>>>>>>>>>>>>>>>> values: ' + JSON.stringify(values));
loadCustomDataPHsList(values);
const btnSaveAll = document.getElementById('btnSaveAll');
btnSaveAll.disabled = true;
// Disable save button and handle save actions
function handleSaveAllClick(e) {
e.preventDefault();
saveAll();
clearFields();
}
btnSaveAll.addEventListener('click', handleSaveAllClick);
const btnNew = document.getElementById('btnNew');
// Show the new item form
function handleNewClick(e) {
e.preventDefault();
e.target.disabled = true;
document.getElementById('formNew').style.display = 'block';
window.scrollTo({
top: 0,
behavior: 'smooth'
});
}
btnNew.addEventListener('click', handleNewClick);
// for the new prompt form
let btnNew_elements = document.querySelectorAll(".input_new");
if(btnNew_elements) {
btnNew_elements.forEach(element => {
element.addEventListener('change', (e) => {
e.preventDefault();
checkFields();
});
});
}
const textareas = document.querySelectorAll('.editor');
autocompleteSuggestions = (await getPlaceholders())
.filter(p => p.is_default == "1")
.map(p => ({ command: '{%' + p.id + '%}', type: p.type }));
// console.log('>>>>>>>>>>> suggestions: ' + JSON.stringify(suggestions));
textareas.forEach(textarea => {
textareaAutocomplete(textarea, autocompleteSuggestions);
textareas.forEach(textarea => {
textarea.addEventListener('input', (e) => {
checkPromptsConfigForPlaceholders(e.target);
});
textareaAutocomplete(textarea, autocompleteSuggestions);
});
});
i18n.updateDocument();
//To add a new item
var txtIdNew = document.getElementById('txtIdNew');
var txtNameNew = document.getElementById('txtNameNew');
var txtTextNew = document.getElementById('txtTextNew');
var selectTypeNew = document.getElementById('selectTypeNew');
var checkboxNeedSelectedNew = document.getElementById('checkboxNeedSelectedNew');
var checkboxNeedCustomTextNew = document.getElementById('checkboxNeedCustomTextNew');
const btnAddNew = document.getElementById('btnAddNew');
btnAddNew.addEventListener('click', (e) => {
e.preventDefault();
if(!checkFields()) {
return;
}
let newItemData = {
id: String(txtIdNew.value.trim()).toLocaleLowerCase(),
name: txtNameNew.value.trim(),
text: txtTextNew.value.trim(),
type: selectTypeNew.value,
need_selected: (checkboxNeedSelectedNew.checked) ? 1 : 0,
need_custom_text: (checkboxNeedCustomTextNew.checked) ? 1 : 0,
enabled: 1,
is_default: 0,
idnum: idnumMax + 1,
};
let newItem = customDataPHsList.add(newItemData);
idnumMax++;
let curr_idnum = newItem[0].values().idnum;
let checkboxes = document.querySelectorAll(`tr[data-idnum="${curr_idnum}"] input[type="checkbox"]`);
checkSelectedBoxes(checkboxes);
let editBtn = document.querySelector(`tr[data-idnum="${curr_idnum}"] button.btnEditItem`);
//console.log(`>>>>>>>>>>>> tr[data-idnum="${curr_idnum}"] button.btnEditItem`);
editBtn.addEventListener('click', handleEditClick);
//console.log('>>>>>>>>>>>>> editBtn: ' + JSON.stringify(editBtn));
let deleteBtn = document.querySelector(`tr[data-idnum="${curr_idnum}"] button.btnDeleteItem`);
//console.log(`>>>>>>>>>>>> tr[data-idnum="${curr_idnum}"] button.btnDeleteItem`);
deleteBtn.addEventListener('click', handleDeleteClick);
let okBtn = document.querySelector(`tr[data-idnum="${curr_idnum}"] button.btnConfirmItem`);
okBtn.addEventListener('click', handleConfirmClick);
let cancelBtn = document.querySelector(`tr[data-idnum="${curr_idnum}"] button.btnCancelItem`);
cancelBtn.addEventListener('click', handleCancelClick);
// console.log('>>>>>>>>>>>>> deleteBtn: ' + JSON.stringify(deleteBtn));
// console.log('>>>>>>>>>>>>> newItem: ' + JSON.stringify(newItem));
document.getElementById('btnNew').disabled = false;
clearFields();
setSomethingChanged();
i18n.updateDocument();
window.scrollTo({
top: document.body.scrollHeight,
behavior: 'smooth'
});
});
//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);
customDataPHsList.clear();
loadCustomDataPHsList(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 });
//========= 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';
const text_output = tr.querySelector('.text_output');
text_output.style.display = 'inline';
textareaAutocomplete(text_output, autocompleteSuggestions)
tr.querySelector('.text_show').style.display = 'none';
tr.querySelector('.type_output').style.display = 'inline';
tr.querySelector('.type_show').style.display = 'none';
tr.querySelector('input.need_selected').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('input.need_selected').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'));
customDataPHsList.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
tr.querySelector('.id_output').value = tr.querySelector('.id_show').innerText.toLocaleUpperCase();
tr.querySelector('.name_output').value = tr.querySelector('.name_show').innerText;
tr.querySelector('.text_output').value = sanitizeHtml(tr.querySelector('.text_show').innerHTML).replace(/<br\s*\/?>/gi, "\n");
tr.querySelector('.type_output').value = tr.querySelector('.type').innerText;
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;
// 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'));
if (e.target.classList.contains('need_selected') || e.target.classList.contains('need_custom_text') || e.target.classList.contains('need_selected_new') || e.target.classList.contains('need_custom_text_new')) {
let textarea = e.target.closest('tr').querySelector('.text_output');
checkPromptsConfigForPlaceholders(textarea);
}
}
// Enable save button on input change
function handleInputChange(e) {
e.preventDefault();
setSomethingChanged();
}
//========= handling an item in a row - END
function loadCustomDataPHsList(values){
// console.log('>>>>>>>> loadCustomDataPHsList values: ' + JSON.stringify(values));
let options = {
valueNames: [
{ data: ['idnum'] },
'is_default',
'id',
'name',
'text',
'type',
{ name: 'need_selected', attr: 'checked_val'},
{ name: 'need_custom_text', attr: 'checked_val'},
{ name: 'enabled', attr: 'checked_val'}
],
item: function(values) {
values.id = placeholdersUtils.stripCustomDataPH_ID_Prefix(values.id);
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 output = `<tr ` + ((values.is_default == 1) ? 'class="is_default"':'') + `>
<td class="w08"><i>thunderai_custom_</i><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>
<div class="autocomplete-container">
<textarea class="hiddendata text_output editor">` + values.text.replace(/<br\s*\/?>/gi, "\n") + `</textarea>
<ul class="autocomplete-list hidden"></ul>
</div>
</td>
<td class="w08"><span class="field_title_s">__MSG_customPrompts_add_to_menu__:</span>
<br>
<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">
<label><span class="need_selected_span"><input type="checkbox" class="need_selected" disabled> __MSG_customPrompts_form_label_need_selected__</span></label>
<br>
<label><span class="need_custom_text_span"><input type="checkbox" class="need_custom_text` + ((values.is_default == 1) ? ' input_mod':'') + `"` + ((values.is_default == 0) ? ' disabled':'') + ` > __MSG_customPrompts_form_label_need_custom_text__</span></label>
<br>
<label><input type="checkbox" class="enabled input_mod"> __MSG_customPrompts_form_label_enabled__</label>
<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));
idnumMax = Math.max(idnumMax, values.idnum);
return output;
}
};
// console.log('>>>>>>>>>>>>> options: ' + JSON.stringify(options));
// console.log('>>>>>>>>>>>>> values: ' + JSON.stringify(values));
customDataPHsList = new List('all_custom_dataplaceholders', 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() {
//console.log('>>>>>>>>>>>>> typeof customDataPHsList: ' + typeof customDataPHsList);
//console.log('>>>>>>>>>>>>> Array.isArray(customDataPHsList): ' + Array.isArray(customDataPHsList));
// the id must be unique and without spaces
let is_error = false;
let id_value = document.getElementById('txtIdNew').value.trim();
if ((id_value == '')||
(/\s/.test(id_value))) {
inputSetError('txtIdNew');
is_error = true;
} else {
let exists = customDataPHsList.get("id", id_value);
//console.log('>>>>>>>>>>>>> exists: ' + JSON.stringify(exists));
if(exists && exists.length > 0) {
inputSetError('txtIdNew');
is_error = true;
} else {
inputClearError('txtIdNew');
}
}
if (document.getElementById('txtNameNew').value.trim() == '') {
inputSetError('txtNameNew');
is_error = true;
} else {
inputClearError('txtNameNew');
}
if (document.getElementById('txtTextNew').value.trim() == '') {
inputSetError('txtTextNew');
is_error = true;
} else {
inputClearError('txtTextNew');
}
document.getElementById('btnAddNew').disabled = is_error;
return !is_error;
}
function clearFields() {
document.getElementById('txtIdNew').value = '';
document.getElementById('txtNameNew').value = '';
document.getElementById('txtTextNew').value = '';
document.getElementById('selectTypeNew').value = '0';
document.getElementById('checkboxNeedSelectedNew').value = '0';
document.getElementById('checkboxNeedCustomTextNew').value = '0';
document.getElementById('formNew').style.display = 'none';
}
function inputSetError(input) {
document.getElementById(input).style.borderColor = 'red';
}
function inputClearError(input) {
document.getElementById(input).style.borderColor = 'green';
}
function setSomethingChanged(){
clearTimeout(msgTimeout);
somethingChanged = true;
document.getElementById('btnSaveAll').disabled = false;
let msgDisplay = document.getElementById('msgDisplay');
msgDisplay.textContent = browser.i18n.getMessage('customPrompts_unsaved_changes');
msgDisplay.style.display = 'inline';
msgDisplay.style.color = 'red';
}
function setNothingChanged(){
somethingChanged = false;
document.getElementById('btnSaveAll').disabled = true;
let msgDisplay = document.getElementById('msgDisplay');
msgDisplay.disabled = true;
msgDisplay.textContent = ''
msgDisplay.style.display = 'none';
msgDisplay.style.color = '';
}
function checkSelectedBoxes(checkboxes = null) {
if(checkboxes == null){
checkboxes = [
...document.querySelectorAll('.need_selected[type="checkbox"]'),
...document.querySelectorAll('.need_custom_text[type="checkbox"]'),
...document.querySelectorAll('.enabled[type="checkbox"]'),
];
}
// Iterate through the checkboxes
checkboxes.forEach(checkbox => {
// Check if the 'checked' attribute is "0"
if (checkbox.getAttribute('checked_val') == "0") {
// Uncheck the checkbox
checkbox.checked = false;
} else {
checkbox.checked = true;
}
});
}
//Save all custom data placeholders
async function saveAll() {
setMessage(browser.i18n.getMessage('customDataPH_start_saving'));
setNothingChanged();
if(customDataPHsList != null) {
setMessage(browser.i18n.getMessage('customPrompts_reindexing_list'));
customDataPHsList.reIndex();
let newCustomDataPHs = customDataPHsList.items.filter(item => item.values().is_default == 0).map(item => item.values());
taLog.log('newCustomDataPlaceholders: ' + JSON.stringify(newCustomDataPHs));
// newCustomDataPHs.forEach(prompt => {
// console.log('>>>>>>>>>>>>> id: ' + JSON.stringify(prompt));
// });
//console.log('>>>>>>>>>>>>> saveAll: ' + JSON.stringify(newCustomDataPHs));
await setCustomPlaceholders(newCustomDataPHs);
setMessage(browser.i18n.getMessage('customDataPH_saved'),'green');
msgTimeout = setTimeout(() => {
clearMessage();
}, 10000)
}
setStorageSpace();
}
function setMessage(msg, color = '') {
clearTimeout(msgTimeout);
let msgDisplay = document.getElementById('msgDisplay');
msgDisplay.textContent = msg;
msgDisplay.style.display = 'inline';
msgDisplay.style.color = color;
}
function clearMessage() {
let msgDisplay = document.getElementById('msgDisplay');
msgDisplay.textContent = '';
msgDisplay.style.display = 'none';
msgDisplay.style.color = '';
}
async function setStorageSpace() {
let storage_space = await getLocalStorageUsedSpace();
document.getElementById('storage_space').textContent = storage_space;
}
if(await isThunderbird128OrGreater()){
window.addEventListener('beforeunload', function (event) {
// Check if any changes have been made (Only for Thunderbird 128+ see https://github.com/micz/ThunderAI/issues/88)
if (somethingChanged) {
event.preventDefault();
}
});
}
function checkPromptsConfigForPlaceholders(textarea){
// check additional_text and selected_text placeholders presence and the corrispondent checkboxes
let tr_ancestor = textarea.closest('tr');
let need_custom_text_element = tr_ancestor.querySelector('.need_custom_text') || tr_ancestor.querySelector('.need_custom_text_new');
if(String(textarea.value).indexOf('{%additional_text%}') != -1){
if(!need_custom_text_element.checked){
need_custom_text_element.closest('.need_custom_text_span').style.border = '2px solid red';
}else{
need_custom_text_element.closest('.need_custom_text_span').style.border = '';
}
}else{
need_custom_text_element.closest('.need_custom_text_span').style.border = '';
}
let tr_ancestor2 = textarea.closest('tr');
let selected_text_element = tr_ancestor2.querySelector('.need_selected') || tr_ancestor2.querySelector('.need_selected_new');
if((String(textarea.value).indexOf('{%selected_text%}') != -1)||(String(textarea.value).indexOf('{%selected_html%}') != -1)){
if(!selected_text_element.checked){
selected_text_element.closest('.need_selected_span').style.border = '2px solid red';
}else{
selected_text_element.closest('.need_selected_span').style.border = '';
}
}else{
selected_text_element.closest('.need_selected_span').style.border = '';
}
}