<template> <div class="post"> <div class="votes"> <div class="vote bump" :class="{ active: post.hasBump }" @click="vote(1)" >+</div> <div class="tally">{{ tally }}</div> <div class="vote sink" :class="{ active: post.hasSink }" @click="vote(-1)" >-</div> </div> <a :href="post.link || `/s/${post.shelf.slug}/post/${post.id}`" target="_blank" class="title-link" > <img class="thumbnail" :src="blockedIcon" > </a> <div class="body"> <div class="header"> <h2 class="title"> <a :href="`/s/${post.shelf.slug}/post/${post.id}`" class="title-link" >{{ post.title }}</a> </h2> <a v-if="post.link" :href="post.link" target="_blank" class="link" >{{ post.link }}</a> </div> <div class="meta"> <a :href="`/s/${post.shelf.slug}`" class="shelf link" >s/{{ post.shelf.slug }}</a> <a :href="`/user/${post.user.username}`" class="username link" >u/{{ post.user.username }}</a> <span :title="format(post.createdAt, 'MMMM d, yyyy hh:mm:ss')" class="timestamp" >{{ formatDistance(post.createdAt, now, { includeSeconds: true }) }} ago</span> </div> <div class="actions"> <a :href="`/s/${post.shelf.slug}/post/${post.id}`" class="link comments" >{{ post.commentCount }} comments</a> </div> </div> <a :href="`/s/${post.shelf.slug}/post/${post.id}`" class="fill" /> </div> </template> <script setup> import { ref } from 'vue'; import { format, formatDistance } from 'date-fns'; import blockedIcon from '../../assets/icons/blocked.svg?url'; // eslint-disable-line import/no-unresolved import { usePageContext } from '../../renderer/usePageContext'; import * as api from '../../assets/js/api'; const { now } = usePageContext(); const props = defineProps({ post: { type: Object, default: null, }, shelf: { type: Object, default: null, }, }); const tally = ref(props.post.tally); async function vote(value) { const effect = props.post.hasBump || props.post.hasSink ? 0 : value; await api.post(`/posts/${props.post.id}/votes`, { value: effect }); tally.value += effect; } </script> <style scoped> .post { display: flex; color: var(--text); border-radius: .25rem; margin-bottom: .25rem; background: var(--background); text-decoration: none; & :hover { cursor: pointer; .title { color: var(--text); } } } .body { margin-left: 1rem; } .header { display: flex; align-items: center; } .title { display: inline-block; padding: .25rem 0; margin: .25rem 1rem 0 0; font-size: 1rem; font-weight: bold; color: var(--grey-dark-30); } .title-link { color: inherit; text-decoration: none; } .thumbnail { width: 7rem; height: 4rem; box-sizing: border-box; padding: 1rem; border-radius: .25rem; margin: .5rem; background: var(--grey-light-10); opacity: .25; } .votes { width: 2rem; display: flex; flex-direction: column; align-items: center; justify-content: center; } .vote, .tally { display: flex; align-items: center; height: 1rem; font-weight: bold; } .vote { display: inline-flex; align-items: center; justify-content: center; width: 1.5rem; height: 1rem; box-sizing: border-box; padding: .6rem; margin: .25rem 0; border-radius: .5rem; background: var(--shadow-weak-40); } .tally { color: var(--shadow-strong-20); font-size: .9rem; } .bump.active { color: var(--text-light); background: var(--bump); } .sink.active { color: var(--text-light); background: var(--sink); } .meta { display: flex; gap: 1rem; margin-bottom: .25rem; font-size: .9rem; } .username { color: inherit; } .shelf { color: inherit; font-weight: bold; } .timestamp { color: var(--grey-dark-20); } .comments { color: inherit; font-size: .9rem; } .fill { flex-grow: 1; } </style>