Compress photos for email to fit mailto limits

- Added compressImageForEmail function (300x300px, quality 0.3)
- Photos in email are now ~5-15KB instead of ~50-200KB
- sendEmail now async to handle photo compression
- Updated email text to indicate photos are resized
- Original quality preserved in CSV export
- Reduced mailto limit check to 64KB
v1.0.1
killercow 2 weeks ago
parent 38bcbd90c7
commit 03991517b2

@ -1203,6 +1203,40 @@ input.invalid, select.invalid, textarea.invalid {
reader.readAsDataURL(file);
}
// Compress image for email (much smaller)
function compressImageForEmail(dataUrl, callback) {
const img = new Image();
img.onload = function() {
const canvas = document.createElement('canvas');
const maxSize = 300; // Max 300x300 for email
let width = img.width;
let height = img.height;
// Scale down to max 300px
if (width > height) {
if (width > maxSize) {
height = height * (maxSize / width);
width = maxSize;
}
} else {
if (height > maxSize) {
width = width * (maxSize / height);
height = maxSize;
}
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
// Very low quality for email
callback(canvas.toDataURL('image/jpeg', 0.3));
};
img.src = dataUrl;
}
// Remove photo
window.removePhoto = function(fieldId) {
const preview = document.getElementById(fieldId);
@ -1262,7 +1296,7 @@ input.invalid, select.invalid, textarea.invalid {
}
// Use setTimeout to allow UI to update before heavy processing
setTimeout(function() {
setTimeout(async function() {
const data = getFormData();
// Build subject
@ -1283,8 +1317,9 @@ input.invalid, select.invalid, textarea.invalid {
body += 'Versie: ' + CONFIG.version + '\n';
body += 'Datum: ' + formatDateTime(new Date()) + '\n\n';
// Collect photos separately
// Collect photos separately and compress them
const photos = [];
const photoPromises = [];
Object.entries(data).forEach(([key, value]) => {
if (value && value !== '') {
@ -1292,14 +1327,18 @@ input.invalid, select.invalid, textarea.invalid {
if (typeof value === 'string' && value.startsWith('data:image/')) {
const label = document.querySelector(`label[for="${key}"]`);
const labelText = label ? label.textContent.replace('*', '').trim() : key;
// Extract extension from data URL (e.g., data:image/jpeg;base64,...)
const mimeMatch = value.match(/data:image\/([a-z]+);/);
const ext = mimeMatch ? mimeMatch[1] : 'jpg';
photos.push({
name: labelText,
extension: ext,
data: value
// Compress photo for email
const promise = new Promise((resolve) => {
compressImageForEmail(value, function(compressedData) {
resolve({
name: labelText,
extension: 'jpg',
data: compressedData
});
});
});
photoPromises.push(promise);
} else {
const label = document.querySelector(`label[for="${key}"]`);
const labelText = label ? label.textContent.replace('*', '').trim() : key;
@ -1309,17 +1348,19 @@ input.invalid, select.invalid, textarea.invalid {
}
});
// Wait for all photos to be compressed
const compressedPhotos = await Promise.all(photoPromises);
body += '\n========================\n';
// Add photos section only if there are photos
if (photos.length > 0) {
body += '\nFOTO BIJLAGEN (BASE64)\n';
body += '========================\n';
body += 'Onderstaande foto\'s zijn gecodeerd in base64 formaat.\n';
body += 'Kopieer de tekst tussen START en EINDE naar een base64 decoder\n';
body += 'of gebruik een online tool zoals base64-image.de\n\n';
photos.forEach((photo, index) => {
if (compressedPhotos.length > 0) {
body += '\nFOTO BIJLAGEN (BASE64 - verkleind voor email)\n';
body += '==============================================\n';
body += 'Foto\'s zijn verkleind naar 300x300px voor email.\n';
body += 'Voor originele kwaliteit, gebruik CSV export.\n\n';
compressedPhotos.forEach((photo, index) => {
const filename = photo.name.replace(/[^a-zA-Z0-9]/g, '_') + '.' + photo.extension;
body += '--- FOTO ' + (index + 1) + ': ' + filename + ' ---\n';
body += '>>> START BASE64 >>>\n';
@ -1338,9 +1379,9 @@ input.invalid, select.invalid, textarea.invalid {
button.disabled = false;
}
// Check if mailto URL is too long (most clients support ~2000 chars)
if (mailto.length > 100000) {
showToast('Email te groot door foto. Gebruik CSV export.', 'error');
// Check if mailto URL is too long
if (mailto.length > 64000) {
showToast('Email nog steeds te groot. Verklein foto of gebruik CSV.', 'error');
return;
}

@ -1234,6 +1234,40 @@ select {
reader.readAsDataURL(file);
}
// Compress image for email (much smaller)
function compressImageForEmail(dataUrl, callback) {
const img = new Image();
img.onload = function() {
const canvas = document.createElement('canvas');
const maxSize = 300; // Max 300x300 for email
let width = img.width;
let height = img.height;
// Scale down to max 300px
if (width > height) {
if (width > maxSize) {
height = height * (maxSize / width);
width = maxSize;
}
} else {
if (height > maxSize) {
width = width * (maxSize / height);
height = maxSize;
}
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
// Very low quality for email
callback(canvas.toDataURL('image/jpeg', 0.3));
};
img.src = dataUrl;
}
// Remove photo
window.removePhoto = function(fieldId) {
const preview = document.getElementById(fieldId);
@ -1293,7 +1327,7 @@ select {
}
// Use setTimeout to allow UI to update before heavy processing
setTimeout(function() {
setTimeout(async function() {
const data = getFormData();
// Build subject
@ -1314,8 +1348,9 @@ select {
body += 'Versie: ' + CONFIG.version + '\n';
body += 'Datum: ' + formatDateTime(new Date()) + '\n\n';
// Collect photos separately
// Collect photos separately and compress them
const photos = [];
const photoPromises = [];
Object.entries(data).forEach(([key, value]) => {
if (value && value !== '') {
@ -1323,14 +1358,18 @@ select {
if (typeof value === 'string' && value.startsWith('data:image/')) {
const label = document.querySelector(`label[for="${key}"]`);
const labelText = label ? label.textContent.replace('*', '').trim() : key;
// Extract extension from data URL (e.g., data:image/jpeg;base64,...)
const mimeMatch = value.match(/data:image\/([a-z]+);/);
const ext = mimeMatch ? mimeMatch[1] : 'jpg';
photos.push({
name: labelText,
extension: ext,
data: value
// Compress photo for email
const promise = new Promise((resolve) => {
compressImageForEmail(value, function(compressedData) {
resolve({
name: labelText,
extension: 'jpg',
data: compressedData
});
});
});
photoPromises.push(promise);
} else {
const label = document.querySelector(`label[for="${key}"]`);
const labelText = label ? label.textContent.replace('*', '').trim() : key;
@ -1340,17 +1379,19 @@ select {
}
});
// Wait for all photos to be compressed
const compressedPhotos = await Promise.all(photoPromises);
body += '\n========================\n';
// Add photos section only if there are photos
if (photos.length > 0) {
body += '\nFOTO BIJLAGEN (BASE64)\n';
body += '========================\n';
body += 'Onderstaande foto\'s zijn gecodeerd in base64 formaat.\n';
body += 'Kopieer de tekst tussen START en EINDE naar een base64 decoder\n';
body += 'of gebruik een online tool zoals base64-image.de\n\n';
photos.forEach((photo, index) => {
if (compressedPhotos.length > 0) {
body += '\nFOTO BIJLAGEN (BASE64 - verkleind voor email)\n';
body += '==============================================\n';
body += 'Foto\'s zijn verkleind naar 300x300px voor email.\n';
body += 'Voor originele kwaliteit, gebruik CSV export.\n\n';
compressedPhotos.forEach((photo, index) => {
const filename = photo.name.replace(/[^a-zA-Z0-9]/g, '_') + '.' + photo.extension;
body += '--- FOTO ' + (index + 1) + ': ' + filename + ' ---\n';
body += '>>> START BASE64 >>>\n';
@ -1369,9 +1410,9 @@ select {
button.disabled = false;
}
// Check if mailto URL is too long (most clients support ~2000 chars)
if (mailto.length > 100000) {
showToast('Email te groot door foto. Gebruik CSV export.', 'error');
// Check if mailto URL is too long
if (mailto.length > 64000) {
showToast('Email nog steeds te groot. Verklein foto of gebruik CSV.', 'error');
return;
}

@ -1265,6 +1265,40 @@ input.invalid, select.invalid, textarea.invalid {
reader.readAsDataURL(file);
}
// Compress image for email (much smaller)
function compressImageForEmail(dataUrl, callback) {
const img = new Image();
img.onload = function() {
const canvas = document.createElement('canvas');
const maxSize = 300; // Max 300x300 for email
let width = img.width;
let height = img.height;
// Scale down to max 300px
if (width > height) {
if (width > maxSize) {
height = height * (maxSize / width);
width = maxSize;
}
} else {
if (height > maxSize) {
width = width * (maxSize / height);
height = maxSize;
}
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
// Very low quality for email
callback(canvas.toDataURL('image/jpeg', 0.3));
};
img.src = dataUrl;
}
// Remove photo
window.removePhoto = function(fieldId) {
const preview = document.getElementById(fieldId);
@ -1324,7 +1358,7 @@ input.invalid, select.invalid, textarea.invalid {
}
// Use setTimeout to allow UI to update before heavy processing
setTimeout(function() {
setTimeout(async function() {
const data = getFormData();
// Build subject
@ -1345,8 +1379,9 @@ input.invalid, select.invalid, textarea.invalid {
body += 'Versie: ' + CONFIG.version + '\n';
body += 'Datum: ' + formatDateTime(new Date()) + '\n\n';
// Collect photos separately
// Collect photos separately and compress them
const photos = [];
const photoPromises = [];
Object.entries(data).forEach(([key, value]) => {
if (value && value !== '') {
@ -1354,14 +1389,18 @@ input.invalid, select.invalid, textarea.invalid {
if (typeof value === 'string' && value.startsWith('data:image/')) {
const label = document.querySelector(`label[for="${key}"]`);
const labelText = label ? label.textContent.replace('*', '').trim() : key;
// Extract extension from data URL (e.g., data:image/jpeg;base64,...)
const mimeMatch = value.match(/data:image\/([a-z]+);/);
const ext = mimeMatch ? mimeMatch[1] : 'jpg';
photos.push({
name: labelText,
extension: ext,
data: value
// Compress photo for email
const promise = new Promise((resolve) => {
compressImageForEmail(value, function(compressedData) {
resolve({
name: labelText,
extension: 'jpg',
data: compressedData
});
});
});
photoPromises.push(promise);
} else {
const label = document.querySelector(`label[for="${key}"]`);
const labelText = label ? label.textContent.replace('*', '').trim() : key;
@ -1371,17 +1410,19 @@ input.invalid, select.invalid, textarea.invalid {
}
});
// Wait for all photos to be compressed
const compressedPhotos = await Promise.all(photoPromises);
body += '\n========================\n';
// Add photos section only if there are photos
if (photos.length > 0) {
body += '\nFOTO BIJLAGEN (BASE64)\n';
body += '========================\n';
body += 'Onderstaande foto\'s zijn gecodeerd in base64 formaat.\n';
body += 'Kopieer de tekst tussen START en EINDE naar een base64 decoder\n';
body += 'of gebruik een online tool zoals base64-image.de\n\n';
photos.forEach((photo, index) => {
if (compressedPhotos.length > 0) {
body += '\nFOTO BIJLAGEN (BASE64 - verkleind voor email)\n';
body += '==============================================\n';
body += 'Foto\'s zijn verkleind naar 300x300px voor email.\n';
body += 'Voor originele kwaliteit, gebruik CSV export.\n\n';
compressedPhotos.forEach((photo, index) => {
const filename = photo.name.replace(/[^a-zA-Z0-9]/g, '_') + '.' + photo.extension;
body += '--- FOTO ' + (index + 1) + ': ' + filename + ' ---\n';
body += '>>> START BASE64 >>>\n';
@ -1400,9 +1441,9 @@ input.invalid, select.invalid, textarea.invalid {
button.disabled = false;
}
// Check if mailto URL is too long (most clients support ~2000 chars)
if (mailto.length > 100000) {
showToast('Email te groot door foto. Gebruik CSV export.', 'error');
// Check if mailto URL is too long
if (mailto.length > 64000) {
showToast('Email nog steeds te groot. Verklein foto of gebruik CSV.', 'error');
return;
}

@ -1469,6 +1469,40 @@ JAVASCRIPT = """
reader.readAsDataURL(file);
}
// Compress image for email (much smaller)
function compressImageForEmail(dataUrl, callback) {
const img = new Image();
img.onload = function() {
const canvas = document.createElement('canvas');
const maxSize = 300; // Max 300x300 for email
let width = img.width;
let height = img.height;
// Scale down to max 300px
if (width > height) {
if (width > maxSize) {
height = height * (maxSize / width);
width = maxSize;
}
} else {
if (height > maxSize) {
width = width * (maxSize / height);
height = maxSize;
}
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
// Very low quality for email
callback(canvas.toDataURL('image/jpeg', 0.3));
};
img.src = dataUrl;
}
// Remove photo
window.removePhoto = function(fieldId) {
const preview = document.getElementById(fieldId);
@ -1528,7 +1562,7 @@ JAVASCRIPT = """
}
// Use setTimeout to allow UI to update before heavy processing
setTimeout(function() {
setTimeout(async function() {
const data = getFormData();
// Build subject
@ -1549,8 +1583,9 @@ JAVASCRIPT = """
body += 'Versie: ' + CONFIG.version + '\\n';
body += 'Datum: ' + formatDateTime(new Date()) + '\\n\\n';
// Collect photos separately
// Collect photos separately and compress them
const photos = [];
const photoPromises = [];
Object.entries(data).forEach(([key, value]) => {
if (value && value !== '') {
@ -1558,14 +1593,18 @@ JAVASCRIPT = """
if (typeof value === 'string' && value.startsWith('data:image/')) {
const label = document.querySelector(`label[for="${key}"]`);
const labelText = label ? label.textContent.replace('*', '').trim() : key;
// Extract extension from data URL (e.g., data:image/jpeg;base64,...)
const mimeMatch = value.match(/data:image\\/([a-z]+);/);
const ext = mimeMatch ? mimeMatch[1] : 'jpg';
photos.push({
name: labelText,
extension: ext,
data: value
// Compress photo for email
const promise = new Promise((resolve) => {
compressImageForEmail(value, function(compressedData) {
resolve({
name: labelText,
extension: 'jpg',
data: compressedData
});
});
});
photoPromises.push(promise);
} else {
const label = document.querySelector(`label[for="${key}"]`);
const labelText = label ? label.textContent.replace('*', '').trim() : key;
@ -1575,17 +1614,19 @@ JAVASCRIPT = """
}
});
// Wait for all photos to be compressed
const compressedPhotos = await Promise.all(photoPromises);
body += '\\n========================\\n';
// Add photos section only if there are photos
if (photos.length > 0) {
body += '\\nFOTO BIJLAGEN (BASE64)\\n';
body += '========================\\n';
body += 'Onderstaande foto\\'s zijn gecodeerd in base64 formaat.\\n';
body += 'Kopieer de tekst tussen START en EINDE naar een base64 decoder\\n';
body += 'of gebruik een online tool zoals base64-image.de\\n\\n';
photos.forEach((photo, index) => {
if (compressedPhotos.length > 0) {
body += '\\nFOTO BIJLAGEN (BASE64 - verkleind voor email)\\n';
body += '==============================================\\n';
body += 'Foto\\'s zijn verkleind naar 300x300px voor email.\\n';
body += 'Voor originele kwaliteit, gebruik CSV export.\\n\\n';
compressedPhotos.forEach((photo, index) => {
const filename = photo.name.replace(/[^a-zA-Z0-9]/g, '_') + '.' + photo.extension;
body += '--- FOTO ' + (index + 1) + ': ' + filename + ' ---\\n';
body += '>>> START BASE64 >>>\\n';
@ -1604,9 +1645,9 @@ JAVASCRIPT = """
button.disabled = false;
}
// Check if mailto URL is too long (most clients support ~2000 chars)
if (mailto.length > 100000) {
showToast('Email te groot door foto. Gebruik CSV export.', 'error');
// Check if mailto URL is too long
if (mailto.length > 64000) {
showToast('Email nog steeds te groot. Verklein foto of gebruik CSV.', 'error');
return;
}

Loading…
Cancel
Save

Powered by TurnKey Linux.