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:
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r--app/assets/javascripts/blob_edit/blob_bundle.js5
-rw-r--r--app/assets/javascripts/build.js19
-rw-r--r--app/assets/javascripts/commons/bootstrap.js1
-rw-r--r--app/assets/javascripts/copy_as_gfm.js10
-rw-r--r--app/assets/javascripts/dispatcher.js118
-rw-r--r--app/assets/javascripts/droplab/plugins/ajax.js13
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_non_user.js3
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_utils.js60
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js1
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js6
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js55
-rw-r--r--app/assets/javascripts/gl_dropdown.js6
-rw-r--r--app/assets/javascripts/gpg_badges.js15
-rw-r--r--app/assets/javascripts/graphs/graphs_bundle.js2
-rw-r--r--app/assets/javascripts/graphs/graphs_charts.js63
-rw-r--r--app/assets/javascripts/graphs/graphs_show.js21
-rw-r--r--app/assets/javascripts/groups/components/group_identicon.vue45
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue13
-rw-r--r--app/assets/javascripts/how_to_merge.js12
-rw-r--r--app/assets/javascripts/init_issuable_sidebar.js18
-rw-r--r--app/assets/javascripts/init_legacy_filters.js15
-rw-r--r--app/assets/javascripts/init_notes.js14
-rw-r--r--app/assets/javascripts/integrations/integration_settings_form.js2
-rw-r--r--app/assets/javascripts/issuable_context.js6
-rw-r--r--app/assets/javascripts/labels_select.js14
-rw-r--r--app/assets/javascripts/lazy_loader.js76
-rw-r--r--app/assets/javascripts/lib/utils/ajax_cache.js4
-rw-r--r--app/assets/javascripts/main.js32
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_store.js2
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js3
-rw-r--r--app/assets/javascripts/milestone_select.js2
-rw-r--r--app/assets/javascripts/notes.js4
-rw-r--r--app/assets/javascripts/pipelines/pipelines_charts.js38
-rw-r--r--app/assets/javascripts/pipelines/pipelines_times.js27
-rw-r--r--app/assets/javascripts/profile/gl_crop.js2
-rw-r--r--app/assets/javascripts/project.js29
-rw-r--r--app/assets/javascripts/projects/project_new.js85
-rw-r--r--app/assets/javascripts/ref_select_dropdown.js3
-rw-r--r--app/assets/javascripts/sidebar/sidebar_bundle.js3
-rw-r--r--app/assets/javascripts/snippets_list.js9
-rw-r--r--app/assets/javascripts/star.js6
-rw-r--r--app/assets/javascripts/todos.js4
-rw-r--r--app/assets/javascripts/two_factor_auth.js13
-rw-r--r--app/assets/javascripts/ui_development_kit.js22
-rw-r--r--app/assets/javascripts/users/activity_calendar.js239
-rw-r--r--app/assets/javascripts/users/index.js24
-rw-r--r--app/assets/javascripts/users/user.js34
-rw-r--r--app/assets/javascripts/users/user_tabs.js177
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js11
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js2
51 files changed, 1041 insertions, 350 deletions
diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js
index 1c64ccf536f..b5500ac116f 100644
--- a/app/assets/javascripts/blob_edit/blob_bundle.js
+++ b/app/assets/javascripts/blob_edit/blob_bundle.js
@@ -8,6 +8,7 @@ import BlobFileDropzone from '../blob/blob_file_dropzone';
$(() => {
const editBlobForm = $('.js-edit-blob-form');
const uploadBlobForm = $('.js-upload-blob-form');
+ const deleteBlobForm = $('.js-delete-blob-form');
if (editBlobForm.length) {
const urlRoot = editBlobForm.data('relative-url-root');
@@ -30,4 +31,8 @@ $(() => {
'.btn-upload-file',
);
}
+
+ if (deleteBlobForm.length) {
+ new NewCommitForm(deleteBlobForm);
+ }
});
diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js
index 1dfa064acfd..b3d3bbcf84f 100644
--- a/app/assets/javascripts/build.js
+++ b/app/assets/javascripts/build.js
@@ -64,7 +64,7 @@ window.Build = (function () {
$(window)
.off('scroll')
.on('scroll', () => {
- const contentHeight = this.$buildTraceOutput.prop('scrollHeight');
+ const contentHeight = this.$buildTraceOutput.height();
if (contentHeight > this.windowSize) {
// means the user did not scroll, the content was updated.
this.windowSize = contentHeight;
@@ -105,16 +105,17 @@ window.Build = (function () {
};
Build.prototype.canScroll = function () {
- return document.body.scrollHeight > window.innerHeight;
+ return $(document).height() > $(window).height();
};
Build.prototype.toggleScroll = function () {
- const currentPosition = document.body.scrollTop;
- const windowHeight = window.innerHeight;
+ const currentPosition = $(document).scrollTop();
+ const scrollHeight = $(document).height();
+ const windowHeight = $(window).height();
if (this.canScroll()) {
if (currentPosition > 0 &&
- (document.body.scrollHeight - currentPosition !== windowHeight)) {
+ (scrollHeight - currentPosition !== windowHeight)) {
// User is in the middle of the log
this.toggleDisableButton(this.$scrollTopBtn, false);
@@ -124,7 +125,7 @@ window.Build = (function () {
this.toggleDisableButton(this.$scrollTopBtn, true);
this.toggleDisableButton(this.$scrollBottomBtn, false);
- } else if (document.body.scrollHeight - currentPosition === windowHeight) {
+ } else if (scrollHeight - currentPosition === windowHeight) {
// User is at the bottom of the build log.
this.toggleDisableButton(this.$scrollTopBtn, false);
@@ -137,7 +138,7 @@ window.Build = (function () {
};
Build.prototype.scrollDown = function () {
- document.body.scrollTop = document.body.scrollHeight;
+ $(document).scrollTop($(document).height());
};
Build.prototype.scrollToBottom = function () {
@@ -147,7 +148,7 @@ window.Build = (function () {
};
Build.prototype.scrollToTop = function () {
- document.body.scrollTop = 0;
+ $(document).scrollTop(0);
this.hasBeenScrolled = true;
this.toggleScroll();
};
@@ -178,7 +179,7 @@ window.Build = (function () {
this.state = log.state;
}
- this.windowSize = this.$buildTraceOutput.prop('scrollHeight');
+ this.windowSize = this.$buildTraceOutput.height();
if (log.append) {
this.$buildTraceOutput.append(log.html);
diff --git a/app/assets/javascripts/commons/bootstrap.js b/app/assets/javascripts/commons/bootstrap.js
index 36bfe457be9..510bedbf641 100644
--- a/app/assets/javascripts/commons/bootstrap.js
+++ b/app/assets/javascripts/commons/bootstrap.js
@@ -8,6 +8,7 @@ import 'bootstrap-sass/assets/javascripts/bootstrap/modal';
import 'bootstrap-sass/assets/javascripts/bootstrap/tab';
import 'bootstrap-sass/assets/javascripts/bootstrap/transition';
import 'bootstrap-sass/assets/javascripts/bootstrap/tooltip';
+import 'bootstrap-sass/assets/javascripts/bootstrap/popover';
// custom jQuery functions
$.fn.extend({
diff --git a/app/assets/javascripts/copy_as_gfm.js b/app/assets/javascripts/copy_as_gfm.js
index ba9d9a3e1f7..54257531284 100644
--- a/app/assets/javascripts/copy_as_gfm.js
+++ b/app/assets/javascripts/copy_as_gfm.js
@@ -1,6 +1,7 @@
/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
import './lib/utils/common_utils';
+import { placeholderImage } from './lazy_loader';
const gfmRules = {
// The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
@@ -56,6 +57,11 @@ const gfmRules = {
return text;
},
},
+ ImageLazyLoadFilter: {
+ 'img'(el, text) {
+ return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
+ },
+ },
VideoLinkFilter: {
'.video-container'(el) {
const videoEl = el.querySelector('video');
@@ -163,7 +169,9 @@ const gfmRules = {
return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
},
'img'(el) {
- return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
+ const imageSrc = el.src;
+ const imageUrl = imageSrc && imageSrc !== placeholderImage ? imageSrc : (el.dataset.src || '');
+ return `![${el.getAttribute('alt')}](${imageUrl})`;
},
'a.anchor'(el, text) {
// Don't render a Markdown link for the anchor link inside a heading
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index ffe97c071ba..178e72a1127 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -8,6 +8,8 @@
/* global LabelsSelect */
/* global MilestoneSelect */
/* global Commit */
+/* global CommitsList */
+/* global NewBranchForm */
/* global NotificationsForm */
/* global NotificationsDropdown */
/* global GroupAvatar */
@@ -18,13 +20,20 @@
/* global Search */
/* global Admin */
/* global NamespaceSelects */
+/* global NewCommitForm */
+/* global NewBranchForm */
/* global Project */
/* global ProjectAvatar */
+/* global MergeRequest */
+/* global Compare */
/* global CompareAutocomplete */
+/* global ProjectFindFile */
/* global ProjectNew */
/* global ProjectShow */
+/* global ProjectImport */
/* global Labels */
/* global Shortcuts */
+/* global ShortcutsFindFile */
/* global Sidebar */
/* global ShortcutsWiki */
@@ -62,6 +71,10 @@ import initSettingsPanels from './settings_panels';
import initExperimentalFlags from './experimental_flags';
import OAuthRememberMe from './oauth_remember_me';
import PerformanceBar from './performance_bar';
+import initNotes from './init_notes';
+import initLegacyFilters from './init_legacy_filters';
+import initIssuableSidebar from './init_issuable_sidebar';
+import GpgBadges from './gpg_badges';
(function() {
var Dispatcher;
@@ -126,6 +139,8 @@ import PerformanceBar from './performance_bar';
.init();
}
+ const filteredSearchEnabled = gl.FilteredSearchManager && document.querySelector('.filtered-search');
+
switch (page) {
case 'profiles:preferences:show':
initExperimentalFlags();
@@ -142,7 +157,7 @@ import PerformanceBar from './performance_bar';
break;
case 'projects:merge_requests:index':
case 'projects:issues:index':
- if (gl.FilteredSearchManager && document.querySelector('.filtered-search')) {
+ if (filteredSearchEnabled) {
const filteredSearchManager = new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests');
filteredSearchManager.setup();
}
@@ -156,6 +171,8 @@ import PerformanceBar from './performance_bar';
new Issue();
shortcut_handler = new ShortcutsIssuable();
new ZenMode();
+ initIssuableSidebar();
+ initNotes();
break;
case 'dashboard:milestones:index':
new ProjectSelect();
@@ -166,9 +183,17 @@ import PerformanceBar from './performance_bar';
new Milestone();
new Sidebar();
break;
- case 'groups:issues':
+ case 'dashboard:issues':
+ case 'dashboard:merge_requests':
case 'groups:merge_requests':
- new UsersSelect();
+ new ProjectSelect();
+ initLegacyFilters();
+ break;
+ case 'groups:issues':
+ if (filteredSearchEnabled) {
+ const filteredSearchManager = new gl.FilteredSearchManager('issues');
+ filteredSearchManager.setup();
+ }
new ProjectSelect();
break;
case 'dashboard:todos:index':
@@ -184,7 +209,6 @@ import PerformanceBar from './performance_bar';
break;
case 'explore:groups:index':
new GroupsList();
-
const landingElement = document.querySelector('.js-explore-groups-landing');
if (!landingElement) break;
const exploreGroupsLanding = new Landing(
@@ -207,6 +231,10 @@ import PerformanceBar from './performance_bar';
case 'projects:compare:show':
new gl.Diff();
break;
+ case 'projects:branches:new':
+ case 'projects:branches:create':
+ new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML));
+ break;
case 'projects:branches:index':
gl.AjaxLoadingSpinner.init();
new DeleteModal();
@@ -221,6 +249,19 @@ import PerformanceBar from './performance_bar';
new gl.IssuableTemplateSelectors();
break;
case 'projects:merge_requests:creations:new':
+ const mrNewCompareNode = document.querySelector('.js-merge-request-new-compare');
+ if (mrNewCompareNode) {
+ new Compare({
+ targetProjectUrl: mrNewCompareNode.dataset.targetProjectUrl,
+ sourceBranchUrl: mrNewCompareNode.dataset.sourceBranchUrl,
+ targetBranchUrl: mrNewCompareNode.dataset.targetBranchUrl,
+ });
+ } else {
+ const mrNewSubmitNode = document.querySelector('.js-merge-request-new-submit');
+ new MergeRequest({
+ action: mrNewSubmitNode.dataset.mrSubmitAction,
+ });
+ }
case 'projects:merge_requests:creations:diffs':
case 'projects:merge_requests:edit':
new gl.Diff();
@@ -235,7 +276,10 @@ import PerformanceBar from './performance_bar';
case 'projects:tags:new':
new ZenMode();
new gl.GLForm($('.tag-form'), true);
- new RefSelectDropdown($('.js-branch-select'), window.gl.availableRefs);
+ new RefSelectDropdown($('.js-branch-select'));
+ break;
+ case 'projects:snippets:show':
+ initNotes();
break;
case 'projects:snippets:new':
case 'projects:snippets:edit':
@@ -257,15 +301,18 @@ import PerformanceBar from './performance_bar';
new gl.Diff();
shortcut_handler = new ShortcutsIssuable(true);
new ZenMode();
+
+ initIssuableSidebar();
+ initNotes();
+
+ const mrShowNode = document.querySelector('.merge-request');
+ window.mergeRequest = new MergeRequest({
+ action: mrShowNode.dataset.mrAction,
+ });
break;
case 'dashboard:activity':
new gl.Activities();
break;
- case 'dashboard:issues':
- case 'dashboard:merge_requests':
- new ProjectSelect();
- new UsersSelect();
- break;
case 'projects:commit:show':
new Commit();
new gl.Diff();
@@ -274,16 +321,25 @@ import PerformanceBar from './performance_bar';
new MiniPipelineGraph({
container: '.js-commit-pipeline-graph',
}).bindEvents();
+ initNotes();
+ $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
break;
case 'projects:commit:pipelines':
new MiniPipelineGraph({
container: '.js-commit-pipeline-graph',
}).bindEvents();
+ $('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
break;
- case 'projects:commits:show':
case 'projects:activity':
+ new gl.Activities();
shortcut_handler = new ShortcutsNavigation();
break;
+ case 'projects:commits:show':
+ CommitsList.init(document.querySelector('.js-project-commits-show').dataset.commitsLimit);
+ new gl.Activities();
+ shortcut_handler = new ShortcutsNavigation();
+ GpgBadges.fetch();
+ break;
case 'projects:show':
shortcut_handler = new ShortcutsNavigation();
new NotificationsForm();
@@ -297,6 +353,12 @@ import PerformanceBar from './performance_bar';
case 'projects:edit':
setupProjectEdit();
break;
+ case 'projects:imports:show':
+ new ProjectImport();
+ break;
+ case 'projects:pipelines:new':
+ new NewBranchForm($('.js-new-pipeline-form'));
+ break;
case 'projects:pipelines:builds':
case 'projects:pipelines:failures':
case 'projects:pipelines:show':
@@ -350,8 +412,19 @@ import PerformanceBar from './performance_bar';
shortcut_handler = new ShortcutsNavigation();
new TreeView();
new BlobViewer();
+ new NewCommitForm($('.js-create-dir-form'));
+ $('#tree-slider').waitForImages(function() {
+ gl.utils.ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath);
+ });
break;
case 'projects:find_file:show':
+ const findElement = document.querySelector('.js-file-finder');
+ const projectFindFile = new ProjectFindFile($(".file-finder-holder"), {
+ url: findElement.dataset.fileFindUrl,
+ treeUrl: findElement.dataset.findTreeUrl,
+ blobUrlTemplate: findElement.dataset.blobUrlTemplate,
+ });
+ new ShortcutsFindFile(projectFindFile);
shortcut_handler = true;
break;
case 'projects:blob:show':
@@ -367,10 +440,20 @@ import PerformanceBar from './performance_bar';
case 'projects:labels:edit':
new Labels();
break;
+ case 'groups:labels:index':
case 'projects:labels:index':
if ($('.prioritized-labels').length) {
new gl.LabelManager();
}
+ $('.label-subscription').each((i, el) => {
+ const $el = $(el);
+
+ if ($el.find('.dropdown-group-label').length) {
+ new gl.GroupLabelSubscription($el);
+ } else {
+ new gl.ProjectLabelSubscription($el);
+ }
+ });
break;
case 'projects:network:show':
// Ensure we don't create a particular shortcut handler here. This is
@@ -415,10 +498,15 @@ import PerformanceBar from './performance_bar';
case 'snippets:show':
new LineHighlighter();
new BlobViewer();
+ initNotes();
break;
case 'import:fogbugz:new_user_map':
new UsersSelect();
break;
+ case 'profiles:personal_access_tokens:index':
+ case 'admin:impersonation_tokens:index':
+ new gl.DueDateSelectors();
+ break;
}
switch (path.first()) {
case 'sessions':
@@ -492,6 +580,7 @@ import PerformanceBar from './performance_bar';
shortcut_handler = new ShortcutsWiki();
new ZenMode();
new gl.GLForm($('.wiki-form'), true);
+ new Sidebar();
break;
case 'snippets':
shortcut_handler = new ShortcutsNavigation();
@@ -515,6 +604,13 @@ import PerformanceBar from './performance_bar';
case 'protected_branches':
shortcut_handler = new ShortcutsNavigation();
}
+ break;
+ case 'users':
+ const action = path[1];
+ import(/* webpackChunkName: 'user_profile' */ './users')
+ .then(user => user.default(action))
+ .catch(() => {});
+ break;
}
// If we haven't installed a custom shortcut handler, install the default one
if (!shortcut_handler) {
diff --git a/app/assets/javascripts/droplab/plugins/ajax.js b/app/assets/javascripts/droplab/plugins/ajax.js
index c0da5866139..267b53fa4f2 100644
--- a/app/assets/javascripts/droplab/plugins/ajax.js
+++ b/app/assets/javascripts/droplab/plugins/ajax.js
@@ -11,6 +11,16 @@ const Ajax = {
if (!self.destroyed) self.hook.list[config.method].call(self.hook.list, data);
},
+ preprocessing: function preprocessing(config, data) {
+ let results = data;
+
+ if (config.preprocessing && !data.preprocessed) {
+ results = config.preprocessing(data);
+ AjaxCache.override(config.endpoint, results);
+ }
+
+ return results;
+ },
init: function init(hook) {
var self = this;
self.destroyed = false;
@@ -31,7 +41,8 @@ const Ajax = {
dynamicList.outerHTML = loadingTemplate.outerHTML;
}
- AjaxCache.retrieve(config.endpoint)
+ return AjaxCache.retrieve(config.endpoint)
+ .then(self.preprocessing.bind(null, config))
.then((data) => self._loadData(data, config, self))
.catch(config.onError);
},
diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js b/app/assets/javascripts/filtered_search/dropdown_non_user.js
index 2615d626c4c..0bc4b6f22a9 100644
--- a/app/assets/javascripts/filtered_search/dropdown_non_user.js
+++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js
@@ -6,7 +6,7 @@ import './filtered_search_dropdown';
class DropdownNonUser extends gl.FilteredSearchDropdown {
constructor(options = {}) {
- const { input, endpoint, symbol } = options;
+ const { input, endpoint, symbol, preprocessing } = options;
super(options);
this.symbol = symbol;
this.config = {
@@ -14,6 +14,7 @@ class DropdownNonUser extends gl.FilteredSearchDropdown {
endpoint,
method: 'setData',
loadingTemplate: this.loadingTemplate,
+ preprocessing,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js
index ef8fe071012..9c7a4d9f6ad 100644
--- a/app/assets/javascripts/filtered_search/dropdown_utils.js
+++ b/app/assets/javascripts/filtered_search/dropdown_utils.js
@@ -50,6 +50,66 @@ class DropdownUtils {
return updatedItem;
}
+ static mergeDuplicateLabels(dataMap, newLabel) {
+ const updatedMap = dataMap;
+ const key = newLabel.title;
+
+ const hasKeyProperty = Object.prototype.hasOwnProperty.call(updatedMap, key);
+
+ if (!hasKeyProperty) {
+ updatedMap[key] = newLabel;
+ } else {
+ const existing = updatedMap[key];
+
+ if (!existing.multipleColors) {
+ existing.multipleColors = [existing.color];
+ }
+
+ existing.multipleColors.push(newLabel.color);
+ }
+
+ return updatedMap;
+ }
+
+ static duplicateLabelColor(labelColors) {
+ const colors = labelColors;
+ const spacing = 100 / colors.length;
+
+ // Reduce the colors to 4
+ colors.length = Math.min(colors.length, 4);
+
+ const color = colors.map((c, i) => {
+ const percentFirst = Math.floor(spacing * i);
+ const percentSecond = Math.floor(spacing * (i + 1));
+ return `${c} ${percentFirst}%, ${c} ${percentSecond}%`;
+ }).join(', ');
+
+ return `linear-gradient(${color})`;
+ }
+
+ static duplicateLabelPreprocessing(data) {
+ const results = [];
+ const dataMap = {};
+
+ data.forEach(DropdownUtils.mergeDuplicateLabels.bind(null, dataMap));
+
+ Object.keys(dataMap)
+ .forEach((key) => {
+ const label = dataMap[key];
+
+ if (label.multipleColors) {
+ label.color = DropdownUtils.duplicateLabelColor(label.multipleColors);
+ label.text_color = '#000000';
+ }
+
+ results.push(label);
+ });
+
+ results.preprocessed = true;
+
+ return results;
+ }
+
static filterHint(config, item) {
const { input, allowedKeys } = config;
const updatedItem = item;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index 61cef435209..47cecd5b5f7 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
@@ -54,6 +54,7 @@ class FilteredSearchDropdownManager {
extraArguments: {
endpoint: `${this.baseEndpoint}/labels.json`,
symbol: '~',
+ preprocessing: gl.DropdownUtils.duplicateLabelPreprocessing,
},
element: this.container.querySelector('#js-dropdown-label'),
},
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 7872e9e68ad..3ce8b8607ad 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -20,13 +20,13 @@ class FilteredSearchManager {
allowedKeys: this.filteredSearchTokenKeys.getKeys(),
});
this.searchHistoryDropdownElement = document.querySelector('.js-filtered-search-history-dropdown');
- const projectPath = this.searchHistoryDropdownElement ?
- this.searchHistoryDropdownElement.dataset.projectFullPath : 'project';
+ const fullPath = this.searchHistoryDropdownElement ?
+ this.searchHistoryDropdownElement.dataset.fullPath : 'project';
let recentSearchesPagePrefix = 'issue-recent-searches';
if (this.page === 'merge_requests') {
recentSearchesPagePrefix = 'merge-request-recent-searches';
}
- const recentSearchesKey = `${projectPath}-${recentSearchesPagePrefix}`;
+ const recentSearchesKey = `${fullPath}-${recentSearchesPagePrefix}`;
this.recentSearchesService = new RecentSearchesService(recentSearchesKey);
}
diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
index e9278140af0..243ee4d723a 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -58,29 +58,54 @@ class FilteredSearchVisualTokens {
`;
}
+ static setTokenStyle(tokenContainer, backgroundColor, textColor) {
+ const token = tokenContainer;
+
+ // Labels with linear gradient should not override default background color
+ if (backgroundColor.indexOf('linear-gradient') === -1) {
+ token.style.backgroundColor = backgroundColor;
+ }
+
+ token.style.color = textColor;
+
+ if (textColor === '#FFFFFF') {
+ const removeToken = token.querySelector('.remove-token');
+ removeToken.classList.add('inverted');
+ }
+
+ return token;
+ }
+
+ static preprocessLabel(labelsEndpoint, labels) {
+ let processed = labels;
+
+ if (!labels.preprocessed) {
+ processed = gl.DropdownUtils.duplicateLabelPreprocessing(labels);
+ AjaxCache.override(labelsEndpoint, processed);
+ processed.preprocessed = true;
+ }
+
+ return processed;
+ }
+
static updateLabelTokenColor(tokenValueContainer, tokenValue) {
const filteredSearchInput = FilteredSearchContainer.container.querySelector('.filtered-search');
const baseEndpoint = filteredSearchInput.dataset.baseEndpoint;
const labelsEndpoint = `${baseEndpoint}/labels.json`;
return AjaxCache.retrieve(labelsEndpoint)
- .then((labels) => {
- const matchingLabel = (labels || []).find(label => `~${gl.DropdownUtils.getEscapedText(label.title)}` === tokenValue);
-
- if (!matchingLabel) {
- return;
- }
+ .then(FilteredSearchVisualTokens.preprocessLabel.bind(null, labelsEndpoint))
+ .then((labels) => {
+ const matchingLabel = (labels || []).find(label => `~${gl.DropdownUtils.getEscapedText(label.title)}` === tokenValue);
- const tokenValueStyle = tokenValueContainer.style;
- tokenValueStyle.backgroundColor = matchingLabel.color;
- tokenValueStyle.color = matchingLabel.text_color;
+ if (!matchingLabel) {
+ return;
+ }
- if (matchingLabel.text_color === '#FFFFFF') {
- const removeToken = tokenValueContainer.querySelector('.remove-token');
- removeToken.classList.add('inverted');
- }
- })
- .catch(() => new Flash('An error occurred while fetching label colors.'));
+ FilteredSearchVisualTokens
+ .setTokenStyle(tokenValueContainer, matchingLabel.color, matchingLabel.text_color);
+ })
+ .catch(() => new Flash('An error occurred while fetching label colors.'));
}
static updateUserTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) {
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 3babe273100..9475498e176 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -730,10 +730,10 @@ GitLabDropdown = (function() {
GitLabDropdown.prototype.focusTextInput = function(triggerFocus = false) {
if (this.options.filterable) {
- $(':focus').blur();
-
this.dropdown.one('transitionend', () => {
- this.filterInput.focus();
+ if (this.dropdown.is('.open')) {
+ this.filterInput.focus();
+ }
});
if (triggerFocus) {
diff --git a/app/assets/javascripts/gpg_badges.js b/app/assets/javascripts/gpg_badges.js
new file mode 100644
index 00000000000..1c379e9bb67
--- /dev/null
+++ b/app/assets/javascripts/gpg_badges.js
@@ -0,0 +1,15 @@
+export default class GpgBadges {
+ static fetch() {
+ const form = $('.commits-search-form');
+
+ $.get({
+ url: form.data('signatures-path'),
+ data: form.serialize(),
+ }).done((response) => {
+ const badges = $('.js-loading-gpg-badge');
+ response.signatures.forEach((signature) => {
+ badges.filter(`[data-commit-sha="${signature.commit_sha}"]`).replaceWith(signature.html);
+ });
+ });
+ }
+}
diff --git a/app/assets/javascripts/graphs/graphs_bundle.js b/app/assets/javascripts/graphs/graphs_bundle.js
index a433c7ba8f0..534bc535bb6 100644
--- a/app/assets/javascripts/graphs/graphs_bundle.js
+++ b/app/assets/javascripts/graphs/graphs_bundle.js
@@ -1,6 +1,4 @@
import Chart from 'vendor/Chart';
-import ContributorsStatGraph from './stat_graph_contributors';
// export to global scope
window.Chart = Chart;
-window.ContributorsStatGraph = ContributorsStatGraph;
diff --git a/app/assets/javascripts/graphs/graphs_charts.js b/app/assets/javascripts/graphs/graphs_charts.js
new file mode 100644
index 00000000000..279ffef770f
--- /dev/null
+++ b/app/assets/javascripts/graphs/graphs_charts.js
@@ -0,0 +1,63 @@
+import Chart from 'vendor/Chart';
+
+document.addEventListener('DOMContentLoaded', () => {
+ const projectChartData = JSON.parse(document.getElementById('projectChartData').innerHTML);
+
+ const responsiveChart = (selector, data) => {
+ const options = {
+ scaleOverlay: true,
+ responsive: true,
+ pointHitDetectionRadius: 2,
+ maintainAspectRatio: false,
+ };
+ // get selector by context
+ const ctx = selector.get(0).getContext('2d');
+ // pointing parent container to make chart.js inherit its width
+ const container = $(selector).parent();
+ const generateChart = () => {
+ selector.attr('width', $(container).width());
+ if (window.innerWidth < 768) {
+ // Scale fonts if window width lower than 768px (iPad portrait)
+ options.scaleFontSize = 8;
+ }
+ return new Chart(ctx).Bar(data, options);
+ };
+ // enabling auto-resizing
+ $(window).resize(generateChart);
+ return generateChart();
+ };
+
+ const chartData = (keys, values) => {
+ const data = {
+ labels: keys,
+ datasets: [{
+ fillColor: 'rgba(220,220,220,0.5)',
+ strokeColor: 'rgba(220,220,220,1)',
+ barStrokeWidth: 1,
+ barValueSpacing: 1,
+ barDatasetSpacing: 1,
+ data: values,
+ }],
+ };
+ return data;
+ };
+
+ const hourData = chartData(projectChartData.hour.keys, projectChartData.hour.values);
+ responsiveChart($('#hour-chart'), hourData);
+
+ const dayData = chartData(projectChartData.weekDays.keys, projectChartData.weekDays.values);
+ responsiveChart($('#weekday-chart'), dayData);
+
+ const monthData = chartData(projectChartData.month.keys, projectChartData.month.values);
+ responsiveChart($('#month-chart'), monthData);
+
+ const data = projectChartData.languages;
+ const ctx = $('#languages-chart').get(0).getContext('2d');
+ const options = {
+ scaleOverlay: true,
+ responsive: true,
+ maintainAspectRatio: false,
+ };
+
+ new Chart(ctx).Pie(data, options);
+});
diff --git a/app/assets/javascripts/graphs/graphs_show.js b/app/assets/javascripts/graphs/graphs_show.js
new file mode 100644
index 00000000000..36bad6db3e1
--- /dev/null
+++ b/app/assets/javascripts/graphs/graphs_show.js
@@ -0,0 +1,21 @@
+import ContributorsStatGraph from './stat_graph_contributors';
+
+document.addEventListener('DOMContentLoaded', () => {
+ $.ajax({
+ type: 'GET',
+ url: document.querySelector('.js-graphs-show').dataset.projectGraphPath,
+ dataType: 'json',
+ success(data) {
+ const graph = new ContributorsStatGraph();
+ graph.init(data);
+
+ $('#brush_change').change(() => {
+ graph.change_date_header();
+ graph.redraw_authors();
+ });
+
+ $('.stat-graph').fadeIn();
+ $('.loading-graph').hide();
+ },
+ });
+});
diff --git a/app/assets/javascripts/groups/components/group_identicon.vue b/app/assets/javascripts/groups/components/group_identicon.vue
new file mode 100644
index 00000000000..0edd820743f
--- /dev/null
+++ b/app/assets/javascripts/groups/components/group_identicon.vue
@@ -0,0 +1,45 @@
+<script>
+export default {
+ props: {
+ entityId: {
+ type: Number,
+ required: true,
+ },
+ entityName: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ /**
+ * This method is based on app/helpers/application_helper.rb#project_identicon
+ */
+ identiconStyles() {
+ const allowedColors = [
+ '#FFEBEE',
+ '#F3E5F5',
+ '#E8EAF6',
+ '#E3F2FD',
+ '#E0F2F1',
+ '#FBE9E7',
+ '#EEEEEE',
+ ];
+
+ const backgroundColor = allowedColors[this.entityId % 7];
+
+ return `background-color: ${backgroundColor}; color: #555;`;
+ },
+ identiconTitle() {
+ return this.entityName.charAt(0).toUpperCase();
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="avatar s40 identicon"
+ :style="identiconStyles">
+ {{identiconTitle}}
+ </div>
+</template>
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index b1db34b9c50..cb133cf7535 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -1,7 +1,11 @@
<script>
import eventHub from '../event_hub';
+import groupIdenticon from './group_identicon.vue';
export default {
+ components: {
+ groupIdenticon,
+ },
props: {
group: {
type: Object,
@@ -92,6 +96,9 @@ export default {
hasGroups() {
return Object.keys(this.group.subGroups).length > 0;
},
+ hasAvatar() {
+ return this.group.avatarUrl && this.group.avatarUrl.indexOf('/assets/no_group_avatar') === -1;
+ },
},
};
</script>
@@ -194,9 +201,15 @@ export default {
<a
:href="group.groupPath">
<img
+ v-if="hasAvatar"
class="avatar s40"
:src="group.avatarUrl"
/>
+ <group-identicon
+ v-else
+ :entity-id=group.id
+ :entity-name="group.name"
+ />
</a>
</div>
<div
diff --git a/app/assets/javascripts/how_to_merge.js b/app/assets/javascripts/how_to_merge.js
new file mode 100644
index 00000000000..19f4a946f73
--- /dev/null
+++ b/app/assets/javascripts/how_to_merge.js
@@ -0,0 +1,12 @@
+document.addEventListener('DOMContentLoaded', () => {
+ const modal = $('#modal_merge_info').modal({
+ modal: true,
+ show: false,
+ });
+ $('.how_to_merge_link').on('click', () => {
+ modal.show();
+ });
+ $('.modal-header .close').on('click', () => {
+ modal.hide();
+ });
+});
diff --git a/app/assets/javascripts/init_issuable_sidebar.js b/app/assets/javascripts/init_issuable_sidebar.js
new file mode 100644
index 00000000000..29e3d2ea94e
--- /dev/null
+++ b/app/assets/javascripts/init_issuable_sidebar.js
@@ -0,0 +1,18 @@
+/* eslint-disable no-new */
+/* global MilestoneSelect */
+/* global LabelsSelect */
+/* global IssuableContext */
+/* global Sidebar */
+
+export default () => {
+ const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
+
+ new MilestoneSelect({
+ full_path: sidebarOptions.fullPath,
+ });
+ new LabelsSelect();
+ new IssuableContext(sidebarOptions.currentUser);
+ gl.Subscription.bindAll('.subscription');
+ new gl.DueDateSelectors();
+ window.sidebar = new Sidebar();
+};
diff --git a/app/assets/javascripts/init_legacy_filters.js b/app/assets/javascripts/init_legacy_filters.js
new file mode 100644
index 00000000000..1211c2c802c
--- /dev/null
+++ b/app/assets/javascripts/init_legacy_filters.js
@@ -0,0 +1,15 @@
+/* eslint-disable no-new */
+/* global LabelsSelect */
+/* global MilestoneSelect */
+/* global IssueStatusSelect */
+/* global SubscriptionSelect */
+
+import UsersSelect from './users_select';
+
+export default () => {
+ new UsersSelect();
+ new LabelsSelect();
+ new MilestoneSelect();
+ new IssueStatusSelect();
+ new SubscriptionSelect();
+};
diff --git a/app/assets/javascripts/init_notes.js b/app/assets/javascripts/init_notes.js
new file mode 100644
index 00000000000..3a8b4360cb6
--- /dev/null
+++ b/app/assets/javascripts/init_notes.js
@@ -0,0 +1,14 @@
+/* global Notes */
+
+export default () => {
+ const dataEl = document.querySelector('.js-notes-data');
+ const {
+ notesUrl,
+ notesIds,
+ now,
+ diffView,
+ autocomplete,
+ } = JSON.parse(dataEl.innerHTML);
+
+ window.notes = new Notes(notesUrl, notesIds, now, diffView, autocomplete);
+};
diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js
index ddd3a6aab99..cf1e6a14725 100644
--- a/app/assets/javascripts/integrations/integration_settings_form.js
+++ b/app/assets/javascripts/integrations/integration_settings_form.js
@@ -102,7 +102,7 @@ export default class IntegrationSettingsForm {
})
.done((res) => {
if (res.error) {
- new Flash(res.message, null, null, {
+ new Flash(`${res.message} ${res.service_response}`, null, null, {
title: 'Save anyway',
clickHandler: (e) => {
e.preventDefault();
diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js
index a4d7bf096ef..26392db4b5b 100644
--- a/app/assets/javascripts/issuable_context.js
+++ b/app/assets/javascripts/issuable_context.js
@@ -4,6 +4,8 @@
import Cookies from 'js-cookie';
import UsersSelect from './users_select';
+const PARTICIPANTS_ROW_COUNT = 7;
+
(function() {
this.IssuableContext = (function() {
function IssuableContext(currentUser) {
@@ -50,11 +52,9 @@ import UsersSelect from './users_select';
}
IssuableContext.prototype.initParticipants = function() {
- var _this;
- _this = this;
$(document).on("click", ".js-participants-more", this.toggleHiddenParticipants);
return $(".js-participants-author").each(function(i) {
- if (i >= _this.PARTICIPANTS_ROW_COUNT) {
+ if (i >= PARTICIPANTS_ROW_COUNT) {
return $(this).addClass("js-participants-hidden").hide();
}
});
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index 8d7d3d73571..f0e02ca0fb2 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -3,6 +3,7 @@
/* global ListLabel */
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
+import DropdownUtils from './filtered_search/dropdown_utils';
(function() {
this.LabelsSelect = (function() {
@@ -218,18 +219,7 @@ import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
}
}
if (label.duplicate) {
- spacing = 100 / label.color.length;
- // Reduce the colors to 4
- label.color = label.color.filter(function(color, i) {
- return i < 4;
- });
- color = _.map(label.color, function(color, i) {
- var percentFirst, percentSecond;
- percentFirst = Math.floor(spacing * i);
- percentSecond = Math.floor(spacing * (i + 1));
- return color + " " + percentFirst + "%," + color + " " + percentSecond + "% ";
- }).join(',');
- color = "linear-gradient(" + color + ")";
+ color = gl.DropdownUtils.duplicateLabelColor(label.color);
}
else {
if (label.color != null) {
diff --git a/app/assets/javascripts/lazy_loader.js b/app/assets/javascripts/lazy_loader.js
new file mode 100644
index 00000000000..3d64b121fa7
--- /dev/null
+++ b/app/assets/javascripts/lazy_loader.js
@@ -0,0 +1,76 @@
+/* eslint-disable one-export, one-var, one-var-declaration-per-line */
+
+import _ from 'underscore';
+
+export const placeholderImage = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
+const SCROLL_THRESHOLD = 300;
+
+export default class LazyLoader {
+ constructor(options = {}) {
+ this.lazyImages = [];
+ this.observerNode = options.observerNode || '#content-body';
+
+ const throttledScrollCheck = _.throttle(() => this.scrollCheck(), 300);
+ const debouncedElementsInView = _.debounce(() => this.checkElementsInView(), 300);
+
+ window.addEventListener('scroll', throttledScrollCheck);
+ window.addEventListener('resize', debouncedElementsInView);
+
+ const scrollContainer = options.scrollContainer || window;
+ scrollContainer.addEventListener('load', () => this.loadCheck());
+ }
+ searchLazyImages() {
+ this.lazyImages = [].slice.call(document.querySelectorAll('.lazy'));
+ this.checkElementsInView();
+ }
+ startContentObserver() {
+ const contentNode = document.querySelector(this.observerNode) || document.querySelector('body');
+
+ if (contentNode) {
+ const observer = new MutationObserver(() => this.searchLazyImages());
+
+ observer.observe(contentNode, {
+ childList: true,
+ subtree: true,
+ });
+ }
+ }
+ loadCheck() {
+ this.searchLazyImages();
+ this.startContentObserver();
+ }
+ scrollCheck() {
+ requestAnimationFrame(() => this.checkElementsInView());
+ }
+ checkElementsInView() {
+ const scrollTop = pageYOffset;
+ const visHeight = scrollTop + innerHeight + SCROLL_THRESHOLD;
+ let imgBoundRect, imgTop, imgBound;
+
+ // Loading Images which are in the current viewport or close to them
+ this.lazyImages = this.lazyImages.filter((selectedImage) => {
+ if (selectedImage.getAttribute('data-src')) {
+ imgBoundRect = selectedImage.getBoundingClientRect();
+
+ imgTop = scrollTop + imgBoundRect.top;
+ imgBound = imgTop + imgBoundRect.height;
+
+ if (scrollTop < imgBound && visHeight > imgTop) {
+ LazyLoader.loadImage(selectedImage);
+ return false;
+ }
+
+ return true;
+ }
+ return false;
+ });
+ }
+ static loadImage(img) {
+ if (img.getAttribute('data-src')) {
+ img.setAttribute('src', img.getAttribute('data-src'));
+ img.removeAttribute('data-src');
+ img.classList.remove('lazy');
+ img.classList.add('js-lazy-loaded');
+ }
+ }
+}
diff --git a/app/assets/javascripts/lib/utils/ajax_cache.js b/app/assets/javascripts/lib/utils/ajax_cache.js
index 7477b5a5214..629d8f44e18 100644
--- a/app/assets/javascripts/lib/utils/ajax_cache.js
+++ b/app/assets/javascripts/lib/utils/ajax_cache.js
@@ -6,6 +6,10 @@ class AjaxCache extends Cache {
this.pendingRequests = { };
}
+ override(endpoint, data) {
+ this.internalStorage[endpoint] = data;
+ }
+
retrieve(endpoint, forceRetrieve) {
if (this.hasData(endpoint) && !forceRetrieve) {
return Promise.resolve(this.get(endpoint));
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 26c67fb721c..cd45091c211 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -109,6 +109,7 @@ import './label_manager';
import './labels';
import './labels_select';
import './layout_nav';
+import LazyLoader from './lazy_loader';
import './line_highlighter';
import './logo';
import './member_expiration_date';
@@ -144,7 +145,6 @@ import './right_sidebar';
import './search';
import './search_autocomplete';
import './smart_interval';
-import './snippets_list';
import './star';
import './subscription';
import './subscription_select';
@@ -158,6 +158,8 @@ document.addEventListener('beforeunload', function () {
$(document).off('scroll');
// Close any open tooltips
$('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy');
+ // Close any open popover
+ $('[data-toggle="popover"]').popover('destroy');
});
window.addEventListener('hashchange', gl.utils.handleLocationHash);
@@ -166,6 +168,11 @@ window.addEventListener('load', function onLoad() {
gl.utils.handleLocationHash();
}, false);
+gl.lazyLoader = new LazyLoader({
+ scrollContainer: window,
+ observerNode: '#content-body'
+});
+
$(function () {
var $body = $('body');
var $document = $(document);
@@ -241,6 +248,11 @@ $(function () {
return $(el).data('placement') || 'bottom';
}
});
+ // Initialize popovers
+ $body.popover({
+ selector: '[data-toggle="popover"]',
+ trigger: 'focus'
+ });
$('.trigger-submit').on('change', function () {
return $(this).parents('form').submit();
// Form submitter
@@ -284,13 +296,7 @@ $(function () {
return $container.remove();
// Commit show suppressed diff
});
- $('.navbar-toggle').on('click', function () {
- $('.header-content .title, .header-content .navbar-sub-nav').toggle();
- $('.header-content .header-logo').toggle();
- $('.header-content .navbar-collapse').toggle();
- $('.js-navbar-toggle-left, .js-navbar-toggle-right, .title-container').toggle();
- return $('.navbar-toggle').toggleClass('active');
- });
+ $('.navbar-toggle').on('click', () => $('.header-content').toggleClass('menu-expanded'));
// Show/hide comments on diff
$body.on('click', '.js-toggle-diff-comments', function (e) {
var $this = $(this);
@@ -347,4 +353,14 @@ $(function () {
gl.utils.renderTimeago();
$(document).trigger('init.scrolling-tabs');
+
+ $('form.filter-form').on('submit', function (event) {
+ const link = document.createElement('a');
+ link.href = this.action;
+
+ const action = `${this.action}${link.search === '' ? '?' : '&'}`;
+
+ event.preventDefault();
+ gl.utils.visitUrl(`${action}${$(this).serialize()}`);
+ });
});
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
index c4e379a4a0b..8be7314ded8 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
@@ -175,7 +175,7 @@ import Cookies from 'js-cookie';
getConflictsCountText() {
const count = this.getConflictsCount();
- const text = count ? 'conflicts' : 'conflict';
+ const text = count > 1 ? 'conflicts' : 'conflict';
return `${count} ${text}`;
},
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
index 17030c3e4d3..d74cf5328ad 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
+++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
@@ -2,6 +2,7 @@
/* global Flash */
import Vue from 'vue';
+import initIssuableSidebar from '../init_issuable_sidebar';
import './merge_conflict_store';
import './merge_conflict_service';
import './mixins/line_conflict_utils';
@@ -19,6 +20,8 @@ $(() => {
resolveConflictsPath: conflictsEl.dataset.resolveConflictsPath
});
+ initIssuableSidebar();
+
gl.MergeConflictsResolverApp = new Vue({
el: '#conflicts',
data: mergeConflictsStore.state,
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index 9d481d7c003..6756ab0b3aa 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -8,7 +8,7 @@
var _this, $els;
if (currentProject != null) {
_this = this;
- this.currentProject = JSON.parse(currentProject);
+ this.currentProject = typeof currentProject === 'string' ? JSON.parse(currentProject) : currentProject;
}
$els = $(els);
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index b2c503d1656..dfa07a2def4 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -529,6 +529,7 @@ export default class Notes {
form.find('#note_line_code').remove();
form.find('#note_position').remove();
form.find('#note_type').val('');
+ form.find('#note_project_id').remove();
form.find('#in_reply_to_discussion_id').remove();
form.find('.js-comment-resolve-button').closest('comment-and-resolve-btn').remove();
this.parentTimeline = form.parents('.timeline');
@@ -556,6 +557,7 @@ export default class Notes {
form.find('#note_noteable_id').val(),
form.find('#note_commit_id').val(),
form.find('#note_type').val(),
+ form.find('#note_project_id').val(),
form.find('#in_reply_to_discussion_id').val(),
// LegacyDiffNote
@@ -848,6 +850,8 @@ export default class Notes {
form.find('#in_reply_to_discussion_id').val(discussionID);
}
+ form.find('#note_project_id').val(dataHolder.data('discussionProjectId'));
+
form.attr('data-line-code', dataHolder.data('lineCode'));
form.find('#line_type').val(dataHolder.data('lineType'));
diff --git a/app/assets/javascripts/pipelines/pipelines_charts.js b/app/assets/javascripts/pipelines/pipelines_charts.js
new file mode 100644
index 00000000000..001faf4be33
--- /dev/null
+++ b/app/assets/javascripts/pipelines/pipelines_charts.js
@@ -0,0 +1,38 @@
+import Chart from 'vendor/Chart';
+
+document.addEventListener('DOMContentLoaded', () => {
+ const chartData = JSON.parse(document.getElementById('pipelinesChartsData').innerHTML);
+ const buildChart = (chartScope) => {
+ const data = {
+ labels: chartScope.labels,
+ datasets: [{
+ fillColor: '#7f8fa4',
+ strokeColor: '#7f8fa4',
+ pointColor: '#7f8fa4',
+ pointStrokeColor: '#EEE',
+ data: chartScope.totalValues,
+ },
+ {
+ fillColor: '#44aa22',
+ strokeColor: '#44aa22',
+ pointColor: '#44aa22',
+ pointStrokeColor: '#fff',
+ data: chartScope.successValues,
+ },
+ ],
+ };
+ const ctx = $(`#${chartScope.scope}Chart`).get(0).getContext('2d');
+ const options = {
+ scaleOverlay: true,
+ responsive: true,
+ maintainAspectRatio: false,
+ };
+ if (window.innerWidth < 768) {
+ // Scale fonts if window width lower than 768px (iPad portrait)
+ options.scaleFontSize = 8;
+ }
+ new Chart(ctx).Line(data, options);
+ };
+
+ chartData.forEach(scope => buildChart(scope));
+});
diff --git a/app/assets/javascripts/pipelines/pipelines_times.js b/app/assets/javascripts/pipelines/pipelines_times.js
new file mode 100644
index 00000000000..b5e7a0e53d9
--- /dev/null
+++ b/app/assets/javascripts/pipelines/pipelines_times.js
@@ -0,0 +1,27 @@
+import Chart from 'vendor/Chart';
+
+document.addEventListener('DOMContentLoaded', () => {
+ const chartData = JSON.parse(document.getElementById('pipelinesTimesChartsData').innerHTML);
+ const data = {
+ labels: chartData.labels,
+ datasets: [{
+ fillColor: 'rgba(220,220,220,0.5)',
+ strokeColor: 'rgba(220,220,220,1)',
+ barStrokeWidth: 1,
+ barValueSpacing: 1,
+ barDatasetSpacing: 1,
+ data: chartData.values,
+ }],
+ };
+ const ctx = $('#build_timesChart').get(0).getContext('2d');
+ const options = {
+ scaleOverlay: true,
+ responsive: true,
+ maintainAspectRatio: false,
+ };
+ if (window.innerWidth < 768) {
+ // Scale fonts if window width lower than 768px (iPad portrait)
+ options.scaleFontSize = 8;
+ }
+ new Chart(ctx).Bar(data, options);
+});
diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js
index cf1566eeb87..f32b2413725 100644
--- a/app/assets/javascripts/profile/gl_crop.js
+++ b/app/assets/javascripts/profile/gl_crop.js
@@ -1,6 +1,6 @@
/* eslint-disable no-useless-escape, max-len, quotes, no-var, no-underscore-dangle, func-names, space-before-function-paren, no-unused-vars, no-return-assign, object-shorthand, one-var, one-var-declaration-per-line, comma-dangle, consistent-return, class-methods-use-this, new-parens */
-import 'vendor/cropper';
+import 'cropper';
((global) => {
// Matches everything but the file name
diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js
index 738e710deb9..a3f7d69b98d 100644
--- a/app/assets/javascripts/project.js
+++ b/app/assets/javascripts/project.js
@@ -6,21 +6,22 @@ import Cookies from 'js-cookie';
(function() {
this.Project = (function() {
function Project() {
- $('ul.clone-options-dropdown a').click(function() {
- var url;
- if ($(this).hasClass('active')) {
- return;
- }
- $('.active').not($(this)).removeClass('active');
- $(this).toggleClass('active');
- url = $("#project_clone").val();
- $('#project_clone').val(url);
+ const $cloneOptions = $('ul.clone-options-dropdown');
+ const $projectCloneField = $('#project_clone');
+ const $cloneBtnText = $('a.clone-dropdown-btn span');
+
+ $('a', $cloneOptions).on('click', (e) => {
+ const $this = $(e.currentTarget);
+ const url = $this.attr('href');
+
+ e.preventDefault();
+
+ $('.active', $cloneOptions).not($this).removeClass('active');
+ $this.toggleClass('active');
+ $projectCloneField.val(url);
+ $cloneBtnText.text($this.text());
+
return $('.clone').text(url);
- // Git protocol switcher
- // Remove the active class for all buttons (ssh, http, kerberos if shown)
- // Add the active class for the clicked button
- // Update the input field
- // Update the command line instructions
});
// Ref switcher
this.initRefSwitcher();
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
new file mode 100644
index 00000000000..1dc1dbf356d
--- /dev/null
+++ b/app/assets/javascripts/projects/project_new.js
@@ -0,0 +1,85 @@
+let hasUserDefinedProjectPath = false;
+
+const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => {
+ if ($projectImportUrl.attr('disabled') || hasUserDefinedProjectPath) {
+ return;
+ }
+
+ let importUrl = $projectImportUrl.val().trim();
+ if (importUrl.length === 0) {
+ return;
+ }
+
+ /*
+ \/?: remove trailing slash
+ (\.git\/?)?: remove trailing .git (with optional trailing slash)
+ (\?.*)?: remove query string
+ (#.*)?: remove fragment identifier
+ */
+ importUrl = importUrl.replace(/\/?(\.git\/?)?(\?.*)?(#.*)?$/, '');
+
+ // extract everything after the last slash
+ const pathMatch = /\/([^/]+)$/.exec(importUrl);
+ if (pathMatch) {
+ $projectPath.val(pathMatch[1]);
+ }
+};
+
+const bindEvents = () => {
+ const $newProjectForm = $('#new_project');
+ const importBtnTooltip = 'Please enter a valid project name.';
+ const $importBtnWrapper = $('.import_gitlab_project');
+ const $projectImportUrl = $('#project_import_url');
+ const $projectPath = $('#project_path');
+
+ if ($newProjectForm.length !== 1) {
+ return;
+ }
+
+ $('.how_to_import_link').on('click', (e) => {
+ e.preventDefault();
+ $('.how_to_import_link').next('.modal').show();
+ });
+
+ $('.modal-header .close').on('click', () => {
+ $('.modal').hide();
+ });
+
+ $('.btn_import_gitlab_project').on('click', () => {
+ const importHref = $('a.btn_import_gitlab_project').attr('href');
+ $('.btn_import_gitlab_project').attr('href', `${importHref}?namespace_id=${$('#project_namespace_id').val()}&path=${$projectPath.val()}`);
+ });
+
+ $('.btn_import_gitlab_project').attr('disabled', !$projectPath.val().trim().length);
+ $importBtnWrapper.attr('title', importBtnTooltip);
+
+ $newProjectForm.on('submit', () => {
+ $projectPath.val($projectPath.val().trim());
+ });
+
+ $projectPath.on('keyup', () => {
+ hasUserDefinedProjectPath = $projectPath.val().trim().length > 0;
+ if (hasUserDefinedProjectPath) {
+ $('.btn_import_gitlab_project').attr('disabled', false);
+ $importBtnWrapper.attr('title', '');
+ $importBtnWrapper.removeClass('has-tooltip');
+ } else {
+ $('.btn_import_gitlab_project').attr('disabled', true);
+ $importBtnWrapper.addClass('has-tooltip');
+ }
+ });
+
+ $projectImportUrl.disable();
+ $projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl, $projectPath));
+
+ $('.import_git').on('click', () => {
+ $projectImportUrl.attr('disabled', !$projectImportUrl.attr('disabled'));
+ });
+};
+
+document.addEventListener('DOMContentLoaded', bindEvents);
+
+export default {
+ bindEvents,
+ deriveProjectPathFromUrl,
+};
diff --git a/app/assets/javascripts/ref_select_dropdown.js b/app/assets/javascripts/ref_select_dropdown.js
index 215cd6fbdfd..65e4101352c 100644
--- a/app/assets/javascripts/ref_select_dropdown.js
+++ b/app/assets/javascripts/ref_select_dropdown.js
@@ -1,7 +1,8 @@
class RefSelectDropdown {
constructor($dropdownButton, availableRefs) {
+ const availableRefsValue = availableRefs || JSON.parse(document.getElementById('availableRefs').innerHTML);
$dropdownButton.glDropdown({
- data: availableRefs,
+ data: availableRefsValue,
filterable: true,
filterByText: true,
remote: false,
diff --git a/app/assets/javascripts/sidebar/sidebar_bundle.js b/app/assets/javascripts/sidebar/sidebar_bundle.js
index 2b02af87d8a..a9df66748c5 100644
--- a/app/assets/javascripts/sidebar/sidebar_bundle.js
+++ b/app/assets/javascripts/sidebar/sidebar_bundle.js
@@ -5,7 +5,8 @@ import sidebarAssignees from './components/assignees/sidebar_assignees';
import Mediator from './sidebar_mediator';
function domContentLoaded() {
- const mediator = new Mediator(gl.sidebarOptions);
+ const sidebarOptions = JSON.parse(document.querySelector('.js-sidebar-options').innerHTML);
+ const mediator = new Mediator(sidebarOptions);
mediator.fetch();
const sidebarAssigneesEl = document.querySelector('#js-vue-sidebar-assignees');
diff --git a/app/assets/javascripts/snippets_list.js b/app/assets/javascripts/snippets_list.js
deleted file mode 100644
index 3b6d999b1c3..00000000000
--- a/app/assets/javascripts/snippets_list.js
+++ /dev/null
@@ -1,9 +0,0 @@
-function SnippetsList() {
- const $holder = $('.snippets-list-holder');
-
- $holder.find('.pagination').on('ajax:success', (e, data) => {
- $holder.replaceWith(data.html);
- });
-}
-
-window.gl.SnippetsList = SnippetsList;
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js
index 6d38124f1c1..3a06b477d7c 100644
--- a/app/assets/javascripts/star.js
+++ b/app/assets/javascripts/star.js
@@ -1,6 +1,8 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-unused-vars, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, no-new, max-len */
/* global Flash */
+import { __, s__ } from './locale';
+
export default class Star {
constructor() {
$('.project-home-panel .toggle-star').on('ajax:success', function(e, data, status, xhr) {
@@ -11,10 +13,10 @@ export default class Star {
toggleStar = function(isStarred) {
$this.parent().find('.star-count').text(data.star_count);
if (isStarred) {
- $starSpan.removeClass('starred').text('Star');
+ $starSpan.removeClass('starred').text(s__('StarProject|Star'));
$starIcon.removeClass('fa-star').addClass('fa-star-o');
} else {
- $starSpan.addClass('starred').text('Unstar');
+ $starSpan.addClass('starred').text(__('Unstar'));
$starIcon.removeClass('fa-star-o').addClass('fa-star');
}
};
diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js
index cd305631c10..bba8b5abbb4 100644
--- a/app/assets/javascripts/todos.js
+++ b/app/assets/javascripts/todos.js
@@ -37,10 +37,6 @@ export default class Todos {
this.initFilterDropdown($('.js-type-search'), 'type');
this.initFilterDropdown($('.js-action-search'), 'action_id');
- $('form.filter-form').on('submit', function applyFilters(event) {
- event.preventDefault();
- gl.utils.visitUrl(`${this.action}&${$(this).serialize()}`);
- });
return new UsersSelect();
}
diff --git a/app/assets/javascripts/two_factor_auth.js b/app/assets/javascripts/two_factor_auth.js
new file mode 100644
index 00000000000..d26f61562a5
--- /dev/null
+++ b/app/assets/javascripts/two_factor_auth.js
@@ -0,0 +1,13 @@
+/* global U2FRegister */
+document.addEventListener('DOMContentLoaded', () => {
+ const twoFactorNode = document.querySelector('.js-two-factor-auth');
+ const skippable = twoFactorNode.dataset.twoFactorSkippable === 'true';
+ if (skippable) {
+ const button = `<a class="btn btn-xs btn-warning pull-right" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`;
+ const flashAlert = document.querySelector('.flash-alert .container-fluid');
+ if (flashAlert) flashAlert.insertAdjacentHTML('beforeend', button);
+ }
+
+ const u2fRegister = new U2FRegister($('#js-register-u2f'), gon.u2f);
+ u2fRegister.start();
+});
diff --git a/app/assets/javascripts/ui_development_kit.js b/app/assets/javascripts/ui_development_kit.js
new file mode 100644
index 00000000000..f503076715c
--- /dev/null
+++ b/app/assets/javascripts/ui_development_kit.js
@@ -0,0 +1,22 @@
+import Api from './api';
+
+document.addEventListener('DOMContentLoaded', () => {
+ $('#js-project-dropdown').glDropdown({
+ data: (term, callback) => {
+ Api.projects(term, {
+ order_by: 'last_activity_at',
+ }, (data) => {
+ callback(data);
+ });
+ },
+ text: project => (project.name_with_namespace || project.name),
+ selectable: true,
+ fieldName: 'author_id',
+ filterable: true,
+ search: {
+ fields: ['name_with_namespace'],
+ },
+ id: data => data.id,
+ isSelected: data => (data.id === 2),
+ });
+});
diff --git a/app/assets/javascripts/users/activity_calendar.js b/app/assets/javascripts/users/activity_calendar.js
index b7f50cfd083..f091e319f44 100644
--- a/app/assets/javascripts/users/activity_calendar.js
+++ b/app/assets/javascripts/users/activity_calendar.js
@@ -1,10 +1,28 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, camelcase, vars-on-top, object-shorthand, comma-dangle, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len, class-methods-use-this */
-
import d3 from 'd3';
+const LOADING_HTML = `
+ <div class="text-center">
+ <i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i>
+ </div>
+`;
+
+function formatTooltipText({ date, count }) {
+ const dateObject = new Date(date);
+ const dateDayName = gl.utils.getDayName(dateObject);
+ const dateText = dateObject.format('mmm d, yyyy');
+
+ let contribText = 'No contributions';
+ if (count > 0) {
+ contribText = `${count} contribution${count > 1 ? 's' : ''}`;
+ }
+ return `${contribText}<br />${dateDayName} ${dateText}`;
+}
+
+const initColorKey = () => d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]);
+
export default class ActivityCalendar {
- constructor(timestamps, calendar_activities_path) {
- this.calendar_activities_path = calendar_activities_path;
+ constructor(container, timestamps, calendarActivitiesPath) {
+ this.calendarActivitiesPath = calendarActivitiesPath;
this.clickDay = this.clickDay.bind(this);
this.currentSelectedDate = '';
this.daySpace = 1;
@@ -12,25 +30,26 @@ export default class ActivityCalendar {
this.daySizeWithSpace = this.daySize + (this.daySpace * 2);
this.monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
this.months = [];
+
// Loop through the timestamps to create a group of objects
// The group of objects will be grouped based on the day of the week they are
this.timestampsTmp = [];
- var group = 0;
+ let group = 0;
- var today = new Date();
+ const today = new Date();
today.setHours(0, 0, 0, 0, 0);
- var oneYearAgo = new Date(today);
+ const oneYearAgo = new Date(today);
oneYearAgo.setFullYear(today.getFullYear() - 1);
- var days = gl.utils.getDayDifference(oneYearAgo, today);
+ const days = gl.utils.getDayDifference(oneYearAgo, today);
- for (var i = 0; i <= days; i += 1) {
- var date = new Date(oneYearAgo);
+ for (let i = 0; i <= days; i += 1) {
+ const date = new Date(oneYearAgo);
date.setDate(date.getDate() + i);
- var day = date.getDay();
- var count = timestamps[date.format('yyyy-mm-dd')];
+ const day = date.getDay();
+ const count = timestamps[date.format('yyyy-mm-dd')] || 0;
// Create a new group array if this is the first day of the week
// or if is first object
@@ -39,129 +58,119 @@ export default class ActivityCalendar {
group += 1;
}
- var innerArray = this.timestampsTmp[group - 1];
// Push to the inner array the values that will be used to render map
- innerArray.push({
- count: count || 0,
- date: date,
- day: day
- });
+ const innerArray = this.timestampsTmp[group - 1];
+ innerArray.push({ count, date, day });
}
// Init color functions
- this.colorKey = this.initColorKey();
+ this.colorKey = initColorKey();
this.color = this.initColor();
+
// Init the svg element
- this.renderSvg(group);
+ this.svg = this.renderSvg(container, group);
this.renderDays();
this.renderMonths();
this.renderDayTitles();
this.renderKey();
- this.initTooltips();
+
+ // Init tooltips
+ $(`${container} .js-tooltip`).tooltip({ html: true });
}
// Add extra padding for the last month label if it is also the last column
getExtraWidthPadding(group) {
- var extraWidthPadding = 0;
- var lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth();
- var secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth();
+ let extraWidthPadding = 0;
+ const lastColMonth = this.timestampsTmp[group - 1][0].date.getMonth();
+ const secondLastColMonth = this.timestampsTmp[group - 2][0].date.getMonth();
- if (lastColMonth != secondLastColMonth) {
+ if (lastColMonth !== secondLastColMonth) {
extraWidthPadding = 3;
}
return extraWidthPadding;
}
- renderSvg(group) {
- var width = (group + 1) * this.daySizeWithSpace + this.getExtraWidthPadding(group);
- return this.svg = d3.select('.js-contrib-calendar').append('svg').attr('width', width).attr('height', 167).attr('class', 'contrib-calendar');
+ renderSvg(container, group) {
+ const width = ((group + 1) * this.daySizeWithSpace) + this.getExtraWidthPadding(group);
+ return d3.select(container)
+ .append('svg')
+ .attr('width', width)
+ .attr('height', 167)
+ .attr('class', 'contrib-calendar');
}
renderDays() {
- return this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g').attr('transform', (function(_this) {
- return function(group, i) {
- _.each(group, function(stamp, a) {
- var lastMonth, lastMonthX, month, x;
+ this.svg.selectAll('g').data(this.timestampsTmp).enter().append('g')
+ .attr('transform', (group, i) => {
+ _.each(group, (stamp, a) => {
if (a === 0 && stamp.day === 0) {
- month = stamp.date.getMonth();
- x = (_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace;
- lastMonth = _.last(_this.months);
- if (lastMonth != null) {
- lastMonthX = lastMonth.x;
- }
- if (lastMonth == null) {
- return _this.months.push({
- month: month,
- x: x
- });
- } else if (month !== lastMonth.month && x - _this.daySizeWithSpace !== lastMonthX) {
- return _this.months.push({
- month: month,
- x: x
- });
+ const month = stamp.date.getMonth();
+ const x = (this.daySizeWithSpace * i) + 1 + this.daySizeWithSpace;
+ const lastMonth = _.last(this.months);
+ if (
+ lastMonth == null ||
+ (month !== lastMonth.month && x - this.daySizeWithSpace !== lastMonth.x)
+ ) {
+ this.months.push({ month, x });
}
}
});
- return "translate(" + ((_this.daySizeWithSpace * i + 1) + _this.daySizeWithSpace) + ", 18)";
- };
- })(this)).selectAll('rect').data(function(stamp) {
- return stamp;
- }).enter().append('rect').attr('x', '0').attr('y', (function(_this) {
- return function(stamp, i) {
- return _this.daySizeWithSpace * stamp.day;
- };
- })(this)).attr('width', this.daySize).attr('height', this.daySize).attr('title', (function(_this) {
- return function(stamp) {
- var contribText, date, dateText;
- date = new Date(stamp.date);
- contribText = 'No contributions';
- if (stamp.count > 0) {
- contribText = stamp.count + " contribution" + (stamp.count > 1 ? 's' : '');
- }
- dateText = date.format('mmm d, yyyy');
- return contribText + "<br />" + (gl.utils.getDayName(date)) + " " + dateText;
- };
- })(this)).attr('class', 'user-contrib-cell js-tooltip').attr('fill', (function(_this) {
- return function(stamp) {
- if (stamp.count !== 0) {
- return _this.color(Math.min(stamp.count, 40));
- } else {
- return '#ededed';
- }
- };
- })(this)).attr('data-container', 'body').on('click', this.clickDay);
+ return `translate(${(this.daySizeWithSpace * i) + 1 + this.daySizeWithSpace}, 18)`;
+ })
+ .selectAll('rect')
+ .data(stamp => stamp)
+ .enter()
+ .append('rect')
+ .attr('x', '0')
+ .attr('y', stamp => this.daySizeWithSpace * stamp.day)
+ .attr('width', this.daySize)
+ .attr('height', this.daySize)
+ .attr('fill', stamp => (
+ stamp.count !== 0 ? this.color(Math.min(stamp.count, 40)) : '#ededed'
+ ))
+ .attr('title', stamp => formatTooltipText(stamp))
+ .attr('class', 'user-contrib-cell js-tooltip')
+ .attr('data-container', 'body')
+ .on('click', this.clickDay);
}
renderDayTitles() {
- var days;
- days = [
+ const days = [
{
text: 'M',
- y: 29 + (this.daySizeWithSpace * 1)
+ y: 29 + (this.daySizeWithSpace * 1),
}, {
text: 'W',
- y: 29 + (this.daySizeWithSpace * 3)
+ y: 29 + (this.daySizeWithSpace * 3),
}, {
text: 'F',
- y: 29 + (this.daySizeWithSpace * 5)
- }
+ y: 29 + (this.daySizeWithSpace * 5),
+ },
];
- return this.svg.append('g').selectAll('text').data(days).enter().append('text').attr('text-anchor', 'middle').attr('x', 8).attr('y', function(day) {
- return day.y;
- }).text(function(day) {
- return day.text;
- }).attr('class', 'user-contrib-text');
+ this.svg.append('g')
+ .selectAll('text')
+ .data(days)
+ .enter()
+ .append('text')
+ .attr('text-anchor', 'middle')
+ .attr('x', 8)
+ .attr('y', day => day.y)
+ .text(day => day.text)
+ .attr('class', 'user-contrib-text');
}
renderMonths() {
- return this.svg.append('g').attr('direction', 'ltr').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) {
- return date.x;
- }).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) {
- return function(date) {
- return _this.monthNames[date.month];
- };
- })(this));
+ this.svg.append('g')
+ .attr('direction', 'ltr')
+ .selectAll('text')
+ .data(this.months)
+ .enter()
+ .append('text')
+ .attr('x', date => date.x)
+ .attr('y', 10)
+ .attr('class', 'user-contrib-text')
+ .text(date => this.monthNames[date.month]);
}
renderKey() {
@@ -169,7 +178,7 @@ export default class ActivityCalendar {
const keyColors = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
this.svg.append('g')
- .attr('transform', `translate(18, ${this.daySizeWithSpace * 8 + 16})`)
+ .attr('transform', `translate(18, ${(this.daySizeWithSpace * 8) + 16})`)
.selectAll('rect')
.data(keyColors)
.enter()
@@ -185,43 +194,31 @@ export default class ActivityCalendar {
}
initColor() {
- var colorRange;
- colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
+ const colorRange = ['#ededed', this.colorKey(0), this.colorKey(1), this.colorKey(2), this.colorKey(3)];
return d3.scale.threshold().domain([0, 10, 20, 30]).range(colorRange);
}
- initColorKey() {
- return d3.scale.linear().range(['#acd5f2', '#254e77']).domain([0, 3]);
- }
-
clickDay(stamp) {
- var formatted_date;
if (this.currentSelectedDate !== stamp.date) {
this.currentSelectedDate = stamp.date;
- formatted_date = this.currentSelectedDate.getFullYear() + "-" + (this.currentSelectedDate.getMonth() + 1) + "-" + this.currentSelectedDate.getDate();
- return $.ajax({
- url: this.calendar_activities_path,
- data: {
- date: formatted_date
- },
+
+ const date = [
+ this.currentSelectedDate.getFullYear(),
+ this.currentSelectedDate.getMonth() + 1,
+ this.currentSelectedDate.getDate(),
+ ].join('-');
+
+ $.ajax({
+ url: this.calendarActivitiesPath,
+ data: { date },
cache: false,
dataType: 'html',
- beforeSend: function() {
- return $('.user-calendar-activities').html('<div class="text-center"><i class="fa fa-spinner fa-spin user-calendar-activities-loading"></i></div>');
- },
- success: function(data) {
- return $('.user-calendar-activities').html(data);
- }
+ beforeSend: () => $('.user-calendar-activities').html(LOADING_HTML),
+ success: data => $('.user-calendar-activities').html(data),
});
} else {
this.currentSelectedDate = '';
- return $('.user-calendar-activities').html('');
+ $('.user-calendar-activities').html('');
}
}
-
- initTooltips() {
- return $('.js-contrib-calendar .js-tooltip').tooltip({
- html: true
- });
- }
}
diff --git a/app/assets/javascripts/users/index.js b/app/assets/javascripts/users/index.js
index ecd8e09161e..33a83f8dae5 100644
--- a/app/assets/javascripts/users/index.js
+++ b/app/assets/javascripts/users/index.js
@@ -1,7 +1,19 @@
-import ActivityCalendar from './activity_calendar';
-import User from './user';
+import Cookies from 'js-cookie';
+import UserTabs from './user_tabs';
-// use legacy exports until embedded javascript is refactored
-window.Calendar = ActivityCalendar;
-window.gl = window.gl || {};
-window.gl.User = User;
+export default function initUserProfile(action) {
+ // place profile avatars to top
+ $('.profile-groups-avatars').tooltip({
+ placement: 'top',
+ });
+
+ // eslint-disable-next-line no-new
+ new UserTabs({ parentEl: '.user-profile', action });
+
+ // hide project limit message
+ $('.hide-project-limit-message').on('click', (e) => {
+ e.preventDefault();
+ Cookies.set('hide_project_limit_message', 'false');
+ $(this).parents('.project-limit-message').remove();
+ });
+}
diff --git a/app/assets/javascripts/users/user.js b/app/assets/javascripts/users/user.js
deleted file mode 100644
index 0b0a3e1afb4..00000000000
--- a/app/assets/javascripts/users/user.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/* eslint-disable class-methods-use-this */
-
-import Cookies from 'js-cookie';
-import UserTabs from './user_tabs';
-
-export default class User {
- constructor({ action }) {
- this.action = action;
- this.placeProfileAvatarsToTop();
- this.initTabs();
- this.hideProjectLimitMessage();
- }
-
- placeProfileAvatarsToTop() {
- $('.profile-groups-avatars').tooltip({
- placement: 'top',
- });
- }
-
- initTabs() {
- return new UserTabs({
- parentEl: '.user-profile',
- action: this.action,
- });
- }
-
- hideProjectLimitMessage() {
- $('.hide-project-limit-message').on('click', (e) => {
- e.preventDefault();
- Cookies.set('hide_project_limit_message', 'false');
- $(this).parents('.project-limit-message').remove();
- });
- }
-}
diff --git a/app/assets/javascripts/users/user_tabs.js b/app/assets/javascripts/users/user_tabs.js
index f8e23c8624d..5fe6603ce7b 100644
--- a/app/assets/javascripts/users/user_tabs.js
+++ b/app/assets/javascripts/users/user_tabs.js
@@ -1,72 +1,76 @@
-/* eslint-disable max-len, space-before-function-paren, no-underscore-dangle, consistent-return, comma-dangle, no-unused-vars, dot-notation, no-new, no-return-assign, camelcase, no-param-reassign, class-methods-use-this */
-
-/*
-UserTabs
-
-Handles persisting and restoring the current tab selection and lazily-loading
-content on the Users#show page.
-
-### Example Markup
-
- <ul class="nav-links">
- <li class="activity-tab active">
- <a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username">
- Activity
- </a>
- </li>
- <li class="groups-tab">
- <a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups">
- Groups
- </a>
- </li>
- <li class="contributed-tab">
- <a data-action="contributed" data-target="#contributed" data-toggle="tab" href="/u/username/contributed">
- Contributed projects
- </a>
- </li>
- <li class="projects-tab">
- <a data-action="projects" data-target="#projects" data-toggle="tab" href="/u/username/projects">
- Personal projects
- </a>
- </li>
- <li class="snippets-tab">
- <a data-action="snippets" data-target="#snippets" data-toggle="tab" href="/u/username/snippets">
- </a>
- </li>
- </ul>
-
- <div class="tab-content">
- <div class="tab-pane" id="activity">
- Activity Content
- </div>
- <div class="tab-pane" id="groups">
- Groups Content
- </div>
- <div class="tab-pane" id="contributed">
- Contributed projects content
- </div>
- <div class="tab-pane" id="projects">
- Projects content
- </div>
- <div class="tab-pane" id="snippets">
- Snippets content
- </div>
+import ActivityCalendar from './activity_calendar';
+
+/**
+ * UserTabs
+ *
+ * Handles persisting and restoring the current tab selection and lazily-loading
+ * content on the Users#show page.
+ *
+ * ### Example Markup
+ *
+ * <ul class="nav-links">
+ * <li class="activity-tab active">
+ * <a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username">
+ * Activity
+ * </a>
+ * </li>
+ * <li class="groups-tab">
+ * <a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups">
+ * Groups
+ * </a>
+ * </li>
+ * <li class="contributed-tab">
+ * ...
+ * </li>
+ * <li class="projects-tab">
+ * ...
+ * </li>
+ * <li class="snippets-tab">
+ * ...
+ * </li>
+ * </ul>
+ *
+ * <div class="tab-content">
+ * <div class="tab-pane" id="activity">
+ * Activity Content
+ * </div>
+ * <div class="tab-pane" id="groups">
+ * Groups Content
+ * </div>
+ * <div class="tab-pane" id="contributed">
+ * Contributed projects content
+ * </div>
+ * <div class="tab-pane" id="projects">
+ * Projects content
+ * </div>
+ * <div class="tab-pane" id="snippets">
+ * Snippets content
+ * </div>
+ * </div>
+ *
+ * <div class="loading-status">
+ * <div class="loading">
+ * Loading Animation
+ * </div>
+ * </div>
+ */
+
+const CALENDAR_TEMPLATE = `
+ <div class="clearfix calendar">
+ <div class="js-contrib-calendar"></div>
+ <div class="calendar-hint">
+ Summary of issues, merge requests, push events, and comments
+ </div>
</div>
-
- <div class="loading-status">
- <div class="loading">
- Loading Animation
- </div>
- </div>
-*/
+`;
export default class UserTabs {
- constructor ({ defaultAction, action, parentEl }) {
+ constructor({ defaultAction, action, parentEl }) {
this.loaded = {};
this.defaultAction = defaultAction || 'activity';
this.action = action || this.defaultAction;
this.$parentEl = $(parentEl) || $(document);
- this._location = window.location;
+ this.windowLocation = window.location;
this.$parentEl.find('.nav-links a')
.each((i, navLink) => {
this.loaded[$(navLink).attr('data-action')] = false;
@@ -82,12 +86,10 @@ export default class UserTabs {
}
bindEvents() {
- this.changeProjectsPageWrapper = this.changeProjectsPage.bind(this);
-
- this.$parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]')
- .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event));
-
- this.$parentEl.on('click', '.gl-pagination a', this.changeProjectsPageWrapper);
+ this.$parentEl
+ .off('shown.bs.tab', '.nav-links a[data-toggle="tab"]')
+ .on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', event => this.tabShown(event))
+ .on('click', '.gl-pagination a', event => this.changeProjectsPage(event));
}
changeProjectsPage(e) {
@@ -122,7 +124,7 @@ export default class UserTabs {
const loadableActions = ['groups', 'contributed', 'projects', 'snippets'];
if (loadableActions.indexOf(action) > -1) {
- return this.loadTab(action, endpoint);
+ this.loadTab(action, endpoint);
}
}
@@ -131,25 +133,38 @@ export default class UserTabs {
beforeSend: () => this.toggleLoading(true),
complete: () => this.toggleLoading(false),
dataType: 'json',
- type: 'GET',
url: endpoint,
success: (data) => {
const tabSelector = `div#${action}`;
this.$parentEl.find(tabSelector).html(data.html);
this.loaded[action] = true;
- return gl.utils.localTimeAgo($('.js-timeago', tabSelector));
- }
+ gl.utils.localTimeAgo($('.js-timeago', tabSelector));
+ },
});
}
loadActivities() {
- if (this.loaded['activity']) {
+ if (this.loaded.activity) {
return;
}
const $calendarWrap = this.$parentEl.find('.user-calendar');
- $calendarWrap.load($calendarWrap.data('href'));
+ const calendarPath = $calendarWrap.data('calendarPath');
+ const calendarActivitiesPath = $calendarWrap.data('calendarActivitiesPath');
+
+ $.ajax({
+ dataType: 'json',
+ url: calendarPath,
+ success: (activityData) => {
+ $calendarWrap.html(CALENDAR_TEMPLATE);
+
+ // eslint-disable-next-line no-new
+ new ActivityCalendar('.js-contrib-calendar', activityData, calendarActivitiesPath);
+ },
+ });
+
+ // eslint-disable-next-line no-new
new gl.Activities();
- return this.loaded['activity'] = true;
+ this.loaded.activity = true;
}
toggleLoading(status) {
@@ -158,13 +173,13 @@ export default class UserTabs {
}
setCurrentAction(source) {
- let new_state = source;
- new_state = new_state.replace(/\/+$/, '');
- new_state += this._location.search + this._location.hash;
+ let newState = source;
+ newState = newState.replace(/\/+$/, '');
+ newState += this.windowLocation.search + this.windowLocation.hash;
history.replaceState({
- url: new_state
- }, document.title, new_state);
- return new_state;
+ url: newState,
+ }, document.title, newState);
+ return newState;
}
getCurrentAction() {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
index e8e22ad93a5..744a1cd24fa 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
@@ -108,7 +108,8 @@ export default {
</div>
<mr-widget-memory-usage
v-if="deployment.metrics_url"
- :metricsUrl="deployment.metrics_url"
+ :metrics-url="deployment.metrics_url"
+ :metrics-monitoring-url="deployment.metrics_monitoring_url"
/>
</div>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
index 76cb71b6c12..534e2a88eff 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js
@@ -7,7 +7,14 @@ import MRWidgetService from '../services/mr_widget_service';
export default {
name: 'MemoryUsage',
props: {
- metricsUrl: { type: String, required: true },
+ metricsUrl: {
+ type: String,
+ required: true,
+ },
+ metricsMonitoringUrl: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
@@ -124,7 +131,7 @@ export default {
<p
v-if="shouldShowMemoryGraph"
class="usage-info js-usage-info">
- Memory usage <b>{{memoryChangeType}}</b> from {{memoryFrom}}MB to {{memoryTo}}MB
+ <a :href="metricsMonitoringUrl">Memory</a> usage <b>{{memoryChangeType}}</b> from {{memoryFrom}}MB to {{memoryTo}}MB
</p>
<p
v-if="shouldShowLoadFailure"
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index 72a13108404..fddafb0ddfa 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -65,7 +65,7 @@ export default class MergeRequestStore {
this.mergeCheckPath = data.merge_check_path;
this.mergeActionsContentPath = data.commit_change_content_path;
this.isRemovingSourceBranch = this.isRemovingSourceBranch || false;
- this.isOpen = data.state === 'opened' || data.state === 'reopened' || false;
+ this.isOpen = data.state === 'opened';
this.hasMergeableDiscussionsState = data.mergeable_discussions_state === false;
this.canRemoveSourceBranch = currentUser.can_remove_source_branch || false;
this.canMerge = !!data.merge_path;