Twitterクローンを作ります #29 ツイート表示ページ(2)

前回ツイート表示ページを大まかに作ったので細かいところを修正していきましょう。

- 選択したツイートを強調表示
- ツイートをクリックしたらツイート表示ページへ遷移
- ツイート表示ページでのいいねやリツイートの挙動

# ツイートの強調表示

```vue:PostPanel.vue
<template>
  <div class="uk-margin-right">
...
        <div
          class="uk-flex uk-margin-small-top"
          :class="{ 'uk-text-large': main }"
        >
          {{ displayPost.content }}
        </div>
...
  </div>
</template>

<script>
import { computed } from "vue";
import { useStore } from "vuex";

import AvatarPic from "@/components/AvatarPic.vue";
import DisplayName from "@/components/DisplayName.vue";
import UsernameLink from "@/components/UsernameLink.vue";

export default {
  components: {
    AvatarPic,
    DisplayName,
    UsernameLink,
  },
  props: {
    post: Object,
    disableLink: Boolean,
    main: {
      type: Boolean,
      default: false,
    },
  },
  setup(props, context) {
...
  },
};
</script>

```

PostPanel に main を指定したら文字を大きくするようにします。

```vue:client/src/views/PostView.vue
<template>
  <div>
    <h3 class="uk-margin-top">ツイート</h3>
    <post-panel
      v-if="Boolean(post.reply_to)"
      :post="post.reply_to"
    ></post-panel>
    <post-panel :post="post" main="true"></post-panel>
    <post-panel
      v-for="reply in post.replies"
      :post="reply"
      :key="reply._id"
    ></post-panel>
  </div>
</template>

...

```

![スクリーンショット 2022-03-24 8.37.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536525/7197e559-ceee-86c5-9e20-ef26ac40bd08.png)

選択したツイートがどれかがわかりやすくなりましたね。

# ツイートのクリック

PostPanelの一番外側のdivにクリックのハンドラを設定しましょう。
返信のダイアログでは遷移したくないので、disableLinkフラグで動作を切り替えられるようにしておきます。

また、PostPanel全体にクリックハンドラを設定してしまうと、その中のいいねボタンなどをクリックしたときも遷移が発生してしまいます。これを回避するために、いいねボタンなどのクリックハンドラの指定を `@click.stop="xxxx"` のようにして親のクリックイベントが起きないようにしておきます。

```vue:client/src/components/PostPanel.vue
<template>
  <div
    class="uk-margin-right"
    :class="{ clickable: !disableLink }"
    @click="onBodyClick"
  >
    <div class="uk-margin-small-left uk-margin-small-bottom" v-if="isRetweet">
      <router-link
        @click.stop
        :to="'/profile/' + post.posted_by.username"
        class="uk-link-text"
      >
        {{ post.posted_by.display_name }}がリツイート
      </router-link>
    </div>
    <div class="uk-flex uk-flex-top">
      <avatar-pic
        @click.stop
        :user="displayPost.posted_by"
        :disableLink="disableLink"
      ></avatar-pic>
      <div class="uk-flex uk-flex-left uk-flex-column uk-margin-left uk-flex-1">
        <div class="uk-flex uk-flex-middle uk-flex-row">
          <display-name
            @click.stop
            :user="displayPost.posted_by"
            :disableLink="disableLink"
          ></display-name>
          <username-link
            @click.stop
            :user="displayPost.posted_by"
            :disableLink="disableLink"
            class="uk-margin-small-left"
          ></username-link>
          <span class="uk-margin-small-left uk-text-muted">
            {{ displayPost.created_at }}
          </span>
          <span class="uk-flex-1"></span>
          <span uk-icon="icon: close; ratio: 0.8"></span>
        </div>
        <div v-if="isReply && !disableLink">
          <router-link
            @click.stop
            :to="
              displayPost.reply_to
                ? '/profile/' + displayPost.reply_to.posted_by.username
                : ''
            "
          >
            @{{
              displayPost.reply_to
                ? displayPost.reply_to.posted_by.username
                : ""
            }}
          </router-link>
          へ返信
        </div>
        <div
          class="uk-flex uk-margin-small-top"
          :class="{ 'uk-text-large': main }"
        >
          {{ displayPost.content }}
        </div>
        <div
          class="uk-flex uk-flex-between uk-margin-small-top"
          v-if="!disableLink"
        >
          <!-- 返信 -->
          <span class="uk-flex-1 reply">
            <span uk-icon="comment" @click.stop="onReplyClick"></span>
            <span class="uk-margin-small-left">
              {{ displayPost.reply_count ? displayPost.reply_count : "" }}
            </span>
          </span>
          <!-- リツイート -->
          <span
            class="uk-flex-1 retweet"
            :class="{ active: displayPost.retweeting }"
          >
            <span uk-icon="bolt" @click.stop="onRetweetClick"></span>
            <span class="uk-margin-small-left">{{
              displayPost.retweet_count ? displayPost.retweet_count : ""
            }}</span>
          </span>
          <!-- いいね -->
          <span class="uk-flex-1 like" :class="{ active: displayPost.liking }">
            <span uk-icon="heart" @click.stop="onLikeClick"></span>
            <span class="uk-margin-small-left">{{
              displayPost.like_count ? displayPost.like_count : ""
            }}</span>
          </span>
        </div>
      </div>
    </div>
    <hr />
  </div>
</template>

<script>
import { computed } from "vue";
import { useRouter } from "vue-router";
import { useStore } from "vuex";

import AvatarPic from "@/components/AvatarPic.vue";
import DisplayName from "@/components/DisplayName.vue";
import UsernameLink from "@/components/UsernameLink.vue";

export default {
  components: {
    AvatarPic,
    DisplayName,
    UsernameLink,
  },
  props: {
    post: Object,
    disableLink: Boolean,
    main: {
      type: Boolean,
      default: false,
    },
  },
  setup(props, context) {
...

    const router = useRouter();
    const onBodyClick = () => {
      if (props.disableLink) {
        return;
      }
      router.push(`/posts/${displayPost.value._id}`);
    };

    return {
      isRetweet,
      isReply,
      displayPost,
      onLikeClick,
      onRetweetClick,
      onReplyClick,
      onBodyClick,
    };
  },
};
</script>

<style scoped>
...

.clickable {
  cursor: pointer;
}
</style>
```

これでツイートをクリックして遷移できるようになりました。

# ツイート表示ページでのいいねやリツイートの挙動

ツイートページでいいねやリツイートボタンをおすとAPIは呼び出されるのですがいいね数やハイライトが更新されないのでこれを修正します。

```javascript:client/src/store/index.js
...
    updatePost(state, args) {
      const orig = state.posts.find((p) => p._id === args.post._id);
      if (orig) {
        Object.assign(orig, args.post);
      }
      state.posts
        .filter(
          (p) => p.retweeted_post && p.retweeted_post._id === args.post._id
        )
        .forEach((p) => Object.assign(p.retweeted_post, args.post));

      if (state.post._id === args.post._id) {
        Object.assign(state.post, args.post);
      }

      if (state.post.reply_to && state.post.reply_to._id === args.post._id) {
        Object.assign(state.post.reply_to, args.post);
      }

      state.post.replies
        .filter((p) => p._id === args.post._id)
        .forEach((p) => Object.assign(p, args.post));
    },
...
```

ツイート表示ページはこんな感じでいいでしょう。