Commit 423943da authored by Jacob Schatz's avatar Jacob Schatz

Merge branch 'desplacturing' into 'master'

Refactor remnants of CoffeeScript destructured opts and super into ES6

## What does this MR do?

Gets rid of some CoffeeScript remnants that are difficult to work with, and updates them to use ES6 syntax/constructs. I updated nearby code to ES6, but these updates usually just included using the es6 class syntax and scoped variable keywords. 

**Important**: For the most part, I tried to avoid refactoring the design of the existing code. If I attempted that, this would've taken much much longer and it would've been out of this issue's scope. 

## Why was this MR needed?

The compiled coffeescript for destructured default params is very difficult to work in. 

These changes makes some of these rough patches more maintainable:

e.g.
![Screen_Shot_2016-09-19_at_4.43.08_PM](/uploads/40cd3f79b9c5f595f3738213b0dadbbf/Screen_Shot_2016-09-19_at_4.43.08_PM.png)

## Does this MR meet the acceptance criteria?

- [x] [CHANGELOG](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CHANGELOG) entry added
- [x] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/doc_styleguide.md)
- [x] API support added
- Tests
  - [x] Added for this feature/bug
  - [x] All builds are passing
- [x] Conform by the [merge request performance guides](http://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html)
- [x] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides)
- [x] Branch has no merge conflicts with `master` (if you do - rebase it please)
- [x] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)

## What are the relevant issue numbers?

Meta Issue: 
https://gitlab.com/gitlab-org/gitlab-ce/issues/21887

Child Issues: 

https://gitlab.com/gitlab-org/gitlab-ce/issues/21942

https://gitlab.com/gitlab-org/gitlab-ce/issues/21941

https://gitlab.com/gitlab-org/gitlab-ce/issues/21943

## To Test Manually: 

#### app/assets/javascripts/LabelManager.js.es6 -> [diff](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6261/diffs#diff-1)
- Navigate to [Labels](http://localhost:3000/gitlab-org/gitlab-test/labels) and click around (toggle label priority, add new labels, subcribe, edit, delete)

#### app/assets/javascripts/blob/blob_ci_yaml.js.es6  -> [diff](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6261/diffs#diff-3)
- [Create new file](http://localhost:3000/gitlab-org/gitlab-test/new/master) and give it the name `.gitlab-ci.yml`, then attempt to select a template

#### app/assets/javascripts/blob/blob_license_selectors.js.es6 -> [diff](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6261/diffs#diff-7)
- [Create new file](http://localhost:3000/gitlab-org/gitlab-test/new/master) and give it the name `license`, then attempt to select a template

#### app/assets/javascripts/blob/template_selector.js.es6 -> [diff](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6261/diffs#diff-8)
- This class is extended by the two previously mentioned files. As long as they work, this should work.

#### app/assets/javascripts/issues-bulk-assignment.js.es6 -> [diff](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6261/diffs#diff-11)
- [Visit issues](http://localhost:3000/gitlab-org/gitlab-test/issues), assign multiple issues a new label or milestone and update.

#### app/assets/javascripts/profile/gl_crop.js.es6 -> [diff](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6261/diffs#diff-12)
- [Visit your profile settings](http://localhost:3000/profile), upload a photo and attempt to crop and set as new profile picture.

#### app/assets/javascripts/profile/profile.js.es6 > [diff](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6261/diffs#diff-14)
- [Visit your profile settings](http://localhost:3000/profile), change various fields (Name, Bio, Email, Notifs settings) and submit form

#### app/assets/javascripts/todos.js.es6 -> [diff](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6261/diffs#diff-18)
- [Visit an issue](http://localhost:3000/documentcloud/underscore/issues/6) and Add Todo, then visit [Todos](http://localhost:3000/dashboard/todos). Try out the sorting features, making sure everything looks right. Mark the Todo done. 

#### app/assets/javascripts/user.js.es6 -> [diff](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6261/diffs#diff-19)
- [Visit user profile](http://localhost:3000/u/root) and click between tabs. 

#### app/assets/javascripts/user_tabs.js.es6 -> [diff](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6261/diffs#diff-20)
- Same as previous.

cc: @jschatz1

See merge request !6261
parents 219413c5 2d41c3f5
...@@ -80,6 +80,7 @@ v 8.12.2 ...@@ -80,6 +80,7 @@ v 8.12.2
- Only update issuable labels if they have been changed - Only update issuable labels if they have been changed
- Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv) - Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv)
- Fix resolve discussion buttons endpoint path - Fix resolve discussion buttons endpoint path
- Refactor remnants of CoffeeScript destructured opts and super !6261
v 8.12.1 v 8.12.1
- Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST
......
(function() { ((global) => {
this.LabelManager = (function() {
LabelManager.prototype.errorMessage = 'Unable to update label prioritization at this time';
function LabelManager(opts) { class LabelManager {
// Defaults constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) {
var ref, ref1, ref2; this.togglePriorityButton = togglePriorityButton || $('.js-toggle-priority');
if (opts == null) { this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels');
opts = {}; this.otherLabels = otherLabels || $('.js-other-labels');
} this.errorMessage = 'Unable to update label prioritization at this time';
this.togglePriorityButton = (ref = opts.togglePriorityButton) != null ? ref : $('.js-toggle-priority'), this.prioritizedLabels = (ref1 = opts.prioritizedLabels) != null ? ref1 : $('.js-prioritized-labels'), this.otherLabels = (ref2 = opts.otherLabels) != null ? ref2 : $('.js-other-labels');
this.prioritizedLabels.sortable({ this.prioritizedLabels.sortable({
items: 'li', items: 'li',
placeholder: 'list-placeholder', placeholder: 'list-placeholder',
...@@ -18,33 +15,30 @@ ...@@ -18,33 +15,30 @@
this.bindEvents(); this.bindEvents();
} }
LabelManager.prototype.bindEvents = function() { bindEvents() {
return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick); return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick);
}; }
LabelManager.prototype.onTogglePriorityClick = function(e) { onTogglePriorityClick(e) {
var $btn, $label, $tooltip, _this, action;
e.preventDefault(); e.preventDefault();
_this = e.data; const _this = e.data;
$btn = $(e.currentTarget); const $btn = $(e.currentTarget);
$label = $("#" + ($btn.data('domId'))); const $label = $(`#${$btn.data('domId')}`);
action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add'; const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add';
// Make sure tooltip will hide const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`);
$tooltip = $("#" + ($btn.find('.has-tooltip:visible').attr('aria-describedby')));
$tooltip.tooltip('destroy'); $tooltip.tooltip('destroy');
return _this.toggleLabelPriority($label, action); return _this.toggleLabelPriority($label, action);
}; }
LabelManager.prototype.toggleLabelPriority = function($label, action, persistState) { toggleLabelPriority($label, action, persistState) {
var $from, $target, _this, url, xhr;
if (persistState == null) { if (persistState == null) {
persistState = true; persistState = true;
} }
_this = this; let xhr;
url = $label.find('.js-toggle-priority').data('url'); const _this = this;
$target = this.prioritizedLabels; const url = $label.find('.js-toggle-priority').data('url');
$from = this.otherLabels; let $target = this.prioritizedLabels;
// Optimistic update let $from = this.otherLabels;
if (action === 'remove') { if (action === 'remove') {
$target = this.otherLabels; $target = this.otherLabels;
$from = this.prioritizedLabels; $from = this.prioritizedLabels;
...@@ -62,7 +56,7 @@ ...@@ -62,7 +56,7 @@
} }
if (action === 'remove') { if (action === 'remove') {
xhr = $.ajax({ xhr = $.ajax({
url: url, url,
type: 'DELETE' type: 'DELETE'
}); });
// Restore empty message // Restore empty message
...@@ -73,43 +67,40 @@ ...@@ -73,43 +67,40 @@
xhr = this.savePrioritySort($label, action); xhr = this.savePrioritySort($label, action);
} }
return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action)); return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action));
}; }
LabelManager.prototype.onPrioritySortUpdate = function() { onPrioritySortUpdate() {
var xhr; const xhr = this.savePrioritySort();
xhr = this.savePrioritySort();
return xhr.fail(function() { return xhr.fail(function() {
return new Flash(this.errorMessage, 'alert'); return new Flash(this.errorMessage, 'alert');
}); });
}; }
LabelManager.prototype.savePrioritySort = function() { savePrioritySort() {
return $.post({ return $.post({
url: this.prioritizedLabels.data('url'), url: this.prioritizedLabels.data('url'),
data: { data: {
label_ids: this.getSortedLabelsIds() label_ids: this.getSortedLabelsIds()
} }
}); });
}; }
LabelManager.prototype.rollbackLabelPosition = function($label, originalAction) { rollbackLabelPosition($label, originalAction) {
var action; const action = originalAction === 'remove' ? 'add' : 'remove';
action = originalAction === 'remove' ? 'add' : 'remove';
this.toggleLabelPriority($label, action, false); this.toggleLabelPriority($label, action, false);
return new Flash(this.errorMessage, 'alert'); return new Flash(this.errorMessage, 'alert');
}; }
LabelManager.prototype.getSortedLabelsIds = function() { getSortedLabelsIds() {
var sortedIds; const sortedIds = [];
sortedIds = [];
this.prioritizedLabels.find('li').each(function() { this.prioritizedLabels.find('li').each(function() {
return sortedIds.push($(this).data('id')); sortedIds.push($(this).data('id'));
}); });
return sortedIds; return sortedIds;
}; }
}
return LabelManager; gl.LabelManager = LabelManager;
})(); })(window.gl || (window.gl = {}));
}).call(this);
/*= require blob/template_selector */
(function() {
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
this.BlobCiYamlSelector = (function(superClass) {
extend(BlobCiYamlSelector, superClass);
function BlobCiYamlSelector() {
return BlobCiYamlSelector.__super__.constructor.apply(this, arguments);
}
BlobCiYamlSelector.prototype.requestFile = function(query) {
return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this));
};
return BlobCiYamlSelector;
})(TemplateSelector);
this.BlobCiYamlSelectors = (function() {
function BlobCiYamlSelectors(opts) {
var ref;
this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-gitlab-ci-yml-selector'), this.editor = opts.editor;
this.$dropdowns.each((function(_this) {
return function(i, dropdown) {
var $dropdown;
$dropdown = $(dropdown);
return new BlobCiYamlSelector({
pattern: /(.gitlab-ci.yml)/,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
dropdown: $dropdown,
editor: _this.editor
});
};
})(this));
}
return BlobCiYamlSelectors;
})();
}).call(this);
/*= require blob/template_selector */
((global) => {
class BlobCiYamlSelector extends gl.TemplateSelector {
requestFile(query) {
return Api.gitlabCiYml(query.name, this.requestFileSuccess.bind(this));
}
requestFileSuccess(file) {
return super.requestFileSuccess(file);
}
}
global.BlobCiYamlSelector = BlobCiYamlSelector;
class BlobCiYamlSelectors {
constructor({ editor, $dropdowns } = {}) {
this.editor = editor;
this.$dropdowns = $dropdowns || $('.js-gitlab-ci-yml-selector');
this.initSelectors();
}
initSelectors() {
const editor = this.editor;
this.$dropdowns.each((i, dropdown) => {
const $dropdown = $(dropdown);
return new BlobCiYamlSelector({
editor,
pattern: /(.gitlab-ci.yml)/,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
dropdown: $dropdown
});
});
}
}
global.BlobCiYamlSelectors = BlobCiYamlSelectors;
})(window.gl || (window.gl = {}));
...@@ -18,6 +18,6 @@ ...@@ -18,6 +18,6 @@
return BlobGitignoreSelector; return BlobGitignoreSelector;
})(TemplateSelector); })(gl.TemplateSelector);
}).call(this); }).call(this);
...@@ -23,6 +23,6 @@ ...@@ -23,6 +23,6 @@
return BlobLicenseSelector; return BlobLicenseSelector;
})(TemplateSelector); })(gl.TemplateSelector);
}).call(this); }).call(this);
(function() {
this.BlobLicenseSelectors = (function() {
function BlobLicenseSelectors(opts) {
var ref;
this.$dropdowns = (ref = opts.$dropdowns) != null ? ref : $('.js-license-selector'), this.editor = opts.editor;
this.$dropdowns.each((function(_this) {
return function(i, dropdown) {
var $dropdown;
$dropdown = $(dropdown);
return new BlobLicenseSelector({
pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-license-selector-wrap'),
dropdown: $dropdown,
editor: _this.editor
});
};
})(this));
}
return BlobLicenseSelectors;
})();
}).call(this);
((global) => {
class BlobLicenseSelectors {
constructor({ $dropdowns, editor }) {
this.$dropdowns = $('.js-license-selector');
this.editor = editor;
this.$dropdowns.each((i, dropdown) => {
const $dropdown = $(dropdown);
return new BlobLicenseSelector({
editor,
pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-license-selector-wrap'),
dropdown: $dropdown,
});
});
}
}
global.BlobLicenseSelectors = BlobLicenseSelectors;
})(window.gl || (window.gl = {}));
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
this.TemplateSelector = (function() {
function TemplateSelector(opts) {
var ref;
if (opts == null) {
opts = {};
}
this.onClick = bind(this.onClick, this);
this.dropdown = opts.dropdown, this.data = opts.data, this.pattern = opts.pattern, this.wrapper = opts.wrapper, this.editor = opts.editor, this.fileEndpoint = opts.fileEndpoint, this.$input = (ref = opts.$input) != null ? ref : $('#file_name');
this.dropdownIcon = $('.fa-chevron-down', this.dropdown);
this.buildDropdown();
this.bindEvents();
this.onFilenameUpdate();
this.autosizeUpdateEvent = document.createEvent('Event');
this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
}
TemplateSelector.prototype.buildDropdown = function() {
return this.dropdown.glDropdown({
data: this.data,
filterable: true,
selectable: true,
toggleLabel: this.toggleLabel,
search: {
fields: ['name']
},
clicked: this.onClick,
text: function(item) {
return item.name;
}
});
};
TemplateSelector.prototype.bindEvents = function() {
return this.$input.on('keyup blur', (function(_this) {
return function(e) {
return _this.onFilenameUpdate();
};
})(this));
};
TemplateSelector.prototype.toggleLabel = function(item) {
return item.name;
};
TemplateSelector.prototype.onFilenameUpdate = function() {
var filenameMatches;
if (!this.$input.length) {
return;
}
filenameMatches = this.pattern.test(this.$input.val().trim());
if (!filenameMatches) {
this.wrapper.addClass('hidden');
return;
}
return this.wrapper.removeClass('hidden');
};
TemplateSelector.prototype.onClick = function(item, el, e) {
e.preventDefault();
return this.requestFile(item);
};
TemplateSelector.prototype.requestFile = function(item) {
// This `requestFile` method is an abstract method that should
// be added by all subclasses.
};
// To be implemented on the extending class
// e.g.
// Api.gitignoreText item.name, @requestFileSuccess.bind(@)
TemplateSelector.prototype.requestFileSuccess = function(file, opts) {
var oldValue = this.editor.getValue();
var newValue = file.content;
if (opts == null) {
opts = {};
}
if (opts.append && oldValue.length && oldValue !== newValue) {
newValue = oldValue + '\n\n' + newValue;
}
this.editor.setValue(newValue, 1);
if (!opts.skipFocus) this.editor.focus();
if (this.editor instanceof jQuery) {
this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
}
};
TemplateSelector.prototype.startLoadingSpinner = function() {
this.dropdownIcon
.addClass('fa-spinner fa-spin')
.removeClass('fa-chevron-down');
};
TemplateSelector.prototype.stopLoadingSpinner = function() {
this.dropdownIcon
.addClass('fa-chevron-down')
.removeClass('fa-spinner fa-spin');
};
return TemplateSelector;
})();
}).call(this);
((global) => {
class TemplateSelector {
constructor({ dropdown, data, pattern, wrapper, editor, fileEndpoint, $input } = {}) {
this.onClick = this.onClick.bind(this);
this.dropdown = dropdown;
this.data = data;
this.pattern = pattern;
this.wrapper = wrapper;
this.editor = editor;
this.fileEndpoint = fileEndpoint;
this.$input = $input || $('#file_name');
this.dropdownIcon = $('.fa-chevron-down', this.dropdown);
this.buildDropdown();
this.bindEvents();
this.onFilenameUpdate();
this.autosizeUpdateEvent = document.createEvent('Event');
this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
}
buildDropdown() {
return this.dropdown.glDropdown({
data: this.data,
filterable: true,
selectable: true,
toggleLabel: this.toggleLabel,
search: {
fields: ['name']
},
clicked: this.onClick,
text: function(item) {
return item.name;
}
});
}
bindEvents() {
return this.$input.on('keyup blur', (e) => this.onFilenameUpdate());
}
toggleLabel(item) {
return item.name;
}
onFilenameUpdate() {
var filenameMatches;
if (!this.$input.length) {
return;
}
filenameMatches = this.pattern.test(this.$input.val().trim());
if (!filenameMatches) {
this.wrapper.addClass('hidden');
return;
}
return this.wrapper.removeClass('hidden');
}
onClick(item, el, e) {
e.preventDefault();
return this.requestFile(item);
}
requestFile(item) {
// This `requestFile` method is an abstract method that should
// be added by all subclasses.
}
// To be implemented on the extending class
// e.g.
// Api.gitignoreText item.name, @requestFileSuccess.bind(@)
requestFileSuccess(file, { skipFocus, append } = {}) {
const oldValue = this.editor.getValue();
let newValue = file.content;
if (append && oldValue.length && oldValue !== newValue) {
newValue = oldValue + '\n\n' + newValue;
}
this.editor.setValue(newValue, 1);
if (!skipFocus) this.editor.focus();
if (this.editor instanceof jQuery) {
this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
}
}
startLoadingSpinner() {
this.dropdownIcon
.addClass('fa-spinner fa-spin')
.removeClass('fa-chevron-down');
}
stopLoadingSpinner() {
this.dropdownIcon
.addClass('fa-chevron-down')
.removeClass('fa-spinner fa-spin');
}
}
global.TemplateSelector = TemplateSelector;
})(window.gl || ( window.gl = {}));
...@@ -23,13 +23,13 @@ ...@@ -23,13 +23,13 @@
})(this)); })(this));
this.initModePanesAndLinks(); this.initModePanesAndLinks();
this.initSoftWrap(); this.initSoftWrap();
new BlobLicenseSelectors({ new gl.BlobLicenseSelectors({
editor: this.editor editor: this.editor
}); });
new BlobGitignoreSelectors({ new BlobGitignoreSelectors({
editor: this.editor editor: this.editor
}); });
new BlobCiYamlSelectors({ new gl.BlobCiYamlSelectors({
editor: this.editor editor: this.editor
}); });
} }
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
case 'projects:merge_requests:index': case 'projects:merge_requests:index':
case 'projects:issues:index': case 'projects:issues:index':
Issuable.init(); Issuable.init();
new IssuableBulkActions(); new gl.IssuableBulkActions();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
break; break;
case 'projects:issues:show': case 'projects:issues:show':
...@@ -40,7 +40,7 @@ ...@@ -40,7 +40,7 @@
new Milestone(); new Milestone();
break; break;
case 'dashboard:todos:index': case 'dashboard:todos:index':
new Todos(); new gl.Todos();
break; break;
case 'projects:milestones:new': case 'projects:milestones:new':
case 'projects:milestones:edit': case 'projects:milestones:edit':
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new GLForm($('.issue-form')); new GLForm($('.issue-form'));
new IssuableForm($('.issue-form')); new IssuableForm($('.issue-form'));
new IssuableTemplateSelectors(); new gl.IssuableTemplateSelectors();
break; break;
case 'projects:merge_requests:new': case 'projects:merge_requests:new':
case 'projects:merge_requests:edit': case 'projects:merge_requests:edit':
...@@ -67,7 +67,7 @@ ...@@ -67,7 +67,7 @@
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new GLForm($('.merge-request-form')); new GLForm($('.merge-request-form'));
new IssuableForm($('.merge-request-form')); new IssuableForm($('.merge-request-form'));
new IssuableTemplateSelectors(); new gl.IssuableTemplateSelectors();
break; break;
case 'projects:tags:new': case 'projects:tags:new':
new ZenMode(); new ZenMode();
...@@ -165,7 +165,7 @@ ...@@ -165,7 +165,7 @@
break; break;
case 'projects:labels:index': case 'projects:labels:index':
if ($('.prioritized-labels').length) { if ($('.prioritized-labels').length) {
new LabelManager(); new gl.LabelManager();
} }
break; break;
case 'projects:network:show': case 'projects:network:show':
...@@ -279,7 +279,7 @@ ...@@ -279,7 +279,7 @@
Dispatcher.prototype.initSearch = function() { Dispatcher.prototype.initSearch = function() {
// Only when search form is present // Only when search form is present
if ($('.search').length) { if ($('.search').length) {
return new SearchAutocomplete(); return new gl.SearchAutocomplete();
} }
}; };
......
(function() { ((global) => {
this.IssuableBulkActions = (function() {
function IssuableBulkActions(opts) { class IssuableBulkActions {
// Set defaults constructor({ container, form, issues } = {}) {
var ref, ref1, ref2; this.container = container || $('.content'),
if (opts == null) { this.form = form || this.getElement('.bulk-update');
opts = {}; this.issues = issues || this.getElement('.issues-list .issue');
}
this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issuable-list > li');
// Save instance
this.form.data('bulkActions', this); this.form.data('bulkActions', this);
this.willUpdateLabels = false; this.willUpdateLabels = false;
this.bindEvents(); this.bindEvents();
...@@ -15,53 +12,46 @@ ...@@ -15,53 +12,46 @@
Issuable.initChecks(); Issuable.initChecks();
} }
IssuableBulkActions.prototype.getElement = function(selector) { getElement(selector) {
return this.container.find(selector); return this.container.find(selector);
}; }
IssuableBulkActions.prototype.bindEvents = function() { bindEvents() {
return this.form.off('submit').on('submit', this.onFormSubmit.bind(this)); return this.form.off('submit').on('submit', this.onFormSubmit.bind(this));
}; }
IssuableBulkActions.prototype.onFormSubmit = function(e) { onFormSubmit(e) {
e.preventDefault(); e.preventDefault();
return this.submit(); return this.submit();
}; }
IssuableBulkActions.prototype.submit = function() { submit() {
var _this, xhr; const _this = this;
_this = this; const xhr = $.ajax({
xhr = $.ajax({
url: this.form.attr('action'), url: this.form.attr('action'),
method: this.form.attr('method'), method: this.form.attr('method'),
dataType: 'JSON', dataType: 'JSON',
data: this.getFormDataAsObject() data: this.getFormDataAsObject()
}); });
xhr.done(function(response, status, xhr) { xhr.done(() => window.location.reload());
return location.reload(); xhr.fail(() => new Flash("Issue update failed"));
});
xhr.fail(function() {
return new Flash("Issue update failed");
});
return xhr.always(this.onFormSubmitAlways.bind(this)); return xhr.always(this.onFormSubmitAlways.bind(this));
}; }
IssuableBulkActions.prototype.onFormSubmitAlways = function() { onFormSubmitAlways() {
return this.form.find('[type="submit"]').enable(); return this.form.find('[type="submit"]').enable();
}; }
IssuableBulkActions.prototype.getSelectedIssues = function() { getSelectedIssues() {
return this.issues.has('.selected_issue:checked'); return this.issues.has('.selected_issue:checked');
}; }
IssuableBulkActions.prototype.getLabelsFromSelection = function() { getLabelsFromSelection() {
var labels; const labels = [];
labels = [];
this.getSelectedIssues().map(function() { this.getSelectedIssues().map(function() {
var _labels; const labelsData = $(this).data('labels');
_labels = $(this).data('labels'); if (labelsData) {
if (_labels) { return labelsData.map(function(labelId) {
return _labels.map(function(labelId) {
if (labels.indexOf(labelId) === -1) { if (labels.indexOf(labelId) === -1) {
return labels.push(labelId); return labels.push(labelId);
} }
...@@ -69,7 +59,7 @@ ...@@ -69,7 +59,7 @@
} }
}); });
return labels; return labels;
}; }
/** /**
...@@ -77,25 +67,21 @@ ...@@ -77,25 +67,21 @@
* @return {Array} Label IDs * @return {Array} Label IDs
*/ */
IssuableBulkActions.prototype.getUnmarkedIndeterminedLabels = function() { getUnmarkedIndeterminedLabels() {
var el, i, id, j, labelsToKeep, len, len1, ref, ref1, result; const result = [];
result = []; const labelsToKeep = [];
labelsToKeep = [];
ref = this.getElement('.labels-filter .is-indeterminate'); this.getElement('.labels-filter .is-indeterminate')
for (i = 0, len = ref.length; i < len; i++) { .each((i, el) => labelsToKeep.push($(el).data('labelId')));
el = ref[i];
labelsToKeep.push($(el).data('labelId')); this.getLabelsFromSelection().forEach((id) => {
}
ref1 = this.getLabelsFromSelection();
for (j = 0, len1 = ref1.length; j < len1; j++) {
id = ref1[j];
// Only the ones that we are not going to keep
if (labelsToKeep.indexOf(id) === -1) { if (labelsToKeep.indexOf(id) === -1) {
result.push(id); result.push(id);
} }
} });
return result; return result;
}; }
/** /**
...@@ -103,9 +89,8 @@ ...@@ -103,9 +89,8 @@
* Returns key/value pairs from form data * Returns key/value pairs from form data
*/ */
IssuableBulkActions.prototype.getFormDataAsObject = function() { getFormDataAsObject() {
var formData; const formData = {
formData = {
update: { update: {
state_event: this.form.find('input[name="update[state_event]"]').val(), state_event: this.form.find('input[name="update[state_event]"]').val(),
assignee_id: this.form.find('input[name="update[assignee_id]"]').val(), assignee_id: this.form.find('input[name="update[assignee_id]"]').val(),
...@@ -125,19 +110,18 @@ ...@@ -125,19 +110,18 @@
}); });
} }
return formData; return formData;
}; }
IssuableBulkActions.prototype.getLabelsToApply = function() { getLabelsToApply() {
var $labels, labelIds; const labelIds = [];
labelIds = []; const $labels = this.form.find('.labels-filter input[name="update[label_ids][]"]');
$labels = this.form.find('.labels-filter input[name="update[label_ids][]"]');
$labels.each(function(k, label) { $labels.each(function(k, label) {
if (label) { if (label) {
return labelIds.push(parseInt($(label).val())); return labelIds.push(parseInt($(label).val()));
} }
}); });
return labelIds; return labelIds;
}; }
/** /**
...@@ -145,11 +129,10 @@ ...@@ -145,11 +129,10 @@
* @return {Array} Array of labels IDs * @return {Array} Array of labels IDs
*/ */
IssuableBulkActions.prototype.getLabelsToRemove = function() { getLabelsToRemove() {
var indeterminatedLabels, labelsToApply, result; const result = [];
result = []; const indeterminatedLabels = this.getUnmarkedIndeterminedLabels();
indeterminatedLabels = this.getUnmarkedIndeterminedLabels(); const labelsToApply = this.getLabelsToApply();
labelsToApply = this.getLabelsToApply();
indeterminatedLabels.map(function(id) { indeterminatedLabels.map(function(id) {
// We need to exclude label IDs that will be applied // We need to exclude label IDs that will be applied
// By not doing this will cause issues from selection to not add labels at all // By not doing this will cause issues from selection to not add labels at all
...@@ -158,10 +141,9 @@ ...@@ -158,10 +141,9 @@
} }
}); });
return result; return result;
}; }
}
return IssuableBulkActions;
})(); global.IssuableBulkActions = IssuableBulkActions;
}).call(this); })(window.gl || (window.gl = {}));
(function() { ((global) => {
var GitLabCrop,
bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
GitLabCrop = (function() {
var FILENAMEREGEX;
// Matches everything but the file name // Matches everything but the file name
FILENAMEREGEX = /^.*[\\\/]/; const FILENAMEREGEX = /^.*[\\\/]/;
function GitLabCrop(input, opts) { class GitLabCrop {
var ref, ref1, ref2, ref3, ref4; constructor(input, { filename, previewImage, modalCrop, pickImageEl, uploadImageBtn, modalCropImg,
if (opts == null) { exportWidth = 200, exportHeight = 200, cropBoxWidth = 200, cropBoxHeight = 200 } = {}) {
opts = {};
} this.onUploadImageBtnClick = this.onUploadImageBtnClick.bind(this);
this.onUploadImageBtnClick = bind(this.onUploadImageBtnClick, this); this.onModalHide = this.onModalHide.bind(this);
this.onModalHide = bind(this.onModalHide, this); this.onModalShow = this.onModalShow.bind(this);
this.onModalShow = bind(this.onModalShow, this); this.onPickImageClick = this.onPickImageClick.bind(this);
this.onPickImageClick = bind(this.onPickImageClick, this);
this.fileInput = $(input); this.fileInput = $(input);
// We should rename to avoid spec to fail
// Form will submit the proper input filed with a file using FormData
this.fileInput.attr('name', (this.fileInput.attr('name')) + "-trigger").attr('id', (this.fileInput.attr('id')) + "-trigger");
// Set defaults
this.exportWidth = (ref = opts.exportWidth) != null ? ref : 200, this.exportHeight = (ref1 = opts.exportHeight) != null ? ref1 : 200, this.cropBoxWidth = (ref2 = opts.cropBoxWidth) != null ? ref2 : 200, this.cropBoxHeight = (ref3 = opts.cropBoxHeight) != null ? ref3 : 200, this.form = (ref4 = opts.form) != null ? ref4 : this.fileInput.parents('form'), this.filename = opts.filename, this.previewImage = opts.previewImage, this.modalCrop = opts.modalCrop, this.pickImageEl = opts.pickImageEl, this.uploadImageBtn = opts.uploadImageBtn, this.modalCropImg = opts.modalCropImg;
// Required params
// Ensure needed elements are jquery objects
// If selector is provided we will convert them to a jQuery Object
this.filename = this.getElement(this.filename);
this.previewImage = this.getElement(this.previewImage);
this.pickImageEl = this.getElement(this.pickImageEl);
// Modal elements usually are outside the @form element
this.modalCrop = _.isString(this.modalCrop) ? $(this.modalCrop) : this.modalCrop;
this.uploadImageBtn = _.isString(this.uploadImageBtn) ? $(this.uploadImageBtn) : this.uploadImageBtn;
this.modalCropImg = _.isString(this.modalCropImg) ? $(this.modalCropImg) : this.modalCropImg; this.modalCropImg = _.isString(this.modalCropImg) ? $(this.modalCropImg) : this.modalCropImg;
this.fileInput.attr('name', `${this.fileInput.attr('name')}-trigger`).attr('id', `this.fileInput.attr('id')-trigger`);
this.exportWidth = exportWidth;
this.exportHeight = exportHeight;
this.cropBoxWidth = cropBoxWidth;
this.cropBoxHeight = cropBoxHeight;
this.form = this.fileInput.parents('form');
this.filename = filename;
this.previewImage = previewImage;
this.modalCrop = modalCrop;
this.pickImageEl = pickImageEl;
this.uploadImageBtn = uploadImageBtn;
this.modalCropImg = modalCropImg;
this.filename = this.getElement(filename);
this.previewImage = this.getElement(previewImage);
this.pickImageEl = this.getElement(pickImageEl);
this.modalCrop = _.isString(modalCrop) ? $(modalCrop) : modalCrop;
this.uploadImageBtn = _.isString(uploadImageBtn) ? $(uploadImageBtn) : uploadImageBtn;
this.modalCropImg = _.isString(modalCropImg) ? $(modalCropImg) : modalCropImg;
this.cropActionsBtn = this.modalCrop.find('[data-method]'); this.cropActionsBtn = this.modalCrop.find('[data-method]');
this.bindEvents(); this.bindEvents();
} }
GitLabCrop.prototype.getElement = function(selector) { getElement(selector) {
return $(selector, this.form); return $(selector, this.form);
}; }
GitLabCrop.prototype.bindEvents = function() { bindEvents() {
var _this; var _this;
_this = this; _this = this;
this.fileInput.on('change', function(e) { this.fileInput.on('change', function(e) {
...@@ -57,13 +55,13 @@ ...@@ -57,13 +55,13 @@
return _this.onActionBtnClick(btn); return _this.onActionBtnClick(btn);
}); });
return this.croppedImageBlob = null; return this.croppedImageBlob = null;
}; }
GitLabCrop.prototype.onPickImageClick = function() { onPickImageClick() {
return this.fileInput.trigger('click'); return this.fileInput.trigger('click');
}; }
GitLabCrop.prototype.onModalShow = function() { onModalShow() {
var _this; var _this;
_this = this; _this = this;
return this.modalCropImg.cropper({ return this.modalCropImg.cropper({
...@@ -95,44 +93,44 @@ ...@@ -95,44 +93,44 @@
}); });
} }
}); });
}; }
GitLabCrop.prototype.onModalHide = function() { onModalHide() {
return this.modalCropImg.attr('src', '').cropper('destroy'); return this.modalCropImg.attr('src', '').cropper('destroy');
}; }
GitLabCrop.prototype.onUploadImageBtnClick = function(e) { // Remove attached image onUploadImageBtnClick(e) {
e.preventDefault(); // Destroy cropper instance e.preventDefault();
this.setBlob(); this.setBlob();
this.setPreview(); this.setPreview();
this.modalCrop.modal('hide'); this.modalCrop.modal('hide');
return this.fileInput.val(''); return this.fileInput.val('');
}; }
GitLabCrop.prototype.onActionBtnClick = function(btn) { onActionBtnClick(btn) {
var data, result; var data, result;
data = $(btn).data(); data = $(btn).data();
if (this.modalCropImg.data('cropper') && data.method) { if (this.modalCropImg.data('cropper') && data.method) {
return result = this.modalCropImg.cropper(data.method, data.option); return result = this.modalCropImg.cropper(data.method, data.option);
} }
}; }
GitLabCrop.prototype.onFileInputChange = function(e, input) { onFileInputChange(e, input) {
return this.readFile(input); return this.readFile(input);
}; }
GitLabCrop.prototype.readFile = function(input) { readFile(input) {
var _this, reader; var _this, reader;
_this = this; _this = this;
reader = new FileReader; reader = new FileReader;
reader.onload = function() { reader.onload = () => {
_this.modalCropImg.attr('src', reader.result); _this.modalCropImg.attr('src', reader.result);
return _this.modalCrop.modal('show'); return _this.modalCrop.modal('show');
}; };
return reader.readAsDataURL(input.files[0]); return reader.readAsDataURL(input.files[0]);
}; }
GitLabCrop.prototype.dataURLtoBlob = function(dataURL) { dataURLtoBlob(dataURL) {
var array, binary, i, k, len, v; var array, binary, i, k, len, v;
binary = atob(dataURL.split(',')[1]); binary = atob(dataURL.split(',')[1]);
array = []; array = [];
...@@ -143,35 +141,32 @@ ...@@ -143,35 +141,32 @@
return new Blob([new Uint8Array(array)], { return new Blob([new Uint8Array(array)], {
type: 'image/png' type: 'image/png'
}); });
}; }
GitLabCrop.prototype.setPreview = function() { setPreview() {
var filename; var filename;
this.previewImage.attr('src', this.dataURL); this.previewImage.attr('src', this.dataURL);
filename = this.fileInput.val().replace(FILENAMEREGEX, ''); filename = this.fileInput.val().replace(FILENAMEREGEX, '');
return this.filename.text(filename); return this.filename.text(filename);
}; }
GitLabCrop.prototype.setBlob = function() { setBlob() {
this.dataURL = this.modalCropImg.cropper('getCroppedCanvas', { this.dataURL = this.modalCropImg.cropper('getCroppedCanvas', {
width: 200, width: 200,
height: 200 height: 200
}).toDataURL('image/png'); }).toDataURL('image/png');
return this.croppedImageBlob = this.dataURLtoBlob(this.dataURL); return this.croppedImageBlob = this.dataURLtoBlob(this.dataURL);
}; }
GitLabCrop.prototype.getBlob = function() { getBlob() {
return this.croppedImageBlob; return this.croppedImageBlob;
}; }
}
return GitLabCrop;
})();
$.fn.glCrop = function(opts) { $.fn.glCrop = function(opts) {
return this.each(function() { return this.each(function() {
return $(this).data('glcrop', new GitLabCrop(this, opts)); return $(this).data('glcrop', new GitLabCrop(this, opts));
}); });
}; }
}).call(this); })(window.gl || (window.gl = {}));
(function() { ((global) => {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
this.Profile = (function() { class Profile {
function Profile(opts) { constructor({ form } = {}) {
var cropOpts, ref; this.onSubmitForm = this.onSubmitForm.bind(this);
if (opts == null) { this.form = form || $('.edit-user');
opts = {};
}
this.onSubmitForm = bind(this.onSubmitForm, this);
this.form = (ref = opts.form) != null ? ref : $('.edit-user');
$('.js-preferences-form').on('change.preference', 'input[type=radio]', function() {
return $(this).parents('form').submit();
// Automatically submit the Preferences form when any of its radio buttons change
});
$('#user_notification_email').on('change', function() {
return $(this).parents('form').submit();
// Automatically submit email form when it changes
});
$('.update-username').on('ajax:before', function() {
$('.loading-username').show();
$(this).find('.update-success').hide();
return $(this).find('.update-failed').hide();
});
$('.update-username').on('ajax:complete', function() {
$('.loading-username').hide();
$(this).find('.btn-save').enable();
return $(this).find('.loading-gif').hide();
});
$('.update-notifications').on('ajax:success', function(e, data) {
if (data.saved) {
return new Flash("Notification settings saved", "notice");
} else {
return new Flash("Failed to save new settings", "alert");
}
});
this.bindEvents(); this.bindEvents();
cropOpts = { this.initAvatarGlCrop();
}
initAvatarGlCrop() {
const cropOpts = {
filename: '.js-avatar-filename', filename: '.js-avatar-filename',
previewImage: '.avatar-image .avatar', previewImage: '.avatar-image .avatar',
modalCrop: '.modal-profile-crop', modalCrop: '.modal-profile-crop',
...@@ -46,23 +20,51 @@ ...@@ -46,23 +20,51 @@
this.avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data('glcrop'); this.avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data('glcrop');
} }
Profile.prototype.bindEvents = function() { bindEvents() {
return this.form.on('submit', this.onSubmitForm); $('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm);
}; $('#user_notification_email').on('change', this.submitForm);
$('.update-username').on('ajax:before', this.beforeUpdateUsername);
$('.update-username').on('ajax:complete', this.afterUpdateUsername);
$('.update-notifications').on('ajax:success', this.onUpdateNotifs);
this.form.on('submit', this.onSubmitForm);
}
submitForm() {
return $(this).parents('form').submit();
}
Profile.prototype.onSubmitForm = function(e) { onSubmitForm(e) {
e.preventDefault(); e.preventDefault();
return this.saveForm(); return this.saveForm();
}; }
beforeUpdateUsername() {
$('.loading-username').show();
$(this).find('.update-success').hide();
return $(this).find('.update-failed').hide();
}
afterUpdateUsername() {
$('.loading-username').hide();
$(this).find('.btn-save').enable();
return $(this).find('.loading-gif').hide();
}
onUpdateNotifs(e, data) {
return data.saved ?
new Flash("Notification settings saved", "notice") :
new Flash("Failed to save new settings", "alert");
}
saveForm() {
const self = this;
const formData = new FormData(this.form[0]);
const avatarBlob = this.avatarGlCrop.getBlob();
Profile.prototype.saveForm = function() {
var avatarBlob, formData, self;
self = this;
formData = new FormData(this.form[0]);
avatarBlob = this.avatarGlCrop.getBlob();
if (avatarBlob != null) { if (avatarBlob != null) {
formData.append('user[avatar]', avatarBlob, 'avatar.png'); formData.append('user[avatar]', avatarBlob, 'avatar.png');
} }
return $.ajax({ return $.ajax({
url: this.form.attr('action'), url: this.form.attr('action'),
type: this.form.attr('method'), type: this.form.attr('method'),
...@@ -70,37 +72,29 @@ ...@@ -70,37 +72,29 @@
dataType: "json", dataType: "json",
processData: false, processData: false,
contentType: false, contentType: false,
success: function(response) { success: response => new Flash(response.message, 'notice'),
return new Flash(response.message, 'notice'); error: jqXHR => new Flash(jqXHR.responseJSON.message, 'alert'),
}, complete: () => {
error: function(jqXHR) {
return new Flash(jqXHR.responseJSON.message, 'alert');
},
complete: function() {
window.scrollTo(0, 0); window.scrollTo(0, 0);
// Enable submit button after requests ends // Enable submit button after requests ends
return self.form.find(':input[disabled]').enable(); return self.form.find(':input[disabled]').enable();
} }
}); });
}; }
}
return Profile;
})();
$(function() { $(function() {
$(document).on('focusout.ssh_key', '#key_key', function() { $(document).on('focusout.ssh_key', '#key_key', function() {
var $title, comment; const $title = $('#key_title');
$title = $('#key_title'); const comment = $(this).val().match(/^\S+ \S+ (.+)\n?$/);
comment = $(this).val().match(/^\S+ \S+ (.+)\n?$/);
if (comment && comment.length > 1 && $title.val() === '') { if (comment && comment.length > 1 && $title.val() === '') {
return $title.val(comment[1]).change(); return $title.val(comment[1]).change();
} }
// Extract the SSH Key title from its comment // Extract the SSH Key title from its comment
}); });
if (gl.utils.getPagePath() === 'profiles') { if (global.utils.getPagePath() === 'profiles') {
return new Profile(); return new Profile();
} }
}); });
}).call(this); })(window.gl || (window.gl = {}));
(function() { ((global) => {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
this.SearchAutocomplete = (function() { const KEYCODE = {
var KEYCODE;
KEYCODE = {
ESCAPE: 27, ESCAPE: 27,
BACKSPACE: 8, BACKSPACE: 8,
ENTER: 13, ENTER: 13,
...@@ -12,19 +8,14 @@ ...@@ -12,19 +8,14 @@
DOWN: 40 DOWN: 40
}; };
function SearchAutocomplete(opts) { class SearchAutocomplete {
var ref, ref1, ref2, ref3, ref4; constructor({ wrap, optsEl, autocompletePath, projectId, projectRef } = {}) {
if (opts == null) { this.bindEventContext();
opts = {}; this.wrap = wrap || $('.search');
} this.optsEl = optsEl || this.wrap.find('.search-autocomplete-opts');
this.onSearchInputBlur = bind(this.onSearchInputBlur, this); this.autocompletePath = autocompletePath || this.optsEl.data('autocomplete-path');
this.onClearInputClick = bind(this.onClearInputClick, this); this.projectId = projectId || (this.optsEl.data('autocomplete-project-id') || '');
this.onSearchInputFocus = bind(this.onSearchInputFocus, this); this.projectRef = projectRef || (this.optsEl.data('autocomplete-project-ref') || '');
this.onSearchInputClick = bind(this.onSearchInputClick, this);
this.onSearchInputKeyUp = bind(this.onSearchInputKeyUp, this);
this.onSearchInputKeyDown = bind(this.onSearchInputKeyDown, this);
this.wrap = (ref = opts.wrap) != null ? ref : $('.search'), this.optsEl = (ref1 = opts.optsEl) != null ? ref1 : this.wrap.find('.search-autocomplete-opts'), this.autocompletePath = (ref2 = opts.autocompletePath) != null ? ref2 : this.optsEl.data('autocomplete-path'), this.projectId = (ref3 = opts.projectId) != null ? ref3 : this.optsEl.data('autocomplete-project-id') || '', this.projectRef = (ref4 = opts.projectRef) != null ? ref4 : this.optsEl.data('autocomplete-project-ref') || '';
// Dropdown Element
this.dropdown = this.wrap.find('.dropdown'); this.dropdown = this.wrap.find('.dropdown');
this.dropdownContent = this.dropdown.find('.dropdown-content'); this.dropdownContent = this.dropdown.find('.dropdown-content');
this.locationBadgeEl = this.getElement('.location-badge'); this.locationBadgeEl = this.getElement('.location-badge');
...@@ -46,19 +37,27 @@ ...@@ -46,19 +37,27 @@
} }
// Finds an element inside wrapper element // Finds an element inside wrapper element
SearchAutocomplete.prototype.getElement = function(selector) { bindEventContext() {
this.onSearchInputBlur = this.onSearchInputBlur.bind(this);
this.onClearInputClick = this.onClearInputClick.bind(this);
this.onSearchInputFocus = this.onSearchInputFocus.bind(this);
this.onSearchInputClick = this.onSearchInputClick.bind(this);
this.onSearchInputKeyUp = this.onSearchInputKeyUp.bind(this);
this.onSearchInputKeyDown = this.onSearchInputKeyDown.bind(this);
}
getElement(selector) {
return this.wrap.find(selector); return this.wrap.find(selector);
}; }
SearchAutocomplete.prototype.saveOriginalState = function() { saveOriginalState() {
return this.originalState = this.serializeState(); return this.originalState = this.serializeState();
}; }
SearchAutocomplete.prototype.saveTextLength = function() { saveTextLength() {
return this.lastTextLength = this.searchInput.val().length; return this.lastTextLength = this.searchInput.val().length;
}; }
SearchAutocomplete.prototype.createAutocomplete = function() { createAutocomplete() {
return this.searchInput.glDropdown({ return this.searchInput.glDropdown({
filterInputBlur: false, filterInputBlur: false,
filterable: true, filterable: true,
...@@ -73,9 +72,9 @@ ...@@ -73,9 +72,9 @@
selectable: true, selectable: true,
clicked: this.onClick.bind(this) clicked: this.onClick.bind(this)
}); });
}; }
SearchAutocomplete.prototype.getData = function(term, callback) { getData(term, callback) {
var _this, contents, jqXHR; var _this, contents, jqXHR;
_this = this; _this = this;
if (!term) { if (!term) {
...@@ -138,9 +137,9 @@ ...@@ -138,9 +137,9 @@
}).always(function() { }).always(function() {
return _this.loadingSuggestions = false; return _this.loadingSuggestions = false;
}); });
}; }
SearchAutocomplete.prototype.getCategoryContents = function() { getCategoryContents() {
var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, utils; var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, utils;
userId = gon.current_user_id; userId = gon.current_user_id;
utils = gl.utils, projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions; utils = gl.utils, projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions;
...@@ -173,9 +172,9 @@ ...@@ -173,9 +172,9 @@
items.splice(0, 1); items.splice(0, 1);
} }
return items; return items;
}; }
SearchAutocomplete.prototype.serializeState = function() { serializeState() {
return { return {
// Search Criteria // Search Criteria
search_project_id: this.projectInputEl.val(), search_project_id: this.projectInputEl.val(),
...@@ -186,9 +185,9 @@ ...@@ -186,9 +185,9 @@
// Location badge // Location badge
_location: this.locationBadgeEl.text() _location: this.locationBadgeEl.text()
}; };
}; }
SearchAutocomplete.prototype.bindEvents = function() { bindEvents() {
this.searchInput.on('keydown', this.onSearchInputKeyDown); this.searchInput.on('keydown', this.onSearchInputKeyDown);
this.searchInput.on('keyup', this.onSearchInputKeyUp); this.searchInput.on('keyup', this.onSearchInputKeyUp);
this.searchInput.on('click', this.onSearchInputClick); this.searchInput.on('click', this.onSearchInputClick);
...@@ -200,9 +199,9 @@ ...@@ -200,9 +199,9 @@
return _this.searchInput.focus(); return _this.searchInput.focus();
}; };
})(this)); })(this));
}; }
SearchAutocomplete.prototype.enableAutocomplete = function() { enableAutocomplete() {
var _this; var _this;
// No need to enable anything if user is not logged in // No need to enable anything if user is not logged in
if (!gon.current_user_id) { if (!gon.current_user_id) {
...@@ -216,12 +215,12 @@ ...@@ -216,12 +215,12 @@
} }
}; };
SearchAutocomplete.prototype.onSearchInputKeyDown = function() {
// Saves last length of the entered text // Saves last length of the entered text
onSearchInputKeyDown() {
return this.saveTextLength(); return this.saveTextLength();
}; }
SearchAutocomplete.prototype.onSearchInputKeyUp = function(e) { onSearchInputKeyUp(e) {
switch (e.keyCode) { switch (e.keyCode) {
case KEYCODE.BACKSPACE: case KEYCODE.BACKSPACE:
// when trying to remove the location badge // when trying to remove the location badge
...@@ -259,54 +258,53 @@ ...@@ -259,54 +258,53 @@
} }
} }
this.wrap.toggleClass('has-value', !!e.target.value); this.wrap.toggleClass('has-value', !!e.target.value);
}; }
// Avoid falsy value to be returned // Avoid falsy value to be returned
SearchAutocomplete.prototype.onSearchInputClick = function(e) { onSearchInputClick(e) {
// Prevents closing the dropdown menu
return e.stopImmediatePropagation(); return e.stopImmediatePropagation();
}; }
SearchAutocomplete.prototype.onSearchInputFocus = function() { onSearchInputFocus() {
this.isFocused = true; this.isFocused = true;
this.wrap.addClass('search-active'); this.wrap.addClass('search-active');
if (this.getValue() === '') { if (this.getValue() === '') {
return this.getData(); return this.getData();
} }
}; }
SearchAutocomplete.prototype.getValue = function() { getValue() {
return this.searchInput.val(); return this.searchInput.val();
}; }
SearchAutocomplete.prototype.onClearInputClick = function(e) { onClearInputClick(e) {
e.preventDefault(); e.preventDefault();
return this.searchInput.val('').focus(); return this.searchInput.val('').focus();
}; }
SearchAutocomplete.prototype.onSearchInputBlur = function(e) { onSearchInputBlur(e) {
this.isFocused = false; this.isFocused = false;
this.wrap.removeClass('search-active'); this.wrap.removeClass('search-active');
// If input is blank then restore state // If input is blank then restore state
if (this.searchInput.val() === '') { if (this.searchInput.val() === '') {
return this.restoreOriginalState(); return this.restoreOriginalState();
} }
}; }
SearchAutocomplete.prototype.addLocationBadge = function(item) { addLocationBadge(item) {
var badgeText, category, value; var badgeText, category, value;
category = item.category != null ? item.category + ": " : ''; category = item.category != null ? item.category + ": " : '';
value = item.value != null ? item.value : ''; value = item.value != null ? item.value : '';
badgeText = "" + category + value; badgeText = "" + category + value;
this.locationBadgeEl.text(badgeText).show(); this.locationBadgeEl.text(badgeText).show();
return this.wrap.addClass('has-location-badge'); return this.wrap.addClass('has-location-badge');
}; }
SearchAutocomplete.prototype.hasLocationBadge = function() { hasLocationBadge() {
return this.wrap.is('.has-location-badge'); return this.wrap.is('.has-location-badge');
}; };
SearchAutocomplete.prototype.restoreOriginalState = function() { restoreOriginalState() {
var i, input, inputs, len; var i, input, inputs, len;
inputs = Object.keys(this.originalState); inputs = Object.keys(this.originalState);
for (i = 0, len = inputs.length; i < len; i++) { for (i = 0, len = inputs.length; i < len; i++) {
...@@ -320,13 +318,13 @@ ...@@ -320,13 +318,13 @@
value: this.originalState._location value: this.originalState._location
}); });
} }
}; }
SearchAutocomplete.prototype.badgePresent = function() { badgePresent() {
return this.locationBadgeEl.length; return this.locationBadgeEl.length;
}; }
SearchAutocomplete.prototype.resetSearchState = function() { resetSearchState() {
var i, input, inputs, len, results; var i, input, inputs, len, results;
inputs = Object.keys(this.originalState); inputs = Object.keys(this.originalState);
results = []; results = [];
...@@ -339,30 +337,30 @@ ...@@ -339,30 +337,30 @@
results.push(this.getElement("#" + input).val('')); results.push(this.getElement("#" + input).val(''));
} }
return results; return results;
}; }
SearchAutocomplete.prototype.removeLocationBadge = function() { removeLocationBadge() {
this.locationBadgeEl.hide(); this.locationBadgeEl.hide();
this.resetSearchState(); this.resetSearchState();
this.wrap.removeClass('has-location-badge'); this.wrap.removeClass('has-location-badge');
return this.disableAutocomplete(); return this.disableAutocomplete();
}; }
SearchAutocomplete.prototype.disableAutocomplete = function() { disableAutocomplete() {
if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) { if (!this.searchInput.hasClass('disabled') && this.dropdown.hasClass('open')) {
this.searchInput.addClass('disabled'); this.searchInput.addClass('disabled');
this.dropdown.removeClass('open').trigger('hidden.bs.dropdown'); this.dropdown.removeClass('open').trigger('hidden.bs.dropdown');
this.restoreMenu(); this.restoreMenu();
} }
}; }
SearchAutocomplete.prototype.restoreMenu = function() { restoreMenu() {
var html; var html;
html = "<ul> <li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li> </ul>"; html = "<ul> <li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li> </ul>";
return this.dropdownContent.html(html); return this.dropdownContent.html(html);
}; };
SearchAutocomplete.prototype.onClick = function(item, $el, e) { onClick(item, $el, e) {
if (location.pathname.indexOf(item.url) !== -1) { if (location.pathname.indexOf(item.url) !== -1) {
e.preventDefault(); e.preventDefault();
if (!this.badgePresent) { if (!this.badgePresent) {
...@@ -385,9 +383,9 @@ ...@@ -385,9 +383,9 @@
} }
}; };
return SearchAutocomplete; }
})(); global.SearchAutocomplete = SearchAutocomplete;
$(function() { $(function() {
var $projectOptionsDataEl = $('.js-search-project-options'); var $projectOptionsDataEl = $('.js-search-project-options');
...@@ -426,4 +424,4 @@ ...@@ -426,4 +424,4 @@
} }
}); });
}).call(this); })(window.gl || (window.gl = {}));
/*= require ../blob/template_selector */ /*= require ../blob/template_selector */
((global) => { ((global) => {
class IssuableTemplateSelector extends TemplateSelector { class IssuableTemplateSelector extends gl.TemplateSelector {
constructor(...args) { constructor(...args) {
super(...args); super(...args);
this.projectPath = this.dropdown.data('project-path'); this.projectPath = this.dropdown.data('project-path');
...@@ -50,4 +50,4 @@ ...@@ -50,4 +50,4 @@
} }
global.IssuableTemplateSelector = IssuableTemplateSelector; global.IssuableTemplateSelector = IssuableTemplateSelector;
})(window); })(window.gl || (window.gl = {}));
((global) => { ((global) => {
class IssuableTemplateSelectors { class IssuableTemplateSelectors {
constructor(opts = {}) { constructor({ $dropdowns, editor } = {}) {
this.$dropdowns = opts.$dropdowns || $('.js-issuable-selector'); this.$dropdowns = $dropdowns || $('.js-issuable-selector');
this.editor = opts.editor || this.initEditor(); this.editor = editor || this.initEditor();
this.$dropdowns.each((i, dropdown) => { this.$dropdowns.each((i, dropdown) => {
let $dropdown = $(dropdown); const $dropdown = $(dropdown);
new IssuableTemplateSelector({ new gl.IssuableTemplateSelector({
pattern: /(\.md)/, pattern: /(\.md)/,
data: $dropdown.data('data'), data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-issuable-selector-wrap'), wrapper: $dropdown.closest('.js-issuable-selector-wrap'),
...@@ -26,4 +26,4 @@ ...@@ -26,4 +26,4 @@
} }
global.IssuableTemplateSelectors = IssuableTemplateSelectors; global.IssuableTemplateSelectors = IssuableTemplateSelectors;
})(window); })(window.gl || (window.gl = {}));
(function() { ((global) => {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
class Todos {
this.Todos = (function() { constructor({ el } = {}) {
function Todos(opts) { this.allDoneClicked = this.allDoneClicked.bind(this);
var ref; this.doneClicked = this.doneClicked.bind(this);
if (opts == null) { this.el = el || $('.js-todos-options');
opts = {};
}
this.allDoneClicked = bind(this.allDoneClicked, this);
this.doneClicked = bind(this.doneClicked, this);
this.el = (ref = opts.el) != null ? ref : $('.js-todos-options');
this.perPage = this.el.data('perPage'); this.perPage = this.el.data('perPage');
this.clearListeners(); this.clearListeners();
this.initBtnListeners(); this.initBtnListeners();
this.initFilters(); this.initFilters();
} }
Todos.prototype.clearListeners = function() { clearListeners() {
$('.done-todo').off('click'); $('.done-todo').off('click');
$('.js-todos-mark-all').off('click'); $('.js-todos-mark-all').off('click');
return $('.todo').off('click'); return $('.todo').off('click');
}; }
Todos.prototype.initBtnListeners = function() { initBtnListeners() {
$('.done-todo').on('click', this.doneClicked); $('.done-todo').on('click', this.doneClicked);
$('.js-todos-mark-all').on('click', this.allDoneClicked); $('.js-todos-mark-all').on('click', this.allDoneClicked);
return $('.todo').on('click', this.goToTodoUrl); return $('.todo').on('click', this.goToTodoUrl);
}; }
Todos.prototype.initFilters = function() { initFilters() {
new UsersSelect(); new UsersSelect();
this.initFilterDropdown($('.js-project-search'), 'project_id', ['text']); this.initFilterDropdown($('.js-project-search'), 'project_id', ['text']);
this.initFilterDropdown($('.js-type-search'), 'type'); this.initFilterDropdown($('.js-type-search'), 'type');
...@@ -38,125 +33,117 @@ ...@@ -38,125 +33,117 @@
event.preventDefault(); event.preventDefault();
Turbolinks.visit(this.action + '&' + $(this).serialize()); Turbolinks.visit(this.action + '&' + $(this).serialize());
}); });
}; }
Todos.prototype.initFilterDropdown = function($dropdown, fieldName, searchFields) { initFilterDropdown($dropdown, fieldName, searchFields) {
$dropdown.glDropdown({ $dropdown.glDropdown({
fieldName,
selectable: true, selectable: true,
filterable: searchFields ? true : false, filterable: searchFields ? true : false,
fieldName: fieldName,
search: { fields: searchFields }, search: { fields: searchFields },
data: $dropdown.data('data'), data: $dropdown.data('data'),
clicked: function() { clicked: function() {
return $dropdown.closest('form.filter-form').submit(); return $dropdown.closest('form.filter-form').submit();
} }
}) })
}; }
Todos.prototype.doneClicked = function(e) { doneClicked(e) {
var $this;
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
$this = $(e.currentTarget); const $target = $(e.currentTarget);
$this.disable(); $target.disable();
return $.ajax({ return $.ajax({
type: 'POST', type: 'POST',
url: $this.attr('href'), url: $target.attr('href'),
dataType: 'json', dataType: 'json',
data: { data: {
'_method': 'delete' '_method': 'delete'
}, },
success: (function(_this) { success: (data) => {
return function(data) { this.redirectIfNeeded(data.count);
_this.redirectIfNeeded(data.count); this.clearDone($target.closest('li'));
_this.clearDone($this.closest('li')); return this.updateBadges(data);
return _this.updateBadges(data); }
};
})(this)
}); });
}; }
Todos.prototype.allDoneClicked = function(e) { allDoneClicked(e) {
var $this;
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
$this = $(e.currentTarget); $target = $(e.currentTarget);
$this.disable(); $target.disable();
return $.ajax({ return $.ajax({
type: 'POST', type: 'POST',
url: $this.attr('href'), url: $target.attr('href'),
dataType: 'json', dataType: 'json',
data: { data: {
'_method': 'delete' '_method': 'delete'
}, },
success: (function(_this) { success: (data) => {
return function(data) { $target.remove();
$this.remove();
$('.prepend-top-default').html('<div class="nothing-here-block">You\'re all done!</div>'); $('.prepend-top-default').html('<div class="nothing-here-block">You\'re all done!</div>');
return _this.updateBadges(data); return this.updateBadges(data);
}; }
})(this)
}); });
}; }
Todos.prototype.clearDone = function($row) { clearDone($row) {
var $ul; const $ul = $row.closest('ul');
$ul = $row.closest('ul');
$row.remove(); $row.remove();
if (!$ul.find('li').length) { if (!$ul.find('li').length) {
return $ul.parents('.panel').remove(); return $ul.parents('.panel').remove();
} }
}; }
Todos.prototype.updateBadges = function(data) { updateBadges(data) {
$('.todos-pending .badge, .todos-pending-count').text(data.count); $('.todos-pending .badge, .todos-pending-count').text(data.count);
return $('.todos-done .badge').text(data.done_count); return $('.todos-done .badge').text(data.done_count);
}; }
Todos.prototype.getTotalPages = function() { getTotalPages() {
return this.el.data('totalPages'); return this.el.data('totalPages');
}; }
Todos.prototype.getCurrentPage = function() { getCurrentPage() {
return this.el.data('currentPage'); return this.el.data('currentPage');
}; }
Todos.prototype.getTodosPerPage = function() { getTodosPerPage() {
return this.el.data('perPage'); return this.el.data('perPage');
}; }
redirectIfNeeded(total) {
const currPages = this.getTotalPages();
const currPage = this.getCurrentPage();
Todos.prototype.redirectIfNeeded = function(total) {
var currPage, currPages, newPages, pageParams, url;
currPages = this.getTotalPages();
currPage = this.getCurrentPage();
// Refresh if no remaining Todos // Refresh if no remaining Todos
if (!total) { if (!total) {
location.reload(); window.location.reload();
return; return;
} }
// Do nothing if no pagination // Do nothing if no pagination
if (!currPages) { if (!currPages) {
return; return;
} }
newPages = Math.ceil(total / this.getTodosPerPage());
// Includes query strings const newPages = Math.ceil(total / this.getTodosPerPage());
url = location.href; let url = location.href;
// If new total of pages is different than we have now
if (newPages !== currPages) { if (newPages !== currPages) {
// Redirect to previous page if there's one available // Redirect to previous page if there's one available
if (currPages > 1 && currPage === currPages) { if (currPages > 1 && currPage === currPages) {
pageParams = { const pageParams = {
page: currPages - 1 page: currPages - 1
}; };
url = gl.utils.mergeUrlParams(pageParams, url); url = gl.utils.mergeUrlParams(pageParams, url);
} }
return Turbolinks.visit(url); return Turbolinks.visit(url);
} }
}; }
Todos.prototype.goToTodoUrl = function(e) { goToTodoUrl(e) {
var todoLink; const todoLink = $(this).data('url');
todoLink = $(this).data('url');
if (!todoLink) { if (!todoLink) {
return; return;
} }
...@@ -167,10 +154,8 @@ ...@@ -167,10 +154,8 @@
} else { } else {
return Turbolinks.visit(todoLink); return Turbolinks.visit(todoLink);
} }
}; }
}
return Todos;
})();
}).call(this); global.Todos = Todos;
})(window.gl || (window.gl = {}));
(global => { ((global) => {
global.User = class { global.User = class {
constructor(opts) { constructor({ action }) {
this.opts = opts; this.action = action;
this.placeProfileAvatarsToTop(); this.placeProfileAvatarsToTop();
this.initTabs(); this.initTabs();
this.hideProjectLimitMessage(); this.hideProjectLimitMessage();
...@@ -14,9 +14,9 @@ ...@@ -14,9 +14,9 @@
} }
initTabs() { initTabs() {
return new UserTabs({ return new global.UserTabs({
parentEl: '.user-profile', parentEl: '.user-profile',
action: this.opts.action action: this.action
}); });
} }
......
// 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>
// </div>
//
// <div class="loading-status">
// <div class="loading">
// Loading Animation
// </div>
// </div>
//
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
this.UserTabs = (function() {
function UserTabs(opts) {
this.tabShown = bind(this.tabShown, this);
var i, item, len, ref, ref1, ref2, ref3;
this.action = (ref = opts.action) != null ? ref : 'activity', this.defaultAction = (ref1 = opts.defaultAction) != null ? ref1 : 'activity', this.parentEl = (ref2 = opts.parentEl) != null ? ref2 : $(document);
// Make jQuery object if selector is provided
if (typeof this.parentEl === 'string') {
this.parentEl = $(this.parentEl);
}
// Store the `location` object, allowing for easier stubbing in tests
this._location = location;
// Set tab states
this.loaded = {};
ref3 = this.parentEl.find('.nav-links a');
for (i = 0, len = ref3.length; i < len; i++) {
item = ref3[i];
this.loaded[$(item).attr('data-action')] = false;
}
// Actions
this.actions = Object.keys(this.loaded);
this.bindEvents();
// Set active tab
if (this.action === 'show') {
this.action = this.defaultAction;
}
this.activateTab(this.action);
}
UserTabs.prototype.bindEvents = function() {
// Toggle event listeners
return this.parentEl.off('shown.bs.tab', '.nav-links a[data-toggle="tab"]').on('shown.bs.tab', '.nav-links a[data-toggle="tab"]', this.tabShown);
};
UserTabs.prototype.tabShown = function(event) {
var $target, action, source;
$target = $(event.target);
action = $target.data('action');
source = $target.attr('href');
this.setTab(source, action);
return this.setCurrentAction(action);
};
UserTabs.prototype.activateTab = function(action) {
return this.parentEl.find(".nav-links .js-" + action + "-tab a").tab('show');
};
UserTabs.prototype.setTab = function(source, action) {
if (this.loaded[action] === true) {
return;
}
if (action === 'activity') {
this.loadActivities(source);
}
if (action === 'groups' || action === 'contributed' || action === 'projects' || action === 'snippets') {
return this.loadTab(source, action);
}
};
UserTabs.prototype.loadTab = function(source, action) {
return $.ajax({
beforeSend: (function(_this) {
return function() {
return _this.toggleLoading(true);
};
})(this),
complete: (function(_this) {
return function() {
return _this.toggleLoading(false);
};
})(this),
dataType: 'json',
type: 'GET',
url: source + ".json",
success: (function(_this) {
return function(data) {
var tabSelector;
tabSelector = 'div#' + action;
_this.parentEl.find(tabSelector).html(data.html);
_this.loaded[action] = true;
// Fix tooltips
return gl.utils.localTimeAgo($('.js-timeago', tabSelector));
};
})(this)
});
};
UserTabs.prototype.loadActivities = function(source) {
var $calendarWrap;
if (this.loaded['activity'] === true) {
return;
}
$calendarWrap = this.parentEl.find('.user-calendar');
$calendarWrap.load($calendarWrap.data('href'));
new Activities();
return this.loaded['activity'] = true;
};
UserTabs.prototype.toggleLoading = function(status) {
return this.parentEl.find('.loading-status .loading').toggle(status);
};
UserTabs.prototype.setCurrentAction = function(action) {
var new_state, regExp;
// Remove possible actions from URL
regExp = new RegExp('\/(' + this.actions.join('|') + ')(\.html)?\/?$');
new_state = this._location.pathname;
// remove trailing slashes
new_state = new_state.replace(/\/+$/, "");
new_state = new_state.replace(regExp, '');
// Append the new action if we're on a tab other than 'activity'
if (action !== this.defaultAction) {
new_state += "/" + action;
}
// Ensure parameters and hash come along for the ride
new_state += this._location.search + this._location.hash;
history.replaceState({
turbolinks: true,
url: new_state
}, document.title, new_state);
return new_state;
};
return UserTabs;
})();
}).call(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>
</div>
<div class="loading-status">
<div class="loading">
Loading Animation
</div>
</div>
*/
((global) => {
class UserTabs {
constructor ({ defaultAction, action, parentEl }) {
this.loaded = {};
this.defaultAction = defaultAction || 'activity';
this.action = action || this.defaultAction;
this.$parentEl = $(parentEl) || $(document);
this._location = window.location;
this.$parentEl.find('.nav-links a')
.each((i, navLink) => {
this.loaded[$(navLink).attr('data-action')] = false;
});
this.actions = Object.keys(this.loaded);
this.bindEvents();
if (this.action === 'show') {
this.action = this.defaultAction;
}
this.activateTab(this.action);
}
bindEvents() {
return 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));
}
tabShown(event) {
const $target = $(event.target);
const action = $target.data('action');
const source = $target.attr('href');
this.setTab(source, action);
return this.setCurrentAction(action);
}
activateTab(action) {
return this.$parentEl.find(`.nav-links .js-${action}-tab a`)
.tab('show');
}
setTab(source, action) {
if (this.loaded[action]) {
return;
}
if (action === 'activity') {
this.loadActivities(source);
}
const loadableActions = [ 'groups', 'contributed', 'projects', 'snippets' ];
if (loadableActions.indexOf(action) > -1) {
return this.loadTab(source, action);
}
}
loadTab(source, action) {
return $.ajax({
beforeSend: () => this.toggleLoading(true),
complete: () => this.toggleLoading(false),
dataType: 'json',
type: 'GET',
url: `${source}.json`,
success: (data) => {
const tabSelector = `div#${action}`;
this.$parentEl.find(tabSelector).html(data.html);
this.loaded[action] = true;
return gl.utils.localTimeAgo($('.js-timeago', tabSelector));
}
});
}
loadActivities(source) {
if (this.loaded['activity']) {
return;
}
const $calendarWrap = this.$parentEl.find('.user-calendar');
$calendarWrap.load($calendarWrap.data('href'));
new Activities();
return this.loaded['activity'] = true;
}
toggleLoading(status) {
return this.$parentEl.find('.loading-status .loading')
.toggle(status);
}
setCurrentAction(action) {
const regExp = new RegExp(`\/(${this.actions.join('|')})(\.html)?\/?$`);
let new_state = this._location.pathname;
new_state = new_state.replace(/\/+$/, '');
new_state = new_state.replace(regExp, '');
if (action !== this.defaultAction) {
new_state += `/${action}`;
}
new_state += this._location.search + this._location.hash;
history.replaceState({
turbolinks: true,
url: new_state
}, document.title, new_state);
return new_state;
}
}
global.UserTabs = UserTabs;
})(window.gl || (window.gl = {}));
...@@ -112,7 +112,7 @@ ...@@ -112,7 +112,7 @@
fixture.preload('search_autocomplete.html'); fixture.preload('search_autocomplete.html');
beforeEach(function() { beforeEach(function() {
fixture.load('search_autocomplete.html'); fixture.load('search_autocomplete.html');
return widget = new SearchAutocomplete; return widget = new gl.SearchAutocomplete;
}); });
it('should show Dashboard specific dropdown menu', function() { it('should show Dashboard specific dropdown menu', function() {
var list; var list;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment