Initial commit: Templates Reply Add-on für Hotel Park Soltau

Modifiziertes Templates Reply Thunderbird Add-on mit:
- HTML-Import von Netzlaufwerk
- Massen-Delete mit Checkboxen
- Deutsches UI mit "Vorlagen" Button
- 16 Hotel-Vorlagen als HTML-Dateien
This commit is contained in:
Kendrick Bollens
2026-04-09 00:37:18 +02:00
commit 3906c33967
25 changed files with 1400 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Manage Templates</title>
<style>
body { font-family: sans-serif; padding: 20px; }
#templates-list { margin-top: 20px; border: 1px solid #ccc; padding: 10px; }
.template-item { padding: 10px; border-bottom: 1px dashed #eee; display: flex; justify-content: space-between; align-items: center; }
.template-item:last-child { border-bottom: none; }
.template-name { font-weight: bold; }
button { margin-left: 10px; }
</style>
</head>
<body>
<h2>Templates manager</h2>
<fieldset>
<legend>HTML-Dateien importieren</legend>
<p style="font-size: 0.9em; color: #555;">Wähle eine oder mehrere .html Dateien aus (z.B. vom Netzlaufwerk). Dateiname = Template-Name. Bestehende Templates mit gleichem Namen werden überschrieben.</p>
<input type="file" id="import-files" accept=".html,.htm" multiple style="margin-bottom: 10px;">
<br>
<button type="button" id="import-button">Importieren</button>
<span id="import-status" style="margin-left: 10px; color: green; display: none;"></span>
</fieldset>
<br>
<fieldset>
<legend id="form-legend">New Template</legend>
<form id="template-form">
<input type="hidden" id="template-id">
<label for="template-name">Title:</label><br>
<input type="text" id="template-name" required style="width: 98%; margin-bottom: 10px;"><br>
<label for="template-content">Text (HTML/Tekst):</label><br>
<textarea id="template-content" required rows="10" style="width: 98%; margin-bottom: 10px;"></textarea><br>
<button type="submit" id="save-button">Save</button>
<button type="button" id="cancel-edit" style="display: none;">Discard</button>
</form>
</fieldset>
<h3>Existing Templates</h3>
<div style="margin-bottom: 10px;">
<button type="button" id="select-all-button">Alle auswählen</button>
<button type="button" id="delete-selected-button" style="color: red;">Ausgewählte löschen</button>
</div>
<div id="templates-list">
<p id="no-templates">There is no saved templates.</p>
</div>
<script src="templates_options.js"></script>
</body>
</html>

View File

@@ -0,0 +1,253 @@
// templates_options/templates_options.js
const TEMPLATE_STORAGE_KEY = 'message_templates';
// Initialization of IDs
const templateForm = document.getElementById('template-form');
const templateList = document.getElementById('templates-list');
const noTemplatesMessage = document.getElementById('no-templates');
const saveButton = document.getElementById('save-button');
const cancelButton = document.getElementById('cancel-edit');
const formLegend = document.getElementById('form-legend');
/**
* Retrieves all templates from Thunderbird 'storage'
* @returns {Promise<Array>} Array of template objects
*/
async function getTemplates() {
try {
const result = await browser.storage.local.get(TEMPLATE_STORAGE_KEY);
// Returns an empty array if no data is stored
return result[TEMPLATE_STORAGE_KEY] || [];
} catch (error) {
console.error("Error retrieving templates:", error);
return [];
}
}
/**
* Saves the array of templates to Thunderbird 'storage'
* @param {Array} templates Array of template objects
*/
async function saveTemplates(templates) {
try {
await browser.storage.local.set({ [TEMPLATE_STORAGE_KEY]: templates });
console.log("Templates successfully saved.");
} catch (error) {
console.error("Error saving templates:", error);
}
}
/**
* Displays templates on the HTML page
* @param {Array} templates Array of template objects
*/
function renderTemplates(templates) {
templateList.innerHTML = ''; // Clear the existing list
if (templates.length === 0) {
templateList.appendChild(noTemplatesMessage);
noTemplatesMessage.style.display = 'block';
return;
}
noTemplatesMessage.style.display = 'none';
templates.forEach(template => {
const item = document.createElement('div');
item.className = 'template-item';
item.innerHTML = `
<input type="checkbox" class="template-checkbox" data-id="${template.id}" style="margin-right: 10px;">
<span class="template-name">${template.name}</span>
<div>
<button data-id="${template.id}" class="edit-btn">Edit</button>
<button data-id="${template.id}" class="delete-btn">Delete</button>
</div>
`;
templateList.appendChild(item);
});
// Adding Listeners for Edit/Delete
document.querySelectorAll('.edit-btn').forEach(button => {
button.addEventListener('click', handleEdit);
});
document.querySelectorAll('.delete-btn').forEach(button => {
button.addEventListener('click', handleDelete);
});
}
/**
* Handles form submission (Add or Save Edited)
*/
templateForm.addEventListener('submit', async (e) => {
e.preventDefault();
const id = document.getElementById('template-id').value;
const name = document.getElementById('template-name').value;
const content = document.getElementById('template-content').value;
let templates = await getTemplates();
if (id) {
// EDITING EXISTING
const index = templates.findIndex(t => t.id === id);
if (index > -1) {
templates[index] = { id, name, content };
}
} else {
// ADDING NEW
const newId = Date.now().toString(); // Simple unique ID
templates.push({ id: newId, name, content });
}
await saveTemplates(templates);
renderTemplates(templates);
resetForm();
});
/**
* Deletes a template
*/
async function handleDelete(e) {
if (!confirm('Are you sure you want to delete this template?')) {
return;
}
const idToDelete = e.target.dataset.id;
let templates = await getTemplates();
// Filter to exclude the template with that ID
templates = templates.filter(t => t.id !== idToDelete);
await saveTemplates(templates);
renderTemplates(templates);
}
/**
* Loads template data into the edit form
*/
async function handleEdit(e) {
const idToEdit = e.target.dataset.id;
const templates = await getTemplates();
const template = templates.find(t => t.id === idToEdit);
if (template) {
document.getElementById('template-id').value = template.id;
document.getElementById('template-name').value = template.name;
document.getElementById('template-content').value = template.content;
// Update UI to signal editing
formLegend.textContent = 'Edit Template';
saveButton.textContent = 'Update Template';
cancelButton.style.display = 'inline';
// Scroll to the top of the form
window.scrollTo(0, 0);
}
}
/**
* Resets the editing state and clears the form
*/
function resetForm() {
templateForm.reset();
document.getElementById('template-id').value = '';
formLegend.textContent = 'Add New Template';
saveButton.textContent = 'Save Template';
cancelButton.style.display = 'none';
}
cancelButton.addEventListener('click', resetForm);
/**
* Select all / deselect all checkboxes
*/
document.getElementById('select-all-button').addEventListener('click', () => {
const checkboxes = document.querySelectorAll('.template-checkbox');
const allChecked = Array.from(checkboxes).every(cb => cb.checked);
checkboxes.forEach(cb => cb.checked = !allChecked);
});
/**
* Delete all selected templates
*/
document.getElementById('delete-selected-button').addEventListener('click', async () => {
const checked = document.querySelectorAll('.template-checkbox:checked');
if (checked.length === 0) return;
if (!confirm(`${checked.length} Template(s) wirklich löschen?`)) return;
const idsToDelete = new Set(Array.from(checked).map(cb => cb.dataset.id));
let templates = await getTemplates();
templates = templates.filter(t => !idsToDelete.has(t.id));
await saveTemplates(templates);
renderTemplates(templates);
});
/**
* Handles import of HTML files as templates.
* Filename (without extension) becomes the template name.
* Existing templates with the same name are overwritten.
*/
document.getElementById('import-button').addEventListener('click', async () => {
const fileInput = document.getElementById('import-files');
const statusEl = document.getElementById('import-status');
const files = fileInput.files;
if (files.length === 0) {
statusEl.textContent = 'Bitte Dateien auswählen!';
statusEl.style.color = 'red';
statusEl.style.display = 'inline';
return;
}
let templates = await getTemplates();
let importCount = 0;
for (const file of files) {
const content = await file.text();
const name = file.name.replace(/\.html?$/i, '');
// Extract body content if it's a full HTML document
let body = content;
const bodyMatch = content.match(/<body[^>]*>([\s\S]*)<\/body>/i);
if (bodyMatch) {
body = bodyMatch[1].trim();
}
// Check if template with same name exists
const existingIndex = templates.findIndex(
t => t.name.toLowerCase() === name.toLowerCase()
);
if (existingIndex > -1) {
// Overwrite existing
templates[existingIndex].content = body;
} else {
// Add new
templates.push({
id: Date.now().toString() + importCount,
name: name,
content: body
});
}
importCount++;
}
await saveTemplates(templates);
renderTemplates(templates);
statusEl.textContent = `${importCount} Template(s) importiert!`;
statusEl.style.color = 'green';
statusEl.style.display = 'inline';
fileInput.value = '';
setTimeout(() => { statusEl.style.display = 'none'; }, 3000);
});
// Load templates on page load
window.addEventListener('load', async () => {
const templates = await getTemplates();
renderTemplates(templates);
});