First working version

This commit is contained in:
mic 2024-04-01 22:58:39 +02:00
parent e6363e7af2
commit 41e4a2e547
21 changed files with 885 additions and 0 deletions

111
_locales/en/messages.json Normal file
View file

@ -0,0 +1,111 @@
{
"extensionDescription": {
"message": "Use ChatGPT to enhance you emails!",
"description": "Description of the extension."
},
"menu_title": {
"message": "AI",
"description": ""
},
"prompt_lang": {
"message": "Replay in ",
"description": ""
},
"prompt_translate_lang": {
"message": "english.",
"description": ""
},
"prompt_reply": {
"message": "Reply to this",
"description": ""
},
"prompt_rewrite_polite": {
"message": "Rewrite polite",
"description": ""
},
"prompt_rewrite_formal": {
"message": "Rewrite formal",
"description": ""
},
"prompt_classify": {
"message": "Classify",
"description": ""
},
"prompt_summarize_this": {
"message": "Summarize this",
"description": ""
},
"prompt_translate_this": {
"message": "Translate this",
"description": ""
},
"prompt_selection_needed": {
"message": "To proceed, you need to select some text!",
"description": ""
},
"chatgpt_win_working": {
"message": "Work in progress...",
"description": ""
},
"chatgpt_win_job_completed": {
"message": "Completed!",
"description": ""
},
"chatgpt_win_get_answer": {
"message": "Use last answer",
"description": ""
},
"chatgpt_win_close": {
"message": "Close",
"description": ""
},
"prefsInfoTitle": {
"message": "Important Information",
"description": ""
},
"prefsInfoDesc_1": {
"message": "It's not always possibile to get the right file type from the email received by Sharepoint or OneDrive.",
"description": ""
},
"prefsInfoDesc_2": {
"message": "If this extension can't understand the file type, it will show four buttons: one to use Word, one to use Excel, one to use PowerPoint and one to open the file in the browser.",
"description": ""
},
"prefsInfoDesc_3": {
"message": "The option to force to use Microsoft Edge is for a scenario where you do not use Edge as the default browser, but is the browser used with your Business account.",
"description": ""
},
"prefsDonation_1": {
"message": "Do you like this addon?",
"description": ""
},
"prefsDonation_2": {
"message": "Consider to make a donation!",
"description": ""
},
"backToOptionsText": {
"message": "Options",
"description": ""
}
}

111
_locales/it/messages.json Normal file
View file

@ -0,0 +1,111 @@
{
"extensionDescription": {
"message": "Apri un condiviso di un file di SharePoint o OneDrive direttamente con l'applicazione corretta, invece che il browser.",
"description": "Descrizione dell'estensione."
},
"menu_title": {
"message": "IA",
"description": ""
},
"prompt_lang": {
"message": "Rispondi in ",
"description": ""
},
"prompt_translate_lang": {
"message": "italiano.",
"description": ""
},
"prompt_reply": {
"message": "Rispondi",
"description": ""
},
"prompt_rewrite_polite": {
"message": "Riscrivi cortese",
"description": ""
},
"prompt_rewrite_formal": {
"message": "Riscrivi formale",
"description": ""
},
"prompt_classify": {
"message": "Classifica",
"description": ""
},
"prompt_summarize_this": {
"message": "Riassumi",
"description": ""
},
"prompt_translate_this": {
"message": "Traduci",
"description": ""
},
"prompt_selection_needed": {
"message": "Per procedere è necessario che selezioni del testo!",
"description": ""
},
"chatgpt_win_working": {
"message": "Elaborazione in corso...",
"description": ""
},
"chatgpt_win_job_completed": {
"message": "Completato!",
"description": ""
},
"chatgpt_win_get_answer": {
"message": "Usa l'ultima risposta",
"description": ""
},
"chatgpt_win_close": {
"message": "Chiudi",
"description": ""
},
"prefsInfoTitle": {
"message": "Informazioni Importanti",
"description": ""
},
"prefsInfoDesc_1": {
"message": "Non è sempre possibile capire il tupo di file dalla mail ricevuta da SharePoint o OneDrive.",
"description": ""
},
"prefsInfoDesc_2": {
"message": "Se questa estensione non riesce a riconoscere il tipo del file, mostrerà quattro pulsanti: uno per usare Word, uno per Excel, uno per PowerPoint e uno per aprire il link nel browser.",
"description": ""
},
"prefsInfoDesc_3": {
"message": "L'opzione di forzare l'apertura in Microsoft Edge è per lo scenario in cui non stai usando Edge come browser predefinito, ma è il browser con il tuo account aziendale.",
"description": ""
},
"prefsDonation_1": {
"message": "Ti piace questo componente aggiuntivo?",
"description": ""
},
"prefsDonation_2": {
"message": "Considera di fare una donazione!",
"description": ""
},
"backToOptionsText": {
"message": "Opzioni",
"description": ""
}
}

