render deck description with markdown; strip images

To support images on that screen, we'll first need to adjust the base url
for each platform, or rewrite the local image URLs, as otherwise they
are resolved to _anki/pages/...
This commit is contained in:
Damien Elmes 2021-02-06 15:02:40 +10:00
parent 6ba321f818
commit ded626f0b9
15 changed files with 391 additions and 8 deletions

28
Cargo.lock generated
View file

@ -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"

View file

@ -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",

View file

@ -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 <raph.levien@gmail.com>|Marcus Klaas de Vries <mail@marcusklaas.nl>",
"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 <kwantam@gmail.com>|Manish Goregaokar <manishsmail@gmail.com>",
"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",

56
cargo/remote/BUILD.getopts-0.2.21.bazel vendored Normal file
View file

@ -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

View file

@ -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

View file

@ -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 = [
],
)

View file

@ -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",

View file

@ -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"

View file

@ -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",

View file

@ -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 {

View file

@ -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;

11
rslib/src/markdown.rs Normal file
View file

@ -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
}

View file

@ -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)

View file

@ -226,6 +226,18 @@ pub fn strip_html_preserving_media_filenames(html: &str) -> Cow<str> {
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<str> {
if !is_nfc(s) {
s.chars().nfc().collect::<String>().into()

View file

@ -32,6 +32,11 @@
.congrats-inner {
max-width: 30em;
}
.description {
border: 1px solid var(--border);
padding: 1em;
}
</style>
<div class="congrats-outer">
@ -62,6 +67,10 @@
{/if}
{/if}
{@html info.deckDescription}
{#if info.deckDescription}
<div class="description">
{@html info.deckDescription}
</div>
{/if}
</div>
</div>