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:
106
background.js
106
background.js
@@ -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 }));
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
50
message_popup.html
Normal 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
21
message_popup.js
Normal 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.
@@ -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">⚙</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>
|
||||
|
||||
|
||||
@@ -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, ''')}'>${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, '"')}">
|
||||
</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
9
toolbar_popup.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html><head><meta charset="UTF-8"></head>
|
||||
<body>
|
||||
<script>
|
||||
browser.runtime.openOptionsPage();
|
||||
window.close();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user