1use crate::check::register_meta_check;
4use crate::defaults::{MetaDefaults, TrailingSlash};
5use crate::headers::install_headers_middleware;
6use crate::page::MetaPage;
7use crate::slash::install_slash_middleware;
8use sova_core::{App, Plugin};
9
10pub struct Meta {
12 defaults: MetaDefaults,
13}
14
15impl Meta {
16 pub fn new() -> Self {
17 Self {
18 defaults: MetaDefaults::default(),
19 }
20 }
21
22 pub fn site_name(mut self, name: impl Into<String>) -> Self {
23 self.defaults.site_name = Some(name.into());
24 self
25 }
26
27 pub fn title_template(mut self, t: impl Into<String>) -> Self {
28 self.defaults.title_template = Some(t.into());
29 self
30 }
31
32 pub fn default_image(mut self, img: impl Into<String>) -> Self {
33 self.defaults.default_image = Some(img.into());
34 self
35 }
36
37 pub fn twitter_site(mut self, s: impl Into<String>) -> Self {
38 self.defaults.twitter_site = Some(s.into());
39 self
40 }
41
42 pub fn public_url(mut self, u: impl Into<String>) -> Self {
43 self.defaults.public_url = Some(u.into());
44 self
45 }
46
47 pub fn trailing_slash(mut self, p: TrailingSlash) -> Self {
48 self.defaults.trailing_slash = p;
49 self
50 }
51
52 pub fn soft_check(mut self) -> Self {
54 self.defaults.check_strict = false;
55 self
56 }
57
58 pub fn page() -> MetaPage {
59 MetaPage::new()
60 }
61
62 pub fn noindex() -> MetaPage {
63 MetaPage::new().noindex()
64 }
65}
66
67impl Default for Meta {
68 fn default() -> Self {
69 Self::new()
70 }
71}
72
73impl Plugin for Meta {
74 fn id(&self) -> &'static str {
75 "meta"
76 }
77
78 fn meta(&self) -> sova_core::PluginMeta {
79 sova_core::PluginMeta::new("Meta")
80 .description("Document meta, OG/Twitter, JSON-LD, and head inject")
81 .version(env!("CARGO_PKG_VERSION"))
82 }
83
84 fn install(mut self, app: &mut App) {
85 if let Some(doc) = app.config_doc() {
87 if let Some(section) = doc.section("meta") {
88 if section
89 .get("robots")
90 .and_then(|v| v.as_str())
91 == Some("block-all")
92 {
93 self.defaults.robots_block_all = true;
94 }
95 if self.defaults.public_url.is_none() {
96 if let Some(v) = section.get("public_url").and_then(|v| v.as_str()) {
97 self.defaults.public_url = Some(v.to_string());
98 }
99 }
100 if self.defaults.site_name.is_none() {
101 if let Some(v) = section.get("site_name").and_then(|v| v.as_str()) {
102 self.defaults.site_name = Some(v.to_string());
103 }
104 }
105 if self.defaults.title_template.is_none() {
106 if let Some(v) = section.get("title_template").and_then(|v| v.as_str()) {
107 self.defaults.title_template = Some(v.to_string());
108 }
109 }
110 if self.defaults.default_image.is_none() {
111 if let Some(v) = section.get("default_image").and_then(|v| v.as_str()) {
112 self.defaults.default_image = Some(v.to_string());
113 }
114 }
115 if section
116 .get("check")
117 .and_then(|v| v.as_str())
118 == Some("soft")
119 {
120 self.defaults.check_strict = false;
121 }
122 }
123 }
124
125 let defaults = self.defaults.clone();
126 app.state(defaults);
127
128 install_slash_middleware(app);
129 install_headers_middleware(app);
130
131 #[cfg(feature = "templates")]
132 {
133 use crate::html::render_html;
134 use crate::resolve_meta;
135 use minijinja::Value;
136 use sova_core::Request;
137 sova_templates::register_per_request(app, "meta", |req: &Request| {
138 let html = render_html(&resolve_meta(req));
139 Value::from_function(move || html.clone())
140 });
141 }
142
143 register_meta_check(app);
144 }
145}