ftml/render/html/element/
date.rs1use super::prelude::*;
22use crate::tree::DateItem;
23
24pub fn render_date(
25 ctx: &mut HtmlContext,
26 date: DateItem,
27 date_format: Option<&str>,
28 hover: bool,
29) {
30 let formatted_datetime = date.format_or_default(date_format, ctx.language());
31
32 match ctx.layout() {
33 Layout::Wikidot => {
34 render_date_wikidot(ctx, date, date_format, hover, &formatted_datetime)
35 }
36 Layout::Wikijump => {
37 render_date_wikijump(ctx, date, date_format, hover, &formatted_datetime)
38 }
39 }
40}
41
42fn render_date_wikidot(
43 ctx: &mut HtmlContext,
44 date: DateItem,
45 date_format: Option<&str>,
46 hover: bool,
47 formatted_datetime: &str,
48) {
49 let timestamp = date.timestamp();
50 let mut class = format!("odate time_{timestamp}");
51 push_date_format_class(&mut class, date_format, hover);
52 let style = if hover {
53 "cursor: help; display: inline;"
54 } else {
55 "display: inline;"
56 };
57
58 ctx.html()
59 .span()
60 .attr(attr!(
61 "class" => &class,
62 "style" => style,
63 ))
64 .contents(formatted_datetime);
65}
66
67fn render_date_wikijump(
68 ctx: &mut HtmlContext,
69 date: DateItem,
70 date_format: Option<&str>,
71 hover: bool,
72 formatted_datetime: &str,
73) {
74 let timestamp = str!(date.timestamp());
75 let delta = str!(date.time_since());
76 let mut class = str!("wj-date");
77
78 if hover {
79 class.push_str(" wj-date-hover");
80 }
81
82 push_date_format_class(&mut class, date_format, false);
83
84 ctx.html()
85 .span()
86 .attr(attr!(
87 "class" => &class,
88 "data-timestamp" => ×tamp,
89 "data-delta" => &delta,
90 ))
91 .contents(formatted_datetime);
92}
93
94fn push_date_format_class(
95 class: &mut String,
96 date_format: Option<&str>,
97 append_ago_hover: bool,
98) {
99 if let Some(date_format) = date_format {
100 class.push_str(" format_");
101 class.push_str(&encode_date_format(date_format));
102
103 if append_ago_hover {
104 class.push_str("%7Cagohover");
105 }
106 } else if append_ago_hover {
107 class.push_str(" format_");
108 class.push_str("%7Cagohover");
109 }
110}
111
112fn encode_date_format(date_format: &str) -> String {
113 let mut encoded = String::new();
114 for byte in date_format.bytes() {
115 match byte {
116 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
117 encoded.push(char::from(byte));
118 }
119 _ => {
120 encoded.push('%');
121 str_write!(&mut encoded, "{byte:02X}");
122 }
123 }
124 }
125
126 encoded
127}
128
129#[test]
130fn date_format_encoding() {
131 assert_eq!(encode_date_format("%d. %m. %Y"), "%25d.%20%25m.%20%25Y");
132}
133
134#[test]
135fn wikidot_date_class_includes_format() {
136 let mut class = str!("odate time_1216153821");
137 push_date_format_class(&mut class, Some("%d. %m. %Y"), false);
138
139 assert_eq!(class, "odate time_1216153821 format_%25d.%20%25m.%20%25Y");
140}
141
142#[test]
143fn wikidot_date_class_includes_ago_hover() {
144 let mut class = str!("odate time_1216153821");
145 push_date_format_class(&mut class, Some("%d. %m. %Y"), true);
146
147 assert_eq!(
148 class,
149 "odate time_1216153821 format_%25d.%20%25m.%20%25Y%7Cagohover"
150 );
151}
152
153#[test]
154fn wikidot_date_class_allows_ago_hover_without_format() {
155 let mut class = str!("odate time_1216153821");
156 push_date_format_class(&mut class, None, true);
157
158 assert_eq!(class, "odate time_1216153821 format_%7Cagohover");
159}
160
161#[test]
162fn wikijump_date_class_includes_format() {
163 let mut class = str!("wj-date");
164 push_date_format_class(&mut class, Some("%d. %m. %Y"), false);
165
166 assert_eq!(class, "wj-date format_%25d.%20%25m.%20%25Y");
167}