Toolbar-Button Fix, QuickMove-Tab, Schlagwörter-Sync, Abteilungsverwaltung

- Toolbar-Button öffnet Settings via browserAction.onClicked statt defektem Popup
- Button-Label "Vorlagen & Signaturen" statt Icon
- Tab "Erledigt" → "QuickMove" umbenannt
- QuickMove: E-Mails markieren + in Zielordner verschieben
- Schlagwörter-Sync aus Gitea (_config/schlagwoerter.json)
- Abteilungen anlegen (+Button)
- attachSignature-Fix entfernt
- message_display_action für QuickMove-Button

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kendrick Bollens
2026-06-03 14:02:56 +02:00
parent ee24caf8b7
commit edb979a1b2
9 changed files with 509 additions and 25 deletions

View File

@@ -1,5 +1,109 @@
// ── Toolbar button: open settings page ──
browser.browserAction.onClicked.addListener(() => {
browser.runtime.openOptionsPage();
});
// ── "Erledigt" button in message display ──
async function executeErledigtAction(tab, actionConfig) {
const message = await messenger.messageDisplay.getDisplayedMessage(tab.id);
if (!message) {
browser.notifications.create({ type: 'basic', iconUrl: browser.runtime.getURL('icons/icon.png'), title: 'Fehler', message: 'Keine Nachricht ausgewählt.' });
return;
}
const storage = await browser.storage.local.get(['gitea_config', 'schlagwoerter_cache']);
const config = storage.gitea_config || {};
const schlagwoerter = storage.schlagwoerter_cache;
// Apply user's tag
let tagKey = null;
if (Array.isArray(schlagwoerter) && config.authorName) {
const match = schlagwoerter.find(u => u.name.toLowerCase() === config.authorName.toLowerCase());
if (match) {
tagKey = `$hps_${match.name.toLowerCase().replace(/\s+/g, '_')}`;
}
}
if (tagKey) {
const currentTags = message.tags || [];
if (!currentTags.includes(tagKey)) {
await messenger.messages.update(message.id, { tags: [...currentTags, tagKey] });
}
}
// Move to target folder
if (actionConfig.targetFolder) {
const folderInfo = JSON.parse(actionConfig.targetFolder);
await messenger.messages.move([message.id], folderInfo);
}
// Feedback
const parts = [];
if (tagKey) parts.push('markiert');
if (actionConfig.targetFolder) parts.push('verschoben');
const title = actionConfig.name || 'Erledigt';
browser.notifications.create({
type: 'basic',
iconUrl: browser.runtime.getURL('icons/icon.png'),
title,
message: parts.length ? `Nachricht ${parts.join(' & ')}.` : 'Kein Schlagwort oder Zielordner konfiguriert.'
});
}
// Single action: direct click without popup
messenger.messageDisplayAction.onClicked.addListener(async (tab) => {
try {
const result = await browser.storage.local.get('erledigt_config');
const actions = (result.erledigt_config || {}).actions || [];
await executeErledigtAction(tab, actions[0] || {});
} catch (e) {
console.error('Erledigt-Button Fehler:', e);
browser.notifications.create({ type: 'basic', iconUrl: browser.runtime.getURL('icons/icon.png'), title: 'Fehler', message: e.message });
}
});
// Toggle popup vs direct click based on action count
async function updateErledigtPopup() {
const result = await browser.storage.local.get('erledigt_config');
const actions = (result.erledigt_config || {}).actions || [];
if (actions.length > 1) {
await messenger.messageDisplayAction.setPopup({ popup: 'message_popup.html' });
await messenger.messageDisplayAction.setTitle({ title: 'Aktion wählen' });
} else {
await messenger.messageDisplayAction.setPopup({ popup: '' });
await messenger.messageDisplayAction.setTitle({ title: actions[0]?.name || 'Erledigt' });
}
}
// Update on config change
browser.storage.onChanged.addListener((changes, area) => {
if (area === 'local' && changes.erledigt_config) updateErledigtPopup();
});
updateErledigtPopup();
// ── Template insertion ──
browser.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.action !== 'insertTemplate') return false;
if (msg.action === 'erledigtAction') {
(async () => {
try {
const [tab] = await browser.tabs.query({ active: true, currentWindow: true });
const result = await browser.storage.local.get('erledigt_config');
const actions = (result.erledigt_config || {}).actions || [];
const action = actions[msg.index] || {};
await executeErledigtAction(tab, action);
sendResponse({ success: true });
} catch (e) {
console.error('Erledigt-Action Fehler:', e);
browser.notifications.create({ type: 'basic', iconUrl: browser.runtime.getURL('icons/icon.png'), title: 'Fehler', message: e.message });
sendResponse({ success: false, error: e.message });
}
})();
return true;
}
if (msg.action !== 'insertTemplate') return;
handleInsertTemplate(msg).then(() => sendResponse())
.catch(err => sendResponse({ error: err.message }));

