Commit 189bf0ed authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'ph/emojiPickerFrequentlyUsedEmojis' into 'master'

Added frequently used emojis to new picker

See merge request gitlab-org/gitlab!56332
parents 0ac30ddf 0361f42c
<script>
import { GlIntersectionObserver } from '@gitlab/ui';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import { humanize } from '~/lib/utils/text_utility';
import EmojiGroup from './emoji_group.vue';
export default {
......@@ -25,7 +25,7 @@ export default {
},
computed: {
categoryTitle() {
return capitalizeFirstCharacter(this.category);
return humanize(this.category);
},
},
methods: {
......@@ -33,9 +33,6 @@ export default {
this.renderGroup = true;
this.$emit('appear', this.category);
},
categoryDissappeared() {
this.renderGroup = false;
},
},
};
</script>
......
<script>
import { GlIcon, GlDropdown, GlSearchBoxByType } from '@gitlab/ui';
import { findLastIndex } from 'lodash';
import VirtualList from 'vue-virtual-scroll-list';
import { CATEGORY_NAMES } from '~/emoji';
import { CATEGORY_ICON_MAP } from '../constants';
import { CATEGORY_ICON_MAP, FREQUENTLY_USED_KEY } from '../constants';
import Category from './category.vue';
import EmojiList from './emoji_list.vue';
import { getEmojiCategories } from './utils';
import { addToFrequentlyUsed, getEmojiCategories, hasFrequentlyUsedEmojis } from './utils';
export default {
components: {
......@@ -25,13 +26,16 @@ export default {
},
data() {
return {
currentCategory: null,
currentCategory: 0,
searchValue: '',
};
},
computed: {
categoryNames() {
return CATEGORY_NAMES.map((category) => ({
return CATEGORY_NAMES.filter((c) => {
if (c === FREQUENTLY_USED_KEY) return hasFrequentlyUsedEmojis();
return true;
}).map((category) => ({
name: category,
icon: CATEGORY_ICON_MAP[category],
}));
......@@ -50,6 +54,7 @@ export default {
selectEmoji(name) {
this.$emit('click', name);
this.$refs.dropdown.hide();
addToFrequentlyUsed(name);
},
getBoundaryElement() {
return document.querySelector('.content-wrapper') || 'scrollParent';
......@@ -58,6 +63,11 @@ export default {
this.$refs.virtualScoller.setScrollTop(0);
this.$refs.virtualScoller.forceRender();
},
async onScroll(event, { offset }) {
const categories = await getEmojiCategories();
this.currentCategory = findLastIndex(Object.values(categories), ({ top }) => offset >= top);
},
},
};
</script>
......@@ -86,10 +96,10 @@ export default {
class="gl-display-flex gl-mx-5 gl-border-b-solid gl-border-gray-100 gl-border-b-1"
>
<button
v-for="category in categoryNames"
v-for="(category, index) in categoryNames"
:key="category.name"
:class="{
'gl-text-black-normal! emoji-picker-category-active': category.name === currentCategory,
'gl-text-black-normal! 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"
......@@ -100,18 +110,20 @@ export default {
</div>
<emoji-list :search-value="searchValue">
<template #default="{ filteredCategories }">
<virtual-list ref="virtualScoller" :size="258" :remain="1" :bench="2" variable>
<virtual-list
ref="virtualScoller"
:size="258"
:remain="1"
:bench="2"
variable
:onscroll="onScroll"
>
<div
v-for="(category, categoryKey) in filteredCategories"
:key="categoryKey"
:style="{ height: category.height + 'px' }"
>
<category
:category="categoryKey"
:emojis="category.emojis"
@appear="categoryAppeared"
@click="selectEmoji"
/>
<category :category="categoryKey" :emojis="category.emojis" @click="selectEmoji" />
</div>
</virtual-list>
</template>
......
import { chunk, memoize } from 'lodash';
import Cookies from 'js-cookie';
import { chunk, memoize, uniq } from 'lodash';
import { initEmojiMap, getEmojiCategoryMap } from '~/emoji';
import { EMOJIS_PER_ROW, EMOJI_ROW_HEIGHT, CATEGORY_ROW_HEIGHT } from '../constants';
import {
EMOJIS_PER_ROW,
EMOJI_ROW_HEIGHT,
CATEGORY_ROW_HEIGHT,
FREQUENTLY_USED_KEY,
FREQUENTLY_USED_COOKIE_KEY,
} from '../constants';
export const generateCategoryHeight = (emojisLength) =>
emojisLength * EMOJI_ROW_HEIGHT + CATEGORY_ROW_HEIGHT;
export const getFrequentlyUsedEmojis = () => {
const savedEmojis = Cookies.get(FREQUENTLY_USED_COOKIE_KEY);
if (!savedEmojis) return null;
const emojis = chunk(uniq(savedEmojis.split(',')), 9);
return {
frequently_used: {
emojis,
top: 0,
height: generateCategoryHeight(emojis.length),
},
};
};
export const addToFrequentlyUsed = (emoji) => {
const frequentlyUsedEmojis = uniq(
(Cookies.get(FREQUENTLY_USED_COOKIE_KEY) || '')
.split(',')
.filter((e) => e)
.concat(emoji),
);
Cookies.set(FREQUENTLY_USED_COOKIE_KEY, frequentlyUsedEmojis.join(','), { expires: 365 });
};
export const hasFrequentlyUsedEmojis = () => getFrequentlyUsedEmojis() !== null;
export const getEmojiCategories = memoize(async () => {
await initEmojiMap();
const categories = await getEmojiCategoryMap();
let top = 0;
const frequentlyUsedEmojis = getFrequentlyUsedEmojis();
let top = frequentlyUsedEmojis
? frequentlyUsedEmojis.frequently_used.top + frequentlyUsedEmojis.frequently_used.height
: 0;
return Object.freeze(
Object.keys(categories).reduce((acc, category) => {
const emojis = chunk(categories[category], EMOJIS_PER_ROW);
const height = generateCategoryHeight(emojis.length);
const newAcc = {
...acc,
[category]: { emojis, height, top },
};
top += height;
return newAcc;
}, {}),
Object.keys(categories)
.filter((c) => c !== FREQUENTLY_USED_KEY)
.reduce((acc, category) => {
const emojis = chunk(categories[category], EMOJIS_PER_ROW);
const height = generateCategoryHeight(emojis.length);
const newAcc = {
...acc,
[category]: { emojis, height, top },
};
top += height;
return newAcc;
}, frequentlyUsedEmojis || {}),
);
});
export const FREQUENTLY_USED_KEY = 'frequently_used';
export const FREQUENTLY_USED_COOKIE_KEY = 'frequently_used_emojis';
export const CATEGORY_ICON_MAP = {
[FREQUENTLY_USED_KEY]: 'history',
activity: 'dumbbell',
people: 'smiley',
nature: 'nature',
......
import Cookies from 'js-cookie';
import { getFrequentlyUsedEmojis, addToFrequentlyUsed } from '~/emoji/components/utils';
jest.mock('js-cookie');
describe('getFrequentlyUsedEmojis', () => {
it('it returns null when no saved emojis set', () => {
jest.spyOn(Cookies, 'get').mockReturnValue(null);
expect(getFrequentlyUsedEmojis()).toBe(null);
});
it('it returns frequently used emojis object', () => {
jest.spyOn(Cookies, 'get').mockReturnValue('thumbsup,thumbsdown');
expect(getFrequentlyUsedEmojis()).toEqual({
frequently_used: {
emojis: [['thumbsup', 'thumbsdown']],
top: 0,
height: 71,
},
});
});
});
describe('addToFrequentlyUsed', () => {
it('sets cookie value', () => {
jest.spyOn(Cookies, 'get').mockReturnValue(null);
addToFrequentlyUsed('thumbsup');
expect(Cookies.set).toHaveBeenCalledWith('frequently_used_emojis', 'thumbsup', {
expires: 365,
});
});
it('sets cookie value to include previously set cookie value', () => {
jest.spyOn(Cookies, 'get').mockReturnValue('thumbsdown');
addToFrequentlyUsed('thumbsup');
expect(Cookies.set).toHaveBeenCalledWith('frequently_used_emojis', 'thumbsdown,thumbsup', {
expires: 365,
});
});
it('sets cookie value with uniq values', () => {
jest.spyOn(Cookies, 'get').mockReturnValue('thumbsup');
addToFrequentlyUsed('thumbsup');
expect(Cookies.set).toHaveBeenCalledWith('frequently_used_emojis', 'thumbsup', {
expires: 365,
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment