feat: added custom head and footer to posts
ea6bb03e
4 file(s) · +67 −5
| 166 | 166 | [], |
|
| 167 | 167 | ) |
|
| 168 | 168 | .ok(); |
|
| 169 | + | conn.execute( |
|
| 170 | + | "INSERT OR IGNORE INTO settings (key, value) VALUES ('custom_header', '')", |
|
| 171 | + | [], |
|
| 172 | + | ) |
|
| 173 | + | .ok(); |
|
| 174 | + | conn.execute( |
|
| 175 | + | "INSERT OR IGNORE INTO settings (key, value) VALUES ('custom_footer', '<a href=\"/feed.xml\" class=\"rss-link\" title=\"RSS Feed\"><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"32\" height=\"32\" fill=\"currentColor\" viewBox=\"0 0 256 256\"><path fill=\"currentColor\" d=\"M104.08 151.92A67.52 67.52 0 0 1 124 200a4 4 0 0 1-8 0a60 60 0 0 0-60-60a4 4 0 0 1 0-8a67.52 67.52 0 0 1 48.08 19.92M56 84a4 4 0 0 0 0 8a108 108 0 0 1 108 108a4 4 0 0 0 8 0A116 116 0 0 0 56 84m116 0A162.92 162.92 0 0 0 56 36a4 4 0 0 0 0 8a155 155 0 0 1 110.31 45.69A155 155 0 0 1 212 200a4 4 0 0 0 8 0a162.92 162.92 0 0 0-48-116M60 188a8 8 0 1 0 8 8a8 8 0 0 0-8-8\"/></svg></a>')", |
|
| 176 | + | [], |
|
| 177 | + | ) |
|
| 178 | + | .ok(); |
|
| 169 | 179 | ||
| 170 | 180 | Arc::new(Mutex::new(conn)) |
|
| 171 | 181 | } |
| 42 | 42 | nav_links: Vec<NavLink>, |
|
| 43 | 43 | favicon_url: String, |
|
| 44 | 44 | og_image_url: String, |
|
| 45 | + | header_html: String, |
|
| 46 | + | footer_html: String, |
|
| 45 | 47 | } |
|
| 46 | 48 | ||
| 47 | 49 | #[derive(Template)] |
|
| 65 | 67 | favicon_url: String, |
|
| 66 | 68 | og_image_url: String, |
|
| 67 | 69 | site_url: String, |
|
| 70 | + | header_html: String, |
|
| 71 | + | footer_html: String, |
|
| 68 | 72 | } |
|
| 69 | 73 | ||
| 70 | 74 | #[derive(Template)] |
|
| 77 | 81 | favicon_url: String, |
|
| 78 | 82 | og_image_url: String, |
|
| 79 | 83 | site_url: String, |
|
| 84 | + | header_html: String, |
|
| 85 | + | footer_html: String, |
|
| 80 | 86 | } |
|
| 81 | 87 | ||
| 82 | 88 | #[derive(Template)] |
|
| 89 | 95 | favicon_url: String, |
|
| 90 | 96 | og_image_url: String, |
|
| 91 | 97 | site_url: String, |
|
| 98 | + | header_html: String, |
|
| 99 | + | footer_html: String, |
|
| 92 | 100 | } |
|
| 93 | 101 | ||
| 94 | 102 | #[derive(Template)] |
|
| 128 | 136 | default_css: String, |
|
| 129 | 137 | favicon_url: String, |
|
| 130 | 138 | og_image_url: String, |
|
| 139 | + | custom_header: String, |
|
| 140 | + | custom_footer: String, |
|
| 131 | 141 | success: bool, |
|
| 132 | 142 | } |
|
| 133 | 143 | ||
| 140 | 150 | favicon_url: String, |
|
| 141 | 151 | og_image_url: String, |
|
| 142 | 152 | site_url: String, |
|
| 153 | + | header_html: String, |
|
| 154 | + | footer_html: String, |
|
| 143 | 155 | } |
|
| 144 | 156 | ||
| 145 | 157 | #[derive(Template)] |
|
| 257 | 269 | custom_css: String, |
|
| 258 | 270 | favicon_url: String, |
|
| 259 | 271 | og_image_url: String, |
|
| 272 | + | custom_header: String, |
|
| 273 | + | custom_footer: String, |
|
| 260 | 274 | } |
|
| 261 | 275 | ||
| 262 | 276 | // --- Helpers --- |
|
| 283 | 297 | } |
|
| 284 | 298 | } |
|
| 285 | 299 | ||
| 300 | + | fn get_header_footer_html(db: &db::Db) -> (String, String) { |
|
| 301 | + | let custom_header = db::get_setting(db, "custom_header") |
|
| 302 | + | .ok() |
|
| 303 | + | .flatten() |
|
| 304 | + | .unwrap_or_default(); |
|
| 305 | + | let custom_footer = db::get_setting(db, "custom_footer") |
|
| 306 | + | .ok() |
|
| 307 | + | .flatten() |
|
| 308 | + | .unwrap_or_default(); |
|
| 309 | + | let header_html = render_markdown(&custom_header); |
|
| 310 | + | let footer_html = render_markdown(&custom_footer); |
|
| 311 | + | (header_html, footer_html) |
|
| 312 | + | } |
|
| 313 | + | ||
| 286 | 314 | fn render_markdown(content: &str) -> String { |
|
| 287 | 315 | let mut options = Options::empty(); |
|
| 288 | 316 | options.insert(Options::ENABLE_STRIKETHROUGH); |
|
| 514 | 542 | ||
| 515 | 543 | let favicon_url = get_favicon_url(&state.db); |
|
| 516 | 544 | let og_image_url = get_og_image_url(&state.db); |
|
| 545 | + | let (header_html, footer_html) = get_header_footer_html(&state.db); |
|
| 517 | 546 | WebTemplate(IndexTemplate { |
|
| 518 | 547 | blog_title, |
|
| 519 | 548 | blog_description, |
|
| 523 | 552 | favicon_url, |
|
| 524 | 553 | og_image_url, |
|
| 525 | 554 | site_url: state.site_url.clone(), |
|
| 555 | + | header_html, |
|
| 556 | + | footer_html, |
|
| 526 | 557 | }) |
|
| 527 | 558 | .into_response() |
|
| 528 | 559 | } |
|
| 544 | 575 | let nav_links = get_nav_links(&state.db); |
|
| 545 | 576 | let favicon_url = get_favicon_url(&state.db); |
|
| 546 | 577 | let og_image_url = get_og_image_url(&state.db); |
|
| 578 | + | let (header_html, footer_html) = get_header_footer_html(&state.db); |
|
| 547 | 579 | WebTemplate(PostTemplate { |
|
| 548 | 580 | blog_title, |
|
| 549 | 581 | nav_links, |
|
| 552 | 584 | favicon_url, |
|
| 553 | 585 | og_image_url, |
|
| 554 | 586 | site_url: state.site_url.clone(), |
|
| 587 | + | header_html, |
|
| 588 | + | footer_html, |
|
| 555 | 589 | }) |
|
| 556 | 590 | .into_response() |
|
| 557 | 591 | } |
|
| 574 | 608 | let nav_links = get_nav_links(&state.db); |
|
| 575 | 609 | let favicon_url = get_favicon_url(&state.db); |
|
| 576 | 610 | let og_image_url = get_og_image_url(&state.db); |
|
| 611 | + | let (header_html, footer_html) = get_header_footer_html(&state.db); |
|
| 577 | 612 | WebTemplate(PageTemplate { |
|
| 578 | 613 | blog_title, |
|
| 579 | 614 | nav_links, |
|
| 582 | 617 | favicon_url, |
|
| 583 | 618 | og_image_url, |
|
| 584 | 619 | site_url: state.site_url.clone(), |
|
| 620 | + | header_html, |
|
| 621 | + | footer_html, |
|
| 585 | 622 | }) |
|
| 586 | 623 | .into_response() |
|
| 587 | 624 | } |
|
| 599 | 636 | let favicon_url = get_favicon_url(&state.db); |
|
| 600 | 637 | let og_image_url = get_og_image_url(&state.db); |
|
| 601 | 638 | ||
| 639 | + | let (header_html, footer_html) = get_header_footer_html(&state.db); |
|
| 640 | + | ||
| 602 | 641 | match db::get_published_posts(&state.db) { |
|
| 603 | 642 | Ok(posts) => WebTemplate(PostsListTemplate { |
|
| 604 | 643 | blog_title, |
|
| 607 | 646 | favicon_url, |
|
| 608 | 647 | og_image_url, |
|
| 609 | 648 | site_url: state.site_url.clone(), |
|
| 649 | + | header_html, |
|
| 650 | + | footer_html, |
|
| 610 | 651 | }) |
|
| 611 | 652 | .into_response(), |
|
| 612 | 653 | Err(e) => { |
|
| 946 | 987 | let custom_css = db::get_setting(&state.db, "custom_css").ok().flatten().unwrap_or_default(); |
|
| 947 | 988 | let favicon_url = db::get_setting(&state.db, "favicon_url").ok().flatten().unwrap_or_default(); |
|
| 948 | 989 | let og_image_url = db::get_setting(&state.db, "og_image_url").ok().flatten().unwrap_or_default(); |
|
| 990 | + | let custom_header = db::get_setting(&state.db, "custom_header").ok().flatten().unwrap_or_default(); |
|
| 991 | + | let custom_footer = db::get_setting(&state.db, "custom_footer").ok().flatten().unwrap_or_default(); |
|
| 949 | 992 | let default_css = Static::get("styles.css") |
|
| 950 | 993 | .map(|f| String::from_utf8_lossy(&f.data).into_owned()) |
|
| 951 | 994 | .unwrap_or_default(); |
|
| 959 | 1002 | default_css, |
|
| 960 | 1003 | favicon_url, |
|
| 961 | 1004 | og_image_url, |
|
| 1005 | + | custom_header, |
|
| 1006 | + | custom_footer, |
|
| 962 | 1007 | success: q.success, |
|
| 963 | 1008 | }) |
|
| 964 | 1009 | .into_response() |
|
| 976 | 1021 | let _ = db::set_setting(&state.db, "custom_css", &form.custom_css); |
|
| 977 | 1022 | let _ = db::set_setting(&state.db, "favicon_url", form.favicon_url.trim()); |
|
| 978 | 1023 | let _ = db::set_setting(&state.db, "og_image_url", form.og_image_url.trim()); |
|
| 1024 | + | let _ = db::set_setting(&state.db, "custom_header", &form.custom_header); |
|
| 1025 | + | let _ = db::set_setting(&state.db, "custom_footer", &form.custom_footer); |
|
| 979 | 1026 | Redirect::to("/admin/settings?success=true").into_response() |
|
| 980 | 1027 | } |
|
| 981 | 1028 | ||
| 29 | 29 | <label for="custom_css">custom CSS (overrides default styles)</label> |
|
| 30 | 30 | <textarea id="custom_css" name="custom_css" class="post-content">{% if custom_css.is_empty() %}{{ default_css }}{% else %}{{ custom_css }}{% endif %}</textarea> |
|
| 31 | 31 | </div> |
|
| 32 | + | <label for="custom_header">custom header (markdown or HTML, shown above nav on all pages)</label> |
|
| 33 | + | <textarea id="custom_header" name="custom_header" class="nav-links-input">{{ custom_header }}</textarea> |
|
| 34 | + | <label for="custom_footer">custom footer (markdown or HTML, shown at bottom of all pages)</label> |
|
| 35 | + | <textarea id="custom_footer" name="custom_footer" class="post-content">{{ custom_footer }}</textarea> |
|
| 32 | 36 | <button type="submit">save</button> |
|
| 33 | 37 | </form> |
|
| 34 | 38 | <h3>Data Export</h3> |
| 20 | 20 | <link rel="stylesheet" href="/custom-styles.css"> |
|
| 21 | 21 | </head> |
|
| 22 | 22 | <body> |
|
| 23 | + | {% if !header_html.is_empty() %} |
|
| 24 | + | <div class="custom-header"> |
|
| 25 | + | {{ header_html|safe }} |
|
| 26 | + | </div> |
|
| 27 | + | {% endif %} |
|
| 23 | 28 | <header class="header"> |
|
| 24 | 29 | <a href="/" class="logo">{{ blog_title }}</a> |
|
| 25 | 30 | <nav class="links"> |
|
| 32 | 37 | {% block content %}{% endblock %} |
|
| 33 | 38 | </main> |
|
| 34 | 39 | <footer class="footer"> |
|
| 35 | - | <a href="/feed.xml" class="rss-link" title="RSS Feed"> |
|
| 36 | - | <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="currentColor" viewBox="0 0 256 256"> |
|
| 37 | - | <path fill="currentColor" d="M104.08 151.92A67.52 67.52 0 0 1 124 200a4 4 0 0 1-8 0a60 60 0 0 0-60-60a4 4 0 0 1 0-8a67.52 67.52 0 0 1 48.08 19.92M56 84a4 4 0 0 0 0 8a108 108 0 0 1 108 108a4 4 0 0 0 8 0A116 116 0 0 0 56 84m116 0A162.92 162.92 0 0 0 56 36a4 4 0 0 0 0 8a155 155 0 0 1 110.31 45.69A155 155 0 0 1 212 200a4 4 0 0 0 8 0a162.92 162.92 0 0 0-48-116M60 188a8 8 0 1 0 8 8a8 8 0 0 0-8-8"/> |
|
| 38 | - | </svg> |
|
| 39 | - | </a> |
|
| 40 | + | {{ footer_html|safe }} |
|
| 40 | 41 | </footer> |
|
| 41 | 42 | </body> |
|
| 42 | 43 | </html> |
|