Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add markdown alert support #423

Merged
merged 1 commit into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/markdown/elements.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::text_style::TextStyle;
use comrak::nodes::AlertType;
use std::{fmt, iter, path::PathBuf, str::FromStr};
use unicode_width::UnicodeWidthStr;

Expand Down Expand Up @@ -51,6 +52,18 @@ pub(crate) enum MarkdownElement {

/// A block quote containing a list of lines.
BlockQuote(Vec<Line>),

/// An alert.
Alert {
/// The alert's type.
alert_type: AlertType,

/// The optional title.
title: Option<String>,

/// The content lines in this alert.
lines: Vec<Line>,
},
}

#[derive(Clone, Copy, Debug, Default)]
Expand Down
36 changes: 28 additions & 8 deletions src/markdown/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use comrak::{
arena_tree::Node,
format_commonmark,
nodes::{
Ast, AstNode, ListDelimType, ListType, NodeCodeBlock, NodeHeading, NodeHtmlBlock, NodeList, NodeValue,
Sourcepos,
Ast, AstNode, ListDelimType, ListType, NodeAlert, NodeCodeBlock, NodeHeading, NodeHtmlBlock, NodeList,
NodeValue, Sourcepos,
},
parse_document,
};
Expand All @@ -31,6 +31,7 @@ impl Default for ParserOptions {
options.extension.table = true;
options.extension.strikethrough = true;
options.extension.multiline_block_quotes = true;
options.extension.alerts = true;
Self(options)
}
}
Expand Down Expand Up @@ -76,6 +77,7 @@ impl<'a> MarkdownParser<'a> {
NodeValue::ThematicBreak => MarkdownElement::ThematicBreak,
NodeValue::HtmlBlock(block) => self.parse_html_block(block, data.sourcepos)?,
NodeValue::BlockQuote | NodeValue::MultilineBlockQuote(_) => self.parse_block_quote(node)?,
NodeValue::Alert(alert) => self.parse_alert(alert, node)?,
other => return Err(ParseErrorKind::UnsupportedElement(other.identifier()).with_sourcepos(data.sourcepos)),
};
Ok(vec![element])
Expand Down Expand Up @@ -106,19 +108,19 @@ impl<'a> MarkdownParser<'a> {
}

fn parse_block_quote(&self, node: &'a AstNode<'a>) -> ParseResult<MarkdownElement> {
let mut elements = Vec::new();
let mut lines = Vec::new();
let inlines = InlinesParser::new(self.arena, SoftBreak::Newline, StringifyImages::Yes).parse(node)?;
for inline in inlines {
match inline {
Inline::Text(text) => elements.push(text),
Inline::LineBreak => elements.push(Line::from("")),
Inline::Text(text) => lines.push(text),
Inline::LineBreak => lines.push(Line::from("")),
Inline::Image { .. } => {}
}
}
if elements.last() == Some(&Line::from("")) {
elements.pop();
if lines.last() == Some(&Line::from("")) {
lines.pop();
}
Ok(MarkdownElement::BlockQuote(elements))
Ok(MarkdownElement::BlockQuote(lines))
}

fn parse_code_block(block: &NodeCodeBlock, sourcepos: Sourcepos) -> ParseResult<MarkdownElement> {
Expand All @@ -132,6 +134,11 @@ impl<'a> MarkdownParser<'a> {
})
}

fn parse_alert(&self, alert: &NodeAlert, node: &'a AstNode<'a>) -> ParseResult<MarkdownElement> {
let MarkdownElement::BlockQuote(lines) = self.parse_block_quote(node)? else { panic!("not a block quote") };
Ok(MarkdownElement::Alert { alert_type: alert.alert_type, title: alert.title.clone(), lines })
}

