4 Commits
v2.6.0 ... main

Author SHA1 Message Date
Kendrick Bollens
730d32ab29 Web-Editor: Farben/Inline-Styles beim Einfügen aus E-Mails behalten
TinyMCE registriert unter Chromium (Chrome/Edge) und Safari einen
PastePreProcess-Filter, der bei der Default-Einstellung
paste_webkit_styles:'none' ALLE inline-Styles (inkl. color) aus extern
eingefügtem Inhalt entfernt. Interner Copy/Paste ist ausgenommen — daher
ging webapp->webapp, aber E-Mail->Editor verlor die Farbe.

paste_webkit_styles:'all' schaltet den Stripper ab und behält die Styles.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011GkKJBbiNV421xfCbXikGV
2026-07-23 11:37:29 +02:00
Kendrick Bollens
3f792a6434 Web-Editor: robustes Einfügen aus externen Quellen (E-Mails)
TinyMCEs Standard-Paste nutzt bei externem Inhalt einen fragilen
Pastebin-Fallback, der bei manchen Quellen (E-Mail-Clients) still
fehlschlägt → "nichts passiert". Interner Copy/Paste ging, weil TinyMCE
dabei text/html selbst auf die Zwischenablage setzt.

Eigener paste-Handler liest text/html bzw. text/plain direkt aus und
fügt es deterministisch ein (Formatierung bleibt erhalten, Office/Outlook-
Fragment wird ausgeschnitten). Interner Paste und Bild-Paste bleiben dem
Standard-Handler überlassen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011GkKJBbiNV421xfCbXikGV
2026-07-23 11:21:51 +02:00
Kendrick Bollens
8057870f8b Release v2.6.0 — updates.json (Auto-Update-Manifest)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XcAzPQhKgwS4HNzvXEFeN
2026-07-22 18:34:43 +02:00
Kendrick Bollens
2b6e2267c2 Vorlage beim Durchswitchen ersetzen statt stapeln (v2.6.0)
HTML-Vorlagen werden jetzt in <div id="hps-vorlage"> gewickelt und
direkt im Editor-DOM eingefügt. Vor dem Einfügen wird ein evtl. schon
vorhandener hps-vorlage-Block entfernt — so ersetzt Durchswitchen die
Vorlage statt sie oben draufzustapeln. insertText bleibt als Fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XcAzPQhKgwS4HNzvXEFeN
2026-07-22 18:34:26 +02:00
5 changed files with 79 additions and 16 deletions

View File

@@ -276,31 +276,50 @@ async function handleInsertTemplate(msg) {
const newBody = msg.text + (old ? '\n' + old : ''); const newBody = msg.text + (old ? '\n' + old : '');
await browser.compose.setComposeDetails(tab.id, { plainTextBody: newBody }); await browser.compose.setComposeDetails(tab.id, { plainTextBody: newBody });
} else { } else {
// HTML mode: use insertText API to go through the editor's rendering pipeline // HTML mode: wrap the template in a marker element (id=hps-vorlage) and
// replace any previously inserted one — so durchswitchen ersetzt statt stapelt.
const htmlContent = isHtmlTemplate ? msg.text : msg.text.replace(/\n/g, '<br>'); const htmlContent = isHtmlTemplate ? msg.text : msg.text.replace(/\n/g, '<br>');
// Move cursor to beginning of body first via script injection // Directly manipulate the editor DOM: remove the old template block (if any),
await browser.tabs.executeScript(tab.id, { // then insert the new one at the top of the body. Returns true on success.
const injected = await browser.tabs.executeScript(tab.id, {
code: ` code: `
(function () {
const editor = document.getElementById('messageEditor'); const editor = document.getElementById('messageEditor');
const editorDoc = editor ? editor.contentDocument : document; const editorDoc = editor ? editor.contentDocument : document;
const body = editorDoc.body; const body = editorDoc && editorDoc.body;
if (body) { if (!body) return false;
// Remove a previously inserted template block, so switching replaces it
const prev = editorDoc.getElementById('hps-vorlage');
if (prev) prev.remove();
// Wrap the new template in a marker element with a stable id
const wrapper = editorDoc.createElement('div');
wrapper.id = 'hps-vorlage';
wrapper.innerHTML = ${JSON.stringify(htmlContent)};
// Insert at the very top of the body (above quote/signature/typed text)
body.insertBefore(wrapper, body.firstChild);
// Place the cursor right after the inserted block
const range = editorDoc.createRange(); const range = editorDoc.createRange();
range.setStart(body, 0); range.setStartAfter(wrapper);
range.collapse(true); range.collapse(true);
const sel = editorDoc.getSelection(); const sel = editorDoc.getSelection();
sel.removeAllRanges(); sel.removeAllRanges();
sel.addRange(range); sel.addRange(range);
} return true;
})();
` `
}).catch(() => { }).then(r => (Array.isArray(r) ? r[0] : r)).catch(() => false);
// Fallback: some TB versions structure the editor differently
});
// Insert HTML at cursor position - this goes through the editor's render pipeline if (!injected) {
// Fallback: editor DOM not reachable (TB version differences) — use the
// insertText pipeline. Note: this variant cannot replace, only prepend.
await browser.compose.insertText(tab.id, htmlContent + '<br>', { insertAsText: false }); await browser.compose.insertText(tab.id, htmlContent + '<br>', { insertAsText: false });
} }
}
} catch (e) { } catch (e) {
console.error('background.js error:', e); console.error('background.js error:', e);

View File

@@ -1,7 +1,7 @@
{ {
"manifest_version": 2, "manifest_version": 2,
"name": "HPS Vorlagen & Signaturen", "name": "HPS Vorlagen & Signaturen",
"version": "2.5.0", "version": "2.6.0",
"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": {

Binary file not shown.

View File

@@ -2,6 +2,16 @@
"addons": { "addons": {
"it@hotel-park-soltau.de": { "it@hotel-park-soltau.de": {
"updates": [ "updates": [
{
"version": "2.6.0",
"update_link": "https://git.hotel-park-soltau.de/hps/hps-thunderbird-templates/releases/download/v2.6.0/templates-reply-hotel.xpi",
"update_hash": "sha256:563e65f7686db7be30712fbc2be934f51c9ce8f4d35bb37f07046d8da40b562d",
"applications": {
"gecko": {
"strict_min_version": "109.0"
}
}
},
{ {
"version": "2.5.0", "version": "2.5.0",
"update_link": "https://git.hotel-park-soltau.de/hps/hps-thunderbird-templates/releases/download/v2.5.0/templates-reply-hotel.xpi", "update_link": "https://git.hotel-park-soltau.de/hps/hps-thunderbird-templates/releases/download/v2.5.0/templates-reply-hotel.xpi",

View File

@@ -364,7 +364,9 @@
valid_elements: '*[*]', extended_valid_elements: '*[*]', valid_children: '+body[style]', valid_elements: '*[*]', extended_valid_elements: '*[*]', valid_children: '+body[style]',
verify_html: false, convert_urls: false, verify_html: false, convert_urls: false,
content_style: 'body{font-family:Arial,Helvetica,sans-serif;font-size:14px;color:#1f2a30;line-height:1.6;padding:10px 12px;} img{max-width:100%;height:auto;} table{border-collapse:collapse;}', content_style: 'body{font-family:Arial,Helvetica,sans-serif;font-size:14px;color:#1f2a30;line-height:1.6;padding:10px 12px;} img{max-width:100%;height:auto;} table{border-collapse:collapse;}',
paste_data_images: true, automatic_uploads: false, file_picker_types: 'image', // Chrome/Edge/Safari entfernen sonst beim Einfügen aus externen Quellen (E-Mails)
// ALLE inline-Styles inkl. Farben ('paste_webkit_styles' default 'none'). 'all' behält sie.
paste_data_images: true, paste_webkit_styles: 'all', automatic_uploads: false, file_picker_types: 'image',
file_picker_callback: function (cb) { file_picker_callback: function (cb) {
const input = document.createElement('input'); input.type = 'file'; input.accept = 'image/*'; const input = document.createElement('input'); input.type = 'file'; input.accept = 'image/*';
input.onchange = function () { const file = input.files[0]; if (!file) return; const r = new FileReader(); r.onload = function () { cb(r.result, { title: file.name }); }; r.readAsDataURL(file); }; input.onchange = function () { const file = input.files[0]; if (!file) return; const r = new FileReader(); r.onload = function () { cb(r.result, { title: file.name }); }; r.readAsDataURL(file); };
@@ -374,6 +376,38 @@
ed = editor; ed = editor;
editor.on('init', function () { edReady = true; }); editor.on('init', function () { edReady = true; });
editor.on('input ExecCommand Undo Redo SetContent paste', function () { if (!suppressDirty) markDirty(); }); editor.on('input ExecCommand Undo Redo SetContent paste', function () { if (!suppressDirty) markDirty(); });
// Robustes Einfügen aus externen Quellen (z.B. E-Mails). TinyMCEs eigener
// Fallback (verstecktes „Pastebin"-Feld) schlägt bei manchen Quellen still
// fehl → „nichts passiert". Wir lesen die Zwischenablage direkt aus und
// fügen HTML mitsamt Formatierung ein. Interner Copy/Paste (funktioniert
// bereits) und Bild-Paste bleiben dem Standard-Handler überlassen.
editor.on('paste', function (e) {
const cd = e.clipboardData;
if (!cd) return; // keine Zwischenablage → Standard-Handler versuchen lassen
let html = '';
try { html = cd.getData('text/html') || ''; } catch (_) {}
// TinyMCE-interner Inhalt trägt diesen Marker → Standard-Pfad übernehmen.
if (html.indexOf('x-tinymce/html') !== -1) return;
let text = '';
try { text = cd.getData('text/plain') || ''; } catch (_) {}
// Reines Bild (Screenshot o.ä.) → TinyMCEs Bildverarbeitung übernehmen.
const hasImage =
(cd.types && Array.prototype.indexOf.call(cd.types, 'Files') !== -1) ||
(cd.items && Array.prototype.some.call(cd.items, function (it) { return it.kind === 'file' && /^image\//.test(it.type); }));
if (!html && !text && hasImage) return;
if (!html && !text) return; // nichts Brauchbares → Standard-Fallback lassen
e.preventDefault(); // unterdrückt den fragilen TinyMCE-Standard-Handler
let content;
if (html) {
// Office/Outlook markieren die eigentliche Auswahl per Fragment-Kommentar.
const frag = /<!--\s*StartFragment\s*-->([\s\S]*?)<!--\s*EndFragment\s*-->/i.exec(html);
content = frag ? frag[1] : html;
} else {
content = editor.dom.encode(text).replace(/\r\n?|\n/g, '<br>');
}
editor.insertContent(content);
markDirty();
});
}, },
}).then(() => { edReady = true; }); }).then(() => { edReady = true; });
} }