Merge pull request 'add an index page and add name functions to the API and cache busting' (#7) from index-page into main
Reviewed-on: #7
This commit is contained in:
commit
50d92e998a
18 changed files with 682 additions and 174 deletions
111
Cargo.lock
generated
111
Cargo.lock
generated
|
@ -17,6 +17,15 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
@ -443,6 +452,15 @@ version = "1.0.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "form_urlencoded"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
|
||||||
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsevent-sys"
|
name = "fsevent-sys"
|
||||||
version = "4.1.0"
|
version = "4.1.0"
|
||||||
|
@ -596,6 +614,12 @@ version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.9"
|
version = "0.2.9"
|
||||||
|
@ -653,6 +677,16 @@ dependencies = [
|
||||||
"want",
|
"want",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-normalization",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.3"
|
version = "1.9.3"
|
||||||
|
@ -816,6 +850,12 @@ dependencies = [
|
||||||
"regex-automata",
|
"regex-automata",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "md5"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
@ -824,13 +864,17 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "meowy-webring"
|
name = "meowy-webring"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"askama",
|
"askama",
|
||||||
"askama_rocket",
|
"askama_rocket",
|
||||||
|
"hex",
|
||||||
"log",
|
"log",
|
||||||
|
"md5",
|
||||||
"notify",
|
"notify",
|
||||||
|
"proc_macros",
|
||||||
"rocket",
|
"rocket",
|
||||||
|
"rocket_cors",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -1067,6 +1111,14 @@ dependencies = [
|
||||||
"yansi",
|
"yansi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc_macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.28"
|
version = "1.0.28"
|
||||||
|
@ -1161,6 +1213,8 @@ version = "1.8.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
|
checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
"regex-syntax 0.7.2",
|
"regex-syntax 0.7.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1240,6 +1294,20 @@ dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rocket_cors"
|
||||||
|
version = "0.6.0-alpha2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b12771b47f52e34d5d0e0e444aeba382863e73263cb9e18847e7d5b74aa2cbd0"
|
||||||
|
dependencies = [
|
||||||
|
"http",
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
"rocket",
|
||||||
|
"unicase",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rocket_http"
|
name = "rocket_http"
|
||||||
version = "0.5.0-rc.3"
|
version = "0.5.0-rc.3"
|
||||||
|
@ -1579,6 +1647,21 @@ dependencies = [
|
||||||
"time-core",
|
"time-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec_macros"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.29.0"
|
version = "1.29.0"
|
||||||
|
@ -1775,18 +1858,44 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-bidi"
|
||||||
|
version = "0.3.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.9"
|
version = "1.0.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
|
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-normalization"
|
||||||
|
version = "0.1.22"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "url"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"idna",
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|
20
Cargo.toml
20
Cargo.toml
|
@ -1,20 +1,27 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["cli", "shared"]
|
members = ["cli", "shared", "proc-macros"]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "meowy-webring"
|
name = "meowy-webring"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.70"
|
rust-version = "1.70"
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
lto = false
|
||||||
|
[profile.release]
|
||||||
|
lto = "thin"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
hex = "0.4"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
md5 = "0.7"
|
||||||
|
|
||||||
[dependencies.rocket]
|
[dependencies.rocket]
|
||||||
version = "=0.5.0-rc.3"
|
version = "=0.5.0-rc.3"
|
||||||
default_features = false
|
default-features = false
|
||||||
features = ["json"]
|
features = ["json"]
|
||||||
|
|
||||||
[dependencies.rust-embed]
|
[dependencies.rust-embed]
|
||||||
|
@ -51,3 +58,10 @@ default-features = false
|
||||||
version = "6"
|
version = "6"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["macos_fsevent"]
|
features = ["macos_fsevent"]
|
||||||
|
|
||||||
|
[dependencies.rocket_cors]
|
||||||
|
version = "=0.6.0-alpha2"
|
||||||
|
default_features = false
|
||||||
|
|
||||||
|
[dependencies.proc_macros]
|
||||||
|
path = "./proc-macros"
|
||||||
|
|
64
LICENSE
64
LICENSE
|
@ -2,14 +2,14 @@ Creative Commons Legal Code
|
||||||
|
|
||||||
CC0 1.0 Universal
|
CC0 1.0 Universal
|
||||||
|
|
||||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||||
HEREUNDER.
|
HEREUNDER.
|
||||||
|
|
||||||
Statement of Purpose
|
Statement of Purpose
|
||||||
|
|
||||||
|
@ -43,22 +43,22 @@ Related Rights"). Copyright and Related Rights include, but are not
|
||||||
limited to, the following:
|
limited to, the following:
|
||||||
|
|
||||||
i. the right to reproduce, adapt, distribute, perform, display,
|
i. the right to reproduce, adapt, distribute, perform, display,
|
||||||
communicate, and translate a Work;
|
communicate, and translate a Work;
|
||||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||||
iii. publicity and privacy rights pertaining to a person's image or
|
iii. publicity and privacy rights pertaining to a person's image or
|
||||||
likeness depicted in a Work;
|
likeness depicted in a Work;
|
||||||
iv. rights protecting against unfair competition in regards to a Work,
|
iv. rights protecting against unfair competition in regards to a Work,
|
||||||
subject to the limitations in paragraph 4(a), below;
|
subject to the limitations in paragraph 4(a), below;
|
||||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||||
in a Work;
|
in a Work;
|
||||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||||
European Parliament and of the Council of 11 March 1996 on the legal
|
European Parliament and of the Council of 11 March 1996 on the legal
|
||||||
protection of databases, and under any national implementation
|
protection of databases, and under any national implementation
|
||||||
thereof, including any amended or successor version of such
|
thereof, including any amended or successor version of such
|
||||||
directive); and
|
directive); and
|
||||||
vii. other similar, equivalent or corresponding rights throughout the
|
vii. other similar, equivalent or corresponding rights throughout the
|
||||||
world based on applicable law or treaty, and any national
|
world based on applicable law or treaty, and any national
|
||||||
implementations thereof.
|
implementations thereof.
|
||||||
|
|
||||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||||
|
@ -102,20 +102,20 @@ express Statement of Purpose.
|
||||||
4. Limitations and Disclaimers.
|
4. Limitations and Disclaimers.
|
||||||
|
|
||||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||||
surrendered, licensed or otherwise affected by this document.
|
surrendered, licensed or otherwise affected by this document.
|
||||||
b. Affirmer offers the Work as-is and makes no representations or
|
b. Affirmer offers the Work as-is and makes no representations or
|
||||||
warranties of any kind concerning the Work, express, implied,
|
warranties of any kind concerning the Work, express, implied,
|
||||||
statutory or otherwise, including without limitation warranties of
|
statutory or otherwise, including without limitation warranties of
|
||||||
title, merchantability, fitness for a particular purpose, non
|
title, merchantability, fitness for a particular purpose, non
|
||||||
infringement, or the absence of latent or other defects, accuracy, or
|
infringement, or the absence of latent or other defects, accuracy, or
|
||||||
the present or absence of errors, whether or not discoverable, all to
|
the present or absence of errors, whether or not discoverable, all to
|
||||||
the greatest extent permissible under applicable law.
|
the greatest extent permissible under applicable law.
|
||||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||||
that may apply to the Work or any use thereof, including without
|
that may apply to the Work or any use thereof, including without
|
||||||
limitation any person's Copyright and Related Rights in the Work.
|
limitation any person's Copyright and Related Rights in the Work.
|
||||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||||
consents, permissions or other rights required for any use of the
|
consents, permissions or other rights required for any use of the
|
||||||
Work.
|
Work.
|
||||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||||
party to this document and has no duty or obligation with respect to
|
party to this document and has no duty or obligation with respect to
|
||||||
this CC0 or use of the Work.
|
this CC0 or use of the Work.
|
||||||
|
|
13
proc-macros/Cargo.toml
Normal file
13
proc-macros/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "proc_macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = "2.0"
|
||||||
|
quote = "1.0"
|
35
proc-macros/src/lib.rs
Normal file
35
proc-macros/src/lib.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{parse::Parser, parse_macro_input, DeriveInput};
|
||||||
|
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn uses_base_template(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
|
let mut input = parse_macro_input!(item as DeriveInput);
|
||||||
|
|
||||||
|
let base_template_field = syn::Field::parse_named
|
||||||
|
.parse2(
|
||||||
|
quote! {
|
||||||
|
pub base_template: BaseTemplate
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
match &mut input.data {
|
||||||
|
syn::Data::Struct(ref mut struct_data) => {
|
||||||
|
match &mut struct_data.fields {
|
||||||
|
syn::Fields::Named(fields) => {
|
||||||
|
fields.named.push(base_template_field);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
return quote! {
|
||||||
|
#input
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("bad")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
43
public/hyperlegible.css
Normal file
43
public/hyperlegible.css
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: Atkinson Hyperlegible;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(/public/woff2/atkinson-hyperlegible-latin-ext-400-normal.woff2) format("woff2"),
|
||||||
|
url(/public/woff/atkinson-hyperlegible-all-400-normal.woff) format("woff");
|
||||||
|
unicode-range: U+0100-024F,
|
||||||
|
U+0259,
|
||||||
|
U+1E00-1EFF,
|
||||||
|
U+2020,
|
||||||
|
U+20A0-20AB,
|
||||||
|
U+20AD-20CF,
|
||||||
|
U+2113,
|
||||||
|
U+2C60-2C7F,
|
||||||
|
U+A720-A7FF
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: Atkinson Hyperlegible;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(/public/woff2/atkinson-hyperlegible-latin-400-normal.woff2) format("woff2"),
|
||||||
|
url(/public/woff/atkinson-hyperlegible-all-400-normal.woff) format("woff");
|
||||||
|
unicode-range: U+0000-00FF,
|
||||||
|
U+0131,
|
||||||
|
U+0152-0153,
|
||||||
|
U+02BB-02BC,
|
||||||
|
U+02C6,
|
||||||
|
U+02DA,
|
||||||
|
U+02DC,
|
||||||
|
U+2000-206F,
|
||||||
|
U+2074,
|
||||||
|
U+20AC,
|
||||||
|
U+2122,
|
||||||
|
U+2191,
|
||||||
|
U+2193,
|
||||||
|
U+2212,
|
||||||
|
U+2215,
|
||||||
|
U+FEFF,
|
||||||
|
U+FFFD
|
||||||
|
}
|
|
@ -14,59 +14,51 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--h1-font-size: 3.225rem;
|
||||||
|
--h2-font-size: 2.825rem;
|
||||||
|
--h3-font-size: 2.225rem;
|
||||||
|
--h4-font-size: 1.665rem;
|
||||||
|
--default-font-size: 1.375rem;
|
||||||
|
--h6-font-size: 1.185rem;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: "Atkinson Hyperlegible", sans-serif;
|
font-family: "Atkinson Hyperlegible", sans-serif;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
font-size: var(--default-font-size);
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
color: var(--text-color)
|
color: var(--text-color)
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 22px;
|
a {
|
||||||
|
font-size: var(--default-font-size);
|
||||||
|
color: var(--link-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
h1 {
|
||||||
font-family: Atkinson Hyperlegible;
|
font-size: var(--h1-font-size);
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
font-weight: 400;
|
|
||||||
src: url(/public/woff2/atkinson-hyperlegible-latin-ext-400-normal.woff2) format("woff2"),
|
|
||||||
url(/public/woff/atkinson-hyperlegible-all-400-normal.woff) format("woff");
|
|
||||||
unicode-range: U+0100-024F,
|
|
||||||
U+0259,
|
|
||||||
U+1E00-1EFF,
|
|
||||||
U+2020,
|
|
||||||
U+20A0-20AB,
|
|
||||||
U+20AD-20CF,
|
|
||||||
U+2113,
|
|
||||||
U+2C60-2C7F,
|
|
||||||
U+A720-A7FF
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
h2 {
|
||||||
font-family: Atkinson Hyperlegible;
|
font-size: var(--h2-font-size);
|
||||||
font-style: normal;
|
}
|
||||||
font-display: swap;
|
|
||||||
font-weight: 400;
|
h3 {
|
||||||
src: url(/public/woff2/atkinson-hyperlegible-latin-400-normal.woff2) format("woff2"),
|
font-size: var(--h3-font-size);
|
||||||
url(/public/woff/atkinson-hyperlegible-all-400-normal.woff) format("woff");
|
}
|
||||||
unicode-range: U+0000-00FF,
|
|
||||||
U+0131,
|
h4 {
|
||||||
U+0152-0153,
|
font-size: var(--h4-font-size);
|
||||||
U+02BB-02BC,
|
}
|
||||||
U+02C6,
|
|
||||||
U+02DA,
|
h5 {
|
||||||
U+02DC,
|
font-size: var(--default-font-size);
|
||||||
U+2000-206F,
|
}
|
||||||
U+2074,
|
|
||||||
U+20AC,
|
h6 {
|
||||||
U+2122,
|
font-size: var(--h6-font-size);
|
||||||
U+2191,
|
|
||||||
U+2193,
|
|
||||||
U+2212,
|
|
||||||
U+2215,
|
|
||||||
U+FEFF,
|
|
||||||
U+FFFD
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use askama_rocket::Template;
|
|
||||||
use rocket::http::Status;
|
|
||||||
use rust_embed::RustEmbed;
|
|
||||||
|
|
||||||
#[derive(RustEmbed)]
|
|
||||||
#[folder = "public/"]
|
|
||||||
pub struct PublicAssets;
|
|
||||||
|
|
||||||
#[derive(Responder)]
|
|
||||||
#[response(status = 200, content_type = "font/woff2")]
|
|
||||||
pub struct RawWoff2Font(pub Cow<'static, [u8]>);
|
|
||||||
|
|
||||||
#[derive(Responder)]
|
|
||||||
#[response(status = 200, content_type = "font/woff")]
|
|
||||||
pub struct RawWoffFont(pub Cow<'static, [u8]>);
|
|
||||||
|
|
||||||
#[derive(Template)]
|
|
||||||
#[template(path = "error.html")]
|
|
||||||
pub struct ErrorTemplate<'a> {
|
|
||||||
pub error: &'a str,
|
|
||||||
pub error_description: &'a str
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Responder)]
|
|
||||||
pub struct ErrorTemplateResponder<'a> {
|
|
||||||
template: ErrorTemplate<'a>
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/style.css")]
|
|
||||||
pub fn style() -> Result<rocket::response::content::RawCss<String>, Status> {
|
|
||||||
let style = PublicAssets::get("style.css").unwrap();
|
|
||||||
match std::str::from_utf8(&style.data) {
|
|
||||||
Ok(style) => Ok(rocket::response::content::RawCss::<String>(style.to_string())),
|
|
||||||
Err(_) => Err(Status::InternalServerError),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/woff2/<font>")]
|
|
||||||
pub fn woff2_font(font: &str) -> Result<RawWoff2Font, Status> {
|
|
||||||
let latin = "atkinson-hyperlegible-latin-400-normal.woff2";
|
|
||||||
let latin_ext = "atkinson-hyperlegible-latin-ext-400-normal.woff2";
|
|
||||||
|
|
||||||
if font == latin {
|
|
||||||
Ok(RawWoff2Font(PublicAssets::get(latin).unwrap().data))
|
|
||||||
} else if font == latin_ext {
|
|
||||||
Ok(RawWoff2Font(PublicAssets::get(latin_ext).unwrap().data))
|
|
||||||
} else {
|
|
||||||
Err(Status::NotFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[get("/woff/<font>")]
|
|
||||||
pub fn woff_font(font: &str) -> Result<RawWoffFont, Status> {
|
|
||||||
let all = "atkinson-hyperlegible-all-400-normal.woff";
|
|
||||||
|
|
||||||
if font == all {
|
|
||||||
Ok(RawWoffFont(PublicAssets::get(all).unwrap().data))
|
|
||||||
} else {
|
|
||||||
Err(Status::NotFound)
|
|
||||||
}
|
|
||||||
}
|
|
96
src/assets/files.rs
Normal file
96
src/assets/files.rs
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
use rocket::http::Status;
|
||||||
|
use rust_embed::RustEmbed;
|
||||||
|
use std::{borrow::Cow, sync::OnceLock};
|
||||||
|
|
||||||
|
#[derive(RustEmbed)]
|
||||||
|
#[folder = "public/"]
|
||||||
|
struct Assets;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Files {
|
||||||
|
pub atkinson_latin_woff2: BinaryFile,
|
||||||
|
pub atkinson_latin_ext_woff2: BinaryFile,
|
||||||
|
pub atkinson_all_woff: BinaryFile,
|
||||||
|
pub style: TextFile,
|
||||||
|
pub hyperlegible: TextFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FileMetadata {
|
||||||
|
pub filename: String,
|
||||||
|
pub extension: String,
|
||||||
|
pub hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileMetadata {
|
||||||
|
pub fn get_hash_filename(&self) -> String {
|
||||||
|
let mut hash = self.hash.clone();
|
||||||
|
hash.truncate(8);
|
||||||
|
format!("{}.{}.{}", self.filename, hash, self.extension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BinaryFile {
|
||||||
|
pub data: Cow<'static, [u8]>,
|
||||||
|
pub metadata: FileMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TextFile {
|
||||||
|
pub text: String,
|
||||||
|
pub metadata: FileMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static FILES: OnceLock<Files> = OnceLock::new();
|
||||||
|
|
||||||
|
pub fn get_file_wrapper() -> Result<&'static Files, Status> {
|
||||||
|
match FILES.get() {
|
||||||
|
Some(files) => Ok(files),
|
||||||
|
None => Err(Status::InternalServerError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_binary_file(filename: &str, extension: &str) -> Option<BinaryFile> {
|
||||||
|
match Assets::get(&format!("{}.{}", filename, extension)) {
|
||||||
|
Some(file) => {
|
||||||
|
let metadata = FileMetadata {
|
||||||
|
filename: filename.into(),
|
||||||
|
extension: extension.into(),
|
||||||
|
hash: hex::encode(file.metadata.sha256_hash()),
|
||||||
|
};
|
||||||
|
Some(BinaryFile {
|
||||||
|
data: file.data,
|
||||||
|
metadata: metadata,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_text_file(filename: &str, extension: &str) -> Option<TextFile> {
|
||||||
|
let file = get_binary_file(filename, extension)?;
|
||||||
|
match std::str::from_utf8(&file.data) {
|
||||||
|
Ok(string) => Some(TextFile {
|
||||||
|
text: string.into(),
|
||||||
|
metadata: file.metadata,
|
||||||
|
}),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initialize_files() {
|
||||||
|
let files = Files {
|
||||||
|
atkinson_latin_woff2: get_binary_file("atkinson-hyperlegible-latin-400-normal", "woff2")
|
||||||
|
.unwrap(),
|
||||||
|
atkinson_latin_ext_woff2: get_binary_file(
|
||||||
|
"atkinson-hyperlegible-latin-ext-400-normal",
|
||||||
|
"woff2",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
atkinson_all_woff: get_binary_file("atkinson-hyperlegible-all-400-normal", "woff").unwrap(),
|
||||||
|
style: get_text_file("style", "css").unwrap(),
|
||||||
|
hyperlegible: get_text_file("hyperlegible", "css").unwrap(),
|
||||||
|
};
|
||||||
|
FILES.set(files).unwrap();
|
||||||
|
}
|
7
src/assets/mod.rs
Normal file
7
src/assets/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
pub mod files;
|
||||||
|
mod routes;
|
||||||
|
pub mod templates;
|
||||||
|
|
||||||
|
pub use routes::style;
|
||||||
|
pub use routes::woff2_font;
|
||||||
|
pub use routes::woff_font;
|
96
src/assets/routes.rs
Normal file
96
src/assets/routes.rs
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
use super::{files::get_file_wrapper, templates::ErrorTemplate};
|
||||||
|
use rocket::{
|
||||||
|
http::{Header, Status},
|
||||||
|
response::{self, content::RawCss, Responder},
|
||||||
|
Response,
|
||||||
|
};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
#[derive(Responder)]
|
||||||
|
#[response(status = 200, content_type = "font/woff2")]
|
||||||
|
pub struct RawWoff2Font(pub Cow<'static, [u8]>);
|
||||||
|
|
||||||
|
#[derive(Responder)]
|
||||||
|
#[response(status = 200, content_type = "font/woff")]
|
||||||
|
pub struct RawWoffFont(pub Cow<'static, [u8]>);
|
||||||
|
|
||||||
|
#[derive(Responder)]
|
||||||
|
pub struct ErrorTemplateResponder<'a> {
|
||||||
|
template: ErrorTemplate<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CachedResponse<T> {
|
||||||
|
inner: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, T> Responder<'r, 'static> for CachedResponse<T>
|
||||||
|
where
|
||||||
|
T: Responder<'r, 'static>,
|
||||||
|
{
|
||||||
|
fn respond_to(self, request: &'r rocket::Request<'_>) -> response::Result<'static> {
|
||||||
|
Response::build_from(self.inner.respond_to(request)?)
|
||||||
|
.header(Header::new("Cache-Control", "max-age=31536000, immutable"))
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r, T> From<T> for CachedResponse<T>
|
||||||
|
where
|
||||||
|
T: Responder<'r, 'static>,
|
||||||
|
{
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
CachedResponse { inner: value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/css/<style>")]
|
||||||
|
pub fn style(style: &str) -> Result<CachedResponse<RawCss<String>>, Status> {
|
||||||
|
let style_file = &get_file_wrapper()?.style;
|
||||||
|
let hyperlegible_file = &get_file_wrapper()?.hyperlegible;
|
||||||
|
|
||||||
|
let style_name = style_file.metadata.get_hash_filename();
|
||||||
|
let hyperlegible_name = hyperlegible_file.metadata.get_hash_filename();
|
||||||
|
|
||||||
|
if style == style_name {
|
||||||
|
Ok(CachedResponse::from(RawCss::<String>(
|
||||||
|
style_file.text.clone(),
|
||||||
|
)))
|
||||||
|
} else if style == hyperlegible_name {
|
||||||
|
Ok(CachedResponse::from(RawCss::<String>(
|
||||||
|
hyperlegible_file.text.clone(),
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Err(Status::NotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/woff2/<font>")]
|
||||||
|
pub fn woff2_font(font: &str) -> Result<CachedResponse<RawWoff2Font>, Status> {
|
||||||
|
let latin_file = &get_file_wrapper()?.atkinson_latin_woff2;
|
||||||
|
let latin_ext_file = &get_file_wrapper()?.atkinson_latin_ext_woff2;
|
||||||
|
|
||||||
|
let latin = latin_file.metadata.get_hash_filename();
|
||||||
|
let latin_ext = latin_file.metadata.get_hash_filename();
|
||||||
|
|
||||||
|
if font == latin {
|
||||||
|
Ok(CachedResponse::from(RawWoff2Font(latin_file.data.clone())))
|
||||||
|
} else if font == latin_ext {
|
||||||
|
Ok(CachedResponse::from(RawWoff2Font(
|
||||||
|
latin_ext_file.data.clone(),
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Err(Status::NotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/woff/<font>")]
|
||||||
|
pub fn woff_font(font: &str) -> Result<CachedResponse<RawWoffFont>, Status> {
|
||||||
|
let all_file = &get_file_wrapper()?.atkinson_all_woff;
|
||||||
|
let all = all_file.metadata.get_hash_filename();
|
||||||
|
|
||||||
|
if font == all {
|
||||||
|
Ok(CachedResponse::from(RawWoffFont(all_file.data.clone())))
|
||||||
|
} else {
|
||||||
|
Err(Status::NotFound)
|
||||||
|
}
|
||||||
|
}
|
25
src/assets/templates.rs
Normal file
25
src/assets/templates.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use askama::Template;
|
||||||
|
use proc_macros::uses_base_template;
|
||||||
|
use shared::names::Site;
|
||||||
|
|
||||||
|
pub struct BaseTemplate {
|
||||||
|
pub style_filename: String,
|
||||||
|
pub atkinson_latin_woff2_filename: String,
|
||||||
|
pub atkinson_latin_ext_woff2_filename: String,
|
||||||
|
pub atkinson_all_woff_filename: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "error.html")]
|
||||||
|
#[uses_base_template]
|
||||||
|
pub struct ErrorTemplate<'a> {
|
||||||
|
pub error: &'a str,
|
||||||
|
pub error_description: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "index.html")]
|
||||||
|
#[uses_base_template]
|
||||||
|
pub struct IndexTemplate {
|
||||||
|
pub sites: Vec<Site>,
|
||||||
|
}
|
28
src/links.rs
28
src/links.rs
|
@ -17,4 +17,30 @@ pub fn next_url(source_url: &String, names: &Vec<Site>) -> Option<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: previous_name, next_name
|
pub fn previous_name(source_url: &String, names: &Vec<Site>) -> Option<String> {
|
||||||
|
match names.iter().position(|r| &r.url == source_url) {
|
||||||
|
Some(index) if index == 0 => match &names[names.len() - 1].name {
|
||||||
|
Some(name) => Some(name.clone()),
|
||||||
|
None => previous_url(source_url, names),
|
||||||
|
},
|
||||||
|
Some(index) => match &names[index - 1].name {
|
||||||
|
Some(name) => Some(name.clone()),
|
||||||
|
None => previous_url(source_url, names),
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_name(source_url: &String, names: &Vec<Site>) -> Option<String> {
|
||||||
|
match names.iter().position(|r| &r.url == source_url) {
|
||||||
|
Some(index) if index == names.len() - 1 => match &names[0].name {
|
||||||
|
Some(name) => Some(name.clone()),
|
||||||
|
None => next_url(source_url, names),
|
||||||
|
},
|
||||||
|
Some(index) => match &names[index + 1].name {
|
||||||
|
Some(name) => Some(name.clone()),
|
||||||
|
None => next_url(source_url, names),
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::watcher::hot_reloading;
|
use crate::watcher::hot_reloading;
|
||||||
|
use assets::files::initialize_files;
|
||||||
use rocket::tokio;
|
use rocket::tokio;
|
||||||
use sites::init_names;
|
use sites::init_names;
|
||||||
|
|
||||||
|
@ -14,9 +15,12 @@ mod watcher;
|
||||||
#[launch]
|
#[launch]
|
||||||
async fn rocket() -> _ {
|
async fn rocket() -> _ {
|
||||||
init_names().unwrap();
|
init_names().unwrap();
|
||||||
|
initialize_files();
|
||||||
|
let cors = rocket_cors::CorsOptions::default().to_cors().unwrap();
|
||||||
tokio::task::spawn_blocking(hot_reloading);
|
tokio::task::spawn_blocking(hot_reloading);
|
||||||
|
|
||||||
rocket::build()
|
rocket::build()
|
||||||
|
.manage(cors)
|
||||||
.mount(
|
.mount(
|
||||||
"/",
|
"/",
|
||||||
routes![routes::index, routes::previous, routes::next, routes::name],
|
routes![routes::index, routes::previous, routes::next, routes::name],
|
||||||
|
|
|
@ -1,19 +1,50 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
assets::ErrorTemplate,
|
assets::{
|
||||||
links::{next_url, previous_url},
|
files::{get_file_wrapper, FileMetadata},
|
||||||
|
templates::{BaseTemplate, ErrorTemplate, IndexTemplate},
|
||||||
|
},
|
||||||
|
links::{next_name, next_url, previous_name, previous_url},
|
||||||
sites::get_global_names,
|
sites::get_global_names,
|
||||||
};
|
};
|
||||||
|
|
||||||
use rocket::{
|
use rocket::{
|
||||||
http::Status,
|
http::Status,
|
||||||
response::Redirect,
|
response::Redirect,
|
||||||
serde::{json::Json, Serialize},
|
serde::{json::Json, Serialize},
|
||||||
};
|
};
|
||||||
|
use rocket_cors::{Guard, Responder};
|
||||||
|
|
||||||
const NOT_FOUND_ERROR: ErrorTemplate = ErrorTemplate {
|
fn get_hash_filename(metadata: &FileMetadata) -> Result<String, Status> {
|
||||||
error: "Not Found",
|
Ok(metadata.get_hash_filename())
|
||||||
error_description: "this URL could not be found on the webring.",
|
}
|
||||||
};
|
|
||||||
|
fn get_base_template() -> Result<BaseTemplate, Status> {
|
||||||
|
let files = get_file_wrapper()?;
|
||||||
|
|
||||||
|
let style_filename = get_hash_filename(&files.style.metadata)?;
|
||||||
|
let atkinson_latin_woff2_filename = get_hash_filename(&files.atkinson_latin_woff2.metadata)?;
|
||||||
|
let atkinson_latin_ext_woff2_filename =
|
||||||
|
get_hash_filename(&files.atkinson_latin_ext_woff2.metadata)?;
|
||||||
|
let atkinson_all_woff_filename = get_hash_filename(&files.atkinson_all_woff.metadata)?;
|
||||||
|
|
||||||
|
let template = BaseTemplate {
|
||||||
|
style_filename,
|
||||||
|
atkinson_latin_woff2_filename,
|
||||||
|
atkinson_latin_ext_woff2_filename,
|
||||||
|
atkinson_all_woff_filename,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(template)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn not_found_error() -> Result<ErrorTemplate<'static>, Status> {
|
||||||
|
let base_template = get_base_template()?;
|
||||||
|
let template = ErrorTemplate {
|
||||||
|
error: "Not Found",
|
||||||
|
error_description: "this URL could not be found on the webring.",
|
||||||
|
base_template,
|
||||||
|
};
|
||||||
|
Ok(template)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(crate = "rocket::serde")]
|
#[serde(crate = "rocket::serde")]
|
||||||
|
@ -23,8 +54,13 @@ pub struct JsonResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub fn index() -> &'static str {
|
pub async fn index() -> Result<IndexTemplate, Status> {
|
||||||
"Like, this is a webring, meow!"
|
let base_template = get_base_template()?;
|
||||||
|
let template = IndexTemplate {
|
||||||
|
sites: get_global_names().await,
|
||||||
|
base_template,
|
||||||
|
};
|
||||||
|
Ok(template)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/previous?<source_url>")]
|
#[get("/previous?<source_url>")]
|
||||||
|
@ -44,21 +80,25 @@ pub async fn next(source_url: String) -> Result<Redirect, Status> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/name?<source_url>")]
|
#[get("/name?<source_url>")]
|
||||||
pub async fn name(source_url: String) -> Result<Json<JsonResponse>, Status> {
|
pub async fn name(
|
||||||
let previous_site_name = previous_url(&source_url, &get_global_names().await);
|
source_url: String,
|
||||||
let next_site_name = next_url(&source_url, &get_global_names().await);
|
cors: Guard<'_>,
|
||||||
|
) -> Responder<Result<Json<JsonResponse>, Status>> {
|
||||||
|
let previous_site_name = previous_name(&source_url, &get_global_names().await);
|
||||||
|
let next_site_name = next_name(&source_url, &get_global_names().await);
|
||||||
|
|
||||||
if previous_site_name.is_none() && next_site_name.is_none() {
|
if previous_site_name.is_none() && next_site_name.is_none() {
|
||||||
return Err(Status::NotFound);
|
return cors.responder(Err(Status::NotFound));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Json(JsonResponse {
|
let response = Ok(Json(JsonResponse {
|
||||||
previous_site_name,
|
previous_site_name,
|
||||||
next_site_name,
|
next_site_name,
|
||||||
}))
|
}));
|
||||||
|
cors.responder(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[catch(404)]
|
#[catch(404)]
|
||||||
pub fn not_found() -> ErrorTemplate<'static> {
|
pub fn not_found() -> Result<ErrorTemplate<'static>, Status> {
|
||||||
NOT_FOUND_ERROR
|
not_found_error()
|
||||||
}
|
}
|
||||||
|
|
60
templates/base.html
Normal file
60
templates/base.html
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Meowy Webring{% block title %}{% endblock %}</title>
|
||||||
|
{% block head %}
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: Atkinson Hyperlegible;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url("/public/woff2/{{ base_template.atkinson_latin_ext_woff2_filename }}") format("woff2"),
|
||||||
|
url("/public/woff/{{ base_template.atkinson_all_woff_filename }}") format("woff");
|
||||||
|
unicode-range: U+0100-024F,
|
||||||
|
U+0259,
|
||||||
|
U+1E00-1EFF,
|
||||||
|
U+2020,
|
||||||
|
U+20A0-20AB,
|
||||||
|
U+20AD-20CF,
|
||||||
|
U+2113,
|
||||||
|
U+2C60-2C7F,
|
||||||
|
U+A720-A7FF
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: Atkinson Hyperlegible;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url("/public/woff2/{{ base_template.atkinson_latin_woff2_filename }}") format("woff2"),
|
||||||
|
url("/public/woff/{{ base_template.atkinson_all_woff_filename }}") format("woff");
|
||||||
|
unicode-range: U+0000-00FF,
|
||||||
|
U+0131,
|
||||||
|
U+0152-0153,
|
||||||
|
U+02BB-02BC,
|
||||||
|
U+02C6,
|
||||||
|
U+02DA,
|
||||||
|
U+02DC,
|
||||||
|
U+2000-206F,
|
||||||
|
U+2074,
|
||||||
|
U+20AC,
|
||||||
|
U+2122,
|
||||||
|
U+2191,
|
||||||
|
U+2193,
|
||||||
|
U+2212,
|
||||||
|
U+2215,
|
||||||
|
U+FEFF,
|
||||||
|
U+FFFD
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="stylesheet" href="/public/css/{{ base_template.style_filename }}" />
|
||||||
|
{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,15 +1,10 @@
|
||||||
<!DOCTYPE html>
|
{% extends "base.html" %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
{% block title %} - {{ error }}{% endblock %}
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width">
|
{% block content %}
|
||||||
<title>Meowy Webring - {{ error }}</title>
|
<main>
|
||||||
<link rel="stylesheet" href="/public/style.css" />
|
<h1>{{ error }}</h1>
|
||||||
</head>
|
<p>{{ error_description }}</p>
|
||||||
<body>
|
</main>
|
||||||
<main>
|
{% endblock %}
|
||||||
<h1>{{ error }}</h1>
|
|
||||||
<p>{{ error_description }}</p>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
16
templates/index.html
Normal file
16
templates/index.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<main>
|
||||||
|
<h1>Meowy Webring</h1>
|
||||||
|
<h2>Sites</h2>
|
||||||
|
{% for site in sites %}
|
||||||
|
{% match site.name %}
|
||||||
|
{% when Some with (value) %}
|
||||||
|
<p><a href="https://{{ site.url }}">{{ value }}</a></p>
|
||||||
|
{% when None %}
|
||||||
|
<p><a href="https://{{ site.url }}">{{ site.url }}</a></p>
|
||||||
|
{% endmatch %}
|
||||||
|
{% endfor %}
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
Loading…
Reference in a new issue