Simple mobile support

This commit is contained in:
Cadence Ember 2025-04-09 17:42:51 +12:00
parent 924c7395cf
commit 852a053e2b
17 changed files with 207 additions and 93 deletions

View file

@ -1,7 +1,9 @@
let lastWidth = 0
function movePlayer() {
const pc = document.getElementById("player-container")
const playerExists = pc.querySelector("iframe")
if (!playerExists) return
lastWidth = window.innerWidth
const pm = document.getElementById("player-marker")
pm.style.display = "block"
pm.style.height = `${pc.clientHeight}px`
@ -9,12 +11,20 @@ function movePlayer() {
}
movePlayer()
document.body.addEventListener("htmx:load", movePlayer)
window.addEventListener("resize", () => {
if ((window.innerWidth > 980 && lastWidth < 980) || (window.innerWidth < 980 && lastWidth > 980)) {
showPlayer()
movePlayer()
}
})
function addPopoverStyle() {
document.querySelectorAll("[popovertarget]").forEach(e => {
e.addEventListener("click", () => {
const rect = e.getBoundingClientRect()
const t = `:popover-open { position: fixed; top: ${Math.floor(rect.bottom)}px; left: ${Math.floor(rect.left + rect.width / 2)}px; width: ${Math.floor(rect.width + 85)}px; transform: translateX(-50%); margin: 0 }`
const width = Math.floor(rect.width + 85)
const left = Math.max(Math.floor(rect.left + rect.width / 2), width / 2)
const t = `:popover-open { position: fixed; top: ${Math.floor(rect.bottom)}px; left: ${left}px; width: ${width}px; transform: translateX(-50%); margin: 0 }`
document.styleSheets[0].insertRule(t, document.styleSheets[0].cssRules.length)
})
})
@ -23,9 +33,32 @@ addPopoverStyle()
document.body.addEventListener("htmx:load", addPopoverStyle)
document.body.addEventListener("htmx:beforeHistoryUpdate", o => {
console.log("beforeHistoryUpdate:", o)
const page = document.getElementById("page")
if (o?.detail?.requestConfig?.target === page) {
while (page.firstChild) page.firstChild.remove()
}
})
function hidePlayer() {
document.getElementById("player-container").style.visibility = "hidden"
document.getElementById("toggle-player").setAttribute("aria-pressed", "false")
document.getElementById("toggle-player").classList.remove("is-selected")
}
function showPlayer() {
document.getElementById("player-container").style.visibility = "visible"
document.getElementById("toggle-player").setAttribute("aria-pressed", "true")
document.getElementById("toggle-player").classList.add("is-selected")
}
function togglePlayer() {
const pc = document.getElementById("player-container")
if (pc.style.visibility === "visible") hidePlayer()
else showPlayer()
}
function setupTogglePlayerButton(event) {
if (event?.target?.id === "player") showPlayer()
else hidePlayer()
document.getElementById("toggle-player").removeEventListener("click", togglePlayer)
document.getElementById("toggle-player").addEventListener("click", togglePlayer)
}
setupTogglePlayerButton()
document.body.addEventListener("htmx:load", setupTogglePlayerButton)

View file

@ -1,5 +1,17 @@
.ws340 {
width: 340px;
margin: auto;
}
@media screen and (max-width: 980px) {
.ws340 {
width: calc(100% - 32px);
max-width: 700px; /* bandcamp iframe body has this max-width */
}
}
@media screen and (min-width: 981px) {
#player-container {
visibility: visible !important;
}
}
.themed {
--theme-base-primary-color-h: 191;
@ -22,6 +34,12 @@
.s-navigation__toggle.s-navigation {
--_na-item-bg: var(--black-150);
}
@media screen and (max-width: 450px) {
.s-navigation__toggle.s-navigation .is-selected svg {
visibility: hidden;
width: 0;
}
}
.duration-last-col td:last-child {
text-align: right;
white-space: pre;
@ -48,6 +66,28 @@ button.s-link.is-loading {
.s-btn__dropdown:has(+ :popover-open) {
background-color: var(--theme-topbar-item-background-hover, var(--black-200)) !important;
}
@media screen and (max-width: 700px) {
.preview-cover:nth-of-type(n + 5) {
display: none !important;
}
}
@media screen and (max-width: 550px) {
.preview-cover:nth-of-type(n + 3) {
display: none !important;
}
}
.album-grid {
grid-template-columns: repeat(auto-fit, 210px)
}
@media screen and (max-width: 980px) {
.album-grid {
grid-template-columns: repeat(auto-fit, 150px);
}
.cover img {
width: 150px;
height: 150px;
}
}
/* album covers are done with styles instead of attributes to reduce bytes of html needing to be downloaded and parsed */
.cover {
@ -71,17 +111,17 @@ button.s-link.is-loading {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transition: 1s ease-out;
transition: opacity 1s ease-out;
color: var(--black);
}
.cover:hover svg {
transition: 1.5s ease-out 0.7s;
transition: opacity 1.5s ease-out 0.7s;
opacity: 1;
}
.cover img {
transition: 1s ease-out;
transition: opacity 1s ease-out;
}
.cover:hover img {
transition: 2s ease-out 0.7s;
transition: opacity 2s ease-out 0.7s;
opacity: 0.3;
}

View file

@ -6,7 +6,7 @@ block title
block view
.mx-auto.w100.wmx11.fs-body1#content
!= icons.useTemplate(["star-solid", "play-solid", "music-note", "compact-disc", "people-tag", "flower"])
.d-grid.gx8.gy12.jc-center.break-word(style="grid-template-columns: repeat(auto-fit, 210px)")
.d-grid.gx8.gy12.jc-center.break-word.album-grid
each item in items
div
a.cover&attributes(getAlbumCoverAttributes(event, item))
@ -16,7 +16,7 @@ block view
p= item.item_title
.d-flex.fw-wrap.g4
if item.why
a.s-tag.s-tag__xs.fc-orange-400(title=item.why href=and({filter_field: "why", filter: "reviewed"}))
a.s-tag.s-tag__xs.fc-orange-400(title=(item.why + (item.featured_track_title ? ` -- favourite track: ${item.featured_track_title}` : "")) href=and({filter_field: "why", filter: "reviewed"}))
!= icons.use("star-solid", 16)
if hasFullTrackData
a.s-tag.s-tag__xs(href=and({arrange: "track", filter_field: "item_id", filter: item.item_id, filter_fuzzy: null}))

View file

@ -10,7 +10,7 @@ block view
each item in items
.d-flex.g4
.fl-grow1.pb12
.fs-headline1= item.band_name
.fs-headline1.break-word= item.band_name
.d-flex.fw-wrap.g4
a.s-tag.s-tag__xs(href=and({arrange: "album", filter_field: "band_name", filter: item.band_name, filter_fuzzy: null}))
span.s-tag--sponsor!= icons.use("album", 16)
@ -28,5 +28,5 @@ block view
span.s-tag--sponsor!= icons.use("flower", 16)
= label
each preview in item.previews
a.d-flex&attributes(getAlbumCoverAttributes(event, preview))
a.d-flex.preview-cover(title=preview.item_title)&attributes(getAlbumCoverAttributes(event, preview))
img(loading="lazy" src=preview.item_art_url width=210 height=210 style="height: 70px; width: 70px;")

5
pug/collection-stats.pug Normal file
View file

@ -0,0 +1,5 @@
extends includes/layout.pug
block view
.mx-auto.w100.wmx11.fs-body1#content
include includes/collection-stats.pug

View file

@ -5,7 +5,7 @@ html
<meta name="viewport" content="width=device-width, initial-scale=1" />
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.css")
script(src="/static/htmx.js")
meta(name="htmx-config" content='{"requestClass":"is-loading"}')
body.themed.theme-system.overflow-y-scroll

View file

@ -1,5 +1,5 @@
mixin navi(key, value, icon, text)
a.s-navigation--item(href=and({[key]: value}) class={"is-selected": query[key] === value})&attributes(attributes)
a.s-navigation--item(href="./" + and({shape: query && query.shape || "grid", [key]: value}) class={"is-selected": query && query[key] === value})&attributes(attributes)
if icon
!= icons.get(icon)
if text
@ -19,7 +19,7 @@ html
- title = `${searchText} | ${title}`
title#title= title
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.css")
link(rel="stylesheet" type="text/css" href="/static/style.css")
script(src="/static/htmx.js")
script(src="/static/wordcloud.js")
@ -60,7 +60,7 @@ html
each currency in currencies
option(selected=(currency === count.displayCurrency))= currency
.fl-grow1
nav
nav.d-block(class="md:d-none")
ul.s-navigation
li: +navi("arrange", "album", "album", "Album")
li: +navi("arrange", "artist", "people-tag", "Artist")
@ -68,16 +68,38 @@ html
//- asana, flower, component, circle-spark, rhombus, sphere, union-alt, color-wheel, community, combine
li: +navi("arrange", "tag", "label", "Tag")
li: +navi("arrange", "track", "music-note", "Track")
.px16
li.d-none(class="md:d-block")
a.s-navigation--item(href="collection-stats" class={"is-selected": isStatsPage})
!= icons.get("graph-up")
span.ml4 Stats
ul.s-navigation.s-navigation__toggle.d-none(class="md:d-block")
button.s-navigation--item.s-navigation--item__dropdown(popovertarget="arranges")
!= icons.get("lens")
span.ml4 Arrange
#arranges(popover data-popper-placement="bottom" style="display: revert;").s-popover.overflow-visible
.s-popover--arrow.s-popover--arrow__tc
ul.s-navigation.s-navigation__vertical
li: +navi("arrange", "album", "album", "Album")
li: +navi("arrange", "artist", "people-tag", "Artist")
li: +navi("arrange", "label", "flower", "Label")
li: +navi("arrange", "tag", "label", "Tag")
li: +navi("arrange", "track", "music-note", "Track")
li
a.s-navigation--item(href="collection-stats" class={"is-selected": isStatsPage})
!= icons.get("graph-up")
span.ml4 Stats
if !isStatsPage
.px16(class="md:px4")
nav
ul.s-navigation.s-navigation__toggle.g0
li: +navi("shape", "grid").brr0!= icons.get("view-grid")
li: +navi("shape", "list").blr0!= icons.get("align-justify")
.fl-grow1
button#toggle-player.s-btn.s-btn__xs.d-none.mr4(class="md:d-block")!= icons.get("playlist")
.d-flex.py24.px16.g24.fs-body1.fd-row-reverse
.d-flex.py24.px16.g24.fs-body1.fd-row-reverse(class="md:fd-column")
aside.ws340.fl-shrink0
.ps-fixed.ws340.d-flex.fd-column.g12(style="top: 80px")
.ps-fixed.ws340.d-flex.fd-column.g12(class="md:ps-static md:jc-center" style="top: 80px")
if arrange === "tag"
include tag-status.pug
@ -99,14 +121,15 @@ html
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")
#player-marker.pe-none(style="display: none")
#player-marker.pe-none(class="md:d-none" style="display: none")
#collection-sync.d-none
div(class="md:d-none")
include collection-stats.pug
main.fl-grow1
block view
#player-container.ps-fixed.r16.ws340(hx-preserve="true")
#player-container.ps-fixed.r16.ws340.z-modal(class="md:t64 md:l16 md:r16 md:b16" hx-preserve="true")
#player

View file

@ -28,7 +28,7 @@ if downloader.total > 0 || downloader.outcome
!= icons.get("cloud-check")
.fl-grow1 Tags downloaded.
- downloader.resolve()
a.s-btn.s-btn__outlined(href="") Refresh
a.s-btn.s-btn__outlined(href=and({arrange: "tag"}) hx-boost="true") Refresh
else
.s-notice.s-notice__danger.p8.gx16.pl16.d-flex.ai-center

View file

@ -12,7 +12,7 @@ block view
.fl-grow1.pb12
- let minBandURL = item.band_url.replace(/https?:\/\/(.*?)\.bandcamp\.com.*/, "$1")
- let label = item.display_name.replace(/https?:\/\/(.*?)\.bandcamp\.com.*/, "$1")
.fs-headline1= label
.fs-headline1.break-word= label
.d-flex.fw-wrap.g4
a.s-tag.s-tag__xs(href=and({arrange: "album", filter_field: "band_url", filter: minBandURL, filter_fuzzy: null}))
span.s-tag--sponsor!= icons.use("album", 16)
@ -28,5 +28,5 @@ block view
span.s-tag--sponsor!= icons.use("compact-disc", 16)
= item.total_duration
each preview in item.previews
a.d-flex&attributes(getAlbumCoverAttributes(event, preview))
img(loading="lazy" src=preview.item_art_url width=210 height=210 style="height: auto; width: auto; max-height: 70px")
a.d-flex.preview-cover(title=preview.item_title)&attributes(getAlbumCoverAttributes(event, preview))
img(loading="lazy" src=preview.item_art_url width=210 height=210 style="height: 70px; width: 70px;")

View file

@ -18,7 +18,7 @@ sync.require("./settings")
// Files
router.get("/static/stacks.min.css", defineEventHandler({
router.get("/static/stacks.css", defineEventHandler({
onBeforeResponse: pugSync.compressResponse,
handler: async event => {
handleCacheHeaders(event, {maxAge: 86400})
@ -65,7 +65,7 @@ router.get("/static/player-marker.js", defineEventHandler({
router.get("/favicon.png", defineEventHandler({
handler: async event => {
handleCacheHeaders(event, {maxAge: 86400})
defaultContentType(event, "text/javascript")
defaultContentType(event, "image/png")
return fs.promises.readFile("public/favicon.png")
}
}))

View file

@ -1,14 +1,13 @@
// @ts-check
const {z} = require("zod")
const {defineEventHandler, getQuery, getValidatedQuery, sendRedirect, createError, getValidatedRouterParams, getCookie} = require("h3")
const {router, db, sync, select, from} = require("../passthrough")
const {router, db, sync} = require("../passthrough")
/** @type {import("../pug-sync")} */
const pugSync = sync.require("../pug-sync")
/** @type {import("./load-tags")} */
const loadTags = sync.require("./load-tags")
/** @type {import("./schema")} */
const schema = sync.require("./schema")
const sqls = {
album_grid: "SELECT item.*, count(*) AS track_count, iif(sum(duration) > 3600, cast(total(duration)/3600 AS INTEGER) || 'h ' || cast(total(duration)/60%60 AS INTEGER) || 'm', cast(total(duration)/60 AS INTEGER) || 'm') AS total_duration FROM item INNER JOIN track USING (account, item_id) {JOIN TAG} WHERE account = ? {WHERE} GROUP BY item_id {ORDER}",
@ -24,7 +23,7 @@ const sqls = {
function loadPreviews(locals, field, number, whereClause, account, filter_field, filter, filter_fuzzy) {
const params = [account, number]
let sql = `SELECT ${field}, item_id, item_type, item_url, item_art_url FROM (SELECT ${field}, item_id, item_type, item_url, item_art_url, row_number() OVER (PARTITION BY ${field} ORDER BY purchased DESC) AS row_number FROM item {JOIN TAG} WHERE account = ? {WHERE}) WHERE row_number <= ?`
let sql = `SELECT ${field}, item_id, item_title, item_type, item_url, item_art_url FROM (SELECT ${field}, item_title, item_id, item_type, item_url, item_art_url, row_number() OVER (PARTITION BY ${field} ORDER BY purchased DESC) AS row_number FROM item {JOIN TAG} WHERE account = ? {WHERE}) WHERE row_number <= ?`
sql = sql.replace("{WHERE}", whereClause)
if (whereClause) {
if (filter_field === "band_url" || filter_fuzzy) {
@ -69,25 +68,12 @@ pugSync.addGlobals({
}
})
const schema = {
query: z.object({
arrange: z.enum(["album", "artist", "label", "tag", "track"]),
shape: z.enum(["grid", "list"]),
filter_field: z.enum(["band_name", "band_url", "item_title", "item_id", "tag", "why"]).optional(),
filter: z.string().optional(),
filter_fuzzy: z.enum(["true"]).optional()
}),
params: z.object({
account: z.string()
})
}
router.get("/:account/", defineEventHandler({
onBeforeResponse: pugSync.compressResponse,
handler: async event => {
const {account} = await getValidatedRouterParams(event, schema.schema.account.parse)
try {
var {account} = await getValidatedRouterParams(event, schema.params.parse)
var {arrange, shape, filter, filter_field, filter_fuzzy} = await getValidatedQuery(event, schema.query.parse)
var {arrange, shape, filter, filter_field, filter_fuzzy} = await getValidatedQuery(event, schema.schema.appQuery.parse)
if (filter_field === "why" && arrange !== "album") throw new Error("filter not compatible with arrangement")
} catch (e) {
return sendRedirect(event, "?arrange=album&shape=grid", 302)

View file

@ -1,12 +1,14 @@
// @ts-check
const {getCookie, defineEventHandler, readValidatedBody, setCookie} = require("h3")
const {z} = require("zod")
const {getCookie, defineEventHandler, readValidatedBody, setCookie, getValidatedRouterParams} = require("h3")
const {sync, select, db, router} = require("../passthrough")
/** @type {import("../pug-sync")} */
const pugSync = sync.require("../pug-sync")
/** @type {import("./schema")} */
const schema = sync.require("./schema")
const currencyExchange = new Map([
["AUD", 0.63],
["BRL", 0.17],
@ -21,6 +23,12 @@ const currencyExchange = new Map([
])
const currencies = [...currencyExchange.keys()]
pugSync.beforeInclude("includes/layout.pug", async (from, event, locals) => {
return {
currencies
}
})
pugSync.beforeInclude("includes/collection-stats.pug", async (from, event, {account, currency}) => {
let displayCurrency = currency || getCookie(event, "bcex-currency") || ""
if (!currencyExchange.has(displayCurrency)) displayCurrency = "NZD"
@ -44,20 +52,17 @@ pugSync.beforeInclude("includes/collection-stats.pug", async (from, event, {acco
}).reduce((a, c) => a + c, 0)) * currencyRoundTo,
displayCurrency,
taste: db.prepare("with popularity (a) as (select avg(also_collected_count) from item WHERE account = ? group by band_url) select sum(iif(a >= 0 and a < 20, 1, 0)) as cold, sum(iif(a >= 20 and a < 200, 1, 0)) as warm, sum(iif(a >= 200 and a < 2000, 1, 0)) as hot, sum(iif(a >= 2000, 1, 0)) as supernova from popularity").raw().get(account)
},
currencies
}
}
})
const schema = {
currency: z.object({
currency: z.string().regex(/^[A-Z]{3}$/),
account: z.string()
})
}
router.get("/:account/collection-stats", defineEventHandler(async event => {
const {account} = await getValidatedRouterParams(event, schema.schema.account.parse)
return pugSync.render(event, "collection-stats.pug", {account, isStatsPage: true})
}))
router.post("/api/settings/currency", defineEventHandler(async event => {
const {currency, account} = await readValidatedBody(event, schema.currency.parse)
const {currency, account} = await readValidatedBody(event, schema.schema.postCurrency.parse)
setCookie(event, "bcex-currency", currency)
return pugSync.render(event, "includes/collection-stats.pug", {account, currency})
}))

View file

@ -1,23 +1,26 @@
// @ts-check
const assert = require("assert/strict")
const fs = require("fs")
const sqlite = require("better-sqlite3")
const domino = require("domino")
const {defineEventHandler, readValidatedBody, setCookie, getCookie} = require("h3")
const {z} = require("zod")
const {sync, db, router} = require("../passthrough")
/** @type {import("../pug-sync")} */
const pugSync = sync.require("../pug-sync")
/** @type {import("./load-tags")} */
const loadTags = sync.require("./load-tags")
/** @type {import("./schema")} */
const schema = sync.require("./schema")
async function loadCollection(inputUsername) {
assert.match(inputUsername, /^[a-z0-9_-]+$/)
const html = await fetch(`https://bandcamp.com/${inputUsername}`).then(res => res.text())
const doc = domino.createDocument(html)
const first = doc.querySelector(".collection-item-container")
const first = doc.querySelector(".collection-item-container[data-token]")
assert(first)
const token = first.getAttribute("data-token")
assert(token)
@ -49,6 +52,7 @@ async function loadCollection(inputUsername) {
const preparedItem = db.prepare(`INSERT INTO item (${columns.join(", ")}) VALUES (${columns.map(x => "@" + x).join(", ")}) ON CONFLICT DO UPDATE SET ${upsert_columns.map(x => `${x} = @${x}`).join(", ")}`)
db.transaction(() => {
for (const item of items.items) {
if (!item.tralbum_type.match(/[at]/)) continue // p=product and s=subscription not supported
preparedItem.run({
account,
...item,
@ -88,12 +92,8 @@ async function loadCollection(inputUsername) {
}
}
const schema = z.object({
account: z.string()
})
router.post("/api/load-collection", defineEventHandler(async event => {
const {account} = await readValidatedBody(event, schema.parse)
const {account} = await readValidatedBody(event, schema.schema.account.parse)
const result = await loadCollection(account)
setCookie(event, "accounts", (getCookie(event, "accounts") || "").split("|").concat(account).join("|"))
return pugSync.render(event, "collection-loaded.pug", result)

View file

@ -1,12 +1,16 @@
// @ts-check
const domino = require("domino")
const {z} = require("zod")
const {getValidatedQuery, readValidatedBody, defineEventHandler} = require("h3")
const {sync, db, from, router} = require("../passthrough")
const {sync, db, router} = require("../passthrough")
/** @type {import("../pug-sync")} */
const pugSync = sync.require("../pug-sync")
/** @type {import("./schema")} */
const schema = sync.require("./schema")
const insertTag = db.prepare("INSERT OR IGNORE INTO item_tag (account, item_id, tag) VALUES (?, ?, ?)")
const insertTrack = 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)")
@ -118,17 +122,13 @@ const downloadManager = new class {
}
}
const schema = z.object({
account: z.string()
})
router.get("/api/tag-download", defineEventHandler(async event => {
const {account} = await getValidatedQuery(event, schema.parse)
const {account} = await getValidatedQuery(event, schema.schema.account.parse)
return pugSync.render(event, "includes/tag-status.pug", {account})
}))
router.post("/api/tag-download", defineEventHandler(async event => {
const {account} = await readValidatedBody(event, schema.parse)
const {account} = await readValidatedBody(event, schema.schema.account.parse)
downloadManager.start(account)
return pugSync.render(event, "includes/tag-status.pug", {account})
}))

View file

@ -1,6 +1,5 @@
// @ts-check
const {z} = require("zod")
const {sync, router} = require("../passthrough")
const {defineEventHandler} = require("h3")
const {getValidatedRouterParams} = require("h3")
@ -8,14 +7,11 @@ 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}),
track_id: z.number({coerce: true}).optional()
})
/** @type {import("./schema")} */
const schema = sync.require("./schema")
const play = defineEventHandler(async event => {
const locals = await getValidatedRouterParams(event, schema.parse)
const locals = await getValidatedRouterParams(event, schema.schema.play.parse)
return pugSync.render(event, "player.pug", locals)
})

30
routes/schema.js Normal file
View file

@ -0,0 +1,30 @@
// @ts-check
const {z} = require("zod")
const schema = {
appQuery: z.object({
arrange: z.enum(["album", "artist", "label", "tag", "track"]),
shape: z.enum(["grid", "list"]),
filter_field: z.enum(["band_name", "band_url", "item_title", "item_id", "tag", "why"]).optional(),
filter: z.string().optional(),
filter_fuzzy: z.enum(["true"]).optional()
}),
account: z.object({
account: z.string().regex(/^[a-z0-9_-]+$/)
}),
postCurrency: z.object({
currency: z.string().regex(/^[A-Z]{3}$/),
account: z.string()
}),
play: z.object({
item_type: z.enum(["album", "track"]),
item_id: z.number({coerce: true}),
track_id: z.number({coerce: true}).optional()
}),
inlinePlayer: z.object({
inline_player: z.string().optional()
})
}
module.exports.schema = schema

View file

@ -1,17 +1,13 @@
// @ts-check
const {z} = require("zod")
const {router} = require("../passthrough")
const {sync, router} = require("../passthrough")
const {defineEventHandler, readValidatedBody, setCookie, setResponseHeader} = require("h3")
const schema = {
inline_player: z.object({
inline_player: z.string().optional()
})
}
/** @type {import("./schema")} */
const schema = sync.require("./schema")
router.post("/api/settings/inline-player", defineEventHandler(async event => {
const {inline_player} = await readValidatedBody(event, schema.inline_player.parse)
const {inline_player} = await readValidatedBody(event, schema.schema.inlinePlayer.parse)
setCookie(event, "bcex-inline-player-disabled", String(!inline_player))
setResponseHeader(event, "HX-Refresh", "true")
return null