add: better layout

This commit is contained in:
trisua 2025-07-25 15:12:15 -04:00
parent 2cc9ed7445
commit dbd70d9592
19 changed files with 451 additions and 87 deletions

3
app/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
public/docs/**/*
!public/docs/metadata.md
icons/

11
app/docs/metadata.md Normal file
View 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).

View file

@ -224,3 +224,109 @@ globalThis.check_exists_input = (e) => {
e.target.reportValidity();
}, 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
View file

@ -0,0 +1 @@
../../target/doc

View file

@ -12,7 +12,7 @@
--color-shadow: rgba(0, 0, 0, 0.08);
--color-red: hsl(0, 84%, 40%);
--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-primary: oklch(67.3% 0.182 276.935);
@ -24,6 +24,9 @@
--pad-2: 0.35rem;
--pad-3: 0.5rem;
--pad-4: 1rem;
--radius: 0.2rem;
--nav-height: 36px;
}
* {
@ -45,7 +48,7 @@
--color-link: #93c5fd;
--color-red: hsl(0, 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%);
}
@ -73,7 +76,7 @@ main {
article {
margin: var(--pad-2) 0;
height: calc(100dvh - var(--pad-4) * 2);
height: calc(100dvh - var(--pad-4) - var(--nav-height) * 2);
}
.tab {
@ -89,6 +92,14 @@ article {
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) {
main,
article,
@ -117,7 +128,7 @@ article {
}
.content_container {
margin: var(--pad-2) auto;
margin: 0 auto var(--pad-2);
width: 100%;
}
@ -167,7 +178,7 @@ video {
display: flex;
justify-content: center;
align-items: center;
gap: var(--gap-2);
gap: var(--pad-2);
padding: var(--pad-2) calc(var(--pad-3) * 1.5);
cursor: pointer;
background: var(--color-raised);
@ -187,7 +198,7 @@ video {
--h: 28px;
}
.button:hover {
.button:not(:has(.button:hover)):not(.camo):hover {
background: var(--color-super-raised);
}
@ -196,10 +207,70 @@ video {
color: inherit;
}
.bar .button.camo:hover {
.bar .button:not(.simple).camo:hover {
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 {
--h: 36px;
@ -276,6 +347,7 @@ pre {
padding: var(--pad-2) var(--pad-4);
border-left: solid 5px var(--color-primary);
background: var(--color-surface);
border-radius: var(--radius);
}
code {
@ -297,8 +369,22 @@ code * {
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 {
stroke: currentColor;
fill: currentColor;
width: 18px;
height: 1em;
}
@ -307,6 +393,10 @@ svg.icon.filled {
fill: currentColor;
}
.no_fill svg.icon {
fill: transparent;
}
button svg {
pointer-events: none;
}
@ -384,6 +474,10 @@ a {
color: var(--color-link);
}
.color_block a {
color: inherit;
}
a.flush {
color: inherit;
}
@ -477,12 +571,12 @@ blockquote {
.cm-comment,
.hljs-keyword {
color: rgb(153 27 27) !important;
color: oklch(47% 0.157 37.304) !important;
}
.cm-comment:is(.dark *),
.hljs-keyword:is(.dark *) {
color: rgb(254, 202, 202) !important;
color: oklch(90.1% 0.076 70.697) !important;
}
.cm-link {

View file

@ -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 %}")

View 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 %}")

View file

@ -19,7 +19,13 @@
("class" "button camo tab_button")
("id" "metadata_tab_button")
("onclick" "tab_metadata()")
(text "Metadata")))
(text "Metadata")
(a
("class" "button simple surface")
("href" "/docs/metadata")
("target" "_blank")
("title" "Info")
(text "i"))))
(div
("class" "card tab tabs container")
("id" "tabs_group")
@ -74,7 +80,6 @@
("class" "button red")
("ui_ident" "delete")
(text "Delete"))))
(text "{{ components::footer() }}")
; editor
(script ("src" "https://unpkg.com/codemirror@5.39.2/lib/codemirror.js"))

View file

@ -6,5 +6,4 @@
(div
("class" "card")
(p (text "{{ error }}")))
(text "{{ components::footer() }}")
(text "{% endblock %}")

View file

@ -1,6 +1,9 @@
(text "{% extends \"root.lisp\" %} {% block head %}")
(title
(text "{{ name }}"))
(meta ("property" "og:title") ("content" "{{ name }}"))
(meta ("property" "twitter:title") ("content" "{{ name }}"))
(link ("rel" "icon") ("href" "/public/favicon.svg"))
(text "{% endblock %} {% block body %}")
(div
@ -19,7 +22,13 @@
("class" "button camo tab_button")
("id" "metadata_tab_button")
("onclick" "tab_metadata()")
(text "Metadata")))
(text "Metadata")
(a
("class" "button simple surface")
("href" "/docs/metadata")
("target" "_blank")
("title" "Info")
(text "i"))))
(div
("class" "card tab tabs container")
("id" "tabs_group")
@ -55,7 +64,6 @@
("name" "slug")
("oninput" "check_exists_input(event)")
("placeholder" "Custom url"))))
(text "{{ components::footer() }}")
; editor
(script ("src" "https://unpkg.com/codemirror@5.39.2/lib/codemirror.js"))

View file

@ -1,4 +1,3 @@
(text "{%- import \"components.lisp\" as components -%}")
(text "<!doctype html>")
(html
("lang" "en")
@ -19,8 +18,64 @@
(text "{% block head %}{% endblock %}"))
(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
("class" "content_container flex flex-col")
("id" "page")
(ul ("id" "messages"))
(text "{% block body %}{% endblock %}"))))
(text "{% block body %}{% endblock %}")
(div ("style" "min-height: 32px")))
(script (text "setTimeout(() => init_dropdowns(document.body), 150);"))))

View file

@ -4,6 +4,11 @@
(text "{{ entry.slug }}"))
(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 -%}")
(link ("rel" "icon") ("href" "/public/favicon.svg"))
(text "{%- endif %}")
@ -30,7 +35,7 @@
; auto theme
(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);"))
(text "{%- endif %}")
@ -44,11 +49,17 @@
(a ("class" "button small") ("href" "/{{ metadata.access_easy_read }}") (b (text "E2R")))
(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"))
(script ("src" "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"))
(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 %}")