diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-01 15:14:27 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-01 15:14:27 +0000 |
commit | afbaf78b0d819326741a01b093bdbc4702570417 (patch) | |
tree | a16a6f2a8fcc18e60f25ac72df6ab22cfe0eae79 /app/assets/javascripts | |
parent | 38b3003b67db3f2eadfa81fd28b13d168f665766 (diff) | |
download | gitlab-ce-afbaf78b0d819326741a01b093bdbc4702570417.tar.gz |
Add latest changes from gitlab-org/gitlab@13-10-stable-ee
Diffstat (limited to 'app/assets/javascripts')
6 files changed, 93 insertions, 34 deletions
diff --git a/app/assets/javascripts/emoji/components/category.vue b/app/assets/javascripts/emoji/components/category.vue index a11122d5403..db6ead3ff69 100644 --- a/app/assets/javascripts/emoji/components/category.vue +++ b/app/assets/javascripts/emoji/components/category.vue @@ -1,6 +1,6 @@ <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> diff --git a/app/assets/javascripts/emoji/components/picker.vue b/app/assets/javascripts/emoji/components/picker.vue index 7cd20d82329..37f3433b781 100644 --- a/app/assets/javascripts/emoji/components/picker.vue +++ b/app/assets/javascripts/emoji/components/picker.vue @@ -1,11 +1,12 @@ <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> diff --git a/app/assets/javascripts/emoji/components/utils.js b/app/assets/javascripts/emoji/components/utils.js index b95b56a1d6f..3465a8ae7e6 100644 --- a/app/assets/javascripts/emoji/components/utils.js +++ b/app/assets/javascripts/emoji/components/utils.js @@ -1,27 +1,68 @@ -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 || {}), ); }); diff --git a/app/assets/javascripts/emoji/constants.js b/app/assets/javascripts/emoji/constants.js index bf73d1ca5a9..e9f2272e759 100644 --- a/app/assets/javascripts/emoji/constants.js +++ b/app/assets/javascripts/emoji/constants.js @@ -1,4 +1,8 @@ +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', diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js index d3b658a4020..7faf0fe5f08 100644 --- a/app/assets/javascripts/emoji/index.js +++ b/app/assets/javascripts/emoji/index.js @@ -2,7 +2,7 @@ import { escape, minBy } from 'lodash'; import emojiAliases from 'emojis/aliases.json'; import AccessorUtilities from '../lib/utils/accessor'; import axios from '../lib/utils/axios_utils'; -import { CATEGORY_ICON_MAP } from './constants'; +import { CATEGORY_ICON_MAP, FREQUENTLY_USED_KEY } from './constants'; let emojiMap = null; let validEmojiNames = null; @@ -162,6 +162,9 @@ let emojiCategoryMap; export function getEmojiCategoryMap() { if (!emojiCategoryMap) { emojiCategoryMap = CATEGORY_NAMES.reduce((acc, category) => { + if (category === FREQUENTLY_USED_KEY) { + return acc; + } return { ...acc, [category]: [] }; }, {}); Object.keys(emojiMap).forEach((name) => { diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue index a49eb7fd611..04ab0fd00aa 100644 --- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue @@ -3,6 +3,8 @@ import { throttle } from 'lodash'; import { numberToHumanSize } from '~/lib/utils/number_utils'; import { encodeSaferUrl } from '~/lib/utils/url_utility'; +const BLOB_PREFIX = 'blob:'; + export default { props: { path: { @@ -45,7 +47,7 @@ export default { return this.width && this.height; }, safePath() { - return encodeSaferUrl(this.path); + return this.path.startsWith(BLOB_PREFIX) ? this.path : encodeSaferUrl(this.path); }, }, beforeDestroy() { |