add: cleaner ui, prior view check, audio element, rendering fixes

This commit is contained in:
trisua 2025-08-16 23:24:20 -04:00
parent f215d038b3
commit cacd992f53
7 changed files with 320 additions and 57 deletions

View file

@ -147,6 +147,19 @@ globalThis.tab_editor = () => {
} }
}; };
globalThis.get_preview = async () => {
return await (
await fetch("/api/v1/render", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
content: globalThis.editor.getValue(),
metadata: globalThis.metadata_editor.getValue(),
}),
})
).text();
};
globalThis.tab_preview = async () => { globalThis.tab_preview = async () => {
if ( if (
!document !document
@ -157,16 +170,7 @@ globalThis.tab_preview = async () => {
} }
// render // render
const res = await ( const res = await get_preview();
await fetch("/api/v1/render", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
content: globalThis.editor.getValue(),
metadata: globalThis.metadata_editor.getValue(),
}),
})
).text();
document.getElementById("preview_tab").innerHTML = res; document.getElementById("preview_tab").innerHTML = res;
hljs.highlightAll(); hljs.highlightAll();
@ -366,3 +370,24 @@ setTimeout(() => {
// run initial hash check // run initial hash check
hash_check(window.location.hash); hash_check(window.location.hash);
}, 150); }, 150);
globalThis.submitter_load = (submitter) => {
return {
load() {
submitter.querySelector("[ui_ident=text]").classList.add("hidden");
submitter
.querySelector("[ui_ident=loader]")
.classList.remove("hidden");
submitter.setAttribute("disabled", "true");
},
failed() {
submitter
.querySelector("[ui_ident=text]")
.classList.remove("hidden");
submitter
.querySelector("[ui_ident=loader]")
.classList.add("hidden");
submitter.removeAttribute("disabled");
},
};
};

View file

@ -14,6 +14,8 @@
--color-green: hsl(100, 84%, 20%); --color-green: hsl(100, 84%, 20%);
--color-yellow: oklch(47% 0.157 37.304); --color-yellow: oklch(47% 0.157 37.304);
--color-purple: hsl(284, 84%, 20%); --color-purple: hsl(284, 84%, 20%);
--color-green-lowered: hsl(100, 84%, 15%);
--color-red-lowered: hsl(0, 84%, 35%);
--shadow-x-offset: 0; --shadow-x-offset: 0;
--shadow-y-offset: 0.125rem; --shadow-y-offset: 0.125rem;
@ -207,6 +209,11 @@ video {
appearance: none; appearance: none;
} }
.button:disabled {
opacity: 50%;
cursor: not-allowed;
}
.button.small { .button.small {
--h: 28px; --h: 28px;
} }
@ -243,6 +250,24 @@ video {
background: var(--color-super-raised); background: var(--color-super-raised);
} }
.button.green:not(.dark *) {
background: var(--color-green);
color: white !important;
&:hover {
background: var(--color-green-lowered) !important;
}
}
.button.red:not(.dark *) {
background: var(--color-red);
color: white !important;
&:hover {
background: var(--color-red-lowered) !important;
}
}
/* dropdown */ /* dropdown */
.dropdown { .dropdown {
position: relative; position: relative;
@ -272,9 +297,8 @@ video {
} }
.dropdown .inner .title { .dropdown .inner .title {
font-weight: 500; font-weight: 600;
border-top: solid 1px var(--color-super-lowered); font-size: 14px;
margin-top: var(--pad-2);
} }
.dropdown:has(.inner.open) .button:nth-child(1):not(.inner *) { .dropdown:has(.inner.open) .button:nth-child(1):not(.inner *) {
@ -710,6 +734,23 @@ span {
} }
} }
.loader {
animation: spin linear infinite 2s forwards running;
display: flex;
justify-content: center;
align-items: center;
}
@keyframes spin {
from {
transform: rotateZ(0deg);
}
to {
transform: rotateZ(360deg);
}
}
.items-end { .items-end {
align-items: flex-end; align-items: flex-end;
} }
@ -753,3 +794,32 @@ details .content {
padding: var(--pad-4); padding: var(--pad-4);
background: var(--color-surface); background: var(--color-surface);
} }
/* dialog */
dialog {
background: var(--color-surface);
color: var(--color-text);
box-shadow: var(--shadow-x-offset) var(--shadow-y-offset) var(--shadow-size)
var(--color-shadow);
animation: fadein ease-in-out 1 0.25s forwards running;
max-width: 95%;
width: 30rem;
margin: auto;
padding: var(--pad-4);
border: 0;
}
dialog.inner {
display: flex;
flex-direction: column;
gap: var(--pad-2);
}
dialog::backdrop {
background: hsla(0, 0%, 0%, 25%);
backdrop-filter: blur(2px);
}
dialog:is(.dark *)::backdrop {
background: hsla(0, 0%, 100%, 15%);
}

