Commit 3b0aa265 authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-10-17

parents 8b077edc 28d412e5
......@@ -51,7 +51,7 @@ export default function initCopyToClipboard() {
* the last minute to deconstruct this JSON hash and set the `text/plain` and `text/x-gfm` copy
* data types to the intended values.
*/
$(document).on('copy', 'body > textarea[readonly]', (e) => {
$(document).on('copy', 'body > textarea[readonly]', e => {
const { clipboardData } = e.originalEvent;
if (!clipboardData) return;
......
......@@ -2,7 +2,9 @@ import $ from 'jquery';
$(() => {
$('body').on('click', '.js-details-target', function target() {
$(this).closest('.js-details-container').toggleClass('open');
$(this)
.closest('.js-details-container')
.toggleClass('open');
});
// Show details content. Hides link after click.
......@@ -13,7 +15,9 @@ $(() => {
//
$('body').on('click', '.js-details-expand', function expand(e) {
e.preventDefault();
$(this).next('.js-details-content').removeClass('hide');
$(this)
.next('.js-details-content')
.removeClass('hide');
$(this).hide();
const truncatedItem = $(this).siblings('.js-details-short');
......
......@@ -34,7 +34,7 @@ const gfmRules = {
},
},
AutolinkFilter: {
'a'(el, text) {
a(el, text) {
// Fallback on the regular MarkdownFilter's `a` handler.
if (text !== el.getAttribute('href')) return false;
......@@ -60,7 +60,7 @@ const gfmRules = {
},
},
ImageLazyLoadFilter: {
'img'(el, text) {
img(el, text) {
return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
},
},
......@@ -71,7 +71,7 @@ const gfmRules = {
return CopyAsGFM.nodeToGFM(videoEl);
},
'video'(el) {
video(el) {
return `![${el.dataset.title}](${el.getAttribute('src')})`;
},
},
......@@ -118,11 +118,14 @@ const gfmRules = {
'a[name]:not([href]):empty'(el) {
return el.outerHTML;
},
'dl'(el, text) {
let lines = text.replace(/\n\n/g, '\n').trim().split('\n');
dl(el, text) {
let lines = text
.replace(/\n\n/g, '\n')
.trim()
.split('\n');
// Add two spaces to the front of subsequent list items lines,
// or leave the line entirely blank.
lines = lines.map((l) => {
lines = lines.map(l => {
const line = l.trim();
if (line.length === 0) return '';
......@@ -151,27 +154,30 @@ const gfmRules = {
// Prefixes lines with 4 spaces if the code contains triple backticks
if (lang === '' && text.match(/^```/gm)) {
return text.split('\n').map((l) => {
const line = l.trim();
if (line.length === 0) return '';
return ` ${line}`;
}).join('\n');
return text
.split('\n')
.map(l => {
const line = l.trim();
if (line.length === 0) return '';
return ` ${line}`;
})
.join('\n');
}
return `\`\`\`${lang}\n${text}\n\`\`\``;
},
'pre > code'(el, text) {
// Don't wrap code blocks in ``
// Don't wrap code blocks in ``
return text;
},
},
MarkdownFilter: {
'br'(el) {
br(el) {
// Two spaces at the end of a line are turned into a BR
return ' ';
},
'code'(el, text) {
code(el, text) {
let backtickCount = 1;
const backtickMatch = text.match(/`+/);
if (backtickMatch) {
......@@ -183,27 +189,31 @@ const gfmRules = {
return backticks + spaceOrNoSpace + text.trim() + spaceOrNoSpace + backticks;
},
'blockquote'(el, text) {
return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
blockquote(el, text) {
return text
.trim()
.split('\n')
.map(s => `> ${s}`.trim())
.join('\n');
},
'img'(el) {
img(el) {
const imageSrc = el.src;
const imageUrl = imageSrc && imageSrc !== placeholderImage ? imageSrc : (el.dataset.src || '');
const imageUrl = imageSrc && imageSrc !== placeholderImage ? imageSrc : el.dataset.src || '';
return `![${el.getAttribute('alt')}](${imageUrl})`;
},
'a.anchor'(el, text) {
// Don't render a Markdown link for the anchor link inside a heading
return text;
},
'a'(el, text) {
a(el, text) {
return `[${text}](${el.getAttribute('href')})`;
},
'li'(el, text) {
li(el, text) {
const lines = text.trim().split('\n');
const firstLine = `- ${lines.shift()}`;
// Add four spaces to the front of subsequent list items lines,
// or leave the line entirely blank.
const nextLines = lines.map((s) => {
const nextLines = lines.map(s => {
if (s.trim().length === 0) return '';
return ` ${s}`;
......@@ -211,49 +221,49 @@ const gfmRules = {
return `${firstLine}\n${nextLines.join('\n')}`;
},
'ul'(el, text) {
ul(el, text) {
return text;
},
'ol'(el, text) {
ol(el, text) {
// LIs get a `- ` prefix by default, which we replace by `1. ` for ordered lists.
return text.replace(/^- /mg, '1. ');
return text.replace(/^- /gm, '1. ');
},
'h1'(el, text) {
h1(el, text) {
return `# ${text.trim()}\n`;
},
'h2'(el, text) {
h2(el, text) {
return `## ${text.trim()}\n`;
},
'h3'(el, text) {
h3(el, text) {
return `### ${text.trim()}\n`;
},
'h4'(el, text) {
h4(el, text) {
return `#### ${text.trim()}\n`;
},
'h5'(el, text) {
h5(el, text) {
return `##### ${text.trim()}\n`;
},
'h6'(el, text) {
h6(el, text) {
return `###### ${text.trim()}\n`;
},
'strong'(el, text) {
strong(el, text) {
return `**${text}**`;
},
'em'(el, text) {
em(el, text) {
return `_${text}_`;
},
'del'(el, text) {
del(el, text) {
return `~~${text}~~`;
},
'hr'(el) {
hr(el) {
// extra leading \n is to ensure that there is a blank line between
// a list followed by an hr, otherwise this breaks old redcarpet rendering
return '\n-----\n';
},
'p'(el, text) {
p(el, text) {
return `${text.trim()}\n`;
},
'table'(el) {
table(el) {
const theadEl = el.querySelector('thead');
const tbodyEl = el.querySelector('tbody');
if (!theadEl || !tbodyEl) return false;
......@@ -263,8 +273,8 @@ const gfmRules = {
return [theadText, tbodyText].join('\n');
},
'thead'(el, text) {
const cells = _.map(el.querySelectorAll('th'), (cell) => {
thead(el, text) {
const cells = _.map(el.querySelectorAll('th'), cell => {
let chars = CopyAsGFM.nodeToGFM(cell).length + 2;
let before = '';
......@@ -296,7 +306,7 @@ const gfmRules = {
return [text, separatorRow].join('\n');
},
'tr'(el) {
tr(el) {
const cellEls = el.querySelectorAll('td, th');
if (cellEls.length === 0) return false;
......@@ -315,8 +325,12 @@ export class CopyAsGFM {
const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent);
if (isIOS) return;
$(document).on('copy', '.md, .wiki', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
$(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
$(document).on('copy', '.md, .wiki', e => {
CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection);
});
$(document).on('copy', 'pre.code.highlight, .diff-content .line_content', e => {
CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection);
});
$(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM);
}
......@@ -356,7 +370,7 @@ export class CopyAsGFM {
// This will break down when the actual code block contains an uneven
// number of backticks, but this is a rare edge case.
const backtickMatch = textBefore.match(/`/g);
const insideCodeBlock = backtickMatch && (backtickMatch.length % 2) === 1;
const insideCodeBlock = backtickMatch && backtickMatch.length % 2 === 1;
if (insideCodeBlock) {
return text;
......@@ -393,7 +407,9 @@ export class CopyAsGFM {
let lineSelector = '.line';
if (target) {
const lineClass = ['left-side', 'right-side'].filter(name => target.classList.contains(name))[0];
const lineClass = ['left-side', 'right-side'].filter(name =>
target.classList.contains(name),
)[0];
if (lineClass) {
lineSelector = `.line_content.${lineClass} ${lineSelector}`;
}
......@@ -436,7 +452,8 @@ export class CopyAsGFM {
return node.textContent;
}
const respectWhitespace = respectWhitespaceParam || (node.nodeName === 'PRE' || node.nodeName === 'CODE');
const respectWhitespace =
respectWhitespaceParam || (node.nodeName === 'PRE' || node.nodeName === 'CODE');
const text = this.innerGFM(node, respectWhitespace);
......
......@@ -32,7 +32,9 @@ export default function renderMath($els) {
Promise.all([
import(/* webpackChunkName: 'katex' */ 'katex'),
import(/* webpackChunkName: 'katex' */ 'katex/dist/katex.min.css'),
]).then(([katex]) => {
renderWithKaTeX($els, katex);
}).catch(() => flash(__('An error occurred while rendering KaTeX')));
])
.then(([katex]) => {
renderWithKaTeX($els, katex);
})
.catch(() => flash(__('An error occurred while rendering KaTeX')));
}
......@@ -17,41 +17,43 @@ import flash from '~/flash';
export default function renderMermaid($els) {
if (!$els.length) return;
import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid').then((mermaid) => {
mermaid.initialize({
// mermaid core options
mermaid: {
startOnLoad: false,
},
// mermaidAPI options
theme: 'neutral',
});
import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid')
.then(mermaid => {
mermaid.initialize({
// mermaid core options
mermaid: {
startOnLoad: false,
},
// mermaidAPI options
theme: 'neutral',
});
$els.each((i, el) => {
const source = el.textContent;
$els.each((i, el) => {
const source = el.textContent;
// Remove any extra spans added by the backend syntax highlighting.
Object.assign(el, { textContent: source });
// Remove any extra spans added by the backend syntax highlighting.
Object.assign(el, { textContent: source });
mermaid.init(undefined, el, (id) => {
const svg = document.getElementById(id);
mermaid.init(undefined, el, id => {
const svg = document.getElementById(id);
svg.classList.add('mermaid');
svg.classList.add('mermaid');
// pre > code > svg
svg.closest('pre').replaceWith(svg);
// pre > code > svg
svg.closest('pre').replaceWith(svg);
// We need to add the original source into the DOM to allow Copy-as-GFM
// to access it.
const sourceEl = document.createElement('text');
sourceEl.classList.add('source');
sourceEl.setAttribute('display', 'none');
sourceEl.textContent = source;
// We need to add the original source into the DOM to allow Copy-as-GFM
// to access it.
const sourceEl = document.createElement('text');
sourceEl.classList.add('source');
sourceEl.setAttribute('display', 'none');
sourceEl.textContent = source;
svg.appendChild(sourceEl);
svg.appendChild(sourceEl);
});
});
})
.catch(err => {
flash(`Can't load mermaid module: ${err}`);
});
}).catch((err) => {
flash(`Can't load mermaid module: ${err}`);
});
}
......@@ -26,7 +26,7 @@ MarkdownPreview.prototype.emptyMessage = 'Nothing to preview.';
MarkdownPreview.prototype.ajaxCache = {};
MarkdownPreview.prototype.showPreview = function ($form) {
MarkdownPreview.prototype.showPreview = function($form) {
var mdText;
var markdownVersion;
var url;
......@@ -44,34 +44,40 @@ MarkdownPreview.prototype.showPreview = function ($form) {
this.hideReferencedUsers($form);
} else {
preview.addClass('md-preview-loading').text('Loading...');
this.fetchMarkdownPreview(mdText, url, (function (response) {
var body;
if (response.body.length > 0) {
({ body } = response);
} else {
body = this.emptyMessage;
}
preview.removeClass('md-preview-loading').html(body);
preview.renderGFM();
this.renderReferencedUsers(response.references.users, $form);
if (response.references.commands) {
this.renderReferencedCommands(response.references.commands, $form);
}
}).bind(this));
this.fetchMarkdownPreview(
mdText,
url,
function(response) {
var body;
if (response.body.length > 0) {
({ body } = response);
} else {
body = this.emptyMessage;
}
preview.removeClass('md-preview-loading').html(body);
preview.renderGFM();
this.renderReferencedUsers(response.references.users, $form);
if (response.references.commands) {
this.renderReferencedCommands(response.references.commands, $form);
}
}.bind(this),
);
}
};
MarkdownPreview.prototype.versionedPreviewPath = function (markdownPreviewPath, markdownVersion) {
MarkdownPreview.prototype.versionedPreviewPath = function(markdownPreviewPath, markdownVersion) {
if (typeof markdownVersion === 'undefined') {
return markdownPreviewPath;
}
return `${markdownPreviewPath}${markdownPreviewPath.indexOf('?') === -1 ? '?' : '&'}markdown_version=${markdownVersion}`;
return `${markdownPreviewPath}${
markdownPreviewPath.indexOf('?') === -1 ? '?' : '&'
}markdown_version=${markdownVersion}`;
};
MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) {
MarkdownPreview.prototype.fetchMarkdownPreview = function(text, url, success) {
if (!url) {
return;
}
......@@ -79,24 +85,25 @@ MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) {
success(this.ajaxCache.response);
return;
}
axios.post(url, {
text,
})
.then(({ data }) => {
this.ajaxCache = {
text: text,
response: data,
};
success(data);
})
.catch(() => flash(__('An error occurred while fetching markdown preview')));
axios
.post(url, {
text,
})
.then(({ data }) => {
this.ajaxCache = {
text: text,
response: data,
};
success(data);
})
.catch(() => flash(__('An error occurred while fetching markdown preview')));
};
MarkdownPreview.prototype.hideReferencedUsers = function ($form) {
MarkdownPreview.prototype.hideReferencedUsers = function($form) {
$form.find('.referenced-users').hide();
};
MarkdownPreview.prototype.renderReferencedUsers = function (users, $form) {
MarkdownPreview.prototype.renderReferencedUsers = function(users, $form) {
var referencedUsers;
referencedUsers = $form.find('.referenced-users');
if (referencedUsers.length) {
......@@ -109,11 +116,11 @@ MarkdownPreview.prototype.renderReferencedUsers = function (users, $form) {
}
};
MarkdownPreview.prototype.hideReferencedCommands = function ($form) {
MarkdownPreview.prototype.hideReferencedCommands = function($form) {
$form.find('.referenced-commands').hide();
};
MarkdownPreview.prototype.renderReferencedCommands = function (commands, $form) {
MarkdownPreview.prototype.renderReferencedCommands = function(commands, $form) {
var referencedCommands;
referencedCommands = $form.find('.referenced-commands');
if (commands.length > 0) {
......@@ -132,14 +139,14 @@ writeButtonSelector = '.js-md-write-button';
lastTextareaPreviewed = null;
const markdownToolbar = $('.md-header-toolbar');
$.fn.setupMarkdownPreview = function () {
$.fn.setupMarkdownPreview = function() {
var $form = $(this);
$form.find('textarea.markdown-area').on('input', function () {
$form.find('textarea.markdown-area').on('input', function() {
markdownPreview.hideReferencedUsers($form);
});
};
$(document).on('markdown-preview:show', function (e, $form) {
$(document).on('markdown-preview:show', function(e, $form) {
if (!$form) {
return;
}
......@@ -148,8 +155,14 @@ $(document).on('markdown-preview:show', function (e, $form) {
lastTextareaHeight = lastTextareaPreviewed.height();
// toggle tabs
$form.find(writeButtonSelector).parent().removeClass('active');
$form.find(previewButtonSelector).parent().addClass('active');
$form
.find(writeButtonSelector)
.parent()
.removeClass('active');
$form
.find(previewButtonSelector)
.parent()
.addClass('active');
// toggle content
$form.find('.md-write-holder').hide();
......@@ -158,7 +171,7 @@ $(document).on('markdown-preview:show', function (e, $form) {
markdownPreview.showPreview($form);
});
$(document).on('markdown-preview:hide', function (e, $form) {
$(document).on('markdown-preview:hide', function(e, $form) {
if (!$form) {
return;
}
......@@ -169,8 +182,14 @@ $(document).on('markdown-preview:hide', function (e, $form) {
}
// toggle tabs
$form.find(writeButtonSelector).parent().addClass('active');
$form.find(previewButtonSelector).parent().removeClass('active');
$form
.find(writeButtonSelector)
.parent()
.addClass('active');
$form
.find(previewButtonSelector)
.parent()
.removeClass('active');
// toggle content
$form.find('.md-write-holder').show();
......@@ -181,7 +200,7 @@ $(document).on('markdown-preview:hide', function (e, $form) {
markdownPreview.hideReferencedCommands($form);
});
$(document).on('markdown-preview:toggle', function (e, keyboardEvent) {
$(document).on('markdown-preview:toggle', function(e, keyboardEvent) {
var $target;
$target = $(keyboardEvent.target);
if ($target.is('textarea.markdown-area')) {
......@@ -194,14 +213,14 @@ $(document).on('markdown-preview:toggle', function (e, keyboardEvent) {
}
});
$(document).on('click', previewButtonSelector, function (e) {
$(document).on('click', previewButtonSelector, function(e) {
var $form;
e.preventDefault();
$form = $(this).closest('form');
$(document).triggerHandler('markdown-preview:show', [$form]);
});
$(document).on('click', writeButtonSelector, function (e) {
$(document).on('click', writeButtonSelector, function(e) {
var $form;
e.preventDefault();
$form = $(this).closest('form');
......
......@@ -28,7 +28,7 @@ function keyCodeIs(e, keyCode) {
return e.keyCode === keyCode;
}
$(document).on('keydown.quick_submit', '.js-quick-submit', (e) => {
$(document).on('keydown.quick_submit', '.js-quick-submit', e => {
// Enter
if (!keyCodeIs(e, 13)) {
return;
......@@ -55,23 +55,25 @@ $(document).on('keydown.quick_submit', '.js-quick-submit', (e) => {
// If the user tabs to a submit button on a `js-quick-submit` form, display a
// tooltip to let them know they could've used the hotkey
$(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', function displayTooltip(e) {
// Tab
if (!keyCodeIs(e, 9)) {
return;
}
$(document).on(
'keyup.quick_submit',
'.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]',
function displayTooltip(e) {
// Tab
if (!keyCodeIs(e, 9)) {
return;
}
const $this = $(this);
const title = isMac() ?
'You can also press ⌘-Enter' :
'You can also press Ctrl-Enter';
const $this = $(this);
const title = isMac() ? 'You can also press ⌘-Enter' : 'You can also press Ctrl-Enter';
$this.tooltip({
container: 'body',
html: 'true',
placement: 'top',
title,
trigger: 'manual',
});
$this.tooltip('show').one('blur click', () => $this.tooltip('hide'));
});
$this.tooltip({
container: 'body',
html: 'true',
placement: 'top',
title,
trigger: 'manual',
});
$this.tooltip('show').one('blur click', () => $this.tooltip('hide'));
},
);
......@@ -18,7 +18,8 @@ import '../commons/bootstrap';
$.fn.requiresInput = function requiresInput() {
const $form = $(this);
const $button = $('button[type=submit], input[type=submit]', $form);
const fieldSelector = 'input[required=required], select[required=required], textarea[required=required]';
const fieldSelector =
'input[required=required], select[required=required], textarea[required=required]';
function requireInput() {
// Collect the input values of *all* required fields
......
......@@ -32,16 +32,18 @@ export default class SecretValues {
updateDom(isRevealed) {
const values = this.container.querySelectorAll(this.valueSelector);
values.forEach((value) => {
values.forEach(value => {
value.classList.toggle('hide', !isRevealed);
});
const placeholders = this.container.querySelectorAll(this.placeholderSelector);
placeholders.forEach((placeholder) => {
placeholders.forEach(placeholder => {
placeholder.classList.toggle('hide', isRevealed);
});
this.revealButton.textContent = isRevealed ? n__('Hide value', 'Hide values', values.length) : n__('Reveal value', 'Reveal values', values.length);
this.revealButton.textContent = isRevealed
? n__('Hide value', 'Hide values', values.length)
: n__('Reveal value', 'Reveal values', values.length);
this.revealButton.dataset.secretRevealStatus = isRevealed;
}
}
......@@ -88,22 +88,24 @@ export default class Shortcuts {
return null;
}
return axios.get(gon.shortcuts_path, {
responseType: 'text',
}).then(({ data }) => {
$.globalEval(data);
if (location && location.length > 0) {
const results = [];
for (let i = 0, len = location.length; i < len; i += 1) {
results.push($(location[i]).show());
return axios
.get(gon.shortcuts_path, {
responseType: 'text',
})
.then(({ data }) => {
$.globalEval(data);
if (location && location.length > 0) {
const results = [];
for (let i = 0, len = location.length; i < len; i += 1) {
results.push($(location[i]).show());
}
return results;
}
return results;
}
$('.hidden-shortcut').show();
return $('.js-more-help-button').remove();
});
$('.hidden-shortcut').show();
return $('.js-more-help-button').remove();
});
}
focusFilter(e) {
......
......@@ -18,9 +18,7 @@ $(() => {
.toggleClass('fa-chevron-up', toggleState)
.toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
$container
.find('.js-toggle-content')
.toggle(toggleState);
$container.find('.js-toggle-content').toggle(toggleState);
}
$('body').on('click', '.js-toggle-button', function toggleButton(e) {
......
......@@ -18,12 +18,7 @@ export default class Renderer {
this.loader = new STLLoader();
this.fov = 45;
this.camera = new THREE.PerspectiveCamera(
this.fov,
this.width / this.height,
1,
1000,
);
this.camera = new THREE.PerspectiveCamera(this.fov, this.width / this.height, 1, 1000);
this.scene = new THREE.Scene();
......@@ -35,10 +30,7 @@ export default class Renderer {
this.setupLight();
// Set up OrbitControls
this.controls = new OrbitControls(
this.camera,
this.renderer.domElement,
);
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.minDistance = 5;
this.controls.maxDistance = 30;
this.controls.enableKeys = false;
......@@ -51,47 +43,32 @@ export default class Renderer {
antialias: true,
});
this.renderer.setClearColor(0xFFFFFF);
this.renderer.setClearColor(0xffffff);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize(
this.width,
this.height,
);
this.renderer.setSize(this.width, this.height);
}
setupLight() {
// Point light illuminates the object
const pointLight = new THREE.PointLight(
0xFFFFFF,
2,
0,
);
const pointLight = new THREE.PointLight(0xffffff, 2, 0);
pointLight.castShadow = true;
this.camera.add(pointLight);
// Ambient light illuminates the scene
const ambientLight = new THREE.AmbientLight(
0xFFFFFF,
1,
);
const ambientLight = new THREE.AmbientLight(0xffffff, 1);
this.scene.add(ambientLight);
}
setupGrid() {
this.grid = new THREE.GridHelper(
20,
20,
0x000000,
0x000000,
);
this.grid = new THREE.GridHelper(20, 20, 0x000000, 0x000000);
this.scene.add(this.grid);
}
loadFile() {
this.loader.load(this.container.dataset.endpoint, (geo) => {
this.loader.load(this.container.dataset.endpoint, geo => {
const obj = new MeshObject(geo);
this.objects.push(obj);
......@@ -116,30 +93,23 @@ export default class Renderer {
}
render() {
this.renderer.render(
this.scene,
this.camera,
);
this.renderer.render(this.scene, this.camera);
requestAnimationFrame(this.renderWrapper);
}
changeObjectMaterials(type) {
this.objects.forEach((obj) => {
this.objects.forEach(obj => {
obj.changeMaterial(type);
});
}
setDefaultCameraPosition() {
const obj = this.objects[0];
const radius = (obj.geometry.boundingSphere.radius / 1.5);
const dist = radius / (Math.sin((this.fov * (Math.PI / 180)) / 2));
this.camera.position.set(
0,
dist + 1,
dist,
);
const radius = obj.geometry.boundingSphere.radius / 1.5;
const dist = radius / Math.sin((this.fov * (Math.PI / 180)) / 2);
this.camera.position.set(0, dist + 1, dist);
this.camera.lookAt(this.grid);
this.controls.update();
......
import {
Matrix4,
MeshLambertMaterial,
Mesh,
} from 'three/build/three.module';
import { Matrix4, MeshLambertMaterial, Mesh } from 'three/build/three.module';
const defaultColor = 0xE24329;
const defaultColor = 0xe24329;
const materials = {
default: new MeshLambertMaterial({
color: defaultColor,
......@@ -17,10 +13,7 @@ const materials = {
export default class MeshObject extends Mesh {
constructor(geo) {
super(
geo,
materials.default,
);
super(geo, materials.default);
this.geometry.computeBoundingSphere();
......@@ -29,13 +22,7 @@ export default class MeshObject extends Mesh {
if (this.geometry.boundingSphere.radius > 4) {
const scale = 4 / this.geometry.boundingSphere.radius;
this.geometry.applyMatrix(
new Matrix4().makeScale(
scale,
scale,
scale,
),
);
this.geometry.applyMatrix(new Matrix4().makeScale(scale, scale, scale));
this.geometry.computeBoundingSphere();
this.position.x = -this.geometry.boundingSphere.center.x;
......
......@@ -42,7 +42,7 @@ class BalsamiqViewer {
this.initDatabase(loadEvent.target.response);
const previews = this.getPreviews();
previews.forEach((preview) => {
previews.forEach(preview => {
const renderedPreview = this.renderPreview(preview);
container.appendChild(renderedPreview);
......
......@@ -41,39 +41,45 @@ export default class BlobFileDropzone {
addRemoveLinks: true,
previewsContainer: '.dropzone-previews',
headers: csrf.headers,
init: function () {
this.on('addedfile', function () {
init: function() {
this.on('addedfile', function() {
toggleLoading(submitButton, submitButtonLoadingIcon, false);
dropzoneMessage.addClass(HIDDEN_CLASS);
$('.dropzone-alerts').html('').hide();
$('.dropzone-alerts')
.html('')
.hide();
});
this.on('removedfile', function () {
this.on('removedfile', function() {
toggleLoading(submitButton, submitButtonLoadingIcon, false);
dropzoneMessage.removeClass(HIDDEN_CLASS);
});
this.on('success', function (header, response) {
this.on('success', function(header, response) {
$('#modal-upload-blob').modal('hide');
visitUrl(response.filePath);
});
this.on('maxfilesexceeded', function (file) {
this.on('maxfilesexceeded', function(file) {
dropzoneMessage.addClass(HIDDEN_CLASS);
this.removeFile(file);
});
this.on('sending', function (file, xhr, formData) {
this.on('sending', function(file, xhr, formData) {
formData.append('branch_name', form.find('.js-branch-name').val());
formData.append('create_merge_request', form.find('.js-create-merge-request').val());
formData.append('commit_message', form.find('.js-commit-message').val());
});
},
// Override behavior of adding error underneath preview
error: function (file, errorMessage) {
const stripped = $('<div/>').html(errorMessage).text();
$('.dropzone-alerts').html(`Error uploading file: "${stripped}"`).show();
error: function(file, errorMessage) {
const stripped = $('<div/>')
.html(errorMessage)
.text();
$('.dropzone-alerts')
.html(`Error uploading file: "${stripped}"`)
.show();
this.removeFile(file);
},
});
submitButton.on('click', (e) => {
submitButton.on('click', e => {
e.preventDefault();
e.stopPropagation();
if (dropzone[0].dropzone.getQueuedFiles().length === 0) {
......
......@@ -2,17 +2,19 @@ import { getLocationHash } from '../lib/utils/url_utility';
const lineNumberRe = /^L[0-9]+/;
const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => {
const updateLineNumbersOnBlobPermalinks = linksToUpdate => {
const hash = getLocationHash();
if (hash && lineNumberRe.test(hash)) {
const hashUrlString = `#${hash}`;
[].concat(Array.prototype.slice.call(linksToUpdate)).forEach((permalinkButton) => {
const baseHref = permalinkButton.getAttribute('data-original-href') || (() => {
const href = permalinkButton.getAttribute('href');
permalinkButton.setAttribute('data-original-href', href);
return href;
})();
[].concat(Array.prototype.slice.call(linksToUpdate)).forEach(permalinkButton => {
const baseHref =
permalinkButton.getAttribute('data-original-href') ||
(() => {
const href = permalinkButton.getAttribute('href');
permalinkButton.setAttribute('data-original-href', href);
return href;
})();
permalinkButton.setAttribute('href', `${baseHref}${hashUrlString}`);
});
}
......@@ -26,7 +28,7 @@ function BlobLinePermalinkUpdater(blobContentHolder, lineNumberSelector, element
}, 0);
};
blobContentHolder.addEventListener('click', (e) => {
blobContentHolder.addEventListener('click', e => {
if (e.target.matches(lineNumberSelector)) {
updateBlameAndBlobPermalinkCb();
}
......
......@@ -45,15 +45,11 @@ export default class FileTemplateSelector {
}
renderLoading() {
this.$loadingIcon
.addClass('fa-spinner fa-spin')
.removeClass('fa-chevron-down');
this.$loadingIcon.addClass('fa-spinner fa-spin').removeClass('fa-chevron-down');
}
renderLoaded() {
this.$loadingIcon
.addClass('fa-chevron-down')
.removeClass('fa-spinner fa-spin');
this.$loadingIcon.addClass('fa-chevron-down').removeClass('fa-spinner fa-spin');
}
reportSelection(options) {
......
......@@ -40,13 +40,14 @@ export default () => {
},
methods: {
loadFile() {
axios.get(el.dataset.endpoint)
axios
.get(el.dataset.endpoint)
.then(res => res.data)
.then((data) => {
.then(data => {
this.json = data;
this.loading = false;
})
.catch((e) => {
.catch(e => {
if (e.status !== 200) {
this.loadError = true;
}
......
......@@ -13,7 +13,7 @@ export default class SketchLoader {
return this.getZipFile()
.then(data => JSZip.loadAsync(data))
.then(asyncResult => asyncResult.files['previews/preview.png'].async('uint8array'))
.then((content) => {
.then(content => {
const url = window.URL || window.webkitURL;
const blob = new Blob([new Uint8Array(content)], {
type: 'image/png',
......
......@@ -3,8 +3,8 @@ import Renderer from './3d_viewer';
export default () => {
const viewer = new Renderer(document.getElementById('js-stl-viewer'));
[].slice.call(document.querySelectorAll('.js-material-changer')).forEach((el) => {
el.addEventListener('click', (e) => {
[].slice.call(document.querySelectorAll('.js-material-changer')).forEach(el => {
el.addEventListener('click', e => {
const { target } = e;
e.preventDefault();
......
......@@ -81,14 +81,10 @@ export default class TemplateSelector {
}
startLoadingSpinner() {
this.$dropdownIcon
.addClass('fa-spinner fa-spin')
.removeClass('fa-chevron-down');
this.$dropdownIcon.addClass('fa-spinner fa-spin').removeClass('fa-chevron-down');
}
stopLoadingSpinner() {
this.$dropdownIcon
.addClass('fa-chevron-down')
.removeClass('fa-spinner fa-spin');
this.$dropdownIcon.addClass('fa-chevron-down').removeClass('fa-spinner fa-spin');
}
}
......@@ -22,7 +22,7 @@ export default class BlobLicenseSelector extends FileTemplateSelector {
search: {
fields: ['name'],
},
clicked: (options) => {
clicked: options => {
const { e } = options;
const el = options.$el;
const query = options.selectedObj;
......
......@@ -21,5 +21,4 @@ export default class FileTemplateTypeSelector extends FileTemplateSelector {
text: item => item.name,
});
}
}
......@@ -22,9 +22,8 @@ export default class BlobViewer {
const viewer = document.querySelector('.blob-viewer[data-type="rich"]');
if (!viewer || !viewer.dataset.richType) return;
const initViewer = promise => promise
.then(module => module.default(viewer))
.catch((error) => {
const initViewer = promise =>
promise.then(module => module.default(viewer)).catch(error => {
Flash('Error loading file viewer.');
throw error;
});
......@@ -79,10 +78,9 @@ export default class BlobViewer {
initBindings() {
if (this.switcherBtns.length) {
Array.from(this.switcherBtns)
.forEach((el) => {
el.addEventListener('click', this.switchViewHandler.bind(this));
});
Array.from(this.switcherBtns).forEach(el => {
el.addEventListener('click', this.switchViewHandler.bind(this));
});
}
if (this.copySourceBtn) {
......@@ -109,7 +107,10 @@ export default class BlobViewer {
this.copySourceBtn.setAttribute('title', 'Copy source to clipboard');
this.copySourceBtn.classList.remove('disabled');
} else if (this.activeViewer === this.simpleViewer) {
this.copySourceBtn.setAttribute('title', 'Wait for the source to load to copy it to the clipboard');
this.copySourceBtn.setAttribute(
'title',
'Wait for the source to load to copy it to the clipboard',
);
this.copySourceBtn.classList.add('disabled');
} else {
this.copySourceBtn.setAttribute('title', 'Switch to the source to copy it to the clipboard');
......@@ -147,15 +148,15 @@ export default class BlobViewer {
this.toggleCopyButtonState();
BlobViewer.loadViewer(newViewer)
.then((viewer) => {
$(viewer).renderGFM();
.then(viewer => {
$(viewer).renderGFM();
this.$fileHolder.trigger('highlight:line');
handleLocationHash();
this.$fileHolder.trigger('highlight:line');
handleLocationHash();
this.toggleCopyButtonState();
})
.catch(() => new Flash('Error loading viewer'));
this.toggleCopyButtonState();
})
.catch(() => new Flash('Error loading viewer'));
}
static loadViewer(viewerParam) {
......@@ -168,12 +169,11 @@ export default class BlobViewer {
viewer.setAttribute('data-loading', 'true');
return axios.get(url)
.then(({ data }) => {
viewer.innerHTML = data.html;
viewer.setAttribute('data-loaded', 'true');
return axios.get(url).then(({ data }) => {
viewer.innerHTML = data.html;
viewer.setAttribute('data-loaded', 'true');
return viewer;
});
return viewer;
});
}
}
<script>
import Icon from '~/vue_shared/components/icon.vue';
import iconCycleAnalyticsSplash from 'icons/_icon_cycle_analytics_splash.svg';
import Icon from '~/vue_shared/components/icon.vue';
import iconCycleAnalyticsSplash from 'icons/_icon_cycle_analytics_splash.svg';
export default {
components: {
Icon,
export default {
components: {
Icon,
},
props: {
documentationLink: {
type: String,
required: true,
},
props: {
documentationLink: {
type: String,
required: true,
},
},
computed: {
iconCycleAnalyticsSplash() {
return iconCycleAnalyticsSplash;
},
computed: {
iconCycleAnalyticsSplash() {
return iconCycleAnalyticsSplash;
},
},
methods: {
dismissOverviewDialog() {
this.$emit('dismiss-overview-dialog');
},
methods: {
dismissOverviewDialog() {
this.$emit('dismiss-overview-dialog');
},
},
};
},
};
</script>
<template>
<div class="landing content-block">
......
<script>
import tooltip from '../../vue_shared/directives/tooltip';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
directives: {
tooltip,
export default {
directives: {
tooltip,
},
props: {
count: {
type: Number,
required: true,
},
props: {
count: {
type: Number,
required: true,
},
},
};
},
};
</script>
<template>
<span
......
<script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
export default {
components: {
userAvatarImage,
limitWarning,
totalTime,
export default {
components: {
userAvatarImage,
limitWarning,
totalTime,
},
props: {
items: {
type: Array,
default: () => [],
},
props: {
items: {
type: Array,
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
stage: {
type: Object,
default: () => ({}),
},
};
},
};
</script>
<template>
<div>
......
<script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
export default {
components: {
userAvatarImage,
limitWarning,
totalTime,
export default {
components: {
userAvatarImage,
limitWarning,
totalTime,
},
props: {
items: {
type: Array,
default: () => [],
},
props: {
items: {
type: Array,
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
stage: {
type: Object,
default: () => ({}),
},
};
},
};
</script>
<template>
<div>
......@@ -73,4 +73,3 @@
</ul>
</div>
</template>
<script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import iconCommit from '../svg/icon_commit.svg';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import iconCommit from '../svg/icon_commit.svg';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
export default {
components: {
userAvatarImage,
totalTime,
limitWarning,
export default {
components: {
userAvatarImage,
totalTime,
limitWarning,
},
props: {
items: {
type: Array,
default: () => [],
},
props: {
items: {
type: Array,
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
stage: {
type: Object,
default: () => ({}),
},
computed: {
iconCommit() {
return iconCommit;
},
},
computed: {
iconCommit() {
return iconCommit;
},
};
},
};
</script>
<template>
<div>
......@@ -74,4 +74,3 @@
</ul>
</div>
</template>
<script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
import icon from '../../vue_shared/components/icon.vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
import icon from '../../vue_shared/components/icon.vue';
export default {
components: {
userAvatarImage,
totalTime,
limitWarning,
icon,
export default {
components: {
userAvatarImage,
totalTime,
limitWarning,
icon,
},
props: {
items: {
type: Array,
default: () => [],
},
props: {
items: {
type: Array,
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
stage: {
type: Object,
default: () => ({}),
},
};
},
};
</script>
<template>
<div>
......
<script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import iconBranch from '../svg/icon_branch.svg';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
import icon from '../../vue_shared/components/icon.vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import iconBranch from '../svg/icon_branch.svg';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
import icon from '../../vue_shared/components/icon.vue';
export default {
components: {
userAvatarImage,
totalTime,
limitWarning,
icon,
export default {
components: {
userAvatarImage,
totalTime,
limitWarning,
icon,
},
props: {
items: {
type: Array,
default: () => [],
},
props: {
items: {
type: Array,
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
stage: {
type: Object,
default: () => ({}),
},
computed: {
iconBranch() {
return iconBranch;
},
},
computed: {
iconBranch() {
return iconBranch;
},
};
},
};
</script>
<template>
<div>
......
<script>
import iconBuildStatus from '../svg/icon_build_status.svg';
import iconBranch from '../svg/icon_branch.svg';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
import icon from '../../vue_shared/components/icon.vue';
import iconBuildStatus from '../svg/icon_build_status.svg';
import iconBranch from '../svg/icon_branch.svg';
import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue';
import icon from '../../vue_shared/components/icon.vue';
export default {
components: {
totalTime,
limitWarning,
icon,
export default {
components: {
totalTime,
limitWarning,
icon,
},
props: {
items: {
type: Array,
default: () => [],
},
props: {
items: {
type: Array,
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
stage: {
type: Object,
default: () => ({}),
},
computed: {
iconBuildStatus() {
return iconBuildStatus;
},
iconBranch() {
return iconBranch;
},
},
computed: {
iconBuildStatus() {
return iconBuildStatus;
},
};
iconBranch() {
return iconBranch;
},
},
};
</script>
<template>
<div>
......
<script>
export default {
props: {
time: {
type: Object,
required: false,
default: () => ({}),
},
export default {
props: {
time: {
type: Object,
required: false,
default: () => ({}),
},
computed: {
hasData() {
return Object.keys(this.time).length;
},
},
computed: {
hasData() {
return Object.keys(this.time).length;
},
};
},
};
</script>
<template>
<span class="total-time">
......
......@@ -18,7 +18,8 @@ Vue.use(Translate);
export default () => {
const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed';
new Vue({ // eslint-disable-line no-new
// eslint-disable-next-line no-new
new Vue({
el: '#cycle-analytics',
name: 'CycleAnalytics',
components: {
......@@ -66,14 +67,17 @@ export default () => {
const $dropdown = $('.js-ca-dropdown');
const $label = $dropdown.find('.dropdown-label');
$dropdown.find('li a').off('click').on('click', (e) => {
e.preventDefault();
const $target = $(e.currentTarget);
this.startDate = $target.data('value');
$dropdown
.find('li a')
.off('click')
.on('click', e => {
e.preventDefault();
const $target = $(e.currentTarget);
this.startDate = $target.data('value');
$label.text($target.text().trim());
this.fetchCycleAnalyticsData({ startDate: this.startDate });
});
$label.text($target.text().trim());
this.fetchCycleAnalyticsData({ startDate: this.startDate });
});
},
fetchCycleAnalyticsData(options) {
const fetchOptions = options || { startDate: this.startDate };
......@@ -82,7 +86,7 @@ export default () => {
this.service
.fetchCycleAnalyticsData(fetchOptions)
.then((response) => {
.then(response => {
this.store.setCycleAnalyticsData(response);
this.selectDefaultStage();
this.initDropdown();
......@@ -115,7 +119,7 @@ export default () => {
stage,
startDate: this.startDate,
})
.then((response) => {
.then(response => {
this.isEmptyStage = !response.events.length;
this.store.setStageEvents(response.events, stage);
this.isLoadingStage = false;
......
......@@ -18,10 +18,7 @@ export default class CycleAnalyticsService {
}
fetchStageData(options) {
const {
stage,
startDate,
} = options;
const { stage, startDate } = options;
return this.axios
.get(`events/${stage.name}.json`, {
......
......@@ -5,13 +5,27 @@ import { dasherize } from '../lib/utils/text_utility';
import DEFAULT_EVENT_OBJECTS from './default_event_objects';
const EMPTY_STAGE_TEXTS = {
issue: __('The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.'),
plan: __('The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.'),
code: __('The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.'),
test: __('The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.'),
review: __('The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.'),
staging: __('The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.'),
production: __('The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.'),
issue: __(
'The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.',
),
plan: __(
'The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.',
),
code: __(
'The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.',
),
test: __(
'The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.',
),
review: __(
'The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.',
),
staging: __(
'The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.',
),
production: __(
'The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.',
),
};
export default {
......@@ -31,11 +45,11 @@ export default {
newData.stages = data.stats || [];
newData.summary = data.summary || [];
newData.summary.forEach((item) => {
newData.summary.forEach(item => {
item.value = item.value || '-';
});
newData.stages.forEach((item) => {
newData.stages.forEach(item => {
const stageSlug = dasherize(item.name.toLowerCase());
item.active = false;
item.isUserAllowed = data.permissions[stageSlug];
......@@ -53,7 +67,7 @@ export default {
this.state.hasError = state;
},
deactivateAllStages() {
this.state.stages.forEach((stage) => {
this.state.stages.forEach(stage => {
stage.active = false;
});
},
......@@ -67,7 +81,7 @@ export default {
decorateEvents(events, stage) {
const newEvents = [];
events.forEach((item) => {
events.forEach(item => {
if (!item) return;
const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item);
......
......@@ -18,52 +18,56 @@ const CommentAndResolveBtn = Vue.extend({
};
},
computed: {
showButton: function () {
showButton: function() {
if (this.discussion) {
return this.discussion.isResolvable();
} else {
return false;
}
},
isDiscussionResolved: function () {
isDiscussionResolved: function() {
return this.discussion.isResolved();
},
buttonText: function () {
buttonText: function() {
if (this.isDiscussionResolved) {
if (this.textareaIsEmpty) {
return "Unresolve discussion";
return 'Unresolve discussion';
} else {
return "Comment & unresolve discussion";
return 'Comment & unresolve discussion';
}
} else {
if (this.textareaIsEmpty) {
return "Resolve discussion";
return 'Resolve discussion';
} else {
return "Comment & resolve discussion";
return 'Comment & resolve discussion';
}
}
}
},
},
created() {
if (this.discussionId) {
this.discussion = CommentsStore.state[this.discussionId];
}
},
mounted: function () {
mounted: function() {
if (!this.discussionId) return;
const $textarea = $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`);
const $textarea = $(
`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`,
);
this.textareaIsEmpty = $textarea.val() === '';
$textarea.on('input.comment-and-resolve-btn', () => {
this.textareaIsEmpty = $textarea.val() === '';
});
},
destroyed: function () {
destroyed: function() {
if (!this.discussionId) return;
$(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off('input.comment-and-resolve-btn');
}
$(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off(
'input.comment-and-resolve-btn',
);
},
});
Vue.component('comment-and-resolve-btn', CommentAndResolveBtn);
......@@ -83,7 +83,11 @@ const DiffNoteAvatars = Vue.extend({
this.addNoCommentClass();
this.setDiscussionVisible();
this.lineType = $(this.$el).closest('.diff-line-num').hasClass('old_line') ? 'old' : 'new';
this.lineType = $(this.$el)
.closest('.diff-line-num')
.hasClass('old_line')
? 'old'
: 'new';
});
$(document).on('toggle.comments', () => {
......@@ -113,20 +117,30 @@ const DiffNoteAvatars = Vue.extend({
addNoCommentClass() {
const { notesCount } = this;
$(this.$el).closest('.js-avatar-container')
$(this.$el)
.closest('.js-avatar-container')
.toggleClass('no-comment-btn', notesCount > 0)
.nextUntil('.js-avatar-container')
.toggleClass('no-comment-btn', notesCount > 0);
},
toggleDiscussionsToggleState() {
const $notesHolders = $(this.$el).closest('.code').find('.notes_holder');
const $notesHolders = $(this.$el)
.closest('.code')
.find('.notes_holder');
const $visibleNotesHolders = $notesHolders.filter(':visible');
const $toggleDiffCommentsBtn = $(this.$el).closest('.diff-file').find('.js-toggle-diff-comments');
$toggleDiffCommentsBtn.toggleClass('active', $notesHolders.length === $visibleNotesHolders.length);
const $toggleDiffCommentsBtn = $(this.$el)
.closest('.diff-file')
.find('.js-toggle-diff-comments');
$toggleDiffCommentsBtn.toggleClass(
'active',
$notesHolders.length === $visibleNotesHolders.length,
);
},
setDiscussionVisible() {
this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible');
this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(
':visible',
);
},
getTooltipText(note) {
return `${note.authorName}: ${note.noteTruncated}`;
......
......@@ -14,24 +14,24 @@ const JumpToDiscussion = Vue.extend({
required: true,
},
},
data: function () {
data: function() {
return {
discussions: CommentsStore.state,
discussion: {},
};
},
computed: {
buttonText: function () {
buttonText: function() {
if (this.discussionId) {
return 'Jump to next unresolved discussion';
} else {
return 'Jump to first unresolved discussion';
}
},
allResolved: function () {
allResolved: function() {
return this.unresolvedDiscussionCount === 0;
},
showButton: function () {
showButton: function() {
if (this.discussionId) {
if (this.unresolvedDiscussionCount > 1) {
return true;
......@@ -42,7 +42,7 @@ const JumpToDiscussion = Vue.extend({
return this.unresolvedDiscussionCount >= 1;
}
},
lastResolvedId: function () {
lastResolvedId: function() {
let lastId;
for (const discussionId in this.discussions) {
const discussion = this.discussions[discussionId];
......@@ -52,13 +52,13 @@ const JumpToDiscussion = Vue.extend({
}
}
return lastId;
}
},
},
created() {
this.discussion = this.discussions[this.discussionId];
},
methods: {
jumpToNextUnresolvedDiscussion: function () {
jumpToNextUnresolvedDiscussion: function() {
let discussionsSelector;
let discussionIdsInScope;
let firstUnresolvedDiscussionId;
......@@ -68,9 +68,11 @@ const JumpToDiscussion = Vue.extend({
let jumpToFirstDiscussion = !this.discussionId;
const discussionIdsForElements = function(elements) {
return elements.map(function() {
return $(this).attr('data-discussion-id');
}).toArray();
return elements
.map(function() {
return $(this).attr('data-discussion-id');
})
.toArray();
};
const { discussions } = this;
......@@ -144,8 +146,7 @@ const JumpToDiscussion = Vue.extend({
if (!discussion.isResolved()) {
nextUnresolvedDiscussionId = discussionId;
break;
}
else {
} else {
continue;
}
}
......@@ -175,9 +176,9 @@ const JumpToDiscussion = Vue.extend({
// Resolved discussions are hidden in the diffs tab by default.
// If they are marked unresolved on the notes tab, they will still be hidden on the diffs tab.
// When jumping between unresolved discussions on the diffs tab, we show them.
$target.closest(".content").show();
$target.closest('.content').show();
const $notesHolder = $target.closest("tr.notes_holder");
const $notesHolder = $target.closest('tr.notes_holder');
// Image diff discussions does not use notes_holder
// so we should keep original $target value in those cases
......@@ -194,7 +195,7 @@ const JumpToDiscussion = Vue.extend({
prevEl = $target.prev();
// If the discussion doesn't have 4 lines above it, we'll have to do with fewer.
if (!prevEl.hasClass("line_holder")) {
if (!prevEl.hasClass('line_holder')) {
break;
}
......@@ -203,9 +204,9 @@ const JumpToDiscussion = Vue.extend({
}
$.scrollTo($target, {
offset: -150
offset: -150,
});
}
},
},
});
......
......@@ -13,17 +13,17 @@ window.ResolveCount = Vue.extend({
required: true,
},
},
data: function () {
data: function() {
return {
discussions: CommentsStore.state
discussions: CommentsStore.state,
};
},
computed: {
allResolved: function () {
allResolved: function() {
return this.resolvedDiscussionCount === this.discussionCount;
},
resolvedCountText() {
return this.discussionCount === 1 ? 'discussion' : 'discussions';
}
}
},
},
});
......@@ -2,10 +2,10 @@
const DiscussionMixins = {
computed: {
discussionCount: function () {
discussionCount: function() {
return Object.keys(this.discussions).length;
},
resolvedDiscussionCount: function () {
resolvedDiscussionCount: function() {
let resolvedCount = 0;
for (const discussionId in this.discussions) {
......@@ -18,7 +18,7 @@ const DiscussionMixins = {
return resolvedCount;
},
unresolvedDiscussionCount: function () {
unresolvedDiscussionCount: function() {
let unresolvedCount = 0;
for (const discussionId in this.discussions) {
......@@ -30,8 +30,8 @@ const DiscussionMixins = {
}
return unresolvedCount;
}
}
},
},
};
export default DiscussionMixins;
......@@ -6,22 +6,22 @@ import Vue from 'vue';
import { localTimeAgo } from '../../lib/utils/datetime_utility';
class DiscussionModel {
constructor (discussionId) {
constructor(discussionId) {
this.id = discussionId;
this.notes = {};
this.loading = false;
this.canResolve = false;
}
createNote (noteObj) {
createNote(noteObj) {
Vue.set(this.notes, noteObj.noteId, new NoteModel(this.id, noteObj));
}
deleteNote (noteId) {
deleteNote(noteId) {
Vue.delete(this.notes, noteId);
}
getNote (noteId) {
getNote(noteId) {
return this.notes[noteId];
}
......@@ -29,7 +29,7 @@ class DiscussionModel {
return Object.keys(this.notes).length;
}
isResolved () {
isResolved() {
for (const noteId in this.notes) {
const note = this.notes[noteId];
......@@ -40,7 +40,7 @@ class DiscussionModel {
return true;
}
resolveAllNotes (resolved_by) {
resolveAllNotes(resolved_by) {
for (const noteId in this.notes) {
const note = this.notes[noteId];
......@@ -51,7 +51,7 @@ class DiscussionModel {
}
}
unResolveAllNotes () {
unResolveAllNotes() {
for (const noteId in this.notes) {
const note = this.notes[noteId];
......@@ -62,7 +62,7 @@ class DiscussionModel {
}
}
updateHeadline (data) {
updateHeadline(data) {
const discussionSelector = `.discussion[data-discussion-id="${this.id}"]`;
const $discussionHeadline = $(`${discussionSelector} .js-discussion-headline`);
......@@ -79,7 +79,7 @@ class DiscussionModel {
}
}
isResolvable () {
isResolvable() {
if (!this.canResolve) {
return false;
}
......
......@@ -5,10 +5,10 @@ import Vue from 'vue';
window.CommentsStore = {
state: {},
get: function (discussionId, noteId) {
get: function(discussionId, noteId) {
return this.state[discussionId].getNote(noteId);
},
createDiscussion: function (discussionId, canResolve) {
createDiscussion: function(discussionId, canResolve) {
let discussion = this.state[discussionId];
if (!this.state[discussionId]) {
discussion = new DiscussionModel(discussionId);
......@@ -21,18 +21,18 @@ window.CommentsStore = {
return discussion;
},
create: function (noteObj) {
create: function(noteObj) {
const discussion = this.createDiscussion(noteObj.discussionId);
discussion.createNote(noteObj);
},
update: function (discussionId, noteId, resolved, resolved_by) {
update: function(discussionId, noteId, resolved, resolved_by) {
const discussion = this.state[discussionId];
const note = discussion.getNote(noteId);
note.resolved = resolved;
note.resolved_by = resolved_by;
},
delete: function (discussionId, noteId) {
delete: function(discussionId, noteId) {
const discussion = this.state[discussionId];
discussion.deleteNote(noteId);
......@@ -40,7 +40,7 @@ window.CommentsStore = {
Vue.delete(this.state, discussionId);
}
},
unresolvedDiscussionIds: function () {
unresolvedDiscussionIds: function() {
const ids = [];
for (const discussionId in this.state) {
......@@ -52,5 +52,5 @@ window.CommentsStore = {
}
return ids;
}
},
};
......@@ -43,7 +43,9 @@ export default {
return (this.commit.author && this.commit.author.name) || this.commit.authorName;
},
authorUrl() {
return (this.commit.author && this.commit.author.webUrl) || `mailto:${this.commit.authorEmail}`;
return (
(this.commit.author && this.commit.author.webUrl) || `mailto:${this.commit.authorEmail}`
);
},
authorAvatar() {
return (this.commit.author && this.commit.author.avatarUrl) || this.commit.authorGravatarUrl;
......
......@@ -46,10 +46,10 @@ export default {
showExpandMessage() {
return (
this.isCollapsed ||
!this.file.highlightedDiffLines &&
!this.isLoadingCollapsedDiff &&
!this.file.tooLarge &&
this.file.text
(!this.file.highlightedDiffLines &&
!this.isLoadingCollapsedDiff &&
!this.file.tooLarge &&
this.file.text)
);
},
showLoadingIcon() {
......
......@@ -6,11 +6,4 @@ const IGNORE_CLASS = 'droplab-item-ignore';
// Matches `{{anything}}` and `{{ everything }}`.
const TEMPLATE_REGEX = /\{\{(.+?)\}\}/g;
export {
DATA_TRIGGER,
DATA_DROPDOWN,
SELECTED_CLASS,
ACTIVE_CLASS,
TEMPLATE_REGEX,
IGNORE_CLASS,
};
export { DATA_TRIGGER, DATA_DROPDOWN, SELECTED_CLASS, ACTIVE_CLASS, TEMPLATE_REGEX, IGNORE_CLASS };
......@@ -2,7 +2,7 @@ import utils from './utils';
import { SELECTED_CLASS, IGNORE_CLASS } from './constants';
class DropDown {
constructor(list, config = { }) {
constructor(list, config = {}) {
this.currentIndex = 0;
this.hidden = true;
this.list = typeof list === 'string' ? document.querySelector(list) : list;
......@@ -157,7 +157,7 @@ class DropDown {
static setImagesSrc(template) {
const images = [...template.querySelectorAll('img[data-src]')];
images.forEach((image) => {
images.forEach(image => {
const img = image;
img.src = img.getAttribute('data-src');
......
......@@ -51,7 +51,7 @@ class DropLab {
}
processData(trigger, data, methodName) {
this.hooks.forEach((hook) => {
this.hooks.forEach(hook => {
if (Array.isArray(trigger)) hook.list[methodName](trigger);
if (hook.trigger.id === trigger) hook.list[methodName](data);
......@@ -78,7 +78,8 @@ class DropLab {
}
changeHookList(trigger, list, plugins, config) {
const availableTrigger = typeof trigger === 'string' ? document.getElementById(trigger) : trigger;
const availableTrigger =
typeof trigger === 'string' ? document.getElementById(trigger) : trigger;
this.hooks.forEach((hook, i) => {
const aHook = hook;
......
......@@ -2,15 +2,18 @@
import { ACTIVE_CLASS } from './constants';
const Keyboard = function () {
const Keyboard = function() {
var currentKey;
var currentFocus;
var isUpArrow = false;
var isDownArrow = false;
var removeHighlight = function removeHighlight(list) {
var itemElements = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider):not(.hidden)'), 0);
var itemElements = Array.prototype.slice.call(
list.list.querySelectorAll('li:not(.divider):not(.hidden)'),
0,
);
var listItems = [];
for(var i = 0; i < itemElements.length; i++) {
for (var i = 0; i < itemElements.length; i++) {
var listItem = itemElements[i];
listItem.classList.remove(ACTIVE_CLASS);
......@@ -23,13 +26,13 @@ const Keyboard = function () {
var setMenuForArrows = function setMenuForArrows(list) {
var listItems = removeHighlight(list);
if(list.currentIndex>0){
if(!listItems[list.currentIndex-1]){
list.currentIndex = list.currentIndex-1;
if (list.currentIndex > 0) {
if (!listItems[list.currentIndex - 1]) {
list.currentIndex = list.currentIndex - 1;
}
if (listItems[list.currentIndex-1]) {
var el = listItems[list.currentIndex-1];
if (listItems[list.currentIndex - 1]) {
var el = listItems[list.currentIndex - 1];
var filterDropdownEl = el.closest('.filter-dropdown');
el.classList.add(ACTIVE_CLASS);
......@@ -55,7 +58,7 @@ const Keyboard = function () {
};
var selectItem = function selectItem(list) {
var listItems = removeHighlight(list);
var currentItem = listItems[list.currentIndex-1];
var currentItem = listItems[list.currentIndex - 1];
var listEvent = new CustomEvent('click.dl', {
detail: {
list: list,
......@@ -65,43 +68,49 @@ const Keyboard = function () {
});
list.list.dispatchEvent(listEvent);
list.hide();
}
};
var keydown = function keydown(e){
var keydown = function keydown(e) {
var typedOn = e.target;
var list = e.detail.hook.list;
var currentIndex = list.currentIndex;
isUpArrow = false;
isDownArrow = false;
if(e.detail.which){
if (e.detail.which) {
currentKey = e.detail.which;
if(currentKey === 13){
if (currentKey === 13) {
selectItem(e.detail.hook.list);
return;
}
if(currentKey === 38) {
if (currentKey === 38) {
isUpArrow = true;
}
if(currentKey === 40) {
if (currentKey === 40) {
isDownArrow = true;
}
} else if(e.detail.key) {
} else if (e.detail.key) {
currentKey = e.detail.key;
if(currentKey === 'Enter'){
if (currentKey === 'Enter') {
selectItem(e.detail.hook.list);
return;
}
if(currentKey === 'ArrowUp') {
if (currentKey === 'ArrowUp') {
isUpArrow = true;
}
if(currentKey === 'ArrowDown') {
if (currentKey === 'ArrowDown') {
isDownArrow = true;
}
}
if(isUpArrow){ currentIndex--; }
if(isDownArrow){ currentIndex++; }
if(currentIndex < 0){ currentIndex = 0; }
if (isUpArrow) {
currentIndex--;
}
if (isDownArrow) {
currentIndex++;
}
if (currentIndex < 0) {
currentIndex = 0;
}
list.currentIndex = currentIndex;
setMenuForArrows(e.detail.hook.list);
};
......
......@@ -43,12 +43,12 @@ const Ajax = {
return AjaxCache.retrieve(config.endpoint)
.then(self.preprocessing.bind(null, config))
.then((data) => self._loadData(data, config, self))
.then(data => self._loadData(data, config, self))
.catch(config.onError);
},
destroy: function() {
this.destroyed = true;
}
},
};
export default Ajax;
......@@ -41,8 +41,10 @@ const AjaxFilter = {
if (config.searchValueFunction) {
searchValue = config.searchValueFunction();
}
if (config.loadingTemplate && this.hook.list.data === undefined ||
this.hook.list.data.length === 0) {
if (
(config.loadingTemplate && this.hook.list.data === undefined) ||
this.hook.list.data.length === 0
) {
var dynamicList = this.hook.list.list.querySelector('[data-dynamic]');
var loadingTemplate = document.createElement('div');
loadingTemplate.innerHTML = config.loadingTemplate;
......@@ -61,7 +63,7 @@ const AjaxFilter = {
params[config.searchKey] = searchValue;
var url = config.endpoint + this.buildParams(params);
return AjaxCache.retrieve(url)
.then((data) => {
.then(data => {
this._loadData(data, config);
if (config.onLoadingFinished) {
config.onLoadingFinished(data);
......@@ -72,8 +74,7 @@ const AjaxFilter = {
_loadData(data, config) {
const list = this.hook.list;
if (config.loadingTemplate && list.data === undefined ||
list.data.length === 0) {
if ((config.loadingTemplate && list.data === undefined) || list.data.length === 0) {
const dataLoadingTemplate = list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) {
dataLoadingTemplate.outerHTML = this.listTemplate;
......@@ -81,7 +82,8 @@ const AjaxFilter = {
}
if (!this.destroyed) {
var hookListChildren = list.list.children;
var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
var onlyDynamicList =
hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
if (onlyDynamicList && data.length === 0) {
list.hide();
}
......@@ -100,12 +102,12 @@ const AjaxFilter = {
},
destroy: function destroy() {
if (this.timeout)clearTimeout(this.timeout);
if (this.timeout) clearTimeout(this.timeout);
this.destroyed = true;
this.hook.trigger.removeEventListener('keydown.dl', this.eventWrapper.debounceTrigger);
this.hook.trigger.removeEventListener('focus', this.eventWrapper.debounceTrigger);
}
},
};
export default AjaxFilter;
/* eslint-disable */
const Filter = {
keydown: function(e){
keydown: function(e) {
if (this.destroyed) return;
var hiddenCount = 0;
......@@ -14,14 +14,14 @@ const Filter = {
var matches = [];
var filterFunction;
// will only work on dynamically set data
if(!data){
if (!data) {
return;
}
if (config && config.filterFunction && typeof config.filterFunction === 'function') {
filterFunction = config.filterFunction;
} else {
filterFunction = function(o){
filterFunction = function(o) {
// cheap string search
o.droplab_hidden = o[config.template].toLowerCase().indexOf(value) === -1;
return o;
......@@ -47,20 +47,23 @@ const Filter = {
},
debounceKeydown: function debounceKeydown(e) {
if ([
13, // enter
16, // shift
17, // ctrl
18, // alt
20, // caps lock
37, // left arrow
38, // up arrow
39, // right arrow
40, // down arrow
91, // left window
92, // right window
93, // select
].indexOf(e.detail.which || e.detail.keyCode) > -1) return;
if (
[
13, // enter
16, // shift
17, // ctrl
18, // alt
20, // caps lock
37, // left arrow
38, // up arrow
39, // right arrow
40, // down arrow
91, // left window
92, // right window
93, // select
].indexOf(e.detail.which || e.detail.keyCode) > -1
)
return;
if (this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(this.keydown.bind(this, e), 200);
......@@ -87,7 +90,7 @@ const Filter = {
this.hook.trigger.removeEventListener('keydown.dl', this.eventWrapper.debounceKeydown);
this.hook.trigger.removeEventListener('mousedown.dl', this.eventWrapper.debounceKeydown);
}
},
};
export default Filter;
......@@ -36,8 +36,8 @@ const InputSetter = {
const inputAttribute = config.inputAttribute;
if (input.hasAttribute(inputAttribute)) return input.setAttribute(inputAttribute, newValue);
if (input.tagName === 'INPUT') return input.value = newValue;
return input.textContent = newValue;
if (input.tagName === 'INPUT') return (input.value = newValue);
return (input.textContent = newValue);
},
destroy() {
......
......@@ -5,7 +5,12 @@ import { DATA_TRIGGER, DATA_DROPDOWN, TEMPLATE_REGEX } from './constants';
const utils = {
toCamelCase(attr) {
return this.camelize(attr.split('-').slice(1).join(' '));
return this.camelize(
attr
.split('-')
.slice(1)
.join(' '),
);
},
template(templateString, data) {
......@@ -17,9 +22,11 @@ const utils = {
},
camelize(str) {
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => {
return index === 0 ? letter.toLowerCase() : letter.toUpperCase();
}).replace(/\s+/g, '');
return str
.replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => {
return index === 0 ? letter.toLowerCase() : letter.toUpperCase();
})
.replace(/\s+/g, '');
},
closest(thisTag, stopTag) {
......
<script>
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import environmentTable from '../components/environments_table.vue';
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import environmentTable from '../components/environments_table.vue';
export default {
components: {
environmentTable,
tablePagination,
export default {
components: {
environmentTable,
tablePagination,
},
props: {
isLoading: {
type: Boolean,
required: true,
},
props: {
isLoading: {
type: Boolean,
required: true,
},
environments: {
type: Array,
required: true,
},
pagination: {
type: Object,
required: true,
},
canCreateDeployment: {
type: Boolean,
required: true,
},
canReadEnvironment: {
type: Boolean,
required: true,
},
environments: {
type: Array,
required: true,
},
methods: {
onChangePage(page) {
this.$emit('onChangePage', page);
},
pagination: {
type: Object,
required: true,
},
};
canCreateDeployment: {
type: Boolean,
required: true,
},
canReadEnvironment: {
type: Boolean,
required: true,
},
},
methods: {
onChangePage(page) {
this.$emit('onChangePage', page);
},
},
};
</script>
<template>
......
<script>
export default {
name: 'EnvironmentsEmptyState',
props: {
newPath: {
type: String,
required: true,
},
canCreateEnvironment: {
type: Boolean,
required: true,
},
helpPath: {
type: String,
required: true,
},
export default {
name: 'EnvironmentsEmptyState',
props: {
newPath: {
type: String,
required: true,
},
};
canCreateEnvironment: {
type: Boolean,
required: true,
},
helpPath: {
type: String,
required: true,
},
},
};
</script>
<template>
<div class="blank-state-row">
......
......@@ -38,7 +38,9 @@ export default {
computed: {
title() {
return this.isLastDeployment ? s__('Environments|Re-deploy to environment') : s__('Environments|Rollback environment');
return this.isLastDeployment
? s__('Environments|Re-deploy to environment')
: s__('Environments|Rollback environment');
},
},
......
......@@ -5,31 +5,32 @@ import Translate from '../../vue_shared/translate';
Vue.use(Translate);
export default () => new Vue({
el: '#environments-folder-list-view',
components: {
environmentsFolderApp,
},
data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
export default () =>
new Vue({
el: '#environments-folder-list-view',
components: {
environmentsFolderApp,
},
data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
return {
endpoint: environmentsData.endpoint,
folderName: environmentsData.folderName,
cssContainerClass: environmentsData.cssClass,
canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
};
},
render(createElement) {
return createElement('environments-folder-app', {
props: {
endpoint: this.endpoint,
folderName: this.folderName,
cssContainerClass: this.cssContainerClass,
canCreateDeployment: this.canCreateDeployment,
canReadEnvironment: this.canReadEnvironment,
},
});
},
});
return {
endpoint: environmentsData.endpoint,
folderName: environmentsData.folderName,
cssContainerClass: environmentsData.cssClass,
canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
};
},
render(createElement) {
return createElement('environments-folder-app', {
props: {
endpoint: this.endpoint,
folderName: this.folderName,
cssContainerClass: this.cssContainerClass,
canCreateDeployment: this.canCreateDeployment,
canReadEnvironment: this.canReadEnvironment,
},
});
},
});
<script>
import environmentsMixin from '../mixins/environments_mixin';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
import StopEnvironmentModal from '../components/stop_environment_modal.vue';
import environmentsMixin from '../mixins/environments_mixin';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
import StopEnvironmentModal from '../components/stop_environment_modal.vue';
export default {
components: {
StopEnvironmentModal,
},
export default {
components: {
StopEnvironmentModal,
},
mixins: [
environmentsMixin,
CIPaginationMixin,
],
mixins: [environmentsMixin, CIPaginationMixin],
props: {
endpoint: {
type: String,
required: true,
},
folderName: {
type: String,
required: true,
},
cssContainerClass: {
type: String,
required: true,
},
canCreateDeployment: {
type: Boolean,
required: true,
},
canReadEnvironment: {
type: Boolean,
required: true,
},
props: {
endpoint: {
type: String,
required: true,
},
folderName: {
type: String,
required: true,
},
cssContainerClass: {
type: String,
required: true,
},
canCreateDeployment: {
type: Boolean,
required: true,
},
canReadEnvironment: {
type: Boolean,
required: true,
},
methods: {
successCallback(resp) {
this.saveData(resp);
},
},
methods: {
successCallback(resp) {
this.saveData(resp);
},
};
},
};
</script>
<template>
<div :class="cssContainerClass">
......
......@@ -5,35 +5,36 @@ import Translate from '../vue_shared/translate';
Vue.use(Translate);
export default () => new Vue({
el: '#environments-list-view',
components: {
environmentsComponent,
},
data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
export default () =>
new Vue({
el: '#environments-list-view',
components: {
environmentsComponent,
},
data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
return {
endpoint: environmentsData.environmentsDataEndpoint,
newEnvironmentPath: environmentsData.newEnvironmentPath,
helpPagePath: environmentsData.helpPagePath,
cssContainerClass: environmentsData.cssClass,
canCreateEnvironment: convertPermissionToBoolean(environmentsData.canCreateEnvironment),
canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
};
},
render(createElement) {
return createElement('environments-component', {
props: {
endpoint: this.endpoint,
newEnvironmentPath: this.newEnvironmentPath,
helpPagePath: this.helpPagePath,
cssContainerClass: this.cssContainerClass,
canCreateEnvironment: this.canCreateEnvironment,
canCreateDeployment: this.canCreateDeployment,
canReadEnvironment: this.canReadEnvironment,
},
});
},
});
return {
endpoint: environmentsData.environmentsDataEndpoint,
newEnvironmentPath: environmentsData.newEnvironmentPath,
helpPagePath: environmentsData.helpPagePath,
cssContainerClass: environmentsData.cssClass,
canCreateEnvironment: convertPermissionToBoolean(environmentsData.canCreateEnvironment),
canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
};
},
render(createElement) {
return createElement('environments-component', {
props: {
endpoint: this.endpoint,
newEnvironmentPath: this.newEnvironmentPath,
helpPagePath: this.helpPagePath,
cssContainerClass: this.cssContainerClass,
canCreateEnvironment: this.canCreateEnvironment,
canCreateDeployment: this.canCreateDeployment,
canReadEnvironment: this.canReadEnvironment,
},
});
},
});
......@@ -4,9 +4,7 @@
import _ from 'underscore';
import Visibility from 'visibilityjs';
import Poll from '../../lib/utils/poll';
import {
getParameterByName,
} from '../../lib/utils/common_utils';
import { getParameterByName } from '../../lib/utils/common_utils';
import { s__ } from '../../locale';
import Flash from '../../flash';
import eventHub from '../event_hub';
......@@ -19,7 +17,6 @@ import tabs from '../../vue_shared/components/navigation_tabs.vue';
import container from '../components/container.vue';
export default {
components: {
environmentTable,
container,
......@@ -65,7 +62,8 @@ export default {
updateContent(parameters) {
this.updateInternalState(parameters);
// fetch new data
return this.service.fetchEnvironments(this.requestData)
return this.service
.fetchEnvironments(this.requestData)
.then(response => this.successCallback(response))
.then(() => {
// restart polling
......@@ -88,7 +86,8 @@ export default {
if (!this.isMakingRequest) {
this.isLoading = true;
this.service.postAction(endpoint)
this.service
.postAction(endpoint)
.then(() => this.fetchEnvironments())
.catch(() => {
this.isLoading = false;
......@@ -100,7 +99,8 @@ export default {
fetchEnvironments() {
this.isLoading = true;
return this.service.fetchEnvironments(this.requestData)
return this.service
.fetchEnvironments(this.requestData)
.then(this.successCallback)
.catch(this.errorCallback);
},
......@@ -111,7 +111,9 @@ export default {
stopEnvironment(environment) {
const endpoint = environment.stop_path;
const errorMessage = s__('Environments|An error occurred while stopping the environment, please try again');
const errorMessage = s__(
'Environments|An error occurred while stopping the environment, please try again',
);
this.postAction({ endpoint, errorMessage });
},
},
......@@ -149,7 +151,7 @@ export default {
data: this.requestData,
successCallback: this.successCallback,
errorCallback: this.errorCallback,
notificationCallback: (isMakingRequest) => {
notificationCallback: isMakingRequest => {
this.isMakingRequest = isMakingRequest;
},
});
......
import $ from 'jquery';
import {
getSelector,
inserted,
} from './feature_highlight_helper';
import {
togglePopover,
mouseenter,
debouncedMouseleave,
} from '../shared/popover';
import { getSelector, inserted } from './feature_highlight_helper';
import { togglePopover, mouseenter, debouncedMouseleave } from '../shared/popover';
export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
const $selector = $(getSelector(id));
......@@ -41,8 +34,9 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
export function findHighestPriorityFeature() {
let priorityFeature;
const sortedFeatureEls = [].slice.call(document.querySelectorAll('.js-feature-highlight')).sort((a, b) =>
(a.dataset.highlightPriority || 0) < (b.dataset.highlightPriority || 0));
const sortedFeatureEls = [].slice
.call(document.querySelectorAll('.js-feature-highlight'))
.sort((a, b) => (a.dataset.highlightPriority || 0) < (b.dataset.highlightPriority || 0));
const [priorityFeatureEl] = sortedFeatureEls;
if (priorityFeatureEl) {
......
......@@ -8,10 +8,17 @@ import { togglePopover } from '../shared/popover';
export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`;
export function dismiss(highlightId) {
axios.post(this.attr('data-dismiss-endpoint'), {
feature_name: highlightId,
})
.catch(() => Flash(__('An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again.')));
axios
.post(this.attr('data-dismiss-endpoint'), {
feature_name: highlightId,
})
.catch(() =>
Flash(
__(
'An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again.',
),
),
);
togglePopover.call(this, false);
this.hide();
......@@ -23,8 +30,7 @@ export function inserted() {
const $popover = $(this);
const dismissWrapper = dismiss.bind($popover, highlightId);
$(`#${popoverId} .dismiss-feature-highlight`)
.on('click', dismissWrapper);
$(`#${popoverId} .dismiss-feature-highlight`).on('click', dismissWrapper);
const lazyImg = $(`#${popoverId} .feature-highlight-illustration`)[0];
if (lazyImg) {
......
import FilteredSearchTokenKeys from './filtered_search_token_keys';
const tokenKeys = [{
key: 'status',
type: 'string',
param: 'status',
symbol: '',
icon: 'messages',
tag: 'status',
}, {
key: 'type',
type: 'string',
param: 'type',
symbol: '',
icon: 'cube',
tag: 'type',
}];
const tokenKeys = [
{
key: 'status',
type: 'string',
param: 'status',
symbol: '',
icon: 'messages',
tag: 'status',
},
{
key: 'type',
type: 'string',
param: 'type',
symbol: '',
icon: 'cube',
tag: 'type',
},
];
const AdminRunnersFilteredSearchTokenKeys = new FilteredSearchTokenKeys(tokenKeys);
......
......@@ -21,9 +21,11 @@ export default {
},
computed: {
processedItems() {
return this.items.map((item) => {
const { tokens, searchToken }
= FilteredSearchTokenizer.processTokens(item, this.allowedKeys);
return this.items.map(item => {
const { tokens, searchToken } = FilteredSearchTokenizer.processTokens(
item,
this.allowedKeys,
);
const resultantTokens = tokens.map(token => ({
prefix: `${token.key}:`,
......
......@@ -24,8 +24,12 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
};
import(/* webpackChunkName: 'emoji' */ '~/emoji')
.then(({ glEmojiTag }) => { this.glEmojiTag = glEmojiTag; })
.catch(() => { /* ignore error and leave emoji name in the search bar */ });
.then(({ glEmojiTag }) => {
this.glEmojiTag = glEmojiTag;
})
.catch(() => {
/* ignore error and leave emoji name in the search bar */
});
this.unbindEvents();
this.bindEvents();
......@@ -48,7 +52,7 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
}
itemClicked(e) {
super.itemClicked(e, (selected) => {
super.itemClicked(e, selected => {
const name = selected.querySelector('.js-data-value').innerText.trim();
return DropdownUtils.getEscapedText(name);
});
......@@ -64,7 +68,7 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
// Replace empty gl-emoji tag to real content
const dropdownItems = [...this.dropdown.querySelectorAll('.filter-dropdown-item')];
dropdownItems.forEach((dropdownItem) => {
dropdownItems.forEach(dropdownItem => {
const name = dropdownItem.querySelector('.js-data-value').innerText;
const emojiTag = this.glEmojiTag(name);
const emojiElement = dropdownItem.querySelector('gl-emoji');
......@@ -73,7 +77,6 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
}
init() {
this.droplab
.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
this.droplab.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
}
}
......@@ -41,8 +41,10 @@ export default class DropdownHint extends FilteredSearchDropdown {
previousInputValues.forEach((value, index) => {
searchTerms.push(value);
if (index === previousInputValues.length - 1
&& token.indexOf(value.toLowerCase()) !== -1) {
if (
index === previousInputValues.length - 1 &&
token.indexOf(value.toLowerCase()) !== -1
) {
searchTerms.pop();
}
});
......@@ -64,13 +66,12 @@ export default class DropdownHint extends FilteredSearchDropdown {
}
renderContent() {
const dropdownData = this.tokenKeys.get()
.map(tokenKey => ({
icon: `${gon.sprite_icons}#${tokenKey.icon}`,
hint: tokenKey.key,
tag: `:${tokenKey.tag}`,
type: tokenKey.type,
}));
const dropdownData = this.tokenKeys.get().map(tokenKey => ({
icon: `${gon.sprite_icons}#${tokenKey.icon}`,
hint: tokenKey.key,
tag: `:${tokenKey.tag}`,
type: tokenKey.type,
}));
this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config);
this.droplab.setData(this.hookId, dropdownData);
......
......@@ -29,20 +29,18 @@ export default class DropdownNonUser extends FilteredSearchDropdown {
}
itemClicked(e) {
super.itemClicked(e, (selected) => {
super.itemClicked(e, selected => {
const title = selected.querySelector('.js-data-value').innerText.trim();
return `${this.symbol}${DropdownUtils.getEscapedText(title)}`;
});
}
renderContent(forceShowList = false) {
this.droplab
.changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
this.droplab.changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
super.renderContent(forceShowList);
}
init() {
this.droplab
.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
this.droplab.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
}
}
......@@ -41,7 +41,7 @@ export default class DropdownUtils {
// Removes the first character if it is a quotation so that we can search
// with multiple words
if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) {
if ((value[0] === '"' || value[0] === "'") && title.indexOf(' ') !== -1) {
value = value.slice(1);
}
......@@ -82,11 +82,13 @@ export default class DropdownUtils {
// Reduce the colors to 4
colors.length = Math.min(colors.length, 4);
const color = colors.map((c, i) => {
const percentFirst = Math.floor(spacing * i);
const percentSecond = Math.floor(spacing * (i + 1));
return `${c} ${percentFirst}%, ${c} ${percentSecond}%`;
}).join(', ');
const color = colors
.map((c, i) => {
const percentFirst = Math.floor(spacing * i);
const percentSecond = Math.floor(spacing * (i + 1));
return `${c} ${percentFirst}%, ${c} ${percentSecond}%`;
})
.join(', ');
return `linear-gradient(${color})`;
}
......@@ -97,17 +99,16 @@ export default class DropdownUtils {
data.forEach(DropdownUtils.mergeDuplicateLabels.bind(null, dataMap));
Object.keys(dataMap)
.forEach((key) => {
const label = dataMap[key];
Object.keys(dataMap).forEach(key => {
const label = dataMap[key];
if (label.multipleColors) {
label.color = DropdownUtils.duplicateLabelColor(label.multipleColors);
label.text_color = '#000000';
}
if (label.multipleColors) {
label.color = DropdownUtils.duplicateLabelColor(label.multipleColors);
label.text_color = '#000000';
}
results.push(label);
});
results.push(label);
});
results.preprocessed = true;
......@@ -118,8 +119,7 @@ export default class DropdownUtils {
const { input, allowedKeys } = config;
const updatedItem = item;
const searchInput = DropdownUtils.getSearchQuery(input);
const { lastToken, tokens } =
FilteredSearchTokenizer.processTokens(searchInput, allowedKeys);
const { lastToken, tokens } = FilteredSearchTokenizer.processTokens(searchInput, allowedKeys);
const lastKey = lastToken.key || lastToken || '';
const allowMultiple = item.type === 'array';
const itemInExistingTokens = tokens.some(t => t.key === item.hint);
......@@ -154,7 +154,10 @@ export default class DropdownUtils {
static getVisualTokenValues(visualToken) {
const tokenName = visualToken && visualToken.querySelector('.name').textContent.trim();
let tokenValue = visualToken && visualToken.querySelector('.value') && visualToken.querySelector('.value').textContent.trim();
let tokenValue =
visualToken &&
visualToken.querySelector('.value') &&
visualToken.querySelector('.value').textContent.trim();
if (tokenName === 'label' && tokenValue) {
// remove leading symbol and wrapping quotes
tokenValue = tokenValue.replace(/^~("|')?(.*)/, '$2').replace(/("|')$/, '');
......@@ -174,7 +177,7 @@ export default class DropdownUtils {
tokens.splice(inputIndex + 1);
}
tokens.forEach((token) => {
tokens.forEach(token => {
if (token.classList.contains('js-visual-token')) {
const name = token.querySelector('.name');
const value = token.querySelector('.value');
......@@ -194,8 +197,9 @@ export default class DropdownUtils {
values.push(name.innerText);
}
} else if (token.classList.contains('input-token')) {
const { isLastVisualTokenValid } =
FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
const {
isLastVisualTokenValid,
} = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
const inputValue = input && input.value;
......@@ -209,9 +213,7 @@ export default class DropdownUtils {
}
});
return values
.map(value => value.trim())
.join(' ');
return values.map(value => value.trim()).join(' ');
}
static getSearchInput(filteredSearchInput) {
......@@ -227,7 +229,9 @@ export default class DropdownUtils {
// Replace all spaces inside quote marks with underscores
// (will continue to match entire string until an end quote is found if any)
// This helps with matching the beginning & end of a token:key
inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => str.replace(/\s/g, '_'));
inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str =>
str.replace(/\s/g, '_'),
);
// Get the right position for the word selected
// Regex matches first space
......
......@@ -87,10 +87,12 @@ export default class FilteredSearchDropdown {
dispatchInputEvent() {
// Propogate input change to FilteredSearchDropdownManager
// so that it can determine which dropdowns to open
this.input.dispatchEvent(new CustomEvent('input', {
bubbles: true,
cancelable: true,
}));
this.input.dispatchEvent(
new CustomEvent('input', {
bubbles: true,
cancelable: true,
}),
);
}
dispatchFormSubmitEvent() {
......@@ -114,7 +116,7 @@ export default class FilteredSearchDropdown {
if (!data) return;
const results = data.map((o) => {
const results = data.map(o => {
const updated = o;
updated.droplab_hidden = false;
return updated;
......
......@@ -42,19 +42,21 @@ export default class FilteredSearchTokenKeys {
}
searchByKeyParam(keyParam) {
return this.tokenKeysWithAlternative.find((tokenKey) => {
let tokenKeyParam = tokenKey.key;
return (
this.tokenKeysWithAlternative.find(tokenKey => {
let tokenKeyParam = tokenKey.key;
// Replace hyphen with underscore to compare keyParam with tokenKeyParam
// e.g. 'my-reaction' => 'my_reaction'
tokenKeyParam = tokenKeyParam.replace('-', '_');
// Replace hyphen with underscore to compare keyParam with tokenKeyParam
// e.g. 'my-reaction' => 'my_reaction'
tokenKeyParam = tokenKeyParam.replace('-', '_');
if (tokenKey.param) {
tokenKeyParam += `_${tokenKey.param}`;
}
if (tokenKey.param) {
tokenKeyParam += `_${tokenKey.param}`;
}
return keyParam === tokenKeyParam;
}) || null;
return keyParam === tokenKeyParam;
}) || null
);
}
searchByConditionUrl(url) {
......@@ -62,8 +64,10 @@ export default class FilteredSearchTokenKeys {
}
searchByConditionKeyValue(key, value) {
return this.conditions
.find(condition => condition.tokenKey === key && condition.value === value) || null;
return (
this.conditions.find(condition => condition.tokenKey === key && condition.value === value) ||
null
);
}
addExtraTokensForMergeRequests() {
......
......@@ -4,41 +4,48 @@ export default class FilteredSearchTokenizer {
static processTokens(input, allowedKeys) {
// Regex extracts `(token):(symbol)(value)`
// Values that start with a double quote must end in a double quote (same for single)
const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g');
const tokenRegex = new RegExp(
`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`,
'g',
);
const tokens = [];
const tokenIndexes = []; // stores key+value for simple search
let lastToken = null;
const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => {
let tokenValue = v1 || v2 || v3;
let tokenSymbol = symbol;
let tokenIndex = '';
if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') {
tokenSymbol = tokenValue;
tokenValue = '';
}
tokenIndex = `${key}:${tokenValue}`;
// Prevent adding duplicates
if (tokenIndexes.indexOf(tokenIndex) === -1) {
tokenIndexes.push(tokenIndex);
tokens.push({
key,
value: tokenValue || '',
symbol: tokenSymbol || '',
});
}
return '';
}).replace(/\s{2,}/g, ' ').trim() || '';
const searchToken =
input
.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => {
let tokenValue = v1 || v2 || v3;
let tokenSymbol = symbol;
let tokenIndex = '';
if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') {
tokenSymbol = tokenValue;
tokenValue = '';
}
tokenIndex = `${key}:${tokenValue}`;
// Prevent adding duplicates
if (tokenIndexes.indexOf(tokenIndex) === -1) {
tokenIndexes.push(tokenIndex);
tokens.push({
key,
value: tokenValue || '',
symbol: tokenSymbol || '',
});
}
return '';
})
.replace(/\s{2,}/g, ' ')
.trim() || '';
if (tokens.length > 0) {
const last = tokens[tokens.length - 1];
const lastString = `${last.key}:${last.symbol}${last.value}`;
lastToken = input.lastIndexOf(lastString) ===
input.length - lastString.length ? last : searchToken;
lastToken =
input.lastIndexOf(lastString) === input.length - lastString.length ? last : searchToken;
} else {
lastToken = searchToken;
}
......
......@@ -13,7 +13,10 @@ export default class FilteredSearchVisualTokens {
return {
lastVisualToken,
isLastVisualTokenValid: lastVisualToken === null || lastVisualToken.className.indexOf('filtered-search-term') !== -1 || (lastVisualToken && lastVisualToken.querySelector('.value') !== null),
isLastVisualTokenValid:
lastVisualToken === null ||
lastVisualToken.className.indexOf('filtered-search-term') !== -1 ||
(lastVisualToken && lastVisualToken.querySelector('.value') !== null),
};
}
......@@ -33,7 +36,9 @@ export default class FilteredSearchVisualTokens {
}
static unselectTokens() {
const otherTokens = FilteredSearchContainer.container.querySelectorAll('.js-visual-token .selectable.selected');
const otherTokens = FilteredSearchContainer.container.querySelectorAll(
'.js-visual-token .selectable.selected',
);
[].forEach.call(otherTokens, t => t.classList.remove('selected'));
}
......@@ -56,11 +61,7 @@ export default class FilteredSearchVisualTokens {
}
static createVisualTokenElementHTML(options = {}) {
const {
canEdit = true,
uppercaseTokenName = false,
capitalizeTokenValue = false,
} = options;
const { canEdit = true, uppercaseTokenName = false, capitalizeTokenValue = false } = options;
return `
<div class="${canEdit ? 'selectable' : 'hidden'}" role="button">
......@@ -115,15 +116,20 @@ export default class FilteredSearchVisualTokens {
return AjaxCache.retrieve(labelsEndpoint)
.then(FilteredSearchVisualTokens.preprocessLabel.bind(null, labelsEndpoint))
.then((labels) => {
const matchingLabel = (labels || []).find(label => `~${DropdownUtils.getEscapedText(label.title)}` === tokenValue);
.then(labels => {
const matchingLabel = (labels || []).find(
label => `~${DropdownUtils.getEscapedText(label.title)}` === tokenValue,
);
if (!matchingLabel) {
return;
}
FilteredSearchVisualTokens
.setTokenStyle(tokenValueContainer, matchingLabel.color, matchingLabel.text_color);
FilteredSearchVisualTokens.setTokenStyle(
tokenValueContainer,
matchingLabel.color,
matchingLabel.text_color,
);
})
.catch(() => new Flash('An error occurred while fetching label colors.'));
}
......@@ -134,39 +140,43 @@ export default class FilteredSearchVisualTokens {
}
const username = tokenValue.replace(/^@/, '');
return UsersCache.retrieve(username)
.then((user) => {
if (!user) {
return;
}
/* eslint-disable no-param-reassign */
tokenValueContainer.dataset.originalValue = tokenValue;
tokenValueElement.innerHTML = `
return (
UsersCache.retrieve(username)
.then(user => {
if (!user) {
return;
}
/* eslint-disable no-param-reassign */
tokenValueContainer.dataset.originalValue = tokenValue;
tokenValueElement.innerHTML = `
<img class="avatar s20" src="${user.avatar_url}" alt="">
${_.escape(user.name)}
`;
/* eslint-enable no-param-reassign */
})
// ignore error and leave username in the search bar
.catch(() => { });
/* eslint-enable no-param-reassign */
})
// ignore error and leave username in the search bar
.catch(() => {})
);
}
static updateEmojiTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) {
const container = tokenValueContainer;
const element = tokenValueElement;
return import(/* webpackChunkName: 'emoji' */ '../emoji')
.then((Emoji) => {
if (!Emoji.isEmojiNameValid(tokenValue)) {
return;
}
container.dataset.originalValue = tokenValue;
element.innerHTML = Emoji.glEmojiTag(tokenValue);
})
// ignore error and leave emoji name in the search bar
.catch(() => { });
return (
import(/* webpackChunkName: 'emoji' */ '../emoji')
.then(Emoji => {
if (!Emoji.isEmojiNameValid(tokenValue)) {
return;
}
container.dataset.originalValue = tokenValue;
element.innerHTML = Emoji.glEmojiTag(tokenValue);
})
// ignore error and leave emoji name in the search bar
.catch(() => {})
);
}
static renderVisualTokenValue(parentElement, tokenName, tokenValue) {
......@@ -177,24 +187,23 @@ export default class FilteredSearchVisualTokens {
const tokenType = tokenName.toLowerCase();
if (tokenType === 'label') {
FilteredSearchVisualTokens.updateLabelTokenColor(tokenValueContainer, tokenValue);
} else if ((tokenType === 'author') || (tokenType === 'assignee')) {
} else if (tokenType === 'author' || tokenType === 'assignee') {
FilteredSearchVisualTokens.updateUserTokenAppearance(
tokenValueContainer, tokenValueElement, tokenValue,
tokenValueContainer,
tokenValueElement,
tokenValue,
);
} else if (tokenType === 'my-reaction') {
FilteredSearchVisualTokens.updateEmojiTokenAppearance(
tokenValueContainer, tokenValueElement, tokenValue,
tokenValueContainer,
tokenValueElement,
tokenValue,
);
}
}
static addVisualTokenElement(name, value, options = {}) {
const {
isSearchTerm = false,
canEdit,
uppercaseTokenName,
capitalizeTokenValue,
} = options;
const { isSearchTerm = false, canEdit, uppercaseTokenName, capitalizeTokenValue } = options;
const li = document.createElement('li');
li.classList.add('js-visual-token');
li.classList.add(isSearchTerm ? 'filtered-search-term' : 'filtered-search-token');
......@@ -217,8 +226,10 @@ export default class FilteredSearchVisualTokens {
}
static addValueToPreviousVisualTokenElement(value) {
const { lastVisualToken, isLastVisualTokenValid } =
FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
const {
lastVisualToken,
isLastVisualTokenValid,
} = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
if (!isLastVisualTokenValid && lastVisualToken.classList.contains('filtered-search-token')) {
const name = FilteredSearchVisualTokens.getLastTokenPartial();
......@@ -228,13 +239,15 @@ export default class FilteredSearchVisualTokens {
}
}
static addFilterVisualToken(tokenName, tokenValue, {
canEdit,
uppercaseTokenName = false,
capitalizeTokenValue = false,
} = {}) {
const { lastVisualToken, isLastVisualTokenValid }
= FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
static addFilterVisualToken(
tokenName,
tokenValue,
{ canEdit, uppercaseTokenName = false, capitalizeTokenValue = false } = {},
) {
const {
lastVisualToken,
isLastVisualTokenValid,
} = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
const { addVisualTokenElement } = FilteredSearchVisualTokens;
if (isLastVisualTokenValid) {
......@@ -308,8 +321,7 @@ export default class FilteredSearchVisualTokens {
static tokenizeInput() {
const input = FilteredSearchContainer.container.querySelector('.filtered-search');
const { isLastVisualTokenValid } =
FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
const { isLastVisualTokenValid } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
if (input.value) {
if (isLastVisualTokenValid) {
......@@ -375,8 +387,7 @@ export default class FilteredSearchVisualTokens {
FilteredSearchVisualTokens.tokenizeInput();
if (!tokenContainer.lastElementChild.isEqualNode(inputLi)) {
const { isLastVisualTokenValid } =
FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
const { isLastVisualTokenValid } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
if (!isLastVisualTokenValid) {
const lastPartial = FilteredSearchVisualTokens.getLastTokenPartial();
......
import FilteredSearchTokenKeys from './filtered_search_token_keys';
export const tokenKeys = [{
key: 'author',
type: 'string',
param: 'username',
symbol: '@',
icon: 'pencil',
tag: '@author',
}, {
key: 'assignee',
type: 'string',
param: 'username',
symbol: '@',
icon: 'user',
tag: '@assignee',
}, {
key: 'milestone',
type: 'string',
param: 'title',
symbol: '%',
icon: 'clock',
tag: '%milestone',
}, {
key: 'label',
type: 'array',
param: 'name[]',
symbol: '~',
icon: 'labels',
tag: '~label',
}];
export const tokenKeys = [
{
key: 'author',
type: 'string',
param: 'username',
symbol: '@',
icon: 'pencil',
tag: '@author',
},
{
key: 'assignee',
type: 'string',
param: 'username',
symbol: '@',
icon: 'user',
tag: '@assignee',
},
{
key: 'milestone',
type: 'string',
param: 'title',
symbol: '%',
icon: 'clock',
tag: '%milestone',
},
{
key: 'label',
type: 'array',
param: 'name[]',
symbol: '~',
icon: 'labels',
tag: '~label',
},
];
if (gon.current_user_id) {
// Appending tokenkeys only logged-in
......@@ -42,36 +47,47 @@ if (gon.current_user_id) {
});
}
export const alternativeTokenKeys = [{
key: 'label',
type: 'string',
param: 'name',
symbol: '~',
}];
export const alternativeTokenKeys = [
{
key: 'label',
type: 'string',
param: 'name',
symbol: '~',
},
];
export const conditions = [{
url: 'assignee_id=0',
tokenKey: 'assignee',
value: 'none',
}, {
url: 'milestone_title=No+Milestone',
tokenKey: 'milestone',
value: 'none',
}, {
url: 'milestone_title=%23upcoming',
tokenKey: 'milestone',
value: 'upcoming',
}, {
url: 'milestone_title=%23started',
tokenKey: 'milestone',
value: 'started',
}, {
url: 'label_name[]=No+Label',
tokenKey: 'label',
value: 'none',
}];
export const conditions = [
{
url: 'assignee_id=0',
tokenKey: 'assignee',
value: 'none',
},
{
url: 'milestone_title=No+Milestone',
tokenKey: 'milestone',
value: 'none',
},
{
url: 'milestone_title=%23upcoming',
tokenKey: 'milestone',
value: 'upcoming',
},
{
url: 'milestone_title=%23started',
tokenKey: 'milestone',
value: 'started',
},
{
url: 'label_name[]=No+Label',
tokenKey: 'label',
value: 'none',
},
];
const IssuableFilteredSearchTokenKeys =
new FilteredSearchTokenKeys(tokenKeys, alternativeTokenKeys, conditions);
const IssuableFilteredSearchTokenKeys = new FilteredSearchTokenKeys(
tokenKeys,
alternativeTokenKeys,
conditions,
);
export default IssuableFilteredSearchTokenKeys;
......@@ -3,11 +3,7 @@ import RecentSearchesDropdownContent from './components/recent_searches_dropdown
import eventHub from './event_hub';
class RecentSearchesRoot {
constructor(
recentSearchesStore,
recentSearchesService,
wrapperElement,
) {
constructor(recentSearchesStore, recentSearchesService, wrapperElement) {
this.store = recentSearchesStore;
this.service = recentSearchesService;
this.wrapperElement = wrapperElement;
......@@ -35,7 +31,9 @@ class RecentSearchesRoot {
components: {
RecentSearchesDropdownContent,
},
data() { return state; },
data() {
return state;
},
template: `
<recent-searches-dropdown-content
:items="recentSearches"
......@@ -57,7 +55,6 @@ class RecentSearchesRoot {
this.vm.$destroy();
}
}
}
export default RecentSearchesRoot;
......@@ -2,11 +2,14 @@ import _ from 'underscore';
class RecentSearchesStore {
constructor(initialState = {}, allowedKeys) {
this.state = Object.assign({
isLocalStorageAvailable: true,
recentSearches: [],
allowedKeys,
}, initialState);
this.state = Object.assign(
{
isLocalStorageAvailable: true,
recentSearches: [],
allowedKeys,
},
initialState,
);
}
addRecentSearch(newSearch) {
......
......@@ -26,14 +26,17 @@ export default class IssuableIndex {
static resetIncomingEmailToken() {
const $resetToken = $('.incoming-email-token-reset');
$resetToken.on('click', (e) => {
$resetToken.on('click', e => {
e.preventDefault();
$resetToken.text('resetting...');
axios.put($resetToken.attr('href'))
axios
.put($resetToken.attr('href'))
.then(({ data }) => {
$('#issuable_email').val(data.new_address).focus();
$('#issuable_email')
.val(data.new_address)
.focus();
$resetToken.text('reset it');
})
......
......@@ -28,7 +28,7 @@ export default class Issue {
}
// Listen to state changes in the Vue app
document.addEventListener('issuable_vue_app:change', (event) => {
document.addEventListener('issuable_vue_app:change', event => {
this.updateTopState(event.detail.isClosed, event.detail.data);
});
}
......@@ -55,7 +55,13 @@ export default class Issue {
$(document).trigger('issuable:change', isClosed);
this.toggleCloseReopenButton(isClosed);
let numProjectIssues = Number(projectIssuesCounter.first().text().trim().replace(/[^\d]/, ''));
let numProjectIssues = Number(
projectIssuesCounter
.first()
.text()
.trim()
.replace(/[^\d]/, ''),
);
numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1;
projectIssuesCounter.text(addDelimiter(numProjectIssues));
......@@ -76,29 +82,34 @@ export default class Issue {
initIssueBtnEventListeners() {
const issueFailMessage = 'Unable to update this issue at this time.';
return $(document).on('click', '.js-issuable-actions a.btn-close, .js-issuable-actions a.btn-reopen', (e) => {
var $button, shouldSubmit, url;
e.preventDefault();
e.stopImmediatePropagation();
$button = $(e.currentTarget);
shouldSubmit = $button.hasClass('btn-comment');
if (shouldSubmit) {
Issue.submitNoteForm($button.closest('form'));
}
this.disableCloseReopenButton($button);
return $(document).on(
'click',
'.js-issuable-actions a.btn-close, .js-issuable-actions a.btn-reopen',
e => {
var $button, shouldSubmit, url;
e.preventDefault();
e.stopImmediatePropagation();
$button = $(e.currentTarget);
shouldSubmit = $button.hasClass('btn-comment');
if (shouldSubmit) {
Issue.submitNoteForm($button.closest('form'));
}
url = $button.attr('href');
return axios.put(url)
.then(({ data }) => {
const isClosed = $button.hasClass('btn-close');
this.updateTopState(isClosed, data);
})
.catch(() => flash(issueFailMessage))
.then(() => {
this.disableCloseReopenButton($button, false);
});
});
this.disableCloseReopenButton($button);
url = $button.attr('href');
return axios
.put(url)
.then(({ data }) => {
const isClosed = $button.hasClass('btn-close');
this.updateTopState(isClosed, data);
})
.catch(() => flash(issueFailMessage))
.then(() => {
this.disableCloseReopenButton($button, false);
});
},
);
}
initCloseReopenReport() {
......@@ -124,7 +135,7 @@ export default class Issue {
static submitNoteForm(form) {
var noteText;
noteText = form.find("textarea.js-note-text").val();
noteText = form.find('textarea.js-note-text').val();
if (noteText && noteText.trim().length > 0) {
return form.submit();
}
......@@ -133,22 +144,26 @@ export default class Issue {
static initMergeRequests() {
var $container;
$container = $('#merge-requests');
return axios.get($container.data('url'))
return axios
.get($container.data('url'))
.then(({ data }) => {
if ('html' in data) {
$container.html(data.html);
}
}).catch(() => flash('Failed to load referenced merge requests'));
})
.catch(() => flash('Failed to load referenced merge requests'));
}
static initRelatedBranches() {
var $container;
$container = $('#related-branches');
return axios.get($container.data('url'))
return axios
.get($container.data('url'))
.then(({ data }) => {
if ('html' in data) {
$container.html(data.html);
}
}).catch(() => flash('Failed to load related branches'));
})
.catch(() => flash('Failed to load related branches'));
}
}
......@@ -42,16 +42,14 @@ export default class Job extends LogOutputBehaviours {
this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
this.$window
.off('scroll')
.on('scroll', () => {
if (!isScrolledToBottom()) {
this.toggleScrollAnimation(false);
} else if (isScrolledToBottom() && !this.isLogComplete) {
this.toggleScrollAnimation(true);
}
this.scrollThrottled();
});
this.$window.off('scroll').on('scroll', () => {
if (!isScrolledToBottom()) {
this.toggleScrollAnimation(false);
} else if (isScrolledToBottom() && !this.isLogComplete) {
this.toggleScrollAnimation(true);
}
this.scrollThrottled();
});
this.$window
.off('resize.build')
......@@ -87,10 +85,11 @@ export default class Job extends LogOutputBehaviours {
}
getBuildTrace() {
return axios.get(`${this.pagePath}/trace.json`, {
params: { state: this.state },
})
.then((res) => {
return axios
.get(`${this.pagePath}/trace.json`, {
params: { state: this.state },
})
.then(res => {
const log = res.data;
if (!this.fetchingStatusFavicon) {
......@@ -186,5 +185,4 @@ export default class Job extends LogOutputBehaviours {
sidebarOnClick() {
if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
}
}
......@@ -47,7 +47,10 @@ export default class LabelManager {
}
toggleEmptyState($label, $btn, action) {
this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li'));
this.emptyState.classList.toggle(
'hidden',
!!this.prioritizedLabels[0].querySelector(':scope > li'),
);
}
toggleLabelPriority($label, action, persistState) {
......@@ -80,16 +83,14 @@ export default class LabelManager {
return;
}
if (action === 'remove') {
axios.delete(url)
.catch(rollbackLabelPosition);
axios.delete(url).catch(rollbackLabelPosition);
// Restore empty message
if (!$from.find('li').length) {
$from.find('.empty-message').removeClass('hidden');
}
} else {
this.savePrioritySort($label, action)
.catch(rollbackLabelPosition);
this.savePrioritySort($label, action).catch(rollbackLabelPosition);
}
}
......@@ -102,8 +103,7 @@ export default class LabelManager {
}
onPrioritySortUpdate() {
this.savePrioritySort()
.catch(() => flash(this.errorMessage));
this.savePrioritySort().catch(() => flash(this.errorMessage));
}
savePrioritySort() {
......
......@@ -22,7 +22,7 @@ export default class Labels {
updateColorPreview() {
const previewColor = $('input#label_color').val();
return $('div.label-color-preview').css('background-color', previewColor);
// Updates the the preview color with the hex-color input
// Updates the the preview color with the hex-color input
}
// Updates the preview color with a click on a suggested color
......
......@@ -5,7 +5,9 @@ import initFlyOutNav from './fly_out_nav';
function hideEndFade($scrollingTabs) {
$scrollingTabs.each(function scrollTabsLoop() {
const $this = $(this);
$this.siblings('.fade-right').toggleClass('scrolling', Math.round($this.width()) < $this.prop('scrollWidth'));
$this
.siblings('.fade-right')
.toggleClass('scrolling', Math.round($this.width()) < $this.prop('scrollWidth'));
});
}
......@@ -15,38 +17,42 @@ export default function initLayoutNav() {
initFlyOutNav();
$(document).on('init.scrolling-tabs', () => {
const $scrollingTabs = $('.scrolling-tabs').not('.is-initialized');
$scrollingTabs.addClass('is-initialized');
$(document)
.on('init.scrolling-tabs', () => {
const $scrollingTabs = $('.scrolling-tabs').not('.is-initialized');
$scrollingTabs.addClass('is-initialized');
$(window).on('resize.nav', () => {
hideEndFade($scrollingTabs);
}).trigger('resize.nav');
$(window)
.on('resize.nav', () => {
hideEndFade($scrollingTabs);
})
.trigger('resize.nav');
$scrollingTabs.on('scroll', function tabsScrollEvent() {
const $this = $(this);
const currentPosition = $this.scrollLeft();
const maxPosition = $this.prop('scrollWidth') - $this.outerWidth();
$scrollingTabs.on('scroll', function tabsScrollEvent() {
const $this = $(this);
const currentPosition = $this.scrollLeft();
const maxPosition = $this.prop('scrollWidth') - $this.outerWidth();
$this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0);
$this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1);
});
$this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0);
$this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1);
});
$scrollingTabs.each(function scrollTabsEachLoop() {
const $this = $(this);
const scrollingTabWidth = $this.width();
const $active = $this.find('.active');
const activeWidth = $active.width();
$scrollingTabs.each(function scrollTabsEachLoop() {
const $this = $(this);
const scrollingTabWidth = $this.width();
const $active = $this.find('.active');
const activeWidth = $active.width();
if ($active.length) {
const offset = $active.offset().left + activeWidth;
if ($active.length) {
const offset = $active.offset().left + activeWidth;
if (offset > scrollingTabWidth - 30) {
const scrollLeft = (offset - (scrollingTabWidth / 2)) - (activeWidth / 2);
if (offset > scrollingTabWidth - 30) {
const scrollLeft = offset - scrollingTabWidth / 2 - activeWidth / 2;
$this.scrollLeft(scrollLeft);
$this.scrollLeft(scrollLeft);
}
}
}
});
}).trigger('init.scrolling-tabs');
});
})
.trigger('init.scrolling-tabs');
}
......@@ -70,7 +70,7 @@ LineHighlighter.prototype.highlightHash = function(newHash) {
const scrollOptions = {
// Scroll to the first highlighted line on initial load
// Offset -50 for the sticky top bar, and another -100 for some context
offset: -150
offset: -150,
};
if (this.options.scrollFileHolder) {
$(this.options.fileHolderSelector).scrollTo(lineSelector, scrollOptions);
......@@ -85,7 +85,9 @@ LineHighlighter.prototype.clickHandler = function(event) {
var current, lineNumber, range;
event.preventDefault();
this.clearHighlight();
lineNumber = $(event.target).closest('a').data('lineNumber');
lineNumber = $(event.target)
.closest('a')
.data('lineNumber');
current = this.hashToRange(this._hash);
if (!(current[0] && event.shiftKey)) {
// If there's no current selection, or there is but Shift wasn't held,
......@@ -104,7 +106,7 @@ LineHighlighter.prototype.clickHandler = function(event) {
};
LineHighlighter.prototype.clearHighlight = function() {
return $("." + this.highlightLineClass).removeClass(this.highlightLineClass);
return $('.' + this.highlightLineClass).removeClass(this.highlightLineClass);
};
// Convert a URL hash String into line numbers
......@@ -135,7 +137,7 @@ LineHighlighter.prototype.hashToRange = function(hash) {
//
// lineNumber - Line number to highlight
LineHighlighter.prototype.highlightLine = function(lineNumber) {
return $("#LC" + lineNumber).addClass(this.highlightLineClass);
return $('#LC' + lineNumber).addClass(this.highlightLineClass);
};
// Highlight all lines within a range
......@@ -160,9 +162,9 @@ LineHighlighter.prototype.highlightRange = function(range) {
LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) {
var hash;
if (lastLineNumber) {
hash = "#L" + firstLineNumber + "-" + lastLineNumber;
hash = '#L' + firstLineNumber + '-' + lastLineNumber;
} else {
hash = "#L" + firstLineNumber;
hash = '#L' + firstLineNumber;
}
this._hash = hash;
return this.__setLocationHash__(hash);
......@@ -172,11 +174,15 @@ LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) {
//
// This method is stubbed in tests.
LineHighlighter.prototype.__setLocationHash__ = function(value) {
return window.history.pushState({
url: value
// We're using pushState instead of assigning location.hash directly to
// prevent the page from scrolling on the hashchange event
}, document.title, value);
return window.history.pushState(
{
url: value,
// We're using pushState instead of assigning location.hash directly to
// prevent the page from scrolling on the hashchange event
},
document.title,
value,
);
};
export default LineHighlighter;
......@@ -15,7 +15,7 @@ export default (input, parameters, escapeParameters = true) => {
let output = input;
if (parameters) {
Object.keys(parameters).forEach((parameterName) => {
Object.keys(parameters).forEach(parameterName => {
const parameterValue = parameters[parameterName];
const escapedParameterValue = escapeParameters ? _.escape(parameterValue) : parameterValue;
output = output.replace(new RegExp(`%{${parameterName}}`, 'g'), escapedParameterValue);
......
......@@ -9,7 +9,9 @@ import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
//
export default function memberExpirationDate(selector = '.js-access-expiration-date') {
function toggleClearInput() {
$(this).closest('.clearable-input').toggleClass('has-value', $(this).val() !== '');
$(this)
.closest('.clearable-input')
.toggleClass('has-value', $(this).val() !== '');
}
const inputs = $(selector);
......@@ -40,7 +42,9 @@ export default function memberExpirationDate(selector = '.js-access-expiration-d
inputs.next('.js-clear-input').on('click', function clicked(event) {
event.preventDefault();
const input = $(this).closest('.clearable-input').find(selector);
const input = $(this)
.closest('.clearable-input')
.find(selector);
const calendar = input.data('pikaday');
calendar.setDate(null);
......
......@@ -16,26 +16,29 @@ function MergeRequest(opts) {
this.opts = opts != null ? opts : {};
this.submitNoteForm = this.submitNoteForm.bind(this);
this.$el = $('.merge-request');
this.$('.show-all-commits').on('click', (function(_this) {
return function() {
return _this.showAllCommits();
};
})(this));
this.$('.show-all-commits').on(
'click',
(function(_this) {
return function() {
return _this.showAllCommits();
};
})(this),
);
this.initTabs();
this.initMRBtnListeners();
this.initCommitMessageListeners();
this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
if ($("a.btn-close").length) {
if ($('a.btn-close').length) {
this.taskList = new TaskList({
dataType: 'merge_request',
fieldName: 'description',
selector: '.detail-page-description',
onSuccess: (result) => {
onSuccess: result => {
document.querySelector('#task_status').innerText = result.task_status;
document.querySelector('#task_status_short').innerText = result.task_status_short;
}
},
});
}
}
......@@ -84,7 +87,7 @@ MergeRequest.prototype.initMRBtnListeners = function() {
MergeRequest.prototype.submitNoteForm = function(form, $button) {
var noteText;
noteText = form.find("textarea.js-note-text").val();
noteText = form.find('textarea.js-note-text').val();
if (noteText.trim().length > 0) {
form.submit();
$button.data('submitted', true);
......@@ -122,7 +125,7 @@ MergeRequest.setStatusBoxToMerged = function() {
MergeRequest.decreaseCounter = function(by = 1) {
const $el = $('.js-merge-counter');
const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0);
const count = Math.max(parseInt($el.text().replace(/[^\d]/, ''), 10) - by, 0);
$el.text(addDelimiter(count));
};
......
......@@ -15,7 +15,7 @@ export default class Milestone {
}
bindTabsSwitching() {
return $('a[data-toggle="tab"]').on('show.bs.tab', (e) => {
return $('a[data-toggle="tab"]').on('show.bs.tab', e => {
const $target = $(e.target);
window.location.hash = $target.attr('href');
......@@ -36,7 +36,8 @@ export default class Milestone {
const tabElId = $target.attr('href');
if (endpoint && !$target.hasClass('is-loaded')) {
axios.get(endpoint)
axios
.get(endpoint)
.then(({ data }) => {
$(tabElId).html(data.html);
$target.addClass('is-loaded');
......@@ -46,23 +47,28 @@ export default class Milestone {
}
static initDeprecationMessage() {
const deprecationMesssageContainer = document.querySelector('.js-milestone-deprecation-message');
const deprecationMesssageContainer = document.querySelector(
'.js-milestone-deprecation-message',
);
if (!deprecationMesssageContainer) return;
const deprecationMessage = deprecationMesssageContainer.querySelector('.js-milestone-deprecation-message-template').innerHTML;
const deprecationMessage = deprecationMesssageContainer.querySelector(
'.js-milestone-deprecation-message-template',
).innerHTML;
const $popover = $('.js-popover-link', deprecationMesssageContainer);
const hideOnScroll = togglePopover.bind($popover, false);
$popover.popover({
content: deprecationMessage,
html: true,
placement: 'bottom',
})
.on('mouseenter', mouseenter)
.on('mouseleave', debouncedMouseleave())
.on('show.bs.popover', () => {
window.addEventListener('scroll', hideOnScroll, { once: true });
});
$popover
.popover({
content: deprecationMessage,
html: true,
placement: 'bottom',
})
.on('mouseenter', mouseenter)
.on('mouseleave', debouncedMouseleave())
.on('show.bs.popover', () => {
window.addEventListener('scroll', hideOnScroll, { once: true });
});
}
}
......@@ -46,7 +46,7 @@ export default class MiniPipelineGraph {
$(document).on(
'click',
`${this.container} .js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item`,
(e) => {
e => {
e.stopPropagation();
},
);
......@@ -82,7 +82,8 @@ export default class MiniPipelineGraph {
this.renderBuildsList(button, '');
this.toggleLoading(button);
axios.get(endpoint)
axios
.get(endpoint)
.then(({ data }) => {
this.toggleLoading(button);
this.renderBuildsList(button, data.html);
......@@ -90,7 +91,11 @@ export default class MiniPipelineGraph {
})
.catch(() => {
this.toggleLoading(button);
if ($(button).parent().hasClass('open')) {
if (
$(button)
.parent()
.hasClass('open')
) {
$(button).dropdown('toggle');
}
flash('An error occurred while fetching the builds.', 'alert');
......@@ -104,8 +109,8 @@ export default class MiniPipelineGraph {
* @return {type}
*/
toggleLoading(stageContainer) {
stageContainer.parentElement.querySelector(
`${this.dropdownListSelector} .js-builds-dropdown-loading`,
).classList.toggle('hidden');
stageContainer.parentElement
.querySelector(`${this.dropdownListSelector} .js-builds-dropdown-loading`)
.classList.toggle('hidden');
}
}
......@@ -14,14 +14,14 @@ export default class NamespaceSelect {
selectable: true,
filterRemote: true,
search: {
fields: ['path']
fields: ['path'],
},
fieldName: fieldName,
toggleLabel: function(selected) {
if (selected.id == null) {
return selected.text;
} else {
return selected.kind + ": " + selected.full_path;
return selected.kind + ': ' + selected.full_path;
}
},
data: function(term, dataCallback) {
......@@ -29,7 +29,7 @@ export default class NamespaceSelect {
if (isFilter) {
const anyNamespace = {
text: 'Any namespace',
id: null
id: null,
};
namespaces.unshift(anyNamespace);
namespaces.splice(1, 0, 'divider');
......@@ -41,7 +41,7 @@ export default class NamespaceSelect {
if (namespace.id == null) {
return namespace.text;
} else {
return namespace.kind + ": " + namespace.full_path;
return namespace.kind + ': ' + namespace.full_path;
}
},
renderRow: this.renderRow,
......
......@@ -20,7 +20,7 @@ export default (function() {
this.mtime = 0;
this.mspace = 0;
this.parents = {};
this.colors = ["#000"];
this.colors = ['#000'];
this.offsetX = 150;
this.offsetY = 20;
this.unitTime = 30;
......@@ -30,9 +30,10 @@ export default (function() {
}
BranchGraph.prototype.load = function() {
axios.get(this.options.url)
axios
.get(this.options.url)
.then(({ data }) => {
$(".loading", this.element).hide();
$('.loading', this.element).hide();
this.prepareData(data.days, data.commits);
this.buildGraph();
})
......@@ -71,17 +72,19 @@ export default (function() {
c = ref[j];
this.mtime = Math.max(this.mtime, c.time);
this.mspace = Math.max(this.mspace, c.space);
results.push((function() {
var l, len1, ref1, results1;
ref1 = c.parents;
results1 = [];
for (l = 0, len1 = ref1.length; l < len1; l += 1) {
p = ref1[l];
this.parents[p[0]] = true;
results1.push(this.mspace = Math.max(this.mspace, p[1]));
}
return results1;
}).call(this));
results.push(
function() {
var l, len1, ref1, results1;
ref1 = c.parents;
results1 = [];
for (l = 0, len1 = ref1.length; l < len1; l += 1) {
p = ref1[l];
this.parents[p[0]] = true;
results1.push((this.mspace = Math.max(this.mspace, p[1])));
}
return results1;
}.call(this),
);
}
return results;
};
......@@ -91,11 +94,11 @@ export default (function() {
k = 0;
results = [];
while (k < this.mspace) {
this.colors.push(Raphael.getColor(.8));
this.colors.push(Raphael.getColor(0.8));
// Skipping a few colors in the spectrum to get more contrast between colors
Raphael.getColor();
Raphael.getColor();
results.push(k += 1);
results.push((k += 1));
}
return results;
};
......@@ -104,12 +107,12 @@ export default (function() {
var cuday, cumonth, day, j, len, mm, ref;
const { r } = this;
cuday = 0;
cumonth = "";
cumonth = '';
r.rect(0, 0, 40, this.barHeight).attr({
fill: "#222"
fill: '#222',
});
r.rect(40, 0, 30, this.barHeight).attr({
fill: "#444"
fill: '#444',
});
ref = this.days;
......@@ -118,16 +121,16 @@ export default (function() {
if (cuday !== day[0] || cumonth !== day[1]) {
// Dates
r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({
font: "12px Monaco, monospace",
fill: "#BBB"
font: '12px Monaco, monospace',
fill: '#BBB',
});
[cuday] = day;
}
if (cumonth !== day[1]) {
// Months
r.text(20, this.offsetY + this.unitTime * mm, day[1]).attr({
font: "12px Monaco, monospace",
fill: "#EEE"
font: '12px Monaco, monospace',
fill: '#EEE',
});
// eslint-disable-next-line prefer-destructuring
......@@ -173,11 +176,13 @@ export default (function() {
BranchGraph.prototype.bindEvents = function() {
const { element } = this;
return $(element).scroll((function(_this) {
return function(event) {
return _this.renderPartialGraph();
};
})(this));
return $(element).scroll(
(function(_this) {
return function(event) {
return _this.renderPartialGraph();
};
})(this),
);
};
BranchGraph.prototype.scrollDown = function() {
......@@ -219,46 +224,53 @@ export default (function() {
shortrefs = commit.refs;
// Truncate if longer than 15 chars
if (shortrefs.length > 17) {
shortrefs = shortrefs.substr(0, 15) + "";
shortrefs = shortrefs.substr(0, 15) + '';
}
text = r.text(x + 4, y, shortrefs).attr({
"text-anchor": "start",
font: "10px Monaco, monospace",
fill: "#FFF",
title: commit.refs
'text-anchor': 'start',
font: '10px Monaco, monospace',
fill: '#FFF',
title: commit.refs,
});
textbox = text.getBBox();
// Create rectangle based on the size of the textbox
rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({
fill: "#000",
"fill-opacity": .5,
stroke: "none"
fill: '#000',
'fill-opacity': 0.5,
stroke: 'none',
});
triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr({
fill: "#000",
"fill-opacity": .5,
stroke: "none"
triangle = r.path(['M', x - 5, y, 'L', x - 15, y - 4, 'L', x - 15, y + 4, 'Z']).attr({
fill: '#000',
'fill-opacity': 0.5,
stroke: 'none',
});
label = r.set(rect, text);
label.transform(["t", -rect.getBBox().width - 15, 0]);
label.transform(['t', -rect.getBBox().width - 15, 0]);
// Set text to front
return text.toFront();
};
BranchGraph.prototype.appendAnchor = function(x, y, commit) {
const { r, top, options } = this;
const anchor = r.circle(x, y, 10).attr({
fill: "#000",
opacity: 0,
cursor: "pointer"
}).click(function() {
return window.open(options.commit_url.replace("%s", commit.id), "_blank");
}).hover(function() {
this.tooltip = r.commitTooltip(x + 5, y, commit);
return top.push(this.tooltip.insertBefore(this));
}, function() {
return this.tooltip && this.tooltip.remove() && delete this.tooltip;
});
const anchor = r
.circle(x, y, 10)
.attr({
fill: '#000',
opacity: 0,
cursor: 'pointer',
})
.click(function() {
return window.open(options.commit_url.replace('%s', commit.id), '_blank');
})
.hover(
function() {
this.tooltip = r.commitTooltip(x + 5, y, commit);
return top.push(this.tooltip.insertBefore(this));
},
function() {
return this.tooltip && this.tooltip.remove() && delete this.tooltip;
},
);
return top.push(anchor);
};
......@@ -266,7 +278,7 @@ export default (function() {
const { r } = this;
r.circle(x, y, 3).attr({
fill: this.colors[commit.space],
stroke: "none"
stroke: 'none',
});
const avatar_box_x = this.offsetX + this.unitSpace * this.mspace + 10;
......@@ -274,13 +286,15 @@ export default (function() {
r.rect(avatar_box_x, avatar_box_y, 20, 20).attr({
stroke: this.colors[commit.space],
"stroke-width": 2
'stroke-width': 2,
});
r.image(commit.author.icon, avatar_box_x, avatar_box_y, 20, 20);
return r.text(this.offsetX + this.unitSpace * this.mspace + 35, y, commit.message.split("\n")[0]).attr({
"text-anchor": "start",
font: "14px Monaco, monospace"
});
return r
.text(this.offsetX + this.unitSpace * this.mspace + 35, y, commit.message.split('\n')[0])
.attr({
'text-anchor': 'start',
font: '14px Monaco, monospace',
});
};
BranchGraph.prototype.drawLines = function(x, y, commit) {
......@@ -304,30 +318,32 @@ export default (function() {
// Build line shape
if (parent[1] === commit.space) {
offset = [0, 5];
arrow = "l-2,5,4,0,-2,-5,0,5";
arrow = 'l-2,5,4,0,-2,-5,0,5';
} else if (parent[1] < commit.space) {
offset = [3, 3];
arrow = "l5,0,-2,4,-3,-4,4,2";
arrow = 'l5,0,-2,4,-3,-4,4,2';
} else {
offset = [-3, 3];
arrow = "l-5,0,2,4,3,-4,-4,2";
arrow = 'l-5,0,2,4,3,-4,-4,2';
}
// Start point
route = ["M", x + offset[0], y + offset[1]];
route = ['M', x + offset[0], y + offset[1]];
// Add arrow if not first parent
if (i > 0) {
route.push(arrow);
}
// Circumvent if overlap
if (commit.space !== parentCommit.space || commit.space !== parent[1]) {
route.push("L", parentX2, y + 10, "L", parentX2, parentY - 5);
route.push('L', parentX2, y + 10, 'L', parentX2, parentY - 5);
}
// End point
route.push("L", parentX1, parentY);
results.push(r.path(route).attr({
stroke: color,
"stroke-width": 2
}));
route.push('L', parentX1, parentY);
results.push(
r.path(route).attr({
stroke: color,
'stroke-width': 2,
}),
);
}
return results;
};
......@@ -337,10 +353,10 @@ export default (function() {
const { r } = this;
const x = this.offsetX + this.unitSpace * (this.mspace - commit.space);
const y = this.offsetY + this.unitTime * commit.time;
r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr({
fill: "#000",
"fill-opacity": .5,
stroke: "none"
r.path(['M', x + 5, y, 'L', x + 15, y + 4, 'L', x + 15, y - 4, 'Z']).attr({
fill: '#000',
'fill-opacity': 0.5,
stroke: 'none',
});
// Displayed in the center
return this.element.scrollTop(y - this.graphHeight / 2);
......
......@@ -49,7 +49,7 @@ Raphael.prototype.textWrap = function testWrap(t, width) {
const s = [];
for (let j = 0, len = words.length; j < len; j += 1) {
const word = words[j];
if (x + (word.length * letterWidth) > width) {
if (x + word.length * letterWidth > width) {
s.push('\n');
x = 0;
}
......
......@@ -30,24 +30,24 @@ export default class NewBranchForm {
startsWith = {
pattern: /^(\/|\.)/g,
prefix: "can't start with",
conjunction: "or"
conjunction: 'or',
};
endsWith = {
pattern: /(\/|\.|\.lock)$/g,
prefix: "can't end in",
conjunction: "or"
conjunction: 'or',
};
invalid = {
pattern: /(\s|~|\^|:|\?|\*|\[|\\|\.\.|@\{|\/{2,}){1}/g,
prefix: "can't contain",
conjunction: ", "
conjunction: ', ',
};
single = {
pattern: /^@+$/g,
prefix: "can't be",
conjunction: "or"
conjunction: 'or',
};
return this.restrictions = [startsWith, invalid, endsWith, single];
return (this.restrictions = [startsWith, invalid, endsWith, single]);
}
validate() {
......@@ -73,7 +73,7 @@ export default class NewBranchForm {
return "'" + value + "'";
}
});
return restriction.prefix + " " + (formatted.join(restriction.conjunction));
return restriction.prefix + ' ' + formatted.join(restriction.conjunction);
};
validator = (function(_this) {
return function(errors, restriction) {
......@@ -88,7 +88,7 @@ export default class NewBranchForm {
})(this);
errors = this.restrictions.reduce(validator, []);
if (errors.length > 0) {
errorMessage = $("<span/>").text(errors.join(', '));
errorMessage = $('<span/>').text(errors.join(', '));
return this.branchNameError.append(errorMessage);
}
}
......
......@@ -6,9 +6,7 @@ export default class NewCommitForm {
this.branchName = form.find('.js-branch-name');
this.originalBranch = form.find('.js-original-branch');
this.createMergeRequest = form.find('.js-create-merge-request');
this.createMergeRequestContainer = form.find(
'.js-create-merge-request-container',
);
this.createMergeRequestContainer = form.find('.js-create-merge-request-container');
this.branchName.keyup(this.renderDestination);
this.renderDestination();
}
......
......@@ -18,7 +18,9 @@ export default function notificationsDropdown() {
$(document).on('ajax:success', '.notification-form', (e, data) => {
if (data.saved) {
$(e.currentTarget).closest('.js-notification-dropdown').replaceWith(data.html);
$(e.currentTarget)
.closest('.js-notification-dropdown')
.replaceWith(data.html);
} else {
Flash('Failed to save new settings', 'alert');
}
......
......@@ -22,7 +22,8 @@ export default class NotificationsForm {
// eslint-disable-next-line class-methods-use-this
showCheckboxLoadingSpinner($parent) {
$parent.addClass('is-loading')
$parent
.addClass('is-loading')
.find('.custom-notification-event-loading')
.removeClass('fa-check')
.addClass('fa-spin fa-spinner')
......@@ -38,9 +39,12 @@ export default class NotificationsForm {
.then(({ data }) => {
$checkbox.enable();
if (data.saved) {
$parent.find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done');
$parent
.find('.custom-notification-event-loading')
.toggleClass('fa-spin fa-spinner fa-check is-done');
setTimeout(() => {
$parent.removeClass('is-loading')
$parent
.removeClass('is-loading')
.find('.custom-notification-event-loading')
.toggleClass('fa-spin fa-spinner fa-check is-done');
}, 2000);
......
......@@ -24,22 +24,25 @@ export default {
getOld() {
this.loading.show();
axios.get(this.url, {
params: {
limit: this.limit,
offset: this.offset,
},
}).then(({ data }) => {
this.append(data.count, this.prepareData(data.html));
this.callback();
axios
.get(this.url, {
params: {
limit: this.limit,
offset: this.offset,
},
})
.then(({ data }) => {
this.append(data.count, this.prepareData(data.html));
this.callback();
// keep loading until we've filled the viewport height
if (!this.disable && !this.isScrollable()) {
this.getOld();
} else {
this.loading.hide();
}
}).catch(() => this.loading.hide());
// keep loading until we've filled the viewport height
if (!this.disable && !this.isScrollable()) {
this.getOld();
} else {
this.loading.hide();
}
})
.catch(() => this.loading.hide());
},
append(count, html) {
......
<script>
import pdfjsLib from 'vendor/pdf';
import workerSrc from 'vendor/pdf.worker.min';
import pdfjsLib from 'vendor/pdf';
import workerSrc from 'vendor/pdf.worker.min';
import page from './page/index.vue';
import page from './page/index.vue';
export default {
components: { page },
props: {
pdf: {
type: [String, Uint8Array],
required: true,
},
export default {
components: { page },
props: {
pdf: {
type: [String, Uint8Array],
required: true,
},
data() {
return {
loading: false,
pages: [],
};
},
data() {
return {
loading: false,
pages: [],
};
},
computed: {
document() {
return typeof this.pdf === 'string' ? this.pdf : { data: this.pdf };
},
computed: {
document() {
return typeof this.pdf === 'string' ? this.pdf : { data: this.pdf };
},
hasPDF() {
return this.pdf && this.pdf.length > 0;
},
hasPDF() {
return this.pdf && this.pdf.length > 0;
},
watch: { pdf: 'load' },
mounted() {
pdfjsLib.PDFJS.workerSrc = workerSrc;
if (this.hasPDF) this.load();
},
watch: { pdf: 'load' },
mounted() {
pdfjsLib.PDFJS.workerSrc = workerSrc;
if (this.hasPDF) this.load();
},
methods: {
load() {
this.pages = [];
return pdfjsLib
.getDocument(this.document)
.then(this.renderPages)
.then(() => this.$emit('pdflabload'))
.catch(error => this.$emit('pdflaberror', error))
.then(() => {
this.loading = false;
});
},
methods: {
load() {
this.pages = [];
return pdfjsLib.getDocument(this.document)
.then(this.renderPages)
.then(() => this.$emit('pdflabload'))
.catch(error => this.$emit('pdflaberror', error))
.then(() => { this.loading = false; });
},
renderPages(pdf) {
const pagePromises = [];
this.loading = true;
for (let num = 1; num <= pdf.numPages; num += 1) {
pagePromises.push(
pdf.getPage(num).then(p => this.pages.push(p)),
);
}
return Promise.all(pagePromises);
},
renderPages(pdf) {
const pagePromises = [];
this.loading = true;
for (let num = 1; num <= pdf.numPages; num += 1) {
pagePromises.push(pdf.getPage(num).then(p => this.pages.push(p)));
}
return Promise.all(pagePromises);
},
};
},
};
</script>
<template>
......@@ -69,9 +70,9 @@
</template>
<style>
.pdf-viewer {
background: url('./assets/img/bg.gif');
display: flex;
flex-flow: column nowrap;
}
.pdf-viewer {
background: url('./assets/img/bg.gif');
display: flex;
flex-flow: column nowrap;
}
</style>
<script>
export default {
props: {
page: {
type: Object,
required: true,
},
number: {
type: Number,
required: true,
},
export default {
props: {
page: {
type: Object,
required: true,
},
data() {
return {
scale: 4,
rendering: false,
};
number: {
type: Number,
required: true,
},
},
data() {
return {
scale: 4,
rendering: false,
};
},
computed: {
viewport() {
return this.page.getViewport(this.scale);
},
computed: {
viewport() {
return this.page.getViewport(this.scale);
},
context() {
return this.$refs.canvas.getContext('2d');
},
renderContext() {
return {
canvasContext: this.context,
viewport: this.viewport,
};
},
context() {
return this.$refs.canvas.getContext('2d');
},
mounted() {
this.$refs.canvas.height = this.viewport.height;
this.$refs.canvas.width = this.viewport.width;
this.rendering = true;
this.page.render(this.renderContext)
.then(() => { this.rendering = false; })
.catch(error => this.$emit('pdflaberror', error));
renderContext() {
return {
canvasContext: this.context,
viewport: this.viewport,
};
},
};
},
mounted() {
this.$refs.canvas.height = this.viewport.height;
this.$refs.canvas.width = this.viewport.width;
this.rendering = true;
this.page
.render(this.renderContext)
.then(() => {
this.rendering = false;
})
.catch(error => this.$emit('pdflaberror', error));
},
};
</script>
<template>
......@@ -51,20 +54,20 @@
</template>
<style>
.pdf-page {
margin: 8px auto 0 auto;
border-top: 1px #ddd solid;
border-bottom: 1px #ddd solid;
width: 100%;
}
.pdf-page {
margin: 8px auto 0 auto;
border-top: 1px #ddd solid;
border-bottom: 1px #ddd solid;
width: 100%;
}
.pdf-page:first-child {
margin-top: 0px;
border-top: 0px;
}
.pdf-page:first-child {
margin-top: 0px;
border-top: 0px;
}
.pdf-page:last-child {
margin-bottom: 0px;
border-bottom: 0px;
}
.pdf-page:last-child {
margin-bottom: 0px;
border-bottom: 0px;
}
</style>
......@@ -64,10 +64,7 @@ export default {
return [];
}
const { details } = this.pipeline;
return [
...(details.manual_actions || []),
...(details.scheduled_actions || []),
];
return [...(details.manual_actions || []), ...(details.scheduled_actions || [])];
},
/**
* If provided, returns the commit tag.
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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