diff --git a/install/data/defaults.json b/install/data/defaults.json index 2f733961ea..5b1f91eeb7 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -57,6 +57,7 @@ "rejectImageWidth": 5000, "rejectImageHeight": 5000, "resizeImageQuality": 80, + "convertPastedImageTo": "image/jpeg", "topicThumbSize": 512, "minimumTitleLength": 3, "maximumTitleLength": 255, diff --git a/public/language/en-GB/admin/settings/uploads.json b/public/language/en-GB/admin/settings/uploads.json index e91a7bee36..b08d56a5f8 100644 --- a/public/language/en-GB/admin/settings/uploads.json +++ b/public/language/en-GB/admin/settings/uploads.json @@ -21,6 +21,11 @@ "reject-image-width-help": "Images wider than this value will be rejected.", "reject-image-height": "Maximum Image Height (in pixels)", "reject-image-height-help": "Images taller than this value will be rejected.", + "convert-pasted-images-to": "Convert pasted images to:", + "convert-pasted-images-to-default": "No Conversion (Keep Original Format)", + "convert-pasted-images-to-png": "PNG", + "convert-pasted-images-to-jpeg": "JPEG", + "convert-pasted-images-to-webp": "WebP", "allow-topic-thumbnails": "Allow users to upload topic thumbnails", "show-post-uploads-as-thumbnails": "Show post uploads as thumbnails", "topic-thumb-size": "Topic Thumb Size", diff --git a/public/openapi/read/config.yaml b/public/openapi/read/config.yaml index d6e012bdae..16d2043667 100644 --- a/public/openapi/read/config.yaml +++ b/public/openapi/read/config.yaml @@ -87,6 +87,8 @@ get: type: number maximumFileSize: type: number + convertPastedImageTo: + type: string theme:id: type: string theme:src: diff --git a/public/src/modules/uploadHelpers.js b/public/src/modules/uploadHelpers.js index a465a44054..3e56550315 100644 --- a/public/src/modules/uploadHelpers.js +++ b/public/src/modules/uploadHelpers.js @@ -123,25 +123,43 @@ define('uploadHelpers', ['alerts'], function (alerts) { uploadHelpers.handlePaste = function (options) { const container = options.container; - container.on('paste', function (event) { + container.on('paste', async function (event) { const items = (event.clipboardData || event.originalEvent.clipboardData || {}).items; const files = []; const fileNames = []; - let formData = null; - if (window.FormData) { - formData = new FormData(); - } - [].forEach.call(items, function (item) { - const file = item.getAsFile(); - if (file) { - const fileName = utils.generateUUID() + '-' + file.name; - if (formData) { - formData.append('files[]', file, fileName); - } - files.push(file); - fileNames.push(fileName); + const formData = window.FormData ? new FormData() : null; + + function addFile(file, fileName) { + files.push(file); + fileNames.push(fileName); + if (formData) { + formData.append('files[]', file, fileName); } - }); + } + const { convertPastedImageTo } = config; + for (const item of items) { + const file = item.getAsFile(); + if (!file) continue; + try { + if (convertPastedImageTo && file.type.match(/image./) && file.type !== convertPastedImageTo) { + // eslint-disable-next-line no-await-in-loop + const convertedBlob = await convertImage(file, convertPastedImageTo, 0.9); + const ext = convertedBlob.type.split('/')[1]; + const fileName = `${utils.generateUUID()}-image.${ext}`; + + const convertedFile = new File([convertedBlob], fileName, { + type: convertedBlob.type, + }); + addFile(convertedFile, fileName); + } else { + const fileName = utils.generateUUID() + '-' + file.name; + addFile(file, fileName); + } + } catch (err) { + alerts.error(err); + console.error(err); + } + } if (files.length) { options.callback({ @@ -217,5 +235,34 @@ define('uploadHelpers', ['alerts'], function (alerts) { options.uploadForm.submit(); }; + function convertImage(file, mime, quality = 0.9) { + return new Promise((resolve, reject) => { + const img = new Image(); + const reader = new FileReader(); + + reader.onload = e => { + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + + const ctx = canvas.getContext('2d'); + ctx.drawImage(img, 0, 0); + + canvas.toBlob(blob => { + if (!blob) return reject(new Error('Conversion failed')); + resolve(blob); + }, mime, quality); + }; + + img.onerror = reject; + img.src = e.target.result; + }; + + reader.onerror = reject; + reader.readAsDataURL(file); + }); + } + return uploadHelpers; }); diff --git a/src/controllers/api.js b/src/controllers/api.js index f55b411bfa..4fd83dcd5b 100644 --- a/src/controllers/api.js +++ b/src/controllers/api.js @@ -64,6 +64,7 @@ apiController.loadConfig = async function (req) { topicsPerPage: meta.config.topicsPerPage || 20, postsPerPage: meta.config.postsPerPage || 20, maximumFileSize: meta.config.maximumFileSize, + convertPastedImageTo: meta.config.convertPastedImageTo, 'theme:id': meta.config['theme:id'], 'theme:src': meta.config['theme:src'], defaultLang: meta.config.defaultLang || 'en-GB', diff --git a/src/views/admin/settings/uploads.tpl b/src/views/admin/settings/uploads.tpl index 400058eb20..d2bb25076c 100644 --- a/src/views/admin/settings/uploads.tpl +++ b/src/views/admin/settings/uploads.tpl @@ -83,12 +83,22 @@
+