Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5eebd1b06 | ||
|
|
c7383f3a77 | ||
|
|
fa817050d8 | ||
|
|
f223f55933 | ||
|
|
0d05f9412c |
@@ -16,6 +16,8 @@ async function executeErledigtAction(tab, actionConfig) {
|
|||||||
const storage = await browser.storage.local.get(['gitea_config', 'schlagwoerter_cache']);
|
const storage = await browser.storage.local.get(['gitea_config', 'schlagwoerter_cache']);
|
||||||
const config = storage.gitea_config || {};
|
const config = storage.gitea_config || {};
|
||||||
const schlagwoerter = storage.schlagwoerter_cache;
|
const schlagwoerter = storage.schlagwoerter_cache;
|
||||||
|
// "Als gelesen markieren" — pro Aktion konfigurierbar, standardmäßig an.
|
||||||
|
const markRead = (actionConfig || {}).markRead !== false;
|
||||||
|
|
||||||
// Apply user's tag
|
// Apply user's tag
|
||||||
let tagKey = null;
|
let tagKey = null;
|
||||||
@@ -33,6 +35,11 @@ async function executeErledigtAction(tab, actionConfig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark as read (before the move, while the id is still valid in this folder)
|
||||||
|
if (markRead && !message.read) {
|
||||||
|
await messenger.messages.update(message.id, { read: true });
|
||||||
|
}
|
||||||
|
|
||||||
// Move to target folder
|
// Move to target folder
|
||||||
if (actionConfig.targetFolder) {
|
if (actionConfig.targetFolder) {
|
||||||
const folderInfo = JSON.parse(actionConfig.targetFolder);
|
const folderInfo = JSON.parse(actionConfig.targetFolder);
|
||||||
@@ -42,6 +49,7 @@ async function executeErledigtAction(tab, actionConfig) {
|
|||||||
// Feedback
|
// Feedback
|
||||||
const parts = [];
|
const parts = [];
|
||||||
if (tagKey) parts.push('markiert');
|
if (tagKey) parts.push('markiert');
|
||||||
|
if (markRead && !message.read) parts.push('gelesen');
|
||||||
if (actionConfig.targetFolder) parts.push('verschoben');
|
if (actionConfig.targetFolder) parts.push('verschoben');
|
||||||
const title = actionConfig.name || 'Erledigt';
|
const title = actionConfig.name || 'Erledigt';
|
||||||
browser.notifications.create({
|
browser.notifications.create({
|
||||||
@@ -68,12 +76,19 @@ messenger.messageDisplayAction.onClicked.addListener(async (tab) => {
|
|||||||
async function updateErledigtPopup() {
|
async function updateErledigtPopup() {
|
||||||
const result = await browser.storage.local.get('erledigt_config');
|
const result = await browser.storage.local.get('erledigt_config');
|
||||||
const actions = (result.erledigt_config || {}).actions || [];
|
const actions = (result.erledigt_config || {}).actions || [];
|
||||||
|
const setLabel = (label) => {
|
||||||
|
// setLabel ist Thunderbird-spezifisch — defensiv prüfen, damit der Start nie bricht.
|
||||||
|
if (messenger.messageDisplayAction.setLabel) return messenger.messageDisplayAction.setLabel({ label });
|
||||||
|
};
|
||||||
if (actions.length > 1) {
|
if (actions.length > 1) {
|
||||||
await messenger.messageDisplayAction.setPopup({ popup: 'message_popup.html' });
|
await messenger.messageDisplayAction.setPopup({ popup: 'message_popup.html' });
|
||||||
await messenger.messageDisplayAction.setTitle({ title: 'Aktion wählen' });
|
await messenger.messageDisplayAction.setTitle({ title: 'Aktion wählen' });
|
||||||
|
await setLabel('QuickMove');
|
||||||
} else {
|
} else {
|
||||||
|
const name = actions[0]?.name || 'QuickMove';
|
||||||
await messenger.messageDisplayAction.setPopup({ popup: '' });
|
await messenger.messageDisplayAction.setPopup({ popup: '' });
|
||||||
await messenger.messageDisplayAction.setTitle({ title: actions[0]?.name || 'Erledigt' });
|
await messenger.messageDisplayAction.setTitle({ title: name });
|
||||||
|
await setLabel(name); // Button heißt wie die (einzige) Aktion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +98,85 @@ browser.storage.onChanged.addListener((changes, area) => {
|
|||||||
});
|
});
|
||||||
updateErledigtPopup();
|
updateErledigtPopup();
|
||||||
|
|
||||||
|
// ── Signatur in neuen Compose-Fenstern automatisch aktualisieren ──
|
||||||
|
//
|
||||||
|
// Thunderbird merkt sich die Signatur einer Identität pro Sitzung. Öffnet der
|
||||||
|
// Benutzer nach einem Hintergrund-Sync ein neues Verfassen-Fenster, steckt darin
|
||||||
|
// noch die ALTE Signatur (alter Footer) — `identities.update({signature})`
|
||||||
|
// schreibt zwar den Pref, aber das Compose-Fenster fügt die gecachte Fassung ein.
|
||||||
|
// Ein Event „neues Compose-Fenster" gibt es in der WebExtension-API nicht, daher
|
||||||
|
// hängen wir uns an tabs.onCreated und tauschen NUR den Footer-Teil (ab dem
|
||||||
|
// SIG_FOOTER_START-Marker) im eingefügten Signaturblock gegen den frisch
|
||||||
|
// gesyncten aus. Header, Trenner ("-- ") und Struktur bleiben unangetastet.
|
||||||
|
// Vollautomatisch, ohne jede Benutzeraktion.
|
||||||
|
|
||||||
|
const SIG_FOOTER_MARK = '<!-- SIG_FOOTER_START -->';
|
||||||
|
const sigSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||||
|
|
||||||
|
async function getFreshFooter() {
|
||||||
|
const c = await browser.storage.local.get('sig_footer_cache');
|
||||||
|
return c.sig_footer_cache || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshComposeSignature(tabId, tabType) {
|
||||||
|
// Nicht-Compose-Tabs sofort ignorieren, wenn der Typ bekannt ist.
|
||||||
|
if (tabType && tabType !== 'messageCompose') return;
|
||||||
|
|
||||||
|
const footer = await getFreshFooter();
|
||||||
|
if (!footer) return;
|
||||||
|
|
||||||
|
// Läuft IM Compose-Dokument: findet den Signaturblock, ersetzt nur den Footer.
|
||||||
|
const code = `(function(){
|
||||||
|
const editor = document.getElementById('messageEditor');
|
||||||
|
const doc = editor ? editor.contentDocument : document;
|
||||||
|
const sig = doc.querySelector('.moz-signature');
|
||||||
|
if (!sig) return 'notready';
|
||||||
|
const MARK = ${JSON.stringify(SIG_FOOTER_MARK)};
|
||||||
|
const cur = sig.innerHTML;
|
||||||
|
const i = cur.indexOf(MARK);
|
||||||
|
if (i === -1) return 'unmanaged'; // keine vom Plugin verwaltete Signatur
|
||||||
|
const before = cur.slice(0, i + MARK.length); // "-- <br>" + Header + Marker
|
||||||
|
const curFooter = cur.slice(i + MARK.length).replace(/^\\s+/, '');
|
||||||
|
const NEW = ${JSON.stringify(footer)};
|
||||||
|
if (curFooter === NEW) return 'current'; // schon aktuell, nichts tun
|
||||||
|
sig.innerHTML = before + '\\n' + NEW;
|
||||||
|
return 'updated';
|
||||||
|
})();`;
|
||||||
|
|
||||||
|
// Thunderbird fügt die Signatur evtl. erst kurz nach dem Öffnen ein → kurz pollen.
|
||||||
|
let throwCount = 0;
|
||||||
|
for (let attempt = 0; attempt < 20; attempt++) {
|
||||||
|
let details;
|
||||||
|
try {
|
||||||
|
details = await browser.compose.getComposeDetails(tabId);
|
||||||
|
} catch (_) {
|
||||||
|
// Kein Compose-Tab oder noch nicht bereit. Bei bekanntem Typ weiter pollen,
|
||||||
|
// sonst nach ein paar Fehlversuchen aufgeben.
|
||||||
|
if (tabType !== 'messageCompose' && ++throwCount > 3) return;
|
||||||
|
await sigSleep(150);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!details) { await sigSleep(150); continue; }
|
||||||
|
if (details.isPlainText) return; // Footer ist HTML — Plaintext nicht anfassen
|
||||||
|
if (details.type === 'draft') return; // gespeicherte Entwürfe nicht überschreiben
|
||||||
|
|
||||||
|
let res;
|
||||||
|
try {
|
||||||
|
const out = await browser.tabs.executeScript(tabId, { code });
|
||||||
|
res = Array.isArray(out) ? out[0] : out;
|
||||||
|
} catch (_) {
|
||||||
|
return; // Fenster bereits geschlossen o. Ä.
|
||||||
|
}
|
||||||
|
if (res !== 'notready') return; // fertig: updated / current / unmanaged
|
||||||
|
await sigSleep(150);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
browser.tabs.onCreated.addListener((tab) => {
|
||||||
|
refreshComposeSignature(tab.id, tab && tab.type).catch((err) =>
|
||||||
|
console.error('[Sig] Compose-Refresh fehlgeschlagen:', err));
|
||||||
|
});
|
||||||
|
|
||||||
// ── Template insertion ──
|
// ── Template insertion ──
|
||||||
|
|
||||||
browser.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
browser.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||||
|
|||||||
@@ -400,6 +400,19 @@ class SyncManager {
|
|||||||
return { success: true, pushed };
|
return { success: true, pushed };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Kanonischer Signatur-Slug, identisch zum Web-Editor (slugifyHeaderName):
|
||||||
|
// toFilename(...) + lowercase + Leerzeichen→Bindestrich. So lesen/schreiben
|
||||||
|
// Web-Editor und Plugin DIESELBE Datei (z.B. ".kendrick-bollens.html").
|
||||||
|
get sigSlug() {
|
||||||
|
return SyncManager.toFilename(this.config.authorName || '')
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\s+/g, '-');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Altes Plugin-Format (Groß-/Kleinschreibung + Leerzeichen, z.B.
|
||||||
|
// ".Kendrick Bollens.html"). Nur noch für Lese-Fallback, damit Dateien von
|
||||||
|
// Clients mit alter Plugin-Version weiterhin angewandt werden. Nicht mehr
|
||||||
|
// schreiben — sonst entstehen wieder Doppel-Dateien.
|
||||||
get authorSlug() {
|
get authorSlug() {
|
||||||
return SyncManager.toFilename(this.config.authorName || '');
|
return SyncManager.toFilename(this.config.authorName || '');
|
||||||
}
|
}
|
||||||
@@ -567,11 +580,30 @@ class SyncManager {
|
|||||||
|
|
||||||
if (source.startsWith('=')) continue;
|
if (source.startsWith('=')) continue;
|
||||||
|
|
||||||
// Try personal header: email.authorslug.html
|
// Personal-Header suchen. headerMap-Keys sind lowercase, daher
|
||||||
let targetFile = null;
|
// Kandidaten ebenfalls lowercase nachschlagen (behebt den Bug,
|
||||||
|
// dass Groß-Namen NIE gefunden wurden → Plugin fiel auf die lokale
|
||||||
|
// Signatur zurück und revertierte beim Push die Web-Editor-Änderung).
|
||||||
|
// Kandidaten:
|
||||||
|
// • kanonisches Web-Editor-Format (lowercase-bindestrich)
|
||||||
|
// • altes Plugin-Format (Groß/Leerzeichen) von Clients mit alter Version
|
||||||
|
// Bei mehreren Treffern gewinnt der NEUESTE Commit — so verdeckt eine
|
||||||
|
// veraltete Datei keine frische Web-Editor-Änderung, egal welche der
|
||||||
|
// beiden Dateien zuletzt im Web-Editor bearbeitet wurde.
|
||||||
|
const candidates = [];
|
||||||
|
if (this.sigSlug) {
|
||||||
|
const f = headerMap[`${email}.${this.sigSlug}.html`.toLowerCase()];
|
||||||
|
if (f) candidates.push(f);
|
||||||
|
}
|
||||||
if (this.authorSlug) {
|
if (this.authorSlug) {
|
||||||
const personalName = `${email}.${this.authorSlug}.html`;
|
const f = headerMap[`${email}.${this.authorSlug}.html`.toLowerCase()];
|
||||||
targetFile = headerMap[personalName] || null;
|
if (f && !candidates.includes(f)) candidates.push(f);
|
||||||
|
}
|
||||||
|
let targetFile = null;
|
||||||
|
let targetTime = -1;
|
||||||
|
for (const f of candidates) {
|
||||||
|
const t = Date.parse(f.last_author_date || f.last_committer_date || '') || 0;
|
||||||
|
if (t >= targetTime) { targetFile = f; targetTime = t; }
|
||||||
}
|
}
|
||||||
|
|
||||||
let header = null;
|
let header = null;
|
||||||
@@ -634,7 +666,7 @@ class SyncManager {
|
|||||||
async pushSignatures() {
|
async pushSignatures() {
|
||||||
if (!this.isConfigured) throw new Error('Sync nicht konfiguriert');
|
if (!this.isConfigured) throw new Error('Sync nicht konfiguriert');
|
||||||
if (!this.config.authorName) throw new Error('Bitte Name eintragen (für Commit-Zuordnung)');
|
if (!this.config.authorName) throw new Error('Bitte Name eintragen (für Commit-Zuordnung)');
|
||||||
if (!this.authorSlug) throw new Error('Name fehlt für Dateiname');
|
if (!this.sigSlug) throw new Error('Name fehlt für Dateiname');
|
||||||
|
|
||||||
const sourceResult = await browser.storage.local.get('sig_source_map');
|
const sourceResult = await browser.storage.local.get('sig_source_map');
|
||||||
const sourceMap = sourceResult.sig_source_map || {};
|
const sourceMap = sourceResult.sig_source_map || {};
|
||||||
@@ -655,7 +687,12 @@ class SyncManager {
|
|||||||
const header = SyncManager.extractHeader(identity.signature);
|
const header = SyncManager.extractHeader(identity.signature);
|
||||||
if (!header.trim()) continue;
|
if (!header.trim()) continue;
|
||||||
|
|
||||||
const filename = `${email}.${this.authorSlug}.html`;
|
// Ins kanonische Web-Editor-Format schreiben (lowercase-bindestrich),
|
||||||
|
// damit Plugin und Web-Editor dieselbe Datei pflegen. Alte
|
||||||
|
// Groß-Dateien bleiben unangetastet (keine Breaking Changes für
|
||||||
|
// Clients mit alter Plugin-Version), werden aber nicht mehr neu
|
||||||
|
// geschrieben.
|
||||||
|
const filename = `${email}.${this.sigSlug}.html`;
|
||||||
const filepath = `signatures/headers/${filename}`;
|
const filepath = `signatures/headers/${filename}`;
|
||||||
const commitMsg = `Signatur-Header ${identity.email} - von ${this.config.authorName}`;
|
const commitMsg = `Signatur-Header ${identity.email} - von ${this.config.authorName}`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "HPS Vorlagen & Signaturen",
|
"name": "HPS Vorlagen & Signaturen",
|
||||||
"version": "2.3.1",
|
"version": "2.3.6",
|
||||||
"description": "Vorlagen- und Signaturverwaltung für Hotel Park Soltau mit Git-Sync",
|
"description": "Vorlagen- und Signaturverwaltung für Hotel Park Soltau mit Git-Sync",
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
@@ -28,7 +28,10 @@
|
|||||||
"*://*/*"
|
"*://*/*"
|
||||||
],
|
],
|
||||||
"background": {
|
"background": {
|
||||||
"scripts": ["lib/gitea-sync.js", "background.js"],
|
"scripts": [
|
||||||
|
"lib/gitea-sync.js",
|
||||||
|
"background.js"
|
||||||
|
],
|
||||||
"persistent": true
|
"persistent": true
|
||||||
},
|
},
|
||||||
"browser_action": {
|
"browser_action": {
|
||||||
|
|||||||
Binary file not shown.
@@ -1879,6 +1879,12 @@ function renderErledigtAction(action, index) {
|
|||||||
${erledigtFolderOptions}
|
${erledigtFolderOptions}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group" style="flex:0 0 auto;">
|
||||||
|
${index === 0 ? '<label>Gelesen</label>' : ''}
|
||||||
|
<label style="display:flex;align-items:center;gap:6px;height:34px;white-space:nowrap;font-size:13px;cursor:pointer;">
|
||||||
|
<input type="checkbox" class="erledigt-markread" ${action.markRead !== false ? 'checked' : ''}> als gelesen
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<button type="button" class="btn btn-danger btn-sm erledigt-remove" title="Entfernen" style="margin-bottom:2px;">✕</button>
|
<button type="button" class="btn btn-danger btn-sm erledigt-remove" title="Entfernen" style="margin-bottom:2px;">✕</button>
|
||||||
`;
|
`;
|
||||||
if (action.targetFolder) {
|
if (action.targetFolder) {
|
||||||
@@ -1900,10 +1906,11 @@ function getErledigtActionsFromForm() {
|
|||||||
const actions = [];
|
const actions = [];
|
||||||
const names = document.querySelectorAll('.erledigt-name');
|
const names = document.querySelectorAll('.erledigt-name');
|
||||||
const folders = document.querySelectorAll('.erledigt-folder');
|
const folders = document.querySelectorAll('.erledigt-folder');
|
||||||
|
const reads = document.querySelectorAll('.erledigt-markread');
|
||||||
for (let i = 0; i < names.length; i++) {
|
for (let i = 0; i < names.length; i++) {
|
||||||
const name = names[i].value.trim();
|
const name = names[i].value.trim();
|
||||||
if (!name) continue;
|
if (!name) continue;
|
||||||
actions.push({ name, targetFolder: folders[i].value || '' });
|
actions.push({ name, targetFolder: folders[i].value || '', markRead: reads[i] ? reads[i].checked : true });
|
||||||
}
|
}
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|||||||
40
updates.json
40
updates.json
@@ -2,6 +2,46 @@
|
|||||||
"addons": {
|
"addons": {
|
||||||
"it@hotel-park-soltau.de": {
|
"it@hotel-park-soltau.de": {
|
||||||
"updates": [
|
"updates": [
|
||||||
|
{
|
||||||
|
"version": "2.3.5",
|
||||||
|
"update_link": "https://git.hotel-park-soltau.de/hps/hps-thunderbird-templates/releases/download/v2.3.5/templates-reply-hotel.xpi",
|
||||||
|
"update_hash": "sha256:41335a1b572ee38fe659d716ab7b89754fa997ec532e626930f8e470fac31281",
|
||||||
|
"applications": {
|
||||||
|
"gecko": {
|
||||||
|
"strict_min_version": "109.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "2.3.4",
|
||||||
|
"update_link": "https://git.hotel-park-soltau.de/hps/hps-thunderbird-templates/releases/download/v2.3.4/templates-reply-hotel.xpi",
|
||||||
|
"update_hash": "sha256:1825f81c309c7d2be4bba048b1e41d0758bb145823b765c0dbb8add75cd4ad2e",
|
||||||
|
"applications": {
|
||||||
|
"gecko": {
|
||||||
|
"strict_min_version": "109.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "2.3.3",
|
||||||
|
"update_link": "https://git.hotel-park-soltau.de/hps/hps-thunderbird-templates/releases/download/v2.3.3/templates-reply-hotel.xpi",
|
||||||
|
"update_hash": "sha256:7845dddcf7335f8e1411162a2aae821e9293a9de1c4bc4016cc357d2a159165a",
|
||||||
|
"applications": {
|
||||||
|
"gecko": {
|
||||||
|
"strict_min_version": "109.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "2.3.2",
|
||||||
|
"update_link": "https://git.hotel-park-soltau.de/hps/hps-thunderbird-templates/releases/download/v2.3.2/templates-reply-hotel.xpi",
|
||||||
|
"update_hash": "sha256:8466d174aeda6c15577a45ed1cd442bc592d35a1da602d79b1f169ae3d012bd7",
|
||||||
|
"applications": {
|
||||||
|
"gecko": {
|
||||||
|
"strict_min_version": "109.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"update_link": "https://git.hotel-park-soltau.de/hps/hps-thunderbird-templates/releases/download/v2.3.1/templates-reply-hotel.xpi",
|
"update_link": "https://git.hotel-park-soltau.de/hps/hps-thunderbird-templates/releases/download/v2.3.1/templates-reply-hotel.xpi",
|
||||||
|
|||||||
Reference in New Issue
Block a user