From edd72b1b0cbe96f53a85e888fc94b53268c38e57 Mon Sep 17 00:00:00 2001 From: Cody Casterline Date: Thu, 29 Jul 2021 16:52:54 -0700 Subject: [PATCH 01/23] WIP: Implement OGP for link preview cards. Also: * Server-side Identicons! Still to do: * Ditch identicon.js for server-side identicons. smaller & cacheable. * Implement a "share" button. * Try not to parse the Markdown twice on the HTML view. * Add article description. (Parsed from Markdown.) --- Cargo.lock | 112 +++++++++++++++++++--- Cargo.toml | 7 ++ src/markdown.rs | 54 ++++++++++- src/server.rs | 7 ++ src/server/html.rs | 69 ++++++++++++- src/server/non_standard.rs | 36 +++++++ templates/page.html | 2 +- templates/post.html | 28 +++++- web-client/components/ProfileImage.svelte | 3 + 9 files changed, 297 insertions(+), 21 deletions(-) create mode 100644 src/server/non_standard.rs diff --git a/Cargo.lock b/Cargo.lock index 0c23c5f..6d2c385 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "actix-codec" version = "0.3.0" @@ -276,6 +278,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + [[package]] name = "ahash" version = "0.4.7" @@ -468,7 +476,7 @@ dependencies = [ "addr2line", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.4.1", "object", "rustc-demangle", ] @@ -592,11 +600,17 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" +[[package]] +name = "bytemuck" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b" + [[package]] name = "byteorder" -version = "1.3.1" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" @@ -665,6 +679,12 @@ dependencies = [ "bitflags", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + [[package]] name = "comrak" version = "0.9.1" @@ -737,6 +757,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "deflate" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +dependencies = [ + "adler32", + "byteorder", +] + [[package]] name = "derive_more" version = "0.99.10" @@ -881,6 +911,7 @@ dependencies = [ "comrak", "env_logger", "futures", + "identicon", "log", "logging_timer", "mime_guess", @@ -910,7 +941,7 @@ dependencies = [ "cfg-if", "crc32fast", "libc", - "miniz_oxide", + "miniz_oxide 0.4.1", ] [[package]] @@ -1193,6 +1224,14 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a" +[[package]] +name = "identicon" +version = "0.2.0" +source = "git+https://github.com/NfNitLoop/identicon?rev=e29ac7793d0469ae15fbf23953b7354cb10fd340#e29ac7793d0469ae15fbf23953b7354cb10fd340" +dependencies = [ + "image", +] + [[package]] name = "idna" version = "0.2.0" @@ -1204,6 +1243,21 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "image" +version = "0.23.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-iter", + "num-rational 0.3.2", + "num-traits", + "png", +] + [[package]] name = "indexmap" version = "1.6.0" @@ -1271,9 +1325,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.77" +version = "0.2.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" +checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" [[package]] name = "libsodium-sys" @@ -1389,6 +1443,15 @@ dependencies = [ "unicase", ] +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + [[package]] name = "miniz_oxide" version = "0.4.1" @@ -1482,7 +1545,7 @@ dependencies = [ "num-complex", "num-integer", "num-iter", - "num-rational", + "num-rational 0.2.2", "num-traits", ] @@ -1508,11 +1571,11 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ - "autocfg 0.1.4", + "autocfg 1.0.1", "num-traits", ] @@ -1539,13 +1602,24 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg 1.0.1", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" -version = "0.2.8" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "autocfg 0.1.4", + "autocfg 1.0.1", ] [[package]] @@ -1713,6 +1787,18 @@ version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +dependencies = [ + "bitflags", + "crc32fast", + "deflate", + "miniz_oxide 0.3.7", +] + [[package]] name = "ppv-lite86" version = "0.2.5" diff --git a/Cargo.toml b/Cargo.toml index 5353a0f..149b9ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,13 @@ blocking = "*" tempfile = "*" +[dependencies.identicon] +git = "https://github.com/NfNitLoop/identicon" +rev = "e29ac7793d0469ae15fbf23953b7354cb10fd340" +default-features = false + + + [dependencies.rusqlite] # TODO: Switch to sqlx for async sql support? version = "0.24" diff --git a/src/markdown.rs b/src/markdown.rs index 7891665..d571246 100644 --- a/src/markdown.rs +++ b/src/markdown.rs @@ -10,6 +10,9 @@ pub(crate) trait ToHTML { fn md_to_html(&self) -> String; fn md_to_html_with(&self, options: Options) -> String; + + /// Find all images embedded in the Markdown. + fn md_get_images(&self) -> Vec; } impl ToHTML for str { @@ -31,6 +34,44 @@ impl ToHTML for str { format_html(root, &md_options, &mut html).expect("Should be no I/O errors writing to a vec![]"); to_string_lossy(html) } + + fn md_get_images(&self) -> Vec { + let md_options = ComrakOptions::default(); + + let arena = Arena::new(); + let root = parse_document(&arena, self, &md_options); + + let mut images = vec![]; + + iter_nodes_mut(root, &mut |node| { + if let NodeValue::Image(ref img) = node.data.borrow().value { + images.push(image_from_node(node, img)); + } + }); + + + return images + } +} + +fn image_from_node<'a>(node: &'a Node<'a, RefCell>, img: &NodeLink) -> Image { + + let url = to_string_lossy(img.url.clone()); + + // A node's alt text is stored as a child text node: + let alt = node.children().next().map(|it| { + if let NodeValue::Text(ref text) = it.data.borrow().value { + return Some(to_string_lossy(text.clone())); + } + return None; + }).flatten(); + + Image{url, alt} +} + +pub(crate) struct Image { + pub url: String, + pub alt: Option, } fn fix_relative_links<'a>(arena: &Arena>>, root: &'a AstNode<'a>, options: &Options) { @@ -80,16 +121,25 @@ where F : Fn(&'a AstNode<'a>) } } +fn iter_nodes_mut<'a, F>(node: &'a AstNode<'a>, f: &mut F) +where F : FnMut(&'a AstNode<'a>) +{ + f(node); + for c in node.children() { + iter_nodes_mut(c, f); + } +} + fn to_string_lossy(bytes: Vec) -> String { let err = match String::from_utf8(bytes) { - // This is the efficient happy path: + // This is the efficient happy path: (in-place conversion) Ok(s) => return s, Err(e) => e, }; - // Use a lossy copy instead: + // Use a lossy copy instead: (allocates) let s = String::from_utf8_lossy(err.as_bytes()); String::from(s) } diff --git a/src/server.rs b/src/server.rs index d49d5a9..7272e6e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -37,6 +37,7 @@ mod client; mod html; mod pagination; mod rest; +mod non_standard; use pagination::Paginator; @@ -139,6 +140,12 @@ fn routes(cfg: &mut web::ServiceConfig) { .wrap(cors_ok_headers()) ) + .service( + web::resource("/u/{user_id}/icon.png") + .route(get().to(non_standard::identicon_get)) + .wrap_fn(immutable_etag) + ) + .route("/u/{userID}/i/{signature}/", get().to(html::show_item)) .service( web::resource("/u/{userID}/i/{signature}/proto3") diff --git a/src/server/html.rs b/src/server/html.rs index e2917a7..8a96c8f 100644 --- a/src/server/html.rs +++ b/src/server/html.rs @@ -7,7 +7,7 @@ use actix_web::{HttpRequest, HttpResponse, Responder, http::StatusCode, web::{Da use askama::Template; use protobuf::Message; -use crate::{backend::{ItemDisplayRow, ItemRow, Signature, UserID}, protos::Item, server::{IndexPageItem, Nav, pagination::Paginator}}; +use crate::{backend::{ItemDisplayRow, ItemRow, Signature, UserID}, markdown::ToHTML, protos::Item, server::{IndexPageItem, Nav, non_standard::identicon_url, pagination::Paginator}}; use super::{AppData, Error, ProfileFollow, pagination::Pagination}; mod filters; @@ -265,6 +265,7 @@ pub(crate) async fn show_item( href: "/".into() } ], + meta: get_post_meta(&req, &user_id, &p), user_id, display_name, signature, @@ -277,11 +278,49 @@ pub(crate) async fn show_item( Ok(page.respond_to(&req).await?) }, Some(ItemType::comment(_)) => Ok( - HttpResponse::Ok().body("TODO: Display comments in HTML") + HttpResponse::Ok().body("To view comments, please use the web client at /client/.") ) } } +fn get_post_meta(req: &HttpRequest, user_id: &UserID, post: &crate::protos::Post) -> OGPMeta { + + let info = req.connection_info(); + let scheme = info.scheme(); + let host = info.host(); // seems to include :port. + + let post_url = format!("{}://{}{}", scheme, host, req.uri().path()); + + // TODO: Const somewhere? + // We only include images that are directly attached. + // Why? Apple Messages, for example, doesn't generate a card if the image isn't local. + let files_prefix = "files/"; + + let mut images: Vec<_> = post.body.md_get_images() + .into_iter() + .filter(|i| i.url.starts_with(files_prefix)) + .map(|i| OGPImage{ + url: format!("{}{}", post_url, i.url), + alt: i.alt + }) + .collect(); + + if images.is_empty() { + // TODO: Eventually: Show the user's profile photo, if they have one. + // Fall back to the user's identicon: + images.push(OGPImage{ + url: format!("{}://{}{}", scheme, host, identicon_url(user_id)), + alt: None, + }) + } + + OGPMeta { + url: post_url, + images, + description: Some(format!("TODO: Put a description here.")), + } +} + #[derive(Template)] #[template(path = "index.html")] @@ -413,4 +452,30 @@ struct PostPage { title: String, timestamp_utc_ms: i64, utc_offset_minutes: i32, + + meta: OGPMeta, +} + +/// Open Graph Protocol Metadata +/// +/// See: https://ogp.me/ +struct OGPMeta { + // The FQ URL of this item. + // make sure to detect hostname/port from request. + url: String, + + // TODO: Try to parse this out of the Markdown? + description: Option, + + // Already included in PostPage: title, timestamp + // Should fall back to some other image + images: Vec, + +} + +struct OGPImage { + url: String, + + /// alt text. (NOT a caption, says ogp.me) + alt: Option } \ No newline at end of file diff --git a/src/server/non_standard.rs b/src/server/non_standard.rs new file mode 100644 index 0000000..78df4a8 --- /dev/null +++ b/src/server/non_standard.rs @@ -0,0 +1,36 @@ +//! Non-standard endpoints. +//! +//! These are not part of the documented FeoBlog standard, but are used by this +//! particular FeoBlog implementation to provide extra features. + +use actix_web::{HttpResponse, web::Path}; +use identicon::IdenticonJSOptions; + +use crate::backend::UserID; + +/// This is not really defined as part of the standard for FeoBlogs. +/// BUT, having a default user image is handy when implementing the Open Graph Protocol. +/// (... which is itself also not a strict requirement for a FeoBlog.) +pub(crate) fn identicon_get(Path(user_id): Path) -> HttpResponse { + use identicon::{Identicon, Mode::IdenticonJS}; + + // Note: Must be >=16 bytes, but userIDs are bigger: + let icon = Identicon::new(user_id.bytes()) + .mode(IdenticonJS(Default::default())) + ; + + let mut png = vec![]; + if let Err(err) = icon.to_png(&mut png) { + return HttpResponse::InternalServerError() + .body(format!("Error: {}", err)); + } + + HttpResponse::Ok() + .content_type("image/png") + .body(png) +} + +/// server-absolute identicon URL. ex: /u/__/icon.png +pub(crate) fn identicon_url(user_id: &UserID) -> String { + format!("/u/{}/icon.png", user_id.to_base58()) +} diff --git a/templates/page.html b/templates/page.html index 41e698e..a9c3cf6 100644 --- a/templates/page.html +++ b/templates/page.html @@ -1,5 +1,5 @@ - + {% block title %}FeoBlog{% endblock %} diff --git a/templates/post.html b/templates/post.html index f55d498..cfec83b 100644 --- a/templates/post.html +++ b/templates/post.html @@ -9,11 +9,33 @@ {%- endif -%} {% endblock %} +{% block head %} + {# + 4 required: title, type, url, image. + see: https://ogp.me/ + #} + {%- if title.len() > 0 -%} + + {%- else -%} + + {%- endif -%} + + + + {% for image in meta.images %} + + {% if image.alt.is_some() %} + + {% endif %} + {% endfor %} + + {# TODO: Article published time. #} + +{% endblock %} + {% block body %}
- {# {%- let timestmap = with_offset(×tamp_utc_ms, &utc_offset_minutes) -%} #} - {% let timestamp = "timestamp" %}
{% if title.len() > 0 %}

{{ title }}

{% endif %} - {# TODO: Show comments from users followed by this user. #} + {# Comments are not shown here. Use the web (2.0) client to render and interact with them. #}
{% endblock %} \ No newline at end of file diff --git a/web-client/components/ProfileImage.svelte b/web-client/components/ProfileImage.svelte index d10944c..d1eaae5 100644 --- a/web-client/components/ProfileImage.svelte +++ b/web-client/components/ProfileImage.svelte @@ -37,6 +37,9 @@ let isPhoto = false let style = "" $: style = `background-image: url("${imgSrc}");` $: imgSrc = getImgSrc(userID) + +// TODO: Use a factory for these to re-use icons data: +// TODO: Actually, just use the server-side icons instead, since they can be cached easily. function getImgSrc(userID: UserID): string { let icon = new Identicon(userID.toHex(), { size: 100, From fc0f8b4c1e5821b47dadb90eaa8c37d8ea7b36dd Mon Sep 17 00:00:00 2001 From: Cody Casterline Date: Thu, 29 Jul 2021 20:07:38 -0700 Subject: [PATCH 02/23] Add og:description metadata. --- src/markdown.rs | 138 +++++++++++++++++++++++++++++++++++++++++++- src/server/html.rs | 2 +- templates/post.html | 23 +++++++- 3 files changed, 157 insertions(+), 6 deletions(-) diff --git a/src/markdown.rs b/src/markdown.rs index d571246..ceb5640 100644 --- a/src/markdown.rs +++ b/src/markdown.rs @@ -1,10 +1,10 @@ -use std::{cell::RefCell, mem, str::from_utf8}; +use std::{cell::RefCell, mem, num::NonZeroUsize, str::from_utf8}; use crate::backend::{Signature, UserID}; use comrak::{Arena, ComrakOptions, arena_tree::Node, format_html, nodes::{Ast, AstNode, NodeLink, NodeValue}, parse_document}; - +// TODO: Getting all these individually requires parsing multiple times. Optimize? (Seems premature.) pub(crate) trait ToHTML { /// Convert this markdown to a safe subset of HTML. fn md_to_html(&self) -> String; @@ -13,6 +13,9 @@ pub(crate) trait ToHTML { /// Find all images embedded in the Markdown. fn md_get_images(&self) -> Vec; + + /// Get a text summary: + fn md_get_summary(&self, max_len: usize) -> String; } impl ToHTML for str { @@ -52,6 +55,71 @@ impl ToHTML for str { return images } + + fn md_get_summary(&self, max_len: usize) -> String { + let mut out = String::new(); + + let md_options = ComrakOptions::default(); + + let arena = Arena::new(); + let root = parse_document(&arena, self, &md_options); + + iter_nodes_mut(root, &mut |node| { + if out.len() >= max_len { return } + + match node.data.borrow().value { + NodeValue::Text(ref text) => { + if in_image(node) { return } + + if !out.is_empty() && !out.ends_with(' ') { out.push(' '); } + let text = to_string_lossy(text.clone()); + out.push_str(text.trim()); + if is_heading(node) && !text.ends_with(":") { out.push(':') } + }, + _ => {}, + } + }); + + if out.len() > max_len { + safe_truncate(&mut out, max_len - 1); + out.push('…'); + } + + out + } +} + + +fn safe_truncate(value: &mut String, mut len: usize) { + if value.len() <= len { return } + + // truncate panics if you try to truncate at a non-char-boundary. >.< + while !value.is_char_boundary(len) { + len -= 1; + } + + value.truncate(len); +} + +fn is_heading(node: &Node>) -> bool { + if let Some(parent) = node.parent() { + if let NodeValue::Heading(_) = parent.data.borrow().value { + return true; + } + } + false +} + +fn in_image(node: &Node>) -> bool { + let parent = match node.parent() { + Some(p) => p, + _ => return false, + }; + + match parent.data.borrow().value { + NodeValue::Image(_) => true, + _ => false, + } } fn image_from_node<'a>(node: &'a Node<'a, RefCell>, img: &NodeLink) -> Image { @@ -153,3 +221,69 @@ pub(crate) struct Options<'a> { pub signature: Option<&'a Signature>, } +#[test] +fn test_get_summary() { + let plaintext = "Hello, world! This is a test."; + assert_eq!(plaintext, plaintext.md_get_summary(1000)); + + let heading = " +Heading +======= + +Hello, world! +"; + assert_eq!("Heading: Hello, world!", heading.md_get_summary(1000)); + + let heading2 = " +Heading: +======= + +Hello, world! +"; + assert_eq!("Heading: Hello, world!", heading2.md_get_summary(1000)); + + let comment = " + +Hello, world! +"; + assert_eq!("Hello, world!", comment.md_get_summary(1000)); + + let inline_html = r#" +
+This is some inline HTML. We strip this out. +
+ +Plain text. +"#; + assert_eq!("Plain text.", inline_html.md_get_summary(1000)); + + let links = r#" + +If there are [links] I expect [just][1] the text. + +[links]: https://www.google.com/ +[1]: https://www.twitter.com/ + +"#; + assert_eq!("If there are links I expect just the text.", links.md_get_summary(1000)); + + let paragraphs = r#" +If there are multiple paragraphs + +I expect them to get joined with a space. +"#; + + assert_eq!("If there are multiple paragraphs I expect…", paragraphs.md_get_summary(42)); + + let nihongo = "日本語はかわいいです。"; + // 日 is 4 bytes in utf-8. The first part of 本 will get dropped to be safe: + assert_eq!("日…", nihongo.md_get_summary(5)); + + let image = r#" +Here's an image: ![image] + +[image]: files/image.png +"#; + + assert_eq!("Here's an image:", image.md_get_summary(1000)); +} \ No newline at end of file diff --git a/src/server/html.rs b/src/server/html.rs index 8a96c8f..b94c9f3 100644 --- a/src/server/html.rs +++ b/src/server/html.rs @@ -317,7 +317,7 @@ fn get_post_meta(req: &HttpRequest, user_id: &UserID, post: &crate::protos::Post OGPMeta { url: post_url, images, - description: Some(format!("TODO: Put a description here.")), + description: Some(post.body.md_get_summary(200)), } } diff --git a/templates/post.html b/templates/post.html index cfec83b..2c38a77 100644 --- a/templates/post.html +++ b/templates/post.html @@ -14,10 +14,15 @@ 4 required: title, type, url, image. see: https://ogp.me/ #} - {%- if title.len() > 0 -%} - - {%- else -%} + {% let has_title = title.trim().len() > 0 %} + {% let has_name = display_name.trim().len() > 0 %} + + {%- if has_title -%} + + {%- else if has_name -%} + {%- else -%} + {%- endif -%} @@ -29,6 +34,18 @@ {% endif %} {% endfor %} + {% if meta.description.is_some() %} + + {% endif %} + + {# TODO: Make this something site admins can set? #} + {% if has_name && has_title %} + {# note: If we don't have a title, the name was already displayed above #} + + {% else %} + + {% endif %} + {# TODO: Article published time. #} {% endblock %} From 38d42033f0e6cbd6ea925f42c69dfe2311d75ab3 Mon Sep 17 00:00:00 2001 From: Cody Casterline Date: Thu, 29 Jul 2021 20:18:36 -0700 Subject: [PATCH 03/23] Add Twitter Card support. --- templates/post.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/templates/post.html b/templates/post.html index 2c38a77..b991847 100644 --- a/templates/post.html +++ b/templates/post.html @@ -46,6 +46,8 @@ {% endif %} + + {# TODO: Article published time. #} {% endblock %} From b2b1200fe3cb1b1d873e86bc4035983007889d73 Mon Sep 17 00:00:00 2001 From: Cody Casterline Date: Sat, 31 Jul 2021 13:36:25 -0700 Subject: [PATCH 04/23] Add share links to posts. TODO: * Add for comments/profiles. * Move profile edit button up here too? --- web-client/client.css | 1 - web-client/components/Button.svelte | 12 +- web-client/components/CopyBox.svelte | 117 ++++++++++++++++ web-client/components/ItemHeader.svelte | 163 ++++++++++++++++++++++- web-client/components/ItemView.svelte | 11 +- web-client/components/OpenArrow.svelte | 56 ++++++++ web-client/components/PageHeading.svelte | 2 + 7 files changed, 347 insertions(+), 15 deletions(-) create mode 100644 web-client/components/CopyBox.svelte create mode 100644 web-client/components/OpenArrow.svelte diff --git a/web-client/client.css b/web-client/client.css index 7f3aa29..1dd3d88 100644 --- a/web-client/client.css +++ b/web-client/client.css @@ -102,7 +102,6 @@ input:hover, input:focus, textarea:hover, textarea:focus { } .item .header { - /* border: 1px solid red; */ display: flex; background-color: #eee; align-items: center; diff --git a/web-client/components/Button.svelte b/web-client/components/Button.svelte index 8cc2819..67516b8 100644 --- a/web-client/components/Button.svelte +++ b/web-client/components/Button.svelte @@ -24,6 +24,8 @@ export let disabled = false export let requiresConfirmation = false // Optionally specify an href to make this act like a link. +// If it starts with #, then it's an internal link and we navigate there. +// Otherwise, we open up a new window (tab) to the new external URL. export let href = "" // This button requires confirmation, and is currently asking for confirmation @@ -57,7 +59,11 @@ function clicked(event: MouseEvent) { } if (href) { - navigateTo(href) + if (href.startsWith("#")) { + navigateTo(href) + } else { + window.open(href) + } return } @@ -79,9 +85,9 @@ function onMouseLeave() { .button { border-radius: 3px; margin: 2px; - padding: 3px 8px; + padding: 0.2em 8px; display: inline-block; - box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.5); + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.25); user-select: none; cursor: pointer; background-color: #fff; diff --git a/web-client/components/CopyBox.svelte b/web-client/components/CopyBox.svelte new file mode 100644 index 0000000..bc1b229 --- /dev/null +++ b/web-client/components/CopyBox.svelte @@ -0,0 +1,117 @@ + + + {label} + + + + + + \ No newline at end of file diff --git a/web-client/components/ItemHeader.svelte b/web-client/components/ItemHeader.svelte index a90fd98..826c772 100644 --- a/web-client/components/ItemHeader.svelte +++ b/web-client/components/ItemHeader.svelte @@ -17,11 +17,43 @@ {/if}
+ +{#if arrowOpen} + + {#if shareURL && feoBlogURL} + + Share To + + + + + + + Full URL + + + Relative URL + + + {/if} + + {#if viewMode} + + {#each renderModes as [mode, name] (name)} + {viewMode=mode}} >{name} + {/each} + + {/if} + + +{/if} + + diff --git a/web-client/components/ItemView.svelte b/web-client/components/ItemView.svelte index 0071c84..2402a63 100644 --- a/web-client/components/ItemView.svelte +++ b/web-client/components/ItemView.svelte @@ -32,6 +32,8 @@ let item: Item|null|undefined = undefined export let appState: Writable + +// TODO: Remove export let showDetail = false // Show information about what this is in reply to. @@ -159,7 +161,7 @@ function onClick(event: Event) { No such item: /u/{userID}/i/{signature}/ {:else if item.post} - +
{#if item.post.title}

{ item.post.title }

@@ -174,13 +176,6 @@ function onClick(event: Event) {
{JSON.stringify(item.toObject(), null, 4)}
{/if} - {#if showDetail} -
- {#if viewMode != "normal"}{/if} - {#if viewMode != "markdown"}{/if} - {#if viewMode != "data"}{/if} -
- {/if}
{:else if item.profile} diff --git a/web-client/components/OpenArrow.svelte b/web-client/components/OpenArrow.svelte new file mode 100644 index 0000000..7869fee --- /dev/null +++ b/web-client/components/OpenArrow.svelte @@ -0,0 +1,56 @@ + + + + + + + + + \ No newline at end of file diff --git a/web-client/components/PageHeading.svelte b/web-client/components/PageHeading.svelte index 87358f7..82f7d32 100644 --- a/web-client/components/PageHeading.svelte +++ b/web-client/components/PageHeading.svelte @@ -113,6 +113,8 @@ class MouseLeftHandler { top: 0px; transition: all 300ms; max-width: 55rem; + /* Required so that transform'd items don't bleed through. Weird. */ + z-index: 1; } .pageHeading :global(h1) { From eaab9e65b38a75524c16feeaed634d2aaddc745e Mon Sep 17 00:00:00 2001 From: Cody Casterline Date: Sat, 31 Jul 2021 13:51:53 -0700 Subject: [PATCH 05/23] Add a color picker for profiles. Wow, that was easy. HTML has this built-in these days. --- web-client/components/ViewSavedLogin.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-client/components/ViewSavedLogin.svelte b/web-client/components/ViewSavedLogin.svelte index 7eb8860..d071c0a 100644 --- a/web-client/components/ViewSavedLogin.svelte +++ b/web-client/components/ViewSavedLogin.svelte @@ -96,7 +96,7 @@ class EventData { Color: - + From 990f33a46c121c22c56e7b81de71332d9dae84e3 Mon Sep 17 00:00:00 2001 From: Cody Casterline Date: Sat, 31 Jul 2021 16:00:20 -0700 Subject: [PATCH 06/23] Use server-side identicons. We're already generating them. And they're more cacheable than the ones we were previously generating in JavaScript. --- Cargo.lock | 2 +- Cargo.toml | 4 +- src/server/non_standard.rs | 1 + web-client/client.css | 1 + web-client/components/ProfileImage.svelte | 75 +- web-client/components/ViewSavedLogin.svelte | 2 +- web-client/package-lock.json | 1577 ++++++++++++++++++- web-client/package.json | 2 - 8 files changed, 1574 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d2c385..eada85a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1227,7 +1227,7 @@ checksum = "3c1ad908cc71012b7bea4d0c53ba96a8cba9962f048fa68d143376143d863b7a" [[package]] name = "identicon" version = "0.2.0" -source = "git+https://github.com/NfNitLoop/identicon?rev=e29ac7793d0469ae15fbf23953b7354cb10fd340#e29ac7793d0469ae15fbf23953b7354cb10fd340" +source = "git+https://github.com/NfNitLoop/identicon?rev=dcee725b1d72088128a396d9d84a684434b77012#dcee725b1d72088128a396d9d84a684434b77012" dependencies = [ "image", ] diff --git a/Cargo.toml b/Cargo.toml index 149b9ec..f55665b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,11 +68,9 @@ tempfile = "*" [dependencies.identicon] git = "https://github.com/NfNitLoop/identicon" -rev = "e29ac7793d0469ae15fbf23953b7354cb10fd340" +rev = "dcee725b1d72088128a396d9d84a684434b77012" default-features = false - - [dependencies.rusqlite] # TODO: Switch to sqlx for async sql support? version = "0.24" diff --git a/src/server/non_standard.rs b/src/server/non_standard.rs index 78df4a8..590470b 100644 --- a/src/server/non_standard.rs +++ b/src/server/non_standard.rs @@ -17,6 +17,7 @@ pub(crate) fn identicon_get(Path(user_id): Path) -> HttpResponse { // Note: Must be >=16 bytes, but userIDs are bigger: let icon = Identicon::new(user_id.bytes()) .mode(IdenticonJS(Default::default())) + .background_rgb(255, 255, 255) ; let mut png = vec![]; diff --git a/web-client/client.css b/web-client/client.css index 1dd3d88..c157e8b 100644 --- a/web-client/client.css +++ b/web-client/client.css @@ -108,6 +108,7 @@ input:hover, input:focus, textarea:hover, textarea:focus { padding: 0.2rem 1rem; border-radius: 20px 20px 0 0; + gap: 0.3rem; } diff --git a/web-client/components/ProfileImage.svelte b/web-client/components/ProfileImage.svelte index d1eaae5..86eb909 100644 --- a/web-client/components/ProfileImage.svelte +++ b/web-client/components/ProfileImage.svelte @@ -1,88 +1,35 @@ -{#if iconSize} -
-{:else} -{/if} \ No newline at end of file diff --git a/web-client/components/ViewSavedLogin.svelte b/web-client/components/ViewSavedLogin.svelte index d071c0a..5a3e8e1 100644 --- a/web-client/components/ViewSavedLogin.svelte +++ b/web-client/components/ViewSavedLogin.svelte @@ -78,7 +78,7 @@ class EventData {
- +
{#if isLoggedIn} diff --git a/web-client/package-lock.json b/web-client/package-lock.json index 4925dc0..b349852 100644 --- a/web-client/package-lock.json +++ b/web-client/package-lock.json @@ -1,8 +1,1556 @@ { "name": "feoblog-web-client", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "feoblog-web-client", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "base58": "*", + "bs58": "*", + "bs58check": "^2.1.2", + "buffer": "^5.7.1", + "commonmark": "^0.29.3", + "google-protobuf": "^3.14.0", + "luxon": "1.25.0", + "svelte-spa-router": "^3.1.0", + "tweetnacl": "^1.0.3" + }, + "devDependencies": { + "@snowpack/plugin-run-script": "*", + "@snowpack/plugin-svelte": "^3.5.2", + "@tsconfig/svelte": "^1.0.10", + "@types/bs58": "^4.0.1", + "@types/commonmark": "^0.27.4", + "@types/luxon": "^1.25.0", + "protoc-gen-ts": "^0.3.5", + "rollup-plugin-copy": "^3.3.0", + "snowpack": "^3", + "svelte": "^3.32.1", + "svelte-check": "^1.1.25", + "svelte-preprocess": "^4.6.1", + "typescript": "^4" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", + "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.4", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", + "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.4", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@snowpack/plugin-run-script": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@snowpack/plugin-run-script/-/plugin-run-script-2.3.0.tgz", + "integrity": "sha512-rIbD67uzTPkzemMFbw5seUm/hZFrToTSqN/SGGq5XaU45ftX2sCRzp0lqSH+lZYhC5QTSm5RxXW1HcuPOlbPXQ==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "npm-run-path": "^4.0.1" + } + }, + "node_modules/@snowpack/plugin-svelte": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/@snowpack/plugin-svelte/-/plugin-svelte-3.5.2.tgz", + "integrity": "sha512-i7OhkIRNt1uJ/y3qpowlU7icjtSgFPIl/GjMmBxhtwoIqHAnowFLh1Ur5G6S36GCFo65ymClnYfEIM/2x1+wPQ==", + "dev": true, + "dependencies": { + "rollup-plugin-svelte": "^7.0.0", + "svelte-hmr": "^0.12.1", + "svelte-preprocess": "^4.6.0" + }, + "peerDependencies": { + "svelte": "^3.21.0" + } + }, + "node_modules/@tsconfig/svelte": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-1.0.10.tgz", + "integrity": "sha512-EBrpH2iXXfaf/9z81koiDYkp2mlwW2XzFcAqn6qh7VKyP8zBvHHAQzNhY+W9vH5arAjmGAm5g8ElWq6YmXm3ig==", + "dev": true + }, + "node_modules/@types/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-yfAgiWgVLjFCmRv8zAcOIHywYATEwiTVccTLnRp6UxTNavT55M9d/uhK3T03St/+8/z/wW+CRjGKUNmEqoHHCA==", + "dev": true, + "dependencies": { + "base-x": "^3.0.6" + } + }, + "node_modules/@types/commonmark": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@types/commonmark/-/commonmark-0.27.4.tgz", + "integrity": "sha512-7koSjp08QxKoS1/+3T15+kD7+vqOUvZRHvM8PutF3Xsk5aAEkdlIGRsHJ3/XsC3izoqTwBdRW/vH7rzCKkIicA==", + "dev": true + }, + "node_modules/@types/fs-extra": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.1.tgz", + "integrity": "sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/luxon": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-1.25.0.tgz", + "integrity": "sha512-iIJp2CP6C32gVqI08HIYnzqj55tlLnodIBMCcMf28q9ckqMfMzocCmIzd9JWI/ALLPMUiTkCu1JGv3FFtu6t3g==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "14.14.20", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz", + "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==", + "dev": true + }, + "node_modules/@types/pug": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.4.tgz", + "integrity": "sha1-h3L80EGOPNLMFxVV1zAHQVBR9LI=", + "dev": true + }, + "node_modules/@types/sass": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@types/sass/-/sass-1.16.0.tgz", + "integrity": "sha512-2XZovu4NwcqmtZtsBR5XYLw18T8cBCnU2USFHTnYLLHz9fkhnoEMoDsqShJIOFsFhn5aJHjweiUUdTrDGujegA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/base-x": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz", + "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/base58": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/base58/-/base58-2.0.1.tgz", + "integrity": "sha512-qK6gt2fMSxN2xGOi+btI5oAnXL+vEq0AsHWHhf5jfm2hE6MwmW+2414qF96utV3Xfg3En8hEA9Q4lif4lbXcgw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.0.tgz", + "integrity": "sha512-JgQM9JS92ZbFR4P90EvmzNpSGhpPBGBSj10PILeDyYFwp4h2/D9OM03wsJ4zW1fEp4ka2DGrnUeD7FuvQ2aZ2Q==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.1" + } + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/colorette": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", + "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", + "dev": true + }, + "node_modules/commonmark": { + "version": "0.29.3", + "resolved": "https://registry.npmjs.org/commonmark/-/commonmark-0.29.3.tgz", + "integrity": "sha512-fvt/NdOFKaL2gyhltSy6BC4LxbbxbnPxBMl923ittqO/JBM0wQHaoYZliE4tp26cRxX/ZZtRsJlZzQrVdUkXAA==", + "dependencies": { + "entities": "~2.0", + "mdurl": "~1.0.1", + "minimist": ">=1.2.2", + "string.prototype.repeat": "^0.2.0" + }, + "bin": { + "commonmark": "bin/commonmark" + }, + "engines": { + "node": "*" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/detect-indent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz", + "integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/entities": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==" + }, + "node_modules/esbuild": { + "version": "0.8.49", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.8.49.tgz", + "integrity": "sha512-itiFVYv5UZz4NooO7/Y0bRGVDGz/M/cxKbl6zyNI5pnKaz1mZjvZXAFhhDVz6rGCmcdTKj5oag6rh8DaaSSmfQ==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + } + }, + "node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true + }, + "node_modules/execa": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", + "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-glob": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fastq": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.1.tgz", + "integrity": "sha512-AWuv6Ery3pM+dY7LYS8YIaCiQvUaos9OB1RyNgaOWnaX+Tik7Onvcsf8x8c+YtDeT0maYLniBip2hox5KtEXXA==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", + "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", + "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", + "dev": true, + "dependencies": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/google-protobuf": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.14.0.tgz", + "integrity": "sha512-bwa8dBuMpOxg7COyqkW6muQuvNnWgVN8TX/epDRGW5m0jcrmq2QJyCyiV8ZE2/6LaIIqJtiv9bYokFhfpy/o6w==" + }, + "node_modules/graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash-base/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-docker": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", + "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/luxon": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.25.0.tgz", + "integrity": "sha512-hEgLurSH8kQRjY6i4YLey+mcKVAWXbDNlZRmM6AgWDJ1cY3atl8Ztf5wEY7VBReFbmGnwQPz7KYJblL8B2k0jQ==", + "engines": { + "node": "*" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/protoc-gen-ts": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/protoc-gen-ts/-/protoc-gen-ts-0.3.5.tgz", + "integrity": "sha512-Q/jVJ5QUUzgwin7b/aoN+MCyOQnM/p3BEnBytkxuhl5orbf0Zs7rjd4NTmpuPcoHrx4mhmTXYOXc23cMhDKdnw==", + "dev": true, + "bin": { + "protoc-gen-ts": "bin/protoc-gen-ts" + }, + "peerDependencies": { + "google-protobuf": "^3.13.0", + "typescript": "^4.0.2" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regexparam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexparam/-/regexparam-1.3.0.tgz", + "integrity": "sha512-6IQpFBv6e5vz1QAqI+V4k8P2e/3gRrqfCJ9FI+O1FLQTO+Uz6RXZEZOPmTJ6hlGj7gkERzY5BRCv09whKP96/g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/require-relative": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", + "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=", + "dev": true + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/rollup": { + "version": "2.39.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.39.0.tgz", + "integrity": "sha512-+WR3bttcq7zE+BntH09UxaW3bQo3vItuYeLsyk4dL2tuwbeSKJuvwiawyhEnvRdRgrII0Uzk00FpctHO/zB1kw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.1" + } + }, + "node_modules/rollup-plugin-copy": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.3.0.tgz", + "integrity": "sha512-euDjCUSBXZa06nqnwCNADbkAcYDfzwowfZQkto9K/TFhiH+QG7I4PUsEMwM9tDgomGWJc//z7KLW8t+tZwxADA==", + "dev": true, + "dependencies": { + "@types/fs-extra": "^8.0.1", + "colorette": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "10.0.1", + "is-plain-object": "^3.0.0" + }, + "engines": { + "node": ">=8.3" + } + }, + "node_modules/rollup-plugin-copy/node_modules/is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup-plugin-svelte": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-svelte/-/rollup-plugin-svelte-7.1.0.tgz", + "integrity": "sha512-vopCUq3G+25sKjwF5VilIbiY6KCuMNHP1PFvx2Vr3REBNMDllKHFZN2B9jwwC+MqNc3UPKkjXnceLPEjTjXGXg==", + "dev": true, + "dependencies": { + "require-relative": "^0.8.7", + "rollup-pluginutils": "^2.8.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "rollup": ">=2.0.0", + "svelte": ">=3.5.0" + } + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "dependencies": { + "estree-walker": "^0.6.1" + } + }, + "node_modules/run-parallel": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", + "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/snowpack": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/snowpack/-/snowpack-3.0.11.tgz", + "integrity": "sha512-lBxgkvWTgdg0szE31JUt01wQkA9Lnmm+6lxqeV9rxDfflpx7ASnldVHFvu7Se70QJmPTQB0UJjfKI+xmYGwiiQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.8.7", + "open": "^7.0.4", + "rollup": "^2.34.0" + }, + "bin": { + "snowpack": "index.bin.js", + "sp": "index.bin.js" + }, + "engines": { + "node": ">=10.19.0" + }, + "optionalDependencies": { + "fsevents": "^2.2.0" + } + }, + "node_modules/source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/string.prototype.repeat": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz", + "integrity": "sha1-q6Nt4I3O5qWjN9SbLqHaGyj8Ds8=" + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/svelte": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.32.1.tgz", + "integrity": "sha512-j1KmD2ZOU0RGq1/STDXjwfh0/eJ/Deh2NXyuz1bpR9eOcz9yImn4CGxXdbSAN7cMTm9a7IyPUIbuBCzu/pXK0g==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/svelte-check": { + "version": "1.1.25", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-1.1.25.tgz", + "integrity": "sha512-GrECBxcH/m86CtxVFC4bfjBRupUA0EG8xZ5Vz9N/d3X6X7SP/DVXYuJVy1rpzsxcBY4sitRaLh8DJ4hKG9izmQ==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "chokidar": "^3.4.1", + "glob": "^7.1.6", + "import-fresh": "^3.2.1", + "minimist": "^1.2.5", + "source-map": "^0.7.3", + "svelte-preprocess": "^4.0.0", + "typescript": "*" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "peerDependencies": { + "svelte": "^3.24.0" + } + }, + "node_modules/svelte-check/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/svelte-check/node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/svelte-check/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/svelte-check/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/svelte-check/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/svelte-check/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/svelte-hmr": { + "version": "0.12.6", + "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.12.6.tgz", + "integrity": "sha512-q08BjoUSbwXpKo9ij6aym93jBrt7K8eutLwix5Y9cuSFLS5djxumOoUVsSzDkdKssLlg5X//Kg/0MWHHBeQRTg==", + "dev": true, + "peerDependencies": { + "svelte": ">=3.19.0" + } + }, + "node_modules/svelte-preprocess": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-4.6.1.tgz", + "integrity": "sha512-s7KdhR2pOsffyOzZIMEb315f6pfgeDnOWN47m6YKFeSEx3NMf/79Znc3vuG/Ai79SL/vsi86WDrjFPLGRfDesg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@types/pug": "^2.0.4", + "@types/sass": "^1.16.0", + "detect-indent": "^6.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">= 9.11.2" + }, + "peerDependencies": { + "@babel/core": "^7.10.2", + "coffeescript": "^2.5.1", + "less": "^3.11.3", + "postcss": "^7 || ^8", + "postcss-load-config": "^2.1.0", + "pug": "^3.0.0", + "sass": "^1.26.8", + "stylus": "^0.54.7", + "sugarss": "^2.0.0", + "svelte": "^3.23.0", + "typescript": "^3.9.5 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "coffeescript": { + "optional": true + }, + "less": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "postcss": { + "optional": true + }, + "postcss-load-config": { + "optional": true + }, + "pug": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/svelte-spa-router": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/svelte-spa-router/-/svelte-spa-router-3.1.0.tgz", + "integrity": "sha512-jlM/xwjn57mylr+pzHYCOOy+IPQauT46gOucNGTBu6jHcFXu3F+oaojN4PXC1LYizRGxFB6QA0qnYbZnRfX7Sg==", + "dependencies": { + "regexparam": "1.3.0" + }, + "funding": { + "url": "https://github.com/sponsors/ItalyPaleAle" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + }, + "node_modules/typescript": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz", + "integrity": "sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + }, "dependencies": { "@nodelib/fs.scandir": { "version": "2.1.4", @@ -91,12 +1639,6 @@ "@types/node": "*" } }, - "@types/identicon.js": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@types/identicon.js/-/identicon.js-2.3.0.tgz", - "integrity": "sha512-5IcozLuaUkQCjeNqS8GcgEfkWSQB1DdADhrbvmZOCAjdRPPXstR7EqKxf4xA5AfBLCZTlHudIEhVaQca3wiOpQ==", - "dev": true - }, "@types/luxon": { "version": "1.25.0", "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-1.25.0.tgz", @@ -483,11 +2025,6 @@ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, - "identicon.js": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/identicon.js/-/identicon.js-2.3.3.tgz", - "integrity": "sha512-/qgOkXKZ7YbeCYbawJ9uQQ3XJ3uBg9VDpvHjabCAPp6aRMhjLaFAxG90+1TxzrhKaj6AYpVGrx6UXQfQA41UEA==" - }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -746,7 +2283,8 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/protoc-gen-ts/-/protoc-gen-ts-0.3.5.tgz", "integrity": "sha512-Q/jVJ5QUUzgwin7b/aoN+MCyOQnM/p3BEnBytkxuhl5orbf0Zs7rjd4NTmpuPcoHrx4mhmTXYOXc23cMhDKdnw==", - "dev": true + "dev": true, + "requires": {} }, "readable-stream": { "version": "3.6.0", @@ -907,11 +2445,6 @@ "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", "dev": true }, - "string.prototype.repeat": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz", - "integrity": "sha1-q6Nt4I3O5qWjN9SbLqHaGyj8Ds8=" - }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -927,6 +2460,11 @@ } } }, + "string.prototype.repeat": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-0.2.0.tgz", + "integrity": "sha1-q6Nt4I3O5qWjN9SbLqHaGyj8Ds8=" + }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -1019,7 +2557,8 @@ "version": "0.12.6", "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.12.6.tgz", "integrity": "sha512-q08BjoUSbwXpKo9ij6aym93jBrt7K8eutLwix5Y9cuSFLS5djxumOoUVsSzDkdKssLlg5X//Kg/0MWHHBeQRTg==", - "dev": true + "dev": true, + "requires": {} }, "svelte-preprocess": { "version": "4.6.1", diff --git a/web-client/package.json b/web-client/package.json index 8befc78..89c2291 100644 --- a/web-client/package.json +++ b/web-client/package.json @@ -10,7 +10,6 @@ "buffer": "^5.7.1", "commonmark": "^0.29.3", "google-protobuf": "^3.14.0", - "identicon.js": "^2.3.3", "luxon": "1.25.0", "svelte-spa-router": "^3.1.0", "tweetnacl": "^1.0.3" @@ -20,7 +19,6 @@ "@snowpack/plugin-svelte": "^3.5.2", "@tsconfig/svelte": "^1.0.10", "@types/commonmark": "^0.27.4", - "@types/identicon.js": "^2.3.0", "@types/bs58": "^4.0.1", "@types/luxon": "^1.25.0", "protoc-gen-ts": "^0.3.5", From 1e0f62eaa98a0389c658b9b954b938517bbe027e Mon Sep 17 00:00:00 2001 From: Cody Casterline Date: Sat, 31 Jul 2021 23:16:27 -0700 Subject: [PATCH 07/23] Fix rendering bug when posting a comment. The CommentEditor.hasText property was not getting properly updated. (Still have open questions about that in the Svelte Discord.) Also: * Updated Svelte. (That was not the problem.) --- web-client/components/CommentEditor.svelte | 9 +++++---- web-client/components/ItemView.svelte | 1 + web-client/package-lock.json | 12 ++++++------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/web-client/components/CommentEditor.svelte b/web-client/components/CommentEditor.svelte index 4c79c2b..80b29ee 100644 --- a/web-client/components/CommentEditor.svelte +++ b/web-client/components/CommentEditor.svelte @@ -56,16 +56,17 @@ let currentView: "Edit"|"Preview" = "Edit" let text = "" $: errors = !hasText ? ["Can not submit an empty comment"] : [] -$: -{ +$: { // Whenever any of these change, clear the text: // This avoids an issue where the box gets re-used on the next item page. - $appState; replyToUserID; replyToSignature + $appState; replyToUserID; replyToSignature; clear() } -export function clear() { +function clear() { text = "" + // WHYYYYY IS THIS NECESSARY? IT'S A DNYAMIC VARIABLE. + hasText = false currentView = "Edit" } diff --git a/web-client/components/ItemView.svelte b/web-client/components/ItemView.svelte index 2402a63..4103067 100644 --- a/web-client/components/ItemView.svelte +++ b/web-client/components/ItemView.svelte @@ -24,6 +24,7 @@ export let signature: string // Caller can provide a pre-fetched Item. // DO NOT BIND. If you want to see the item loaded, use on:itemLoaded let initialItem: Item|null|undefined // = undefined // weird, causes type errors in callers. +// TODO: types don't seem to work well w/ export aliases like this. Just change the names: export {initialItem as item} diff --git a/web-client/package-lock.json b/web-client/package-lock.json index b349852..d6c23b4 100644 --- a/web-client/package-lock.json +++ b/web-client/package-lock.json @@ -1301,9 +1301,9 @@ } }, "node_modules/svelte": { - "version": "3.32.1", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.32.1.tgz", - "integrity": "sha512-j1KmD2ZOU0RGq1/STDXjwfh0/eJ/Deh2NXyuz1bpR9eOcz9yImn4CGxXdbSAN7cMTm9a7IyPUIbuBCzu/pXK0g==", + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.41.0.tgz", + "integrity": "sha512-X9/lnTcRBCrMdyFBVjfmqy1T2vyN8ejUE1OfbWSccc2Z42Amn3ab3XdBgVl+oDkZvzPfPMoxo6CEbWca7pXOew==", "dev": true, "engines": { "node": ">= 8" @@ -2481,9 +2481,9 @@ } }, "svelte": { - "version": "3.32.1", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.32.1.tgz", - "integrity": "sha512-j1KmD2ZOU0RGq1/STDXjwfh0/eJ/Deh2NXyuz1bpR9eOcz9yImn4CGxXdbSAN7cMTm9a7IyPUIbuBCzu/pXK0g==", + "version": "3.41.0", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.41.0.tgz", + "integrity": "sha512-X9/lnTcRBCrMdyFBVjfmqy1T2vyN8ejUE1OfbWSccc2Z42Amn3ab3XdBgVl+oDkZvzPfPMoxo6CEbWca7pXOew==", "dev": true }, "svelte-check": { From db0f5f71c2524c44c6d718894e0f0e65e7c872ac Mon Sep 17 00:00:00 2001 From: Cody Casterline Date: Sat, 31 Jul 2021 23:31:46 -0700 Subject: [PATCH 08/23] Implement viewMode for comments. --- web-client/components/CommentView.svelte | 18 +++++++++++++++--- web-client/components/ItemHeader.svelte | 6 +++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/web-client/components/CommentView.svelte b/web-client/components/CommentView.svelte index 1b632ee..b4aafe5 100644 --- a/web-client/components/CommentView.svelte +++ b/web-client/components/CommentView.svelte @@ -3,9 +3,18 @@ Does NOT include a
around it. -->
- +
- {@html markdownToHtml(item.comment.text, {stripImages: true})} + {#if viewMode == "normal"} + {@html markdownToHtml(item.comment.text, {stripImages: true, relativeBase: `/u/${userID}/i/${signature}`})} + {:else if viewMode == "markdown"} + Markdown source: +
{item.comment.text}
+ {:else} + JSON representation of Protobuf Item: +
{JSON.stringify(item.toObject(), null, 4)}
+ {/if} +
@@ -18,7 +27,8 @@ import type { AppState } from "../ts/app"; import type {UserID} from "../ts/client" import {markdownToHtml, fixLinks} from "../ts/common" -import ItemHeader from "./ItemHeader.svelte"; +import ItemHeader from "./ItemHeader.svelte" +import type {ViewMode} from "./ItemHeader.svelte" export let appState: Writable export let item: Item @@ -27,6 +37,8 @@ export let showReplyTo = true export let userID:UserID export let linkMode: "fix"|"ignore"|"newWindow" = "fix" +let viewMode: ViewMode = "normal" + // If we want to use this as a preview, we must account for an invalid signature: export let signature: string diff --git a/web-client/components/ItemHeader.svelte b/web-client/components/ItemHeader.svelte index 826c772..19618ec 100644 --- a/web-client/components/ItemHeader.svelte +++ b/web-client/components/ItemHeader.svelte @@ -51,6 +51,11 @@ {/if} + + + diff --git a/web-client/components/pages/ItemPage.svelte b/web-client/components/pages/ItemPage.svelte index 40f513f..e92f53d 100644 --- a/web-client/components/pages/ItemPage.svelte +++ b/web-client/components/pages/ItemPage.svelte @@ -127,8 +127,4 @@ async function loadComments(allowComments: boolean, userID: UserID, signature: S } } - - - \ No newline at end of file + \ No newline at end of file From 0a9a043056bf9f960a13ac398ecd62cb1d8809d7 Mon Sep 17 00:00:00 2001 From: Cody Casterline Date: Sun, 1 Aug 2021 21:25:28 -0700 Subject: [PATCH 11/23] Keep browser scrollbars present. ... even when content (temporarily) is shorter. --- web-client/components/IndexPage.svelte | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/web-client/components/IndexPage.svelte b/web-client/components/IndexPage.svelte index 8b24af4..0572d7e 100644 --- a/web-client/components/IndexPage.svelte +++ b/web-client/components/IndexPage.svelte @@ -106,3 +106,17 @@ $: { + + + From 35249dc702ce36f10ab8193aee2e7f9d1df497b1 Mon Sep 17 00:00:00 2001 From: Cody Casterline Date: Sun, 1 Aug 2021 21:58:12 -0700 Subject: [PATCH 12/23] Fix a bug showing profile items. --- src/server/html.rs | 21 +++++++++++++++++---- templates/profile_update.html | 15 +++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 templates/profile_update.html diff --git a/src/server/html.rs b/src/server/html.rs index b94c9f3..abe802b 100644 --- a/src/server/html.rs +++ b/src/server/html.rs @@ -251,7 +251,12 @@ pub(crate) async fn show_item( use crate::protos::Item_oneof_item_type as ItemType; match item.item_type { None => Ok(HttpResponse::InternalServerError().body("No known item type provided.")), - Some(ItemType::profile(_)) => Ok(HttpResponse::Ok().body("Profile update.")), + Some(ItemType::profile(_)) => { + let profile_url = format!("/u/{}/profile/", user_id.to_base58()); + let page = ProfileUpdatePage{ nav: vec![], profile_url}; + + Ok(page.respond_to(&req).await?) + }, Some(ItemType::post(p)) => { let page = PostPage { nav: vec![ @@ -277,9 +282,10 @@ pub(crate) async fn show_item( Ok(page.respond_to(&req).await?) }, - Some(ItemType::comment(_)) => Ok( - HttpResponse::Ok().body("To view comments, please use the web client at /client/.") - ) + Some(ItemType::comment(_)) => { + let page = NotFoundPage{message: "To view comments, please use the web client at /client/.".to_string()}; + Ok(page.respond_to(&req).await?) + } } } @@ -441,6 +447,13 @@ struct ProfilePage { utc_offset_minutes: i32, } +#[derive(Template)] +#[template(path = "profile_update.html")] +struct ProfileUpdatePage { + nav: Vec