Rust library to generate static websites
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(website): Docs layouts

+278 -27
+4
Cargo.toml
··· 6 6 maudit = { version = "0.1.0", path = "crates/framework" } 7 7 maud = { version = "0.26.0" } 8 8 serde = { version = "1.0.216" } 9 + 10 + [profile.dev] 11 + debug = 0 12 + strip = "debuginfo"
+1 -1
examples/blog/src/pages/index.rs
··· 12 12 13 13 impl Page for Index { 14 14 fn render(&self, ctx: &mut RouteContext) -> RenderResult { 15 - let articles = ctx.content.get_collection::<ArticleContent>("articles"); 15 + let articles = ctx.content.get_source::<ArticleContent>("articles"); 16 16 17 17 let markup = html! { 18 18 ul {
+1 -1
examples/kitchen-sink/src/pages/dynamic.rs
··· 10 10 pub page: u128, 11 11 } 12 12 13 - impl DynamicPage for DynamicExample { 13 + impl DynamicRoute for DynamicExample { 14 14 fn routes(&self, _: &DynamicRouteContext) -> Vec<RouteParams> { 15 15 (0..1).map(|i| Params { page: i }.into()).collect() 16 16 }
+4
package.json
··· 3 3 "private": true, 4 4 "devDependencies": { 5 5 "tailwindcss": "^3.4.17" 6 + }, 7 + "packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c", 8 + "dependencies": { 9 + "@tailwindcss/typography": "^0.5.15" 6 10 } 7 11 }
+41
pnpm-lock.yaml
··· 8 8 9 9 .: 10 10 dependencies: 11 + '@tailwindcss/typography': 12 + specifier: ^0.5.15 13 + version: 0.5.15(tailwindcss@3.4.17) 14 + devDependencies: 11 15 tailwindcss: 12 16 specifier: ^3.4.17 13 17 version: 3.4.17 ··· 55 59 '@pkgjs/parseargs@0.11.0': 56 60 resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 57 61 engines: {node: '>=14'} 62 + 63 + '@tailwindcss/typography@0.5.15': 64 + resolution: {integrity: sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==} 65 + peerDependencies: 66 + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20' 58 67 59 68 ansi-regex@5.0.1: 60 69 resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} ··· 219 228 lines-and-columns@1.2.4: 220 229 resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} 221 230 231 + lodash.castarray@4.4.0: 232 + resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} 233 + 234 + lodash.isplainobject@4.0.6: 235 + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} 236 + 237 + lodash.merge@4.6.2: 238 + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 239 + 222 240 lru-cache@10.4.3: 223 241 resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} 224 242 ··· 317 335 peerDependencies: 318 336 postcss: ^8.2.14 319 337 338 + postcss-selector-parser@6.0.10: 339 + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} 340 + engines: {node: '>=4'} 341 + 320 342 postcss-selector-parser@6.1.2: 321 343 resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} 322 344 engines: {node: '>=4'} ··· 476 498 '@pkgjs/parseargs@0.11.0': 477 499 optional: true 478 500 501 + '@tailwindcss/typography@0.5.15(tailwindcss@3.4.17)': 502 + dependencies: 503 + lodash.castarray: 4.4.0 504 + lodash.isplainobject: 4.0.6 505 + lodash.merge: 4.6.2 506 + postcss-selector-parser: 6.0.10 507 + tailwindcss: 3.4.17 508 + 479 509 ansi-regex@5.0.1: {} 480 510 481 511 ansi-regex@6.1.0: {} ··· 626 656 627 657 lines-and-columns@1.2.4: {} 628 658 659 + lodash.castarray@4.4.0: {} 660 + 661 + lodash.isplainobject@4.0.6: {} 662 + 663 + lodash.merge@4.6.2: {} 664 + 629 665 lru-cache@10.4.3: {} 630 666 631 667 merge2@1.4.1: {} ··· 697 733 dependencies: 698 734 postcss: 8.4.49 699 735 postcss-selector-parser: 6.1.2 736 + 737 + postcss-selector-parser@6.0.10: 738 + dependencies: 739 + cssesc: 3.0.0 740 + util-deprecate: 1.0.2 700 741 701 742 postcss-selector-parser@6.1.2: 702 743 dependencies:
+29
website/content/docs/index.md
··· 1 + --- 2 + title: "Prologue" 3 + description: "You're gonna learn" 4 + --- 5 + 6 + ## Section 1: Formatting Text 7 + 8 + ### Bold and Italic 9 + 10 + - **Bold Text** makes things stand out. 11 + - _Italic Text_ gives emphasis to words. 12 + - **_Bold and Italic_** combined for maximum emphasis. 13 + 14 + ### Mixed Formatting 15 + 16 + - **This is bold and _italic_** to combine styles. 17 + - _This is italic and **bold**_ as well. 18 + 19 + ## Section 2: Links 20 + 21 + You can include links like this: 22 + 23 + - [Check out the Markdown Guide](https://www.markdownguide.org/) 24 + 25 + ## Section 3: Combining All Features 26 + 27 + Here’s a sentence that combines everything: 28 + 29 + - **_Check out the [Markdown Guide](https://www.markdownguide.org/) for more tips_**.
+6
website/content/docs/installation.md
··· 1 + --- 2 + title: "Installation" 3 + description: "How to install Maudit" 4 + section: "getting-started" 5 + --- 6 +
+7
website/content/docs/philosophy.md
··· 1 + --- 2 + title: "Philosophy" 3 + description: "What is Maudit?" 4 + section: "getting-started" 5 + --- 6 + 7 + Maudit is a tool for creating and managing documentation websites. It is designed to be simple, fast, and easy to use. Maudit is built with [Hugo](https://gohugo.io/), a static site generator written in Go.
+32
website/src/content.rs
··· 1 + use maud::{PreEscaped, Render}; 2 + use maudit::content::{glob_markdown, ContentSource, ContentSources}; 3 + use maudit::content_sources; 4 + use serde::Deserialize; 5 + 6 + #[derive(Deserialize, Eq, PartialEq, PartialOrd, Hash, Clone)] 7 + #[serde(rename_all = "kebab-case")] 8 + pub enum DocsSection { 9 + GettingStarted, 10 + } 11 + 12 + impl Render for DocsSection { 13 + fn render(&self) -> PreEscaped<String> { 14 + match self { 15 + DocsSection::GettingStarted => PreEscaped("Getting Started".to_string()), 16 + } 17 + } 18 + } 19 + 20 + #[derive(Deserialize)] 21 + pub struct DocsContent { 22 + pub title: String, 23 + pub description: String, 24 + pub section: Option<DocsSection>, 25 + } 26 + 27 + pub fn content_sources() -> ContentSources { 28 + content_sources!(ContentSource::new( 29 + "docs", 30 + glob_markdown::<DocsContent>("content/docs/*.md") 31 + )) 32 + }
+22
website/src/layout.rs
··· 1 1 use maud::{html, Markup}; 2 + mod docs_sidebar; 2 3 mod header; 4 + 5 + use docs_sidebar::sidebar; 3 6 4 7 pub use header::header; 5 8 use maudit::generator; 6 9 use maudit::page::{RenderResult, RouteContext}; 7 10 11 + pub fn docs_layout(main: Markup, ctx: &mut RouteContext) -> RenderResult { 12 + ctx.assets.include_style("assets/prin.css", true); 13 + 14 + layout( 15 + html! { 16 + div.container.mx-auto.grid-cols-docs.grid.p-6.py-8 { 17 + aside { 18 + (sidebar(ctx)) 19 + } 20 + main.w-full.max-w-larger-prose.mx-auto { 21 + (main) 22 + } 23 + } 24 + }, 25 + ctx, 26 + ) 27 + } 28 + 8 29 pub fn layout(main: Markup, ctx: &mut RouteContext) -> RenderResult { 9 30 ctx.assets.include_style("assets/prin.css", true); 10 31 ··· 14 35 (generator()) 15 36 } 16 37 body { 38 + (header(ctx)) 17 39 (main) 18 40 } 19 41 }
+40
website/src/layout/docs_sidebar.rs
··· 1 + use maud::{html, Markup}; 2 + use maudit::page::RouteContext; 3 + 4 + use crate::content::DocsContent; 5 + 6 + pub fn sidebar(ctx: &mut RouteContext) -> Markup { 7 + let content = ctx.content.get_source::<DocsContent>("docs"); 8 + 9 + // Map entries into HTML 10 + let mut sections = std::collections::HashMap::new(); 11 + 12 + for entry in content.entries.iter() { 13 + if let Some(section) = &entry.data.section { 14 + sections.entry(section).or_insert_with(Vec::new).push(entry); 15 + } 16 + } 17 + 18 + let entries = sections.iter().map(|(section, entries)| { 19 + html! { 20 + li { 21 + h2.text-lg.font-bold { (section) } 22 + ul.pl-1 { 23 + @for entry in entries { 24 + li { 25 + a href=(format!("/docs/{}", entry.id)) { (entry.data.title) } 26 + } 27 + } 28 + } 29 + } 30 + } 31 + }); 32 + 33 + html! { 34 + ul { 35 + @for entry in entries { 36 + (entry) 37 + } 38 + } 39 + } 40 + }
+1 -2
website/src/layout/header.rs
··· 6 6 pub fn header(_: &mut RouteContext) -> Markup { 7 7 html! { 8 8 header.px-8.py-4.bg-faded-black.text-our-white { 9 - div.container.flex.items-center.gap-x-8 { 9 + div.container.flex.items-center.gap-x-8.mx-auto { 10 10 a.flex.gap-x-2.items-center."hover:text-brighter-brand" href="/" { 11 11 (PreEscaped(include_str!("../../assets/logo.svg"))) 12 12 h1.text-2xl.tracking-wide { "Maudit" } ··· 14 14 nav.text-lg.flex.gap-x-12.relative."top-[2px]" { 15 15 a."hover:text-brighter-brand" href="/docs" { "Documentation" } 16 16 a."hover:text-brighter-brand" href="/news" { "News" } 17 - a."hover:text-brighter-brand" href="/community" { "Community" } 18 17 } 19 18 div {} 20 19 }
+9 -6
website/src/main.rs
··· 1 + use content::content_sources; 1 2 use maudit::{coronate, routes, BuildOptions, BuildOutput}; 2 3 4 + mod content; 3 5 mod layout; 4 - mod pages { 5 - mod index; 6 - pub use index::Index; 7 - } 6 + mod pages; 8 7 9 - pub use pages::Index; 8 + use pages::{DocsIndex, DocsPage, Index}; 10 9 11 10 fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> { 12 - coronate(routes![Index], vec![].into(), BuildOptions::default()) 11 + coronate( 12 + routes![Index, DocsIndex, DocsPage], 13 + content_sources(), 14 + BuildOptions::default(), 15 + ) 13 16 }
+62
website/src/pages/docs.rs
··· 1 + use maud::{html, Markup, PreEscaped}; 2 + use maudit::{content::ContentEntry, page::prelude::*}; 3 + 4 + use crate::{content::DocsContent, layout::docs_layout}; 5 + 6 + #[route("/docs")] 7 + pub struct DocsIndex; 8 + 9 + impl Page for DocsIndex { 10 + fn render(&self, ctx: &mut RouteContext) -> RenderResult { 11 + let index_page = ctx 12 + .content 13 + .get_source::<DocsContent>("docs") 14 + .get_entry("index"); 15 + 16 + docs_layout(render_entry(index_page), ctx) 17 + } 18 + } 19 + 20 + fn render_entry(entry: &ContentEntry<DocsContent>) -> Markup { 21 + html! { 22 + section.mb-4 { 23 + @if let Some(section) = &entry.data.section { 24 + p.text-sm.font-bold.mb-2 { (section) } 25 + } 26 + h2.text-5xl.font-bold.mb-2 { (entry.data.title) } 27 + h3.text-lg { (entry.data.description) } 28 + } 29 + section.prose."lg:prose-lg".max-w-none { 30 + (PreEscaped((entry.render)())) 31 + } 32 + } 33 + } 34 + 35 + #[route("/docs/[slug]")] 36 + pub struct DocsPage; 37 + 38 + #[derive(Params)] 39 + struct DocsPageParams { 40 + slug: String, 41 + } 42 + 43 + impl DynamicRoute for DocsPage { 44 + fn routes(&self, ctx: &DynamicRouteContext) -> Vec<RouteParams> { 45 + let content = ctx.content.get_source::<DocsContent>("docs"); 46 + 47 + content.into_params(|entry| DocsPageParams { 48 + slug: entry.id.clone(), 49 + }) 50 + } 51 + } 52 + 53 + impl Page for DocsPage { 54 + fn render(&self, ctx: &mut RouteContext) -> RenderResult { 55 + let entry = ctx 56 + .content 57 + .get_source::<DocsContent>("docs") 58 + .get_entry(&ctx.params::<DocsPageParams>().slug); 59 + 60 + docs_layout(render_entry(entry), ctx) 61 + } 62 + }
+3 -16
website/src/pages/index.rs
··· 1 1 use maud::html; 2 2 use maudit::page::prelude::*; 3 3 4 - use crate::layout::{header, layout}; 4 + use crate::layout::layout; 5 5 6 6 #[route("/")] 7 7 pub struct Index; 8 8 9 9 impl Page for Index { 10 10 fn render(&self, ctx: &mut RouteContext) -> RenderResult { 11 - let features = [("Feature 1", "Description 1")] 12 - .iter() 13 - .map(|(title, description)| { 14 - html! { 15 - div.card { 16 - h4 { (title) } 17 - p { (description) } 18 - } 19 - } 20 - }) 21 - .collect::<Vec<_>>(); 22 - 23 11 layout( 24 12 html! { 25 - (header(ctx)) 26 13 div.w-screen { 27 14 div."lg:container".mx-auto.hero-background { 28 15 div.px-6.py-10.mx-6.my-14 { ··· 36 23 "Or, in simpler words, " span.text-brand-red {"a static site generator"} "." 37 24 } 38 25 div.mt-6.leading-tight { 39 - a.btn.block.group href="/docs" { "Get Started" span.inline-block."group-hover:translate-x-4".transition-transform.translate-x-2 { "→" } } 40 - span.opacity-75.italic { "or scroll down to learn more" } 26 + a.btn.block.group.inline-block href="/docs" { "Get Started" span.inline-block."group-hover:translate-x-3".transition-transform.translate-x-2 { "→" } } 27 + p.opacity-75.italic { "or scroll down to learn more" } 41 28 } 42 29 } 43 30 }
+4
website/src/pages/mod.rs
··· 1 + mod index; 2 + pub use index::Index; 3 + mod docs; 4 + pub use docs::{DocsIndex, DocsPage};
+12 -1
website/tailwind.config.js
··· 1 + import typography from "@tailwindcss/typography"; 1 2 import plugin from "tailwindcss/plugin"; 2 3 3 4 /** @type {import('tailwindcss').Config} */ ··· 12 13 "brand-red": "#BA1F33", 13 14 "brighter-brand": "#FA3252", 14 15 }, 16 + gridTemplateColumns: { 17 + docs: "0.15fr 0.70fr 0.15fr", 18 + }, 19 + maxWidth: { 20 + "larger-prose": "75ch", 21 + }, 15 22 }, 16 23 }, 17 24 plugins: [ 25 + typography, 18 26 plugin(({ addBase, theme }) => { 19 27 addBase({ 20 28 "html, body": { ··· 46 54 backgroundImage: 47 55 "url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHZpZXdCb3g9IjAgMCA0NjYgNDY1Ij48ZGVmcz48Y2xpcFBhdGggaWQ9ImEiIGNsaXBQYXRoVW5pdHM9InVzZXJTcGFjZU9uVXNlIj48cGF0aCBkPSJNMjMgMTRoNDc2djQ3NUgyM1ptMjQ3IDU4Yy0yOCAwLTU1IDYtODEgMjBhMTY0IDE2NCAwIDAgMC03MCAyMjRjNDUgODEgMTQ3IDExMSAyMjkgNjggODMtNDMgMTE0LTE0NCA3MC0yMjRsLTEzIDdjNDAgNzMgMTIgMTY0LTY0IDIwNC03NSAzOS0xNjggMTEtMjA4LTYyLTQxLTc0LTEyLTE2NSA2My0yMDRzMTY5LTExIDIwOSA2MmwxMy03Yy0zMC01Ni04OC04Ny0xNDgtODhabTcyIDI0OSAzNCA2di01MmwtMi01Mi00LTIyLTMtMjIgNC0zIDUtM2MxLTIgMi02IDEtMTItMS00LTEtNS00LTdzLTMtMi04LTJoLTZsLTQgNS00IDV2MTJsNCAzIDMgMi0xMSAzOGMtMTAgMzYtMTEgMzktMTQgMzlsLTI2LTc3IDMtMSA0LTNjMi0zIDEtOC0xLTEzLTItNC0zLTQtNy01bC03IDFjLTMgMy00IDMtNSA3bC0xIDQgMyA0IDMgNC04IDMxLTExIDM3LTMgOWMtMSAyLTQtMy0xNS0yNC0xMS0yMi0xMS0yMi0xNS0zNGwtNC0xNCAzLTdjMy04IDMtMTAgMS0xNS0zLTQtNi02LTEyLTQtNCAwLTQgMS04IDVsLTQgNCAxIDYgNSAxMSAyIDVhMTIzMSAxMjMxIDAgMCAwLTExIDQwbC0zIDEwLTM1LTQ1LTgtMTIgMS0yIDItNy0yLTgtNi0yYy00LTEtNC0xLTcgMS00IDItNCAyLTQgNi0xIDUgMSAxMSA1IDEybDEgMSAyIDY5IDMgNjlhOTA4IDkwOCAwIDAgMSA1OS00bDQxLTIgMjAgMmMyMSAwIDIxIDAgNTMgNnoiIGNsYXNzPSJwb3dlcmNsaXAiIHN0eWxlPSJjb2xvcjojMDAwO2ZpbGw6I2ZmZjtzdHJva2Utd2lkdGg6MTstaW5rc2NhcGUtc3Ryb2tlOm5vbmUiLz48L2NsaXBQYXRoPjwvZGVmcz48cGF0aCBkPSJNNDU3IDQxMWMtNyAwLTE1IDItMjEgNy0zNSAyNi0xMCA3NSAzMSA0MSAzMy0yNyAxNC00OS0xMC00OFpNMjkwIDE5Yy0zMSAwLTY3IDE0LTg3IDI1LTE1IDgtNjQgMzctMTA3IDQ2LTU1IDEwLTg3IDYzLTU1IDkwIDU0IDQ0IDU2IDEwMSAxNiAxNDMtNDQgNDYgMSAxMTMgNjQgMTA2IDYzLTggODkgMCAxMjEgMzIgNDEgNDEgMTMyIDI1IDE1NC0zMyAxMC0yNiAyNC01NiA1OC02OCA0MS0xNCA1My01OSAyNi05MS0xOS0yMy0yMS00NyA1LTk1IDIyLTQwIDUtODQtNTYtODEtNDUgMy02NC0yNy05Ny01OWE1OSA1OSAwIDAgMC00Mi0xNXoiIGNsaXAtcGF0aD0idXJsKCNhKSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTI4IC0xOSkiIGZpbGw9IiNiYTFmMzMiLz48L3N2Zz4K');", 48 56 backgroundRepeat: "no-repeat", 49 - backgroundPositionX: "calc(100% - 5rem)", 57 + backgroundPositionX: "calc(100%)", 58 + "@media (min-width: 1280px)": { 59 + backgroundPositionX: "calc(100% - 5rem)", 60 + }, 50 61 }, 51 62 }); 52 63 }),