Skip to content

Commit d34e344

Browse files
authored
docs: some docs (#36)
* docs: some docs * fix: perf * fix: wteak
1 parent 779bef7 commit d34e344

11 files changed

Lines changed: 467 additions & 69 deletions

File tree

website/content/docs/content.md

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
---
2+
title: "Content"
3+
description: "Learn how to load and use content and data in your Maudit site."
4+
section: "core-concepts"
5+
---
6+
7+
Maudit lets you load content and data in different formats to use across your website.
8+
9+
For example, you can configure a folder of Markdown (.md) files to be converted into HTML and included in your pages. You can also fetch a remote JSON file at build time and use its data within your site.
10+
11+
In Maudit, this concept is called Content Sources. A content source is a collection of content or data, usually (but not necessarily) structured in a homogeneous way, that your website can use.
12+
13+
## Defining a content source
14+
15+
Content sources are defined in the coronate entry point through the `content_sources!` macro.
16+
17+
```rs
18+
use maudit::content::content_sources;
19+
20+
#[markdown_entry]
21+
pub struct BlogPost {
22+
pub title: String,
23+
pub description: Option<String>,
24+
}
25+
26+
fn main() {
27+
coronate(
28+
routes![
29+
// ...
30+
],
31+
content_sources![
32+
"source_name" => loader(...),
33+
"another_source" => glob_markdown<BlogPost>("path/to/files/*.md", None)
34+
],
35+
Default::default()
36+
);
37+
}
38+
```
39+
40+
Where `loader` and `glob_markdown` are functions returning a Vec of `ContentEntry`. Typically, a loader also accepts a type argument specifying the shape of the data for each entries it returns, which will be used inside your pages to provide typed content.
41+
42+
## Using a content source in pages
43+
44+
Once a content source is defined, it can be accessed in pages through the `RouteContext#content` property.
45+
46+
```rs
47+
use maudit::page::prelude::*;
48+
use maud::{html, PreEscaped};
49+
50+
#[route("/some-article")]
51+
pub struct SomeArticlePage;
52+
53+
impl Page for SomeArticlePage {
54+
fn render(&self, ctx: &mut RouteContext) -> RenderResult {
55+
let entry = ctx
56+
.content
57+
.get_source::<BlogPost>("source_name")
58+
.get_entry("entry_id");
59+
60+
let entry_data = entry.data(ctx);
61+
62+
html! {
63+
h1 { (entry_data.title) }
64+
@if let Some(description) = &entry_data.description {
65+
p { (description) }
66+
}
67+
68+
(PreEscaped(entry.render(ctx)))
69+
}.into()
70+
}
71+
}
72+
```
73+
74+
## Loaders
75+
76+
### Built-in loaders
77+
78+
#### `glob_markdown`
79+
80+
The `glob_markdown` loader can be used to load one or multiple folders of Markdown (`.md`) files.
81+
82+
```rs
83+
use maudit::content::{glob_markdown, markdown_entry};
84+
85+
#[markdown_entry]
86+
pub struct DocsContent {
87+
pub title: String,
88+
pub description: Option<String>,
89+
pub section: Option<DocsSection>,
90+
}
91+
92+
"docs" => glob_markdown::<DocsContent>("content/docs/*.md", None)
93+
```
94+
95+
This loader take a glob pattern (compatible with [the `glob` crate](https://github.com/rust-lang/glob)) as its first argument, and an optional `MarkdownOptions` struct as its second argument to customise Markdown rendering. The frontmatter of each Markdown file will be deserialized using [Serde](https://serde.rs) into the type argument provided to `glob_markdown`, which can use the `#[markdown_entry]` macro to derive the necessary traits and add the necessary properties to the struct. Note that using this feature require the installation of Serde into your project as the macro uses Serde's derive macros.
96+
97+
### Custom loaders
98+
99+
As said previously, a loader is simply a function returning a Vec of `ContentEntry`. This means you can create your own loaders to load content from any source you want, as long as you return the right type.
100+
101+
For instance, you could create a loader that fetches a remote JSON file and deserializes it into a struct, producing a content source with a single entry:
102+
103+
```rs
104+
use maudit::content::{ContentEntry};
105+
106+
#[derive(serde::Deserialize)]
107+
pub struct MyType {
108+
pub id: u32,
109+
pub name: String,
110+
}
111+
112+
pub fn my_loader(path: &str) -> Vec<ContentEntry<MyType>> {
113+
let response = reqwest::blocking::get(path).unwrap();
114+
let data = response.json::<MyType>().unwrap();
115+
116+
vec![ContentEntry::new(data.id.into(), None, None, data, None)]
117+
}
118+
119+
// Use it as a content source:
120+
use maudit::content::content_sources;
121+
122+
content_sources![
123+
"my_data" => my_loader("https://example.com/data.json")
124+
];
125+
```
126+
127+
and then in pages, you could access the data like this:
128+
129+
```rs
130+
use maudit::page::prelude::*;
131+
132+
#[route("/data")]
133+
pub struct DataPage;
134+
135+
impl Page for DataPage {
136+
fn render(&self, ctx: &mut RouteContext) -> RenderResult {
137+
let entry = ctx
138+
.content
139+
.get_source::<MyType>("my_data")
140+
.get_entry("0");
141+
142+
let entry_data = entry.data();
143+
144+
format!(
145+
"<h1>Data</h1><p>ID: {}, Name: {}</p>",
146+
entry_data.id, entry_data.name
147+
).into()
148+
}
149+
}
150+
```
151+
152+
Content entries can also be rendered by passing a render function to the `render` method of `ContentEntry`.
153+
154+
```rs
155+
ContentEntry::new(
156+
data.id.into(),
157+
Some(Box::new(|content, ctx| {
158+
// render the content string into HTML
159+
maudit::render_markdown(content, markdown_options, None, ctx)
160+
})),
161+
None,
162+
data,
163+
None,
164+
)
165+
```
166+
167+
## Markdown rendering
168+
169+
Either through loaders or by using the `render_markdown` function directly, Maudit supports rendering local and remote Markdown and enriching it with shortcodes and custom components.
170+
171+
### Shortcodes
172+
173+
Shortcodes provide a way to extend Markdown with custom functionality. They serve a similar role to [components in MDX](https://mdxjs.com) or [tags in Markdoc](https://markdoc.dev/docs/tags), allowing authors to define and reuse snippets throughout their content. Shortcodes can accept attributes and content, and can be self-closing or not.
174+
175+
```markdown
176+
---
177+
title: { { enhance title="Super Title" / } }
178+
---
179+
180+
Here's an image with a caption:
181+
182+
{{ image src="./image.png" }}
183+
This is a caption!
184+
{{ /image }}
185+
```
186+
187+
This snippet could expand into something like this:
188+
189+
```markdown
190+
---
191+
title: Very Cool Super Title
192+
---
193+
194+
Here's an image with a caption:
195+
196+
<figure>
197+
<img src="/_maudit/image.hash.webp" width="200" height="200" loading="lazy" decoding="async" />
198+
<figcaption>This is a caption!</figcaption>
199+
</figure>
200+
```
201+
202+
To define a shortcode, create an instance of `MarkdownShortcodes` and use the `register` method to register shortcodes. This can then be passed as the `shortcodes` parameter of `MarkdownOptions`.
203+
204+
```rs
205+
use maudit::shortcodes::MarkdownShortcodes;
206+
207+
fn main() {
208+
let create_markdown_options = || {
209+
let mut shortcodes = MarkdownShortcodes::default();
210+
211+
shortcodes.register("enhance", |attrs, _| {
212+
let title = attrs.get_required("title");
213+
214+
format!("Very Cool {}", title)
215+
})
216+
217+
MarkdownOptions {
218+
shortcodes,
219+
..Default::default()
220+
}
221+
}
222+
223+
coronate(
224+
routes![
225+
// ...
226+
],
227+
content_sources![
228+
"blog" => glob_markdown::<BlogPost>("content/blog/**/*.md", Some(create_markdown_options())),
229+
],
230+
..Default::default()
231+
);
232+
}
233+
```
234+
235+
Note that shortcodes expand before Markdown is rendered, so you can use shortcodes to generate Markdown content as well as HTML.
236+
237+
### Components
238+
239+
Maudit supports using custom components to render Markdown content. For instance, by default `# Title` will be rendered as `<h1>Title</h1>`, but you can override this behaviour by providing your own component for headings.
240+
241+
To do so, create an instance of `MarkdownComponents` and use the various builder (`.heading`, `.link`, `.paragraph`, etc.) methods to register components. This can then be passed as the `components` parameter of `MarkdownOptions`.
242+
243+
```rs
244+
use maudit::components::MarkdownComponents;
245+
246+
struct TestCustomHeading;
247+
248+
impl HeadingComponent for TestCustomHeading {
249+
fn render_start(&self, level: u8, id: Option<&str>, classes: &[&str]) -> String {
250+
let id_attr = id.map(|i| format!(" id=\"{}\"", i)).unwrap_or_default();
251+
let class_attr = if classes.is_empty() {
252+
String::new()
253+
} else {
254+
format!(" class=\"{}\"", classes.join(" "))
255+
};
256+
format!(
257+
"<h{}{}{}> This is a custom Heading: ",
258+
level, id_attr, class_attr
259+
)
260+
}
261+
262+
fn render_end(&self, level: u8) -> String {
263+
format!("</h{}>", level)
264+
}
265+
}
266+
267+
fn main() {
268+
let create_markdown_options = || {
269+
let mut components = MarkdownComponents::new().heading(TestCustomHeading);
270+
271+
MarkdownOptions {
272+
components,
273+
..Default::default()
274+
}
275+
};
276+
277+
coronate(
278+
routes![
279+
// ...
280+
],
281+
content_sources![
282+
"blog" => glob_markdown::<BlogPost>("content/blog/**/*.md", Some(create_markdown_options())),
283+
],
284+
..Default::default(),
285+
);
286+
}
287+
288+
```
289+
290+
Unlike shortcodes, components are used during the Markdown rendering process, so they can only generate HTML, not Markdown.

website/content/docs/entrypoint.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ fn main() -> Result<BuildOutput, Box<dyn std::error::Error>> {
1717
}
1818
```
1919

20-
### Registering Routes
20+
## Registering Routes
2121

2222
All kinds of routes must be passed to the `coronate` function in order for them to be built.
2323

@@ -35,6 +35,6 @@ coronate(
3535

3636
See the [Routing](/docs/routing) documentation for more information on how to define routes.
3737

38-
### Content
38+
## Content
3939

40-
### Options
40+
## Options

website/content/docs/images.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
title: "Images"
3+
description: "Learn how to import and use images in your Maudit site."
4+
section: "core-concepts"
5+
---
6+
7+
Maudit includes support using images in various contexts. In your pages as img tags, collocated next to and linked in your Markdown files, or inside JSON files, and more.
8+
9+
Additionally, Maudit supports processing (i.e. resizing and converting) images at build time.
10+
11+
## Using images
12+
13+
### In pages
14+
15+
To use an image in a page, add it anywhere in your project's directory, and use the `ctx.assets.add_image()` method to add it to a page's assets.
16+
17+
```rs
18+
use maudit::page::prelude::*;
19+
20+
#[route("/blog")]
21+
pub struct Blog;
22+
23+
impl Page for Blog {
24+
fn render(&self, ctx: &mut RouteContext) -> RenderResult {
25+
let image = ctx.assets.add_image("logo.png");
26+
27+
format!("", image.url).into()
28+
}
29+
}
30+
```
31+
32+
Paths to image are resolved relative to the root of your project, not from the page's location.
33+
34+
### In Markdown
35+
36+
To use an image in Markdown, link to it using standard Markdown syntax.
37+
38+
```markdown
39+
![Description](./image.png)
40+
```
41+
42+
Images can be collocated next to your content, or anywhere else in your project and are resolved relatively to your Markdown file.
43+
44+
## Processing images
45+
46+
Images added to pages can be transformed by using `ctx.assets.add_image_with_options()`.
47+
48+
```rs
49+
use maudit::page::prelude::*;
50+
51+
#[route("/image")]
52+
pub struct ImagePage;
53+
54+
impl Page for ImagePage {
55+
fn render(&self, ctx: &mut RouteContext) -> RenderResult {
56+
let image = ctx.assets.add_image_with_options(
57+
"path/to/image.jpg",
58+
ImageOptions {
59+
width: Some(800),
60+
height: None,
61+
format: Some(ImageFormat::Png)
62+
},
63+
)?;
64+
65+
format!("<img src=\"{}\" alt=\"Processed Image\" />", image.url).into()
66+
}
67+
}
68+
```
69+
70+
Processing images in Markdown files is currently not supported.

0 commit comments

Comments
 (0)