+13
-129
src/atproto/mod.rs
+13
-129
src/atproto/mod.rs
···
1
use std::path::PathBuf;
2
3
-
use atrium_api::{
4
-
app::bsky::embed::defs::AspectRatio,
5
-
types::{BlobRef, Unknown},
6
-
};
7
-
8
mod client;
9
mod renderable;
10
-
pub use atrium_api::app::bsky::feed::post::RecordData as BskyPost;
11
pub use client::fetch_records;
12
pub use renderable::Renderable;
13
use tera::{Context, Tera};
14
-
15
-
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
16
-
pub struct Document {
17
-
pub author: String,
18
-
pub description: String,
19
-
pub title: String,
20
-
pub pages: Vec<Page>,
21
-
#[serde(rename = "postRef")]
22
-
pub post_ref: Option<Unknown>,
23
-
pub publication: Unknown,
24
-
#[serde(rename = "publishedAt")]
25
-
pub published_at: String,
26
-
#[serde(skip)]
27
-
pub host: String,
28
-
#[serde(skip)]
29
-
pub did: String,
30
-
}
31
-
32
-
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
33
-
pub struct Page {
34
-
pub blocks: Vec<BlockWrapper>,
35
-
}
36
-
37
-
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
38
-
pub struct BlockWrapper {
39
-
pub block: Block,
40
-
}
41
-
42
-
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
43
-
#[serde(tag = "$type")]
44
-
pub enum Block {
45
-
#[serde(rename = "pub.leaflet.blocks.blockquote")]
46
-
Blockquote {
47
-
plaintext: String,
48
-
// TODO facets: [&pub.leaflet.richtext.facet]
49
-
},
50
-
51
-
#[serde(rename = "pub.leaflet.blocks.code")]
52
-
Code {
53
-
plaintext: String,
54
-
language: Option<String>,
55
-
#[serde(rename = "syntaxHighlightingTheme")]
56
-
syntax_highlighting_theme: Option<String>,
57
-
},
58
-
59
-
#[serde(rename = "pub.leaflet.blocks.bskyPost")]
60
-
BskyPost {
61
-
#[serde(rename = "postRef")]
62
-
post_ref: String,
63
-
},
64
-
65
-
#[serde(rename = "pub.leaflet.blocks.header")]
66
-
Header {
67
-
level: Option<u32>,
68
-
plaintext: String,
69
-
// TODO: facets: [&pub.leaflet.richtext.facet
70
-
},
71
-
72
-
#[serde(rename = "pub.leaflet.blocks.horizontalRule")]
73
-
HorizontalRule {},
74
-
75
-
#[serde(rename = "pub.leaflet.blocks.iframe")]
76
-
IFrame { url: String, height: Option<u32> },
77
-
78
-
#[serde(rename = "pub.leaflet.blocks.image")]
79
-
Image {
80
-
image: BlobRef,
81
-
alt: Option<String>,
82
-
#[serde(rename = "aspectRatio")]
83
-
aspect_ratio: AspectRatio,
84
-
},
85
-
86
-
#[serde(rename = "pub.leaflet.blocks.math")]
87
-
Math { tex: String },
88
-
89
-
#[serde(rename = "pub.leaflet.blocks.text")]
90
-
Text {
91
-
plaintext: String,
92
-
// TODO: facets: [&pub.leaflet.richtext.facet]
93
-
},
94
-
95
-
#[serde(rename = "pub.leaflet.blocks.unorderedList")]
96
-
UnorderedList { children: Vec<ListItem> },
97
-
98
-
#[serde(other)]
99
-
Unknown,
100
-
}
101
-
102
-
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
103
-
pub struct ListItem {
104
-
content: ListItemContent,
105
-
}
106
-
107
-
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
108
-
#[serde(tag = "$type")]
109
-
pub enum ListItemContent {
110
-
#[serde(rename = "pub.leaflet.blocks.text")]
111
-
Text { plaintext: String },
112
-
#[serde(rename = "pub.leaflet.blocks.header")]
113
-
Header {
114
-
level: Option<u32>,
115
-
plaintext: String,
116
-
},
117
-
#[serde(rename = "pub.leaflet.blocks.image")]
118
-
Image {
119
-
image: BlobRef,
120
-
alt: Option<String>,
121
-
#[serde(rename = "aspectRatio")]
122
-
aspect_ratio: AspectRatio,
123
-
},
124
-
}
125
-
126
-
#[derive(Debug, serde::Deserialize, serde::Serialize)]
127
-
pub struct Website {
128
-
src: String,
129
-
description: Option<String>,
130
-
title: Option<String>,
131
-
#[serde(rename = "previewImage")]
132
-
preview_image: BlobRef,
133
-
}
134
135
#[derive(Clone, serde::Serialize)]
136
pub enum ContentType {
137
Document(Document),
138
-
BskyPost(BskyPost),
139
}
140
141
#[derive(Clone, serde::Serialize)]
···
179
fn build_name(content: &ContentType) -> String {
180
match content {
181
ContentType::Document(doc) => doc.title.clone(),
182
-
ContentType::BskyPost(post) => String::from(post.created_at.as_str()),
183
}
184
}
185
}
···
238
context.insert("host", &doc.host);
239
context.insert("did", &doc.did);
240
}
241
-
ContentType::BskyPost(post) => {
242
context.insert("post", post);
243
}
244
}
245
tera.render(&self.template, &context)
···
1
use std::path::PathBuf;
2
3
mod client;
4
mod renderable;
5
+
pub mod types;
6
+
7
pub use client::fetch_records;
8
pub use renderable::Renderable;
9
use tera::{Context, Tera};
10
+
use types::bsky::Post;
11
+
use types::leaflet::Document;
12
+
use types::tangled::Repo;
13
14
#[derive(Clone, serde::Serialize)]
15
pub enum ContentType {
16
Document(Document),
17
+
Post(Post),
18
+
Repo(Repo),
19
}
20
21
#[derive(Clone, serde::Serialize)]
···
59
fn build_name(content: &ContentType) -> String {
60
match content {
61
ContentType::Document(doc) => doc.title.clone(),
62
+
ContentType::Post(post) => String::from(post.created_at.as_str()),
63
+
ContentType::Repo(repo) => repo.name.clone(),
64
}
65
}
66
}
···
119
context.insert("host", &doc.host);
120
context.insert("did", &doc.did);
121
}
122
+
ContentType::Post(post) => {
123
context.insert("post", post);
124
+
}
125
+
ContentType::Repo(repo) => {
126
+
context.insert("repo", repo);
127
}
128
}
129
tera.render(&self.template, &context)
+1
src/atproto/types/bsky.rs
+1
src/atproto/types/bsky.rs
···
···
1
+
pub use atrium_api::app::bsky::feed::post::RecordData as Post;
+124
src/atproto/types/leaflet.rs
+124
src/atproto/types/leaflet.rs
···
···
1
+
use atrium_api::{
2
+
app::bsky::embed::defs::AspectRatio,
3
+
types::{BlobRef, Unknown},
4
+
};
5
+
6
+
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
7
+
pub struct Document {
8
+
pub author: String,
9
+
pub description: String,
10
+
pub title: String,
11
+
pub pages: Vec<Page>,
12
+
#[serde(rename = "postRef")]
13
+
pub post_ref: Option<Unknown>,
14
+
pub publication: Unknown,
15
+
#[serde(rename = "publishedAt")]
16
+
pub published_at: String,
17
+
#[serde(skip)]
18
+
pub host: String,
19
+
#[serde(skip)]
20
+
pub did: String,
21
+
}
22
+
23
+
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
24
+
pub struct Page {
25
+
pub blocks: Vec<BlockWrapper>,
26
+
}
27
+
28
+
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
29
+
pub struct BlockWrapper {
30
+
pub block: Block,
31
+
}
32
+
33
+
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
34
+
#[serde(tag = "$type")]
35
+
pub enum Block {
36
+
#[serde(rename = "pub.leaflet.blocks.blockquote")]
37
+
Blockquote {
38
+
plaintext: String,
39
+
// TODO facets: [&pub.leaflet.richtext.facet]
40
+
},
41
+
42
+
#[serde(rename = "pub.leaflet.blocks.code")]
43
+
Code {
44
+
plaintext: String,
45
+
language: Option<String>,
46
+
#[serde(rename = "syntaxHighlightingTheme")]
47
+
syntax_highlighting_theme: Option<String>,
48
+
},
49
+
50
+
#[serde(rename = "pub.leaflet.blocks.bskyPost")]
51
+
BskyPost {
52
+
#[serde(rename = "postRef")]
53
+
post_ref: String,
54
+
},
55
+
56
+
#[serde(rename = "pub.leaflet.blocks.header")]
57
+
Header {
58
+
level: Option<u32>,
59
+
plaintext: String,
60
+
// TODO: facets: [&pub.leaflet.richtext.facet
61
+
},
62
+
63
+
#[serde(rename = "pub.leaflet.blocks.horizontalRule")]
64
+
HorizontalRule {},
65
+
66
+
#[serde(rename = "pub.leaflet.blocks.iframe")]
67
+
IFrame { url: String, height: Option<u32> },
68
+
69
+
#[serde(rename = "pub.leaflet.blocks.image")]
70
+
Image {
71
+
image: BlobRef,
72
+
alt: Option<String>,
73
+
#[serde(rename = "aspectRatio")]
74
+
aspect_ratio: AspectRatio,
75
+
},
76
+
77
+
#[serde(rename = "pub.leaflet.blocks.math")]
78
+
Math { tex: String },
79
+
80
+
#[serde(rename = "pub.leaflet.blocks.text")]
81
+
Text {
82
+
plaintext: String,
83
+
// TODO: facets: [&pub.leaflet.richtext.facet]
84
+
},
85
+
86
+
#[serde(rename = "pub.leaflet.blocks.unorderedList")]
87
+
UnorderedList { children: Vec<ListItem> },
88
+
89
+
#[serde(other)]
90
+
Unknown,
91
+
}
92
+
93
+
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
94
+
pub struct ListItem {
95
+
content: ListItemContent,
96
+
}
97
+
98
+
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
99
+
#[serde(tag = "$type")]
100
+
pub enum ListItemContent {
101
+
#[serde(rename = "pub.leaflet.blocks.text")]
102
+
Text { plaintext: String },
103
+
#[serde(rename = "pub.leaflet.blocks.header")]
104
+
Header {
105
+
level: Option<u32>,
106
+
plaintext: String,
107
+
},
108
+
#[serde(rename = "pub.leaflet.blocks.image")]
109
+
Image {
110
+
image: BlobRef,
111
+
alt: Option<String>,
112
+
#[serde(rename = "aspectRatio")]
113
+
aspect_ratio: AspectRatio,
114
+
},
115
+
}
116
+
117
+
#[derive(Debug, serde::Deserialize, serde::Serialize)]
118
+
pub struct Website {
119
+
src: String,
120
+
description: Option<String>,
121
+
title: Option<String>,
122
+
#[serde(rename = "previewImage")]
123
+
preview_image: BlobRef,
124
+
}
+10
src/atproto/types/tangled.rs
+10
src/atproto/types/tangled.rs
+6
-4
src/config/mod.rs
+6
-4
src/config/mod.rs
+5
-2
src/generator.rs
+5
-2
src/generator.rs
···
7
use crate::config::Config;
8
use crate::templates::filters;
9
10
+
use crate::atproto::types::{
11
+
bsky::Post,
12
+
leaflet::Document,
13
+
tangled::{Issue, Repo},
14
};
15
+
use crate::atproto::{ContentPage, ContentType, IndexPage, Renderable, fetch_records};
16
17
use std::path::{Path, PathBuf};
18
+46
src/init.rs
+46
src/init.rs
···
···
1
+
use std::{
2
+
fs::{self, create_dir},
3
+
path::Path,
4
+
};
5
+
6
+
pub fn init() -> Result<(), Box<dyn std::error::Error>> {
7
+
if Path::new("config.toml").exists() {
8
+
return Err("Configuration file already exists, exiting.".into());
9
+
} else {
10
+
let config_contents = r#"[pds]
11
+
username = "your-handle.bsky.social"
12
+
password = "your-app-password"
13
+
host = "https://bsky.social"
14
+
"#;
15
+
fs::write("config.toml", config_contents)?;
16
+
}
17
+
18
+
match create_dir("templates") {
19
+
Ok(_) => println!("Created 'templates' directory"),
20
+
Err(_) => {
21
+
return Err(
22
+
"Could not create 'templates' directory. Are you in an empty folder?".into(),
23
+
);
24
+
}
25
+
}
26
+
27
+
let template_base = include_str!("../templates/base.html");
28
+
std::fs::write("templates/base.html", template_base)?;
29
+
30
+
let template_home = include_str!("../templates/home.html");
31
+
std::fs::write("templates/home.html", template_home)?;
32
+
33
+
let template_document = include_str!("../templates/document.html");
34
+
std::fs::write("templates/document.html", template_document)?;
35
+
36
+
let template_post = include_str!("../templates/post.html");
37
+
std::fs::write("templates/post.html", template_post)?;
38
+
39
+
let template_index = include_str!("../templates/index.html");
40
+
std::fs::write("templates/index.html", template_index)?;
41
+
42
+
let template_macros = include_str!("../templates/macros.html");
43
+
std::fs::write("templates/macros.html", template_macros)?;
44
+
45
+
Ok(())
46
+
}
+6
-6
src/main.rs
+6
-6
src/main.rs
···
1
use std::path::PathBuf;
2
3
-
mod generator;
4
-
use generator::generate;
5
mod config;
6
-
use config::load_config;
7
mod atproto;
8
mod server;
9
mod templates;
10
···
30
#[tokio::main]
31
async fn main() -> Result<(), Box<dyn std::error::Error>> {
32
let cli = Cli::parse();
33
-
let config = load_config(&PathBuf::from("config.toml"))?;
34
35
match &cli.command {
36
Commands::Init => {
37
-
println!("init called");
38
}
39
Commands::Build => {
40
-
generate(&config).await?;
41
}
42
Commands::Serve => {
43
server::serve(&config)?;
···
1
use std::path::PathBuf;
2
3
mod config;
4
+
mod generator;
5
+
use config::Config;
6
mod atproto;
7
+
mod init;
8
mod server;
9
mod templates;
10
···
30
#[tokio::main]
31
async fn main() -> Result<(), Box<dyn std::error::Error>> {
32
let cli = Cli::parse();
33
+
let config = Config::from(&PathBuf::from("config.toml"))?;
34
35
match &cli.command {
36
Commands::Init => {
37
+
init::init()?;
38
}
39
Commands::Build => {
40
+
generator::generate(&config).await?;
41
}
42
Commands::Serve => {
43
server::serve(&config)?;
+13
-1
templates/base.html
+13
-1
templates/base.html
···
1
<DOCTYPE! html>
2
<html>
3
<head>
4
+
<meta charset="utf-8">
5
+
<meta name="viewport" content="width=device-width, initial-scale=1">
6
+
<meta name="color-scheme" content="light dark">
7
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
8
<title>{% block title %}{% endblock %}</title>
9
</head>
10
<body>
11
+
<main class="container">
12
+
<header>
13
+
<a href="/">Johannes</a>
14
+
<nav>
15
+
</nav>
16
+
</header>
17
+
18
+
{% block content %}{% endblock %}
19
+
</main>
20
</body>
21
</html>
-22
templates/partials/pub.leaflet.blocks.html
-22
templates/partials/pub.leaflet.blocks.html
···
1
-
{% set type = block_w.block["$type"] %}
2
-
{% set block = block_w.block %}
3
-
4
-
{% if type == "pub.leaflet.blocks.text" %}
5
-
<p>{{ block.plaintext }}</p>
6
-
{% elif type == "pub.leaflet.blocks.code" %}
7
-
<pre><code class="{{ block.language }}">{{ block.plaintext }}</code></pre>
8
-
{% elif type == "pub.leaflet.blocks.image" %}
9
-
<p><img src="{{ block.image | blob_to_url(host=host, did=did) | safe }}"></img></p>
10
-
{% if block.alt %}<small>{{ block.alt }}</small>{% endif %}
11
-
{% elif type == "pub.leaflet.blocks.header" %}
12
-
{% set level = block.level | default(value="3") %}
13
-
<h{{ level }}>{{ block.plaintext }}</h{{ level }}>
14
-
{% elif type == "pub.leaflet.blocks.unorderedList" %}
15
-
<ul>
16
-
{% for child in block.children %}
17
-
<li>{{ child.content["$type"] }}</li>
18
-
{% endfor %}
19
-
</ul>
20
-
{% else %}
21
-
<p>Unknown block type: {{ type }}</p>
22
-
{% endif %}
···
History
2 rounds
0 comments
hello.j23n.com
submitted
#1
expand 0 comments
pull request successfully merged
hello.j23n.com
submitted
#0