diff --git a/Cargo.lock b/Cargo.lock index 46dd9c99..1d4a011a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,15 +173,6 @@ dependencies = [ "scoped-tls", ] -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bitflags" version = "1.3.2" @@ -329,9 +320,9 @@ dependencies = [ [[package]] name = "comrak" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c93ab3577cca16b4a1d80a88c2e0cd8b6e969e51696f0bbb0d1dcb0157109832" +checksum = "d8c32ff8b21372fab0e9ecc4e42536055702dc5faa418362bffd1544f9d12637" dependencies = [ "caseless", "derive_builder", @@ -385,15 +376,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - [[package]] name = "criterion" version = "0.4.0" @@ -598,20 +580,8 @@ dependencies = [ "regex", "serde", "serde_json", - "syntect", "termcolor", "tokio", - "tree-sitter-bash", - "tree-sitter-css", - "tree-sitter-highlight", - "tree-sitter-html", - "tree-sitter-javascript", - "tree-sitter-json", - "tree-sitter-md", - "tree-sitter-regex", - "tree-sitter-rust", - "tree-sitter-typescript", - "tree-sitter-xml", ] [[package]] @@ -705,18 +675,18 @@ dependencies = [ [[package]] name = "derive_builder" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd33f37ee6a119146a1781d3356a7c26028f83d779b2e04ecd45fdc75c76877b" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7431fa049613920234f22c47fdc33e6cf3ee83067091ea4277a3f8c4587aae38" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ "darling", "proc-macro2", @@ -726,9 +696,9 @@ dependencies = [ [[package]] name = "derive_builder_macro" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", "syn 2.0.79", @@ -818,16 +788,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "flate2" -version = "1.0.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - [[package]] name = "fnv" version = "1.0.7" @@ -1351,28 +1311,6 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" -[[package]] -name = "onig" -version = "6.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" -dependencies = [ - "bitflags 1.3.2", - "libc", - "once_cell", - "onig_sys", -] - -[[package]] -name = "onig_sys" -version = "69.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" -dependencies = [ - "cc", - "pkg-config", -] - [[package]] name = "oorandom" version = "11.1.4" @@ -1554,12 +1492,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" - [[package]] name = "plotters" version = "0.3.7" @@ -2309,26 +2241,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "syntect" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" -dependencies = [ - "bincode", - "bitflags 1.3.2", - "flate2", - "fnv", - "once_cell", - "onig", - "regex-syntax", - "serde", - "serde_derive", - "serde_json", - "thiserror", - "walkdir", -] - [[package]] name = "tap" version = "1.0.1" @@ -2484,128 +2396,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "tree-sitter" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7cc499ceadd4dcdf7ec6d4cbc34ece92c3fa07821e287aedecd4416c516dca" -dependencies = [ - "cc", - "regex", -] - -[[package]] -name = "tree-sitter-bash" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5244703ad2e08a616d859a0557d7aa290adcd5e0990188a692e628ffe9dce40" -dependencies = [ - "cc", - "tree-sitter", -] - -[[package]] -name = "tree-sitter-css" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e08e324b1cf60fd3291774b49724c66de2ce8fcf4d358d0b4b82e37b41b1c9b" -dependencies = [ - "cc", - "tree-sitter", -] - -[[package]] -name = "tree-sitter-highlight" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaca0fe34fa96eec6aaa8e63308dbe1bafe65a6317487c287f93938959b21907" -dependencies = [ - "lazy_static", - "regex", - "thiserror", - "tree-sitter", -] - -[[package]] -name = "tree-sitter-html" -version = "0.20.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8766b5ad3721517f8259e6394aefda9c686aebf7a8c74ab8624f2c3b46902fd5" -dependencies = [ - "cc", - "tree-sitter", -] - -[[package]] -name = "tree-sitter-javascript" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8710a71bc6779e33811a8067bdda3ed08bed1733296ff915e44faf60f8c533d7" -dependencies = [ - "cc", - "tree-sitter", -] - -[[package]] -name = "tree-sitter-json" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b737dcb73c35d74b7d64a5f3dde158113c86a012bf3cee2bfdf2150d23b05db" -dependencies = [ - "cc", - "tree-sitter", -] - -[[package]] -name = "tree-sitter-md" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c3cfd068f2527250bbd8ff407431164e12b17863e7eafb76e311dd3f96965a" -dependencies = [ - "cc", - "tree-sitter", -] - -[[package]] -name = "tree-sitter-regex" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ff1286fe9651b2797484839ffa37aa76c8618d4ccb6836d7e31765dfd60c0d5" -dependencies = [ - "cc", - "tree-sitter", -] - -[[package]] -name = "tree-sitter-rust" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "277690f420bf90741dea984f3da038ace46c4fe6047cba57a66822226cde1c93" -dependencies = [ - "cc", - "tree-sitter", -] - -[[package]] -name = "tree-sitter-typescript" -version = "0.21.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecb35d98a688378e56c18c9c159824fd16f730ccbea19aacf4f206e5d5438ed9" -dependencies = [ - "cc", - "tree-sitter", -] - -[[package]] -name = "tree-sitter-xml" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c3a1b08e9842143f84fde1a18ac40ee77ca80a80b14077e4ca67a3b4808b8b" -dependencies = [ - "cc", - "tree-sitter", -] - [[package]] name = "triomphe" version = "0.1.14" diff --git a/Cargo.toml b/Cargo.toml index a926c13f..7929e76b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ name = "deno_doc" [[example]] name = "ddoc" -required-features = ["html"] +required-features = ["html", "comrak"] [dependencies] anyhow = "1.0.86" @@ -37,33 +37,13 @@ regex = "1.10.6" serde.workspace = true serde_json = { version = "1.0.122", features = ["preserve_order"] } termcolor = "1.4.1" +itoa = "1.0.11" html-escape = { version = "0.2.13", optional = true } -comrak = { version = "0.28.0", optional = true, default-features = false } -handlebars = { version = "6.1", optional = true, features = ["string_helpers"] } -syntect = { version = "5.2.0", optional = true, default-features = false, features = [ - "parsing", - "default-syntaxes", - "default-themes", - "html", - "dump-load", - "regex-onig", -] } +handlebars = { version = "6.1", features = ["string_helpers"], optional = true } +comrak = { version = "0.29.0", optional = true, default-features = false } ammonia = { version = "4.0.0", optional = true } -tree-sitter-highlight = { version = "0.22.6", optional = true } -tree-sitter-javascript = { version = "0.21.4", optional = true } -tree-sitter-typescript = { version = "0.21.2", optional = true } -tree-sitter-json = { version = "0.21.0", optional = true } -tree-sitter-regex = { version = "0.21.0", optional = true } -tree-sitter-css = { version = "0.21.0", optional = true } -tree-sitter-md = { version = "0.2.3", optional = true } -tree-sitter-rust = { version = "0.21.2", optional = true } -tree-sitter-html = { version = "0.20.3", optional = true } -tree-sitter-bash = { version = "0.21.0", optional = true } -tree-sitter-xml = { version = "0.6.4", optional = true } -itoa = "1.0.11" - [dev-dependencies] anyhow = { version = "1.0.86" } clap = "2.34.0" @@ -75,22 +55,10 @@ pretty_assertions = "1.4.0" insta = { version = "1.39.0", features = ["json"] } [features] -default = ["html", "rust", "tree-sitter"] +default = ["rust", "html", "comrak"] rust = [] -html = ["html-escape", "comrak", "handlebars", "ammonia"] -tree-sitter = [ - "tree-sitter-highlight", - "tree-sitter-javascript", - "tree-sitter-typescript", - "tree-sitter-json", - "tree-sitter-regex", - "tree-sitter-css", - "tree-sitter-md", - "tree-sitter-rust", - "tree-sitter-html", - "tree-sitter-bash", - "tree-sitter-xml", -] +html = ["html-escape", "handlebars"] +comrak = ["dep:comrak", "ammonia"] [[test]] name = "specs" diff --git a/build_css.ts b/build_css.ts index 5a1e061a..497de32d 100644 --- a/build_css.ts +++ b/build_css.ts @@ -33,10 +33,22 @@ const reset = await $`deno run -A npm:tailwindcss@3.4.3 --input src/html/templates/pages/reset.css` .bytes(); const resetFinal = transform({ - filename: "./page.css", + filename: "./reset.css", code: reset, minify: true, targets: browserslistToTargets(browsers), analyzeDependencies: false, }); await Deno.writeFile("src/html/templates/pages/reset.gen.css", resetFinal.code); + +const comrak = + await $`deno run -A npm:tailwindcss@3.4.3 --input src/html/templates/comrak.css` + .bytes(); +const comrakFinal = transform({ + filename: "./comrak.css", + code: comrak, + minify: true, + targets: browserslistToTargets(browsers), + analyzeDependencies: false, +}); +await Deno.writeFile("src/html/templates/comrak.gen.css", comrakFinal.code); diff --git a/deno.json b/deno.json index 2bd4c0b6..21307b59 100644 --- a/deno.json +++ b/deno.json @@ -3,7 +3,7 @@ "build": "cp LICENSE js/LICENSE && deno run -A jsr:@deno/wasmbuild@0.17.1 --project deno_doc_wasm --out js", "test": "deno test -A", "tailwind": "deno run -A build_css.ts", - "gen_html": "deno task tailwind && cargo run --example ddoc --features=html -- --name=Test --html ./tests/testdata/multiple/* --output generated_docs/", + "gen_html": "deno task tailwind && cargo run --example ddoc -- --name=Test --html ./tests/testdata/multiple/* --output generated_docs/", "test:update": "UPDATE=1 cargo test --locked --all-targets && cargo insta test --accept" }, "workspace": ["js"], diff --git a/examples/ddoc/main.rs b/examples/ddoc/main.rs index e9f0f755..1b8c8c10 100644 --- a/examples/ddoc/main.rs +++ b/examples/ddoc/main.rs @@ -3,8 +3,10 @@ use clap::App; use clap::Arg; use deno_doc::find_nodes_by_name_recursively; -use deno_doc::html::HrefResolver; use deno_doc::html::UrlResolveKind; +use deno_doc::html::{ + DocNodeWithContext, HrefResolver, UsageComposer, UsageComposerEntry, +}; use deno_doc::DocNodeKind; use deno_doc::DocParser; use deno_doc::DocParserOptions; @@ -171,7 +173,7 @@ fn main() { block_on(future); } -struct EmptyResolver(); +struct EmptyResolver; impl HrefResolver for EmptyResolver { fn resolve_path( @@ -194,12 +196,6 @@ impl HrefResolver for EmptyResolver { None } - fn resolve_usage(&self, current_resolve: UrlResolveKind) -> Option { - current_resolve - .get_file() - .map(|current_file| current_file.specifier.to_string()) - } - fn resolve_source(&self, location: &deno_doc::Location) -> Option { Some(location.filename.to_string()) } @@ -213,6 +209,32 @@ impl HrefResolver for EmptyResolver { } } +impl UsageComposer for EmptyResolver { + fn is_single_mode(&self) -> bool { + true + } + + fn compose( + &self, + nodes: &[DocNodeWithContext], + current_resolve: UrlResolveKind, + usage_to_md: deno_doc::html::UsageToMd, + ) -> IndexMap { + current_resolve + .get_file() + .map(|current_file| { + IndexMap::from([( + UsageComposerEntry { + name: "".to_string(), + icon: None, + }, + usage_to_md(nodes, current_file.specifier.as_str()), + )]) + }) + .unwrap_or_default() + } +} + fn generate_docs_directory( package_name: Option, output_dir: String, @@ -230,13 +252,23 @@ fn generate_docs_directory( let options = deno_doc::html::GenerateOptions { package_name, main_entrypoint, - href_resolver: Rc::new(EmptyResolver()), - usage_composer: None, + href_resolver: Rc::new(EmptyResolver), + usage_composer: Rc::new(EmptyResolver), rewrite_map: Some(index_map), category_docs: None, disable_search: false, symbol_redirect_map: None, default_symbol_map: None, + markdown_renderer: deno_doc::html::comrak::create_renderer( + None, None, None, + ), + markdown_stripper: Rc::new(deno_doc::html::comrak::strip), + head_inject: Some(Rc::new(|root| { + format!( + r#""#, + deno_doc::html::comrak::COMRAK_STYLESHEET_FILENAME + ) + })), }; let html = deno_doc::html::generate(options, doc_nodes_by_url)?; diff --git a/src/html/comrak.rs b/src/html/comrak.rs new file mode 100644 index 00000000..43dd33d2 --- /dev/null +++ b/src/html/comrak.rs @@ -0,0 +1,430 @@ +use crate::html::ShortPath; +use comrak::nodes::Ast; +use comrak::nodes::AstNode; +use comrak::nodes::NodeHtmlBlock; +use comrak::nodes::NodeValue; +use comrak::Arena; +use std::borrow::Cow; +use std::cell::RefCell; +use std::io::BufWriter; +use std::io::Write; +use std::rc::Rc; +use std::sync::Arc; + +pub const COMRAK_STYLESHEET: &str = include_str!("./templates/comrak.gen.css"); +pub const COMRAK_STYLESHEET_FILENAME: &str = "comrak.css"; + +pub type URLRewriter = + Arc, &str) -> String) + Send + Sync>; + +thread_local! { + static CURRENT_FILE: RefCell>> = const { RefCell::new(None) }; + static URL_REWRITER: RefCell>> = const { RefCell::new(None) }; +} + +fn create_ammonia<'a>() -> ammonia::Builder<'a> { + let mut ammonia_builder = ammonia::Builder::default(); + + ammonia_builder + .add_tags(["video", "button", "svg", "path", "rect"]) + .add_generic_attributes(["id", "align"]) + .add_tag_attributes("button", ["data-copy"]) + .add_tag_attributes( + "svg", + [ + "width", + "height", + "viewBox", + "fill", + "xmlns", + "stroke", + "stroke-width", + "stroke-linecap", + "stroke-linejoin", + ], + ) + .add_tag_attributes( + "path", + [ + "d", + "fill", + "fill-rule", + "clip-rule", + "stroke", + "stroke-width", + "stroke-linecap", + "stroke-linejoin", + ], + ) + .add_tag_attributes("rect", ["x", "y", "width", "height", "fill"]) + .add_tag_attributes("video", ["src", "controls"]) + .add_allowed_classes("pre", ["highlight"]) + .add_allowed_classes("button", ["context_button"]) + .add_allowed_classes( + "div", + [ + "alert", + "alert-note", + "alert-tip", + "alert-important", + "alert-warning", + "alert-caution", + ], + ) + .link_rel(Some("nofollow")) + .url_relative(ammonia::UrlRelative::Custom(Box::new( + AmmoniaRelativeUrlEvaluator(), + ))); + + ammonia_builder +} + +struct AmmoniaRelativeUrlEvaluator(); + +impl<'b> ammonia::UrlRelativeEvaluate<'b> for AmmoniaRelativeUrlEvaluator { + fn evaluate<'a>(&self, url: &'a str) -> Option> { + URL_REWRITER.with(|url_rewriter| { + if let Some(url_rewriter) = url_rewriter.borrow().as_ref().unwrap() { + CURRENT_FILE.with(|current_file| { + Some( + url_rewriter(current_file.borrow().as_ref().unwrap().as_ref(), url) + .into(), + ) + }) + } else { + Some(Cow::Borrowed(url)) + } + }) + } +} + +enum Alert { + Note, + Tip, + Important, + Warning, + Caution, +} + +fn match_node_value<'a>( + arena: &'a Arena>, + node: &'a AstNode<'a>, + options: &comrak::Options, + plugins: &comrak::Plugins, +) { + match &node.data.borrow().value { + NodeValue::BlockQuote => { + if let Some(paragraph_child) = node.first_child() { + if paragraph_child.data.borrow().value == NodeValue::Paragraph { + let alert = paragraph_child.first_child().and_then(|text_child| { + if let NodeValue::Text(text) = &text_child.data.borrow().value { + match text + .split_once(' ') + .map_or((text.as_str(), None), |(kind, title)| { + (kind, Some(title)) + }) { + ("[!NOTE]", title) => { + Some((Alert::Note, title.unwrap_or("Note").to_string())) + } + ("[!TIP]", title) => { + Some((Alert::Tip, title.unwrap_or("Tip").to_string())) + } + ("[!IMPORTANT]", title) => Some(( + Alert::Important, + title.unwrap_or("Important").to_string(), + )), + ("[!WARNING]", title) => { + Some((Alert::Warning, title.unwrap_or("Warning").to_string())) + } + ("[!CAUTION]", title) => { + Some((Alert::Caution, title.unwrap_or("Caution").to_string())) + } + _ => None, + } + } else { + None + } + }); + + if let Some((alert, title)) = alert { + let start_col = node.data.borrow().sourcepos.start; + + let document = arena.alloc(AstNode::new(RefCell::new(Ast::new( + NodeValue::Document, + start_col, + )))); + + let node_without_alert = arena.alloc(AstNode::new(RefCell::new( + Ast::new(NodeValue::Paragraph, start_col), + ))); + + for child_node in paragraph_child.children().skip(1) { + node_without_alert.append(child_node); + } + for child_node in node.children().skip(1) { + node_without_alert.append(child_node); + } + + document.append(node_without_alert); + + let html = render_node(document, options, plugins); + + let alert_title = match alert { + Alert::Note => format!( + "{}{title}", + include_str!("./templates/icons/info-circle.svg") + ), + Alert::Tip => { + format!("{}{title}", include_str!("./templates/icons/bulb.svg")) + } + Alert::Important => format!( + "{}{title}", + include_str!("./templates/icons/warning-message.svg") + ), + Alert::Warning => format!( + "{}{title}", + include_str!("./templates/icons/warning-triangle.svg") + ), + Alert::Caution => format!( + "{}{title}", + include_str!("./templates/icons/warning-octagon.svg") + ), + }; + + let html = format!( + r#"
{alert_title}
{html}
"#, + match alert { + Alert::Note => "note", + Alert::Tip => "tip", + Alert::Important => "important", + Alert::Warning => "warning", + Alert::Caution => "caution", + } + ); + + let alert_node = arena.alloc(AstNode::new(RefCell::new(Ast::new( + NodeValue::HtmlBlock(NodeHtmlBlock { + block_type: 6, + literal: html, + }), + start_col, + )))); + node.insert_before(alert_node); + node.detach(); + } + } + } + } + NodeValue::Link(link) => { + if link.url.ends_with(".mov") || link.url.ends_with(".mp4") { + let start_col = node.data.borrow().sourcepos.start; + + let html = format!(r#""#, link.url); + + let alert_node = arena.alloc(AstNode::new(RefCell::new(Ast::new( + NodeValue::HtmlBlock(NodeHtmlBlock { + block_type: 6, + literal: html, + }), + start_col, + )))); + node.insert_before(alert_node); + node.detach(); + } + } + _ => {} + } +} + +fn walk_node<'a>( + arena: &'a Arena>, + node: &'a AstNode<'a>, + options: &comrak::Options, + plugins: &comrak::Plugins, +) { + for child in node.children() { + match_node_value(arena, child, options, plugins); + walk_node(arena, child, options, plugins); + } +} + +fn walk_node_title<'a>(node: &'a AstNode<'a>) { + for child in node.children() { + if matches!( + child.data.borrow().value, + NodeValue::Document + | NodeValue::Paragraph + | NodeValue::Heading(_) + | NodeValue::Text(_) + | NodeValue::Code(_) + | NodeValue::HtmlInline(_) + | NodeValue::Emph + | NodeValue::Strong + | NodeValue::Strikethrough + | NodeValue::Superscript + | NodeValue::Link(_) + | NodeValue::Math(_) + | NodeValue::Escaped + | NodeValue::WikiLink(_) + | NodeValue::Underline + | NodeValue::SoftBreak + ) { + walk_node_title(child); + } else { + // delete the node + child.detach(); + } + } +} + +fn render_node<'a>( + node: &'a AstNode<'a>, + options: &comrak::Options, + plugins: &comrak::Plugins, +) -> String { + let mut bw = BufWriter::new(Vec::new()); + comrak::format_html_with_plugins(node, options, &mut bw, plugins).unwrap(); + String::from_utf8(bw.into_inner().unwrap()).unwrap() +} + +pub fn strip(md: &str) -> String { + let mut options = comrak::Options::default(); + options.extension.autolink = true; + options.extension.description_lists = true; + options.extension.strikethrough = true; + options.extension.superscript = true; + options.extension.table = true; + options.extension.tagfilter = true; + options.extension.tasklist = true; + options.render.escape = true; + + let arena = Arena::new(); + let root = comrak::parse_document(&arena, md, &options); + + walk_node(&arena, root, &options, &Default::default()); + + fn collect_text<'a>(node: &'a AstNode<'a>, output: &mut BufWriter>) { + match node.data.borrow().value { + NodeValue::Text(ref literal) + | NodeValue::Code(comrak::nodes::NodeCode { ref literal, .. }) => { + output.write_all(literal.as_bytes()).unwrap(); + } + NodeValue::LineBreak | NodeValue::SoftBreak => { + output.write_all(&[b' ']).unwrap() + } + _ => { + for n in node.children() { + collect_text(n, output); + } + } + } + } + + let mut bw = BufWriter::new(Vec::new()); + collect_text(root, &mut bw); + String::from_utf8(bw.into_inner().unwrap()).unwrap() +} + +pub type AmmoniaHook = Box; + +pub fn create_renderer( + syntax_highlighter: Option< + Rc, + >, + ammonia_hook: Option, + url_rewriter: Option, +) -> super::jsdoc::MarkdownRenderer { + let mut options = comrak::Options::default(); + options.extension.autolink = true; + options.extension.description_lists = true; + options.extension.strikethrough = true; + options.extension.superscript = true; + options.extension.table = true; + options.extension.tagfilter = true; + options.extension.tasklist = true; + options.render.unsafe_ = true; // its fine because we run ammonia afterwards + + let mut ammonia = create_ammonia(); + + if let Some(ammonia_hook) = ammonia_hook { + ammonia_hook(&mut ammonia); + } + + let renderer = move |md: &str, + title_only: bool, + file_path: Option, + anchorizer: super::jsdoc::Anchorizer| + -> Option { + let mut plugins = comrak::Plugins::default(); + let heading_adapter = ComrakHeadingAdapter(anchorizer); + + if !title_only { + plugins.render.codefence_syntax_highlighter = + syntax_highlighter.as_deref(); + plugins.render.heading_adapter = Some(&heading_adapter); + } + + let html = { + let arena = Arena::new(); + let root = comrak::parse_document(&arena, md, &options); + + if title_only { + walk_node_title(root); + + if let Some(child) = root.first_child() { + render_node(child, &options, &plugins) + } else { + return None; + } + } else { + walk_node(&arena, root, &options, &plugins); + render_node(root, &options, &plugins) + } + }; + + CURRENT_FILE.set(Some(file_path)); + URL_REWRITER.set(Some(url_rewriter.clone())); + + let class_name = if title_only { + "markdown_summary" + } else { + "markdown" + }; + + let html = format!( + r#"
{}
"#, + ammonia.clean(&html) + ); + + CURRENT_FILE.set(None); + URL_REWRITER.set(None); + + Some(html) + }; + + Rc::new(renderer) +} + +pub struct ComrakHeadingAdapter(super::jsdoc::Anchorizer); + +impl comrak::adapters::HeadingAdapter for ComrakHeadingAdapter { + fn enter( + &self, + output: &mut dyn Write, + heading: &comrak::adapters::HeadingMeta, + _sourcepos: Option, + ) -> std::io::Result<()> { + let anchor = self.0(heading.content.clone(), heading.level); + + writeln!(output, r#""#, heading.level) + } + + fn exit( + &self, + output: &mut dyn Write, + heading: &comrak::adapters::HeadingMeta, + ) -> std::io::Result<()> { + writeln!(output, "", heading.level)?; + Ok(()) + } +} diff --git a/src/html/comrak_adapters.rs b/src/html/comrak_adapters.rs deleted file mode 100644 index 81b37dfc..00000000 --- a/src/html/comrak_adapters.rs +++ /dev/null @@ -1,372 +0,0 @@ -// Copied and modified from https://github.com/kivikakk/comrak/blob/main/src/plugins/syntect.rs - -//! Adapter for the Syntect syntax highlighter plugin. - -#![allow(clippy::print_stderr)] - -#[cfg(any( - not(any(feature = "syntect", feature = "tree-sitter")), - all(feature = "syntect", feature = "tree-sitter") -))] -compile_error!( - "Either feature \"syntect\" or \"tree-sitter\" must be enabled, not both or neither." -); - -use comrak::adapters::HeadingAdapter; -use comrak::adapters::HeadingMeta; -use comrak::adapters::SyntaxHighlighterAdapter; -use comrak::nodes::Sourcepos; -use std::cmp::Ordering; -use std::collections::HashMap; -use std::io::Write; -use std::sync::Arc; -use std::sync::Mutex; - -#[derive(Debug)] -/// Syntect syntax highlighter plugin. -pub struct HighlightAdapter { - #[cfg(feature = "syntect")] - pub syntax_set: syntect::parsing::SyntaxSet, - #[cfg(feature = "syntect")] - pub theme_set: syntect::highlighting::ThemeSet, - #[cfg(feature = "tree-sitter")] - pub language_cb: - fn(&str) -> Option<&'static tree_sitter_highlight::HighlightConfiguration>, - pub show_line_numbers: bool, -} - -impl HighlightAdapter { - fn highlight_html<'a, I, H>( - &self, - iter: I, - mut highlighter: H, - ) -> Result - where - I: Iterator, - H: FnMut(&mut String, &str) -> Result<(), anyhow::Error>, - { - let mut line_numbers = String::new(); - let mut lines = String::new(); - - for (i, line) in iter.enumerate() { - let n = i + 1; - - if self.show_line_numbers { - line_numbers.push_str(&format!( - r##"{n}"##, - )); - - lines.push_str(&format!( - r#""# - )); - } - - highlighter(&mut lines, line)?; - - if self.show_line_numbers { - lines.push_str(""); - } - } - - if self.show_line_numbers { - Ok(format!( - r##"
{line_numbers}
{lines}
"## - )) - } else { - Ok(lines) - } - } - - fn write_button( - &self, - output: &mut dyn Write, - source: &str, - ) -> std::io::Result<()> { - write!(output, "")?; - write!( - output, - r#""#, - html_escape::encode_double_quoted_attribute(source), - include_str!("./templates/icons/copy.svg") - )?; - write!(output, "") - } -} - -impl SyntaxHighlighterAdapter for HighlightAdapter { - #[cfg(all(feature = "syntect", not(feature = "tree-sitter")))] - fn write_highlighted( - &self, - output: &mut dyn Write, - lang: Option<&str>, - code: &str, - ) -> std::io::Result<()> { - let lang = match lang { - Some(l) if !l.is_empty() => l, - _ => "Plain Text", - }; - - let syntax = - self - .syntax_set - .find_syntax_by_token(lang) - .unwrap_or_else(|| { - self - .syntax_set - .find_syntax_by_first_line(code) - .unwrap_or_else(|| self.syntax_set.find_syntax_plain_text()) - }); - - let theme = &self.theme_set.themes["InspiredGitHub"]; - let mut highlighter = syntect::easy::HighlightLines::new(syntax, theme); - - match self.highlight_html( - syntect::util::LinesWithEndings::from(code), - |lines, line| { - let regions = highlighter.highlight_line(line, &self.syntax_set)?; - - syntect::html::append_highlighted_html_for_styled_line( - ®ions, - syntect::html::IncludeBackground::No, - lines, - )?; - - Ok(()) - }, - ) { - Ok(highlighted_code) => output.write_all(highlighted_code.as_bytes())?, - Err(_) => output.write_all(code.as_bytes())?, - } - - self.write_button(output, code) - } - - #[cfg(all(feature = "tree-sitter", not(feature = "syntect")))] - fn write_highlighted( - &self, - output: &mut dyn Write, - lang: Option<&str>, - code: &str, - ) -> std::io::Result<()> { - let lang = lang.unwrap_or_default(); - let config = (self.language_cb)(lang); - let source = code.as_bytes(); - if let Some(config) = config { - let mut highlighter = tree_sitter_highlight::Highlighter::new(); - let res = highlighter.highlight(config, source, None, self.language_cb); - match res { - Ok(highlighter) => { - let mut renderer = tree_sitter_highlight::HtmlRenderer::new(); - match renderer.render(highlighter, source, &|highlight| { - crate::html::tree_sitter::classes(highlight) - }) { - Ok(()) => { - let html = self - .highlight_html(renderer.lines(), |lines, line| { - lines.push_str(line); - Ok(()) - }) - .unwrap(); - - output.write_all(html.as_bytes())?; - return self.write_button(output, code); - } - Err(err) => { - eprintln!("Error rendering code: {}", err); - } - }; - } - Err(err) => { - eprintln!("Error highlighting code: {}", err); - } - } - } - comrak::html::escape(output, source)?; - self.write_button(output, code) - } - - fn write_pre_tag( - &self, - output: &mut dyn Write, - mut attributes: HashMap, - ) -> std::io::Result<()> { - attributes - .entry("class".into()) - .or_default() - .push_str(" highlight"); - comrak::html::write_opening_tag(output, "pre", attributes) - } - - fn write_code_tag( - &self, - output: &mut dyn Write, - mut attributes: HashMap, - ) -> std::io::Result<()> { - if self.show_line_numbers { - attributes - .entry("class".into()) - .or_default() - .push_str(" !flex gap-2"); - } - comrak::html::write_opening_tag(output, "code", attributes) - } -} - -#[derive(Debug)] -pub struct ToCEntry { - pub level: u8, - pub content: String, - pub anchor: String, -} - -#[derive(Default)] -pub struct Anchorizer { - map: HashMap, - itoa_buffer: itoa::Buffer, -} - -impl Anchorizer { - /// Returns a String that has been converted into an anchor using the GFM algorithm. - /// This replaces comrak's implementation to improve the performance. - /// @see https://docs.rs/comrak/latest/comrak/struct.Anchorizer.html#method.anchorize - pub fn anchorize(&mut self, s: &str) -> String { - let mut s = REJECTED_CHARS - .replace_all(&s.to_lowercase(), "") - .replace(' ', "-"); - - if let Some(count) = self.map.get_mut(&s) { - let a = self.itoa_buffer.format(*count); - s.push('-'); - s.push_str(a); - - *count += 1; - } else { - self.map.insert(s.clone(), 1); - } - - s - } -} - -#[derive(Clone)] -pub struct HeadingToCAdapter { - toc: Arc>>, - offset: Arc>, - anchorizer: Arc>, -} - -impl Default for HeadingToCAdapter { - fn default() -> Self { - Self { - toc: Arc::new(Mutex::new(vec![])), - anchorizer: Arc::new(Mutex::new(Default::default())), - offset: Arc::new(Mutex::new(0)), - } - } -} - -lazy_static! { - static ref REJECTED_CHARS: regex::Regex = - regex::Regex::new(r"[^\p{L}\p{M}\p{N}\p{Pc} -]").unwrap(); -} - -impl HeadingToCAdapter { - pub fn anchorize(&self, content: &str) -> String { - let mut anchorizer = self.anchorizer.lock().unwrap(); - anchorizer.anchorize(content) - } - - pub fn add_entry(&self, level: u8, content: &str, anchor: &str) { - let mut toc = self.toc.lock().unwrap(); - let mut offset = self.offset.lock().unwrap(); - - *offset = level; - - if toc.last().map_or(true, |toc| toc.content != content) { - toc.push(ToCEntry { - level, - content: content.to_owned(), - anchor: anchor.to_owned(), - }); - } - } - - pub fn render(self) -> Option { - let toc = Arc::into_inner(self.toc).unwrap().into_inner().unwrap(); - - if toc.is_empty() { - return None; - } - - let mut toc_content = vec!["
    ".to_string()]; - let mut current_level = toc.iter().map(|entry| entry.level).min().unwrap(); - - let mut level_diff = 0; - for entry in toc { - match current_level.cmp(&entry.level) { - Ordering::Equal => {} - Ordering::Less => { - level_diff += 1; - toc_content.push(r#"
    • "#.to_string()); - current_level = entry.level; - } - Ordering::Greater => { - level_diff -= 1; - toc_content.push("
  • ".to_string()); - current_level = entry.level; - } - } - - toc_content.push(format!( - r##"
  • {}
  • "##, - entry.anchor, - html_escape::encode_double_quoted_attribute(&entry.content), - entry.content - )); - } - - for _ in 0..level_diff { - toc_content.push("
".to_string()); - } - - toc_content.push(String::from("")); - - Some(toc_content.join("")) - } -} - -impl HeadingAdapter for HeadingToCAdapter { - fn enter( - &self, - output: &mut dyn Write, - heading: &HeadingMeta, - _sourcepos: Option, - ) -> std::io::Result<()> { - let mut anchorizer = self.anchorizer.lock().unwrap(); - let offset = self.offset.lock().unwrap(); - - let anchor = anchorizer.anchorize(&heading.content); - writeln!(output, r#""#, heading.level)?; - - let mut toc = self.toc.lock().unwrap(); - toc.push(ToCEntry { - level: heading.level + *offset, - content: heading.content.clone(), - anchor, - }); - - Ok(()) - } - - fn exit( - &self, - output: &mut dyn Write, - heading: &HeadingMeta, - ) -> std::io::Result<()> { - writeln!(output, "", heading.level)?; - Ok(()) - } -} - -pub type URLRewriter = - Arc, &str) -> String) + Send + Sync>; diff --git a/src/html/default_newlines.packdump b/src/html/default_newlines.packdump deleted file mode 100644 index fd024eff..00000000 Binary files a/src/html/default_newlines.packdump and /dev/null differ diff --git a/src/html/jsdoc.rs b/src/html/jsdoc.rs index bf2e8dcd..725d1007 100644 --- a/src/html/jsdoc.rs +++ b/src/html/jsdoc.rs @@ -1,20 +1,11 @@ use super::render_context::RenderContext; use super::util::*; -use crate::html::comrak_adapters::URLRewriter; use crate::html::ShortPath; use crate::js_doc::JsDoc; use crate::js_doc::JsDocTag; use crate::DocNodeKind; -use comrak::nodes::Ast; -use comrak::nodes::AstNode; -use comrak::nodes::NodeHtmlBlock; -use comrak::nodes::NodeValue; -use comrak::Arena; use serde::Serialize; use std::borrow::Cow; -use std::cell::RefCell; -use std::io::BufWriter; -use std::io::Write; lazy_static! { static ref JSDOC_LINK_RE: regex::Regex = regex::Regex::new( @@ -27,76 +18,6 @@ lazy_static! { regex::Regex::new(r"^\[(\S+)\](?:\.(\S+)|\s|)$").unwrap(); } -lazy_static! { - static ref AMMONIA: ammonia::Builder<'static> = { - let mut ammonia_builder = ammonia::Builder::default(); - - ammonia_builder - .add_tags(["video", "button", "svg", "path", "rect"]) - .add_generic_attributes(["id", "align"]) - .add_tag_attributes("button", ["data-copy"]) - .add_tag_attributes( - "svg", - [ - "width", - "height", - "viewBox", - "fill", - "xmlns", - "stroke", - "stroke-width", - "stroke-linecap", - "stroke-linejoin", - ], - ) - .add_tag_attributes( - "path", - [ - "d", - "fill", - "fill-rule", - "clip-rule", - "stroke", - "stroke-width", - "stroke-linecap", - "stroke-linejoin", - ], - ) - .add_tag_attributes("rect", ["x", "y", "width", "height", "fill"]) - .add_tag_attributes("video", ["src", "controls"]) - .add_allowed_classes("pre", ["highlight"]) - .add_allowed_classes("button", ["context_button"]) - .add_allowed_classes( - "div", - [ - "alert", - "alert-note", - "alert-tip", - "alert-important", - "alert-warning", - "alert-caution", - ], - ) - .link_rel(Some("nofollow")) - .url_relative(ammonia::UrlRelative::Custom(Box::new( - AmmoniaRelativeUrlEvaluator(), - ))); - - #[cfg(feature = "syntect")] - ammonia_builder.add_tag_attributes("span", ["style"]); - - #[cfg(feature = "tree-sitter")] - ammonia_builder.add_allowed_classes("span", super::tree_sitter::CLASSES); - - ammonia_builder - }; -} - -thread_local! { - static CURRENT_FILE: RefCell>> = const { RefCell::new(None) }; - static URL_REWRITER: RefCell>> = const { RefCell::new(None) }; -} - fn parse_links<'a>(md: &'a str, ctx: &RenderContext) -> Cow<'a, str> { JSDOC_LINK_RE.replace_all(md, |captures: ®ex::Captures| { let code = captures @@ -219,323 +140,63 @@ fn split_markdown_title(md: &str) -> (Option<&str>, Option<&str>) { } } -struct AmmoniaRelativeUrlEvaluator(); - -impl<'b> ammonia::UrlRelativeEvaluate<'b> for AmmoniaRelativeUrlEvaluator { - fn evaluate<'a>(&self, url: &'a str) -> Option> { - URL_REWRITER.with(|url_rewriter| { - if let Some(url_rewriter) = url_rewriter.borrow().as_ref().unwrap() { - CURRENT_FILE.with(|current_file| { - Some( - url_rewriter(current_file.borrow().as_ref().unwrap().as_ref(), url) - .into(), - ) - }) - } else { - Some(Cow::Borrowed(url)) - } - }) - } -} - -enum Alert { - Note, - Tip, - Important, - Warning, - Caution, -} - -fn match_node_value<'a>( - arena: &'a Arena>, - node: &'a AstNode<'a>, - options: &comrak::Options, - plugins: &comrak::Plugins, -) { - match &node.data.borrow().value { - NodeValue::BlockQuote => { - if let Some(paragraph_child) = node.first_child() { - if paragraph_child.data.borrow().value == NodeValue::Paragraph { - let alert = paragraph_child.first_child().and_then(|text_child| { - if let NodeValue::Text(text) = &text_child.data.borrow().value { - match text - .split_once(' ') - .map_or((text.as_str(), None), |(kind, title)| { - (kind, Some(title)) - }) { - ("[!NOTE]", title) => { - Some((Alert::Note, title.unwrap_or("Note").to_string())) - } - ("[!TIP]", title) => { - Some((Alert::Tip, title.unwrap_or("Tip").to_string())) - } - ("[!IMPORTANT]", title) => Some(( - Alert::Important, - title.unwrap_or("Important").to_string(), - )), - ("[!WARNING]", title) => { - Some((Alert::Warning, title.unwrap_or("Warning").to_string())) - } - ("[!CAUTION]", title) => { - Some((Alert::Caution, title.unwrap_or("Caution").to_string())) - } - _ => None, - } - } else { - None - } - }); - - if let Some((alert, title)) = alert { - let start_col = node.data.borrow().sourcepos.start; - - let document = arena.alloc(AstNode::new(RefCell::new(Ast::new( - NodeValue::Document, - start_col, - )))); - - let node_without_alert = arena.alloc(AstNode::new(RefCell::new( - Ast::new(NodeValue::Paragraph, start_col), - ))); - - for child_node in paragraph_child.children().skip(1) { - node_without_alert.append(child_node); - } - for child_node in node.children().skip(1) { - node_without_alert.append(child_node); - } - - document.append(node_without_alert); - - let html = render_node(document, options, plugins); - - let alert_title = match alert { - Alert::Note => format!( - "{}{title}", - include_str!("./templates/icons/info-circle.svg") - ), - Alert::Tip => { - format!("{}{title}", include_str!("./templates/icons/bulb.svg")) - } - Alert::Important => format!( - "{}{title}", - include_str!("./templates/icons/warning-message.svg") - ), - Alert::Warning => format!( - "{}{title}", - include_str!("./templates/icons/warning-triangle.svg") - ), - Alert::Caution => format!( - "{}{title}", - include_str!("./templates/icons/warning-octagon.svg") - ), - }; - - let html = format!( - r#"
{alert_title}
{html}
"#, - match alert { - Alert::Note => "note", - Alert::Tip => "tip", - Alert::Important => "important", - Alert::Warning => "warning", - Alert::Caution => "caution", - } - ); - - let alert_node = arena.alloc(AstNode::new(RefCell::new(Ast::new( - NodeValue::HtmlBlock(NodeHtmlBlock { - block_type: 6, - literal: html, - }), - start_col, - )))); - node.insert_before(alert_node); - node.detach(); - } - } - } - } - NodeValue::Link(link) => { - if link.url.ends_with(".mov") || link.url.ends_with(".mp4") { - let start_col = node.data.borrow().sourcepos.start; - - let html = format!(r#""#, link.url); - - let alert_node = arena.alloc(AstNode::new(RefCell::new(Ast::new( - NodeValue::HtmlBlock(NodeHtmlBlock { - block_type: 6, - literal: html, - }), - start_col, - )))); - node.insert_before(alert_node); - node.detach(); - } - } - _ => {} - } -} - -fn walk_node<'a>( - arena: &'a Arena>, - node: &'a AstNode<'a>, - options: &comrak::Options, - plugins: &comrak::Plugins, -) { - for child in node.children() { - match_node_value(arena, child, options, plugins); - walk_node(arena, child, options, plugins); - } -} - -fn walk_node_title<'a>(node: &'a AstNode<'a>) { - for child in node.children() { - if matches!( - child.data.borrow().value, - NodeValue::Document - | NodeValue::Paragraph - | NodeValue::Heading(_) - | NodeValue::Text(_) - | NodeValue::Code(_) - | NodeValue::HtmlInline(_) - | NodeValue::Emph - | NodeValue::Strong - | NodeValue::Strikethrough - | NodeValue::Superscript - | NodeValue::Link(_) - | NodeValue::Math(_) - | NodeValue::Escaped - | NodeValue::WikiLink(_) - | NodeValue::Underline - | NodeValue::SoftBreak - ) { - walk_node_title(child); - } else { - // delete the node - child.detach(); - } - } -} - -fn render_node<'a>( - node: &'a AstNode<'a>, - options: &comrak::Options, - plugins: &comrak::Plugins, -) -> String { - let mut bw = BufWriter::new(Vec::new()); - comrak::format_html_with_plugins(node, options, &mut bw, plugins).unwrap(); - String::from_utf8(bw.into_inner().unwrap()).unwrap() -} - pub struct MarkdownToHTMLOptions { pub title_only: bool, pub no_toc: bool, } -pub fn strip(render_ctx: &RenderContext, md: &str) -> String { - let mut options = comrak::Options::default(); - options.extension.autolink = true; - options.extension.description_lists = true; - options.extension.strikethrough = true; - options.extension.superscript = true; - options.extension.table = true; - options.extension.tagfilter = true; - options.extension.tasklist = true; - options.render.escape = true; +pub type MarkdownStripper = std::rc::Rc String)>; +pub fn strip(render_ctx: &RenderContext, md: &str) -> String { let md = parse_links(md, render_ctx); - let arena = Arena::new(); - let root = comrak::parse_document(&arena, &md, &options); + (render_ctx.ctx.markdown_stripper)(&md) +} - walk_node(&arena, root, &options, &Default::default()); +pub type Anchorizer = + std::sync::Arc String + Send + Sync>; - fn collect_text<'a>(node: &'a AstNode<'a>, output: &mut BufWriter>) { - match node.data.borrow().value { - NodeValue::Text(ref literal) - | NodeValue::Code(comrak::nodes::NodeCode { ref literal, .. }) => { - output.write_all(literal.as_bytes()).unwrap(); - } - NodeValue::LineBreak | NodeValue::SoftBreak => { - output.write_all(&[b' ']).unwrap() - } - _ => { - for n in node.children() { - collect_text(n, output); - } - } - } - } - - let mut bw = BufWriter::new(Vec::new()); - collect_text(root, &mut bw); - String::from_utf8(bw.into_inner().unwrap()).unwrap() -} +pub type MarkdownRenderer = std::rc::Rc< + dyn (Fn(&str, bool, Option, Anchorizer) -> Option), +>; pub fn markdown_to_html( render_ctx: &RenderContext, md: &str, render_options: MarkdownToHTMLOptions, ) -> Option { - // TODO(bartlomieju): this should be initialized only once - let mut options = comrak::Options::default(); - options.extension.autolink = true; - options.extension.description_lists = true; - options.extension.strikethrough = true; - options.extension.superscript = true; - options.extension.table = true; - options.extension.tagfilter = true; - options.extension.tasklist = true; - options.render.unsafe_ = true; // its fine because we run ammonia afterwards - - let mut plugins = comrak::Plugins::default(); - - if !render_options.title_only { - plugins.render.codefence_syntax_highlighter = - Some(&render_ctx.ctx.highlight_adapter); - if !render_options.no_toc { - plugins.render.heading_adapter = Some(&render_ctx.toc); - } - } + let toc = render_ctx.toc.clone(); - let md = parse_links(md, render_ctx); + let anchorizer = move |content: String, level: u8| { + let mut anchorizer = toc.anchorizer.lock().unwrap(); + let offset = toc.offset.lock().unwrap(); - let class_name = if render_options.title_only { - "markdown_summary" - } else { - "markdown" - }; - - let html = { - let arena = Arena::new(); - let root = comrak::parse_document(&arena, &md, &options); - - if render_options.title_only { - walk_node_title(root); + let anchor = anchorizer.anchorize(&content); - if let Some(child) = root.first_child() { - render_node(child, &options, &plugins) - } else { - return None; - } - } else { - walk_node(&arena, root, &options, &plugins); - render_node(root, &options, &plugins) + if !render_options.no_toc { + let mut toc = toc.toc.lock().unwrap(); + toc.push(crate::html::render_context::ToCEntry { + level: level + *offset, + content, + anchor: anchor.clone(), + }); } + + anchor }; - CURRENT_FILE.set(Some(render_ctx.get_current_resolve().get_file().cloned())); - URL_REWRITER.set(Some(render_ctx.ctx.url_rewriter.clone())); + let anchorizer = std::sync::Arc::new(anchorizer); - let html = Some(format!( - r#"
{}
"#, - AMMONIA.clean(&html) - )); + let md = parse_links(md, render_ctx); - CURRENT_FILE.set(None); - URL_REWRITER.set(None); + let file = render_ctx.get_current_resolve().get_file().cloned(); - html + (render_ctx.ctx.markdown_renderer)( + &md, + render_options.title_only, + file, + anchorizer, + ) } pub(crate) fn render_markdown( @@ -718,21 +379,26 @@ impl ModuleDocCtx { mod test { use crate::html::href_path_resolve; use crate::html::jsdoc::parse_links; + use crate::html::DocNodeWithContext; use crate::html::GenerateCtx; use crate::html::GenerateOptions; use crate::html::HrefResolver; + use crate::html::UsageComposer; + use crate::html::UsageComposerEntry; use crate::DocNode; use crate::Location; use deno_ast::ModuleSpecifier; use indexmap::IndexMap; + use std::rc::Rc; + use crate::html::usage::UsageToMd; use crate::html::RenderContext; use crate::html::UrlResolveKind; use crate::interface::InterfaceDef; use crate::js_doc::JsDoc; use crate::node::DeclarationKind; - struct EmptyResolver {} + struct EmptyResolver; impl HrefResolver for EmptyResolver { fn resolve_path( @@ -755,12 +421,6 @@ mod test { None } - fn resolve_usage(&self, current_resolve: UrlResolveKind) -> Option { - current_resolve - .get_file() - .map(|current_file| current_file.display_name().to_string()) - } - fn resolve_source(&self, _location: &Location) -> Option { None } @@ -774,19 +434,50 @@ mod test { } } + impl UsageComposer for EmptyResolver { + fn is_single_mode(&self) -> bool { + true + } + + fn compose( + &self, + nodes: &[DocNodeWithContext], + current_resolve: UrlResolveKind, + usage_to_md: UsageToMd, + ) -> IndexMap { + current_resolve + .get_file() + .map(|current_file| { + IndexMap::from([( + UsageComposerEntry { + name: "".to_string(), + icon: None, + }, + usage_to_md(nodes, current_file.display_name()), + )]) + }) + .unwrap_or_default() + } + } + #[test] fn parse_links_test() { let ctx = GenerateCtx::new( GenerateOptions { package_name: None, main_entrypoint: None, - href_resolver: std::rc::Rc::new(EmptyResolver {}), - usage_composer: None, + href_resolver: Rc::new(EmptyResolver), + usage_composer: Rc::new(EmptyResolver), rewrite_map: None, category_docs: None, disable_search: false, symbol_redirect_map: None, default_symbol_map: None, + markdown_renderer: crate::html::comrak::create_renderer( + None, None, None, + ), + markdown_stripper: Rc::new(crate::html::comrak::strip), + head_inject: None, }, Default::default(), Default::default(), @@ -936,13 +627,18 @@ mod test { GenerateOptions { package_name: None, main_entrypoint: None, - href_resolver: std::rc::Rc::new(EmptyResolver {}), - usage_composer: None, + href_resolver: Rc::new(EmptyResolver), + usage_composer: Rc::new(EmptyResolver), rewrite_map: None, category_docs: None, disable_search: false, symbol_redirect_map: None, default_symbol_map: None, + markdown_renderer: crate::html::comrak::create_renderer( + None, None, None, + ), + markdown_stripper: Rc::new(crate::html::comrak::strip), + head_inject: None, }, Default::default(), Default::default(), diff --git a/src/html/mod.rs b/src/html/mod.rs index ca95699e..c7594d6d 100644 --- a/src/html/mod.rs +++ b/src/html/mod.rs @@ -11,7 +11,6 @@ use std::collections::HashMap; use std::path::PathBuf; use std::rc::Rc; -pub mod comrak_adapters; pub mod jsdoc; pub mod pages; mod parameters; @@ -19,12 +18,13 @@ pub mod partition; mod render_context; mod search; mod symbols; -#[cfg(feature = "tree-sitter")] -pub mod tree_sitter; mod types; mod usage; pub mod util; +#[cfg(feature = "comrak")] +pub mod comrak; + use crate::html::pages::SymbolPage; use crate::js_doc::JsDocTag; pub use pages::generate_symbol_pages_for_module; @@ -33,7 +33,9 @@ pub use search::generate_search_index; pub use symbols::namespace; pub use symbols::SymbolContentCtx; pub use symbols::SymbolGroupCtx; -pub use usage::usage_to_md; +pub use usage::UsageComposer; +pub use usage::UsageComposerEntry; +pub use usage::UsageToMd; pub use util::compute_namespaced_symbols; pub use util::href_path_resolve; pub use util::qualify_drilldown_name; @@ -46,6 +48,8 @@ pub use util::TopSymbolCtx; pub use util::TopSymbolsCtx; pub use util::UrlResolveKind; +pub const COPY_BUTTON: &str = include_str!("./templates/icons/copy.svg"); + pub const STYLESHEET: &str = include_str!("./templates/styles.gen.css"); pub const STYLESHEET_FILENAME: &str = "styles.css"; @@ -203,10 +207,7 @@ fn setup_hbs() -> Result, anyhow::Error> { "icons/arrow", include_str!("./templates/icons/arrow.svg"), )?; - reg.register_template_string( - "icons/copy", - include_str!("./templates/icons/copy.svg"), - )?; + reg.register_template_string("icons/copy", COPY_BUTTON)?; reg.register_template_string( "icons/link", include_str!("./templates/icons/link.svg"), @@ -227,19 +228,7 @@ lazy_static! { pub static ref HANDLEBARS: Handlebars<'static> = setup_hbs().unwrap(); } -pub type UsageComposer = Rc< - dyn Fn( - &RenderContext, - &[DocNodeWithContext], - String, - ) -> IndexMap, ->; - -#[derive(Eq, PartialEq, Hash)] -pub struct UsageComposerEntry { - pub name: String, - pub icon: Option>, -} +pub type HeadInject = Rc String>; #[derive(Clone)] pub struct GenerateOptions { @@ -250,12 +239,15 @@ pub struct GenerateOptions { /// default to that file. pub main_entrypoint: Option, pub href_resolver: Rc, - pub usage_composer: Option, + pub usage_composer: Rc, pub rewrite_map: Option>, pub category_docs: Option>>, pub disable_search: bool, pub symbol_redirect_map: Option>>, pub default_symbol_map: Option>, + pub markdown_renderer: jsdoc::MarkdownRenderer, + pub markdown_stripper: jsdoc::MarkdownStripper, + pub head_inject: Option, } #[non_exhaustive] @@ -263,10 +255,8 @@ pub struct GenerateCtx { pub package_name: Option, pub common_ancestor: Option, pub doc_nodes: IndexMap, Vec>, - pub highlight_adapter: comrak_adapters::HighlightAdapter, - pub url_rewriter: Option, pub href_resolver: Rc, - pub usage_composer: Option, + pub usage_composer: Rc, pub rewrite_map: Option>, pub main_entrypoint: Option>, pub file_mode: FileMode, @@ -274,6 +264,9 @@ pub struct GenerateCtx { pub disable_search: bool, pub symbol_redirect_map: Option>>, pub default_symbol_map: Option>, + pub markdown_renderer: jsdoc::MarkdownRenderer, + pub markdown_stripper: jsdoc::MarkdownStripper, + pub head_inject: Option, } impl GenerateCtx { @@ -368,8 +361,6 @@ impl GenerateCtx { package_name: options.package_name, common_ancestor, doc_nodes, - highlight_adapter: setup_highlighter(false), - url_rewriter: None, href_resolver: options.href_resolver, usage_composer: options.usage_composer, rewrite_map: options.rewrite_map, @@ -379,6 +370,9 @@ impl GenerateCtx { disable_search: options.disable_search, symbol_redirect_map: options.symbol_redirect_map, default_symbol_map: options.default_symbol_map, + markdown_renderer: options.markdown_renderer, + markdown_stripper: options.markdown_stripper, + head_inject: options.head_inject, }) } @@ -782,23 +776,6 @@ impl core::ops::Deref for DocNodeWithContext { } } -pub fn setup_highlighter( - show_line_numbers: bool, -) -> comrak_adapters::HighlightAdapter { - comrak_adapters::HighlightAdapter { - #[cfg(feature = "syntect")] - syntax_set: syntect::dumps::from_uncompressed_data(include_bytes!( - "./default_newlines.packdump" - )) - .unwrap(), - #[cfg(feature = "syntect")] - theme_set: syntect::highlighting::ThemeSet::load_defaults(), - #[cfg(feature = "tree-sitter")] - language_cb: tree_sitter::tree_sitter_language_cb, - show_line_numbers, - } -} - #[derive(Default, Debug, Eq, PartialEq)] pub enum FileMode { #[default] @@ -955,11 +932,10 @@ pub fn generate( title_parts.pop(); let html_head_ctx = pages::HtmlHeadCtx::new( + &ctx, &root, Some(&title_parts.join(" - ")), - ctx.package_name.as_ref(), Some(short_path), - ctx.disable_search, ); let file_name = @@ -1020,6 +996,11 @@ pub fn generate( files.insert(RESET_STYLESHEET_FILENAME.into(), RESET_STYLESHEET.into()); files.insert(FUSE_FILENAME.into(), FUSE_JS.into()); files.insert(SEARCH_FILENAME.into(), SEARCH_JS.into()); + #[cfg(feature = "comrak")] + files.insert( + comrak::COMRAK_STYLESHEET_FILENAME.into(), + comrak::COMRAK_STYLESHEET.into(), + ); Ok(files) } diff --git a/src/html/pages.rs b/src/html/pages.rs index f83a1680..21b40d90 100644 --- a/src/html/pages.rs +++ b/src/html/pages.rs @@ -39,6 +39,7 @@ pub struct HtmlHeadCtx { script_js: String, fuse_js: String, url_search: String, + head_inject: Option, disable_search: bool, } @@ -46,17 +47,18 @@ impl HtmlHeadCtx { pub const TEMPLATE: &'static str = "pages/html_head"; pub fn new( + ctx: &GenerateCtx, root: &str, page: Option<&str>, - package_name: Option<&String>, current_file: Option<&ShortPath>, - disable_search: bool, ) -> Self { Self { title: format!( "{}{}documentation", page.map(|page| format!("{page} - ")).unwrap_or_default(), - package_name + ctx + .package_name + .as_ref() .map(|package_name| format!("{package_name} ")) .unwrap_or_default() ), @@ -71,7 +73,8 @@ impl HtmlHeadCtx { script_js: format!("{root}{SCRIPT_FILENAME}"), fuse_js: format!("{root}{FUSE_FILENAME}"), url_search: format!("{root}{SEARCH_FILENAME}"), - disable_search, + head_inject: ctx.head_inject.clone().map(|head_inject| head_inject(root)), + disable_search: ctx.disable_search, } } } @@ -232,6 +235,7 @@ impl IndexCtx { ctx.resolve_path(render_ctx.get_current_resolve(), UrlResolveKind::Root); let html_head_ctx = HtmlHeadCtx::new( + ctx, &root, short_path.as_ref().and_then(|short_path| { if short_path.is_main { @@ -240,9 +244,7 @@ impl IndexCtx { Some(short_path.display_name()) } }), - ctx.package_name.as_ref(), None, - ctx.disable_search, ); let overview = match ctx.file_mode { @@ -399,13 +401,7 @@ impl IndexCtx { let root = ctx.resolve_path(UrlResolveKind::Category(name), UrlResolveKind::Root); - let html_head_ctx = HtmlHeadCtx::new( - &root, - Some(name), - ctx.package_name.as_ref(), - None, - ctx.disable_search, - ); + let html_head_ctx = HtmlHeadCtx::new(ctx, &root, Some(name), None); let breadcrumbs_ctx = render_ctx.get_breadcrumbs(); @@ -463,13 +459,7 @@ impl AllSymbolsCtx { }), ); - let html_head_ctx = HtmlHeadCtx::new( - "./", - Some("All Symbols"), - ctx.package_name.as_ref(), - None, - ctx.disable_search, - ); + let html_head_ctx = HtmlHeadCtx::new(ctx, "./", Some("All Symbols"), None); let categories_panel = CategoriesPanelCtx::new(&render_ctx, None); diff --git a/src/html/render_context.rs b/src/html/render_context.rs index 227ed85e..bf398e84 100644 --- a/src/html/render_context.rs +++ b/src/html/render_context.rs @@ -6,9 +6,12 @@ use crate::html::GenerateCtx; use crate::html::UrlResolveKind; use crate::node::DocNodeDef; use deno_graph::ModuleSpecifier; +use std::cmp::Ordering; use std::collections::HashMap; use std::collections::HashSet; use std::rc::Rc; +use std::sync::Arc; +use std::sync::Mutex; #[derive(Clone)] pub struct RenderContext<'ctx> { @@ -21,7 +24,7 @@ pub struct RenderContext<'ctx> { namespace_parts: Rc<[String]>, /// Only some when in `FileMode::SingleDts` and using categories pub category: Option<&'ctx str>, - pub toc: crate::html::comrak_adapters::HeadingToCAdapter, + pub toc: HeadingToCAdapter, } impl<'ctx> RenderContext<'ctx> { @@ -305,6 +308,129 @@ impl<'ctx> RenderContext<'ctx> { } } +#[derive(Debug)] +pub struct ToCEntry { + pub level: u8, + pub content: String, + pub anchor: String, +} + +#[derive(Default)] +pub struct Anchorizer { + map: HashMap, + itoa_buffer: itoa::Buffer, +} + +impl Anchorizer { + /// Returns a String that has been converted into an anchor using the GFM algorithm. + /// This replaces comrak's implementation to improve the performance. + /// @see https://docs.rs/comrak/latest/comrak/struct.Anchorizer.html#method.anchorize + pub fn anchorize(&mut self, s: &str) -> String { + let mut s = REJECTED_CHARS + .replace_all(&s.to_lowercase(), "") + .replace(' ', "-"); + + if let Some(count) = self.map.get_mut(&s) { + let a = self.itoa_buffer.format(*count); + s.push('-'); + s.push_str(a); + + *count += 1; + } else { + self.map.insert(s.clone(), 1); + } + + s + } +} + +#[derive(Clone)] +pub struct HeadingToCAdapter { + pub toc: Arc>>, + pub offset: Arc>, + pub anchorizer: Arc>, +} + +impl Default for HeadingToCAdapter { + fn default() -> Self { + Self { + toc: Arc::new(Mutex::new(vec![])), + anchorizer: Arc::new(Mutex::new(Default::default())), + offset: Arc::new(Mutex::new(0)), + } + } +} + +lazy_static! { + static ref REJECTED_CHARS: regex::Regex = + regex::Regex::new(r"[^\p{L}\p{M}\p{N}\p{Pc} -]").unwrap(); +} + +impl HeadingToCAdapter { + pub fn anchorize(&self, content: &str) -> String { + let mut anchorizer = self.anchorizer.lock().unwrap(); + anchorizer.anchorize(content) + } + + pub fn add_entry(&self, level: u8, content: &str, anchor: &str) { + let mut toc = self.toc.lock().unwrap(); + let mut offset = self.offset.lock().unwrap(); + + *offset = level; + + if toc.last().map_or(true, |toc| toc.content != content) { + toc.push(ToCEntry { + level, + content: content.to_owned(), + anchor: anchor.to_owned(), + }); + } + } + + pub fn render(self) -> Option { + let toc = Arc::into_inner(self.toc).unwrap().into_inner().unwrap(); + + if toc.is_empty() { + return None; + } + + let mut toc_content = vec!["
    ".to_string()]; + let mut current_level = toc.iter().map(|entry| entry.level).min().unwrap(); + + let mut level_diff = 0; + for entry in toc { + match current_level.cmp(&entry.level) { + Ordering::Equal => {} + Ordering::Less => { + level_diff += 1; + toc_content.push(r#"
    • "#.to_string()); + current_level = entry.level; + } + Ordering::Greater => { + level_diff -= 1; + toc_content.push("
  • ".to_string()); + current_level = entry.level; + } + } + + toc_content.push(format!( + r##"
  • {}
  • "##, + entry.anchor, + html_escape::encode_double_quoted_attribute(&entry.content), + entry.content + )); + } + + for _ in 0..level_diff { + toc_content.push("
".to_string()); + } + + toc_content.push(String::from("")); + + Some(toc_content.join("")) + } +} + fn split_with_brackets(s: &str) -> Vec { let mut result = Vec::new(); let mut current = String::new(); @@ -360,14 +486,17 @@ fn get_current_imports( #[cfg(test)] mod test { use super::*; - use crate::html::GenerateOptions; use crate::html::HrefResolver; + use crate::html::{ + GenerateOptions, UsageComposer, UsageComposerEntry, UsageToMd, + }; use crate::node::DeclarationKind; use crate::node::ImportDef; use crate::DocNode; use crate::Location; + use indexmap::IndexMap; - struct TestResolver(); + struct TestResolver; impl HrefResolver for TestResolver { fn resolve_path( @@ -394,12 +523,6 @@ mod test { Some(format!("{src}/{}", symbol.join("."))) } - fn resolve_usage(&self, current_resolve: UrlResolveKind) -> Option { - current_resolve - .get_file() - .map(|current_file| current_file.specifier.to_string()) - } - fn resolve_source(&self, location: &Location) -> Option { Some(location.filename.clone().into_string()) } @@ -413,6 +536,32 @@ mod test { } } + impl UsageComposer for TestResolver { + fn is_single_mode(&self) -> bool { + true + } + + fn compose( + &self, + doc_nodes: &[DocNodeWithContext], + current_resolve: UrlResolveKind, + usage_to_md: UsageToMd, + ) -> IndexMap { + current_resolve + .get_file() + .map(|current_file| { + IndexMap::from([( + UsageComposerEntry { + name: "".to_string(), + icon: None, + }, + usage_to_md(doc_nodes, current_file.specifier.as_str()), + )]) + }) + .unwrap_or_default() + } + } + #[test] fn lookup_symbol_href() { let doc_nodes_by_url = indexmap::IndexMap::from([( @@ -441,13 +590,18 @@ mod test { GenerateOptions { package_name: None, main_entrypoint: None, - href_resolver: Rc::new(TestResolver()), - usage_composer: None, + href_resolver: Rc::new(TestResolver), + usage_composer: Rc::new(TestResolver), rewrite_map: None, category_docs: None, disable_search: false, symbol_redirect_map: None, default_symbol_map: None, + markdown_renderer: crate::html::comrak::create_renderer( + None, None, None, + ), + markdown_stripper: Rc::new(crate::html::comrak::strip), + head_inject: None, }, None, Default::default(), diff --git a/src/html/templates/comrak.css b/src/html/templates/comrak.css new file mode 100644 index 00000000..8f3aeb63 --- /dev/null +++ b/src/html/templates/comrak.css @@ -0,0 +1,183 @@ +.link { + @apply text-blue-600 transition duration-75; +} + +.link:hover { + @apply text-blue-400; +} + +.markdown_summary, .markdown { + a:not(.no_color) { + @apply link; + } +} + +.markdown_summary { + @apply inline text-stone-600; + + p { + @apply inline-block line-clamp-4; + } + + :not(pre) > code { + @apply font-mono text-sm py-0.5 px-1.5 rounded bg-stone-200; + } +} + +.markdown { + @apply space-y-3 shrink min-w-0 max-w-[40ch] sm:max-w-screen-sm + md:max-w-screen-md lg:max-w-[75ch]; + + h1 { + @apply text-xl md:text-2xl lg:text-3xl border-b border-stone-300 pb-1; + } + + h2 { + @apply text-lg md:text-xl lg:text-2xl border-b border-stone-300 pb-1; + } + + h3 { + @apply font-bold md:text-lg md:font-normal lg:text-xl lg:font-normal; + } + + h4 { + @apply font-semibold md:font-bold lg:text-lg lg:font-normal; + } + + h5 { + @apply italic md:font-semibold lg:font-bold; + } + + h6 { + @apply md:italic lg:font-semibold; + } + + hr { + @apply m-2 border-stone-500; + } + + ol, ul { + @apply list-outside ml-4; + } + + ol { + @apply list-decimal; + } + + ul { + @apply list-disc; + } + + /* Inline code */ + + :not(pre) > code { + @apply font-mono text-sm py-0.5 px-1.5 rounded-md bg-stone-200; + } + + h1, h2, h3, h4, h5, h6 { + & > code { + font-size: inherit !important; + } + } + + pre { + @apply font-mono text-sm text-black bg-slate-50 border-t-1.5 border-b-1.5 + border-slate-300 -mx-4 rounded-none md:rounded-md md:border-1.5 md:mx-0; + + & > code:first-child { + @apply overflow-x-auto px-6 py-4 block; + } + } + + p { + @apply my-1 mx-0; + } + + table { + @apply block table-auto overflow-auto w-max max-w-full; + } + + td { + @apply p-2; + } + + th { + @apply font-bold text-center py-1.5; + } + + th, td { + @apply border-1.5 border-slate-300; + } + + tr:nth-child(2n) { + @apply bg-slate-50; + } + + img { + display: inline-block; + } + + .alert { + @apply py-4 px-6 border-2 space-y-2 rounded-lg; + + div:first-child { + @apply font-medium flex items-center gap-1.5; + + svg { + @apply size-5; + } + } + } + + .alert-note { + @apply border-blue-600 bg-blue-600/5; + + div:first-child { + @apply text-blue-600 stroke-blue-600; + } + } + + .alert-tip { + @apply border-green-600 bg-green-600/5; + + div:first-child { + @apply text-green-600 stroke-green-600; + } + } + + .alert-important { + @apply border-purple-600 bg-purple-600/5; + + div:first-child { + @apply text-purple-600 stroke-purple-600; + } + } + + .alert-warning { + @apply border-yellow-600 bg-yellow-600/5; + + div:first-child { + @apply text-yellow-600 stroke-yellow-600; + } + } + + .alert-caution { + @apply border-red-600 bg-red-600/5; + + div:first-child { + @apply text-red-600 stroke-red-600; + } + } +} + +.markdown .highlight { + @apply relative; + + .lineNumbers { + @apply border-r-2 border-stone-300 pr-1 text-right flex-none; + } + + .context_button { + @apply absolute top-3 right-4 opacity-60 hover:opacity-100; + } +} diff --git a/src/html/templates/comrak.gen.css b/src/html/templates/comrak.gen.css new file mode 100644 index 00000000..f6a84aab --- /dev/null +++ b/src/html/templates/comrak.gen.css @@ -0,0 +1 @@ +.link{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:75ms;transition-timing-function:cubic-bezier(.4,0,.2,1)}.link:hover{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity))}:is(.markdown_summary,.markdown) a:not(.no_color){--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:75ms;transition-timing-function:cubic-bezier(.4,0,.2,1)}:is(.markdown_summary,.markdown) a:not(.no_color):hover{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity))}.markdown_summary{--tw-text-opacity:1;color:rgb(87 83 78/var(--tw-text-opacity));display:inline}.markdown_summary p{-webkit-line-clamp:4;-webkit-box-orient:vertical;display:inline-block;overflow:hidden}.markdown_summary :not(pre)>code{--tw-bg-opacity:1;background-color:rgb(231 229 228/var(--tw-bg-opacity));border-radius:.25rem;padding:.125rem .375rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.875rem;line-height:1.25rem}.markdown{flex-shrink:1;min-width:0;max-width:40ch}.markdown>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}@media (min-width:640px){.markdown{max-width:640px}}@media (min-width:768px){.markdown{max-width:768px}}@media (min-width:1024px){.markdown{max-width:75ch}}.markdown h1{--tw-border-opacity:1;border-bottom-width:1px;border-color:rgb(214 211 209/var(--tw-border-opacity));padding-bottom:.25rem;font-size:1.25rem;line-height:1.75rem}@media (min-width:768px){.markdown h1{font-size:1.5rem;line-height:2rem}}@media (min-width:1024px){.markdown h1{font-size:1.875rem;line-height:2.25rem}}.markdown h2{--tw-border-opacity:1;border-bottom-width:1px;border-color:rgb(214 211 209/var(--tw-border-opacity));padding-bottom:.25rem;font-size:1.125rem;line-height:1.75rem}@media (min-width:768px){.markdown h2{font-size:1.25rem;line-height:1.75rem}}@media (min-width:1024px){.markdown h2{font-size:1.5rem;line-height:2rem}}.markdown h3{font-weight:700}@media (min-width:768px){.markdown h3{font-size:1.125rem;font-weight:400;line-height:1.75rem}}@media (min-width:1024px){.markdown h3{font-size:1.25rem;font-weight:400;line-height:1.75rem}}.markdown h4{font-weight:600}@media (min-width:768px){.markdown h4{font-weight:700}}@media (min-width:1024px){.markdown h4{font-size:1.125rem;font-weight:400;line-height:1.75rem}}.markdown h5{font-style:italic}@media (min-width:768px){.markdown h5{font-weight:600}}@media (min-width:1024px){.markdown h5{font-weight:700}}@media (min-width:768px){.markdown h6{font-style:italic}}@media (min-width:1024px){.markdown h6{font-weight:600}}.markdown hr{--tw-border-opacity:1;border-color:rgb(120 113 108/var(--tw-border-opacity));margin:.5rem}.markdown ol,.markdown ul{margin-left:1rem;list-style-position:outside}.markdown ol{list-style-type:decimal}.markdown ul{list-style-type:disc}.markdown :not(pre)>code{--tw-bg-opacity:1;background-color:rgb(231 229 228/var(--tw-bg-opacity));border-radius:.375rem;padding:.125rem .375rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.875rem;line-height:1.25rem}:is(.markdown h1,.markdown h2,.markdown h3,.markdown h4,.markdown h5,.markdown h6)>code{font-size:inherit!important}.markdown pre{--tw-border-opacity:1;border-top-width:1.5px;border-bottom-width:1.5px;border-color:rgb(203 213 225/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity));border-radius:0;margin-left:-1rem;margin-right:-1rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.875rem;line-height:1.25rem}@media (min-width:768px){.markdown pre{border-width:1.5px;border-radius:.375rem;margin-left:0;margin-right:0}}.markdown pre>code:first-child{padding:1rem 1.5rem;display:block;overflow-x:auto}.markdown p{margin:.25rem 0}.markdown table{table-layout:auto;width:max-content;max-width:100%;display:block;overflow:auto}.markdown td{padding:.5rem}.markdown th{text-align:center;padding-top:.375rem;padding-bottom:.375rem;font-weight:700}.markdown th,.markdown td{--tw-border-opacity:1;border-width:1.5px;border-color:rgb(203 213 225/var(--tw-border-opacity))}.markdown tr:nth-child(2n){--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity))}.markdown img{display:inline-block}.markdown .alert>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.markdown .alert{border-width:2px;border-radius:.5rem;padding:1rem 1.5rem}.markdown .alert div:first-child{align-items:center;gap:.375rem;font-weight:500;display:flex}.markdown .alert div:first-child svg{width:1.25rem;height:1.25rem}.markdown .alert-note{--tw-border-opacity:1;border-color:rgb(37 99 235/var(--tw-border-opacity));background-color:#2563eb0d}.markdown .alert-note div:first-child{stroke:#2563eb;--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity))}.markdown .alert-tip{--tw-border-opacity:1;border-color:rgb(22 163 74/var(--tw-border-opacity));background-color:#16a34a0d}.markdown .alert-tip div:first-child{stroke:#16a34a;--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.markdown .alert-important{--tw-border-opacity:1;border-color:rgb(147 51 234/var(--tw-border-opacity));background-color:#9333ea0d}.markdown .alert-important div:first-child{stroke:#9333ea;--tw-text-opacity:1;color:rgb(147 51 234/var(--tw-text-opacity))}.markdown .alert-warning{--tw-border-opacity:1;border-color:rgb(202 138 4/var(--tw-border-opacity));background-color:#ca8a040d}.markdown .alert-warning div:first-child{stroke:#ca8a04;--tw-text-opacity:1;color:rgb(202 138 4/var(--tw-text-opacity))}.markdown .alert-caution{--tw-border-opacity:1;border-color:rgb(220 38 38/var(--tw-border-opacity));background-color:#dc26260d}.markdown .alert-caution div:first-child{stroke:#dc2626;--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity))}.markdown .highlight{position:relative}.markdown .highlight .lineNumbers{--tw-border-opacity:1;border-right-width:2px;border-color:rgb(214 211 209/var(--tw-border-opacity));text-align:right;flex:none;padding-right:.25rem}.markdown .highlight .context_button{opacity:.6;position:absolute;top:.75rem;right:1rem}.markdown .highlight .context_button:hover{opacity:1} \ No newline at end of file diff --git a/src/html/templates/pages/html_head.hbs b/src/html/templates/pages/html_head.hbs index ad14fa20..0f2070ef 100644 --- a/src/html/templates/pages/html_head.hbs +++ b/src/html/templates/pages/html_head.hbs @@ -8,13 +8,14 @@ + {{{~head_inject~}}} - {{#if (not disable_search)}} + {{~#if (not disable_search)~}} - {{/if}} + {{~/if~}}
diff --git a/src/html/templates/styles.css b/src/html/templates/styles.css index 7870e08e..88d77a16 100644 --- a/src/html/templates/styles.css +++ b/src/html/templates/styles.css @@ -82,7 +82,8 @@ a { @apply text-xl leading-6 font-semibold py-1 mb-0; } - > .markdown_summary { + /* markdown */ + > div { @apply text-base max-w-[75ch]; } } @@ -273,11 +274,12 @@ a { } .usageContent { - :not(.markdown) h3 { + > h3 { @apply font-bold text-lg mb-3; } - .markdown { + /* markdown */ + > div { @apply text-xs text-[#686868]; p { @@ -356,302 +358,3 @@ a { display: inline-block; } } - -/* markdown */ -.markdown_border { - @apply ml-1 pl-2.5 border-l-2 border-stone-300/40; -} - -.markdown_summary, .markdown { - a:not(.no_color) { - @apply link; - } -} - -.markdown_summary { - @apply inline text-stone-600; - - p { - @apply inline-block line-clamp-4; - } - - :not(pre) > code { - @apply font-mono text-sm py-0.5 px-1.5 rounded bg-stone-200; - } -} - -.markdown { - @apply space-y-3 shrink min-w-0 max-w-[40ch] sm:max-w-screen-sm - md:max-w-screen-md lg:max-w-[75ch]; - - h1 { - @apply text-xl md:text-2xl lg:text-3xl border-b border-stone-300 pb-1; - } - - h2 { - @apply text-lg md:text-xl lg:text-2xl border-b border-stone-300 pb-1; - } - - h3 { - @apply font-bold md:text-lg md:font-normal lg:text-xl lg:font-normal; - } - - h4 { - @apply font-semibold md:font-bold lg:text-lg lg:font-normal; - } - - h5 { - @apply italic md:font-semibold lg:font-bold; - } - - h6 { - @apply md:italic lg:font-semibold; - } - - hr { - @apply m-2 border-stone-500; - } - - ol, ul { - @apply list-outside ml-4; - } - - ol { - @apply list-decimal; - } - - ul { - @apply list-disc; - } - - /* Inline code */ - - :not(pre) > code { - @apply font-mono text-sm py-0.5 px-1.5 rounded-md bg-stone-200; - } - - h1, h2, h3, h4, h5, h6 { - & > code { - font-size: inherit !important; - } - } - - pre { - @apply font-mono text-sm text-black bg-slate-50 border-t-1.5 border-b-1.5 - border-slate-300 -mx-4 rounded-none md:rounded-md md:border-1.5 md:mx-0; - - & > code:first-child { - @apply overflow-x-auto px-6 py-4 block; - } - } - - p { - @apply my-1 mx-0; - } - - table { - @apply block table-auto overflow-auto w-max max-w-full; - } - - td { - @apply p-2; - } - - th { - @apply font-bold text-center py-1.5; - } - - th, td { - @apply border-1.5 border-slate-300; - } - - tr:nth-child(2n) { - @apply bg-slate-50; - } - - img { - display: inline-block; - } - - .alert { - @apply py-4 px-6 border-2 space-y-2 rounded-lg; - - div:first-child { - @apply font-medium flex items-center gap-1.5; - - svg { - @apply size-5; - } - } - } - - .alert-note { - @apply border-blue-600 bg-blue-600/5; - - div:first-child { - @apply text-blue-600 stroke-blue-600; - } - } - - .alert-tip { - @apply border-green-600 bg-green-600/5; - - div:first-child { - @apply text-green-600 stroke-green-600; - } - } - - .alert-important { - @apply border-purple-600 bg-purple-600/5; - - div:first-child { - @apply text-purple-600 stroke-purple-600; - } - } - - .alert-warning { - @apply border-yellow-600 bg-yellow-600/5; - - div:first-child { - @apply text-yellow-600 stroke-yellow-600; - } - } - - .alert-caution { - @apply border-red-600 bg-red-600/5; - - div:first-child { - @apply text-red-600 stroke-red-600; - } - } -} - -.markdown .highlight { - @apply relative; - - .lineNumbers { - @apply border-r-2 border-stone-300 pr-1 text-right flex-none; - } - - .context_button { - @apply absolute top-3 right-4 opacity-60 hover:opacity-100; - } - - /*! - * GitHub Light v0.5.0 - * Copyright (c) 2012 - 2017 GitHub, Inc. - * Licensed under MIT (https://github.com/primer/github-syntax-theme-generator/blob/master/LICENSE) - */ - - .pl-c /* comment, punctuation.definition.comment, string.comment */ { - color: #6a737d; - } - - .pl-c1 - /* constant, entity.name.constant, variable.other.constant, variable.language, support, meta.property-name, support.constant, support.variable, meta.module-reference, markup.raw, meta.diff.header, meta.output */, - .pl-s .pl-v /* string variable */ { - color: #005cc5; - } - - .pl-e /* entity */, - .pl-en /* entity.name */ { - color: #6f42c1; - } - - .pl-smi - /* variable.parameter.function, storage.modifier.package, storage.modifier.import, storage.type.java, variable.other */, - .pl-s .pl-s1 /* string source */ { - color: #24292e; - } - - .pl-ent /* entity.name.tag, markup.quote */ { - color: #22863a; - } - - .pl-k /* keyword, storage, storage.type */ { - color: #d73a49; - } - - .pl-s /* string */, - .pl-pds - /* punctuation.definition.string, source.regexp, string.regexp.character-class */, - .pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */, - .pl-sr /* string.regexp */, - .pl-sr .pl-cce /* string.regexp constant.character.escape */, - .pl-sr .pl-sre /* string.regexp source.ruby.embedded */, - .pl-sr .pl-sra /* string.regexp string.regexp.arbitrary-repitition */ { - color: #032f62; - } - - .pl-v /* variable */, - .pl-smw /* sublimelinter.mark.warning */ { - color: #e36209; - } - - .pl-bu /* invalid.broken, invalid.deprecated, invalid.unimplemented, message.error, brackethighlighter.unmatched, sublimelinter.mark.error */ - { - color: #b31d28; - } - - .pl-ii /* invalid.illegal */ { - color: #fafbfc; - background-color: #b31d28; - } - - .pl-c2 /* carriage-return */ { - color: #fafbfc; - background-color: #d73a49; - } - - .pl-c2::before /* carriage-return */ { - content: "^M"; - } - - .pl-sr .pl-cce /* string.regexp constant.character.escape */ { - font-weight: bold; - color: #22863a; - } - - .pl-ml /* markup.list */ { - color: #735c0f; - } - - .pl-mh /* markup.heading */, - .pl-mh .pl-en /* markup.heading entity.name */, - .pl-ms /* meta.separator */ { - font-weight: bold; - color: #005cc5; - } - - .pl-mi /* markup.italic */ { - font-style: italic; - color: #24292e; - } - - .pl-mb /* markup.bold */ { - font-weight: bold; - color: #24292e; - } - - .pl-md /* markup.deleted, meta.diff.header.from-file, punctuation.definition.deleted */ - { - color: #b31d28; - background-color: #ffeef0; - } - - .pl-mi1 /* markup.inserted, meta.diff.header.to-file, punctuation.definition.inserted */ - { - color: #22863a; - background-color: #f0fff4; - } - - .pl-mc /* markup.changed, punctuation.definition.changed */ { - color: #e36209; - background-color: #ffebda; - } - - .pl-mi2 /* markup.ignored, markup.untracked */ { - color: #f6f8fa; - background-color: #005cc5; - } -} diff --git a/src/html/templates/styles.gen.css b/src/html/templates/styles.gen.css index c7d954da..1ce7019d 100644 --- a/src/html/templates/styles.gen.css +++ b/src/html/templates/styles.gen.css @@ -1 +1 @@ -.ddoc .container{width:100%}@media (min-width:640px){.ddoc .container{max-width:640px}}@media (min-width:768px){.ddoc .container{max-width:768px}}@media (min-width:1024px){.ddoc .container{max-width:1024px}}@media (min-width:1280px){.ddoc .container{max-width:1280px}}@media (min-width:1536px){.ddoc .container{max-width:1536px}}.ddoc .static{position:static}.ddoc .relative{position:relative}.ddoc .\!mb-0{margin-bottom:0!important}.ddoc .\!mt-2{margin-top:.5rem!important}.ddoc .mb-1{margin-bottom:.25rem}.ddoc .ml-4{margin-left:1rem}.ddoc .ml-indent{margin-left:2ch}.ddoc .mr-2{margin-right:.5rem}.ddoc .mt-3{margin-top:.75rem}.ddoc .block{display:block}.ddoc .inline{display:inline}.ddoc .\!flex{display:flex!important}.ddoc .flex{display:flex}.ddoc .inline-flex{display:inline-flex}.ddoc .table{display:table}.ddoc .contents{display:contents}.ddoc .hidden{display:none}.ddoc .h-4{height:1rem}.ddoc .h-5{height:1.25rem}.ddoc .min-w-0{min-width:0}.ddoc .max-w-\[75ch\]{max-width:75ch}.ddoc .flex-1{flex:1}.ddoc .flex-none{flex:none}.ddoc .grow{flex-grow:1}.ddoc .rotate-90{--tw-rotate:90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y))rotate(var(--tw-rotate))skewX(var(--tw-skew-x))skewY(var(--tw-skew-y))scaleX(var(--tw-scale-x))scaleY(var(--tw-scale-y))}.ddoc .scroll-mt-16{scroll-margin-top:4rem}.ddoc .items-center{align-items:center}.ddoc .gap-0{gap:0}.ddoc .gap-0\.5{gap:.125rem}.ddoc .gap-1{gap:.25rem}.ddoc .gap-2{gap:.5rem}.ddoc .space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.25rem*var(--tw-space-x-reverse));margin-left:calc(.25rem*calc(1 - var(--tw-space-x-reverse)))}.ddoc .space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*calc(1 - var(--tw-space-x-reverse)))}.ddoc .space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .space-y-7>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.75rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.75rem*var(--tw-space-y-reverse))}.ddoc .space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(2rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem*var(--tw-space-y-reverse))}.ddoc .overflow-x-auto{overflow-x:auto}.ddoc .break-words{overflow-wrap:break-word}.ddoc .break-all{word-break:break-all}.ddoc .rounded{border-radius:.25rem}.ddoc .rounded-md{border-radius:.375rem}.ddoc .border{border-width:1px}.ddoc .border-b{border-bottom-width:1px}.ddoc .border-l-2{border-left-width:2px}.ddoc .border-Class\/50{border-color:#20b44b80}.ddoc .border-Enum\/50{border-color:#22abb080}.ddoc .border-Function\/50{border-color:#056cf080}.ddoc .border-Interface\/50{border-color:#d2a06480}.ddoc .border-Method\/50{border-color:#056cf080}.ddoc .border-Namespace\/50{border-color:#d2564680}.ddoc .border-Property\/50{border-color:#7e57c080}.ddoc .border-TypeAlias\/50{border-color:#a4478c80}.ddoc .border-Variable\/50{border-color:#7e57c080}.ddoc .border-abstract\/50{border-color:#0cafc680}.ddoc .border-deprecated\/50{border-color:#dc262680}.ddoc .border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.ddoc .border-new\/50{border-color:#7b61ff80}.ddoc .border-optional\/50{border-color:#0cafc680}.ddoc .border-other\/50{border-color:#57534e80}.ddoc .border-permissions\/50,.ddoc .border-private\/50{border-color:#0cafc680}.ddoc .border-protected\/50,.ddoc .border-readonly\/50{border-color:#7b61ff80}.ddoc .border-stone-300{--tw-border-opacity:1;border-color:rgb(214 211 209/var(--tw-border-opacity))}.ddoc .border-unstable\/50,.ddoc .border-writeonly\/50{border-color:#7b61ff80}.ddoc .bg-Class\/15{background-color:#20b44b26}.ddoc .bg-Class\/5{background-color:#20b44b0d}.ddoc .bg-Enum\/15{background-color:#22abb026}.ddoc .bg-Enum\/5{background-color:#22abb00d}.ddoc .bg-Function\/15{background-color:#056cf026}.ddoc .bg-Function\/5{background-color:#056cf00d}.ddoc .bg-Interface\/15{background-color:#d2a06426}.ddoc .bg-Interface\/5{background-color:#d2a0640d}.ddoc .bg-Method\/15{background-color:#056cf026}.ddoc .bg-Method\/5{background-color:#056cf00d}.ddoc .bg-Namespace\/15{background-color:#d2564626}.ddoc .bg-Namespace\/5{background-color:#d256460d}.ddoc .bg-Property\/15{background-color:#7e57c026}.ddoc .bg-Property\/5{background-color:#7e57c00d}.ddoc .bg-TypeAlias\/15{background-color:#a4478c26}.ddoc .bg-TypeAlias\/5{background-color:#a4478c0d}.ddoc .bg-Variable\/15{background-color:#7e57c026}.ddoc .bg-Variable\/5{background-color:#7e57c00d}.ddoc .bg-abstract\/15{background-color:#0cafc626}.ddoc .bg-abstract\/5{background-color:#0cafc60d}.ddoc .bg-deprecated\/15{background-color:#dc262626}.ddoc .bg-deprecated\/5{background-color:#dc26260d}.ddoc .bg-new\/15{background-color:#7b61ff26}.ddoc .bg-new\/5{background-color:#7b61ff0d}.ddoc .bg-optional\/15{background-color:#0cafc626}.ddoc .bg-optional\/5{background-color:#0cafc60d}.ddoc .bg-other\/15{background-color:#57534e26}.ddoc .bg-other\/5{background-color:#57534e0d}.ddoc .bg-permissions\/15{background-color:#0cafc626}.ddoc .bg-permissions\/5{background-color:#0cafc60d}.ddoc .bg-private\/15{background-color:#0cafc626}.ddoc .bg-private\/5{background-color:#0cafc60d}.ddoc .bg-protected\/15{background-color:#7b61ff26}.ddoc .bg-protected\/5{background-color:#7b61ff0d}.ddoc .bg-readonly\/15{background-color:#7b61ff26}.ddoc .bg-readonly\/5{background-color:#7b61ff0d}.ddoc .bg-stone-100{--tw-bg-opacity:1;background-color:rgb(245 245 244/var(--tw-bg-opacity))}.ddoc .bg-unstable\/15{background-color:#7b61ff26}.ddoc .bg-unstable\/5{background-color:#7b61ff0d}.ddoc .bg-writeonly\/15{background-color:#7b61ff26}.ddoc .bg-writeonly\/5{background-color:#7b61ff0d}.ddoc .px-2{padding-left:.5rem;padding-right:.5rem}.ddoc .px-3{padding-left:.75rem;padding-right:.75rem}.ddoc .px-4{padding-left:1rem;padding-right:1rem}.ddoc .py-1{padding-top:.25rem;padding-bottom:.25rem}.ddoc .py-2{padding-top:.5rem;padding-bottom:.5rem}.ddoc .pb-5{padding-bottom:1.25rem}.ddoc .pt-4{padding-top:1rem}.ddoc .text-2xl{font-size:1.5rem;line-height:2rem}.ddoc .text-base{font-size:1rem;line-height:1.5rem}.ddoc .text-sm{font-size:.875rem;line-height:1.25rem}.ddoc .font-bold{font-weight:700}.ddoc .font-medium{font-weight:500}.ddoc .font-normal{font-weight:400}.ddoc .italic{font-style:italic}.ddoc .leading-none{line-height:1}.ddoc .text-Class{--tw-text-opacity:1;color:rgb(32 180 75/var(--tw-text-opacity))}.ddoc .text-Enum{--tw-text-opacity:1;color:rgb(34 171 176/var(--tw-text-opacity))}.ddoc .text-Function{--tw-text-opacity:1;color:rgb(5 108 240/var(--tw-text-opacity))}.ddoc .text-Interface{--tw-text-opacity:1;color:rgb(210 160 100/var(--tw-text-opacity))}.ddoc .text-Method{--tw-text-opacity:1;color:rgb(5 108 240/var(--tw-text-opacity))}.ddoc .text-Namespace{--tw-text-opacity:1;color:rgb(210 86 70/var(--tw-text-opacity))}.ddoc .text-Property{--tw-text-opacity:1;color:rgb(126 87 192/var(--tw-text-opacity))}.ddoc .text-TypeAlias{--tw-text-opacity:1;color:rgb(164 71 140/var(--tw-text-opacity))}.ddoc .text-Variable{--tw-text-opacity:1;color:rgb(126 87 192/var(--tw-text-opacity))}.ddoc .text-\[\#0F172A\]{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.ddoc .text-abstract{--tw-text-opacity:1;color:rgb(12 175 198/var(--tw-text-opacity))}.ddoc .text-deprecated{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity))}.ddoc .text-new{--tw-text-opacity:1;color:rgb(123 97 255/var(--tw-text-opacity))}.ddoc .text-optional{--tw-text-opacity:1;color:rgb(12 175 198/var(--tw-text-opacity))}.ddoc .text-other{--tw-text-opacity:1;color:rgb(87 83 78/var(--tw-text-opacity))}.ddoc .text-permissions,.ddoc .text-private{--tw-text-opacity:1;color:rgb(12 175 198/var(--tw-text-opacity))}.ddoc .text-protected,.ddoc .text-readonly{--tw-text-opacity:1;color:rgb(123 97 255/var(--tw-text-opacity))}.ddoc .text-stone-500{--tw-text-opacity:1;color:rgb(120 113 108/var(--tw-text-opacity))}.ddoc .text-unstable,.ddoc .text-writeonly{--tw-text-opacity:1;color:rgb(123 97 255/var(--tw-text-opacity))}.ddoc .filter{filter:var(--tw-blur)var(--tw-brightness)var(--tw-contrast)var(--tw-grayscale)var(--tw-hue-rotate)var(--tw-invert)var(--tw-saturate)var(--tw-sepia)var(--tw-drop-shadow)}.ddoc summary::-webkit-details-marker{display:none}.ddoc a{word-wrap:break-word}.ddoc{--ddoc-selection-border-width:2px;--ddoc-selection-border-color-default:#d6d3d1;--ddoc-selection-selected-border-color:#2564eb;--ddoc-selection-selected-bg:#056cf00c;--ddoc-selection-padding:9px 15px}.ddoc .link{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:75ms;transition-timing-function:cubic-bezier(.4,0,.2,1)}.ddoc .link:hover{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity))}.ddoc .anchor{float:left;--tw-text-opacity:1;color:rgb(87 83 78/var(--tw-text-opacity));margin-left:-24px;padding:.25rem;line-height:1;display:none;top:0;bottom:0}.ddoc .anchorable{scroll-margin-top:4rem;position:relative}.ddoc .anchorable:hover .anchor{display:block}.ddoc .deprecated>div:first-child{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity));align-items:center;gap:.25rem;padding-top:.25rem;padding-bottom:.25rem;display:flex}.ddoc .deprecated>div:first-child>span{font-weight:600;line-height:1.5rem}.ddoc .deprecated>div:nth-child(2){--tw-border-opacity:1;border-left-width:4px;border-color:rgb(252 165 165/var(--tw-border-opacity));margin-left:.25rem;padding-left:.5rem}.ddoc .symbolSubtitle>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.125rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem*var(--tw-space-y-reverse))}.ddoc .symbolSubtitle{font-size:.875rem;line-height:1rem}.ddoc .symbolSubtitle .type{--tw-text-opacity:1;color:rgb(168 162 158/var(--tw-text-opacity));font-style:italic}.ddoc .docEntry{margin-bottom:1rem}.ddoc .docEntry>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .docEntry .docEntryHeader{justify-content:space-between;align-items:flex-start;display:flex}@media (min-width:768px){.ddoc .docEntry .docEntryHeader{font-size:1rem;line-height:1.5rem}}.ddoc .docEntry .docEntryHeader>div{overflow-wrap:break-word}.ddoc .section{max-width:75ch;margin-bottom:.5rem;scroll-margin-top:4rem}.ddoc .section>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .section>div:first-child>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .section>div:first-child>h2{margin-bottom:0;padding-top:.25rem;padding-bottom:.25rem;font-size:1.25rem;font-weight:600;line-height:1.5rem}.ddoc .section>div:first-child>.markdown_summary{max-width:75ch;font-size:1rem;line-height:1.5rem}.ddoc .namespaceSection{max-width:75ch;margin-top:1rem}.ddoc .namespaceSection>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.ddoc .namespaceSection .namespaceItem{-moz-column-gap:.625rem;column-gap:.625rem;min-height:0;display:flex}@media (min-width:768px){.ddoc .namespaceSection .namespaceItem{min-height:4rem}}@media (min-width:1024px){.ddoc .namespaceSection .namespaceItem{padding-right:1rem}}.ddoc .namespaceSection .namespaceItem .docNodeKindIcon{flex-direction:column;justify-content:flex-start;width:auto}.ddoc .namespaceSection .namespaceItem .docNodeKindIcon>*+*{margin-top:-.125rem;margin-left:0}.ddoc .namespaceSection .namespaceItem[aria-label=deprecated]{opacity:.6}.ddoc .namespaceSection .namespaceItem[aria-label=deprecated] .namespaceItemContent>a{--tw-text-opacity:1;color:rgb(120 113 108/var(--tw-text-opacity));text-decoration-line:line-through;text-decoration-color:#78716cb3;text-decoration-thickness:2px}.ddoc .namespaceSection .namespaceItem .namespaceItemContent>a,.ddoc .namespaceSection .namespaceItem .namespaceItemContent .namespaceItemContentSubItems a{text-decoration-line:underline;text-decoration-color:#d6d3d1}:is(.ddoc .namespaceSection .namespaceItem .namespaceItemContent>a,.ddoc .namespaceSection .namespaceItem .namespaceItemContent .namespaceItemContentSubItems a):hover{text-decoration-line:none}.ddoc .namespaceSection .namespaceItem .namespaceItemContent>a{word-break:break-all;font-weight:500;line-height:1.25;display:block}.ddoc .namespaceSection .namespaceItem .namespaceItemContent .namespaceItemContentDoc{--tw-text-opacity:1;color:rgb(87 83 78/var(--tw-text-opacity));margin-top:.5rem;font-size:.875rem;line-height:1.25rem}.ddoc .namespaceSection .namespaceItem .namespaceItemContent .namespaceItemContentSubItems{flex-wrap:wrap;row-gap:.25rem;margin-top:.375rem;font-size:.875rem;line-height:1.25rem;display:flex}.ddoc .namespaceSection .namespaceItem .namespaceItemContent .namespaceItemContentSubItems>li:not(:last-child):after{content:"|";-webkit-user-select:none;user-select:none;--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity));margin-left:.5rem;margin-right:.5rem}.ddoc .symbolGroup>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(3rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(3rem*var(--tw-space-y-reverse))}.ddoc .symbolGroup article>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem*var(--tw-space-y-reverse))}.ddoc .symbolGroup article>div:first-child{justify-content:space-between;align-items:flex-start;display:flex}.ddoc .symbolGroup article>div:first-child>div:first-child>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.ddoc .symbolGroup article>div:first-child>div:first-child{font-weight:500}.ddoc .docNodeKindIcon{flex-shrink:0;justify-content:flex-end;display:inline-flex}.ddoc .docNodeKindIcon div{-webkit-user-select:none;user-select:none;text-align:center;vertical-align:middle;border-radius:9999px;flex-shrink:0;width:1.25rem;height:1.25rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.75rem;font-weight:500;line-height:1.25rem}.ddoc .docNodeKindIcon>*+*{margin-left:-.375rem}.ddoc .example-header{margin-bottom:.75rem;font-size:1.125rem;font-weight:700;line-height:1.75rem}.ddoc .toc h3{margin-bottom:.75rem;font-size:1.125rem;font-weight:700;line-height:1.75rem}.ddoc .toc>div>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem*var(--tw-space-y-reverse))}.ddoc .toc .topSymbols>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.ddoc .toc .topSymbols{font-size:.875rem;line-height:1.25rem}.ddoc .toc .topSymbols ul{list-style-type:none}.ddoc .toc .topSymbols ul>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.625rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.625rem*var(--tw-space-y-reverse))}.ddoc .toc .topSymbols ul li{display:block}.ddoc .toc .topSymbols ul li a{align-items:center;gap:.5rem;display:flex}.ddoc .toc .topSymbols ul li a>span{text-overflow:ellipsis;white-space:nowrap;border-radius:.25rem;width:100%;margin-top:-.125rem;margin-bottom:-.125rem;margin-left:-.25rem;padding-top:.125rem;padding-bottom:.125rem;padding-left:.25rem;display:block;overflow:hidden}.ddoc .toc .topSymbols>a:hover{text-decoration-line:underline}.ddoc .toc .documentNavigation>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.ddoc .toc .documentNavigation{font-size:.875rem;line-height:1.25rem}@media not all and (min-width:640px){.ddoc .toc .documentNavigation{display:none}}.ddoc .toc .documentNavigation>ul{flex-grow:1;display:block}.ddoc .toc .documentNavigation>ul>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .toc .documentNavigation>ul{overflow-y:auto}.ddoc .toc .documentNavigation>ul>li{margin-top:.125rem;margin-left:.75rem;margin-right:.75rem}.ddoc .toc .documentNavigation>ul li:has(>ul){margin-top:0!important}.ddoc .toc .documentNavigation>ul li:has(>a){padding-bottom:0!important}.ddoc .toc .documentNavigation>ul ul{margin-left:.875rem}.ddoc .toc .documentNavigation>ul ul>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .toc .documentNavigation>ul ul{--tw-text-opacity:1;color:rgb(134 135 137/var(--tw-text-opacity));font-size:.8rem;line-height:1}.ddoc .toc .documentNavigation>ul ul li{margin-top:.25rem!important}.ddoc .toc .documentNavigation>ul ul li a{padding:.25rem}.ddoc .toc .documentNavigation>ul ul li a:hover{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.ddoc .toc .documentNavigation a{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}.ddoc .toc .documentNavigation a:hover{text-decoration-line:underline}.ddoc .usages nav{flex-direction:row;align-items:center;gap:.5rem;margin-bottom:.75rem;font-weight:600;display:flex}.ddoc .usages nav details>summary{cursor:pointer;-webkit-user-select:none;user-select:none;--tw-border-opacity:1;border-width:1px;border-color:rgb(209 213 219/var(--tw-border-opacity));border-radius:.25rem;gap:.25rem;padding:.5rem .75rem;display:flex}@media (min-width:768px){.ddoc .usages nav details>div{position:relative}}.ddoc .usages nav details>div>div{z-index:30;--tw-border-opacity:1;border-width:1px;border-color:rgb(209 213 219/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));margin-top:.375rem;padding:.5rem;display:block;position:absolute}@media not all and (min-width:768px){.ddoc .usages nav details>div>div{border-left-width:0;border-right-width:0;left:0;right:0}}@media (min-width:768px){.ddoc .usages nav details>div>div{border-radius:.25rem;width:12rem}}.ddoc .usages nav details>div>div label{cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:.125rem;align-items:center;gap:.5rem;padding:.25rem .5rem;line-height:1.5;display:flex}.ddoc .usages nav details>div>div label:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.ddoc .usageContent :not(.markdown) h3{margin-bottom:.75rem;font-size:1.125rem;font-weight:700;line-height:1.75rem}.ddoc .usageContent .markdown{--tw-text-opacity:1;color:rgb(104 104 104/var(--tw-text-opacity));font-size:.75rem;line-height:1rem}.ddoc .usageContent .markdown p{margin:0}.ddoc .usageContent pre.highlight{--tw-border-opacity:1;border-width:1px;border-color:rgb(209 213 219/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}@media not all and (min-width:768px){.ddoc .usageContent pre.highlight{border-left-width:0;border-right-width:0}}.ddoc .usageContent pre.highlight{margin-top:.25rem!important}.ddoc .usageContent pre.highlight>code:first-child{scrollbar-width:thin;padding:.5rem .75rem}.ddoc .usageContent pre.highlight .context_button{border-width:0;display:none;top:.25rem;right:.5rem}.ddoc .usageContent pre.highlight .context_button svg rect{fill:#fff}.ddoc .usageContent pre.highlight:hover .context_button{opacity:1;display:block}.ddoc #categoryPanel{padding-top:.75rem;font-size:.875rem;line-height:1.25rem}.ddoc #categoryPanel ul>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc #categoryPanel ul{overflow-y:auto}.ddoc #categoryPanel ul li{margin-left:.25rem;margin-right:.75rem}.ddoc #categoryPanel ul li a{text-overflow:ellipsis;white-space:nowrap;padding:.375rem .875rem;display:block;overflow:hidden}.ddoc #categoryPanel ul li a:hover{text-decoration-line:underline}.ddoc .contextLink{color:#0e6590cc;text-underline-offset:.15em;text-decoration-line:underline;text-decoration-color:#0e659080;text-decoration-thickness:1.5px}.ddoc .contextLink:hover{--tw-text-opacity:1;color:rgb(14 101 144/var(--tw-text-opacity));text-decoration-color:#0e6590}.ddoc .contextLink{-webkit-text-decoration-skip:ink;-webkit-text-decoration-skip-ink:auto;text-decoration-skip-ink:auto}.ddoc .breadcrumbs{word-break:break-all;flex-wrap:wrap;align-items:center;gap:.25rem;display:inline-flex}.ddoc .breadcrumbs>li:first-child{font-size:1.5rem;font-weight:700;line-height:1}.ddoc .breadcrumbs li{font-size:1.125rem;line-height:.9em;display:inline}@media (min-width:1024px){.ddoc .breadcrumbs li{font-size:1.25rem;line-height:1.75rem}}.ddoc .context_button{z-index:10;cursor:pointer;background-color:inherit;border-width:1px;border-radius:.25rem;padding:.375rem;line-height:0}.ddoc .context_button:hover{--tw-bg-opacity:1;background-color:rgb(231 229 228/var(--tw-bg-opacity))}.ddoc .see{list-style-type:disc;list-style-position:inside}.ddoc .see>li *{display:inline-block}.ddoc .markdown_border{border-color:#d6d3d166;border-left-width:2px;margin-left:.25rem;padding-left:.625rem}:is(.ddoc .markdown_summary,.ddoc .markdown) a:not(.no_color){--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:75ms;transition-timing-function:cubic-bezier(.4,0,.2,1)}:is(.ddoc .markdown_summary,.ddoc .markdown) a:not(.no_color):hover{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity))}.ddoc .markdown_summary{--tw-text-opacity:1;color:rgb(87 83 78/var(--tw-text-opacity));display:inline}.ddoc .markdown_summary p{-webkit-line-clamp:4;-webkit-box-orient:vertical;display:inline-block;overflow:hidden}.ddoc .markdown_summary :not(pre)>code{--tw-bg-opacity:1;background-color:rgb(231 229 228/var(--tw-bg-opacity));border-radius:.25rem;padding:.125rem .375rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.875rem;line-height:1.25rem}.ddoc .markdown{flex-shrink:1;min-width:0;max-width:40ch}.ddoc .markdown>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}@media (min-width:640px){.ddoc .markdown{max-width:640px}}@media (min-width:768px){.ddoc .markdown{max-width:768px}}@media (min-width:1024px){.ddoc .markdown{max-width:75ch}}.ddoc .markdown h1{--tw-border-opacity:1;border-bottom-width:1px;border-color:rgb(214 211 209/var(--tw-border-opacity));padding-bottom:.25rem;font-size:1.25rem;line-height:1.75rem}@media (min-width:768px){.ddoc .markdown h1{font-size:1.5rem;line-height:2rem}}@media (min-width:1024px){.ddoc .markdown h1{font-size:1.875rem;line-height:2.25rem}}.ddoc .markdown h2{--tw-border-opacity:1;border-bottom-width:1px;border-color:rgb(214 211 209/var(--tw-border-opacity));padding-bottom:.25rem;font-size:1.125rem;line-height:1.75rem}@media (min-width:768px){.ddoc .markdown h2{font-size:1.25rem;line-height:1.75rem}}@media (min-width:1024px){.ddoc .markdown h2{font-size:1.5rem;line-height:2rem}}.ddoc .markdown h3{font-weight:700}@media (min-width:768px){.ddoc .markdown h3{font-size:1.125rem;font-weight:400;line-height:1.75rem}}@media (min-width:1024px){.ddoc .markdown h3{font-size:1.25rem;font-weight:400;line-height:1.75rem}}.ddoc .markdown h4{font-weight:600}@media (min-width:768px){.ddoc .markdown h4{font-weight:700}}@media (min-width:1024px){.ddoc .markdown h4{font-size:1.125rem;font-weight:400;line-height:1.75rem}}.ddoc .markdown h5{font-style:italic}@media (min-width:768px){.ddoc .markdown h5{font-weight:600}}@media (min-width:1024px){.ddoc .markdown h5{font-weight:700}}@media (min-width:768px){.ddoc .markdown h6{font-style:italic}}@media (min-width:1024px){.ddoc .markdown h6{font-weight:600}}.ddoc .markdown hr{--tw-border-opacity:1;border-color:rgb(120 113 108/var(--tw-border-opacity));margin:.5rem}.ddoc .markdown ol,.ddoc .markdown ul{margin-left:1rem;list-style-position:outside}.ddoc .markdown ol{list-style-type:decimal}.ddoc .markdown ul{list-style-type:disc}.ddoc .markdown :not(pre)>code{--tw-bg-opacity:1;background-color:rgb(231 229 228/var(--tw-bg-opacity));border-radius:.375rem;padding:.125rem .375rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.875rem;line-height:1.25rem}:is(.ddoc .markdown h1,.ddoc .markdown h2,.ddoc .markdown h3,.ddoc .markdown h4,.ddoc .markdown h5,.ddoc .markdown h6)>code{font-size:inherit!important}.ddoc .markdown pre{--tw-border-opacity:1;border-top-width:1.5px;border-bottom-width:1.5px;border-color:rgb(203 213 225/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity));--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity));border-radius:0;margin-left:-1rem;margin-right:-1rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.875rem;line-height:1.25rem}@media (min-width:768px){.ddoc .markdown pre{border-width:1.5px;border-radius:.375rem;margin-left:0;margin-right:0}}.ddoc .markdown pre>code:first-child{padding:1rem 1.5rem;display:block;overflow-x:auto}.ddoc .markdown p{margin:.25rem 0}.ddoc .markdown table{table-layout:auto;width:max-content;max-width:100%;display:block;overflow:auto}.ddoc .markdown td{padding:.5rem}.ddoc .markdown th{text-align:center;padding-top:.375rem;padding-bottom:.375rem;font-weight:700}.ddoc .markdown th,.ddoc .markdown td{--tw-border-opacity:1;border-width:1.5px;border-color:rgb(203 213 225/var(--tw-border-opacity))}.ddoc .markdown tr:nth-child(2n){--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity))}.ddoc .markdown img{display:inline-block}.ddoc .markdown .alert>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .markdown .alert{border-width:2px;border-radius:.5rem;padding:1rem 1.5rem}.ddoc .markdown .alert div:first-child{align-items:center;gap:.375rem;font-weight:500;display:flex}.ddoc .markdown .alert div:first-child svg{width:1.25rem;height:1.25rem}.ddoc .markdown .alert-note{--tw-border-opacity:1;border-color:rgb(37 99 235/var(--tw-border-opacity));background-color:#2563eb0d}.ddoc .markdown .alert-note div:first-child{stroke:#2563eb;--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity))}.ddoc .markdown .alert-tip{--tw-border-opacity:1;border-color:rgb(22 163 74/var(--tw-border-opacity));background-color:#16a34a0d}.ddoc .markdown .alert-tip div:first-child{stroke:#16a34a;--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}.ddoc .markdown .alert-important{--tw-border-opacity:1;border-color:rgb(147 51 234/var(--tw-border-opacity));background-color:#9333ea0d}.ddoc .markdown .alert-important div:first-child{stroke:#9333ea;--tw-text-opacity:1;color:rgb(147 51 234/var(--tw-text-opacity))}.ddoc .markdown .alert-warning{--tw-border-opacity:1;border-color:rgb(202 138 4/var(--tw-border-opacity));background-color:#ca8a040d}.ddoc .markdown .alert-warning div:first-child{stroke:#ca8a04;--tw-text-opacity:1;color:rgb(202 138 4/var(--tw-text-opacity))}.ddoc .markdown .alert-caution{--tw-border-opacity:1;border-color:rgb(220 38 38/var(--tw-border-opacity));background-color:#dc26260d}.ddoc .markdown .alert-caution div:first-child{stroke:#dc2626;--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity))}.ddoc .markdown .highlight{position:relative}.ddoc .markdown .highlight .lineNumbers{--tw-border-opacity:1;border-right-width:2px;border-color:rgb(214 211 209/var(--tw-border-opacity));text-align:right;flex:none;padding-right:.25rem}.ddoc .markdown .highlight .context_button{opacity:.6;position:absolute;top:.75rem;right:1rem}.ddoc .markdown .highlight .context_button:hover{opacity:1}.ddoc .markdown .highlight .pl-c{color:#6a737d}.ddoc .markdown .highlight .pl-c1,.ddoc .markdown .highlight .pl-s .pl-v{color:#005cc5}.ddoc .markdown .highlight .pl-e,.ddoc .markdown .highlight .pl-en{color:#6f42c1}.ddoc .markdown .highlight .pl-smi,.ddoc .markdown .highlight .pl-s .pl-s1{color:#24292e}.ddoc .markdown .highlight .pl-ent{color:#22863a}.ddoc .markdown .highlight .pl-k{color:#d73a49}.ddoc .markdown .highlight .pl-s,.ddoc .markdown .highlight .pl-pds,.ddoc .markdown .highlight .pl-s .pl-pse .pl-s1,.ddoc .markdown .highlight .pl-sr,.ddoc .markdown .highlight .pl-sr .pl-cce,.ddoc .markdown .highlight .pl-sr .pl-sre,.ddoc .markdown .highlight .pl-sr .pl-sra{color:#032f62}.ddoc .markdown .highlight .pl-v,.ddoc .markdown .highlight .pl-smw{color:#e36209}.ddoc .markdown .highlight .pl-bu{color:#b31d28}.ddoc .markdown .highlight .pl-ii{color:#fafbfc;background-color:#b31d28}.ddoc .markdown .highlight .pl-c2{color:#fafbfc;background-color:#d73a49}.ddoc .markdown .highlight .pl-c2:before{content:"^M"}.ddoc .markdown .highlight .pl-sr .pl-cce{color:#22863a;font-weight:700}.ddoc .markdown .highlight .pl-ml{color:#735c0f}.ddoc .markdown .highlight .pl-mh,.ddoc .markdown .highlight .pl-mh .pl-en,.ddoc .markdown .highlight .pl-ms{color:#005cc5;font-weight:700}.ddoc .markdown .highlight .pl-mi{color:#24292e;font-style:italic}.ddoc .markdown .highlight .pl-mb{color:#24292e;font-weight:700}.ddoc .markdown .highlight .pl-md{color:#b31d28;background-color:#ffeef0}.ddoc .markdown .highlight .pl-mi1{color:#22863a;background-color:#f0fff4}.ddoc .markdown .highlight .pl-mc{color:#e36209;background-color:#ffebda}.ddoc .markdown .highlight .pl-mi2{color:#f6f8fa;background-color:#005cc5}.ddoc .\*\:h-4>*{height:1rem}.ddoc .\*\:h-5>*{height:1.25rem}.ddoc .\*\:w-auto>*{width:auto}.ddoc .\*\:flex-none>*{flex:none}.ddoc .target\:bg-yellow-200:target{--tw-bg-opacity:1;background-color:rgb(254 240 138/var(--tw-bg-opacity))}.ddoc .hover\:bg-Class\/15:hover{background-color:#20b44b26}.ddoc .hover\:bg-Class\/5:hover{background-color:#20b44b0d}.ddoc .hover\:bg-Enum\/15:hover{background-color:#22abb026}.ddoc .hover\:bg-Enum\/5:hover{background-color:#22abb00d}.ddoc .hover\:bg-Function\/15:hover{background-color:#056cf026}.ddoc .hover\:bg-Function\/5:hover{background-color:#056cf00d}.ddoc .hover\:bg-Interface\/15:hover{background-color:#d2a06426}.ddoc .hover\:bg-Interface\/5:hover{background-color:#d2a0640d}.ddoc .hover\:bg-Method\/15:hover{background-color:#056cf026}.ddoc .hover\:bg-Method\/5:hover{background-color:#056cf00d}.ddoc .hover\:bg-Namespace\/15:hover{background-color:#d2564626}.ddoc .hover\:bg-Namespace\/5:hover{background-color:#d256460d}.ddoc .hover\:bg-Property\/15:hover{background-color:#7e57c026}.ddoc .hover\:bg-Property\/5:hover{background-color:#7e57c00d}.ddoc .hover\:bg-TypeAlias\/15:hover{background-color:#a4478c26}.ddoc .hover\:bg-TypeAlias\/5:hover{background-color:#a4478c0d}.ddoc .hover\:bg-Variable\/15:hover{background-color:#7e57c026}.ddoc .hover\:bg-Variable\/5:hover{background-color:#7e57c00d}.ddoc .hover\:bg-abstract\/15:hover{background-color:#0cafc626}.ddoc .hover\:bg-abstract\/5:hover{background-color:#0cafc60d}.ddoc .hover\:bg-deprecated\/15:hover{background-color:#dc262626}.ddoc .hover\:bg-deprecated\/5:hover{background-color:#dc26260d}.ddoc .hover\:bg-new\/15:hover{background-color:#7b61ff26}.ddoc .hover\:bg-new\/5:hover{background-color:#7b61ff0d}.ddoc .hover\:bg-optional\/15:hover{background-color:#0cafc626}.ddoc .hover\:bg-optional\/5:hover{background-color:#0cafc60d}.ddoc .hover\:bg-other\/15:hover{background-color:#57534e26}.ddoc .hover\:bg-other\/5:hover{background-color:#57534e0d}.ddoc .hover\:bg-permissions\/15:hover{background-color:#0cafc626}.ddoc .hover\:bg-permissions\/5:hover{background-color:#0cafc60d}.ddoc .hover\:bg-private\/15:hover{background-color:#0cafc626}.ddoc .hover\:bg-private\/5:hover{background-color:#0cafc60d}.ddoc .hover\:bg-protected\/15:hover{background-color:#7b61ff26}.ddoc .hover\:bg-protected\/5:hover{background-color:#7b61ff0d}.ddoc .hover\:bg-readonly\/15:hover{background-color:#7b61ff26}.ddoc .hover\:bg-readonly\/5:hover{background-color:#7b61ff0d}.ddoc .hover\:bg-unstable\/15:hover{background-color:#7b61ff26}.ddoc .hover\:bg-unstable\/5:hover{background-color:#7b61ff0d}.ddoc .hover\:bg-writeonly\/15:hover{background-color:#7b61ff26}.ddoc .hover\:bg-writeonly\/5:hover{background-color:#7b61ff0d} \ No newline at end of file +.ddoc .container{width:100%}@media (min-width:640px){.ddoc .container{max-width:640px}}@media (min-width:768px){.ddoc .container{max-width:768px}}@media (min-width:1024px){.ddoc .container{max-width:1024px}}@media (min-width:1280px){.ddoc .container{max-width:1280px}}@media (min-width:1536px){.ddoc .container{max-width:1536px}}.ddoc .static{position:static}.ddoc .relative{position:relative}.ddoc .\!mb-0{margin-bottom:0!important}.ddoc .\!mt-2{margin-top:.5rem!important}.ddoc .mb-1{margin-bottom:.25rem}.ddoc .ml-4{margin-left:1rem}.ddoc .ml-indent{margin-left:2ch}.ddoc .mr-2{margin-right:.5rem}.ddoc .mt-3{margin-top:.75rem}.ddoc .inline{display:inline}.ddoc .\!flex{display:flex!important}.ddoc .flex{display:flex}.ddoc .inline-flex{display:inline-flex}.ddoc .table{display:table}.ddoc .contents{display:contents}.ddoc .hidden{display:none}.ddoc .h-4{height:1rem}.ddoc .h-5{height:1.25rem}.ddoc .min-w-0{min-width:0}.ddoc .max-w-\[75ch\]{max-width:75ch}.ddoc .flex-1{flex:1}.ddoc .flex-none{flex:none}.ddoc .rotate-90{--tw-rotate:90deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y))rotate(var(--tw-rotate))skewX(var(--tw-skew-x))skewY(var(--tw-skew-y))scaleX(var(--tw-scale-x))scaleY(var(--tw-scale-y))}.ddoc .scroll-mt-16{scroll-margin-top:4rem}.ddoc .items-center{align-items:center}.ddoc .gap-0{gap:0}.ddoc .gap-0\.5{gap:.125rem}.ddoc .gap-1{gap:.25rem}.ddoc .space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.25rem*var(--tw-space-x-reverse));margin-left:calc(.25rem*calc(1 - var(--tw-space-x-reverse)))}.ddoc .space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*calc(1 - var(--tw-space-x-reverse)))}.ddoc .space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .space-y-7>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.75rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.75rem*var(--tw-space-y-reverse))}.ddoc .space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(2rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem*var(--tw-space-y-reverse))}.ddoc .break-words{overflow-wrap:break-word}.ddoc .break-all{word-break:break-all}.ddoc .rounded{border-radius:.25rem}.ddoc .rounded-md{border-radius:.375rem}.ddoc .border{border-width:1px}.ddoc .border-b{border-bottom-width:1px}.ddoc .border-l-2{border-left-width:2px}.ddoc .border-Class\/50{border-color:#20b44b80}.ddoc .border-Enum\/50{border-color:#22abb080}.ddoc .border-Function\/50{border-color:#056cf080}.ddoc .border-Interface\/50{border-color:#d2a06480}.ddoc .border-Method\/50{border-color:#056cf080}.ddoc .border-Namespace\/50{border-color:#d2564680}.ddoc .border-Property\/50{border-color:#7e57c080}.ddoc .border-TypeAlias\/50{border-color:#a4478c80}.ddoc .border-Variable\/50{border-color:#7e57c080}.ddoc .border-abstract\/50{border-color:#0cafc680}.ddoc .border-deprecated\/50{border-color:#dc262680}.ddoc .border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.ddoc .border-new\/50{border-color:#7b61ff80}.ddoc .border-optional\/50{border-color:#0cafc680}.ddoc .border-other\/50{border-color:#57534e80}.ddoc .border-permissions\/50,.ddoc .border-private\/50{border-color:#0cafc680}.ddoc .border-protected\/50,.ddoc .border-readonly\/50{border-color:#7b61ff80}.ddoc .border-stone-300{--tw-border-opacity:1;border-color:rgb(214 211 209/var(--tw-border-opacity))}.ddoc .border-unstable\/50,.ddoc .border-writeonly\/50{border-color:#7b61ff80}.ddoc .bg-Class\/15{background-color:#20b44b26}.ddoc .bg-Class\/5{background-color:#20b44b0d}.ddoc .bg-Enum\/15{background-color:#22abb026}.ddoc .bg-Enum\/5{background-color:#22abb00d}.ddoc .bg-Function\/15{background-color:#056cf026}.ddoc .bg-Function\/5{background-color:#056cf00d}.ddoc .bg-Interface\/15{background-color:#d2a06426}.ddoc .bg-Interface\/5{background-color:#d2a0640d}.ddoc .bg-Method\/15{background-color:#056cf026}.ddoc .bg-Method\/5{background-color:#056cf00d}.ddoc .bg-Namespace\/15{background-color:#d2564626}.ddoc .bg-Namespace\/5{background-color:#d256460d}.ddoc .bg-Property\/15{background-color:#7e57c026}.ddoc .bg-Property\/5{background-color:#7e57c00d}.ddoc .bg-TypeAlias\/15{background-color:#a4478c26}.ddoc .bg-TypeAlias\/5{background-color:#a4478c0d}.ddoc .bg-Variable\/15{background-color:#7e57c026}.ddoc .bg-Variable\/5{background-color:#7e57c00d}.ddoc .bg-abstract\/15{background-color:#0cafc626}.ddoc .bg-abstract\/5{background-color:#0cafc60d}.ddoc .bg-deprecated\/15{background-color:#dc262626}.ddoc .bg-deprecated\/5{background-color:#dc26260d}.ddoc .bg-new\/15{background-color:#7b61ff26}.ddoc .bg-new\/5{background-color:#7b61ff0d}.ddoc .bg-optional\/15{background-color:#0cafc626}.ddoc .bg-optional\/5{background-color:#0cafc60d}.ddoc .bg-other\/15{background-color:#57534e26}.ddoc .bg-other\/5{background-color:#57534e0d}.ddoc .bg-permissions\/15{background-color:#0cafc626}.ddoc .bg-permissions\/5{background-color:#0cafc60d}.ddoc .bg-private\/15{background-color:#0cafc626}.ddoc .bg-private\/5{background-color:#0cafc60d}.ddoc .bg-protected\/15{background-color:#7b61ff26}.ddoc .bg-protected\/5{background-color:#7b61ff0d}.ddoc .bg-readonly\/15{background-color:#7b61ff26}.ddoc .bg-readonly\/5{background-color:#7b61ff0d}.ddoc .bg-stone-100{--tw-bg-opacity:1;background-color:rgb(245 245 244/var(--tw-bg-opacity))}.ddoc .bg-unstable\/15{background-color:#7b61ff26}.ddoc .bg-unstable\/5{background-color:#7b61ff0d}.ddoc .bg-writeonly\/15{background-color:#7b61ff26}.ddoc .bg-writeonly\/5{background-color:#7b61ff0d}.ddoc .px-2{padding-left:.5rem;padding-right:.5rem}.ddoc .px-3{padding-left:.75rem;padding-right:.75rem}.ddoc .px-4{padding-left:1rem;padding-right:1rem}.ddoc .py-1{padding-top:.25rem;padding-bottom:.25rem}.ddoc .py-2{padding-top:.5rem;padding-bottom:.5rem}.ddoc .pb-5{padding-bottom:1.25rem}.ddoc .pt-4{padding-top:1rem}.ddoc .text-2xl{font-size:1.5rem;line-height:2rem}.ddoc .text-base{font-size:1rem;line-height:1.5rem}.ddoc .text-sm{font-size:.875rem;line-height:1.25rem}.ddoc .font-bold{font-weight:700}.ddoc .font-medium{font-weight:500}.ddoc .font-normal{font-weight:400}.ddoc .italic{font-style:italic}.ddoc .leading-none{line-height:1}.ddoc .text-Class{--tw-text-opacity:1;color:rgb(32 180 75/var(--tw-text-opacity))}.ddoc .text-Enum{--tw-text-opacity:1;color:rgb(34 171 176/var(--tw-text-opacity))}.ddoc .text-Function{--tw-text-opacity:1;color:rgb(5 108 240/var(--tw-text-opacity))}.ddoc .text-Interface{--tw-text-opacity:1;color:rgb(210 160 100/var(--tw-text-opacity))}.ddoc .text-Method{--tw-text-opacity:1;color:rgb(5 108 240/var(--tw-text-opacity))}.ddoc .text-Namespace{--tw-text-opacity:1;color:rgb(210 86 70/var(--tw-text-opacity))}.ddoc .text-Property{--tw-text-opacity:1;color:rgb(126 87 192/var(--tw-text-opacity))}.ddoc .text-TypeAlias{--tw-text-opacity:1;color:rgb(164 71 140/var(--tw-text-opacity))}.ddoc .text-Variable{--tw-text-opacity:1;color:rgb(126 87 192/var(--tw-text-opacity))}.ddoc .text-\[\#0F172A\]{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.ddoc .text-abstract{--tw-text-opacity:1;color:rgb(12 175 198/var(--tw-text-opacity))}.ddoc .text-deprecated{--tw-text-opacity:1;color:rgb(220 38 38/var(--tw-text-opacity))}.ddoc .text-new{--tw-text-opacity:1;color:rgb(123 97 255/var(--tw-text-opacity))}.ddoc .text-optional{--tw-text-opacity:1;color:rgb(12 175 198/var(--tw-text-opacity))}.ddoc .text-other{--tw-text-opacity:1;color:rgb(87 83 78/var(--tw-text-opacity))}.ddoc .text-permissions,.ddoc .text-private{--tw-text-opacity:1;color:rgb(12 175 198/var(--tw-text-opacity))}.ddoc .text-protected,.ddoc .text-readonly{--tw-text-opacity:1;color:rgb(123 97 255/var(--tw-text-opacity))}.ddoc .text-stone-500{--tw-text-opacity:1;color:rgb(120 113 108/var(--tw-text-opacity))}.ddoc .text-unstable,.ddoc .text-writeonly{--tw-text-opacity:1;color:rgb(123 97 255/var(--tw-text-opacity))}.ddoc .filter{filter:var(--tw-blur)var(--tw-brightness)var(--tw-contrast)var(--tw-grayscale)var(--tw-hue-rotate)var(--tw-invert)var(--tw-saturate)var(--tw-sepia)var(--tw-drop-shadow)}.ddoc summary::-webkit-details-marker{display:none}.ddoc a{word-wrap:break-word}.ddoc{--ddoc-selection-border-width:2px;--ddoc-selection-border-color-default:#d6d3d1;--ddoc-selection-selected-border-color:#2564eb;--ddoc-selection-selected-bg:#056cf00c;--ddoc-selection-padding:9px 15px}.ddoc .link{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity));transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:75ms;transition-timing-function:cubic-bezier(.4,0,.2,1)}.ddoc .link:hover{--tw-text-opacity:1;color:rgb(96 165 250/var(--tw-text-opacity))}.ddoc .anchor{float:left;--tw-text-opacity:1;color:rgb(87 83 78/var(--tw-text-opacity));margin-left:-24px;padding:.25rem;line-height:1;display:none;top:0;bottom:0}.ddoc .anchorable{scroll-margin-top:4rem;position:relative}.ddoc .anchorable:hover .anchor{display:block}.ddoc .deprecated>div:first-child{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity));align-items:center;gap:.25rem;padding-top:.25rem;padding-bottom:.25rem;display:flex}.ddoc .deprecated>div:first-child>span{font-weight:600;line-height:1.5rem}.ddoc .deprecated>div:nth-child(2){--tw-border-opacity:1;border-left-width:4px;border-color:rgb(252 165 165/var(--tw-border-opacity));margin-left:.25rem;padding-left:.5rem}.ddoc .symbolSubtitle>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.125rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem*var(--tw-space-y-reverse))}.ddoc .symbolSubtitle{font-size:.875rem;line-height:1rem}.ddoc .symbolSubtitle .type{--tw-text-opacity:1;color:rgb(168 162 158/var(--tw-text-opacity));font-style:italic}.ddoc .docEntry{margin-bottom:1rem}.ddoc .docEntry>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .docEntry .docEntryHeader{justify-content:space-between;align-items:flex-start;display:flex}@media (min-width:768px){.ddoc .docEntry .docEntryHeader{font-size:1rem;line-height:1.5rem}}.ddoc .docEntry .docEntryHeader>div{overflow-wrap:break-word}.ddoc .section{max-width:75ch;margin-bottom:.5rem;scroll-margin-top:4rem}.ddoc .section>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .section>div:first-child>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .section>div:first-child>h2{margin-bottom:0;padding-top:.25rem;padding-bottom:.25rem;font-size:1.25rem;font-weight:600;line-height:1.5rem}.ddoc .section>div:first-child>div{max-width:75ch;font-size:1rem;line-height:1.5rem}.ddoc .namespaceSection{max-width:75ch;margin-top:1rem}.ddoc .namespaceSection>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.ddoc .namespaceSection .namespaceItem{-moz-column-gap:.625rem;column-gap:.625rem;min-height:0;display:flex}@media (min-width:768px){.ddoc .namespaceSection .namespaceItem{min-height:4rem}}@media (min-width:1024px){.ddoc .namespaceSection .namespaceItem{padding-right:1rem}}.ddoc .namespaceSection .namespaceItem .docNodeKindIcon{flex-direction:column;justify-content:flex-start;width:auto}.ddoc .namespaceSection .namespaceItem .docNodeKindIcon>*+*{margin-top:-.125rem;margin-left:0}.ddoc .namespaceSection .namespaceItem[aria-label=deprecated]{opacity:.6}.ddoc .namespaceSection .namespaceItem[aria-label=deprecated] .namespaceItemContent>a{--tw-text-opacity:1;color:rgb(120 113 108/var(--tw-text-opacity));text-decoration-line:line-through;text-decoration-color:#78716cb3;text-decoration-thickness:2px}.ddoc .namespaceSection .namespaceItem .namespaceItemContent>a,.ddoc .namespaceSection .namespaceItem .namespaceItemContent .namespaceItemContentSubItems a{text-decoration-line:underline;text-decoration-color:#d6d3d1}:is(.ddoc .namespaceSection .namespaceItem .namespaceItemContent>a,.ddoc .namespaceSection .namespaceItem .namespaceItemContent .namespaceItemContentSubItems a):hover{text-decoration-line:none}.ddoc .namespaceSection .namespaceItem .namespaceItemContent>a{word-break:break-all;font-weight:500;line-height:1.25;display:block}.ddoc .namespaceSection .namespaceItem .namespaceItemContent .namespaceItemContentDoc{--tw-text-opacity:1;color:rgb(87 83 78/var(--tw-text-opacity));margin-top:.5rem;font-size:.875rem;line-height:1.25rem}.ddoc .namespaceSection .namespaceItem .namespaceItemContent .namespaceItemContentSubItems{flex-wrap:wrap;row-gap:.25rem;margin-top:.375rem;font-size:.875rem;line-height:1.25rem;display:flex}.ddoc .namespaceSection .namespaceItem .namespaceItemContent .namespaceItemContentSubItems>li:not(:last-child):after{content:"|";-webkit-user-select:none;user-select:none;--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity));margin-left:.5rem;margin-right:.5rem}.ddoc .symbolGroup>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(3rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(3rem*var(--tw-space-y-reverse))}.ddoc .symbolGroup article>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem*var(--tw-space-y-reverse))}.ddoc .symbolGroup article>div:first-child{justify-content:space-between;align-items:flex-start;display:flex}.ddoc .symbolGroup article>div:first-child>div:first-child>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.ddoc .symbolGroup article>div:first-child>div:first-child{font-weight:500}.ddoc .docNodeKindIcon{flex-shrink:0;justify-content:flex-end;display:inline-flex}.ddoc .docNodeKindIcon div{-webkit-user-select:none;user-select:none;text-align:center;vertical-align:middle;border-radius:9999px;flex-shrink:0;width:1.25rem;height:1.25rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.75rem;font-weight:500;line-height:1.25rem}.ddoc .docNodeKindIcon>*+*{margin-left:-.375rem}.ddoc .example-header{margin-bottom:.75rem;font-size:1.125rem;font-weight:700;line-height:1.75rem}.ddoc .toc h3{margin-bottom:.75rem;font-size:1.125rem;font-weight:700;line-height:1.75rem}.ddoc .toc>div>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem*var(--tw-space-y-reverse))}.ddoc .toc .topSymbols>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.ddoc .toc .topSymbols{font-size:.875rem;line-height:1.25rem}.ddoc .toc .topSymbols ul{list-style-type:none}.ddoc .toc .topSymbols ul>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.625rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.625rem*var(--tw-space-y-reverse))}.ddoc .toc .topSymbols ul li{display:block}.ddoc .toc .topSymbols ul li a{align-items:center;gap:.5rem;display:flex}.ddoc .toc .topSymbols ul li a>span{text-overflow:ellipsis;white-space:nowrap;border-radius:.25rem;width:100%;margin-top:-.125rem;margin-bottom:-.125rem;margin-left:-.25rem;padding-top:.125rem;padding-bottom:.125rem;padding-left:.25rem;display:block;overflow:hidden}.ddoc .toc .topSymbols>a:hover{text-decoration-line:underline}.ddoc .toc .documentNavigation>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.ddoc .toc .documentNavigation{font-size:.875rem;line-height:1.25rem}@media not all and (min-width:640px){.ddoc .toc .documentNavigation{display:none}}.ddoc .toc .documentNavigation>ul{flex-grow:1;display:block}.ddoc .toc .documentNavigation>ul>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .toc .documentNavigation>ul{overflow-y:auto}.ddoc .toc .documentNavigation>ul>li{margin-top:.125rem;margin-left:.75rem;margin-right:.75rem}.ddoc .toc .documentNavigation>ul li:has(>ul){margin-top:0!important}.ddoc .toc .documentNavigation>ul li:has(>a){padding-bottom:0!important}.ddoc .toc .documentNavigation>ul ul{margin-left:.875rem}.ddoc .toc .documentNavigation>ul ul>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc .toc .documentNavigation>ul ul{--tw-text-opacity:1;color:rgb(134 135 137/var(--tw-text-opacity));font-size:.8rem;line-height:1}.ddoc .toc .documentNavigation>ul ul li{margin-top:.25rem!important}.ddoc .toc .documentNavigation>ul ul li a{padding:.25rem}.ddoc .toc .documentNavigation>ul ul li a:hover{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.ddoc .toc .documentNavigation a{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}.ddoc .toc .documentNavigation a:hover{text-decoration-line:underline}.ddoc .usages nav{flex-direction:row;align-items:center;gap:.5rem;margin-bottom:.75rem;font-weight:600;display:flex}.ddoc .usages nav details>summary{cursor:pointer;-webkit-user-select:none;user-select:none;--tw-border-opacity:1;border-width:1px;border-color:rgb(209 213 219/var(--tw-border-opacity));border-radius:.25rem;gap:.25rem;padding:.5rem .75rem;display:flex}@media (min-width:768px){.ddoc .usages nav details>div{position:relative}}.ddoc .usages nav details>div>div{z-index:30;--tw-border-opacity:1;border-width:1px;border-color:rgb(209 213 219/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity));margin-top:.375rem;padding:.5rem;display:block;position:absolute}@media not all and (min-width:768px){.ddoc .usages nav details>div>div{border-left-width:0;border-right-width:0;left:0;right:0}}@media (min-width:768px){.ddoc .usages nav details>div>div{border-radius:.25rem;width:12rem}}.ddoc .usages nav details>div>div label{cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:.125rem;align-items:center;gap:.5rem;padding:.25rem .5rem;line-height:1.5;display:flex}.ddoc .usages nav details>div>div label:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.ddoc .usageContent>h3{margin-bottom:.75rem;font-size:1.125rem;font-weight:700;line-height:1.75rem}.ddoc .usageContent>div{--tw-text-opacity:1;color:rgb(104 104 104/var(--tw-text-opacity));font-size:.75rem;line-height:1rem}.ddoc .usageContent>div p{margin:0}.ddoc .usageContent pre.highlight{--tw-border-opacity:1;border-width:1px;border-color:rgb(209 213 219/var(--tw-border-opacity));--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}@media not all and (min-width:768px){.ddoc .usageContent pre.highlight{border-left-width:0;border-right-width:0}}.ddoc .usageContent pre.highlight{margin-top:.25rem!important}.ddoc .usageContent pre.highlight>code:first-child{scrollbar-width:thin;padding:.5rem .75rem}.ddoc .usageContent pre.highlight .context_button{border-width:0;display:none;top:.25rem;right:.5rem}.ddoc .usageContent pre.highlight .context_button svg rect{fill:#fff}.ddoc .usageContent pre.highlight:hover .context_button{opacity:1;display:block}.ddoc #categoryPanel{padding-top:.75rem;font-size:.875rem;line-height:1.25rem}.ddoc #categoryPanel ul>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.ddoc #categoryPanel ul{overflow-y:auto}.ddoc #categoryPanel ul li{margin-left:.25rem;margin-right:.75rem}.ddoc #categoryPanel ul li a{text-overflow:ellipsis;white-space:nowrap;padding:.375rem .875rem;display:block;overflow:hidden}.ddoc #categoryPanel ul li a:hover{text-decoration-line:underline}.ddoc .contextLink{color:#0e6590cc;text-underline-offset:.15em;text-decoration-line:underline;text-decoration-color:#0e659080;text-decoration-thickness:1.5px}.ddoc .contextLink:hover{--tw-text-opacity:1;color:rgb(14 101 144/var(--tw-text-opacity));text-decoration-color:#0e6590}.ddoc .contextLink{-webkit-text-decoration-skip:ink;-webkit-text-decoration-skip-ink:auto;text-decoration-skip-ink:auto}.ddoc .breadcrumbs{word-break:break-all;flex-wrap:wrap;align-items:center;gap:.25rem;display:inline-flex}.ddoc .breadcrumbs>li:first-child{font-size:1.5rem;font-weight:700;line-height:1}.ddoc .breadcrumbs li{font-size:1.125rem;line-height:.9em;display:inline}@media (min-width:1024px){.ddoc .breadcrumbs li{font-size:1.25rem;line-height:1.75rem}}.ddoc .context_button{z-index:10;cursor:pointer;background-color:inherit;border-width:1px;border-radius:.25rem;padding:.375rem;line-height:0}.ddoc .context_button:hover{--tw-bg-opacity:1;background-color:rgb(231 229 228/var(--tw-bg-opacity))}.ddoc .see{list-style-type:disc;list-style-position:inside}.ddoc .see>li *{display:inline-block}.ddoc .\*\:h-4>*{height:1rem}.ddoc .\*\:h-5>*{height:1.25rem}.ddoc .\*\:w-auto>*{width:auto}.ddoc .\*\:flex-none>*{flex:none}.ddoc .hover\:bg-Class\/15:hover{background-color:#20b44b26}.ddoc .hover\:bg-Class\/5:hover{background-color:#20b44b0d}.ddoc .hover\:bg-Enum\/15:hover{background-color:#22abb026}.ddoc .hover\:bg-Enum\/5:hover{background-color:#22abb00d}.ddoc .hover\:bg-Function\/15:hover{background-color:#056cf026}.ddoc .hover\:bg-Function\/5:hover{background-color:#056cf00d}.ddoc .hover\:bg-Interface\/15:hover{background-color:#d2a06426}.ddoc .hover\:bg-Interface\/5:hover{background-color:#d2a0640d}.ddoc .hover\:bg-Method\/15:hover{background-color:#056cf026}.ddoc .hover\:bg-Method\/5:hover{background-color:#056cf00d}.ddoc .hover\:bg-Namespace\/15:hover{background-color:#d2564626}.ddoc .hover\:bg-Namespace\/5:hover{background-color:#d256460d}.ddoc .hover\:bg-Property\/15:hover{background-color:#7e57c026}.ddoc .hover\:bg-Property\/5:hover{background-color:#7e57c00d}.ddoc .hover\:bg-TypeAlias\/15:hover{background-color:#a4478c26}.ddoc .hover\:bg-TypeAlias\/5:hover{background-color:#a4478c0d}.ddoc .hover\:bg-Variable\/15:hover{background-color:#7e57c026}.ddoc .hover\:bg-Variable\/5:hover{background-color:#7e57c00d}.ddoc .hover\:bg-abstract\/15:hover{background-color:#0cafc626}.ddoc .hover\:bg-abstract\/5:hover{background-color:#0cafc60d}.ddoc .hover\:bg-deprecated\/15:hover{background-color:#dc262626}.ddoc .hover\:bg-deprecated\/5:hover{background-color:#dc26260d}.ddoc .hover\:bg-new\/15:hover{background-color:#7b61ff26}.ddoc .hover\:bg-new\/5:hover{background-color:#7b61ff0d}.ddoc .hover\:bg-optional\/15:hover{background-color:#0cafc626}.ddoc .hover\:bg-optional\/5:hover{background-color:#0cafc60d}.ddoc .hover\:bg-other\/15:hover{background-color:#57534e26}.ddoc .hover\:bg-other\/5:hover{background-color:#57534e0d}.ddoc .hover\:bg-permissions\/15:hover{background-color:#0cafc626}.ddoc .hover\:bg-permissions\/5:hover{background-color:#0cafc60d}.ddoc .hover\:bg-private\/15:hover{background-color:#0cafc626}.ddoc .hover\:bg-private\/5:hover{background-color:#0cafc60d}.ddoc .hover\:bg-protected\/15:hover{background-color:#7b61ff26}.ddoc .hover\:bg-protected\/5:hover{background-color:#7b61ff0d}.ddoc .hover\:bg-readonly\/15:hover{background-color:#7b61ff26}.ddoc .hover\:bg-readonly\/5:hover{background-color:#7b61ff0d}.ddoc .hover\:bg-unstable\/15:hover{background-color:#7b61ff26}.ddoc .hover\:bg-unstable\/5:hover{background-color:#7b61ff0d}.ddoc .hover\:bg-writeonly\/15:hover{background-color:#7b61ff26}.ddoc .hover\:bg-writeonly\/5:hover{background-color:#7b61ff0d} \ No newline at end of file diff --git a/src/html/tree_sitter.rs b/src/html/tree_sitter.rs deleted file mode 100644 index c620d048..00000000 --- a/src/html/tree_sitter.rs +++ /dev/null @@ -1,295 +0,0 @@ -use std::sync::OnceLock; - -use tree_sitter_highlight::Highlight; -use tree_sitter_highlight::HighlightConfiguration; - -macro_rules! highlighter { - [$($name:literal -> $class:literal,)*] => { - /// The capture names to configure on the highlighter. If this is not - /// configured correctly, the highlighter will not work. - pub const CAPTURE_NAMES: &[&str] = &[$($name),*]; - const CLASSES_ATTRIBUTES: &[&str] = &[$(concat!("class=\"", $class, "\"")),*]; - pub const CLASSES: &[&str] = &[$($class),*]; - }; -} - -highlighter! [ - "attribute" -> "pl-c1", - "comment" -> "pl-c", - "constant.builtin" -> "pl-c1", - "constant" -> "pl-c1", - "constructor" -> "pl-v", - "embedded" -> "pl-s1", - "function" -> "pl-en", - "keyword" -> "pl-k", - "number" -> "pl-c1", - "operator" -> "pl-c1", - "property" -> "pl-c1", - "string" -> "pl-s", - "tag" -> "pl-ent", - "type" -> "pl-smi", - "variable.builtin" -> "pl-smi", -]; - -pub(crate) fn classes(highlight: Highlight) -> &'static [u8] { - CLASSES_ATTRIBUTES[highlight.0].as_bytes() -} - -pub fn tree_sitter_language_cb( - lang: &str, -) -> Option<&'static HighlightConfiguration> { - for lang in lang.split(',') { - let cfg = match lang.trim() { - "js" | "javascript" => tree_sitter_language_javascript(), - "jsx" => tree_sitter_language_jsx(), - "ts" | "typescript" => tree_sitter_language_typescript(), - "tsx" => tree_sitter_language_tsx(), - "json" | "jsonc" => tree_sitter_language_json(), - "css" => tree_sitter_language_css(), - "md" | "markdown" => tree_sitter_language_markdown(), - "xml" => tree_sitter_language_xml(), - "dtd" => tree_sitter_language_dtd(), - "regex" => tree_sitter_language_regex(), - "rs" | "rust" => tree_sitter_language_rust(), - "html" => tree_sitter_language_html(), - "sh" | "bash" => tree_sitter_language_bash(), - _ => continue, - }; - return Some(cfg); - } - None -} - -pub fn tree_sitter_language_javascript() -> &'static HighlightConfiguration { - static CONFIG: OnceLock = OnceLock::new(); - CONFIG.get_or_init(|| { - let mut config = HighlightConfiguration::new( - tree_sitter_javascript::language(), - "javascript", - tree_sitter_javascript::HIGHLIGHT_QUERY, - tree_sitter_javascript::INJECTIONS_QUERY, - tree_sitter_javascript::LOCALS_QUERY, - ) - .expect("failed to initialize tree_sitter_javascript highlighter"); - config.configure(CAPTURE_NAMES); - config - }) -} - -pub fn tree_sitter_language_jsx() -> &'static HighlightConfiguration { - static CONFIG: OnceLock = OnceLock::new(); - CONFIG.get_or_init(|| { - let mut config = HighlightConfiguration::new( - tree_sitter_javascript::language(), - "jsx", - format!( - "{} {}", - tree_sitter_javascript::HIGHLIGHT_QUERY, - tree_sitter_javascript::JSX_HIGHLIGHT_QUERY - ) - .leak(), - tree_sitter_javascript::INJECTIONS_QUERY, - tree_sitter_javascript::LOCALS_QUERY, - ) - .expect("failed to initialize tree_sitter_javascript highlighter"); - config.configure(CAPTURE_NAMES); - config - }) -} - -pub fn tree_sitter_language_typescript() -> &'static HighlightConfiguration { - static CONFIG: OnceLock = OnceLock::new(); - CONFIG.get_or_init(|| { - let mut config = HighlightConfiguration::new( - tree_sitter_typescript::language_typescript(), - "typescript", - format!( - "{} {}", - tree_sitter_javascript::HIGHLIGHT_QUERY, - tree_sitter_typescript::HIGHLIGHTS_QUERY - ) - .leak(), - tree_sitter_javascript::INJECTIONS_QUERY, - format!( - "{} {}", - tree_sitter_javascript::LOCALS_QUERY, - tree_sitter_typescript::LOCALS_QUERY - ) - .leak(), - ) - .expect("failed to initialize tree_sitter_typescript highlighter"); - config.configure(CAPTURE_NAMES); - config - }) -} - -pub fn tree_sitter_language_tsx() -> &'static HighlightConfiguration { - static CONFIG: OnceLock = OnceLock::new(); - CONFIG.get_or_init(|| { - let mut config = HighlightConfiguration::new( - tree_sitter_typescript::language_tsx(), - "tsx", - format!( - "{} {} {}", - tree_sitter_javascript::HIGHLIGHT_QUERY, - tree_sitter_javascript::JSX_HIGHLIGHT_QUERY, - tree_sitter_typescript::HIGHLIGHTS_QUERY, - ) - .leak(), - tree_sitter_javascript::INJECTIONS_QUERY, - format!( - "{} {}", - tree_sitter_javascript::LOCALS_QUERY, - tree_sitter_typescript::LOCALS_QUERY - ) - .leak(), - ) - .expect("failed to initialize tree_sitter_typescript highlighter"); - config.configure(CAPTURE_NAMES); - config - }) -} - -fn tree_sitter_language_json() -> &'static HighlightConfiguration { - static CONFIG: OnceLock = OnceLock::new(); - CONFIG.get_or_init(|| { - let mut config = HighlightConfiguration::new( - tree_sitter_json::language(), - "json", - tree_sitter_json::HIGHLIGHTS_QUERY, - "", - "", - ) - .expect("failed to initialize tree_sitter_json highlighter"); - config.configure(CAPTURE_NAMES); - config - }) -} - -fn tree_sitter_language_css() -> &'static HighlightConfiguration { - static CONFIG: OnceLock = OnceLock::new(); - CONFIG.get_or_init(|| { - let mut config = HighlightConfiguration::new( - tree_sitter_css::language(), - "css", - tree_sitter_css::HIGHLIGHTS_QUERY, - "", - "", - ) - .expect("failed to initialize tree_sitter_css highlighter"); - config.configure(CAPTURE_NAMES); - config - }) -} - -fn tree_sitter_language_markdown() -> &'static HighlightConfiguration { - static CONFIG: OnceLock = OnceLock::new(); - CONFIG.get_or_init(|| { - let mut config = HighlightConfiguration::new( - tree_sitter_md::language(), - "markdown", - tree_sitter_md::HIGHLIGHT_QUERY_BLOCK, - tree_sitter_md::INJECTION_QUERY_BLOCK, - "", - ) - .expect("failed to initialize tree_sitter_md highlighter"); - config.configure(CAPTURE_NAMES); - config - }) -} - -fn tree_sitter_language_xml() -> &'static HighlightConfiguration { - static CONFIG: OnceLock = OnceLock::new(); - CONFIG.get_or_init(|| { - let mut config = HighlightConfiguration::new( - tree_sitter_xml::language_xml(), - "xml", - tree_sitter_xml::XML_HIGHLIGHT_QUERY, - "", - "", - ) - .expect("failed to initialize tree_sitter_xml highlighter"); - config.configure(CAPTURE_NAMES); - config - }) -} - -fn tree_sitter_language_dtd() -> &'static HighlightConfiguration { - static CONFIG: OnceLock = OnceLock::new(); - CONFIG.get_or_init(|| { - let mut config = HighlightConfiguration::new( - tree_sitter_xml::language_dtd(), - "dtd", - tree_sitter_xml::DTD_HIGHLIGHT_QUERY, - "", - "", - ) - .expect("failed to initialize tree_sitter_dtd highlighter"); - config.configure(CAPTURE_NAMES); - config - }) -} - -fn tree_sitter_language_regex() -> &'static HighlightConfiguration { - static CONFIG: OnceLock = OnceLock::new(); - CONFIG.get_or_init(|| { - let mut config = HighlightConfiguration::new( - tree_sitter_regex::language(), - "regex", - tree_sitter_regex::HIGHLIGHTS_QUERY, - "", - "", - ) - .expect("failed to initialize tree_sitter_regex highlighter"); - config.configure(CAPTURE_NAMES); - config - }) -} - -fn tree_sitter_language_rust() -> &'static HighlightConfiguration { - static CONFIG: OnceLock = OnceLock::new(); - CONFIG.get_or_init(|| { - let mut config = HighlightConfiguration::new( - tree_sitter_rust::language(), - "rust", - tree_sitter_rust::HIGHLIGHTS_QUERY, - tree_sitter_rust::INJECTIONS_QUERY, - "", - ) - .expect("failed to initialize tree_sitter_rust highlighter"); - config.configure(CAPTURE_NAMES); - config - }) -} - -fn tree_sitter_language_html() -> &'static HighlightConfiguration { - static CONFIG: OnceLock = OnceLock::new(); - CONFIG.get_or_init(|| { - let mut config = HighlightConfiguration::new( - tree_sitter_html::language(), - "html", - tree_sitter_html::HIGHLIGHTS_QUERY, - tree_sitter_html::INJECTIONS_QUERY, - "", - ) - .expect("failed to initialize tree_sitter_html highlighter"); - config.configure(CAPTURE_NAMES); - config - }) -} - -fn tree_sitter_language_bash() -> &'static HighlightConfiguration { - static CONFIG: OnceLock = OnceLock::new(); - CONFIG.get_or_init(|| { - let mut config = HighlightConfiguration::new( - tree_sitter_bash::language(), - "bash", - tree_sitter_bash::HIGHLIGHT_QUERY, - "", - "", - ) - .expect("failed to initialize tree_sitter_bash highlighter"); - config.configure(CAPTURE_NAMES); - config - }) -} diff --git a/src/html/usage.rs b/src/html/usage.rs index 3351c3cd..5b8fd4ab 100644 --- a/src/html/usage.rs +++ b/src/html/usage.rs @@ -4,8 +4,10 @@ use super::RenderContext; use super::UrlResolveKind; use crate::js_doc::JsDocTag; use crate::DocNodeKind; +use indexmap::IndexMap; use regex::Regex; use serde::Serialize; +use std::borrow::Cow; lazy_static! { static ref IDENTIFIER_RE: Regex = Regex::new(r"[^a-zA-Z$_]").unwrap(); @@ -28,7 +30,7 @@ fn render_css_for_usage(name: &str) -> String { ) } -pub fn usage_to_md( +fn usage_to_md( ctx: &RenderContext, doc_nodes: &[DocNodeWithContext], url: &str, @@ -162,6 +164,12 @@ fn get_identifier_for_file(ctx: &RenderContext) -> String { ) } +#[cfg(not(feature = "rust"))] +pub type UsageToMd<'a> = &'a js_sys::Function; + +#[cfg(feature = "rust")] +pub type UsageToMd<'a> = &'a dyn Fn(&[DocNodeWithContext], &str) -> String; + #[derive(Clone, Debug, Serialize)] struct UsageCtx { name: String, @@ -183,52 +191,61 @@ impl UsagesCtx { ctx: &RenderContext, doc_nodes: &[DocNodeWithContext], ) -> Option { - if ctx.ctx.usage_composer.is_none() - && ctx.ctx.file_mode == FileMode::SingleDts - { + let is_single_mode = ctx.ctx.usage_composer.is_single_mode(); + + if is_single_mode && ctx.ctx.file_mode == FileMode::SingleDts { return None; } - let url = ctx - .ctx - .href_resolver - .resolve_usage(ctx.get_current_resolve())?; + let usage_ctx = ctx.clone(); + let usage_to_md_closure = move |nodes: &[DocNodeWithContext], url: &str| { + usage_to_md(&usage_ctx, nodes, url) + }; - if let Some(usage_composer) = &ctx.ctx.usage_composer { - let usages = usage_composer(ctx, doc_nodes, url); + let usages = ctx.ctx.usage_composer.compose( + doc_nodes, + ctx.get_current_resolve(), + &usage_to_md_closure, + ); + if usages.is_empty() { + None + } else { let usages = usages .into_iter() .map(|(entry, content)| UsageCtx { - additional_css: render_css_for_usage(&entry.name), + additional_css: if is_single_mode { + String::new() + } else { + render_css_for_usage(&entry.name) + }, name: entry.name, icon: entry.icon, content: crate::html::jsdoc::render_markdown(ctx, &content, true), }) .collect::>(); - if usages.is_empty() { - None - } else { - Some(UsagesCtx { - usages, - composed: true, - }) - } - } else { - let import_statement = usage_to_md(ctx, doc_nodes, &url); - let rendered_import_statement = - crate::html::jsdoc::render_markdown(ctx, &import_statement, true); - Some(UsagesCtx { - usages: vec![UsageCtx { - name: "".to_string(), - content: rendered_import_statement, - icon: None, - additional_css: "".to_string(), - }], - composed: false, + usages, + composed: !is_single_mode, }) } } } + +#[derive(Eq, PartialEq, Hash)] +pub struct UsageComposerEntry { + pub name: String, + pub icon: Option>, +} + +pub trait UsageComposer { + fn is_single_mode(&self) -> bool; + + fn compose( + &self, + doc_nodes: &[DocNodeWithContext], + current_resolve: UrlResolveKind, + usage_to_md: UsageToMd, + ) -> IndexMap; +} diff --git a/src/html/util.rs b/src/html/util.rs index 45b00120..285287f0 100644 --- a/src/html/util.rs +++ b/src/html/util.rs @@ -306,9 +306,6 @@ pub trait HrefResolver { fn resolve_import_href(&self, symbol: &[String], src: &str) -> Option; - /// Resolve the URL used in "usage" blocks. - fn resolve_usage(&self, current_resolve: UrlResolveKind) -> Option; - /// Resolve the URL used in source code link buttons. fn resolve_source(&self, location: &crate::Location) -> Option; diff --git a/tests/html_test.rs b/tests/html_test.rs index e3780b47..f1723ce1 100644 --- a/tests/html_test.rs +++ b/tests/html_test.rs @@ -45,7 +45,7 @@ impl Loader for SourceFileLoader { } } -struct EmptyResolver {} +struct EmptyResolver; impl HrefResolver for EmptyResolver { fn resolve_path( @@ -68,12 +68,6 @@ impl HrefResolver for EmptyResolver { None } - fn resolve_usage(&self, current_resolve: UrlResolveKind) -> Option { - current_resolve - .get_file() - .map(|current_file| current_file.path.to_string()) - } - fn resolve_source(&self, _location: &deno_doc::Location) -> Option { None } @@ -87,6 +81,32 @@ impl HrefResolver for EmptyResolver { } } +impl UsageComposer for EmptyResolver { + fn is_single_mode(&self) -> bool { + true + } + + fn compose( + &self, + doc_nodes: &[DocNodeWithContext], + current_resolve: UrlResolveKind, + usage_to_md: UsageToMd, + ) -> IndexMap { + current_resolve + .get_file() + .map(|current_file| { + IndexMap::from([( + UsageComposerEntry { + name: "".to_string(), + icon: None, + }, + usage_to_md(doc_nodes, current_file.path.as_str()), + )]) + }) + .unwrap_or_default() + } +} + async fn get_files(subpath: &str) -> IndexMap> { let files = fs::read_dir( std::env::current_dir() @@ -146,13 +166,16 @@ async fn html_doc_files() { GenerateOptions { package_name: None, main_entrypoint: None, - href_resolver: Rc::new(EmptyResolver {}), - usage_composer: None, + href_resolver: Rc::new(EmptyResolver), + usage_composer: Rc::new(EmptyResolver), rewrite_map: None, category_docs: None, disable_search: false, symbol_redirect_map: None, default_symbol_map: None, + markdown_renderer: comrak::create_renderer(None, None, None), + markdown_stripper: Rc::new(comrak::strip), + head_inject: None, }, get_files("single").await, ) @@ -172,6 +195,7 @@ async fn html_doc_files() { "./~/Foo.prototype.html", "./~/Foobar.html", "./~/Foobar.prototype.html", + "comrak.css", "fuse.js", "page.css", "reset.css", @@ -182,7 +206,6 @@ async fn html_doc_files() { ] ); - #[cfg(feature = "tree-sitter")] { insta::assert_snapshot!(files.get("./all_symbols.html").unwrap()); insta::assert_snapshot!(files.get("./index.html").unwrap()); @@ -219,13 +242,16 @@ async fn html_doc_files_rewrite() { GenerateOptions { package_name: None, main_entrypoint: Some(main_specifier), - href_resolver: Rc::new(EmptyResolver {}), - usage_composer: None, + href_resolver: Rc::new(EmptyResolver), + usage_composer: Rc::new(EmptyResolver), rewrite_map: Some(rewrite_map), category_docs: None, disable_search: false, symbol_redirect_map: None, default_symbol_map: None, + markdown_renderer: comrak::create_renderer(None, None, None), + markdown_stripper: Rc::new(comrak::strip), + head_inject: None, }, get_files("multiple").await, ) @@ -260,6 +286,7 @@ async fn html_doc_files_rewrite() { "./~/c.html", "./~/d.html", "./~/qaz.html", + "comrak.css", "foo/index.html", "foo/~/default.html", "foo/~/x.html", @@ -273,7 +300,6 @@ async fn html_doc_files_rewrite() { ] ); - #[cfg(feature = "tree-sitter")] { insta::assert_snapshot!(files.get("./all_symbols.html").unwrap()); insta::assert_snapshot!(files.get("./index.html").unwrap()); @@ -327,13 +353,16 @@ async fn symbol_group() { main_entrypoint: Some( ModuleSpecifier::from_file_path(multiple_dir.join("a.ts")).unwrap(), ), - href_resolver: Rc::new(EmptyResolver {}), - usage_composer: None, + href_resolver: Rc::new(EmptyResolver), + usage_composer: Rc::new(EmptyResolver), rewrite_map: Some(rewrite_map), category_docs: None, disable_search: false, symbol_redirect_map: None, default_symbol_map: None, + markdown_renderer: comrak::create_renderer(None, None, None), + markdown_stripper: Rc::new(comrak::strip), + head_inject: None, }, None, Default::default(), @@ -365,11 +394,10 @@ async fn symbol_group() { ); let html_head_ctx = pages::HtmlHeadCtx::new( + &ctx, &root, Some(&symbol_group_ctx.name), - ctx.package_name.as_ref(), Some(short_path), - false, ); Some(pages::SymbolPageCtx { @@ -387,7 +415,6 @@ async fn symbol_group() { } } - #[cfg(feature = "tree-sitter")] insta::assert_json_snapshot!(files); } @@ -417,13 +444,16 @@ async fn symbol_search() { main_entrypoint: Some( ModuleSpecifier::from_file_path(multiple_dir.join("a.ts")).unwrap(), ), - href_resolver: Rc::new(EmptyResolver {}), - usage_composer: None, + href_resolver: Rc::new(EmptyResolver), + usage_composer: Rc::new(EmptyResolver), rewrite_map: Some(rewrite_map), category_docs: None, disable_search: false, symbol_redirect_map: None, default_symbol_map: None, + markdown_renderer: comrak::create_renderer(None, None, None), + markdown_stripper: Rc::new(comrak::strip), + head_inject: None, }, None, Default::default(), @@ -462,13 +492,16 @@ async fn module_doc() { main_entrypoint: Some( ModuleSpecifier::from_file_path(multiple_dir.join("a.ts")).unwrap(), ), - href_resolver: Rc::new(EmptyResolver {}), - usage_composer: None, + href_resolver: Rc::new(EmptyResolver), + usage_composer: Rc::new(EmptyResolver), rewrite_map: Some(rewrite_map), category_docs: None, disable_search: false, symbol_redirect_map: None, default_symbol_map: None, + markdown_renderer: comrak::create_renderer(None, None, None), + markdown_stripper: Rc::new(comrak::strip), + head_inject: None, }, None, FileMode::Single, @@ -486,6 +519,5 @@ async fn module_doc() { module_docs.push(module_doc); } - #[cfg(feature = "tree-sitter")] insta::assert_json_snapshot!(module_docs); } diff --git a/tests/snapshots/html_test__html_doc_files-2.snap b/tests/snapshots/html_test__html_doc_files-2.snap index 59d9cabd..0736aa73 100644 --- a/tests/snapshots/html_test__html_doc_files-2.snap +++ b/tests/snapshots/html_test__html_doc_files-2.snap @@ -11,13 +11,10 @@ expression: "files.get(\"./index.html\").unwrap()" - - - + - - +