loomio/loomio

View on GitHub
vue/src/components/strand/members_list.vue

Summary

Maintainability
Test Coverage
<script lang="js">
import EventBus from '@/shared/services/event_bus';
import Records from '@/shared/services/records';
import Session from '@/shared/services/session';
import Flash from '@/shared/services/flash';
import RecipientsAutocomplete from '@/components/common/recipients_autocomplete';
import DiscussionReaderService from '@/shared/services/discussion_reader_service';
import {map, debounce} from 'lodash-es';

export default {
  components: {
    RecipientsAutocomplete
  },

  props: {
    discussion: Object
  },

  data() {
    return {
      readers: [],
      query: '',
      searchResults: [],
      recipients: [],
      membershipsByUserId: {},
      readerUserIds: [],
      reset: false,
      saving: false,
      message: '',
      actionNames: [],
      service: DiscussionReaderService
    };
  },

  mounted() {
    this.actionNames = ['makeAdmin', 'removeAdmin', 'revoke']; // 'resend'

    this.watchRecords({
      collections: ['discussionReaders', 'memberships'],
      query: records => this.updateReaders()
    });
  },

  computed: {
    hasRecipients() {
      return this.discussion.recipientAudience ||
      this.discussion.recipientUserIds.length ||
      this.discussion.recipientChatbotIds.length ||
      this.discussion.recipientEmails.length;
    },

    model() { return this.discussion; },

    excludedUserIds() {
      return this.readerUserIds.concat(Session.user().id);
    }
  },

  methods: {
    isGroupAdmin(reader) {
      return this.discussion.groupId && 
      this.membershipsByUserId[reader.userId] &&
      this.membershipsByUserId[reader.userId].admin;
    },

    inviteRecipients() {
      const count = this.recipients.length;
      this.saving = true;
      const params = Object.assign(
        {discussion_id: this.discussion.id}
      , {
        recipient_audience: this.discussion.recipientAudience,
        recipient_user_ids: this.discussion.recipientUserIds,
        recipient_chatbot_ids: this.discussion.recipientChatbotIds,
        recipient_emails: this.discussion.recipientEmails,
        recipient_message: this.message
      }
      );
      Records.remote.post('announcements', params).then(() => {
        this.reset = !this.reset;
        Flash.success('announcement.flash.success', { count });
      }).catch(error => {
        Flash.custom(error.error, 'error', 5000);
      }).finally(() => {
        this.saving = false;
      });
    },

    newQuery(query) {
      this.query = query;
      this.updateReaders();
      this.fetchReaders();
    },

    newRecipients(recipients) { this.recipients = recipients; },

    fetchReaders: debounce(function() {
      Records.discussionReaders.fetch({
        params: {
          exclude_types: 'discussion',
          query: this.query,
          discussion_id: this.discussion.id
        }
      }).then(records => {
        const userIds = map(records['users'], 'id');
        Records.memberships.fetch({
          params: {
            exclude_types: 'group inviter',
            group_id: this.discussion.groupId,
            user_xids: userIds.join('x')
          }
        });
      }).finally(() => this.updateReaders());
    } , 300),

    updateReaders() {
      let chain = Records.discussionReaders.collection.chain().
              find({discussionId: this.discussion.id}).
              find({revokedAt: null});

      if (this.query) {
        const users = Records.users.collection.find({
          $or: [
            {name: {'$regex': [`^${this.query}`, "i"]}},
            {email: {'$regex': [`${this.query}`, "i"]}},
            {username: {'$regex': [`^${this.query}`, "i"]}},
            {name: {'$regex': [` ${this.query}`, "i"]}}
          ]});
        chain = chain.find({userId: {$in: map(users, 'id')}});
      }

      chain = chain.simplesort('id', true);
      this.readers = chain.data();
      this.readerUserIds = map(Records.discussionReaders.collection.find({discussionId: this.discussion.id}), 'userId');

      this.membershipsByUserId = {};
      Records.memberships.collection.find({userId: {$in: this.readerUserIds}},
                                          {groupId: this.discussion.groupId}).forEach(m => {
        this.membershipsByUserId[m.userId] = m;
      });
    }
  }
};

</script>

<template lang="pug">
.strand-members-list
  .px-4.pt-4
    .d-flex.justify-space-between
      h1.text-h5(v-t="'announcement.form.discussion_announced.title'")
      dismiss-modal-button

    recipients-autocomplete(
      :label="$t('announcement.form.discussion_announced.helptext')"
      :placeholder="$t('announcement.form.placeholder')"
      :model="discussion"
      :excluded-audiences="['discussion_group']"
      :reset="reset"
      @new-query="newQuery"
      @new-recipients="newRecipients")

    v-textarea(
      v-if="hasRecipients"
      filled
      rows="3"
      v-model="message"
      :label="$t('announcement.form.invitation_message_label')"
      :placeholder="$t('announcement.form.invitation_message_placeholder')"
    )

    .d-flex
      v-spacer
      v-btn.strand-members-list__submit(
        color="primary"
        :disabled="!recipients.length"
        :loading="saving"
        @click="inviteRecipients"
        v-t="'common.action.invite'")

  v-list(two-line)
    v-subheader
      span(v-t="'membership_card.discussion_members'")
      space
      span ({{discussion.membersCount}})
    v-list-item(v-for="reader in readers" :user="reader.user()" :key="reader.id")
      v-list-item-avatar
        user-avatar(:user="reader.user()" :size="24")
      v-list-item-content
        v-list-item-title
          span.mr-2 {{reader.user().nameWithTitle(discussion.group())}}
          v-chip.mr-1(v-if="discussion.groupId && reader.guest" outlined x-small label v-t="'members_panel.guest'" :title="$t('announcement.inviting_guests_to_thread')")
          v-chip.mr-1(v-if="reader.admin" outlined x-small label v-t="'announcement.members_list.thread_admin'")
          v-chip.mr-1(v-if="isGroupAdmin(reader)" outlined x-small label v-t="'announcement.members_list.group_admin'")
          v-chip.mr-1(v-if="!reader.user().emailVerified" outlined x-small label v-t="'announcement.members_list.has_not_joined_yet'" :title="$t('announcement.members_list.has_not_joined_yet_hint')")
        v-list-item-subtitle
          span(v-if="reader.lastReadAt" v-t="{ path: 'announcement.members_list.last_read_at', args: { time: approximateDate(reader.lastReadAt) } }")
          span(v-else v-t="'announcement.members_list.has_not_read_thread'")
          //- time-ago(:date="reader.lastReadAt")
      v-list-item-action
        v-menu(offset-y)
          template(v-slot:activator="{on, attrs}")
            v-btn.membership-dropdown__button(icon v-on="on" v-bind="attrs")
              common-icon(name="mdi-dots-vertical")
          v-list
            v-list-item(v-for="action in actionNames" v-if="service[action].canPerform(reader)" @click="service[action].perform(reader)" :key="action")
              v-list-item-title(v-t="service[action].name")
    v-list-item(v-if="query && readers.length == 0")
      v-list-item-title(v-t="{ path: 'discussions_panel.no_results_found', args: { search: query }}")
</template>