diff --git a/Cargo.lock b/Cargo.lock
index a6bdf40..352f5b5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5787,6 +5787,7 @@ dependencies = [
"pin-utils",
"regex",
"reqwest",
+ "smol_str",
"syntect",
"thiserror 2.0.17",
"tokio",
diff --git a/crates/weaver-renderer/Cargo.toml b/crates/weaver-renderer/Cargo.toml
index d135e50..f91d98f 100644
--- a/crates/weaver-renderer/Cargo.toml
+++ b/crates/weaver-renderer/Cargo.toml
@@ -28,6 +28,7 @@ pin-utils = "0.1.0"
pin-project = "1.1.10"
dynosaur = "0.2.0"
async-trait = "0.1.88"
+smol_str = { version = "0.3", features = ["serde"] }
reqwest = { version = "0.12.7", default-features = false, features = [
"json",
"rustls-tls",
diff --git a/crates/weaver-renderer/src/code_pretty.rs b/crates/weaver-renderer/src/code_pretty.rs
index 30308e1..28fbde5 100644
--- a/crates/weaver-renderer/src/code_pretty.rs
+++ b/crates/weaver-renderer/src/code_pretty.rs
@@ -9,7 +9,7 @@ use syntect::util::LinesWithEndings;
/// This requires an external stylesheet, also generated by syntect to be loaded by the page.
/// The syntect SyntaxSet is also provided, so that it is not re-created on every call.
pub fn highlight(
- syn_set: SyntaxSet,
+ syn_set: &SyntaxSet,
lang: Option<&str>,
code: impl AsRef,
writer: &mut M,
@@ -33,7 +33,7 @@ where
let mut html_gen = ClassedHTMLGenerator::new_with_class_style(
lang_syn,
- &syn_set,
+ syn_set,
ClassStyle::SpacedPrefixed { prefix: CSS_PREFIX },
);
for line in LinesWithEndings::from(code.as_ref()) {
diff --git a/crates/weaver-renderer/src/css.rs b/crates/weaver-renderer/src/css.rs
new file mode 100644
index 0000000..cf9e983
--- /dev/null
+++ b/crates/weaver-renderer/src/css.rs
@@ -0,0 +1,200 @@
+use crate::theme::Theme;
+use miette::IntoDiagnostic;
+use syntect::highlighting::ThemeSet;
+use syntect::html::{ClassStyle, css_for_theme_with_class_style};
+use syntect::parsing::SyntaxSet;
+
+pub fn generate_base_css(theme: &Theme) -> String {
+ format!(
+ r#"/* CSS Reset */
+*, *::before, *::after {{
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}}
+
+/* CSS Variables */
+:root {{
+ --color-background: {};
+ --color-foreground: {};
+ --color-link: {};
+ --color-link-hover: {};
+
+ --font-body: {};
+ --font-heading: {};
+ --font-mono: {};
+
+ --spacing-base: {};
+ --spacing-line-height: {};
+ --spacing-scale: {};
+}}
+
+/* Base Styles */
+html {{
+ font-size: var(--spacing-base);
+ line-height: var(--spacing-line-height);
+}}
+
+body {{
+ font-family: var(--font-body);
+ color: var(--color-foreground);
+ background-color: var(--color-background);
+ max-width: 65ch;
+ margin: 0 auto;
+ padding: 2rem 1rem;
+}}
+
+/* Typography */
+h1, h2, h3, h4, h5, h6 {{
+ font-family: var(--font-heading);
+ margin-top: calc(1rem * var(--spacing-scale));
+ margin-bottom: 0.5rem;
+ line-height: 1.2;
+}}
+
+h1 {{ font-size: 2.5rem; }}
+h2 {{ font-size: 2rem; }}
+h3 {{ font-size: 1.5rem; }}
+h4 {{ font-size: 1.25rem; }}
+h5 {{ font-size: 1.125rem; }}
+h6 {{ font-size: 1rem; }}
+
+p {{
+ margin-bottom: 1rem;
+}}
+
+a {{
+ color: var(--color-link);
+ text-decoration: none;
+}}
+
+a:hover {{
+ color: var(--color-link-hover);
+ text-decoration: underline;
+}}
+
+/* Lists */
+ul, ol {{
+ margin-left: 2rem;
+ margin-bottom: 1rem;
+}}
+
+li {{
+ margin-bottom: 0.25rem;
+}}
+
+/* Code */
+code {{
+ font-family: var(--font-mono);
+ background-color: rgba(0, 0, 0, 0.05);
+ padding: 0.125rem 0.25rem;
+ border-radius: 3px;
+ font-size: 0.9em;
+}}
+
+pre {{
+ overflow-x: auto;
+ margin-bottom: 1rem;
+}}
+
+pre code {{
+ display: block;
+ padding: 1rem;
+ background-color: rgba(0, 0, 0, 0.03);
+ border-radius: 5px;
+}}
+
+/* Math */
+.math {{
+ font-family: var(--font-mono);
+}}
+
+.math-display {{
+ display: block;
+ margin: 1rem 0;
+ text-align: center;
+}}
+
+/* Blockquotes */
+blockquote {{
+ border-left: 4px solid var(--color-link);
+ padding-left: 1rem;
+ margin: 1rem 0;
+ font-style: italic;
+}}
+
+/* Tables */
+table {{
+ border-collapse: collapse;
+ width: 100%;
+ margin-bottom: 1rem;
+}}
+
+th, td {{
+ border: 1px solid rgba(0, 0, 0, 0.1);
+ padding: 0.5rem;
+ text-align: left;
+}}
+
+th {{
+ background-color: rgba(0, 0, 0, 0.05);
+ font-weight: 600;
+}}
+
+/* Footnotes */
+.footnote-reference {{
+ font-size: 0.8em;
+}}
+
+.footnote-definition {{
+ margin-top: 2rem;
+ padding-top: 0.5rem;
+ border-top: 1px solid rgba(0, 0, 0, 0.1);
+ font-size: 0.9em;
+}}
+
+.footnote-definition-label {{
+ font-weight: 600;
+ margin-right: 0.5rem;
+}}
+
+/* Horizontal Rule */
+hr {{
+ border: none;
+ border-top: 1px solid rgba(0, 0, 0, 0.1);
+ margin: 2rem 0;
+}}
+"#,
+ theme.colors.background,
+ theme.colors.foreground,
+ theme.colors.link,
+ theme.colors.link_hover,
+ theme.fonts.body,
+ theme.fonts.heading,
+ theme.fonts.monospace,
+ theme.spacing.base_font_size,
+ theme.spacing.line_height,
+ theme.spacing.scale,
+ )
+}
+
+pub fn generate_syntax_css(
+ syntect_theme_name: &str,
+ _syntax_set: &SyntaxSet,
+) -> miette::Result {
+ let theme_set = ThemeSet::load_defaults();
+ let theme = theme_set
+ .themes
+ .get(syntect_theme_name)
+ .ok_or_else(|| miette::miette!("Theme '{}' not found", syntect_theme_name))?;
+
+ let css = css_for_theme_with_class_style(
+ theme,
+ ClassStyle::SpacedPrefixed {
+ prefix: crate::code_pretty::CSS_PREFIX,
+ },
+ )
+ .into_diagnostic()?;
+
+ Ok(css)
+}
diff --git a/crates/weaver-renderer/src/lib.rs b/crates/weaver-renderer/src/lib.rs
index de75b48..a4d18e4 100644
--- a/crates/weaver-renderer/src/lib.rs
+++ b/crates/weaver-renderer/src/lib.rs
@@ -27,7 +27,9 @@ use std::task::Poll;
pub mod atproto;
pub mod base_html;
pub mod code_pretty;
+pub mod css;
pub mod static_site;
+pub mod theme;
pub mod types;
pub mod utils;
pub mod walker;
diff --git a/crates/weaver-renderer/src/static_site.rs b/crates/weaver-renderer/src/static_site.rs
index 3e01fe7..a52e090 100644
--- a/crates/weaver-renderer/src/static_site.rs
+++ b/crates/weaver-renderer/src/static_site.rs
@@ -5,45 +5,30 @@
//! URLs in the notebook are mostly unaltered. It is compatible with GitHub or Cloudflare Pages
//! and other similar static hosting services.
-use std::{
- path::{Path, PathBuf},
- sync::Arc,
-};
+pub mod context;
+pub mod document;
+pub mod writer;
use crate::{
ContextIterator, NotebookProcessor,
- base_html::TableState,
+ static_site::{context::StaticSiteContext, writer::StaticPageWriter},
utils::flatten_dir_to_just_one_parent,
walker::{WalkOptions, vault_contents},
};
use bitflags::bitflags;
-use dashmap::DashMap;
-use markdown_weaver::{
- Alignment, BlockQuoteKind, BrokenLink, CodeBlockKind, CowStr, EmbedType, Event, LinkType,
- Parser, Tag, WeaverAttributes,
-};
-use markdown_weaver_escape::{
- FmtWriter, StrWrite, escape_href, escape_html, escape_html_body_text,
-};
+use markdown_weaver::{BrokenLink, CowStr, Parser};
+use markdown_weaver_escape::FmtWriter;
use miette::IntoDiagnostic;
#[cfg(all(target_family = "wasm", target_os = "unknown"))]
use n0_future::io::AsyncWriteExt;
-use n0_future::{IterExt, StreamExt};
+use std::{
+ path::{Path, PathBuf},
+ sync::Arc,
+};
#[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
use tokio::io::AsyncWriteExt;
use unicode_normalization::UnicodeNormalization;
-use weaver_common::{
- aturi_to_http,
- jacquard::{
- client::{Agent, AgentSession, AgentSessionExt},
- identity::resolver::IdentityError,
- prelude::*,
- types::blob::MimeType,
- },
-};
-use yaml_rust2::Yaml;
-
-use crate::{Frontmatter, NotebookContext};
+use weaver_common::jacquard::{client::AgentSession, prelude::*};
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -88,413 +73,14 @@ pub fn default_md_options() -> markdown_weaver::Options {
| markdown_weaver::Options::ENABLE_HEADING_ATTRIBUTES
}
-pub struct StaticSiteContext<'a, A: AgentSession> {
- options: StaticSiteOptions,
- md_options: markdown_weaver::Options,
- pub bsky_appview: CowStr<'a>,
- root: PathBuf,
- pub destination: PathBuf,
- start_at: PathBuf,
- pub frontmatter: Arc>,
- dir_contents: Option>,
- reference_map: Arc, PathBuf>>,
- titles: Arc>>,
- position: usize,
- client: Option,
- agent: Option>>,
-}
-
-impl<'a, A: AgentSession> Clone for StaticSiteContext<'a, A> {
- fn clone(&self) -> Self {
- Self {
- options: self.options.clone(),
- md_options: self.md_options.clone(),
- bsky_appview: self.bsky_appview.clone(),
- root: self.root.clone(),
- destination: self.destination.clone(),
- start_at: self.start_at.clone(),
- frontmatter: self.frontmatter.clone(),
- dir_contents: self.dir_contents.clone(),
- reference_map: self.reference_map.clone(),
- titles: self.titles.clone(),
- position: self.position.clone(),
- client: self.client.clone(),
- agent: self.agent.clone(),
- }
- }
-}
-
-impl StaticSiteContext<'_, A> {
- pub fn clone_with_dir_contents(&self, dir_contents: &[PathBuf]) -> Self {
- Self {
- start_at: self.start_at.clone(),
- root: self.root.clone(),
- bsky_appview: self.bsky_appview.clone(),
- options: self.options.clone(),
- md_options: self.md_options.clone(),
- frontmatter: self.frontmatter.clone(),
- dir_contents: Some(Arc::from(dir_contents)),
- destination: self.destination.clone(),
- reference_map: self.reference_map.clone(),
- titles: self.titles.clone(),
- position: self.position,
- client: self.client.clone(),
- agent: self.agent.clone(),
- }
- }
-
- pub fn clone_with_path(&self, path: impl AsRef) -> Self {
- let position = if let Some(dir_contents) = &self.dir_contents {
- dir_contents
- .iter()
- .position(|p| p == path.as_ref())
- .unwrap_or(0)
- } else {
- 0
- };
- Self {
- start_at: self.start_at.clone(),
- root: self.root.clone(),
- bsky_appview: self.bsky_appview.clone(),
- options: self.options.clone(),
- md_options: self.md_options.clone(),
- frontmatter: self.frontmatter.clone(),
- dir_contents: self.dir_contents.clone(),
- destination: self.destination.clone(),
- reference_map: self.reference_map.clone(),
- titles: self.titles.clone(),
- position,
- client: Some(reqwest::Client::default()),
- agent: self.agent.clone(),
- }
- }
- pub fn new(root: PathBuf, destination: PathBuf, session: Option) -> Self {
- Self {
- start_at: root.clone(),
- root,
- bsky_appview: CowStr::Borrowed("deer.social"),
- options: StaticSiteOptions::default(),
- md_options: default_md_options(),
- frontmatter: Arc::new(DashMap::new()),
- dir_contents: None,
- destination,
- reference_map: Arc::new(DashMap::new()),
- titles: Arc::new(DashMap::new()),
- position: 0,
- client: Some(reqwest::Client::default()),
- agent: session.map(|session| Arc::new(Agent::new(session))),
- }
- }
-
- pub fn current_path(&self) -> &PathBuf {
- if let Some(dir_contents) = &self.dir_contents {
- &dir_contents[self.position]
- } else {
- &self.start_at
- }
- }
-
- #[inline]
- pub fn handle_link_aturi<'s>(&self, link: Tag<'s>) -> Tag<'s> {
- let link = crate::utils::resolve_at_ident_or_uri(&link, &self.bsky_appview);
- self.handle_link_normal(link)
- }
-
- pub async fn handle_embed_aturi<'s>(&self, embed: Tag<'s>) -> Tag<'s> {
- match &embed {
- Tag::Embed {
- embed_type,
- dest_url,
- title,
- id,
- attrs,
- } => {
- if dest_url.starts_with("at://") {
- let width = if let Some(attrs) = attrs {
- let mut width = 600;
- for attr in &attrs.attrs {
- if attr.0 == CowStr::Borrowed("width".into()) {
- width = attr.1.parse::().unwrap_or(600);
- break;
- }
- }
- width
- } else {
- 600
- };
- let html = if let Some(client) = &self.client {
- if let Ok(resp) = client
- .get("https://embed.bsky.app/oembed")
- .query(&[
- ("url", dest_url.clone().into_string()),
- ("maxwidth", width.to_string()),
- ])
- .send()
- .await
- {
- resp.text().await.ok()
- } else {
- None
- }
- } else {
- None
- };
- if let Some(html) = html {
- let link = aturi_to_http(&dest_url, &self.bsky_appview)
- .expect("assuming the at-uri is valid rn");
- let mut attrs = if let Some(attrs) = attrs {
- attrs.clone()
- } else {
- WeaverAttributes {
- classes: vec![],
- attrs: vec![],
- }
- };
- attrs.attrs.push(("content".into(), html.into()));
- Tag::Embed {
- embed_type: EmbedType::Comments, // change this when i update markdown-weaver
- dest_url: link.into_static(),
- title: title.clone(),
- id: id.clone(),
- attrs: Some(attrs),
- }
- } else {
- self.handle_embed_normal(embed).await
- }
- } else {
- self.handle_embed_normal(embed).await
- }
- }
- _ => embed,
- }
- }
-
- pub async fn handle_embed_normal<'s>(&self, embed: Tag<'s>) -> Tag<'s> {
- // This option will REALLY slow down iteration over events.
- if self.options.contains(StaticSiteOptions::INLINE_EMBEDS) {
- match &embed {
- Tag::Embed {
- embed_type: _,
- dest_url,
- title,
- id,
- attrs,
- } => {
- let mut attrs = if let Some(attrs) = attrs {
- attrs.clone()
- } else {
- WeaverAttributes {
- classes: vec![],
- attrs: vec![],
- }
- };
- let contents = if crate::utils::is_local_path(dest_url) {
- let file_path = if crate::utils::is_relative_link(dest_url) {
- let root_path = self.root.clone();
- root_path.join(Path::new(&dest_url as &str))
- } else {
- PathBuf::from(&dest_url as &str)
- };
- crate::utils::inline_file(&file_path).await
- } else if let Some(client) = &self.client {
- if let Ok(resp) = client.get(dest_url.clone().into_string()).send().await {
- resp.text().await.ok()
- } else {
- None
- }
- } else {
- None
- };
- if let Some(contents) = contents {
- attrs.attrs.push(("content".into(), contents.into()));
- Tag::Embed {
- embed_type: EmbedType::Markdown, // change this when i update markdown-weaver
- dest_url: dest_url.clone(),
- title: title.clone(),
- id: id.clone(),
- attrs: Some(attrs),
- }
- } else {
- embed
- }
- }
- _ => embed,
- }
- } else {
- embed
- }
- }
-
- /// This is a no-op for the static site renderer currently.
- #[inline]
- pub fn handle_link_normal<'s>(&self, link: Tag<'s>) -> Tag<'s> {
- link
- }
-
- /// This is a no-op for the static site renderer currently.
- #[inline]
- pub fn handle_image_normal<'s>(&self, image: Tag<'s>) -> Tag<'s> {
- image
- }
-}
-
-impl StaticSiteContext<'_, A> {
- /// TODO: rework this a bit, to not just do the same thing as whitewind
- /// (also need to make a record to refer to them) that being said, doing
- /// this with the static site renderer isn't *really* the standard workflow
- pub async fn upload_image<'s>(&self, image: Tag<'s>) -> Tag<'s> {
- if let Some(agent) = &self.agent {
- match &image {
- Tag::Image {
- link_type,
- dest_url,
- title,
- id,
- attrs,
- } => {
- if crate::utils::is_local_path(&dest_url) {
- let root_path = self.root.clone();
- let file_path = root_path.join(Path::new(&dest_url as &str));
- if let Ok(image_data) = std::fs::read(&file_path) {
- if let Ok(blob) = agent
- .upload_blob(image_data, MimeType::new_static("image/jpg"))
- .await
- {
- let (did, _) = agent.info().await.unwrap();
- let url = weaver_common::blob_url(
- &did,
- agent.endpoint().await.as_str(),
- &blob.r#ref.0,
- );
- return Tag::Image {
- link_type: *link_type,
- dest_url: url.into(),
- title: title.clone(),
- id: id.clone(),
- attrs: attrs.clone(),
- };
- }
- }
- }
- }
- _ => {}
- }
- }
- image
- }
-}
-
-impl NotebookContext for StaticSiteContext<'_, A> {
- fn set_entry_title(&self, title: CowStr<'_>) {
- let path = self.current_path();
- self.titles
- .insert(path.clone(), title.clone().into_static());
- self.frontmatter.get_mut(path).map(|frontmatter| {
- if let Ok(mut yaml) = frontmatter.yaml.write() {
- if yaml.get(0).is_some_and(|y| y.is_hash()) {
- let map = yaml.get_mut(0).unwrap().as_mut_hash().unwrap();
- map.insert(
- Yaml::String("title".into()),
- Yaml::String(title.into_static().into()),
- );
- }
- }
- });
- }
- fn entry_title(&self) -> CowStr<'_> {
- let path = self.current_path();
- self.titles.get(path).unwrap().clone()
- }
-
- fn frontmatter(&self) -> Frontmatter {
- let path = self.current_path();
- self.frontmatter.get(path).unwrap().value().clone()
- }
-
- fn set_frontmatter(&self, frontmatter: Frontmatter) {
- let path = self.current_path();
- self.frontmatter.insert(path.clone(), frontmatter);
- }
-
- async fn handle_link<'s>(&self, link: Tag<'s>) -> Tag<'s> {
- bitflags::bitflags_match!(self.options, {
- // Split this somehow or just combine the options
- StaticSiteOptions::RESOLVE_AT_URIS | StaticSiteOptions::RESOLVE_AT_IDENTIFIERS => {
- self.handle_link_aturi(link)
- }
- _ => match &link {
- Tag::Link { link_type, dest_url, title, id } => {
- if self.options.contains(StaticSiteOptions::FLATTEN_STRUCTURE) {
- let (parent, filename) = crate::utils::flatten_dir_to_just_one_parent(&dest_url);
- let dest_url = if crate::utils::is_relative_link(&dest_url)
- && self.options.contains(StaticSiteOptions::CREATE_CHAPTERS_BY_DIRECTORY) {
- if !parent.is_empty() {
- CowStr::Boxed(format!("./{}/{}", parent, filename).into_boxed_str())
- } else {
- CowStr::Boxed(format!("./{}", filename).into_boxed_str())
- }
- } else {
- CowStr::Boxed(format!("./entry/{}", filename).into_boxed_str())
- };
- Tag::Link {
- link_type: *link_type,
- dest_url,
- title: title.clone(),
- id: id.clone(),
- }
- } else {
- link
-
- }
- },
- _ => link,
- }
- })
- }
-
- async fn handle_image<'s>(&self, image: Tag<'s>) -> Tag<'s> {
- if self.options.contains(StaticSiteOptions::UPLOAD_BLOBS) {
- self.upload_image(image).await
- } else {
- self.handle_image_normal(image)
- }
- }
-
- async fn handle_embed<'s>(&self, embed: Tag<'s>) -> Tag<'s> {
- if self.options.contains(StaticSiteOptions::RESOLVE_AT_URIS)
- || self.options.contains(StaticSiteOptions::ADD_LINK_PREVIEWS)
- {
- self.handle_embed_aturi(embed).await
- } else {
- self.handle_embed_normal(embed).await
- }
- }
-
- fn handle_reference(&self, reference: CowStr<'_>) -> CowStr<'_> {
- let reference = reference.into_static();
- if let Some(reference) = self.reference_map.get(&reference) {
- let path = reference.value().clone();
- CowStr::Boxed(path.to_string_lossy().into_owned().into_boxed_str())
- } else {
- reference
- }
- }
-
- fn add_reference(&self, reference: CowStr<'_>) {
- let path = self.current_path();
- self.reference_map
- .insert(reference.into_static(), path.clone());
- }
-}
-
-pub struct StaticSiteWriter<'a, A>
+pub struct StaticSiteWriter
where
A: AgentSession,
{
- context: StaticSiteContext<'a, A>,
+ context: StaticSiteContext,
}
-impl<'a, A> StaticSiteWriter<'a, A>
+impl StaticSiteWriter
where
A: AgentSession,
{
@@ -504,9 +90,9 @@ where
}
}
-impl<'a, A> StaticSiteWriter<'a, A>
+impl StaticSiteWriter
where
- A: AgentSession + IdentityResolver + 'a,
+ A: AgentSession + IdentityResolver + 'static,
{
pub async fn run(mut self) -> Result<(), miette::Report> {
if !self.context.root.exists() {
@@ -555,6 +141,9 @@ where
));
}
+ let mut writers =
+ Vec::with_capacity(self.context.dir_contents.clone().unwrap_or_default().len());
+
for file in self
.context
.dir_contents
@@ -565,35 +154,46 @@ where
.filter(|file| file.starts_with(&self.context.start_at))
{
let context = self.context.clone();
- let relative_path = file
- .strip_prefix(context.start_at.clone())
- .expect("file should always be nested under root")
- .to_path_buf();
- if context
- .options
- .contains(StaticSiteOptions::FLATTEN_STRUCTURE)
- {
- let path_str = relative_path.to_string_lossy();
- let (parent, file) = flatten_dir_to_just_one_parent(&path_str);
- let output_path = context
- .destination
- .join(String::from(parent))
- .join(String::from(file));
+ let file = file.clone();
+ // we'll see if this is a problem or not
+ writers.push(n0_future::task::spawn(async move {
+ let relative_path = file
+ .strip_prefix(context.start_at.clone())
+ .expect("file should always be nested under root")
+ .to_path_buf();
+ if context
+ .options
+ .contains(StaticSiteOptions::FLATTEN_STRUCTURE)
+ {
+ let path_str = relative_path.to_string_lossy();
+ let (parent, file) = flatten_dir_to_just_one_parent(&path_str);
+ let output_path = context
+ .destination
+ .join(String::from(parent))
+ .join(String::from(file));
+
+ write_page(context.clone(), relative_path, output_path).await?;
+ } else {
+ let output_path = context.destination.join(relative_path.clone());
- write_page(context.clone(), relative_path, output_path).await?;
- } else {
- let output_path = context.destination.join(relative_path.clone());
+ write_page(context.clone(), relative_path, output_path).await?;
+ }
+ Ok::<(), miette::Report>(())
+ }));
+ }
- write_page(context.clone(), relative_path, output_path).await?;
- }
+ // def want to scope these so we wait until they all complete before we return
+ // and then we def want the errors, or at least the first error
+ for fut in n0_future::join_all(writers).await.into_iter() {
+ fut.into_diagnostic()??;
}
Ok(())
}
}
-pub async fn export_page<'s, 'input, A>(
+pub async fn export_page<'input, A>(
contents: &'input str,
- context: StaticSiteContext<'s, A>,
+ context: StaticSiteContext,
) -> Result
where
A: AgentSession + IdentityResolver,
@@ -616,8 +216,8 @@ where
Ok(output)
}
-pub async fn write_page<'s, A>(
- context: StaticSiteContext<'s, A>,
+pub async fn write_page(
+ context: StaticSiteContext,
input_path: impl AsRef,
output_path: impl AsRef,
) -> Result<(), miette::Report>
@@ -637,661 +237,6 @@ where
Ok(())
}
-pub struct StaticPageWriter<
- 'a,
- 'input,
- I: Iterator- >,
- A: AgentSession,
- W: StrWrite,
-> {
- context: NotebookProcessor<'input, I, StaticSiteContext<'a, A>>,
- writer: W,
- /// Whether or not the last write wrote a newline.
- end_newline: bool,
-
- /// Whether if inside a metadata block (text should not be written)
- in_non_writing_block: bool,
-
- table_state: TableState,
- table_alignments: Vec,
- table_cell_index: usize,
- numbers: DashMap, usize>,
-}
-
-impl<'a, 'input, I: Iterator
- >, A: AgentSession, W: StrWrite>
- StaticPageWriter<'a, 'input, I, A, W>
-{
- pub fn new(context: NotebookProcessor<'input, I, StaticSiteContext<'a, A>>, writer: W) -> Self {
- Self {
- context,
- writer,
- end_newline: true,
- in_non_writing_block: false,
- table_state: TableState::Head,
- table_alignments: vec![],
- table_cell_index: 0,
- numbers: DashMap::new(),
- }
- }
-
- /// Writes a new line.
- #[inline]
- fn write_newline(&mut self) -> Result<(), W::Error> {
- self.end_newline = true;
- self.writer.write_str("\n")
- }
-
- /// Writes a buffer, and tracks whether or not a newline was written.
- #[inline]
- fn write(&mut self, s: &str) -> Result<(), W::Error> {
- self.writer.write_str(s)?;
-
- if !s.is_empty() {
- self.end_newline = s.ends_with('\n');
- }
- Ok(())
- }
-
- fn end_tag(&mut self, tag: markdown_weaver::TagEnd) -> Result<(), W::Error> {
- use markdown_weaver::TagEnd;
- match tag {
- TagEnd::HtmlBlock => {}
- TagEnd::Paragraph => {
- self.write("
\n")?;
- }
- TagEnd::Heading(level) => {
- self.write("")?;
- write!(&mut self.writer, "{}", level)?;
- self.write(">\n")?;
- }
- TagEnd::Table => {
- self.write("\n")?;
- }
- TagEnd::TableHead => {
- self.write("\n")?;
- self.table_state = TableState::Body;
- }
- TagEnd::TableRow => {
- self.write("\n")?;
- }
- TagEnd::TableCell => {
- match self.table_state {
- TableState::Head => {
- self.write("")?;
- }
- TableState::Body => {
- self.write("")?;
- }
- }
- self.table_cell_index += 1;
- }
- TagEnd::BlockQuote(_) => {
- self.write("\n")?;
- }
- TagEnd::CodeBlock => {
- self.write("\n")?;
- }
- TagEnd::List(true) => {
- self.write("\n")?;
- }
- TagEnd::List(false) => {
- self.write("\n")?;
- }
- TagEnd::Item => {
- self.write("\n")?;
- }
- TagEnd::DefinitionList => {
- self.write("\n")?;
- }
- TagEnd::DefinitionListTitle => {
- self.write("\n")?;
- }
- TagEnd::DefinitionListDefinition => {
- self.write("\n")?;
- }
- TagEnd::Emphasis => {
- self.write("")?;
- }
- TagEnd::Superscript => {
- self.write("")?;
- }
- TagEnd::Subscript => {
- self.write("")?;
- }
- TagEnd::Strong => {
- self.write("")?;
- }
- TagEnd::Strikethrough => {
- self.write("")?;
- }
- TagEnd::Link => {
- self.write("")?;
- }
- TagEnd::Image => (), // shouldn't happen, handled in start
- TagEnd::Embed => (), // shouldn't happen, handled in start
- TagEnd::WeaverBlock(_) => {
- self.in_non_writing_block = false;
- }
- TagEnd::FootnoteDefinition => {
- self.write("\n")?;
- }
- TagEnd::MetadataBlock(_) => {
- self.in_non_writing_block = false;
- }
- }
- Ok(())
- }
-}
-
-impl<'a, 'input, I: Iterator- >, A: AgentSession + IdentityResolver, W: StrWrite>
- StaticPageWriter<'a, 'input, I, A, W>
-{
- async fn run(mut self) -> Result<(), W::Error> {
- while let Some(event) = self.context.next().await {
- self.process_event(event).await?
- }
- Ok(())
- }
-
- async fn process_event(&mut self, event: Event<'input>) -> Result<(), W::Error> {
- use markdown_weaver::Event::*;
- match event {
- Start(tag) => {
- self.start_tag(tag).await?;
- }
- End(tag) => {
- self.end_tag(tag)?;
- }
- Text(text) => {
- if !self.in_non_writing_block {
- escape_html_body_text(&mut self.writer, &text)?;
- self.end_newline = text.ends_with('\n');
- }
- }
- Code(text) => {
- self.write("
")?;
- escape_html_body_text(&mut self.writer, &text)?;
- self.write("")?;
- }
- InlineMath(text) => {
- self.write(r#""#)?;
- escape_html(&mut self.writer, &text)?;
- self.write("")?;
- }
- DisplayMath(text) => {
- self.write(r#""#)?;
- escape_html(&mut self.writer, &text)?;
- self.write("")?;
- }
- Html(html) | InlineHtml(html) => {
- self.write(&html)?;
- }
- SoftBreak => {
- self.write_newline()?;
- }
- HardBreak => {
- self.write("
\n")?;
- }
- Rule => {
- if self.end_newline {
- self.write("
\n")?;
- } else {
- self.write("\n
\n")?;
- }
- }
- FootnoteReference(name) => {
- let len = self.numbers.len() + 1;
- self.write("")?;
- }
- TaskListMarker(true) => {
- self.write("\n")?;
- }
- TaskListMarker(false) => {
- self.write("\n")?;
- }
- WeaverBlock(_text) => {}
- }
- Ok(())
- }
-
- // run raw text, consuming end tag
- async fn raw_text(&mut self) -> Result<(), W::Error> {
- use markdown_weaver::Event::*;
- let mut nest = 0;
- while let Some(event) = self.context.next().await {
- match event {
- Start(_) => nest += 1,
- End(_) => {
- if nest == 0 {
- break;
- }
- nest -= 1;
- }
- Html(_) => {}
- InlineHtml(text) | Code(text) | Text(text) => {
- // Don't use escape_html_body_text here.
- // The output of this function is used in the `alt` attribute.
- escape_html(&mut self.writer, &text)?;
- self.end_newline = text.ends_with('\n');
- }
- InlineMath(text) => {
- self.write("$")?;
- escape_html(&mut self.writer, &text)?;
- self.write("$")?;
- }
- DisplayMath(text) => {
- self.write("$$")?;
- escape_html(&mut self.writer, &text)?;
- self.write("$$")?;
- }
- SoftBreak | HardBreak | Rule => {
- self.write(" ")?;
- }
- FootnoteReference(name) => {
- let len = self.numbers.len() + 1;
- let number = *self.numbers.entry(name.into_static()).or_insert(len);
- write!(&mut self.writer, "[{}]", number)?;
- }
- TaskListMarker(true) => self.write("[x]")?,
- TaskListMarker(false) => self.write("[ ]")?,
- WeaverBlock(_) => {
- println!("Weaver block internal");
- }
- }
- }
- Ok(())
- }
-
- /// Writes the start of an HTML tag.
- async fn start_tag(&mut self, tag: Tag<'input>) -> Result<(), W::Error> {
- match tag {
- Tag::HtmlBlock => Ok(()),
- Tag::Paragraph => {
- if self.end_newline {
- self.write("")
- } else {
- self.write("\n
")
- }
- }
- Tag::Heading {
- level,
- id,
- classes,
- attrs,
- } => {
- if self.end_newline {
- self.write("<")?;
- } else {
- self.write("\n<")?;
- }
- write!(&mut self.writer, "{}", level)?;
- if let Some(id) = id {
- self.write(" id=\"")?;
- escape_html(&mut self.writer, &id)?;
- self.write("\"")?;
- }
- let mut classes = classes.iter();
- if let Some(class) = classes.next() {
- self.write(" class=\"")?;
- escape_html(&mut self.writer, class)?;
- for class in classes {
- self.write(" ")?;
- escape_html(&mut self.writer, class)?;
- }
- self.write("\"")?;
- }
- for (attr, value) in attrs {
- self.write(" ")?;
- escape_html(&mut self.writer, &attr)?;
- if let Some(val) = value {
- self.write("=\"")?;
- escape_html(&mut self.writer, &val)?;
- self.write("\"")?;
- } else {
- self.write("=\"\"")?;
- }
- }
- self.write(">")
- }
- Tag::Table(alignments) => {
- self.table_alignments = alignments;
- self.write("
")
- }
- Tag::TableHead => {
- self.table_state = TableState::Head;
- self.table_cell_index = 0;
- self.write("")
- }
- Tag::TableRow => {
- self.table_cell_index = 0;
- self.write("
")
- }
- Tag::TableCell => {
- match self.table_state {
- TableState::Head => {
- self.write("| {
- self.write(" | self.write(" style=\"text-align: left\">"),
- Some(&Alignment::Center) => self.write(" style=\"text-align: center\">"),
- Some(&Alignment::Right) => self.write(" style=\"text-align: right\">"),
- _ => self.write(">"),
- }
- }
- Tag::BlockQuote(kind) => {
- let class_str = match kind {
- None => "",
- Some(kind) => match kind {
- BlockQuoteKind::Note => " class=\"markdown-alert-note\"",
- BlockQuoteKind::Tip => " class=\"markdown-alert-tip\"",
- BlockQuoteKind::Important => " class=\"markdown-alert-important\"",
- BlockQuoteKind::Warning => " class=\"markdown-alert-warning\"",
- BlockQuoteKind::Caution => " class=\"markdown-alert-caution\"",
- },
- };
- if self.end_newline {
- self.write(&format!("\n", class_str))
- } else {
- self.write(&format!("\n\n", class_str))
- }
- }
- Tag::CodeBlock(info) => {
- if !self.end_newline {
- self.write_newline()?;
- }
- match info {
- CodeBlockKind::Fenced(info) => {
- let lang = info.split(' ').next().unwrap();
- if lang.is_empty() {
- self.write("")
- } else {
- self.write("")
- }
- }
- CodeBlockKind::Indented => self.write(""),
- }
- }
- Tag::List(Some(1)) => {
- if self.end_newline {
- self.write("\n")
- } else {
- self.write("\n\n")
- }
- }
- Tag::List(Some(start)) => {
- if self.end_newline {
- self.write("\n")
- }
- Tag::List(None) => {
- if self.end_newline {
- self.write("\n")
- } else {
- self.write("\n\n")
- }
- }
- Tag::Item => {
- if self.end_newline {
- self.write("- ")
- } else {
- self.write("\n
- ")
- }
- }
- Tag::DefinitionList => {
- if self.end_newline {
- self.write("
\n")
- } else {
- self.write("\n\n")
- }
- }
- Tag::DefinitionListTitle => {
- if self.end_newline {
- self.write("- ")
- } else {
- self.write("\n
- ")
- }
- }
- Tag::DefinitionListDefinition => {
- if self.end_newline {
- self.write("
- ")
- } else {
- self.write("\n
- ")
- }
- }
- Tag::Subscript => self.write(""),
- Tag::Superscript => self.write(""),
- Tag::Emphasis => self.write(""),
- Tag::Strong => self.write(""),
- Tag::Strikethrough => self.write("
"),
- Tag::Link {
- link_type: LinkType::Email,
- dest_url,
- title,
- id: _,
- } => {
- self.write("")
- }
- Tag::Link {
- link_type: _,
- dest_url,
- title,
- id: _,
- } => {
- self.write("")
- }
- Tag::Image {
- link_type,
- dest_url,
- title,
- id,
- attrs,
- } => {
- self.write_image(Tag::Image {
- link_type,
- dest_url,
- title,
- id,
- attrs,
- })
- .await
- }
- Tag::Embed {
- embed_type,
- dest_url,
- title,
- id,
- attrs,
- } => {
- if let Some(attrs) = attrs {
- if let Some((_, content)) = attrs
- .attrs
- .iter()
- .find(|(attr, _)| attr.as_ref() == "content")
- {
- match embed_type {
- EmbedType::Image => {
- self.write_image(Tag::Image {
- link_type: LinkType::Inline,
- dest_url,
- title,
- id,
- attrs: Some(attrs.clone()),
- })
- .await?
- }
- EmbedType::Comments => {
- self.write("leaflet would go here\n")?;
- }
- EmbedType::Post => {
- // Bluesky post embed, basically just render the raw html we got
- self.write(content)?;
- self.write_newline()?;
- }
- EmbedType::Markdown => {
- // let context = self
- // .context
- // .context
- // .clone_with_path(&Path::new(&dest_url.to_string()));
- // let callback =
- // if let Some(dir_contents) = context.dir_contents.clone() {
- // Some(VaultBrokenLinkCallback {
- // vault_contents: dir_contents,
- // })
- // } else {
- // None
- // };
- // let parser = Parser::new_with_broken_link_callback(
- // &content,
- // context.md_options,
- // callback,
- // );
- // let iterator = ContextIterator::default(parser);
- // let mut stream = NotebookProcessor::new(context, iterator);
- // while let Some(event) = stream.next().await {
- // self.process_event(event).await?;
- // }
- //
- self.write("markdown embed would go here\n")?;
- }
- EmbedType::Leaflet => {
- self.write("leaflet would go here\n")?;
- }
- EmbedType::Other => {
- self.write("other embed would go here\n")?;
- }
- }
- }
- } else {
- self.write("")?;
- }
- Ok(())
- }
- Tag::WeaverBlock(_, _attrs) => {
- println!("Weaver block");
- self.in_non_writing_block = true;
- Ok(())
- }
- Tag::FootnoteDefinition(name) => {
- if self.end_newline {
- self.write("
|
\n")?;
+ }
+ TagEnd::TableHead => {
+ self.write("\n")?;
+ self.table_state = TableState::Body;
+ }
+ TagEnd::TableRow => {
+ self.write("\n")?;
+ }
+ TagEnd::TableCell => {
+ match self.table_state {
+ TableState::Head => {
+ self.write("")?;
+ }
+ TableState::Body => {
+ self.write("")?;
+ }
+ }
+ self.table_cell_index += 1;
+ }
+ TagEnd::BlockQuote(_) => {
+ self.write("\n")?;
+ }
+ TagEnd::CodeBlock => {
+ if let Some((lang, buffer)) = self.code_buffer.take() {
+ if let Some(ref lang_str) = lang {
+ // Use a temporary String buffer for syntect
+ let mut temp_output = String::new();
+ match crate::code_pretty::highlight(
+ &self.context.context.syntax_set,
+ Some(lang_str),
+ &buffer,
+ &mut temp_output,
+ ) {
+ Ok(_) => {
+ self.write(&temp_output)?;
+ },
+ Err(_) => {
+ // Fallback to plain code block
+ self.write("")?;
+ escape_html_body_text(&mut self.writer, &buffer)?;
+ self.write("
\n")?;
+ }
+ }
+ } else {
+ self.write("")?;
+ escape_html_body_text(&mut self.writer, &buffer)?;
+ self.write("
\n")?;
+ }
+ } else {
+ self.write("\n")?;
+ }
+ }
+ TagEnd::List(true) => {
+ self.write("\n")?;
+ }
+ TagEnd::List(false) => {
+ self.write("\n")?;
+ }
+ TagEnd::Item => {
+ self.write("\n")?;
+ }
+ TagEnd::DefinitionList => {
+ self.write("\n")?;
+ }
+ TagEnd::DefinitionListTitle => {
+ self.write("\n")?;
+ }
+ TagEnd::DefinitionListDefinition => {
+ self.write("\n")?;
+ }
+ TagEnd::Emphasis => {
+ self.write("")?;
+ }
+ TagEnd::Superscript => {
+ self.write("")?;
+ }
+ TagEnd::Subscript => {
+ self.write("")?;
+ }
+ TagEnd::Strong => {
+ self.write("")?;
+ }
+ TagEnd::Strikethrough => {
+ self.write("")?;
+ }
+ TagEnd::Link => {
+ self.write("")?;
+ }
+ TagEnd::Image => (), // shouldn't happen, handled in start
+ TagEnd::Embed => (), // shouldn't happen, handled in start
+ TagEnd::WeaverBlock(_) => {
+ self.in_non_writing_block = false;
+ }
+ TagEnd::FootnoteDefinition => {
+ self.write("\n")?;
+ }
+ TagEnd::MetadataBlock(_) => {
+ self.in_non_writing_block = false;
+ }
+ }
+ Ok(())
+ }
+}
+
+impl<'input, I: Iterator- >, A: AgentSession + IdentityResolver, W: StrWrite>
+ StaticPageWriter<'input, I, A, W>
+{
+ pub async fn run(mut self) -> Result<(), W::Error> {
+ while let Some(event) = self.context.next().await {
+ self.process_event(event).await?
+ }
+ Ok(())
+ }
+
+ async fn process_event(&mut self, event: Event<'input>) -> Result<(), W::Error> {
+ use markdown_weaver::Event::*;
+ match event {
+ Start(tag) => {
+ self.start_tag(tag).await?;
+ }
+ End(tag) => {
+ self.end_tag(tag)?;
+ }
+ Text(text) => {
+ // If buffering code, append to buffer instead of writing
+ if let Some((_, ref mut buffer)) = self.code_buffer {
+ buffer.push_str(&text);
+ } else if !self.in_non_writing_block {
+ escape_html_body_text(&mut self.writer, &text)?;
+ self.end_newline = text.ends_with('\n');
+ }
+ }
+ Code(text) => {
+ self.write("
")?;
+ escape_html_body_text(&mut self.writer, &text)?;
+ self.write("")?;
+ }
+ InlineMath(text) => {
+ self.write(r#""#)?;
+ escape_html(&mut self.writer, &text)?;
+ self.write("")?;
+ }
+ DisplayMath(text) => {
+ self.write(r#""#)?;
+ escape_html(&mut self.writer, &text)?;
+ self.write("")?;
+ }
+ Html(html) | InlineHtml(html) => {
+ self.write(&html)?;
+ }
+ SoftBreak => {
+ self.write_newline()?;
+ }
+ HardBreak => {
+ self.write("
\n")?;
+ }
+ Rule => {
+ if self.end_newline {
+ self.write("
\n")?;
+ } else {
+ self.write("\n
\n")?;
+ }
+ }
+ FootnoteReference(name) => {
+ let len = self.numbers.len() + 1;
+ self.write("")?;
+ }
+ TaskListMarker(true) => {
+ self.write("\n")?;
+ }
+ TaskListMarker(false) => {
+ self.write("\n")?;
+ }
+ WeaverBlock(_text) => {}
+ }
+ Ok(())
+ }
+
+ // run raw text, consuming end tag
+ async fn raw_text(&mut self) -> Result<(), W::Error> {
+ use markdown_weaver::Event::*;
+ let mut nest = 0;
+ while let Some(event) = self.context.next().await {
+ match event {
+ Start(_) => nest += 1,
+ End(_) => {
+ if nest == 0 {
+ break;
+ }
+ nest -= 1;
+ }
+ Html(_) => {}
+ InlineHtml(text) | Code(text) | Text(text) => {
+ // Don't use escape_html_body_text here.
+ // The output of this function is used in the `alt` attribute.
+ escape_html(&mut self.writer, &text)?;
+ self.end_newline = text.ends_with('\n');
+ }
+ InlineMath(text) => {
+ self.write("$")?;
+ escape_html(&mut self.writer, &text)?;
+ self.write("$")?;
+ }
+ DisplayMath(text) => {
+ self.write("$$")?;
+ escape_html(&mut self.writer, &text)?;
+ self.write("$$")?;
+ }
+ SoftBreak | HardBreak | Rule => {
+ self.write(" ")?;
+ }
+ FootnoteReference(name) => {
+ let len = self.numbers.len() + 1;
+ let number = *self.numbers.entry(name.into_static()).or_insert(len);
+ write!(&mut self.writer, "[{}]", number)?;
+ }
+ TaskListMarker(true) => self.write("[x]")?,
+ TaskListMarker(false) => self.write("[ ]")?,
+ WeaverBlock(_) => {
+ println!("Weaver block internal");
+ }
+ }
+ }
+ Ok(())
+ }
+
+ /// Writes the start of an HTML tag.
+ async fn start_tag(&mut self, tag: Tag<'input>) -> Result<(), W::Error> {
+ match tag {
+ Tag::HtmlBlock => Ok(()),
+ Tag::Paragraph => {
+ if self.end_newline {
+ self.write("")
+ } else {
+ self.write("\n
")
+ }
+ }
+ Tag::Heading {
+ level,
+ id,
+ classes,
+ attrs,
+ } => {
+ if self.end_newline {
+ self.write("<")?;
+ } else {
+ self.write("\n<")?;
+ }
+ write!(&mut self.writer, "{}", level)?;
+ if let Some(id) = id {
+ self.write(" id=\"")?;
+ escape_html(&mut self.writer, &id)?;
+ self.write("\"")?;
+ }
+ let mut classes = classes.iter();
+ if let Some(class) = classes.next() {
+ self.write(" class=\"")?;
+ escape_html(&mut self.writer, class)?;
+ for class in classes {
+ self.write(" ")?;
+ escape_html(&mut self.writer, class)?;
+ }
+ self.write("\"")?;
+ }
+ for (attr, value) in attrs {
+ self.write(" ")?;
+ escape_html(&mut self.writer, &attr)?;
+ if let Some(val) = value {
+ self.write("=\"")?;
+ escape_html(&mut self.writer, &val)?;
+ self.write("\"")?;
+ } else {
+ self.write("=\"\"")?;
+ }
+ }
+ self.write(">")
+ }
+ Tag::Table(alignments) => {
+ self.table_alignments = alignments;
+ self.write("
")
+ }
+ Tag::TableHead => {
+ self.table_state = TableState::Head;
+ self.table_cell_index = 0;
+ self.write("")
+ }
+ Tag::TableRow => {
+ self.table_cell_index = 0;
+ self.write("
")
+ }
+ Tag::TableCell => {
+ match self.table_state {
+ TableState::Head => {
+ self.write("| {
+ self.write(" | self.write(" style=\"text-align: left\">"),
+ Some(&Alignment::Center) => self.write(" style=\"text-align: center\">"),
+ Some(&Alignment::Right) => self.write(" style=\"text-align: right\">"),
+ _ => self.write(">"),
+ }
+ }
+ Tag::BlockQuote(kind) => {
+ let class_str = match kind {
+ None => "",
+ Some(kind) => match kind {
+ BlockQuoteKind::Note => " class=\"markdown-alert-note\"",
+ BlockQuoteKind::Tip => " class=\"markdown-alert-tip\"",
+ BlockQuoteKind::Important => " class=\"markdown-alert-important\"",
+ BlockQuoteKind::Warning => " class=\"markdown-alert-warning\"",
+ BlockQuoteKind::Caution => " class=\"markdown-alert-caution\"",
+ },
+ };
+ if self.end_newline {
+ self.write(&format!("\n", class_str))
+ } else {
+ self.write(&format!("\n\n", class_str))
+ }
+ }
+ Tag::CodeBlock(info) => {
+ if !self.end_newline {
+ self.write_newline()?;
+ }
+ match info {
+ CodeBlockKind::Fenced(info) => {
+ let lang = info.split(' ').next().unwrap();
+ let lang_opt = if lang.is_empty() {
+ None
+ } else {
+ Some(lang.to_string())
+ };
+ // Start buffering
+ self.code_buffer = Some((lang_opt, String::new()));
+ Ok(())
+ }
+ CodeBlockKind::Indented => {
+ // Start buffering with no language
+ self.code_buffer = Some((None, String::new()));
+ Ok(())
+ }
+ }
+ }
+ Tag::List(Some(1)) => {
+ if self.end_newline {
+ self.write("\n")
+ } else {
+ self.write("\n\n")
+ }
+ }
+ Tag::List(Some(start)) => {
+ if self.end_newline {
+ self.write("\n")
+ }
+ Tag::List(None) => {
+ if self.end_newline {
+ self.write("\n")
+ } else {
+ self.write("\n\n")
+ }
+ }
+ Tag::Item => {
+ if self.end_newline {
+ self.write("- ")
+ } else {
+ self.write("\n
- ")
+ }
+ }
+ Tag::DefinitionList => {
+ if self.end_newline {
+ self.write("
\n")
+ } else {
+ self.write("\n\n")
+ }
+ }
+ Tag::DefinitionListTitle => {
+ if self.end_newline {
+ self.write("- ")
+ } else {
+ self.write("\n
- ")
+ }
+ }
+ Tag::DefinitionListDefinition => {
+ if self.end_newline {
+ self.write("
- ")
+ } else {
+ self.write("\n
- ")
+ }
+ }
+ Tag::Subscript => self.write(""),
+ Tag::Superscript => self.write(""),
+ Tag::Emphasis => self.write(""),
+ Tag::Strong => self.write(""),
+ Tag::Strikethrough => self.write("
"),
+ Tag::Link {
+ link_type: LinkType::Email,
+ dest_url,
+ title,
+ id: _,
+ } => {
+ self.write("")
+ }
+ Tag::Link {
+ link_type: _,
+ dest_url,
+ title,
+ id: _,
+ } => {
+ self.write("")
+ }
+ Tag::Image {
+ link_type,
+ dest_url,
+ title,
+ id,
+ attrs,
+ } => {
+ self.write_image(Tag::Image {
+ link_type,
+ dest_url,
+ title,
+ id,
+ attrs,
+ })
+ .await
+ }
+ Tag::Embed {
+ embed_type,
+ dest_url,
+ title,
+ id,
+ attrs,
+ } => {
+ if let Some(attrs) = attrs {
+ if let Some((_, content)) = attrs
+ .attrs
+ .iter()
+ .find(|(attr, _)| attr.as_ref() == "content")
+ {
+ match embed_type {
+ EmbedType::Image => {
+ self.write_image(Tag::Image {
+ link_type: LinkType::Inline,
+ dest_url,
+ title,
+ id,
+ attrs: Some(attrs.clone()),
+ })
+ .await?
+ }
+ EmbedType::Comments => {
+ self.write("leaflet would go here\n")?;
+ }
+ EmbedType::Post => {
+ // Bluesky post embed, basically just render the raw html we got
+ self.write(content)?;
+ self.write_newline()?;
+ }
+ EmbedType::Markdown => {
+ // let context = self
+ // .context
+ // .context
+ // .clone_with_path(&Path::new(&dest_url.to_string()));
+ // let callback =
+ // if let Some(dir_contents) = context.dir_contents.clone() {
+ // Some(VaultBrokenLinkCallback {
+ // vault_contents: dir_contents,
+ // })
+ // } else {
+ // None
+ // };
+ // let parser = Parser::new_with_broken_link_callback(
+ // &content,
+ // context.md_options,
+ // callback,
+ // );
+ // let iterator = ContextIterator::default(parser);
+ // let mut stream = NotebookProcessor::new(context, iterator);
+ // while let Some(event) = stream.next().await {
+ // self.process_event(event).await?;
+ // }
+ //
+ self.write("markdown embed would go here\n")?;
+ }
+ EmbedType::Leaflet => {
+ self.write("leaflet would go here\n")?;
+ }
+ EmbedType::Other => {
+ self.write("other embed would go here\n")?;
+ }
+ }
+ }
+ } else {
+ self.write("")?;
+ }
+ Ok(())
+ }
+ Tag::WeaverBlock(_, _attrs) => {
+ println!("Weaver block");
+ self.in_non_writing_block = true;
+ Ok(())
+ }
+ Tag::FootnoteDefinition(name) => {
+ if self.end_newline {
+ self.write(" |