59. 댓글
ChatGPT에게 물었습니다.
I want to leave a comment.
댓글을 쓰고 싶어.
To implement a comment feature in your blog application using Vue, Vuex, Vuetify, and Firestore, you need to follow these steps:
Vue, Vuex, Vuetify 및 Firestore를 사용하여 블로그 애플리케이션에 댓글 기능을 구현하려면 다음 단계를 따라야 합니다.
- Add Comment Form: Create a form for users to submit comments.
댓글 양식 추가: 사용자가 댓글을 제출할 수 있는 양식을 만듭니다. - Store Comments in Firestore: Save comments in Firestore under each post.
Firestore에 댓글 저장: 각 게시물 아래에 Firestore에 댓글을 저장합니다. - Display Comments: Retrieve and display comments for each post.
댓글 표시: 각 게시물에 대한 댓글을 검색하고 표시합니다.
Step-by-Step Implementation
단계별 구현
Step 1: Add Comment Form
1단계: 댓글 양식 추가
Create a form in your PostDetailView.vue component to allow users to submit comments.
사용자가 댓글을 제출할 수 있도록 PostDetailView.vue 구성 요소에 양식을 만듭니다.
<template>
<v-container>
<v-alert v-if="error" type="error" dismissible>{{ error.message }}</v-alert>
<v-progress-circular v-if="loading" indeterminate></v-progress-circular>
<div v-if="post">
<h1>{{ post.title }}</h1>
<div>{{ post.userName }} - {{ formatDate(post.createdAt) }}</div>
<div>{{ post.content }}</div>
<div>Views: {{ post.views }}</div>
<!-- Comments Section -->
<v-divider class="my-4"></v-divider>
<h3>Comments</h3>
<v-list>
<v-list-item v-for="comment in comments" :key="comment.id">
<v-list-item-content>
<v-list-item-title>{{ comment.userName }}</v-list-item-title>
<v-list-item-subtitle>{{ comment.content }}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
<!-- Add Comment Form -->
<v-form @submit.prevent="addComment">
<v-text-field
v-model="newComment"
label="Add a comment"
required
></v-text-field>
<v-btn type="submit" color="primary">Submit</v-btn>
</v-form>
</div>
</v-container>
</template>
<script>
import { db, auth } from '../firebase';
import firebase from 'firebase/app';
import { v4 as uuidv4 } from 'uuid';
export default {
data() {
return {
post: null,
comments: [],
newComment: '',
loading: false,
error: null
};
},
async created() {
const postId = this.$route.params.id;
this.loading = true;
try {
const postRef = db.collection('posts').doc(postId);
const postDoc = await postRef.get();
if (postDoc.exists) {
this.post = { id: postDoc.id, ...postDoc.data() };
this.loadComments(postId);
const today = new Date().toISOString().split('T')[0];
let userId;
if (auth.currentUser) {
userId = auth.currentUser.uid;
} else {
userId = this.getOrCreateAnonymousUserId();
}
const alreadyViewed = this.post.viewedBy.some(view => view.userId === userId && view.date === today);
if (!alreadyViewed) {
await postRef.update({
views: firebase.firestore.FieldValue.increment(1),
viewedBy: firebase.firestore.FieldValue.arrayUnion({ userId, date: today })
});
this.post.views += 1; // Update the local view count
}
} else {
this.error = { message: 'Post not found' };
}
} catch (error) {
this.error = error;
} finally {
this.loading = false;
}
},
methods: {
async loadComments(postId) {
try {
const commentsSnapshot = await db.collection('posts').doc(postId).collection('comments').get();
this.comments = commentsSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
} catch (error) {
this.error = error;
}
},
async addComment() {
const postId = this.post.id;
const userName = auth.currentUser ? auth.currentUser.displayName : 'Anonymous';
const comment = {
content: this.newComment,
userName,
createdAt: new Date()
};
try {
await db.collection('posts').doc(postId).collection('comments').add(comment);
this.comments.push(comment);
this.newComment = '';
} catch (error) {
this.error = error;
}
},
formatDate(date) {
if (date && date.toDate) {
return date.toDate().toLocaleString();
}
return '';
},
getOrCreateAnonymousUserId() {
let userId = localStorage.getItem('anonymousUserId');
if (!userId) {
userId = uuidv4();
localStorage.setItem('anonymousUserId', userId);
}
return userId;
}
}
};
</script>
<style scoped>
/* Add any additional styling here */
</style>
Explanation 설명
1. Template:
- Added a section to display existing comments.
기존 댓글을 표시하는 섹션을 추가했습니다. - Added a form to submit new comments.
새로운 의견을 제출하는 양식을 추가했습니다.
2. Data:
- comments: Array to store the comments.
comments: 댓글을 저장할 배열입니다. - newComment: Model for the comment input field.
newComment: 댓글 입력 필드의 모델입니다
3. Methods:
- loadComments: Loads comments from Firestore for the current post.
loadComments: Firestore에서 현재 게시물에 대한 댓글을 로드합니다. - addComment: Adds a new comment to the Firestore comments collection and updates the local comments array.
addComment: Firestore 댓글 컬렉션에 새 댓글을 추가하고 로컬 댓글 배열을 업데이트합니다.
4. Created Hook:
- Loads the post and its comments when the component is created.
컴포넌트 생성 시 게시물과 해당 댓글을 로드합니다.
Step 2: Store Comments in Firestore
2단계: Firestore에 댓글 저장
Comments are stored in a sub-collection comments under each post document in Firestore. The addComment method handles adding new comments to this sub-collection.
댓글은 Firestore의 각 게시물 문서 아래 하위 컬렉션 'comments'에 저장됩니다. addComment 메소드는 이 하위 컬렉션에 새 댓글 추가를 처리합니다.
Step 3: Display Comments
3단계: 댓글 표시
Comments are retrieved from the Firestore sub-collection and displayed in the template using a v-list.
댓글은 Firestore 하위 컬렉션에서 검색되고 'v-list'를 사용하여 템플릿에 표시됩니다.
By following these steps, you have implemented a comment feature where users can leave comments on posts, and these comments are displayed along with the post details.
이러한 단계를 수행하면 사용자가 게시물에 댓글을 남길 수 있는 댓글 기능이 구현되었으며, 이러한 댓글은 게시물 세부정보와 함께 표시됩니다.
ChatGPT에게 물었습니다.
Please allow only logged in members to post comments.
로그인 한 회원만 댓글을 쓸수 있도록 해줘.
To allow only logged-in members to post comments, we can check the authentication state before allowing the submission of a comment. If the user is not authenticated, we can display a message prompting them to log in.
로그인한 회원만 댓글을 작성할 수 있도록 댓글 작성을 허용하기 전에 인증 상태를 확인할 수 있습니다. 사용자가 인증되지 않은 경우 로그인하라는 메시지를 표시할 수 있습니다.
Step-by-Step Implementation
단계별 구현
- Check Authentication State: Check if the user is logged in before showing the comment form.
인증 상태 확인: 댓글 양식을 표시하기 전에 사용자가 로그인되어 있는지 확인하세요. - Prompt Login: If the user is not logged in, show a prompt to log in.
프롬프트 로그인: 사용자가 로그인되어 있지 않은 경우 로그인하라는 프롬프트를 표시합니다.
Updated PostDetailView.vue
<template>
<v-container>
<v-alert v-if="error" type="error" dismissible>{{ error.message }}</v-alert>
<v-progress-circular v-if="loading" indeterminate></v-progress-circular>
<div v-if="post">
<h1>{{ post.title }}</h1>
<div>{{ post.userName }} - {{ formatDate(post.createdAt) }}</div>
<div>{{ post.content }}</div>
<div>Views: {{ post.views }}</div>
<!-- Comments Section -->
<v-divider class="my-4"></v-divider>
<h3>Comments</h3>
<v-list>
<v-list-item v-for="comment in comments" :key="comment.id">
<v-list-item-content>
<v-list-item-title>{{ comment.userName }}</v-list-item-title>
<v-list-item-subtitle>{{ comment.content }}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
<!-- Add Comment Form -->
<v-form v-if="isAuthenticated" @submit.prevent="addComment">
<v-text-field
v-model="newComment"
label="Add a comment"
required
></v-text-field>
<v-btn type="submit" color="primary">Submit</v-btn>
</v-form>
<v-alert v-else type="info" dismissible>Please log in to post a comment.</v-alert>
</div>
</v-container>
</template>
<script>
import { db, auth } from '../firebase';
import firebase from 'firebase/app';
import { v4 as uuidv4 } from 'uuid';
export default {
data() {
return {
post: null,
comments: [],
newComment: '',
loading: false,
error: null
};
},
computed: {
isAuthenticated() {
return !!auth.currentUser;
}
},
async created() {
const postId = this.$route.params.id;
this.loading = true;
try {
const postRef = db.collection('posts').doc(postId);
const postDoc = await postRef.get();
if (postDoc.exists) {
this.post = { id: postDoc.id, ...postDoc.data() };
this.loadComments(postId);
const today = new Date().toISOString().split('T')[0];
let userId;
if (auth.currentUser) {
userId = auth.currentUser.uid;
} else {
userId = this.getOrCreateAnonymousUserId();
}
const alreadyViewed = this.post.viewedBy.some(view => view.userId === userId && view.date === today);
if (!alreadyViewed) {
await postRef.update({
views: firebase.firestore.FieldValue.increment(1),
viewedBy: firebase.firestore.FieldValue.arrayUnion({ userId, date: today })
});
this.post.views += 1; // Update the local view count
}
} else {
this.error = { message: 'Post not found' };
}
} catch (error) {
this.error = error;
} finally {
this.loading = false;
}
},
methods: {
async loadComments(postId) {
try {
const commentsSnapshot = await db.collection('posts').doc(postId).collection('comments').get();
this.comments = commentsSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
} catch (error) {
this.error = error;
}
},
async addComment() {
const postId = this.post.id;
const userName = auth.currentUser.displayName;
const comment = {
content: this.newComment,
userName,
createdAt: new Date()
};
try {
await db.collection('posts').doc(postId).collection('comments').add(comment);
this.comments.push(comment);
this.newComment = '';
} catch (error) {
this.error = error;
}
},
formatDate(date) {
if (date && date.toDate) {
return date.toDate().toLocaleString();
}
return '';
},
getOrCreateAnonymousUserId() {
let userId = localStorage.getItem('anonymousUserId');
if (!userId) {
userId = uuidv4();
localStorage.setItem('anonymousUserId', userId);
}
return userId;
}
}
};
</script>
<style scoped>
/* Add any additional styling here */
</style>
Explanation 설명
1. Template
- Added a v-if="isAuthenticated" directive to the comment form, so it only appears if the user is authenticated.
댓글 양식에 v-if="isAuthenticated" 지시문을 추가하여 사용자가 인증된 경우에만 표시됩니다. - Added a v-alert message to prompt users to log in if they are not authenticated.
사용자가 인증되지 않은 경우 로그인하라는 메시지를 표시하는 'v-alert' 메시지가 추가되었습니다.
2. Computed Property:
- Added isAuthenticated computed property to determine if a user is logged in.
사용자가 로그인했는지 확인하기 위해 isAuthenticated 계산 속성을 추가했습니다.
3. Methods:
- The addComment method now only adds comments if the user is authenticated.
addComment 메소드는 이제 사용자가 인증된 경우에만 댓글을 추가합니다.
By following these steps, you ensure that only logged-in users can post comments. If a user is not logged in, they will see a message prompting them to log in.
다음 단계를 따르면 로그인한 사용자만 댓글을 게시할 수 있습니다. 사용자가 로그인하지 않은 경우 로그인하라는 메시지가 표시됩니다.
ChatGPT가 시키는 대로 했습니다.
PostDetailView.vue 수정
실행
Source Code
src/viewa/PostDetailView.vue
Copy<!-- src/viewa/PostDetailView.vue -->
<template>
<v-container>
<v-alert v-if="error" type="error" dismissible>{{ error.message }}</v-alert>
<v-progress-circular v-if="loading" indeterminate></v-progress-circular>
<v-card v-if="!loading && post">
<v-card-title>{{ post.title }}</v-card-title>
<v-card-text>{{ post.userName }} - {{ formatDate(post.createdAt) }}</v-card-text>
<v-card-text v-html="postContent"></v-card-text>
<v-card-text>Views: {{ post.views }}</v-card-text>
<!-- Comments Section -->
<v-divider class="my-4"></v-divider>
<h3>Comments</h3>
<v-list>
<v-list-item v-for="comment in comments" :key="comment.id">
<v-list-item-content>
<v-list-item-title>{{ comment.content}}</v-list-item-title>
<v-list-item-subtitle>{{ comment.userName }}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
<!-- Add Comment Form -->
<v-form v-if="isAuthenticated" @submit.prevent="addComment">
<v-text-field v-model="newComment" label="Add a comment" required></v-text-field>
<v-btn type="submit" color="primary">Submit</v-btn>
</v-form>
<v-alert v-else type="info" dismissible>Please log in to post a comment.</v-alert>
</v-card>
</v-container>
</template>
<script>
import { db } from '../firebase';
import firebase from 'firebase/compat/app';
import sanitizeHtml from 'sanitize-html';
import { v4 as uuidv4 } from 'uuid'; // Importing uuidv4
export default {
data() {
return {
post: null,
comments: [], // 댓글
newComment: '',
postContent: '',
loading: false,
error: null
};
},
computed: {
isAuthenticated() {
//return !!auth.currentUser;
return this.$store.state.auth.user;
}
},
async created() {
this.loading = true;
const postId = this.$route.params.id;
try {
const postRef = db.collection('posts').doc(postId);
//const postDoc = await db.collection('posts').doc(postId).get();
const postDoc = await postRef.get();
if (postDoc.exists) {
this.post = postDoc.data();
this.postContent = this.sanitizeContent(this.post.content);
this.loadComments(postId);
const today = new Date().toISOString().split('T')[0];
let userId;
const user = this.$store.state.auth.user;
if (user) {
//userId = auth.currentUser.uid;
userId = user.id;
} else {
userId = this.getOrCreateAnonymousUserId();
}
const alreadyViewed = this.post.viewedBy.some(view => view.userId === userId && view.date === today);
if (!alreadyViewed) {
await postRef.update({
views: firebase.firestore.FieldValue.increment(1),
viewedBy: firebase.firestore.FieldValue.arrayUnion({ userId, date: today })
});
this.post.views += 1; // Update the local view count
}
} else {
this.error = { message: 'Post not found' };
}
} catch (error) {
this.error = error;
} finally {
this.loading = false;
}
},
methods: {
sanitizeContent(content) {
return sanitizeHtml(content.replace(/\n/g, '<br>'), {
allowedTags: ['b', 'i', 'em', 'strong', 'p', 'br'],
allowedAttributes: {}
});
},
async loadComments(postId) {
try {
const commentsSnapshot = await db.collection('posts').doc(postId).collection('comments').get();
this.comments = commentsSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
} catch (error) {
this.error = error;
}
},
async addComment() {
//const postId = this.post.id;
const postId = this.$route.params.id;
//const userName = auth.currentUser.displayName;
const user = this.$store.state.auth.user;
const userName = user.name;
const comment = {
content: this.newComment,
userName,
createdAt: new Date()
};
try {
await db.collection('posts').doc(postId).collection('comments').add(comment);
this.comments.push(comment);
this.newComment = '';
} catch (error) {
this.error = error;
}
},
formatDate(date) {
if (date && date.toDate) {
return date.toDate().toLocaleString();
}
return '';
},
getOrCreateAnonymousUserId() {
let userId = localStorage.getItem('anonymousUserId');
if (!userId) {
userId = uuidv4();
localStorage.setItem('anonymousUserId', userId);
}
return userId;
}
}
};
</script>
'PWA' 카테고리의 다른 글
ChatGPT와 함께 PWA Blog 개발 - 댓글 삭제 (0) | 2024.08.24 |
---|---|
ChatGPT와 함께 PWA Blog 개발 - 답글 (0) | 2024.08.23 |
ChatGPT와 함께 PWA Blog 개발 - 조회수 구현 (0) | 2024.08.22 |
ChatGPT와 함께 PWA Blog 개발 - 조회수 (0) | 2024.08.22 |
ChatGPT와 함께 PWA Blog 개발 - 리스트에 글쓴이 (0) | 2024.08.21 |