diff --git a/public/index.html b/public/index.html index 2df5589..aaabee7 100644 --- a/public/index.html +++ b/public/index.html @@ -43,6 +43,13 @@ +
+ +
+

Lade Assets...

+
+
+ diff --git a/public/script.js b/public/script.js index 80c1d6f..967a07e 100644 --- a/public/script.js +++ b/public/script.js @@ -4,21 +4,28 @@ document.addEventListener('DOMContentLoaded', () => { const submitBtn = document.getElementById('submitBtn'); const historyList = document.getElementById('historyList'); const clearHistoryBtn = document.getElementById('clearHistory'); + const assetGrid = document.getElementById('assetGrid'); - // Load history on page load + // Load history and assets on page load loadHistory(); + loadAssets(); // Form submission form.addEventListener('submit', async (e) => { e.preventDefault(); const formData = new FormData(form); + const selectedAssets = Array.from( + document.querySelectorAll('input[name="footerAssets"]:checked') + ).map(cb => cb.value); + const data = { to: formData.get('to'), cc: formData.get('cc') || undefined, subject: formData.get('subject'), body: formData.get('body'), - isHtml: formData.get('format') === 'html' + isHtml: formData.get('format') === 'html', + footerAssets: selectedAssets }; submitBtn.disabled = true; @@ -80,6 +87,42 @@ document.addEventListener('DOMContentLoaded', () => { } } + // Load assets from server + async function loadAssets() { + try { + const response = await fetch('/api/assets'); + const result = await response.json(); + + if (result.success) { + renderAssetGrid(result.assets); + } else { + assetGrid.innerHTML = '

Fehler beim Laden der Assets

'; + } + } catch (error) { + console.error('Error loading assets:', error); + assetGrid.innerHTML = '

Fehler beim Laden der Assets

'; + } + } + + // Render asset grid + function renderAssetGrid(assets) { + if (!assets || assets.length === 0) { + assetGrid.innerHTML = '

Keine Assets verfügbar

'; + return; + } + + assetGrid.innerHTML = assets.map(filename => { + const name = filename.replace(/\.[^.]+$/, ''); + return ` + + `; + }).join(''); + } + // Render history list function renderHistory(history) { if (!history || history.length === 0) { diff --git a/public/style.css b/public/style.css index f6b57a9..1e6a4b2 100644 --- a/public/style.css +++ b/public/style.css @@ -235,12 +235,64 @@ button:disabled { background: none; } -.empty-message { +.empty-message, +.loading-message { text-align: center; color: #999; padding: 40px 20px; } +/* Asset Grid */ +.asset-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(70px, 1fr)); + gap: 10px; + padding: 10px; + background: #f9f9f9; + border: 1px solid #ddd; + border-radius: 4px; +} + +.asset-item { + display: flex; + flex-direction: column; + align-items: center; + padding: 8px; + border: 2px solid transparent; + border-radius: 4px; + cursor: pointer; + transition: all 0.15s ease; +} + +.asset-item:hover { + background: #eee; +} + +.asset-item img { + width: 40px; + height: 40px; + object-fit: contain; +} + +.asset-item .asset-name { + font-size: 11px; + color: #666; + margin-top: 4px; + text-align: center; + word-break: break-all; +} + +.asset-item:has(input:checked) { + border-color: #3498db; + background: #e7f3fd; +} + +.asset-item input[type="checkbox"] { + position: absolute; + opacity: 0; + pointer-events: none; +} + @media (max-width: 900px) { .container { flex-direction: column; diff --git a/src/mailer.js b/src/mailer.js index 3879db4..f525f75 100644 --- a/src/mailer.js +++ b/src/mailer.js @@ -15,23 +15,25 @@ const transporter = nodemailer.createTransport({ }); const FOOTER_NAME = process.env.MAIL_FOOTER_NAME || 'Joachim Hummel'; -const FOOTER_IMAGE = path.join(__dirname, '..', 'assets', 'homeicon.png'); -function getHtmlFooter() { +function getHtmlFooter(assets) { + if (!assets || assets.length === 0) { + return ''; + } + + const icons = assets.map(filename => { + const cid = filename.replace(/\.[^.]+$/, ''); + return `${cid}`; + }).join(''); + return `


- - - - - -
- Home - - Absender
- ${FOOTER_NAME} -
+
+
${icons}
+ Absender
+ ${FOOTER_NAME} +
`; } @@ -40,17 +42,19 @@ function getTextFooter() { } async function sendMail(email) { + const footerAssets = email.footerAssets || []; + + const attachments = footerAssets.map(filename => ({ + filename, + path: path.join(__dirname, '..', 'assets', filename), + cid: filename.replace(/\.[^.]+$/, '') + })); + const mailOptions = { from: `"${process.env.MAIL_FROM_NAME}" <${process.env.MAIL_FROM_EMAIL}>`, to: email.to, subject: email.subject, - attachments: [ - { - filename: 'homeicon.png', - path: FOOTER_IMAGE, - cid: 'homeicon' - } - ] + attachments }; if (email.cc) { @@ -58,11 +62,10 @@ async function sendMail(email) { } if (email.isHtml) { - mailOptions.html = email.body + getHtmlFooter(); + mailOptions.html = email.body + getHtmlFooter(footerAssets); } else { mailOptions.text = email.body + getTextFooter(); - // Also send HTML version with footer for better display - mailOptions.html = `
${email.body}
` + getHtmlFooter(); + mailOptions.html = `
${email.body}
` + getHtmlFooter(footerAssets); } const info = await transporter.sendMail(mailOptions); diff --git a/src/server.js b/src/server.js index d43a56c..373a977 100644 --- a/src/server.js +++ b/src/server.js @@ -1,6 +1,7 @@ require('dotenv').config(); const express = require('express'); +const fs = require('fs'); const path = require('path'); const db = require('./database'); const mailer = require('./mailer'); @@ -11,6 +12,7 @@ const PORT = process.env.PORT || 3000; // Middleware app.use(express.json()); app.use(express.static(path.join(__dirname, '..', 'public'))); +app.use('/assets', express.static(path.join(__dirname, '..', 'assets'))); // Email validation helper function isValidEmail(email) { @@ -20,9 +22,29 @@ function isValidEmail(email) { // API Routes +// Get available assets +app.get('/api/assets', (req, res) => { + const assetsDir = path.join(__dirname, '..', 'assets'); + const allowedExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.svg']; + + try { + const files = fs.readdirSync(assetsDir); + const assets = files + .filter(file => { + const ext = path.extname(file).toLowerCase(); + return allowedExtensions.includes(ext); + }) + .sort(); + + res.json({ success: true, assets }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + // Send email app.post('/api/send', async (req, res) => { - const { to, cc, subject, body, isHtml } = req.body; + const { to, cc, subject, body, isHtml, footerAssets } = req.body; // Validation if (!to || !subject || !body) { @@ -46,7 +68,7 @@ app.post('/api/send', async (req, res) => { }); } - const email = { to, cc, subject, body, isHtml: !!isHtml }; + const email = { to, cc, subject, body, isHtml: !!isHtml, footerAssets: footerAssets || [] }; try { const info = await mailer.sendMail(email);