diff --git a/server/src/email_extract.rs b/server/src/email_extract.rs index 0053c04..65cf10c 100644 --- a/server/src/email_extract.rs +++ b/server/src/email_extract.rs @@ -1,3 +1,29 @@ +// Inline Askama filters module for template use +mod filters { + // Usage: {{ items|batch(7) }} + pub fn batch( + items: &[T], + _: &dyn ::askama::Values, + size: usize, + ) -> askama::Result>> { + if size == 0 { + return Ok(vec![]); + } + let mut out = Vec::new(); + let mut chunk = Vec::with_capacity(size); + for item in items { + chunk.push(item.clone()); + if chunk.len() == size { + out.push(chunk); + chunk = Vec::with_capacity(size); + } + } + if !chunk.is_empty() { + out.push(chunk); + } + Ok(out) + } +} use std::io::{Cursor, Read}; use askama::Template; @@ -1099,7 +1125,9 @@ pub struct IcalSummaryTemplate<'a> { pub local_fmt_end: &'a str, pub organizer: &'a str, pub organizer_cn: &'a str, - pub calendar_html: &'a str, + pub all_days: Vec, + pub event_days: Vec, + pub caption: String, pub description_paragraphs: &'a [String], } @@ -1385,11 +1413,45 @@ pub fn render_ical_summary(ical_data: &str) -> Result { (String::new(), String::new(), vec![]) }; - // Render calendar table HTML - let calendar_html = if !event_days.is_empty() { - render_merged_calendar_table(&event_days) + // Compute calendar grid for template rendering + let (all_days, caption) = if !event_days.is_empty() { + let first_event = event_days.first().unwrap(); + let last_event = event_days.last().unwrap(); + let first_of_month = + NaiveDate::from_ymd_opt(first_event.year(), first_event.month(), 1).unwrap(); + let last_of_month = { + let next_month = if last_event.month() == 12 { + NaiveDate::from_ymd_opt(last_event.year() + 1, 1, 1).unwrap() + } else { + NaiveDate::from_ymd_opt(last_event.year(), last_event.month() + 1, 1) + .unwrap() + }; + next_month.pred_opt().unwrap() + }; + let mut cal_start = first_of_month; + while cal_start.weekday() != chrono::Weekday::Sun { + cal_start = cal_start.pred_opt().unwrap(); + } + let mut cal_end = last_of_month; + while cal_end.weekday() != chrono::Weekday::Sat { + cal_end = cal_end.succ_opt().unwrap(); + } + let mut all_days = vec![]; + let mut d = cal_start; + while d <= cal_end { + all_days.push(d); + d = d.succ_opt().unwrap(); + } + let start_month = first_event.format("%B %Y"); + let end_month = last_event.format("%B %Y"); + let caption = if start_month.to_string() == end_month.to_string() { + start_month.to_string() + } else { + format!("{} – {}", start_month, end_month) + }; + (all_days, caption) } else { - String::new() + (vec![], String::new()) }; // Description paragraphs @@ -1408,7 +1470,6 @@ pub fn render_ical_summary(ical_data: &str) -> Result { let organizer_cn_val = organizer_cn.unwrap_or(""); let local_fmt_start_val = &local_fmt_start; let local_fmt_end_val = &local_fmt_end; - let calendar_html_val = &calendar_html; let description_paragraphs_val = &description_paragraphs; let template = IcalSummaryTemplate { summary: summary_val, @@ -1416,7 +1477,9 @@ pub fn render_ical_summary(ical_data: &str) -> Result { local_fmt_end: local_fmt_end_val, organizer: organizer_val, organizer_cn: organizer_cn_val, - calendar_html: calendar_html_val, + all_days, + event_days: event_days.clone(), + caption, description_paragraphs: description_paragraphs_val, }; summary_parts.push(template.render()?); diff --git a/server/templates/ical_summary.html b/server/templates/ical_summary.html index 0b11c10..73b9168 100644 --- a/server/templates/ical_summary.html +++ b/server/templates/ical_summary.html @@ -1,55 +1,86 @@ -
-
-
Summary: {{ summary }}
-
Start: {{ local_fmt_start }}
-
End: {{ local_fmt_end }}
- {% if !organizer_cn.is_empty() %} -
Organizer: {{ organizer_cn }}
- {% elif !organizer.is_empty() %} -
Organizer: {{ organizer }}
- {% endif %} -
- {% if !calendar_html.is_empty() %} - {% if !calendar_html.is_empty() %} -
{{ calendar_html | safe }}
- {% endif %} - {% endif %} +
+
+
+
Summary: {{ summary + }}
+
Start: {{ local_fmt_start }}
+
End: {{ local_fmt_end }}
+ {% if !organizer_cn.is_empty() %} +
Organizer: {{ organizer_cn }}
+ {% elif !organizer.is_empty() %} +
Organizer: {{ organizer }}
+ {% endif %} +
+ {% if all_days.len() > 0 %} +
+ + + + {% for wd in ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] %} + + {% endfor %} + + + {% for week in all_days|batch(7) %} + + {% for day in week %} + {% if event_days.contains(day) %} + + {% else %} + + {% endif %} + {% endfor %} + + {% endfor %} + +
{{ caption }}
{{ wd }}
{{ day.day() }}{{ day.day() }}
+
+ {% endif %} +
+
{% if !description_paragraphs.is_empty() %}
- {% for p in description_paragraphs %} -

{{ p }}

- {% endfor %} + {% for p in description_paragraphs %} +

{{ p }}

+ {% endfor %}
{% endif %}