server: move calendar widget to askama
This commit is contained in:
parent
5b48c5dbc3
commit
43d856ae7e
@ -1,3 +1,29 @@
|
|||||||
|
// Inline Askama filters module for template use
|
||||||
|
mod filters {
|
||||||
|
// Usage: {{ items|batch(7) }}
|
||||||
|
pub fn batch<T: Clone>(
|
||||||
|
items: &[T],
|
||||||
|
_: &dyn ::askama::Values,
|
||||||
|
size: usize,
|
||||||
|
) -> askama::Result<Vec<Vec<T>>> {
|
||||||
|
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 std::io::{Cursor, Read};
|
||||||
|
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
@ -1099,7 +1125,9 @@ pub struct IcalSummaryTemplate<'a> {
|
|||||||
pub local_fmt_end: &'a str,
|
pub local_fmt_end: &'a str,
|
||||||
pub organizer: &'a str,
|
pub organizer: &'a str,
|
||||||
pub organizer_cn: &'a str,
|
pub organizer_cn: &'a str,
|
||||||
pub calendar_html: &'a str,
|
pub all_days: Vec<NaiveDate>,
|
||||||
|
pub event_days: Vec<NaiveDate>,
|
||||||
|
pub caption: String,
|
||||||
pub description_paragraphs: &'a [String],
|
pub description_paragraphs: &'a [String],
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1385,11 +1413,45 @@ pub fn render_ical_summary(ical_data: &str) -> Result<String, ServerError> {
|
|||||||
(String::new(), String::new(), vec![])
|
(String::new(), String::new(), vec![])
|
||||||
};
|
};
|
||||||
|
|
||||||
// Render calendar table HTML
|
// Compute calendar grid for template rendering
|
||||||
let calendar_html = if !event_days.is_empty() {
|
let (all_days, caption) = if !event_days.is_empty() {
|
||||||
render_merged_calendar_table(&event_days)
|
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 {
|
} else {
|
||||||
String::new()
|
(vec![], String::new())
|
||||||
};
|
};
|
||||||
|
|
||||||
// Description paragraphs
|
// Description paragraphs
|
||||||
@ -1408,7 +1470,6 @@ pub fn render_ical_summary(ical_data: &str) -> Result<String, ServerError> {
|
|||||||
let organizer_cn_val = organizer_cn.unwrap_or("");
|
let organizer_cn_val = organizer_cn.unwrap_or("");
|
||||||
let local_fmt_start_val = &local_fmt_start;
|
let local_fmt_start_val = &local_fmt_start;
|
||||||
let local_fmt_end_val = &local_fmt_end;
|
let local_fmt_end_val = &local_fmt_end;
|
||||||
let calendar_html_val = &calendar_html;
|
|
||||||
let description_paragraphs_val = &description_paragraphs;
|
let description_paragraphs_val = &description_paragraphs;
|
||||||
let template = IcalSummaryTemplate {
|
let template = IcalSummaryTemplate {
|
||||||
summary: summary_val,
|
summary: summary_val,
|
||||||
@ -1416,7 +1477,9 @@ pub fn render_ical_summary(ical_data: &str) -> Result<String, ServerError> {
|
|||||||
local_fmt_end: local_fmt_end_val,
|
local_fmt_end: local_fmt_end_val,
|
||||||
organizer: organizer_val,
|
organizer: organizer_val,
|
||||||
organizer_cn: organizer_cn_val,
|
organizer_cn: organizer_cn_val,
|
||||||
calendar_html: calendar_html_val,
|
all_days,
|
||||||
|
event_days: event_days.clone(),
|
||||||
|
caption,
|
||||||
description_paragraphs: description_paragraphs_val,
|
description_paragraphs: description_paragraphs_val,
|
||||||
};
|
};
|
||||||
summary_parts.push(template.render()?);
|
summary_parts.push(template.render()?);
|
||||||
|
|||||||
@ -1,55 +1,86 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
.ical-flex {
|
.ical-flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
max-width: 700px;
|
max-width: 700px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.ical-flex .summary-block {
|
|
||||||
flex: 1 1 0%;
|
.ical-flex .summary-block {
|
||||||
}
|
flex: 1 1 0%;
|
||||||
.ical-flex .calendar-block {
|
}
|
||||||
flex: none;
|
|
||||||
margin-left: auto;
|
.ical-flex .calendar-block {
|
||||||
}
|
flex: none;
|
||||||
@media (max-width: 599px) {
|
margin-left: auto;
|
||||||
.ical-flex {
|
}
|
||||||
flex-direction: column;
|
|
||||||
}
|
@media (max-width: 599px) {
|
||||||
.ical-flex > div.summary-block {
|
.ical-flex {
|
||||||
margin-bottom: 0.5em;
|
flex-direction: column;
|
||||||
margin-left: 0;
|
}
|
||||||
}
|
|
||||||
.ical-flex > div.calendar-block {
|
.ical-flex>div.summary-block {
|
||||||
margin-left: 0;
|
margin-bottom: 0.5em;
|
||||||
}
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ical-flex>div.calendar-block {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="ical-flex">
|
<div class="ical-flex">
|
||||||
<div class="summary-block" style="background:#f7f7f7; border-radius:8px; box-shadow:0 2px 8px #bbb; padding:16px 18px; margin:0 0 8px 0; min-width:220px; max-width:400px; font-size:15px; color:#222;">
|
<div class="summary-block"
|
||||||
<div style="font-size:17px; font-weight:bold; margin-bottom:8px; color:#333;"><b>Summary:</b> {{ summary }}</div>
|
style="background:#f7f7f7; border-radius:8px; box-shadow:0 2px 8px #bbb; padding:16px 18px; margin:0 0 8px 0; min-width:220px; max-width:700px; font-size:15px; color:#222;">
|
||||||
<div style="margin-bottom:4px;"><b>Start:</b> {{ local_fmt_start }}</div>
|
<div
|
||||||
<div style="margin-bottom:4px;"><b>End:</b> {{ local_fmt_end }}</div>
|
style="display: flex; flex-direction: row; flex-wrap: wrap; align-items: flex-start; gap: 0.5em; width: 100%;">
|
||||||
{% if !organizer_cn.is_empty() %}
|
<div style="flex: 1 1 220px; min-width: 180px;">
|
||||||
<div style="margin-bottom:4px;"><b>Organizer:</b> {{ organizer_cn }}</div>
|
<div style="font-size:17px; font-weight:bold; margin-bottom:8px; color:#333;"><b>Summary:</b> {{ summary
|
||||||
{% elif !organizer.is_empty() %}
|
}}</div>
|
||||||
<div style="margin-bottom:4px;"><b>Organizer:</b> {{ organizer }}</div>
|
<div style="margin-bottom:4px;"><b>Start:</b> {{ local_fmt_start }}</div>
|
||||||
{% endif %}
|
<div style="margin-bottom:4px;"><b>End:</b> {{ local_fmt_end }}</div>
|
||||||
</div>
|
{% if !organizer_cn.is_empty() %}
|
||||||
{% if !calendar_html.is_empty() %}
|
<div style="margin-bottom:4px;"><b>Organizer:</b> {{ organizer_cn }}</div>
|
||||||
{% if !calendar_html.is_empty() %}
|
{% elif !organizer.is_empty() %}
|
||||||
<div class="calendar-block" style="margin-top: 8px;">{{ calendar_html | safe }}</div>
|
<div style="margin-bottom:4px;"><b>Organizer:</b> {{ organizer }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
</div>
|
||||||
|
{% if all_days.len() > 0 %}
|
||||||
|
<div class="calendar-block" style="flex: none; margin-left: auto; min-width: 180px;">
|
||||||
|
<table class="ical-month" style="border-collapse:collapse; min-width:220px; background:#fff; box-shadow:0 2px 8px #bbb; font-size:14px; margin:0;">
|
||||||
|
<caption style="caption-side:top; text-align:center; font-weight:bold; font-size:16px; padding:8px 0;">{{ caption }}</caption>
|
||||||
|
<thead><tr>
|
||||||
|
{% for wd in ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] %}
|
||||||
|
<th style="padding:4px 6px; border-bottom:1px solid #ccc; color:#666; font-weight:600; background:#f7f7f7">{{ wd }}</th>
|
||||||
|
{% endfor %}
|
||||||
|
</tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for week in all_days|batch(7) %}
|
||||||
|
<tr>
|
||||||
|
{% for day in week %}
|
||||||
|
{% if event_days.contains(day) %}
|
||||||
|
<td style="background:#ffd700; color:#222; font-weight:bold; border:1px solid #aaa; border-radius:4px; text-align:center;">{{ day.day() }}</td>
|
||||||
|
{% else %}
|
||||||
|
<td style="border:1px solid #eee; text-align:center;background:#f7f7f7;color:#bbb;">{{ day.day() }}</td>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if !description_paragraphs.is_empty() %}
|
{% if !description_paragraphs.is_empty() %}
|
||||||
<div style="max-width:700px; width:100%;">
|
<div style="max-width:700px; width:100%;">
|
||||||
{% for p in description_paragraphs %}
|
{% for p in description_paragraphs %}
|
||||||
<p style="margin: 0 0 8px 0; color:#444;">{{ p }}</p>
|
<p style="margin: 0 0 8px 0; color:#444;">{{ p }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user