Vue PWA mylog Source Code

MyLogView.vue

그랜파 개발자 2024. 11. 26. 04:56
<!-- src/views/MyLogView.vue -->
<template>
  <v-container>
    <v-card v-if="mylog">

      <!--글쓴이의 마이로그 이름-->
      <v-card-title v-if=author style="cursor: pointer; font-size:1em" @click="goToUserMylogs(author.id)">
        {{ getMylogName }}<v-icon>mdi-chevron-right</v-icon>
      </v-card-title>

      <!-- 마이로그 제목 -->
       <v-card-title v-if=author style="cursor: pointer; font-size:1em">
        {{ mylog.title }}
       </v-card-title>

       <!-- 마이로그 내용 -->
      <!-- eslint-disable -->
      <v-card-text style="font-size:1em" v-html="content"></v-card-text>
      <!-- eslint-enable -->

      <!-- 글쓴이 이름, 작성일, 조회수 -->
      <v-card-subtitle style="text-align:right">
          {{ mylog.userName }} . {{ mylog.createdAt.toDate().toISOString().split('T')[0] }} 
          <span v-if="mylog.views > 0" > ({{ mylog.views }})</span>
      </v-card-subtitle>      

      <v-card-actions >
        <v-spacer></v-spacer>

        <!-- 구독 버튼 -->
        <v-btn small text v-if="!isAuthor && user" @click="toggleSubscription">
          {{ isSubscribed ? "구독 취소" : "구독" }} &nbsp;<v-icon>mdi-check-circle</v-icon>
        </v-btn>
        
        <!-- 게시물 수정 버튼 -->
        <v-btn small text v-if="isAuthor" @click="goToEditMylog()">
          수정 &nbsp; <v-icon>mdi-pencil</v-icon>
        </v-btn>
      </v-card-actions>  

      <!-- 댓글 작성 폼 -->
      <v-form v-if="user" @submit.prevent="submitComment" class="ml-2 mr-2">
        <v-textarea outlined style="border-color: #fffeee;" v-model="newComment" label="댓글 쓰기 ..." rows="3" required></v-textarea> 
        <v-btn class="ml-1 mt-n10" small outlined type="submit" color="primary">댓글</v-btn>       
      </v-form>
      
      <v-list >
        <!-- 댓글 리스트 -->
        <v-list-item v-for="comment in comments" :key="comment.id"> 
          <v-list-item-content>
            <!-- eslint-disable -->
            <v-list-item-subtitle style="white-space: normal;" v-html="sanitizeContent(comment.content)"></v-list-item-subtitle>
            <!--댓글 -->
            <!-- eslint-enable -->
            <v-list-item-subtitle class="mt-1"> 
              {{ comment.userName }} . {{ formatDate(comment.createdAt) }}                 
              <v-btn v-if="isOwner(comment.userId)" icon @click="doDeleteComment(comment.id)">
                <v-icon>mdi-delete</v-icon>
              </v-btn>
              <!-- reply 버튼 -->  
              <v-btn v-if="user" small text @click="showReplyForm(comment.id)">답글</v-btn>
            </v-list-item-subtitle>    
            
            <!-- 답글 리스트 -->
            <v-list class="ml-5" v-if="comment.replies && comment.replies.length">
              <!-- 답글 -->
              <v-list-item v-for="reply in comment.replies" :key="reply.id">
                <v-list-item-content>
                  <!-- eslint-disable -->
                  <v-list-item-subtitle style="white-space: normal;" v-html="sanitizeContent(reply.content)"></v-list-item-subtitle>
                  <!-- eslint-enable -->
                  <v-list-item-subtitle class="ml-2">
                    {{ reply.userName }} : {{ formatDate(reply.createdAt) }}
                    <v-btn v-if="isOwner(reply.userId)" icon @click="doDeleteReply(comment.id, reply.id)">
                      <v-icon>mdi-delete</v-icon>
                    </v-btn>
                  </v-list-item-subtitle>
                </v-list-item-content>
              </v-list-item>
            </v-list>
                      
            <!-- 답글 쓰기-->
            <v-form class="ml-5 mt-1 mr-2" v-if="replyingTo === comment.id" @submit.prevent="submitReply(comment.id)">
              <v-textarea  outlined style="border-color: #fffeee; width: 90%" v-model="newReply" label="답글 쓰기 ..." rows="3" required></v-textarea>
              <v-btn class="ma-0 mt-n12" small text @click="showReplyForm(null)">취소</v-btn>&nbsp;
              <v-btn class="ma-0 mt-n12" small text type="submit" color="primary">답글</v-btn>
            </v-form>
            <!-- reply end --->

          </v-list-item-content>
        </v-list-item>
      </v-list> 
    </v-card>

    <v-alert v-else-if="!loading" type="error" dismissible @input="resetErrorMsg" class="my-alert">
      Mylog not found. Please check the link and try again.
    </v-alert>
    <div class="text-center">
      <v-progress-circular v-if="loading" indeterminate></v-progress-circular>
    </div>
  </v-container>
