Add tag cloud
This commit is contained in:
parent
15ff7e5b47
commit
44e1b73b1f
7 changed files with 131 additions and 39 deletions
7
package-lock.json
generated
7
package-lock.json
generated
|
@ -19,6 +19,7 @@
|
||||||
"h3": "^1.15.1",
|
"h3": "^1.15.1",
|
||||||
"heatsync": "^2.8.1",
|
"heatsync": "^2.8.1",
|
||||||
"htmx.org": "^2.0.4",
|
"htmx.org": "^2.0.4",
|
||||||
|
"wordcloud": "^1.2.3",
|
||||||
"zod": "^3.24.2"
|
"zod": "^3.24.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -983,6 +984,12 @@
|
||||||
"node": ">= 10.0.0"
|
"node": ">= 10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wordcloud": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/wordcloud/-/wordcloud-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-9by77b7Sd9e1K75kSmVeAD+JnGpiLR1Z4EX1mYQL91jKrU1/4bHw4h4DExQ+dzfT+PvihDcH7OS7V4Y5UkbF2w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/wrappy": {
|
"node_modules/wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
"h3": "^1.15.1",
|
"h3": "^1.15.1",
|
||||||
"heatsync": "^2.8.1",
|
"heatsync": "^2.8.1",
|
||||||
"htmx.org": "^2.0.4",
|
"htmx.org": "^2.0.4",
|
||||||
|
"wordcloud": "^1.2.3",
|
||||||
"zod": "^3.24.2"
|
"zod": "^3.24.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,12 @@ block view
|
||||||
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
|
||||||
span.s-tag.s-tag__xs
|
|
||||||
span.s-tag--sponsor!= icons.get("compact-disc", 16)
|
|
||||||
= item.total_duration
|
|
||||||
a.s-tag.s-tag__xs(href=and({arrange: "track", filter_field: "item_id", filter: item.item_id}))
|
a.s-tag.s-tag__xs(href=and({arrange: "track", filter_field: "item_id", filter: item.item_id}))
|
||||||
span.s-tag--sponsor!= icons.get("music-note", 16)
|
span.s-tag--sponsor!= icons.get("music-note", 16)
|
||||||
= item.track_count
|
= item.track_count
|
||||||
|
span.s-tag.s-tag__xs
|
||||||
|
span.s-tag--sponsor!= icons.get("compact-disc", 16)
|
||||||
|
= item.total_duration
|
||||||
a.s-tag.s-tag__xs(href=and({filter_field: "band_name", filter: item.band_name}))
|
a.s-tag.s-tag__xs(href=and({filter_field: "band_name", filter: item.band_name}))
|
||||||
span.s-tag--sponsor!= icons.get("people-tag", 16)
|
span.s-tag--sponsor!= icons.get("people-tag", 16)
|
||||||
= item.band_name
|
= item.band_name
|
||||||
|
|
|
@ -14,6 +14,7 @@ html
|
||||||
title BC Explorer
|
title BC Explorer
|
||||||
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")
|
||||||
meta(name="htmx-config" content='{"requestClass":"is-loading"}')
|
meta(name="htmx-config" content='{"requestClass":"is-loading"}')
|
||||||
style.
|
style.
|
||||||
.themed {
|
.themed {
|
||||||
|
@ -40,11 +41,17 @@ html
|
||||||
.s-sidebarwidget th {
|
.s-sidebarwidget th {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
color: var(--black-400);
|
color: var(--black-400);
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
.album-grid-link {
|
.album-grid-link {
|
||||||
--_li-fc: var(--black);
|
--_li-fc: var(--black);
|
||||||
--_li-fc-visited: var(--black-400);
|
--_li-fc-visited: var(--black-400);
|
||||||
}
|
}
|
||||||
|
.wc-hl {
|
||||||
|
cursor: pointer;
|
||||||
|
color: blue;
|
||||||
|
outline: 1px solid black;
|
||||||
|
}
|
||||||
body.themed.theme-system.overflow-y-scroll(hx-boost="true")
|
body.themed.theme-system.overflow-y-scroll(hx-boost="true")
|
||||||
header.s-topbar
|
header.s-topbar
|
||||||
.s-topbar--container.wmx9
|
.s-topbar--container.wmx9
|
||||||
|
@ -81,15 +88,7 @@ html
|
||||||
.s-sidebarwidget.wmn3
|
.s-sidebarwidget.wmn3
|
||||||
.s-sidebarwidget--header Collection
|
.s-sidebarwidget--header Collection
|
||||||
table.s-sidebarwidget--content.s-sidebarwidget__items
|
table.s-sidebarwidget--content.s-sidebarwidget__items
|
||||||
tr.s-sidebarwidget--item
|
each stat in count
|
||||||
th albums
|
tr.s-sidebarwidget--item
|
||||||
td= albumCount
|
th= stat[0]
|
||||||
tr.s-sidebarwidget--item
|
td= stat[1]
|
||||||
th singles
|
|
||||||
td= singleCount
|
|
||||||
tr.s-sidebarwidget--item
|
|
||||||
th tracks
|
|
||||||
td= trackCount
|
|
||||||
tr.s-sidebarwidget--item
|
|
||||||
th value
|
|
||||||
td #{displayCurrencySymbol}#{purchaseValue} #{displayCurrency}
|
|
||||||
|
|
40
pug/tag_grid.pug
Normal file
40
pug/tag_grid.pug
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
extends includes/layout.pug
|
||||||
|
|
||||||
|
block view
|
||||||
|
.mx-auto.w100.wmx11.fs-body1#content(style="cursor: default")
|
||||||
|
script
|
||||||
|
| var items =
|
||||||
|
!= JSON.stringify(items)
|
||||||
|
script.
|
||||||
|
setTimeout(() => {
|
||||||
|
const content = document.getElementById("content")
|
||||||
|
content.style.height = `${Math.round(Math.min(content.clientWidth, window.innerHeight)*0.8)}px`
|
||||||
|
WordCloud(content, {
|
||||||
|
list: items,
|
||||||
|
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI Adjusted", "Segoe UI", "Liberation Sans", sans-serif',
|
||||||
|
fontWeight: "bold",
|
||||||
|
color: "random-dark",
|
||||||
|
minSize: 4,
|
||||||
|
gridSize: Math.round(content.clientWidth / 200),
|
||||||
|
weightFactor: size => size * content.clientWidth / 180,
|
||||||
|
rotateRatio: 0,
|
||||||
|
click: item => {
|
||||||
|
const highlightedItem = document.querySelector(".wc-hl")?.textContent || item[0]
|
||||||
|
const newURL = new URL(location)
|
||||||
|
newURL.searchParams.set("filter", highlightedItem)
|
||||||
|
newURL.searchParams.set("filter_field", "tag")
|
||||||
|
newURL.searchParams.set("arrange", "label")
|
||||||
|
location = newURL
|
||||||
|
}
|
||||||
|
})
|
||||||
|
content.addEventListener("wordcloudstop", () => {
|
||||||
|
for (const child of content.children) {
|
||||||
|
child.addEventListener("mouseenter", () => {
|
||||||
|
child.classList.add("wc-hl")
|
||||||
|
})
|
||||||
|
child.addEventListener("mouseleave", () => {
|
||||||
|
child.classList.remove("wc-hl")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
|
@ -18,17 +18,29 @@ const currencyExchange = new Map([
|
||||||
])
|
])
|
||||||
|
|
||||||
const sqls = {
|
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 (item_id) {WHERE} GROUP BY item_id {ORDER}",
|
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 (item_id) {JOIN TAG} {WHERE} GROUP BY item_id {ORDER}",
|
||||||
album_list: "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 (item_id) {WHERE} GROUP BY item_id ORDER BY band_url, band_name COLLATE NOCASE, item_title COLLATE NOCASE",
|
album_list: "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 (item_id) {JOIN TAG} {WHERE} GROUP BY item_id ORDER BY band_url, band_name COLLATE NOCASE, item_title COLLATE NOCASE",
|
||||||
artist_grid: "SELECT band_name, count(DISTINCT item_id) AS album_count, group_concat(DISTINCT band_url) AS labels, 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 (item_id) {WHERE} GROUP BY band_name ORDER BY band_name COLLATE NOCASE",
|
artist_grid: "SELECT band_name, count(DISTINCT item_id) AS album_count, group_concat(DISTINCT band_url) AS labels, 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 (item_id) {JOIN TAG} {WHERE} GROUP BY band_name ORDER BY band_name COLLATE NOCASE",
|
||||||
artist_list: "SELECT band_name, count(DISTINCT item_id) AS album_count, band_url, 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 (item_id) {WHERE} GROUP BY band_name ORDER BY band_name COLLATE NOCASE",
|
artist_list: "SELECT band_name, count(DISTINCT item_id) AS album_count, band_url, 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 (item_id) {JOIN TAG} {WHERE} GROUP BY band_name ORDER BY band_name COLLATE NOCASE",
|
||||||
label_grid: "SELECT iif(count(DISTINCT band_name) = 1, band_name, band_url) AS display_name, band_url, count(DISTINCT item_id) AS album_count, count(DISTINCT band_name) AS artist_count, 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 (item_id) {WHERE} GROUP BY band_url ORDER BY display_name COLLATE NOCASE",
|
label_grid: "SELECT iif(count(DISTINCT band_name) = 1, band_name, band_url) AS display_name, band_url, count(DISTINCT item_id) AS album_count, count(DISTINCT band_name) AS artist_count, 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 (item_id) {JOIN TAG} {WHERE} GROUP BY band_url ORDER BY display_name COLLATE NOCASE",
|
||||||
label_list: "SELECT iif(count(DISTINCT band_name) = 1, band_name, band_url) AS display_name, band_url, count(DISTINCT item_id) AS album_count, count(DISTINCT band_name) AS artist_count, 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 (item_id) {WHERE} GROUP BY band_url ORDER BY display_name COLLATE NOCASE",
|
label_list: "SELECT iif(count(DISTINCT band_name) = 1, band_name, band_url) AS display_name, band_url, count(DISTINCT item_id) AS album_count, count(DISTINCT band_name) AS artist_count, 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 (item_id) {JOIN TAG} {WHERE} GROUP BY band_url ORDER BY display_name COLLATE NOCASE",
|
||||||
track_list: "SELECT * FROM track INNER JOIN item USING (item_id) {WHERE} ORDER BY band_url, item_title COLLATE NOCASE, track_number"
|
tag_grid: "SELECT tag, count(*) AS count FROM (SELECT tag, band_url, band_name, item_id, count(*) AS count FROM item_tag INNER JOIN item USING (item_id) GROUP BY tag, band_url) {WHERE} GROUP BY tag ORDER BY count DESC",
|
||||||
|
track_list: "SELECT * FROM track INNER JOIN item USING (item_id) {JOIN TAG} {WHERE} ORDER BY band_url, item_title COLLATE NOCASE, track_number"
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPreviews(locals, field, number) {
|
function loadPreviews(locals, field, number, whereClause, filter_field, filter) {
|
||||||
const previews = db.prepare(`SELECT ${field}, item_url, item_art_url FROM (SELECT ${field}, item_url, item_art_url, row_number() OVER (PARTITION BY ${field} ORDER BY purchased DESC) AS row_number FROM item) WHERE row_number <= ?`).all(number)
|
const params = [number]
|
||||||
|
let sql = `SELECT ${field}, item_url, item_art_url FROM (SELECT ${field}, item_url, item_art_url, row_number() OVER (PARTITION BY ${field} ORDER BY purchased DESC) AS row_number FROM item {JOIN TAG} {WHERE}) WHERE row_number <= ?`
|
||||||
|
sql = sql.replace("{WHERE}", whereClause)
|
||||||
|
if (whereClause) {
|
||||||
|
params.unshift(filter)
|
||||||
|
}
|
||||||
|
if (filter_field === "tag" && filter) {
|
||||||
|
sql = sql.replace("{JOIN TAG}", "INNER JOIN item_tag USING (item_id)")
|
||||||
|
} else {
|
||||||
|
sql = sql.replace("{JOIN TAG}", "")
|
||||||
|
}
|
||||||
|
const previews = db.prepare(sql).all(params)
|
||||||
// TODO: performance?
|
// TODO: performance?
|
||||||
for (const item of locals.items) {
|
for (const item of locals.items) {
|
||||||
item.previews = []
|
item.previews = []
|
||||||
|
@ -43,7 +55,7 @@ function loadPreviews(locals, field, number) {
|
||||||
const schema = z.object({
|
const schema = 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"]).optional(),
|
filter_field: z.enum(["band_name", "band_url", "item_id", "tag"]).optional(),
|
||||||
filter: z.string().optional()
|
filter: z.string().optional()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -63,31 +75,54 @@ router.get("/", defineEventHandler({
|
||||||
const mode = `${arrange}_${shape}`
|
const mode = `${arrange}_${shape}`
|
||||||
const params = []
|
const params = []
|
||||||
let sql = sqls[mode]
|
let sql = sqls[mode]
|
||||||
|
let whereClause = ""
|
||||||
if (filter_field && filter) {
|
if (filter_field && filter) {
|
||||||
sql = sql.replace("{WHERE}", `WHERE ${filter_field} LIKE ?`)
|
let operator = "="
|
||||||
|
if (filter_field === "band_url") {
|
||||||
|
operator = "LIKE"
|
||||||
|
params.push(`%${filter}%`)
|
||||||
|
} else {
|
||||||
|
params.push(filter)
|
||||||
|
}
|
||||||
|
whereClause = `WHERE ${filter_field} ${operator} ?`
|
||||||
sql = sql.replace("{ORDER}", "ORDER BY item_title COLLATE NOCASE")
|
sql = sql.replace("{ORDER}", "ORDER BY item_title COLLATE NOCASE")
|
||||||
params.push(`%${filter}%`)
|
|
||||||
} else {
|
} else {
|
||||||
sql = sql.replace("{WHERE}", "")
|
|
||||||
sql = sql.replace("{ORDER}", "ORDER BY purchased DESC")
|
sql = sql.replace("{ORDER}", "ORDER BY purchased DESC")
|
||||||
}
|
}
|
||||||
|
sql = sql.replace("{WHERE}", whereClause)
|
||||||
|
if (filter_field === "tag" && filter) {
|
||||||
|
sql = sql.replace("{JOIN TAG}", "INNER JOIN item_tag USING (item_id)")
|
||||||
|
} else {
|
||||||
|
sql = sql.replace("{JOIN TAG}", "")
|
||||||
|
}
|
||||||
const prepared = db.prepare(sql)
|
const prepared = db.prepare(sql)
|
||||||
|
if (arrange === "tag") {
|
||||||
|
prepared.raw()
|
||||||
|
}
|
||||||
const locals = {
|
const locals = {
|
||||||
items: prepared.all(params),
|
items: prepared.all(params),
|
||||||
albumCount: db.prepare("SELECT count(*) FROM item WHERE item_type = 'album'").pluck().get(),
|
query,
|
||||||
singleCount: db.prepare("SELECT count(*) FROM item WHERE item_type = 'track'").pluck().get(),
|
count: [
|
||||||
trackCount: db.prepare("SELECT count(*) FROM track").pluck().get(),
|
["total", db.prepare("SELECT count(*) FROM item").pluck().get()],
|
||||||
purchaseValue: Math.round(select("item", ["currency", "price"]).all().map(c => {
|
["runtime", db.prepare("SELECT iif(sum(duration) > 86400, cast(total(duration)/86400 AS INTEGER) || 'd ' || cast(total(duration)/3600%24 AS INTEGER) || 'h', cast(total(duration)/3600 AS INTEGER) || 'h') FROM track").pluck().get()],
|
||||||
return (currencyExchange.get(c.currency) || 0.5) * c.price / (currencyExchange.get(displayCurrency) || 1) / 10
|
["albums", db.prepare("SELECT count(*) FROM item WHERE item_type = 'album'").pluck().get()],
|
||||||
}).reduce((a, c) => a + c, 0)) * 10,
|
["singles", db.prepare("SELECT count(*) FROM item WHERE item_type = 'track'").pluck().get()],
|
||||||
displayCurrencySymbol,
|
["free", db.prepare("SELECT count(*) FROM item WHERE price = 0").pluck().get()],
|
||||||
displayCurrency,
|
["paid", db.prepare("SELECT count(*) FROM item WHERE price > 0").pluck().get()],
|
||||||
query
|
["tracks", db.prepare("SELECT count(*) FROM track").pluck().get()],
|
||||||
|
["avg tracks", Math.round(db.prepare("SELECT avg(count) FROM (SELECT count(*) AS count FROM track INNER JOIN item USING (item_id) WHERE item_type = 'album' GROUP BY item_id)").pluck().get()*10)/10],
|
||||||
|
["tags", db.prepare("SELECT count(*) FROM item_tag").pluck().get()],
|
||||||
|
["avg tags", Math.round(db.prepare("SELECT avg(count) FROM (SELECT count(*) AS count FROM item_tag GROUP BY item_id)").pluck().get()*10)/10],
|
||||||
|
["lonely tags", db.prepare("SELECT count(*) FROM (SELECT tag FROM item_tag GROUP BY tag HAVING count(*) = 1)").pluck().get()],
|
||||||
|
["value", displayCurrencySymbol + Math.round(select("item", ["currency", "price"]).all().map(c => {
|
||||||
|
return (currencyExchange.get(c.currency) || 0.6) * c.price / (currencyExchange.get(displayCurrency) || 1) / 10
|
||||||
|
}).reduce((a, c) => a + c, 0)) * 10 + " " + displayCurrency]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
if (mode === "artist_grid") {
|
if (mode === "artist_grid") {
|
||||||
loadPreviews(locals, "band_name", 4)
|
loadPreviews(locals, "band_name", 4, whereClause, filter_field, filter)
|
||||||
} else if (mode === "label_grid") {
|
} else if (mode === "label_grid") {
|
||||||
loadPreviews(locals, "band_url", 6)
|
loadPreviews(locals, "band_url", 6, whereClause, filter_field, filter)
|
||||||
}
|
}
|
||||||
return pugSync.render(event, `${arrange}_${shape}.pug`, locals)
|
return pugSync.render(event, `${arrange}_${shape}.pug`, locals)
|
||||||
}
|
}
|
||||||
|
|
10
server.js
10
server.js
|
@ -11,6 +11,7 @@ const {defineEventHandler, defaultContentType, getRequestHeader, setResponseHead
|
||||||
|
|
||||||
const passthrough = require("./passthrough")
|
const passthrough = require("./passthrough")
|
||||||
const sync = new HeatSync()
|
const sync = new HeatSync()
|
||||||
|
sync.events.on("error", console.error)
|
||||||
passthrough.db = new sqlite("bc-explorer.db")
|
passthrough.db = new sqlite("bc-explorer.db")
|
||||||
const app = createApp()
|
const app = createApp()
|
||||||
const router = createRouter()
|
const router = createRouter()
|
||||||
|
@ -56,4 +57,13 @@ router.get("/static/htmx.js", defineEventHandler({
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
router.get("/static/wordcloud.js", defineEventHandler({
|
||||||
|
onBeforeResponse: pugSync.compressResponse,
|
||||||
|
handler: async event => {
|
||||||
|
handleCacheHeaders(event, {maxAge: 86400})
|
||||||
|
defaultContentType(event, "text/javascript")
|
||||||
|
return fs.promises.readFile(require.resolve("wordcloud/src/wordcloud2.js"), "utf-8")
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
createServer(toNodeListener(app)).listen(2239)
|
createServer(toNodeListener(app)).listen(2239)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue