Commit 86f24489 authored by Phil Hughes's avatar Phil Hughes

Merge branch 'ce-to-ee-2018-10-25' into 'master'

CE upstream - 2018-10-25 01:22 UTC

Closes gitlab-org/release/tasks#483

See merge request gitlab-org/gitlab-ee!8078
parents 619bc37f 16992141
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.18-chrome-69.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29" image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.5-golang-1.9-git-2.18-chrome-69.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29"
.dedicated-runner: &dedicated-runner .dedicated-runner: &dedicated-runner
retry: 1 retry: 1
...@@ -6,7 +6,7 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git ...@@ -6,7 +6,7 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git
- gitlab-org - gitlab-org
.default-cache: &default-cache .default-cache: &default-cache
key: "ruby-2.4.4-debian-stretch-with-yarn" key: "ruby-2.4.5-debian-stretch-with-yarn"
paths: paths:
- vendor/ruby - vendor/ruby
- .yarn-cache/ - .yarn-cache/
...@@ -752,7 +752,7 @@ static-analysis: ...@@ -752,7 +752,7 @@ static-analysis:
script: script:
- scripts/static-analysis - scripts/static-analysis
cache: cache:
key: "ruby-2.4.4-debian-stretch-with-yarn-and-rubocop" key: "ruby-2.4.5-debian-stretch-with-yarn-and-rubocop"
paths: paths:
- vendor/ruby - vendor/ruby
- .yarn-cache/ - .yarn-cache/
......
import $ from 'jquery'; import $ from 'jquery';
export const addTooltipToEl = (el) => { export const addTooltipToEl = el => {
const textEl = el.querySelector('.js-breadcrumb-item-text'); const textEl = el.querySelector('.js-breadcrumb-item-text');
if (textEl && textEl.scrollWidth > textEl.offsetWidth) { if (textEl && textEl.scrollWidth > textEl.offsetWidth) {
...@@ -14,17 +14,18 @@ export default () => { ...@@ -14,17 +14,18 @@ export default () => {
const breadcrumbs = document.querySelector('.js-breadcrumbs-list'); const breadcrumbs = document.querySelector('.js-breadcrumbs-list');
if (breadcrumbs) { if (breadcrumbs) {
const topLevelLinks = [...breadcrumbs.children].filter(el => !el.classList.contains('dropdown')) const topLevelLinks = [...breadcrumbs.children]
.filter(el => !el.classList.contains('dropdown'))
.map(el => el.querySelector('a')) .map(el => el.querySelector('a'))
.filter(el => el); .filter(el => el);
const $expander = $('.js-breadcrumbs-collapsed-expander'); const $expander = $('.js-breadcrumbs-collapsed-expander');
topLevelLinks.forEach(el => addTooltipToEl(el)); topLevelLinks.forEach(el => addTooltipToEl(el));
$expander.closest('.dropdown') $expander.closest('.dropdown').on('show.bs.dropdown hide.bs.dropdown', e => {
.on('show.bs.dropdown hide.bs.dropdown', (e) => { $('.js-breadcrumbs-collapsed-expander', e.currentTarget)
$('.js-breadcrumbs-collapsed-expander', e.currentTarget).toggleClass('open') .toggleClass('open')
.tooltip('hide'); .tooltip('hide');
}); });
} }
}; };
...@@ -12,16 +12,16 @@ export default class BuildArtifacts { ...@@ -12,16 +12,16 @@ export default class BuildArtifacts {
} }
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
disablePropagation() { disablePropagation() {
$('.top-block').on('click', '.download', function (e) { $('.top-block').on('click', '.download', function(e) {
return e.stopPropagation(); return e.stopPropagation();
}); });
return $('.tree-holder').on('click', 'tr[data-link] a', function (e) { return $('.tree-holder').on('click', 'tr[data-link] a', function(e) {
return e.stopImmediatePropagation(); return e.stopImmediatePropagation();
}); });
} }
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
setupEntryClick() { setupEntryClick() {
return $('.tree-holder').on('click', 'tr[data-link]', function () { return $('.tree-holder').on('click', 'tr[data-link]', function() {
visitUrl(this.dataset.link, convertPermissionToBoolean(this.dataset.externalLink)); visitUrl(this.dataset.link, convertPermissionToBoolean(this.dataset.externalLink));
}); });
} }
...@@ -37,11 +37,15 @@ export default class BuildArtifacts { ...@@ -37,11 +37,15 @@ export default class BuildArtifacts {
// We want the tooltip to show if you hover anywhere on the row // We want the tooltip to show if you hover anywhere on the row
// But be placed below and in the middle of the file name // But be placed below and in the middle of the file name
$('.js-artifact-tree-row') $('.js-artifact-tree-row')
.on('mouseenter', (e) => { .on('mouseenter', e => {
$(e.currentTarget).find('.js-artifact-tree-tooltip').tooltip('show'); $(e.currentTarget)
.find('.js-artifact-tree-tooltip')
.tooltip('show');
}) })
.on('mouseleave', (e) => { .on('mouseleave', e => {
$(e.currentTarget).find('.js-artifact-tree-tooltip').tooltip('hide'); $(e.currentTarget)
.find('.js-artifact-tree-tooltip')
.tooltip('hide');
}); });
} }
} }
...@@ -7,11 +7,13 @@ import statusCodes from '../lib/utils/http_status'; ...@@ -7,11 +7,13 @@ import statusCodes from '../lib/utils/http_status';
import VariableList from './ci_variable_list'; import VariableList from './ci_variable_list';
function generateErrorBoxContent(errors) { function generateErrorBoxContent(errors) {
const errorList = [].concat(errors).map(errorString => ` const errorList = [].concat(errors).map(
errorString => `
<li> <li>
${_.escape(errorString)} ${_.escape(errorString)}
</li> </li>
`); `,
);
return ` return `
<p> <p>
...@@ -25,13 +27,7 @@ function generateErrorBoxContent(errors) { ...@@ -25,13 +27,7 @@ function generateErrorBoxContent(errors) {
// Used for the variable list on CI/CD projects/groups settings page // Used for the variable list on CI/CD projects/groups settings page
export default class AjaxVariableList { export default class AjaxVariableList {
constructor({ constructor({ container, saveButton, errorBox, formField = 'variables', saveEndpoint }) {
container,
saveButton,
errorBox,
formField = 'variables',
saveEndpoint,
}) {
this.container = container; this.container = container;
this.saveButton = saveButton; this.saveButton = saveButton;
this.errorBox = errorBox; this.errorBox = errorBox;
...@@ -58,18 +54,21 @@ export default class AjaxVariableList { ...@@ -58,18 +54,21 @@ export default class AjaxVariableList {
// to match it up in `updateRowsWithPersistedVariables` // to match it up in `updateRowsWithPersistedVariables`
this.variableList.toggleEnableRow(false); this.variableList.toggleEnableRow(false);
return axios.patch(this.saveEndpoint, { return axios
variables_attributes: this.variableList.getAllData(), .patch(
}, { this.saveEndpoint,
// We want to be able to process the `res.data` from a 400 error response {
// and print the validation messages such as duplicate variable keys variables_attributes: this.variableList.getAllData(),
validateStatus: status => ( },
status >= statusCodes.OK && {
status < statusCodes.MULTIPLE_CHOICES // We want to be able to process the `res.data` from a 400 error response
) || // and print the validation messages such as duplicate variable keys
status === statusCodes.BAD_REQUEST, validateStatus: status =>
}) (status >= statusCodes.OK && status < statusCodes.MULTIPLE_CHOICES) ||
.then((res) => { status === statusCodes.BAD_REQUEST,
},
)
.then(res => {
loadingIcon.classList.toggle('hide', true); loadingIcon.classList.toggle('hide', true);
this.variableList.toggleEnableRow(true); this.variableList.toggleEnableRow(true);
...@@ -90,18 +89,21 @@ export default class AjaxVariableList { ...@@ -90,18 +89,21 @@ export default class AjaxVariableList {
} }
updateRowsWithPersistedVariables(persistedVariables = []) { updateRowsWithPersistedVariables(persistedVariables = []) {
const persistedVariableMap = [].concat(persistedVariables).reduce((variableMap, variable) => ({ const persistedVariableMap = [].concat(persistedVariables).reduce(
...variableMap, (variableMap, variable) => ({
[variable.key]: variable, ...variableMap,
}), {}); [variable.key]: variable,
}),
{},
);
this.container.querySelectorAll('.js-row').forEach((row) => { this.container.querySelectorAll('.js-row').forEach(row => {
// If we submitted a row that was destroyed, remove it so we don't try // If we submitted a row that was destroyed, remove it so we don't try
// to destroy it again which would cause a BE error // to destroy it again which would cause a BE error
const destroyInput = row.querySelector('.js-ci-variable-input-destroy'); const destroyInput = row.querySelector('.js-ci-variable-input-destroy');
if (convertPermissionToBoolean(destroyInput.value)) { if (convertPermissionToBoolean(destroyInput.value)) {
row.remove(); row.remove();
// Update the ID input so any future edits and `_destroy` will apply on the BE // Update the ID input so any future edits and `_destroy` will apply on the BE
} else { } else {
const key = row.querySelector('.js-ci-variable-input-key').value; const key = row.querySelector('.js-ci-variable-input-key').value;
const persistedVariable = persistedVariableMap[key]; const persistedVariable = persistedVariableMap[key];
......
...@@ -16,10 +16,7 @@ function createEnvironmentItem(value) { ...@@ -16,10 +16,7 @@ function createEnvironmentItem(value) {
} }
export default class VariableList { export default class VariableList {
constructor({ constructor({ container, formField }) {
container,
formField,
}) {
this.$container = $(container); this.$container = $(container);
this.formField = formField; this.formField = formField;
this.environmentDropdownMap = new WeakMap(); this.environmentDropdownMap = new WeakMap();
...@@ -71,7 +68,7 @@ export default class VariableList { ...@@ -71,7 +68,7 @@ export default class VariableList {
this.initRow(rowEl); this.initRow(rowEl);
}); });
this.$container.on('click', '.js-row-remove-button', (e) => { this.$container.on('click', '.js-row-remove-button', e => {
e.preventDefault(); e.preventDefault();
this.removeRow($(e.currentTarget).closest('.js-row')); this.removeRow($(e.currentTarget).closest('.js-row'));
}); });
...@@ -81,7 +78,7 @@ export default class VariableList { ...@@ -81,7 +78,7 @@ export default class VariableList {
.join(','); .join(',');
// Remove any empty rows except the last row // Remove any empty rows except the last row
this.$container.on('blur', inputSelector, (e) => { this.$container.on('blur', inputSelector, e => {
const $row = $(e.currentTarget).closest('.js-row'); const $row = $(e.currentTarget).closest('.js-row');
if ($row.is(':not(:last-child)') && !this.checkIfRowTouched($row)) { if ($row.is(':not(:last-child)') && !this.checkIfRowTouched($row)) {
...@@ -136,7 +133,7 @@ export default class VariableList { ...@@ -136,7 +133,7 @@ export default class VariableList {
$rowClone.removeAttr('data-is-persisted'); $rowClone.removeAttr('data-is-persisted');
// Reset the inputs to their defaults // Reset the inputs to their defaults
Object.keys(this.inputMap).forEach((name) => { Object.keys(this.inputMap).forEach(name => {
const entry = this.inputMap[name]; const entry = this.inputMap[name];
$rowClone.find(entry.selector).val(entry.default); $rowClone.find(entry.selector).val(entry.default);
}); });
...@@ -171,7 +168,7 @@ export default class VariableList { ...@@ -171,7 +168,7 @@ export default class VariableList {
} }
checkIfRowTouched($row) { checkIfRowTouched($row) {
return Object.keys(this.inputMap).some((name) => { return Object.keys(this.inputMap).some(name => {
const entry = this.inputMap[name]; const entry = this.inputMap[name];
const $el = $row.find(entry.selector); const $el = $row.find(entry.selector);
return $el.length && $el.val() !== entry.default; return $el.length && $el.val() !== entry.default;
...@@ -190,11 +187,14 @@ export default class VariableList { ...@@ -190,11 +187,14 @@ export default class VariableList {
getAllData() { getAllData() {
// Ignore the last empty row because we don't want to try persist // Ignore the last empty row because we don't want to try persist
// a blank variable and run into validation problems. // a blank variable and run into validation problems.
const validRows = this.$container.find('.js-row').toArray().slice(0, -1); const validRows = this.$container
.find('.js-row')
.toArray()
.slice(0, -1);
return validRows.map((rowEl) => { return validRows.map(rowEl => {
const resultant = {}; const resultant = {};
Object.keys(this.inputMap).forEach((name) => { Object.keys(this.inputMap).forEach(name => {
const entry = this.inputMap[name]; const entry = this.inputMap[name];
const $input = $(rowEl).find(entry.selector); const $input = $(rowEl).find(entry.selector);
if ($input.length) { if ($input.length) {
...@@ -207,11 +207,16 @@ export default class VariableList { ...@@ -207,11 +207,16 @@ export default class VariableList {
} }
getEnvironmentValues() { getEnvironmentValues() {
const valueMap = this.$container.find(this.inputMap.environment_scope.selector).toArray() const valueMap = this.$container
.reduce((prevValueMap, envInput) => ({ .find(this.inputMap.environment_scope.selector)
...prevValueMap, .toArray()
[envInput.value]: envInput.value, .reduce(
}), {}); (prevValueMap, envInput) => ({
...prevValueMap,
[envInput.value]: envInput.value,
}),
{},
);
return Object.keys(valueMap).map(createEnvironmentItem); return Object.keys(valueMap).map(createEnvironmentItem);
} }
......
...@@ -2,10 +2,7 @@ import $ from 'jquery'; ...@@ -2,10 +2,7 @@ import $ from 'jquery';
import VariableList from './ci_variable_list'; import VariableList from './ci_variable_list';
// Used for the variable list on scheduled pipeline edit page // Used for the variable list on scheduled pipeline edit page
export default function setupNativeFormVariableList({ export default function setupNativeFormVariableList({ container, formField = 'variables' }) {
container,
formField = 'variables',
}) {
const $container = $(container); const $container = $(container);
const variableList = new VariableList({ const variableList = new VariableList({
......
...@@ -76,12 +76,8 @@ export default class ClusterStore { ...@@ -76,12 +76,8 @@ export default class ClusterStore {
this.state.status = serverState.status; this.state.status = serverState.status;
this.state.statusReason = serverState.status_reason; this.state.statusReason = serverState.status_reason;
serverState.applications.forEach((serverAppEntry) => { serverState.applications.forEach(serverAppEntry => {
const { const { name: appId, status, status_reason: statusReason } = serverAppEntry;
name: appId,
status,
status_reason: statusReason,
} = serverAppEntry;
this.state.applications[appId] = { this.state.applications[appId] = {
...(this.state.applications[appId] || {}), ...(this.state.applications[appId] || {}),
......
...@@ -24,36 +24,44 @@ class CommentTypeToggle { ...@@ -24,36 +24,44 @@ class CommentTypeToggle {
setConfig() { setConfig() {
const config = { const config = {
InputSetter: [{ InputSetter: [
input: this.noteTypeInput, {
valueAttribute: 'data-value', input: this.noteTypeInput,
}, valueAttribute: 'data-value',
{ },
input: this.submitButton, {
valueAttribute: 'data-submit-text', input: this.submitButton,
}], valueAttribute: 'data-submit-text',
},
],
}; };
if (this.closeButton) { if (this.closeButton) {
config.InputSetter.push({ config.InputSetter.push(
input: this.closeButton, {
valueAttribute: 'data-close-text', input: this.closeButton,
}, { valueAttribute: 'data-close-text',
input: this.closeButton, },
valueAttribute: 'data-close-text', {
inputAttribute: 'data-alternative-text', input: this.closeButton,
}); valueAttribute: 'data-close-text',
inputAttribute: 'data-alternative-text',
},
);
} }
if (this.reopenButton) { if (this.reopenButton) {
config.InputSetter.push({ config.InputSetter.push(
input: this.reopenButton, {
valueAttribute: 'data-reopen-text', input: this.reopenButton,
}, { valueAttribute: 'data-reopen-text',
input: this.reopenButton, },
valueAttribute: 'data-reopen-text', {
inputAttribute: 'data-alternative-text', input: this.reopenButton,
}); valueAttribute: 'data-reopen-text',
inputAttribute: 'data-alternative-text',
},
);
} }
return config; return config;
......
...@@ -9,44 +9,60 @@ const viewModes = ['two-up', 'swipe']; ...@@ -9,44 +9,60 @@ const viewModes = ['two-up', 'swipe'];
export default class ImageFile { export default class ImageFile {
constructor(file) { constructor(file) {
this.file = file; this.file = file;
this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) { this.requestImageInfo(
return function(deletedWidth, deletedHeight) { $('.two-up.view .frame.deleted img', this.file),
return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) { (function(_this) {
_this.initViewModes(); return function(deletedWidth, deletedHeight) {
return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(
// Load two-up view after images are loaded width,
// so that we can display the correct width and height information height,
const $images = $('.two-up.view img', _this.file); ) {
_this.initViewModes();
$images.waitForImages(function() {
_this.initView('two-up'); // Load two-up view after images are loaded
// so that we can display the correct width and height information
const $images = $('.two-up.view img', _this.file);
$images.waitForImages(function() {
_this.initView('two-up');
});
}); });
}); };
}; })(this),
})(this)); );
} }
initViewModes() { initViewModes() {
const viewMode = viewModes[0]; const viewMode = viewModes[0];
$('.view-modes', this.file).removeClass('hide'); $('.view-modes', this.file).removeClass('hide');
$('.view-modes-menu', this.file).on('click', 'li', (function(_this) { $('.view-modes-menu', this.file).on(
return function(event) { 'click',
if (!$(event.currentTarget).hasClass('active')) { 'li',
return _this.activateViewMode(event.currentTarget.className); (function(_this) {
} return function(event) {
}; if (!$(event.currentTarget).hasClass('active')) {
})(this)); return _this.activateViewMode(event.currentTarget.className);
}
};
})(this),
);
return this.activateViewMode(viewMode); return this.activateViewMode(viewMode);
} }
activateViewMode(viewMode) { activateViewMode(viewMode) {
$('.view-modes-menu li', this.file).removeClass('active').filter("." + viewMode).addClass('active'); $('.view-modes-menu li', this.file)
return $(".view:visible:not(." + viewMode + ")", this.file).fadeOut(200, (function(_this) { .removeClass('active')
return function() { .filter('.' + viewMode)
$(".view." + viewMode, _this.file).fadeIn(200); .addClass('active');
return _this.initView(viewMode); return $('.view:visible:not(.' + viewMode + ')', this.file).fadeOut(
}; 200,
})(this)); (function(_this) {
return function() {
$('.view.' + viewMode, _this.file).fadeIn(200);
return _this.initView(viewMode);
};
})(this),
);
} }
initView(viewMode) { initView(viewMode) {
...@@ -63,135 +79,154 @@ export default class ImageFile { ...@@ -63,135 +79,154 @@ export default class ImageFile {
$body.css('user-select', 'none'); $body.css('user-select', 'none');
}); });
$body.off('mouseup').off('mousemove').on('mouseup', function() { $body
dragging = false; .off('mouseup')
$body.css('user-select', ''); .off('mousemove')
}) .on('mouseup', function() {
.on('mousemove', function(e) { dragging = false;
var left; $body.css('user-select', '');
if (!dragging) return; })
.on('mousemove', function(e) {
left = e.pageX - ($offsetEl.offset().left + padding); var left;
if (!dragging) return;
callback(e, left);
}); left = e.pageX - ($offsetEl.offset().left + padding);
callback(e, left);
});
} }
prepareFrames(view) { prepareFrames(view) {
var maxHeight, maxWidth; var maxHeight, maxWidth;
maxWidth = 0; maxWidth = 0;
maxHeight = 0; maxHeight = 0;
$('.frame', view).each((function(_this) { $('.frame', view)
return function(index, frame) { .each(
var height, width; (function(_this) {
width = $(frame).width(); return function(index, frame) {
height = $(frame).height(); var height, width;
maxWidth = width > maxWidth ? width : maxWidth; width = $(frame).width();
return maxHeight = height > maxHeight ? height : maxHeight; height = $(frame).height();
}; maxWidth = width > maxWidth ? width : maxWidth;
})(this)).css({ return (maxHeight = height > maxHeight ? height : maxHeight);
width: maxWidth, };
height: maxHeight })(this),
}); )
.css({
width: maxWidth,
height: maxHeight,
});
return [maxWidth, maxHeight]; return [maxWidth, maxHeight];
} }
views = { views = {
'two-up': function() { 'two-up': function() {
return $('.two-up.view .wrap', this.file).each((function(_this) { return $('.two-up.view .wrap', this.file).each(
return function(index, wrap) { (function(_this) {
$('img', wrap).each(function() { return function(index, wrap) {
var currentWidth; $('img', wrap).each(function() {
currentWidth = $(this).width(); var currentWidth;
if (currentWidth > availWidth / 2) { currentWidth = $(this).width();
return $(this).width(availWidth / 2); if (currentWidth > availWidth / 2) {
} return $(this).width(availWidth / 2);
}); }
return _this.requestImageInfo($('img', wrap), function(width, height) { });
$('.image-info .meta-width', wrap).text(width + "px"); return _this.requestImageInfo($('img', wrap), function(width, height) {
$('.image-info .meta-height', wrap).text(height + "px"); $('.image-info .meta-width', wrap).text(width + 'px');
return $('.image-info', wrap).removeClass('hide'); $('.image-info .meta-height', wrap).text(height + 'px');
}); return $('.image-info', wrap).removeClass('hide');
}; });
})(this)); };
})(this),
);
}, },
'swipe': function() { swipe() {
var maxHeight, maxWidth; var maxHeight, maxWidth;
maxWidth = 0; maxWidth = 0;
maxHeight = 0; maxHeight = 0;
return $('.swipe.view', this.file).each((function(_this) { return $('.swipe.view', this.file).each(
return function(index, view) { (function(_this) {
var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref; return function(index, view) {
ref = _this.prepareFrames(view), [maxWidth, maxHeight] = ref; var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref;
$swipeFrame = $('.swipe-frame', view); (ref = _this.prepareFrames(view)), ([maxWidth, maxHeight] = ref);
$swipeWrap = $('.swipe-wrap', view); $swipeFrame = $('.swipe-frame', view);
$swipeBar = $('.swipe-bar', view); $swipeWrap = $('.swipe-wrap', view);
$swipeBar = $('.swipe-bar', view);
$swipeFrame.css({
width: maxWidth + 16, $swipeFrame.css({
height: maxHeight + 28 width: maxWidth + 16,
}); height: maxHeight + 28,
$swipeWrap.css({ });
width: maxWidth + 1, $swipeWrap.css({
height: maxHeight + 2 width: maxWidth + 1,
}); height: maxHeight + 2,
// Set swipeBar left position to match image frame });
$swipeBar.css({ // Set swipeBar left position to match image frame
left: 1 $swipeBar.css({
}); left: 1,
});
wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10);
wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10);
_this.initDraggable($swipeBar, wrapPadding, function(e, left) {
if (left > 0 && left < $swipeFrame.width() - (wrapPadding * 2)) { _this.initDraggable($swipeBar, wrapPadding, function(e, left) {
$swipeWrap.width((maxWidth + 1) - left); if (left > 0 && left < $swipeFrame.width() - wrapPadding * 2) {
$swipeBar.css('left', left); $swipeWrap.width(maxWidth + 1 - left);
} $swipeBar.css('left', left);
}); }
}; });
})(this)); };
})(this),
);
}, },
'onion-skin': function() { 'onion-skin': function() {
var dragTrackWidth, maxHeight, maxWidth; var dragTrackWidth, maxHeight, maxWidth;
maxWidth = 0; maxWidth = 0;
maxHeight = 0; maxHeight = 0;
dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width(); dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width();
return $('.onion-skin.view', this.file).each((function(_this) { return $('.onion-skin.view', this.file).each(
return function(index, view) { (function(_this) {
var $frame, $track, $dragger, $frameAdded, framePadding, ref, dragging = false; return function(index, view) {
ref = _this.prepareFrames(view), [maxWidth, maxHeight] = ref; var $frame,
$frame = $('.onion-skin-frame', view); $track,
$frameAdded = $('.frame.added', view); $dragger,
$track = $('.drag-track', view); $frameAdded,
$dragger = $('.dragger', $track); framePadding,
ref,
$frame.css({ dragging = false;
width: maxWidth + 16, (ref = _this.prepareFrames(view)), ([maxWidth, maxHeight] = ref);
height: maxHeight + 28 $frame = $('.onion-skin-frame', view);
}); $frameAdded = $('.frame.added', view);
$('.swipe-wrap', view).css({ $track = $('.drag-track', view);
width: maxWidth + 1, $dragger = $('.dragger', $track);
height: maxHeight + 2
}); $frame.css({
$dragger.css({ width: maxWidth + 16,
left: dragTrackWidth height: maxHeight + 28,
}); });
$('.swipe-wrap', view).css({
$frameAdded.css('opacity', 1); width: maxWidth + 1,
framePadding = parseInt($frameAdded.css('right').replace('px', ''), 10); height: maxHeight + 2,
});
_this.initDraggable($dragger, framePadding, function(e, left) { $dragger.css({
var opacity = left / dragTrackWidth; left: dragTrackWidth,
});
if (opacity >= 0 && opacity <= 1) {
$dragger.css('left', left); $frameAdded.css('opacity', 1);
$frameAdded.css('opacity', opacity); framePadding = parseInt($frameAdded.css('right').replace('px', ''), 10);
}
}); _this.initDraggable($dragger, framePadding, function(e, left) {
}; var opacity = left / dragTrackWidth;
})(this));
} if (opacity >= 0 && opacity <= 1) {
} $dragger.css('left', left);
$frameAdded.css('opacity', opacity);
}
});
};
})(this),
);
},
};
requestImageInfo(img, callback) { requestImageInfo(img, callback) {
const domImg = img.get(0); const domImg = img.get(0);
...@@ -199,11 +234,14 @@ export default class ImageFile { ...@@ -199,11 +234,14 @@ export default class ImageFile {
if (domImg.complete) { if (domImg.complete) {
return callback.call(this, domImg.naturalWidth, domImg.naturalHeight); return callback.call(this, domImg.naturalWidth, domImg.naturalHeight);
} else { } else {
return img.on('load', (function(_this) { return img.on(
return function() { 'load',
return callback.call(_this, domImg.naturalWidth, domImg.naturalHeight); (function(_this) {
}; return function() {
})(this)); return callback.call(_this, domImg.naturalWidth, domImg.naturalHeight);
};
})(this),
);
} }
} }
} }
......
...@@ -19,11 +19,13 @@ export default () => { ...@@ -19,11 +19,13 @@ export default () => {
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view'); const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
if (pipelineTableViewEl) { if (pipelineTableViewEl) {
// Update MR and Commits tabs // Update MR and Commits tabs
pipelineTableViewEl.addEventListener('update-pipelines-count', (event) => { pipelineTableViewEl.addEventListener('update-pipelines-count', event => {
if (event.detail.pipelines && if (
event.detail.pipelines &&
event.detail.pipelines.count && event.detail.pipelines.count &&
event.detail.pipelines.count.all) { event.detail.pipelines.count.all
) {
const badge = document.querySelector('.js-pipelines-mr-count'); const badge = document.querySelector('.js-pipelines-mr-count');
badge.textContent = event.detail.pipelines.count.all; badge.textContent = event.detail.pipelines.count.all;
......
<script> <script>
import PipelinesService from '../../pipelines/services/pipelines_service'; import PipelinesService from '../../pipelines/services/pipelines_service';
import PipelineStore from '../../pipelines/stores/pipelines_store'; import PipelineStore from '../../pipelines/stores/pipelines_store';
import pipelinesMixin from '../../pipelines/mixins/pipelines'; import pipelinesMixin from '../../pipelines/mixins/pipelines';
export default { export default {
mixins: [ mixins: [pipelinesMixin],
pipelinesMixin, props: {
], endpoint: {
props: { type: String,
endpoint: { required: true,
type: String,
required: true,
},
helpPagePath: {
type: String,
required: true,
},
autoDevopsHelpPath: {
type: String,
required: true,
},
errorStateSvgPath: {
type: String,
required: true,
},
viewType: {
type: String,
required: false,
default: 'child',
},
}, },
helpPagePath: {
type: String,
required: true,
},
autoDevopsHelpPath: {
type: String,
required: true,
},
errorStateSvgPath: {
type: String,
required: true,
},
viewType: {
type: String,
required: false,
default: 'child',
},
},
data() { data() {
const store = new PipelineStore(); const store = new PipelineStore();
return { return {
store, store,
state: store.state, state: store.state,
}; };
}, },
computed: { computed: {
shouldRenderTable() { shouldRenderTable() {
return !this.isLoading && return !this.isLoading && this.state.pipelines.length > 0 && !this.hasError;
this.state.pipelines.length > 0 &&
!this.hasError;
},
shouldRenderErrorState() {
return this.hasError && !this.isLoading;
},
}, },
created() { shouldRenderErrorState() {
this.service = new PipelinesService(this.endpoint); return this.hasError && !this.isLoading;
}, },
methods: { },
successCallback(resp) { created() {
// depending of the endpoint the response can either bring a `pipelines` key or not. this.service = new PipelinesService(this.endpoint);
const pipelines = resp.data.pipelines || resp.data; },
this.setCommonData(pipelines); methods: {
successCallback(resp) {
// depending of the endpoint the response can either bring a `pipelines` key or not.
const pipelines = resp.data.pipelines || resp.data;
this.setCommonData(pipelines);
const updatePipelinesEvent = new CustomEvent('update-pipelines-count', { const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
detail: { detail: {
pipelines: resp.data, pipelines: resp.data,
}, },
}); });
// notifiy to update the count in tabs // notifiy to update the count in tabs
if (this.$el.parentElement) { if (this.$el.parentElement) {
this.$el.parentElement.dispatchEvent(updatePipelinesEvent); this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
} }
},
}, },
}; },
};
</script> </script>
<template> <template>
<div class="content-list pipelines"> <div class="content-list pipelines">
......
...@@ -50,7 +50,7 @@ export function createContent(mergeRequests) { ...@@ -50,7 +50,7 @@ export function createContent(mergeRequests) {
if (mergeRequests.length === 0) { if (mergeRequests.length === 0) {
$content.text(s__('Commits|No related merge requests found')); $content.text(s__('Commits|No related merge requests found'));
} else { } else {
mergeRequests.forEach((mergeRequest) => { mergeRequests.forEach(mergeRequest => {
const $header = createHeader($content.children().length, mergeRequests.length); const $header = createHeader($content.children().length, mergeRequests.length);
const $item = createItem(mergeRequest); const $item = createItem(mergeRequest);
$content.append($header); $content.append($header);
...@@ -64,8 +64,9 @@ export function createContent(mergeRequests) { ...@@ -64,8 +64,9 @@ export function createContent(mergeRequests) {
export function fetchCommitMergeRequests() { export function fetchCommitMergeRequests() {
const $container = $('.merge-requests'); const $container = $('.merge-requests');
axios.get($container.data('projectCommitPath')) axios
.then((response) => { .get($container.data('projectCommitPath'))
.then(response => {
const $content = createContent(response.data); const $content = createContent(response.data);
$container.html($content); $container.html($content);
......
...@@ -32,22 +32,31 @@ export default class CommitsList { ...@@ -32,22 +32,31 @@ export default class CommitsList {
if (search === this.lastSearch) return Promise.resolve(); if (search === this.lastSearch) return Promise.resolve();
const commitsUrl = `${form.attr('action')}?${form.serialize()}`; const commitsUrl = `${form.attr('action')}?${form.serialize()}`;
this.content.fadeTo('fast', 0.5); this.content.fadeTo('fast', 0.5);
const params = form.serializeArray().reduce((acc, obj) => Object.assign(acc, { const params = form.serializeArray().reduce(
[obj.name]: obj.value, (acc, obj) =>
}), {}); Object.assign(acc, {
[obj.name]: obj.value,
}),
{},
);
return axios.get(form.attr('action'), { return axios
params, .get(form.attr('action'), {
}) params,
})
.then(({ data }) => { .then(({ data }) => {
this.lastSearch = search; this.lastSearch = search;
this.content.html(data.html); this.content.html(data.html);
this.content.fadeTo('fast', 1.0); this.content.fadeTo('fast', 1.0);
// Change url so if user reload a page - search results are saved // Change url so if user reload a page - search results are saved
window.history.replaceState({ window.history.replaceState(
page: commitsUrl, {
}, document.title, commitsUrl); page: commitsUrl,
},
document.title,
commitsUrl,
);
}) })
.catch(() => { .catch(() => {
this.content.fadeTo('fast', 1.0); this.content.fadeTo('fast', 1.0);
...@@ -75,8 +84,15 @@ export default class CommitsList { ...@@ -75,8 +84,15 @@ export default class CommitsList {
processedData = $processedData.not(`li.js-commit-header[data-day='${loadedShownDayFirst}']`); processedData = $processedData.not(`li.js-commit-header[data-day='${loadedShownDayFirst}']`);
// Update commits count in the previous commits header. // Update commits count in the previous commits header.
commitsCount += Number($(processedData).nextUntil('li.js-commit-header').first().find('li.commit').length); commitsCount += Number(
$commitsHeadersLast.find('span.commits-count').text(`${commitsCount} ${pluralize('commit', commitsCount)}`); $(processedData)
.nextUntil('li.js-commit-header')
.first()
.find('li.commit').length,
);
$commitsHeadersLast
.find('span.commits-count')
.text(`${commitsCount} ${pluralize('commit', commitsCount)}`);
} }
localTimeAgo($processedData.find('.js-timeago')); localTimeAgo($processedData.find('.js-timeago'));
......
...@@ -5,6 +5,14 @@ import 'bootstrap'; ...@@ -5,6 +5,14 @@ import 'bootstrap';
// custom jQuery functions // custom jQuery functions
$.fn.extend({ $.fn.extend({
disable() { return $(this).prop('disabled', true).addClass('disabled'); }, disable() {
enable() { return $(this).prop('disabled', false).removeClass('disabled'); }, return $(this)
.prop('disabled', true)
.addClass('disabled');
},
enable() {
return $(this)
.prop('disabled', false)
.removeClass('disabled');
},
}); });
...@@ -13,19 +13,23 @@ function openConfirmDangerModal($form, text) { ...@@ -13,19 +13,23 @@ function openConfirmDangerModal($form, text) {
$submit.disable(); $submit.disable();
$input.focus(); $input.focus();
$('.js-confirm-danger-input').off('input').on('input', function handleInput() { $('.js-confirm-danger-input')
const confirmText = rstrip($(this).val()); .off('input')
if (confirmText === confirmTextMatch) { .on('input', function handleInput() {
$submit.enable(); const confirmText = rstrip($(this).val());
} else { if (confirmText === confirmTextMatch) {
$submit.disable(); $submit.enable();
} } else {
}); $submit.disable();
$('.js-confirm-danger-submit').off('click').on('click', () => $form.submit()); }
});
$('.js-confirm-danger-submit')
.off('click')
.on('click', () => $form.submit());
} }
export default function initConfirmDangerModal() { export default function initConfirmDangerModal() {
$(document).on('click', '.js-confirm-danger', (e) => { $(document).on('click', '.js-confirm-danger', e => {
e.preventDefault(); e.preventDefault();
const $btn = $(e.target); const $btn = $(e.target);
const $form = $btn.closest('form'); const $form = $btn.closest('form');
......
...@@ -20,8 +20,11 @@ export default class ContextualSidebar { ...@@ -20,8 +20,11 @@ export default class ContextualSidebar {
} }
bindEvents() { bindEvents() {
document.addEventListener('click', (e) => { document.addEventListener('click', e => {
if (!e.target.closest('.nav-sidebar') && (bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md')) { if (
!e.target.closest('.nav-sidebar') &&
(bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md')
) {
this.toggleCollapsedSidebar(true); this.toggleCollapsedSidebar(true);
} }
}); });
......
...@@ -36,7 +36,7 @@ export default class CreateItemDropdown { ...@@ -36,7 +36,7 @@ export default class CreateItemDropdown {
}, },
selectable: true, selectable: true,
toggleLabel(selected) { toggleLabel(selected) {
return (selected && 'id' in selected) ? _.escape(selected.title) : this.defaultToggleLabel; return selected && 'id' in selected ? _.escape(selected.title) : this.defaultToggleLabel;
}, },
fieldName: this.fieldName, fieldName: this.fieldName,
text(item) { text(item) {
...@@ -46,7 +46,7 @@ export default class CreateItemDropdown { ...@@ -46,7 +46,7 @@ export default class CreateItemDropdown {
return _.escape(item.id); return _.escape(item.id);
}, },
onFilter: this.toggleCreateNewButton.bind(this), onFilter: this.toggleCreateNewButton.bind(this),
clicked: (options) => { clicked: options => {
options.e.preventDefault(); options.e.preventDefault();
this.onSelect(); this.onSelect();
}, },
...@@ -77,9 +77,8 @@ export default class CreateItemDropdown { ...@@ -77,9 +77,8 @@ export default class CreateItemDropdown {
getData(term, callback) { getData(term, callback) {
this.getDataOption(term, (data = []) => { this.getDataOption(term, (data = []) => {
// Ensure the selected item isn't already in the data to avoid duplicates // Ensure the selected item isn't already in the data to avoid duplicates
const alreadyHasSelectedItem = this.selectedItem && data.some(item => const alreadyHasSelectedItem =
item.id === this.selectedItem.id, this.selectedItem && data.some(item => item.id === this.selectedItem.id);
);
let uniqueData = data; let uniqueData = data;
if (!alreadyHasSelectedItem) { if (!alreadyHasSelectedItem) {
...@@ -106,9 +105,7 @@ export default class CreateItemDropdown { ...@@ -106,9 +105,7 @@ export default class CreateItemDropdown {
if (newValue) { if (newValue) {
this.selectedItem = this.createNewItemFromValue(newValue); this.selectedItem = this.createNewItemFromValue(newValue);
this.$dropdownContainer this.$dropdownContainer.find('.js-dropdown-create-new-item code').text(newValue);
.find('.js-dropdown-create-new-item code')
.text(newValue);
} }
this.toggleFooter(!newValue); this.toggleFooter(!newValue);
......
...@@ -37,7 +37,7 @@ export default class CreateLabelDropdown { ...@@ -37,7 +37,7 @@ export default class CreateLabelDropdown {
addBinding() { addBinding() {
const self = this; const self = this;
this.$colorSuggestions.on('click', function (e) { this.$colorSuggestions.on('click', function(e) {
const $this = $(this); const $this = $(this);
self.addColorValue(e, $this); self.addColorValue(e, $this);
}); });
...@@ -47,7 +47,7 @@ export default class CreateLabelDropdown { ...@@ -47,7 +47,7 @@ export default class CreateLabelDropdown {
this.$dropdownBack.on('click', this.resetForm.bind(this)); this.$dropdownBack.on('click', this.resetForm.bind(this));
this.$cancelButton.on('click', function (e) { this.$cancelButton.on('click', function(e) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
...@@ -79,13 +79,9 @@ export default class CreateLabelDropdown { ...@@ -79,13 +79,9 @@ export default class CreateLabelDropdown {
} }
resetForm() { resetForm() {
this.$newLabelField this.$newLabelField.val('').trigger('change');
.val('')
.trigger('change');
this.$newColorField this.$newColorField.val('').trigger('change');
.val('')
.trigger('change');
this.$colorPreview this.$colorPreview
.css('background-color', '') .css('background-color', '')
...@@ -97,31 +93,34 @@ export default class CreateLabelDropdown { ...@@ -97,31 +93,34 @@ export default class CreateLabelDropdown {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
Api.newLabel(this.namespacePath, this.projectPath, { Api.newLabel(
title: this.$newLabelField.val(), this.namespacePath,
color: this.$newColorField.val(), this.projectPath,
}, (label) => { {
this.$newLabelCreateButton.enable(); title: this.$newLabelField.val(),
color: this.$newColorField.val(),
if (label.message) { },
let errors; label => {
this.$newLabelCreateButton.enable();
if (typeof label.message === 'string') {
errors = label.message; if (label.message) {
let errors;
if (typeof label.message === 'string') {
errors = label.message;
} else {
errors = Object.keys(label.message)
.map(key => `${humanize(key)} ${label.message[key].join(', ')}`)
.join('<br/>');
}
this.$newLabelError.html(errors).show();
} else { } else {
errors = Object.keys(label.message).map(key => this.$dropdownBack.trigger('click');
`${humanize(key)} ${label.message[key].join(', ')}`,
).join('<br/>');
}
this.$newLabelError $(document).trigger('created.label', label);
.html(errors) }
.show(); },
} else { );
this.$dropdownBack.trigger('click');
$(document).trigger('created.label', label);
}
});
} }
} }
...@@ -95,8 +95,10 @@ export default { ...@@ -95,8 +95,10 @@ export default {
.catch(() => new Flash(s__('DeployKeys|Error enabling deploy key'))); .catch(() => new Flash(s__('DeployKeys|Error enabling deploy key')));
}, },
disableKey(deployKey, callback) { disableKey(deployKey, callback) {
// eslint-disable-next-line no-alert if (
if (window.confirm(s__('DeployKeys|You are going to remove this deploy key. Are you sure?'))) { // eslint-disable-next-line no-alert
window.confirm(s__('DeployKeys|You are going to remove this deploy key. Are you sure?'))
) {
this.service this.service
.disableKey(deployKey.id) .disableKey(deployKey.id)
.then(this.fetchKeys) .then(this.fetchKeys)
......
...@@ -8,17 +8,14 @@ export default class DeployKeysService { ...@@ -8,17 +8,14 @@ export default class DeployKeysService {
} }
getKeys() { getKeys() {
return this.axios.get() return this.axios.get().then(response => response.data);
.then(response => response.data);
} }
enableKey(id) { enableKey(id) {
return this.axios.put(`${id}/enable`) return this.axios.put(`${id}/enable`).then(response => response.data);
.then(response => response.data);
} }
disableKey(id) { disableKey(id) {
return this.axios.put(`${id}/disable`) return this.axios.put(`${id}/disable`).then(response => response.data);
.then(response => response.data);
} }
} }
...@@ -21,9 +21,12 @@ export default class Diff { ...@@ -21,9 +21,12 @@ export default class Diff {
}); });
const tab = document.getElementById('diffs'); const tab = document.getElementById('diffs');
if (!tab || (tab && tab.dataset && tab.dataset.isLocked !== '')) FilesCommentButton.init($diffFile); if (!tab || (tab && tab.dataset && tab.dataset.isLocked !== ''))
FilesCommentButton.init($diffFile);
const firstFile = $('.files').first().get(0); const firstFile = $('.files')
.first()
.get(0);
const canCreateNote = firstFile && firstFile.hasAttribute('data-can-create-note'); const canCreateNote = firstFile && firstFile.hasAttribute('data-can-create-note');
$diffFile.each((index, file) => imageDiffHelper.initImageDiff(file, canCreateNote)); $diffFile.each((index, file) => imageDiffHelper.initImageDiff(file, canCreateNote));
...@@ -73,9 +76,10 @@ export default class Diff { ...@@ -73,9 +76,10 @@ export default class Diff {
const view = file.data('view'); const view = file.data('view');
const params = { since, to, bottom, offset, unfold, view }; const params = { since, to, bottom, offset, unfold, view };
axios.get(link, { params }) axios
.then(({ data }) => $target.parent().replaceWith(data)) .get(link, { params })
.catch(() => flash(__('An error occurred while loading diff'))); .then(({ data }) => $target.parent().replaceWith(data))
.catch(() => flash(__('An error occurred while loading diff')));
} }
openAnchoredDiff(cb) { openAnchoredDiff(cb) {
......
...@@ -136,7 +136,7 @@ export default function dropzoneInput(form) { ...@@ -136,7 +136,7 @@ export default function dropzoneInput(form) {
// removeAllFiles(true) stops uploading files (if any) // removeAllFiles(true) stops uploading files (if any)
// and remove them from dropzone files queue. // and remove them from dropzone files queue.
$cancelButton.on('click', (e) => { $cancelButton.on('click', e => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
Dropzone.forElement($formDropzone.get(0)).removeAllFiles(true); Dropzone.forElement($formDropzone.get(0)).removeAllFiles(true);
...@@ -146,8 +146,10 @@ export default function dropzoneInput(form) { ...@@ -146,8 +146,10 @@ export default function dropzoneInput(form) {
// clear dropzone files queue, change status of failed files to undefined, // clear dropzone files queue, change status of failed files to undefined,
// and add that files to the dropzone files queue again. // and add that files to the dropzone files queue again.
// addFile() adds file to dropzone files queue and upload it. // addFile() adds file to dropzone files queue and upload it.
$retryLink.on('click', (e) => { $retryLink.on('click', e => {
const dropzoneInstance = Dropzone.forElement(e.target.closest('.js-main-target-form').querySelector('.div-dropzone')); const dropzoneInstance = Dropzone.forElement(
e.target.closest('.js-main-target-form').querySelector('.div-dropzone'),
);
const failedFiles = dropzoneInstance.files; const failedFiles = dropzoneInstance.files;
e.preventDefault(); e.preventDefault();
...@@ -156,7 +158,7 @@ export default function dropzoneInput(form) { ...@@ -156,7 +158,7 @@ export default function dropzoneInput(form) {
// uploading of files that are being uploaded at the moment. // uploading of files that are being uploaded at the moment.
dropzoneInstance.removeAllFiles(true); dropzoneInstance.removeAllFiles(true);
failedFiles.map((failedFile) => { failedFiles.map(failedFile => {
const file = failedFile; const file = failedFile;
if (file.status === Dropzone.ERROR) { if (file.status === Dropzone.ERROR) {
...@@ -168,7 +170,7 @@ export default function dropzoneInput(form) { ...@@ -168,7 +170,7 @@ export default function dropzoneInput(form) {
}); });
}); });
// eslint-disable-next-line consistent-return // eslint-disable-next-line consistent-return
handlePaste = (event) => { handlePaste = event => {
const pasteEvent = event.originalEvent; const pasteEvent = event.originalEvent;
if (pasteEvent.clipboardData && pasteEvent.clipboardData.items) { if (pasteEvent.clipboardData && pasteEvent.clipboardData.items) {
const image = isImage(pasteEvent); const image = isImage(pasteEvent);
...@@ -182,7 +184,7 @@ export default function dropzoneInput(form) { ...@@ -182,7 +184,7 @@ export default function dropzoneInput(form) {
} }
}; };
isImage = (data) => { isImage = data => {
let i = 0; let i = 0;
while (i < data.clipboardData.items.length) { while (i < data.clipboardData.items.length) {
const item = data.clipboardData.items[i]; const item = data.clipboardData.items[i];
...@@ -203,8 +205,12 @@ export default function dropzoneInput(form) { ...@@ -203,8 +205,12 @@ export default function dropzoneInput(form) {
const caretStart = textarea.selectionStart; const caretStart = textarea.selectionStart;
const caretEnd = textarea.selectionEnd; const caretEnd = textarea.selectionEnd;
const textEnd = $(child).val().length; const textEnd = $(child).val().length;
const beforeSelection = $(child).val().substring(0, caretStart); const beforeSelection = $(child)
const afterSelection = $(child).val().substring(caretEnd, textEnd); .val()
.substring(0, caretStart);
const afterSelection = $(child)
.val()
.substring(caretEnd, textEnd);
$(child).val(beforeSelection + formattedText + afterSelection); $(child).val(beforeSelection + formattedText + afterSelection);
textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length); textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
textarea.style.height = `${textarea.scrollHeight}px`; textarea.style.height = `${textarea.scrollHeight}px`;
...@@ -212,11 +218,11 @@ export default function dropzoneInput(form) { ...@@ -212,11 +218,11 @@ export default function dropzoneInput(form) {
return formTextarea.trigger('input'); return formTextarea.trigger('input');
}; };
addFileToForm = (path) => { addFileToForm = path => {
$(form).append(`<input type="hidden" name="files[]" value="${_.escape(path)}">`); $(form).append(`<input type="hidden" name="files[]" value="${_.escape(path)}">`);
}; };
getFilename = (e) => { getFilename = e => {
let value; let value;
if (window.clipboardData && window.clipboardData.getData) { if (window.clipboardData && window.clipboardData.getData) {
value = window.clipboardData.getData('Text'); value = window.clipboardData.getData('Text');
...@@ -231,7 +237,7 @@ export default function dropzoneInput(form) { ...@@ -231,7 +237,7 @@ export default function dropzoneInput(form) {
const closeSpinner = () => $uploadingProgressContainer.addClass('hide'); const closeSpinner = () => $uploadingProgressContainer.addClass('hide');
const showError = (message) => { const showError = message => {
$uploadingErrorContainer.removeClass('hide'); $uploadingErrorContainer.removeClass('hide');
$uploadingErrorMessage.html(message); $uploadingErrorMessage.html(message);
}; };
...@@ -252,14 +258,15 @@ export default function dropzoneInput(form) { ...@@ -252,14 +258,15 @@ export default function dropzoneInput(form) {
showSpinner(); showSpinner();
closeAlertMessage(); closeAlertMessage();
axios.post(uploadsPath, formData) axios
.post(uploadsPath, formData)
.then(({ data }) => { .then(({ data }) => {
const md = data.link.markdown; const md = data.link.markdown;
insertToTextArea(filename, md); insertToTextArea(filename, md);
closeSpinner(); closeSpinner();
}) })
.catch((e) => { .catch(e => {
showError(e.response.data.message); showError(e.response.data.message);
closeSpinner(); closeSpinner();
}); });
...@@ -267,7 +274,8 @@ export default function dropzoneInput(form) { ...@@ -267,7 +274,8 @@ export default function dropzoneInput(form) {
updateAttachingMessage = (files, messageContainer) => { updateAttachingMessage = (files, messageContainer) => {
let attachingMessage; let attachingMessage;
const filesCount = files.filter(file => file.status === 'uploading' || file.status === 'queued').length; const filesCount = files.filter(file => file.status === 'uploading' || file.status === 'queued')
.length;
// Dinamycally change uploading files text depending on files number in // Dinamycally change uploading files text depending on files number in
// dropzone files queue. // dropzone files queue.
...@@ -282,7 +290,10 @@ export default function dropzoneInput(form) { ...@@ -282,7 +290,10 @@ export default function dropzoneInput(form) {
form.find('.markdown-selector').click(function onMarkdownClick(e) { form.find('.markdown-selector').click(function onMarkdownClick(e) {
e.preventDefault(); e.preventDefault();
$(this).closest('.gfm-form').find('.div-dropzone').click(); $(this)
.closest('.gfm-form')
.find('.div-dropzone')
.click();
formTextarea.focus(); formTextarea.focus();
}); });
......
...@@ -13,9 +13,11 @@ const rainbowCodePoint = 127752; // parseInt('1F308', 16) ...@@ -13,9 +13,11 @@ const rainbowCodePoint = 127752; // parseInt('1F308', 16)
function isRainbowFlagEmoji(emojiUnicode) { function isRainbowFlagEmoji(emojiUnicode) {
const characters = Array.from(emojiUnicode); const characters = Array.from(emojiUnicode);
// Length 4 because flags are made of 2 characters which are surrogate pairs // Length 4 because flags are made of 2 characters which are surrogate pairs
return emojiUnicode.length === 4 && return (
emojiUnicode.length === 4 &&
characters[0].codePointAt(0) === baseFlagCodePoint && characters[0].codePointAt(0) === baseFlagCodePoint &&
characters[1].codePointAt(0) === rainbowCodePoint; characters[1].codePointAt(0) === rainbowCodePoint
);
} }
// Chrome <57 renders keycaps oddly // Chrome <57 renders keycaps oddly
...@@ -26,22 +28,28 @@ function isKeycapEmoji(emojiUnicode) { ...@@ -26,22 +28,28 @@ function isKeycapEmoji(emojiUnicode) {
} }
// Check for a skin tone variation emoji which aren't always supported // Check for a skin tone variation emoji which aren't always supported
const tone1 = 127995;// parseInt('1F3FB', 16) const tone1 = 127995; // parseInt('1F3FB', 16)
const tone5 = 127999;// parseInt('1F3FF', 16) const tone5 = 127999; // parseInt('1F3FF', 16)
function isSkinToneComboEmoji(emojiUnicode) { function isSkinToneComboEmoji(emojiUnicode) {
return emojiUnicode.length > 2 && Array.from(emojiUnicode).some((char) => { return (
const cp = char.codePointAt(0); emojiUnicode.length > 2 &&
return cp >= tone1 && cp <= tone5; Array.from(emojiUnicode).some(char => {
}); const cp = char.codePointAt(0);
return cp >= tone1 && cp <= tone5;
})
);
} }
// macOS supports most skin tone emoji's but // macOS supports most skin tone emoji's but
// doesn't support the skin tone versions of horse racing // doesn't support the skin tone versions of horse racing
const horseRacingCodePoint = 127943;// parseInt('1F3C7', 16) const horseRacingCodePoint = 127943; // parseInt('1F3C7', 16)
function isHorceRacingSkinToneComboEmoji(emojiUnicode) { function isHorceRacingSkinToneComboEmoji(emojiUnicode) {
const firstCharacter = Array.from(emojiUnicode)[0]; const firstCharacter = Array.from(emojiUnicode)[0];
return firstCharacter && firstCharacter.codePointAt(0) === horseRacingCodePoint && return (
isSkinToneComboEmoji(emojiUnicode); firstCharacter &&
firstCharacter.codePointAt(0) === horseRacingCodePoint &&
isSkinToneComboEmoji(emojiUnicode)
);
} }
// Check for `family_*`, `kiss_*`, `couple_*` // Check for `family_*`, `kiss_*`, `couple_*`
...@@ -52,7 +60,7 @@ const personEndCodePoint = 128105; // parseInt('1F469', 16) ...@@ -52,7 +60,7 @@ const personEndCodePoint = 128105; // parseInt('1F469', 16)
function isPersonZwjEmoji(emojiUnicode) { function isPersonZwjEmoji(emojiUnicode) {
let hasPersonEmoji = false; let hasPersonEmoji = false;
let hasZwj = false; let hasZwj = false;
Array.from(emojiUnicode).forEach((character) => { Array.from(emojiUnicode).forEach(character => {
const cp = character.codePointAt(0); const cp = character.codePointAt(0);
if (cp === zwj) { if (cp === zwj) {
hasZwj = true; hasZwj = true;
...@@ -80,10 +88,7 @@ function checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) { ...@@ -80,10 +88,7 @@ function checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) {
// in `isEmojiUnicodeSupported` logic // in `isEmojiUnicodeSupported` logic
function checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) { function checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) {
const isSkinToneResult = isSkinToneComboEmoji(emojiUnicode); const isSkinToneResult = isSkinToneComboEmoji(emojiUnicode);
return ( return (unicodeSupportMap.skinToneModifier && isSkinToneResult) || !isSkinToneResult;
(unicodeSupportMap.skinToneModifier && isSkinToneResult) ||
!isSkinToneResult
);
} }
// Helper func so we don't have to run `isHorceRacingSkinToneComboEmoji` twice // Helper func so we don't have to run `isHorceRacingSkinToneComboEmoji` twice
...@@ -91,8 +96,7 @@ function checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) { ...@@ -91,8 +96,7 @@ function checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) {
function checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) { function checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) {
const isHorseRacingSkinToneResult = isHorceRacingSkinToneComboEmoji(emojiUnicode); const isHorseRacingSkinToneResult = isHorceRacingSkinToneComboEmoji(emojiUnicode);
return ( return (
(unicodeSupportMap.horseRacing && isHorseRacingSkinToneResult) || (unicodeSupportMap.horseRacing && isHorseRacingSkinToneResult) || !isHorseRacingSkinToneResult
!isHorseRacingSkinToneResult
); );
} }
...@@ -100,10 +104,7 @@ function checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnico ...@@ -100,10 +104,7 @@ function checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnico
// in `isEmojiUnicodeSupported` logic // in `isEmojiUnicodeSupported` logic
function checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode) { function checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode) {
const isPersonZwjResult = isPersonZwjEmoji(emojiUnicode); const isPersonZwjResult = isPersonZwjEmoji(emojiUnicode);
return ( return (unicodeSupportMap.personZwj && isPersonZwjResult) || !isPersonZwjResult;
(unicodeSupportMap.personZwj && isPersonZwjResult) ||
!isPersonZwjResult
);
} }
// Takes in a support map and determines whether // Takes in a support map and determines whether
...@@ -111,16 +112,20 @@ function checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode) { ...@@ -111,16 +112,20 @@ function checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode) {
// //
// Combines all the edge case tests into a one-stop shop method // Combines all the edge case tests into a one-stop shop method
function isEmojiUnicodeSupported(unicodeSupportMap = {}, emojiUnicode, unicodeVersion) { function isEmojiUnicodeSupported(unicodeSupportMap = {}, emojiUnicode, unicodeVersion) {
const isOlderThanChrome57 = unicodeSupportMap.meta && unicodeSupportMap.meta.isChrome && const isOlderThanChrome57 =
unicodeSupportMap.meta &&
unicodeSupportMap.meta.isChrome &&
unicodeSupportMap.meta.chromeVersion < 57; unicodeSupportMap.meta.chromeVersion < 57;
// For comments about each scenario, see the comments above each individual respective function // For comments about each scenario, see the comments above each individual respective function
return unicodeSupportMap[unicodeVersion] && return (
unicodeSupportMap[unicodeVersion] &&
!(isOlderThanChrome57 && isKeycapEmoji(emojiUnicode)) && !(isOlderThanChrome57 && isKeycapEmoji(emojiUnicode)) &&
checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) && checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) &&
checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) && checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) &&
checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) && checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) &&
checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode); checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode)
);
} }
export { export {
......
...@@ -2,7 +2,7 @@ import $ from 'jquery'; ...@@ -2,7 +2,7 @@ import $ from 'jquery';
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
export default () => { export default () => {
$('.js-experiment-feature-toggle').on('change', (e) => { $('.js-experiment-feature-toggle').on('change', e => {
const el = e.target; const el = e.target;
Cookies.set(el.name, el.value, { Cookies.set(el.name, el.value, {
......
...@@ -25,13 +25,15 @@ export default { ...@@ -25,13 +25,15 @@ export default {
if (!this.userCanCreateNote) { if (!this.userCanCreateNote) {
// data-can-create-note is an empty string when true, otherwise undefined // data-can-create-note is an empty string when true, otherwise undefined
this.userCanCreateNote = $diffFile.closest(DIFF_CONTAINER_SELECTOR).data('canCreateNote') === ''; this.userCanCreateNote =
$diffFile.closest(DIFF_CONTAINER_SELECTOR).data('canCreateNote') === '';
} }
this.isParallelView = Cookies.get('diff_view') === 'parallel'; this.isParallelView = Cookies.get('diff_view') === 'parallel';
if (this.userCanCreateNote) { if (this.userCanCreateNote) {
$diffFile.on('mouseover', LINE_COLUMN_CLASSES, e => this.showButton(this.isParallelView, e)) $diffFile
.on('mouseover', LINE_COLUMN_CLASSES, e => this.showButton(this.isParallelView, e))
.on('mouseleave', LINE_COLUMN_CLASSES, e => this.hideButton(this.isParallelView, e)); .on('mouseleave', LINE_COLUMN_CLASSES, e => this.hideButton(this.isParallelView, e));
} }
}, },
...@@ -64,9 +66,11 @@ export default { ...@@ -64,9 +66,11 @@ export default {
}, },
validateButtonParent(buttonParentElement) { validateButtonParent(buttonParentElement) {
return !buttonParentElement.classList.contains(EMPTY_CELL_CLASS) && return (
!buttonParentElement.classList.contains(EMPTY_CELL_CLASS) &&
!buttonParentElement.classList.contains(UNFOLDABLE_LINE_CLASS) && !buttonParentElement.classList.contains(UNFOLDABLE_LINE_CLASS) &&
!buttonParentElement.classList.contains(NO_COMMENT_CLASS) && !buttonParentElement.classList.contains(NO_COMMENT_CLASS) &&
!buttonParentElement.parentNode.classList.contains(DIFF_EXPANDED_CLASS); !buttonParentElement.parentNode.classList.contains(DIFF_EXPANDED_CLASS)
);
}, },
}; };
...@@ -65,12 +65,15 @@ export default class FilterableList { ...@@ -65,12 +65,15 @@ export default class FilterableList {
this.isBusy = true; this.isBusy = true;
return axios.get(this.getFilterEndpoint(), { return axios
params, .get(this.getFilterEndpoint(), {
}).then((res) => { params,
this.onFilterSuccess(res, params); })
this.onFilterComplete(); .then(res => {
}).catch(() => this.onFilterComplete()); this.onFilterSuccess(res, params);
this.onFilterComplete();
})
.catch(() => this.onFilterComplete());
} }
onFilterSuccess(response, queryData) { onFilterSuccess(response, queryData) {
...@@ -81,9 +84,13 @@ export default class FilterableList { ...@@ -81,9 +84,13 @@ export default class FilterableList {
// Change url so if user reload a page - search results are saved // Change url so if user reload a page - search results are saved
const currentPath = this.getPagePath(queryData); const currentPath = this.getPagePath(queryData);
return window.history.replaceState({ return window.history.replaceState(
page: currentPath, {
}, document.title, currentPath); page: currentPath,
},
document.title,
currentPath,
);
} }
onFilterComplete() { onFilterComplete() {
......
...@@ -8,14 +8,19 @@ const hideFlash = (flashEl, fadeTransition = true) => { ...@@ -8,14 +8,19 @@ const hideFlash = (flashEl, fadeTransition = true) => {
}); });
} }
flashEl.addEventListener('transitionend', () => { flashEl.addEventListener(
flashEl.remove(); 'transitionend',
window.dispatchEvent(new Event('resize')); () => {
if (document.body.classList.contains('flash-shown')) document.body.classList.remove('flash-shown'); flashEl.remove();
}, { window.dispatchEvent(new Event('resize'));
once: true, if (document.body.classList.contains('flash-shown'))
passive: true, document.body.classList.remove('flash-shown');
}); },
{
once: true,
passive: true,
},
);
if (!fadeTransition) flashEl.dispatchEvent(new Event('transitionend')); if (!fadeTransition) flashEl.dispatchEvent(new Event('transitionend'));
}; };
...@@ -84,7 +89,9 @@ const createFlash = function createFlash( ...@@ -84,7 +89,9 @@ const createFlash = function createFlash(
flashEl.innerHTML += createAction(actionConfig); flashEl.innerHTML += createAction(actionConfig);
if (actionConfig.clickHandler) { if (actionConfig.clickHandler) {
flashEl.querySelector('.flash-action').addEventListener('click', e => actionConfig.clickHandler(e)); flashEl
.querySelector('.flash-action')
.addEventListener('click', e => actionConfig.clickHandler(e));
} }
} }
...@@ -95,11 +102,5 @@ const createFlash = function createFlash( ...@@ -95,11 +102,5 @@ const createFlash = function createFlash(
return flashContainer; return flashContainer;
}; };
export { export { createFlash as default, createFlashEl, createAction, hideFlash, removeFlashClickListener };
createFlash as default,
createFlashEl,
createAction,
hideFlash,
removeFlashClickListener,
};
window.Flash = createFlash; window.Flash = createFlash;
...@@ -11,9 +11,13 @@ let sidebar; ...@@ -11,9 +11,13 @@ let sidebar;
export const mousePos = []; export const mousePos = [];
export const setSidebar = (el) => { sidebar = el; }; export const setSidebar = el => {
sidebar = el;
};
export const getOpenMenu = () => currentOpenMenu; export const getOpenMenu = () => currentOpenMenu;
export const setOpenMenu = (menu = null) => { currentOpenMenu = menu; }; export const setOpenMenu = (menu = null) => {
currentOpenMenu = menu;
};
export const slope = (a, b) => (b.y - a.y) / (b.x - a.x); export const slope = (a, b) => (b.y - a.y) / (b.x - a.x);
...@@ -21,9 +25,10 @@ let headerHeight = 50; ...@@ -21,9 +25,10 @@ let headerHeight = 50;
export const getHeaderHeight = () => headerHeight; export const getHeaderHeight = () => headerHeight;
export const isSidebarCollapsed = () => sidebar && sidebar.classList.contains('sidebar-collapsed-desktop'); export const isSidebarCollapsed = () =>
sidebar && sidebar.classList.contains('sidebar-collapsed-desktop');
export const canShowActiveSubItems = (el) => { export const canShowActiveSubItems = el => {
if (el.classList.contains('active') && !isSidebarCollapsed()) { if (el.classList.contains('active') && !isSidebarCollapsed()) {
return false; return false;
} }
...@@ -31,7 +36,10 @@ export const canShowActiveSubItems = (el) => { ...@@ -31,7 +36,10 @@ export const canShowActiveSubItems = (el) => {
return true; return true;
}; };
export const canShowSubItems = () => bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md' || bp.getBreakpointSize() === 'lg'; export const canShowSubItems = () =>
bp.getBreakpointSize() === 'sm' ||
bp.getBreakpointSize() === 'md' ||
bp.getBreakpointSize() === 'lg';
export const getHideSubItemsInterval = () => { export const getHideSubItemsInterval = () => {
if (!currentOpenMenu || !mousePos.length) return 0; if (!currentOpenMenu || !mousePos.length) return 0;
...@@ -41,11 +49,12 @@ export const getHideSubItemsInterval = () => { ...@@ -41,11 +49,12 @@ export const getHideSubItemsInterval = () => {
const currentMousePosY = currentMousePos.y; const currentMousePosY = currentMousePos.y;
const [menuTop, menuBottom] = menuCornerLocs; const [menuTop, menuBottom] = menuCornerLocs;
if (currentMousePosY < menuTop.y || if (currentMousePosY < menuTop.y || currentMousePosY > menuBottom.y) return 0;
currentMousePosY > menuBottom.y) return 0;
if (slope(prevMousePos, menuBottom) < slope(currentMousePos, menuBottom) && if (
slope(prevMousePos, menuTop) > slope(currentMousePos, menuTop)) { slope(prevMousePos, menuBottom) < slope(currentMousePos, menuBottom) &&
slope(prevMousePos, menuTop) > slope(currentMousePos, menuTop)
) {
return HIDE_INTERVAL_TIMEOUT; return HIDE_INTERVAL_TIMEOUT;
} }
...@@ -56,11 +65,12 @@ export const calculateTop = (boundingRect, outerHeight) => { ...@@ -56,11 +65,12 @@ export const calculateTop = (boundingRect, outerHeight) => {
const windowHeight = window.innerHeight; const windowHeight = window.innerHeight;
const bottomOverflow = windowHeight - (boundingRect.top + outerHeight); const bottomOverflow = windowHeight - (boundingRect.top + outerHeight);
return bottomOverflow < 0 ? (boundingRect.top - outerHeight) + boundingRect.height : return bottomOverflow < 0
boundingRect.top; ? boundingRect.top - outerHeight + boundingRect.height
: boundingRect.top;
}; };
export const hideMenu = (el) => { export const hideMenu = el => {
if (!el) return; if (!el) return;
const parentEl = el.parentNode; const parentEl = el.parentNode;
...@@ -101,7 +111,7 @@ export const moveSubItemsToPosition = (el, subItems) => { ...@@ -101,7 +111,7 @@ export const moveSubItemsToPosition = (el, subItems) => {
} }
}; };
export const showSubLevelItems = (el) => { export const showSubLevelItems = el => {
const subItems = el.querySelector('.sidebar-sub-level-items'); const subItems = el.querySelector('.sidebar-sub-level-items');
const isIconOnly = subItems && subItems.classList.contains('is-fly-out-only'); const isIconOnly = subItems && subItems.classList.contains('is-fly-out-only');
...@@ -128,16 +138,20 @@ export const mouseEnterTopItems = (el, timeout = getHideSubItemsInterval()) => { ...@@ -128,16 +138,20 @@ export const mouseEnterTopItems = (el, timeout = getHideSubItemsInterval()) => {
}, timeout); }, timeout);
}; };
export const mouseLeaveTopItem = (el) => { export const mouseLeaveTopItem = el => {
const subItems = el.querySelector('.sidebar-sub-level-items'); const subItems = el.querySelector('.sidebar-sub-level-items');
if (!canShowSubItems() || !canShowActiveSubItems(el) || if (
(subItems && subItems === currentOpenMenu)) return; !canShowSubItems() ||
!canShowActiveSubItems(el) ||
(subItems && subItems === currentOpenMenu)
)
return;
el.classList.remove(IS_OVER_CLASS); el.classList.remove(IS_OVER_CLASS);
}; };
export const documentMouseMove = (e) => { export const documentMouseMove = e => {
mousePos.push({ mousePos.push({
x: e.clientX, x: e.clientX,
y: e.clientY, y: e.clientY,
...@@ -146,7 +160,7 @@ export const documentMouseMove = (e) => { ...@@ -146,7 +160,7 @@ export const documentMouseMove = (e) => {
if (mousePos.length > 6) mousePos.shift(); if (mousePos.length > 6) mousePos.shift();
}; };
export const subItemsMouseLeave = (relatedTarget) => { export const subItemsMouseLeave = relatedTarget => {
clearTimeout(timeoutId); clearTimeout(timeoutId);
if (relatedTarget && !relatedTarget.closest(`.${IS_OVER_CLASS}`)) { if (relatedTarget && !relatedTarget.closest(`.${IS_OVER_CLASS}`)) {
...@@ -174,7 +188,7 @@ export default () => { ...@@ -174,7 +188,7 @@ export default () => {
headerHeight = document.querySelector('.nav-sidebar').offsetTop; headerHeight = document.querySelector('.nav-sidebar').offsetTop;
items.forEach((el) => { items.forEach(el => {
const subItems = el.querySelector('.sidebar-sub-level-items'); const subItems = el.querySelector('.sidebar-sub-level-items');
if (subItems) { if (subItems) {
......
...@@ -116,7 +116,8 @@ export default class GlFieldError { ...@@ -116,7 +116,8 @@ export default class GlFieldError {
this.form.focusOnFirstInvalid.apply(this.form); this.form.focusOnFirstInvalid.apply(this.form);
// For UX, wait til after first invalid submission to check each keyup // For UX, wait til after first invalid submission to check each keyup
this.inputElement.off('keyup.fieldValidator') this.inputElement
.off('keyup.fieldValidator')
.on('keyup.fieldValidator', this.updateValidity.bind(this)); .on('keyup.fieldValidator', this.updateValidity.bind(this));
} }
......
...@@ -16,9 +16,12 @@ export default class GlFieldErrors { ...@@ -16,9 +16,12 @@ export default class GlFieldErrors {
initValidators() { initValidators() {
// register selectors here as needed // register selectors here as needed
const validateSelectors = [':text', ':password', '[type=email]'] const validateSelectors = [':text', ':password', '[type=email]']
.map(selector => `input${selector}`).join(','); .map(selector => `input${selector}`)
.join(',');
this.state.inputs = this.form.find(validateSelectors).toArray() this.state.inputs = this.form
.find(validateSelectors)
.toArray()
.filter(input => !input.classList.contains(customValidationFlag)) .filter(input => !input.classList.contains(customValidationFlag))
.map(input => new GlFieldError({ input, formErrors: this })); .map(input => new GlFieldError({ input, formErrors: this }));
...@@ -42,7 +45,7 @@ export default class GlFieldErrors { ...@@ -42,7 +45,7 @@ export default class GlFieldErrors {
/* Public method for triggering validity updates manually */ /* Public method for triggering validity updates manually */
updateFormValidityState() { updateFormValidityState() {
this.state.inputs.forEach((field) => { this.state.inputs.forEach(field => {
if (field.state.submitted) { if (field.state.submitted) {
field.updateValidity(); field.updateValidity();
} }
...@@ -50,8 +53,9 @@ export default class GlFieldErrors { ...@@ -50,8 +53,9 @@ export default class GlFieldErrors {
} }
focusOnFirstInvalid() { focusOnFirstInvalid() {
const firstInvalid = this.state.inputs const firstInvalid = this.state.inputs.filter(
.filter(input => !input.inputDomElement.validity.valid)[0]; input => !input.inputDomElement.validity.valid,
)[0];
firstInvalid.inputElement.focus(); firstInvalid.inputElement.focus();
} }
} }
...@@ -39,7 +39,10 @@ export default class GLForm { ...@@ -39,7 +39,10 @@ export default class GLForm {
this.form.find('.div-dropzone').remove(); this.form.find('.div-dropzone').remove();
this.form.addClass('gfm-form'); this.form.addClass('gfm-form');
// remove notify commit author checkbox for non-commit notes // remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion')); gl.utils.disableButtonIfEmptyField(
this.form.find('.js-note-text'),
this.form.find('.js-comment-button, .js-note-new-discussion'),
);
this.autoComplete = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources); this.autoComplete = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources);
this.autoComplete.setup(this.form.find('.js-gfm-input'), this.enableGFM); this.autoComplete.setup(this.form.find('.js-gfm-input'), this.enableGFM);
dropzoneInput(this.form); dropzoneInput(this.form);
...@@ -55,11 +58,9 @@ export default class GLForm { ...@@ -55,11 +58,9 @@ export default class GLForm {
} }
setupAutosize() { setupAutosize() {
this.textarea.off('autosize:resized') this.textarea.off('autosize:resized').on('autosize:resized', this.setHeightData.bind(this));
.on('autosize:resized', this.setHeightData.bind(this));
this.textarea.off('mouseup.autosize') this.textarea.off('mouseup.autosize').on('mouseup.autosize', this.destroyAutosize.bind(this));
.on('mouseup.autosize', this.destroyAutosize.bind(this));
setTimeout(() => { setTimeout(() => {
autosize(this.textarea); autosize(this.textarea);
...@@ -91,10 +92,14 @@ export default class GLForm { ...@@ -91,10 +92,14 @@ export default class GLForm {
addEventListeners() { addEventListeners() {
this.textarea.on('focus', function focusTextArea() { this.textarea.on('focus', function focusTextArea() {
$(this).closest('.md-area').addClass('is-focused'); $(this)
.closest('.md-area')
.addClass('is-focused');
}); });
this.textarea.on('blur', function blurTextArea() { this.textarea.on('blur', function blurTextArea() {
$(this).closest('.md-area').removeClass('is-focused'); $(this)
.closest('.md-area')
.removeClass('is-focused');
}); });
} }
} }
...@@ -7,8 +7,9 @@ export default function groupAvatar() { ...@@ -7,8 +7,9 @@ export default function groupAvatar() {
}); });
$('.js-group-avatar-input').on('change', function onChangeAvatarInput() { $('.js-group-avatar-input').on('change', function onChangeAvatarInput() {
const form = $(this).closest('form'); const form = $(this).closest('form');
// eslint-disable-next-line no-useless-escape const filename = $(this)
const filename = $(this).val().replace(/^.*[\\\/]/, ''); .val()
.replace(/^.*[\\\/]/, ''); // eslint-disable-line no-useless-escape
return form.find('.js-avatar-filename').text(filename); return form.find('.js-avatar-filename').text(filename);
}); });
} }
...@@ -23,7 +23,8 @@ export default class GroupLabelSubscription { ...@@ -23,7 +23,8 @@ export default class GroupLabelSubscription {
event.preventDefault(); event.preventDefault();
const url = this.$unsubscribeButtons.attr('data-url'); const url = this.$unsubscribeButtons.attr('data-url');
axios.post(url) axios
.post(url)
.then(() => { .then(() => {
this.toggleSubscriptionButtons(); this.toggleSubscriptionButtons();
this.$unsubscribeButtons.removeAttr('data-url'); this.$unsubscribeButtons.removeAttr('data-url');
...@@ -39,7 +40,8 @@ export default class GroupLabelSubscription { ...@@ -39,7 +40,8 @@ export default class GroupLabelSubscription {
this.$unsubscribeButtons.attr('data-url', url); this.$unsubscribeButtons.attr('data-url', url);
axios.post(url) axios
.post(url)
.then(() => GroupLabelSubscription.setNewTooltip($btn)) .then(() => GroupLabelSubscription.setNewTooltip($btn))
.then(() => this.toggleSubscriptionButtons()) .then(() => this.toggleSubscriptionButtons())
.catch(() => flash(__('There was an error when subscribing to this label.'))); .catch(() => flash(__('There was an error when subscribing to this label.')));
...@@ -58,6 +60,8 @@ export default class GroupLabelSubscription { ...@@ -58,6 +60,8 @@ export default class GroupLabelSubscription {
const newTitle = tooltipTitles[type]; const newTitle = tooltipTitles[type];
$('.js-unsubscribe-button', $button.closest('.label-actions-list')) $('.js-unsubscribe-button', $button.closest('.label-actions-list'))
.tooltip('hide').attr('title', newTitle).tooltip('_fixTitle'); .tooltip('hide')
.attr('title', newTitle)
.tooltip('_fixTitle');
} }
} }
<script> <script>
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { import {
ITEM_TYPE, ITEM_TYPE,
VISIBILITY_TYPE_ICON, VISIBILITY_TYPE_ICON,
GROUP_VISIBILITY_TYPE, GROUP_VISIBILITY_TYPE,
PROJECT_VISIBILITY_TYPE, PROJECT_VISIBILITY_TYPE,
} from '../constants'; } from '../constants';
import itemStatsValue from './item_stats_value.vue'; import itemStatsValue from './item_stats_value.vue';
export default { export default {
components: { components: {
icon, icon,
timeAgoTooltip, timeAgoTooltip,
itemStatsValue, itemStatsValue,
},
props: {
item: {
type: Object,
required: true,
}, },
props: { },
item: { computed: {
type: Object, visibilityIcon() {
required: true, return VISIBILITY_TYPE_ICON[this.item.visibility];
},
}, },
computed: { visibilityTooltip() {
visibilityIcon() { if (this.item.type === ITEM_TYPE.GROUP) {
return VISIBILITY_TYPE_ICON[this.item.visibility]; return GROUP_VISIBILITY_TYPE[this.item.visibility];
}, }
visibilityTooltip() { return PROJECT_VISIBILITY_TYPE[this.item.visibility];
if (this.item.type === ITEM_TYPE.GROUP) {
return GROUP_VISIBILITY_TYPE[this.item.visibility];
}
return PROJECT_VISIBILITY_TYPE[this.item.visibility];
},
isProject() {
return this.item.type === ITEM_TYPE.PROJECT;
},
isGroup() {
return this.item.type === ITEM_TYPE.GROUP;
},
}, },
}; isProject() {
return this.item.type === ITEM_TYPE.PROJECT;
},
isGroup() {
return this.item.type === ITEM_TYPE.GROUP;
},
},
};
</script> </script>
<template> <template>
......
<script> <script>
import tooltip from '~/vue_shared/directives/tooltip'; import tooltip from '~/vue_shared/directives/tooltip';
import icon from '~/vue_shared/components/icon.vue'; import icon from '~/vue_shared/components/icon.vue';
export default { export default {
components: { components: {
icon, icon,
},
directives: {
tooltip,
},
props: {
title: {
type: String,
required: false,
default: '',
}, },
directives: { cssClass: {
tooltip, type: String,
required: false,
default: '',
}, },
props: { iconName: {
title: { type: String,
type: String, required: true,
required: false,
default: '',
},
cssClass: {
type: String,
required: false,
default: '',
},
iconName: {
type: String,
required: true,
},
tooltipPlacement: {
type: String,
required: false,
default: 'bottom',
},
/**
* value could either be number or string
* as `memberCount` is always passed as string
* while `subgroupCount` & `projectCount`
* are always number
*/
value: {
type: [Number, String],
required: false,
default: '',
},
}, },
computed: { tooltipPlacement: {
isValuePresent() { type: String,
return this.value !== ''; required: false,
}, default: 'bottom',
}, },
}; /**
* value could either be number or string
* as `memberCount` is always passed as string
* while `subgroupCount` & `projectCount`
* are always number
*/
value: {
type: [Number, String],
required: false,
default: '',
},
},
computed: {
isValuePresent() {
return this.value !== '';
},
},
};
</script> </script>
<template> <template>
......
...@@ -37,20 +37,22 @@ export default class NewGroupChild { ...@@ -37,20 +37,22 @@ export default class NewGroupChild {
getDroplabConfig() { getDroplabConfig() {
return { return {
InputSetter: [{ InputSetter: [
input: this.newGroupChildButton, {
valueAttribute: 'data-value', input: this.newGroupChildButton,
inputAttribute: 'data-action', valueAttribute: 'data-value',
}, { inputAttribute: 'data-action',
input: this.newGroupChildButton, },
valueAttribute: 'data-text', {
}], input: this.newGroupChildButton,
valueAttribute: 'data-text',
},
],
}; };
} }
bindEvents() { bindEvents() {
this.newGroupChildButton this.newGroupChildButton.addEventListener('click', this.onClickNewGroupChildButton.bind(this));
.addEventListener('click', this.onClickNewGroupChildButton.bind(this));
} }
onClickNewGroupChildButton(e) { onClickNewGroupChildButton(e) {
......
...@@ -17,13 +17,14 @@ export default class GroupsStore { ...@@ -17,13 +17,14 @@ export default class GroupsStore {
} }
setSearchedGroups(rawGroups) { setSearchedGroups(rawGroups) {
const formatGroups = groups => groups.map((group) => { const formatGroups = groups =>
const formattedGroup = this.formatGroupItem(group); groups.map(group => {
if (formattedGroup.children && formattedGroup.children.length) { const formattedGroup = this.formatGroupItem(group);
formattedGroup.children = formatGroups(formattedGroup.children); if (formattedGroup.children && formattedGroup.children.length) {
} formattedGroup.children = formatGroups(formattedGroup.children);
return formattedGroup; }
}); return formattedGroup;
});
if (rawGroups && rawGroups.length) { if (rawGroups && rawGroups.length) {
this.state.groups = formatGroups(rawGroups); this.state.groups = formatGroups(rawGroups);
...@@ -62,10 +63,10 @@ export default class GroupsStore { ...@@ -62,10 +63,10 @@ export default class GroupsStore {
formatGroupItem(rawGroupItem) { formatGroupItem(rawGroupItem) {
const groupChildren = rawGroupItem.children || []; const groupChildren = rawGroupItem.children || [];
const groupIsOpen = (groupChildren.length > 0) || false; const groupIsOpen = groupChildren.length > 0 || false;
const childrenCount = this.hideProjects ? const childrenCount = this.hideProjects
rawGroupItem.subgroup_count : ? rawGroupItem.subgroup_count
rawGroupItem.children_count; : rawGroupItem.children_count;
return { return {
id: rawGroupItem.id, id: rawGroupItem.id,
......
...@@ -22,7 +22,7 @@ export default class TransferDropdown { ...@@ -22,7 +22,7 @@ export default class TransferDropdown {
search: { fields: ['text'] }, search: { fields: ['text'] },
data: extraOptions.concat(this.data), data: extraOptions.concat(this.data),
text: item => item.text, text: item => item.text,
clicked: (options) => { clicked: options => {
const { e } = options; const { e } = options;
e.preventDefault(); e.preventDefault();
this.assignSelected(options.selectedObj); this.assignSelected(options.selectedObj);
......
...@@ -23,7 +23,7 @@ export default function groupsSelect() { ...@@ -23,7 +23,7 @@ export default function groupsSelect() {
axios[params.type.toLowerCase()](params.url, { axios[params.type.toLowerCase()](params.url, {
params: params.data, params: params.data,
}) })
.then((res) => { .then(res => {
const results = res.data || []; const results = res.data || [];
const headers = normalizeHeaders(res.headers); const headers = normalizeHeaders(res.headers);
const currentPage = parseInt(headers['X-PAGE'], 10) || 0; const currentPage = parseInt(headers['X-PAGE'], 10) || 0;
...@@ -36,7 +36,8 @@ export default function groupsSelect() { ...@@ -36,7 +36,8 @@ export default function groupsSelect() {
more, more,
}, },
}); });
}).catch(params.error); })
.catch(params.error);
}, },
data(search, page) { data(search, page) {
return { return {
...@@ -68,7 +69,9 @@ export default function groupsSelect() { ...@@ -68,7 +69,9 @@ export default function groupsSelect() {
} }
}, },
formatResult(object) { formatResult(object) {
return `<div class='group-result'> <div class='group-name'>${object.full_name}</div> <div class='group-path'>${object.full_path}</div> </div>`; return `<div class='group-result'> <div class='group-name'>${
object.full_name
}</div> <div class='group-path'>${object.full_path}</div> </div>`;
}, },
formatSelection(object) { formatSelection(object) {
return object.full_name; return object.full_name;
......
...@@ -19,7 +19,9 @@ export function renderIdenticon(entity, options = {}) { ...@@ -19,7 +19,9 @@ export function renderIdenticon(entity, options = {}) {
const bgClass = getIdenticonBackgroundClass(entity.id); const bgClass = getIdenticonBackgroundClass(entity.id);
const title = getIdenticonTitle(entity.name); const title = getIdenticonTitle(entity.name);
return `<div class="avatar identicon ${_.escape(sizeClass)} ${_.escape(bgClass)}">${_.escape(title)}</div>`; return `<div class="avatar identicon ${_.escape(sizeClass)} ${_.escape(bgClass)}">${_.escape(
title,
)}</div>`;
} }
export function renderAvatar(entity, options = {}) { export function renderAvatar(entity, options = {}) {
......
...@@ -60,8 +60,10 @@ export default class ImageDiff { ...@@ -60,8 +60,10 @@ export default class ImageDiff {
} }
renderBadge(discussionEl, index) { renderBadge(discussionEl, index) {
const imageBadge = imageDiffHelper const imageBadge = imageDiffHelper.generateBadgeFromDiscussionDOM(
.generateBadgeFromDiscussionDOM(this.imageFrameEl, discussionEl); this.imageFrameEl,
discussionEl,
);
this.imageBadges.push(imageBadge); this.imageBadges.push(imageBadge);
......
...@@ -8,5 +8,6 @@ export default () => { ...@@ -8,5 +8,6 @@ export default () => {
const diffFileEls = document.querySelectorAll('.timeline-content .diff-file.js-image-file'); const diffFileEls = document.querySelectorAll('.timeline-content .diff-file.js-image-file');
[...diffFileEls].forEach(diffFileEl => [...diffFileEls].forEach(diffFileEl =>
imageDiffHelper.initImageDiff(diffFileEl, canCreateNote, renderCommentBadge)); imageDiffHelper.initImageDiff(diffFileEl, canCreateNote, renderCommentBadge),
);
}; };
...@@ -26,7 +26,7 @@ export default class ReplacedImageDiff extends ImageDiff { ...@@ -26,7 +26,7 @@ export default class ReplacedImageDiff extends ImageDiff {
this.imageEls = {}; this.imageEls = {};
const viewTypeNames = Object.getOwnPropertyNames(viewTypes); const viewTypeNames = Object.getOwnPropertyNames(viewTypes);
viewTypeNames.forEach((viewType) => { viewTypeNames.forEach(viewType => {
this.imageEls[viewType] = this.imageFrameEls[viewType].querySelector('img'); this.imageEls[viewType] = this.imageFrameEls[viewType].querySelector('img');
}); });
} }
...@@ -79,13 +79,12 @@ export default class ReplacedImageDiff extends ImageDiff { ...@@ -79,13 +79,12 @@ export default class ReplacedImageDiff extends ImageDiff {
// Re-render indicator in new view // Re-render indicator in new view
if (indicator.removed) { if (indicator.removed) {
const normalizedIndicator = imageDiffHelper const normalizedIndicator = imageDiffHelper.resizeCoordinatesToImageElement(this.imageEl, {
.resizeCoordinatesToImageElement(this.imageEl, { x: indicator.x,
x: indicator.x, y: indicator.y,
y: indicator.y, width: indicator.image.width,
width: indicator.image.width, height: indicator.image.height,
height: indicator.image.height, });
});
imageDiffHelper.showCommentIndicator(this.imageFrameEl, normalizedIndicator); imageDiffHelper.showCommentIndicator(this.imageFrameEl, normalizedIndicator);
} }
} }
......
...@@ -60,66 +60,71 @@ class ImporterStatus { ...@@ -60,66 +60,71 @@ class ImporterStatus {
attributes = Object.assign(repoData, attributes); attributes = Object.assign(repoData, attributes);
} }
return axios.post(this.importUrl, attributes) return axios
.then(({ data }) => { .post(this.importUrl, attributes)
const job = $(`tr#repo_${id}`); .then(({ data }) => {
job.attr('id', `project_${data.id}`); const job = $(`tr#repo_${id}`);
job.attr('id', `project_${data.id}`);
job.find('.import-target').html(`<a href="${data.full_path}">${data.full_path}</a>`);
$('table.import-jobs tbody').prepend(job); job.find('.import-target').html(`<a href="${data.full_path}">${data.full_path}</a>`);
$('table.import-jobs tbody').prepend(job);
job.addClass('table-active');
const connectingVerb = this.ciCdOnly ? __('connecting') : __('importing'); job.addClass('table-active');
job.find('.import-actions').html(sprintf( const connectingVerb = this.ciCdOnly ? __('connecting') : __('importing');
_.escape(__('%{loadingIcon} Started')), { job.find('.import-actions').html(
loadingIcon: `<i class="fa fa-spinner fa-spin" aria-label="${_.escape(connectingVerb)}"></i>`, sprintf(
}, _.escape(__('%{loadingIcon} Started')),
false, {
)); loadingIcon: `<i class="fa fa-spinner fa-spin" aria-label="${_.escape(
}) connectingVerb,
.catch((error) => { )}"></i>`,
let details = error; },
false,
const $statusField = $(`#repo_${this.id} .job-status`); ),
$statusField.text(__('Failed')); );
})
if (error.response && error.response.data && error.response.data.errors) { .catch(error => {
details = error.response.data.errors; let details = error;
}
const $statusField = $(`#repo_${this.id} .job-status`);
flash(sprintf(__('An error occurred while importing project: %{details}'), { details })); $statusField.text(__('Failed'));
});
if (error.response && error.response.data && error.response.data.errors) {
details = error.response.data.errors;
}
flash(sprintf(__('An error occurred while importing project: %{details}'), { details }));
});
} }
autoUpdate() { autoUpdate() {
return axios.get(this.jobsUrl) return axios.get(this.jobsUrl).then(({ data = [] }) => {
.then(({ data = [] }) => { data.forEach(job => {
data.forEach((job) => { const jobItem = $(`#project_${job.id}`);
const jobItem = $(`#project_${job.id}`); const statusField = jobItem.find('.job-status');
const statusField = jobItem.find('.job-status');
const spinner = '<i class="fa fa-spinner fa-spin"></i>';
const spinner = '<i class="fa fa-spinner fa-spin"></i>';
switch (job.import_status) {
switch (job.import_status) { case 'finished':
case 'finished': jobItem.removeClass('table-active').addClass('table-success');
jobItem.removeClass('table-active').addClass('table-success'); statusField.html(`<span><i class="fa fa-check"></i> ${__('Done')}</span>`);
statusField.html(`<span><i class="fa fa-check"></i> ${__('Done')}</span>`); break;
break; case 'scheduled':
case 'scheduled': statusField.html(`${spinner} ${__('Scheduled')}`);
statusField.html(`${spinner} ${__('Scheduled')}`); break;
break; case 'started':
case 'started': statusField.html(`${spinner} ${__('Started')}`);
statusField.html(`${spinner} ${__('Started')}`); break;
break; case 'failed':
case 'failed': statusField.html(__('Failed'));
statusField.html(__('Failed')); break;
break; default:
default: statusField.html(job.import_status);
statusField.html(job.import_status); break;
break; }
}
});
}); });
});
} }
setAutoUpdate() { setAutoUpdate() {
...@@ -141,7 +146,4 @@ function initImporterStatus() { ...@@ -141,7 +146,4 @@ function initImporterStatus() {
} }
} }
export { export { initImporterStatus as default, ImporterStatus };
initImporterStatus as default,
ImporterStatus,
};
import $ from 'jquery'; import $ from 'jquery';
import { stickyMonitor } from './lib/utils/sticky'; import { stickyMonitor } from './lib/utils/sticky';
export default (stickyTop) => { export default stickyTop => {
stickyMonitor(document.querySelector('.js-diff-files-changed'), stickyTop); stickyMonitor(document.querySelector('.js-diff-files-changed'), stickyTop);
$('.js-diff-stats-dropdown').glDropdown({ $('.js-diff-stats-dropdown').glDropdown({
......
...@@ -2,13 +2,7 @@ import Notes from './notes'; ...@@ -2,13 +2,7 @@ import Notes from './notes';
export default () => { export default () => {
const dataEl = document.querySelector('.js-notes-data'); const dataEl = document.querySelector('.js-notes-data');
const { const { notesUrl, notesIds, now, diffView, enableGFM } = JSON.parse(dataEl.innerHTML);
notesUrl,
notesIds,
now,
diffView,
enableGFM,
} = JSON.parse(dataEl.innerHTML);
// Create a singleton so that we don't need to assign // Create a singleton so that we don't need to assign
// into the window object, we can just access the current isntance with Notes.instance // into the window object, we can just access the current isntance with Notes.instance
......
...@@ -97,7 +97,8 @@ export default class IntegrationSettingsForm { ...@@ -97,7 +97,8 @@ export default class IntegrationSettingsForm {
testSettings(formData) { testSettings(formData) {
this.toggleSubmitBtnState(true); this.toggleSubmitBtnState(true);
return axios.put(this.testEndPoint, formData) return axios
.put(this.testEndPoint, formData)
.then(({ data }) => { .then(({ data }) => {
if (data.error) { if (data.error) {
let flashActions; let flashActions;
...@@ -105,7 +106,7 @@ export default class IntegrationSettingsForm { ...@@ -105,7 +106,7 @@ export default class IntegrationSettingsForm {
if (data.test_failed) { if (data.test_failed) {
flashActions = { flashActions = {
title: 'Save anyway', title: 'Save anyway',
clickHandler: (e) => { clickHandler: e => {
e.preventDefault(); e.preventDefault();
this.$form.submit(); this.$form.submit();
}, },
......
...@@ -27,7 +27,10 @@ class AutoWidthDropdownSelect { ...@@ -27,7 +27,10 @@ class AutoWidthDropdownSelect {
// We have to look at the parent because // We have to look at the parent because
// `offsetParent` on a `display: none;` is `null` // `offsetParent` on a `display: none;` is `null`
const offsetParentWidth = $(this).parent().offsetParent().width(); const offsetParentWidth = $(this)
.parent()
.offsetParent()
.width();
// Reset any width to let it naturally flow // Reset any width to let it naturally flow
$dropdown.css('width', 'auto'); $dropdown.css('width', 'auto');
if ($dropdown.outerWidth(false) > offsetParentWidth) { if ($dropdown.outerWidth(false) > offsetParentWidth) {
......
...@@ -32,7 +32,7 @@ export default { ...@@ -32,7 +32,7 @@ export default {
onFormSubmitFailure() { onFormSubmitFailure() {
this.form.find('[type="submit"]').enable(); this.form.find('[type="submit"]').enable();
return new Flash("Issue update failed"); return new Flash('Issue update failed');
}, },
getSelectedIssues() { getSelectedIssues() {
...@@ -63,7 +63,7 @@ export default { ...@@ -63,7 +63,7 @@ export default {
const result = []; const result = [];
const labelsToKeep = this.$labelDropdown.data('indeterminate'); const labelsToKeep = this.$labelDropdown.data('indeterminate');
this.getLabelsFromSelection().forEach((id) => { this.getLabelsFromSelection().forEach(id => {
if (labelsToKeep.indexOf(id) === -1) { if (labelsToKeep.indexOf(id) === -1) {
result.push(id); result.push(id);
} }
...@@ -89,8 +89,8 @@ export default { ...@@ -89,8 +89,8 @@ export default {
issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(), issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(),
subscription_event: this.form.find('input[name="update[subscription_event]"]').val(), subscription_event: this.form.find('input[name="update[subscription_event]"]').val(),
add_label_ids: [], add_label_ids: [],
remove_label_ids: [] remove_label_ids: [],
} },
}; };
if (this.willUpdateLabels) { if (this.willUpdateLabels) {
formData.update.add_label_ids = this.$labelDropdown.data('marked'); formData.update.add_label_ids = this.$labelDropdown.data('marked');
...@@ -134,7 +134,7 @@ export default { ...@@ -134,7 +134,7 @@ export default {
// Collect unique label IDs for all checked issues // Collect unique label IDs for all checked issues
this.getElement('.selected-issuable:checked').each((i, el) => { this.getElement('.selected-issuable:checked').each((i, el) => {
issuableLabels = this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels'); issuableLabels = this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels');
issuableLabels.forEach((labelId) => { issuableLabels.forEach(labelId => {
// Store unique IDs // Store unique IDs
if (uniqueIds.indexOf(labelId) === -1) { if (uniqueIds.indexOf(labelId) === -1) {
uniqueIds.push(labelId); uniqueIds.push(labelId);
......
...@@ -11,4 +11,4 @@ ...@@ -11,4 +11,4 @@
%p %p
= _('Enable usage ping to get an overview of how you are using GitLab from a feature perspective.') = _('Enable usage ping to get an overview of how you are using GitLab from a feature perspective.')
- if current_user.admin? - if current_user.admin?
= link_to _('Enable usage ping'), admin_application_settings_path(anchor: 'usage-statistics'), class: 'btn btn-primary' = link_to _('Enable usage ping'), metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings'), class: 'btn btn-primary'
...@@ -15,7 +15,7 @@ parser = OptionParser.new do |opts| ...@@ -15,7 +15,7 @@ parser = OptionParser.new do |opts|
options[:version] = version&.tr('.', '-') options[:version] = version&.tr('.', '-')
end end
opts.on('-b', '--branch security-fix-branch', 'Original branch name') do |branch| opts.on('-b', '--branch security-fix-branch', 'Original branch name (optional, defaults to current)') do |branch|
options[:branch] = branch options[:branch] = branch
end end
...@@ -32,15 +32,21 @@ end ...@@ -32,15 +32,21 @@ end
parser.parse! parser.parse!
options[:branch] ||= `git rev-parse --abbrev-ref HEAD`
abort("Missing options. Use #{$0} --help to see the list of options available".red) if options.values.include?(nil) abort("Missing options. Use #{$0} --help to see the list of options available".red) if options.values.include?(nil)
abort("Wrong version format #{options[:version].bold}".red) unless options[:version] =~ /\A\d*\-\d*\Z/ abort("Wrong version format #{options[:version].bold}".red) unless options[:version] =~ /\A\d*\-\d*\Z/
branch = "#{options[:branch]}-#{options[:version]}" ee = File.exist?('./CHANGELOG-EE.md')
original_branch = options[:branch].strip
branch = "#{original_branch}-#{options[:version]}"
branch.prepend("#{BRANCH_PREFIX}-") unless branch.start_with?("#{BRANCH_PREFIX}-") branch.prepend("#{BRANCH_PREFIX}-") unless branch.start_with?("#{BRANCH_PREFIX}-")
branch = branch.freeze branch = branch.freeze
stable_branch = "#{BRANCH_PREFIX}-#{options[:version]}".freeze stable_branch = "#{BRANCH_PREFIX}-#{options[:version]}".tap do |name|
name << "-ee" if ee
end.freeze
command = "git fetch #{REMOTE} #{stable_branch} && git checkout #{stable_branch} && git pull #{REMOTE} #{stable_branch} && git checkout -B #{branch} && git cherry-pick #{options[:sha]} && git push #{REMOTE} #{branch}" command = "git fetch #{REMOTE} #{stable_branch} && git checkout #{stable_branch} && git pull #{REMOTE} #{stable_branch} && git checkout -B #{branch} && git cherry-pick #{options[:sha]} && git push #{REMOTE} #{branch} && git checkout #{original_branch}"
_stdin, stdout, stderr = Open3.popen3(command) _stdin, stdout, stderr = Open3.popen3(command)
......
---
title: "fix link to enable usage ping from convdev index"
merge_request: 22545
author: Anand Capur
type: fixed
...@@ -132,9 +132,9 @@ Remove the old Ruby 1.8 if present: ...@@ -132,9 +132,9 @@ Remove the old Ruby 1.8 if present:
Download Ruby and compile it: Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby mkdir /tmp/ruby && cd /tmp/ruby
curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.4.tar.gz curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.5.tar.gz
echo 'ec82b0d53bd0adad9b19e6b45e44d54e9ec3f10c ruby-2.4.4.tar.gz' | shasum -c - && tar xzf ruby-2.4.4.tar.gz echo '4d650f302f1ec00256450b112bb023644b6ab6dd ruby-2.4.5.tar.gz' | shasum -c - && tar xzf ruby-2.4.5.tar.gz
cd ruby-2.4.4 cd ruby-2.4.5
./configure --disable-install-rdoc ./configure --disable-install-rdoc
make make
......
...@@ -39,9 +39,9 @@ Download Ruby and compile it: ...@@ -39,9 +39,9 @@ Download Ruby and compile it:
```bash ```bash
mkdir /tmp/ruby && cd /tmp/ruby mkdir /tmp/ruby && cd /tmp/ruby
curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.4.tar.gz curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.5.tar.gz
echo 'ec82b0d53bd0adad9b19e6b45e44d54e9ec3f10c ruby-2.4.4.tar.gz' | shasum -c - && tar xzf ruby-2.4.4.tar.gz echo '4d650f302f1ec00256450b112bb023644b6ab6dd ruby-2.4.5.tar.gz' | shasum -c - && tar xzf ruby-2.4.5.tar.gz
cd ruby-2.4.4 cd ruby-2.4.5
./configure --disable-install-rdoc ./configure --disable-install-rdoc
make make
......
...@@ -2,5 +2,16 @@ unless Rails.env.production? ...@@ -2,5 +2,16 @@ unless Rails.env.production?
require 'haml_lint/rake_task' require 'haml_lint/rake_task'
require 'haml_lint/inline_javascript' require 'haml_lint/inline_javascript'
# Workaround for warnings from parser/current
# Keep it even if it no longer emits any warnings,
# because we'll still see warnings in console/server anyway,
# and we don't need to break static-analysis for this.
task :haml_lint do
require 'parser'
def Parser.warn(*args)
puts(*args) # static-analysis ignores stdout if status is 0
end
end
HamlLint::RakeTask.new HamlLint::RakeTask.new
end end
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