feat(provenance): ProvenancePanel — peer post cards (read-only)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<section v-if="show" class="fc-prov" aria-label="Provenance">
|
||||
<h3 class="fc-prov__title">Provenance</h3>
|
||||
|
||||
<v-progress-circular
|
||||
v-if="state && state.loading" indeterminate color="accent" size="22"
|
||||
/>
|
||||
<v-alert
|
||||
v-else-if="state && state.error" type="error" variant="tonal"
|
||||
density="compact"
|
||||
>{{ state.error }}</v-alert>
|
||||
|
||||
<template v-else>
|
||||
<article
|
||||
v-for="e in state.entries" :key="e.provenance_id" class="fc-prov__card"
|
||||
>
|
||||
<div class="fc-prov__head">
|
||||
<span class="fc-prov__platform">{{ e.source.platform }}</span>
|
||||
<span v-if="postDate(e)" class="fc-prov__date">{{ postDate(e) }}</span>
|
||||
</div>
|
||||
<div class="fc-prov__post">
|
||||
{{ e.post.title || `Post ${e.post.external_post_id}` }}
|
||||
</div>
|
||||
<div class="fc-prov__meta">
|
||||
<RouterLink :to="`/artist/${e.artist.slug}`">
|
||||
by {{ e.artist.name }}
|
||||
</RouterLink>
|
||||
<span v-if="e.post.attachment_count != null">
|
||||
· {{ e.post.attachment_count }} files
|
||||
</span>
|
||||
</div>
|
||||
<div class="fc-prov__actions">
|
||||
<a
|
||||
v-if="e.post.url" :href="e.post.url"
|
||||
target="_blank" rel="noopener noreferrer"
|
||||
>↗ View original post</a>
|
||||
<a href="#" @click.prevent="openPost(e.post.id)">
|
||||
View images from this post
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
v-if="e.post.description_html"
|
||||
class="fc-prov__desc" v-html="e.post.description_html"
|
||||
/>
|
||||
</article>
|
||||
</template>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useModalStore } from '../../stores/modal.js'
|
||||
import { useProvenanceStore } from '../../stores/provenance.js'
|
||||
import { formatPostDate } from '../../utils/date.js'
|
||||
|
||||
const modal = useModalStore()
|
||||
const prov = useProvenanceStore()
|
||||
const router = useRouter()
|
||||
|
||||
watch(
|
||||
() => modal.currentImageId,
|
||||
(id) => { if (id != null) prov.loadForImage(id) },
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const state = computed(() =>
|
||||
modal.currentImageId == null ? null : prov.imageProv(modal.currentImageId)
|
||||
)
|
||||
|
||||
// Show the panel while loading or on error, or when there is >=1 entry.
|
||||
// Hide entirely when the image has zero provenance.
|
||||
const show = computed(() => {
|
||||
const st = state.value
|
||||
if (!st) return false
|
||||
if (st.loading || st.error) return true
|
||||
return Array.isArray(st.entries) && st.entries.length > 0
|
||||
})
|
||||
|
||||
function postDate(e) { return formatPostDate(e.post.date) }
|
||||
|
||||
function openPost(postId) {
|
||||
router.push({ path: '/gallery', query: { post_id: postId } })
|
||||
modal.close()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fc-prov { padding: 16px 16px 0; }
|
||||
.fc-prov__title {
|
||||
font-family: 'Fraunces', Georgia, serif;
|
||||
font-size: 18px; font-weight: 500;
|
||||
color: rgb(var(--v-theme-on-surface));
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.fc-prov__card {
|
||||
border: 1px solid rgb(var(--v-theme-surface-light));
|
||||
border-radius: 6px; padding: 10px 12px; margin-bottom: 10px;
|
||||
}
|
||||
.fc-prov__head {
|
||||
display: flex; justify-content: space-between; align-items: baseline;
|
||||
font-size: 12px; color: rgb(var(--v-theme-on-surface-variant));
|
||||
text-transform: lowercase;
|
||||
}
|
||||
.fc-prov__post {
|
||||
font-weight: 600; margin: 4px 0;
|
||||
color: rgb(var(--v-theme-on-surface));
|
||||
}
|
||||
.fc-prov__meta {
|
||||
font-size: 13px; color: rgb(var(--v-theme-on-surface-variant));
|
||||
display: flex; gap: 6px; flex-wrap: wrap;
|
||||
}
|
||||
.fc-prov__meta a { color: rgb(var(--v-theme-accent)); text-decoration: none; }
|
||||
.fc-prov__actions {
|
||||
display: flex; gap: 12px; flex-wrap: wrap;
|
||||
margin-top: 6px; font-size: 13px;
|
||||
}
|
||||
.fc-prov__actions a {
|
||||
color: rgb(var(--v-theme-accent)); text-decoration: none;
|
||||
}
|
||||
.fc-prov__desc {
|
||||
margin-top: 8px; font-size: 13px; line-height: 1.5;
|
||||
max-height: 180px; overflow: auto;
|
||||
color: rgb(var(--v-theme-on-surface-variant));
|
||||
}
|
||||
.fc-prov__desc :deep(a) { color: rgb(var(--v-theme-accent)); }
|
||||
</style>
|
||||
Reference in New Issue
Block a user