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:
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user