Add footer asset selector for email customization
- New /api/assets endpoint to list available icons from assets folder - Dynamic footer generation with multiple selectable icons - Thumbnail grid UI for asset selection in the mail form - Selected icons are embedded as CID attachments in emails Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,13 @@
|
||||
<textarea id="body" name="body" required rows="8" placeholder="Ihre Nachricht..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Footer-Icons</label>
|
||||
<div id="assetGrid" class="asset-grid">
|
||||
<p class="loading-message">Lade Assets...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="submitBtn">Senden</button>
|
||||
</form>
|
||||
|
||||
|
||||
@@ -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 = '<p class="empty-message">Fehler beim Laden der Assets</p>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading assets:', error);
|
||||
assetGrid.innerHTML = '<p class="empty-message">Fehler beim Laden der Assets</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// Render asset grid
|
||||
function renderAssetGrid(assets) {
|
||||
if (!assets || assets.length === 0) {
|
||||
assetGrid.innerHTML = '<p class="empty-message">Keine Assets verfügbar</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
assetGrid.innerHTML = assets.map(filename => {
|
||||
const name = filename.replace(/\.[^.]+$/, '');
|
||||
return `
|
||||
<label class="asset-item">
|
||||
<input type="checkbox" name="footerAssets" value="${filename}">
|
||||
<img src="/assets/${filename}" alt="${name}">
|
||||
<span class="asset-name">${name}</span>
|
||||
</label>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Render history list
|
||||
function renderHistory(history) {
|
||||
if (!history || history.length === 0) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user