Skip to content

Commit

Permalink
Article comments (#21)
Browse files Browse the repository at this point in the history
* wip create comments in public page

* add comment component and create get request comment

* covert static comments to dynamic comments

* change variable and condition for dynamic object

* create register page

* wip create comments in frontEnd

* create and test send and get comment component

* create and test send and get comment component

---------

Co-authored-by: esakh <[email protected]>
  • Loading branch information
khanzadimahdi and Eisakhanzadi authored Aug 15, 2024
1 parent b9e1212 commit 75c2cd9
Show file tree
Hide file tree
Showing 6 changed files with 387 additions and 65 deletions.
119 changes: 67 additions & 52 deletions frontend/components/comments/index.vue
Original file line number Diff line number Diff line change
@@ -1,58 +1,73 @@
<template>
<div>
<h5 class="mb-3">
<span class="fa-regular fa-comment"></span>
<span class="mx-1">دیدگاه ها</span>
</h5>

<form v-if="params.isLogin" class="my-3">
<textarea class="form-control mb-3" placeholder="دیدگاه خود را اینجا بنویسید" rows="3" required></textarea>
<button class="btn btn-success" type="submit">ثبت دیدگاه</button>
</form>

<div v-else class="alert alert-light">
<i class="fa-regular fa-bell fa-shake fa-xl"></i>
<span class="mx-1">برای ثبت دیدگاه خود</span>
<NuxtLink class="mx-1" href="/auth/register">ثبت نام کنید</NuxtLink>
<span>یا</span>
<NuxtLink class="mx-1" to="/auth/login">وارد شوید</NuxtLink>
</div>

<section class="card mb-3">
<div class="card-body d-flex flex-start">
<img class="rounded-circle shadow-1-strong ms-3" src="https://mdbcdn.b-cdn.net/img/Photos/Avatars/img%20(3).webp" alt="avatar" width="65" height="65">
<div class="flex-grow-1 flex-shrink-1">
<div class="d-flex justify-content-between align-items-center">
<p class="mb-1">
<span>افشار زند</span>
<span class="text-muted small">
<span class="fa-regular fa-clock mx-1"></span>
<time datetime="">{{ useTime().toAgo('2024/05/20') }}</time>
</span>
</p>
<div v-if="params.isLogin" class="text-nowrap">
<button class="btn text-danger btn-sm">
<i class="fas fa-trash fa-xs"></i>
</button>
<span>|</span>
<button class="btn btn-sm">
<i class="fas fa-reply fa-xs"></i>
</button>
</div>
</div>
<p class="small mb-0 pt-2 border-top">
طراحان معمولا از لورم ایپسوم
استفاده میکنند تا فقط به مشتری یا کار فرما نشان دهند که قالب طراحی شده بعد از اینکه متن
در آن قرار میگرد چگونه خواهد بود و فونت ها و اندازه ها چگونه در نظر گرفته شده است.
</p>
</div>
</div>
</section>
<section>
<h5 class="mb-3">
<span class="fa-regular fa-comment"></span>
<span class="mx-1">دیدگاه ها</span>
</h5>
<comments-write-new :disabled="disabled"
@sendComment="sendComment" v-if="params.isLogin"/>
<div v-else class="alert alert-light">
<i class="fa-regular fa-bell fa-shake fa-xl"></i>
<span class="mx-1">برای ثبت دیدگاه خود</span>
<NuxtLink class="mx-1" href="/auth/register">ثبت نام کنید</NuxtLink>
<span>یا</span>
<NuxtLink class="mx-1" to="/auth/login">وارد شوید</NuxtLink>
</div>
<comments-item v-for="(item , index) in comments" :key="index" :data="item" v-if="comments && comments.length"/>
</section>
</template>

<script lang="ts" setup>
const params = reactive({
isLogin: useAuth().isLogin(),
const route = useRoute()
const {data} = defineProps(['data'])
useState('clearDataAfterCloseComment', ()=>false)
const comments = (data && data.length) ? createTree(data) : ""
const disabled = ref()
function createTree(comments: []): [] {
const map = new Map();
comments.forEach(comment => map.set(comment.uuid, comment));
const tree: [] = [];
comments.forEach(comment => {
const parent = map.get(comment.parent_uuid);
if (!parent) {
tree.push(comment);
}
});
// تکمیل کردن زیرمجموعه‌ها
tree.forEach((node: object) => {
const fillSubtree = (node: object) => {
node.sub = comments.filter(comment => comment.parent_uuid === node.uuid);
node.sub.forEach(fillSubtree);
};
fillSubtree(node);
});
return tree;
}
const sendComment = async (text: string) => {
const body = {
object_uuid: route.params.uuid,
body: text,
object_type: 'article',
parent_uuid: ""
}
try {
disabled.value = true /* برای غیر فعال شدن دکمه ارسال کامنت بعد فشرده شدن تا زمان برشگت درخواست */
useState('clearDataAfterCloseComment').value = false /* برای خالی کردن مقدار کامنت در صورت موفقیت بودن درخواست */
const data = await useUser().$fetch(useApiUrlResolver().resolve('api/comments'), {
method: 'POST',
headers: {authorization: `Bearer ${useAuth().accessToken()}`},
body: JSON.stringify(body),
})
</script>
useState('clearDataAfterCloseComment').value = true
} catch (e) {
console.log(e)
} finally {
disabled.value = false /*جواب درخواست ما هرچی که باشه درانتها هر چی که باشه دکمه از حالت disable در میاد*/
}
}
const params = reactive({
isLogin: useAuth().isLogin(),
})
</script>
145 changes: 145 additions & 0 deletions frontend/components/comments/item.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<template>
<section :class="{child:child }" v-if="data">
<section class="card mb-3">
<div class="card-body d-flex flex-start">
<img v-if="data.author.avatar" class="rounded-circle shadow-1-strong ms-3"
:src="useFilesUrlResolver().resolve(data.author.avatar)" alt="avatar" width="65"
height="65">
<div class="flex-grow-1 flex-shrink-1 ">
<div class="d-flex justify-content-between align-items-center">
<p class="info mb-1">
<span v-if="data.author.name">{{ data.author.name }}</span>
<span class="text-muted small me-1" v-if="data.created_at && data.created_at.length">
<span class="fa-regular fa-clock mx-1"></span>
<time datetime="">{{ useTime().toAgo(data.created_at) }}</time>
</span>
</p>
<div v-if="params.isLogin" class="text-nowrap">
<button class="btn text-danger btn-sm" v-if="false">
<i class="fas fa-trash fa-xs"></i>
</button>
<span v-if="false">|</span>
<button class="btn btn-sm" @click="showWriteComment">
<i class="fas fa-reply fa-xs"></i>
</button>
</div>
</div>
<p v-if="data.body && data.body.length" class="text small mb-0 pt-2 border-top ">{{ data.body }}</p>
</div>
</div>
</section>
<section class="write-comment px-1" ref="writeComment">
<comments-write-new :clear-data="clearDataAfterCloseComment" :disabled="disabled" :parentInfo="data.uuid"
@send-comment="sendComment" :replyTheme="true">
<button class="btn btn-sm btn-md-lg btn-danger align-self-start mt-2" @click.prevent="showWriteComment">بستن
</button>
</comments-write-new>
</section>
<comments-item v-if="data.sub && data.sub.length" v-for="(item , index) in data.sub" :key="index" :data="item"
:child="true"/>
</section>
</template>

<script setup lang="ts">
const {uuid} = useRoute().params
import {useFilesUrlResolver} from "~/composables/urlResolver";
const disabled = ref()
const writeComment = ref(null)
const clearDataAfterCloseComment = ref(false)
const {data, child} = defineProps({
data: {
type: Object,
},
child: {
type: Boolean
}
})
/* در فانکشن زیر ما برای ظاهر شدن کامپوننت کامنت از maxHeight استفاده کردیم
به این صورت که در ابتدا صفر و با کلیک برروی ریپلای به اندازه طول
اسکرول آن بعلاوه 20 پیکسل بیشتر که ارفاع ارور آن دررمان ظاهر شدن است */
const showWriteComment = () => {
if (writeComment.value.style.maxHeight) {
writeComment.value.style.maxHeight = null
useState('clearDataAfterCloseComment').value = true
} else {
writeComment.value.style.maxHeight = writeComment.value.scrollHeight + 'px'
useState('clearDataAfterCloseComment').value = false
}
}
const sendComment = async (text: string, parentUuid: string) => {
const body = {
body: text,
object_type: 'article',
parent_uuid: parentUuid,
object_uuid: uuid
}
try {
disabled.value = true /* برای غیر فعال شدن دکمه ارسال کامنت بعد فشرده شدن تا زمان برشگت درخواست */
const data = await useUser().$fetch(useApiUrlResolver().resolve('api/comments'), {
method: 'POST',
headers: {authorization: `Bearer ${useAuth().accessToken()}`},
body: JSON.stringify(body)
})
showWriteComment() /*برای بسته شدن ریپلای در صورت موفقیت آمیز بودن ارسال درخواست */
} catch (e) {
console.log(e)
} finally {
disabled.value = false /* عملیات ارسال دیتا در نهایت هرچی که باشه دکمه ما تغیر حالت میده */
}
}
const params = reactive({
isLogin: useAuth().isLogin(),
})
</script>

<style scoped lang="scss">
card {
transition: 0.5s;
}
.child {
margin-right: 3%;
}
p {
&.text {
line-height: 30px;
font-size: 14px;
color: #6c757d;
}
&.info {
line-height: 20px;
font-size: 14px;
}
}
.transform-enter-active {
animation: reply ease-out 0.35s;
z-index: 1;
}
.transform-leave-active {
animation: reply ease-in 0.35s reverse;
}
.write-comment {
max-height: 0;
overflow: hidden;
transition: 0.5s !important;
}
@keyframes reply {
0% {
transform: translateY(-10%);
opacity: -1;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
</style>
52 changes: 52 additions & 0 deletions frontend/components/comments/writeNew.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<script setup lang="ts">
const {parentInfo, replyTheme, disabled} = defineProps({
parentInfo: {
type: String
},
replyTheme: {
type: Boolean
},
disabled: {
type: Boolean
}
})
const emit = defineEmits(['sendComment'])
const commentData = reactive({
body: "",
error: false
})
watch(() => useState('clearDataAfterCloseComment').value, () => {
commentData.body = ""
})
const sendComment = () => {
if (commentData.body.length > 4) {
emit('sendComment', commentData.body, parentInfo)
} else {
commentData.error = true
}
}
const removeError = () => {
commentData.error ? commentData.error = false : ""
}
</script>

<template>
<form class="my-3 py-1 d-flex flex-column" :class="{'mt-0':replyTheme}" @submit.prevent="sendComment">
<textarea class="form-control mb-1" placeholder="دیدگاه خود را اینجا بنویسید" rows="3" required
v-model.trim="commentData.body" @keyup="removeError"></textarea>
<span class="error text-danger" v-if="commentData.error">متن پیام باید بیشتر باشد .</span>
<div class="d-flex gap-2">
<button :class="`btn btn-success align-self-start ${ replyTheme ?'btn-sm mt-2': 'mt-3'}`" :disabled="disabled"
type="submit">ثبت دیدگاه
</button>
<slot/>
</div>
</form>
</template>

<style scoped>
.error {
font-size: 12px;
}
</style>
19 changes: 11 additions & 8 deletions frontend/pages/articles/[uuid].vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,12 @@
<div v-if="data.tags" class="card-text">
<a class="hashtag" :href="`/hashtags/${tag}`" :key="index" v-for="(tag, index) in data.tags">{{ tag }}</a>
</div>

<aside v-if="false" class="mt-5">
<Comments />
<aside class="mt-5">
<Comments :data="comments.items"/>
</aside>
</section>
</div>
</div>

<div v-if="data.elements">
<template v-for="(element, index) in data.elements" :key="index">
<Jumbotron :key="index" v-if="element.type === 'jumbotron'" :body="element.body" />
Expand All @@ -40,14 +38,19 @@
</template>

<script setup>
import hljs from 'highlight.js'
import hljs from 'highlight.js'
const {uuid} = useRoute().params;
const {uuid} = useRoute().params;
const resolveFileUrl = useFilesUrlResolver().resolve
const data = await $fetch(useApiUrlResolver().resolve(`api/articles/${uuid}`))
const comments = await $fetch( useApiUrlResolver().resolve('api/comments'), {
query: {
object_type: 'article',
object_uuid: uuid,
page: 1
}
})
useHead({
title: data.title,
meta: [
Expand Down
15 changes: 10 additions & 5 deletions frontend/pages/auth/login.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,20 @@
</div>
</button>
</div>
<div class="form-group d-flex flex-column flex-md-row mt-2 pt-2 justify-content-between align-items-center">
<div>
<label class="d-flex align-items-center gap-1 checkbox-primary mb-3">
<div class="form-group d-flex mt-2">
<label class="d-flex align-items-center gap-1 checkbox-primary">
<input type="checkbox" checked>
<span>من را به خاطر بسپار</span>
</label>
</div>
<div
class="form-group d-flex flex-column flex-md-row mt-2 pt-2 justify-content-md-end gap-3 align-items-md-center">
<div>
<nuxt-link to="/auth/forgot-password" class="btn btn-outline-danger w-100 btn-sm ">بازیابی کلمه عبور
</nuxt-link>
</div>
<div>
<nuxt-link to="/auth/forgot-password" class="btn btn-outline-danger w-100 btn-sm ">بازیابی کلمه عبور</nuxt-link>
<nuxt-link to="/auth/register" class="btn btn-outline-success w-100 btn-sm ">ثبت نام</nuxt-link>
</div>
</div>
</form>
Expand Down Expand Up @@ -102,4 +107,4 @@ input::placeholder {
color: #9a9999;
font-size: 0.9rem;
}
</style>
</style>
Loading

0 comments on commit 75c2cd9

Please sign in to comment.