BIN
images/icon-16px.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 B

BIN
images/icon-32px.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
images/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

156
js/mzta-chatgpt.js Normal file
View file

@ -0,0 +1,156 @@
// Original methods derived from https://github.com/KudoAI/chatgpt.js/blob/7eb8463cd61143fa9e1d5a8ec3c14d3c1b286e54/chatgpt.js
// Using a full string to inject it in the chatgpt page to avoid any security error
export const mzta_script = `
async function chatgpt_sendMsg(msg, method ='') {
const textArea = document.querySelector('form textarea'),
sendButton = document.querySelector('form button[class*="bottom"]');
textArea.value = msg;
textArea.dispatchEvent(new Event('input', { bubbles: true })); // enable send button
const delaySend = setInterval(() => {
if (!sendButton?.hasAttribute('disabled')) { // send msg
method.toLowerCase() == 'click' ? sendButton.click()
: textArea.dispatchEvent(new KeyboardEvent('keydown', { keyCode: 13, bubbles: true }));
clearInterval(delaySend);
}
}, 25);
}
async function chatgpt_isIdle() {
return new Promise(resolve => {
const intervalId = setInterval(() => {
if (chatgpt_getRegenerateButton()) {
clearInterval(intervalId); resolve(true);
}}, 100);});}
function chatgpt_getRegenerateButton() {
for (const mainSVG of document.querySelectorAll('main svg')) {
if (mainSVG.querySelector('path[d*="M4.5 2.5C5.05228"]')) // regen icon found
return mainSVG.parentNode.parentNode;
}
}
async function chatgpt_getFromDOM(pos) {
const responseDivs = document.querySelectorAll('div[data-testid*="conversation-turn"]:nth-child(odd)'),
strPos = pos.toString().toLowerCase();
let response = '';
if (responseDivs.length) {
if (/last|final/.test(strPos)) // get last response
response = responseDivs[responseDivs.length - 1].textContent;
else { // get nth response
const nthOfResponse = (
// Calculate base number
Number.isInteger(pos) ? pos : // do nothing for integers
/^\d+/.test(strPos) ? /^\d+/.exec(strPos)[0] : // extract first digits for strings w/ them
( // convert words to integers for digitless strings
/^(?:1|one|fir)(?:st)?$/.test(strPos) ? 1
: /^(?:2|tw(?:o|en|el(?:ve|f))|seco)(?:nd|t[yi])?(?:e?th)?$/.test(strPos) ? 2
: /^(?:3|th(?:ree|ir?))(?:rd|teen|t[yi])?(?:e?th)?$/.test(strPos) ? 3
: /^(?:4|fou?r)(?:teen|t[yi])?(?:e?th)?$/.test(strPos) ? 4
: /^(?:5|fi(?:ve|f))(?:teen|t[yi])?(?:e?th)?$/.test(strPos) ? 5
: /^(?:6|six)(?:teen|t[yi])?(?:e?th)?$/.test(strPos) ? 6
: /^(?:7|seven)(?:teen|t[yi])?(?:e?th)?$/.test(strPos) ? 7
: /^(?:8|eight?)(?:teen|t[yi])?(?:e?th)?$/.test(strPos) ? 8
: /^(?:9|nine?)(?:teen|t[yi])?(?:e?th)?$/.test(strPos) ? 9
: /^(?:10|ten)(?:th)?$/.test(strPos) ? 10 : 1 )
// Transform base number if suffixed
* ( /(ty|ieth)$/.test(strPos) ? 10 : 1 ) // x 10 if -ty/ieth
+ ( /teen(th)?$/.test(strPos) ? 10 : 0 ) // + 10 if -teen/teenth
);
response = responseDivs[nthOfResponse - 1].textContent;
}
response = response.replace(/^ChatGPTChatGPT/, ''); // strip sender name
}
return response;
}
function chatpgt_scrollToBottom () {
try { document.querySelector('button[class*="cursor"][class*="bottom"]').click(); }
catch (err) { console.error('', err); }
}
function addCustomDiv(prompt_action,tabId) {
// Create <style> element for the CSS
var style = document.createElement('style');
style.innerHTML = ".mzta-header-fixed {position: fixed;top: 0;left: 0;height:100px;width: 100%;background-color: #333;color: white;text-align: center;padding: 10px 0;z-index: 1000;}"
style.innerHTML += "body {padding-top: 100px;}";
style.innerHTML += "#mzta-ok_btn {background-color: #007bff;border: none;color: white;padding: 8px 15px;text-align: center;text-decoration: none;display: inline-block;font-size: 16px;margin: 4px 2px;transition-duration: 0.4s;cursor: pointer;border-radius: 5px;}";
style.innerHTML += "#mzta-ok_btn:hover {background-color: #0056b3;color: white;}";
style.innerHTML += "#mzta-curr_msg{}";
// Add <style> to the page's <head>
document.head.appendChild(style);
// Fixed div
var divFisso = document.createElement('div');
divFisso.classList.add('mzta-header-fixed');
divFisso.textContent = '';
// span for the text
var curr_msg = document.createElement('span');
curr_msg.id='mzta-curr_msg';
curr_msg.innerHTML = browser.i18n.getMessage("chatgpt_win_working");
divFisso.appendChild(curr_msg);
var pulsante = document.createElement('button');
pulsante.id="mzta-ok_btn";
//console.log('default: '+prompt_action)
switch(prompt_action){
default:
case 0: // close window
pulsante.innerHTML = browser.i18n.getMessage("chatgpt_win_close");
pulsante.onclick = async function() {
browser.runtime.sendMessage({command: "chatgpt_close"});
};
break;
case 1: // do reply
pulsante.innerHTML = browser.i18n.getMessage("chatgpt_win_get_answer");
pulsante.onclick = async function() { // TODO
const response = await chatgpt_getFromDOM('last');
//console.log(response);
browser.runtime.sendMessage({command: "chatgpt_close"});
};
break;
case 2: // replace text
pulsante.innerHTML = browser.i18n.getMessage("chatgpt_win_get_answer");
pulsante.onclick = async function() { // TODO
const response = await chatgpt_getFromDOM('last');
//console.log('replace text: '+tabId)
browser.runtime.sendMessage({command: "chatgpt_replaceSelectedText", text: response, tabId: tabId});
//console.log(response);
browser.runtime.sendMessage({command: "chatgpt_close"});
};
break;
}
pulsante.style.display = 'none';
divFisso.appendChild(pulsante);
document.body.insertBefore(divFisso, document.body.firstChild);
}
function operation_done(){
document.getElementById('mzta-curr_msg').innerHTML = browser.i18n.getMessage("chatgpt_win_job_completed")+"<br>";
document.getElementById('mzta-ok_btn').style.display = 'inline';
chatpgt_scrollToBottom();
}
// Nello script di contenuto
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.command === "chatgpt_send") {
addCustomDiv(message.action,message.tabId);
//console.log(message.prompt);
(async () => {
//await chatgpt.isLoaded();
await chatgpt_sendMsg(message.prompt,'click');
await chatgpt_isIdle();
// console.log(response);
operation_done();
})();
}
});
`

