icons are now in a menu

This commit is contained in:
mic 2026-03-24 22:15:32 +01:00
parent 630938e30f
commit 6b37ba894b

View file

@ -16,6 +16,85 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
function createThreeDotsMenu(isDark, menuItems, panelColors) {
const wrapper = document.createElement('div');
wrapper.style.cssText = 'position: relative; display: inline-block;';
const dotsBtn = document.createElement('span');
dotsBtn.textContent = '\u22EE';
dotsBtn.style.cssText = 'cursor: pointer; opacity: 0.7; font-size: 18px; padding: 2px 6px; line-height: 1; user-select: none; transition: opacity 0.2s;';
dotsBtn.onmouseover = () => dotsBtn.style.opacity = '1';
dotsBtn.onmouseout = () => dotsBtn.style.opacity = '0.7';
const dropdown = document.createElement('div');
const dropdownBg = panelColors.bg;
const dropdownBorder = panelColors.border;
const defaultTextColor = panelColors.text;
dropdown.style.cssText = `display: none; position: absolute; right: 0; top: 100%; z-index: 9999; min-width: 180px; background-color: ${dropdownBg}; border: 1px solid ${dropdownBorder}; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.15); overflow: hidden;`;
menuItems.forEach(item => {
const row = document.createElement('div');
row.style.cssText = `display: flex; align-items: center; gap: 8px; padding: 8px 12px; cursor: pointer; font-size: 13px; color: ${defaultTextColor}; transition: background-color 0.15s, color 0.15s;`;
const iconSpan = document.createElement('span');
iconSpan.textContent = item.icon;
iconSpan.style.cssText = 'font-size: 15px; width: 18px; text-align: center;';
const labelSpan = document.createElement('span');
labelSpan.textContent = item.label;
row.appendChild(iconSpan);
row.appendChild(labelSpan);
const hoverBg = item.hoverColor === '#cc0000'
? (isDark ? 'rgba(204,0,0,0.2)' : 'rgba(204,0,0,0.1)')
: (isDark ? 'rgba(77,157,224,0.2)' : 'rgba(26,95,168,0.1)');
row.onmouseover = () => {
row.style.backgroundColor = hoverBg;
row.style.color = item.hoverColor;
};
row.onmouseout = () => {
row.style.backgroundColor = '';
row.style.color = defaultTextColor;
};
row.onclick = (e) => {
e.stopPropagation();
dropdown.style.display = 'none';
if (item.disableAfterClick) {
row.onclick = null;
row.style.opacity = '0.5';
row.style.pointerEvents = 'none';
}
item.onClick();
};
dropdown.appendChild(row);
});
dotsBtn.onclick = (e) => {
e.stopPropagation();
dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
};
document.addEventListener('click', (e) => {
if (!wrapper.contains(e.target)) {
dropdown.style.display = 'none';
}
}, true);
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && dropdown.style.display !== 'none') {
dropdown.style.display = 'none';
}
});
wrapper.appendChild(dotsBtn);
wrapper.appendChild(dropdown);
return wrapper;
}
browser.runtime.onMessage.addListener((message) => { browser.runtime.onMessage.addListener((message) => {
switch (message.command) { switch (message.command) {
case "getSelectedText": { case "getSelectedText": {
@ -702,34 +781,31 @@ switch (message.command) {
branding.textContent = browser.i18n.getMessage("antispam_by") + " ThunderAI"; branding.textContent = browser.i18n.getMessage("antispam_by") + " ThunderAI";
branding.style.cssText = 'margin-left: auto; font-style: italic; font-size: 10px; opacity: 0.5;'; branding.style.cssText = 'margin-left: auto; font-style: italic; font-size: 10px; opacity: 0.5;';
const spamRefreshBtn = document.createElement('span'); const spamMenu = createThreeDotsMenu(isDark, [
spamRefreshBtn.textContent = '↻'; {
spamRefreshBtn.title = browser.i18n.getMessage("spamfilter_refresh") || 'Refresh spam report'; icon: '↻',
spamRefreshBtn.style.cssText = 'cursor: pointer; opacity: 0.6; font-size: 16px; padding: 0 5px; transition: opacity 0.2s, color 0.2s;'; label: browser.i18n.getMessage("spamfilter_refresh") || 'Refresh spam report',
spamRefreshBtn.onmouseover = () => { spamRefreshBtn.style.opacity = '1'; spamRefreshBtn.style.color = isDark ? '#4d9de0' : '#1a5fa8'; }; hoverColor: isDark ? '#4d9de0' : '#1a5fa8',
spamRefreshBtn.onmouseout = () => { spamRefreshBtn.style.opacity = '0.6'; spamRefreshBtn.style.color = ''; }; disableAfterClick: true,
spamRefreshBtn.onclick = function() { onClick: () => {
spamRefreshBtn.onclick = null; browser.runtime.sendMessage({ command: "refreshSpamReport", headerMessageId: data.headerMessageId });
spamRefreshBtn.style.opacity = '0.6'; }
browser.runtime.sendMessage({ command: "refreshSpamReport", headerMessageId: data.headerMessageId }); },
}; {
icon: '×',
const closeBtn = document.createElement('span'); label: browser.i18n.getMessage("spamfilter_delete") || 'Delete spam report',
closeBtn.textContent = '×'; hoverColor: '#cc0000',
closeBtn.style.cssText = 'cursor: pointer; opacity: 0.6; font-size: 16px; padding: 0 5px; transition: opacity 0.2s, color 0.2s;'; onClick: () => {
closeBtn.title = browser.i18n.getMessage("spamfilter_delete"); container.remove();
closeBtn.onmouseover = () => { closeBtn.style.opacity = '1'; closeBtn.style.color = '#cc0000'; }; browser.runtime.sendMessage({ command: "removeSpamReport", headerMessageId: data.headerMessageId });
closeBtn.onmouseout = () => { closeBtn.style.opacity = '0.6'; closeBtn.style.color = ''; }; }
closeBtn.onclick = function() { }
container.remove(); ], { bg: bgColor, border: borderColor, text: textColor });
browser.runtime.sendMessage({ command: "removeSpamReport", headerMessageId: data.headerMessageId });
};
container.appendChild(scoreText); container.appendChild(scoreText);
container.appendChild(reasonText); container.appendChild(reasonText);
container.appendChild(branding); container.appendChild(branding);
container.appendChild(spamRefreshBtn); container.appendChild(spamMenu);
container.appendChild(closeBtn);
document.body.insertBefore(container, document.body.firstChild); document.body.insertBefore(container, document.body.firstChild);
return Promise.resolve(true); return Promise.resolve(true);
@ -773,47 +849,32 @@ switch (message.command) {
summaryTitle.textContent = browser.i18n.getMessage("summarize_title"); summaryTitle.textContent = browser.i18n.getMessage("summarize_title");
summaryTitle.style.cssText = `font-weight: bold; font-size: 14px; color: ${titleColor};`; summaryTitle.style.cssText = `font-weight: bold; font-size: 14px; color: ${titleColor};`;
const refreshBtn = document.createElement('span'); const summaryMenu = createThreeDotsMenu(isDarkSummary, [
refreshBtn.textContent = '↻'; {
refreshBtn.title = browser.i18n.getMessage("summarize_refresh") || 'Refresh summary'; icon: '↻',
refreshBtn.style.cssText = `cursor: pointer; opacity: 0.6; font-size: 16px; transition: opacity 0.2s, color 0.2s;`; label: browser.i18n.getMessage("summarize_refresh") || 'Refresh summary',
refreshBtn.onmouseover = () => { refreshBtn.style.opacity = '1'; refreshBtn.style.color = isDarkSummary ? '#4d9de0' : '#1a5fa8'; }; hoverColor: isDarkSummary ? '#4d9de0' : '#1a5fa8',
refreshBtn.onmouseout = () => { refreshBtn.style.opacity = '0.6'; refreshBtn.style.color = ''; }; disableAfterClick: true,
refreshBtn.onclick = async () => { onClick: () => {
refreshBtn.onclick = null; browser.runtime.sendMessage({
refreshBtn.style.opacity = '0.6'; command: "refreshSummary",
browser.runtime.sendMessage({ headerMessageId: summaryData.headerMessageId
command: "refreshSummary", });
headerMessageId: summaryData.headerMessageId }
}); },
}; {
icon: '×',
const summaryCloseBtn = document.createElement('span'); label: browser.i18n.getMessage("summarize_delete") || 'Delete summary',
summaryCloseBtn.textContent = '×'; hoverColor: '#cc0000',
summaryCloseBtn.style.cssText = 'cursor: pointer; opacity: 0.6; font-size: 16px; padding: 0 5px; transition: opacity 0.2s, color 0.2s;'; onClick: () => {
summaryCloseBtn.title = browser.i18n.getMessage("summarize_delete"); summaryContainer.remove();
summaryCloseBtn.onmouseover = () => { summaryCloseBtn.style.opacity = '1'; summaryCloseBtn.style.color = '#cc0000'; }; browser.runtime.sendMessage({ command: "removeSummary", headerMessageId: summaryData.headerMessageId });
summaryCloseBtn.onmouseout = () => { summaryCloseBtn.style.opacity = '0.6'; summaryCloseBtn.style.color = ''; }; }
summaryCloseBtn.onclick = function() { }
summaryContainer.remove(); ], { bg: bgColorSummary, border: borderColorSummary, text: textColorSummary });
browser.runtime.sendMessage({ command: "removeSummary", headerMessageId: summaryData.headerMessageId });
};
const collapseBtn = document.createElement('span');
collapseBtn.textContent = '∧';
collapseBtn.title = browser.i18n.getMessage("summarize_collapse") || 'Collapse summary';
collapseBtn.style.cssText = `cursor: pointer; opacity: 0.6; font-size: 16px; transition: opacity 0.2s;`;
collapseBtn.onmouseover = () => collapseBtn.style.opacity = '1';
collapseBtn.onmouseout = () => collapseBtn.style.opacity = '0.6';
const summaryBtnGroup = document.createElement('span');
summaryBtnGroup.style.cssText = 'display: flex; align-items: center; gap: 5px;';
summaryBtnGroup.appendChild(refreshBtn);
summaryBtnGroup.appendChild(summaryCloseBtn);
summaryBtnGroup.appendChild(collapseBtn);
summaryHeader.appendChild(summaryTitle); summaryHeader.appendChild(summaryTitle);
summaryHeader.appendChild(summaryBtnGroup); summaryHeader.appendChild(summaryMenu);
summaryContainer.appendChild(summaryHeader); summaryContainer.appendChild(summaryHeader);
const summaryText = document.createElement('div'); const summaryText = document.createElement('div');
@ -825,18 +886,6 @@ switch (message.command) {
} }
summaryText.style.cssText = `font-size: 14px; line-height: 1.4;`; summaryText.style.cssText = `font-size: 14px; line-height: 1.4;`;
collapseBtn.onclick = () => {
if (summaryText.style.display === 'none') {
summaryText.style.display = '';
summaryHeader.style.marginBottom = '0.5rem';
collapseBtn.textContent = '∧';
} else {
summaryText.style.display = 'none';
summaryHeader.style.marginBottom = '0';
collapseBtn.textContent = '';
}
};
summaryContainer.appendChild(summaryText); summaryContainer.appendChild(summaryText);
document.body.insertBefore(summaryContainer, document.body.firstChild); document.body.insertBefore(summaryContainer, document.body.firstChild);