Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-01-31 00:08:47 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-01-31 00:08:47 +0300
commitc8f773a8593926f4f2dec6f446a3b3e59e9c9909 (patch)
tree4e5ea1d3b861ff99015f6112da567de7873868aa /app/assets/javascripts
parent929b887e5391dea7cb53b88b77b9a35351c87d99 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r--app/assets/javascripts/frequent_items/components/app.vue6
-rw-r--r--app/assets/javascripts/frequent_items/components/frequent_items_list.vue6
-rw-r--r--app/assets/javascripts/frequent_items/utils.js7
-rw-r--r--app/assets/javascripts/groups_select.js7
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue2
-rw-r--r--app/assets/javascripts/user_popovers.js160
-rw-r--r--app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue11
7 files changed, 98 insertions, 101 deletions
diff --git a/app/assets/javascripts/frequent_items/components/app.vue b/app/assets/javascripts/frequent_items/components/app.vue
index 8cf939254c1..2ffecce0a56 100644
--- a/app/assets/javascripts/frequent_items/components/app.vue
+++ b/app/assets/javascripts/frequent_items/components/app.vue
@@ -5,7 +5,7 @@ import AccessorUtilities from '~/lib/utils/accessor';
import eventHub from '../event_hub';
import store from '../store/';
import { FREQUENT_ITEMS, STORAGE_KEY } from '../constants';
-import { isMobile, updateExistingFrequentItem } from '../utils';
+import { isMobile, updateExistingFrequentItem, sanitizeItem } from '../utils';
import FrequentItemsSearchInput from './frequent_items_search_input.vue';
import FrequentItemsList from './frequent_items_list.vue';
import frequentItemsMixin from './frequent_items_mixin';
@@ -64,7 +64,9 @@ export default {
this.fetchFrequentItems();
}
},
- logItemAccess(storageKey, item) {
+ logItemAccess(storageKey, unsanitizedItem) {
+ const item = sanitizeItem(unsanitizedItem);
+
if (!AccessorUtilities.isLocalStorageAccessSafe()) {
return false;
}
diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_list.vue b/app/assets/javascripts/frequent_items/components/frequent_items_list.vue
index 67ffa97a046..0ece64692ae 100644
--- a/app/assets/javascripts/frequent_items/components/frequent_items_list.vue
+++ b/app/assets/javascripts/frequent_items/components/frequent_items_list.vue
@@ -1,6 +1,7 @@
<script>
import FrequentItemsListItem from './frequent_items_list_item.vue';
import frequentItemsMixin from './frequent_items_mixin';
+import { sanitizeItem } from '../utils';
export default {
components: {
@@ -48,6 +49,9 @@ export default {
? this.translations.itemListErrorMessage
: this.translations.itemListEmptyMessage;
},
+ sanitizedItems() {
+ return this.items.map(sanitizeItem);
+ },
},
};
</script>
@@ -59,7 +63,7 @@ export default {
{{ listEmptyMessage }}
</li>
<frequent-items-list-item
- v-for="item in items"
+ v-for="item in sanitizedItems"
v-else
:key="item.id"
:item-id="item.id"
diff --git a/app/assets/javascripts/frequent_items/utils.js b/app/assets/javascripts/frequent_items/utils.js
index cc1668b1a0d..5188d6118ac 100644
--- a/app/assets/javascripts/frequent_items/utils.js
+++ b/app/assets/javascripts/frequent_items/utils.js
@@ -1,5 +1,6 @@
import _ from 'underscore';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
+import sanitize from 'sanitize-html';
import { FREQUENT_ITEMS, HOUR_IN_MS } from './constants';
export const isMobile = () => ['md', 'sm', 'xs'].includes(bp.getBreakpointSize());
@@ -43,3 +44,9 @@ export const updateExistingFrequentItem = (frequentItem, item) => {
lastAccessedOn: accessedOverHourAgo ? Date.now() : frequentItem.lastAccessedOn,
};
};
+
+export const sanitizeItem = item => ({
+ ...item,
+ name: sanitize(item.name.toString(), { allowedTags: [] }),
+ namespace: sanitize(item.namespace.toString(), { allowedTags: [] }),
+});
diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js
index a5e38022b8d..4daa8c60e58 100644
--- a/app/assets/javascripts/groups_select.js
+++ b/app/assets/javascripts/groups_select.js
@@ -1,6 +1,7 @@
import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import Api from './api';
+import { escape } from 'lodash';
import { normalizeHeaders } from './lib/utils/common_utils';
import { __ } from '~/locale';
@@ -75,10 +76,12 @@ const groupsSelect = () => {
}
},
formatResult(object) {
- return `<div class='group-result'> <div class='group-name'>${object.full_name}</div> <div class='group-path'>${object.full_path}</div> </div>`;
+ return `<div class='group-result'> <div class='group-name'>${escape(
+ object.full_name,
+ )}</div> <div class='group-path'>${object.full_path}</div> </div>`;
},
formatSelection(object) {
- return object.full_name;
+ return escape(object.full_name);
},
dropdownCssClass: 'ajax-groups-dropdown select2-infinite',
// we do not want to escape markup since we are displaying html in results
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index be2adb07526..762228dd138 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -14,7 +14,7 @@ import placeholderSystemNote from '../../vue_shared/components/notes/placeholder
import skeletonLoadingContainer from '../../vue_shared/components/notes/skeleton_note.vue';
import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user';
import { __ } from '~/locale';
-import initUserPopovers from '../../user_popovers';
+import initUserPopovers from '~/user_popovers';
export default {
name: 'NotesApp',
diff --git a/app/assets/javascripts/user_popovers.js b/app/assets/javascripts/user_popovers.js
index 157d89a3a40..5b9e3817f3a 100644
--- a/app/assets/javascripts/user_popovers.js
+++ b/app/assets/javascripts/user_popovers.js
@@ -3,108 +3,92 @@ import Vue from 'vue';
import UsersCache from './lib/utils/users_cache';
import UserPopover from './vue_shared/components/user_popover/user_popover.vue';
-let renderedPopover;
-let renderFn;
-
-const handleUserPopoverMouseOut = event => {
- const { target } = event;
- target.removeEventListener('mouseleave', handleUserPopoverMouseOut);
-
- if (renderFn) {
- clearTimeout(renderFn);
- }
- if (renderedPopover) {
- renderedPopover.$destroy();
- renderedPopover = null;
- }
- target.removeAttribute('aria-describedby');
+const removeTitle = el => {
+ // Removing titles so its not showing tooltips also
+
+ el.dataset.originalTitle = '';
+ el.setAttribute('title', '');
+};
+
+const getPreloadedUserInfo = dataset => {
+ const userId = dataset.user || dataset.userId;
+ const { username, name, avatarUrl } = dataset;
+
+ return {
+ userId,
+ username,
+ name,
+ avatarUrl,
+ };
};
/**
* Adds a UserPopover component to the body, hands over as much data as the target element has in data attributes.
* loads based on data-user-id more data about a user from the API and sets it on the popover
*/
-const handleUserPopoverMouseOver = event => {
- const { target } = event;
- // Add listener to actually remove it again
- target.addEventListener('mouseleave', handleUserPopoverMouseOut);
-
- renderFn = setTimeout(() => {
- // Helps us to use current markdown setup without maybe breaking or duplicating for now
- if (target.dataset.user) {
- target.dataset.userId = target.dataset.user;
- // Removing titles so its not showing tooltips also
- target.dataset.originalTitle = '';
- target.setAttribute('title', '');
- }
-
- const { userId, username, name, avatarUrl } = target.dataset;
+const populateUserInfo = user => {
+ const { userId } = user;
+
+ return Promise.all([UsersCache.retrieveById(userId), UsersCache.retrieveStatusById(userId)]).then(
+ ([userData, status]) => {
+ if (userData) {
+ Object.assign(user, {
+ avatarUrl: userData.avatar_url,
+ username: userData.username,
+ name: userData.name,
+ location: userData.location,
+ bio: userData.bio,
+ organization: userData.organization,
+ loaded: true,
+ });
+ }
+
+ if (status) {
+ Object.assign(user, {
+ status,
+ });
+ }
+
+ return user;
+ },
+ );
+};
+
+export default (elements = document.querySelectorAll('.js-user-link')) => {
+ const userLinks = Array.from(elements);
+
+ return userLinks.map(el => {
+ const UserPopoverComponent = Vue.extend(UserPopover);
const user = {
- userId,
- username,
- name,
- avatarUrl,
location: null,
bio: null,
organization: null,
status: null,
loaded: false,
};
- if (userId || username) {
- const UserPopoverComponent = Vue.extend(UserPopover);
- renderedPopover = new UserPopoverComponent({
- propsData: {
- target,
- user,
- },
- });
-
- renderedPopover.$mount();
-
- UsersCache.retrieveById(userId)
- .then(userData => {
- if (!userData) {
- return undefined;
- }
-
- Object.assign(user, {
- avatarUrl: userData.avatar_url,
- username: userData.username,
- name: userData.name,
- location: userData.location,
- bio: userData.bio,
- organization: userData.organization,
- status: userData.status,
- loaded: true,
- });
-
- if (userData.status) {
- return Promise.resolve();
- }
-
- return UsersCache.retrieveStatusById(userId);
- })
- .then(status => {
- if (!status) {
- return;
- }
-
- Object.assign(user, {
- status,
- });
- })
- .catch(() => {
- renderedPopover.$destroy();
- renderedPopover = null;
- });
- }
- }, 200); // 200ms delay so not every mouseover triggers Popover + API Call
-};
+ const renderedPopover = new UserPopoverComponent({
+ propsData: {
+ target: el,
+ user,
+ },
+ });
+
+ renderedPopover.$mount();
+
+ el.addEventListener('mouseenter', ({ target }) => {
+ removeTitle(target);
+ const preloadedUserInfo = getPreloadedUserInfo(target.dataset);
+
+ Object.assign(user, preloadedUserInfo);
-export default elements => {
- const userLinks = elements || [...document.querySelectorAll('.js-user-link')];
+ if (preloadedUserInfo.userId) {
+ populateUserInfo(user);
+ }
+ });
+ el.addEventListener('mouseleave', ({ target }) => {
+ target.removeAttribute('aria-describedby');
+ });
- userLinks.forEach(el => {
- el.addEventListener('mouseenter', handleUserPopoverMouseOver);
+ return renderedPopover;
});
};
diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
index 37e3643bf6c..ca25d9ee738 100644
--- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
+++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue
@@ -56,19 +56,16 @@ export default {
</script>
<template>
- <gl-popover :target="target" boundary="viewport" placement="top" offset="0, 1" show>
+ <!-- 200ms delay so not every mouseover triggers Popover -->
+ <gl-popover :target="target" :delay="200" boundary="viewport" triggers="hover" placement="top">
<div class="user-popover d-flex">
<div class="p-1 flex-shrink-1">
<user-avatar-image :img-src="user.avatarUrl" :size="60" css-classes="mr-2" />
</div>
<div class="p-1 w-100">
<h5 class="m-0">
- {{ user.name }}
- <gl-skeleton-loading
- v-if="nameIsLoading"
- :lines="1"
- class="animation-container-small mb-1"
- />
+ <span v-if="user.name">{{ user.name }}</span>
+ <gl-skeleton-loading v-else :lines="1" class="animation-container-small mb-1" />
</h5>
<div class="text-secondary mb-2">
<span v-if="user.username">@{{ user.username }}</span>