summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/vue_shared/components/gl_mentions.vue
blob: 0ef4f1eda2747526927ba0ad26a34cb4a105fa73 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
<script>
import { escape } from 'lodash';
import Tribute from 'tributejs';
import axios from '~/lib/utils/axios_utils';
import { spriteIcon } from '~/lib/utils/common_utils';
import SidebarMediator from '~/sidebar/sidebar_mediator';

/**
 * Creates the HTML template for each row of the mentions dropdown.
 *
 * @param original - An object from the array returned from the `autocomplete_sources/members` API
 * @returns {string} - An HTML template
 */
function menuItemTemplate({ original }) {
  const rectAvatarClass = original.type === 'Group' ? 'rect-avatar' : '';

  const avatarClasses = `avatar avatar-inline center s26 ${rectAvatarClass}
    gl-display-inline-flex! gl-align-items-center gl-justify-content-center`;

  const avatarTag = original.avatar_url
    ? `<img
        src="${original.avatar_url}"
        alt="${original.username}'s avatar"
        class="${avatarClasses}"/>`
    : `<div class="${avatarClasses}">${original.username.charAt(0).toUpperCase()}</div>`;

  const name = escape(original.name);

  const count = original.count && !original.mentionsDisabled ? ` (${original.count})` : '';

  const icon = original.mentionsDisabled
    ? spriteIcon('notifications-off', 's16 gl-vertical-align-middle gl-ml-3')
    : '';

  return `${avatarTag}
    ${original.username}
    <small class="gl-text-small gl-font-weight-normal gl-reset-color">${name}${count}</small>
    ${icon}`;
}

export default {
  name: 'GlMentions',
  props: {
    dataSources: {
      type: Object,
      required: false,
      default: () => gl.GfmAutoComplete?.dataSources || {},
    },
  },
  data() {
    return {
      assignees: undefined,
      members: undefined,
    };
  },
  mounted() {
    this.tribute = new Tribute({
      trigger: '@',
      fillAttr: 'username',
      lookup: value => value.name + value.username,
      menuItemTemplate,
      values: this.getMembers,
    });

    const input = this.$slots.default[0].elm;
    this.tribute.attach(input);
  },
  beforeDestroy() {
    const input = this.$slots.default[0].elm;
    this.tribute.detach(input);
  },
  methods: {
    /**
     * Creates the list of users to show in the mentions dropdown.
     *
     * @param inputText - The text entered by the user in the mentions input field
     * @param processValues - Callback function to set the list of users to show in the mentions dropdown
     */
    getMembers(inputText, processValues) {
      if (this.members) {
        processValues(this.getFilteredMembers());
      } else if (this.dataSources.members) {
        axios
          .get(this.dataSources.members)
          .then(response => {
            this.members = response.data;
            processValues(this.getFilteredMembers());
          })
          .catch(() => {});
      } else {
        processValues([]);
      }
    },
    getFilteredMembers() {
      const fullText = this.$slots.default[0].elm.value;

      if (!this.assignees) {
        this.assignees =
          SidebarMediator.singleton?.store?.assignees?.map(assignee => assignee.username) || [];
      }

      if (fullText.startsWith('/assign @')) {
        return this.members.filter(member => !this.assignees.includes(member.username));
      }

      if (fullText.startsWith('/unassign @')) {
        return this.members.filter(member => this.assignees.includes(member.username));
      }

      return this.members;
    },
  },
  render(createElement) {
    return createElement('div', this.$slots.default);
  },
};
</script>