From 58a6c8fe4dffa5d9c274a2ae78cc0dd739918e19 Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Tue, 24 Jan 2023 10:35:25 -0800 Subject: [PATCH 1/5] Show unordered list decorations (#1353) --- static/index.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/index.css b/static/index.css index 37d55cb3d3..f77e8d95c8 100644 --- a/static/index.css +++ b/static/index.css @@ -91,7 +91,7 @@ dl { overflow-wrap: break-word; } -ul, li { +ul { overflow: hidden; } @@ -104,7 +104,7 @@ ul, li { .blocks { font-family: monospace, monospace; list-style-position: inside; - padding-inline-start: 0; + padding-inline-start: 1rem; white-space: nowrap; } From c29bee8bdcc2dd05888d7674c08d9d1eb4e0d58b Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Tue, 24 Jan 2023 10:53:19 -0800 Subject: [PATCH 2/5] Support PDF Inscriptions (#1352) --- Cargo.toml | 2 +- src/media.rs | 2 ++ src/subcommand/server.rs | 47 ++++++++++++++++++++++++++++++++++---- src/templates.rs | 4 +++- src/templates/preview.rs | 5 ++++ static/preview-pdf.css | 14 ++++++++++++ static/preview-pdf.js | 23 +++++++++++++++++++ templates/preview-pdf.html | 11 +++++++++ 8 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 static/preview-pdf.css create mode 100644 static/preview-pdf.js create mode 100644 templates/preview-pdf.html diff --git a/Cargo.toml b/Cargo.toml index 61ccc8c483..c83476e34b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ tempfile = "3.2.0" tokio = { version = "1.17.0", features = ["rt-multi-thread"] } tokio-stream = "0.1.9" tokio-util = {version = "0.7.3", features = ["compat"] } -tower-http = { version = "0.3.3", features = ["set-header"] } +tower-http = { version = "0.3.3", features = ["cors", "set-header"] } [dev-dependencies] executable-path = "1.0.0" diff --git a/src/media.rs b/src/media.rs index 0dfd9e78c2..df622fb5b2 100644 --- a/src/media.rs +++ b/src/media.rs @@ -5,6 +5,7 @@ pub(crate) enum Media { Audio, Iframe, Image, + Pdf, Text, Unknown, } @@ -48,6 +49,7 @@ impl FromStr for Media { } const TABLE: &[(&str, Media, &[&str])] = &[ + ("application/pdf", Media::Pdf, &["pdf"]), ("audio/flac", Media::Audio, &["flac"]), ("audio/mpeg", Media::Audio, &["mp3"]), ("audio/wav", Media::Audio, &["wav"]), diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 2293f3165d..9ade61c47f 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -6,8 +6,8 @@ use { super::*, crate::templates::{ BlockHtml, ClockSvg, HomeHtml, InputHtml, InscriptionHtml, InscriptionsHtml, OutputHtml, - PageContent, PageHtml, PreviewAudioHtml, PreviewImageHtml, PreviewTextHtml, PreviewUnknownHtml, - RangeHtml, RareTxt, SatHtml, TransactionHtml, + PageContent, PageHtml, PreviewAudioHtml, PreviewImageHtml, PreviewPdfHtml, PreviewTextHtml, + PreviewUnknownHtml, RangeHtml, RareTxt, SatHtml, TransactionHtml, }, axum::{ body, @@ -28,7 +28,10 @@ use { }, std::{cmp::Ordering, str}, tokio_stream::StreamExt, - tower_http::set_header::SetResponseHeaderLayer, + tower_http::{ + cors::{Any, CorsLayer}, + set_header::SetResponseHeaderLayer, + }, }; mod error; @@ -163,7 +166,12 @@ impl Server { .layer(SetResponseHeaderLayer::overriding( header::STRICT_TRANSPORT_SECURITY, HeaderValue::from_static("max-age=31536000; includeSubDomains; preload"), - )); + )) + .layer( + CorsLayer::new() + .allow_methods([http::Method::GET]) + .allow_origin(Any), + ); match (self.http_port(), self.https_port()) { (Some(http_port), None) => { @@ -751,6 +759,16 @@ impl Server { .ok_or_not_found(|| format!("inscription {inscription_id} content"))? .into_response(), ), + Media::Pdf => Ok( + ( + [( + header::CONTENT_SECURITY_POLICY, + "script-src-elem 'self' https://cdn.jsdelivr.net", + )], + PreviewPdfHtml { inscription_id }, + ) + .into_response(), + ), Media::Text => { let content = inscription .body() @@ -1995,6 +2013,27 @@ mod tests { ); } + #[test] + fn pdf_preview() { + let server = TestServer::new(); + server.mine_blocks(1); + + let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0)], + witness: inscription("application/pdf", "hello").to_witness(), + ..Default::default() + }); + let inscription_id = InscriptionId::from(txid); + + server.mine_blocks(1); + + server.assert_response_regex( + format!("/preview/{inscription_id}"), + StatusCode::OK, + format!(r".*.*"), + ); + } + #[test] fn image_preview() { let server = TestServer::new(); diff --git a/src/templates.rs b/src/templates.rs index 8ad882af8c..8283e2916b 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -9,7 +9,9 @@ pub(crate) use { inscription::InscriptionHtml, inscriptions::InscriptionsHtml, output::OutputHtml, - preview::{PreviewAudioHtml, PreviewImageHtml, PreviewTextHtml, PreviewUnknownHtml}, + preview::{ + PreviewAudioHtml, PreviewImageHtml, PreviewPdfHtml, PreviewTextHtml, PreviewUnknownHtml, + }, range::RangeHtml, rare::RareTxt, sat::SatHtml, diff --git a/src/templates/preview.rs b/src/templates/preview.rs index ba06b70516..f0bdb16998 100644 --- a/src/templates/preview.rs +++ b/src/templates/preview.rs @@ -10,6 +10,11 @@ pub(crate) struct PreviewImageHtml { pub(crate) inscription_id: InscriptionId, } +#[derive(boilerplate::Boilerplate)] +pub(crate) struct PreviewPdfHtml { + pub(crate) inscription_id: InscriptionId, +} + #[derive(boilerplate::Boilerplate)] pub(crate) struct PreviewTextHtml<'a> { pub(crate) text: &'a str, diff --git a/static/preview-pdf.css b/static/preview-pdf.css new file mode 100644 index 0000000000..4944cb4128 --- /dev/null +++ b/static/preview-pdf.css @@ -0,0 +1,14 @@ +html { + height: 100%; +} + +body { + height: 100%; + margin: 0; +} + +canvas { + height: 100%; + width: 100%; + object-fit: contain; +} diff --git a/static/preview-pdf.js b/static/preview-pdf.js new file mode 100644 index 0000000000..a76ff3abd1 --- /dev/null +++ b/static/preview-pdf.js @@ -0,0 +1,23 @@ +import pdfjs from 'https://cdn.jsdelivr.net/npm/pdfjs-dist@3.2.146/+esm'; + +pdfjs.GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@3.2.146/build/pdf.worker.min.js'; + +let canvas = document.querySelector('canvas'); + +let pdf = await pdfjs.getDocument(`/content/${canvas.dataset.inscription}`).promise; + +let page = await pdf.getPage(1); + +let scale = window.devicePixelRatio || 1; + +let viewport = page.getViewport({ scale }); + +canvas.width = Math.ceil(viewport.width * scale); + +canvas.height = Math.ceil(viewport.height * scale); + +page.render({ + canvasContext: canvas.getContext('2d'), + transform: [scale, 0, 0, scale, 0, 0], + viewport, +}); diff --git a/templates/preview-pdf.html b/templates/preview-pdf.html new file mode 100644 index 0000000000..e6fb528747 --- /dev/null +++ b/templates/preview-pdf.html @@ -0,0 +1,11 @@ + + + + + + + + + + + From 5751a2244144eaed0875c829874084a81ca4befa Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Tue, 24 Jan 2023 11:08:01 -0800 Subject: [PATCH 3/5] Support video inscriptions (#1349) --- src/media.rs | 2 ++ src/subcommand/server.rs | 35 ++++++++++++++++++++++++++++------- src/templates.rs | 1 + src/templates/preview.rs | 5 +++++ static/preview-video.css | 16 ++++++++++++++++ templates/preview-video.html | 12 ++++++++++++ 6 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 static/preview-video.css create mode 100644 templates/preview-video.html diff --git a/src/media.rs b/src/media.rs index df622fb5b2..d1fa1d4d0b 100644 --- a/src/media.rs +++ b/src/media.rs @@ -8,6 +8,7 @@ pub(crate) enum Media { Pdf, Text, Unknown, + Video, } impl Media { @@ -61,6 +62,7 @@ const TABLE: &[(&str, Media, &[&str])] = &[ ("image/webp", Media::Image, &["webp"]), ("text/html;charset=utf-8", Media::Iframe, &["html"]), ("text/plain;charset=utf-8", Media::Text, &["txt"]), + ("video/webm", Media::Video, &["webm"]), ]; #[cfg(test)] diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 9ade61c47f..0471832100 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -7,7 +7,7 @@ use { crate::templates::{ BlockHtml, ClockSvg, HomeHtml, InputHtml, InscriptionHtml, InscriptionsHtml, OutputHtml, PageContent, PageHtml, PreviewAudioHtml, PreviewImageHtml, PreviewPdfHtml, PreviewTextHtml, - PreviewUnknownHtml, RangeHtml, RareTxt, SatHtml, TransactionHtml, + PreviewUnknownHtml, PreviewVideoHtml, RangeHtml, RareTxt, SatHtml, TransactionHtml, }, axum::{ body, @@ -744,6 +744,11 @@ impl Server { return match inscription.media() { Media::Audio => Ok(PreviewAudioHtml { inscription_id }.into_response()), + Media::Iframe => Ok( + Self::content_response(inscription) + .ok_or_not_found(|| format!("inscription {inscription_id} content"))? + .into_response(), + ), Media::Image => Ok( ( [( @@ -754,11 +759,6 @@ impl Server { ) .into_response(), ), - Media::Iframe => Ok( - Self::content_response(inscription) - .ok_or_not_found(|| format!("inscription {inscription_id} content"))? - .into_response(), - ), Media::Pdf => Ok( ( [( @@ -773,7 +773,6 @@ impl Server { let content = inscription .body() .ok_or_not_found(|| format!("inscription {inscription_id} content"))?; - Ok( PreviewTextHtml { text: str::from_utf8(content) @@ -783,6 +782,7 @@ impl Server { ) } Media::Unknown => Ok(PreviewUnknownHtml.into_response()), + Media::Video => Ok(PreviewVideoHtml { inscription_id }.into_response()), }; } @@ -2098,6 +2098,27 @@ mod tests { ); } + #[test] + fn video_preview() { + let server = TestServer::new(); + server.mine_blocks(1); + + let txid = server.bitcoin_rpc_server.broadcast_tx(TransactionTemplate { + inputs: &[(1, 0, 0)], + witness: inscription("video/webm", "hello").to_witness(), + ..Default::default() + }); + let inscription_id = InscriptionId::from(txid); + + server.mine_blocks(1); + + server.assert_response_regex( + format!("/preview/{inscription_id}"), + StatusCode::OK, + format!(r".*