moving to custom swap engine

Signed-off-by: ari melody <ari@arimelody.me>
This commit is contained in:
ari melody 2024-04-16 17:53:24 +01:00
parent 749f9bc8b7
commit 13d802d361
21 changed files with 4361 additions and 225 deletions

View file

@ -13,7 +13,7 @@ tmp_dir = "tmp"
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_dir = [".", "views", "api"]
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"

View file

@ -1,18 +1,18 @@
version: '3.9'
services:
db:
image: postgres:16.1-alpine3.18
container_name: arimelody.me-db
ports:
- 5432:5432
volumes:
- arimelody-db:/var/lib/postgresql/data
environment:
POSTGRES_DB: arimelody
POSTGRES_USER: arimelody
POSTGRES_PASSWORD: fuckingpassword
db:
image: postgres:16.1-alpine3.18
container_name: arimelody.me-db
ports:
- 5432:5432
volumes:
- arimelody-db:/var/lib/postgresql/data
environment:
POSTGRES_DB: arimelody
POSTGRES_USER: arimelody
POSTGRES_PASSWORD: fuckingpassword
volumes:
arimelody-db:
external: true
arimelody-db:
external: true

64
main.go
View file

@ -6,7 +6,6 @@ import (
"log"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"time"
@ -37,7 +36,7 @@ var base_template = template.Must(template.ParseFiles(
"views/footer.html",
"views/prideflag.html",
))
var htmx_template = template.Must(template.New("root").Parse(`<head>{{block "head" .}}{{end}}</head>{{block "content" .}}{{end}}`))
// var htmx_template = template.Must(template.New("root").Parse(`<head>{{block "head" .}}{{end}}</head>{{block "content" .}}{{end}}`))
func log_request(req *http.Request, code int, start_time time.Time) {
now := time.Now()
@ -61,25 +60,27 @@ func handle_request(writer http.ResponseWriter, req *http.Request) {
uri := req.URL.Path
start_time := time.Now()
hx_request := len(req.Header["Hx-Request"]) > 0 && req.Header["Hx-Request"][0] == "true"
// don't bother fulfilling requests to a page that's already loaded on the client!
if hx_request && len(req.Header["Referer"]) > 0 && len(req.Header["Hx-Current-Url"]) > 0 {
regex := regexp.MustCompile(`https?:\/\/[^\/]+`)
current_location := regex.ReplaceAllString(req.Header["Hx-Current-Url"][0], "")
if current_location == req.URL.Path {
writer.WriteHeader(204);
return
}
}
// hx_request := len(req.Header["Hx-Request"]) > 0 && req.Header["Hx-Request"][0] == "true"
//
// // don't bother fulfilling requests to a page that's already loaded on the client!
// if hx_request && len(req.Header["Referer"]) > 0 && len(req.Header["Hx-Current-Url"]) > 0 {
// regex := regexp.MustCompile(`https?:\/\/[^\/]+`)
// current_location := regex.ReplaceAllString(req.Header["Hx-Current-Url"][0], "")
// if current_location == req.URL.Path {
// writer.WriteHeader(204);
// return
// }
// }
code := func(writer http.ResponseWriter, req *http.Request) int {
var root *template.Template
if hx_request {
root = template.Must(htmx_template.Clone())
} else {
root = template.Must(base_template.Clone())
}
// var root *template.Template
// if hx_request {
// root = template.Must(htmx_template.Clone())
// } else {
// root = template.Must(base_template.Clone())
// }
var root = template.Must(base_template.Clone())
if req.URL.Path == "/" {
return index_handler(writer, root)
@ -93,7 +94,7 @@ func handle_request(writer http.ResponseWriter, req *http.Request) {
return music_gateway_handler(writer, req, root)
}
return static_handler(writer, req)
return static_handler(writer, req, root)
}(writer, req)
log_request(req, code, start_time)
@ -126,8 +127,7 @@ func music_gateway_handler(writer http.ResponseWriter, req *http.Request, root *
// return
release, ok := music.GetRelease(id)
if !ok {
http.Error(writer, "404 not found", http.StatusNotFound)
return 404
return handle_not_found(writer, req, root)
}
gateway_template := template.Must(root.ParseFiles("views/music-gateway.html"))
err := gateway_template.Execute(writer, release)
@ -138,14 +138,13 @@ func music_gateway_handler(writer http.ResponseWriter, req *http.Request, root *
return 200
}
func static_handler(writer http.ResponseWriter, req *http.Request) int {
func static_handler(writer http.ResponseWriter, req *http.Request, root *template.Template) int {
filename := "public/" + req.URL.Path[1:]
// check the file's metadata
info, err := os.Stat(filename)
if err != nil {
http.Error(writer, "404 not found", http.StatusNotFound)
return 404
return handle_not_found(writer, req, root)
}
if len(req.Header["If-Modified-Since"]) > 0 && req.Header["If-Modified-Since"][0] != "" {
@ -185,6 +184,21 @@ func static_handler(writer http.ResponseWriter, req *http.Request) int {
return 200
}
func handle_not_found(writer http.ResponseWriter, req *http.Request, root *template.Template) int {
type ErrorData struct {
Target string
}
error_data := ErrorData{ Target: req.URL.Path }
writer.WriteHeader(404);
error_template := template.Must(root.ParseFiles("views/404.html"))
err := error_template.Execute(writer, error_data)
if err != nil {
http.Error(writer, err.Error(), http.StatusInternalServerError)
return 500
}
return 404
}
func parse_markdown(md []byte) []byte {
extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
p := parser.NewWithExtensions(extensions)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -1,44 +0,0 @@
const accessibility = JSON.parse(localStorage.getItem("accessibility")) || {};
function toggle_accessibility_setting(name) {
if (accessibility[name]) {
delete accessibility[name];
update_accessibility();
return true;
}
accessibility[name] = true;
update_accessibility();
return true;
}
function set_accessibility_setting(name, value) {
accessibility[name] = value;
update_accessibility();
return true;
}
function clear_accessibility_setting(name) {
if (!accessibility[name]) return false;
delete accessibility[name];
update_accessibility();
return true;
}
function update_accessibility() {
localStorage.setItem("accessibility", JSON.stringify(accessibility));
}
if (accessibility) {
if (accessibility.disable_crt) {
document.querySelector('div#overlay').setAttribute("hidden", true);
document.body.style.textShadow = "none";
document.getElementById('toggle-crt').classList.add("disabled");
}
}
document.getElementById("toggle-crt").addEventListener("click", () => {
toggle_accessibility_setting("disable_crt");
document.querySelector('div#overlay').toggleAttribute("hidden");
document.getElementById('toggle-crt').className = accessibility.disable_crt ? "disabled" : "";
});

42
public/script/config.js Normal file
View file

@ -0,0 +1,42 @@
function toggle_config_setting(config, name) {
if (config[name]) {
delete config[name];
update_config(config);
return true;
}
config[name] = true;
update_config(config);
return true;
}
function set_config_setting(config, name, value) {
config[name] = value;
update_config(config);
return true;
}
function clear_config_setting(config, name) {
if (!config[name]) return false;
delete config[name];
update_config(config);
return true;
}
function update_config(config) {
localStorage.setItem("config", JSON.stringify(config));
}
const config = JSON.parse(localStorage.getItem("config")) || {};
if (config) {
if (config.disable_crt) {
document.querySelector('div#overlay').setAttribute("hidden", true);
document.body.style.textShadow = "none";
document.getElementById('toggle-crt').classList.add("disabled");
}
}
document.getElementById("toggle-crt").addEventListener("click", () => {
toggle_config_setting(config, "disable_crt");
document.querySelector('div#overlay').toggleAttribute("hidden");
document.getElementById('toggle-crt').className = config.disable_crt ? "disabled" : "";
});

View file

@ -1,14 +1,12 @@
const header_links = document.getElementById("header-links");
const hamburger = document.getElementById("header-links-toggle");
function toggle_header_links() {
header_links.classList.toggle("open");
}
document.addEventListener("click", event => {
if (!header_links.contains(event.target) && !hamburger.contains(event.target) && !header_links.href) {
header_links.classList.remove("open");
}
});
hamburger.addEventListener("click", event => { toggle_header_links(); });
hamburger.addEventListener("click", event => {
header_links.classList.toggle("open");
});

View file

@ -0,0 +1,147 @@
// This adds the "preload" extension to htmx. By default, this will
// preload the targets of any tags with `href` or `hx-get` attributes
// if they also have a `preload` attribute as well. See documentation
// for more details
htmx.defineExtension("preload", {
onEvent: function(name, event) {
// Only take actions on "htmx:afterProcessNode"
if (name !== "htmx:afterProcessNode") {
return;
}
// SOME HELPER FUNCTIONS WE'LL NEED ALONG THE WAY
// attr gets the closest non-empty value from the attribute.
var attr = function(node, property) {
if (node == undefined) {return undefined;}
return node.getAttribute(property) || node.getAttribute("data-" + property) || attr(node.parentElement, property)
}
// load handles the actual HTTP fetch, and uses htmx.ajax in cases where we're
// preloading an htmx resource (this sends the same HTTP headers as a regular htmx request)
var load = function(node) {
// Called after a successful AJAX request, to mark the
// content as loaded (and prevent additional AJAX calls.)
var done = function(html) {
if (!node.preloadAlways) {
node.preloadState = "DONE"
}
if (attr(node, "preload-images") == "true") {
document.createElement("div").innerHTML = html // create and populate a node to load linked resources, too.
}
}
return function() {
// If this value has already been loaded, then do not try again.
if (node.preloadState !== "READY") {
return;
}
// Special handling for HX-GET - use built-in htmx.ajax function
// so that headers match other htmx requests, then set
// node.preloadState = TRUE so that requests are not duplicated
// in the future
var hxGet = node.getAttribute("hx-get") || node.getAttribute("data-hx-get")
if (hxGet) {
htmx.ajax("GET", hxGet, {
source: node,
handler:function(elt, info) {
done(info.xhr.responseText);
}
});
return;
}
// Otherwise, perform a standard xhr request, then set
// node.preloadState = TRUE so that requests are not duplicated
// in the future.
if (node.getAttribute("href")) {
var r = new XMLHttpRequest();
r.open("GET", node.getAttribute("href"));
r.onload = function() {done(r.responseText);};
r.send();
return;
}
}
}
// This function processes a specific node and sets up event handlers.
// We'll search for nodes and use it below.
var init = function(node) {
// If this node DOES NOT include a "GET" transaction, then there's nothing to do here.
if (node.getAttribute("href") + node.getAttribute("hx-get") + node.getAttribute("data-hx-get") == "") {
return;
}
// Guarantee that we only initialize each node once.
if (node.preloadState !== undefined) {
return;
}
// Get event name from config.
var on = attr(node, "preload") || "mousedown"
const always = on.indexOf("always") !== -1
if (always) {
on = on.replace('always', '').trim()
}
// FALL THROUGH to here means we need to add an EventListener
// Apply the listener to the node
node.addEventListener(on, function(evt) {
if (node.preloadState === "PAUSE") { // Only add one event listener
node.preloadState = "READY"; // Required for the `load` function to trigger
// Special handling for "mouseover" events. Wait 100ms before triggering load.
if (on === "mouseover") {
window.setTimeout(load(node), 100);
} else {
load(node)() // all other events trigger immediately.
}
}
})
// Special handling for certain built-in event handlers
switch (on) {
case "mouseover":
// Mirror `touchstart` events (fires immediately)
node.addEventListener("touchstart", load(node));
// WHhen the mouse leaves, immediately disable the preload
node.addEventListener("mouseout", function(evt) {
if ((evt.target === node) && (node.preloadState === "READY")) {
node.preloadState = "PAUSE";
}
})
break;
case "mousedown":
// Mirror `touchstart` events (fires immediately)
node.addEventListener("touchstart", load(node));
break;
}
// Mark the node as ready to run.
node.preloadState = "PAUSE";
node.preloadAlways = always;
htmx.trigger(node, "preload:init") // This event can be used to load content immediately.
}
// Search for all child nodes that have a "preload" attribute
event.target.querySelectorAll("[preload]").forEach(function(node) {
// Initialize the node with the "preload" attribute
init(node)
// Initialize all child elements that are anchors or have `hx-get` (use with care)
node.querySelectorAll("a,[hx-get],[data-hx-get]").forEach(init)
})
}
})

3922
public/script/lib/htmx.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
import "./header.js";
import "./accessibility.js";
import "./config.js";
function type_out(e) {
const text = e.innerText;
@ -34,46 +34,50 @@ function type_out(e) {
}
function fill_list(list) {
const items = list.querySelectorAll("li a, li span");
items.innerText = "";
const delay = 100;
const items = list.querySelectorAll("li a, li span");
items.innerText = "";
const delay = 100;
items.forEach((item, iter) => {
item.style.animationDelay = `${iter * delay}ms`;
item.style.animationPlayState = "playing";
});
items.forEach((item, iter) => {
item.style.animationDelay = `${iter * delay}ms`;
item.style.animationPlayState = "playing";
});
}
[...document.querySelectorAll("h1, h2, h3, h4, h5, h6")]
.filter((e) => e.innerText != "")
.forEach((e) => {
type_out(e);
});
[...document.querySelectorAll("ol, ul")]
.filter((e) => e.innerText != "")
.forEach((e) => {
fill_list(e);
});
function start() {
[...document.querySelectorAll("h1, h2, h3, h4, h5, h6")]
.filter((e) => e.innerText != "")
.forEach((e) => {
type_out(e);
});
[...document.querySelectorAll("ol, ul")]
.filter((e) => e.innerText != "")
.forEach((e) => {
fill_list(e);
});
document.addEventListener("htmx:afterSwap", async event => {
const res = await event.detail.xhr.response;
var new_head = res.substring(res.indexOf("<head>")+1, res.indexOf("</head>"));
if (new_head) {
document.head.innerHTML = new_head;
}
window.scrollY = 0;
});
document.addEventListener("htmx:afterSwap", async event => {
const res = await event.detail.xhr.response;
var new_head = res.substring(res.indexOf("<head>")+1, res.indexOf("</head>"));
if (new_head) {
document.head.innerHTML = new_head;
}
window.scrollY = 0;
});
const top_button = document.getElementById("backtotop");
window.onscroll = () => {
if (!top_button) return;
const btt_threshold = 100;
if (
document.body.scrollTop > btt_threshold ||
const top_button = document.getElementById("backtotop");
window.onscroll = () => {
if (!top_button) return;
const btt_threshold = 100;
if (
document.body.scrollTop > btt_threshold ||
document.documentElement.scrollTop > btt_threshold
) {
top_button.classList.add("active");
} else {
top_button.classList.remove("active");
) {
top_button.classList.add("active");
} else {
top_button.classList.remove("active");
}
}
}
start();

View file

@ -1,44 +1,24 @@
import "./main.js";
const bg = document.getElementById("background");
bg.style.backgroundImage = `url(${bg.dataset.url})`;
bg.removeAttribute("data-url");
const share_btn = document.getElementById("share");
share_btn.onclick = (e) => {
navigator.clipboard.writeText(window.location.href);
share_btn.classList.remove('active');
void share_btn.offsetWidth;
share_btn.classList.add('active');
}
const go_back_btn = document.getElementById("go-back")
go_back_btn.innerText = "<";
go_back_btn.addEventListener("click", () => {
window.history.back();
});
apply_funny_bob_to_upcoming_tags();
function apply_funny_bob_to_upcoming_tags() {
const upcomingTags = document.querySelectorAll("#type.upcoming");
for (var i = 0; i < upcomingTags.length; i++) {
const tag = upcomingTags[i];
const upcoming_tags = document.querySelectorAll("#type.upcoming");
for (var i = 0; i < upcoming_tags.length; i++) {
const tag = upcoming_tags[i];
const chars = tag.innerText.split("");
const result = chars.map((c, i) => `<span style="animation-delay: -${i * 100}ms;">${c}</span>`);
tag.innerHTML = result.join("");
}
}
const extras_pairs = Array.from(document.querySelectorAll("div#info > div").values()).map(container => {
return {
container,
button: document.getElementById("extras").querySelector(`ul li a[href="#${container.id}"]`)
};
});
const info_container = document.getElementById("info")
info_container.addEventListener("scroll", update_extras_buttons);
function update_extras_buttons() {
const extras_pairs = Array.from(document.querySelectorAll("div#info > div").values()).map(container => {
return {
container,
button: document.getElementById("extras").querySelector(`ul li a[href="#${container.id}"]`)
};
});
const info_container = document.getElementById("info")
info_container.addEventListener("scroll", update_extras_buttons);
const info_rect = info_container.getBoundingClientRect();
const info_y = info_rect.y;
const font_size = parseFloat(getComputedStyle(document.documentElement).fontSize);
@ -52,57 +32,38 @@ function update_extras_buttons() {
if (scroll_diff <= 0) current = pair;
})
current.button.classList.add("active");
}
update_extras_buttons();
document.querySelectorAll("div#extras ul li a[href]").forEach(link => {
link.addEventListener("click", event => {
event.preventDefault();
location.replace(link.href);
document.querySelectorAll("div#extras ul li a[href]").forEach(link => {
link.addEventListener("click", event => {
event.preventDefault();
location.replace(link.href);
});
});
});
}
/*
* handling track previews (currently not implemented)
const previews = document.querySelectorAll("[id^=preview-]");
for (const preview of previews) {
preview.addEventListener("click", (e) => {
if (e.target.classList.contains('playing')) {
stopPreview(e.target);
} else {
playPreview(e.target);
}
function bind_go_back_btn() {
const go_back_btn = document.getElementById("go-back")
go_back_btn.innerText = "<";
go_back_btn.addEventListener("click", () => {
window.history.back();
});
preview.querySelector('audio').addEventListener("ended", () => { stopPreview(preview); });
}
var stupidsounds = false;
function stopPreviews() {
for (const preview of previews) stopPreview(preview);
function bind_share_btn() {
const share_btn = document.getElementById("share");
share_btn.onclick = (e) => {
navigator.clipboard.writeText(window.location.href);
share_btn.classList.remove('active');
void share_btn.offsetWidth;
share_btn.classList.add('active');
}
}
function playPreview(preview) {
if (!stupidsounds) stopPreviews();
const btn = preview.querySelector('i');
btn.classList.remove("play", "fa-play");
btn.classList.add("pause", "fa-pause");
const audio = preview.querySelector('audio');
audio.play();
preview.classList.add('playing');
function start() {
bind_share_btn();
bind_go_back_btn();
apply_funny_bob_to_upcoming_tags();
update_extras_buttons();
}
function stopPreview(preview) {
const btn = preview.querySelector('i');
btn.classList.remove("pause", "fa-pause");
btn.classList.add("play", "fa-play");
const audio = preview.querySelector('audio');
audio.pause();
audio.currentTime = 0;
preview.classList.remove('playing');
}
stopPreviews();
*/
start();

53
public/script/swap.js Normal file
View file

@ -0,0 +1,53 @@
async function swap(url) {
const res = await fetch(url);
if (res.status !== 200) return;
if (!res.headers.get("content-type").startsWith("text/html")) return;
const text = await res.text();
const content = new DOMParser().parseFromString(text, "text/html");
const stylesheets = [...content.querySelectorAll("link[rel='stylesheet']")];
// swap title
document.title = content.title;
// swap body html
document.body.innerHTML = content.body.innerHTML;
// swap head html
document.head.innerHTML = content.head.innerHTML;
// swap stylesheets
// const old_sheets = document.head.querySelectorAll("link[rel='stylesheet']");
// stylesheets.forEach(stylesheet => { document.head.appendChild(stylesheet) });
// old_sheets.forEach(stylesheet => { stylesheet.remove(); });
// push history
window.history.pushState({}, "", url);
bind(document.body);
}
function bind(content) {
if (typeof(content) !== 'object' || content.nodeType !== Node.ELEMENT_NODE) return;
content.querySelectorAll("a[href]").forEach(element => {
if (element.href.includes("#")) return;
if (!element.href.startsWith(window.location.origin)) return;
const href = element.href.substring(window.location.origin.length);
if (href.includes(".")) return;
element.addEventListener("click", event => {
event.preventDefault();
swap(element.href);
})
});
content.querySelectorAll("[swap-url]").forEach(element => {
const href = element.attributes.getNamedItem('swap-url').value;
element.addEventListener("click", event => {
event.preventDefault();
swap(href);
});
});
}
bind(document.body);

18
public/style/error.css Normal file
View file

@ -0,0 +1,18 @@
@import url("/style/main.css");
main {
width: min(calc(100% - 4rem), 720px);
min-height: calc(100vh - 11.5rem);
margin: 0 auto 2rem auto;
padding-top: 4rem;
}
main h1 {
line-height: 3rem;
color: #f7215b;
}
small {
font-size: 1em;
color: #aaa;
}

21
views/404.html Normal file
View file

@ -0,0 +1,21 @@
{{define "head"}}
<title>404 - ari melody 💫</title>
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
<link rel="stylesheet" href="/style/error.css">
{{end}}
{{define "content"}}
<main>
<h1>
# 404 - not found!
</h1>
<p>
the page you're looking for does not exist.
<br>
if you like, you can head back <a href="/">home</a> or <a href="{{.Target}}">try again!</a>
</p>
<p><small>status: ERR_NOT_FOUND</small></p>
</main>
{{end}}

View file

@ -9,12 +9,14 @@
{{block "head" .}}{{end}}
<meta name="htmx-config" content='{"htmx.config.scrollIntoViewOnBoost":false}'>
<script type="application/javascript" src="/script/lib/htmx.min.js"></script>
<script type="application/javascript" src="/script/lib/htmx-head-support.js"></script>
<!-- <meta name="htmx-config" content='{"htmx.config.scrollIntoViewOnBoost":false}'> -->
<!-- <script type="application/javascript" src="/script/lib/htmx.js"></script> -->
<!-- <script type="application/javascript" src="/script/lib/htmx.min.js"></script> -->
<!-- <script type="application/javascript" src="/script/lib/htmx-preload.js"></script> -->
<script type="application/javascript" src="/script/swap.js" defer></script>
</head>
<body hx-ext="head-support">
<body hx-ext="head-support, preload">
{{template "header"}}
{{block "content" .}}

View file

@ -2,7 +2,7 @@
<header>
<nav>
<div id="header-home" hx-get="/" hx-on="click" hx-target="main" hx-swap="outerHTML show:window:top" hx-push-url="true">
<div id="header-home" hx-get="/" hx-on="click" hx-target="body" hx-swap="outerHTML show:window:top" preload="mouseover" hx-push-url="true">
<img src="/img/favicon.png" id="header-icon" width="100" height="100" alt="">
<div id="header-text">
<h1>ari melody</h1>
@ -16,12 +16,12 @@
<rect y="40" width="70" height="10" rx="5" fill="#eee" />
</svg>
</a>
<ul id="header-links" hx-boost="true" hx-target="main" hx-swap="outerHTML show:window:top">
<ul id="header-links" hx-boost="true" hx-target="body" hx-swap="outerHTML show:window:top">
<li>
<a href="/">home</a>
<a href="/" preload="mouseover">home</a>
</li>
<li>
<a href="/music">music</a>
<a href="/music" preload="mouseover">music</a>
</li>
<li>
<a href="https://git.arimelody.me/ari/arimelody.me" target="_blank">source</a>

View file

@ -1,10 +0,0 @@
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{{block "head" .}}{{end}}
<meta name="htmx-config" content='{"htmx.config.scrollIntoViewOnBoost":false}'>
<script type="application/javascript" src="/script/lib/htmx.min.js"></script>
<script type="application/javascript" src="/script/lib/htmx-head-support.js"></script>
{{block "content" .}}{{end}}

View file

@ -12,7 +12,7 @@
<meta property="og:description" content="home to your local SPACEGIRL 💫">
<link rel="stylesheet" href="/style/index.css">
<script type="module" src="/script/main.js" defer></script>
<link rel="me" href="https://ice.arimelody.me/@ari">
<link rel="me" href="https://wetdry.world/@ari">
{{end}}
@ -153,6 +153,9 @@
<a href="https://mae.wtf" target="_blank">
<img src="/img/buttons/mae.png" alt="vimae web button">
</a>
<a href="https://zvava.org" target="_blank">
<img src="/img/buttons/zvava.png" alt="zvava web button">
</a>
<hr>
@ -177,5 +180,7 @@
<img src="/img/buttons/misc/epicblazed.png" alt="epic blazed">
</a>
</div>
<script type="module" src="/script/main.js" defer></script>
</main>
{{end}}

View file

@ -25,14 +25,14 @@
<meta name="twitter:image:alt" content="Cover art for &quot;{{.Title}}&quot;">
<link rel="stylesheet" href="/style/music-gateway.css">
<script type="module" src="/script/music-gateway.js" defer></script>
{{end}}
{{define "content"}}
<main>
<div id="background" data-url="{{.ResolveArtwork}}"></div>
<div id="background" style="background-image: url({{.ResolveArtwork}})"></div>
<a href="/music" id="go-back" title="back to arimelody.me">back to arimelody.me</a>
<!-- <a href="/music" hx-boost="true" hx-target="body" hx-swap="innerHTML" id="go-back" title="back to arimelody.me">back to arimelody.me</a> -->
<a href="/music" hx-boost="true" hx-target="body" hx-swap="innerHTML" id="go-back" title="back to arimelody.me">&lt;</a>
<br><br>
<div id="music-container">
@ -161,5 +161,7 @@
<!-- <% } %> -->
<!-- </div> -->
</div>
<script type="module" src="/script/music-gateway.js" defer></script>
</main>
{{end}}

View file

@ -12,7 +12,6 @@
<meta property="og:description" content="music from your local SPACEGIRL 💫">
<link rel="stylesheet" href="/style/music.css">
<script type="module" src="/script/music.js" defer></script>
{{end}}
{{define "content"}}
@ -23,11 +22,11 @@
<div id="music-container">
{{range $Album := .}}
<div class="music" id="{{$Album.Id}}" hx-get="/music/{{$Album.Id}}" hx-trigger="click" hx-target="main" hx-swap="outerHTML" hx-push-url="true">
<div class="music" id="{{$Album.Id}}" swap-url="/music/{{$Album.Id}}">
<div class="music-artwork">
<img src="{{$Album.ResolveArtwork}}" alt="{{$Album.Title}} artwork" width="128">
</div>
<div class="music-details" hx-boost="true" hx-target="main" hx-swap="outerHTML">
<div class="music-details">
<a href="/music/{{$Album.Id}}"><h1 class="music-title">{{$Album.Title}}</h1></a>
<h2 class="music-artist">{{$Album.PrintPrimaryArtists false}}</h2>
<h3 class="music-type-{{.ResolveType}}">{{$Album.ResolveType}}</h3>
@ -85,5 +84,7 @@
</div>
<a href="#" id="backtotop">back to top</a>
<script type="module" src="/script/music.js" defer></script>
</main>
{{end}}