Auto-Update über Gitea einrichten + Web-Editor + Sync-Verbesserungen

- Thunderbird Auto-Update: update_url im Manifest, updates.json, release.sh
- .xpi neu gebaut (mit update_url, ohne defaults.local.json/Token)
- README + CLAUDE.md: Auto-Update-Doku, Repo muss public bleiben
- web-editor/ (Node/Docker WYSIWYG-Editor) hinzugefügt
- gitea-sync.js + templates_options: bestehende Anpassungen

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kendrick Bollens
2026-06-18 00:12:33 +02:00
parent edb979a1b2
commit eff90e9517
23 changed files with 3437 additions and 41 deletions

View File

@@ -250,6 +250,9 @@ class SyncManager {
const folders = [SHARED_FOLDER];
if (this.department) folders.push(this.department);
if (this.authorEmail) folders.push(`${USER_FOLDER}/${this.authorEmail}`);
// Signatur-Bausteine mitüberwachen, damit Footer-/Header-Änderungen
// auf dem Server den Hintergrund-Sync (pullSignatures) auslösen.
folders.push('signatures/footers', 'signatures/headers');
for (const folder of folders) {
const files = await this.client.listDir(folder);
@@ -461,25 +464,50 @@ class SyncManager {
}
/**
* Load footer for editing (returns HTML)
* Resolve the footer file path for a given scope.
* - 'shared' → signatures/footers/_default.html (gilt für alle Abteilungen)
* - 'department' → signatures/footers/{department}.html
*/
async loadFooter() {
footerPathForScope(scope) {
if (scope === 'shared') return 'signatures/footers/_default.html';
if (!this.department) throw new Error('Keine Abteilung ausgewählt');
return `signatures/footers/${this.department}.html`;
}
/**
* Load footer for editing (returns HTML).
* - scope 'shared'/'department': lädt genau diese Datei (kein Fallback,
* damit der Editor den echten Inhalt der gewählten Ebene zeigt).
* - ohne scope: Fallback-Kette (Abteilung → gemeinsam) für die
* automatische Anwendung an die Signatur.
*/
async loadFooter(scope) {
if (!this.isConfigured) throw new Error('Sync nicht konfiguriert');
if (scope === 'shared' || scope === 'department') {
const filepath = this.footerPathForScope(scope);
const fileData = await this.client.getFile(filepath);
const html = (fileData && fileData.content) ? GiteaClient.fromBase64(fileData.content) : '';
return { success: true, html, scope };
}
const footer = await this.pullFooter();
return { success: true, html: footer };
}
/**
* Push footer for current department to signatures/footers/{department}.html
* Push footer to the chosen scope:
* - 'shared' → signatures/footers/_default.html
* - 'department' → signatures/footers/{department}.html
*/
async pushFooter(html) {
async pushFooter(html, scope) {
if (!this.isConfigured) throw new Error('Sync nicht konfiguriert');
if (!this.config.authorName) throw new Error('Bitte Name eintragen');
if (!this.department) throw new Error('Keine Abteilung ausgewählt');
const filepath = `signatures/footers/${this.department}.html`;
const commitMsg = `Signatur-Footer ${this.department} - von ${this.config.authorName}`;
const targetScope = (scope === 'shared') ? 'shared' : 'department';
const filepath = this.footerPathForScope(targetScope);
const label = targetScope === 'shared' ? 'gemeinsam' : this.department;
const commitMsg = `Signatur-Footer ${label} - von ${this.config.authorName}`;
const existing = await this.client.getFile(filepath);
@@ -492,8 +520,10 @@ class SyncManager {
await this.client.createFile(filepath, html, commitMsg);
}
await browser.storage.local.set({ 'sig_footer_cache': html });
return { success: true };
// Refresh cache with the footer that actually applies (Abteilung gewinnt
// vor gemeinsam) — nicht zwingend das gerade gespeicherte html.
await this.pullFooter();
return { success: true, scope: targetScope };
}
/**
@@ -544,15 +574,29 @@ class SyncManager {
targetFile = headerMap[personalName] || null;
}
if (!targetFile) continue; // No header file yet — user hasn't set up signature
let header = null;
if (targetFile) {
const fileData = await this.client.getFile(targetFile.path);
if (fileData) header = GiteaClient.fromBase64(fileData.content);
}
const fileData = await this.client.getFile(targetFile.path);
if (!fileData) continue;
// Fallback: kein Server-Header → lokalen Header aus der aktuellen
// Signatur verwenden, damit der Footer trotzdem angewandt wird.
if (header === null) {
const existing = identity.signature || '';
const localHeader = SyncManager.extractHeader(existing);
// Leere, nie eingerichtete Konten nicht mit reinem Footer versehen
if (!localHeader.trim() && !existing.includes(SyncManager.FOOTER_SEPARATOR)) continue;
header = localHeader;
}
const header = GiteaClient.fromBase64(fileData.content);
loadedHeaders[email] = header;
const fullSig = SyncManager.combineSignature(header, footer);
// Nur schreiben, wenn sich etwas ändert (vermeidet unnötige Writes
// bei jedem Hintergrund-Sync)
if (fullSig === identity.signature) continue;
await browser.identities.update(identity.id, {
signature: fullSig,
signatureIsPlainText: false,
@@ -854,10 +898,10 @@ browser.runtime.onMessage.addListener(async (msg, sender) => {
return await syncManager.loadSignatureTemplate();
case 'loadFooter':
return await syncManager.loadFooter();
return await syncManager.loadFooter(msg.scope);
case 'pushFooter':
return await syncManager.pushFooter(msg.html);
return await syncManager.pushFooter(msg.html, msg.scope);
case 'autoDetect':
const config = await syncManager.autoDetect();