76
js/mzta-compose-script.js Normal file
View file

@ -0,0 +1,76 @@
// Modified version derived from https://github.com/ali-raheem/Aify/blob/13ff87583bc520fb80f555ab90a90c5c9df797a7/plugin/content_scripts/compose.js
const makeParagraphs = (text, func) => {
const chunks = text.split(/\n{2,}/);
if (chunks.length == 1) {
return func(document.createTextNode(text));
}
const paragraphs = chunks.map((t) => {
const p = document.createElement("p");
p.innerText = t;
return p;
});
for (let i = paragraphs.length - 1; i >= 0; i--) {
func(paragraphs[i]);
}
};
const insert = function (text) {
const prefix = window.document.body.getElementsByClassName("moz-cite-prefix");
if (prefix.length > 0) {
const divider = prefix[0];
let sibling = divider.previousSibling;
while (sibling) {
window.document.body.removeChild(sibling);
sibling = divider.previousSibling;
}
}
return makeParagraphs(text, function (p) {
window.document.body.insertBefore(p, window.document.body.firstChild);
});
}
browser.runtime.onMessage.addListener((message) => {
switch (message.command) {
case "getSelectedText":
return Promise.resolve(window.getSelection().toString());
case "replaceSelectedText":
const selectedText = window.getSelection().toString();
if (selectedText === '') {
return insert(message.text);
}
const sel = window.getSelection();
if (!sel || sel.type !== "Range" || !sel.rangeCount) {
return;
}
const r = sel.getRangeAt(0);
r.deleteContents();
makeParagraphs(message.text, function (p) {
r.insertNode(p);
});
break;
case "getText":
let t = '';
const children = window.document.body.childNodes;
for (const node of children) {
if (node instanceof Element) {
if (node.classList.contains('moz-signature')) {
continue;
}
}
t += node.textContent;
}
return Promise.resolve(t);
case "getTextOnly":
return Promise.resolve(window.document.body.innerText);
default:
// do nothing
break;
}
});

