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() { ...@@ -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 * the last minute to deconstruct this JSON hash and set the `text/plain` and `text/x-gfm` copy
* data types to the intended values. * data types to the intended values.
*/ */
$(document).on('copy', 'body > textarea[readonly]', (e) => { $(document).on('copy', 'body > textarea[readonly]', e => {
const { clipboardData } = e.originalEvent; const { clipboardData } = e.originalEvent;
if (!clipboardData) return; if (!clipboardData) return;
......
...@@ -2,7 +2,9 @@ import $ from 'jquery'; ...@@ -2,7 +2,9 @@ import $ from 'jquery';
$(() => { $(() => {
$('body').on('click', '.js-details-target', function target() { $('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. // Show details content. Hides link after click.
...@@ -13,7 +15,9 @@ $(() => { ...@@ -13,7 +15,9 @@ $(() => {
// //
$('body').on('click', '.js-details-expand', function expand(e) { $('body').on('click', '.js-details-expand', function expand(e) {
e.preventDefault(); e.preventDefault();
$(this).next('.js-details-content').removeClass('hide'); $(this)
.next('.js-details-content')
.removeClass('hide');
$(this).hide(); $(this).hide();
const truncatedItem = $(this).siblings('.js-details-short'); const truncatedItem = $(this).siblings('.js-details-short');
......
...@@ -34,7 +34,7 @@ const gfmRules = { ...@@ -34,7 +34,7 @@ const gfmRules = {
}, },
}, },
AutolinkFilter: { AutolinkFilter: {
'a'(el, text) { a(el, text) {
// Fallback on the regular MarkdownFilter's `a` handler. // Fallback on the regular MarkdownFilter's `a` handler.
if (text !== el.getAttribute('href')) return false; if (text !== el.getAttribute('href')) return false;
...@@ -60,7 +60,7 @@ const gfmRules = { ...@@ -60,7 +60,7 @@ const gfmRules = {
}, },
}, },
ImageLazyLoadFilter: { ImageLazyLoadFilter: {
'img'(el, text) { img(el, text) {
return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`; return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
}, },
}, },
...@@ -71,7 +71,7 @@ const gfmRules = { ...@@ -71,7 +71,7 @@ const gfmRules = {
return CopyAsGFM.nodeToGFM(videoEl); return CopyAsGFM.nodeToGFM(videoEl);
}, },
'video'(el) { video(el) {
return `![${el.dataset.title}](${el.getAttribute('src')})`; return `![${el.dataset.title}](${el.getAttribute('src')})`;
}, },
}, },
...@@ -118,11 +118,14 @@ const gfmRules = { ...@@ -118,11 +118,14 @@ const gfmRules = {
'a[name]:not([href]):empty'(el) { 'a[name]:not([href]):empty'(el) {
return el.outerHTML; return el.outerHTML;
}, },
'dl'(el, text) { dl(el, text) {
let lines = text.replace(/\n\n/g, '\n').trim().split('\n'); let lines = text
.replace(/\n\n/g, '\n')
.trim()
.split('\n');
// Add two spaces to the front of subsequent list items lines, // Add two spaces to the front of subsequent list items lines,
// or leave the line entirely blank. // or leave the line entirely blank.
lines = lines.map((l) => { lines = lines.map(l => {
const line = l.trim(); const line = l.trim();
if (line.length === 0) return ''; if (line.length === 0) return '';
...@@ -151,27 +154,30 @@ const gfmRules = { ...@@ -151,27 +154,30 @@ const gfmRules = {
// Prefixes lines with 4 spaces if the code contains triple backticks // Prefixes lines with 4 spaces if the code contains triple backticks
if (lang === '' && text.match(/^```/gm)) { if (lang === '' && text.match(/^```/gm)) {
return text.split('\n').map((l) => { return text
const line = l.trim(); .split('\n')
if (line.length === 0) return ''; .map(l => {
const line = l.trim();
return ` ${line}`; if (line.length === 0) return '';
}).join('\n');
return ` ${line}`;
})
.join('\n');
} }
return `\`\`\`${lang}\n${text}\n\`\`\``; return `\`\`\`${lang}\n${text}\n\`\`\``;
}, },
'pre > code'(el, text) { 'pre > code'(el, text) {
// Don't wrap code blocks in `` // Don't wrap code blocks in ``
return text; return text;
}, },
}, },
MarkdownFilter: { MarkdownFilter: {
'br'(el) { br(el) {
// Two spaces at the end of a line are turned into a BR // Two spaces at the end of a line are turned into a BR
return ' '; return ' ';
}, },
'code'(el, text) { code(el, text) {
let backtickCount = 1; let backtickCount = 1;
const backtickMatch = text.match(/`+/); const backtickMatch = text.match(/`+/);
if (backtickMatch) { if (backtickMatch) {
...@@ -183,27 +189,31 @@ const gfmRules = { ...@@ -183,27 +189,31 @@ const gfmRules = {
return backticks + spaceOrNoSpace + text.trim() + spaceOrNoSpace + backticks; return backticks + spaceOrNoSpace + text.trim() + spaceOrNoSpace + backticks;
}, },
'blockquote'(el, text) { blockquote(el, text) {
return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n'); return text
.trim()
.split('\n')
.map(s => `> ${s}`.trim())
.join('\n');
}, },
'img'(el) { img(el) {
const imageSrc = el.src; 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})`; return `![${el.getAttribute('alt')}](${imageUrl})`;
}, },
'a.anchor'(el, text) { 'a.anchor'(el, text) {
// Don't render a Markdown link for the anchor link inside a heading // Don't render a Markdown link for the anchor link inside a heading
return text; return text;
}, },
'a'(el, text) { a(el, text) {
return `[${text}](${el.getAttribute('href')})`; return `[${text}](${el.getAttribute('href')})`;
}, },
'li'(el, text) { li(el, text) {
const lines = text.trim().split('\n'); const lines = text.trim().split('\n');
const firstLine = `- ${lines.shift()}`; const firstLine = `- ${lines.shift()}`;
// Add four spaces to the front of subsequent list items lines, // Add four spaces to the front of subsequent list items lines,
// or leave the line entirely blank. // or leave the line entirely blank.
const nextLines = lines.map((s) => { const nextLines = lines.map(s => {
if (s.trim().length === 0) return ''; if (s.trim().length === 0) return '';
return ` ${s}`; return ` ${s}`;
...@@ -211,49 +221,49 @@ const gfmRules = { ...@@ -211,49 +221,49 @@ const gfmRules = {
return `${firstLine}\n${nextLines.join('\n')}`; return `${firstLine}\n${nextLines.join('\n')}`;
}, },
'ul'(el, text) { ul(el, text) {
return text; return text;
}, },
'ol'(el, text) { ol(el, text) {
// LIs get a `- ` prefix by default, which we replace by `1. ` for ordered lists. // 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`; return `# ${text.trim()}\n`;
}, },
'h2'(el, text) { h2(el, text) {
return `## ${text.trim()}\n`; return `## ${text.trim()}\n`;
}, },
'h3'(el, text) { h3(el, text) {
return `### ${text.trim()}\n`; return `### ${text.trim()}\n`;
}, },
'h4'(el, text) { h4(el, text) {
return `#### ${text.trim()}\n`; return `#### ${text.trim()}\n`;
}, },
'h5'(el, text) { h5(el, text) {
return `##### ${text.trim()}\n`; return `##### ${text.trim()}\n`;
}, },
'h6'(el, text) { h6(el, text) {
return `###### ${text.trim()}\n`; return `###### ${text.trim()}\n`;
}, },
'strong'(el, text) { strong(el, text) {
return `**${text}**`; return `**${text}**`;
}, },
'em'(el, text) { em(el, text) {
return `_${text}_`; return `_${text}_`;
}, },
'del'(el, text) { del(el, text) {
return `~~${text}~~`; return `~~${text}~~`;
}, },
'hr'(el) { hr(el) {
// extra leading \n is to ensure that there is a blank line between // 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 // a list followed by an hr, otherwise this breaks old redcarpet rendering
return '\n-----\n'; return '\n-----\n';
}, },
'p'(el, text) { p(el, text) {
return `${text.trim()}\n`; return `${text.trim()}\n`;
}, },
'table'(el) { table(el) {
const theadEl = el.querySelector('thead'); const theadEl = el.querySelector('thead');
const tbodyEl = el.querySelector('tbody'); const tbodyEl = el.querySelector('tbody');
if (!theadEl || !tbodyEl) return false; if (!theadEl || !tbodyEl) return false;
...@@ -263,8 +273,8 @@ const gfmRules = { ...@@ -263,8 +273,8 @@ const gfmRules = {
return [theadText, tbodyText].join('\n'); return [theadText, tbodyText].join('\n');
}, },
'thead'(el, text) { thead(el, text) {
const cells = _.map(el.querySelectorAll('th'), (cell) => { const cells = _.map(el.querySelectorAll('th'), cell => {
let chars = CopyAsGFM.nodeToGFM(cell).length + 2; let chars = CopyAsGFM.nodeToGFM(cell).length + 2;
let before = ''; let before = '';
...@@ -296,7 +306,7 @@ const gfmRules = { ...@@ -296,7 +306,7 @@ const gfmRules = {
return [text, separatorRow].join('\n'); return [text, separatorRow].join('\n');
}, },
'tr'(el) { tr(el) {
const cellEls = el.querySelectorAll('td, th'); const cellEls = el.querySelectorAll('td, th');
if (cellEls.length === 0) return false; if (cellEls.length === 0) return false;
...@@ -315,8 +325,12 @@ export class CopyAsGFM { ...@@ -315,8 +325,12 @@ export class CopyAsGFM {
const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent); const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent);
if (isIOS) return; if (isIOS) return;
$(document).on('copy', '.md, .wiki', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); }); $(document).on('copy', '.md, .wiki', e => {
$(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); }); 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); $(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM);
} }
...@@ -356,7 +370,7 @@ export class CopyAsGFM { ...@@ -356,7 +370,7 @@ export class CopyAsGFM {
// This will break down when the actual code block contains an uneven // This will break down when the actual code block contains an uneven
// number of backticks, but this is a rare edge case. // number of backticks, but this is a rare edge case.
const backtickMatch = textBefore.match(/`/g); const backtickMatch = textBefore.match(/`/g);
const insideCodeBlock = backtickMatch && (backtickMatch.length % 2) === 1; const insideCodeBlock = backtickMatch && backtickMatch.length % 2 === 1;
if (insideCodeBlock) { if (insideCodeBlock) {
return text; return text;
...@@ -393,7 +407,9 @@ export class CopyAsGFM { ...@@ -393,7 +407,9 @@ export class CopyAsGFM {
let lineSelector = '.line'; let lineSelector = '.line';
if (target) { 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) { if (lineClass) {
lineSelector = `.line_content.${lineClass} ${lineSelector}`; lineSelector = `.line_content.${lineClass} ${lineSelector}`;
} }
...@@ -436,7 +452,8 @@ export class CopyAsGFM { ...@@ -436,7 +452,8 @@ export class CopyAsGFM {
return node.textContent; 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); const text = this.innerGFM(node, respectWhitespace);
......
...@@ -32,7 +32,9 @@ export default function renderMath($els) { ...@@ -32,7 +32,9 @@ export default function renderMath($els) {
Promise.all([ Promise.all([
import(/* webpackChunkName: 'katex' */ 'katex'), import(/* webpackChunkName: 'katex' */ 'katex'),
import(/* webpackChunkName: 'katex' */ 'katex/dist/katex.min.css'), import(/* webpackChunkName: 'katex' */ 'katex/dist/katex.min.css'),
]).then(([katex]) => { ])
renderWithKaTeX($els, katex); .then(([katex]) => {
}).catch(() => flash(__('An error occurred while rendering KaTeX'))); renderWithKaTeX($els, katex);
})
.catch(() => flash(__('An error occurred while rendering KaTeX')));
} }
...@@ -17,41 +17,43 @@ import flash from '~/flash'; ...@@ -17,41 +17,43 @@ import flash from '~/flash';
export default function renderMermaid($els) { export default function renderMermaid($els) {
if (!$els.length) return; if (!$els.length) return;
import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid').then((mermaid) => { import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid')
mermaid.initialize({ .then(mermaid => {
// mermaid core options mermaid.initialize({
mermaid: { // mermaid core options
startOnLoad: false, mermaid: {
}, startOnLoad: false,
// mermaidAPI options },
theme: 'neutral', // mermaidAPI options
}); theme: 'neutral',
});
$els.each((i, el) => { $els.each((i, el) => {
const source = el.textContent; const source = el.textContent;
// Remove any extra spans added by the backend syntax highlighting. // Remove any extra spans added by the backend syntax highlighting.
Object.assign(el, { textContent: source }); Object.assign(el, { textContent: source });
mermaid.init(undefined, el, (id) => { mermaid.init(undefined, el, id => {
const svg = document.getElementById(id); const svg = document.getElementById(id);
svg.classList.add('mermaid'); svg.classList.add('mermaid');
// pre > code > svg // pre > code > svg
svg.closest('pre').replaceWith(svg); svg.closest('pre').replaceWith(svg);
// We need to add the original source into the DOM to allow Copy-as-GFM // We need to add the original source into the DOM to allow Copy-as-GFM
// to access it. // to access it.
const sourceEl = document.createElement('text'); const sourceEl = document.createElement('text');
sourceEl.classList.add('source'); sourceEl.classList.add('source');
sourceEl.setAttribute('display', 'none'); sourceEl.setAttribute('display', 'none');
sourceEl.textContent = source; 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.'; ...@@ -26,7 +26,7 @@ MarkdownPreview.prototype.emptyMessage = 'Nothing to preview.';
MarkdownPreview.prototype.ajaxCache = {}; MarkdownPreview.prototype.ajaxCache = {};
MarkdownPreview.prototype.showPreview = function ($form) { MarkdownPreview.prototype.showPreview = function($form) {
var mdText; var mdText;
var markdownVersion; var markdownVersion;
var url; var url;
...@@ -44,34 +44,40 @@ MarkdownPreview.prototype.showPreview = function ($form) { ...@@ -44,34 +44,40 @@ MarkdownPreview.prototype.showPreview = function ($form) {
this.hideReferencedUsers($form); this.hideReferencedUsers($form);
} else { } else {
preview.addClass('md-preview-loading').text('Loading...'); preview.addClass('md-preview-loading').text('Loading...');
this.fetchMarkdownPreview(mdText, url, (function (response) { this.fetchMarkdownPreview(
var body; mdText,
if (response.body.length > 0) { url,
({ body } = response); function(response) {
} else { var body;
body = this.emptyMessage; if (response.body.length > 0) {
} ({ body } = response);
} else {
preview.removeClass('md-preview-loading').html(body); body = this.emptyMessage;
preview.renderGFM(); }
this.renderReferencedUsers(response.references.users, $form);
preview.removeClass('md-preview-loading').html(body);
if (response.references.commands) { preview.renderGFM();
this.renderReferencedCommands(response.references.commands, $form); this.renderReferencedUsers(response.references.users, $form);
}
}).bind(this)); 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') { if (typeof markdownVersion === 'undefined') {
return markdownPreviewPath; 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) { if (!url) {
return; return;
} }
...@@ -79,24 +85,25 @@ MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) { ...@@ -79,24 +85,25 @@ MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) {
success(this.ajaxCache.response); success(this.ajaxCache.response);
return; return;
} }
axios.post(url, { axios
text, .post(url, {
}) text,
.then(({ data }) => { })
this.ajaxCache = { .then(({ data }) => {
text: text, this.ajaxCache = {
response: data, text: text,
}; response: data,
success(data); };
}) success(data);
.catch(() => flash(__('An error occurred while fetching markdown preview'))); })
.catch(() => flash(__('An error occurred while fetching markdown preview')));
}; };
MarkdownPreview.prototype.hideReferencedUsers = function ($form) { MarkdownPreview.prototype.hideReferencedUsers = function($form) {
$form.find('.referenced-users').hide(); $form.find('.referenced-users').hide();
}; };
MarkdownPreview.prototype.renderReferencedUsers = function (users, $form) { MarkdownPreview.prototype.renderReferencedUsers = function(users, $form) {
var referencedUsers; var referencedUsers;
referencedUsers = $form.find('.referenced-users'); referencedUsers = $form.find('.referenced-users');
if (referencedUsers.length) { if (referencedUsers.length) {
...@@ -109,11 +116,11 @@ MarkdownPreview.prototype.renderReferencedUsers = function (users, $form) { ...@@ -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(); $form.find('.referenced-commands').hide();
}; };
MarkdownPreview.prototype.renderReferencedCommands = function (commands, $form) { MarkdownPreview.prototype.renderReferencedCommands = function(commands, $form) {
var referencedCommands; var referencedCommands;
referencedCommands = $form.find('.referenced-commands'); referencedCommands = $form.find('.referenced-commands');
if (commands.length > 0) { if (commands.length > 0) {
...@@ -132,14 +139,14 @@ writeButtonSelector = '.js-md-write-button'; ...@@ -132,14 +139,14 @@ writeButtonSelector = '.js-md-write-button';
lastTextareaPreviewed = null; lastTextareaPreviewed = null;
const markdownToolbar = $('.md-header-toolbar'); const markdownToolbar = $('.md-header-toolbar');
$.fn.setupMarkdownPreview = function () { $.fn.setupMarkdownPreview = function() {
var $form = $(this); var $form = $(this);
$form.find('textarea.markdown-area').on('input', function () { $form.find('textarea.markdown-area').on('input', function() {
markdownPreview.hideReferencedUsers($form); markdownPreview.hideReferencedUsers($form);
}); });
}; };
$(document).on('markdown-preview:show', function (e, $form) { $(document).on('markdown-preview:show', function(e, $form) {
if (!$form) { if (!$form) {
return; return;
} }
...@@ -148,8 +155,14 @@ $(document).on('markdown-preview:show', function (e, $form) { ...@@ -148,8 +155,14 @@ $(document).on('markdown-preview:show', function (e, $form) {
lastTextareaHeight = lastTextareaPreviewed.height(); lastTextareaHeight = lastTextareaPreviewed.height();
// toggle tabs // toggle tabs
$form.find(writeButtonSelector).parent().removeClass('active'); $form
$form.find(previewButtonSelector).parent().addClass('active'); .find(writeButtonSelector)
.parent()
.removeClass('active');
$form
.find(previewButtonSelector)
.parent()
.addClass('active');
// toggle content // toggle content
$form.find('.md-write-holder').hide(); $form.find('.md-write-holder').hide();
...@@ -158,7 +171,7 @@ $(document).on('markdown-preview:show', function (e, $form) { ...@@ -158,7 +171,7 @@ $(document).on('markdown-preview:show', function (e, $form) {
markdownPreview.showPreview($form); markdownPreview.showPreview($form);
}); });
$(document).on('markdown-preview:hide', function (e, $form) { $(document).on('markdown-preview:hide', function(e, $form) {
if (!$form) { if (!$form) {
return; return;
} }
...@@ -169,8 +182,14 @@ $(document).on('markdown-preview:hide', function (e, $form) { ...@@ -169,8 +182,14 @@ $(document).on('markdown-preview:hide', function (e, $form) {
} }
// toggle tabs // toggle tabs
$form.find(writeButtonSelector).parent().addClass('active'); $form
$form.find(previewButtonSelector).parent().removeClass('active'); .find(writeButtonSelector)
.parent()
.addClass('active');
$form
.find(previewButtonSelector)
.parent()
.removeClass('active');
// toggle content // toggle content
$form.find('.md-write-holder').show(); $form.find('.md-write-holder').show();
...@@ -181,7 +200,7 @@ $(document).on('markdown-preview:hide', function (e, $form) { ...@@ -181,7 +200,7 @@ $(document).on('markdown-preview:hide', function (e, $form) {
markdownPreview.hideReferencedCommands($form); markdownPreview.hideReferencedCommands($form);
}); });
$(document).on('markdown-preview:toggle', function (e, keyboardEvent) { $(document).on('markdown-preview:toggle', function(e, keyboardEvent) {
var $target; var $target;
$target = $(keyboardEvent.target); $target = $(keyboardEvent.target);
if ($target.is('textarea.markdown-area')) { if ($target.is('textarea.markdown-area')) {
...@@ -194,14 +213,14 @@ $(document).on('markdown-preview:toggle', function (e, keyboardEvent) { ...@@ -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; var $form;
e.preventDefault(); e.preventDefault();
$form = $(this).closest('form'); $form = $(this).closest('form');
$(document).triggerHandler('markdown-preview:show', [$form]); $(document).triggerHandler('markdown-preview:show', [$form]);
}); });
$(document).on('click', writeButtonSelector, function (e) { $(document).on('click', writeButtonSelector, function(e) {
var $form; var $form;
e.preventDefault(); e.preventDefault();
$form = $(this).closest('form'); $form = $(this).closest('form');
......
...@@ -28,7 +28,7 @@ function keyCodeIs(e, keyCode) { ...@@ -28,7 +28,7 @@ function keyCodeIs(e, keyCode) {
return e.keyCode === keyCode; return e.keyCode === keyCode;
} }
$(document).on('keydown.quick_submit', '.js-quick-submit', (e) => { $(document).on('keydown.quick_submit', '.js-quick-submit', e => {
// Enter // Enter
if (!keyCodeIs(e, 13)) { if (!keyCodeIs(e, 13)) {
return; return;
...@@ -55,23 +55,25 @@ $(document).on('keydown.quick_submit', '.js-quick-submit', (e) => { ...@@ -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 // 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 // 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) { $(document).on(
// Tab 'keyup.quick_submit',
if (!keyCodeIs(e, 9)) { '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]',
return; function displayTooltip(e) {
} // Tab
if (!keyCodeIs(e, 9)) {
return;
}
const $this = $(this); const $this = $(this);
const title = isMac() ? const title = isMac() ? 'You can also press ⌘-Enter' : 'You can also press Ctrl-Enter';
'You can also press ⌘-Enter' :
'You can also press Ctrl-Enter';
$this.tooltip({ $this.tooltip({
container: 'body', container: 'body',
html: 'true', html: 'true',
placement: 'top', placement: 'top',
title, title,
trigger: 'manual', trigger: 'manual',
}); });
$this.tooltip('show').one('blur click', () => $this.tooltip('hide')); $this.tooltip('show').one('blur click', () => $this.tooltip('hide'));
}); },
);
...@@ -18,7 +18,8 @@ import '../commons/bootstrap'; ...@@ -18,7 +18,8 @@ import '../commons/bootstrap';
$.fn.requiresInput = function requiresInput() { $.fn.requiresInput = function requiresInput() {
const $form = $(this); const $form = $(this);
const $button = $('button[type=submit], input[type=submit]', $form); 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() { function requireInput() {
// Collect the input values of *all* required fields // Collect the input values of *all* required fields
......
...@@ -32,16 +32,18 @@ export default class SecretValues { ...@@ -32,16 +32,18 @@ export default class SecretValues {
updateDom(isRevealed) { updateDom(isRevealed) {
const values = this.container.querySelectorAll(this.valueSelector); const values = this.container.querySelectorAll(this.valueSelector);
values.forEach((value) => { values.forEach(value => {
value.classList.toggle('hide', !isRevealed); value.classList.toggle('hide', !isRevealed);
}); });
const placeholders = this.container.querySelectorAll(this.placeholderSelector); const placeholders = this.container.querySelectorAll(this.placeholderSelector);
placeholders.forEach((placeholder) => { placeholders.forEach(placeholder => {
placeholder.classList.toggle('hide', isRevealed); 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; this.revealButton.dataset.secretRevealStatus = isRevealed;
} }
} }
...@@ -88,22 +88,24 @@ export default class Shortcuts { ...@@ -88,22 +88,24 @@ export default class Shortcuts {
return null; return null;
} }
return axios.get(gon.shortcuts_path, { return axios
responseType: 'text', .get(gon.shortcuts_path, {
}).then(({ data }) => { responseType: 'text',
$.globalEval(data); })
.then(({ data }) => {
if (location && location.length > 0) { $.globalEval(data);
const results = [];
for (let i = 0, len = location.length; i < len; i += 1) { if (location && location.length > 0) {
results.push($(location[i]).show()); 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(); $('.hidden-shortcut').show();
return $('.js-more-help-button').remove(); return $('.js-more-help-button').remove();
}); });
} }
focusFilter(e) { focusFilter(e) {
......
...@@ -18,9 +18,7 @@ $(() => { ...@@ -18,9 +18,7 @@ $(() => {
.toggleClass('fa-chevron-up', toggleState) .toggleClass('fa-chevron-up', toggleState)
.toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined); .toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
$container $container.find('.js-toggle-content').toggle(toggleState);
.find('.js-toggle-content')
.toggle(toggleState);
} }
$('body').on('click', '.js-toggle-button', function toggleButton(e) { $('body').on('click', '.js-toggle-button', function toggleButton(e) {
......
...@@ -18,12 +18,7 @@ export default class Renderer { ...@@ -18,12 +18,7 @@ export default class Renderer {
this.loader = new STLLoader(); this.loader = new STLLoader();
this.fov = 45; this.fov = 45;
this.camera = new THREE.PerspectiveCamera( this.camera = new THREE.PerspectiveCamera(this.fov, this.width / this.height, 1, 1000);
this.fov,
this.width / this.height,
1,
1000,
);
this.scene = new THREE.Scene(); this.scene = new THREE.Scene();
...@@ -35,10 +30,7 @@ export default class Renderer { ...@@ -35,10 +30,7 @@ export default class Renderer {
this.setupLight(); this.setupLight();
// Set up OrbitControls // Set up OrbitControls
this.controls = new OrbitControls( this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.camera,
this.renderer.domElement,
);
this.controls.minDistance = 5; this.controls.minDistance = 5;
this.controls.maxDistance = 30; this.controls.maxDistance = 30;
this.controls.enableKeys = false; this.controls.enableKeys = false;
...@@ -51,47 +43,32 @@ export default class Renderer { ...@@ -51,47 +43,32 @@ export default class Renderer {
antialias: true, antialias: true,
}); });
this.renderer.setClearColor(0xFFFFFF); this.renderer.setClearColor(0xffffff);
this.renderer.setPixelRatio(window.devicePixelRatio); this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setSize( this.renderer.setSize(this.width, this.height);
this.width,
this.height,
);
} }
setupLight() { setupLight() {
// Point light illuminates the object // Point light illuminates the object
const pointLight = new THREE.PointLight( const pointLight = new THREE.PointLight(0xffffff, 2, 0);
0xFFFFFF,
2,
0,
);
pointLight.castShadow = true; pointLight.castShadow = true;
this.camera.add(pointLight); this.camera.add(pointLight);
// Ambient light illuminates the scene // Ambient light illuminates the scene
const ambientLight = new THREE.AmbientLight( const ambientLight = new THREE.AmbientLight(0xffffff, 1);
0xFFFFFF,
1,
);
this.scene.add(ambientLight); this.scene.add(ambientLight);
} }
setupGrid() { setupGrid() {
this.grid = new THREE.GridHelper( this.grid = new THREE.GridHelper(20, 20, 0x000000, 0x000000);
20,
20,
0x000000,
0x000000,
);
this.scene.add(this.grid); this.scene.add(this.grid);
} }
loadFile() { loadFile() {
this.loader.load(this.container.dataset.endpoint, (geo) => { this.loader.load(this.container.dataset.endpoint, geo => {
const obj = new MeshObject(geo); const obj = new MeshObject(geo);
this.objects.push(obj); this.objects.push(obj);
...@@ -116,30 +93,23 @@ export default class Renderer { ...@@ -116,30 +93,23 @@ export default class Renderer {
} }
render() { render() {
this.renderer.render( this.renderer.render(this.scene, this.camera);
this.scene,
this.camera,
);
requestAnimationFrame(this.renderWrapper); requestAnimationFrame(this.renderWrapper);
} }
changeObjectMaterials(type) { changeObjectMaterials(type) {
this.objects.forEach((obj) => { this.objects.forEach(obj => {
obj.changeMaterial(type); obj.changeMaterial(type);
}); });
} }
setDefaultCameraPosition() { setDefaultCameraPosition() {
const obj = this.objects[0]; const obj = this.objects[0];
const radius = (obj.geometry.boundingSphere.radius / 1.5); const radius = obj.geometry.boundingSphere.radius / 1.5;
const dist = radius / (Math.sin((this.fov * (Math.PI / 180)) / 2)); const dist = radius / Math.sin((this.fov * (Math.PI / 180)) / 2);
this.camera.position.set( this.camera.position.set(0, dist + 1, dist);
0,
dist + 1,
dist,
);
this.camera.lookAt(this.grid); this.camera.lookAt(this.grid);
this.controls.update(); this.controls.update();
......
import { import { Matrix4, MeshLambertMaterial, Mesh } from 'three/build/three.module';
Matrix4,
MeshLambertMaterial,
Mesh,
} from 'three/build/three.module';
const defaultColor = 0xE24329; const defaultColor = 0xe24329;
const materials = { const materials = {
default: new MeshLambertMaterial({ default: new MeshLambertMaterial({
color: defaultColor, color: defaultColor,
...@@ -17,10 +13,7 @@ const materials = { ...@@ -17,10 +13,7 @@ const materials = {
export default class MeshObject extends Mesh { export default class MeshObject extends Mesh {
constructor(geo) { constructor(geo) {
super( super(geo, materials.default);
geo,
materials.default,
);
this.geometry.computeBoundingSphere(); this.geometry.computeBoundingSphere();
...@@ -29,13 +22,7 @@ export default class MeshObject extends Mesh { ...@@ -29,13 +22,7 @@ export default class MeshObject extends Mesh {
if (this.geometry.boundingSphere.radius > 4) { if (this.geometry.boundingSphere.radius > 4) {
const scale = 4 / this.geometry.boundingSphere.radius; const scale = 4 / this.geometry.boundingSphere.radius;
this.geometry.applyMatrix( this.geometry.applyMatrix(new Matrix4().makeScale(scale, scale, scale));
new Matrix4().makeScale(
scale,
scale,
scale,
),
);
this.geometry.computeBoundingSphere(); this.geometry.computeBoundingSphere();
this.position.x = -this.geometry.boundingSphere.center.x; this.position.x = -this.geometry.boundingSphere.center.x;
......
...@@ -42,7 +42,7 @@ class BalsamiqViewer { ...@@ -42,7 +42,7 @@ class BalsamiqViewer {
this.initDatabase(loadEvent.target.response); this.initDatabase(loadEvent.target.response);
const previews = this.getPreviews(); const previews = this.getPreviews();
previews.forEach((preview) => { previews.forEach(preview => {
const renderedPreview = this.renderPreview(preview); const renderedPreview = this.renderPreview(preview);
container.appendChild(renderedPreview); container.appendChild(renderedPreview);
......
...@@ -41,39 +41,45 @@ export default class BlobFileDropzone { ...@@ -41,39 +41,45 @@ export default class BlobFileDropzone {
addRemoveLinks: true, addRemoveLinks: true,
previewsContainer: '.dropzone-previews', previewsContainer: '.dropzone-previews',
headers: csrf.headers, headers: csrf.headers,
init: function () { init: function() {
this.on('addedfile', function () { this.on('addedfile', function() {
toggleLoading(submitButton, submitButtonLoadingIcon, false); toggleLoading(submitButton, submitButtonLoadingIcon, false);
dropzoneMessage.addClass(HIDDEN_CLASS); dropzoneMessage.addClass(HIDDEN_CLASS);
$('.dropzone-alerts').html('').hide(); $('.dropzone-alerts')
.html('')
.hide();
}); });
this.on('removedfile', function () { this.on('removedfile', function() {
toggleLoading(submitButton, submitButtonLoadingIcon, false); toggleLoading(submitButton, submitButtonLoadingIcon, false);
dropzoneMessage.removeClass(HIDDEN_CLASS); dropzoneMessage.removeClass(HIDDEN_CLASS);
}); });
this.on('success', function (header, response) { this.on('success', function(header, response) {
$('#modal-upload-blob').modal('hide'); $('#modal-upload-blob').modal('hide');
visitUrl(response.filePath); visitUrl(response.filePath);
}); });
this.on('maxfilesexceeded', function (file) { this.on('maxfilesexceeded', function(file) {
dropzoneMessage.addClass(HIDDEN_CLASS); dropzoneMessage.addClass(HIDDEN_CLASS);
this.removeFile(file); 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('branch_name', form.find('.js-branch-name').val());
formData.append('create_merge_request', form.find('.js-create-merge-request').val()); formData.append('create_merge_request', form.find('.js-create-merge-request').val());
formData.append('commit_message', form.find('.js-commit-message').val()); formData.append('commit_message', form.find('.js-commit-message').val());
}); });
}, },
// Override behavior of adding error underneath preview // Override behavior of adding error underneath preview
error: function (file, errorMessage) { error: function(file, errorMessage) {
const stripped = $('<div/>').html(errorMessage).text(); const stripped = $('<div/>')
$('.dropzone-alerts').html(`Error uploading file: "${stripped}"`).show(); .html(errorMessage)
.text();
$('.dropzone-alerts')
.html(`Error uploading file: "${stripped}"`)
.show();
this.removeFile(file); this.removeFile(file);
}, },
}); });
submitButton.on('click', (e) => { submitButton.on('click', e => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (dropzone[0].dropzone.getQueuedFiles().length === 0) { if (dropzone[0].dropzone.getQueuedFiles().length === 0) {
......
...@@ -2,17 +2,19 @@ import { getLocationHash } from '../lib/utils/url_utility'; ...@@ -2,17 +2,19 @@ import { getLocationHash } from '../lib/utils/url_utility';
const lineNumberRe = /^L[0-9]+/; const lineNumberRe = /^L[0-9]+/;
const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => { const updateLineNumbersOnBlobPermalinks = linksToUpdate => {
const hash = getLocationHash(); const hash = getLocationHash();
if (hash && lineNumberRe.test(hash)) { if (hash && lineNumberRe.test(hash)) {
const hashUrlString = `#${hash}`; const hashUrlString = `#${hash}`;
[].concat(Array.prototype.slice.call(linksToUpdate)).forEach((permalinkButton) => { [].concat(Array.prototype.slice.call(linksToUpdate)).forEach(permalinkButton => {
const baseHref = permalinkButton.getAttribute('data-original-href') || (() => { const baseHref =
const href = permalinkButton.getAttribute('href'); permalinkButton.getAttribute('data-original-href') ||
permalinkButton.setAttribute('data-original-href', href); (() => {
return href; const href = permalinkButton.getAttribute('href');
})(); permalinkButton.setAttribute('data-original-href', href);
return href;
})();
permalinkButton.setAttribute('href', `${baseHref}${hashUrlString}`); permalinkButton.setAttribute('href', `${baseHref}${hashUrlString}`);
}); });
} }
...@@ -26,7 +28,7 @@ function BlobLinePermalinkUpdater(blobContentHolder, lineNumberSelector, element ...@@ -26,7 +28,7 @@ function BlobLinePermalinkUpdater(blobContentHolder, lineNumberSelector, element
}, 0); }, 0);
}; };
blobContentHolder.addEventListener('click', (e) => { blobContentHolder.addEventListener('click', e => {
if (e.target.matches(lineNumberSelector)) { if (e.target.matches(lineNumberSelector)) {
updateBlameAndBlobPermalinkCb(); updateBlameAndBlobPermalinkCb();
} }
......
...@@ -45,15 +45,11 @@ export default class FileTemplateSelector { ...@@ -45,15 +45,11 @@ export default class FileTemplateSelector {
} }
renderLoading() { renderLoading() {
this.$loadingIcon this.$loadingIcon.addClass('fa-spinner fa-spin').removeClass('fa-chevron-down');
.addClass('fa-spinner fa-spin')
.removeClass('fa-chevron-down');
} }
renderLoaded() { renderLoaded() {
this.$loadingIcon this.$loadingIcon.addClass('fa-chevron-down').removeClass('fa-spinner fa-spin');
.addClass('fa-chevron-down')
.removeClass('fa-spinner fa-spin');
} }
reportSelection(options) { reportSelection(options) {
......
...@@ -40,13 +40,14 @@ export default () => { ...@@ -40,13 +40,14 @@ export default () => {
}, },
methods: { methods: {
loadFile() { loadFile() {
axios.get(el.dataset.endpoint) axios
.get(el.dataset.endpoint)
.then(res => res.data) .then(res => res.data)
.then((data) => { .then(data => {
this.json = data; this.json = data;
this.loading = false; this.loading = false;
}) })
.catch((e) => { .catch(e => {
if (e.status !== 200) { if (e.status !== 200) {
this.loadError = true; this.loadError = true;
} }
......
...@@ -13,7 +13,7 @@ export default class SketchLoader { ...@@ -13,7 +13,7 @@ export default class SketchLoader {
return this.getZipFile() return this.getZipFile()
.then(data => JSZip.loadAsync(data)) .then(data => JSZip.loadAsync(data))
.then(asyncResult => asyncResult.files['previews/preview.png'].async('uint8array')) .then(asyncResult => asyncResult.files['previews/preview.png'].async('uint8array'))
.then((content) => { .then(content => {
const url = window.URL || window.webkitURL; const url = window.URL || window.webkitURL;
const blob = new Blob([new Uint8Array(content)], { const blob = new Blob([new Uint8Array(content)], {
type: 'image/png', type: 'image/png',
......
...@@ -3,8 +3,8 @@ import Renderer from './3d_viewer'; ...@@ -3,8 +3,8 @@ import Renderer from './3d_viewer';
export default () => { export default () => {
const viewer = new Renderer(document.getElementById('js-stl-viewer')); const viewer = new Renderer(document.getElementById('js-stl-viewer'));
[].slice.call(document.querySelectorAll('.js-material-changer')).forEach((el) => { [].slice.call(document.querySelectorAll('.js-material-changer')).forEach(el => {
el.addEventListener('click', (e) => { el.addEventListener('click', e => {
const { target } = e; const { target } = e;
e.preventDefault(); e.preventDefault();
......
...@@ -81,14 +81,10 @@ export default class TemplateSelector { ...@@ -81,14 +81,10 @@ export default class TemplateSelector {
} }
startLoadingSpinner() { startLoadingSpinner() {
this.$dropdownIcon this.$dropdownIcon.addClass('fa-spinner fa-spin').removeClass('fa-chevron-down');
.addClass('fa-spinner fa-spin')
.removeClass('fa-chevron-down');
} }
stopLoadingSpinner() { stopLoadingSpinner() {
this.$dropdownIcon this.$dropdownIcon.addClass('fa-chevron-down').removeClass('fa-spinner fa-spin');
.addClass('fa-chevron-down')
.removeClass('fa-spinner fa-spin');
} }
} }
...@@ -22,7 +22,7 @@ export default class BlobLicenseSelector extends FileTemplateSelector { ...@@ -22,7 +22,7 @@ export default class BlobLicenseSelector extends FileTemplateSelector {
search: { search: {
fields: ['name'], fields: ['name'],
}, },
clicked: (options) => { clicked: options => {
const { e } = options; const { e } = options;
const el = options.$el; const el = options.$el;
const query = options.selectedObj; const query = options.selectedObj;
......
...@@ -21,5 +21,4 @@ export default class FileTemplateTypeSelector extends FileTemplateSelector { ...@@ -21,5 +21,4 @@ export default class FileTemplateTypeSelector extends FileTemplateSelector {
text: item => item.name, text: item => item.name,
}); });
} }
} }
...@@ -22,9 +22,8 @@ export default class BlobViewer { ...@@ -22,9 +22,8 @@ export default class BlobViewer {
const viewer = document.querySelector('.blob-viewer[data-type="rich"]'); const viewer = document.querySelector('.blob-viewer[data-type="rich"]');
if (!viewer || !viewer.dataset.richType) return; if (!viewer || !viewer.dataset.richType) return;
const initViewer = promise => promise const initViewer = promise =>
.then(module => module.default(viewer)) promise.then(module => module.default(viewer)).catch(error => {
.catch((error) => {
Flash('Error loading file viewer.'); Flash('Error loading file viewer.');
throw error; throw error;
}); });
...@@ -79,10 +78,9 @@ export default class BlobViewer { ...@@ -79,10 +78,9 @@ export default class BlobViewer {
initBindings() { initBindings() {
if (this.switcherBtns.length) { if (this.switcherBtns.length) {
Array.from(this.switcherBtns) Array.from(this.switcherBtns).forEach(el => {
.forEach((el) => { el.addEventListener('click', this.switchViewHandler.bind(this));
el.addEventListener('click', this.switchViewHandler.bind(this)); });
});
} }
if (this.copySourceBtn) { if (this.copySourceBtn) {
...@@ -109,7 +107,10 @@ export default class BlobViewer { ...@@ -109,7 +107,10 @@ export default class BlobViewer {
this.copySourceBtn.setAttribute('title', 'Copy source to clipboard'); this.copySourceBtn.setAttribute('title', 'Copy source to clipboard');
this.copySourceBtn.classList.remove('disabled'); this.copySourceBtn.classList.remove('disabled');
} else if (this.activeViewer === this.simpleViewer) { } 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'); this.copySourceBtn.classList.add('disabled');
} else { } else {
this.copySourceBtn.setAttribute('title', 'Switch to the source to copy it to the clipboard'); this.copySourceBtn.setAttribute('title', 'Switch to the source to copy it to the clipboard');
...@@ -147,15 +148,15 @@ export default class BlobViewer { ...@@ -147,15 +148,15 @@ export default class BlobViewer {
this.toggleCopyButtonState(); this.toggleCopyButtonState();
BlobViewer.loadViewer(newViewer) BlobViewer.loadViewer(newViewer)
.then((viewer) => { .then(viewer => {
$(viewer).renderGFM(); $(viewer).renderGFM();
this.$fileHolder.trigger('highlight:line'); this.$fileHolder.trigger('highlight:line');
handleLocationHash(); handleLocationHash();
this.toggleCopyButtonState(); this.toggleCopyButtonState();
}) })
.catch(() => new Flash('Error loading viewer')); .catch(() => new Flash('Error loading viewer'));
} }
static loadViewer(viewerParam) { static loadViewer(viewerParam) {
...@@ -168,12 +169,11 @@ export default class BlobViewer { ...@@ -168,12 +169,11 @@ export default class BlobViewer {
viewer.setAttribute('data-loading', 'true'); viewer.setAttribute('data-loading', 'true');
return axios.get(url) return axios.get(url).then(({ data }) => {
.then(({ data }) => { viewer.innerHTML = data.html;
viewer.innerHTML = data.html; viewer.setAttribute('data-loaded', 'true');
viewer.setAttribute('data-loaded', 'true');
return viewer; return viewer;
}); });
} }
} }
<script> <script>
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
import iconCycleAnalyticsSplash from 'icons/_icon_cycle_analytics_splash.svg'; import iconCycleAnalyticsSplash from 'icons/_icon_cycle_analytics_splash.svg';
export default { export default {
components: { components: {
Icon, Icon,
},
props: {
documentationLink: {
type: String,
required: true,
}, },
props: { },
documentationLink: { computed: {
type: String, iconCycleAnalyticsSplash() {
required: true, return iconCycleAnalyticsSplash;
},
}, },
computed: { },
iconCycleAnalyticsSplash() { methods: {
return iconCycleAnalyticsSplash; dismissOverviewDialog() {
}, this.$emit('dismiss-overview-dialog');
}, },
methods: { },
dismissOverviewDialog() { };
this.$emit('dismiss-overview-dialog');
},
},
};
</script> </script>
<template> <template>
<div class="landing content-block"> <div class="landing content-block">
......
<script> <script>
import tooltip from '../../vue_shared/directives/tooltip'; import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
directives: { directives: {
tooltip, tooltip,
},
props: {
count: {
type: Number,
required: true,
}, },
props: { },
count: { };
type: Number,
required: true,
},
},
};
</script> </script>
<template> <template>
<span <span
......
<script> <script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue'; import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue'; import totalTime from './total_time_component.vue';
export default { export default {
components: { components: {
userAvatarImage, userAvatarImage,
limitWarning, limitWarning,
totalTime, totalTime,
},
props: {
items: {
type: Array,
default: () => [],
}, },
props: { stage: {
items: { type: Object,
type: Array, default: () => ({}),
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
}, },
}; },
};
</script> </script>
<template> <template>
<div> <div>
......
<script> <script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue'; import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue'; import totalTime from './total_time_component.vue';
export default { export default {
components: { components: {
userAvatarImage, userAvatarImage,
limitWarning, limitWarning,
totalTime, totalTime,
},
props: {
items: {
type: Array,
default: () => [],
}, },
props: { stage: {
items: { type: Object,
type: Array, default: () => ({}),
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
}, },
}; },
};
</script> </script>
<template> <template>
<div> <div>
...@@ -73,4 +73,3 @@ ...@@ -73,4 +73,3 @@
</ul> </ul>
</div> </div>
</template> </template>
<script> <script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import iconCommit from '../svg/icon_commit.svg'; import iconCommit from '../svg/icon_commit.svg';
import limitWarning from './limit_warning_component.vue'; import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue'; import totalTime from './total_time_component.vue';
export default { export default {
components: { components: {
userAvatarImage, userAvatarImage,
totalTime, totalTime,
limitWarning, limitWarning,
},
props: {
items: {
type: Array,
default: () => [],
}, },
props: { stage: {
items: { type: Object,
type: Array, default: () => ({}),
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
}, },
computed: { },
iconCommit() { computed: {
return iconCommit; iconCommit() {
}, return iconCommit;
}, },
}; },
};
</script> </script>
<template> <template>
<div> <div>
...@@ -74,4 +74,3 @@ ...@@ -74,4 +74,3 @@
</ul> </ul>
</div> </div>
</template> </template>
<script> <script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import limitWarning from './limit_warning_component.vue'; import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue'; import totalTime from './total_time_component.vue';
import icon from '../../vue_shared/components/icon.vue'; import icon from '../../vue_shared/components/icon.vue';
export default { export default {
components: { components: {
userAvatarImage, userAvatarImage,
totalTime, totalTime,
limitWarning, limitWarning,
icon, icon,
},
props: {
items: {
type: Array,
default: () => [],
}, },
props: { stage: {
items: { type: Object,
type: Array, default: () => ({}),
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
}, },
}; },
};
</script> </script>
<template> <template>
<div> <div>
......
<script> <script>
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import iconBranch from '../svg/icon_branch.svg'; import iconBranch from '../svg/icon_branch.svg';
import limitWarning from './limit_warning_component.vue'; import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue'; import totalTime from './total_time_component.vue';
import icon from '../../vue_shared/components/icon.vue'; import icon from '../../vue_shared/components/icon.vue';
export default { export default {
components: { components: {
userAvatarImage, userAvatarImage,
totalTime, totalTime,
limitWarning, limitWarning,
icon, icon,
},
props: {
items: {
type: Array,
default: () => [],
}, },
props: { stage: {
items: { type: Object,
type: Array, default: () => ({}),
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
}, },
computed: { },
iconBranch() { computed: {
return iconBranch; iconBranch() {
}, return iconBranch;
}, },
}; },
};
</script> </script>
<template> <template>
<div> <div>
......
<script> <script>
import iconBuildStatus from '../svg/icon_build_status.svg'; import iconBuildStatus from '../svg/icon_build_status.svg';
import iconBranch from '../svg/icon_branch.svg'; import iconBranch from '../svg/icon_branch.svg';
import limitWarning from './limit_warning_component.vue'; import limitWarning from './limit_warning_component.vue';
import totalTime from './total_time_component.vue'; import totalTime from './total_time_component.vue';
import icon from '../../vue_shared/components/icon.vue'; import icon from '../../vue_shared/components/icon.vue';
export default { export default {
components: { components: {
totalTime, totalTime,
limitWarning, limitWarning,
icon, icon,
},
props: {
items: {
type: Array,
default: () => [],
}, },
props: { stage: {
items: { type: Object,
type: Array, default: () => ({}),
default: () => [],
},
stage: {
type: Object,
default: () => ({}),
},
}, },
computed: { },
iconBuildStatus() { computed: {
return iconBuildStatus; iconBuildStatus() {
}, return iconBuildStatus;
iconBranch() {
return iconBranch;
},
}, },
}; iconBranch() {
return iconBranch;
},
},
};
</script> </script>
<template> <template>
<div> <div>
......
<script> <script>
export default { export default {
props: { props: {
time: { time: {
type: Object, type: Object,
required: false, required: false,
default: () => ({}), default: () => ({}),
},
}, },
computed: { },
hasData() { computed: {
return Object.keys(this.time).length; hasData() {
}, return Object.keys(this.time).length;
}, },
}; },
};
</script> </script>
<template> <template>
<span class="total-time"> <span class="total-time">
......
...@@ -18,7 +18,8 @@ Vue.use(Translate); ...@@ -18,7 +18,8 @@ Vue.use(Translate);
export default () => { export default () => {
const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed'; 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', el: '#cycle-analytics',
name: 'CycleAnalytics', name: 'CycleAnalytics',
components: { components: {
...@@ -66,14 +67,17 @@ export default () => { ...@@ -66,14 +67,17 @@ export default () => {
const $dropdown = $('.js-ca-dropdown'); const $dropdown = $('.js-ca-dropdown');
const $label = $dropdown.find('.dropdown-label'); const $label = $dropdown.find('.dropdown-label');
$dropdown.find('li a').off('click').on('click', (e) => { $dropdown
e.preventDefault(); .find('li a')
const $target = $(e.currentTarget); .off('click')
this.startDate = $target.data('value'); .on('click', e => {
e.preventDefault();
const $target = $(e.currentTarget);
this.startDate = $target.data('value');
$label.text($target.text().trim()); $label.text($target.text().trim());
this.fetchCycleAnalyticsData({ startDate: this.startDate }); this.fetchCycleAnalyticsData({ startDate: this.startDate });
}); });
}, },
fetchCycleAnalyticsData(options) { fetchCycleAnalyticsData(options) {
const fetchOptions = options || { startDate: this.startDate }; const fetchOptions = options || { startDate: this.startDate };
...@@ -82,7 +86,7 @@ export default () => { ...@@ -82,7 +86,7 @@ export default () => {
this.service this.service
.fetchCycleAnalyticsData(fetchOptions) .fetchCycleAnalyticsData(fetchOptions)
.then((response) => { .then(response => {
this.store.setCycleAnalyticsData(response); this.store.setCycleAnalyticsData(response);
this.selectDefaultStage(); this.selectDefaultStage();
this.initDropdown(); this.initDropdown();
...@@ -115,7 +119,7 @@ export default () => { ...@@ -115,7 +119,7 @@ export default () => {
stage, stage,
startDate: this.startDate, startDate: this.startDate,
}) })
.then((response) => { .then(response => {
this.isEmptyStage = !response.events.length; this.isEmptyStage = !response.events.length;
this.store.setStageEvents(response.events, stage); this.store.setStageEvents(response.events, stage);
this.isLoadingStage = false; this.isLoadingStage = false;
......
...@@ -18,10 +18,7 @@ export default class CycleAnalyticsService { ...@@ -18,10 +18,7 @@ export default class CycleAnalyticsService {
} }
fetchStageData(options) { fetchStageData(options) {
const { const { stage, startDate } = options;
stage,
startDate,
} = options;
return this.axios return this.axios
.get(`events/${stage.name}.json`, { .get(`events/${stage.name}.json`, {
......
...@@ -5,13 +5,27 @@ import { dasherize } from '../lib/utils/text_utility'; ...@@ -5,13 +5,27 @@ import { dasherize } from '../lib/utils/text_utility';
import DEFAULT_EVENT_OBJECTS from './default_event_objects'; import DEFAULT_EVENT_OBJECTS from './default_event_objects';
const EMPTY_STAGE_TEXTS = { 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.'), issue: __(
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.'), '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.',
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.'), plan: __(
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.'), '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.',
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.'), 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 { export default {
...@@ -31,11 +45,11 @@ export default { ...@@ -31,11 +45,11 @@ export default {
newData.stages = data.stats || []; newData.stages = data.stats || [];
newData.summary = data.summary || []; newData.summary = data.summary || [];
newData.summary.forEach((item) => { newData.summary.forEach(item => {
item.value = item.value || '-'; item.value = item.value || '-';
}); });
newData.stages.forEach((item) => { newData.stages.forEach(item => {
const stageSlug = dasherize(item.name.toLowerCase()); const stageSlug = dasherize(item.name.toLowerCase());
item.active = false; item.active = false;
item.isUserAllowed = data.permissions[stageSlug]; item.isUserAllowed = data.permissions[stageSlug];
...@@ -53,7 +67,7 @@ export default { ...@@ -53,7 +67,7 @@ export default {
this.state.hasError = state; this.state.hasError = state;
}, },
deactivateAllStages() { deactivateAllStages() {
this.state.stages.forEach((stage) => { this.state.stages.forEach(stage => {
stage.active = false; stage.active = false;
}); });
}, },
...@@ -67,7 +81,7 @@ export default { ...@@ -67,7 +81,7 @@ export default {
decorateEvents(events, stage) { decorateEvents(events, stage) {
const newEvents = []; const newEvents = [];
events.forEach((item) => { events.forEach(item => {
if (!item) return; if (!item) return;
const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item); const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item);
......
...@@ -18,52 +18,56 @@ const CommentAndResolveBtn = Vue.extend({ ...@@ -18,52 +18,56 @@ const CommentAndResolveBtn = Vue.extend({
}; };
}, },
computed: { computed: {
showButton: function () { showButton: function() {
if (this.discussion) { if (this.discussion) {
return this.discussion.isResolvable(); return this.discussion.isResolvable();
} else { } else {
return false; return false;
} }
}, },
isDiscussionResolved: function () { isDiscussionResolved: function() {
return this.discussion.isResolved(); return this.discussion.isResolved();
}, },
buttonText: function () { buttonText: function() {
if (this.isDiscussionResolved) { if (this.isDiscussionResolved) {
if (this.textareaIsEmpty) { if (this.textareaIsEmpty) {
return "Unresolve discussion"; return 'Unresolve discussion';
} else { } else {
return "Comment & unresolve discussion"; return 'Comment & unresolve discussion';
} }
} else { } else {
if (this.textareaIsEmpty) { if (this.textareaIsEmpty) {
return "Resolve discussion"; return 'Resolve discussion';
} else { } else {
return "Comment & resolve discussion"; return 'Comment & resolve discussion';
} }
} }
} },
}, },
created() { created() {
if (this.discussionId) { if (this.discussionId) {
this.discussion = CommentsStore.state[this.discussionId]; this.discussion = CommentsStore.state[this.discussionId];
} }
}, },
mounted: function () { mounted: function() {
if (!this.discussionId) return; 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() === ''; this.textareaIsEmpty = $textarea.val() === '';
$textarea.on('input.comment-and-resolve-btn', () => { $textarea.on('input.comment-and-resolve-btn', () => {
this.textareaIsEmpty = $textarea.val() === ''; this.textareaIsEmpty = $textarea.val() === '';
}); });
}, },
destroyed: function () { destroyed: function() {
if (!this.discussionId) return; 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); Vue.component('comment-and-resolve-btn', CommentAndResolveBtn);
...@@ -83,7 +83,11 @@ const DiffNoteAvatars = Vue.extend({ ...@@ -83,7 +83,11 @@ const DiffNoteAvatars = Vue.extend({
this.addNoCommentClass(); this.addNoCommentClass();
this.setDiscussionVisible(); 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', () => { $(document).on('toggle.comments', () => {
...@@ -113,20 +117,30 @@ const DiffNoteAvatars = Vue.extend({ ...@@ -113,20 +117,30 @@ const DiffNoteAvatars = Vue.extend({
addNoCommentClass() { addNoCommentClass() {
const { notesCount } = this; const { notesCount } = this;
$(this.$el).closest('.js-avatar-container') $(this.$el)
.closest('.js-avatar-container')
.toggleClass('no-comment-btn', notesCount > 0) .toggleClass('no-comment-btn', notesCount > 0)
.nextUntil('.js-avatar-container') .nextUntil('.js-avatar-container')
.toggleClass('no-comment-btn', notesCount > 0); .toggleClass('no-comment-btn', notesCount > 0);
}, },
toggleDiscussionsToggleState() { 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 $visibleNotesHolders = $notesHolders.filter(':visible');
const $toggleDiffCommentsBtn = $(this.$el).closest('.diff-file').find('.js-toggle-diff-comments'); const $toggleDiffCommentsBtn = $(this.$el)
.closest('.diff-file')
$toggleDiffCommentsBtn.toggleClass('active', $notesHolders.length === $visibleNotesHolders.length); .find('.js-toggle-diff-comments');
$toggleDiffCommentsBtn.toggleClass(
'active',
$notesHolders.length === $visibleNotesHolders.length,
);
}, },
setDiscussionVisible() { 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) { getTooltipText(note) {
return `${note.authorName}: ${note.noteTruncated}`; return `${note.authorName}: ${note.noteTruncated}`;
......
...@@ -14,24 +14,24 @@ const JumpToDiscussion = Vue.extend({ ...@@ -14,24 +14,24 @@ const JumpToDiscussion = Vue.extend({
required: true, required: true,
}, },
}, },
data: function () { data: function() {
return { return {
discussions: CommentsStore.state, discussions: CommentsStore.state,
discussion: {}, discussion: {},
}; };
}, },
computed: { computed: {
buttonText: function () { buttonText: function() {
if (this.discussionId) { if (this.discussionId) {
return 'Jump to next unresolved discussion'; return 'Jump to next unresolved discussion';
} else { } else {
return 'Jump to first unresolved discussion'; return 'Jump to first unresolved discussion';
} }
}, },
allResolved: function () { allResolved: function() {
return this.unresolvedDiscussionCount === 0; return this.unresolvedDiscussionCount === 0;
}, },
showButton: function () { showButton: function() {
if (this.discussionId) { if (this.discussionId) {
if (this.unresolvedDiscussionCount > 1) { if (this.unresolvedDiscussionCount > 1) {
return true; return true;
...@@ -42,7 +42,7 @@ const JumpToDiscussion = Vue.extend({ ...@@ -42,7 +42,7 @@ const JumpToDiscussion = Vue.extend({
return this.unresolvedDiscussionCount >= 1; return this.unresolvedDiscussionCount >= 1;
} }
}, },
lastResolvedId: function () { lastResolvedId: function() {
let lastId; let lastId;
for (const discussionId in this.discussions) { for (const discussionId in this.discussions) {
const discussion = this.discussions[discussionId]; const discussion = this.discussions[discussionId];
...@@ -52,13 +52,13 @@ const JumpToDiscussion = Vue.extend({ ...@@ -52,13 +52,13 @@ const JumpToDiscussion = Vue.extend({
} }
} }
return lastId; return lastId;
} },
}, },
created() { created() {
this.discussion = this.discussions[this.discussionId]; this.discussion = this.discussions[this.discussionId];
}, },
methods: { methods: {
jumpToNextUnresolvedDiscussion: function () { jumpToNextUnresolvedDiscussion: function() {
let discussionsSelector; let discussionsSelector;
let discussionIdsInScope; let discussionIdsInScope;
let firstUnresolvedDiscussionId; let firstUnresolvedDiscussionId;
...@@ -68,9 +68,11 @@ const JumpToDiscussion = Vue.extend({ ...@@ -68,9 +68,11 @@ const JumpToDiscussion = Vue.extend({
let jumpToFirstDiscussion = !this.discussionId; let jumpToFirstDiscussion = !this.discussionId;
const discussionIdsForElements = function(elements) { const discussionIdsForElements = function(elements) {
return elements.map(function() { return elements
return $(this).attr('data-discussion-id'); .map(function() {
}).toArray(); return $(this).attr('data-discussion-id');
})
.toArray();
}; };
const { discussions } = this; const { discussions } = this;
...@@ -144,8 +146,7 @@ const JumpToDiscussion = Vue.extend({ ...@@ -144,8 +146,7 @@ const JumpToDiscussion = Vue.extend({
if (!discussion.isResolved()) { if (!discussion.isResolved()) {
nextUnresolvedDiscussionId = discussionId; nextUnresolvedDiscussionId = discussionId;
break; break;
} } else {
else {
continue; continue;
} }
} }
...@@ -175,9 +176,9 @@ const JumpToDiscussion = Vue.extend({ ...@@ -175,9 +176,9 @@ const JumpToDiscussion = Vue.extend({
// Resolved discussions are hidden in the diffs tab by default. // 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. // 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. // 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 // Image diff discussions does not use notes_holder
// so we should keep original $target value in those cases // so we should keep original $target value in those cases
...@@ -194,7 +195,7 @@ const JumpToDiscussion = Vue.extend({ ...@@ -194,7 +195,7 @@ const JumpToDiscussion = Vue.extend({
prevEl = $target.prev(); prevEl = $target.prev();
// If the discussion doesn't have 4 lines above it, we'll have to do with fewer. // 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; break;
} }
...@@ -203,9 +204,9 @@ const JumpToDiscussion = Vue.extend({ ...@@ -203,9 +204,9 @@ const JumpToDiscussion = Vue.extend({
} }
$.scrollTo($target, { $.scrollTo($target, {
offset: -150 offset: -150,
}); });
} },
}, },
}); });
......
...@@ -13,17 +13,17 @@ window.ResolveCount = Vue.extend({ ...@@ -13,17 +13,17 @@ window.ResolveCount = Vue.extend({
required: true, required: true,
}, },
}, },
data: function () { data: function() {
return { return {
discussions: CommentsStore.state discussions: CommentsStore.state,
}; };
}, },
computed: { computed: {
allResolved: function () { allResolved: function() {
return this.resolvedDiscussionCount === this.discussionCount; return this.resolvedDiscussionCount === this.discussionCount;
}, },
resolvedCountText() { resolvedCountText() {
return this.discussionCount === 1 ? 'discussion' : 'discussions'; return this.discussionCount === 1 ? 'discussion' : 'discussions';
} },
} },
}); });
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
const DiscussionMixins = { const DiscussionMixins = {
computed: { computed: {
discussionCount: function () { discussionCount: function() {
return Object.keys(this.discussions).length; return Object.keys(this.discussions).length;
}, },
resolvedDiscussionCount: function () { resolvedDiscussionCount: function() {
let resolvedCount = 0; let resolvedCount = 0;
for (const discussionId in this.discussions) { for (const discussionId in this.discussions) {
...@@ -18,7 +18,7 @@ const DiscussionMixins = { ...@@ -18,7 +18,7 @@ const DiscussionMixins = {
return resolvedCount; return resolvedCount;
}, },
unresolvedDiscussionCount: function () { unresolvedDiscussionCount: function() {
let unresolvedCount = 0; let unresolvedCount = 0;
for (const discussionId in this.discussions) { for (const discussionId in this.discussions) {
...@@ -30,8 +30,8 @@ const DiscussionMixins = { ...@@ -30,8 +30,8 @@ const DiscussionMixins = {
} }
return unresolvedCount; return unresolvedCount;
} },
} },
}; };
export default DiscussionMixins; export default DiscussionMixins;
...@@ -6,22 +6,22 @@ import Vue from 'vue'; ...@@ -6,22 +6,22 @@ import Vue from 'vue';
import { localTimeAgo } from '../../lib/utils/datetime_utility'; import { localTimeAgo } from '../../lib/utils/datetime_utility';
class DiscussionModel { class DiscussionModel {
constructor (discussionId) { constructor(discussionId) {
this.id = discussionId; this.id = discussionId;
this.notes = {}; this.notes = {};
this.loading = false; this.loading = false;
this.canResolve = false; this.canResolve = false;
} }
createNote (noteObj) { createNote(noteObj) {
Vue.set(this.notes, noteObj.noteId, new NoteModel(this.id, noteObj)); Vue.set(this.notes, noteObj.noteId, new NoteModel(this.id, noteObj));
} }
deleteNote (noteId) { deleteNote(noteId) {
Vue.delete(this.notes, noteId); Vue.delete(this.notes, noteId);
} }
getNote (noteId) { getNote(noteId) {
return this.notes[noteId]; return this.notes[noteId];
} }
...@@ -29,7 +29,7 @@ class DiscussionModel { ...@@ -29,7 +29,7 @@ class DiscussionModel {
return Object.keys(this.notes).length; return Object.keys(this.notes).length;
} }
isResolved () { isResolved() {
for (const noteId in this.notes) { for (const noteId in this.notes) {
const note = this.notes[noteId]; const note = this.notes[noteId];
...@@ -40,7 +40,7 @@ class DiscussionModel { ...@@ -40,7 +40,7 @@ class DiscussionModel {
return true; return true;
} }
resolveAllNotes (resolved_by) { resolveAllNotes(resolved_by) {
for (const noteId in this.notes) { for (const noteId in this.notes) {
const note = this.notes[noteId]; const note = this.notes[noteId];
...@@ -51,7 +51,7 @@ class DiscussionModel { ...@@ -51,7 +51,7 @@ class DiscussionModel {
} }
} }
unResolveAllNotes () { unResolveAllNotes() {
for (const noteId in this.notes) { for (const noteId in this.notes) {
const note = this.notes[noteId]; const note = this.notes[noteId];
...@@ -62,7 +62,7 @@ class DiscussionModel { ...@@ -62,7 +62,7 @@ class DiscussionModel {
} }
} }
updateHeadline (data) { updateHeadline(data) {
const discussionSelector = `.discussion[data-discussion-id="${this.id}"]`; const discussionSelector = `.discussion[data-discussion-id="${this.id}"]`;
const $discussionHeadline = $(`${discussionSelector} .js-discussion-headline`); const $discussionHeadline = $(`${discussionSelector} .js-discussion-headline`);
...@@ -79,7 +79,7 @@ class DiscussionModel { ...@@ -79,7 +79,7 @@ class DiscussionModel {
} }
} }
isResolvable () { isResolvable() {
if (!this.canResolve) { if (!this.canResolve) {
return false; return false;
} }
......
...@@ -5,10 +5,10 @@ import Vue from 'vue'; ...@@ -5,10 +5,10 @@ import Vue from 'vue';
window.CommentsStore = { window.CommentsStore = {
state: {}, state: {},
get: function (discussionId, noteId) { get: function(discussionId, noteId) {
return this.state[discussionId].getNote(noteId); return this.state[discussionId].getNote(noteId);
}, },
createDiscussion: function (discussionId, canResolve) { createDiscussion: function(discussionId, canResolve) {
let discussion = this.state[discussionId]; let discussion = this.state[discussionId];
if (!this.state[discussionId]) { if (!this.state[discussionId]) {
discussion = new DiscussionModel(discussionId); discussion = new DiscussionModel(discussionId);
...@@ -21,18 +21,18 @@ window.CommentsStore = { ...@@ -21,18 +21,18 @@ window.CommentsStore = {
return discussion; return discussion;
}, },
create: function (noteObj) { create: function(noteObj) {
const discussion = this.createDiscussion(noteObj.discussionId); const discussion = this.createDiscussion(noteObj.discussionId);
discussion.createNote(noteObj); discussion.createNote(noteObj);
}, },
update: function (discussionId, noteId, resolved, resolved_by) { update: function(discussionId, noteId, resolved, resolved_by) {
const discussion = this.state[discussionId]; const discussion = this.state[discussionId];
const note = discussion.getNote(noteId); const note = discussion.getNote(noteId);
note.resolved = resolved; note.resolved = resolved;
note.resolved_by = resolved_by; note.resolved_by = resolved_by;
}, },
delete: function (discussionId, noteId) { delete: function(discussionId, noteId) {
const discussion = this.state[discussionId]; const discussion = this.state[discussionId];
discussion.deleteNote(noteId); discussion.deleteNote(noteId);
...@@ -40,7 +40,7 @@ window.CommentsStore = { ...@@ -40,7 +40,7 @@ window.CommentsStore = {
Vue.delete(this.state, discussionId); Vue.delete(this.state, discussionId);
} }
}, },
unresolvedDiscussionIds: function () { unresolvedDiscussionIds: function() {
const ids = []; const ids = [];
for (const discussionId in this.state) { for (const discussionId in this.state) {
...@@ -52,5 +52,5 @@ window.CommentsStore = { ...@@ -52,5 +52,5 @@ window.CommentsStore = {
} }
return ids; return ids;
} },
}; };
...@@ -43,7 +43,9 @@ export default { ...@@ -43,7 +43,9 @@ export default {
return (this.commit.author && this.commit.author.name) || this.commit.authorName; return (this.commit.author && this.commit.author.name) || this.commit.authorName;
}, },
authorUrl() { 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() { authorAvatar() {
return (this.commit.author && this.commit.author.avatarUrl) || this.commit.authorGravatarUrl; return (this.commit.author && this.commit.author.avatarUrl) || this.commit.authorGravatarUrl;
......
...@@ -46,10 +46,10 @@ export default { ...@@ -46,10 +46,10 @@ export default {
showExpandMessage() { showExpandMessage() {
return ( return (
this.isCollapsed || this.isCollapsed ||
!this.file.highlightedDiffLines && (!this.file.highlightedDiffLines &&
!this.isLoadingCollapsedDiff && !this.isLoadingCollapsedDiff &&
!this.file.tooLarge && !this.file.tooLarge &&
this.file.text this.file.text)
); );
}, },
showLoadingIcon() { showLoadingIcon() {
......
...@@ -6,11 +6,4 @@ const IGNORE_CLASS = 'droplab-item-ignore'; ...@@ -6,11 +6,4 @@ const IGNORE_CLASS = 'droplab-item-ignore';
// Matches `{{anything}}` and `{{ everything }}`. // Matches `{{anything}}` and `{{ everything }}`.
const TEMPLATE_REGEX = /\{\{(.+?)\}\}/g; const TEMPLATE_REGEX = /\{\{(.+?)\}\}/g;
export { export { DATA_TRIGGER, DATA_DROPDOWN, SELECTED_CLASS, ACTIVE_CLASS, TEMPLATE_REGEX, IGNORE_CLASS };
DATA_TRIGGER,
DATA_DROPDOWN,
SELECTED_CLASS,
ACTIVE_CLASS,
TEMPLATE_REGEX,
IGNORE_CLASS,
};
...@@ -2,7 +2,7 @@ import utils from './utils'; ...@@ -2,7 +2,7 @@ import utils from './utils';
import { SELECTED_CLASS, IGNORE_CLASS } from './constants'; import { SELECTED_CLASS, IGNORE_CLASS } from './constants';
class DropDown { class DropDown {
constructor(list, config = { }) { constructor(list, config = {}) {
this.currentIndex = 0; this.currentIndex = 0;
this.hidden = true; this.hidden = true;
this.list = typeof list === 'string' ? document.querySelector(list) : list; this.list = typeof list === 'string' ? document.querySelector(list) : list;
...@@ -157,7 +157,7 @@ class DropDown { ...@@ -157,7 +157,7 @@ class DropDown {
static setImagesSrc(template) { static setImagesSrc(template) {
const images = [...template.querySelectorAll('img[data-src]')]; const images = [...template.querySelectorAll('img[data-src]')];
images.forEach((image) => { images.forEach(image => {
const img = image; const img = image;
img.src = img.getAttribute('data-src'); img.src = img.getAttribute('data-src');
......
...@@ -51,7 +51,7 @@ class DropLab { ...@@ -51,7 +51,7 @@ class DropLab {
} }
processData(trigger, data, methodName) { processData(trigger, data, methodName) {
this.hooks.forEach((hook) => { this.hooks.forEach(hook => {
if (Array.isArray(trigger)) hook.list[methodName](trigger); if (Array.isArray(trigger)) hook.list[methodName](trigger);
if (hook.trigger.id === trigger) hook.list[methodName](data); if (hook.trigger.id === trigger) hook.list[methodName](data);
...@@ -78,7 +78,8 @@ class DropLab { ...@@ -78,7 +78,8 @@ class DropLab {
} }
changeHookList(trigger, list, plugins, config) { 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) => { this.hooks.forEach((hook, i) => {
const aHook = hook; const aHook = hook;
......
...@@ -2,15 +2,18 @@ ...@@ -2,15 +2,18 @@
import { ACTIVE_CLASS } from './constants'; import { ACTIVE_CLASS } from './constants';
const Keyboard = function () { const Keyboard = function() {
var currentKey; var currentKey;
var currentFocus; var currentFocus;
var isUpArrow = false; var isUpArrow = false;
var isDownArrow = false; var isDownArrow = false;
var removeHighlight = function removeHighlight(list) { 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 = []; var listItems = [];
for(var i = 0; i < itemElements.length; i++) { for (var i = 0; i < itemElements.length; i++) {
var listItem = itemElements[i]; var listItem = itemElements[i];
listItem.classList.remove(ACTIVE_CLASS); listItem.classList.remove(ACTIVE_CLASS);
...@@ -23,13 +26,13 @@ const Keyboard = function () { ...@@ -23,13 +26,13 @@ const Keyboard = function () {
var setMenuForArrows = function setMenuForArrows(list) { var setMenuForArrows = function setMenuForArrows(list) {
var listItems = removeHighlight(list); var listItems = removeHighlight(list);
if(list.currentIndex>0){ if (list.currentIndex > 0) {
if(!listItems[list.currentIndex-1]){ if (!listItems[list.currentIndex - 1]) {
list.currentIndex = list.currentIndex-1; list.currentIndex = list.currentIndex - 1;
} }
if (listItems[list.currentIndex-1]) { if (listItems[list.currentIndex - 1]) {
var el = listItems[list.currentIndex-1]; var el = listItems[list.currentIndex - 1];
var filterDropdownEl = el.closest('.filter-dropdown'); var filterDropdownEl = el.closest('.filter-dropdown');
el.classList.add(ACTIVE_CLASS); el.classList.add(ACTIVE_CLASS);
...@@ -55,7 +58,7 @@ const Keyboard = function () { ...@@ -55,7 +58,7 @@ const Keyboard = function () {
}; };
var selectItem = function selectItem(list) { var selectItem = function selectItem(list) {
var listItems = removeHighlight(list); var listItems = removeHighlight(list);
var currentItem = listItems[list.currentIndex-1]; var currentItem = listItems[list.currentIndex - 1];
var listEvent = new CustomEvent('click.dl', { var listEvent = new CustomEvent('click.dl', {
detail: { detail: {
list: list, list: list,
...@@ -65,43 +68,49 @@ const Keyboard = function () { ...@@ -65,43 +68,49 @@ const Keyboard = function () {
}); });
list.list.dispatchEvent(listEvent); list.list.dispatchEvent(listEvent);
list.hide(); list.hide();
} };
var keydown = function keydown(e){ var keydown = function keydown(e) {
var typedOn = e.target; var typedOn = e.target;
var list = e.detail.hook.list; var list = e.detail.hook.list;
var currentIndex = list.currentIndex; var currentIndex = list.currentIndex;
isUpArrow = false; isUpArrow = false;
isDownArrow = false; isDownArrow = false;
if(e.detail.which){ if (e.detail.which) {
currentKey = e.detail.which; currentKey = e.detail.which;
if(currentKey === 13){ if (currentKey === 13) {
selectItem(e.detail.hook.list); selectItem(e.detail.hook.list);
return; return;
} }
if(currentKey === 38) { if (currentKey === 38) {
isUpArrow = true; isUpArrow = true;
} }
if(currentKey === 40) { if (currentKey === 40) {
isDownArrow = true; isDownArrow = true;
} }
} else if(e.detail.key) { } else if (e.detail.key) {
currentKey = e.detail.key; currentKey = e.detail.key;
if(currentKey === 'Enter'){ if (currentKey === 'Enter') {
selectItem(e.detail.hook.list); selectItem(e.detail.hook.list);
return; return;
} }
if(currentKey === 'ArrowUp') { if (currentKey === 'ArrowUp') {
isUpArrow = true; isUpArrow = true;
} }
if(currentKey === 'ArrowDown') { if (currentKey === 'ArrowDown') {
isDownArrow = true; isDownArrow = true;
} }
} }
if(isUpArrow){ currentIndex--; } if (isUpArrow) {
if(isDownArrow){ currentIndex++; } currentIndex--;
if(currentIndex < 0){ currentIndex = 0; } }
if (isDownArrow) {
currentIndex++;
}
if (currentIndex < 0) {
currentIndex = 0;
}
list.currentIndex = currentIndex; list.currentIndex = currentIndex;
setMenuForArrows(e.detail.hook.list); setMenuForArrows(e.detail.hook.list);
}; };
......
...@@ -43,12 +43,12 @@ const Ajax = { ...@@ -43,12 +43,12 @@ const Ajax = {
return AjaxCache.retrieve(config.endpoint) return AjaxCache.retrieve(config.endpoint)
.then(self.preprocessing.bind(null, config)) .then(self.preprocessing.bind(null, config))
.then((data) => self._loadData(data, config, self)) .then(data => self._loadData(data, config, self))
.catch(config.onError); .catch(config.onError);
}, },
destroy: function() { destroy: function() {
this.destroyed = true; this.destroyed = true;
} },
}; };
export default Ajax; export default Ajax;
...@@ -41,8 +41,10 @@ const AjaxFilter = { ...@@ -41,8 +41,10 @@ const AjaxFilter = {
if (config.searchValueFunction) { if (config.searchValueFunction) {
searchValue = config.searchValueFunction(); searchValue = config.searchValueFunction();
} }
if (config.loadingTemplate && this.hook.list.data === undefined || if (
this.hook.list.data.length === 0) { (config.loadingTemplate && this.hook.list.data === undefined) ||
this.hook.list.data.length === 0
) {
var dynamicList = this.hook.list.list.querySelector('[data-dynamic]'); var dynamicList = this.hook.list.list.querySelector('[data-dynamic]');
var loadingTemplate = document.createElement('div'); var loadingTemplate = document.createElement('div');
loadingTemplate.innerHTML = config.loadingTemplate; loadingTemplate.innerHTML = config.loadingTemplate;
...@@ -61,7 +63,7 @@ const AjaxFilter = { ...@@ -61,7 +63,7 @@ const AjaxFilter = {
params[config.searchKey] = searchValue; params[config.searchKey] = searchValue;
var url = config.endpoint + this.buildParams(params); var url = config.endpoint + this.buildParams(params);
return AjaxCache.retrieve(url) return AjaxCache.retrieve(url)
.then((data) => { .then(data => {
this._loadData(data, config); this._loadData(data, config);
if (config.onLoadingFinished) { if (config.onLoadingFinished) {
config.onLoadingFinished(data); config.onLoadingFinished(data);
...@@ -72,8 +74,7 @@ const AjaxFilter = { ...@@ -72,8 +74,7 @@ const AjaxFilter = {
_loadData(data, config) { _loadData(data, config) {
const list = this.hook.list; const list = this.hook.list;
if (config.loadingTemplate && list.data === undefined || if ((config.loadingTemplate && list.data === undefined) || list.data.length === 0) {
list.data.length === 0) {
const dataLoadingTemplate = list.list.querySelector('[data-loading-template]'); const dataLoadingTemplate = list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) { if (dataLoadingTemplate) {
dataLoadingTemplate.outerHTML = this.listTemplate; dataLoadingTemplate.outerHTML = this.listTemplate;
...@@ -81,7 +82,8 @@ const AjaxFilter = { ...@@ -81,7 +82,8 @@ const AjaxFilter = {
} }
if (!this.destroyed) { if (!this.destroyed) {
var hookListChildren = list.list.children; 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) { if (onlyDynamicList && data.length === 0) {
list.hide(); list.hide();
} }
...@@ -100,12 +102,12 @@ const AjaxFilter = { ...@@ -100,12 +102,12 @@ const AjaxFilter = {
}, },
destroy: function destroy() { destroy: function destroy() {
if (this.timeout)clearTimeout(this.timeout); if (this.timeout) clearTimeout(this.timeout);
this.destroyed = true; this.destroyed = true;
this.hook.trigger.removeEventListener('keydown.dl', this.eventWrapper.debounceTrigger); this.hook.trigger.removeEventListener('keydown.dl', this.eventWrapper.debounceTrigger);
this.hook.trigger.removeEventListener('focus', this.eventWrapper.debounceTrigger); this.hook.trigger.removeEventListener('focus', this.eventWrapper.debounceTrigger);
} },
}; };
export default AjaxFilter; export default AjaxFilter;
/* eslint-disable */ /* eslint-disable */
const Filter = { const Filter = {
keydown: function(e){ keydown: function(e) {
if (this.destroyed) return; if (this.destroyed) return;
var hiddenCount = 0; var hiddenCount = 0;
...@@ -14,14 +14,14 @@ const Filter = { ...@@ -14,14 +14,14 @@ const Filter = {
var matches = []; var matches = [];
var filterFunction; var filterFunction;
// will only work on dynamically set data // will only work on dynamically set data
if(!data){ if (!data) {
return; return;
} }
if (config && config.filterFunction && typeof config.filterFunction === 'function') { if (config && config.filterFunction && typeof config.filterFunction === 'function') {
filterFunction = config.filterFunction; filterFunction = config.filterFunction;
} else { } else {
filterFunction = function(o){ filterFunction = function(o) {
// cheap string search // cheap string search
o.droplab_hidden = o[config.template].toLowerCase().indexOf(value) === -1; o.droplab_hidden = o[config.template].toLowerCase().indexOf(value) === -1;
return o; return o;
...@@ -47,20 +47,23 @@ const Filter = { ...@@ -47,20 +47,23 @@ const Filter = {
}, },
debounceKeydown: function debounceKeydown(e) { debounceKeydown: function debounceKeydown(e) {
if ([ if (
13, // enter [
16, // shift 13, // enter
17, // ctrl 16, // shift
18, // alt 17, // ctrl
20, // caps lock 18, // alt
37, // left arrow 20, // caps lock
38, // up arrow 37, // left arrow
39, // right arrow 38, // up arrow
40, // down arrow 39, // right arrow
91, // left window 40, // down arrow
92, // right window 91, // left window
93, // select 92, // right window
].indexOf(e.detail.which || e.detail.keyCode) > -1) return; 93, // select
].indexOf(e.detail.which || e.detail.keyCode) > -1
)
return;
if (this.timeout) clearTimeout(this.timeout); if (this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(this.keydown.bind(this, e), 200); this.timeout = setTimeout(this.keydown.bind(this, e), 200);
...@@ -87,7 +90,7 @@ const Filter = { ...@@ -87,7 +90,7 @@ const Filter = {
this.hook.trigger.removeEventListener('keydown.dl', this.eventWrapper.debounceKeydown); this.hook.trigger.removeEventListener('keydown.dl', this.eventWrapper.debounceKeydown);
this.hook.trigger.removeEventListener('mousedown.dl', this.eventWrapper.debounceKeydown); this.hook.trigger.removeEventListener('mousedown.dl', this.eventWrapper.debounceKeydown);
} },
}; };
export default Filter; export default Filter;
...@@ -36,8 +36,8 @@ const InputSetter = { ...@@ -36,8 +36,8 @@ const InputSetter = {
const inputAttribute = config.inputAttribute; const inputAttribute = config.inputAttribute;
if (input.hasAttribute(inputAttribute)) return input.setAttribute(inputAttribute, newValue); if (input.hasAttribute(inputAttribute)) return input.setAttribute(inputAttribute, newValue);
if (input.tagName === 'INPUT') return input.value = newValue; if (input.tagName === 'INPUT') return (input.value = newValue);
return input.textContent = newValue; return (input.textContent = newValue);
}, },
destroy() { destroy() {
......
...@@ -5,7 +5,12 @@ import { DATA_TRIGGER, DATA_DROPDOWN, TEMPLATE_REGEX } from './constants'; ...@@ -5,7 +5,12 @@ import { DATA_TRIGGER, DATA_DROPDOWN, TEMPLATE_REGEX } from './constants';
const utils = { const utils = {
toCamelCase(attr) { toCamelCase(attr) {
return this.camelize(attr.split('-').slice(1).join(' ')); return this.camelize(
attr
.split('-')
.slice(1)
.join(' '),
);
}, },
template(templateString, data) { template(templateString, data) {
...@@ -17,9 +22,11 @@ const utils = { ...@@ -17,9 +22,11 @@ const utils = {
}, },
camelize(str) { camelize(str) {
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => { return str
return index === 0 ? letter.toLowerCase() : letter.toUpperCase(); .replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => {
}).replace(/\s+/g, ''); return index === 0 ? letter.toLowerCase() : letter.toUpperCase();
})
.replace(/\s+/g, '');
}, },
closest(thisTag, stopTag) { closest(thisTag, stopTag) {
......
<script> <script>
import tablePagination from '../../vue_shared/components/table_pagination.vue'; import tablePagination from '../../vue_shared/components/table_pagination.vue';
import environmentTable from '../components/environments_table.vue'; import environmentTable from '../components/environments_table.vue';
export default { export default {
components: { components: {
environmentTable, environmentTable,
tablePagination, tablePagination,
},
props: {
isLoading: {
type: Boolean,
required: true,
}, },
props: { environments: {
isLoading: { type: Array,
type: Boolean, required: true,
required: true,
},
environments: {
type: Array,
required: true,
},
pagination: {
type: Object,
required: true,
},
canCreateDeployment: {
type: Boolean,
required: true,
},
canReadEnvironment: {
type: Boolean,
required: true,
},
}, },
methods: { pagination: {
onChangePage(page) { type: Object,
this.$emit('onChangePage', page); required: true,
},
}, },
}; canCreateDeployment: {
type: Boolean,
required: true,
},
canReadEnvironment: {
type: Boolean,
required: true,
},
},
methods: {
onChangePage(page) {
this.$emit('onChangePage', page);
},
},
};
</script> </script>
<template> <template>
......
<script> <script>
export default { export default {
name: 'EnvironmentsEmptyState', name: 'EnvironmentsEmptyState',
props: { props: {
newPath: { newPath: {
type: String, type: String,
required: true, required: true,
},
canCreateEnvironment: {
type: Boolean,
required: true,
},
helpPath: {
type: String,
required: true,
},
}, },
}; canCreateEnvironment: {
type: Boolean,
required: true,
},
helpPath: {
type: String,
required: true,
},
},
};
</script> </script>
<template> <template>
<div class="blank-state-row"> <div class="blank-state-row">
......
...@@ -38,7 +38,9 @@ export default { ...@@ -38,7 +38,9 @@ export default {
computed: { computed: {
title() { 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'; ...@@ -5,31 +5,32 @@ import Translate from '../../vue_shared/translate';
Vue.use(Translate); Vue.use(Translate);
export default () => new Vue({ export default () =>
el: '#environments-folder-list-view', new Vue({
components: { el: '#environments-folder-list-view',
environmentsFolderApp, components: {
}, environmentsFolderApp,
data() { },
const environmentsData = document.querySelector(this.$options.el).dataset; data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
return { return {
endpoint: environmentsData.endpoint, endpoint: environmentsData.endpoint,
folderName: environmentsData.folderName, folderName: environmentsData.folderName,
cssContainerClass: environmentsData.cssClass, cssContainerClass: environmentsData.cssClass,
canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment), canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment), canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
}; };
}, },
render(createElement) { render(createElement) {
return createElement('environments-folder-app', { return createElement('environments-folder-app', {
props: { props: {
endpoint: this.endpoint, endpoint: this.endpoint,
folderName: this.folderName, folderName: this.folderName,
cssContainerClass: this.cssContainerClass, cssContainerClass: this.cssContainerClass,
canCreateDeployment: this.canCreateDeployment, canCreateDeployment: this.canCreateDeployment,
canReadEnvironment: this.canReadEnvironment, canReadEnvironment: this.canReadEnvironment,
}, },
}); });
}, },
}); });
<script> <script>
import environmentsMixin from '../mixins/environments_mixin'; import environmentsMixin from '../mixins/environments_mixin';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
import StopEnvironmentModal from '../components/stop_environment_modal.vue'; import StopEnvironmentModal from '../components/stop_environment_modal.vue';
export default { export default {
components: { components: {
StopEnvironmentModal, StopEnvironmentModal,
}, },
mixins: [ mixins: [environmentsMixin, CIPaginationMixin],
environmentsMixin,
CIPaginationMixin,
],
props: { props: {
endpoint: { endpoint: {
type: String, type: String,
required: true, required: true,
}, },
folderName: { folderName: {
type: String, type: String,
required: true, required: true,
}, },
cssContainerClass: { cssContainerClass: {
type: String, type: String,
required: true, required: true,
}, },
canCreateDeployment: { canCreateDeployment: {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
canReadEnvironment: { canReadEnvironment: {
type: Boolean, type: Boolean,
required: true, required: true,
},
}, },
methods: { },
successCallback(resp) { methods: {
this.saveData(resp); successCallback(resp) {
}, this.saveData(resp);
}, },
}; },
};
</script> </script>
<template> <template>
<div :class="cssContainerClass"> <div :class="cssContainerClass">
......
...@@ -5,35 +5,36 @@ import Translate from '../vue_shared/translate'; ...@@ -5,35 +5,36 @@ import Translate from '../vue_shared/translate';
Vue.use(Translate); Vue.use(Translate);
export default () => new Vue({ export default () =>
el: '#environments-list-view', new Vue({
components: { el: '#environments-list-view',
environmentsComponent, components: {
}, environmentsComponent,
data() { },
const environmentsData = document.querySelector(this.$options.el).dataset; data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
return { return {
endpoint: environmentsData.environmentsDataEndpoint, endpoint: environmentsData.environmentsDataEndpoint,
newEnvironmentPath: environmentsData.newEnvironmentPath, newEnvironmentPath: environmentsData.newEnvironmentPath,
helpPagePath: environmentsData.helpPagePath, helpPagePath: environmentsData.helpPagePath,
cssContainerClass: environmentsData.cssClass, cssContainerClass: environmentsData.cssClass,
canCreateEnvironment: convertPermissionToBoolean(environmentsData.canCreateEnvironment), canCreateEnvironment: convertPermissionToBoolean(environmentsData.canCreateEnvironment),
canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment), canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment),
canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment), canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment),
}; };
}, },
render(createElement) { render(createElement) {
return createElement('environments-component', { return createElement('environments-component', {
props: { props: {
endpoint: this.endpoint, endpoint: this.endpoint,
newEnvironmentPath: this.newEnvironmentPath, newEnvironmentPath: this.newEnvironmentPath,
helpPagePath: this.helpPagePath, helpPagePath: this.helpPagePath,
cssContainerClass: this.cssContainerClass, cssContainerClass: this.cssContainerClass,
canCreateEnvironment: this.canCreateEnvironment, canCreateEnvironment: this.canCreateEnvironment,
canCreateDeployment: this.canCreateDeployment, canCreateDeployment: this.canCreateDeployment,
canReadEnvironment: this.canReadEnvironment, canReadEnvironment: this.canReadEnvironment,
}, },
}); });
}, },
}); });
...@@ -4,9 +4,7 @@ ...@@ -4,9 +4,7 @@
import _ from 'underscore'; import _ from 'underscore';
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import Poll from '../../lib/utils/poll'; import Poll from '../../lib/utils/poll';
import { import { getParameterByName } from '../../lib/utils/common_utils';
getParameterByName,
} from '../../lib/utils/common_utils';
import { s__ } from '../../locale'; import { s__ } from '../../locale';
import Flash from '../../flash'; import Flash from '../../flash';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
...@@ -19,7 +17,6 @@ import tabs from '../../vue_shared/components/navigation_tabs.vue'; ...@@ -19,7 +17,6 @@ import tabs from '../../vue_shared/components/navigation_tabs.vue';
import container from '../components/container.vue'; import container from '../components/container.vue';
export default { export default {
components: { components: {
environmentTable, environmentTable,
container, container,
...@@ -65,7 +62,8 @@ export default { ...@@ -65,7 +62,8 @@ export default {
updateContent(parameters) { updateContent(parameters) {
this.updateInternalState(parameters); this.updateInternalState(parameters);
// fetch new data // fetch new data
return this.service.fetchEnvironments(this.requestData) return this.service
.fetchEnvironments(this.requestData)
.then(response => this.successCallback(response)) .then(response => this.successCallback(response))
.then(() => { .then(() => {
// restart polling // restart polling
...@@ -88,7 +86,8 @@ export default { ...@@ -88,7 +86,8 @@ export default {
if (!this.isMakingRequest) { if (!this.isMakingRequest) {
this.isLoading = true; this.isLoading = true;
this.service.postAction(endpoint) this.service
.postAction(endpoint)
.then(() => this.fetchEnvironments()) .then(() => this.fetchEnvironments())
.catch(() => { .catch(() => {
this.isLoading = false; this.isLoading = false;
...@@ -100,7 +99,8 @@ export default { ...@@ -100,7 +99,8 @@ export default {
fetchEnvironments() { fetchEnvironments() {
this.isLoading = true; this.isLoading = true;
return this.service.fetchEnvironments(this.requestData) return this.service
.fetchEnvironments(this.requestData)
.then(this.successCallback) .then(this.successCallback)
.catch(this.errorCallback); .catch(this.errorCallback);
}, },
...@@ -111,7 +111,9 @@ export default { ...@@ -111,7 +111,9 @@ export default {
stopEnvironment(environment) { stopEnvironment(environment) {
const endpoint = environment.stop_path; 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 }); this.postAction({ endpoint, errorMessage });
}, },
}, },
...@@ -149,7 +151,7 @@ export default { ...@@ -149,7 +151,7 @@ export default {
data: this.requestData, data: this.requestData,
successCallback: this.successCallback, successCallback: this.successCallback,
errorCallback: this.errorCallback, errorCallback: this.errorCallback,
notificationCallback: (isMakingRequest) => { notificationCallback: isMakingRequest => {
this.isMakingRequest = isMakingRequest; this.isMakingRequest = isMakingRequest;
}, },
}); });
......
import $ from 'jquery'; import $ from 'jquery';
import { import { getSelector, inserted } from './feature_highlight_helper';
getSelector, import { togglePopover, mouseenter, debouncedMouseleave } from '../shared/popover';
inserted,
} from './feature_highlight_helper';
import {
togglePopover,
mouseenter,
debouncedMouseleave,
} from '../shared/popover';
export function setupFeatureHighlightPopover(id, debounceTimeout = 300) { export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
const $selector = $(getSelector(id)); const $selector = $(getSelector(id));
...@@ -41,8 +34,9 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) { ...@@ -41,8 +34,9 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
export function findHighestPriorityFeature() { export function findHighestPriorityFeature() {
let priorityFeature; let priorityFeature;
const sortedFeatureEls = [].slice.call(document.querySelectorAll('.js-feature-highlight')).sort((a, b) => const sortedFeatureEls = [].slice
(a.dataset.highlightPriority || 0) < (b.dataset.highlightPriority || 0)); .call(document.querySelectorAll('.js-feature-highlight'))
.sort((a, b) => (a.dataset.highlightPriority || 0) < (b.dataset.highlightPriority || 0));
const [priorityFeatureEl] = sortedFeatureEls; const [priorityFeatureEl] = sortedFeatureEls;
if (priorityFeatureEl) { if (priorityFeatureEl) {
......
...@@ -8,10 +8,17 @@ import { togglePopover } from '../shared/popover'; ...@@ -8,10 +8,17 @@ import { togglePopover } from '../shared/popover';
export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`; export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`;
export function dismiss(highlightId) { export function dismiss(highlightId) {
axios.post(this.attr('data-dismiss-endpoint'), { axios
feature_name: highlightId, .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.'))); })
.catch(() =>
Flash(
__(
'An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again.',
),
),
);
togglePopover.call(this, false); togglePopover.call(this, false);
this.hide(); this.hide();
...@@ -23,8 +30,7 @@ export function inserted() { ...@@ -23,8 +30,7 @@ export function inserted() {
const $popover = $(this); const $popover = $(this);
const dismissWrapper = dismiss.bind($popover, highlightId); const dismissWrapper = dismiss.bind($popover, highlightId);
$(`#${popoverId} .dismiss-feature-highlight`) $(`#${popoverId} .dismiss-feature-highlight`).on('click', dismissWrapper);
.on('click', dismissWrapper);
const lazyImg = $(`#${popoverId} .feature-highlight-illustration`)[0]; const lazyImg = $(`#${popoverId} .feature-highlight-illustration`)[0];
if (lazyImg) { if (lazyImg) {
......
import FilteredSearchTokenKeys from './filtered_search_token_keys'; import FilteredSearchTokenKeys from './filtered_search_token_keys';
const tokenKeys = [{ const tokenKeys = [
key: 'status', {
type: 'string', key: 'status',
param: 'status', type: 'string',
symbol: '', param: 'status',
icon: 'messages', symbol: '',
tag: 'status', icon: 'messages',
}, { tag: 'status',
key: 'type', },
type: 'string', {
param: 'type', key: 'type',
symbol: '', type: 'string',
icon: 'cube', param: 'type',
tag: 'type', symbol: '',
}]; icon: 'cube',
tag: 'type',
},
];
const AdminRunnersFilteredSearchTokenKeys = new FilteredSearchTokenKeys(tokenKeys); const AdminRunnersFilteredSearchTokenKeys = new FilteredSearchTokenKeys(tokenKeys);
......
...@@ -21,9 +21,11 @@ export default { ...@@ -21,9 +21,11 @@ export default {
}, },
computed: { computed: {
processedItems() { processedItems() {
return this.items.map((item) => { return this.items.map(item => {
const { tokens, searchToken } const { tokens, searchToken } = FilteredSearchTokenizer.processTokens(
= FilteredSearchTokenizer.processTokens(item, this.allowedKeys); item,
this.allowedKeys,
);
const resultantTokens = tokens.map(token => ({ const resultantTokens = tokens.map(token => ({
prefix: `${token.key}:`, prefix: `${token.key}:`,
......
...@@ -24,8 +24,12 @@ export default class DropdownEmoji extends FilteredSearchDropdown { ...@@ -24,8 +24,12 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
}; };
import(/* webpackChunkName: 'emoji' */ '~/emoji') import(/* webpackChunkName: 'emoji' */ '~/emoji')
.then(({ glEmojiTag }) => { this.glEmojiTag = glEmojiTag; }) .then(({ glEmojiTag }) => {
.catch(() => { /* ignore error and leave emoji name in the search bar */ }); this.glEmojiTag = glEmojiTag;
})
.catch(() => {
/* ignore error and leave emoji name in the search bar */
});
this.unbindEvents(); this.unbindEvents();
this.bindEvents(); this.bindEvents();
...@@ -48,7 +52,7 @@ export default class DropdownEmoji extends FilteredSearchDropdown { ...@@ -48,7 +52,7 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
} }
itemClicked(e) { itemClicked(e) {
super.itemClicked(e, (selected) => { super.itemClicked(e, selected => {
const name = selected.querySelector('.js-data-value').innerText.trim(); const name = selected.querySelector('.js-data-value').innerText.trim();
return DropdownUtils.getEscapedText(name); return DropdownUtils.getEscapedText(name);
}); });
...@@ -64,7 +68,7 @@ export default class DropdownEmoji extends FilteredSearchDropdown { ...@@ -64,7 +68,7 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
// Replace empty gl-emoji tag to real content // Replace empty gl-emoji tag to real content
const dropdownItems = [...this.dropdown.querySelectorAll('.filter-dropdown-item')]; const dropdownItems = [...this.dropdown.querySelectorAll('.filter-dropdown-item')];
dropdownItems.forEach((dropdownItem) => { dropdownItems.forEach(dropdownItem => {
const name = dropdownItem.querySelector('.js-data-value').innerText; const name = dropdownItem.querySelector('.js-data-value').innerText;
const emojiTag = this.glEmojiTag(name); const emojiTag = this.glEmojiTag(name);
const emojiElement = dropdownItem.querySelector('gl-emoji'); const emojiElement = dropdownItem.querySelector('gl-emoji');
...@@ -73,7 +77,6 @@ export default class DropdownEmoji extends FilteredSearchDropdown { ...@@ -73,7 +77,6 @@ export default class DropdownEmoji extends FilteredSearchDropdown {
} }
init() { init() {
this.droplab this.droplab.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
} }
} }
...@@ -41,8 +41,10 @@ export default class DropdownHint extends FilteredSearchDropdown { ...@@ -41,8 +41,10 @@ export default class DropdownHint extends FilteredSearchDropdown {
previousInputValues.forEach((value, index) => { previousInputValues.forEach((value, index) => {
searchTerms.push(value); searchTerms.push(value);
if (index === previousInputValues.length - 1 if (
&& token.indexOf(value.toLowerCase()) !== -1) { index === previousInputValues.length - 1 &&
token.indexOf(value.toLowerCase()) !== -1
) {
searchTerms.pop(); searchTerms.pop();
} }
}); });
...@@ -64,13 +66,12 @@ export default class DropdownHint extends FilteredSearchDropdown { ...@@ -64,13 +66,12 @@ export default class DropdownHint extends FilteredSearchDropdown {
} }
renderContent() { renderContent() {
const dropdownData = this.tokenKeys.get() const dropdownData = this.tokenKeys.get().map(tokenKey => ({
.map(tokenKey => ({ icon: `${gon.sprite_icons}#${tokenKey.icon}`,
icon: `${gon.sprite_icons}#${tokenKey.icon}`, hint: tokenKey.key,
hint: tokenKey.key, tag: `:${tokenKey.tag}`,
tag: `:${tokenKey.tag}`, type: tokenKey.type,
type: tokenKey.type, }));
}));
this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config); this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config);
this.droplab.setData(this.hookId, dropdownData); this.droplab.setData(this.hookId, dropdownData);
......
...@@ -29,20 +29,18 @@ export default class DropdownNonUser extends FilteredSearchDropdown { ...@@ -29,20 +29,18 @@ export default class DropdownNonUser extends FilteredSearchDropdown {
} }
itemClicked(e) { itemClicked(e) {
super.itemClicked(e, (selected) => { super.itemClicked(e, selected => {
const title = selected.querySelector('.js-data-value').innerText.trim(); const title = selected.querySelector('.js-data-value').innerText.trim();
return `${this.symbol}${DropdownUtils.getEscapedText(title)}`; return `${this.symbol}${DropdownUtils.getEscapedText(title)}`;
}); });
} }
renderContent(forceShowList = false) { renderContent(forceShowList = false) {
this.droplab this.droplab.changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
.changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
super.renderContent(forceShowList); super.renderContent(forceShowList);
} }
init() { init() {
this.droplab this.droplab.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
} }
} }
...@@ -41,7 +41,7 @@ export default class DropdownUtils { ...@@ -41,7 +41,7 @@ export default class DropdownUtils {
// Removes the first character if it is a quotation so that we can search // Removes the first character if it is a quotation so that we can search
// with multiple words // with multiple words
if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) { if ((value[0] === '"' || value[0] === "'") && title.indexOf(' ') !== -1) {
value = value.slice(1); value = value.slice(1);
} }
...@@ -82,11 +82,13 @@ export default class DropdownUtils { ...@@ -82,11 +82,13 @@ export default class DropdownUtils {
// Reduce the colors to 4 // Reduce the colors to 4
colors.length = Math.min(colors.length, 4); colors.length = Math.min(colors.length, 4);
const color = colors.map((c, i) => { const color = colors
const percentFirst = Math.floor(spacing * i); .map((c, i) => {
const percentSecond = Math.floor(spacing * (i + 1)); const percentFirst = Math.floor(spacing * i);
return `${c} ${percentFirst}%, ${c} ${percentSecond}%`; const percentSecond = Math.floor(spacing * (i + 1));
}).join(', '); return `${c} ${percentFirst}%, ${c} ${percentSecond}%`;
})
.join(', ');
return `linear-gradient(${color})`; return `linear-gradient(${color})`;
} }
...@@ -97,17 +99,16 @@ export default class DropdownUtils { ...@@ -97,17 +99,16 @@ export default class DropdownUtils {
data.forEach(DropdownUtils.mergeDuplicateLabels.bind(null, dataMap)); data.forEach(DropdownUtils.mergeDuplicateLabels.bind(null, dataMap));
Object.keys(dataMap) Object.keys(dataMap).forEach(key => {
.forEach((key) => { const label = dataMap[key];
const label = dataMap[key];
if (label.multipleColors) { if (label.multipleColors) {
label.color = DropdownUtils.duplicateLabelColor(label.multipleColors); label.color = DropdownUtils.duplicateLabelColor(label.multipleColors);
label.text_color = '#000000'; label.text_color = '#000000';
} }
results.push(label); results.push(label);
}); });
results.preprocessed = true; results.preprocessed = true;
...@@ -118,8 +119,7 @@ export default class DropdownUtils { ...@@ -118,8 +119,7 @@ export default class DropdownUtils {
const { input, allowedKeys } = config; const { input, allowedKeys } = config;
const updatedItem = item; const updatedItem = item;
const searchInput = DropdownUtils.getSearchQuery(input); const searchInput = DropdownUtils.getSearchQuery(input);
const { lastToken, tokens } = const { lastToken, tokens } = FilteredSearchTokenizer.processTokens(searchInput, allowedKeys);
FilteredSearchTokenizer.processTokens(searchInput, allowedKeys);
const lastKey = lastToken.key || lastToken || ''; const lastKey = lastToken.key || lastToken || '';
const allowMultiple = item.type === 'array'; const allowMultiple = item.type === 'array';
const itemInExistingTokens = tokens.some(t => t.key === item.hint); const itemInExistingTokens = tokens.some(t => t.key === item.hint);
...@@ -154,7 +154,10 @@ export default class DropdownUtils { ...@@ -154,7 +154,10 @@ export default class DropdownUtils {
static getVisualTokenValues(visualToken) { static getVisualTokenValues(visualToken) {
const tokenName = visualToken && visualToken.querySelector('.name').textContent.trim(); 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) { if (tokenName === 'label' && tokenValue) {
// remove leading symbol and wrapping quotes // remove leading symbol and wrapping quotes
tokenValue = tokenValue.replace(/^~("|')?(.*)/, '$2').replace(/("|')$/, ''); tokenValue = tokenValue.replace(/^~("|')?(.*)/, '$2').replace(/("|')$/, '');
...@@ -174,7 +177,7 @@ export default class DropdownUtils { ...@@ -174,7 +177,7 @@ export default class DropdownUtils {
tokens.splice(inputIndex + 1); tokens.splice(inputIndex + 1);
} }
tokens.forEach((token) => { tokens.forEach(token => {
if (token.classList.contains('js-visual-token')) { if (token.classList.contains('js-visual-token')) {
const name = token.querySelector('.name'); const name = token.querySelector('.name');
const value = token.querySelector('.value'); const value = token.querySelector('.value');
...@@ -194,8 +197,9 @@ export default class DropdownUtils { ...@@ -194,8 +197,9 @@ export default class DropdownUtils {
values.push(name.innerText); values.push(name.innerText);
} }
} else if (token.classList.contains('input-token')) { } else if (token.classList.contains('input-token')) {
const { isLastVisualTokenValid } = const {
FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); isLastVisualTokenValid,
} = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
const input = FilteredSearchContainer.container.querySelector('.filtered-search'); const input = FilteredSearchContainer.container.querySelector('.filtered-search');
const inputValue = input && input.value; const inputValue = input && input.value;
...@@ -209,9 +213,7 @@ export default class DropdownUtils { ...@@ -209,9 +213,7 @@ export default class DropdownUtils {
} }
}); });
return values return values.map(value => value.trim()).join(' ');
.map(value => value.trim())
.join(' ');
} }
static getSearchInput(filteredSearchInput) { static getSearchInput(filteredSearchInput) {
...@@ -227,7 +229,9 @@ export default class DropdownUtils { ...@@ -227,7 +229,9 @@ export default class DropdownUtils {
// Replace all spaces inside quote marks with underscores // Replace all spaces inside quote marks with underscores
// (will continue to match entire string until an end quote is found if any) // (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 // 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 // Get the right position for the word selected
// Regex matches first space // Regex matches first space
......
...@@ -87,10 +87,12 @@ export default class FilteredSearchDropdown { ...@@ -87,10 +87,12 @@ export default class FilteredSearchDropdown {
dispatchInputEvent() { dispatchInputEvent() {
// Propogate input change to FilteredSearchDropdownManager // Propogate input change to FilteredSearchDropdownManager
// so that it can determine which dropdowns to open // so that it can determine which dropdowns to open
this.input.dispatchEvent(new CustomEvent('input', { this.input.dispatchEvent(
bubbles: true, new CustomEvent('input', {
cancelable: true, bubbles: true,
})); cancelable: true,
}),
);
} }
dispatchFormSubmitEvent() { dispatchFormSubmitEvent() {
...@@ -114,7 +116,7 @@ export default class FilteredSearchDropdown { ...@@ -114,7 +116,7 @@ export default class FilteredSearchDropdown {
if (!data) return; if (!data) return;
const results = data.map((o) => { const results = data.map(o => {
const updated = o; const updated = o;
updated.droplab_hidden = false; updated.droplab_hidden = false;
return updated; return updated;
......
...@@ -42,19 +42,21 @@ export default class FilteredSearchTokenKeys { ...@@ -42,19 +42,21 @@ export default class FilteredSearchTokenKeys {
} }
searchByKeyParam(keyParam) { searchByKeyParam(keyParam) {
return this.tokenKeysWithAlternative.find((tokenKey) => { return (
let tokenKeyParam = tokenKey.key; this.tokenKeysWithAlternative.find(tokenKey => {
let tokenKeyParam = tokenKey.key;
// Replace hyphen with underscore to compare keyParam with tokenKeyParam // Replace hyphen with underscore to compare keyParam with tokenKeyParam
// e.g. 'my-reaction' => 'my_reaction' // e.g. 'my-reaction' => 'my_reaction'
tokenKeyParam = tokenKeyParam.replace('-', '_'); tokenKeyParam = tokenKeyParam.replace('-', '_');
if (tokenKey.param) { if (tokenKey.param) {
tokenKeyParam += `_${tokenKey.param}`; tokenKeyParam += `_${tokenKey.param}`;
} }
return keyParam === tokenKeyParam; return keyParam === tokenKeyParam;
}) || null; }) || null
);
} }
searchByConditionUrl(url) { searchByConditionUrl(url) {
...@@ -62,8 +64,10 @@ export default class FilteredSearchTokenKeys { ...@@ -62,8 +64,10 @@ export default class FilteredSearchTokenKeys {
} }
searchByConditionKeyValue(key, value) { searchByConditionKeyValue(key, value) {
return this.conditions return (
.find(condition => condition.tokenKey === key && condition.value === value) || null; this.conditions.find(condition => condition.tokenKey === key && condition.value === value) ||
null
);
} }
addExtraTokensForMergeRequests() { addExtraTokensForMergeRequests() {
......
...@@ -4,41 +4,48 @@ export default class FilteredSearchTokenizer { ...@@ -4,41 +4,48 @@ export default class FilteredSearchTokenizer {
static processTokens(input, allowedKeys) { static processTokens(input, allowedKeys) {
// Regex extracts `(token):(symbol)(value)` // Regex extracts `(token):(symbol)(value)`
// Values that start with a double quote must end in a double quote (same for single) // 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 tokens = [];
const tokenIndexes = []; // stores key+value for simple search const tokenIndexes = []; // stores key+value for simple search
let lastToken = null; let lastToken = null;
const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => { const searchToken =
let tokenValue = v1 || v2 || v3; input
let tokenSymbol = symbol; .replace(tokenRegex, (match, key, symbol, v1, v2, v3) => {
let tokenIndex = ''; let tokenValue = v1 || v2 || v3;
let tokenSymbol = symbol;
if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') { let tokenIndex = '';
tokenSymbol = tokenValue;
tokenValue = ''; if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') {
} tokenSymbol = tokenValue;
tokenValue = '';
tokenIndex = `${key}:${tokenValue}`; }
// Prevent adding duplicates tokenIndex = `${key}:${tokenValue}`;
if (tokenIndexes.indexOf(tokenIndex) === -1) {
tokenIndexes.push(tokenIndex); // Prevent adding duplicates
if (tokenIndexes.indexOf(tokenIndex) === -1) {
tokens.push({ tokenIndexes.push(tokenIndex);
key,
value: tokenValue || '', tokens.push({
symbol: tokenSymbol || '', key,
}); value: tokenValue || '',
} symbol: tokenSymbol || '',
});
return ''; }
}).replace(/\s{2,}/g, ' ').trim() || '';
return '';
})
.replace(/\s{2,}/g, ' ')
.trim() || '';
if (tokens.length > 0) { if (tokens.length > 0) {
const last = tokens[tokens.length - 1]; const last = tokens[tokens.length - 1];
const lastString = `${last.key}:${last.symbol}${last.value}`; const lastString = `${last.key}:${last.symbol}${last.value}`;
lastToken = input.lastIndexOf(lastString) === lastToken =
input.length - lastString.length ? last : searchToken; input.lastIndexOf(lastString) === input.length - lastString.length ? last : searchToken;
} else { } else {
lastToken = searchToken; lastToken = searchToken;
} }
......
...@@ -13,7 +13,10 @@ export default class FilteredSearchVisualTokens { ...@@ -13,7 +13,10 @@ export default class FilteredSearchVisualTokens {
return { return {
lastVisualToken, 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 { ...@@ -33,7 +36,9 @@ export default class FilteredSearchVisualTokens {
} }
static unselectTokens() { 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')); [].forEach.call(otherTokens, t => t.classList.remove('selected'));
} }
...@@ -56,11 +61,7 @@ export default class FilteredSearchVisualTokens { ...@@ -56,11 +61,7 @@ export default class FilteredSearchVisualTokens {
} }
static createVisualTokenElementHTML(options = {}) { static createVisualTokenElementHTML(options = {}) {
const { const { canEdit = true, uppercaseTokenName = false, capitalizeTokenValue = false } = options;
canEdit = true,
uppercaseTokenName = false,
capitalizeTokenValue = false,
} = options;
return ` return `
<div class="${canEdit ? 'selectable' : 'hidden'}" role="button"> <div class="${canEdit ? 'selectable' : 'hidden'}" role="button">
...@@ -115,15 +116,20 @@ export default class FilteredSearchVisualTokens { ...@@ -115,15 +116,20 @@ export default class FilteredSearchVisualTokens {
return AjaxCache.retrieve(labelsEndpoint) return AjaxCache.retrieve(labelsEndpoint)
.then(FilteredSearchVisualTokens.preprocessLabel.bind(null, labelsEndpoint)) .then(FilteredSearchVisualTokens.preprocessLabel.bind(null, labelsEndpoint))
.then((labels) => { .then(labels => {
const matchingLabel = (labels || []).find(label => `~${DropdownUtils.getEscapedText(label.title)}` === tokenValue); const matchingLabel = (labels || []).find(
label => `~${DropdownUtils.getEscapedText(label.title)}` === tokenValue,
);
if (!matchingLabel) { if (!matchingLabel) {
return; return;
} }
FilteredSearchVisualTokens FilteredSearchVisualTokens.setTokenStyle(
.setTokenStyle(tokenValueContainer, matchingLabel.color, matchingLabel.text_color); tokenValueContainer,
matchingLabel.color,
matchingLabel.text_color,
);
}) })
.catch(() => new Flash('An error occurred while fetching label colors.')); .catch(() => new Flash('An error occurred while fetching label colors.'));
} }
...@@ -134,39 +140,43 @@ export default class FilteredSearchVisualTokens { ...@@ -134,39 +140,43 @@ export default class FilteredSearchVisualTokens {
} }
const username = tokenValue.replace(/^@/, ''); const username = tokenValue.replace(/^@/, '');
return UsersCache.retrieve(username) return (
.then((user) => { UsersCache.retrieve(username)
if (!user) { .then(user => {
return; if (!user) {
} return;
}
/* eslint-disable no-param-reassign */
tokenValueContainer.dataset.originalValue = tokenValue; /* eslint-disable no-param-reassign */
tokenValueElement.innerHTML = ` tokenValueContainer.dataset.originalValue = tokenValue;
tokenValueElement.innerHTML = `
<img class="avatar s20" src="${user.avatar_url}" alt=""> <img class="avatar s20" src="${user.avatar_url}" alt="">
${_.escape(user.name)} ${_.escape(user.name)}
`; `;
/* eslint-enable no-param-reassign */ /* eslint-enable no-param-reassign */
}) })
// ignore error and leave username in the search bar // ignore error and leave username in the search bar
.catch(() => { }); .catch(() => {})
);
} }
static updateEmojiTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) { static updateEmojiTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) {
const container = tokenValueContainer; const container = tokenValueContainer;
const element = tokenValueElement; const element = tokenValueElement;
return import(/* webpackChunkName: 'emoji' */ '../emoji') return (
.then((Emoji) => { import(/* webpackChunkName: 'emoji' */ '../emoji')
if (!Emoji.isEmojiNameValid(tokenValue)) { .then(Emoji => {
return; if (!Emoji.isEmojiNameValid(tokenValue)) {
} return;
}
container.dataset.originalValue = tokenValue;
element.innerHTML = Emoji.glEmojiTag(tokenValue); container.dataset.originalValue = tokenValue;
}) element.innerHTML = Emoji.glEmojiTag(tokenValue);
// ignore error and leave emoji name in the search bar })
.catch(() => { }); // ignore error and leave emoji name in the search bar
.catch(() => {})
);
} }
static renderVisualTokenValue(parentElement, tokenName, tokenValue) { static renderVisualTokenValue(parentElement, tokenName, tokenValue) {
...@@ -177,24 +187,23 @@ export default class FilteredSearchVisualTokens { ...@@ -177,24 +187,23 @@ export default class FilteredSearchVisualTokens {
const tokenType = tokenName.toLowerCase(); const tokenType = tokenName.toLowerCase();
if (tokenType === 'label') { if (tokenType === 'label') {
FilteredSearchVisualTokens.updateLabelTokenColor(tokenValueContainer, tokenValue); FilteredSearchVisualTokens.updateLabelTokenColor(tokenValueContainer, tokenValue);
} else if ((tokenType === 'author') || (tokenType === 'assignee')) { } else if (tokenType === 'author' || tokenType === 'assignee') {
FilteredSearchVisualTokens.updateUserTokenAppearance( FilteredSearchVisualTokens.updateUserTokenAppearance(
tokenValueContainer, tokenValueElement, tokenValue, tokenValueContainer,
tokenValueElement,
tokenValue,
); );
} else if (tokenType === 'my-reaction') { } else if (tokenType === 'my-reaction') {
FilteredSearchVisualTokens.updateEmojiTokenAppearance( FilteredSearchVisualTokens.updateEmojiTokenAppearance(
tokenValueContainer, tokenValueElement, tokenValue, tokenValueContainer,
tokenValueElement,
tokenValue,
); );
} }
} }
static addVisualTokenElement(name, value, options = {}) { static addVisualTokenElement(name, value, options = {}) {
const { const { isSearchTerm = false, canEdit, uppercaseTokenName, capitalizeTokenValue } = options;
isSearchTerm = false,
canEdit,
uppercaseTokenName,
capitalizeTokenValue,
} = options;
const li = document.createElement('li'); const li = document.createElement('li');
li.classList.add('js-visual-token'); li.classList.add('js-visual-token');
li.classList.add(isSearchTerm ? 'filtered-search-term' : 'filtered-search-token'); li.classList.add(isSearchTerm ? 'filtered-search-term' : 'filtered-search-token');
...@@ -217,8 +226,10 @@ export default class FilteredSearchVisualTokens { ...@@ -217,8 +226,10 @@ export default class FilteredSearchVisualTokens {
} }
static addValueToPreviousVisualTokenElement(value) { static addValueToPreviousVisualTokenElement(value) {
const { lastVisualToken, isLastVisualTokenValid } = const {
FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); lastVisualToken,
isLastVisualTokenValid,
} = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
if (!isLastVisualTokenValid && lastVisualToken.classList.contains('filtered-search-token')) { if (!isLastVisualTokenValid && lastVisualToken.classList.contains('filtered-search-token')) {
const name = FilteredSearchVisualTokens.getLastTokenPartial(); const name = FilteredSearchVisualTokens.getLastTokenPartial();
...@@ -228,13 +239,15 @@ export default class FilteredSearchVisualTokens { ...@@ -228,13 +239,15 @@ export default class FilteredSearchVisualTokens {
} }
} }
static addFilterVisualToken(tokenName, tokenValue, { static addFilterVisualToken(
canEdit, tokenName,
uppercaseTokenName = false, tokenValue,
capitalizeTokenValue = false, { canEdit, uppercaseTokenName = false, capitalizeTokenValue = false } = {},
} = {}) { ) {
const { lastVisualToken, isLastVisualTokenValid } const {
= FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); lastVisualToken,
isLastVisualTokenValid,
} = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
const { addVisualTokenElement } = FilteredSearchVisualTokens; const { addVisualTokenElement } = FilteredSearchVisualTokens;
if (isLastVisualTokenValid) { if (isLastVisualTokenValid) {
...@@ -308,8 +321,7 @@ export default class FilteredSearchVisualTokens { ...@@ -308,8 +321,7 @@ export default class FilteredSearchVisualTokens {
static tokenizeInput() { static tokenizeInput() {
const input = FilteredSearchContainer.container.querySelector('.filtered-search'); const input = FilteredSearchContainer.container.querySelector('.filtered-search');
const { isLastVisualTokenValid } = const { isLastVisualTokenValid } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
if (input.value) { if (input.value) {
if (isLastVisualTokenValid) { if (isLastVisualTokenValid) {
...@@ -375,8 +387,7 @@ export default class FilteredSearchVisualTokens { ...@@ -375,8 +387,7 @@ export default class FilteredSearchVisualTokens {
FilteredSearchVisualTokens.tokenizeInput(); FilteredSearchVisualTokens.tokenizeInput();
if (!tokenContainer.lastElementChild.isEqualNode(inputLi)) { if (!tokenContainer.lastElementChild.isEqualNode(inputLi)) {
const { isLastVisualTokenValid } = const { isLastVisualTokenValid } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
if (!isLastVisualTokenValid) { if (!isLastVisualTokenValid) {
const lastPartial = FilteredSearchVisualTokens.getLastTokenPartial(); const lastPartial = FilteredSearchVisualTokens.getLastTokenPartial();
......
import FilteredSearchTokenKeys from './filtered_search_token_keys'; import FilteredSearchTokenKeys from './filtered_search_token_keys';
export const tokenKeys = [{ export const tokenKeys = [
key: 'author', {
type: 'string', key: 'author',
param: 'username', type: 'string',
symbol: '@', param: 'username',
icon: 'pencil', symbol: '@',
tag: '@author', icon: 'pencil',
}, { tag: '@author',
key: 'assignee', },
type: 'string', {
param: 'username', key: 'assignee',
symbol: '@', type: 'string',
icon: 'user', param: 'username',
tag: '@assignee', symbol: '@',
}, { icon: 'user',
key: 'milestone', tag: '@assignee',
type: 'string', },
param: 'title', {
symbol: '%', key: 'milestone',
icon: 'clock', type: 'string',
tag: '%milestone', param: 'title',
}, { symbol: '%',
key: 'label', icon: 'clock',
type: 'array', tag: '%milestone',
param: 'name[]', },
symbol: '~', {
icon: 'labels', key: 'label',
tag: '~label', type: 'array',
}]; param: 'name[]',
symbol: '~',
icon: 'labels',
tag: '~label',
},
];
if (gon.current_user_id) { if (gon.current_user_id) {
// Appending tokenkeys only logged-in // Appending tokenkeys only logged-in
...@@ -42,36 +47,47 @@ if (gon.current_user_id) { ...@@ -42,36 +47,47 @@ if (gon.current_user_id) {
}); });
} }
export const alternativeTokenKeys = [{ export const alternativeTokenKeys = [
key: 'label', {
type: 'string', key: 'label',
param: 'name', type: 'string',
symbol: '~', param: 'name',
}]; symbol: '~',
},
];
export const conditions = [{ export const conditions = [
url: 'assignee_id=0', {
tokenKey: 'assignee', url: 'assignee_id=0',
value: 'none', tokenKey: 'assignee',
}, { value: 'none',
url: 'milestone_title=No+Milestone', },
tokenKey: 'milestone', {
value: 'none', url: 'milestone_title=No+Milestone',
}, { tokenKey: 'milestone',
url: 'milestone_title=%23upcoming', value: 'none',
tokenKey: 'milestone', },
value: 'upcoming', {
}, { url: 'milestone_title=%23upcoming',
url: 'milestone_title=%23started', tokenKey: 'milestone',
tokenKey: 'milestone', value: 'upcoming',
value: 'started', },
}, { {
url: 'label_name[]=No+Label', url: 'milestone_title=%23started',
tokenKey: 'label', tokenKey: 'milestone',
value: 'none', value: 'started',
}]; },
{
url: 'label_name[]=No+Label',
tokenKey: 'label',
value: 'none',
},
];
const IssuableFilteredSearchTokenKeys = const IssuableFilteredSearchTokenKeys = new FilteredSearchTokenKeys(
new FilteredSearchTokenKeys(tokenKeys, alternativeTokenKeys, conditions); tokenKeys,
alternativeTokenKeys,
conditions,
);
export default IssuableFilteredSearchTokenKeys; export default IssuableFilteredSearchTokenKeys;
...@@ -3,11 +3,7 @@ import RecentSearchesDropdownContent from './components/recent_searches_dropdown ...@@ -3,11 +3,7 @@ import RecentSearchesDropdownContent from './components/recent_searches_dropdown
import eventHub from './event_hub'; import eventHub from './event_hub';
class RecentSearchesRoot { class RecentSearchesRoot {
constructor( constructor(recentSearchesStore, recentSearchesService, wrapperElement) {
recentSearchesStore,
recentSearchesService,
wrapperElement,
) {
this.store = recentSearchesStore; this.store = recentSearchesStore;
this.service = recentSearchesService; this.service = recentSearchesService;
this.wrapperElement = wrapperElement; this.wrapperElement = wrapperElement;
...@@ -35,7 +31,9 @@ class RecentSearchesRoot { ...@@ -35,7 +31,9 @@ class RecentSearchesRoot {
components: { components: {
RecentSearchesDropdownContent, RecentSearchesDropdownContent,
}, },
data() { return state; }, data() {
return state;
},
template: ` template: `
<recent-searches-dropdown-content <recent-searches-dropdown-content
:items="recentSearches" :items="recentSearches"
...@@ -57,7 +55,6 @@ class RecentSearchesRoot { ...@@ -57,7 +55,6 @@ class RecentSearchesRoot {
this.vm.$destroy(); this.vm.$destroy();
} }
} }
} }
export default RecentSearchesRoot; export default RecentSearchesRoot;
...@@ -2,11 +2,14 @@ import _ from 'underscore'; ...@@ -2,11 +2,14 @@ import _ from 'underscore';
class RecentSearchesStore { class RecentSearchesStore {
constructor(initialState = {}, allowedKeys) { constructor(initialState = {}, allowedKeys) {
this.state = Object.assign({ this.state = Object.assign(
isLocalStorageAvailable: true, {
recentSearches: [], isLocalStorageAvailable: true,
allowedKeys, recentSearches: [],
}, initialState); allowedKeys,
},
initialState,
);
} }
addRecentSearch(newSearch) { addRecentSearch(newSearch) {
......
...@@ -26,14 +26,17 @@ export default class IssuableIndex { ...@@ -26,14 +26,17 @@ export default class IssuableIndex {
static resetIncomingEmailToken() { static resetIncomingEmailToken() {
const $resetToken = $('.incoming-email-token-reset'); const $resetToken = $('.incoming-email-token-reset');
$resetToken.on('click', (e) => { $resetToken.on('click', e => {
e.preventDefault(); e.preventDefault();
$resetToken.text('resetting...'); $resetToken.text('resetting...');
axios.put($resetToken.attr('href')) axios
.put($resetToken.attr('href'))
.then(({ data }) => { .then(({ data }) => {
$('#issuable_email').val(data.new_address).focus(); $('#issuable_email')
.val(data.new_address)
.focus();
$resetToken.text('reset it'); $resetToken.text('reset it');
}) })
......
...@@ -28,7 +28,7 @@ export default class Issue { ...@@ -28,7 +28,7 @@ export default class Issue {
} }
// Listen to state changes in the Vue app // 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); this.updateTopState(event.detail.isClosed, event.detail.data);
}); });
} }
...@@ -55,7 +55,13 @@ export default class Issue { ...@@ -55,7 +55,13 @@ export default class Issue {
$(document).trigger('issuable:change', isClosed); $(document).trigger('issuable:change', isClosed);
this.toggleCloseReopenButton(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; numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1;
projectIssuesCounter.text(addDelimiter(numProjectIssues)); projectIssuesCounter.text(addDelimiter(numProjectIssues));
...@@ -76,29 +82,34 @@ export default class Issue { ...@@ -76,29 +82,34 @@ export default class Issue {
initIssueBtnEventListeners() { initIssueBtnEventListeners() {
const issueFailMessage = 'Unable to update this issue at this time.'; 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) => { return $(document).on(
var $button, shouldSubmit, url; 'click',
e.preventDefault(); '.js-issuable-actions a.btn-close, .js-issuable-actions a.btn-reopen',
e.stopImmediatePropagation(); e => {
$button = $(e.currentTarget); var $button, shouldSubmit, url;
shouldSubmit = $button.hasClass('btn-comment'); e.preventDefault();
if (shouldSubmit) { e.stopImmediatePropagation();
Issue.submitNoteForm($button.closest('form')); $button = $(e.currentTarget);
} shouldSubmit = $button.hasClass('btn-comment');
if (shouldSubmit) {
this.disableCloseReopenButton($button); Issue.submitNoteForm($button.closest('form'));
}
url = $button.attr('href'); this.disableCloseReopenButton($button);
return axios.put(url)
.then(({ data }) => { url = $button.attr('href');
const isClosed = $button.hasClass('btn-close'); return axios
this.updateTopState(isClosed, data); .put(url)
}) .then(({ data }) => {
.catch(() => flash(issueFailMessage)) const isClosed = $button.hasClass('btn-close');
.then(() => { this.updateTopState(isClosed, data);
this.disableCloseReopenButton($button, false); })
}); .catch(() => flash(issueFailMessage))
}); .then(() => {
this.disableCloseReopenButton($button, false);
});
},
);
} }
initCloseReopenReport() { initCloseReopenReport() {
...@@ -124,7 +135,7 @@ export default class Issue { ...@@ -124,7 +135,7 @@ export default class Issue {
static submitNoteForm(form) { static submitNoteForm(form) {
var noteText; var noteText;
noteText = form.find("textarea.js-note-text").val(); noteText = form.find('textarea.js-note-text').val();
if (noteText && noteText.trim().length > 0) { if (noteText && noteText.trim().length > 0) {
return form.submit(); return form.submit();
} }
...@@ -133,22 +144,26 @@ export default class Issue { ...@@ -133,22 +144,26 @@ export default class Issue {
static initMergeRequests() { static initMergeRequests() {
var $container; var $container;
$container = $('#merge-requests'); $container = $('#merge-requests');
return axios.get($container.data('url')) return axios
.get($container.data('url'))
.then(({ data }) => { .then(({ data }) => {
if ('html' in data) { if ('html' in data) {
$container.html(data.html); $container.html(data.html);
} }
}).catch(() => flash('Failed to load referenced merge requests')); })
.catch(() => flash('Failed to load referenced merge requests'));
} }
static initRelatedBranches() { static initRelatedBranches() {
var $container; var $container;
$container = $('#related-branches'); $container = $('#related-branches');
return axios.get($container.data('url')) return axios
.get($container.data('url'))
.then(({ data }) => { .then(({ data }) => {
if ('html' in data) { if ('html' in data) {
$container.html(data.html); $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 { ...@@ -42,16 +42,14 @@ export default class Job extends LogOutputBehaviours {
this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100); this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100);
this.$window this.$window.off('scroll').on('scroll', () => {
.off('scroll') if (!isScrolledToBottom()) {
.on('scroll', () => { this.toggleScrollAnimation(false);
if (!isScrolledToBottom()) { } else if (isScrolledToBottom() && !this.isLogComplete) {
this.toggleScrollAnimation(false); this.toggleScrollAnimation(true);
} else if (isScrolledToBottom() && !this.isLogComplete) { }
this.toggleScrollAnimation(true); this.scrollThrottled();
} });
this.scrollThrottled();
});
this.$window this.$window
.off('resize.build') .off('resize.build')
...@@ -87,10 +85,11 @@ export default class Job extends LogOutputBehaviours { ...@@ -87,10 +85,11 @@ export default class Job extends LogOutputBehaviours {
} }
getBuildTrace() { getBuildTrace() {
return axios.get(`${this.pagePath}/trace.json`, { return axios
params: { state: this.state }, .get(`${this.pagePath}/trace.json`, {
}) params: { state: this.state },
.then((res) => { })
.then(res => {
const log = res.data; const log = res.data;
if (!this.fetchingStatusFavicon) { if (!this.fetchingStatusFavicon) {
...@@ -186,5 +185,4 @@ export default class Job extends LogOutputBehaviours { ...@@ -186,5 +185,4 @@ export default class Job extends LogOutputBehaviours {
sidebarOnClick() { sidebarOnClick() {
if (this.shouldHideSidebarForViewport()) this.toggleSidebar(); if (this.shouldHideSidebarForViewport()) this.toggleSidebar();
} }
} }
...@@ -47,7 +47,10 @@ export default class LabelManager { ...@@ -47,7 +47,10 @@ export default class LabelManager {
} }
toggleEmptyState($label, $btn, action) { 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) { toggleLabelPriority($label, action, persistState) {
...@@ -80,16 +83,14 @@ export default class LabelManager { ...@@ -80,16 +83,14 @@ export default class LabelManager {
return; return;
} }
if (action === 'remove') { if (action === 'remove') {
axios.delete(url) axios.delete(url).catch(rollbackLabelPosition);
.catch(rollbackLabelPosition);
// Restore empty message // Restore empty message
if (!$from.find('li').length) { if (!$from.find('li').length) {
$from.find('.empty-message').removeClass('hidden'); $from.find('.empty-message').removeClass('hidden');
} }
} else { } else {
this.savePrioritySort($label, action) this.savePrioritySort($label, action).catch(rollbackLabelPosition);
.catch(rollbackLabelPosition);
} }
} }
...@@ -102,8 +103,7 @@ export default class LabelManager { ...@@ -102,8 +103,7 @@ export default class LabelManager {
} }
onPrioritySortUpdate() { onPrioritySortUpdate() {
this.savePrioritySort() this.savePrioritySort().catch(() => flash(this.errorMessage));
.catch(() => flash(this.errorMessage));
} }
savePrioritySort() { savePrioritySort() {
......
...@@ -22,7 +22,7 @@ export default class Labels { ...@@ -22,7 +22,7 @@ export default class Labels {
updateColorPreview() { updateColorPreview() {
const previewColor = $('input#label_color').val(); const previewColor = $('input#label_color').val();
return $('div.label-color-preview').css('background-color', previewColor); 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 // Updates the preview color with a click on a suggested color
......
...@@ -5,7 +5,9 @@ import initFlyOutNav from './fly_out_nav'; ...@@ -5,7 +5,9 @@ import initFlyOutNav from './fly_out_nav';
function hideEndFade($scrollingTabs) { function hideEndFade($scrollingTabs) {
$scrollingTabs.each(function scrollTabsLoop() { $scrollingTabs.each(function scrollTabsLoop() {
const $this = $(this); 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() { ...@@ -15,38 +17,42 @@ export default function initLayoutNav() {
initFlyOutNav(); initFlyOutNav();
$(document).on('init.scrolling-tabs', () => { $(document)
const $scrollingTabs = $('.scrolling-tabs').not('.is-initialized'); .on('init.scrolling-tabs', () => {
$scrollingTabs.addClass('is-initialized'); const $scrollingTabs = $('.scrolling-tabs').not('.is-initialized');
$scrollingTabs.addClass('is-initialized');
$(window).on('resize.nav', () => { $(window)
hideEndFade($scrollingTabs); .on('resize.nav', () => {
}).trigger('resize.nav'); hideEndFade($scrollingTabs);
})
.trigger('resize.nav');
$scrollingTabs.on('scroll', function tabsScrollEvent() { $scrollingTabs.on('scroll', function tabsScrollEvent() {
const $this = $(this); const $this = $(this);
const currentPosition = $this.scrollLeft(); const currentPosition = $this.scrollLeft();
const maxPosition = $this.prop('scrollWidth') - $this.outerWidth(); const maxPosition = $this.prop('scrollWidth') - $this.outerWidth();
$this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0); $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0);
$this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1); $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1);
}); });
$scrollingTabs.each(function scrollTabsEachLoop() { $scrollingTabs.each(function scrollTabsEachLoop() {
const $this = $(this); const $this = $(this);
const scrollingTabWidth = $this.width(); const scrollingTabWidth = $this.width();
const $active = $this.find('.active'); const $active = $this.find('.active');
const activeWidth = $active.width(); const activeWidth = $active.width();
if ($active.length) { if ($active.length) {
const offset = $active.offset().left + activeWidth; const offset = $active.offset().left + activeWidth;
if (offset > scrollingTabWidth - 30) { if (offset > scrollingTabWidth - 30) {
const scrollLeft = (offset - (scrollingTabWidth / 2)) - (activeWidth / 2); 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) { ...@@ -70,7 +70,7 @@ LineHighlighter.prototype.highlightHash = function(newHash) {
const scrollOptions = { const scrollOptions = {
// Scroll to the first highlighted line on initial load // Scroll to the first highlighted line on initial load
// Offset -50 for the sticky top bar, and another -100 for some context // Offset -50 for the sticky top bar, and another -100 for some context
offset: -150 offset: -150,
}; };
if (this.options.scrollFileHolder) { if (this.options.scrollFileHolder) {
$(this.options.fileHolderSelector).scrollTo(lineSelector, scrollOptions); $(this.options.fileHolderSelector).scrollTo(lineSelector, scrollOptions);
...@@ -85,7 +85,9 @@ LineHighlighter.prototype.clickHandler = function(event) { ...@@ -85,7 +85,9 @@ LineHighlighter.prototype.clickHandler = function(event) {
var current, lineNumber, range; var current, lineNumber, range;
event.preventDefault(); event.preventDefault();
this.clearHighlight(); this.clearHighlight();
lineNumber = $(event.target).closest('a').data('lineNumber'); lineNumber = $(event.target)
.closest('a')
.data('lineNumber');
current = this.hashToRange(this._hash); current = this.hashToRange(this._hash);
if (!(current[0] && event.shiftKey)) { if (!(current[0] && event.shiftKey)) {
// If there's no current selection, or there is but Shift wasn't held, // If there's no current selection, or there is but Shift wasn't held,
...@@ -104,7 +106,7 @@ LineHighlighter.prototype.clickHandler = function(event) { ...@@ -104,7 +106,7 @@ LineHighlighter.prototype.clickHandler = function(event) {
}; };
LineHighlighter.prototype.clearHighlight = function() { LineHighlighter.prototype.clearHighlight = function() {
return $("." + this.highlightLineClass).removeClass(this.highlightLineClass); return $('.' + this.highlightLineClass).removeClass(this.highlightLineClass);
}; };
// Convert a URL hash String into line numbers // Convert a URL hash String into line numbers
...@@ -135,7 +137,7 @@ LineHighlighter.prototype.hashToRange = function(hash) { ...@@ -135,7 +137,7 @@ LineHighlighter.prototype.hashToRange = function(hash) {
// //
// lineNumber - Line number to highlight // lineNumber - Line number to highlight
LineHighlighter.prototype.highlightLine = function(lineNumber) { LineHighlighter.prototype.highlightLine = function(lineNumber) {
return $("#LC" + lineNumber).addClass(this.highlightLineClass); return $('#LC' + lineNumber).addClass(this.highlightLineClass);
}; };
// Highlight all lines within a range // Highlight all lines within a range
...@@ -160,9 +162,9 @@ LineHighlighter.prototype.highlightRange = function(range) { ...@@ -160,9 +162,9 @@ LineHighlighter.prototype.highlightRange = function(range) {
LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) { LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) {
var hash; var hash;
if (lastLineNumber) { if (lastLineNumber) {
hash = "#L" + firstLineNumber + "-" + lastLineNumber; hash = '#L' + firstLineNumber + '-' + lastLineNumber;
} else { } else {
hash = "#L" + firstLineNumber; hash = '#L' + firstLineNumber;
} }
this._hash = hash; this._hash = hash;
return this.__setLocationHash__(hash); return this.__setLocationHash__(hash);
...@@ -172,11 +174,15 @@ LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) { ...@@ -172,11 +174,15 @@ LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) {
// //
// This method is stubbed in tests. // This method is stubbed in tests.
LineHighlighter.prototype.__setLocationHash__ = function(value) { LineHighlighter.prototype.__setLocationHash__ = function(value) {
return window.history.pushState({ return window.history.pushState(
url: value {
// We're using pushState instead of assigning location.hash directly to url: value,
// prevent the page from scrolling on the hashchange event // We're using pushState instead of assigning location.hash directly to
}, document.title, value); // prevent the page from scrolling on the hashchange event
},
document.title,
value,
);
}; };
export default LineHighlighter; export default LineHighlighter;
...@@ -15,7 +15,7 @@ export default (input, parameters, escapeParameters = true) => { ...@@ -15,7 +15,7 @@ export default (input, parameters, escapeParameters = true) => {
let output = input; let output = input;
if (parameters) { if (parameters) {
Object.keys(parameters).forEach((parameterName) => { Object.keys(parameters).forEach(parameterName => {
const parameterValue = parameters[parameterName]; const parameterValue = parameters[parameterName];
const escapedParameterValue = escapeParameters ? _.escape(parameterValue) : parameterValue; const escapedParameterValue = escapeParameters ? _.escape(parameterValue) : parameterValue;
output = output.replace(new RegExp(`%{${parameterName}}`, 'g'), escapedParameterValue); output = output.replace(new RegExp(`%{${parameterName}}`, 'g'), escapedParameterValue);
......
...@@ -9,7 +9,9 @@ import { parsePikadayDate, pikadayToString } from './lib/utils/datefix'; ...@@ -9,7 +9,9 @@ import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
// //
export default function memberExpirationDate(selector = '.js-access-expiration-date') { export default function memberExpirationDate(selector = '.js-access-expiration-date') {
function toggleClearInput() { function toggleClearInput() {
$(this).closest('.clearable-input').toggleClass('has-value', $(this).val() !== ''); $(this)
.closest('.clearable-input')
.toggleClass('has-value', $(this).val() !== '');
} }
const inputs = $(selector); const inputs = $(selector);
...@@ -40,7 +42,9 @@ export default function memberExpirationDate(selector = '.js-access-expiration-d ...@@ -40,7 +42,9 @@ export default function memberExpirationDate(selector = '.js-access-expiration-d
inputs.next('.js-clear-input').on('click', function clicked(event) { inputs.next('.js-clear-input').on('click', function clicked(event) {
event.preventDefault(); event.preventDefault();
const input = $(this).closest('.clearable-input').find(selector); const input = $(this)
.closest('.clearable-input')
.find(selector);
const calendar = input.data('pikaday'); const calendar = input.data('pikaday');
calendar.setDate(null); calendar.setDate(null);
......
...@@ -16,26 +16,29 @@ function MergeRequest(opts) { ...@@ -16,26 +16,29 @@ function MergeRequest(opts) {
this.opts = opts != null ? opts : {}; this.opts = opts != null ? opts : {};
this.submitNoteForm = this.submitNoteForm.bind(this); this.submitNoteForm = this.submitNoteForm.bind(this);
this.$el = $('.merge-request'); this.$el = $('.merge-request');
this.$('.show-all-commits').on('click', (function(_this) { this.$('.show-all-commits').on(
return function() { 'click',
return _this.showAllCommits(); (function(_this) {
}; return function() {
})(this)); return _this.showAllCommits();
};
})(this),
);
this.initTabs(); this.initTabs();
this.initMRBtnListeners(); this.initMRBtnListeners();
this.initCommitMessageListeners(); this.initCommitMessageListeners();
this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport(); this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport();
if ($("a.btn-close").length) { if ($('a.btn-close').length) {
this.taskList = new TaskList({ this.taskList = new TaskList({
dataType: 'merge_request', dataType: 'merge_request',
fieldName: 'description', fieldName: 'description',
selector: '.detail-page-description', selector: '.detail-page-description',
onSuccess: (result) => { onSuccess: result => {
document.querySelector('#task_status').innerText = result.task_status; document.querySelector('#task_status').innerText = result.task_status;
document.querySelector('#task_status_short').innerText = result.task_status_short; document.querySelector('#task_status_short').innerText = result.task_status_short;
} },
}); });
} }
} }
...@@ -84,7 +87,7 @@ MergeRequest.prototype.initMRBtnListeners = function() { ...@@ -84,7 +87,7 @@ MergeRequest.prototype.initMRBtnListeners = function() {
MergeRequest.prototype.submitNoteForm = function(form, $button) { MergeRequest.prototype.submitNoteForm = function(form, $button) {
var noteText; var noteText;
noteText = form.find("textarea.js-note-text").val(); noteText = form.find('textarea.js-note-text').val();
if (noteText.trim().length > 0) { if (noteText.trim().length > 0) {
form.submit(); form.submit();
$button.data('submitted', true); $button.data('submitted', true);
...@@ -122,7 +125,7 @@ MergeRequest.setStatusBoxToMerged = function() { ...@@ -122,7 +125,7 @@ MergeRequest.setStatusBoxToMerged = function() {
MergeRequest.decreaseCounter = function(by = 1) { MergeRequest.decreaseCounter = function(by = 1) {
const $el = $('.js-merge-counter'); 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)); $el.text(addDelimiter(count));
}; };
......
...@@ -15,7 +15,7 @@ export default class Milestone { ...@@ -15,7 +15,7 @@ export default class Milestone {
} }
bindTabsSwitching() { 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); const $target = $(e.target);
window.location.hash = $target.attr('href'); window.location.hash = $target.attr('href');
...@@ -36,7 +36,8 @@ export default class Milestone { ...@@ -36,7 +36,8 @@ export default class Milestone {
const tabElId = $target.attr('href'); const tabElId = $target.attr('href');
if (endpoint && !$target.hasClass('is-loaded')) { if (endpoint && !$target.hasClass('is-loaded')) {
axios.get(endpoint) axios
.get(endpoint)
.then(({ data }) => { .then(({ data }) => {
$(tabElId).html(data.html); $(tabElId).html(data.html);
$target.addClass('is-loaded'); $target.addClass('is-loaded');
...@@ -46,23 +47,28 @@ export default class Milestone { ...@@ -46,23 +47,28 @@ export default class Milestone {
} }
static initDeprecationMessage() { static initDeprecationMessage() {
const deprecationMesssageContainer = document.querySelector('.js-milestone-deprecation-message'); const deprecationMesssageContainer = document.querySelector(
'.js-milestone-deprecation-message',
);
if (!deprecationMesssageContainer) return; 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 $popover = $('.js-popover-link', deprecationMesssageContainer);
const hideOnScroll = togglePopover.bind($popover, false); const hideOnScroll = togglePopover.bind($popover, false);
$popover.popover({ $popover
content: deprecationMessage, .popover({
html: true, content: deprecationMessage,
placement: 'bottom', html: true,
}) placement: 'bottom',
.on('mouseenter', mouseenter) })
.on('mouseleave', debouncedMouseleave()) .on('mouseenter', mouseenter)
.on('show.bs.popover', () => { .on('mouseleave', debouncedMouseleave())
window.addEventListener('scroll', hideOnScroll, { once: true }); .on('show.bs.popover', () => {
}); window.addEventListener('scroll', hideOnScroll, { once: true });
});
} }
} }
...@@ -46,7 +46,7 @@ export default class MiniPipelineGraph { ...@@ -46,7 +46,7 @@ export default class MiniPipelineGraph {
$(document).on( $(document).on(
'click', 'click',
`${this.container} .js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item`, `${this.container} .js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item`,
(e) => { e => {
e.stopPropagation(); e.stopPropagation();
}, },
); );
...@@ -82,7 +82,8 @@ export default class MiniPipelineGraph { ...@@ -82,7 +82,8 @@ export default class MiniPipelineGraph {
this.renderBuildsList(button, ''); this.renderBuildsList(button, '');
this.toggleLoading(button); this.toggleLoading(button);
axios.get(endpoint) axios
.get(endpoint)
.then(({ data }) => { .then(({ data }) => {
this.toggleLoading(button); this.toggleLoading(button);
this.renderBuildsList(button, data.html); this.renderBuildsList(button, data.html);
...@@ -90,7 +91,11 @@ export default class MiniPipelineGraph { ...@@ -90,7 +91,11 @@ export default class MiniPipelineGraph {
}) })
.catch(() => { .catch(() => {
this.toggleLoading(button); this.toggleLoading(button);
if ($(button).parent().hasClass('open')) { if (
$(button)
.parent()
.hasClass('open')
) {
$(button).dropdown('toggle'); $(button).dropdown('toggle');
} }
flash('An error occurred while fetching the builds.', 'alert'); flash('An error occurred while fetching the builds.', 'alert');
...@@ -104,8 +109,8 @@ export default class MiniPipelineGraph { ...@@ -104,8 +109,8 @@ export default class MiniPipelineGraph {
* @return {type} * @return {type}
*/ */
toggleLoading(stageContainer) { toggleLoading(stageContainer) {
stageContainer.parentElement.querySelector( stageContainer.parentElement
`${this.dropdownListSelector} .js-builds-dropdown-loading`, .querySelector(`${this.dropdownListSelector} .js-builds-dropdown-loading`)
).classList.toggle('hidden'); .classList.toggle('hidden');
} }
} }
...@@ -14,14 +14,14 @@ export default class NamespaceSelect { ...@@ -14,14 +14,14 @@ export default class NamespaceSelect {
selectable: true, selectable: true,
filterRemote: true, filterRemote: true,
search: { search: {
fields: ['path'] fields: ['path'],
}, },
fieldName: fieldName, fieldName: fieldName,
toggleLabel: function(selected) { toggleLabel: function(selected) {
if (selected.id == null) { if (selected.id == null) {
return selected.text; return selected.text;
} else { } else {
return selected.kind + ": " + selected.full_path; return selected.kind + ': ' + selected.full_path;
} }
}, },
data: function(term, dataCallback) { data: function(term, dataCallback) {
...@@ -29,7 +29,7 @@ export default class NamespaceSelect { ...@@ -29,7 +29,7 @@ export default class NamespaceSelect {
if (isFilter) { if (isFilter) {
const anyNamespace = { const anyNamespace = {
text: 'Any namespace', text: 'Any namespace',
id: null id: null,
}; };
namespaces.unshift(anyNamespace); namespaces.unshift(anyNamespace);
namespaces.splice(1, 0, 'divider'); namespaces.splice(1, 0, 'divider');
...@@ -41,7 +41,7 @@ export default class NamespaceSelect { ...@@ -41,7 +41,7 @@ export default class NamespaceSelect {
if (namespace.id == null) { if (namespace.id == null) {
return namespace.text; return namespace.text;
} else { } else {
return namespace.kind + ": " + namespace.full_path; return namespace.kind + ': ' + namespace.full_path;
} }
}, },
renderRow: this.renderRow, renderRow: this.renderRow,
......
...@@ -20,7 +20,7 @@ export default (function() { ...@@ -20,7 +20,7 @@ export default (function() {
this.mtime = 0; this.mtime = 0;
this.mspace = 0; this.mspace = 0;
this.parents = {}; this.parents = {};
this.colors = ["#000"]; this.colors = ['#000'];
this.offsetX = 150; this.offsetX = 150;
this.offsetY = 20; this.offsetY = 20;
this.unitTime = 30; this.unitTime = 30;
...@@ -30,9 +30,10 @@ export default (function() { ...@@ -30,9 +30,10 @@ export default (function() {
} }
BranchGraph.prototype.load = function() { BranchGraph.prototype.load = function() {
axios.get(this.options.url) axios
.get(this.options.url)
.then(({ data }) => { .then(({ data }) => {
$(".loading", this.element).hide(); $('.loading', this.element).hide();
this.prepareData(data.days, data.commits); this.prepareData(data.days, data.commits);
this.buildGraph(); this.buildGraph();
}) })
...@@ -71,17 +72,19 @@ export default (function() { ...@@ -71,17 +72,19 @@ export default (function() {
c = ref[j]; c = ref[j];
this.mtime = Math.max(this.mtime, c.time); this.mtime = Math.max(this.mtime, c.time);
this.mspace = Math.max(this.mspace, c.space); this.mspace = Math.max(this.mspace, c.space);
results.push((function() { results.push(
var l, len1, ref1, results1; function() {
ref1 = c.parents; var l, len1, ref1, results1;
results1 = []; ref1 = c.parents;
for (l = 0, len1 = ref1.length; l < len1; l += 1) { results1 = [];
p = ref1[l]; for (l = 0, len1 = ref1.length; l < len1; l += 1) {
this.parents[p[0]] = true; p = ref1[l];
results1.push(this.mspace = Math.max(this.mspace, p[1])); this.parents[p[0]] = true;
} results1.push((this.mspace = Math.max(this.mspace, p[1])));
return results1; }
}).call(this)); return results1;
}.call(this),
);
} }
return results; return results;
}; };
...@@ -91,11 +94,11 @@ export default (function() { ...@@ -91,11 +94,11 @@ export default (function() {
k = 0; k = 0;
results = []; results = [];
while (k < this.mspace) { 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 // Skipping a few colors in the spectrum to get more contrast between colors
Raphael.getColor(); Raphael.getColor();
Raphael.getColor(); Raphael.getColor();
results.push(k += 1); results.push((k += 1));
} }
return results; return results;
}; };
...@@ -104,12 +107,12 @@ export default (function() { ...@@ -104,12 +107,12 @@ export default (function() {
var cuday, cumonth, day, j, len, mm, ref; var cuday, cumonth, day, j, len, mm, ref;
const { r } = this; const { r } = this;
cuday = 0; cuday = 0;
cumonth = ""; cumonth = '';
r.rect(0, 0, 40, this.barHeight).attr({ r.rect(0, 0, 40, this.barHeight).attr({
fill: "#222" fill: '#222',
}); });
r.rect(40, 0, 30, this.barHeight).attr({ r.rect(40, 0, 30, this.barHeight).attr({
fill: "#444" fill: '#444',
}); });
ref = this.days; ref = this.days;
...@@ -118,16 +121,16 @@ export default (function() { ...@@ -118,16 +121,16 @@ export default (function() {
if (cuday !== day[0] || cumonth !== day[1]) { if (cuday !== day[0] || cumonth !== day[1]) {
// Dates // Dates
r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({ r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({
font: "12px Monaco, monospace", font: '12px Monaco, monospace',
fill: "#BBB" fill: '#BBB',
}); });
[cuday] = day; [cuday] = day;
} }
if (cumonth !== day[1]) { if (cumonth !== day[1]) {
// Months // Months
r.text(20, this.offsetY + this.unitTime * mm, day[1]).attr({ r.text(20, this.offsetY + this.unitTime * mm, day[1]).attr({
font: "12px Monaco, monospace", font: '12px Monaco, monospace',
fill: "#EEE" fill: '#EEE',
}); });
// eslint-disable-next-line prefer-destructuring // eslint-disable-next-line prefer-destructuring
...@@ -173,11 +176,13 @@ export default (function() { ...@@ -173,11 +176,13 @@ export default (function() {
BranchGraph.prototype.bindEvents = function() { BranchGraph.prototype.bindEvents = function() {
const { element } = this; const { element } = this;
return $(element).scroll((function(_this) { return $(element).scroll(
return function(event) { (function(_this) {
return _this.renderPartialGraph(); return function(event) {
}; return _this.renderPartialGraph();
})(this)); };
})(this),
);
}; };
BranchGraph.prototype.scrollDown = function() { BranchGraph.prototype.scrollDown = function() {
...@@ -219,46 +224,53 @@ export default (function() { ...@@ -219,46 +224,53 @@ export default (function() {
shortrefs = commit.refs; shortrefs = commit.refs;
// Truncate if longer than 15 chars // Truncate if longer than 15 chars
if (shortrefs.length > 17) { if (shortrefs.length > 17) {
shortrefs = shortrefs.substr(0, 15) + ""; shortrefs = shortrefs.substr(0, 15) + '';
} }
text = r.text(x + 4, y, shortrefs).attr({ text = r.text(x + 4, y, shortrefs).attr({
"text-anchor": "start", 'text-anchor': 'start',
font: "10px Monaco, monospace", font: '10px Monaco, monospace',
fill: "#FFF", fill: '#FFF',
title: commit.refs title: commit.refs,
}); });
textbox = text.getBBox(); textbox = text.getBBox();
// Create rectangle based on the size of the textbox // Create rectangle based on the size of the textbox
rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({ rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({
fill: "#000", fill: '#000',
"fill-opacity": .5, 'fill-opacity': 0.5,
stroke: "none" stroke: 'none',
}); });
triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr({ triangle = r.path(['M', x - 5, y, 'L', x - 15, y - 4, 'L', x - 15, y + 4, 'Z']).attr({
fill: "#000", fill: '#000',
"fill-opacity": .5, 'fill-opacity': 0.5,
stroke: "none" stroke: 'none',
}); });
label = r.set(rect, text); 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 // Set text to front
return text.toFront(); return text.toFront();
}; };
BranchGraph.prototype.appendAnchor = function(x, y, commit) { BranchGraph.prototype.appendAnchor = function(x, y, commit) {
const { r, top, options } = this; const { r, top, options } = this;
const anchor = r.circle(x, y, 10).attr({ const anchor = r
fill: "#000", .circle(x, y, 10)
opacity: 0, .attr({
cursor: "pointer" fill: '#000',
}).click(function() { opacity: 0,
return window.open(options.commit_url.replace("%s", commit.id), "_blank"); cursor: 'pointer',
}).hover(function() { })
this.tooltip = r.commitTooltip(x + 5, y, commit); .click(function() {
return top.push(this.tooltip.insertBefore(this)); return window.open(options.commit_url.replace('%s', commit.id), '_blank');
}, function() { })
return this.tooltip && this.tooltip.remove() && delete this.tooltip; .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); return top.push(anchor);
}; };
...@@ -266,7 +278,7 @@ export default (function() { ...@@ -266,7 +278,7 @@ export default (function() {
const { r } = this; const { r } = this;
r.circle(x, y, 3).attr({ r.circle(x, y, 3).attr({
fill: this.colors[commit.space], fill: this.colors[commit.space],
stroke: "none" stroke: 'none',
}); });
const avatar_box_x = this.offsetX + this.unitSpace * this.mspace + 10; const avatar_box_x = this.offsetX + this.unitSpace * this.mspace + 10;
...@@ -274,13 +286,15 @@ export default (function() { ...@@ -274,13 +286,15 @@ export default (function() {
r.rect(avatar_box_x, avatar_box_y, 20, 20).attr({ r.rect(avatar_box_x, avatar_box_y, 20, 20).attr({
stroke: this.colors[commit.space], stroke: this.colors[commit.space],
"stroke-width": 2 'stroke-width': 2,
}); });
r.image(commit.author.icon, avatar_box_x, avatar_box_y, 20, 20); 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({ return r
"text-anchor": "start", .text(this.offsetX + this.unitSpace * this.mspace + 35, y, commit.message.split('\n')[0])
font: "14px Monaco, monospace" .attr({
}); 'text-anchor': 'start',
font: '14px Monaco, monospace',
});
}; };
BranchGraph.prototype.drawLines = function(x, y, commit) { BranchGraph.prototype.drawLines = function(x, y, commit) {
...@@ -304,30 +318,32 @@ export default (function() { ...@@ -304,30 +318,32 @@ export default (function() {
// Build line shape // Build line shape
if (parent[1] === commit.space) { if (parent[1] === commit.space) {
offset = [0, 5]; 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) { } else if (parent[1] < commit.space) {
offset = [3, 3]; offset = [3, 3];
arrow = "l5,0,-2,4,-3,-4,4,2"; arrow = 'l5,0,-2,4,-3,-4,4,2';
} else { } else {
offset = [-3, 3]; 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 // Start point
route = ["M", x + offset[0], y + offset[1]]; route = ['M', x + offset[0], y + offset[1]];
// Add arrow if not first parent // Add arrow if not first parent
if (i > 0) { if (i > 0) {
route.push(arrow); route.push(arrow);
} }
// Circumvent if overlap // Circumvent if overlap
if (commit.space !== parentCommit.space || commit.space !== parent[1]) { 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 // End point
route.push("L", parentX1, parentY); route.push('L', parentX1, parentY);
results.push(r.path(route).attr({ results.push(
stroke: color, r.path(route).attr({
"stroke-width": 2 stroke: color,
})); 'stroke-width': 2,
}),
);
} }
return results; return results;
}; };
...@@ -337,10 +353,10 @@ export default (function() { ...@@ -337,10 +353,10 @@ export default (function() {
const { r } = this; const { r } = this;
const x = this.offsetX + this.unitSpace * (this.mspace - commit.space); const x = this.offsetX + this.unitSpace * (this.mspace - commit.space);
const y = this.offsetY + this.unitTime * commit.time; 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({ r.path(['M', x + 5, y, 'L', x + 15, y + 4, 'L', x + 15, y - 4, 'Z']).attr({
fill: "#000", fill: '#000',
"fill-opacity": .5, 'fill-opacity': 0.5,
stroke: "none" stroke: 'none',
}); });
// Displayed in the center // Displayed in the center
return this.element.scrollTop(y - this.graphHeight / 2); return this.element.scrollTop(y - this.graphHeight / 2);
......
...@@ -49,7 +49,7 @@ Raphael.prototype.textWrap = function testWrap(t, width) { ...@@ -49,7 +49,7 @@ Raphael.prototype.textWrap = function testWrap(t, width) {
const s = []; const s = [];
for (let j = 0, len = words.length; j < len; j += 1) { for (let j = 0, len = words.length; j < len; j += 1) {
const word = words[j]; const word = words[j];
if (x + (word.length * letterWidth) > width) { if (x + word.length * letterWidth > width) {
s.push('\n'); s.push('\n');
x = 0; x = 0;
} }
......
...@@ -30,24 +30,24 @@ export default class NewBranchForm { ...@@ -30,24 +30,24 @@ export default class NewBranchForm {
startsWith = { startsWith = {
pattern: /^(\/|\.)/g, pattern: /^(\/|\.)/g,
prefix: "can't start with", prefix: "can't start with",
conjunction: "or" conjunction: 'or',
}; };
endsWith = { endsWith = {
pattern: /(\/|\.|\.lock)$/g, pattern: /(\/|\.|\.lock)$/g,
prefix: "can't end in", prefix: "can't end in",
conjunction: "or" conjunction: 'or',
}; };
invalid = { invalid = {
pattern: /(\s|~|\^|:|\?|\*|\[|\\|\.\.|@\{|\/{2,}){1}/g, pattern: /(\s|~|\^|:|\?|\*|\[|\\|\.\.|@\{|\/{2,}){1}/g,
prefix: "can't contain", prefix: "can't contain",
conjunction: ", " conjunction: ', ',
}; };
single = { single = {
pattern: /^@+$/g, pattern: /^@+$/g,
prefix: "can't be", prefix: "can't be",
conjunction: "or" conjunction: 'or',
}; };
return this.restrictions = [startsWith, invalid, endsWith, single]; return (this.restrictions = [startsWith, invalid, endsWith, single]);
} }
validate() { validate() {
...@@ -73,7 +73,7 @@ export default class NewBranchForm { ...@@ -73,7 +73,7 @@ export default class NewBranchForm {
return "'" + value + "'"; return "'" + value + "'";
} }
}); });
return restriction.prefix + " " + (formatted.join(restriction.conjunction)); return restriction.prefix + ' ' + formatted.join(restriction.conjunction);
}; };
validator = (function(_this) { validator = (function(_this) {
return function(errors, restriction) { return function(errors, restriction) {
...@@ -88,7 +88,7 @@ export default class NewBranchForm { ...@@ -88,7 +88,7 @@ export default class NewBranchForm {
})(this); })(this);
errors = this.restrictions.reduce(validator, []); errors = this.restrictions.reduce(validator, []);
if (errors.length > 0) { if (errors.length > 0) {
errorMessage = $("<span/>").text(errors.join(', ')); errorMessage = $('<span/>').text(errors.join(', '));
return this.branchNameError.append(errorMessage); return this.branchNameError.append(errorMessage);
} }
} }
......
...@@ -6,9 +6,7 @@ export default class NewCommitForm { ...@@ -6,9 +6,7 @@ export default class NewCommitForm {
this.branchName = form.find('.js-branch-name'); this.branchName = form.find('.js-branch-name');
this.originalBranch = form.find('.js-original-branch'); this.originalBranch = form.find('.js-original-branch');
this.createMergeRequest = form.find('.js-create-merge-request'); this.createMergeRequest = form.find('.js-create-merge-request');
this.createMergeRequestContainer = form.find( this.createMergeRequestContainer = form.find('.js-create-merge-request-container');
'.js-create-merge-request-container',
);
this.branchName.keyup(this.renderDestination); this.branchName.keyup(this.renderDestination);
this.renderDestination(); this.renderDestination();
} }
......
...@@ -18,7 +18,9 @@ export default function notificationsDropdown() { ...@@ -18,7 +18,9 @@ export default function notificationsDropdown() {
$(document).on('ajax:success', '.notification-form', (e, data) => { $(document).on('ajax:success', '.notification-form', (e, data) => {
if (data.saved) { if (data.saved) {
$(e.currentTarget).closest('.js-notification-dropdown').replaceWith(data.html); $(e.currentTarget)
.closest('.js-notification-dropdown')
.replaceWith(data.html);
} else { } else {
Flash('Failed to save new settings', 'alert'); Flash('Failed to save new settings', 'alert');
} }
......
...@@ -22,7 +22,8 @@ export default class NotificationsForm { ...@@ -22,7 +22,8 @@ export default class NotificationsForm {
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
showCheckboxLoadingSpinner($parent) { showCheckboxLoadingSpinner($parent) {
$parent.addClass('is-loading') $parent
.addClass('is-loading')
.find('.custom-notification-event-loading') .find('.custom-notification-event-loading')
.removeClass('fa-check') .removeClass('fa-check')
.addClass('fa-spin fa-spinner') .addClass('fa-spin fa-spinner')
...@@ -38,9 +39,12 @@ export default class NotificationsForm { ...@@ -38,9 +39,12 @@ export default class NotificationsForm {
.then(({ data }) => { .then(({ data }) => {
$checkbox.enable(); $checkbox.enable();
if (data.saved) { 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(() => { setTimeout(() => {
$parent.removeClass('is-loading') $parent
.removeClass('is-loading')
.find('.custom-notification-event-loading') .find('.custom-notification-event-loading')
.toggleClass('fa-spin fa-spinner fa-check is-done'); .toggleClass('fa-spin fa-spinner fa-check is-done');
}, 2000); }, 2000);
......
...@@ -24,22 +24,25 @@ export default { ...@@ -24,22 +24,25 @@ export default {
getOld() { getOld() {
this.loading.show(); this.loading.show();
axios.get(this.url, { axios
params: { .get(this.url, {
limit: this.limit, params: {
offset: this.offset, limit: this.limit,
}, offset: this.offset,
}).then(({ data }) => { },
this.append(data.count, this.prepareData(data.html)); })
this.callback(); .then(({ data }) => {
this.append(data.count, this.prepareData(data.html));
this.callback();
// keep loading until we've filled the viewport height // keep loading until we've filled the viewport height
if (!this.disable && !this.isScrollable()) { if (!this.disable && !this.isScrollable()) {
this.getOld(); this.getOld();
} else { } else {
this.loading.hide(); this.loading.hide();
} }
}).catch(() => this.loading.hide()); })
.catch(() => this.loading.hide());
}, },
append(count, html) { append(count, html) {
......
<script> <script>
import pdfjsLib from 'vendor/pdf'; import pdfjsLib from 'vendor/pdf';
import workerSrc from 'vendor/pdf.worker.min'; import workerSrc from 'vendor/pdf.worker.min';
import page from './page/index.vue'; import page from './page/index.vue';
export default { export default {
components: { page }, components: { page },
props: { props: {
pdf: { pdf: {
type: [String, Uint8Array], type: [String, Uint8Array],
required: true, required: true,
},
}, },
data() { },
return { data() {
loading: false, return {
pages: [], loading: false,
}; pages: [],
};
},
computed: {
document() {
return typeof this.pdf === 'string' ? this.pdf : { data: this.pdf };
}, },
computed: { hasPDF() {
document() { return this.pdf && this.pdf.length > 0;
return typeof this.pdf === 'string' ? this.pdf : { data: this.pdf };
},
hasPDF() {
return this.pdf && this.pdf.length > 0;
},
}, },
watch: { pdf: 'load' }, },
mounted() { watch: { pdf: 'load' },
pdfjsLib.PDFJS.workerSrc = workerSrc; mounted() {
if (this.hasPDF) this.load(); 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: { renderPages(pdf) {
load() { const pagePromises = [];
this.pages = []; this.loading = true;
return pdfjsLib.getDocument(this.document) for (let num = 1; num <= pdf.numPages; num += 1) {
.then(this.renderPages) pagePromises.push(pdf.getPage(num).then(p => this.pages.push(p)));
.then(() => this.$emit('pdflabload')) }
.catch(error => this.$emit('pdflaberror', error)) return Promise.all(pagePromises);
.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);
},
}, },
}; },
};
</script> </script>
<template> <template>
...@@ -69,9 +70,9 @@ ...@@ -69,9 +70,9 @@
</template> </template>
<style> <style>
.pdf-viewer { .pdf-viewer {
background: url('./assets/img/bg.gif'); background: url('./assets/img/bg.gif');
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: column nowrap;
} }
</style> </style>
<script> <script>
export default { export default {
props: { props: {
page: { page: {
type: Object, type: Object,
required: true, required: true,
},
number: {
type: Number,
required: true,
},
}, },
data() { number: {
return { type: Number,
scale: 4, required: true,
rendering: false, },
}; },
data() {
return {
scale: 4,
rendering: false,
};
},
computed: {
viewport() {
return this.page.getViewport(this.scale);
}, },
computed: { context() {
viewport() { return this.$refs.canvas.getContext('2d');
return this.page.getViewport(this.scale);
},
context() {
return this.$refs.canvas.getContext('2d');
},
renderContext() {
return {
canvasContext: this.context,
viewport: this.viewport,
};
},
}, },
mounted() { renderContext() {
this.$refs.canvas.height = this.viewport.height; return {
this.$refs.canvas.width = this.viewport.width; canvasContext: this.context,
this.rendering = true; viewport: this.viewport,
this.page.render(this.renderContext) };
.then(() => { this.rendering = false; })
.catch(error => this.$emit('pdflaberror', error));
}, },
}; },
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> </script>
<template> <template>
...@@ -51,20 +54,20 @@ ...@@ -51,20 +54,20 @@
</template> </template>
<style> <style>
.pdf-page { .pdf-page {
margin: 8px auto 0 auto; margin: 8px auto 0 auto;
border-top: 1px #ddd solid; border-top: 1px #ddd solid;
border-bottom: 1px #ddd solid; border-bottom: 1px #ddd solid;
width: 100%; width: 100%;
} }
.pdf-page:first-child { .pdf-page:first-child {
margin-top: 0px; margin-top: 0px;
border-top: 0px; border-top: 0px;
} }
.pdf-page:last-child { .pdf-page:last-child {
margin-bottom: 0px; margin-bottom: 0px;
border-bottom: 0px; border-bottom: 0px;
} }
</style> </style>
...@@ -64,10 +64,7 @@ export default { ...@@ -64,10 +64,7 @@ export default {
return []; return [];
} }
const { details } = this.pipeline; const { details } = this.pipeline;
return [ return [...(details.manual_actions || []), ...(details.scheduled_actions || [])];
...(details.manual_actions || []),
...(details.scheduled_actions || []),
];
}, },
/** /**
* If provided, returns the commit tag. * If provided, returns the commit tag.
......
...@@ -10,14 +10,14 @@ import { __ } from '~/locale'; ...@@ -10,14 +10,14 @@ import { __ } from '~/locale';
const highlighter = function(element, text, matches) { const highlighter = function(element, text, matches) {
var highlightText, j, lastIndex, len, matchIndex, matchedChars, unmatched; var highlightText, j, lastIndex, len, matchIndex, matchedChars, unmatched;
lastIndex = 0; lastIndex = 0;
highlightText = ""; highlightText = '';
matchedChars = []; matchedChars = [];
for (j = 0, len = matches.length; j < len; j += 1) { for (j = 0, len = matches.length; j < len; j += 1) {
matchIndex = matches[j]; matchIndex = matches[j];
unmatched = text.substring(lastIndex, matchIndex); unmatched = text.substring(lastIndex, matchIndex);
if (unmatched) { if (unmatched) {
if (matchedChars.length) { if (matchedChars.length) {
element.append(matchedChars.join("").bold()); element.append(matchedChars.join('').bold());
} }
matchedChars = []; matchedChars = [];
element.append(document.createTextNode(unmatched)); element.append(document.createTextNode(unmatched));
...@@ -26,7 +26,7 @@ const highlighter = function(element, text, matches) { ...@@ -26,7 +26,7 @@ const highlighter = function(element, text, matches) {
lastIndex = matchIndex + 1; lastIndex = matchIndex + 1;
} }
if (matchedChars.length) { if (matchedChars.length) {
element.append(matchedChars.join("").bold()); element.append(matchedChars.join('').bold());
} }
return element.append(document.createTextNode(text.substring(lastIndex))); return element.append(document.createTextNode(text.substring(lastIndex)));
}; };
...@@ -40,7 +40,7 @@ export default class ProjectFindFile { ...@@ -40,7 +40,7 @@ export default class ProjectFindFile {
this.selectRowDown = this.selectRowDown.bind(this); this.selectRowDown = this.selectRowDown.bind(this);
this.selectRowUp = this.selectRowUp.bind(this); this.selectRowUp = this.selectRowUp.bind(this);
this.filePaths = {}; this.filePaths = {};
this.inputElement = this.element.find(".file-finder-input"); this.inputElement = this.element.find('.file-finder-input');
// init event // init event
this.initEvent(); this.initEvent();
// focus text input box // focus text input box
...@@ -50,38 +50,51 @@ export default class ProjectFindFile { ...@@ -50,38 +50,51 @@ export default class ProjectFindFile {
} }
initEvent() { initEvent() {
this.inputElement.off("keyup"); this.inputElement.off('keyup');
this.inputElement.on("keyup", (function(_this) { this.inputElement.on(
return function(event) { 'keyup',
var oldValue, ref, target, value; (function(_this) {
target = $(event.target); return function(event) {
value = target.val(); var oldValue, ref, target, value;
oldValue = (ref = target.data("oldValue")) != null ? ref : ""; target = $(event.target);
if (value !== oldValue) { value = target.val();
target.data("oldValue", value); oldValue = (ref = target.data('oldValue')) != null ? ref : '';
_this.findFile(); if (value !== oldValue) {
return _this.element.find("tr.tree-item").eq(0).addClass("selected").focus(); target.data('oldValue', value);
} _this.findFile();
}; return _this.element
})(this)); .find('tr.tree-item')
.eq(0)
.addClass('selected')
.focus();
}
};
})(this),
);
} }
findFile() { findFile() {
var result, searchText; var result, searchText;
searchText = this.inputElement.val(); searchText = this.inputElement.val();
result = searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths; result =
searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths;
return this.renderList(result, searchText); return this.renderList(result, searchText);
// find file // find file
} }
// files pathes load // files pathes load
load(url) { load(url) {
axios.get(url) axios
.get(url)
.then(({ data }) => { .then(({ data }) => {
this.element.find('.loading').hide(); this.element.find('.loading').hide();
this.filePaths = data; this.filePaths = data;
this.findFile(); this.findFile();
this.element.find('.files-slider tr.tree-item').eq(0).addClass('selected').focus(); this.element
.find('.files-slider tr.tree-item')
.eq(0)
.addClass('selected')
.focus();
}) })
.catch(() => flash(__('An error occurred while loading filenames'))); .catch(() => flash(__('An error occurred while loading filenames')));
} }
...@@ -89,7 +102,7 @@ export default class ProjectFindFile { ...@@ -89,7 +102,7 @@ export default class ProjectFindFile {
// render result // render result
renderList(filePaths, searchText) { renderList(filePaths, searchText) {
var blobItemUrl, filePath, html, i, len, matches, results; var blobItemUrl, filePath, html, i, len, matches, results;
this.element.find(".tree-table > tbody").empty(); this.element.find('.tree-table > tbody').empty();
results = []; results = [];
for (i = 0, len = filePaths.length; i < len; i += 1) { for (i = 0, len = filePaths.length; i < len; i += 1) {
...@@ -100,9 +113,9 @@ export default class ProjectFindFile { ...@@ -100,9 +113,9 @@ export default class ProjectFindFile {
if (searchText) { if (searchText) {
matches = fuzzaldrinPlus.match(filePath, searchText); matches = fuzzaldrinPlus.match(filePath, searchText);
} }
blobItemUrl = this.options.blobUrlTemplate + "/" + filePath; blobItemUrl = this.options.blobUrlTemplate + '/' + filePath;
html = ProjectFindFile.makeHtml(filePath, matches, blobItemUrl); html = ProjectFindFile.makeHtml(filePath, matches, blobItemUrl);
results.push(this.element.find(".tree-table > tbody").append(html)); results.push(this.element.find('.tree-table > tbody').append(html));
} }
return results; return results;
} }
...@@ -110,52 +123,56 @@ export default class ProjectFindFile { ...@@ -110,52 +123,56 @@ export default class ProjectFindFile {
// make tbody row html // make tbody row html
static makeHtml(filePath, matches, blobItemUrl) { static makeHtml(filePath, matches, blobItemUrl) {
var $tr; var $tr;
$tr = $("<tr class='tree-item'><td class='tree-item-file-name link-container'><a><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'></span></a></td></tr>"); $tr = $(
"<tr class='tree-item'><td class='tree-item-file-name link-container'><a><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'></span></a></td></tr>",
);
if (matches) { if (matches) {
$tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl)); $tr
.find('a')
.replaceWith(highlighter($tr.find('a'), filePath, matches).attr('href', blobItemUrl));
} else { } else {
$tr.find("a").attr("href", blobItemUrl); $tr.find('a').attr('href', blobItemUrl);
$tr.find(".str-truncated").text(filePath); $tr.find('.str-truncated').text(filePath);
} }
return $tr; return $tr;
} }
selectRow(type) { selectRow(type) {
var next, rows, selectedRow; var next, rows, selectedRow;
rows = this.element.find(".files-slider tr.tree-item"); rows = this.element.find('.files-slider tr.tree-item');
selectedRow = this.element.find(".files-slider tr.tree-item.selected"); selectedRow = this.element.find('.files-slider tr.tree-item.selected');
if (rows && rows.length > 0) { if (rows && rows.length > 0) {
if (selectedRow && selectedRow.length > 0) { if (selectedRow && selectedRow.length > 0) {
if (type === "UP") { if (type === 'UP') {
next = selectedRow.prev(); next = selectedRow.prev();
} else if (type === "DOWN") { } else if (type === 'DOWN') {
next = selectedRow.next(); next = selectedRow.next();
} }
if (next.length > 0) { if (next.length > 0) {
selectedRow.removeClass("selected"); selectedRow.removeClass('selected');
selectedRow = next; selectedRow = next;
} }
} else { } else {
selectedRow = rows.eq(0); selectedRow = rows.eq(0);
} }
return selectedRow.addClass("selected").focus(); return selectedRow.addClass('selected').focus();
} }
} }
selectRowUp() { selectRowUp() {
return this.selectRow("UP"); return this.selectRow('UP');
} }
selectRowDown() { selectRowDown() {
return this.selectRow("DOWN"); return this.selectRow('DOWN');
} }
goToTree() { goToTree() {
return window.location.href = this.options.treeUrl; return (window.location.href = this.options.treeUrl);
} }
goToBlob() { goToBlob() {
var $link = this.element.find(".tree-item.selected .tree-item-file-name a"); var $link = this.element.find('.tree-item.selected .tree-item-file-name a');
if ($link.length) { if ($link.length) {
$link.get(0).click(); $link.get(0).click();
......
...@@ -5,4 +5,3 @@ export default function projectImport() { ...@@ -5,4 +5,3 @@ export default function projectImport() {
visitUrl(window.location.href); visitUrl(window.location.href);
}, 5000); }, 5000);
} }
...@@ -31,32 +31,35 @@ export default class ProjectLabelSubscription { ...@@ -31,32 +31,35 @@ export default class ProjectLabelSubscription {
$btn.addClass('disabled'); $btn.addClass('disabled');
axios.post(url).then(() => { axios
let newStatus; .post(url)
let newAction; .then(() => {
let newStatus;
let newAction;
if (oldStatus === 'unsubscribed') { if (oldStatus === 'unsubscribed') {
[newStatus, newAction] = ['subscribed', 'Unsubscribe']; [newStatus, newAction] = ['subscribed', 'Unsubscribe'];
} else { } else {
[newStatus, newAction] = ['unsubscribed', 'Subscribe']; [newStatus, newAction] = ['unsubscribed', 'Subscribe'];
} }
$btn.removeClass('disabled'); $btn.removeClass('disabled');
this.$buttons.attr('data-status', newStatus); this.$buttons.attr('data-status', newStatus);
this.$buttons.find('> span').text(newAction); this.$buttons.find('> span').text(newAction);
this.$buttons.map((i, button) => { this.$buttons.map((i, button) => {
const $button = $(button); const $button = $(button);
const originalTitle = $button.attr('data-original-title'); const originalTitle = $button.attr('data-original-title');
if (originalTitle) { if (originalTitle) {
ProjectLabelSubscription.setNewTitle($button, originalTitle, newStatus, newAction); ProjectLabelSubscription.setNewTitle($button, originalTitle, newStatus, newAction);
} }
return button; return button;
}); });
}).catch(() => flash(__('There was an error subscribing to this label.'))); })
.catch(() => flash(__('There was an error subscribing to this label.')));
} }
static setNewTitle($button, originalTitle, newStatus) { static setNewTitle($button, originalTitle, newStatus) {
......
...@@ -16,28 +16,28 @@ export default function projectSelect() { ...@@ -16,28 +16,28 @@ export default function projectSelect() {
this.withMergeRequestsEnabled = $(select).data('withMergeRequestsEnabled'); this.withMergeRequestsEnabled = $(select).data('withMergeRequestsEnabled');
this.allowClear = $(select).data('allowClear') || false; this.allowClear = $(select).data('allowClear') || false;
placeholder = "Search for project"; placeholder = 'Search for project';
if (this.includeGroups) { if (this.includeGroups) {
placeholder += " or group"; placeholder += ' or group';
} }
$(select).select2({ $(select).select2({
placeholder: placeholder, placeholder: placeholder,
minimumInputLength: 0, minimumInputLength: 0,
query: (function (_this) { query: (function(_this) {
return function (query) { return function(query) {
var finalCallback, projectsCallback; var finalCallback, projectsCallback;
finalCallback = function (projects) { finalCallback = function(projects) {
var data; var data;
data = { data = {
results: projects results: projects,
}; };
return query.callback(data); return query.callback(data);
}; };
if (_this.includeGroups) { if (_this.includeGroups) {
projectsCallback = function (projects) { projectsCallback = function(projects) {
var groupsCallback; var groupsCallback;
groupsCallback = function (groups) { groupsCallback = function(groups) {
var data; var data;
data = groups.concat(projects); data = groups.concat(projects);
return finalCallback(data); return finalCallback(data);
...@@ -48,17 +48,26 @@ export default function projectSelect() { ...@@ -48,17 +48,26 @@ export default function projectSelect() {
projectsCallback = finalCallback; projectsCallback = finalCallback;
} }
if (_this.groupId) { if (_this.groupId) {
return Api.groupProjects(_this.groupId, query.term, { return Api.groupProjects(
with_issues_enabled: _this.withIssuesEnabled, _this.groupId,
with_merge_requests_enabled: _this.withMergeRequestsEnabled, query.term,
}, projectsCallback); {
with_issues_enabled: _this.withIssuesEnabled,
with_merge_requests_enabled: _this.withMergeRequestsEnabled,
},
projectsCallback,
);
} else { } else {
return Api.projects(query.term, { return Api.projects(
order_by: _this.orderBy, query.term,
with_issues_enabled: _this.withIssuesEnabled, {
with_merge_requests_enabled: _this.withMergeRequestsEnabled, order_by: _this.orderBy,
membership: !_this.allProjects, with_issues_enabled: _this.withIssuesEnabled,
}, projectsCallback); with_merge_requests_enabled: _this.withMergeRequestsEnabled,
membership: !_this.allProjects,
},
projectsCallback,
);
} }
}; };
})(this), })(this),
...@@ -69,7 +78,7 @@ export default function projectSelect() { ...@@ -69,7 +78,7 @@ export default function projectSelect() {
url: project.web_url, url: project.web_url,
}); });
}, },
text: function (project) { text: function(project) {
return project.name_with_namespace || project.name; return project.name_with_namespace || project.name;
}, },
...@@ -79,7 +88,7 @@ export default function projectSelect() { ...@@ -79,7 +88,7 @@ export default function projectSelect() {
allowClear: this.allowClear, allowClear: this.allowClear,
dropdownCssClass: "ajax-project-dropdown" dropdownCssClass: 'ajax-project-dropdown',
}); });
if (simpleFilter) return select; if (simpleFilter) return select;
return new ProjectSelectComboButton(select); return new ProjectSelectComboButton(select);
......
...@@ -14,10 +14,11 @@ export default class ProjectSelectComboButton { ...@@ -14,10 +14,11 @@ export default class ProjectSelectComboButton {
} }
bindEvents() { bindEvents() {
this.projectSelectInput.siblings('.new-project-item-select-button') this.projectSelectInput
.siblings('.new-project-item-select-button')
.on('click', e => this.openDropdown(e)); .on('click', e => this.openDropdown(e));
this.newItemBtn.on('click', (e) => { this.newItemBtn.on('click', e => {
if (!this.getProjectFromLocalStorage()) { if (!this.getProjectFromLocalStorage()) {
e.preventDefault(); e.preventDefault();
this.openDropdown(e); this.openDropdown(e);
...@@ -31,14 +32,21 @@ export default class ProjectSelectComboButton { ...@@ -31,14 +32,21 @@ export default class ProjectSelectComboButton {
const localStorageIsSafe = AccessorUtilities.isLocalStorageAccessSafe(); const localStorageIsSafe = AccessorUtilities.isLocalStorageAccessSafe();
if (localStorageIsSafe) { if (localStorageIsSafe) {
this.localStorageKey = ['group', this.groupId, this.formattedText.localStorageItemType, 'recent-project'].join('-'); this.localStorageKey = [
'group',
this.groupId,
this.formattedText.localStorageItemType,
'recent-project',
].join('-');
this.setBtnTextFromLocalStorage(); this.setBtnTextFromLocalStorage();
} }
} }
// eslint-disable-next-line class-methods-use-this // eslint-disable-next-line class-methods-use-this
openDropdown(event) { openDropdown(event) {
$(event.currentTarget).siblings('.project-item-select').select2('open'); $(event.currentTarget)
.siblings('.project-item-select')
.select2('open');
} }
selectProject() { selectProject() {
...@@ -86,8 +94,14 @@ export default class ProjectSelectComboButton { ...@@ -86,8 +94,14 @@ export default class ProjectSelectComboButton {
const defaultTextPrefix = this.resourceLabel; const defaultTextPrefix = this.resourceLabel;
// the trailing slice call depluralizes each of these strings (e.g. new-issues -> new-issue) // the trailing slice call depluralizes each of these strings (e.g. new-issues -> new-issue)
const localStorageItemType = `new-${this.resourceType.split('_').join('-').slice(0, -1)}`; const localStorageItemType = `new-${this.resourceType
const presetTextSuffix = this.resourceType.split('_').join(' ').slice(0, -1); .split('_')
.join('-')
.slice(0, -1)}`;
const presetTextSuffix = this.resourceType
.split('_')
.join(' ')
.slice(0, -1);
return { return {
localStorageItemType, // new-issue / new-merge-request localStorageItemType, // new-issue / new-merge-request
...@@ -96,4 +110,3 @@ export default class ProjectSelectComboButton { ...@@ -96,4 +110,3 @@ export default class ProjectSelectComboButton {
}; };
} }
} }
...@@ -7,7 +7,7 @@ function setVisibilityOptions(namespaceSelector) { ...@@ -7,7 +7,7 @@ function setVisibilityOptions(namespaceSelector) {
const selectedNamespace = namespaceSelector.options[namespaceSelector.selectedIndex]; const selectedNamespace = namespaceSelector.options[namespaceSelector.selectedIndex];
const { name, visibility, visibilityLevel, showPath, editPath } = selectedNamespace.dataset; const { name, visibility, visibilityLevel, showPath, editPath } = selectedNamespace.dataset;
document.querySelectorAll('.visibility-level-setting .form-check').forEach((option) => { document.querySelectorAll('.visibility-level-setting .form-check').forEach(option => {
const optionInput = option.querySelector('input[type=radio]'); const optionInput = option.querySelector('input[type=radio]');
const optionValue = optionInput ? optionInput.value : 0; const optionValue = optionInput ? optionInput.value : 0;
const optionTitle = option.querySelector('.option-title'); const optionTitle = option.querySelector('.option-title');
...@@ -20,8 +20,7 @@ function setVisibilityOptions(namespaceSelector) { ...@@ -20,8 +20,7 @@ function setVisibilityOptions(namespaceSelector) {
optionInput.disabled = true; optionInput.disabled = true;
const reason = option.querySelector('.option-disabled-reason'); const reason = option.querySelector('.option-disabled-reason');
if (reason) { if (reason) {
reason.innerHTML = reason.innerHTML = `This project cannot be ${optionName} because the visibility of
`This project cannot be ${optionName} because the visibility of
<a href="${showPath}">${name}</a> is ${visibility}. To make this project <a href="${showPath}">${name}</a> is ${visibility}. To make this project
${optionName}, you must first <a href="${editPath}">change the visibility</a> ${optionName}, you must first <a href="${editPath}">change the visibility</a>
of the parent group.`; of the parent group.`;
......
...@@ -65,10 +65,14 @@ export default class PrometheusMetrics { ...@@ -65,10 +65,14 @@ export default class PrometheusMetrics {
let totalMissingEnvVarMetrics = 0; let totalMissingEnvVarMetrics = 0;
let totalExporters = 0; let totalExporters = 0;
metrics.forEach((metric) => { metrics.forEach(metric => {
if (metric.active_metrics > 0) { if (metric.active_metrics > 0) {
totalExporters += 1; totalExporters += 1;
this.$monitoredMetricsList.append(`<li>${_.escape(metric.group)}<span class="badge">${_.escape(metric.active_metrics)}</span></li>`); this.$monitoredMetricsList.append(
`<li>${_.escape(metric.group)}<span class="badge">${_.escape(
metric.active_metrics,
)}</span></li>`,
);
totalMonitoredMetrics += metric.active_metrics; totalMonitoredMetrics += metric.active_metrics;
if (metric.metrics_missing_requirements > 0) { if (metric.metrics_missing_requirements > 0) {
this.$missingEnvVarMetricsList.append(`<li>${_.escape(metric.group)}</li>`); this.$missingEnvVarMetricsList.append(`<li>${_.escape(metric.group)}</li>`);
...@@ -78,17 +82,26 @@ export default class PrometheusMetrics { ...@@ -78,17 +82,26 @@ export default class PrometheusMetrics {
}); });
if (totalMonitoredMetrics === 0) { if (totalMonitoredMetrics === 0) {
const emptyCommonMetricsText = sprintf(s__('PrometheusService|<p class="text-tertiary">No <a href="%{docsUrl}">common metrics</a> were found</p>'), { const emptyCommonMetricsText = sprintf(
docsUrl: this.helpMetricsPath, s__(
}, false); 'PrometheusService|<p class="text-tertiary">No <a href="%{docsUrl}">common metrics</a> were found</p>',
),
{
docsUrl: this.helpMetricsPath,
},
false,
);
this.$monitoredMetricsEmpty.empty(); this.$monitoredMetricsEmpty.empty();
this.$monitoredMetricsEmpty.append(emptyCommonMetricsText); this.$monitoredMetricsEmpty.append(emptyCommonMetricsText);
this.showMonitoringMetricsPanelState(PANEL_STATE.EMPTY); this.showMonitoringMetricsPanelState(PANEL_STATE.EMPTY);
} else { } else {
const metricsCountText = sprintf(s__('PrometheusService|%{exporters} with %{metrics} were found'), { const metricsCountText = sprintf(
exporters: n__('%d exporter', '%d exporters', totalExporters), s__('PrometheusService|%{exporters} with %{metrics} were found'),
metrics: n__('%d metric', '%d metrics', totalMonitoredMetrics), {
}); exporters: n__('%d exporter', '%d exporters', totalExporters),
metrics: n__('%d metric', '%d metrics', totalMonitoredMetrics),
},
);
this.$monitoredMetricsCount.text(metricsCountText); this.$monitoredMetricsCount.text(metricsCountText);
this.showMonitoringMetricsPanelState(PANEL_STATE.LIST); this.showMonitoringMetricsPanelState(PANEL_STATE.LIST);
...@@ -102,7 +115,8 @@ export default class PrometheusMetrics { ...@@ -102,7 +115,8 @@ export default class PrometheusMetrics {
loadActiveMetrics() { loadActiveMetrics() {
this.showMonitoringMetricsPanelState(PANEL_STATE.LOADING); this.showMonitoringMetricsPanelState(PANEL_STATE.LOADING);
backOff((next, stop) => { backOff((next, stop) => {
axios.get(this.activeMetricsEndpoint) axios
.get(this.activeMetricsEndpoint)
.then(({ data }) => { .then(({ data }) => {
if (data && data.success) { if (data && data.success) {
stop(data); stop(data);
...@@ -117,7 +131,7 @@ export default class PrometheusMetrics { ...@@ -117,7 +131,7 @@ export default class PrometheusMetrics {
}) })
.catch(stop); .catch(stop);
}) })
.then((res) => { .then(res => {
if (res && res.data && res.data.length) { if (res && res.data && res.data.length) {
this.populateActiveMetrics(res.data); this.populateActiveMetrics(res.data);
} else { } else {
......
...@@ -9,7 +9,7 @@ const IGNORE_ERRORS = [ ...@@ -9,7 +9,7 @@ const IGNORE_ERRORS = [
'canvas.contentDocument', 'canvas.contentDocument',
'MyApp_RemoveAllHighlights', 'MyApp_RemoveAllHighlights',
'http://tt.epicplay.com', 'http://tt.epicplay.com',
'Can\'t find variable: ZiteReader', "Can't find variable: ZiteReader",
'jigsaw is not defined', 'jigsaw is not defined',
'ComboSearch is not defined', 'ComboSearch is not defined',
'http://loading.retry.widdit.com/', 'http://loading.retry.widdit.com/',
......
...@@ -2,7 +2,8 @@ import $ from 'jquery'; ...@@ -2,7 +2,8 @@ import $ from 'jquery';
class RefSelectDropdown { class RefSelectDropdown {
constructor($dropdownButton, availableRefs) { constructor($dropdownButton, availableRefs) {
const availableRefsValue = availableRefs || JSON.parse(document.getElementById('availableRefs').innerHTML); const availableRefsValue =
availableRefs || JSON.parse(document.getElementById('availableRefs').innerHTML);
$dropdownButton.glDropdown({ $dropdownButton.glDropdown({
data: availableRefsValue, data: availableRefsValue,
filterable: true, filterable: true,
...@@ -29,7 +30,7 @@ class RefSelectDropdown { ...@@ -29,7 +30,7 @@ class RefSelectDropdown {
const $fieldInput = $(`input[name="${$dropdownButton.data('fieldName')}"]`, $dropdownContainer); const $fieldInput = $(`input[name="${$dropdownButton.data('fieldName')}"]`, $dropdownContainer);
const $filterInput = $('input[type="search"]', $dropdownContainer); const $filterInput = $('input[type="search"]', $dropdownContainer);
$filterInput.on('keyup', (e) => { $filterInput.on('keyup', e => {
const keyCode = e.keyCode || e.which; const keyCode = e.keyCode || e.which;
if (keyCode !== 13) return; if (keyCode !== 13) return;
......
...@@ -3,10 +3,14 @@ import { __ } from './locale'; ...@@ -3,10 +3,14 @@ import { __ } from './locale';
function expandSection($section) { function expandSection($section) {
$section.find('.js-settings-toggle:not(.js-settings-toggle-trigger-only)').text(__('Collapse')); $section.find('.js-settings-toggle:not(.js-settings-toggle-trigger-only)').text(__('Collapse'));
$section.find('.settings-content').off('scroll.expandSection').scrollTop(0); $section
.find('.settings-content')
.off('scroll.expandSection')
.scrollTop(0);
$section.addClass('expanded'); $section.addClass('expanded');
if (!$section.hasClass('no-animate')) { if (!$section.hasClass('no-animate')) {
$section.addClass('animating') $section
.addClass('animating')
.one('animationend.animateSection', () => $section.removeClass('animating')); .one('animationend.animateSection', () => $section.removeClass('animating'));
} }
} }
...@@ -16,7 +20,8 @@ function closeSection($section) { ...@@ -16,7 +20,8 @@ function closeSection($section) {
$section.find('.settings-content').on('scroll.expandSection', () => expandSection($section)); $section.find('.settings-content').on('scroll.expandSection', () => expandSection($section));
$section.removeClass('expanded'); $section.removeClass('expanded');
if (!$section.hasClass('no-animate')) { if (!$section.hasClass('no-animate')) {
$section.addClass('animating') $section
.addClass('animating')
.one('animationend.animateSection', () => $section.removeClass('animating')); .one('animationend.animateSection', () => $section.removeClass('animating'));
} }
} }
......
...@@ -10,8 +10,10 @@ import syntaxHighlight from './syntax_highlight'; ...@@ -10,8 +10,10 @@ import syntaxHighlight from './syntax_highlight';
const WRAPPER = '<div class="diff-content"></div>'; const WRAPPER = '<div class="diff-content"></div>';
const LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>'; const LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
const ERROR_HTML = '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>'; const ERROR_HTML =
const COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <button class="click-to-expand btn btn-link">Click to expand it.</button></div>'; '<div class="nothing-here-block"><i class="fa fa-warning"></i> Could not load diff</div>';
const COLLAPSED_HTML =
'<div class="nothing-here-block diff-collapsed">This diff is collapsed. <button class="click-to-expand btn btn-link">Click to expand it.</button></div>';
export default class SingleFileDiff { export default class SingleFileDiff {
constructor(file) { constructor(file) {
...@@ -23,23 +25,36 @@ export default class SingleFileDiff { ...@@ -23,23 +25,36 @@ export default class SingleFileDiff {
this.isOpen = !this.diffForPath; this.isOpen = !this.diffForPath;
if (this.diffForPath) { if (this.diffForPath) {
this.collapsedContent = this.content; this.collapsedContent = this.content;
this.loadingContent = $(WRAPPER).addClass('loading').html(LOADING_HTML).hide(); this.loadingContent = $(WRAPPER)
.addClass('loading')
.html(LOADING_HTML)
.hide();
this.content = null; this.content = null;
this.collapsedContent.after(this.loadingContent); this.collapsedContent.after(this.loadingContent);
this.$toggleIcon.addClass('fa-caret-right'); this.$toggleIcon.addClass('fa-caret-right');
} else { } else {
this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide(); this.collapsedContent = $(WRAPPER)
.html(COLLAPSED_HTML)
.hide();
this.content.after(this.collapsedContent); this.content.after(this.collapsedContent);
this.$toggleIcon.addClass('fa-caret-down'); this.$toggleIcon.addClass('fa-caret-down');
} }
$('.js-file-title, .click-to-expand', this.file).on('click', (function (e) { $('.js-file-title, .click-to-expand', this.file).on(
this.toggleDiff($(e.target)); 'click',
}).bind(this)); function(e) {
this.toggleDiff($(e.target));
}.bind(this),
);
} }
toggleDiff($target, cb) { toggleDiff($target, cb) {
if (!$target.hasClass('js-file-title') && !$target.hasClass('click-to-expand') && !$target.hasClass('diff-toggle-caret')) return; if (
!$target.hasClass('js-file-title') &&
!$target.hasClass('click-to-expand') &&
!$target.hasClass('diff-toggle-caret')
)
return;
this.isOpen = !this.isOpen; this.isOpen = !this.isOpen;
if (!this.isOpen && !this.hasError) { if (!this.isOpen && !this.hasError) {
this.content.hide(); this.content.hide();
...@@ -65,7 +80,8 @@ export default class SingleFileDiff { ...@@ -65,7 +80,8 @@ export default class SingleFileDiff {
this.collapsedContent.hide(); this.collapsedContent.hide();
this.loadingContent.show(); this.loadingContent.show();
axios.get(this.diffForPath) axios
.get(this.diffForPath)
.then(({ data }) => { .then(({ data }) => {
this.loadingContent.hide(); this.loadingContent.hide();
if (data.html) { if (data.html) {
......
...@@ -93,7 +93,9 @@ export default class SmartInterval { ...@@ -93,7 +93,9 @@ export default class SmartInterval {
destroy() { destroy() {
this.cancel(); this.cancel();
document.removeEventListener('visibilitychange', this.handleVisibilityChange); document.removeEventListener('visibilitychange', this.handleVisibilityChange);
$(document).off('visibilitychange').off('beforeunload'); $(document)
.off('visibilitychange')
.off('beforeunload');
} }
/* private */ /* private */
...@@ -111,11 +113,12 @@ export default class SmartInterval { ...@@ -111,11 +113,12 @@ export default class SmartInterval {
triggerCallback() { triggerCallback() {
this.isLoading = true; this.isLoading = true;
this.cfg.callback() this.cfg
.callback()
.then(() => { .then(() => {
this.isLoading = false; this.isLoading = false;
}) })
.catch((err) => { .catch(err => {
this.isLoading = false; this.isLoading = false;
throw err; throw err;
}); });
...@@ -134,9 +137,9 @@ export default class SmartInterval { ...@@ -134,9 +137,9 @@ export default class SmartInterval {
handleVisibilityChange(e) { handleVisibilityChange(e) {
this.state.pageVisibility = e.target.visibilityState; this.state.pageVisibility = e.target.visibilityState;
const intervalAction = this.isPageVisible() ? const intervalAction = this.isPageVisible()
this.onVisibilityVisible : ? this.onVisibilityVisible
this.onVisibilityHidden; : this.onVisibilityHidden;
intervalAction.apply(this); intervalAction.apply(this);
} }
...@@ -162,7 +165,9 @@ export default class SmartInterval { ...@@ -162,7 +165,9 @@ export default class SmartInterval {
this.setCurrentInterval(nextInterval); this.setCurrentInterval(nextInterval);
} }
isPageVisible() { return this.state.pageVisibility === 'visible'; } isPageVisible() {
return this.state.pageVisibility === 'visible';
}
stopTimer() { stopTimer() {
const { state } = this; const { state } = this;
...@@ -170,4 +175,3 @@ export default class SmartInterval { ...@@ -170,4 +175,3 @@ export default class SmartInterval {
state.intervalId = window.clearInterval(state.intervalId); state.intervalId = window.clearInterval(state.intervalId);
} }
} }
...@@ -11,10 +11,14 @@ export default class Star { ...@@ -11,10 +11,14 @@ export default class Star {
const $starSpan = $this.find('span'); const $starSpan = $this.find('span');
const $startIcon = $this.find('svg'); const $startIcon = $this.find('svg');
axios.post($this.data('endpoint')) axios
.post($this.data('endpoint'))
.then(({ data }) => { .then(({ data }) => {
const isStarred = $starSpan.hasClass('starred'); const isStarred = $starSpan.hasClass('starred');
$this.parent().find('.star-count').text(data.star_count); $this
.parent()
.find('.star-count')
.text(data.star_count);
if (isStarred) { if (isStarred) {
$starSpan.removeClass('starred').text(s__('StarProject|Star')); $starSpan.removeClass('starred').text(s__('StarProject|Star'));
......
...@@ -26,7 +26,11 @@ export default class TaskList { ...@@ -26,7 +26,11 @@ export default class TaskList {
// Prevent duplicate event bindings // Prevent duplicate event bindings
this.disable(); this.disable();
$(`${this.selector} .js-task-list-container`).taskList('enable'); $(`${this.selector} .js-task-list-container`).taskList('enable');
$(document).on('tasklist:changed', `${this.selector} .js-task-list-container`, this.update.bind(this)); $(document).on(
'tasklist:changed',
`${this.selector} .js-task-list-container`,
this.update.bind(this),
);
} }
disable() { disable() {
...@@ -41,7 +45,8 @@ export default class TaskList { ...@@ -41,7 +45,8 @@ export default class TaskList {
[this.fieldName]: $target.val(), [this.fieldName]: $target.val(),
}; };
return axios.patch($target.data('updateUrl') || $('form.js-issuable-update').attr('action'), patchData) return axios
.patch($target.data('updateUrl') || $('form.js-issuable-update').attr('action'), patchData)
.then(({ data }) => this.onSuccess(data)) .then(({ data }) => this.onSuccess(data))
.catch(err => this.onError(err)); .catch(err => this.onError(err));
} }
......
...@@ -31,12 +31,18 @@ export default class IssuableTemplateSelector extends TemplateSelector { ...@@ -31,12 +31,18 @@ export default class IssuableTemplateSelector extends TemplateSelector {
requestFile(query) { requestFile(query) {
this.startLoadingSpinner(); this.startLoadingSpinner();
Api.issueTemplate(this.namespacePath, this.projectPath, query.name, this.issuableType, (err, currentTemplate) => { Api.issueTemplate(
this.currentTemplate = currentTemplate; this.namespacePath,
this.stopLoadingSpinner(); this.projectPath,
if (err) return; // Error handled by global AJAX error handler query.name,
this.setInputValueToTemplateContent(); this.issuableType,
}); (err, currentTemplate) => {
this.currentTemplate = currentTemplate;
this.stopLoadingSpinner();
if (err) return; // Error handled by global AJAX error handler
this.setInputValueToTemplateContent();
},
);
return; return;
} }
......
...@@ -4,10 +4,14 @@ import * as fit from 'xterm/lib/addons/fit/fit'; ...@@ -4,10 +4,14 @@ import * as fit from 'xterm/lib/addons/fit/fit';
export default class GLTerminal { export default class GLTerminal {
constructor(options = {}) { constructor(options = {}) {
this.options = Object.assign({}, { this.options = Object.assign(
cursorBlink: true, {},
screenKeys: true, {
}, options); cursorBlink: true,
screenKeys: true,
},
options,
);
this.container = document.querySelector(options.selector); this.container = document.querySelector(options.selector);
......
...@@ -4,15 +4,43 @@ function simulateEvent(el, type, options = {}) { ...@@ -4,15 +4,43 @@ function simulateEvent(el, type, options = {}) {
if (/^mouse/.test(type)) { if (/^mouse/.test(type)) {
event = el.ownerDocument.createEvent('MouseEvents'); event = el.ownerDocument.createEvent('MouseEvents');
event.initMouseEvent(type, true, true, el.ownerDocument.defaultView, event.initMouseEvent(
options.button, options.screenX, options.screenY, options.clientX, options.clientY, type,
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, el); true,
true,
el.ownerDocument.defaultView,
options.button,
options.screenX,
options.screenY,
options.clientX,
options.clientY,
options.ctrlKey,
options.altKey,
options.shiftKey,
options.metaKey,
options.button,
el,
);
} else { } else {
event = el.ownerDocument.createEvent('CustomEvent'); event = el.ownerDocument.createEvent('CustomEvent');
event.initCustomEvent(type, true, true, el.ownerDocument.defaultView, event.initCustomEvent(
options.button, options.screenX, options.screenY, options.clientX, options.clientY, type,
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, el); true,
true,
el.ownerDocument.defaultView,
options.button,
options.screenX,
options.screenY,
options.clientX,
options.clientY,
options.ctrlKey,
options.altKey,
options.shiftKey,
options.metaKey,
options.button,
el,
);
event.dataTransfer = { event.dataTransfer = {
data: {}, data: {},
...@@ -37,14 +65,16 @@ function simulateEvent(el, type, options = {}) { ...@@ -37,14 +65,16 @@ function simulateEvent(el, type, options = {}) {
} }
function isLast(target) { function isLast(target) {
const el = typeof target.el === 'string' ? document.getElementById(target.el.substr(1)) : target.el; const el =
typeof target.el === 'string' ? document.getElementById(target.el.substr(1)) : target.el;
const { children } = el; const { children } = el;
return children.length - 1 === target.index; return children.length - 1 === target.index;
} }
function getTarget(target) { function getTarget(target) {
const el = typeof target.el === 'string' ? document.getElementById(target.el.substr(1)) : target.el; const el =
typeof target.el === 'string' ? document.getElementById(target.el.substr(1)) : target.el;
const { children } = el; const { children } = el;
return ( return (
...@@ -58,13 +88,13 @@ function getTarget(target) { ...@@ -58,13 +88,13 @@ function getTarget(target) {
function getRect(el) { function getRect(el) {
const rect = el.getBoundingClientRect(); const rect = el.getBoundingClientRect();
const width = rect.right - rect.left; const width = rect.right - rect.left;
const height = (rect.bottom - rect.top) + 10; const height = rect.bottom - rect.top + 10;
return { return {
x: rect.left, x: rect.left,
y: rect.top, y: rect.top,
cx: rect.left + (width / 2), cx: rect.left + width / 2,
cy: rect.top + (height / 2), cy: rect.top + height / 2,
w: width, w: width,
h: height, h: height,
hw: width / 2, hw: width / 2,
...@@ -112,8 +142,8 @@ export default function simulateDrag(options) { ...@@ -112,8 +142,8 @@ export default function simulateDrag(options) {
const dragInterval = setInterval(() => { const dragInterval = setInterval(() => {
const progress = (new Date().getTime() - startTime) / duration; const progress = (new Date().getTime() - startTime) / duration;
const x = (fromRect.cx + ((toRect.cx - fromRect.cx) * progress)); const x = fromRect.cx + (toRect.cx - fromRect.cx) * progress;
const y = (fromRect.cy + ((toRect.cy - fromRect.cy) * progress)); const y = fromRect.cy + (toRect.cy - fromRect.cy) * progress;
const overEl = fromEl.ownerDocument.elementFromPoint(x, y); const overEl = fromEl.ownerDocument.elementFromPoint(x, y);
simulateEvent(overEl, 'mousemove', { simulateEvent(overEl, 'mousemove', {
......
...@@ -12,7 +12,7 @@ export default function simulateInput(target, text) { ...@@ -12,7 +12,7 @@ export default function simulateInput(target, text) {
} }
if (text.length > 0) { if (text.length > 0) {
Array.prototype.forEach.call(text, (char) => { Array.prototype.forEach.call(text, char => {
input.value += char; input.value += char;
triggerEvents(input); triggerEvents(input);
}); });
......
...@@ -49,7 +49,7 @@ function onToggleClicked(toggle, input, clickCallback) { ...@@ -49,7 +49,7 @@ function onToggleClicked(toggle, input, clickCallback) {
export default function setupToggleButtons(container, clickCallback = () => {}) { export default function setupToggleButtons(container, clickCallback = () => {}) {
const toggles = container.querySelectorAll('.js-project-feature-toggle'); const toggles = container.querySelectorAll('.js-project-feature-toggle');
toggles.forEach((toggle) => { toggles.forEach(toggle => {
const input = toggle.querySelector('.js-project-feature-toggle-input'); const input = toggle.querySelector('.js-project-feature-toggle-input');
const isOn = convertPermissionToBoolean(input.value); const isOn = convertPermissionToBoolean(input.value);
......
...@@ -8,7 +8,7 @@ export default class TreeView { ...@@ -8,7 +8,7 @@ export default class TreeView {
this.initKeyNav(); this.initKeyNav();
// Code browser tree slider // Code browser tree slider
// Make the entire tree-item row clickable, but not if clicking another link (like a commit message) // Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
$(".tree-content-holder .tree-item").on('click', function(e) { $('.tree-content-holder .tree-item').on('click', function(e) {
var $clickedEl, path; var $clickedEl, path;
$clickedEl = $(e.target); $clickedEl = $(e.target);
path = $('.tree-item-file-name a', this).attr('href'); path = $('.tree-item-file-name a', this).attr('href');
...@@ -27,33 +27,33 @@ export default class TreeView { ...@@ -27,33 +27,33 @@ export default class TreeView {
initKeyNav() { initKeyNav() {
var li, liSelected; var li, liSelected;
li = $("tr.tree-item"); li = $('tr.tree-item');
liSelected = null; liSelected = null;
return $('body').keydown(function(e) { return $('body').keydown(function(e) {
var next, path; var next, path;
if ($("input:focus").length > 0 && (e.which === 38 || e.which === 40)) { if ($('input:focus').length > 0 && (e.which === 38 || e.which === 40)) {
return false; return false;
} }
if (e.which === 40) { if (e.which === 40) {
if (liSelected) { if (liSelected) {
next = liSelected.next(); next = liSelected.next();
if (next.length > 0) { if (next.length > 0) {
liSelected.removeClass("selected"); liSelected.removeClass('selected');
liSelected = next.addClass("selected"); liSelected = next.addClass('selected');
} }
} else { } else {
liSelected = li.eq(0).addClass("selected"); liSelected = li.eq(0).addClass('selected');
} }
return $(liSelected).focus(); return $(liSelected).focus();
} else if (e.which === 38) { } else if (e.which === 38) {
if (liSelected) { if (liSelected) {
next = liSelected.prev(); next = liSelected.prev();
if (next.length > 0) { if (next.length > 0) {
liSelected.removeClass("selected"); liSelected.removeClass('selected');
liSelected = next.addClass("selected"); liSelected = next.addClass('selected');
} }
} else { } else {
liSelected = li.last().addClass("selected"); liSelected = li.last().addClass('selected');
} }
return $(liSelected).focus(); return $(liSelected).focus();
} else if (e.which === 13) { } else if (e.which === 13) {
......
...@@ -49,7 +49,7 @@ export default class U2FAuthenticate { ...@@ -49,7 +49,7 @@ export default class U2FAuthenticate {
start() { start() {
return importU2FLibrary() return importU2FLibrary()
.then((utils) => { .then(utils => {
this.u2fUtils = utils; this.u2fUtils = utils;
this.renderInProgress(); this.renderInProgress();
}) })
...@@ -57,14 +57,19 @@ export default class U2FAuthenticate { ...@@ -57,14 +57,19 @@ export default class U2FAuthenticate {
} }
authenticate() { authenticate() {
return this.u2fUtils.sign(this.appId, this.challenge, this.signRequests, return this.u2fUtils.sign(
(response) => { this.appId,
this.challenge,
this.signRequests,
response => {
if (response.errorCode) { if (response.errorCode) {
const error = new U2FError(response.errorCode, 'authenticate'); const error = new U2FError(response.errorCode, 'authenticate');
return this.renderError(error); return this.renderError(error);
} }
return this.renderAuthenticated(JSON.stringify(response)); return this.renderAuthenticated(JSON.stringify(response));
}, 10); },
10,
);
} }
renderTemplate(name, params) { renderTemplate(name, params) {
...@@ -99,5 +104,4 @@ export default class U2FAuthenticate { ...@@ -99,5 +104,4 @@ export default class U2FAuthenticate {
this.container[0].classList.add('hidden'); this.container[0].classList.add('hidden');
this.fallbackUI.classList.remove('hidden'); this.fallbackUI.classList.remove('hidden');
} }
} }
...@@ -34,7 +34,7 @@ export default class U2FRegister { ...@@ -34,7 +34,7 @@ export default class U2FRegister {
start() { start() {
return importU2FLibrary() return importU2FLibrary()
.then((utils) => { .then(utils => {
this.u2fUtils = utils; this.u2fUtils = utils;
this.renderSetup(); this.renderSetup();
}) })
...@@ -42,14 +42,19 @@ export default class U2FRegister { ...@@ -42,14 +42,19 @@ export default class U2FRegister {
} }
register() { register() {
return this.u2fUtils.register(this.appId, this.registerRequests, this.signRequests, return this.u2fUtils.register(
(response) => { this.appId,
this.registerRequests,
this.signRequests,
response => {
if (response.errorCode) { if (response.errorCode) {
const error = new U2FError(response.errorCode, 'register'); const error = new U2FError(response.errorCode, 'register');
return this.renderError(error); return this.renderError(error);
} }
return this.renderRegistered(JSON.stringify(response)); return this.renderRegistered(JSON.stringify(response));
}, 10); },
10,
);
} }
renderTemplate(name, params) { renderTemplate(name, params) {
......
...@@ -19,11 +19,10 @@ function getChromeVersion(userAgent) { ...@@ -19,11 +19,10 @@ function getChromeVersion(userAgent) {
export function canInjectU2fApi(userAgent) { export function canInjectU2fApi(userAgent) {
const isSupportedChrome = isChrome(userAgent) && getChromeVersion(userAgent) >= 41; const isSupportedChrome = isChrome(userAgent) && getChromeVersion(userAgent) >= 41;
const isSupportedOpera = isOpera(userAgent) && getOperaVersion(userAgent) >= 40; const isSupportedOpera = isOpera(userAgent) && getOperaVersion(userAgent) >= 40;
const isMobile = ( const isMobile =
userAgent.indexOf('droid') >= 0 || userAgent.indexOf('droid') >= 0 ||
userAgent.indexOf('CriOS') >= 0 || userAgent.indexOf('CriOS') >= 0 ||
/\b(iPad|iPhone|iPod)(?=;)/.test(userAgent) /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent);
);
return (isSupportedChrome || isSupportedOpera) && !isMobile; return (isSupportedChrome || isSupportedOpera) && !isMobile;
} }
......
...@@ -4,13 +4,17 @@ import Api from './api'; ...@@ -4,13 +4,17 @@ import Api from './api';
export default () => { export default () => {
$('#js-project-dropdown').glDropdown({ $('#js-project-dropdown').glDropdown({
data: (term, callback) => { data: (term, callback) => {
Api.projects(term, { Api.projects(
order_by: 'last_activity_at', term,
}, (data) => { {
callback(data); order_by: 'last_activity_at',
}); },
data => {
callback(data);
},
);
}, },
text: project => (project.name_with_namespace || project.name), text: project => project.name_with_namespace || project.name,
selectable: true, selectable: true,
fieldName: 'author_id', fieldName: 'author_id',
filterable: true, filterable: true,
...@@ -18,6 +22,6 @@ export default () => { ...@@ -18,6 +22,6 @@ export default () => {
fields: ['name_with_namespace'], fields: ['name_with_namespace'],
}, },
id: data => data.id, id: data => data.id,
isSelected: data => (data.id === 2), isSelected: data => data.id === 2,
}); });
}; };
...@@ -4,7 +4,7 @@ import Flash, { hideFlash } from './flash'; ...@@ -4,7 +4,7 @@ import Flash, { hideFlash } from './flash';
import { convertPermissionToBoolean } from './lib/utils/common_utils'; import { convertPermissionToBoolean } from './lib/utils/common_utils';
export default () => { export default () => {
$('body').on('click', '.js-usage-consent-action', (e) => { $('body').on('click', '.js-usage-consent-action', e => {
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); // overwrite rails listener e.stopImmediatePropagation(); // overwrite rails listener
...@@ -18,7 +18,8 @@ export default () => { ...@@ -18,7 +18,8 @@ export default () => {
const hideConsentMessage = () => hideFlash(document.querySelector('.ping-consent-message')); const hideConsentMessage = () => hideFlash(document.querySelector('.ping-consent-message'));
axios.put(url, data) axios
.put(url, data)
.then(() => { .then(() => {
hideConsentMessage(); hideConsentMessage();
}) })
......
...@@ -15,8 +15,8 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -15,8 +15,8 @@ function UsersSelect(currentUser, els, options = {}) {
var $els; var $els;
this.users = this.users.bind(this); this.users = this.users.bind(this);
this.user = this.user.bind(this); this.user = this.user.bind(this);
this.usersPath = "/autocomplete/users.json"; this.usersPath = '/autocomplete/users.json';
this.userPath = "/autocomplete/users/:id.json"; this.userPath = '/autocomplete/users/:id.json';
if (currentUser != null) { if (currentUser != null) {
if (typeof currentUser === 'object') { if (typeof currentUser === 'object') {
this.currentUser = currentUser; this.currentUser = currentUser;
...@@ -33,156 +33,180 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -33,156 +33,180 @@ function UsersSelect(currentUser, els, options = {}) {
$els = $('.js-user-search'); $els = $('.js-user-search');
} }
$els.each((function(_this) { $els.each(
return function(i, dropdown) { (function(_this) {
var options = {}; return function(i, dropdown) {
var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, defaultNullUser, firstUser, issueURL, selectedId, selectedIdDefault, showAnyUser, showNullUser, showMenuAbove; var options = {};
$dropdown = $(dropdown); var $block,
options.projectId = $dropdown.data('projectId'); $collapsedSidebar,
options.groupId = $dropdown.data('groupId'); $dropdown,
options.showCurrentUser = $dropdown.data('currentUser'); $loading,
options.todoFilter = $dropdown.data('todoFilter'); $selectbox,
options.todoStateFilter = $dropdown.data('todoStateFilter'); $value,
showNullUser = $dropdown.data('nullUser'); abilityName,
defaultNullUser = $dropdown.data('nullUserDefault'); assignTo,
showMenuAbove = $dropdown.data('showMenuAbove'); assigneeTemplate,
showAnyUser = $dropdown.data('anyUser'); collapsedAssigneeTemplate,
firstUser = $dropdown.data('firstUser'); defaultLabel,
options.authorId = $dropdown.data('authorId'); defaultNullUser,
defaultLabel = $dropdown.data('defaultLabel'); firstUser,
issueURL = $dropdown.data('issueUpdate'); issueURL,
$selectbox = $dropdown.closest('.selectbox'); selectedId,
$block = $selectbox.closest('.block'); selectedIdDefault,
abilityName = $dropdown.data('abilityName'); showAnyUser,
$value = $block.find('.value'); showNullUser,
$collapsedSidebar = $block.find('.sidebar-collapsed-user'); showMenuAbove;
$loading = $block.find('.block-loading').fadeOut(); $dropdown = $(dropdown);
selectedIdDefault = (defaultNullUser && showNullUser) ? 0 : null; options.projectId = $dropdown.data('projectId');
selectedId = $dropdown.data('selected'); options.groupId = $dropdown.data('groupId');
options.showCurrentUser = $dropdown.data('currentUser');
if (selectedId === undefined) { options.todoFilter = $dropdown.data('todoFilter');
selectedId = selectedIdDefault; options.todoStateFilter = $dropdown.data('todoStateFilter');
} showNullUser = $dropdown.data('nullUser');
defaultNullUser = $dropdown.data('nullUserDefault');
const assignYourself = function () { showMenuAbove = $dropdown.data('showMenuAbove');
const unassignedSelected = $dropdown.closest('.selectbox') showAnyUser = $dropdown.data('anyUser');
.find(`input[name='${$dropdown.data('fieldName')}'][value=0]`); firstUser = $dropdown.data('firstUser');
options.authorId = $dropdown.data('authorId');
if (unassignedSelected) { defaultLabel = $dropdown.data('defaultLabel');
unassignedSelected.remove(); issueURL = $dropdown.data('issueUpdate');
$selectbox = $dropdown.closest('.selectbox');
$block = $selectbox.closest('.block');
abilityName = $dropdown.data('abilityName');
$value = $block.find('.value');
$collapsedSidebar = $block.find('.sidebar-collapsed-user');
$loading = $block.find('.block-loading').fadeOut();
selectedIdDefault = defaultNullUser && showNullUser ? 0 : null;
selectedId = $dropdown.data('selected');
if (selectedId === undefined) {
selectedId = selectedIdDefault;
} }
// Save current selected user to the DOM const assignYourself = function() {
const input = document.createElement('input'); const unassignedSelected = $dropdown
input.type = 'hidden'; .closest('.selectbox')
input.name = $dropdown.data('fieldName'); .find(`input[name='${$dropdown.data('fieldName')}'][value=0]`);
const currentUserInfo = $dropdown.data('currentUserInfo'); if (unassignedSelected) {
unassignedSelected.remove();
if (currentUserInfo) { }
input.value = currentUserInfo.id;
input.dataset.meta = _.escape(currentUserInfo.name);
} else if (_this.currentUser) {
input.value = _this.currentUser.id;
}
if ($selectbox) {
$dropdown.parent().before(input);
} else {
$dropdown.after(input);
}
};
if ($block[0]) {
$block[0].addEventListener('assignYourself', assignYourself);
}
const getSelectedUserInputs = function() {
return $selectbox
.find(`input[name="${$dropdown.data('fieldName')}"]`);
};
const getSelected = function() { // Save current selected user to the DOM
return getSelectedUserInputs() const input = document.createElement('input');
.map((index, input) => parseInt(input.value, 10)) input.type = 'hidden';
.get(); input.name = $dropdown.data('fieldName');
};
const checkMaxSelect = function() { const currentUserInfo = $dropdown.data('currentUserInfo');
const maxSelect = $dropdown.data('maxSelect');
if (maxSelect) {
const selected = getSelected();
if (selected.length > maxSelect) { if (currentUserInfo) {
const firstSelectedId = selected[0]; input.value = currentUserInfo.id;
const firstSelected = $dropdown.closest('.selectbox') input.dataset.meta = _.escape(currentUserInfo.name);
.find(`input[name='${$dropdown.data('fieldName')}'][value=${firstSelectedId}]`); } else if (_this.currentUser) {
input.value = _this.currentUser.id;
}
firstSelected.remove(); if ($selectbox) {
emitSidebarEvent('sidebar.removeAssignee', { $dropdown.parent().before(input);
id: firstSelectedId, } else {
}); $dropdown.after(input);
} }
} };
};
const getMultiSelectDropdownTitle = function(selectedUser, isSelected) { if ($block[0]) {
const selectedUsers = getSelected() $block[0].addEventListener('assignYourself', assignYourself);
.filter(u => u !== 0);
const firstUser = getSelectedUserInputs()
.map((index, input) => ({
name: input.dataset.meta,
value: parseInt(input.value, 10),
}))
.filter(u => u.id !== 0)
.get(0);
if (selectedUsers.length === 0) {
return 'Unassigned';
} else if (selectedUsers.length === 1) {
return firstUser.name;
} else if (isSelected) {
const otherSelected = selectedUsers.filter(s => s !== selectedUser.id);
return `${selectedUser.name} + ${otherSelected.length} more`;
} else {
return `${firstUser.name} + ${selectedUsers.length - 1} more`;
} }
};
$('.assign-to-me-link').on('click', (e) => { const getSelectedUserInputs = function() {
e.preventDefault(); return $selectbox.find(`input[name="${$dropdown.data('fieldName')}"]`);
$(e.currentTarget).hide(); };
const getSelected = function() {
return getSelectedUserInputs()
.map((index, input) => parseInt(input.value, 10))
.get();
};
const checkMaxSelect = function() {
const maxSelect = $dropdown.data('maxSelect');
if (maxSelect) {
const selected = getSelected();
if (selected.length > maxSelect) {
const firstSelectedId = selected[0];
const firstSelected = $dropdown
.closest('.selectbox')
.find(`input[name='${$dropdown.data('fieldName')}'][value=${firstSelectedId}]`);
firstSelected.remove();
emitSidebarEvent('sidebar.removeAssignee', {
id: firstSelectedId,
});
}
}
};
const getMultiSelectDropdownTitle = function(selectedUser, isSelected) {
const selectedUsers = getSelected().filter(u => u !== 0);
const firstUser = getSelectedUserInputs()
.map((index, input) => ({
name: input.dataset.meta,
value: parseInt(input.value, 10),
}))
.filter(u => u.id !== 0)
.get(0);
if (selectedUsers.length === 0) {
return 'Unassigned';
} else if (selectedUsers.length === 1) {
return firstUser.name;
} else if (isSelected) {
const otherSelected = selectedUsers.filter(s => s !== selectedUser.id);
return `${selectedUser.name} + ${otherSelected.length} more`;
} else {
return `${firstUser.name} + ${selectedUsers.length - 1} more`;
}
};
if ($dropdown.data('multiSelect')) { $('.assign-to-me-link').on('click', e => {
assignYourself(); e.preventDefault();
checkMaxSelect(); $(e.currentTarget).hide();
const currentUserInfo = $dropdown.data('currentUserInfo'); if ($dropdown.data('multiSelect')) {
$dropdown.find('.dropdown-toggle-text').text(getMultiSelectDropdownTitle(currentUserInfo)).removeClass('is-default'); assignYourself();
} else { checkMaxSelect();
const $input = $(`input[name="${$dropdown.data('fieldName')}"]`);
$input.val(gon.current_user_id); const currentUserInfo = $dropdown.data('currentUserInfo');
selectedId = $input.val(); $dropdown
$dropdown.find('.dropdown-toggle-text').text(gon.current_user_fullname).removeClass('is-default'); .find('.dropdown-toggle-text')
} .text(getMultiSelectDropdownTitle(currentUserInfo))
}); .removeClass('is-default');
} else {
$block.on('click', '.js-assign-yourself', (e) => { const $input = $(`input[name="${$dropdown.data('fieldName')}"]`);
e.preventDefault(); $input.val(gon.current_user_id);
return assignTo(_this.currentUser.id); selectedId = $input.val();
}); $dropdown
.find('.dropdown-toggle-text')
assignTo = function(selected) { .text(gon.current_user_fullname)
var data; .removeClass('is-default');
data = {}; }
data[abilityName] = {}; });
data[abilityName].assignee_id = selected != null ? selected : null;
$loading.removeClass('hidden').fadeIn(); $block.on('click', '.js-assign-yourself', e => {
$dropdown.trigger('loading.gl.dropdown'); e.preventDefault();
return assignTo(_this.currentUser.id);
return axios.put(issueURL, data) });
.then(({ data }) => {
assignTo = function(selected) {
var data;
data = {};
data[abilityName] = {};
data[abilityName].assignee_id = selected != null ? selected : null;
$loading.removeClass('hidden').fadeIn();
$dropdown.trigger('loading.gl.dropdown');
return axios.put(issueURL, data).then(({ data }) => {
var user, tooltipTitle; var user, tooltipTitle;
$dropdown.trigger('loaded.gl.dropdown'); $dropdown.trigger('loaded.gl.dropdown');
$loading.fadeOut(); $loading.fadeOut();
...@@ -190,14 +214,14 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -190,14 +214,14 @@ function UsersSelect(currentUser, els, options = {}) {
user = { user = {
name: data.assignee.name, name: data.assignee.name,
username: data.assignee.username, username: data.assignee.username,
avatar: data.assignee.avatar_url avatar: data.assignee.avatar_url,
}; };
tooltipTitle = _.escape(user.name); tooltipTitle = _.escape(user.name);
} else { } else {
user = { user = {
name: 'Unassigned', name: 'Unassigned',
username: '', username: '',
avatar: '' avatar: '',
}; };
tooltipTitle = __('Assignee'); tooltipTitle = __('Assignee');
} }
...@@ -205,319 +229,341 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -205,319 +229,341 @@ function UsersSelect(currentUser, els, options = {}) {
$collapsedSidebar.attr('title', tooltipTitle).tooltip('_fixTitle'); $collapsedSidebar.attr('title', tooltipTitle).tooltip('_fixTitle');
return $collapsedSidebar.html(collapsedAssigneeTemplate(user)); return $collapsedSidebar.html(collapsedAssigneeTemplate(user));
}); });
}; };
collapsedAssigneeTemplate = _.template('<% if( avatar ) { %> <a class="author-link" href="/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>'); collapsedAssigneeTemplate = _.template(
assigneeTemplate = _.template('<% if (username) { %> <a class="author-link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself </a> </span> <% } %>'); '<% if( avatar ) { %> <a class="author-link" href="/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>',
return $dropdown.glDropdown({ );
showMenuAbove: showMenuAbove, assigneeTemplate = _.template(
data: function(term, callback) { '<% if (username) { %> <a class="author-link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself"> No assignee - <a href="#" class="js-assign-yourself"> assign yourself </a> </span> <% } %>',
return _this.users(term, options, function(users) { );
// GitLabDropdownFilter returns this.instance return $dropdown.glDropdown({
// GitLabDropdownRemote returns this.options.instance showMenuAbove: showMenuAbove,
const glDropdown = this.instance || this.options.instance; data: function(term, callback) {
glDropdown.options.processData(term, users, callback); return _this.users(
}.bind(this)); term,
}, options,
processData: function(term, data, callback) { function(users) {
let users = data; // GitLabDropdownFilter returns this.instance
// GitLabDropdownRemote returns this.options.instance
// Only show assigned user list when there is no search term const glDropdown = this.instance || this.options.instance;
if ($dropdown.hasClass('js-multiselect') && term.length === 0) { glDropdown.options.processData(term, users, callback);
const selectedInputs = getSelectedUserInputs(); }.bind(this),
);
// Potential duplicate entries when dealing with issue board },
// because issue board is also managed by vue processData: function(term, data, callback) {
const selectedUsers = _.uniq(selectedInputs, false, a => a.value) let users = data;
.filter((input) => {
const userId = parseInt(input.value, 10); // Only show assigned user list when there is no search term
const inUsersArray = users.find(u => u.id === userId); if ($dropdown.hasClass('js-multiselect') && term.length === 0) {
const selectedInputs = getSelectedUserInputs();
return !inUsersArray && userId !== 0;
}) // Potential duplicate entries when dealing with issue board
.map((input) => { // because issue board is also managed by vue
const userId = parseInt(input.value, 10); const selectedUsers = _.uniq(selectedInputs, false, a => a.value)
const { avatarUrl, avatar_url, name, username } = input.dataset; .filter(input => {
return { const userId = parseInt(input.value, 10);
avatar_url: avatarUrl || avatar_url, const inUsersArray = users.find(u => u.id === userId);
id: userId,
name, return !inUsersArray && userId !== 0;
username, })
}; .map(input => {
}); const userId = parseInt(input.value, 10);
const { avatarUrl, avatar_url, name, username } = input.dataset;
return {
avatar_url: avatarUrl || avatar_url,
id: userId,
name,
username,
};
});
users = data.concat(selectedUsers); users = data.concat(selectedUsers);
} }
let anyUser; let anyUser;
let index; let index;
let len; let len;
let name; let name;
let obj; let obj;
let showDivider; let showDivider;
if (term.length === 0) { if (term.length === 0) {
showDivider = 0; showDivider = 0;
if (firstUser) { if (firstUser) {
// Move current user to the front of the list // Move current user to the front of the list
for (index = 0, len = users.length; index < len; index += 1) { for (index = 0, len = users.length; index < len; index += 1) {
obj = users[index]; obj = users[index];
if (obj.username === firstUser) { if (obj.username === firstUser) {
users.splice(index, 1); users.splice(index, 1);
users.unshift(obj); users.unshift(obj);
break; break;
}
} }
} }
} if (showNullUser) {
if (showNullUser) { showDivider += 1;
showDivider += 1; users.unshift({
users.unshift({ beforeDivider: true,
beforeDivider: true, name: 'Unassigned',
name: 'Unassigned', id: 0,
id: 0 });
}); }
} if (showAnyUser) {
if (showAnyUser) { showDivider += 1;
showDivider += 1; name = showAnyUser;
name = showAnyUser; if (name === true) {
if (name === true) { name = 'Any User';
name = 'Any User'; }
anyUser = {
beforeDivider: true,
name: name,
id: null,
};
users.unshift(anyUser);
} }
anyUser = {
beforeDivider: true,
name: name,
id: null
};
users.unshift(anyUser);
}
if (showDivider) { if (showDivider) {
users.splice(showDivider, 0, 'divider'); users.splice(showDivider, 0, 'divider');
} }
if ($dropdown.hasClass('js-multiselect')) { if ($dropdown.hasClass('js-multiselect')) {
const selected = getSelected().filter(i => i !== 0); const selected = getSelected().filter(i => i !== 0);
if (selected.length > 0) { if (selected.length > 0) {
if ($dropdown.data('dropdownHeader')) { if ($dropdown.data('dropdownHeader')) {
showDivider += 1; showDivider += 1;
users.splice(showDivider, 0, { users.splice(showDivider, 0, {
header: $dropdown.data('dropdownHeader'), header: $dropdown.data('dropdownHeader'),
}); });
} }
const selectedUsers = users const selectedUsers = users
.filter(u => selected.indexOf(u.id) !== -1) .filter(u => selected.indexOf(u.id) !== -1)
.sort((a, b) => a.name > b.name); .sort((a, b) => a.name > b.name);
users = users.filter(u => selected.indexOf(u.id) === -1); users = users.filter(u => selected.indexOf(u.id) === -1);
selectedUsers.forEach((selectedUser) => { selectedUsers.forEach(selectedUser => {
showDivider += 1; showDivider += 1;
users.splice(showDivider, 0, selectedUser); users.splice(showDivider, 0, selectedUser);
}); });
users.splice(showDivider + 1, 0, 'divider'); users.splice(showDivider + 1, 0, 'divider');
}
} }
} }
}
callback(users); callback(users);
if (showMenuAbove) { if (showMenuAbove) {
$dropdown.data('glDropdown').positionMenuAbove(); $dropdown.data('glDropdown').positionMenuAbove();
} }
}, },
filterable: true, filterable: true,
filterRemote: true, filterRemote: true,
search: { search: {
fields: ['name', 'username'] fields: ['name', 'username'],
}, },
selectable: true, selectable: true,
fieldName: $dropdown.data('fieldName'), fieldName: $dropdown.data('fieldName'),
toggleLabel: function(selected, el, glDropdown) { toggleLabel: function(selected, el, glDropdown) {
const inputValue = glDropdown.filterInput.val(); const inputValue = glDropdown.filterInput.val();
if (this.multiSelect && inputValue === '') { if (this.multiSelect && inputValue === '') {
// Remove non-users from the fullData array // Remove non-users from the fullData array
const users = glDropdown.filteredFullData(); const users = glDropdown.filteredFullData();
const callback = glDropdown.parseData.bind(glDropdown); const callback = glDropdown.parseData.bind(glDropdown);
// Update the data model // Update the data model
this.processData(inputValue, users, callback); this.processData(inputValue, users, callback);
} }
if (this.multiSelect) { if (this.multiSelect) {
return getMultiSelectDropdownTitle(selected, $(el).hasClass('is-active')); return getMultiSelectDropdownTitle(selected, $(el).hasClass('is-active'));
} }
if (selected && 'id' in selected && $(el).hasClass('is-active')) { if (selected && 'id' in selected && $(el).hasClass('is-active')) {
$dropdown.find('.dropdown-toggle-text').removeClass('is-default'); $dropdown.find('.dropdown-toggle-text').removeClass('is-default');
if (selected.text) { if (selected.text) {
return selected.text; return selected.text;
} else {
return selected.name;
}
} else { } else {
return selected.name; $dropdown.find('.dropdown-toggle-text').addClass('is-default');
return defaultLabel;
}
},
defaultLabel: defaultLabel,
hidden: function(e) {
if ($dropdown.hasClass('js-multiselect')) {
emitSidebarEvent('sidebar.saveAssignees');
} }
} else {
$dropdown.find('.dropdown-toggle-text').addClass('is-default');
return defaultLabel;
}
},
defaultLabel: defaultLabel,
hidden: function(e) {
if ($dropdown.hasClass('js-multiselect')) {
emitSidebarEvent('sidebar.saveAssignees');
}
if (!$dropdown.data('alwaysShowSelectbox')) {
$selectbox.hide();
// Recalculate where .value is because vue might have changed it if (!$dropdown.data('alwaysShowSelectbox')) {
$block = $selectbox.closest('.block'); $selectbox.hide();
$value = $block.find('.value');
// display:block overrides the hide-collapse rule
$value.css('display', '');
}
},
multiSelect: $dropdown.hasClass('js-multiselect'),
inputMeta: $dropdown.data('inputMeta'),
clicked: function(options) {
const { $el, e, isMarking } = options;
const user = options.selectedObj;
if ($dropdown.hasClass('js-multiselect')) {
const isActive = $el.hasClass('is-active');
const previouslySelected = $dropdown.closest('.selectbox')
.find("input[name='" + ($dropdown.data('fieldName')) + "'][value!=0]");
// Enables support for limiting the number of users selected
// Automatically removes the first on the list if more users are selected
checkMaxSelect();
if (user.beforeDivider && user.name.toLowerCase() === 'unassigned') { // Recalculate where .value is because vue might have changed it
// Unassigned selected $block = $selectbox.closest('.block');
previouslySelected.each((index, element) => { $value = $block.find('.value');
const id = parseInt(element.value, 10); // display:block overrides the hide-collapse rule
element.remove(); $value.css('display', '');
}); }
emitSidebarEvent('sidebar.removeAllAssignees'); },
} else if (isActive) { multiSelect: $dropdown.hasClass('js-multiselect'),
// user selected inputMeta: $dropdown.data('inputMeta'),
emitSidebarEvent('sidebar.addAssignee', user); clicked: function(options) {
const { $el, e, isMarking } = options;
const user = options.selectedObj;
// Remove unassigned selection (if it was previously selected) if ($dropdown.hasClass('js-multiselect')) {
const unassignedSelected = $dropdown.closest('.selectbox') const isActive = $el.hasClass('is-active');
.find("input[name='" + ($dropdown.data('fieldName')) + "'][value=0]"); const previouslySelected = $dropdown
.closest('.selectbox')
.find("input[name='" + $dropdown.data('fieldName') + "'][value!=0]");
// Enables support for limiting the number of users selected
// Automatically removes the first on the list if more users are selected
checkMaxSelect();
if (user.beforeDivider && user.name.toLowerCase() === 'unassigned') {
// Unassigned selected
previouslySelected.each((index, element) => {
const id = parseInt(element.value, 10);
element.remove();
});
emitSidebarEvent('sidebar.removeAllAssignees');
} else if (isActive) {
// user selected
emitSidebarEvent('sidebar.addAssignee', user);
// Remove unassigned selection (if it was previously selected)
const unassignedSelected = $dropdown
.closest('.selectbox')
.find("input[name='" + $dropdown.data('fieldName') + "'][value=0]");
if (unassignedSelected) {
unassignedSelected.remove();
}
} else {
if (previouslySelected.length === 0) {
// Select unassigned because there is no more selected users
this.addInput($dropdown.data('fieldName'), 0, {});
}
if (unassignedSelected) { // User unselected
unassignedSelected.remove(); emitSidebarEvent('sidebar.removeAssignee', user);
}
} else {
if (previouslySelected.length === 0) {
// Select unassigned because there is no more selected users
this.addInput($dropdown.data('fieldName'), 0, {});
} }
// User unselected if (getSelected().find(u => u === gon.current_user_id)) {
emitSidebarEvent('sidebar.removeAssignee', user); $('.assign-to-me-link').hide();
} else {
$('.assign-to-me-link').show();
}
} }
if (getSelected().find(u => u === gon.current_user_id)) { var isIssueIndex, isMRIndex, page, selected;
$('.assign-to-me-link').hide(); page = $('body').attr('data-page');
} else { isIssueIndex = page === 'projects:issues:index';
$('.assign-to-me-link').show(); isMRIndex = page === page && page === 'projects:merge_requests:index';
if (
$dropdown.hasClass('js-filter-bulk-update') ||
$dropdown.hasClass('js-issuable-form-dropdown')
) {
e.preventDefault();
const isSelecting = user.id !== selectedId;
selectedId = isSelecting ? user.id : selectedIdDefault;
if (selectedId === gon.current_user_id) {
$('.assign-to-me-link').hide();
} else {
$('.assign-to-me-link').show();
}
return;
}
if ($el.closest('.add-issues-modal').length) {
ModalStore.store.filter[$dropdown.data('fieldName')] = user.id;
} else if (handleClick) {
e.preventDefault();
handleClick(user, isMarking);
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
return Issuable.filterResults($dropdown.closest('form'));
} else if ($dropdown.hasClass('js-filter-submit')) {
return $dropdown.closest('form').submit();
} else if (!$dropdown.hasClass('js-multiselect')) {
selected = $dropdown
.closest('.selectbox')
.find("input[name='" + $dropdown.data('fieldName') + "']")
.val();
return assignTo(selected);
} }
}
var isIssueIndex, isMRIndex, page, selected; // Automatically close dropdown after assignee is selected
page = $('body').attr('data-page'); // since CE has no multiple assignees
isIssueIndex = page === 'projects:issues:index'; // EE does not have a max-select
isMRIndex = (page === page && page === 'projects:merge_requests:index'); if (
if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { $dropdown.data('maxSelect') &&
e.preventDefault(); getSelected().length === $dropdown.data('maxSelect')
) {
// Close the dropdown
$dropdown.dropdown('toggle');
}
},
id: function(user) {
return user.id;
},
opened: function(e) {
const $el = $(e.currentTarget);
const selected = getSelected();
if ($dropdown.hasClass('js-issue-board-sidebar') && selected.length === 0) {
this.addInput($dropdown.data('fieldName'), 0, {});
}
$el.find('.is-active').removeClass('is-active');
const isSelecting = (user.id !== selectedId); function highlightSelected(id) {
selectedId = isSelecting ? user.id : selectedIdDefault; $el.find(`li[data-user-id="${id}"] .dropdown-menu-user-link`).addClass('is-active');
}
if (selectedId === gon.current_user_id) { if (selected.length > 0) {
$('.assign-to-me-link').hide(); getSelected().forEach(selectedId => highlightSelected(selectedId));
} else if ($dropdown.hasClass('js-issue-board-sidebar')) {
highlightSelected(0);
} else { } else {
$('.assign-to-me-link').show(); highlightSelected(selectedId);
} }
return; },
} updateLabel: $dropdown.data('dropdownTitle'),
if ($el.closest('.add-issues-modal').length) { renderRow: function(user) {
ModalStore.store.filter[$dropdown.data('fieldName')] = user.id; var avatar, img, listClosingTags, listWithName, listWithUserName, username;
} else if (handleClick) { username = user.username ? '@' + user.username : '';
e.preventDefault(); avatar = user.avatar_url ? user.avatar_url : gon.default_avatar_url;
handleClick(user, isMarking);
} else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) {
return Issuable.filterResults($dropdown.closest('form'));
} else if ($dropdown.hasClass('js-filter-submit')) {
return $dropdown.closest('form').submit();
} else if (!$dropdown.hasClass('js-multiselect')) {
selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('fieldName')) + "']").val();
return assignTo(selected);
}
// Automatically close dropdown after assignee is selected
// since CE has no multiple assignees
// EE does not have a max-select
if ($dropdown.data('maxSelect') &&
getSelected().length === $dropdown.data('maxSelect')) {
// Close the dropdown
$dropdown.dropdown('toggle');
}
},
id: function (user) {
return user.id;
},
opened: function(e) {
const $el = $(e.currentTarget);
const selected = getSelected();
if ($dropdown.hasClass('js-issue-board-sidebar') && selected.length === 0) {
this.addInput($dropdown.data('fieldName'), 0, {});
}
$el.find('.is-active').removeClass('is-active');
function highlightSelected(id) {
$el.find(`li[data-user-id="${id}"] .dropdown-menu-user-link`).addClass('is-active');
}
if (selected.length > 0) { let selected = false;
getSelected().forEach(selectedId => highlightSelected(selectedId));
} else if ($dropdown.hasClass('js-issue-board-sidebar')) {
highlightSelected(0);
} else {
highlightSelected(selectedId);
}
},
updateLabel: $dropdown.data('dropdownTitle'),
renderRow: function(user) {
var avatar, img, listClosingTags, listWithName, listWithUserName, username;
username = user.username ? "@" + user.username : "";
avatar = user.avatar_url ? user.avatar_url : gon.default_avatar_url;
let selected = false; if (this.multiSelect) {
selected = getSelected().find(u => user.id === u);
if (this.multiSelect) { const { fieldName } = this;
selected = getSelected().find(u => user.id === u); const field = $dropdown
.closest('.selectbox')
.find("input[name='" + fieldName + "'][value='" + user.id + "']");
const { fieldName } = this; if (field.length) {
const field = $dropdown.closest('.selectbox').find("input[name='" + fieldName + "'][value='" + user.id + "']"); selected = true;
}
if (field.length) { } else {
selected = true; selected = user.id === selectedId;
} }
} else {
selected = user.id === selectedId;
}
img = ""; img = '';
if (user.beforeDivider != null) { if (user.beforeDivider != null) {
`<li><a href='#' class='${selected === true ? 'is-active' : ''}'>${_.escape(user.name)}</a></li>`; `<li><a href='#' class='${selected === true ? 'is-active' : ''}'>${_.escape(
} else { user.name,
img = "<img src='" + avatar + "' class='avatar avatar-inline' width='32' />"; )}</a></li>`;
} } else {
img = "<img src='" + avatar + "' class='avatar avatar-inline' width='32' />";
}
return ` return `
<li data-user-id=${user.id}> <li data-user-id=${user.id}>
<a href='#' class='dropdown-menu-user-link ${selected === true ? 'is-active' : ''}'> <a href='#' class='dropdown-menu-user-link ${selected === true ? 'is-active' : ''}'>
${img} ${img}
...@@ -528,114 +574,117 @@ function UsersSelect(currentUser, els, options = {}) { ...@@ -528,114 +574,117 @@ function UsersSelect(currentUser, els, options = {}) {
</a> </a>
</li> </li>
`; `;
} },
}); });
}; };
})(this)); })(this),
$('.ajax-users-select').each((function(_this) { );
return function(i, select) { $('.ajax-users-select').each(
var firstUser, showAnyUser, showEmailUser, showNullUser; (function(_this) {
var options = {}; return function(i, select) {
options.skipLdap = $(select).hasClass('skip_ldap'); var firstUser, showAnyUser, showEmailUser, showNullUser;
options.projectId = $(select).data('projectId'); var options = {};
options.groupId = $(select).data('groupId'); options.skipLdap = $(select).hasClass('skip_ldap');
options.showCurrentUser = $(select).data('currentUser'); options.projectId = $(select).data('projectId');
options.authorId = $(select).data('authorId'); options.groupId = $(select).data('groupId');
options.skipUsers = $(select).data('skipUsers'); options.showCurrentUser = $(select).data('currentUser');
showNullUser = $(select).data('nullUser'); options.authorId = $(select).data('authorId');
showAnyUser = $(select).data('anyUser'); options.skipUsers = $(select).data('skipUsers');
showEmailUser = $(select).data('emailUser'); showNullUser = $(select).data('nullUser');
firstUser = $(select).data('firstUser'); showAnyUser = $(select).data('anyUser');
return $(select).select2({ showEmailUser = $(select).data('emailUser');
placeholder: "Search for a user", firstUser = $(select).data('firstUser');
multiple: $(select).hasClass('multiselect'), return $(select).select2({
minimumInputLength: 0, placeholder: 'Search for a user',
query: function(query) { multiple: $(select).hasClass('multiselect'),
return _this.users(query.term, options, function(users) { minimumInputLength: 0,
var anyUser, data, emailUser, index, len, name, nullUser, obj, ref; query: function(query) {
data = { return _this.users(query.term, options, function(users) {
results: users var anyUser, data, emailUser, index, len, name, nullUser, obj, ref;
}; data = {
if (query.term.length === 0) { results: users,
if (firstUser) { };
// Move current user to the front of the list if (query.term.length === 0) {
ref = data.results; if (firstUser) {
// Move current user to the front of the list
for (index = 0, len = ref.length; index < len; index += 1) { ref = data.results;
obj = ref[index];
if (obj.username === firstUser) { for (index = 0, len = ref.length; index < len; index += 1) {
data.results.splice(index, 1); obj = ref[index];
data.results.unshift(obj); if (obj.username === firstUser) {
break; data.results.splice(index, 1);
data.results.unshift(obj);
break;
}
} }
} }
} if (showNullUser) {
if (showNullUser) { nullUser = {
nullUser = { name: 'Unassigned',
name: 'Unassigned', id: 0,
id: 0 };
}; data.results.unshift(nullUser);
data.results.unshift(nullUser);
}
if (showAnyUser) {
name = showAnyUser;
if (name === true) {
name = 'Any User';
} }
anyUser = { if (showAnyUser) {
name: name, name = showAnyUser;
id: null if (name === true) {
name = 'Any User';
}
anyUser = {
name: name,
id: null,
};
data.results.unshift(anyUser);
}
}
if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) {
var trimmed = query.term.trim();
emailUser = {
name: 'Invite "' + trimmed + '" by email',
username: trimmed,
id: trimmed,
invite: true,
}; };
data.results.unshift(anyUser); data.results.unshift(emailUser);
} }
} return query.callback(data);
if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) { });
var trimmed = query.term.trim(); },
emailUser = { initSelection: function() {
name: "Invite \"" + trimmed + "\" by email", var args;
username: trimmed, args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
id: trimmed, return _this.initSelection.apply(_this, args);
invite: true },
}; formatResult: function() {
data.results.unshift(emailUser); var args;
} args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
return query.callback(data); return _this.formatResult.apply(_this, args);
}); },
}, formatSelection: function() {
initSelection: function() { var args;
var args; args = 1 <= arguments.length ? [].slice.call(arguments, 0) : [];
args = 1 <= arguments.length ? [].slice.call(arguments, 0) : []; return _this.formatSelection.apply(_this, args);
return _this.initSelection.apply(_this, args); },
}, dropdownCssClass: 'ajax-users-dropdown',
formatResult: function() { // we do not want to escape markup since we are displaying html in results
var args; escapeMarkup: function(m) {
args = 1 <= arguments.length ? [].slice.call(arguments, 0) : []; return m;
return _this.formatResult.apply(_this, args); },
}, });
formatSelection: function() { };
var args; })(this),
args = 1 <= arguments.length ? [].slice.call(arguments, 0) : []; );
return _this.formatSelection.apply(_this, args);
},
dropdownCssClass: "ajax-users-dropdown",
// we do not want to escape markup since we are displaying html in results
escapeMarkup: function(m) {
return m;
}
});
};
})(this));
} }
UsersSelect.prototype.initSelection = function(element, callback) { UsersSelect.prototype.initSelection = function(element, callback) {
var id, nullUser; var id, nullUser;
id = $(element).val(); id = $(element).val();
if (id === "0") { if (id === '0') {
nullUser = { nullUser = {
name: 'Unassigned' name: 'Unassigned',
}; };
return callback(nullUser); return callback(nullUser);
} else if (id !== "") { } else if (id !== '') {
return this.user(id, callback); return this.user(id, callback);
} }
}; };
...@@ -647,7 +696,17 @@ UsersSelect.prototype.formatResult = function(user) { ...@@ -647,7 +696,17 @@ UsersSelect.prototype.formatResult = function(user) {
} else { } else {
avatar = gon.default_avatar_url; avatar = gon.default_avatar_url;
} }
return "<div class='user-result " + (!user.username ? 'no-username' : void 0) + "'> <div class='user-image'><img class='avatar avatar-inline s32' src='" + avatar + "'></div> <div class='user-name dropdown-menu-user-full-name'>" + _.escape(user.name) + "</div> <div class='user-username dropdown-menu-user-username'>" + (!user.invite ? "@" + _.escape(user.username) : "") + "</div> </div>"; return (
"<div class='user-result " +
(!user.username ? 'no-username' : void 0) +
"'> <div class='user-image'><img class='avatar avatar-inline s32' src='" +
avatar +
"'></div> <div class='user-name dropdown-menu-user-full-name'>" +
_.escape(user.name) +
"</div> <div class='user-username dropdown-menu-user-username'>" +
(!user.invite ? '@' + _.escape(user.username) : '') +
'</div> </div>'
);
}; };
UsersSelect.prototype.formatSelection = function(user) { UsersSelect.prototype.formatSelection = function(user) {
...@@ -662,10 +721,9 @@ UsersSelect.prototype.user = function(user_id, callback) { ...@@ -662,10 +721,9 @@ UsersSelect.prototype.user = function(user_id, callback) {
var url; var url;
url = this.buildUrl(this.userPath); url = this.buildUrl(this.userPath);
url = url.replace(':id', user_id); url = url.replace(':id', user_id);
return axios.get(url) return axios.get(url).then(({ data }) => {
.then(({ data }) => { callback(data);
callback(data); });
});
}; };
// Return users list. Filtered by query // Return users list. Filtered by query
...@@ -682,12 +740,11 @@ UsersSelect.prototype.users = function(query, options, callback) { ...@@ -682,12 +740,11 @@ UsersSelect.prototype.users = function(query, options, callback) {
todo_state_filter: options.todoStateFilter || null, todo_state_filter: options.todoStateFilter || null,
current_user: options.showCurrentUser || null, current_user: options.showCurrentUser || null,
author_id: options.authorId || null, author_id: options.authorId || null,
skip_users: options.skipUsers || null skip_users: options.skipUsers || null,
}; };
return axios.get(url, { params }) return axios.get(url, { params }).then(({ data }) => {
.then(({ data }) => { callback(data);
callback(data); });
});
}; };
UsersSelect.prototype.buildUrl = function(url) { UsersSelect.prototype.buildUrl = function(url) {
......
...@@ -47,16 +47,26 @@ export default class ZenMode { ...@@ -47,16 +47,26 @@ export default class ZenMode {
e.preventDefault(); e.preventDefault();
return $(e.currentTarget).trigger('zen_mode:leave'); return $(e.currentTarget).trigger('zen_mode:leave');
}); });
$(document).on('zen_mode:enter', (function(_this) { $(document).on(
return function(e) { 'zen_mode:enter',
return _this.enter($(e.target).closest('.md-area').find('.zen-backdrop')); (function(_this) {
}; return function(e) {
})(this)); return _this.enter(
$(document).on('zen_mode:leave', (function(_this) { $(e.target)
return function(e) { .closest('.md-area')
return _this.exit(); .find('.zen-backdrop'),
}; );
})(this)); };
})(this),
);
$(document).on(
'zen_mode:leave',
(function(_this) {
return function(e) {
return _this.exit();
};
})(this),
);
$(document).on('keydown', function(e) { $(document).on('keydown', function(e) {
// Esc // Esc
if (e.keyCode === 27) { if (e.keyCode === 27) {
...@@ -93,7 +103,7 @@ export default class ZenMode { ...@@ -93,7 +103,7 @@ export default class ZenMode {
scrollTo(zen_area) { scrollTo(zen_area) {
return $.scrollTo(zen_area, 0, { return $.scrollTo(zen_area, 0, {
offset: -150 offset: -150,
}); });
} }
} }
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