Compare commits

..

24 Commits

Author SHA1 Message Date
august 5848b7360c Add date editing for posts 2025-01-09 18:29:24 -05:00
august d0a4b0f6a6 Update Cargo.lock 2025-01-09 12:06:01 -05:00
august 59a6205a1e RSS fixes 2025-01-09 12:05:42 -05:00
august 4bfce7c928 Add RSS and fixups 2025-01-07 17:59:07 -05:00
august d95d764c4a Update Cargo.lock 2025-01-06 20:18:14 -05:00
august ab494f8381 Fix env vars 2025-01-06 20:17:49 -05:00
august 35331ef076 Remove redundant css 2025-01-06 20:17:24 -05:00
august d7e4f91740 Fix youtube embed issues & improve layout 2025-01-06 20:16:28 -05:00
august a477b43f1c Fix binary name 2025-01-06 20:15:24 -05:00
august 450c6d137b Fix staging scipt domain 2025-01-06 20:14:12 -05:00
august f7b850d62a Add meta tags to blog posts 2025-01-06 19:16:25 -05:00
august cb6e666a6e Fix quotes escaping img alt attrs 2025-01-06 19:15:11 -05:00
august 892b2082c1 Fix blog roll text reflow on hover 2025-01-06 17:28:24 -05:00
august 2740bd8c6f Fix line height 2025-01-06 17:20:57 -05:00
august 66dcbdc93a Update footer language 2025-01-06 17:20:03 -05:00
august 46c1edafa0 Footer & link style tweaks 2025-01-06 17:06:11 -05:00
august 1789cb261f Update editor lists tooltip 2025-01-06 16:53:08 -05:00
august d6f16fe65e Fix blockquote issues 2025-01-06 16:52:51 -05:00
august 725a77318a Stop tracking .DS_Store 2025-01-06 15:18:21 -05:00
august 244cddd966 Update gitignore 2025-01-06 15:16:17 -05:00
august 6e9c280542 Update footer layout 2025-01-06 15:12:17 -05:00
august 8c5d5553c5 Fix login page layout 2025-01-06 15:12:17 -05:00
august ff12f2c7b4 Fix overlapping admin entry text 2025-01-06 15:12:17 -05:00
august 114dc25235 Add site footer
Add site footer to default template
2025-01-06 15:12:11 -05:00
25 changed files with 841 additions and 403 deletions
Vendored
BIN
View File
Binary file not shown.
+1
View File
@@ -3,3 +3,4 @@
.env .env
*.db* *.db*
key key
.DS_Store
Generated
+366 -332
View File
File diff suppressed because it is too large Load Diff
+13
View File
@@ -75,6 +75,19 @@ impl BlogDb {
.await?; .await?;
Ok(()) Ok(())
} }
/// Delete post by id
pub async fn set_post_date<S, T>(&self, id: S, date: T) -> anyhow::Result<()>
where
S: AsRef<str>,
T: AsRef<str>,
{
sqlx::query("UPDATE posts SET date=? WHERE id=?")
.bind(date.as_ref())
.bind(id.as_ref())
.execute(&self.db)
.await?;
Ok(())
}
} }
#[cfg(test)] #[cfg(test)]
+5
View File
@@ -3,6 +3,10 @@ name = "server"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[[bin]]
name = "evie"
path = "./src/main.rs"
[dependencies] [dependencies]
blogdb = { path = "../blogdb/" } blogdb = { path = "../blogdb/" }
anyhow = "1.0.89" anyhow = "1.0.89"
@@ -42,6 +46,7 @@ tracing-subscriber = { version = "0.3.18", features = [
"std", "std",
] } ] }
phf = "0.11.2" phf = "0.11.2"
chrono = { version = "0.4.39", features = ["alloc"] }
[build-dependencies] [build-dependencies]
dotenvy = "0.15.7" dotenvy = "0.15.7"
+18 -2
View File
@@ -17,7 +17,7 @@ fn main() {
println!("cargo:rustc-env={key}={value}"); println!("cargo:rustc-env={key}={value}");
} }
let path = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs"); let path = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs");
let mut file = BufWriter::new(File::create(&path).unwrap()); let mut outfile = BufWriter::new(File::create(&path).unwrap());
let template_dir = [&env::var("CARGO_MANIFEST_DIR").unwrap(), "/src/templates"].concat(); let template_dir = [&env::var("CARGO_MANIFEST_DIR").unwrap(), "/src/templates"].concat();
println!("cargo:rerun-if-changed={}", template_dir); println!("cargo:rerun-if-changed={}", template_dir);
let pattern = [&template_dir, "/**/*.html"].concat(); let pattern = [&template_dir, "/**/*.html"].concat();
@@ -47,8 +47,24 @@ fn main() {
Err(_) => todo!(), Err(_) => todo!(),
} }
} }
// this is all a mess cause i'm lazy lol
let feed_template_path = [&template_dir, "/feed.xml"].concat();
let mut feed_file = File::open(feed_template_path).unwrap();
let mut content = "r####\"".to_string();
feed_file.read_to_string(&mut content).unwrap();
content.push_str("\"####");
let feed_template = content;
map.entry("feed".to_string(), &feed_template);
let item_template_path = [&template_dir, "/item.xml"].concat();
let mut item_file = File::open(item_template_path).unwrap();
let mut content = "r####\"".to_string();
item_file.read_to_string(&mut content).unwrap();
content.push_str("\"####");
let item_template = content;
map.entry("item".to_string(), &item_template);
writeln!( writeln!(
&mut file, &mut outfile,
"static TEMPLATES: phf::Map<&'static str, &'static str> = {};", "static TEMPLATES: phf::Map<&'static str, &'static str> = {};",
map.build() map.build()
) )
+16 -2
View File
@@ -6,6 +6,7 @@ main {
gap: var(--default-padding); gap: var(--default-padding);
margin-block-end: 40svb; margin-block-end: 40svb;
padding: 0; padding: 0;
min-block-size: unset;
} }
@media screen and (min-width: 60rem) { @media screen and (min-width: 60rem) {
@@ -21,6 +22,10 @@ main {
flex-wrap: nowrap !important; flex-wrap: nowrap !important;
} }
.admin-widget:not(.admin-widget-user) {
max-inline-size: calc(50% - (0.5 * var(--default-padding)));
}
.admin-widget-user { .admin-widget-user {
flex-direction: row !important; flex-direction: row !important;
justify-content: space-between; justify-content: space-between;
@@ -117,8 +122,8 @@ main {
.blog-admin { .blog-admin {
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
gap: var(--default-padding);
flex-wrap: wrap; flex-wrap: wrap;
gap: var(--default-padding);
box-sizing: border-box; box-sizing: border-box;
min-block-size: 0; min-block-size: 0;
flex: 1; flex: 1;
@@ -198,6 +203,7 @@ main {
min-block-size: 0; min-block-size: 0;
border: var(--border); border: var(--border);
display: flex; display: flex;
max-inline-size: 100%;
&>*:not(form, a) { &>*:not(form, a) {
padding-inline: var(--default-padding); padding-inline: var(--default-padding);
@@ -212,6 +218,7 @@ main {
li { li {
flex: 1; flex: 1;
overflow: hidden;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
@@ -239,10 +246,17 @@ main {
a.entry-content { a.entry-content {
color: inherit; color: inherit;
text-decoration: inherit; text-decoration: inherit;
inline-size: 90%; max-inline-size: 90%;
transition: all 0.3s ease; transition: all 0.3s ease;
& :is(p, h2) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
inline-size: 100%;
}
&:hover, &:hover,
&:focus { &:focus {
transform: translateX(1ch); transform: translateX(1ch);
+5 -1
View File
@@ -8,6 +8,7 @@ h1 {
.blog-roll-entry { .blog-roll-entry {
margin-block: 1rem; margin-block: 1rem;
overflow: visible;
a { a {
border: var(--border); border: var(--border);
@@ -21,7 +22,9 @@ h1 {
.entry-content { .entry-content {
padding: var(--content-padding); padding: var(--content-padding);
transition: padding-inline-start 0.3s ease, box-shadow 0.3s ease; transition: padding 0.3s ease, box-shadow 0.3s ease;
overflow: hidden;
* { * {
margin-block: 0; margin-block: 0;
@@ -32,6 +35,7 @@ h1 {
&:focus-visible { &:focus-visible {
.entry-content { .entry-content {
padding-inline-start: calc(var(--content-padding) * 2); padding-inline-start: calc(var(--content-padding) * 2);
padding-inline-end: 0;
box-shadow: inset 3px 0px 0px var(--color-text); box-shadow: inset 3px 0px 0px var(--color-text);
} }
} }
+7 -1
View File
@@ -91,6 +91,7 @@ main {
@media all and (max-width: 650px) { @media all and (max-width: 650px) {
.ce-toolbar__actions { .ce-toolbar__actions {
&>*:nth-child(1), &>*:nth-child(1),
&>*:nth-child(2) { &>*:nth-child(2) {
border-radius: 0; border-radius: 0;
@@ -141,7 +142,7 @@ main {
} }
* { * {
border-radius: 0 !important; border-radius: 0 !important;
} }
.ce-block__content { .ce-block__content {
@@ -192,6 +193,11 @@ main {
background-color: var(--color-selection) !important; background-color: var(--color-selection) !important;
} }
.cdx-quote__text {
border-block-end: none !important;
margin-block-end: 0 !important;
}
.embed-tool__caption { .embed-tool__caption {
display: none; display: none;
} }
+12 -8
View File
@@ -3,13 +3,8 @@ body {
flex-wrap: wrap; flex-wrap: wrap;
justify-content: stretch; justify-content: stretch;
align-items: stretch; align-items: stretch;
} flex-direction: column;
flex-wrap: nowrap;
@media screen and (max-width: 50rem) {
body {
flex-direction: column;
flex-wrap: nowrap;
}
} }
body>div { body>div {
@@ -39,7 +34,6 @@ main {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
/* block-size: 100%; */
max-block-size: 100%; max-block-size: 100%;
&>:first-child { &>:first-child {
@@ -71,3 +65,13 @@ form {
max-inline-size: 40ch; max-inline-size: 40ch;
gap: 1rem; gap: 1rem;
} }
@media screen and (min-width: 50rem) {
body {
flex-direction: row;
}
main {
margin-block: 0;
}
}
+109 -18
View File
@@ -26,7 +26,6 @@
--font-size: 1rem; --font-size: 1rem;
font-size: var(--font-size); font-size: var(--font-size);
line-height: 1.15;
/* 1. Correct the line height in all browsers. */ /* 1. Correct the line height in all browsers. */
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
/* 2. Prevent adjustments of font size after orientation changes in iOS. */ /* 2. Prevent adjustments of font size after orientation changes in iOS. */
@@ -71,10 +70,12 @@ input,
label, label,
a, a,
blockquote, blockquote,
figcaption,
aside, aside,
ol, ol,
ul { ul {
font-size: calc(var(--font-size) * 1.33); font-size: calc(var(--font-size) * 1.33);
line-height: 1.5em;
} }
h1, h1,
@@ -87,26 +88,22 @@ h6 {
} }
aside, aside,
blockquote { figure {
border: var(--border);
}
blockquote,
aside {
padding: var(--default-padding) calc(var(--default-padding) * 2); padding: var(--default-padding) calc(var(--default-padding) * 2);
border: var(--border);
max-inline-size: 100%; max-inline-size: 100%;
margin-inline: 0; margin-inline: 0;
} }
blockquote { blockquote {
&::before { margin: 0;
content: '“'; }
font-weight: 600;
}
&::after { figure {
content: '”'; & figcaption {
font-weight: 600; font-style: italic;
margin-inline-start: var(--default-padding);
margin-block-start: var(--default-padding);
} }
} }
@@ -119,6 +116,29 @@ ul {
a { a {
color: unset; color: unset;
text-decoration: unset; text-decoration: unset;
&:not(:has(time)):is([href^="http"], [href^="mailto"]) {
padding-inline-end: 0.9em;
&::after {
transition: all 0.3s ease;
position: absolute;
content: '';
display: inline-block;
width: 1em;
height: 1em;
margin-inline-start: -0.05em;
margin-block-start: 0.23em;
background-size: 100%;
transform: scale(0.8);
background-image: url("/assets/images/external.svg");
}
&:is(:hover, :active, :focus-visible)::after {
filter: invert(100%);
}
}
} }
form { form {
@@ -135,7 +155,7 @@ input {
margin: 0; margin: 0;
} }
input:is([type="text"], [type="password"], [type="search"]) { input:is([type="text"], [type="password"], [type="search"], [type="email"], ) {
padding: 0.5ch 1ch; padding: 0.5ch 1ch;
&:focus, &:focus,
@@ -269,7 +289,7 @@ header {
background: var(--color-bg); background: var(--color-bg);
&>:first-child { &>:first-child {
margin: unset; margin-block-end: var(--default-padding);
} }
h1 { h1 {
@@ -347,9 +367,66 @@ header {
} }
iframe {
max-inline-size: 100%;
appearance: unset;
border: var(--border);
}
footer {
border-block-start: var(--border);
margin-block-start: calc(var(--default-padding) * 6);
display: flex;
min-block-size: calc(var(--header-size) * 2.5);
padding: calc(1.5 * var(--default-padding)) var(--default-padding);
gap: calc(2 * var(--default-padding));
flex-direction: column;
justify-content: start;
&>* {
max-inline-size: 100%;
display: flex;
flex-direction: column;
justify-content: start;
}
form {
display: flex;
flex-direction: column;
& div {
display: flex;
max-inline-size: 100%;
&>* {
min-inline-size: 0;
}
input[type="submit"] {
min-inline-size: min-content;
}
}
}
h1,
ul {
margin-block: 0 1rem;
}
p,
li,
input {
margin-block: 0.3rem;
}
}
main { main {
margin-block: var(--default-padding) 40svb; min-block-size: 100svb;
margin-inline: auto; margin: var(--default-padding) auto;
max-inline-size: min(60ch, 80%); max-inline-size: min(60ch, 80%);
padding-block: var(--default-padding); padding-block: var(--default-padding);
gap: var(--default-padding); gap: var(--default-padding);
@@ -416,12 +493,26 @@ main {
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
&>:first-child {
margin-block-end: unset;
}
nav { nav {
justify-content: flex-end; justify-content: flex-end;
box-sizing: border-box; box-sizing: border-box;
} }
} }
footer {
flex-direction: row;
&>* {
max-inline-size: 40ch;
min-inline-size: 30ch;
}
}
main { main {
margin-block: var(--default-padding); margin-block: var(--default-padding);
} }
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M 5 5 L 5 27 L 27 27 L 27 5 Z M 7 7 L 25 7 L 25 25 L 7 25 Z M 13 10 L 13 12 L 18.5625 12 L 9.28125 21.28125 L 10.71875 22.71875 L 20 13.4375 L 20 19 L 22 19 L 22 10 Z"/></svg>

After

Width:  |  Height:  |  Size: 407 B

+11
View File
@@ -0,0 +1,11 @@
const editDateButton = document.getElementById("date-update");
const postActions = document.getElementById("post-actions");
const children = postActions.innerHTML;
dateInput = `<button class="form-action" formaction="/admin" formmethod="get" formnovalidate>✕</button><input class="form-action" type="date" name="date" placeholder="Enter Date" autocomplete="off" aria-label="New Date" required /> <button class="form-action" type="submit" formaction="/api/posts/date">Update Date</button>`;
editDateButton.addEventListener("click", (ev) => {
ev.preventDefault();
console.log(ev, postActions, dateInput);
postActions.innerHTML = dateInput;
});
+3 -13
View File
@@ -1776,13 +1776,13 @@
(this.settings = [ (this.settings = [
{ {
name: "unordered", name: "unordered",
label: this.api.i18n.t("Unordered"), label: this.api.i18n.t("Bulleted"),
icon: n, icon: n,
default: t.defaultStyle === "unordered" || !1, default: t.defaultStyle === "unordered" || !1,
}, },
{ {
name: "ordered", name: "ordered",
label: this.api.i18n.t("Ordered"), label: this.api.i18n.t("Numbered"),
icon: l, icon: l,
default: t.defaultStyle === "ordered" || !0, default: t.defaultStyle === "ordered" || !0,
}, },
@@ -3239,16 +3239,6 @@
static get sanitize() { static get sanitize() {
return { text: { br: !0 }, caption: { br: !0 }, alignment: {} }; return { text: { br: !0 }, caption: { br: !0 }, alignment: {} };
} }
renderSettings() {
const t = (n) => n && n[0].toUpperCase() + n.slice(1);
return this.settings.map((n) => ({
icon: n.icon,
label: this.api.i18n.t(`Align ${t(n.name)}`),
onActivate: () => this._toggleTune(n.name),
isActive: this._data.alignment === n.name,
closeOnActivate: !0,
}));
}
_toggleTune(t) { _toggleTune(t) {
(this._data.alignment = t), this._block.dispatchChange(); (this._data.alignment = t), this._block.dispatchChange();
} }
@@ -13662,7 +13652,7 @@ var editor = new EditorJS({
inlineToolbar: true, inlineToolbar: true,
config: { config: {
quotePlaceholder: "Enter a quote", quotePlaceholder: "Enter a quote",
captionPlaceholder: "Quote's author", captionPlaceholder: "Add author or a caption, leave blank to hide",
}, },
shortcut: "CMD+SHIFT+O", shortcut: "CMD+SHIFT+O",
}, },
+142 -15
View File
@@ -1,3 +1,4 @@
use chrono::prelude::*;
use std::{env, vec}; use std::{env, vec};
use blogdb::posts::Post; use blogdb::posts::Post;
@@ -72,6 +73,12 @@ where
} }
Ok(()) Ok(())
}), }),
element!("site-footer", |site_footer| {
if settings.site_footer {
site_footer.replace(template!("site-footer"), ContentType::Html);
}
Ok(())
}),
element!("br", |br| { element!("br", |br| {
br.remove(); br.remove();
Ok(()) Ok(())
@@ -187,7 +194,7 @@ where
.unwrap_or_default(); .unwrap_or_default();
make_page( make_page(
html, html,
PageSettings::new("Editor", Some(vec!["/assets/css/editor.css"]), true), PageSettings::new("Editor", Some(vec!["/assets/css/editor.css"]), true, false),
) )
} }
@@ -229,7 +236,7 @@ pub(crate) fn login_status(next: &str, success: bool) -> String {
.unwrap_or_default(); .unwrap_or_default();
make_page( make_page(
html, html,
PageSettings::new("Login", Some(vec!["/assets/css/login.css"]), false), PageSettings::new("Login", Some(vec!["/assets/css/login.css"]), false, false),
) )
} }
@@ -237,7 +244,7 @@ pub(crate) async fn admin_page(session_id: i64, db: &BlogDb) -> String {
let content = admin_widgets(template!("admin"), session_id, db).await; let content = admin_widgets(template!("admin"), session_id, db).await;
make_page( make_page(
&content, &content,
PageSettings::new("Admin", Some(vec!["/assets/css/admin.css"]), true), PageSettings::new("Admin", Some(vec!["/assets/css/admin.css"]), true, false),
) )
} }
@@ -288,8 +295,7 @@ async fn admin_entries(entry_type: EntryType, session_id: i64, db: &BlogDb) -> S
let is_empty: bool; let is_empty: bool;
let entries_html = match entry_type { let entries_html = match entry_type {
EntryType::Post => { EntryType::Post => {
let mut entries = db.get_posts().await.unwrap_or(vec![]); let entries = db.get_posts().await.unwrap_or(vec![]);
entries.reverse();
let mut entry_list_html = String::new(); let mut entry_list_html = String::new();
is_empty = entries.is_empty(); is_empty = entries.is_empty();
if !is_empty { if !is_empty {
@@ -441,14 +447,30 @@ where
element_content_handlers: vec![element!("meta", |meta| { element_content_handlers: vec![element!("meta", |meta| {
if let Some(attr) = meta.get_attribute("property") { if let Some(attr) = meta.get_attribute("property") {
let content = match attr.as_str() { let content = match attr.as_str() {
"og:url" => &[env!("DOMAIN"), slug.as_ref()].concat(), "og:url" => {
#[cfg(debug_assertions)]
{
&["http://localhost", slug.as_ref()].concat()
}
#[cfg(not(debug_assertions))]
{
&["https://", env!("DOMAIN"), slug.as_ref()].concat()
}
}
"og:type" => "article", "og:type" => "article",
"og:title" => title.as_ref(), "og:title" => title.as_ref(),
"og:description" => description.as_ref(), "og:description" => description.as_ref(),
"og:image" => { "og:image" => {
let image = image.as_ref(); let image = image.as_ref();
if !image.is_empty() { if !image.is_empty() {
image #[cfg(debug_assertions)]
{
&["http://localhost", image].concat()
}
#[cfg(not(debug_assertions))]
{
&["https://", env!("DOMAIN"), image].concat()
}
} else { } else {
meta.remove(); meta.remove();
"" ""
@@ -476,7 +498,7 @@ where
let html = make_page(template, PageSettings::title("Blog")); let html = make_page(template, PageSettings::title("Blog"));
let post = db.get_post(id).await.unwrap_or_default(); let post = db.get_post(id).await.unwrap_or_default();
let head = head::<_, _, [String; 0]>(post.title, []); let head_content = head::<_, _, [String; 0]>(post.title, []);
let post_content: Blocks = serde_json::from_str(&post.content).unwrap_or_default(); let post_content: Blocks = serde_json::from_str(&post.content).unwrap_or_default();
let post_html: String = post_content.to_html(); let post_html: String = post_content.to_html();
let image = if let Some(url) = post_content.image() { let image = if let Some(url) = post_content.image() {
@@ -498,8 +520,17 @@ where
time.after(&post_html, ContentType::Html); time.after(&post_html, ContentType::Html);
time.replace( time.replace(
&[ &[
r#"<a class="animated-link" style="align-self: flex-start; margin-block-end: 0" href="https://"#, r#"<a class="animated-link" style="align-self: flex-start; margin-block-end: 0" href=""#,
env!("DOMAIN"), {
#[cfg(debug_assertions)]
{
"http://localhost"
}
#[cfg(not(debug_assertions))]
{
&["https://", env!("DOMAIN")].concat()
}
},
"/blog/", "/blog/",
&post.id, &post.id,
r#""><time>"#, r#""><time>"#,
@@ -510,8 +541,11 @@ where
ContentType::Html, ContentType::Html,
); );
Ok(()) Ok(())
}), element!("head", |head| {
head.replace(&head_content, ContentType::Html);
head.append(&meta, ContentType::Html);
Ok(())
})], })],
..RewriteStrSettings::new() ..RewriteStrSettings::new()
}, },
) )
@@ -558,7 +592,7 @@ pub(crate) async fn blog_roll(db: &BlogDb) -> String {
make_page( make_page(
blog_roll_html, blog_roll_html,
PageSettings::new("Blog", Some(vec!["/assets/css/blog.css"]), true), PageSettings::new("Blog", Some(vec!["/assets/css/blog.css"]), true, true),
) )
} }
@@ -653,6 +687,7 @@ pub(crate) async fn search_page(
["Results for “", &query, ""].concat(), ["Results for “", &query, ""].concat(),
Some(vec!["/assets/css/blog.css"]), Some(vec!["/assets/css/blog.css"]),
true, true,
true,
), ),
) )
} }
@@ -694,6 +729,7 @@ pub(crate) struct PageSettings {
title: String, title: String,
stylesheets: Option<Vec<String>>, stylesheets: Option<Vec<String>>,
site_header: bool, site_header: bool,
site_footer: bool,
} }
impl PageSettings { impl PageSettings {
@@ -705,9 +741,15 @@ impl PageSettings {
title: title.to_string(), title: title.to_string(),
stylesheets: None, stylesheets: None,
site_header: true, site_header: true,
site_footer: true,
} }
} }
pub(crate) fn new<S, T>(title: S, stylesheets: Option<Vec<T>>, site_header: bool) -> Self pub(crate) fn new<S, T>(
title: S,
stylesheets: Option<Vec<T>>,
site_header: bool,
site_footer: bool,
) -> Self
where where
S: ToString, S: ToString,
T: ToString, T: ToString,
@@ -718,6 +760,7 @@ impl PageSettings {
title: title.to_string(), title: title.to_string(),
stylesheets, stylesheets,
site_header, site_header,
site_footer,
} }
} }
} }
@@ -729,7 +772,7 @@ where
make_page(message, PageSettings::title("Not found")) make_page(message, PageSettings::title("Not found"))
} }
pub(crate) fn animate_anchors<S>(input: S) -> String pub(crate) fn zhuzh_anchors<S>(input: S) -> String
where where
S: AsRef<str>, S: AsRef<str>,
{ {
@@ -737,9 +780,14 @@ where
input.as_ref(), input.as_ref(),
RewriteStrSettings { RewriteStrSettings {
element_content_handlers: vec![element!("a", move |t| { element_content_handlers: vec![element!("a", move |t| {
if let Some(href) = t.get_attribute("href") {
if !href.starts_with("/") {
t.set_attribute("target", "_blank").unwrap_or_default();
}
}
match t.get_attribute("class") { match t.get_attribute("class") {
Some(class) => t Some(class) => t
.set_attribute("class", &[&class, "animated-link-underline"].concat()) .set_attribute("class", &[&class, " animated-link-underline"].concat())
.unwrap_or_default(), .unwrap_or_default(),
None => { None => {
t.set_attribute("class", "animated-link-underline") t.set_attribute("class", "animated-link-underline")
@@ -767,3 +815,82 @@ pub(crate) fn sanitize<S: AsRef<str>>(input: S) -> String {
) )
.unwrap_or_default() .unwrap_or_default()
} }
pub(crate) async fn rss(db: &BlogDb) -> String {
let posts = db.get_posts().await.unwrap_or_default();
let mut items = String::new();
for post in posts {
let url = {
#[cfg(debug_assertions)]
{
["http://localhost/blog/", &post.id].concat()
}
#[cfg(not(debug_assertions))]
{
["https://", env!("DOMAIN"), "/blog/", &post.id].concat()
}
};
let item = rewrite_str(
template!("item"),
RewriteStrSettings {
element_content_handlers: vec![
element!("title", |title| {
title.set_inner_content(&post.title, ContentType::Text);
Ok(())
}),
element!("link", |link| {
link.replace(&["<link>", &url, "</link>"].concat(), ContentType::Html); // slight hack to get around lol_html weirdness
Ok(())
}),
element!("guid", |guid| {
guid.set_inner_content(&url, ContentType::Text);
Ok(())
}),
element!("pubDate", |pub_date| {
let date = {
let date: [u32; 3] = post
.date
.splitn(3, "-")
.map(|d| d.parse::<u32>().unwrap_or_default())
.collect::<Vec<u32>>()
.try_into()
.unwrap_or_default();
let date_time: DateTime<FixedOffset> =
FixedOffset::west_opt(5 * 3600) // New York City
.unwrap()
.with_ymd_and_hms(date[0] as i32, date[1], date[2], 0, 0, 0)
.unwrap();
date_time.to_rfc2822()
};
pub_date.set_inner_content(&date, ContentType::Text);
Ok(())
}),
element!("description", |description| {
let post_content: Blocks =
serde_json::from_str(&post.content).unwrap_or_default();
let desc = ["<![CDATA[", &post_content.to_html(), "]]>"].concat();
description.set_inner_content(&desc, ContentType::Html);
Ok(())
}),
],
..RewriteStrSettings::new()
},
)
.unwrap_or_default();
items.push_str(&item);
}
let feed = rewrite_str(
template!("feed"),
RewriteStrSettings {
element_content_handlers: vec![element!("channel", move |channel| {
channel.append(&items, ContentType::Html);
Ok(())
})],
..RewriteStrSettings::new()
},
)
.unwrap_or_default();
feed
}
+28 -7
View File
@@ -34,6 +34,7 @@ pub(crate) enum Block {
}, },
quote { quote {
text: String, text: String,
caption: String,
}, },
embed { embed {
service: String, service: String,
@@ -78,7 +79,13 @@ impl Block {
} }
}, },
Block::warning { title } => ["<aside>", title, "</aside>"].concat(), Block::warning { title } => ["<aside>", title, "</aside>"].concat(),
Block::quote { text } => ["<blockquote>", text, "</blockquote>"].concat(), Block::quote { text, caption } => {
if caption.is_empty() {
["<figure><blockquote>“", text, "”</blockquote></figure>"].concat()
} else {
["<figure><blockquote>“", text, "”</blockquote><figcaption>— ", caption,"</figcaption></figure>"].concat()
}
},
Block::embed { Block::embed {
embed, embed,
width, width,
@@ -91,16 +98,27 @@ impl Block {
&height.to_string(), &height.to_string(),
"\" src=\"", "\" src=\"",
embed, embed,
"\"></iframe>", "\" allowfullscreen></iframe>",
] ]
.concat(), .concat(),
Block::image { file, caption } => { Block::image { file, caption } => {
["<img src=\"", &file.url, "\" alt=\"", caption, "\"/>"].concat() let src = {
#[cfg(debug_assertions)]
{
&file.url
}
#[cfg(not(debug_assertions))]
{
&["https://", env!("DOMAIN"), &file.url].concat()
}
};
["<img src=\"", src, "\" alt=\"", &caption.replace(r#"""#, "&quot;").replace("<br>", ""), "\"/>"].concat()
} }
Block::delimiter {} => "<div style=\"inline-size: 100%; block-size: 1px; background: var(--color-text)\"></div>".to_string(), Block::delimiter {} => "<div style=\"inline-size: 100%; block-size: 1px; background: var(--color-text)\"></div>".to_string(),
}; };
html::animate_anchors(html::remove_el(&text, "br")) html::zhuzh_anchors(html::remove_el(&text, "br"))
} }
fn to_plaintext(&self) -> String { fn to_plaintext(&self) -> String {
@@ -109,7 +127,7 @@ impl Block {
Block::header { text, level: _ } => text, Block::header { text, level: _ } => text,
Block::list { style: _, items } => &items.join("\n"), Block::list { style: _, items } => &items.join("\n"),
Block::warning { title } => title, Block::warning { title } => title,
Block::quote { text } => text, Block::quote { text, caption } => &[text, caption.as_str()].concat(),
_ => &"".to_string(), _ => &"".to_string(),
}; };
let text = [text, "\n"].concat(); let text = [text, "\n"].concat();
@@ -241,14 +259,17 @@ impl Blocks {
Block::paragraph { text: _ } => true, Block::paragraph { text: _ } => true,
Block::header { text: _, level } => *level > 1, Block::header { text: _, level } => *level > 1,
Block::warning { title: _ } => true, Block::warning { title: _ } => true,
Block::quote { text: _ } => true, Block::quote {
text: _,
caption: _,
} => true,
_ => false, _ => false,
}) })
.map(|block| match block { .map(|block| match block {
Block::paragraph { text } => text, Block::paragraph { text } => text,
Block::header { text, level: _ } => text, Block::header { text, level: _ } => text,
Block::warning { title } => title, Block::warning { title } => title,
Block::quote { text } => text, Block::quote { text, caption: _ } => text,
_ => "...", _ => "...",
}) })
.unwrap_or("No description"), .unwrap_or("No description"),
+8
View File
@@ -38,6 +38,7 @@ pub(super) fn api(state: BlogState) -> Router {
struct Entries { struct Entries {
#[serde(default)] #[serde(default)]
item: Vec<String>, item: Vec<String>,
date: Option<String>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@@ -45,6 +46,7 @@ struct Entries {
enum PostsEndpoints { enum PostsEndpoints {
delete, delete,
unpublish, unpublish,
date,
} }
async fn posts( async fn posts(
@@ -71,6 +73,12 @@ async fn posts(
state.index_delete(term); state.index_delete(term);
} }
} }
PostsEndpoints::date => {
let date = &data.date.clone().unwrap_or("1970-01-01".to_string());
for post_id in data.item.iter() {
let _ = db.set_post_date(post_id, &date).await;
}
}
} }
state.index_commit(); state.index_commit();
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
+10
View File
@@ -62,6 +62,7 @@ pub(super) async fn make_router(state: BlogState) -> Router {
.nest("/api", api(state.clone())) .nest("/api", api(state.clone()))
.nest("/login", login(State(state.clone()))) .nest("/login", login(State(state.clone())))
.route("/", get(pages).with_state(state.clone())) .route("/", get(pages).with_state(state.clone()))
.route("/feed.xml", get(rss).with_state(state.clone()))
.route("/*path", get(pages).with_state(state.clone())) .route("/*path", get(pages).with_state(state.clone()))
.route("/search", get(search_empty)) .route("/search", get(search_empty))
.route("/search/*query", get(search).with_state(state.clone())) .route("/search/*query", get(search).with_state(state.clone()))
@@ -308,6 +309,15 @@ async fn admin(jar: PrivateCookieJar, State(state): State<BlogState>) -> Respons
(headers, Html(page)).into_response() (headers, Html(page)).into_response()
} }
async fn rss(State(state): State<BlogState>) -> Response {
let mut headers = HeaderMap::new();
headers.append(
CONTENT_TYPE,
HeaderValue::from_str("application/xml").unwrap(),
);
(headers, html::rss(state.db()).await).into_response()
}
#[derive(Serialize, Deserialize, Default, Debug)] #[derive(Serialize, Deserialize, Default, Debug)]
pub(crate) struct SearchResponse(Vec<SearchResponseEntry>); pub(crate) struct SearchResponse(Vec<SearchResponseEntry>);
impl SearchResponse { impl SearchResponse {
@@ -3,11 +3,14 @@
<h1>Published Posts</h1> <h1>Published Posts</h1>
</div> </div>
<form method="post"> <form method="post">
<div class="form-actions"> <div class="form-actions" id="post-actions">
<button class="form-action" type="submit" formaction="/api/posts/delete">Delete posts <button class="form-action" type="submit" formaction="/api/posts/delete">Delete posts
</button> </button>
<button class=" form-action" type="submit" formaction="/api/posts/unpublish">Unpublish Posts <button class=" form-action" type="submit" formaction="/api/posts/unpublish">Unpublish Posts
</button> </button>
<button id="date-update" class="form-action" type="submit" formaction="#update-date" formmethod="get">Edit
Date
</button>
</div> </div>
<ul tabindex="-1"> <ul tabindex="-1">
</ul> </ul>
+1
View File
@@ -3,3 +3,4 @@
<admin-widget type="drafts"></admin-widget> <admin-widget type="drafts"></admin-widget>
<admin-widget type="posts"></admin-widget> <admin-widget type="posts"></admin-widget>
</div> </div>
<script src="/assets/js/admin.js"></script>
+3 -2
View File
@@ -2,8 +2,8 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="/assets/css/style.css" /> <link rel="stylesheet" href="/assets/css/style.css" />
</head> </head>
@@ -11,6 +11,7 @@
<site-header></site-header> <site-header></site-header>
<main> <main>
</main> </main>
<site-footer></site-footer>
</body> </body>
</html> </html>
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Evie Ippolito's Blog</title>
<link>https://evieippolito.com/blog</link>
<atom:link href="https://evieippolito.com/feed.xml" rel="self" type="application/rss+xml" />
<description>Evie's blog!</description>
<language>en-us</language>
<ttl>720</ttl>
<managingEditor>evieippolito@duck.com (Evie Ippolito)</managingEditor>
<webMaster>me@augustkline.com (august kline)</webMaster>
</channel>
</rss>
+7
View File
@@ -0,0 +1,7 @@
<item>
<title></title>
<link>
<guid isPermaLink="true"></guid>
<pubDate></pubDate>
<description></description>
</item>
+56
View File
@@ -0,0 +1,56 @@
<footer>
<div>
<h1>keep in touch</h1>
<div>
<form action="https://buttondown.com/api/emails/embed-subscribe/eviewrites" method="post"
target="popupwindow" onsubmit="window.open('https://buttondown.com/eviewrites', 'popupwindow')"
class="embeddable-buttondown-form">
<label for="bd-email">
<p>Subscribe to my newsletter for updates &lt;3</p>
</label>
<div>
<input type="email" name="email" id="bd-email" required />
<input type="submit" value="Subscribe" style="border-inline-start: none;" />
</div>
</form>
<p>Or subscribe to my <a class="animated-link-underline" href="/feed.xml">RSS feed</a>! RSS rules, you can
learn more
about
it <a href="https://guides.library.yale.edu/keepingup/basics" class="animated-link-underline"
target="_blank" rel="noreferrer">here</a>.</p>
</div>
</div>
<div>
<h1>credits</h1>
<div>
<p><a href="https://augustkline.com" class="animated-link-underline" target="_blank" rel="noreferrer">august
kline</a>
made this website. <a href="mailto:inquiries@augustkline.com" class="animated-link-underline">give her
money</a> and she will make you
one too.</p>
<p><a href="https://www.redaction.us/" class="animated-link-underline" target="_blank"
rel="noreferrer">Redaction</a> and <a href="https://www.brailleinstitute.org/freefont/"
class="animated-link-underline" target="_blank" rel="noreferrer">Atkinson Hyperlegible</a> are used
as display and text typefaces,
respectively.</p>
</div>
</div>
<div>
<h1>elsewhere</h1>
<nav>
<ul>
<li>
<ul>
<li>
<a href="https://www.instagram.com/everzines/" class="animated-link" target="_blank"
rel="noreferrer">insta</a>
</li>
<li>
<a href="mailto:evieippolito@duck.com" class="animated-link">email</a>
</li>
</ul>
</li>
</ul>
</nav>
</div>
</footer>
+1 -1
View File
@@ -6,7 +6,7 @@ set -e
cargo update; cargo update;
DOMAIN=evieippolito.com CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc cargo build --release --target x86_64-unknown-linux-gnu; DOMAIN=staging.evieippolito.com CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER=x86_64-linux-gnu-gcc cargo build --release --target x86_64-unknown-linux-gnu;
scp target/x86_64-unknown-linux-gnu/release/evie kline@augustkline.com:/home/kline/; scp target/x86_64-unknown-linux-gnu/release/evie kline@augustkline.com:/home/kline/;