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

@@ -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);