Add embedded player
This commit is contained in:
parent
70ce8ab72b
commit
aa1095eef2
11 changed files with 160 additions and 110 deletions
10
public/player-marker.js
Normal file
10
public/player-marker.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
function movePlayer() {
|
||||||
|
const pc = document.getElementById("player-container")
|
||||||
|
const playerExists = pc.querySelector("iframe")
|
||||||
|
if (!playerExists) return
|
||||||
|
const pm = document.getElementById("player-marker")
|
||||||
|
pm.style.height = `${pc.clientHeight}px`
|
||||||
|
pc.style.top = `${Math.round(pm.getBoundingClientRect().top)}px`
|
||||||
|
}
|
||||||
|
movePlayer()
|
||||||
|
document.body.addEventListener("htmx:afterSettle", movePlayer)
|
|
@ -8,7 +8,7 @@ block view
|
||||||
.d-grid.gx8.gy12.jc-center.break-word(style="grid-template-columns: repeat(auto-fit, 210px)")
|
.d-grid.gx8.gy12.jc-center.break-word(style="grid-template-columns: repeat(auto-fit, 210px)")
|
||||||
each item in items
|
each item in items
|
||||||
div
|
div
|
||||||
a.album-grid-link(href=item.item_url target="_blank")
|
a.s-link.album-grid-link(href=`/api/play/${item.item_type}/${item.item_id}` hx-target="#player" hx-select="#player" hx-indicator="null" hx-push-url="false")
|
||||||
img(loading="lazy" src=item.item_art_url width=210 height=210)
|
img(loading="lazy" src=item.item_art_url width=210 height=210)
|
||||||
p.fs-body3.mb8= item.item_title
|
p.fs-body3.mb8= item.item_title
|
||||||
.d-flex.fw-wrap.g4
|
.d-flex.fw-wrap.g4
|
||||||
|
|
|
@ -4,6 +4,7 @@ html
|
||||||
meta(charset="utf-8")
|
meta(charset="utf-8")
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
title BC Explorer
|
title BC Explorer
|
||||||
|
link(rel="icon" href="/favicon.png")
|
||||||
link(rel="stylesheet" type="text/css" href="/static/stacks.min.css")
|
link(rel="stylesheet" type="text/css" href="/static/stacks.min.css")
|
||||||
script(src="/static/htmx.js")
|
script(src="/static/htmx.js")
|
||||||
meta(name="htmx-config" content='{"requestClass":"is-loading"}')
|
meta(name="htmx-config" content='{"requestClass":"is-loading"}')
|
||||||
|
|
|
@ -22,6 +22,7 @@ html
|
||||||
link(rel="stylesheet" type="text/css" href="/static/stacks.min.css")
|
link(rel="stylesheet" type="text/css" href="/static/stacks.min.css")
|
||||||
script(src="/static/htmx.js")
|
script(src="/static/htmx.js")
|
||||||
script(src="/static/wordcloud.js")
|
script(src="/static/wordcloud.js")
|
||||||
|
script(defer src="/static/player-marker.js")
|
||||||
meta(name="htmx-config" content='{"requestClass":"is-loading"}')
|
meta(name="htmx-config" content='{"requestClass":"is-loading"}')
|
||||||
style.
|
style.
|
||||||
.themed {
|
.themed {
|
||||||
|
@ -69,108 +70,112 @@ html
|
||||||
svg {
|
svg {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
body.themed.theme-system.overflow-y-scroll(hx-boost="true")
|
body.themed.theme-system.overflow-y-scroll(hx-boost="true" hx-swap="outerHTML" hx-target="#page" hx-select="#page")
|
||||||
header.s-topbar.ps-sticky.t0
|
#page
|
||||||
.s-topbar--container.wmx9
|
header.s-topbar.ps-sticky.t0
|
||||||
.s-topbar--logo
|
.s-topbar--container.wmx9
|
||||||
!= icons.get("compass-solid", 24)
|
.s-topbar--logo
|
||||||
.ml4 BC Explorer
|
!= icons.get("compass-solid", 24)
|
||||||
.fl-grow1
|
.ml4 BC Explorer
|
||||||
nav
|
.fl-grow1
|
||||||
ul.s-navigation
|
nav
|
||||||
li: +navi("arrange", "album", "album", "Album")
|
ul.s-navigation
|
||||||
li: +navi("arrange", "artist", "people-tag", "Artist")
|
li: +navi("arrange", "album", "album", "Album")
|
||||||
li: +navi("arrange", "label", "flower", "Label")
|
li: +navi("arrange", "artist", "people-tag", "Artist")
|
||||||
//- asana, flower, component, circle-spark, rhombus, sphere, union-alt, color-wheel, community, combine
|
li: +navi("arrange", "label", "flower", "Label")
|
||||||
li: +navi("arrange", "tag", "label", "Tag")
|
//- asana, flower, component, circle-spark, rhombus, sphere, union-alt, color-wheel, community, combine
|
||||||
li: +navi("arrange", "track", "music-note", "Track")
|
li: +navi("arrange", "tag", "label", "Tag")
|
||||||
.px16
|
li: +navi("arrange", "track", "music-note", "Track")
|
||||||
nav
|
.px16
|
||||||
ul.s-navigation.s-navigation__toggle.g0
|
nav
|
||||||
li: +navi("shape", "grid").brr0!= icons.get("view-grid")
|
ul.s-navigation.s-navigation__toggle.g0
|
||||||
li: +navi("shape", "list").blr0!= icons.get("align-justify")
|
li: +navi("shape", "grid").brr0!= icons.get("view-grid")
|
||||||
.fl-grow1
|
li: +navi("shape", "list").blr0!= icons.get("align-justify")
|
||||||
#player(hx-preserve)
|
.fl-grow1
|
||||||
button.s-btn.s-btn__outlined.s-btn__xs!= icons.get("play")
|
|
||||||
|
|
||||||
.d-flex.py24.px16.g24.fs-body1
|
.d-flex.py24.px16.g24.fs-body1
|
||||||
.fl-grow1
|
main.fl-grow1
|
||||||
block view
|
block view
|
||||||
|
|
||||||
div
|
aside.ws3
|
||||||
.ps-sticky.d-flex.fd-column.g12.wmx4(style="top: 80px")
|
.ps-sticky.d-flex.fd-column.g12(style="top: 80px")
|
||||||
if arrange === "tag"
|
if arrange === "tag"
|
||||||
include tag-status.pug
|
include tag-status.pug
|
||||||
|
|
||||||
if filter && filter_field
|
if filter && filter_field
|
||||||
.s-sidebarwidget.s-sidebarwidget__blue.d-flex.ai-center.gx16.jc-space-between.p8.pl16
|
.s-sidebarwidget.s-sidebarwidget__blue.d-flex.ai-center.gx16.jc-space-between.p8.pl16
|
||||||
!= icons.get("search")
|
!= icons.get("search")
|
||||||
.fl-grow1 Searching for #[strong= searchText]
|
.fl-grow1 Searching for #[strong= searchText]
|
||||||
a.s-btn.s-notice--btn(href=and({filter: null, filter_field: null, filter_fuzzy: null})) Clear
|
a.s-btn.s-notice--btn(href=and({filter: null, filter_field: null, filter_fuzzy: null})) Clear
|
||||||
else
|
else
|
||||||
form.d-flex.ai-stretch.gx8.jc-space-between.baw0(hx-indicator="#search-submit")
|
form.d-flex.ai-stretch.gx8.jc-space-between.baw0(hx-indicator="#search-submit")
|
||||||
input.s-input(name="filter" placeholder="Search" autocomplete="off").fl-grow1
|
input.s-input(name="filter" placeholder="Search" autocomplete="off").fl-grow1
|
||||||
input(type="hidden" name="filter_field" value=
|
input(type="hidden" name="filter_field" value=
|
||||||
( arrange === "artist" ? "band_name"
|
( arrange === "artist" ? "band_name"
|
||||||
: arrange === "label" ? "band_url"
|
: arrange === "label" ? "band_url"
|
||||||
: arrange === "tag" ? "tag"
|
: arrange === "tag" ? "tag"
|
||||||
: "item_title"))
|
: "item_title"))
|
||||||
input(type="hidden" name="filter_fuzzy" value="true")
|
input(type="hidden" name="filter_fuzzy" value="true")
|
||||||
input(type="hidden" name="arrange" value=arrange)
|
input(type="hidden" name="arrange" value=arrange)
|
||||||
input(type="hidden" name="shape" value=shape)
|
input(type="hidden" name="shape" value=shape)
|
||||||
button.s-btn.s-btn__xs.s-btn__icon.s-btn__outlined.s-btn__muted#search-submit(style="height: 38px")!= icons.get("search")
|
button.s-btn.s-btn__xs.s-btn__icon.s-btn__outlined.s-btn__muted#search-submit(style="height: 38px")!= icons.get("search")
|
||||||
|
|
||||||
.s-sidebarwidget.wmn3
|
#player-marker.pe-none.myn6
|
||||||
.s-sidebarwidget--header Collection
|
|
||||||
table.s-sidebarwidget--content.s-sidebarwidget__items
|
.s-sidebarwidget
|
||||||
tr.s-sidebarwidget--item
|
.s-sidebarwidget--header Collection
|
||||||
th items
|
table.s-sidebarwidget--content.s-sidebarwidget__items
|
||||||
td= count.total
|
tr.s-sidebarwidget--item
|
||||||
tr.s-sidebarwidget--item
|
th items
|
||||||
th runtime
|
td= count.total
|
||||||
td= count.runtime
|
tr.s-sidebarwidget--item
|
||||||
tr.s-sidebarwidget--item
|
th runtime
|
||||||
th format
|
td= count.runtime
|
||||||
td
|
tr.s-sidebarwidget--item
|
||||||
= count.albums
|
th format
|
||||||
span.fc-black-400= ` albums`
|
td
|
||||||
span.fc-black-250= ` / `
|
= count.albums
|
||||||
= count.singles
|
span.fc-black-400= ` albums`
|
||||||
span.fc-black-400= ` singles`
|
span.fc-black-250= ` / `
|
||||||
tr.s-sidebarwidget--item
|
= count.singles
|
||||||
th price
|
span.fc-black-400= ` singles`
|
||||||
td
|
tr.s-sidebarwidget--item
|
||||||
= count.free
|
th price
|
||||||
span.fc-black-400= ` free`
|
td
|
||||||
span.fc-black-250= ` / `
|
= count.free
|
||||||
= count.paid
|
span.fc-black-400= ` free`
|
||||||
span.fc-black-400= ` paid`
|
span.fc-black-250= ` / `
|
||||||
tr.s-sidebarwidget--item
|
= count.paid
|
||||||
th tracks
|
span.fc-black-400= ` paid`
|
||||||
td
|
tr.s-sidebarwidget--item
|
||||||
= count.tracks
|
th tracks
|
||||||
span.pl8.fc-black-250= ` / `
|
td
|
||||||
span.fc-black-400 avg #{count.avgTracks}
|
= count.tracks
|
||||||
tr.s-sidebarwidget--item
|
span.pl8.fc-black-250= ` / `
|
||||||
th tags
|
span.fc-black-400 avg #{count.avgTracks}
|
||||||
td
|
tr.s-sidebarwidget--item
|
||||||
= count.tags
|
th tags
|
||||||
span.pl8.fc-black-250= ` / `
|
td
|
||||||
span.fc-black-400 avg #{count.avgTags}
|
= count.tags
|
||||||
span.fc-black-250= ` / `
|
span.pl8.fc-black-250= ` / `
|
||||||
span.fc-black-400 lonely #{count.lonelyTags}
|
span.fc-black-400 avg #{count.avgTags}
|
||||||
tr.s-sidebarwidget--item
|
span.fc-black-250= ` / `
|
||||||
th value
|
span.fc-black-400 lonely #{count.lonelyTags}
|
||||||
td
|
tr.s-sidebarwidget--item
|
||||||
= `${count.displayCurrencySymbol}${count.value} `
|
th value
|
||||||
span.fc-black-400 #{count.displayCurrency}
|
td
|
||||||
tr.s-sidebarwidget--item
|
= `${count.displayCurrencySymbol}${count.value} `
|
||||||
th diversity
|
span.fc-black-400 #{count.displayCurrency}
|
||||||
//- supernova red-500, warm yellow-500, hot orange-500
|
tr.s-sidebarwidget--item
|
||||||
//- 0-9 black, 10-99 yellow, 100-999 orange, 1000+ red
|
th diversity
|
||||||
td.w100
|
//- supernova red-500, warm yellow-500, hot orange-500
|
||||||
.s-progress.d-grid.g2.h4.mtn6(style=`grid-template-columns: ${count.taste.map(t => t + "fr").join(" ")}`).bg-white.fc-black-400.fs-fine.lh-xxl
|
//- 0-9 black, 10-99 yellow, 100-999 orange, 1000+ red
|
||||||
.s-progress--bar.bg-black-400(title=`${count.taste[0]} labels with <20 fans`)= count.taste[0]
|
td.w100
|
||||||
.s-progress--bar.bg-yellow-400(title=`${count.taste[1]} labels with 20-199 fans`)= count.taste[1]
|
.s-progress.d-grid.g2.h4.mtn6(style=`grid-template-columns: ${count.taste.map(t => t + "fr").join(" ")}`).bg-white.fc-black-400.fs-fine.lh-xxl
|
||||||
.s-progress--bar.bg-orange-400(title=`${count.taste[2]} labels with 200-1999 fans`)= count.taste[2]
|
.s-progress--bar.bg-black-400(title=`${count.taste[0]} labels with <20 fans`)= count.taste[0]
|
||||||
.s-progress--bar.bg-red-400(title=`${count.taste[3]} labels with >2000 fans`)= count.taste[3]
|
.s-progress--bar.bg-yellow-400(title=`${count.taste[1]} labels with 20-199 fans`)= count.taste[1]
|
||||||
|
.s-progress--bar.bg-orange-400(title=`${count.taste[2]} labels with 200-1999 fans`)= count.taste[2]
|
||||||
|
.s-progress--bar.bg-red-400(title=`${count.taste[3]} labels with >2000 fans`)= count.taste[3]
|
||||||
|
|
||||||
|
#player-container.ps-fixed.r16.ws3(hx-preserve="true")
|
||||||
|
#player
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
if downloader.total > 0 || downloader.outcome
|
if downloader.total > 0 || downloader.outcome
|
||||||
#tag-download
|
#tag-download
|
||||||
if !downloader.running && !downloader.outcome
|
if !downloader.running && !downloader.outcome
|
||||||
form.s-notice.s-notice__info.d-flex.ai-center.p8.gx16.pl16(role="status" hx-swap="outerHTML" hx-target="#tag-download" hx-post="/api/tag-download")
|
form.s-notice.s-notice__info.d-flex.ai-center.p8.gx16.pl16(role="status" hx-target="#tag-download" hx-post="/api/tag-download")
|
||||||
!= icons.get("info-circle")
|
!= icons.get("info-circle")
|
||||||
div Tag data needs to be downloaded. This will take a while.
|
div Tag data needs to be downloaded. This will take a while.
|
||||||
input(type="hidden" name="account" value=account)
|
input(type="hidden" name="account" value=account)
|
||||||
button.s-btn.s-btn__outlined Download now
|
button.s-btn.s-btn__outlined Download now
|
||||||
|
|
||||||
else if !downloader.outcome
|
else if !downloader.outcome
|
||||||
.s-notice.p16(role="status" hx-swap="outerHTML" hx-target="#tag-download" hx-get=`/api/tag-download?account=${account}` hx-trigger="every 5s" hx-indicator="null")
|
.s-notice.p16(role="status" hx-target="#tag-download" hx-get=`/api/tag-download?account=${account}` hx-trigger="every 5s" hx-indicator="null")
|
||||||
.d-flex.gx16.ai-center
|
.d-flex.gx16.ai-center
|
||||||
!= icons.get("cloud-download")
|
!= icons.get("cloud-download")
|
||||||
.fl-grow1 Downloading tags...
|
.fl-grow1 Downloading tags...
|
||||||
|
@ -23,7 +23,7 @@ if downloader.total > 0 || downloader.outcome
|
||||||
span= downloader.total
|
span= downloader.total
|
||||||
|
|
||||||
else if downloader.outcome === "Success"
|
else if downloader.outcome === "Success"
|
||||||
.s-notice.s-notice__success.p8.gx16.pl16.d-flex.ai-center.wmn3
|
.s-notice.s-notice__success.p8.gx16.pl16.d-flex.ai-center
|
||||||
title#title(hx-swap-oob="true") * Tags downloaded! | BC Explorer
|
title#title(hx-swap-oob="true") * Tags downloaded! | BC Explorer
|
||||||
!= icons.get("cloud-check")
|
!= icons.get("cloud-check")
|
||||||
.fl-grow1 Tags downloaded.
|
.fl-grow1 Tags downloaded.
|
||||||
|
@ -31,7 +31,7 @@ if downloader.total > 0 || downloader.outcome
|
||||||
a.s-btn.s-btn__outlined(href="") Refresh
|
a.s-btn.s-btn__outlined(href="") Refresh
|
||||||
|
|
||||||
else
|
else
|
||||||
.s-notice.s-notice__danger.p8.gx16.pl16.d-flex.ai-center.wmn3
|
.s-notice.s-notice__danger.p8.gx16.pl16.d-flex.ai-center
|
||||||
title#title(hx-swap-oob="true") * Tag download failed! | BC Explorer
|
title#title(hx-swap-oob="true") * Tag download failed! | BC Explorer
|
||||||
!= icons.get("cloud-xmark")
|
!= icons.get("cloud-xmark")
|
||||||
.fl-grow1= downloader.outcome
|
.fl-grow1= downloader.outcome
|
||||||
|
|
5
pug/player.pug
Normal file
5
pug/player.pug
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
#player
|
||||||
|
.s-sidebarwidget(style="overflow: hidden")
|
||||||
|
div(style="margin: -1px; margin-bottom: -11px").ps-relative
|
||||||
|
a.ps-absolute.bg-white.bar0.t0.r0.s-btn.s-btn__icon.s-btn__muted.s-btn__sm.px16(href=`/api/play/${item_type}/${item_id}` hx-target="#player" hx-select="#player" hx-push-url="false").fc-theme-primary!= icons.get("refresh-double")
|
||||||
|
iframe(style="border: 0; width: 100%; height: 424px;" src=`https://bandcamp.com/EmbeddedPlayer/${item_type}=${item_id}/size=large/bgcol=ffffff/linkcol=63b2cc/artwork=none/transparent=true/`)
|
|
@ -67,7 +67,7 @@ const schema = {
|
||||||
query: z.object({
|
query: z.object({
|
||||||
arrange: z.enum(["album", "artist", "label", "tag", "track"]),
|
arrange: z.enum(["album", "artist", "label", "tag", "track"]),
|
||||||
shape: z.enum(["grid", "list"]),
|
shape: z.enum(["grid", "list"]),
|
||||||
filter_field: z.enum(["band_name", "band_url", "item_id", "tag", "why"]).optional(),
|
filter_field: z.enum(["band_name", "band_url", "item_title", "item_id", "tag", "why"]).optional(),
|
||||||
filter: z.string().optional(),
|
filter: z.string().optional(),
|
||||||
filter_fuzzy: z.enum(["true"]).optional()
|
filter_fuzzy: z.enum(["true"]).optional()
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -61,7 +61,7 @@ async function loadCollection(inputUsername) {
|
||||||
const preparedTrack = db.prepare("INSERT OR IGNORE INTO track (account, item_id, track_id, title, artist, track_number, duration) VALUES (@account, @item_id, @track_id, @title, @artist, @track_number, @duration)")
|
const preparedTrack = db.prepare("INSERT OR IGNORE INTO track (account, item_id, track_id, title, artist, track_number, duration) VALUES (@account, @item_id, @track_id, @title, @artist, @track_number, @duration)")
|
||||||
db.transaction(() => {
|
db.transaction(() => {
|
||||||
for (const [key, tracklist] of Object.entries(items.tracklists)) {
|
for (const [key, tracklist] of Object.entries(items.tracklists)) {
|
||||||
assert.match(key[0], /[at]/)
|
if (!key[0].match(/[at]/)) continue
|
||||||
for (const track of tracklist) {
|
for (const track of tracklist) {
|
||||||
preparedTrack.run({
|
preparedTrack.run({
|
||||||
account,
|
account,
|
||||||
|
|
19
routes/play.js
Normal file
19
routes/play.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const {z} = require("zod")
|
||||||
|
const {sync, router} = require("../passthrough")
|
||||||
|
const {defineEventHandler} = require("h3")
|
||||||
|
const {getValidatedRouterParams} = require("h3")
|
||||||
|
|
||||||
|
/** @type {import("../pug-sync")} */
|
||||||
|
const pugSync = sync.require("../pug-sync")
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
item_type: z.enum(["album", "track"]),
|
||||||
|
item_id: z.number({coerce: true})
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get("/api/play/:item_type/:item_id", defineEventHandler(async event => {
|
||||||
|
const locals = await getValidatedRouterParams(event, schema.parse)
|
||||||
|
return pugSync.render(event, "player.pug", locals)
|
||||||
|
}))
|
|
@ -58,7 +58,7 @@ const har = JSON.parse(fs.readFileSync("scripts/account.har", "utf8"))
|
||||||
const preparedTrack = db.prepare("INSERT OR IGNORE INTO track (account, item_id, track_id, title, artist, track_number, duration, mp3) VALUES (@account, @item_id, @track_id, @title, @artist, @track_number, @duration, @mp3)")
|
const preparedTrack = db.prepare("INSERT OR IGNORE INTO track (account, item_id, track_id, title, artist, track_number, duration, mp3) VALUES (@account, @item_id, @track_id, @title, @artist, @track_number, @duration, @mp3)")
|
||||||
db.transaction(() => {
|
db.transaction(() => {
|
||||||
for (const [key, tracklist] of Object.entries(items.tracklists)) {
|
for (const [key, tracklist] of Object.entries(items.tracklists)) {
|
||||||
assert.match(key[0], /[at]/)
|
if (!key[0].match(/[at]/)) continue
|
||||||
for (const track of tracklist) {
|
for (const track of tracklist) {
|
||||||
preparedTrack.run({
|
preparedTrack.run({
|
||||||
account,
|
account,
|
||||||
|
|
12
start.js
12
start.js
|
@ -36,6 +36,7 @@ pugSync.createRoute(router, "/", "home.pug")
|
||||||
|
|
||||||
sync.require("./routes/app")
|
sync.require("./routes/app")
|
||||||
sync.require("./routes/load-collection")
|
sync.require("./routes/load-collection")
|
||||||
|
sync.require("./routes/play")
|
||||||
|
|
||||||
// Files
|
// Files
|
||||||
|
|
||||||
|
@ -53,7 +54,9 @@ router.get("/static/htmx.js", defineEventHandler({
|
||||||
handler: async event => {
|
handler: async event => {
|
||||||
handleCacheHeaders(event, {maxAge: 86400})
|
handleCacheHeaders(event, {maxAge: 86400})
|
||||||
defaultContentType(event, "text/javascript")
|
defaultContentType(event, "text/javascript")
|
||||||
return fs.promises.readFile(require.resolve("htmx.org/dist/htmx.js"), "utf-8")
|
return Promise.all(["htmx.org/dist/htmx.js"].map(r =>
|
||||||
|
fs.promises.readFile(require.resolve(r), "utf-8")
|
||||||
|
)).then(files => files.join("\n\n\n"))
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
@ -66,6 +69,13 @@ router.get("/static/wordcloud.js", defineEventHandler({
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
router.get("/static/player-marker.js", defineEventHandler({
|
||||||
|
handler: async event => {
|
||||||
|
defaultContentType(event, "text/javascript")
|
||||||
|
return fs.promises.readFile("public/player-marker.js")
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
router.get("/favicon.png", defineEventHandler({
|
router.get("/favicon.png", defineEventHandler({
|
||||||
handler: async event => {
|
handler: async event => {
|
||||||
handleCacheHeaders(event, {maxAge: 86400})
|
handleCacheHeaders(event, {maxAge: 86400})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue