Vue로 PWA 개발

32. mylog 댓글 쓰기 구현

그랜파 개발자 2024. 10. 26. 15:09

마이로그 상세 보기 하단에 댓글들의 리스트를 볼 수 있고, 로그인 후에는 누구나 마이로그에 대한 댓글을 쓸 수 있습니다. 댓글 리스트를 나타내기 위해서는 마이로그의 댓글들 전체 리스트를 로드하여야 합니다. 댓글을 쓰거나 삭제를 하면 댓글들의 리스트를 다시 로드합니다.

1. 댓글 쓰기

2. src/views/MyLogView.vue

<!-- src/views/MyLogView.vue -->
<template>
  <v-container>
    <v-row justify="center">
      <v-col cols="12" md="8">
        <v-card v-if="mylog">
          <v-card-title>{{ mylog.title }}</v-card-title>
          <v-card-subtitle>
            {{ mylog.userName }} Posted on: {{ mylog.createdAt.toDate().toLocaleString() }} ({{ mylog.views }})
          </v-card-subtitle>
          <v-card-text>
            <!-- eslint-disable -->
            <v-card-text v-html="content"></v-card-text>
            <!-- eslint-enable -->
          </v-card-text>

          <v-card-actions v-if="isAuthor">
            <!-- 게시물 수정 버튼 -->
            <v-btn @click="goToEditMylog()"> 수정 &nbsp; <v-icon>mdi-pencil</v-icon></v-btn>
          </v-card-actions>  

          <!-- 댓글 목록 -->
          <v-divider class="my-2"></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 }} : {{ formatDate(comment.createdAt) }} 
                    <v-btn v-if="isCommentOwner(comment.userId)" icon @click="doDeleteComment(comment.id)">
                      <v-icon>mdi-delete</v-icon>
                    </v-btn>
                  </v-list-item-subtitle>
              </v-list-item-content>
            </v-list-item>
          </v-list>

          <!-- 댓글 작성 폼 -->
          <v-form v-if="isAuthenticated" @submit.prevent="submitComment">
            <v-textarea v-model="newComment" label="Write a comment..." rows="3" required></v-textarea>
            <v-btn small outlined type="submit" color="primary"> 댓글 쓰기 </v-btn>
          </v-form>

        </v-card>
        <v-alert v-else type="error" dismissible @input="resetErrorMsg" class="my-alert">
          Mylog not found. Please check the link and try again.
        </v-alert>
        <!-- <v-alert v-if="error" type="error" dismissible @input="resetErrorMsg" class="my-alert">{{ error }}</v-alert> -->
      </v-col>
    </v-row>
  </v-container>
</template>

<script>
import { mapActions, mapGetters } from "vuex";
import sanitizeHtml from 'sanitize-html';

export default {
  data() {
    return {
      content: '',
      newComment: '',
    };
  },
  async created() {
    // 마이로그 가죠오기
    const mylogId = this.$route.params.id; // Get the article ID from the route parameters
    await this.fetchMylog(mylogId); // 마이로그 아이디로 마이로그를 가져온다.
    this.content = this.sanitizeContent(this.mylog.content);  // 내용에 포한된 html을 안전한 html로 변경한다.

    // 조회수를 증가한다.
    // 같은 회원은 몇번을 방문해도 하루 1회, 비회원도 조회수 증가시킴
    const userId = this.$store.state.auth.user ? this.$store.state.auth.user.id : null; // 로그인 여부 확인  
    await this.updateViewCount(mylogId, userId); // 조회수 업데이트 및 기록

    // comments를 로드한다.
    await this.fetchComments(mylogId); // 조회수 업데이트 및 기록
  },  
  computed: {
     // 'mylog' mylogs store의 mylog getter로서 현재 선택된 마이로그를 돌려준다. 
    ...mapGetters('mylogs',['mylog', 'comments']),  
    // 작성자와 로그인한 사용자 비교
    isAuthor() {
      return this.$store.state.auth.user && this.$store.state.auth.user.id === this.mylog.userId;
    },
    isAuthenticated() {
      //return !!auth.currentUser;
      return this.$store.state.auth.user;
    }
  },
  methods: { 
    // 'fetchMylog' mylogs의 action 함수로 마이로그를 firestore 에서 읽어온다. 
    ...mapActions('mylogs', ['fetchMylog', 'resetError', 'updateViewCount','addComment', 'fetchComments','deleteComment']),

    // content를 안전한 html로 바꿔준다.
    sanitizeContent(content) {
      return sanitizeHtml(content.replace(/\n/g, '<br>'), {
        allowedTags: ['b', 'i', 'em', 'strong', 'p', 'br'],
        allowedAttributes: {}
      });
    },
    goToEditMylog() {
      this.$router.push({ name: 'EditMyLogView', params: { mylogId: this.$route.params.id, mylog: this.mylog } });
    },
    resetErrorMsg() {
      this.resetError();
    },
    async submitComment() {
      //myLogId, content, autherName, author
      if (this.newComment.trim()) {
        await this.addComment({
          mylogId: this.$route.params.id,
          content: this.newComment,
          userName: this.$store.state.auth.user.username,
          userId: this.$store.state.auth.user.id,
        });
        this.newComment = ''; // 댓글 입력 필드 초기화
      }
    },
    async doDeleteComment(commentId) {
      await this.deleteComment({
        mylogId: this.$route.params.id, 
        commentId: commentId
      });      
    },
    formatDate(date) {
      //console.log(date.toDate().toLocaleString());
      if (date && date.toDate) {
        return date.toDate().toLocaleString();
      }
      return '';
    },
    isCommentOwner(userId) {
      //console.log(userId, this.$store.state.auth.user.id);
      //console.log(userId === this.$store.state.auth.user.id);
      return userId === this.$store.state.auth.user.id;
    },
  }
};
</script>

3. src/store/modules/mylogs.js

Copy  // comments 가져오기
  async fetchComments({ commit }, mylogId) {
    try {
      const q = query(collection(db, "mylogs", mylogId, "comments"), orderBy("createdAt", "asc"));
      const querySnapshot = await getDocs(q);
      const comments = [];
      querySnapshot.forEach((doc) => {
        comments.push({ id: doc.id, ...doc.data() });
      });
      commit('setComments', comments); // 댓글을 상태에 저장
    } catch (error) {
      commit("setError", error);
      console.error("댓글 가져오기 실패:", error);
    }
  },
  // comments 추가 하기
  async addComment({ commit }, { mylogId, content, userName, userId }) {
    try {
      const commentRef = await addDoc(collection(db, "mylogs", mylogId, "comments"), {
        content: content,
        userName: userName,
        userId: userId,
        //createdAt: serverTimestamp(),
        createdAt: new Date(),
      });
      const newComment = { id: commentRef.id, content, userName, userId, createdAt: new Date() };
      commit('addComment', newComment); // 새 댓글을 상태에 추가
    } catch (error) {
      commit("setError", error);
      console.error("댓글 추가 실패:", error);
    }
  },
  // comments 삭제 하기
  async deleteComment({ commit, dispatch }, { mylogId, commentId }) {
    const commentRef = doc(db, "mylogs", mylogId, "comments", commentId);
    try {
      await deleteDoc(commentRef);
      dispatch('fetchComments', mylogId); // 댓글 다시 로그
      //this.comments = this.comments.filter(c => c.id !== commentId);
      console.log("댓글이 삭제되었습니다.");
    } catch (error) {
      commit("setError", error);
      console.error("댓글 삭제 실패:", error);
    }
  }



'Vue로 PWA 개발' 카테고리의 다른 글

34. mylog 답글 구현  (0) 2024.10.26
33. mylog 답글  (0) 2024.10.26
31. mylog 댓글 쓰기  (0) 2024.10.26
30. mylog 수정, 내 마이로그 보기 구현  (0) 2024.10.25
29. mylog 모아보기  (0) 2024.10.25