From 6ffe5aa1fc9b31f526b9850eca331840cbe42c44 Mon Sep 17 00:00:00 2001 From: Michael Winter Date: Fri, 6 Mar 2026 15:08:30 +0100 Subject: [PATCH] Add work pages with hash-based modal URLs and proper 404 handling - Create dedicated /work/[slug] pages with sticky nav - Use hash URLs (#type|slug) for modals instead of work page URLs - Make works without items non-clickable on index - Add server route to return 404 for non-existent score files --- components/IconButton.vue | 25 ++++++-- components/IndexContent.vue | 71 ++++++++++++++++++++++- pages/work/[slug].vue | 95 +++++++++++++++++++++++++++---- server/routes/scores/[...path].ts | 25 ++++++++ 4 files changed, 197 insertions(+), 19 deletions(-) create mode 100644 server/routes/scores/[...path].ts diff --git a/components/IconButton.vue b/components/IconButton.vue index c06fe41..45de733 100644 --- a/components/IconButton.vue +++ b/components/IconButton.vue @@ -2,9 +2,9 @@
- + @@ -26,11 +26,11 @@ - - @@ -70,4 +70,21 @@ modalStore.setModalProps('document', 'aspect-[1/1.414]', true, '', '', '', '', '', props.link) } } + + const openWithHash = (type) => { + const slug = workSlug.value + const hash = type + '|' + slug + + if (type === 'score') { + modalStore.setModalProps('pdf', 'aspect-[1/1.414]', true, '', '', '', props.link, props.work?.soundcloud_trackid ? 'https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/' + props.work.soundcloud_trackid + '&auto_play=true&show_user=false' : '') + } else if (type === 'video') { + modalStore.setModalProps('video', 'aspect-video', true, '', '', props.work.vimeo_trackid) + } else if (type === 'image') { + modalStore.setModalProps('image', 'aspect-auto', true, 'images', props.work.gallery, '', '', props.work.soundcloud_trackid ? 'https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/' + props.work.soundcloud_trackid + '&auto_play=true&show_user=false' : '') + } + + if (slug && typeof window !== 'undefined') { + window.history.replaceState({}, '', '/#' + hash) + } + } diff --git a/components/IndexContent.vue b/components/IndexContent.vue index 2d81c69..8622f37 100644 --- a/components/IndexContent.vue +++ b/components/IndexContent.vue @@ -8,7 +8,12 @@

{{ item.year }}

- + + + + + +
@@ -61,7 +66,7 @@

albums

{{ item.title }}

- @@ -77,11 +82,12 @@ diff --git a/pages/work/[slug].vue b/pages/work/[slug].vue index ebe890f..81c093c 100644 --- a/pages/work/[slug].vue +++ b/pages/work/[slug].vue @@ -1,20 +1,91 @@ + + diff --git a/server/routes/scores/[...path].ts b/server/routes/scores/[...path].ts new file mode 100644 index 0000000..ef20578 --- /dev/null +++ b/server/routes/scores/[...path].ts @@ -0,0 +1,25 @@ +import { existsSync, createReadStream } from 'fs' +import { join } from 'path' +import { sendStream } from 'h3' +import { createError } from 'h3' + +export default defineEventHandler(async (event) => { + const url = event.path + + // Get the filename from the URL + const filename = url.replace('/scores/', '') + + // Check if file exists in public/scores/ + const filePath = join(process.cwd(), 'public/scores', filename) + + if (!existsSync(filePath)) { + throw createError({ + statusCode: 404, + statusMessage: 'Not Found' + }) + } + + // Serve the file + event.node.res.statusCode = 200 + return sendStream(event, createReadStream(filePath)) +})