</template>

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

export default {
  data() {
    return {
      content: '',
      newComment: '',
      newReply: '',
      replyingTo: null,
      author: null, // 저자의 이름을 가져오기 위해
    };
  },
  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로 변경한다.

    // 마이로그 저자 찾기 - 상태에 로드되어 있는 전체 회원 정보를 이용한다.
    const users = this.$store.state.auth.users;
    this.author = users.find(user => user.id === this.mylog.userId);

    const userId = this.$store.state.auth.user ? this.$store.state.auth.user.id : null; // 로그인 여부 확인

    // 마이로그의 저자에 대해 현재 로그인한 회원이 구독중인지 확인한다.
    const authorId =  this.mylog.userId;
    if(userId) {
      this.checkSubscription({userId, authorId}); // Check if the user is subscribed when the component mounts
    }

    // 조회수를 증가한다.
    // 같은 회원은 몇번을 방문해도 하루 1회, 비회원도 조회수 증가시킴
    await this.updateViewCount(mylogId, userId); // 조회수 업데이트 및 기록

    // comments를 로드한다.
    await this.fetchComments(mylogId);
    
  },
  computed: {
    ...mapGetters('auth',['user']), 
    ...mapGetters('mylogs',['mylog', 'comments', 'isSubscribed', 'subscriptionId', 'loading']), 
    getMylogName() {
      return this.author.mylogname;
    },
    // 저자인가? - 작성자와 로그인한 사용자 비교
    isAuthor() {
      return this.$store.state.auth.user && this.$store.state.auth.user.id === this.mylog.userId;
    },
  },
  methods: {  
    ...mapActions('mylogs', ['fetchMylog', 'resetError', 'updateViewCount','addComment', 'fetchComments','deleteComment', 
      'addReply', 'deleteReply', 'checkSubscription', 'subscribeToUser', 'unsubscribeFromUser']),

    // content를 안전한 html로 바꿔준다.
    sanitizeContent(content) {
      return sanitizeHtml(content.replace(/\n/g, '<br>'), {
        allowedTags: ['b', 'i', 'em', 'strong', 'p', 'br'],
        allowedAttributes: {}
      });
    },
    // 마이로그 수정
    goToEditMylog() {
      this.$router.push({ name: 'edit', 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) {
      if (date && date.toDate) {
        return date.toDate().toISOString().replace('T', ' ').substring(0, 16); 
      }
      return '';
    },
    // 답글 작성 폼 보이기
    showReplyForm(commentId) {
      this.replyingTo = commentId;
    },
    // 로그인한 회원이 마이로그 작성자인가?
    isOwner(userId) {
      if(!this.$store.state.auth.user)
        return false;
      return userId === this.$store.state.auth.user.id;
    },
    // 답글 작성
    async submitReply(commentId) {
      if (this.newReply && this.newReply.trim()) {
        await this.addReply({
          mylogId: this.$route.params.id,
          commentId: commentId,
          content: this.newReply,
          userName: this.$store.state.auth.user.username,
          userId: this.$store.state.auth.user.id,
        });
        this.newReply = ''; // 입력 필드 초기화
        this.replyingTo = null;
      }
    },
    // 답글 삭제 실행
    async doDeleteReply(commentId, replyId) {
      const mylogId = this.$route.params.id;
      await this.deleteReply({
        mylogId: mylogId, 
        commentId: commentId,
        replyId: replyId
      }); 
    },
    // -- 구독 -------------------------------
    // Toggle subscription: subscribe if not subscribed, unsubscribe if already subscribed
    async toggleSubscription() {

      const userId = this.$store.state.auth.user.id;
      const authorId =  this.mylog.userId;
      const subscriptionId =  this.subscriptionId;
      
      if (this.isSubscribed) {
        // Unsubscribe the user        
        await this.unsubscribeFromUser({userId, subscriptionId});
      } else {
        // Subscribe the user
        await this.subscribeToUser({userId, authorId});
      }

      // 변경 내용 갱신을 위하여
      this.checkSubscription({userId, authorId}); // Check if the user is subscribed when the component mounts
    },
    // -- 구독 끝 ----------------------------
    // -- 저자의 마이로그 홈으로 가기
    goToUserMylogs(userId) {
      this.$router.push({ name: 'userlogs', params: { userId } });
    },
  }
};
</script>

'Vue PWA mylog Source Code' 카테고리의 다른 글

NotificationView.vue  (0) 2024.12.01
MyMyLogsView.vue  (0) 2024.11.29
LoginView.vue  (0) 2024.11.23
HomeView.vue  (0) 2024.11.19
EditMyLogView.vue  (1) 2024.11.18