Skip to content

Commit

Permalink
feat: add symbols to alerts (#430)
Browse files Browse the repository at this point in the history
This is a follow up on #423 that adds symbols before the title of alert
type elements. The structure of the theme changed a bit to be able to
pack the styles for each alert type into a single type.

This causes the following presentation:

```markdown
> [!note]
> this is a note

> [!tip]
> this is a tip

> [!important]
> this is important

> [!warning]
> this is warning!

> [!caution]
> this advises caution!

>>> [!note] other title
ez
multiline
>>>
```

To render like this:


![image](https://github.com/user-attachments/assets/ce90a4e0-7543-4100-83b2-5f8d86f4cbe2)
  • Loading branch information
mfontanini authored Feb 1, 2025
2 parents 22af066 + 7643b8f commit f6daf38
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 106 deletions.
31 changes: 8 additions & 23 deletions .github/workflows/merge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,33 @@ name: Merge checks

jobs:
check:
name: Check
name: Checks
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4

- name: Install rust toolchain
uses: dtolnay/[email protected]
with:
components: clippy

- name: Run cargo check
run: cargo check --features sixel

test:
name: Tests
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4

- name: Install rust toolchain
uses: dtolnay/[email protected]

- name: Run cargo test
run: cargo test

lints:
name: Lints
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Run cargo clippy
run: cargo clippy -- -D warnings

- name: Install stable toolchain
- name: Install nightly toolchain
uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt, clippy
components: rustfmt

- name: Run cargo fmt
run: cargo fmt --all -- --check

- name: Run cargo clippy
run: cargo clippy -- -D warnings
run: cargo +nightly fmt --all -- --check

nix-flake:
name: Validate nix flake
Expand Down
11 changes: 4 additions & 7 deletions src/code/snippet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -745,13 +745,10 @@ mod test {
#[test]
fn highlight_line_range() {
let attributes = parse_attributes("bash { 1, 2-4,6 , all , 10 - 12 }");
assert_eq!(attributes.highlight_groups, &[HighlightGroup::new(vec![
Single(1),
Range(2..5),
Single(6),
All,
Range(10..13)
])]);
assert_eq!(
attributes.highlight_groups,
&[HighlightGroup::new(vec![Single(1), Range(2..5), Single(6), All, Range(10..13)])]
);
}

#[test]
Expand Down
39 changes: 21 additions & 18 deletions src/presentation/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -733,21 +733,25 @@ impl<'a> PresentationBuilder<'a> {
}

fn push_alert(&mut self, alert_type: AlertType, title: Option<String>, mut lines: Vec<Line>) -> BuildResult {
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, default_title, symbol) = match alert_type {
AlertType::Note => self.theme.alert.styles.note.as_parts(),
AlertType::Tip => self.theme.alert.styles.tip.as_parts(),
AlertType::Important => self.theme.alert.styles.important.as_parts(),
AlertType::Warning => self.theme.alert.styles.warning.as_parts(),
AlertType::Caution => self.theme.alert.styles.caution.as_parts(),
};
let prefix_color = prefix_color.or(self.theme.alert.colors.base.foreground);
let prefix_color = prefix_color.or(self.theme.alert.base_colors.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 };
let title = match symbol {
Some(symbol) => format!("{symbol} {title}"),
None => title,
};
let title_colors = Colors { foreground: prefix_color, background: self.theme.alert.base_colors.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)
self.push_quoted_text(lines, prefix, self.theme.alert.base_colors, prefix_color)
}

fn push_quoted_text(
Expand Down Expand Up @@ -1681,11 +1685,10 @@ mod test {
#[test]
fn iterate_list_starting_from_other() {
let list = ListIterator::new(
vec![ListItem { depth: 0, contents: "0".into(), item_type: ListItemType::Unordered }, ListItem {
depth: 0,
contents: "1".into(),
item_type: ListItemType::Unordered,
}],
vec![
ListItem { depth: 0, contents: "0".into(), item_type: ListItemType::Unordered },
ListItem { depth: 0, contents: "1".into(), item_type: ListItemType::Unordered },
],
3,
);
let expected_indexes = [3, 4];
Expand Down Expand Up @@ -1772,10 +1775,10 @@ mod test {

#[test]
fn implicit_slide_ends_with_front_matter() {
let elements =
vec![MarkdownElement::FrontMatter("theme:\n name: light".into()), MarkdownElement::SetexHeading {
text: "hi".into(),
}];
let elements = vec![
MarkdownElement::FrontMatter("theme:\n name: light".into()),
MarkdownElement::SetexHeading { text: "hi".into() },
];
let options = PresentationBuilderOptions { implicit_slide_ends: true, ..Default::default() };
let slides = build_presentation_with_options(elements, options).into_slides();
assert_eq!(slides.len(), 1);
Expand Down
183 changes: 142 additions & 41 deletions src/theme.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::markdown::text_style::{Color, Colors, FixedStr, UndefinedPaletteColorError};
use serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, fs, io, path::Path};
use std::{collections::BTreeMap, fmt, fs, io, marker::PhantomData, path::Path};

include!(concat!(env!("OUT_DIR"), "/themes.rs"));

Expand Down Expand Up @@ -395,7 +395,7 @@ pub(crate) struct BlockQuoteStyle {

impl BlockQuoteStyle {
fn resolve_palette_colors(&mut self, palette: &ColorPalette) -> Result<(), UndefinedPaletteColorError> {
let Self { colors, alignment: _alignment, prefix: _prefix } = self;
let Self { colors, alignment: _, prefix: _ } = self;
colors.resolve_palette_colors(palette)?;
Ok(())
}
Expand Down Expand Up @@ -431,82 +431,183 @@ pub(crate) struct AlertStyle {
#[serde(flatten, default)]
pub(crate) alignment: Option<Alignment>,

/// The base colors.
#[serde(default)]
pub(crate) base_colors: Colors,

/// 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.
/// The style for each alert type.
#[serde(default)]
pub(crate) colors: AlertColors,
pub(crate) styles: AlertTypeStyles,
}

impl AlertStyle {
fn resolve_palette_colors(&mut self, palette: &ColorPalette) -> Result<(), UndefinedPaletteColorError> {
let Self { colors, alignment: _alignment, prefix: _prefix } = self;
colors.resolve_palette_colors(palette)?;
let Self { base_colors, styles, alignment: _, prefix: _ } = self;
*base_colors = base_colors.resolve(palette)?;
styles.resolve_palette_colors(palette)?;
Ok(())
}
}

/// The colors of an alert.
/// The style for each alert type.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub(crate) struct AlertColors {
/// The foreground/background colors.
#[serde(flatten)]
pub(crate) base: Colors,
pub(crate) struct AlertTypeStyles {
/// The style for note alert types.
#[serde(default)]
pub(crate) note: AlertTypeStyle<NoteAlertType>,

/// The color of the vertical bar that prefixes each line in the quote.
/// The style for tip alert types.
#[serde(default)]
pub(crate) types: AlertTypeColors,
pub(crate) tip: AlertTypeStyle<TipAlertType>,

/// The style for important alert types.
#[serde(default)]
pub(crate) important: AlertTypeStyle<ImportantAlertType>,

/// The style for warning alert types.
#[serde(default)]
pub(crate) warning: AlertTypeStyle<WarningAlertType>,

/// The style for caution alert types.
#[serde(default)]
pub(crate) caution: AlertTypeStyle<CautionAlertType>,
}

impl AlertColors {
impl AlertTypeStyles {
fn resolve_palette_colors(&mut self, palette: &ColorPalette) -> Result<(), UndefinedPaletteColorError> {
let Self { base, types } = self;
*base = base.resolve(palette)?;
types.resolve_palette_colors(palette)?;
let Self { note, tip, important, warning, caution } = self;
note.resolve_palette_colors(palette)?;
tip.resolve_palette_colors(palette)?;
important.resolve_palette_colors(palette)?;
warning.resolve_palette_colors(palette)?;
caution.resolve_palette_colors(palette)?;
Ok(())
}
}

/// 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.
/// The style for an alert type.
#[derive(Deserialize, Serialize)]
pub(crate) struct AlertTypeStyle<S: AlertTypeProperties> {
/// The color to be used.
#[serde(default)]
pub(crate) tip: Option<Color>,
pub(crate) color: Option<Color>,

/// The color for important type alerts.
#[serde(default)]
pub(crate) important: Option<Color>,
/// The title to be used.
#[serde(default = "S::default_title")]
pub(crate) title: String,

/// The color for warning type alerts.
#[serde(default)]
pub(crate) warning: Option<Color>,
/// The symbol to be used.
#[serde(default = "S::default_symbol")]
pub(crate) symbol: Option<String>,

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

impl AlertTypeColors {
impl<S: AlertTypeProperties> AlertTypeStyle<S> {
pub(crate) fn as_parts(&self) -> (&Option<Color>, &str, Option<&str>) {
(&self.color, &self.title, self.symbol.as_deref())
}

fn resolve_palette_colors(&mut self, palette: &ColorPalette) -> Result<(), UndefinedPaletteColorError> {
let Self { note, tip, important, warning, caution } = self;
for c in [note, tip, important, warning, caution] {
if let Some(c) = c.as_mut() {
*c = c.resolve(palette)?;
}
let Self { color, title: _, symbol: _, _unused: _ } = self;
if let Some(color) = color.as_mut() {
*color = color.resolve(palette)?;
}
Ok(())
}
}

impl<S: AlertTypeProperties> fmt::Debug for AlertTypeStyle<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AlertTypeStyle")
.field("color", &self.color)
.field("title", &self.title)
.field("symbol", &self.symbol)
.field("_unused", &self._unused)
.finish()
}
}

impl<S: AlertTypeProperties> Clone for AlertTypeStyle<S> {
fn clone(&self) -> Self {
Self { color: self.color, title: self.title.clone(), symbol: self.symbol.clone(), _unused: PhantomData }
}
}

impl<S: AlertTypeProperties> Default for AlertTypeStyle<S> {
fn default() -> Self {
Self { color: None, title: S::default_title(), symbol: S::default_symbol(), _unused: PhantomData }
}
}

pub(crate) trait AlertTypeProperties {
fn default_title() -> String;
fn default_symbol() -> Option<String>;
}

pub(crate) struct NoteAlertType;
pub(crate) struct TipAlertType;
pub(crate) struct ImportantAlertType;
pub(crate) struct WarningAlertType;
pub(crate) struct CautionAlertType;

impl AlertTypeProperties for NoteAlertType {
fn default_title() -> String {
"Note".into()
}

fn default_symbol() -> Option<String> {
Some("󰋽".into())
}
}

impl AlertTypeProperties for TipAlertType {
fn default_title() -> String {
"Tip".into()
}

fn default_symbol() -> Option<String> {
Some("".into())
}
}

impl AlertTypeProperties for ImportantAlertType {
fn default_title() -> String {
"Important".into()
}

fn default_symbol() -> Option<String> {
Some("".into())
}
}

impl AlertTypeProperties for WarningAlertType {
fn default_title() -> String {
"Warning".into()
}

fn default_symbol() -> Option<String> {
Some("".into())
}
}

impl AlertTypeProperties for CautionAlertType {
fn default_title() -> String {
"Caution".into()
}

fn default_symbol() -> Option<String> {
Some("󰳦".into())
}
}

/// The style for the presentation introduction slide.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub(crate) struct IntroSlideStyle {
Expand Down
Loading

0 comments on commit f6daf38

Please sign in to comment.