feat(attachments): downloadable attachments in ProvenancePanel + PostInfoHeader

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-19 11:17:14 -04:00
parent 74e34d359b
commit cba662a900
4 changed files with 51 additions and 2 deletions
@@ -21,6 +21,11 @@
v-if="info.data.post.url" :href="info.data.post.url"
target="_blank" rel="noopener noreferrer"
> original post</a>
<a
v-for="at in (info.data.attachments || [])" :key="at.id"
class="fc-postinfo__attach"
:href="at.download_url" :download="at.original_filename"
> {{ at.original_filename }}</a>
</div>
</template>
<span v-else-if="info && info.error" class="fc-postinfo__title">
@@ -90,4 +95,8 @@ function clear() {
font-size: 13px; color: rgb(var(--v-theme-on-surface-variant));
}
.fc-postinfo__main a { color: rgb(var(--v-theme-accent)); text-decoration: none; }
.fc-postinfo__attach {
font-size: 13px; color: rgb(var(--v-theme-accent));
text-decoration: none;
}
</style>
@@ -44,6 +44,17 @@
/>
</article>
</template>
<div v-if="attachments.length" class="fc-prov__attach">
<h4 class="fc-prov__attach-title">Attachments</h4>
<a
v-for="at in attachments" :key="at.id"
class="fc-prov__attach-row"
:href="at.download_url" :download="at.original_filename"
> {{ at.original_filename }}
<span class="fc-prov__attach-size">({{ at.size_bytes }} B)</span>
</a>
</div>
</section>
</template>
@@ -77,6 +88,8 @@ const show = computed(() => {
return Array.isArray(st.entries) && st.entries.length > 0
})
const attachments = computed(() => state.value?.attachments || [])
function postDate(e) { return formatPostDate(e.post.date) }
function openPost(postId) {
@@ -124,4 +137,14 @@ function openPost(postId) {
color: rgb(var(--v-theme-on-surface-variant));
}
.fc-prov__desc :deep(a) { color: rgb(var(--v-theme-accent)); }
.fc-prov__attach { padding: 0 0 12px; }
.fc-prov__attach-title {
font-size: 13px; color: rgb(var(--v-theme-on-surface-variant));
margin: 8px 0 4px;
}
.fc-prov__attach-row {
display: block; font-size: 13px; text-decoration: none;
color: rgb(var(--v-theme-accent)); padding: 2px 0;
}
.fc-prov__attach-size { color: rgb(var(--v-theme-on-surface-variant)); }
</style>
+5 -2
View File
@@ -16,8 +16,11 @@ export const useProvenanceStore = defineStore('provenance', () => {
imageCache.value[id] = { loading: true, error: null, entries: null }
try {
const body = await api.get(`/api/provenance/image/${id}`)
imageCache.value[id] =
{ loading: false, error: null, entries: body.provenance }
imageCache.value[id] = {
loading: false, error: null,
entries: body.provenance,
attachments: body.attachments || [],
}
} catch (e) {
imageCache.value[id] =
{ loading: false, error: e.message, entries: null }
+14
View File
@@ -59,4 +59,18 @@ describe('provenance store', () => {
expect(globalThis.fetch.mock.calls.length).toBe(1)
expect(store.postInfo(3).data.artist.slug).toBe('a')
})
it('loadForImage keeps attachments from the payload', async () => {
const store = useProvenanceStore()
stubFetch(() => ({
status: 200,
body: {
image_id: 1, provenance: [],
attachments: [{ id: 9, original_filename: 'p.zip', size_bytes: 5,
ext: '.zip', download_url: '/api/attachments/9/download' }]
}
}))
await store.loadForImage(1)
expect(store.imageProv(1).attachments[0].id).toBe(9)
})
})