add: better layout
This commit is contained in:
parent
2cc9ed7445
commit
dbd70d9592
19 changed files with 451 additions and 87 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -2551,9 +2551,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tetratto-core"
|
name = "tetratto-core"
|
||||||
version = "12.0.1"
|
version = "12.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2c1b01499f7471ee6f05299c06ebb18760440452714c6f9a6c0c9e0cf9a663bd"
|
checksum = "a367ac3ced8ff302080e1b4a82a67acd24fa606245c4381a6f77dbaaf6ef4b58"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
"base16ct",
|
"base16ct",
|
||||||
|
@ -2570,6 +2570,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tetratto-l10n",
|
"tetratto-l10n",
|
||||||
"tetratto-shared",
|
"tetratto-shared",
|
||||||
|
"tokio",
|
||||||
"toml 0.9.2",
|
"toml 0.9.2",
|
||||||
"totp-rs",
|
"totp-rs",
|
||||||
]
|
]
|
||||||
|
@ -2587,9 +2588,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tetratto-shared"
|
name = "tetratto-shared"
|
||||||
version = "12.0.5"
|
version = "12.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "11c2ba2be9c92a4ac566b9c3b615d7311b3d0e98b175ad84e81b44644b34dd8f"
|
checksum = "286290ad09be3c507f9a47d38e92b024e6fcde34dbb515113f5bdb6b926cbee3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ammonia",
|
"ammonia",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|
|
@ -8,8 +8,8 @@ license = "AGPL-3.0-or-later"
|
||||||
homepage = "https://tetratto.com"
|
homepage = "https://tetratto.com"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tetratto-core = "12.0.1"
|
tetratto-core = "12.0.2"
|
||||||
tetratto-shared = "12.0.5"
|
tetratto-shared = "12.0.6"
|
||||||
tokio = { version = "1.46.1", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.46.1", features = ["macros", "rt-multi-thread"] }
|
||||||
pathbufd = "0.1.4"
|
pathbufd = "0.1.4"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
|
|
3
app/.gitignore
vendored
Normal file
3
app/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
public/docs/**/*
|
||||||
|
!public/docs/metadata.md
|
||||||
|
icons/
|
11
app/docs/metadata.md
Normal file
11
app/docs/metadata.md
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# What is metadata?
|
||||||
|
|
||||||
|
Each entry also includes a separate metadata content. Metadata is used to further customize the look and display of your entries to a finer degree than you can through just Markdown.
|
||||||
|
|
||||||
|
Metadata includes options to customize how your entries look when shared to other platforms, as well as to customize how your entries render when viewed.
|
||||||
|
|
||||||
|
All option names can either be in all lowercase, or all uppercase. While option values for `String` type metadata options _should_ be encased in double quotes, that will be done for you if you leave them out. This is included just for compatibility.
|
||||||
|
|
||||||
|
Metadata options go in the "Metadata" tab in the entry editor page. Each option should be on a new line, and should be formatted as `NAME = value`. If you're familiar with TOML, you should be comfortable with metadata formatting.
|
||||||
|
|
||||||
|
You can view a list of all options and what they do [here](http://localhost:9119/public/reference/attobin/model/struct.EntryMetadata.html#fields).
|
|
@ -224,3 +224,109 @@ globalThis.check_exists_input = (e) => {
|
||||||
e.target.reportValidity();
|
e.target.reportValidity();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// components
|
||||||
|
function close_dropdowns() {
|
||||||
|
for (const dropdown of Array.from(
|
||||||
|
document.querySelectorAll(".inner.open"),
|
||||||
|
)) {
|
||||||
|
dropdown.classList.remove("open");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
globalThis.open_dropdown = (event) => {
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
let target = event.target;
|
||||||
|
|
||||||
|
while (!target.matches(".dropdown")) {
|
||||||
|
target = target.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
// close all others
|
||||||
|
close_dropdowns();
|
||||||
|
|
||||||
|
// open
|
||||||
|
setTimeout(() => {
|
||||||
|
for (const dropdown of Array.from(target.querySelectorAll(".inner"))) {
|
||||||
|
// check y
|
||||||
|
const box = target.getBoundingClientRect();
|
||||||
|
|
||||||
|
let parent = dropdown.parentElement;
|
||||||
|
|
||||||
|
while (!parent.matches("html, .window")) {
|
||||||
|
parent = parent.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parent_height = parent.getBoundingClientRect().y;
|
||||||
|
|
||||||
|
if (parent.nodeName === "HTML") {
|
||||||
|
parent_height = window.screen.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scroll = window.scrollY;
|
||||||
|
const height = parent_height;
|
||||||
|
const y = box.y + scroll;
|
||||||
|
|
||||||
|
if (y > height - scroll - 375) {
|
||||||
|
dropdown.classList.add("top");
|
||||||
|
} else {
|
||||||
|
dropdown.classList.remove("top");
|
||||||
|
}
|
||||||
|
|
||||||
|
// open
|
||||||
|
dropdown.classList.add("open");
|
||||||
|
|
||||||
|
if (dropdown.classList.contains("open")) {
|
||||||
|
dropdown.removeAttribute("aria-hidden");
|
||||||
|
} else {
|
||||||
|
dropdown.setAttribute("aria-hidden", "true");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 5);
|
||||||
|
};
|
||||||
|
|
||||||
|
globalThis.init_dropdowns = (bind_to) => {
|
||||||
|
for (const dropdown of Array.from(document.querySelectorAll(".inner"))) {
|
||||||
|
dropdown.setAttribute("aria-hidden", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
bind_to.addEventListener("click", (event) => {
|
||||||
|
if (
|
||||||
|
event.target.matches(".dropdown") ||
|
||||||
|
event.target.matches("[exclude=dropdown]")
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const dropdown of Array.from(
|
||||||
|
document.querySelectorAll(".inner.open"),
|
||||||
|
)) {
|
||||||
|
dropdown.classList.remove("open");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
globalThis.METADATA_CSS_ENABLED = true;
|
||||||
|
globalThis.toggle_metadata_css = (e) => {
|
||||||
|
e.target.classList.add("yellow");
|
||||||
|
|
||||||
|
METADATA_CSS_ENABLED = !METADATA_CSS_ENABLED;
|
||||||
|
if (!METADATA_CSS_ENABLED) {
|
||||||
|
media_theme_pref(); // user user theme
|
||||||
|
document.getElementById("metadata_css").remove(); // remove css
|
||||||
|
|
||||||
|
// reset colored text
|
||||||
|
for (const element of Array.from(
|
||||||
|
document.querySelectorAll(".color_block"),
|
||||||
|
)) {
|
||||||
|
element.removeAttribute("style");
|
||||||
|
element.classList.remove("color_block");
|
||||||
|
}
|
||||||
|
|
||||||
|
// strikethrough auto theme since it's disabled
|
||||||
|
document.getElementById("auto_theme").style.textDecoration =
|
||||||
|
"line-through";
|
||||||
|
} else {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
1
app/public/reference
Symbolic link
1
app/public/reference
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../target/doc
|
|
@ -12,7 +12,7 @@
|
||||||
--color-shadow: rgba(0, 0, 0, 0.08);
|
--color-shadow: rgba(0, 0, 0, 0.08);
|
||||||
--color-red: hsl(0, 84%, 40%);
|
--color-red: hsl(0, 84%, 40%);
|
||||||
--color-green: hsl(100, 84%, 20%);
|
--color-green: hsl(100, 84%, 20%);
|
||||||
--color-yellow: hsl(41, 63%, 75%);
|
--color-yellow: oklch(47% 0.157 37.304);
|
||||||
--color-purple: hsl(284, 84%, 20%);
|
--color-purple: hsl(284, 84%, 20%);
|
||||||
--color-primary: oklch(67.3% 0.182 276.935);
|
--color-primary: oklch(67.3% 0.182 276.935);
|
||||||
|
|
||||||
|
@ -24,6 +24,9 @@
|
||||||
--pad-2: 0.35rem;
|
--pad-2: 0.35rem;
|
||||||
--pad-3: 0.5rem;
|
--pad-3: 0.5rem;
|
||||||
--pad-4: 1rem;
|
--pad-4: 1rem;
|
||||||
|
|
||||||
|
--radius: 0.2rem;
|
||||||
|
--nav-height: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
@ -45,7 +48,7 @@
|
||||||
--color-link: #93c5fd;
|
--color-link: #93c5fd;
|
||||||
--color-red: hsl(0, 94%, 82%);
|
--color-red: hsl(0, 94%, 82%);
|
||||||
--color-green: hsl(100, 94%, 82%);
|
--color-green: hsl(100, 94%, 82%);
|
||||||
--color-yellow: hsl(41, 63%, 65%);
|
--color-yellow: oklch(90.1% 0.076 70.697);
|
||||||
--color-purple: hsl(284, 94%, 82%);
|
--color-purple: hsl(284, 94%, 82%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +76,7 @@ main {
|
||||||
|
|
||||||
article {
|
article {
|
||||||
margin: var(--pad-2) 0;
|
margin: var(--pad-2) 0;
|
||||||
height: calc(100dvh - var(--pad-4) * 2);
|
height: calc(100dvh - var(--pad-4) - var(--nav-height) * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab {
|
.tab {
|
||||||
|
@ -89,6 +92,14 @@ article {
|
||||||
animation: fadein ease-in-out 1 0.5s forwards running;
|
animation: fadein ease-in-out 1 0.5s forwards running;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
/* background: var(--color-raised); */
|
||||||
|
height: var(--nav-height);
|
||||||
|
/* position: sticky;
|
||||||
|
z-index: 2;
|
||||||
|
top: 2; */
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 900px) {
|
@media screen and (max-width: 900px) {
|
||||||
main,
|
main,
|
||||||
article,
|
article,
|
||||||
|
@ -117,7 +128,7 @@ article {
|
||||||
}
|
}
|
||||||
|
|
||||||
.content_container {
|
.content_container {
|
||||||
margin: var(--pad-2) auto;
|
margin: 0 auto var(--pad-2);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,7 +178,7 @@ video {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--gap-2);
|
gap: var(--pad-2);
|
||||||
padding: var(--pad-2) calc(var(--pad-3) * 1.5);
|
padding: var(--pad-2) calc(var(--pad-3) * 1.5);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: var(--color-raised);
|
background: var(--color-raised);
|
||||||
|
@ -187,7 +198,7 @@ video {
|
||||||
--h: 28px;
|
--h: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:hover {
|
.button:not(:has(.button:hover)):not(.camo):hover {
|
||||||
background: var(--color-super-raised);
|
background: var(--color-super-raised);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,10 +207,70 @@ video {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bar .button.camo:hover {
|
.bar .button:not(.simple).camo:hover {
|
||||||
color: var(--color-link);
|
color: var(--color-link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button.simple {
|
||||||
|
--size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: var(--pad-2) !important;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.surface {
|
||||||
|
background: var(--color-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.surface.simple:is(.camo *) {
|
||||||
|
background: var(--color-super-raised);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* dropdown */
|
||||||
|
.dropdown {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown .inner {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
box-shadow: var(--shadow-x-offset) var(--shadow-y-offset) var(--shadow-size)
|
||||||
|
var(--color-shadow);
|
||||||
|
background: var(--color-raised);
|
||||||
|
color: inherit;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
top: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown .inner.open {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown .inner .button {
|
||||||
|
padding: var(--pad-3) var(--pad-4);
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown:has(.inner.open) .button:nth-child(1):not(.inner *) {
|
||||||
|
background: var(--color-raised);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown .inner.top {
|
||||||
|
top: unset;
|
||||||
|
bottom: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown .inner.left {
|
||||||
|
left: 0;
|
||||||
|
right: unset;
|
||||||
|
}
|
||||||
|
|
||||||
/* input */
|
/* input */
|
||||||
input {
|
input {
|
||||||
--h: 36px;
|
--h: 36px;
|
||||||
|
@ -276,6 +347,7 @@ pre {
|
||||||
padding: var(--pad-2) var(--pad-4);
|
padding: var(--pad-2) var(--pad-4);
|
||||||
border-left: solid 5px var(--color-primary);
|
border-left: solid 5px var(--color-primary);
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
|
border-radius: var(--radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
|
@ -297,8 +369,22 @@ code * {
|
||||||
font-size: 0.8rem !important;
|
font-size: 0.8rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code:not(pre *) {
|
||||||
|
padding: var(--pad-1) var(--pad-2);
|
||||||
|
background: oklch(98% 0.016 73.684 / 25%);
|
||||||
|
color: oklch(90.1% 0.076 70.697);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
white-space: break-spaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
code:not(pre *):not(.dark *) {
|
||||||
|
background: oklch(83.7% 0.128 66.29 / 25%);
|
||||||
|
color: oklch(47% 0.157 37.304);
|
||||||
|
}
|
||||||
|
|
||||||
svg.icon {
|
svg.icon {
|
||||||
stroke: currentColor;
|
stroke: currentColor;
|
||||||
|
fill: currentColor;
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
}
|
}
|
||||||
|
@ -307,6 +393,10 @@ svg.icon.filled {
|
||||||
fill: currentColor;
|
fill: currentColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no_fill svg.icon {
|
||||||
|
fill: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
button svg {
|
button svg {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
@ -384,6 +474,10 @@ a {
|
||||||
color: var(--color-link);
|
color: var(--color-link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.color_block a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
a.flush {
|
a.flush {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
@ -477,12 +571,12 @@ blockquote {
|
||||||
|
|
||||||
.cm-comment,
|
.cm-comment,
|
||||||
.hljs-keyword {
|
.hljs-keyword {
|
||||||
color: rgb(153 27 27) !important;
|
color: oklch(47% 0.157 37.304) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-comment:is(.dark *),
|
.cm-comment:is(.dark *),
|
||||||
.hljs-keyword:is(.dark *) {
|
.hljs-keyword:is(.dark *) {
|
||||||
color: rgb(254, 202, 202) !important;
|
color: oklch(90.1% 0.076 70.697) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-link {
|
.cm-link {
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
(text "{% macro footer() -%}")
|
|
||||||
(footer
|
|
||||||
("class" "flex flex-col items-center gap-2")
|
|
||||||
(hr ("style" "min-width: 20rem; margin-top: calc(var(--pad-4) * 4)"))
|
|
||||||
(div
|
|
||||||
("class" "w-full flex justify-between")
|
|
||||||
(div ("style" "width: 50px"))
|
|
||||||
(div
|
|
||||||
("class" "flex flex-col gap-2 items-center")
|
|
||||||
(div
|
|
||||||
("class" "flex gap-2 flex-wrap")
|
|
||||||
(a
|
|
||||||
("href" "/")
|
|
||||||
(text "new"))
|
|
||||||
|
|
||||||
(a
|
|
||||||
("href" "/{{ what_page_slug }}")
|
|
||||||
(text "what"))
|
|
||||||
|
|
||||||
(a
|
|
||||||
("href" "https://trisua.com/t/attobin")
|
|
||||||
(text "source")))
|
|
||||||
|
|
||||||
(span ("style" "font-size: 14px") ("class" "fade") (text "{{ name }}")))
|
|
||||||
|
|
||||||
; theme switches
|
|
||||||
(button
|
|
||||||
("class" "button camo fade")
|
|
||||||
("id" "switch_light")
|
|
||||||
("title" "Switch theme")
|
|
||||||
("onclick" "set_theme('Dark')")
|
|
||||||
(text "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-sun-icon lucide-sun icon\"><circle cx=\"12\" cy=\"12\" r=\"4\"/><path d=\"M12 2v2\"/><path d=\"M12 20v2\"/><path d=\"m4.93 4.93 1.41 1.41\"/><path d=\"m17.66 17.66 1.41 1.41\"/><path d=\"M2 12h2\"/><path d=\"M20 12h2\"/><path d=\"m6.34 17.66-1.41 1.41\"/><path d=\"m19.07 4.93-1.41 1.41\"/></svg>"))
|
|
||||||
|
|
||||||
(button
|
|
||||||
("class" "button camo fade hidden")
|
|
||||||
("id" "switch_dark")
|
|
||||||
("title" "Switch theme")
|
|
||||||
("onclick" "set_theme('Light')")
|
|
||||||
(text "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-moon-icon lucide-moon icon\"><path d=\"M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z\"/></svg>"))))
|
|
||||||
(text "{%- endmacro %}")
|
|
9
app/templates_src/doc.lisp
Normal file
9
app/templates_src/doc.lisp
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
(text "{% extends \"root.lisp\" %} {% block head %}")
|
||||||
|
(title
|
||||||
|
(text "{{ file_name }} - {{ name }}"))
|
||||||
|
(link ("rel" "icon") ("href" "/public/favicon.svg"))
|
||||||
|
(text "{% endblock %} {% block body %}")
|
||||||
|
(div
|
||||||
|
("class" "card container")
|
||||||
|
(p (text "{{ text|markdown|safe }}")))
|
||||||
|
(text "{% endblock %}")
|
|
@ -19,7 +19,13 @@
|
||||||
("class" "button camo tab_button")
|
("class" "button camo tab_button")
|
||||||
("id" "metadata_tab_button")
|
("id" "metadata_tab_button")
|
||||||
("onclick" "tab_metadata()")
|
("onclick" "tab_metadata()")
|
||||||
(text "Metadata")))
|
(text "Metadata")
|
||||||
|
(a
|
||||||
|
("class" "button simple surface")
|
||||||
|
("href" "/docs/metadata")
|
||||||
|
("target" "_blank")
|
||||||
|
("title" "Info")
|
||||||
|
(text "i"))))
|
||||||
(div
|
(div
|
||||||
("class" "card tab tabs container")
|
("class" "card tab tabs container")
|
||||||
("id" "tabs_group")
|
("id" "tabs_group")
|
||||||
|
@ -74,7 +80,6 @@
|
||||||
("class" "button red")
|
("class" "button red")
|
||||||
("ui_ident" "delete")
|
("ui_ident" "delete")
|
||||||
(text "Delete"))))
|
(text "Delete"))))
|
||||||
(text "{{ components::footer() }}")
|
|
||||||
|
|
||||||
; 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"))
|
||||||
|
|
|
@ -6,5 +6,4 @@
|
||||||
(div
|
(div
|
||||||
("class" "card")
|
("class" "card")
|
||||||
(p (text "{{ error }}")))
|
(p (text "{{ error }}")))
|
||||||
(text "{{ components::footer() }}")
|
|
||||||
(text "{% endblock %}")
|
(text "{% endblock %}")
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
(text "{% extends \"root.lisp\" %} {% block head %}")
|
(text "{% extends \"root.lisp\" %} {% block head %}")
|
||||||
(title
|
(title
|
||||||
(text "{{ name }}"))
|
(text "{{ name }}"))
|
||||||
|
|
||||||
|
(meta ("property" "og:title") ("content" "{{ name }}"))
|
||||||
|
(meta ("property" "twitter:title") ("content" "{{ name }}"))
|
||||||
(link ("rel" "icon") ("href" "/public/favicon.svg"))
|
(link ("rel" "icon") ("href" "/public/favicon.svg"))
|
||||||
(text "{% endblock %} {% block body %}")
|
(text "{% endblock %} {% block body %}")
|
||||||
(div
|
(div
|
||||||
|
@ -19,7 +22,13 @@
|
||||||
("class" "button camo tab_button")
|
("class" "button camo tab_button")
|
||||||
("id" "metadata_tab_button")
|
("id" "metadata_tab_button")
|
||||||
("onclick" "tab_metadata()")
|
("onclick" "tab_metadata()")
|
||||||
(text "Metadata")))
|
(text "Metadata")
|
||||||
|
(a
|
||||||
|
("class" "button simple surface")
|
||||||
|
("href" "/docs/metadata")
|
||||||
|
("target" "_blank")
|
||||||
|
("title" "Info")
|
||||||
|
(text "i"))))
|
||||||
(div
|
(div
|
||||||
("class" "card tab tabs container")
|
("class" "card tab tabs container")
|
||||||
("id" "tabs_group")
|
("id" "tabs_group")
|
||||||
|
@ -55,7 +64,6 @@
|
||||||
("name" "slug")
|
("name" "slug")
|
||||||
("oninput" "check_exists_input(event)")
|
("oninput" "check_exists_input(event)")
|
||||||
("placeholder" "Custom url"))))
|
("placeholder" "Custom url"))))
|
||||||
(text "{{ components::footer() }}")
|
|
||||||
|
|
||||||
; 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"))
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
(text "{%- import \"components.lisp\" as components -%}")
|
|
||||||
(text "<!doctype html>")
|
(text "<!doctype html>")
|
||||||
(html
|
(html
|
||||||
("lang" "en")
|
("lang" "en")
|
||||||
|
@ -19,8 +18,64 @@
|
||||||
(text "{% block head %}{% endblock %}"))
|
(text "{% block head %}{% endblock %}"))
|
||||||
|
|
||||||
(body
|
(body
|
||||||
|
; nav
|
||||||
|
(nav
|
||||||
|
("class" "flex w-full justify-between gap-2")
|
||||||
|
(div
|
||||||
|
("class" "flex side")
|
||||||
|
(div
|
||||||
|
("class" "dropdown")
|
||||||
|
(button
|
||||||
|
("onclick" "open_dropdown(event)")
|
||||||
|
("exclude" "dropdown")
|
||||||
|
("class" "button camo fade")
|
||||||
|
(text "{{ icon \"menu\" }}"))
|
||||||
|
(div
|
||||||
|
("class" "inner")
|
||||||
|
(a
|
||||||
|
("class" "button")
|
||||||
|
("href" "/")
|
||||||
|
(text "new"))
|
||||||
|
(a
|
||||||
|
("class" "button")
|
||||||
|
("href" "/{{ what_page_slug }}")
|
||||||
|
(text "what"))
|
||||||
|
(a
|
||||||
|
("class" "button")
|
||||||
|
("href" "https://trisua.com/t/attobin")
|
||||||
|
(text "source"))))
|
||||||
|
|
||||||
|
(a
|
||||||
|
("class" "button camo fade")
|
||||||
|
("href" "/")
|
||||||
|
("title" "new")
|
||||||
|
(text "{{ icon \"plus\" }}")))
|
||||||
|
|
||||||
|
(div
|
||||||
|
("class" "side flex")
|
||||||
|
(text "{% block nav_extras %}{% endblock %}")
|
||||||
|
|
||||||
|
; theme switches
|
||||||
|
(button
|
||||||
|
("class" "button camo fade")
|
||||||
|
("id" "switch_light")
|
||||||
|
("title" "Switch theme")
|
||||||
|
("onclick" "set_theme('Dark')")
|
||||||
|
(text "{{ icon \"sun\" }}"))
|
||||||
|
|
||||||
|
(button
|
||||||
|
("class" "button camo fade hidden")
|
||||||
|
("id" "switch_dark")
|
||||||
|
("title" "Switch theme")
|
||||||
|
("onclick" "set_theme('Light')")
|
||||||
|
(text "{{ icon \"moon\" }}"))))
|
||||||
|
|
||||||
|
; page
|
||||||
(article
|
(article
|
||||||
("class" "content_container flex flex-col")
|
("class" "content_container flex flex-col")
|
||||||
("id" "page")
|
("id" "page")
|
||||||
(ul ("id" "messages"))
|
(ul ("id" "messages"))
|
||||||
(text "{% block body %}{% endblock %}"))))
|
(text "{% block body %}{% endblock %}")
|
||||||
|
(div ("style" "min-height: 32px")))
|
||||||
|
|
||||||
|
(script (text "setTimeout(() => init_dropdowns(document.body), 150);"))))
|
||||||
|
|
|
@ -4,6 +4,11 @@
|
||||||
(text "{{ entry.slug }}"))
|
(text "{{ entry.slug }}"))
|
||||||
(text "{%- endif %} {{ metadata_head|safe }}")
|
(text "{%- endif %} {{ metadata_head|safe }}")
|
||||||
|
|
||||||
|
(text "{% if not metadata.share_title -%}")
|
||||||
|
(meta ("property" "og:title") ("content" "{{ entry.slug }}"))
|
||||||
|
(meta ("property" "twitter:title") ("content" "{{ entry.slug }}"))
|
||||||
|
(text "{%- endif %}")
|
||||||
|
|
||||||
(text "{% if metadata.page_icon|length == 0 -%}")
|
(text "{% if metadata.page_icon|length == 0 -%}")
|
||||||
(link ("rel" "icon") ("href" "/public/favicon.svg"))
|
(link ("rel" "icon") ("href" "/public/favicon.svg"))
|
||||||
(text "{%- endif %}")
|
(text "{%- endif %}")
|
||||||
|
@ -30,7 +35,7 @@
|
||||||
|
|
||||||
; auto theme
|
; auto theme
|
||||||
(text "{% if metadata.access_recommended_theme != 'None' -%}")
|
(text "{% if metadata.access_recommended_theme != 'None' -%}")
|
||||||
(span (text "Auto theme: {{ metadata.access_recommended_theme }}"))
|
(span ("id" "auto_theme") (text "Auto theme: {{ metadata.access_recommended_theme }}"))
|
||||||
(script ("defer" "true") (text "setTimeout(() => { temporary_set_theme('{{ metadata.access_recommended_theme }}') }, 150);"))
|
(script ("defer" "true") (text "setTimeout(() => { temporary_set_theme('{{ metadata.access_recommended_theme }}') }, 150);"))
|
||||||
(text "{%- endif %}")
|
(text "{%- endif %}")
|
||||||
|
|
||||||
|
@ -44,11 +49,17 @@
|
||||||
(a ("class" "button small") ("href" "/{{ metadata.access_easy_read }}") (b (text "E2R")))
|
(a ("class" "button small") ("href" "/{{ metadata.access_easy_read }}") (b (text "E2R")))
|
||||||
(text "{%- endif %}"))))
|
(text "{%- endif %}"))))
|
||||||
|
|
||||||
(text "{{ metadata_css|safe }}")
|
(div ("style" "display: none") ("id" "metadata_css") (text "{{ metadata_css|safe }}"))
|
||||||
|
|
||||||
(link ("rel" "stylesheet") ("href" "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css"))
|
(link ("rel" "stylesheet") ("href" "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.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"))
|
||||||
(script (text "hljs.highlightAll();"))
|
(script (text "hljs.highlightAll();"))
|
||||||
|
|
||||||
(text "{{ components::footer() }}")
|
(text "{% endblock %}")
|
||||||
|
(text "{% block nav_extras %}")
|
||||||
|
(button
|
||||||
|
("class" "button camo fade no_fill")
|
||||||
|
("title" "Toggle high-contrast")
|
||||||
|
("onclick" "toggle_metadata_css(event)")
|
||||||
|
(text "{{ icon \"contrast\" }}"))
|
||||||
(text "{% endblock %}")
|
(text "{% endblock %}")
|
||||||
|
|
6
justfile
Normal file
6
justfile
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
doc:
|
||||||
|
cargo doc --no-deps --document-private-items
|
||||||
|
|
||||||
|
build:
|
||||||
|
just doc
|
||||||
|
cargo build -r
|
11
src/main.rs
11
src/main.rs
|
@ -7,7 +7,7 @@ use axum::{Extension, Router};
|
||||||
use nanoneo::core::element::Render;
|
use nanoneo::core::element::Render;
|
||||||
use std::{collections::HashMap, env::var, net::SocketAddr, process::exit, sync::Arc};
|
use std::{collections::HashMap, env::var, net::SocketAddr, process::exit, sync::Arc};
|
||||||
use tera::{Tera, Value};
|
use tera::{Tera, Value};
|
||||||
use tetratto_core::sdk::DataClient;
|
use tetratto_core::{html, sdk::DataClient};
|
||||||
use tetratto_shared::hash::salt;
|
use tetratto_shared::hash::salt;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use tower_http::{
|
use tower_http::{
|
||||||
|
@ -64,20 +64,29 @@ async fn main() {
|
||||||
|
|
||||||
// build lisp
|
// build lisp
|
||||||
create_dir_if_not_exists!("./templates_build");
|
create_dir_if_not_exists!("./templates_build");
|
||||||
|
create_dir_if_not_exists!("./icons");
|
||||||
|
|
||||||
for x in glob::glob("./templates_src/**/*").expect("failed to read pattern") {
|
for x in glob::glob("./templates_src/**/*").expect("failed to read pattern") {
|
||||||
match x {
|
match x {
|
||||||
Ok(x) => std::fs::write(
|
Ok(x) => std::fs::write(
|
||||||
x.to_str()
|
x.to_str()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.replace("templates_src/", "templates_build/"),
|
.replace("templates_src/", "templates_build/"),
|
||||||
|
html::pull_icons(
|
||||||
nanoneo::parse(&std::fs::read_to_string(x).expect("failed to read template"))
|
nanoneo::parse(&std::fs::read_to_string(x).expect("failed to read template"))
|
||||||
.render(&mut HashMap::new()),
|
.render(&mut HashMap::new()),
|
||||||
|
"./icons",
|
||||||
|
)
|
||||||
|
.await,
|
||||||
)
|
)
|
||||||
.expect("failed to write template"),
|
.expect("failed to write template"),
|
||||||
Err(e) => panic!("{e}"),
|
Err(e) => panic!("{e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create docs dir
|
||||||
|
create_dir_if_not_exists!("./docs");
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
let mut tera = match Tera::new(&format!("./templates_build/**/*")) {
|
let mut tera = match Tera::new(&format!("./templates_build/**/*")) {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
|
|
|
@ -76,7 +76,7 @@ fn parse_text_color_line(output: &mut String, buffer: &mut String, line: &str) {
|
||||||
// by this point, we have: !
|
// by this point, we have: !
|
||||||
// %color_buffer%main_buffer%%
|
// %color_buffer%main_buffer%%
|
||||||
output.push_str(&format!(
|
output.push_str(&format!(
|
||||||
"<span style=\"color: {color_buffer}\">{buffer}</span>"
|
"<span style=\"color: {color_buffer}\" class=\"color_block\">{buffer}</span>"
|
||||||
));
|
));
|
||||||
|
|
||||||
color_buffer.clear();
|
color_buffer.clear();
|
||||||
|
@ -468,7 +468,14 @@ fn parse_image_line(output: &mut String, buffer: &mut String, line: &str) {
|
||||||
if in_image {
|
if in_image {
|
||||||
// end
|
// end
|
||||||
output.push_str(&format!(
|
output.push_str(&format!(
|
||||||
"<img loading=\"lazy\" alt=\"{alt}\" src=\"{buffer}\" />"
|
"<img loading=\"lazy\" alt=\"{alt}\" src=\"{buffer}\" style=\"float: {}\" />",
|
||||||
|
if buffer.ends_with("#left") {
|
||||||
|
"left"
|
||||||
|
} else if buffer.ends_with("#right") {
|
||||||
|
"right"
|
||||||
|
} else {
|
||||||
|
"unset"
|
||||||
|
}
|
||||||
));
|
));
|
||||||
|
|
||||||
alt = String::new();
|
alt = String::new();
|
||||||
|
|
13
src/model.rs
13
src/model.rs
|
@ -357,6 +357,17 @@ macro_rules! metadata_css {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
($selector:literal, $property:literal !important, $self:ident.$field:ident->$output:ident) => {
|
||||||
|
if !$self.$field.is_empty() {
|
||||||
|
$output.push_str(&format!(
|
||||||
|
"{} {{ {}: {} !important; }}\n",
|
||||||
|
$selector,
|
||||||
|
$property,
|
||||||
|
EntryMetadata::css_escape(&$self.$field)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
($selector:literal, $property:literal, $format:literal, $self:ident.$field:ident->$output:ident) => {
|
($selector:literal, $property:literal, $format:literal, $self:ident.$field:ident->$output:ident) => {
|
||||||
if !$self.$field.is_empty() {
|
if !$self.$field.is_empty() {
|
||||||
$output.push_str(&format!(
|
$output.push_str(&format!(
|
||||||
|
@ -462,7 +473,7 @@ impl EntryMetadata {
|
||||||
metadata_css!(".container", "border-radius", self.container_border_radius->output);
|
metadata_css!(".container", "border-radius", self.container_border_radius->output);
|
||||||
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!(".container a", "color", self.content_text_link_color->output);
|
metadata_css!("*, html *", "--color-link" !important, self.content_text_link_color->output);
|
||||||
|
|
||||||
if self.content_text_align != TextAlignment::Left {
|
if self.content_text_align != TextAlignment::Left {
|
||||||
output.push_str(&format!(
|
output.push_str(&format!(
|
||||||
|
|
|
@ -8,6 +8,8 @@ use axum::{
|
||||||
response::{Html, IntoResponse},
|
response::{Html, IntoResponse},
|
||||||
routing::{get, get_service, post},
|
routing::{get, get_service, post},
|
||||||
};
|
};
|
||||||
|
use axum_extra::extract::CookieJar;
|
||||||
|
use pathbufd::PathBufD;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_valid::Validate;
|
use serde_valid::Validate;
|
||||||
use tera::Context;
|
use tera::Context;
|
||||||
|
@ -32,6 +34,7 @@ pub fn routes() -> Router {
|
||||||
get_service(tower_http::services::ServeDir::new("./public")),
|
get_service(tower_http::services::ServeDir::new("./public")),
|
||||||
)
|
)
|
||||||
.fallback(not_found_request)
|
.fallback(not_found_request)
|
||||||
|
.route("/docs/{name}", get(view_doc_request))
|
||||||
// pages
|
// pages
|
||||||
.route("/", get(index_request))
|
.route("/", get(index_request))
|
||||||
.route("/{slug}", get(view_request))
|
.route("/{slug}", get(view_request))
|
||||||
|
@ -78,6 +81,39 @@ async fn index_request(Extension(data): Extension<State>) -> impl IntoResponse {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn view_doc_request(
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
Path(name): Path<String>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let (ref data, ref tera, ref build_code) = *data.read().await;
|
||||||
|
let path = PathBufD::current().extend(&["docs", &format!("{name}.md")]);
|
||||||
|
|
||||||
|
if !std::fs::exists(&path).unwrap_or(false) {
|
||||||
|
let mut ctx = default_context(&data, &build_code);
|
||||||
|
ctx.insert(
|
||||||
|
"error",
|
||||||
|
&Error::GeneralNotFound("entry".to_string()).to_string(),
|
||||||
|
);
|
||||||
|
return Html(tera.render("error.lisp", &ctx).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = match std::fs::read_to_string(&path) {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => {
|
||||||
|
let mut ctx = default_context(&data, &build_code);
|
||||||
|
ctx.insert("error", &Error::MiscError(e.to_string()).to_string());
|
||||||
|
return Html(tera.render("error.lisp", &ctx).unwrap());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ctx = default_context(&data, &build_code);
|
||||||
|
|
||||||
|
ctx.insert("text", &text);
|
||||||
|
ctx.insert("file_name", &name);
|
||||||
|
|
||||||
|
return Html(tera.render("doc.lisp", &ctx).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
async fn view_request(
|
async fn view_request(
|
||||||
Extension(data): Extension<State>,
|
Extension(data): Extension<State>,
|
||||||
Path(slug): Path<String>,
|
Path(slug): Path<String>,
|
||||||
|
@ -258,27 +294,46 @@ fn default_random() -> String {
|
||||||
salt()
|
salt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The time that must be waited between each entry creation.
|
||||||
|
const CREATE_WAIT_TIME: usize = 5000;
|
||||||
|
|
||||||
async fn create_request(
|
async fn create_request(
|
||||||
|
jar: CookieJar,
|
||||||
Extension(data): Extension<State>,
|
Extension(data): Extension<State>,
|
||||||
Json(req): Json<CreateEntry>,
|
Json(req): Json<CreateEntry>,
|
||||||
) -> impl IntoResponse {
|
) -> std::result::Result<impl IntoResponse, Json<ApiReturn<()>>> {
|
||||||
let (ref data, _, _) = *data.read().await;
|
let (ref data, _, _) = *data.read().await;
|
||||||
|
|
||||||
|
// check wait time
|
||||||
|
if let Some(cookie) = jar.get("__Secure-Claim-Next") {
|
||||||
|
if unix_epoch_timestamp()
|
||||||
|
!= cookie
|
||||||
|
.to_string()
|
||||||
|
.replace("__Secure-Claim-Next=", "")
|
||||||
|
.parse::<usize>()
|
||||||
|
.unwrap_or(0)
|
||||||
|
{
|
||||||
|
return Err(Json(
|
||||||
|
Error::MiscError("You must wait a bit to create another entry".to_string()).into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// check lengths
|
// check lengths
|
||||||
if req.slug.len() < 2 {
|
if req.slug.len() < 2 {
|
||||||
return Json(Error::DataTooShort("slug".to_string()).into());
|
return Err(Json(Error::DataTooShort("slug".to_string()).into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.slug.len() > 32 {
|
if req.slug.len() > 32 {
|
||||||
return Json(Error::DataTooLong("slug".to_string()).into());
|
return Err(Json(Error::DataTooLong("slug".to_string()).into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.content.len() < 2 {
|
if req.content.len() < 2 {
|
||||||
return Json(Error::DataTooShort("content".to_string()).into());
|
return Err(Json(Error::DataTooShort("content".to_string()).into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.content.len() > 150_000 {
|
if req.content.len() > 150_000 {
|
||||||
return Json(Error::DataTooLong("content".to_string()).into());
|
return Err(Json(Error::DataTooLong("content".to_string()).into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// check slug
|
// check slug
|
||||||
|
@ -288,17 +343,19 @@ async fn create_request(
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if regex.captures(&req.slug).is_some() {
|
if regex.captures(&req.slug).is_some() {
|
||||||
return Json(Error::MiscError("This slug contains invalid characters".to_string()).into());
|
return Err(Json(
|
||||||
|
Error::MiscError("This slug contains invalid characters".to_string()).into(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// check metadata
|
// check metadata
|
||||||
let metadata: EntryMetadata = match toml::from_str(&EntryMetadata::ini_to_toml(&req.metadata)) {
|
let metadata: EntryMetadata = match toml::from_str(&EntryMetadata::ini_to_toml(&req.metadata)) {
|
||||||
Ok(x) => x,
|
Ok(x) => x,
|
||||||
Err(e) => return Json(Error::MiscError(e.to_string()).into()),
|
Err(e) => return Err(Json(Error::MiscError(e.to_string()).into())),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = metadata.validate() {
|
if let Err(e) = metadata.validate() {
|
||||||
return Json(Error::MiscError(e.to_string()).into());
|
return Err(Json(Error::MiscError(e.to_string()).into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for existing
|
// check for existing
|
||||||
|
@ -310,7 +367,9 @@ async fn create_request(
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
{
|
{
|
||||||
return Json(Error::MiscError("Slug already in use".to_string()).into());
|
return Err(Json(
|
||||||
|
Error::MiscError("Slug already in use".to_string()).into(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// create
|
// create
|
||||||
|
@ -333,22 +392,31 @@ async fn create_request(
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
return Json(e.into());
|
return Err(Json(e.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = data
|
if let Err(e) = data
|
||||||
.insert(format!("entries.views('{}')", req.slug), 0.to_string())
|
.insert(format!("entries.views('{}')", req.slug), 0.to_string())
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
return Json(e.into());
|
return Err(Json(e.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// return
|
// return
|
||||||
|
Ok((
|
||||||
|
[(
|
||||||
|
"Set-Cookie",
|
||||||
|
format!(
|
||||||
|
"__Secure-Claim-Next={}; SameSite=Lax; Secure; Path=/; HostOnly=true; HttpOnly=true; Max-Age=5",
|
||||||
|
unix_epoch_timestamp() + CREATE_WAIT_TIME
|
||||||
|
),
|
||||||
|
)],
|
||||||
Json(ApiReturn {
|
Json(ApiReturn {
|
||||||
ok: true,
|
ok: true,
|
||||||
message: "Success".to_string(),
|
message: "Success".to_string(),
|
||||||
payload: Some((req.slug, req.edit_code)),
|
payload: Some((req.slug, req.edit_code)),
|
||||||
})
|
}),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue