diff --git a/Cargo.lock b/Cargo.lock index caf06b11f..a1a6adf9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,6 +77,7 @@ dependencies = [ "proc-macro-nested", "prost", "prost-build", + "pulldown-cmark", "rand 0.7.3", "regex", "reqwest", @@ -780,6 +781,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -1717,6 +1727,18 @@ dependencies = [ "prost", ] +[[package]] +name = "pulldown-cmark" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +dependencies = [ + "bitflags", + "getopts", + "memchr", + "unicase", +] + [[package]] name = "pyo3" version = "0.13.1" @@ -2704,6 +2726,12 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + [[package]] name = "unicode-xid" version = "0.2.1" diff --git a/cargo/crates.bzl b/cargo/crates.bzl index f034293ef..7f60f12a8 100644 --- a/cargo/crates.bzl +++ b/cargo/crates.bzl @@ -801,6 +801,16 @@ def raze_fetch_remote_crates(): build_file = Label("//cargo/remote:BUILD.generic-array-0.14.4.bazel"), ) + maybe( + http_archive, + name = "raze__getopts__0_2_21", + url = "https://crates.io/api/v1/crates/getopts/0.2.21/download", + type = "tar.gz", + sha256 = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5", + strip_prefix = "getopts-0.2.21", + build_file = Label("//cargo/remote:BUILD.getopts-0.2.21.bazel"), + ) + maybe( http_archive, name = "raze__getrandom__0_1_16", @@ -1781,6 +1791,16 @@ def raze_fetch_remote_crates(): build_file = Label("//cargo/remote:BUILD.prost-types-0.7.0.bazel"), ) + maybe( + http_archive, + name = "raze__pulldown_cmark__0_8_0", + url = "https://crates.io/api/v1/crates/pulldown-cmark/0.8.0/download", + type = "tar.gz", + sha256 = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8", + strip_prefix = "pulldown-cmark-0.8.0", + build_file = Label("//cargo/remote:BUILD.pulldown-cmark-0.8.0.bazel"), + ) + maybe( http_archive, name = "raze__pyo3__0_13_1", @@ -2751,6 +2771,16 @@ def raze_fetch_remote_crates(): build_file = Label("//cargo/remote:BUILD.unicode-segmentation-1.7.1.bazel"), ) + maybe( + http_archive, + name = "raze__unicode_width__0_1_8", + url = "https://crates.io/api/v1/crates/unicode-width/0.1.8/download", + type = "tar.gz", + sha256 = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3", + strip_prefix = "unicode-width-0.1.8", + build_file = Label("//cargo/remote:BUILD.unicode-width-0.1.8.bazel"), + ) + maybe( http_archive, name = "raze__unicode_xid__0_2_1", diff --git a/cargo/licenses.json b/cargo/licenses.json index 9e8f32aef..fcb3e2e07 100644 --- a/cargo/licenses.json +++ b/cargo/licenses.json @@ -728,6 +728,15 @@ "license_file": null, "description": "Generic types implementing functionality of arrays" }, + { + "name": "getopts", + "version": "0.2.21", + "authors": "The Rust Project Developers", + "repository": "https://github.com/rust-lang/getopts", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "getopts-like option parsing." + }, { "name": "getrandom", "version": "0.1.16", @@ -1610,6 +1619,15 @@ "license_file": null, "description": "A Protocol Buffers implementation for the Rust Language." }, + { + "name": "pulldown-cmark", + "version": "0.8.0", + "authors": "Raph Levien |Marcus Klaas de Vries ", + "repository": "https://github.com/raphlinus/pulldown-cmark", + "license": "MIT", + "license_file": null, + "description": "A pull parser for CommonMark" + }, { "name": "pyo3", "version": "0.13.1", @@ -2492,6 +2510,15 @@ "license_file": null, "description": "This crate provides Grapheme Cluster, Word and Sentence boundaries according to Unicode Standard Annex #29 rules." }, + { + "name": "unicode-width", + "version": "0.1.8", + "authors": "kwantam |Manish Goregaokar ", + "repository": "https://github.com/unicode-rs/unicode-width", + "license": "Apache-2.0 OR MIT", + "license_file": null, + "description": "Determine displayed width of `char` and `str` types according to Unicode Standard Annex #11 rules." + }, { "name": "unicode-xid", "version": "0.2.1", diff --git a/cargo/remote/BUILD.getopts-0.2.21.bazel b/cargo/remote/BUILD.getopts-0.2.21.bazel new file mode 100644 index 000000000..cbdf45e23 --- /dev/null +++ b/cargo/remote/BUILD.getopts-0.2.21.bazel @@ -0,0 +1,56 @@ +""" +@generated +cargo-raze crate build file. + +DO NOT EDIT! Replaced on runs of cargo-raze +""" + +# buildifier: disable=load +load( + "@io_bazel_rules_rust//rust:rust.bzl", + "rust_binary", + "rust_library", + "rust_test", +) + +# buildifier: disable=load +load("@bazel_skylib//lib:selects.bzl", "selects") + +package(default_visibility = [ + # Public for visibility by "@raze__crate__version//" targets. + # + # Prefer access through "//cargo", which limits external + # visibility to explicit Cargo.toml dependencies. + "//visibility:public", +]) + +licenses([ + "notice", # MIT from expression "MIT OR Apache-2.0" +]) + +# Generated Targets + +rust_library( + name = "getopts", + srcs = glob(["**/*.rs"]), + crate_features = [ + ], + crate_root = "src/lib.rs", + crate_type = "lib", + data = [], + edition = "2015", + rustc_flags = [ + "--cap-lints=allow", + ], + tags = [ + "cargo-raze", + "manual", + ], + version = "0.2.21", + # buildifier: leave-alone + deps = [ + "@raze__unicode_width__0_1_8//:unicode_width", + ], +) + +# Unsupported target "smoke" with type "test" omitted diff --git a/cargo/remote/BUILD.pulldown-cmark-0.8.0.bazel b/cargo/remote/BUILD.pulldown-cmark-0.8.0.bazel new file mode 100644 index 000000000..05231842a --- /dev/null +++ b/cargo/remote/BUILD.pulldown-cmark-0.8.0.bazel @@ -0,0 +1,138 @@ +""" +@generated +cargo-raze crate build file. + +DO NOT EDIT! Replaced on runs of cargo-raze +""" + +# buildifier: disable=load +load( + "@io_bazel_rules_rust//rust:rust.bzl", + "rust_binary", + "rust_library", + "rust_test", +) + +# buildifier: disable=load +load("@bazel_skylib//lib:selects.bzl", "selects") + +package(default_visibility = [ + # Public for visibility by "@raze__crate__version//" targets. + # + # Prefer access through "//cargo", which limits external + # visibility to explicit Cargo.toml dependencies. + "//visibility:public", +]) + +licenses([ + "notice", # MIT from expression "MIT" +]) + +# Generated Targets +# buildifier: disable=load-on-top +load( + "@io_bazel_rules_rust//cargo:cargo_build_script.bzl", + "cargo_build_script", +) + +cargo_build_script( + name = "pulldown_cmark_build_script", + srcs = glob(["**/*.rs"]), + build_script_env = { + }, + crate_features = [ + "default", + "getopts", + ], + crate_root = "build.rs", + data = glob(["**"]), + edition = "2018", + rustc_flags = [ + "--cap-lints=allow", + ], + tags = [ + "cargo-raze", + "manual", + ], + version = "0.8.0", + visibility = ["//visibility:private"], + deps = [ + ], +) + +# Unsupported target "html_rendering" with type "bench" omitted + +# Unsupported target "lib" with type "bench" omitted + +rust_binary( + # Prefix bin name to disambiguate from (probable) collision with lib name + # N.B.: The exact form of this is subject to change. + name = "cargo_bin_pulldown_cmark", + srcs = glob(["**/*.rs"]), + crate_features = [ + "default", + "getopts", + ], + crate_root = "src/main.rs", + data = [], + edition = "2018", + rustc_flags = [ + "--cap-lints=allow", + ], + tags = [ + "cargo-raze", + "manual", + ], + version = "0.8.0", + # buildifier: leave-alone + deps = [ + # Binaries get an implicit dependency on their crate's lib + ":pulldown_cmark", + ":pulldown_cmark_build_script", + "@raze__bitflags__1_2_1//:bitflags", + "@raze__getopts__0_2_21//:getopts", + "@raze__memchr__2_3_4//:memchr", + "@raze__unicase__2_6_0//:unicase", + ], +) + +# Unsupported target "broken-link-callbacks" with type "example" omitted + +# Unsupported target "event-filter" with type "example" omitted + +# Unsupported target "string-to-string" with type "example" omitted + +rust_library( + name = "pulldown_cmark", + srcs = glob(["**/*.rs"]), + crate_features = [ + "default", + "getopts", + ], + crate_root = "src/lib.rs", + crate_type = "lib", + data = [], + edition = "2018", + rustc_flags = [ + "--cap-lints=allow", + ], + tags = [ + "cargo-raze", + "manual", + ], + version = "0.8.0", + # buildifier: leave-alone + deps = [ + ":pulldown_cmark_build_script", + "@raze__bitflags__1_2_1//:bitflags", + "@raze__getopts__0_2_21//:getopts", + "@raze__memchr__2_3_4//:memchr", + "@raze__unicase__2_6_0//:unicase", + ], +) + +# Unsupported target "errors" with type "test" omitted + +# Unsupported target "html" with type "test" omitted + +# Unsupported target "lib" with type "test" omitted diff --git a/cargo/remote/BUILD.unicode-width-0.1.8.bazel b/cargo/remote/BUILD.unicode-width-0.1.8.bazel new file mode 100644 index 000000000..1c5020538 --- /dev/null +++ b/cargo/remote/BUILD.unicode-width-0.1.8.bazel @@ -0,0 +1,54 @@ +""" +@generated +cargo-raze crate build file. + +DO NOT EDIT! Replaced on runs of cargo-raze +""" + +# buildifier: disable=load +load( + "@io_bazel_rules_rust//rust:rust.bzl", + "rust_binary", + "rust_library", + "rust_test", +) + +# buildifier: disable=load +load("@bazel_skylib//lib:selects.bzl", "selects") + +package(default_visibility = [ + # Public for visibility by "@raze__crate__version//" targets. + # + # Prefer access through "//cargo", which limits external + # visibility to explicit Cargo.toml dependencies. + "//visibility:public", +]) + +licenses([ + "notice", # MIT from expression "MIT OR Apache-2.0" +]) + +# Generated Targets + +rust_library( + name = "unicode_width", + srcs = glob(["**/*.rs"]), + crate_features = [ + "default", + ], + crate_root = "src/lib.rs", + crate_type = "lib", + data = [], + edition = "2015", + rustc_flags = [ + "--cap-lints=allow", + ], + tags = [ + "cargo-raze", + "manual", + ], + version = "0.1.8", + # buildifier: leave-alone + deps = [ + ], +) diff --git a/rslib/BUILD.bazel b/rslib/BUILD.bazel index a219f621c..52a886621 100644 --- a/rslib/BUILD.bazel +++ b/rslib/BUILD.bazel @@ -97,6 +97,7 @@ rust_library( "//rslib/cargo:once_cell", "//rslib/cargo:pin_project", "//rslib/cargo:prost", + "//rslib/cargo:pulldown_cmark", "//rslib/cargo:rand", "//rslib/cargo:regex", "//rslib/cargo:reqwest", diff --git a/rslib/Cargo.toml b/rslib/Cargo.toml index eb024ce9b..657cb372c 100644 --- a/rslib/Cargo.toml +++ b/rslib/Cargo.toml @@ -80,3 +80,4 @@ async-trait = "0.1.42" # only in Bazel) proc-macro-nested = "=0.1.6" ammonia = "3.1.0" +pulldown-cmark = "0.8.0" diff --git a/rslib/cargo/BUILD.bazel b/rslib/cargo/BUILD.bazel index f5cf3fb2d..3e0b3dae5 100644 --- a/rslib/cargo/BUILD.bazel +++ b/rslib/cargo/BUILD.bazel @@ -264,6 +264,15 @@ alias( ], ) +alias( + name = "pulldown_cmark", + actual = "@raze__pulldown_cmark__0_8_0//:pulldown_cmark", + tags = [ + "cargo-raze", + "manual", + ], +) + alias( name = "rand", actual = "@raze__rand__0_7_3//:rand", diff --git a/rslib/src/decks/mod.rs b/rslib/src/decks/mod.rs index e85061654..fee9160c7 100644 --- a/rslib/src/decks/mod.rs +++ b/rslib/src/decks/mod.rs @@ -1,11 +1,11 @@ // Copyright: Ankitects Pty Ltd and contributors // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -use crate::backend_proto as pb; pub use crate::backend_proto::{ deck_kind::Kind as DeckKind, filtered_search_term::FilteredSearchOrder, Deck as DeckProto, DeckCommon, DeckKind as DeckKindProto, FilteredDeck, FilteredSearchTerm, NormalDeck, }; +use crate::{backend_proto as pb, markdown::render_markdown, text::sanitize_html_no_images}; use crate::{ collection::Collection, deckconf::DeckConfID, @@ -92,6 +92,17 @@ impl Deck { (0, 0) } } + + pub fn rendered_description(&self) -> String { + if let DeckKind::Normal(normal) = &self.kind { + let description = render_markdown(&normal.description); + // before allowing images, we'll need to handle relative image + // links on the various platforms + sanitize_html_no_images(&description) + } else { + String::new() + } + } } fn invalid_char_for_deck_component(c: char) -> bool { diff --git a/rslib/src/lib.rs b/rslib/src/lib.rs index 209d00233..ca91003ab 100644 --- a/rslib/src/lib.rs +++ b/rslib/src/lib.rs @@ -19,6 +19,7 @@ mod fluent_proto; pub mod i18n; pub mod latex; pub mod log; +mod markdown; pub mod media; pub mod notes; pub mod notetype; diff --git a/rslib/src/markdown.rs b/rslib/src/markdown.rs new file mode 100644 index 000000000..34de6dc01 --- /dev/null +++ b/rslib/src/markdown.rs @@ -0,0 +1,11 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +use pulldown_cmark::{html, Parser}; + +pub(crate) fn render_markdown(markdown: &str) -> String { + let mut buf = String::with_capacity(markdown.len()); + let parser = Parser::new(markdown); + html::push_html(&mut buf, parser); + buf +} diff --git a/rslib/src/sched/congrats.rs b/rslib/src/sched/congrats.rs index b77d6ec57..543337316 100644 --- a/rslib/src/sched/congrats.rs +++ b/rslib/src/sched/congrats.rs @@ -2,7 +2,6 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html use crate::backend_proto as pb; -use crate::decks::DeckKind; use crate::prelude::*; #[derive(Debug)] @@ -22,11 +21,7 @@ impl Collection { let today = self.timing_today()?.days_elapsed; let info = self.storage.congrats_info(&deck, today)?; let is_filtered_deck = deck.is_filtered(); - let deck_description = if let DeckKind::Normal(normal) = &deck.kind { - ammonia::clean(&normal.description) - } else { - String::new() - }; + let deck_description = deck.rendered_description(); let secs_until_next_learn = ((info.next_learn_due as i64) - self.learn_ahead_secs() as i64 - TimestampSecs::now().0) diff --git a/rslib/src/text.rs b/rslib/src/text.rs index 40be9342b..be985cbd0 100644 --- a/rslib/src/text.rs +++ b/rslib/src/text.rs @@ -226,6 +226,18 @@ pub fn strip_html_preserving_media_filenames(html: &str) -> Cow { without_html.into_owned().into() } +#[allow(dead_code)] +pub(crate) fn sanitize_html(html: &str) -> String { + ammonia::clean(html) +} + +pub(crate) fn sanitize_html_no_images(html: &str) -> String { + ammonia::Builder::default() + .rm_tags(&["img"]) + .clean(html) + .to_string() +} + pub(crate) fn normalize_to_nfc(s: &str) -> Cow { if !is_nfc(s) { s.chars().nfc().collect::().into() diff --git a/ts/congrats/CongratsPage.svelte b/ts/congrats/CongratsPage.svelte index 73d9b52a0..b061de04a 100644 --- a/ts/congrats/CongratsPage.svelte +++ b/ts/congrats/CongratsPage.svelte @@ -32,6 +32,11 @@ .congrats-inner { max-width: 30em; } + + .description { + border: 1px solid var(--border); + padding: 1em; + }
@@ -62,6 +67,10 @@ {/if} {/if} - {@html info.deckDescription} + {#if info.deckDescription} +
+ {@html info.deckDescription} +
+ {/if}