67
js/mzta-i18n.js Normal file
View file

@ -0,0 +1,67 @@
/*
* This file is provided by the addon-developer-support repository at
* https://github.com/thundernest/addon-developer-support
*
* For usage descriptions, please check:
* https://github.com/thundernest/addon-developer-support/tree/master/scripts/i18n
*
* Version 1.1
*
* Derived from:
* http://github.com/piroor/webextensions-lib-l10n
*
* Original license:
* The MIT License, Copyright (c) 2016-2019 YUKI "Piro" Hiroshi
*
*/
var i18n = {
updateString(string) {
let re = new RegExp(this.keyPrefix + "(.+?)__", "g");
return string.replace(re, (matched) => {
const key = matched.slice(this.keyPrefix.length, -2);
let rv = this.extension
? this.extension.localeData.localizeMessage(key)
: messenger.i18n.getMessage(key);
return rv || matched;
});
},
updateSubtree(node) {
const texts = document.evaluate(
'descendant::text()[contains(self::text(), "' + this.keyPrefix + '")]',
node,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
for (let i = 0, maxi = texts.snapshotLength; i < maxi; i++) {
const text = texts.snapshotItem(i);
if (text.nodeValue.includes(this.keyPrefix))
text.nodeValue = this.updateString(text.nodeValue);
}
const attributes = document.evaluate(
'descendant::*/attribute::*[contains(., "' + this.keyPrefix + '")]',
node,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
for (let i = 0, maxi = attributes.snapshotLength; i < maxi; i++) {
const attribute = attributes.snapshotItem(i);
if (attribute.value.includes(this.keyPrefix))
attribute.value = this.updateString(attribute.value);
}
},
updateDocument(options = {}) {
this.extension = null;
this.keyPrefix = "__MSG_";
if (options) {
if (options.extension) this.extension = options.extension;
if (options.keyPrefix) this.keyPrefix = options.keyPrefix;
}
this.updateSubtree(document);
},
};

25
js/mzta-prompts.js Normal file
View file

@ -0,0 +1,25 @@
// Modified version derived from https://github.com/ali-raheem/Aify/blob/13ff87583bc520fb80f555ab90a90c5c9df797a7/plugin/html/globals.js
/* Types (type attribute):
0: always show (when composing a part of the text must be selected if need_selected = 1)
1: show when reading an email
2: show when composing a mail (a part of the text must be selected if need_selected = 1)
Actions (action attribute):
0: close button
1: do reply
2: substitute text
Only if text selected (attribute need_selected):
0: no selection needed (use all the message body)
1: need a selection
*/
export const defaultPrompts = [
{ name: "__MSG_prompt_reply__", text: "Reply to the following email.", type: 1, action: 1, need_selected: 0 },
{ name: "__MSG_prompt_rewrite_polite__", text: "Rewrite the following text to be more polite. Reply with only the re-written text and with no extra comments or other text.", type: 2, action: 2, need_selected: 1 },
{ name: "__MSG_prompt_rewrite_formal__", text: "Rewrite the following text to be more formal. Reply with only the re-written text and with no extra comments or other text.", type: 2, action: 2, need_selected: 1 },
{ name: "__MSG_prompt_classify__", text: "Classify the following text in terms of Politeness, Warmth, Formality, Assertiveness, Offensiveness giving a percentage for each category. Reply with only the category and score with no extra comments or other text.", type: 0, action: 0, need_selected: 0 },
{ name: "__MSG_prompt_summarize_this__", text: "Summerize the following email into a bullet point list.", type: 0, action: 0, need_selected: 0 },
{ name: "__MSG_prompt_translate_this__", text: "Translate the following email in __MSG_prompt_translate_lang__", type: 0, action: 0, need_selected: 0 },
];

45
manifest.json Normal file
View file

@ -0,0 +1,45 @@
{
"manifest_version": 2,
"name": "ThunderAI",
"description": "__MSG_extensionDescription__",
"version": "0.1",
"author": "Mic (m@micz.it)",
"browser_specific_settings": {
"gecko": {
"id": "thunderai@micz.it",
"strict_min_version": "115.0"
}
},
"message_display_action": {
"default_popup": "messageMenu/mzta-msgmenu.html",
"default_title": "__MSG_menu_title__",
"default_icon": "images/icon-32px.png"
},
"compose_action": {
"default_popup": "messageMenu/mzta-msgmenu.html",
"default_title": "__MSG_menu_title__",
"default_icon": "images/icon-32px.png"
},
"permissions": [
"compose",
"messagesRead",
"storage",
"menus",
"messagesModify",
"tabs",
"activeTab",
"<all_urls>"
],
"background": {
"page": "mzta-background.html"
},
"icons": {
"64": "images/icon.png",
"32": "images/icon-32px.png",
"16": "images/icon-16px.png"
},
"default_locale": "en",
"options_ui": {
"page": "options/mzta-options.html"
}
}

View file

@ -0,0 +1,16 @@
.grid-container {
display: grid;
grid-template-columns: 1fr 6fr;
}
.header {
font-weight: bold
}
.grid-container div {
margin: 1ex;
}
.button {
cursor: pointer;
}

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ChatGPT</title>
<link rel="stylesheet" type="text/css" media="screen" href="mzta-msgmenu.css">
</head>
<body>
<div id="prompts-container"></div>
<script type="module" src="mzta-msgmenu.js"></script>
<script src="../js/mzta-i18n.js"></script>
</body>
</html>

View file

@ -0,0 +1,65 @@
// Modified version derived from https://github.com/ali-raheem/Aify/blob/cfadf52f576b7be3720b5b73af7c8d3129c054da/plugin/html/actions.js
import { defaultPrompts } from '../js/mzta-prompts.js';
const addAction = (curr_prompt, promptsContainer) => {
const promptDiv = document.createElement("div");
promptDiv.classList.add("button");
const nameInput = document.createElement("p");
nameInput.classList.add("flat");
nameInput.innerText = curr_prompt.name;
nameInput.classList.add("prompt-name");
const getHighlight = async () => {
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
return {tabId: tabs[0].id,
selection: await browser.tabs.sendMessage(tabs[0].id, { command: "getSelectedText" }),
text: await browser.tabs.sendMessage(tabs[0].id, { command: "getTextOnly" })
};
};
nameInput.onclick = async () => {
const msg_text = await getHighlight();
//check if a selection is needed
if(curr_prompt.need_selected && (msg_text.selection==='')){
//A selection is needed, but nothing is selected!
alert(browser.i18n.getMessage('prompt_selection_needed'));
return;
}
var body_text = '';
if (msg_text.selection!=='') {
body_text = msg_text.selection.replace(/\s+/g, ' ').trim();
} else {
body_text = msg_text.text.replace(/\s+/g, ' ').trim();
}
//open chatgpt window
//console.log("Click menu item...");
var fullPrompt = curr_prompt.text + " " + browser.i18n.getMessage("prompt_lang") + browser.i18n.getMessage("prompt_translate_lang") + " \"" + msg_text.text + "\" ";
const tabs = await browser.tabs.query({ active: true, currentWindow: true });
browser.runtime.sendMessage({command: "chatgpt_open", prompt: fullPrompt, action: curr_prompt.action, tabId: tabs[0].id});
};
promptDiv.appendChild(nameInput);
promptsContainer.appendChild(promptDiv);
};
async function checkCompose(){
return await browser.windows.getCurrent().then((currWindow) => {
//console.log("currWindow.type: "+currWindow.type);
return currWindow.type == 'messageCompose';
});
}
document.addEventListener("DOMContentLoaded", async () => {
let inCompose = await checkCompose();
//console.log("inCompose: "+inCompose)
const actionsContainer = document.getElementById("prompts-container");
defaultPrompts.forEach((prompt) => {
if((prompt.type == 0)||((prompt.type == 1)&&(!inCompose))||((prompt.type == 2)&&(inCompose))){
addAction(prompt, actionsContainer)
}
});
i18n.updateDocument();
});

9
mzta-background.html Normal file
View file

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="mzta-background.js" type="module"></script>
</head>
</html>

75
mzta-background.js Normal file
View file

@ -0,0 +1,75 @@
import { mzta_script } from './js/mzta-chatgpt.js';
var createdWindowID = null;
browser.composeScripts.register({
js: [{file: "/js/mzta-compose-script.js"}]
});
// Register the message display script for all newly opened message tabs.
messenger.messageDisplayScripts.register({
js: [{ file: "js/mzta-compose-script.js" }],
});
// Inject script and CSS in all already open message tabs.
let openTabs = await messenger.tabs.query();
let messageTabs = openTabs.filter(
tab => ["mail", "messageDisplay"].includes(tab.type)
);
for (let messageTab of messageTabs) {
browser.tabs.executeScript(messageTab.id, {
file: "js/mzta-compose-script.js"
})
}
messenger.runtime.onMessage.addListener((message, sender, sendResponse) => {
// Check what type of message we have received and invoke the appropriate
// handler function.
if (message && message.hasOwnProperty("command")){
switch (message.command) {
case 'chatgpt_open':
openChatGPT(message.prompt,message.action,message.tabId);
return true;
case 'chatgpt_close':
browser.windows.remove(createdWindowID).then(() => {
console.log("ChatGPT window closed successfully.");
return true;
}).catch((error) => {
console.error("Error closing ChatGPT window:", error);
return false;
});
break;
case 'chatgpt_replaceSelectedText':
//console.log('chatgpt_replaceSelectedText: [' + message.tabId +'] ' + message.text)
browser.tabs.sendMessage(message.tabId, { command: "replaceSelectedText", text: message.text });
return true;
default:
break;
}
}
// Return false if the message was not handled by this listener.
return false;
});
function openChatGPT(promptText,action,curr_tabId){
return browser.windows.create({
url: "https://chat.openai.com",
type: "popup",
width: 700,
height: 800
}).then((newWindow) => {
console.log("Script started...");
createdWindowID = newWindow.id;
//console.log(promptText);
const tabId = newWindow.tabs[0].id;
browser.tabs.executeScript(tabId,{code: mzta_script})
.then(async () => {
console.log("Script injected successfully");
browser.tabs.sendMessage(tabId, {command: "chatgpt_send", prompt: promptText, action: action, tabId: curr_tabId});
}).catch(err => {
console.error("Error injecting the script: ", err);
});
});
}

27
options/mzta-options.css Normal file
View file

@ -0,0 +1,27 @@
div#miczDescription{
padding:0px 15px 0px 15px;
font-size: 13px;
}
div#miczDescription h1{
font-size: 15px;
}
div#miczDonation{
width: 100%;
text-align: center;
font-size: 14px;
font-weight: bold;
}
#miczPrefs {
font-size: 13px;
padding: 10px;
}
div#miczRelNotes{
position:absolute;
right: 0px;
top: 0px;
padding: 2px 5px 0px 0px;
}

21
options/mzta-options.html Normal file
View file

@ -0,0 +1,21 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="mzta-options.css">
</head>
<body>
<div id="miczRelNotes"><a href="mzta-release-notes.html">Release Notes</a></div>
<div id="miczPrefs"><label><input type="checkbox" id="force_msedge" name="force_msedge" class="option-input" /> __MSG_forceMsedgeOptionText__</label><br/>
<input type="checkbox" id="always_link" name="always_link" class="option-input" /> __MSG_AlwaysLinkOptionText__</label></div>
<div id="miczDescription">
<h1>__MSG_prefsInfoTitle__</h1>
<p>__MSG_prefsInfoDesc_1__<br/>
__MSG_prefsInfoDesc_2__<br/>
__MSG_prefsInfoDesc_3__</p>
</div>
<div id="miczDonation">__MSG_prefsDonation_1__<br/><a href="http://micz.it/thunderdbird-addon-thunderai/donate/">__MSG_prefsDonation_2__</a></div>
<script src="mzta-options.js"></script>
<script src="../js/mzta-i18n.js"></script>
</body>
</html>

31
options/mzta-options.js Normal file
View file

@ -0,0 +1,31 @@
function saveOptions(e) {
e.preventDefault();
let options = {};
document.querySelectorAll(".option-input").forEach(element => {
options[element.id] = element.checked;
});
browser.storage.sync.set(options);
}
function restoreOptions() {
function setCurrentChoice(result) {
document.querySelectorAll(".option-input").forEach(element => {
element.checked = result[element.id] || false;
});
}
function onError(error) {
console.log(`Error: ${error}`);
}
let getting = browser.storage.sync.get(null);
getting.then(setCurrentChoice, onError);
}
document.addEventListener('DOMContentLoaded', () => {
restoreOptions();
i18n.updateDocument();
document.querySelectorAll(".option-input").forEach(element => {
element.addEventListener("change", saveOptions);
});
}, { once: true });

View file

@ -0,0 +1,19 @@
div#miczBackPrefs{
position:absolute;
right: 0px;
top: 0px;
padding: 2px 5px 0px 0px;
}
h1{
font-size:16px;
}
h2{
font-size: 14px;
margin-bottom: 0px;
}
ul{
margin-top:0px;
}

View file

@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="mzmo-release-notes.css">
</head>
<body>
<div id="miczBackPrefs"><a href="mzta-options.html">__MSG_backToOptionsText__</a></div>
<h1>ThunderAI Release Notes</h1>
<h2>Version 1.0 - 02/12/2023</h2>
<ul><li>First release.</li></ul>
<script src="mzta-release-notes.js"></script>
<script src="../js/mzta-i18n.js"></script>
</body>
</html>

View file

@ -0,0 +1,3 @@
document.addEventListener('DOMContentLoaded', () => {
i18n.updateDocument();
}, { once: true });