Added voting. Improved comments.

This commit is contained in:
2023-06-25 19:52:00 +02:00
parent 754a89b913
commit f42daa2f83
29 changed files with 916 additions and 154 deletions

View File

@@ -1,23 +1,84 @@
<template>
<div class="comment">
<div class="header">
<a
:href="`/user/${comment.user.username}`"
class="username link"
>u/{{ comment.user.username }}</a>
<div class="container">
<div class="frame">
<div class="crumbs">
<div
v-for="index in depth"
:key="`${comment.id}-${index}`"
:style="{ color: `hsl(${index * 60}, 50%, 75%)`, 'border-color': `hsl(${index * 60}, 50%, 90%)` }"
class="crumb"
>{{ String.fromCharCode(96 + index) }}</div>
</div>
<span
:title="format(comment.createdAt, 'MMM d, yyyy hh:mm:ss')"
class="timestamp"
>{{ formatDistance(comment.createdAt, now, { includeSeconds: true }) }} ago</span>
<div
class="comment"
:class="{ nested: comment.parentId }"
:style="{ 'border-left': `solid 2px hsl(${(depth + 1) * 60}, 50%, 90%)` }"
>
<div class="header">
<img
src="/assets/icons/blocked.svg"
class="avatar"
>
<a
:href="`/user/${comment.user.username}`"
class="username link"
>u/{{ comment.user.username }}</a>
<ul class="labels nolist">
<li
v-if="comment.user.id === post.user.id"
class="label op"
>op</li>
</ul>
<span
:title="format(comment.createdAt, 'MMM d, yyyy hh:mm:ss')"
class="timestamp"
>{{ formatDistance(comment.createdAt, now, { includeSeconds: true }) }} ago</span>
</div>
<p class="body">{{ comment.body }}</p>
<div class="actions">
<button
type="button"
class="action link nobutton"
@click="isReplying = !isReplying"
>reply</button>
</div>
<Writer
v-if="isReplying"
:comment="comment"
:post="post"
@cancel="isReplying = false"
/>
</div>
</div>
<p class="body">{{ comment.body }}</p>
<ul class="replies nolist">
<li
v-for="reply in comment.comments"
:key="reply.id"
>
<Comment
:comment="reply"
:post="post"
:depth="depth + 1"
/>
</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { format, formatDistance } from 'date-fns';
import Writer from './writer.vue';
import { usePageContext } from '../../renderer/usePageContext';
const { now } = usePageContext();
@@ -27,20 +88,37 @@ defineProps({
type: Object,
default: null,
},
post: {
type: Object,
default: null,
},
depth: {
type: Number,
default: 0,
},
});
const isReplying = ref(false);
</script>
<style scoped>
.comment {
display: block;
flex-grow: 1;
background: var(--background);
padding: .5rem;
border-radius: .25rem;
margin-bottom: .25rem;
box-sizing: border-box;
border-radius: .25rem .25rem .25rem 0;
margin: .25rem 0;
&.nested {
position: relative;
}
}
.header {
display: flex;
font-size: .9rem;
margin-bottom: .5rem;
padding: .5rem .5rem .25rem .5rem;
}
.username {
@@ -54,6 +132,67 @@ defineProps({
}
.body {
padding: .25rem .5rem .5rem .5rem;
margin: 0;
}
.actions {
border-top: solid 1px var(--shadow-weak-40);
}
.action {
padding: .5rem;
color: var(--grey-dark-20);
cursor: pointer;
}
.labels {
display: inline-block;
margin-right: .5rem;
}
.label {
background: var(--grey);
border-radius: .25rem;
color: var(--text-light);
font-weight: bold;
&.op {
color: var(--op);
background: none;
}
}
.avatar {
width: 1rem;
height: 1rem;
margin-right: .5rem;
opacity: .25;
}
.replies {
display: flex;
flex-direction: column;
}
.frame {
display: flex;
align-items: stretch;
}
.crumbs {
display: flex;
align-items: stretch;
margin-top: -.5rem;
}
.crumb {
display: flex;
align-items: center;
box-sizing: border-box;
padding: .5rem;
border-left: solid 2px var(--shadow-weak-40);
color: var(--shadow-weak-20);
font-size: .8rem;
}
</style>

View File

@@ -0,0 +1,88 @@
<template>
<form
class="writer"
@submit.prevent="addComment"
>
<textarea
ref="input"
v-model="body"
placeholder="Write a new comment"
class="input"
/>
<div class="actions">
<button
:disabled="body.length === 0"
class="button button-submit action submit"
>Comment</button>
<button
v-if="comment"
type="button"
class="button button-cancel action cancel"
@click="$emit('cancel')"
>Cancel</button>
</div>
</form>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import * as api from '../../assets/js/api';
import { reload } from '../../assets/js/navigate';
const props = defineProps({
comment: {
type: Object,
default: null,
},
post: {
type: Object,
default: null,
},
});
const body = ref('');
const input = ref(null);
async function addComment() {
await api.post(`/posts/${props.post.id}/comments`, {
body: body.value,
parentId: props.comment?.id,
});
reload();
}
onMounted(() => {
if (props.comment) {
input.value.focus();
}
});
</script>
<style scoped>
.writer {
width: 100%;
background: var(--background);
box-sizing: border-box;
padding: .5rem;
border-radius: .5rem;
margin-bottom: .5rem;
}
.input {
width: 100%;
margin-bottom: .25rem;
}
.actions {
display: flex;
justify-content: flex-start;
}
.action {
margin-right: .25rem;
}
</style>