diff --git a/background.js b/background.js index 4e7a143..39a2360 100644 --- a/background.js +++ b/background.js @@ -98,6 +98,55 @@ browser.storage.onChanged.addListener((changes, area) => { }); updateErledigtPopup(); +// ── about:config-Prefs bei jedem Start erzwingen ── +// +// Manche Thunderbird-Einstellungen (Startseite, Ansicht, Layout, pro Konto das +// Signatur-/Antwort-Verhalten) „springen zurück" und müssen sonst von Hand +// nachgezogen werden. Wir setzen sie bei JEDEM Start neu über den privilegierten +// forcedPrefs-Experiment (Services.prefs). Die Liste liegt in +// lib/forced-prefs-list.js (globale FORCED_PREFS / FORCED_IDENTITY_PREFS). +// Pro Eintrag kann der Client im Options-Dialog opt-outen (forced_prefs_optout). +const FORCED_PREFS_OPTOUT_KEY = 'forced_prefs_optout'; + +async function enforceForcedPrefs() { + const stored = await browser.storage.local.get(FORCED_PREFS_OPTOUT_KEY); + const disabled = new Set(stored[FORCED_PREFS_OPTOUT_KEY] || []); + + // Globale Prefs + for (const p of FORCED_PREFS) { + if (disabled.has(p.id)) continue; + try { + await browser.forcedPrefs.setPref(p.name, p.type, p.value); + } catch (e) { + console.error('[forcedPrefs] konnte', p.name, 'nicht setzen:', e); + } + } + + // Pro-Identität-Prefs — über alle Konten/Identitäten iterieren + const accounts = await browser.accounts.list(); + for (const acc of accounts) { + for (const ident of (acc.identities || [])) { + for (const p of FORCED_IDENTITY_PREFS) { + if (disabled.has(p.id)) continue; + const name = `mail.identity.${ident.id}.${p.suffix}`; + try { + await browser.forcedPrefs.setPref(name, p.type, p.value); + } catch (e) { + console.error('[forcedPrefs] konnte', name, 'nicht setzen:', e); + } + } + } + } +} + +enforceForcedPrefs(); // Top-Level = läuft einmal pro TB-Start + +// Wird eine Pref im Options-Dialog wieder AKTIVIERT, sofort anwenden +// (statt bis zum nächsten Neustart zu warten). +browser.storage.onChanged.addListener((changes, area) => { + if (area === 'local' && changes[FORCED_PREFS_OPTOUT_KEY]) enforceForcedPrefs(); +}); + // ── Signatur in neuen Compose-Fenstern automatisch aktualisieren ── // // Thunderbird merkt sich die Signatur einer Identität pro Sitzung. Öffnet der diff --git a/experiments/prefs/api.js b/experiments/prefs/api.js new file mode 100644 index 0000000..dd3af94 --- /dev/null +++ b/experiments/prefs/api.js @@ -0,0 +1,61 @@ +"use strict"; + +// Privilegierter Parent-Scope: schreibt/liest about:config-Prefs über Services.prefs. +// Der Modulpfad von ExtensionCommon wechselte mitten im TB-Lebenszyklus: +// TB 115+ -> ExtensionCommon.sys.mjs (ESM) +// TB 109-114 -> ExtensionCommon.jsm (JSM) +// Deshalb defensiv laden, damit dieselbe api.js über die Flotte funktioniert. +var { ExtensionCommon } = (() => { + try { + return ChromeUtils.importESModule( + "resource://gre/modules/ExtensionCommon.sys.mjs" + ); + } catch (e) { + return ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm"); + } +})(); + +// `Services` ist im privilegierten ExtensionAPI-Sandbox in TB 109+ global +// (stellt Services.prefs = nsIPrefBranch bereit); kein Import nötig. + +this.forcedPrefs = class extends ExtensionCommon.ExtensionAPI { + getAPI(context) { + return { + forcedPrefs: { + async setPref(name, type, value) { + switch (type) { + case "bool": + Services.prefs.setBoolPref(name, !!value); + break; + case "int": + Services.prefs.setIntPref(name, value | 0); + break; + case "string": + // setStringPref ist unicode-sicher (im Gegensatz zu setCharPref). + Services.prefs.setStringPref(name, String(value)); + break; + default: + throw new Error("forcedPrefs.setPref: unknown type " + type); + } + return true; + }, + + async getPref(name, type) { + if (Services.prefs.getPrefType(name) === Services.prefs.PREF_INVALID) { + return null; + } + switch (type) { + case "bool": + return Services.prefs.getBoolPref(name, false); + case "int": + return Services.prefs.getIntPref(name, 0); + case "string": + return Services.prefs.getStringPref(name, ""); + default: + throw new Error("forcedPrefs.getPref: unknown type " + type); + } + }, + }, + }; + } +}; diff --git a/experiments/prefs/schema.json b/experiments/prefs/schema.json new file mode 100644 index 0000000..0936c35 --- /dev/null +++ b/experiments/prefs/schema.json @@ -0,0 +1,29 @@ +[ + { + "namespace": "forcedPrefs", + "description": "Read/write about:config preferences from a privileged parent context.", + "functions": [ + { + "name": "setPref", + "type": "function", + "async": true, + "description": "Force an about:config pref to a fixed value. type is one of 'bool','int','string'.", + "parameters": [ + { "name": "name", "type": "string" }, + { "name": "type", "type": "string", "enum": ["bool", "int", "string"] }, + { "name": "value", "type": "any" } + ] + }, + { + "name": "getPref", + "type": "function", + "async": true, + "description": "Read an about:config pref; returns null if unset.", + "parameters": [ + { "name": "name", "type": "string" }, + { "name": "type", "type": "string", "enum": ["bool", "int", "string"] } + ] + } + ] + } +] diff --git a/lib/forced-prefs-list.js b/lib/forced-prefs-list.js new file mode 100644 index 0000000..dd82e79 --- /dev/null +++ b/lib/forced-prefs-list.js @@ -0,0 +1,52 @@ +"use strict"; + +// Single Source of Truth für die about:config-Prefs, die bei jedem TB-Start +// erzwungen werden. Wird als klassisches Script in BEIDEN Kontexten geladen +// (Background + Options-Seite), definiert also nur globale Variablen — keine +// import/export-Mechanik. +// +// Jeder Eintrag: +// id - stabiler Opt-out-Key (NIE umbenennen/wiederverwenden; landet in storage) +// label - Text im Options-Dialog +// type - "bool" | "int" | "string" +// value - der bei jedem Start erzwungene Wert +// FORCED_PREFS zusätzlich: name - voller about:config-Pref-Name +// FORCED_IDENTITY_PREFS: suffix - voller Name = mail.identity.. +// +// WICHTIG: Werte an einem korrekt eingerichteten Referenz-Thunderbird +// (Doku-Seite „Thunderbird einrichten", Page 99) per about:config gegenprüfen — +// dieser Code läuft privilegiert auf allen Clients, ein Tippfehler trifft alle. + +var FORCED_PREFS = [ + { id: "start_page_off", label: "Startseite beim Öffnen ausblenden", + name: "mailnews.start_page.enabled", type: "bool", value: false }, + { id: "listview_cards", label: "Kartenansicht (statt Tabelle)", + name: "mail.threadpane.listview", type: "int", value: 0 }, + { id: "no_grouping", label: "Nachrichten nicht gruppieren", + name: "mailnews.default_view_flags", type: "int", value: 0 }, + { id: "sort_descending", label: "Neue Nachrichten oben (absteigend)", + name: "mailnews.default_sort_order", type: "int", value: 2 }, + { id: "layout_3col", label: "3-Spalten-Ansicht (vertikales Layout)", + name: "mail.pane_config.dynamic", type: "int", value: 2 }, + { id: "no_auto_read", label: "Nachrichten nicht automatisch als gelesen markieren", + name: "mailnews.mark_message_read.auto", type: "bool", value: false }, +]; + +// Pro Identität angewandt: voller Name = mail.identity.. +var FORCED_IDENTITY_PREFS = [ + { id: "reply_on_top", label: "Antwort über dem Zitat", + suffix: "reply_on_top", type: "int", value: 1 }, + // sig_bottom=false -> Signatur direkt unter der Antwort, VOR dem Zitat. + // (true wäre ganz unten nach dem Zitat.) + { id: "sig_bottom", label: "Signatur unter Antwort / vor dem Zitat", + suffix: "sig_bottom", type: "bool", value: false }, + { id: "sig_on_reply", label: "Signatur bei Antworten beifügen", + suffix: "sig_on_reply", type: "bool", value: true }, + { id: "sig_on_fwd", label: "Signatur beim Weiterleiten beifügen", + suffix: "sig_on_fwd", type: "bool", value: true }, +]; + +if (typeof globalThis !== "undefined") { + globalThis.FORCED_PREFS = FORCED_PREFS; + globalThis.FORCED_IDENTITY_PREFS = FORCED_IDENTITY_PREFS; +} diff --git a/manifest.json b/manifest.json index 2c586b2..833100f 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "HPS Vorlagen & Signaturen", - "version": "2.3.6", + "version": "2.4.0", "description": "Vorlagen- und Signaturverwaltung für Hotel Park Soltau mit Git-Sync", "browser_specific_settings": { "gecko": { @@ -29,11 +29,22 @@ ], "background": { "scripts": [ + "lib/forced-prefs-list.js", "lib/gitea-sync.js", "background.js" ], "persistent": true }, + "experiment_apis": { + "forcedPrefs": { + "schema": "experiments/prefs/schema.json", + "parent": { + "scopes": ["addon_parent"], + "paths": [["forcedPrefs"]], + "script": "experiments/prefs/api.js" + } + } + }, "browser_action": { "default_icon": { "16": "icons/icon.png", diff --git a/templates-reply-hotel.xpi b/templates-reply-hotel.xpi index d01d959..227a645 100644 Binary files a/templates-reply-hotel.xpi and b/templates-reply-hotel.xpi differ diff --git a/templates_options/templates_options.html b/templates_options/templates_options.html index ccafcac..d68a1dc 100644 --- a/templates_options/templates_options.html +++ b/templates_options/templates_options.html @@ -1049,6 +1049,13 @@ + +
+
Erzwungene Einstellungen
+
Diese Thunderbird-Einstellungen werden bei jedem Start automatisch gesetzt (sie „springen" sonst zurück). Zum Abschalten den jeweiligen Haken entfernen — der bisherige Wert wird dabei nicht wiederhergestellt, es wird nur nicht mehr erzwungen.
+
+
+
@@ -1088,6 +1095,8 @@ + + diff --git a/templates_options/templates_options.js b/templates_options/templates_options.js index b5d73cd..dc92e61 100644 --- a/templates_options/templates_options.js +++ b/templates_options/templates_options.js @@ -1978,3 +1978,51 @@ window.addEventListener('load', async () => { } }); }); + +// ── Opt-out-Schalter für erzwungene Prefs ── +// FORCED_PREFS / FORCED_IDENTITY_PREFS stammen aus ../lib/forced-prefs-list.js +// (im HTML vor diesem Script geladen). Gespeichert werden die ids der +// DEAKTIVIERTEN Prefs unter 'forced_prefs_optout'; Haken = wird erzwungen. +const FORCED_PREFS_OPTOUT_KEY = 'forced_prefs_optout'; + +async function renderForcedPrefsToggles() { + const container = document.getElementById('forced-prefs-list'); + if (!container || typeof FORCED_PREFS === 'undefined') return; + + const stored = await browser.storage.local.get(FORCED_PREFS_OPTOUT_KEY); + const disabled = new Set(stored[FORCED_PREFS_OPTOUT_KEY] || []); + + container.textContent = ''; + const entries = [...FORCED_PREFS, ...(typeof FORCED_IDENTITY_PREFS !== 'undefined' ? FORCED_IDENTITY_PREFS : [])]; + for (const p of entries) { + const row = document.createElement('label'); + row.className = 'forced-pref-row'; + row.style.display = 'flex'; + row.style.alignItems = 'center'; + row.style.gap = '8px'; + row.style.padding = '4px 0'; + row.style.cursor = 'pointer'; + + const cb = document.createElement('input'); + cb.type = 'checkbox'; + cb.checked = !disabled.has(p.id); // Haken = wird erzwungen + cb.addEventListener('change', () => setForcedPrefEnabled(p.id, cb.checked)); + + const span = document.createElement('span'); + span.textContent = p.label; + + row.append(cb, span); + container.appendChild(row); + } +} + +async function setForcedPrefEnabled(id, enabled) { + const stored = await browser.storage.local.get(FORCED_PREFS_OPTOUT_KEY); + const disabled = new Set(stored[FORCED_PREFS_OPTOUT_KEY] || []); + if (enabled) disabled.delete(id); + else disabled.add(id); + await browser.storage.local.set({ [FORCED_PREFS_OPTOUT_KEY]: [...disabled] }); + // background.js lauscht auf storage.onChanged und wendet Aktivierungen sofort an. +} + +document.addEventListener('DOMContentLoaded', renderForcedPrefsToggles);