View file

@ -27,17 +27,19 @@
("title" "Info") ("title" "Info")
(text "i")))) (text "i"))))
(div (div
("class" "card tab tabs container") ("class" "flex justify_center tab")
("id" "tabs_group")
(div (div
("id" "editor_tab") ("class" "card tab tabs container w_full")
("class" "tab fadein")) ("id" "tabs_group")
(div (div
("id" "preview_tab") ("id" "editor_tab")
("class" "tab fadein hidden")) ("class" "tab fadein w_full"))
(div (div
("id" "metadata_tab") ("id" "preview_tab")
("class" "tab fadein hidden"))) ("class" "tab fadein hidden w_full"))
(div
("id" "metadata_tab")
("class" "tab fadein hidden w_full"))))
(form (form
("class" "w_full flex flex_col gap_2") ("class" "w_full flex flex_col gap_2")
("style" "margin-top: var(--pad-2)") ("style" "margin-top: var(--pad-2)")
@ -80,7 +82,8 @@
("class" "flex gap_2") ("class" "flex gap_2")
(button (button
("class" "button green") ("class" "button green")
(text "Save")) (span ("ui_ident" "text") (text "Save"))
(span ("class" "hidden loader no_fill") ("ui_ident" "loader") (text "{{ icon \"loader-circle\" }}")))
(a (a
("href" "/{{ entry.slug }}") ("href" "/{{ entry.slug }}")
("class" "button") ("class" "button")
@ -88,12 +91,37 @@
(button (button
("class" "button red") ("class" "button red")
("ui_ident" "delete") ("type" "button")
(text "Delete")))) ("onclick" "document.getElementById('delete_modal').showModal()")
("id" "fake_delete_button")
(span ("ui_ident" "text") (text "Delete"))
(span ("class" "hidden loader no_fill") ("ui_ident" "loader") (text "{{ icon \"loader-circle\" }}")))
(dialog
("id" "delete_modal")
(div
("class" "inner")
(h2 ("class" "text_center w_full") (text "Delete {{ entry.slug }}?"))
(p (text "Deleting this entry will make its custom slug claimable by anyone."))
(p (text "Please ensure that you understand the consequences of deleting this entry before continuing."))
(hr ("class" "margin"))
(div
("class" "w_full flex gap_2 justify_between")
(button
("class" "button")
("type" "button")
("onclick" "document.getElementById('delete_modal').close()")
(text "Cancel"))
(button
("class" "button red")
("ui_ident" "delete")
("onclick" "document.getElementById('delete_modal').close()")
(text "Delete")))))))
; editor ; editor
(script ("src" "https://unpkg.com/codemirror@5.39.2/lib/codemirror.js")) (script ("src" "https://unpkg.com/codemirror@5.39.2/lib/codemirror.js"))
(script ("src" "https://unpkg.com/codemirror@5.39.2/mode/markdown/markdown.js")) (script ("src" "https://unpkg.com/codemirror@5.39.2/mode/markdown/markdown.js"))
(script ("src" "https://unpkg.com/codemirror@5.39.2/mode/toml/toml.js"))
(script ("src" "https://unpkg.com/codemirror@5.39.2/addon/display/placeholder.js")) (script ("src" "https://unpkg.com/codemirror@5.39.2/addon/display/placeholder.js"))
(link ("rel" "stylesheet") ("href" "https://unpkg.com/codemirror@5.39.2/lib/codemirror.css")) (link ("rel" "stylesheet") ("href" "https://unpkg.com/codemirror@5.39.2/lib/codemirror.css"))
(script ("src" "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js")) (script ("src" "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"))
@ -105,16 +133,15 @@
(script (script
(text "setTimeout(() => { (text "setTimeout(() => {
globalThis.init_editor(); globalThis.init_editor();
globalThis.init_editor(\"metadata_editor\", \"plain\", \"metadata_tab\", \"editor_metadata_content\"); globalThis.init_editor(\"metadata_editor\", \"toml\", \"metadata_tab\", \"editor_metadata_content\");
}, 150); }, 150);
globalThis.edit_entry = (e) => { globalThis.edit_entry = (e) => {
e.preventDefault(); e.preventDefault();
const rm = e.submitter.getAttribute(\"ui_ident\") === \"delete\"; const rm = e.submitter.getAttribute(\"ui_ident\") === \"delete\";
if (rm && !confirm(\"Are you sure you want to do this?\")) { const { load, failed } = submitter_load(rm ? document.getElementById(\"fake_delete_button\") : e.submitter);
return; load();
}
fetch(\"/api/v1/entries/{{ entry.slug }}\", { fetch(\"/api/v1/entries/{{ entry.slug }}\", {
method: \"POST\", method: \"POST\",
@ -147,7 +174,36 @@
} }
} else { } else {
show_message(res.message, false); show_message(res.message, false);
failed();
} }
}) })
}
globalThis.download = (content, type, name) => {
const blob = new Blob([content], { type });
const url = URL.createObjectURL(blob);
const anchor = document.createElement(\"a\");
anchor.setAttribute(\"download\", name);
anchor.href = url;
anchor.click();
anchor.remove();
}")) }"))
(text "{% endblock %}") (text "{% endblock %}")
(text "{% block dropdown %}")
(hr)
(span ("class" "title") (text "export"))
(button
("class" "button")
("onclick" "download(globalThis.editor.getValue(), 'text/markdown', '{{ entry.slug }}.md')")
(text "markdown"))
(button
("class" "button")
("onclick" "download(globalThis.metadata_editor.getValue(), 'application/toml', '{{ entry.slug }}.toml')")
(text "metadata"))
(button
("class" "button")
("onclick" "(async () => { download(await get_preview(), 'text/html', '{{ entry.slug }}.html') })();")
(text "html"))
(text "{%- endblock %}")

View file

@ -30,24 +30,27 @@
("title" "Info") ("title" "Info")
(text "i")))) (text "i"))))
(div (div
("class" "card tab tabs container") ("class" "flex justify_center tab")
("id" "tabs_group")
(div (div
("id" "editor_tab") ("class" "card tab tabs container w_full")
("class" "tab fadein")) ("id" "tabs_group")
(div (div
("id" "preview_tab") ("id" "editor_tab")
("class" "tab fadein hidden")) ("class" "tab fadein w_full"))
(div (div
("id" "metadata_tab") ("id" "preview_tab")
("class" "tab fadein hidden"))) ("class" "tab fadein hidden w_full"))
(div
("id" "metadata_tab")
("class" "tab fadein hidden w_full"))))
(form (form
("class" "w_full flex justify_between gap_2 flex_collapse_rev") ("class" "w_full flex justify_between gap_2 flex_collapse_rev")
("style" "margin-top: var(--pad-2)") ("style" "margin-top: var(--pad-2)")
("onsubmit" "create_entry(event)") ("onsubmit" "create_entry(event)")
(button (button
("class" "button") ("class" "button")
(text "Go")) (span ("ui_ident" "text") (text "Go"))
(span ("class" "hidden loader no_fill") ("ui_ident" "loader") (text "{{ icon \"loader-circle\" }}")))
(div (div
("class" "flex gap_2") ("class" "flex gap_2")
(input (input
@ -68,6 +71,7 @@
; editor ; editor
(script ("src" "https://unpkg.com/codemirror@5.39.2/lib/codemirror.js")) (script ("src" "https://unpkg.com/codemirror@5.39.2/lib/codemirror.js"))
(script ("src" "https://unpkg.com/codemirror@5.39.2/mode/markdown/markdown.js")) (script ("src" "https://unpkg.com/codemirror@5.39.2/mode/markdown/markdown.js"))
(script ("src" "https://unpkg.com/codemirror@5.39.2/mode/toml/toml.js"))
(script ("src" "https://unpkg.com/codemirror@5.39.2/addon/display/placeholder.js")) (script ("src" "https://unpkg.com/codemirror@5.39.2/addon/display/placeholder.js"))
(link ("rel" "stylesheet") ("href" "https://unpkg.com/codemirror@5.39.2/lib/codemirror.css")) (link ("rel" "stylesheet") ("href" "https://unpkg.com/codemirror@5.39.2/lib/codemirror.css"))
(script ("src" "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js")) (script ("src" "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"))
@ -76,11 +80,15 @@
(script (script
(text "setTimeout(() => { (text "setTimeout(() => {
globalThis.init_editor(); globalThis.init_editor();
globalThis.init_editor(\"metadata_editor\", \"plain\", \"metadata_tab\"); globalThis.init_editor(\"metadata_editor\", \"toml\", \"metadata_tab\");
}, 150); }, 150);
globalThis.create_entry = (e) => { globalThis.create_entry = (e) => {
e.preventDefault(); e.preventDefault();
const { load, failed } = submitter_load(e.submitter);
load();
fetch(\"/api/v1/entries\", { fetch(\"/api/v1/entries\", {
method: \"POST\", method: \"POST\",
headers: { headers: {
@ -102,6 +110,7 @@
window.location.href = `/${res.payload[0]}`; window.location.href = `/${res.payload[0]}`;
} else { } else {
show_message(res.message, false); show_message(res.message, false);
failed();
} }
}) })
}")) }"))

View file

@ -20,14 +20,21 @@ pub fn render_markdown(input: &str) -> String {
allowed_attributes.insert("align"); allowed_attributes.insert("align");
allowed_attributes.insert("src"); allowed_attributes.insert("src");
allowed_attributes.insert("style"); allowed_attributes.insert("style");
allowed_attributes.insert("controls");
allowed_attributes.insert("autoplay");
allowed_attributes.insert("loop");
tetratto_shared::markdown::clean_html( tetratto_shared::markdown::clean_html(
html.replace("<style>", "<span>:temp_style") html.replace("<style>", "<span>:temp_style")
.replace("</style>", "</span>:temp_style"), .replace("</style>", "</span>:temp_style")
.replace("<audio", ":temp_audio<span")
.replace("</audio>", "</span>:temp_audio"),
allowed_attributes, allowed_attributes,
) )
.replace("<span>:temp_style", "<style>") .replace("<span>:temp_style", "<style>")
.replace("</span>:temp_style", "</style>") .replace("</span>:temp_style", "</style>")
.replace(":temp_audio<span", "<audio")
.replace("</span>:temp_audio", "</audio>")
} }
pub(crate) fn is_numeric(value: &str) -> bool { pub(crate) fn is_numeric(value: &str) -> bool {
@ -138,6 +145,12 @@ fn parse_highlight_line(output: &mut String, buffer: &mut String, line: &str) {
close_1 = false; close_1 = false;
} }
if open_1 && char != '=' {
buffer.push('=');
open_1 = false;
is_open = false;
}
match char { match char {
'=' => { '=' => {
if !is_open { if !is_open {
@ -195,7 +208,13 @@ fn parse_underline_line(output: &mut String, buffer: &mut String, line: &str) {
if open_1 && char != '~' { if open_1 && char != '~' {
is_open = false; is_open = false;
open_1 = false; open_1 = false;
buffer.push('!');
if char == '[' {
// image
buffer.push('!');
} else {
buffer.push_str("&excl;");
}
} }
if close_1 && char != '!' { if close_1 && char != '!' {
@ -897,6 +916,7 @@ pub fn get_toc_list(input: &str) -> (String, String) {
let mut output = String::new(); let mut output = String::new();
let mut toc = String::new(); let mut toc = String::new();
let mut in_pre = false; let mut in_pre = false;
let mut hc_offset: Option<usize> = None;
for line in input.split("\n") { for line in input.split("\n") {
if line.starts_with("```") || line.starts_with("<style>") || line.starts_with("</style>") { if line.starts_with("```") || line.starts_with("<style>") || line.starts_with("</style>") {
@ -914,6 +934,7 @@ pub fn get_toc_list(input: &str) -> (String, String) {
if line.starts_with("#") { if line.starts_with("#") {
// get heading count // get heading count
let mut hc = 0; let mut hc = 0;
let real_hc;
for x in line.chars() { for x in line.chars() {
if x != '#' { if x != '#' {
@ -923,8 +944,21 @@ pub fn get_toc_list(input: &str) -> (String, String) {
hc += 1; hc += 1;
} }
real_hc = hc.clone();
if hc_offset.is_none() {
if hc > 1 {
// offset this count to 1 so the list renders properly
hc_offset = Some(hc - 1);
hc = 1;
} else {
hc_offset = Some(0);
}
} else if let Some(offset) = hc_offset {
hc -= offset;
}
// add heading with id // add heading with id
let x = line.replacen(&"#".repeat(hc), "", 1); let x = line.replacen(&"#".repeat(real_hc), "", 1);
let htext = x.trim(); let htext = x.trim();
let id = underscore_chars( let id = underscore_chars(
@ -933,7 +967,7 @@ pub fn get_toc_list(input: &str) -> (String, String) {
); );
output.push_str(&format!( output.push_str(&format!(
"<h{hc} id=\"{id}\">{}</h{hc}>\n", "<h{real_hc} id=\"{id}\">{}</h{real_hc}>\n\n",
render_markdown(&htext) render_markdown(&htext)
)); ));

View file

@ -122,12 +122,24 @@ pub struct EntryMetadata {
#[serde(default, alias = "CONTAINER_PADDING")] #[serde(default, alias = "CONTAINER_PADDING")]
#[validate(max_length = 32)] #[validate(max_length = 32)]
pub container_padding: String, pub container_padding: String,
/// The padding of the container on mobile devices.
///
/// Syntax: <https://developer.mozilla.org/en-US/docs/Web/CSS/padding>
#[serde(default, alias = "CONTAINER_MOBILE_PADDING")]
#[validate(max_length = 32)]
pub container_mobile_padding: String,
/// The maximum width of the container. /// The maximum width of the container.
/// ///
/// Syntax: <https://developer.mozilla.org/en-US/docs/Web/CSS/max-width> /// Syntax: <https://developer.mozilla.org/en-US/docs/Web/CSS/max-width>
#[serde(default, alias = "CONTAINER_MAX_WIDTH")] #[serde(default, alias = "CONTAINER_MAX_WIDTH")]
#[validate(max_length = 16)] #[validate(max_length = 16)]
pub container_max_width: String, pub container_max_width: String,
/// The maximum width of the container on mobile devices.
///
/// Syntax: <https://developer.mozilla.org/en-US/docs/Web/CSS/max-width>
#[serde(default, alias = "CONTAINER_MOBILE_MAX_WIDTH")]
#[validate(max_length = 16)]
pub container_mobile_max_width: String,
/// The padding of the container. /// The padding of the container.
/// The color of the text in the inner container. /// The color of the text in the inner container.
#[serde(default, alias = "CONTAINER_INNER_FOREGROUND_COLOR")] #[serde(default, alias = "CONTAINER_INNER_FOREGROUND_COLOR")]
@ -257,6 +269,12 @@ pub struct EntryMetadata {
#[serde(default, alias = "CONTAINER_BORDER_WIDTH")] #[serde(default, alias = "CONTAINER_BORDER_WIDTH")]
#[validate(max_length = 16)] #[validate(max_length = 16)]
pub container_border_width: String, pub container_border_width: String,
/// The border around the container on mobile devices.
///
/// Syntax: <https://developer.mozilla.org/en-US/docs/Web/CSS/border-width>
#[serde(default, alias = "CONTAINER_MOBILE_BORDER_WIDTH")]
#[validate(max_length = 16)]
pub container_mobile_border_width: String,
/// The border around the container. /// The border around the container.
/// ///
/// Syntax: <https://developer.mozilla.org/en-US/docs/Web/CSS/border-style> /// Syntax: <https://developer.mozilla.org/en-US/docs/Web/CSS/border-style>
@ -269,6 +287,12 @@ pub struct EntryMetadata {
#[serde(default, alias = "CONTAINER_BORDER_RADIUS")] #[serde(default, alias = "CONTAINER_BORDER_RADIUS")]
#[validate(max_length = 16)] #[validate(max_length = 16)]
pub container_border_radius: String, pub container_border_radius: String,
/// The border around the container on mobile devices.
///
/// Syntax: <https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius>
#[serde(default, alias = "CONTAINER_MOBILE_BORDER_RADIUS")]
#[validate(max_length = 16)]
pub container_mobile_border_radius: String,
/// The shadow around the container. /// The shadow around the container.
/// ///
/// Syntax: <https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow> /// Syntax: <https://developer.mozilla.org/en-US/docs/Web/CSS/box-shadow>
@ -392,6 +416,18 @@ macro_rules! metadata_css {
} }
}; };
($selector:expr, $property:literal, $self:ident.$field:ident->$output:ident, $media_query:literal) => {
if !$self.$field.is_empty() {
$output.push_str(&format!(
"@media screen and ({}) {{ {} {{ {}: {}; }} }}\n",
$media_query,
$selector,
$property,
EntryMetadata::css_escape(&$self.$field)
));
}
};
($selector:expr, $property:literal, $field:ident->$output:ident) => { ($selector:expr, $property:literal, $field:ident->$output:ident) => {
if !$field.is_empty() { if !$field.is_empty() {
$output.push_str(&format!( $output.push_str(&format!(
@ -526,7 +562,9 @@ impl EntryMetadata {
let mut output = "<style>".to_string(); let mut output = "<style>".to_string();
metadata_css!(".container", "padding", self.container_padding->output); metadata_css!(".container", "padding", self.container_padding->output);
metadata_css!(".container", "padding", self.container_mobile_padding->output, "max-width: 900px");
metadata_css!(".container", "max-width", self.container_max_width->output); metadata_css!(".container", "max-width", self.container_max_width->output);
metadata_css!(".container", "max-width", self.container_mobile_max_width->output, "max-width: 900px");
metadata_css!(".container", "color", self.container_inner_foreground_color->output); metadata_css!(".container", "color", self.container_inner_foreground_color->output);
metadata_css!(".container", "background", self.container_inner_background->output); metadata_css!(".container", "background", self.container_inner_background->output);
metadata_css!(".container", "background-color", self.container_inner_background_color->output); metadata_css!(".container", "background-color", self.container_inner_background_color->output);
@ -550,7 +588,9 @@ impl EntryMetadata {
metadata_css!(".container", "border-color", self.container_border_color->output); metadata_css!(".container", "border-color", self.container_border_color->output);
metadata_css!(".container", "border-style", self.container_border_style->output); metadata_css!(".container", "border-style", self.container_border_style->output);
metadata_css!(".container", "border-width", self.container_border_width->output); metadata_css!(".container", "border-width", self.container_border_width->output);
metadata_css!(".container", "border-width", self.container_mobile_border_width->output, "max-width: 900px");
metadata_css!(".container", "border-radius", self.container_border_radius->output); metadata_css!(".container", "border-radius", self.container_border_radius->output);
metadata_css!(".container", "border-radius", self.container_mobile_border_radius->output, "max-width: 900px");
metadata_css!(".container", "box-shadow", self.container_shadow->output); metadata_css!(".container", "box-shadow", self.container_shadow->output);
metadata_css!(".container", "text-shadow", self.content_text_shadow->output); metadata_css!(".container", "text-shadow", self.content_text_shadow->output);
metadata_css!("*, html *", "--color-link" !important, self.content_link_color->output); metadata_css!("*, html *", "--color-link" !important, self.content_link_color->output);
@ -641,7 +681,7 @@ impl EntryMetadata {
} }
if self.content_disable_paragraph_margin { if self.content_disable_paragraph_margin {
output.push_str(".container p { margin: 0; }"); output.push_str(".container p { margin: 0 !important; }");
} }
output + "</style>" output + "</style>"

View file

@ -146,6 +146,10 @@ async fn view_request(
}; };
slug = slug.to_lowercase(); slug = slug.to_lowercase();
let viewed_header = (
"Set-Cookie".to_string(),
format!("Atto-Viewed=true; Path=/{slug}; Max-Age=86400"),
);
let entry = match data let entry = match data
.query(&SimplifiedQuery { .query(&SimplifiedQuery {
@ -165,7 +169,10 @@ async fn view_request(
&Error::GeneralNotFound("entry".to_string()).to_string(), &Error::GeneralNotFound("entry".to_string()).to_string(),
); );
return Html(tera.render("error.lisp", &ctx).unwrap()); return (
[viewed_header],
Html(tera.render("error.lisp", &ctx).unwrap()),
);
} }
}; };
@ -179,7 +186,10 @@ async fn view_request(
if let Err(e) = metadata.validate() { if let Err(e) = metadata.validate() {
let mut ctx = default_context(&data, &build_code); let mut ctx = default_context(&data, &build_code);
ctx.insert("error", &e.to_string()); ctx.insert("error", &e.to_string());
return Html(tera.render("error.lisp", &ctx).unwrap()); return (
[viewed_header],
Html(tera.render("error.lisp", &ctx).unwrap()),
);
} }
// ... // ...
@ -188,7 +198,10 @@ async fn view_request(
{ {
let mut ctx = default_context(&data, &build_code); let mut ctx = default_context(&data, &build_code);
ctx.insert("entry", &entry); ctx.insert("entry", &entry);
return Html(tera.render("password.lisp", &ctx).unwrap()); return (
[viewed_header],
Html(tera.render("password.lisp", &ctx).unwrap()),
);
} }
// pull views // pull views
@ -205,11 +218,18 @@ async fn view_request(
// count view // count view
let views = r.value.parse::<usize>().unwrap(); let views = r.value.parse::<usize>().unwrap();
if let Err(e) = data.update(r.id, (views + 1).to_string()).await { if jar.get("Atto-Viewed").is_none() {
let mut ctx = default_context(&data, &build_code); // the Atto-Viewed cookie tells us if we've already viewed this
ctx.insert("error", &e.to_string()); // entry recently (at all in the past week)
if let Err(e) = data.update(r.id, (views + 1).to_string()).await {
let mut ctx = default_context(&data, &build_code);
ctx.insert("error", &e.to_string());
return Html(tera.render("error.lisp", &ctx).unwrap()); return (
[viewed_header],
Html(tera.render("error.lisp", &ctx).unwrap()),
);
}
} }
views views
@ -220,7 +240,10 @@ async fn view_request(
let mut ctx = default_context(&data, &build_code); let mut ctx = default_context(&data, &build_code);
ctx.insert("error", &e.to_string()); ctx.insert("error", &e.to_string());
return Html(tera.render("error.lisp", &ctx).unwrap()); return (
[viewed_header],
Html(tera.render("error.lisp", &ctx).unwrap()),
);
} }
} }
} else { } else {
@ -240,10 +263,16 @@ async fn view_request(
if metadata.safety_content_warning.is_empty() | qflags.contains(&QuickFlag::AcceptWarning) { if metadata.safety_content_warning.is_empty() | qflags.contains(&QuickFlag::AcceptWarning) {
// regular view // regular view
Html(tera.render("view.lisp", &ctx).unwrap()) (
[viewed_header],
Html(tera.render("view.lisp", &ctx).unwrap()),
)
} else { } else {
// warning // warning
Html(tera.render("warning.lisp", &ctx).unwrap()) (
[viewed_header],
Html(tera.render("warning.lisp", &ctx).unwrap()),
)
} }
} }