From 10fc962da6107179ec0ff1c5a127a79672a57e47 Mon Sep 17 00:00:00 2001 From: liamwhite Date: Tue, 5 Oct 2021 21:31:50 -0400 Subject: [PATCH] Video upload previews (#141) --- assets/css/views/_images.scss | 2 +- assets/js/upload.js | 19 ++++++++++++++++--- .../plugs/content_security_policy_plug.ex | 4 ++-- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/assets/css/views/_images.scss b/assets/css/views/_images.scss index 852b1161..a4db1b9d 100644 --- a/assets/css/views/_images.scss +++ b/assets/css/views/_images.scss @@ -286,7 +286,7 @@ span.spoiler div.image-container { &:empty { display: none; } - img { + img, video { max-height: 200px; max-width: 200px; } diff --git a/assets/js/upload.js b/assets/js/upload.js index 7b511f9c..2f9a7db3 100644 --- a/assets/js/upload.js +++ b/assets/js/upload.js @@ -6,12 +6,26 @@ import { fetchJson, handleError } from './utils/requests'; import { $, $$, hideEl, showEl, makeEl, clearEl } from './utils/dom'; import { addTag } from './tagsinput'; +const MATROSKA_MAGIC = 0x1a45dfa3; + function scrapeUrl(url) { return fetchJson('POST', '/images/scrape', { url }) .then(handleError) .then(response => response.json()); } +function elementForEmbeddedImage({ camo_url }) { + // The upload was fetched from the scraper and is a path name + if (typeof camo_url === 'string') { + return makeEl('img', { className: 'scraper-preview--image', src: camo_url }); + } + + // The upload was fetched from a file input and is an ArrayBuffer + const objectUrl = URL.createObjectURL(new Blob([camo_url])); + const tagName = new DataView(camo_url).getUint32(0) === MATROSKA_MAGIC ? 'video' : 'img'; + return makeEl(tagName, { className: 'scraper-preview--image', src: objectUrl }); +} + function setupImageUpload() { const imgPreviews = $('#js-image-upload-previews'); if (!imgPreviews) return; @@ -26,8 +40,7 @@ function setupImageUpload() { clearEl(imgPreviews); images.forEach((image, index) => { - const img = makeEl('img', { className: 'scraper-preview--image' }); - img.src = image.camo_url; + const img = elementForEmbeddedImage(image); const imgWrap = makeEl('span', { className: 'scraper-preview--image-wrapper' }); imgWrap.appendChild(img); @@ -72,7 +85,7 @@ function setupImageUpload() { }); // Watch for files added to the form - fileField.addEventListener('change', () => { fileField.files.length && reader.readAsDataURL(fileField.files[0]); }); + fileField.addEventListener('change', () => { fileField.files.length && reader.readAsArrayBuffer(fileField.files[0]); }); // Watch for [Fetch] clicks fetchButton.addEventListener('click', () => { diff --git a/lib/philomena_web/plugs/content_security_policy_plug.ex b/lib/philomena_web/plugs/content_security_policy_plug.ex index 5959dfd7..5a97a57d 100644 --- a/lib/philomena_web/plugs/content_security_policy_plug.ex +++ b/lib/philomena_web/plugs/content_security_policy_plug.ex @@ -31,8 +31,8 @@ defmodule PhilomenaWeb.ContentSecurityPolicyPlug do {:frame_src, frame_src || ["'none'"]}, {:form_action, ["'self'"]}, {:manifest_src, ["'self'"]}, - {:img_src, ["'self'", "data:", cdn_uri, camo_uri]}, - {:media_src, ["'self'", "data:", cdn_uri, camo_uri]}, + {:img_src, ["'self'", "blob:", "data:", cdn_uri, camo_uri]}, + {:media_src, ["'self'", "blob:", "data:", cdn_uri, camo_uri]}, {:block_all_mixed_content, []} ]