View File

@@ -8,6 +8,7 @@ const FULL_SYNC_COOLDOWN_MS = 10 * 1000; // min 10s between full pulls
const SHARED_FOLDER = '_gemeinsam';
const USER_FOLDER = '_benutzer';
const CONFIG_FOLDER = '_config';
const SCHLAGWOERTER_CACHE_KEY = 'schlagwoerter_cache';
// ── Gitea API Client ──
@@ -146,6 +147,16 @@ class GiteaClient {
return null;
}
}
async getSchlagwoerter() {
const data = await this.getFile(`${CONFIG_FOLDER}/schlagwoerter.json`);
if (!data) return null;
try {
return JSON.parse(GiteaClient.fromBase64(data.content));
} catch (_) {
return null;
}
}
}
// ── Sync Manager ──
@@ -545,7 +556,6 @@ class SyncManager {
await browser.identities.update(identity.id, {
signature: fullSig,
signatureIsPlainText: false,
attachSignature: false
});
updated++;
}
@@ -563,7 +573,6 @@ class SyncManager {
await browser.identities.update(id, {
signature: fullSig,
signatureIsPlainText: false,
attachSignature: false
});
loadedHeaders[email] = srcHeader;
updated++;
@@ -685,6 +694,98 @@ class SyncManager {
return { success: true };
}
static hslToHex(h, s, l) {
s /= 100; l /= 100;
const a = s * Math.min(l, 1 - l);
const f = n => { const k = (n + h / 30) % 12; return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1)); };
const toHex = x => Math.round(x * 255).toString(16).padStart(2, '0');
return `#${toHex(f(0))}${toHex(f(8))}${toHex(f(4))}`;
}
/**
* Sync Schlagwörter (tags) from Gitea config to Thunderbird.
* Auto-registers the current user if not yet listed.
* Tags are never removed so they remain traceable forever.
* Format: [ {"name": "Kenny", "color": "#e74c3c"}, ... ]
*/
async syncTags() {
if (!this.isConfigured) return { success: true, created: 0, updated: 0 };
const userName = this.config.authorName;
// Pull current file (may be null if it doesn't exist yet)
const fileData = await this.client.getFile(`${CONFIG_FOLDER}/schlagwoerter.json`);
let schlagwoerter = [];
let fileSha = null;
if (fileData) {
fileSha = fileData.sha;
try { schlagwoerter = JSON.parse(GiteaClient.fromBase64(fileData.content)); } catch (_) {}
}
if (!Array.isArray(schlagwoerter)) schlagwoerter = [];
// Auto-register current user if not yet listed
let pushed = false;
if (userName) {
const exists = schlagwoerter.some(u => u.name.toLowerCase() === userName.toLowerCase());
if (!exists) {
const hue = Math.abs([...userName].reduce((h, c) => ((h << 5) - h + c.charCodeAt(0)) | 0, 0)) % 360;
const color = SyncManager.hslToHex(hue, 65, 45);
schlagwoerter.push({ name: userName, color });
const json = JSON.stringify(schlagwoerter, null, 2);
const commitMsg = `Schlagwort für ${userName} hinzugefügt`;
if (fileSha) {
await this.client.updateFile(`${CONFIG_FOLDER}/schlagwoerter.json`, json, fileSha, commitMsg);
} else {
await this.client.createFile(`${CONFIG_FOLDER}/schlagwoerter.json`, json, commitMsg);
}
pushed = true;
console.log(`[Sync] Benutzer "${userName}" als Schlagwort registriert`);
}
}
// Cache for background.js access
await browser.storage.local.set({ [SCHLAGWOERTER_CACHE_KEY]: schlagwoerter });
// Create/update Thunderbird tags
const existingTags = await messenger.messages.tags.list();
const existingByKey = {};
for (const t of existingTags) existingByKey[t.key] = t;
let created = 0, updated = 0;
for (const user of schlagwoerter) {
const key = `$hps_${user.name.toLowerCase().replace(/\s+/g, '_')}`;
const color = user.color || '#999999';
if (existingByKey[key]) {
if (existingByKey[key].color !== color) {
await messenger.messages.tags.update(key, { color });
updated++;
}
} else {
await messenger.messages.tags.create(key, user.name, color);
created++;
}
}
return { success: true, created, updated, pushed };
}
/**
* Get the current user's tag key based on config
*/
getMyTagKey(schlagwoerter) {
if (!this.config || !Array.isArray(schlagwoerter)) return null;
const name = this.config.authorName;
if (!name) return null;
const match = schlagwoerter.find(u => u.name.toLowerCase() === name.toLowerCase());
if (!match) return null;
return `$hps_${match.name.toLowerCase().replace(/\s+/g, '_')}`;
}
async testConnection() {
if (!this.isConfigured) throw new Error('Sync nicht konfiguriert');
const repoInfo = await this.client.testConnection();
@@ -699,7 +800,7 @@ const syncManager = new SyncManager();
// ── Background message handler for sync ──
browser.runtime.onMessage.addListener(async (msg, sender) => {
const syncActions = ['testConnection', 'pullTemplates', 'pushTemplates', 'pullSingleTemplate', 'pushSingleTemplate', 'deleteRemoteTemplate', 'listDepartments', 'checkRemoteShas', 'pullSignatures', 'pushSignatures', 'loadSignatureTemplate', 'loadFooter', 'pushFooter', 'autoDetect'];
const syncActions = ['testConnection', 'pullTemplates', 'pushTemplates', 'pullSingleTemplate', 'pushSingleTemplate', 'deleteRemoteTemplate', 'listDepartments', 'addDepartment', 'checkRemoteShas', 'pullSignatures', 'pushSignatures', 'loadSignatureTemplate', 'loadFooter', 'pushFooter', 'autoDetect', 'syncTags', 'getMyTagKey'];
if (!syncActions.includes(msg.action)) return;
try {
@@ -717,6 +818,14 @@ browser.runtime.onMessage.addListener(async (msg, sender) => {
case 'listDepartments':
return await syncManager.listDepartments();
case 'addDepartment': {
if (!syncManager.isConfigured) throw new Error('Sync nicht konfiguriert');
const name = msg.name?.trim();
if (!name) throw new Error('Kein Name angegeben');
await syncManager.client.createFile(`${name}/.gitkeep`, '', `Abteilung "${name}" angelegt`);
return { success: true };
}
case 'checkRemoteShas':
return await syncManager.checkRemoteShas();
@@ -754,6 +863,15 @@ browser.runtime.onMessage.addListener(async (msg, sender) => {
const config = await syncManager.autoDetect();
return { success: true, config };
case 'syncTags':
return await syncManager.syncTags();
case 'getMyTagKey': {
const cached = (await browser.storage.local.get(SCHLAGWOERTER_CACHE_KEY))[SCHLAGWOERTER_CACHE_KEY];
const tagKey = syncManager.getMyTagKey(cached);
return { success: !!tagKey, tagKey };
}
default:
return { success: false, error: 'Unbekannte Aktion' };
}
@@ -767,7 +885,9 @@ browser.runtime.onMessage.addListener(async (msg, sender) => {
let lastKnownShas = null;
let lastFullSync = 0;
let lastTagSync = 0;
let syncInProgress = false;
const TAG_SYNC_INTERVAL_MS = 60 * 1000;
const HASH_STORAGE_KEY_BG = 'sync_hashes';
function simpleHashBg(str) {
@@ -808,18 +928,30 @@ async function smartSync() {
const initialized = await syncManager.init();
if (!initialized) return;
// Lightweight SHA check
syncInProgress = true;
// Tag sync every 60s (schlagwoerter.json is not in SHA-checked folders)
const now = Date.now();
if (now - lastTagSync >= TAG_SYNC_INTERVAL_MS) {
lastTagSync = now;
try {
const tagResult = await syncManager.syncTags();
if (tagResult.created || tagResult.pushed) console.log('[Sync] Tags synchronisiert:', tagResult);
} catch (e) {
console.error('[Sync] Tag-Sync fehlgeschlagen:', e);
}
}
// Lightweight SHA check for templates + signatures
const shaResult = await syncManager.checkRemoteShas();
if (!shaResult?.success) return;
if (!shaResult?.success) { syncInProgress = false; return; }
const currentShas = JSON.stringify(shaResult.remoteShas);
// First run or SHAs changed → full pull
if (lastKnownShas === null || currentShas !== lastKnownShas) {
const now = Date.now();
if (now - lastFullSync < FULL_SYNC_COOLDOWN_MS) return;
if (now - lastFullSync < FULL_SYNC_COOLDOWN_MS) { syncInProgress = false; return; }
syncInProgress = true;
lastFullSync = now;
console.log('[Sync] Änderung erkannt, lade Vorlagen...');
@@ -831,8 +963,8 @@ async function smartSync() {
console.log('[Sync] Signaturen geladen:', sigResult);
lastKnownShas = JSON.stringify((await syncManager.checkRemoteShas()).remoteShas || {});
syncInProgress = false;
}
syncInProgress = false;
} catch (err) {
console.error('[Sync] Check fehlgeschlagen:', err);
syncInProgress = false;
@@ -841,3 +973,4 @@ async function smartSync() {
smartSync();
setInterval(smartSync, SHA_CHECK_INTERVAL_MS);

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "HPS Vorlagen & Signaturen",
"version": "2.2.0",
"version": "2.3.0",
"description": "Vorlagen- und Signaturverwaltung für Hotel Park Soltau mit Git-Sync",
"browser_specific_settings": {
"gecko": {
@@ -15,7 +15,13 @@
"notifications",
"tabs",
"accountsRead",
"accountsIdentities"
"accountsIdentities",
"messagesTagsList",
"messagesTags",
"messagesRead",
"messagesUpdate",
"messagesMove",
"accountsFolders"
],
"optional_permissions": [
"*://*/*"
@@ -24,6 +30,14 @@
"scripts": ["lib/gitea-sync.js", "background.js"],
"persistent": true
},
"browser_action": {
"default_icon": {
"16": "icons/icon.png",
"32": "icons/icon.png"
},
"default_title": "Vorlagen & Signaturen verwalten",
"default_label": "Vorlagen & Signaturen"
},
"compose_action": {
"default_icon": {
"16": "icons/icon.png",
@@ -32,6 +46,13 @@
"default_popup": "popup.html",
"default_label": "Vorlagen"
},
"message_display_action": {
"default_icon": {
"16": "icons/icon.png",
"32": "icons/icon.png"
},
"default_label": "QuickMove"
},
"options_ui": {
"page": "templates_options/templates_options.html",
"browser_style": true

50
message_popup.html Normal file
View File

@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Aktion wählen</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: "Segoe UI", -apple-system, sans-serif;
width: 220px;
background: #fafafa;
color: #333;
}
#action-list {
padding: 6px;
}
#action-list button {
display: block;
width: 100%;
padding: 9px 12px;
margin-bottom: 2px;
background: white;
border: 1px solid #e0e0e0;
border-radius: 6px;
text-align: left;
font-size: 12.5px;
color: #333;
cursor: pointer;
transition: background 0.15s, border-color 0.15s;
}
#action-list button:hover {
background: #e8f0eb;
border-color: #4a7c59;
}
#action-list button:active {
background: #d0e2d5;
}
.empty-state {
padding: 16px 14px;
text-align: center;
font-size: 12px;
color: #888;
}
</style>
</head>
<body>
<div id="action-list"></div>
<script src="message_popup.js"></script>
</body>
</html>

21
message_popup.js Normal file
View File

@@ -0,0 +1,21 @@
(async () => {
const result = await browser.storage.local.get('erledigt_config');
const actions = (result.erledigt_config || {}).actions || [];
const list = document.getElementById('action-list');
if (actions.length === 0) {
list.innerHTML = '<div class="empty-state">Keine Aktionen konfiguriert.</div>';
return;
}
for (let i = 0; i < actions.length; i++) {
const action = actions[i];
const btn = document.createElement('button');
btn.textContent = action.name || `Aktion ${i + 1}`;
btn.addEventListener('click', async () => {
await browser.runtime.sendMessage({ action: 'erledigtAction', index: i });
window.close();
});
list.appendChild(btn);
}
})();

Binary file not shown.

View File

@@ -731,6 +731,7 @@
<div class="tab-bar">
<button class="tab-btn active" data-tab="templates">Vorlagen</button>
<button class="tab-btn" data-tab="signatures">Signaturen</button>
<button class="tab-btn" data-tab="erledigt">QuickMove</button>
<button class="tab-btn tab-btn-settings" data-tab="sync" title="Einstellungen">&#9881;</button>
</div>
@@ -990,6 +991,7 @@
<option value="">— Bitte wählen —</option>
</select>
<button type="button" class="btn btn-secondary btn-sm" id="refresh-departments" title="Abteilungen neu laden">Aktualisieren</button>
<button type="button" class="btn btn-secondary btn-sm" id="add-department" title="Neue Abteilung anlegen">+</button>
</div>
<div class="card-desc" style="margin-top:6px;">Du erhältst Vorlagen aus deiner Abteilung + dem gemeinsamen Ordner (<code>_gemeinsam</code>).</div>
</div>
@@ -1044,6 +1046,24 @@
<div id="sync-log"></div>
</div>
<!-- ═══════════ Tab: Erledigt ═══════════ -->
<div id="tab-erledigt" class="tab-content">
<div class="card">
<div class="card-title">Aktionen</div>
<div class="card-desc">Der Button erscheint in der Nachrichtenansicht. Ein Klick markiert die E-Mail mit deinem Schlagwort und verschiebt sie in den gewählten Ordner. Bei mehreren Aktionen öffnet sich ein Auswahlmenü.</div>
<div id="erledigt-actions-list"></div>
<div style="margin-top:12px;">
<button type="button" class="btn btn-secondary" id="add-erledigt-action">+ Aktion hinzufügen</button>
</div>
<div class="sync-actions" style="margin-top:16px;">
<button type="button" class="btn btn-primary" id="save-erledigt-config">Speichern</button>
</div>
</div>
</div>
<!-- Offline Banner -->
<div id="offline-banner" class="offline-banner">Keine Internetverbindung Synchronisation nicht möglich</div>

View File

@@ -1167,6 +1167,7 @@ function showSigStatus(message, color) {
// Save signature to Thunderbird identity (header + footer)
document.getElementById('sig-save-button').addEventListener('click', async () => {
try {
const identityId = sigIdentitySelect.value;
if (!identityId) {
showSigStatus('Bitte Identität auswählen.', 'red');
@@ -1194,7 +1195,6 @@ document.getElementById('sig-save-button').addEventListener('click', async () =>
await browser.identities.update(identityId, {
signature: fullSignature,
signatureIsPlainText: false,
attachSignature: false
});
if (identity) identity.signature = fullSignature;
@@ -1206,28 +1206,37 @@ document.getElementById('sig-save-button').addEventListener('click', async () =>
await browser.identities.update(otherId.id, {
signature: fullSignature,
signatureIsPlainText: false,
attachSignature: false
});
});
otherId.signature = fullSignature;
}
}
updateSigSyncIndicator();
// Auto-push to server
// Auto-push to server, then pull to resolve reference chains
try {
const pushResult = await browser.runtime.sendMessage({ action: 'pushSignatures' });
await loadIdentities();
for (const id of allIdentities) {
sigSyncedHashes[id.email.toLowerCase()] = simpleHash(id.signature || '');
}
saveSyncHashes();
updateSigSyncIndicator();
const label = source.startsWith('=') ? ` (von ${source.substring(1)})` : '';
showSigStatus(`Signatur gespeichert & hochgeladen!${label}`, 'green');
await browser.runtime.sendMessage({ action: 'pushSignatures' });
} catch (err) {
showSigStatus('Gespeichert, aber Upload fehlgeschlagen: ' + err.message, '#e65100');
return;
}
try {
await browser.runtime.sendMessage({ action: 'pullSignatures' });
} catch (_) {}
await loadIdentities();
for (const id of allIdentities) {
sigSyncedHashes[id.email.toLowerCase()] = simpleHash(id.signature || '');
}
saveSyncHashes();
updateSigSyncIndicator();
const label = source.startsWith('=') ? ` (von ${source.substring(1)})` : '';
showSigStatus(`Signatur gespeichert & hochgeladen!${label}`, 'green');
} catch (err) {
console.error('Signatur-Speichern Fehler:', err);
showSigStatus('Fehler: ' + err.message, 'red');
}
});
// Import signature from HTML file
@@ -1399,6 +1408,30 @@ async function populateEmailDropdown() {
}
}
async function populateFolderDropdown() {
try {
let options = '';
const accounts = await browser.accounts.list();
for (const account of accounts) {
const folders = await browser.folders.getSubFolders(account);
const addFolders = (list, prefix) => {
for (const folder of list) {
const label = prefix ? `${prefix} / ${folder.name}` : `${account.name} / ${folder.name}`;
const val = JSON.stringify({ accountId: account.id, path: folder.path });
options += `<option value='${val.replace(/'/g, '&#39;')}'>${label}</option>`;
if (folder.subFolders && folder.subFolders.length) {
addFolders(folder.subFolders, label);
}
}
};
addFolders(folders, '');
}
erledigtFolderOptions = options;
} catch (e) {
console.error('Ordner laden fehlgeschlagen:', e);
}
}
async function loadSyncConfig() {
try {
const result = await browser.storage.local.get(SYNC_CONFIG_KEY);
@@ -1651,6 +1684,23 @@ for (const id of ['sync-author-name', 'sync-author-email']) {
document.getElementById('refresh-departments').addEventListener('click', loadDepartments);
document.getElementById('add-department').addEventListener('click', async () => {
const name = prompt('Name der neuen Abteilung:');
if (!name || !name.trim()) return;
try {
const result = await browser.runtime.sendMessage({ action: 'addDepartment', name: name.trim() });
if (result?.success) {
showToast(`Abteilung "${name.trim()}" angelegt!`, 'success');
await loadDepartments();
document.getElementById('sync-department').value = name.trim();
} else {
showToast(result?.error || 'Fehler beim Anlegen', 'error');
}
} catch (e) {
showToast('Fehler: ' + e.message, 'error');
}
});
document.getElementById('test-sync-connection').addEventListener('click', async () => {
if (!checkOnline()) return;
const btn = document.getElementById('test-sync-connection');
@@ -1678,6 +1728,80 @@ document.getElementById('test-sync-connection').addEventListener('click', async
btn.textContent = origText;
});
// ── Erledigt-Tab ──
const ERLEDIGT_CONFIG_KEY = 'erledigt_config';
let erledigtFolderOptions = '';
function renderErledigtAction(action, index) {
const div = document.createElement('div');
div.className = 'form-row';
div.style.alignItems = 'end';
div.style.marginBottom = '8px';
div.innerHTML = `
<div class="form-group" style="flex:1;">
${index === 0 ? '<label>Name</label>' : ''}
<input type="text" class="erledigt-name" placeholder="z.B. Erledigt" value="${(action.name || '').replace(/"/g, '&quot;')}">
</div>
<div class="form-group" style="flex:2;">
${index === 0 ? '<label>Zielordner</label>' : ''}
<select class="erledigt-folder" style="width:100%;">
<option value="">— Kein Ordner (nur markieren) —</option>
${erledigtFolderOptions}
</select>
</div>
<button type="button" class="btn btn-danger btn-sm erledigt-remove" title="Entfernen" style="margin-bottom:2px;">✕</button>
`;
if (action.targetFolder) {
div.querySelector('.erledigt-folder').value = action.targetFolder;
}
div.querySelector('.erledigt-remove').addEventListener('click', () => {
div.remove();
});
return div;
}
function renderErledigtActions(actions) {
const list = document.getElementById('erledigt-actions-list');
list.innerHTML = '';
actions.forEach((a, i) => list.appendChild(renderErledigtAction(a, i)));
}
function getErledigtActionsFromForm() {
const actions = [];
const names = document.querySelectorAll('.erledigt-name');
const folders = document.querySelectorAll('.erledigt-folder');
for (let i = 0; i < names.length; i++) {
const name = names[i].value.trim();
if (!name) continue;
actions.push({ name, targetFolder: folders[i].value || '' });
}
return actions;
}
async function loadErledigtConfig() {
const result = await browser.storage.local.get(ERLEDIGT_CONFIG_KEY);
const config = result[ERLEDIGT_CONFIG_KEY] || {};
const actions = config.actions || [];
if (actions.length === 0) actions.push({ name: 'Erledigt', targetFolder: '' });
renderErledigtActions(actions);
}
document.getElementById('add-erledigt-action').addEventListener('click', () => {
const list = document.getElementById('erledigt-actions-list');
list.appendChild(renderErledigtAction({ name: '', targetFolder: '' }, list.children.length));
});
document.getElementById('save-erledigt-config').addEventListener('click', async () => {
const actions = getErledigtActionsFromForm();
if (actions.length === 0) {
showToast('Mindestens eine Aktion mit Namen anlegen.', 'error');
return;
}
await browser.storage.local.set({ [ERLEDIGT_CONFIG_KEY]: { actions } });
showToast('Aktionen gespeichert!', 'success');
});
// ── Init ──
window.addEventListener('load', async () => {
@@ -1686,6 +1810,8 @@ window.addEventListener('load', async () => {
renderTemplates(templates);
updateTplSyncIndicator();
await populateEmailDropdown();
await populateFolderDropdown();
await loadErledigtConfig();
loadSyncConfig();
await loadIdentities();

9
toolbar_popup.html Normal file
View File

@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html><head><meta charset="UTF-8"></head>
<body>
<script>
browser.runtime.openOptionsPage();
window.close();
</script>
</body>
</html>