summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/emoji
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/emoji')
-rw-r--r--app/assets/javascripts/emoji/awards_app/index.js43
-rw-r--r--app/assets/javascripts/emoji/awards_app/store/actions.js51
-rw-r--r--app/assets/javascripts/emoji/awards_app/store/index.js20
-rw-r--r--app/assets/javascripts/emoji/awards_app/store/mutation_types.js6
-rw-r--r--app/assets/javascripts/emoji/awards_app/store/mutations.js23
-rw-r--r--app/assets/javascripts/emoji/components/category.vue2
-rw-r--r--app/assets/javascripts/emoji/components/picker.vue5
7 files changed, 148 insertions, 2 deletions
diff --git a/app/assets/javascripts/emoji/awards_app/index.js b/app/assets/javascripts/emoji/awards_app/index.js
new file mode 100644
index 00000000000..16268910f49
--- /dev/null
+++ b/app/assets/javascripts/emoji/awards_app/index.js
@@ -0,0 +1,43 @@
+import Vue from 'vue';
+import { mapActions, mapState } from 'vuex';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import AwardsList from '~/vue_shared/components/awards_list.vue';
+import createstore from './store';
+
+export default (el) => {
+ const {
+ dataset: { path },
+ } = el;
+ const canAwardEmoji = parseBoolean(el.dataset.canAwardEmoji);
+
+ return new Vue({
+ el,
+ store: createstore(),
+ computed: {
+ ...mapState(['currentUserId', 'canAwardEmoji', 'awards']),
+ },
+ created() {
+ this.setInitialData({ path, currentUserId: window.gon.current_user_id, canAwardEmoji });
+ },
+ mounted() {
+ this.fetchAwards();
+ },
+ methods: {
+ ...mapActions(['setInitialData', 'fetchAwards', 'toggleAward']),
+ },
+ render(createElement) {
+ return createElement(AwardsList, {
+ props: {
+ awards: this.awards,
+ canAwardEmoji: this.canAwardEmoji,
+ currentUserId: this.currentUserId,
+ defaultAwards: ['thumbsup', 'thumbsdown'],
+ selectedClass: 'gl-bg-blue-50! is-active',
+ },
+ on: {
+ award: this.toggleAward,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/emoji/awards_app/store/actions.js b/app/assets/javascripts/emoji/awards_app/store/actions.js
new file mode 100644
index 00000000000..482acc5a3a9
--- /dev/null
+++ b/app/assets/javascripts/emoji/awards_app/store/actions.js
@@ -0,0 +1,51 @@
+import * as Sentry from '@sentry/browser';
+import axios from '~/lib/utils/axios_utils';
+import { normalizeHeaders } from '~/lib/utils/common_utils';
+import { __ } from '~/locale';
+import showToast from '~/vue_shared/plugins/global_toast';
+import {
+ SET_INITIAL_DATA,
+ FETCH_AWARDS_SUCCESS,
+ ADD_NEW_AWARD,
+ REMOVE_AWARD,
+} from './mutation_types';
+
+export const setInitialData = ({ commit }, data) => commit(SET_INITIAL_DATA, data);
+
+export const fetchAwards = async ({ commit, dispatch, state }, page = '1') => {
+ try {
+ const { data, headers } = await axios.get(state.path, { params: { per_page: 100, page } });
+ const normalizedHeaders = normalizeHeaders(headers);
+ const nextPage = normalizedHeaders['X-NEXT-PAGE'];
+
+ commit(FETCH_AWARDS_SUCCESS, data);
+
+ if (nextPage) {
+ dispatch('fetchAwards', nextPage);
+ }
+ } catch (error) {
+ Sentry.captureException(error);
+ }
+};
+
+export const toggleAward = async ({ commit, state }, name) => {
+ const award = state.awards.find((a) => a.name === name && a.user.id === state.currentUserId);
+
+ try {
+ if (award) {
+ await axios.delete(`${state.path}/${award.id}`);
+
+ commit(REMOVE_AWARD, award.id);
+
+ showToast(__('Award removed'));
+ } else {
+ const { data } = await axios.post(state.path, { name });
+
+ commit(ADD_NEW_AWARD, data);
+
+ showToast(__('Award added'));
+ }
+ } catch (error) {
+ Sentry.captureException(error);
+ }
+};
diff --git a/app/assets/javascripts/emoji/awards_app/store/index.js b/app/assets/javascripts/emoji/awards_app/store/index.js
new file mode 100644
index 00000000000..53ed50f9f5d
--- /dev/null
+++ b/app/assets/javascripts/emoji/awards_app/store/index.js
@@ -0,0 +1,20 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+const createState = () => ({
+ awards: [],
+ awardPath: '',
+ currentUserId: null,
+ canAwardEmoji: false,
+});
+
+export default () =>
+ new Vuex.Store({
+ state: createState(),
+ actions,
+ mutations,
+ });
diff --git a/app/assets/javascripts/emoji/awards_app/store/mutation_types.js b/app/assets/javascripts/emoji/awards_app/store/mutation_types.js
new file mode 100644
index 00000000000..af6289d0943
--- /dev/null
+++ b/app/assets/javascripts/emoji/awards_app/store/mutation_types.js
@@ -0,0 +1,6 @@
+export const SET_INITIAL_DATA = 'SET_INITIAL_DATA';
+
+export const FETCH_AWARDS_SUCCESS = 'FETCH_AWARDS_SUCCESS';
+
+export const ADD_NEW_AWARD = 'ADD_NEW_AWARD';
+export const REMOVE_AWARD = 'REMOVE_AWARD';
diff --git a/app/assets/javascripts/emoji/awards_app/store/mutations.js b/app/assets/javascripts/emoji/awards_app/store/mutations.js
new file mode 100644
index 00000000000..8edcfa92885
--- /dev/null
+++ b/app/assets/javascripts/emoji/awards_app/store/mutations.js
@@ -0,0 +1,23 @@
+import {
+ SET_INITIAL_DATA,
+ FETCH_AWARDS_SUCCESS,
+ ADD_NEW_AWARD,
+ REMOVE_AWARD,
+} from './mutation_types';
+
+export default {
+ [SET_INITIAL_DATA](state, { path, currentUserId, canAwardEmoji }) {
+ state.path = path;
+ state.currentUserId = currentUserId;
+ state.canAwardEmoji = canAwardEmoji;
+ },
+ [FETCH_AWARDS_SUCCESS](state, data) {
+ state.awards.push(...data);
+ },
+ [ADD_NEW_AWARD](state, data) {
+ state.awards.push(data);
+ },
+ [REMOVE_AWARD](state, awardId) {
+ state.awards = state.awards.filter(({ id }) => id !== awardId);
+ },
+};
diff --git a/app/assets/javascripts/emoji/components/category.vue b/app/assets/javascripts/emoji/components/category.vue
index db6ead3ff69..39881979c4f 100644
--- a/app/assets/javascripts/emoji/components/category.vue
+++ b/app/assets/javascripts/emoji/components/category.vue
@@ -39,7 +39,7 @@ export default {
<template>
<gl-intersection-observer class="gl-px-5 gl-h-full" @appear="categoryAppeared">
- <div class="gl-top-0 gl-py-3 gl-w-full emoji-picker-category-header">
+ <div class="gl-top-0 gl-py-3 gl-w-full gl-z-index-1 emoji-picker-category-header">
<b>{{ categoryTitle }}</b>
</div>
<template v-if="emojis.length">
diff --git a/app/assets/javascripts/emoji/components/picker.vue b/app/assets/javascripts/emoji/components/picker.vue
index 37f3433b781..71cabe80529 100644
--- a/app/assets/javascripts/emoji/components/picker.vue
+++ b/app/assets/javascripts/emoji/components/picker.vue
@@ -82,6 +82,8 @@ export default {
no-flip
right
lazy
+ @shown="$emit('shown')"
+ @hidden="$emit('hidden')"
>
<template #button-content><slot name="button-content"></slot></template>
<gl-search-box-by-type
@@ -99,10 +101,11 @@ export default {
v-for="(category, index) in categoryNames"
:key="category.name"
:class="{
- 'gl-text-black-normal! emoji-picker-category-active': index === currentCategory,
+ 'gl-text-body! emoji-picker-category-active': index === currentCategory,
}"
type="button"
class="gl-border-0 gl-border-b-2 gl-border-b-solid gl-flex-fill-1 gl-text-gray-300 gl-pt-3 gl-pb-3 gl-bg-transparent emoji-picker-category-tab"
+ :aria-label="category.name"
@click="scrollToCategory(category.name)"
>
<gl-icon :name="category.icon" :size="12" />