fn parse_heading(&self, heading: &NodeHeading, node: &'a AstNode<'a>) -> ParseResult<MarkdownElement> {
let text = self.parse_text(node)?;
if heading.setext {
Expand Down Expand Up @@ -973,4 +980,17 @@ mom
let expected = format!("hi{nl}mom{nl}");
assert_eq!(contents, &expected);
}

#[test]
fn parse_alert() {
let input = r"
> [!note]
> hi mom
> bye **mom**
";
let MarkdownElement::Alert { lines, .. } = parse_single(&input) else {
panic!("not an alert");
};
assert_eq!(lines.len(), 2);
}
}
36 changes: 31 additions & 5 deletions src/presentation/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ use crate::{
separator::RenderSeparator,
},
};
use comrak::nodes::AlertType;
use image::DynamicImage;
use serde::Deserialize;
use std::{borrow::Cow, cell::RefCell, fmt::Display, iter, mem, path::PathBuf, rc::Rc, str::FromStr};
Expand Down Expand Up @@ -287,6 +288,7 @@ impl<'a> PresentationBuilder<'a> {
MarkdownElement::Image { path, title, source_position } => {
self.push_image_from_path(path, title, source_position)?
}
MarkdownElement::Alert { alert_type, title, lines } => self.push_alert(alert_type, title, lines),
};
if should_clear_last {
self.slide_state.last_element = LastElement::Other;
Expand Down Expand Up @@ -716,8 +718,30 @@ impl<'a> PresentationBuilder<'a> {

fn push_block_quote(&mut self, lines: Vec<Line>) {
let prefix = self.theme.block_quote.prefix.clone().unwrap_or_default();
let block_length = lines.iter().map(|line| line.width() + prefix.width()).max().unwrap_or(0) as u16;
let prefix_color = self.theme.block_quote.colors.prefix.or(self.theme.block_quote.colors.base.foreground);
self.push_quoted_text(lines, prefix, self.theme.block_quote.colors.base, prefix_color);
}

fn push_alert(&mut self, alert_type: AlertType, title: Option<String>, mut lines: Vec<Line>) {
let (default_title, prefix_color) = match alert_type {
AlertType::Note => ("Note", self.theme.alert.colors.types.note),
AlertType::Tip => ("Tip", self.theme.alert.colors.types.tip),
AlertType::Important => ("Important", self.theme.alert.colors.types.important),
AlertType::Warning => ("Warning", self.theme.alert.colors.types.warning),
AlertType::Caution => ("Caution", self.theme.alert.colors.types.caution),
};
let prefix_color = prefix_color.or(self.theme.alert.colors.base.foreground);
let title = title.unwrap_or_else(|| default_title.to_string());
let title_colors = Colors { foreground: prefix_color, background: self.theme.alert.colors.base.background };
lines.insert(0, Line::from(Text::from("")));
lines.insert(0, Line::from(Text::new(title, TextStyle::default().colors(title_colors))));

let prefix = self.theme.block_quote.prefix.clone().unwrap_or_default();
self.push_quoted_text(lines, prefix, self.theme.alert.colors.base, prefix_color);
}

fn push_quoted_text(&mut self, lines: Vec<Line>, prefix: String, base_colors: Colors, prefix_color: Option<Color>) {
let block_length = lines.iter().map(|line| line.width() + prefix.width()).max().unwrap_or(0) as u16;
let prefix = Text::new(
prefix,
TextStyle::default()
Expand All @@ -728,9 +752,11 @@ impl<'a> PresentationBuilder<'a> {
for mut line in lines {
// Apply our colors to each chunk in this line.
for text in &mut line.0 {
text.style.colors = self.theme.block_quote.colors.base;
if text.style.is_code() {
text.style.colors = self.theme.inline_code.colors;
if text.style.colors.background.is_none() && text.style.colors.foreground.is_none() {
text.style.colors = base_colors;
if text.style.is_code() {
text.style.colors = self.theme.inline_code.colors;
}
}
}
self.chunk_operations.push(RenderOperation::RenderBlockLine(BlockLine {
Expand All @@ -740,7 +766,7 @@ impl<'a> PresentationBuilder<'a> {
text: line.into(),
block_length,
alignment: alignment.clone(),
block_color: self.theme.block_quote.colors.base.background,
block_color: base_colors.background,
}));
self.push_line_break();
}
Expand Down
59 changes: 59 additions & 0 deletions src/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ pub struct PresentationTheme {
#[serde(default)]
pub(crate) block_quote: BlockQuoteStyle,

/// The style for an alert.
#[serde(default)]
pub(crate) alert: AlertStyle,

/// The default style.
#[serde(rename = "default", default)]
pub(crate) default_style: DefaultStyle,
Expand Down Expand Up @@ -325,9 +329,64 @@ pub(crate) struct BlockQuoteColors {
pub(crate) base: Colors,

/// The color of the vertical bar that prefixes each line in the quote.
#[serde(default)]
pub(crate) prefix: Option<Color>,
}

/// The style of an alert.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub(crate) struct AlertStyle {
/// The alignment.
#[serde(flatten, default)]
pub(crate) alignment: Option<Alignment>,

/// The prefix to be added to this block quote.
///
/// This allows adding something like a vertical bar before the text.
#[serde(default)]
pub(crate) prefix: Option<String>,

/// The colors to be used.
#[serde(default)]
pub(crate) colors: AlertColors,
}

/// The colors of an alert.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub(crate) struct AlertColors {
/// The foreground/background colors.
#[serde(flatten)]
pub(crate) base: Colors,

/// The color of the vertical bar that prefixes each line in the quote.
#[serde(default)]
pub(crate) types: AlertTypeColors,
}

/// The colors of each alert type.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub(crate) struct AlertTypeColors {
/// The color for note type alerts.
#[serde(default)]
pub(crate) note: Option<Color>,

/// The color for tip type alerts.
#[serde(default)]
pub(crate) tip: Option<Color>,

/// The color for important type alerts.
#[serde(default)]
pub(crate) important: Option<Color>,

/// The color for warning type alerts.
#[serde(default)]
pub(crate) warning: Option<Color>,

/// The color for caution type alerts.
#[serde(default)]
pub(crate) caution: Option<Color>,
}

/// The style for the presentation introduction slide.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub(crate) struct IntroSlideStyle {
Expand Down
1 change: 1 addition & 0 deletions src/ui/footer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ impl FooterGenerator {
colors: Colors,
alignment: Alignment,
) -> RenderOperation {
#[allow(unknown_lints)]
#[allow(clippy::literal_string_with_formatting_args)]
let contents = template
.replace("{current_slide}", current_slide)
Expand Down
12 changes: 12 additions & 0 deletions themes/catppuccin-frappe.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ block_quote:
background: "414559"
prefix: "e5c890"

alert:
prefix: ""
colors:
foreground: "c6d0f5"
background: "414559"
types:
note: "8caaee"
tip: "a6d189"
important: "ca9ee6"
warning: "e5c890"
caution: "e78284"

typst:
colors:
foreground: "c6d0f5"
Expand Down
12 changes: 12 additions & 0 deletions themes/catppuccin-latte.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ block_quote:
background: "ccd0da"
prefix: "df8e1d"

alert:
prefix: ""
colors:
foreground: "4c4f69"
background: "ccd0da"
types:
note: "1e66f5"
tip: "40a02b"
important: "8839ef"
warning: "df8e1d"
caution: "d20f39"

typst:
colors:
foreground: "4c4f69"
Expand Down
12 changes: 12 additions & 0 deletions themes/catppuccin-macchiato.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ block_quote:
background: "363a4f"
prefix: "eed49f"

alert:
prefix: ""
colors:
foreground: "cad3f5"
background: "363a4f"
types:
note: "8aadf4"
tip: "a6da95"
important: "c6a0f6"
warning: "f5a97f"
caution: "ed8796"

typst:
colors:
foreground: "cad3f5"
Expand Down
12 changes: 12 additions & 0 deletions themes/catppuccin-mocha.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ block_quote:
background: "313244"
prefix: "f9e2af"

alert:
prefix: ""
colors:
foreground: "cdd6f4"
background: "313244"
types:
note: "89b4fa"
tip: "a6e3a1"
important: "cba6f7"
warning: "fab387"
caution: "f38ba8"

typst:
colors:
foreground: "cdd6f4"
Expand Down
12 changes: 12 additions & 0 deletions themes/dark.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ block_quote:
background: "292e42"
prefix: "ee9322"

alert:
prefix: ""
colors:
foreground: "f0f0f0"
background: "292e42"
types:
note: "3085c3"
tip: "a8df8e"
important: "986ee2"
warning: "ee9322"
caution: "f78ca2"

typst:
colors:
foreground: "f0f0f0"
Expand Down
12 changes: 12 additions & 0 deletions themes/light.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ block_quote:
background: "e9ecef"
prefix: "f77f00"

alert:
prefix: ""
colors:
foreground: "212529"
background: "e9ecef"
types:
note: "1e66f5"
tip: "40a02b"
important: "8839ef"
warning: "df8e1d"
caution: "d20f39"

typst:
colors:
foreground: "212529"
Expand Down
12 changes: 12 additions & 0 deletions themes/terminal-dark.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,18 @@ block_quote:
background: black
prefix: yellow

alert:
prefix: ""
colors:
foreground: white
background: black
types:
note: blue
tip: green
important: magenta
warning: yellow
caution: red

typst:
colors:
foreground: "f0f0f0"
Expand Down
Loading
Loading