diff options
author | Mike Greiling <mike@pixelcog.com> | 2018-10-26 00:45:03 +0000 |
---|---|---|
committer | Mike Greiling <mike@pixelcog.com> | 2018-10-26 00:45:03 +0000 |
commit | 0efbc126d080892214d312a3ae2549f1fc341cb6 (patch) | |
tree | b7131288bcbc3c5b0160ad55ed4b95523ce71c12 /app/assets | |
parent | 46e3ad4b948dedf67245708493ee17c4429f8bf3 (diff) | |
parent | 679c0048a8f679aad456c02e30486150bbd0d93d (diff) | |
download | gitlab-ce-0efbc126d080892214d312a3ae2549f1fc341cb6.tar.gz |
Merge branch 'master' into 'prettify-all-the-things-4'prettify-all-the-things-4
# Conflicts:
# app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
Diffstat (limited to 'app/assets')
302 files changed, 6168 insertions, 4765 deletions
diff --git a/app/assets/javascripts/activities.js b/app/assets/javascripts/activities.js index de4566bb119..05de970e387 100644 --- a/app/assets/javascripts/activities.js +++ b/app/assets/javascripts/activities.js @@ -6,10 +6,12 @@ import Pager from './pager'; import { localTimeAgo } from './lib/utils/datetime_utility'; export default class Activities { - constructor() { - Pager.init(20, true, false, data => data, this.updateTooltips); + constructor(container = '') { + this.container = container; - $('.event-filter-link').on('click', (e) => { + Pager.init(20, true, false, data => data, this.updateTooltips, this.container); + + $('.event-filter-link').on('click', e => { e.preventDefault(); this.toggleFilter(e.currentTarget); this.reloadActivities(); @@ -22,7 +24,7 @@ export default class Activities { reloadActivities() { $('.content_list').html(''); - Pager.init(20, true, false, data => data, this.updateTooltips); + Pager.init(20, true, false, data => data, this.updateTooltips, this.container); } toggleFilter(sender) { diff --git a/app/assets/javascripts/behaviors/copy_to_clipboard.js b/app/assets/javascripts/behaviors/copy_to_clipboard.js index 00419e80cbb..9a33a060c76 100644 --- a/app/assets/javascripts/behaviors/copy_to_clipboard.js +++ b/app/assets/javascripts/behaviors/copy_to_clipboard.js @@ -51,7 +51,7 @@ export default function initCopyToClipboard() { * the last minute to deconstruct this JSON hash and set the `text/plain` and `text/x-gfm` copy * data types to the intended values. */ - $(document).on('copy', 'body > textarea[readonly]', (e) => { + $(document).on('copy', 'body > textarea[readonly]', e => { const { clipboardData } = e.originalEvent; if (!clipboardData) return; diff --git a/app/assets/javascripts/behaviors/details_behavior.js b/app/assets/javascripts/behaviors/details_behavior.js index 1d63f5baeee..9bdfc21c7e4 100644 --- a/app/assets/javascripts/behaviors/details_behavior.js +++ b/app/assets/javascripts/behaviors/details_behavior.js @@ -2,7 +2,9 @@ import $ from 'jquery'; $(() => { $('body').on('click', '.js-details-target', function target() { - $(this).closest('.js-details-container').toggleClass('open'); + $(this) + .closest('.js-details-container') + .toggleClass('open'); }); // Show details content. Hides link after click. @@ -13,7 +15,9 @@ $(() => { // $('body').on('click', '.js-details-expand', function expand(e) { e.preventDefault(); - $(this).next('.js-details-content').removeClass('hide'); + $(this) + .next('.js-details-content') + .removeClass('hide'); $(this).hide(); const truncatedItem = $(this).siblings('.js-details-short'); diff --git a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js index 0d7e8a5a3cb..fe02096d903 100644 --- a/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js +++ b/app/assets/javascripts/behaviors/markdown/copy_as_gfm.js @@ -34,7 +34,7 @@ const gfmRules = { }, }, AutolinkFilter: { - 'a'(el, text) { + a(el, text) { // Fallback on the regular MarkdownFilter's `a` handler. if (text !== el.getAttribute('href')) return false; @@ -60,7 +60,7 @@ const gfmRules = { }, }, ImageLazyLoadFilter: { - 'img'(el, text) { + img(el, text) { return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`; }, }, @@ -71,7 +71,7 @@ const gfmRules = { return CopyAsGFM.nodeToGFM(videoEl); }, - 'video'(el) { + video(el) { return `![${el.dataset.title}](${el.getAttribute('src')})`; }, }, @@ -118,11 +118,14 @@ const gfmRules = { 'a[name]:not([href]):empty'(el) { return el.outerHTML; }, - 'dl'(el, text) { - let lines = text.replace(/\n\n/g, '\n').trim().split('\n'); + dl(el, text) { + let lines = text + .replace(/\n\n/g, '\n') + .trim() + .split('\n'); // Add two spaces to the front of subsequent list items lines, // or leave the line entirely blank. - lines = lines.map((l) => { + lines = lines.map(l => { const line = l.trim(); if (line.length === 0) return ''; @@ -151,27 +154,30 @@ const gfmRules = { // Prefixes lines with 4 spaces if the code contains triple backticks if (lang === '' && text.match(/^```/gm)) { - return text.split('\n').map((l) => { - const line = l.trim(); - if (line.length === 0) return ''; - - return ` ${line}`; - }).join('\n'); + return text + .split('\n') + .map(l => { + const line = l.trim(); + if (line.length === 0) return ''; + + return ` ${line}`; + }) + .join('\n'); } return `\`\`\`${lang}\n${text}\n\`\`\``; }, 'pre > code'(el, text) { - // Don't wrap code blocks in `` + // Don't wrap code blocks in `` return text; }, }, MarkdownFilter: { - 'br'(el) { + br(el) { // Two spaces at the end of a line are turned into a BR return ' '; }, - 'code'(el, text) { + code(el, text) { let backtickCount = 1; const backtickMatch = text.match(/`+/); if (backtickMatch) { @@ -183,27 +189,31 @@ const gfmRules = { return backticks + spaceOrNoSpace + text.trim() + spaceOrNoSpace + backticks; }, - 'blockquote'(el, text) { - return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n'); + blockquote(el, text) { + return text + .trim() + .split('\n') + .map(s => `> ${s}`.trim()) + .join('\n'); }, - 'img'(el) { + img(el) { const imageSrc = el.src; - const imageUrl = imageSrc && imageSrc !== placeholderImage ? imageSrc : (el.dataset.src || ''); + const imageUrl = imageSrc && imageSrc !== placeholderImage ? imageSrc : el.dataset.src || ''; return `![${el.getAttribute('alt')}](${imageUrl})`; }, 'a.anchor'(el, text) { // Don't render a Markdown link for the anchor link inside a heading return text; }, - 'a'(el, text) { + a(el, text) { return `[${text}](${el.getAttribute('href')})`; }, - 'li'(el, text) { + li(el, text) { const lines = text.trim().split('\n'); const firstLine = `- ${lines.shift()}`; // Add four spaces to the front of subsequent list items lines, // or leave the line entirely blank. - const nextLines = lines.map((s) => { + const nextLines = lines.map(s => { if (s.trim().length === 0) return ''; return ` ${s}`; @@ -211,49 +221,49 @@ const gfmRules = { return `${firstLine}\n${nextLines.join('\n')}`; }, - 'ul'(el, text) { + ul(el, text) { return text; }, - 'ol'(el, text) { + ol(el, text) { // LIs get a `- ` prefix by default, which we replace by `1. ` for ordered lists. - return text.replace(/^- /mg, '1. '); + return text.replace(/^- /gm, '1. '); }, - 'h1'(el, text) { + h1(el, text) { return `# ${text.trim()}\n`; }, - 'h2'(el, text) { + h2(el, text) { return `## ${text.trim()}\n`; }, - 'h3'(el, text) { + h3(el, text) { return `### ${text.trim()}\n`; }, - 'h4'(el, text) { + h4(el, text) { return `#### ${text.trim()}\n`; }, - 'h5'(el, text) { + h5(el, text) { return `##### ${text.trim()}\n`; }, - 'h6'(el, text) { + h6(el, text) { return `###### ${text.trim()}\n`; }, - 'strong'(el, text) { + strong(el, text) { return `**${text}**`; }, - 'em'(el, text) { + em(el, text) { return `_${text}_`; }, - 'del'(el, text) { + del(el, text) { return `~~${text}~~`; }, - 'hr'(el) { + hr(el) { // extra leading \n is to ensure that there is a blank line between // a list followed by an hr, otherwise this breaks old redcarpet rendering return '\n-----\n'; }, - 'p'(el, text) { + p(el, text) { return `${text.trim()}\n`; }, - 'table'(el) { + table(el) { const theadEl = el.querySelector('thead'); const tbodyEl = el.querySelector('tbody'); if (!theadEl || !tbodyEl) return false; @@ -263,8 +273,8 @@ const gfmRules = { return [theadText, tbodyText].join('\n'); }, - 'thead'(el, text) { - const cells = _.map(el.querySelectorAll('th'), (cell) => { + thead(el, text) { + const cells = _.map(el.querySelectorAll('th'), cell => { let chars = CopyAsGFM.nodeToGFM(cell).length + 2; let before = ''; @@ -296,7 +306,7 @@ const gfmRules = { return [text, separatorRow].join('\n'); }, - 'tr'(el) { + tr(el) { const cellEls = el.querySelectorAll('td, th'); if (cellEls.length === 0) return false; @@ -315,8 +325,12 @@ export class CopyAsGFM { const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent); if (isIOS) return; - $(document).on('copy', '.md, .wiki', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); }); - $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); }); + $(document).on('copy', '.md, .wiki', e => { + CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); + }); + $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', e => { + CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); + }); $(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM); } @@ -356,7 +370,7 @@ export class CopyAsGFM { // This will break down when the actual code block contains an uneven // number of backticks, but this is a rare edge case. const backtickMatch = textBefore.match(/`/g); - const insideCodeBlock = backtickMatch && (backtickMatch.length % 2) === 1; + const insideCodeBlock = backtickMatch && backtickMatch.length % 2 === 1; if (insideCodeBlock) { return text; @@ -393,7 +407,9 @@ export class CopyAsGFM { let lineSelector = '.line'; if (target) { - const lineClass = ['left-side', 'right-side'].filter(name => target.classList.contains(name))[0]; + const lineClass = ['left-side', 'right-side'].filter(name => + target.classList.contains(name), + )[0]; if (lineClass) { lineSelector = `.line_content.${lineClass} ${lineSelector}`; } @@ -436,7 +452,8 @@ export class CopyAsGFM { return node.textContent; } - const respectWhitespace = respectWhitespaceParam || (node.nodeName === 'PRE' || node.nodeName === 'CODE'); + const respectWhitespace = + respectWhitespaceParam || (node.nodeName === 'PRE' || node.nodeName === 'CODE'); const text = this.innerGFM(node, respectWhitespace); diff --git a/app/assets/javascripts/behaviors/markdown/render_math.js b/app/assets/javascripts/behaviors/markdown/render_math.js index eb4e59d12b1..a68936d79e2 100644 --- a/app/assets/javascripts/behaviors/markdown/render_math.js +++ b/app/assets/javascripts/behaviors/markdown/render_math.js @@ -32,7 +32,9 @@ export default function renderMath($els) { Promise.all([ import(/* webpackChunkName: 'katex' */ 'katex'), import(/* webpackChunkName: 'katex' */ 'katex/dist/katex.min.css'), - ]).then(([katex]) => { - renderWithKaTeX($els, katex); - }).catch(() => flash(__('An error occurred while rendering KaTeX'))); + ]) + .then(([katex]) => { + renderWithKaTeX($els, katex); + }) + .catch(() => flash(__('An error occurred while rendering KaTeX'))); } diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js index 56b1896e9f1..720f30e18e6 100644 --- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js +++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js @@ -17,41 +17,43 @@ import flash from '~/flash'; export default function renderMermaid($els) { if (!$els.length) return; - import(/* webpackChunkName: 'mermaid' */ 'blackst0ne-mermaid').then((mermaid) => { - mermaid.initialize({ - // mermaid core options - mermaid: { - startOnLoad: false, - }, - // mermaidAPI options - theme: 'neutral', - }); + import(/* webpackChunkName: 'mermaid' */ 'mermaid') + .then(mermaid => { + mermaid.initialize({ + // mermaid core options + mermaid: { + startOnLoad: false, + }, + // mermaidAPI options + theme: 'neutral', + }); - $els.each((i, el) => { - const source = el.textContent; + $els.each((i, el) => { + const source = el.textContent; - // Remove any extra spans added by the backend syntax highlighting. - Object.assign(el, { textContent: source }); + // Remove any extra spans added by the backend syntax highlighting. + Object.assign(el, { textContent: source }); - mermaid.init(undefined, el, (id) => { - const svg = document.getElementById(id); + mermaid.init(undefined, el, id => { + const svg = document.getElementById(id); - svg.classList.add('mermaid'); + svg.classList.add('mermaid'); - // pre > code > svg - svg.closest('pre').replaceWith(svg); + // pre > code > svg + svg.closest('pre').replaceWith(svg); - // We need to add the original source into the DOM to allow Copy-as-GFM - // to access it. - const sourceEl = document.createElement('text'); - sourceEl.classList.add('source'); - sourceEl.setAttribute('display', 'none'); - sourceEl.textContent = source; + // We need to add the original source into the DOM to allow Copy-as-GFM + // to access it. + const sourceEl = document.createElement('text'); + sourceEl.classList.add('source'); + sourceEl.setAttribute('display', 'none'); + sourceEl.textContent = source; - svg.appendChild(sourceEl); + svg.appendChild(sourceEl); + }); }); + }) + .catch(err => { + flash(`Can't load mermaid module: ${err}`); }); - }).catch((err) => { - flash(`Can't load mermaid module: ${err}`); - }); } diff --git a/app/assets/javascripts/behaviors/preview_markdown.js b/app/assets/javascripts/behaviors/preview_markdown.js index 0964baf8954..35f1bb6b080 100644 --- a/app/assets/javascripts/behaviors/preview_markdown.js +++ b/app/assets/javascripts/behaviors/preview_markdown.js @@ -26,7 +26,7 @@ MarkdownPreview.prototype.emptyMessage = 'Nothing to preview.'; MarkdownPreview.prototype.ajaxCache = {}; -MarkdownPreview.prototype.showPreview = function ($form) { +MarkdownPreview.prototype.showPreview = function($form) { var mdText; var markdownVersion; var url; @@ -44,34 +44,40 @@ MarkdownPreview.prototype.showPreview = function ($form) { this.hideReferencedUsers($form); } else { preview.addClass('md-preview-loading').text('Loading...'); - this.fetchMarkdownPreview(mdText, url, (function (response) { - var body; - if (response.body.length > 0) { - ({ body } = response); - } else { - body = this.emptyMessage; - } - - preview.removeClass('md-preview-loading').html(body); - preview.renderGFM(); - this.renderReferencedUsers(response.references.users, $form); - - if (response.references.commands) { - this.renderReferencedCommands(response.references.commands, $form); - } - }).bind(this)); + this.fetchMarkdownPreview( + mdText, + url, + function(response) { + var body; + if (response.body.length > 0) { + ({ body } = response); + } else { + body = this.emptyMessage; + } + + preview.removeClass('md-preview-loading').html(body); + preview.renderGFM(); + this.renderReferencedUsers(response.references.users, $form); + + if (response.references.commands) { + this.renderReferencedCommands(response.references.commands, $form); + } + }.bind(this), + ); } }; -MarkdownPreview.prototype.versionedPreviewPath = function (markdownPreviewPath, markdownVersion) { +MarkdownPreview.prototype.versionedPreviewPath = function(markdownPreviewPath, markdownVersion) { if (typeof markdownVersion === 'undefined') { return markdownPreviewPath; } - return `${markdownPreviewPath}${markdownPreviewPath.indexOf('?') === -1 ? '?' : '&'}markdown_version=${markdownVersion}`; + return `${markdownPreviewPath}${ + markdownPreviewPath.indexOf('?') === -1 ? '?' : '&' + }markdown_version=${markdownVersion}`; }; -MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) { +MarkdownPreview.prototype.fetchMarkdownPreview = function(text, url, success) { if (!url) { return; } @@ -79,24 +85,25 @@ MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) { success(this.ajaxCache.response); return; } - axios.post(url, { - text, - }) - .then(({ data }) => { - this.ajaxCache = { - text: text, - response: data, - }; - success(data); - }) - .catch(() => flash(__('An error occurred while fetching markdown preview'))); + axios + .post(url, { + text, + }) + .then(({ data }) => { + this.ajaxCache = { + text: text, + response: data, + }; + success(data); + }) + .catch(() => flash(__('An error occurred while fetching markdown preview'))); }; -MarkdownPreview.prototype.hideReferencedUsers = function ($form) { +MarkdownPreview.prototype.hideReferencedUsers = function($form) { $form.find('.referenced-users').hide(); }; -MarkdownPreview.prototype.renderReferencedUsers = function (users, $form) { +MarkdownPreview.prototype.renderReferencedUsers = function(users, $form) { var referencedUsers; referencedUsers = $form.find('.referenced-users'); if (referencedUsers.length) { @@ -109,11 +116,11 @@ MarkdownPreview.prototype.renderReferencedUsers = function (users, $form) { } }; -MarkdownPreview.prototype.hideReferencedCommands = function ($form) { +MarkdownPreview.prototype.hideReferencedCommands = function($form) { $form.find('.referenced-commands').hide(); }; -MarkdownPreview.prototype.renderReferencedCommands = function (commands, $form) { +MarkdownPreview.prototype.renderReferencedCommands = function(commands, $form) { var referencedCommands; referencedCommands = $form.find('.referenced-commands'); if (commands.length > 0) { @@ -132,14 +139,14 @@ writeButtonSelector = '.js-md-write-button'; lastTextareaPreviewed = null; const markdownToolbar = $('.md-header-toolbar'); -$.fn.setupMarkdownPreview = function () { +$.fn.setupMarkdownPreview = function() { var $form = $(this); - $form.find('textarea.markdown-area').on('input', function () { + $form.find('textarea.markdown-area').on('input', function() { markdownPreview.hideReferencedUsers($form); }); }; -$(document).on('markdown-preview:show', function (e, $form) { +$(document).on('markdown-preview:show', function(e, $form) { if (!$form) { return; } @@ -148,8 +155,14 @@ $(document).on('markdown-preview:show', function (e, $form) { lastTextareaHeight = lastTextareaPreviewed.height(); // toggle tabs - $form.find(writeButtonSelector).parent().removeClass('active'); - $form.find(previewButtonSelector).parent().addClass('active'); + $form + .find(writeButtonSelector) + .parent() + .removeClass('active'); + $form + .find(previewButtonSelector) + .parent() + .addClass('active'); // toggle content $form.find('.md-write-holder').hide(); @@ -158,7 +171,7 @@ $(document).on('markdown-preview:show', function (e, $form) { markdownPreview.showPreview($form); }); -$(document).on('markdown-preview:hide', function (e, $form) { +$(document).on('markdown-preview:hide', function(e, $form) { if (!$form) { return; } @@ -169,8 +182,14 @@ $(document).on('markdown-preview:hide', function (e, $form) { } // toggle tabs - $form.find(writeButtonSelector).parent().addClass('active'); - $form.find(previewButtonSelector).parent().removeClass('active'); + $form + .find(writeButtonSelector) + .parent() + .addClass('active'); + $form + .find(previewButtonSelector) + .parent() + .removeClass('active'); // toggle content $form.find('.md-write-holder').show(); @@ -181,7 +200,7 @@ $(document).on('markdown-preview:hide', function (e, $form) { markdownPreview.hideReferencedCommands($form); }); -$(document).on('markdown-preview:toggle', function (e, keyboardEvent) { +$(document).on('markdown-preview:toggle', function(e, keyboardEvent) { var $target; $target = $(keyboardEvent.target); if ($target.is('textarea.markdown-area')) { @@ -194,14 +213,14 @@ $(document).on('markdown-preview:toggle', function (e, keyboardEvent) { } }); -$(document).on('click', previewButtonSelector, function (e) { +$(document).on('click', previewButtonSelector, function(e) { var $form; e.preventDefault(); $form = $(this).closest('form'); $(document).triggerHandler('markdown-preview:show', [$form]); }); -$(document).on('click', writeButtonSelector, function (e) { +$(document).on('click', writeButtonSelector, function(e) { var $form; e.preventDefault(); $form = $(this).closest('form'); diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js index b6e2781773c..c1ea67f9293 100644 --- a/app/assets/javascripts/behaviors/quick_submit.js +++ b/app/assets/javascripts/behaviors/quick_submit.js @@ -28,7 +28,7 @@ function keyCodeIs(e, keyCode) { return e.keyCode === keyCode; } -$(document).on('keydown.quick_submit', '.js-quick-submit', (e) => { +$(document).on('keydown.quick_submit', '.js-quick-submit', e => { // Enter if (!keyCodeIs(e, 13)) { return; @@ -55,23 +55,25 @@ $(document).on('keydown.quick_submit', '.js-quick-submit', (e) => { // If the user tabs to a submit button on a `js-quick-submit` form, display a // tooltip to let them know they could've used the hotkey -$(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', function displayTooltip(e) { - // Tab - if (!keyCodeIs(e, 9)) { - return; - } +$(document).on( + 'keyup.quick_submit', + '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', + function displayTooltip(e) { + // Tab + if (!keyCodeIs(e, 9)) { + return; + } - const $this = $(this); - const title = isMac() ? - 'You can also press ⌘-Enter' : - 'You can also press Ctrl-Enter'; + const $this = $(this); + const title = isMac() ? 'You can also press ⌘-Enter' : 'You can also press Ctrl-Enter'; - $this.tooltip({ - container: 'body', - html: 'true', - placement: 'top', - title, - trigger: 'manual', - }); - $this.tooltip('show').one('blur click', () => $this.tooltip('hide')); -}); + $this.tooltip({ + container: 'body', + html: 'true', + placement: 'top', + title, + trigger: 'manual', + }); + $this.tooltip('show').one('blur click', () => $this.tooltip('hide')); + }, +); diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js index a8b6dbf0948..c09d9ccddd6 100644 --- a/app/assets/javascripts/behaviors/requires_input.js +++ b/app/assets/javascripts/behaviors/requires_input.js @@ -18,7 +18,8 @@ import '../commons/bootstrap'; $.fn.requiresInput = function requiresInput() { const $form = $(this); const $button = $('button[type=submit], input[type=submit]', $form); - const fieldSelector = 'input[required=required], select[required=required], textarea[required=required]'; + const fieldSelector = + 'input[required=required], select[required=required], textarea[required=required]'; function requireInput() { // Collect the input values of *all* required fields diff --git a/app/assets/javascripts/behaviors/secret_values.js b/app/assets/javascripts/behaviors/secret_values.js index 0d6e0dbefcc..f6bf62d734e 100644 --- a/app/assets/javascripts/behaviors/secret_values.js +++ b/app/assets/javascripts/behaviors/secret_values.js @@ -32,16 +32,18 @@ export default class SecretValues { updateDom(isRevealed) { const values = this.container.querySelectorAll(this.valueSelector); - values.forEach((value) => { + values.forEach(value => { value.classList.toggle('hide', !isRevealed); }); const placeholders = this.container.querySelectorAll(this.placeholderSelector); - placeholders.forEach((placeholder) => { + placeholders.forEach(placeholder => { placeholder.classList.toggle('hide', isRevealed); }); - this.revealButton.textContent = isRevealed ? n__('Hide value', 'Hide values', values.length) : n__('Reveal value', 'Reveal values', values.length); + this.revealButton.textContent = isRevealed + ? n__('Hide value', 'Hide values', values.length) + : n__('Reveal value', 'Reveal values', values.length); this.revealButton.dataset.secretRevealStatus = isRevealed; } } diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js index 6719bfd6d22..8b5a3c1c69d 100644 --- a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js @@ -88,22 +88,24 @@ export default class Shortcuts { return null; } - return axios.get(gon.shortcuts_path, { - responseType: 'text', - }).then(({ data }) => { - $.globalEval(data); - - if (location && location.length > 0) { - const results = []; - for (let i = 0, len = location.length; i < len; i += 1) { - results.push($(location[i]).show()); + return axios + .get(gon.shortcuts_path, { + responseType: 'text', + }) + .then(({ data }) => { + $.globalEval(data); + + if (location && location.length > 0) { + const results = []; + for (let i = 0, len = location.length; i < len; i += 1) { + results.push($(location[i]).show()); + } + return results; } - return results; - } - $('.hidden-shortcut').show(); - return $('.js-more-help-button').remove(); - }); + $('.hidden-shortcut').show(); + return $('.js-more-help-button').remove(); + }); } focusFilter(e) { diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index 4446be0e52f..ef8b8788abf 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -18,9 +18,7 @@ $(() => { .toggleClass('fa-chevron-up', toggleState) .toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined); - $container - .find('.js-toggle-content') - .toggle(toggleState); + $container.find('.js-toggle-content').toggle(toggleState); } $('body').on('click', '.js-toggle-button', function toggleButton(e) { diff --git a/app/assets/javascripts/blob/3d_viewer/index.js b/app/assets/javascripts/blob/3d_viewer/index.js index 1bdf1aeb76c..2d4f45cc365 100644 --- a/app/assets/javascripts/blob/3d_viewer/index.js +++ b/app/assets/javascripts/blob/3d_viewer/index.js @@ -18,12 +18,7 @@ export default class Renderer { this.loader = new STLLoader(); this.fov = 45; - this.camera = new THREE.PerspectiveCamera( - this.fov, - this.width / this.height, - 1, - 1000, - ); + this.camera = new THREE.PerspectiveCamera(this.fov, this.width / this.height, 1, 1000); this.scene = new THREE.Scene(); @@ -35,10 +30,7 @@ export default class Renderer { this.setupLight(); // Set up OrbitControls - this.controls = new OrbitControls( - this.camera, - this.renderer.domElement, - ); + this.controls = new OrbitControls(this.camera, this.renderer.domElement); this.controls.minDistance = 5; this.controls.maxDistance = 30; this.controls.enableKeys = false; @@ -51,47 +43,32 @@ export default class Renderer { antialias: true, }); - this.renderer.setClearColor(0xFFFFFF); + this.renderer.setClearColor(0xffffff); this.renderer.setPixelRatio(window.devicePixelRatio); - this.renderer.setSize( - this.width, - this.height, - ); + this.renderer.setSize(this.width, this.height); } setupLight() { // Point light illuminates the object - const pointLight = new THREE.PointLight( - 0xFFFFFF, - 2, - 0, - ); + const pointLight = new THREE.PointLight(0xffffff, 2, 0); pointLight.castShadow = true; this.camera.add(pointLight); // Ambient light illuminates the scene - const ambientLight = new THREE.AmbientLight( - 0xFFFFFF, - 1, - ); + const ambientLight = new THREE.AmbientLight(0xffffff, 1); this.scene.add(ambientLight); } setupGrid() { - this.grid = new THREE.GridHelper( - 20, - 20, - 0x000000, - 0x000000, - ); + this.grid = new THREE.GridHelper(20, 20, 0x000000, 0x000000); this.scene.add(this.grid); } loadFile() { - this.loader.load(this.container.dataset.endpoint, (geo) => { + this.loader.load(this.container.dataset.endpoint, geo => { const obj = new MeshObject(geo); this.objects.push(obj); @@ -116,30 +93,23 @@ export default class Renderer { } render() { - this.renderer.render( - this.scene, - this.camera, - ); + this.renderer.render(this.scene, this.camera); requestAnimationFrame(this.renderWrapper); } changeObjectMaterials(type) { - this.objects.forEach((obj) => { + this.objects.forEach(obj => { obj.changeMaterial(type); }); } setDefaultCameraPosition() { const obj = this.objects[0]; - const radius = (obj.geometry.boundingSphere.radius / 1.5); - const dist = radius / (Math.sin((this.fov * (Math.PI / 180)) / 2)); - - this.camera.position.set( - 0, - dist + 1, - dist, - ); + const radius = obj.geometry.boundingSphere.radius / 1.5; + const dist = radius / Math.sin((this.fov * (Math.PI / 180)) / 2); + + this.camera.position.set(0, dist + 1, dist); this.camera.lookAt(this.grid); this.controls.update(); diff --git a/app/assets/javascripts/blob/3d_viewer/mesh_object.js b/app/assets/javascripts/blob/3d_viewer/mesh_object.js index 96758884abf..cb7fcff8674 100644 --- a/app/assets/javascripts/blob/3d_viewer/mesh_object.js +++ b/app/assets/javascripts/blob/3d_viewer/mesh_object.js @@ -1,10 +1,6 @@ -import { - Matrix4, - MeshLambertMaterial, - Mesh, -} from 'three/build/three.module'; +import { Matrix4, MeshLambertMaterial, Mesh } from 'three/build/three.module'; -const defaultColor = 0xE24329; +const defaultColor = 0xe24329; const materials = { default: new MeshLambertMaterial({ color: defaultColor, @@ -17,10 +13,7 @@ const materials = { export default class MeshObject extends Mesh { constructor(geo) { - super( - geo, - materials.default, - ); + super(geo, materials.default); this.geometry.computeBoundingSphere(); @@ -29,13 +22,7 @@ export default class MeshObject extends Mesh { if (this.geometry.boundingSphere.radius > 4) { const scale = 4 / this.geometry.boundingSphere.radius; - this.geometry.applyMatrix( - new Matrix4().makeScale( - scale, - scale, - scale, - ), - ); + this.geometry.applyMatrix(new Matrix4().makeScale(scale, scale, scale)); this.geometry.computeBoundingSphere(); this.position.x = -this.geometry.boundingSphere.center.x; diff --git a/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js b/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js index 7986287f7e7..75777b910ca 100644 --- a/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js +++ b/app/assets/javascripts/blob/balsamiq/balsamiq_viewer.js @@ -42,7 +42,7 @@ class BalsamiqViewer { this.initDatabase(loadEvent.target.response); const previews = this.getPreviews(); - previews.forEach((preview) => { + previews.forEach(preview => { const renderedPreview = this.renderPreview(preview); container.appendChild(renderedPreview); diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js index ff1739b1679..cd3251ad1ca 100644 --- a/app/assets/javascripts/blob/blob_file_dropzone.js +++ b/app/assets/javascripts/blob/blob_file_dropzone.js @@ -41,39 +41,45 @@ export default class BlobFileDropzone { addRemoveLinks: true, previewsContainer: '.dropzone-previews', headers: csrf.headers, - init: function () { - this.on('addedfile', function () { + init: function() { + this.on('addedfile', function() { toggleLoading(submitButton, submitButtonLoadingIcon, false); dropzoneMessage.addClass(HIDDEN_CLASS); - $('.dropzone-alerts').html('').hide(); + $('.dropzone-alerts') + .html('') + .hide(); }); - this.on('removedfile', function () { + this.on('removedfile', function() { toggleLoading(submitButton, submitButtonLoadingIcon, false); dropzoneMessage.removeClass(HIDDEN_CLASS); }); - this.on('success', function (header, response) { + this.on('success', function(header, response) { $('#modal-upload-blob').modal('hide'); visitUrl(response.filePath); }); - this.on('maxfilesexceeded', function (file) { + this.on('maxfilesexceeded', function(file) { dropzoneMessage.addClass(HIDDEN_CLASS); this.removeFile(file); }); - this.on('sending', function (file, xhr, formData) { + this.on('sending', function(file, xhr, formData) { formData.append('branch_name', form.find('.js-branch-name').val()); formData.append('create_merge_request', form.find('.js-create-merge-request').val()); formData.append('commit_message', form.find('.js-commit-message').val()); }); }, // Override behavior of adding error underneath preview - error: function (file, errorMessage) { - const stripped = $('<div/>').html(errorMessage).text(); - $('.dropzone-alerts').html(`Error uploading file: "${stripped}"`).show(); + error: function(file, errorMessage) { + const stripped = $('<div/>') + .html(errorMessage) + .text(); + $('.dropzone-alerts') + .html(`Error uploading file: "${stripped}"`) + .show(); this.removeFile(file); }, }); - submitButton.on('click', (e) => { + submitButton.on('click', e => { e.preventDefault(); e.stopPropagation(); if (dropzone[0].dropzone.getQueuedFiles().length === 0) { diff --git a/app/assets/javascripts/blob/blob_line_permalink_updater.js b/app/assets/javascripts/blob/blob_line_permalink_updater.js index d36d9f0de2d..62f0a56ed75 100644 --- a/app/assets/javascripts/blob/blob_line_permalink_updater.js +++ b/app/assets/javascripts/blob/blob_line_permalink_updater.js @@ -2,17 +2,19 @@ import { getLocationHash } from '../lib/utils/url_utility'; const lineNumberRe = /^L[0-9]+/; -const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => { +const updateLineNumbersOnBlobPermalinks = linksToUpdate => { const hash = getLocationHash(); if (hash && lineNumberRe.test(hash)) { const hashUrlString = `#${hash}`; - [].concat(Array.prototype.slice.call(linksToUpdate)).forEach((permalinkButton) => { - const baseHref = permalinkButton.getAttribute('data-original-href') || (() => { - const href = permalinkButton.getAttribute('href'); - permalinkButton.setAttribute('data-original-href', href); - return href; - })(); + [].concat(Array.prototype.slice.call(linksToUpdate)).forEach(permalinkButton => { + const baseHref = + permalinkButton.getAttribute('data-original-href') || + (() => { + const href = permalinkButton.getAttribute('href'); + permalinkButton.setAttribute('data-original-href', href); + return href; + })(); permalinkButton.setAttribute('href', `${baseHref}${hashUrlString}`); }); } @@ -26,7 +28,7 @@ function BlobLinePermalinkUpdater(blobContentHolder, lineNumberSelector, element }, 0); }; - blobContentHolder.addEventListener('click', (e) => { + blobContentHolder.addEventListener('click', e => { if (e.target.matches(lineNumberSelector)) { updateBlameAndBlobPermalinkCb(); } diff --git a/app/assets/javascripts/blob/file_template_selector.js b/app/assets/javascripts/blob/file_template_selector.js index 02228434a29..476901aae75 100644 --- a/app/assets/javascripts/blob/file_template_selector.js +++ b/app/assets/javascripts/blob/file_template_selector.js @@ -45,15 +45,11 @@ export default class FileTemplateSelector { } renderLoading() { - this.$loadingIcon - .addClass('fa-spinner fa-spin') - .removeClass('fa-chevron-down'); + this.$loadingIcon.addClass('fa-spinner fa-spin').removeClass('fa-chevron-down'); } renderLoaded() { - this.$loadingIcon - .addClass('fa-chevron-down') - .removeClass('fa-spinner fa-spin'); + this.$loadingIcon.addClass('fa-chevron-down').removeClass('fa-spinner fa-spin'); } reportSelection(options) { diff --git a/app/assets/javascripts/blob/notebook/index.js b/app/assets/javascripts/blob/notebook/index.js index 6f1350e80fc..071022a9a75 100644 --- a/app/assets/javascripts/blob/notebook/index.js +++ b/app/assets/javascripts/blob/notebook/index.js @@ -40,13 +40,14 @@ export default () => { }, methods: { loadFile() { - axios.get(el.dataset.endpoint) + axios + .get(el.dataset.endpoint) .then(res => res.data) - .then((data) => { + .then(data => { this.json = data; this.loading = false; }) - .catch((e) => { + .catch(e => { if (e.status !== 200) { this.loadError = true; } diff --git a/app/assets/javascripts/blob/sketch/index.js b/app/assets/javascripts/blob/sketch/index.js index 13318c58006..57c1baa9886 100644 --- a/app/assets/javascripts/blob/sketch/index.js +++ b/app/assets/javascripts/blob/sketch/index.js @@ -13,7 +13,7 @@ export default class SketchLoader { return this.getZipFile() .then(data => JSZip.loadAsync(data)) .then(asyncResult => asyncResult.files['previews/preview.png'].async('uint8array')) - .then((content) => { + .then(content => { const url = window.URL || window.webkitURL; const blob = new Blob([new Uint8Array(content)], { type: 'image/png', diff --git a/app/assets/javascripts/blob/stl_viewer.js b/app/assets/javascripts/blob/stl_viewer.js index 339906adc34..f129b6e631e 100644 --- a/app/assets/javascripts/blob/stl_viewer.js +++ b/app/assets/javascripts/blob/stl_viewer.js @@ -3,8 +3,8 @@ import Renderer from './3d_viewer'; export default () => { const viewer = new Renderer(document.getElementById('js-stl-viewer')); - [].slice.call(document.querySelectorAll('.js-material-changer')).forEach((el) => { - el.addEventListener('click', (e) => { + [].slice.call(document.querySelectorAll('.js-material-changer')).forEach(el => { + el.addEventListener('click', e => { const { target } = e; e.preventDefault(); diff --git a/app/assets/javascripts/blob/template_selector.js b/app/assets/javascripts/blob/template_selector.js index 9db1fa70ffb..37e348d93d3 100644 --- a/app/assets/javascripts/blob/template_selector.js +++ b/app/assets/javascripts/blob/template_selector.js @@ -81,14 +81,10 @@ export default class TemplateSelector { } startLoadingSpinner() { - this.$dropdownIcon - .addClass('fa-spinner fa-spin') - .removeClass('fa-chevron-down'); + this.$dropdownIcon.addClass('fa-spinner fa-spin').removeClass('fa-chevron-down'); } stopLoadingSpinner() { - this.$dropdownIcon - .addClass('fa-chevron-down') - .removeClass('fa-spinner fa-spin'); + this.$dropdownIcon.addClass('fa-chevron-down').removeClass('fa-spinner fa-spin'); } } diff --git a/app/assets/javascripts/blob/template_selectors/license_selector.js b/app/assets/javascripts/blob/template_selectors/license_selector.js index ac1fe95eee5..d01ab9257d6 100644 --- a/app/assets/javascripts/blob/template_selectors/license_selector.js +++ b/app/assets/javascripts/blob/template_selectors/license_selector.js @@ -22,7 +22,7 @@ export default class BlobLicenseSelector extends FileTemplateSelector { search: { fields: ['name'], }, - clicked: (options) => { + clicked: options => { const { e } = options; const el = options.$el; const query = options.selectedObj; diff --git a/app/assets/javascripts/blob/template_selectors/type_selector.js b/app/assets/javascripts/blob/template_selectors/type_selector.js index a09381014a7..db3c144cbe3 100644 --- a/app/assets/javascripts/blob/template_selectors/type_selector.js +++ b/app/assets/javascripts/blob/template_selectors/type_selector.js @@ -21,5 +21,4 @@ export default class FileTemplateTypeSelector extends FileTemplateSelector { text: item => item.name, }); } - } diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js index 5485248cfaf..befa1dc455f 100644 --- a/app/assets/javascripts/blob/viewer/index.js +++ b/app/assets/javascripts/blob/viewer/index.js @@ -22,9 +22,8 @@ export default class BlobViewer { const viewer = document.querySelector('.blob-viewer[data-type="rich"]'); if (!viewer || !viewer.dataset.richType) return; - const initViewer = promise => promise - .then(module => module.default(viewer)) - .catch((error) => { + const initViewer = promise => + promise.then(module => module.default(viewer)).catch(error => { Flash('Error loading file viewer.'); throw error; }); @@ -79,10 +78,9 @@ export default class BlobViewer { initBindings() { if (this.switcherBtns.length) { - Array.from(this.switcherBtns) - .forEach((el) => { - el.addEventListener('click', this.switchViewHandler.bind(this)); - }); + Array.from(this.switcherBtns).forEach(el => { + el.addEventListener('click', this.switchViewHandler.bind(this)); + }); } if (this.copySourceBtn) { @@ -109,7 +107,10 @@ export default class BlobViewer { this.copySourceBtn.setAttribute('title', 'Copy source to clipboard'); this.copySourceBtn.classList.remove('disabled'); } else if (this.activeViewer === this.simpleViewer) { - this.copySourceBtn.setAttribute('title', 'Wait for the source to load to copy it to the clipboard'); + this.copySourceBtn.setAttribute( + 'title', + 'Wait for the source to load to copy it to the clipboard', + ); this.copySourceBtn.classList.add('disabled'); } else { this.copySourceBtn.setAttribute('title', 'Switch to the source to copy it to the clipboard'); @@ -147,15 +148,15 @@ export default class BlobViewer { this.toggleCopyButtonState(); BlobViewer.loadViewer(newViewer) - .then((viewer) => { - $(viewer).renderGFM(); + .then(viewer => { + $(viewer).renderGFM(); - this.$fileHolder.trigger('highlight:line'); - handleLocationHash(); + this.$fileHolder.trigger('highlight:line'); + handleLocationHash(); - this.toggleCopyButtonState(); - }) - .catch(() => new Flash('Error loading viewer')); + this.toggleCopyButtonState(); + }) + .catch(() => new Flash('Error loading viewer')); } static loadViewer(viewerParam) { @@ -168,12 +169,11 @@ export default class BlobViewer { viewer.setAttribute('data-loading', 'true'); - return axios.get(url) - .then(({ data }) => { - viewer.innerHTML = data.html; - viewer.setAttribute('data-loaded', 'true'); + return axios.get(url).then(({ data }) => { + viewer.innerHTML = data.html; + viewer.setAttribute('data-loaded', 'true'); - return viewer; - }); + return viewer; + }); } } diff --git a/app/assets/javascripts/breadcrumb.js b/app/assets/javascripts/breadcrumb.js index 1474d93dde6..a37838694ec 100644 --- a/app/assets/javascripts/breadcrumb.js +++ b/app/assets/javascripts/breadcrumb.js @@ -1,6 +1,6 @@ import $ from 'jquery'; -export const addTooltipToEl = (el) => { +export const addTooltipToEl = el => { const textEl = el.querySelector('.js-breadcrumb-item-text'); if (textEl && textEl.scrollWidth > textEl.offsetWidth) { @@ -14,17 +14,18 @@ export default () => { const breadcrumbs = document.querySelector('.js-breadcrumbs-list'); if (breadcrumbs) { - const topLevelLinks = [...breadcrumbs.children].filter(el => !el.classList.contains('dropdown')) + const topLevelLinks = [...breadcrumbs.children] + .filter(el => !el.classList.contains('dropdown')) .map(el => el.querySelector('a')) .filter(el => el); const $expander = $('.js-breadcrumbs-collapsed-expander'); topLevelLinks.forEach(el => addTooltipToEl(el)); - $expander.closest('.dropdown') - .on('show.bs.dropdown hide.bs.dropdown', (e) => { - $('.js-breadcrumbs-collapsed-expander', e.currentTarget).toggleClass('open') - .tooltip('hide'); - }); + $expander.closest('.dropdown').on('show.bs.dropdown hide.bs.dropdown', e => { + $('.js-breadcrumbs-collapsed-expander', e.currentTarget) + .toggleClass('open') + .tooltip('hide'); + }); } }; diff --git a/app/assets/javascripts/build_artifacts.js b/app/assets/javascripts/build_artifacts.js index e338376fcaa..97a1645aa51 100644 --- a/app/assets/javascripts/build_artifacts.js +++ b/app/assets/javascripts/build_artifacts.js @@ -12,16 +12,16 @@ export default class BuildArtifacts { } // eslint-disable-next-line class-methods-use-this disablePropagation() { - $('.top-block').on('click', '.download', function (e) { + $('.top-block').on('click', '.download', function(e) { return e.stopPropagation(); }); - return $('.tree-holder').on('click', 'tr[data-link] a', function (e) { + return $('.tree-holder').on('click', 'tr[data-link] a', function(e) { return e.stopImmediatePropagation(); }); } // eslint-disable-next-line class-methods-use-this setupEntryClick() { - return $('.tree-holder').on('click', 'tr[data-link]', function () { + return $('.tree-holder').on('click', 'tr[data-link]', function() { visitUrl(this.dataset.link, convertPermissionToBoolean(this.dataset.externalLink)); }); } @@ -37,11 +37,15 @@ export default class BuildArtifacts { // We want the tooltip to show if you hover anywhere on the row // But be placed below and in the middle of the file name $('.js-artifact-tree-row') - .on('mouseenter', (e) => { - $(e.currentTarget).find('.js-artifact-tree-tooltip').tooltip('show'); + .on('mouseenter', e => { + $(e.currentTarget) + .find('.js-artifact-tree-tooltip') + .tooltip('show'); }) - .on('mouseleave', (e) => { - $(e.currentTarget).find('.js-artifact-tree-tooltip').tooltip('hide'); + .on('mouseleave', e => { + $(e.currentTarget) + .find('.js-artifact-tree-tooltip') + .tooltip('hide'); }); } } diff --git a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js index b33adff609f..1089d0a72d3 100644 --- a/app/assets/javascripts/ci_variable_list/ajax_variable_list.js +++ b/app/assets/javascripts/ci_variable_list/ajax_variable_list.js @@ -7,11 +7,13 @@ import statusCodes from '../lib/utils/http_status'; import VariableList from './ci_variable_list'; function generateErrorBoxContent(errors) { - const errorList = [].concat(errors).map(errorString => ` + const errorList = [].concat(errors).map( + errorString => ` <li> ${_.escape(errorString)} </li> - `); + `, + ); return ` <p> @@ -25,13 +27,7 @@ function generateErrorBoxContent(errors) { // Used for the variable list on CI/CD projects/groups settings page export default class AjaxVariableList { - constructor({ - container, - saveButton, - errorBox, - formField = 'variables', - saveEndpoint, - }) { + constructor({ container, saveButton, errorBox, formField = 'variables', saveEndpoint }) { this.container = container; this.saveButton = saveButton; this.errorBox = errorBox; @@ -58,18 +54,21 @@ export default class AjaxVariableList { // to match it up in `updateRowsWithPersistedVariables` this.variableList.toggleEnableRow(false); - return axios.patch(this.saveEndpoint, { - variables_attributes: this.variableList.getAllData(), - }, { - // We want to be able to process the `res.data` from a 400 error response - // and print the validation messages such as duplicate variable keys - validateStatus: status => ( - status >= statusCodes.OK && - status < statusCodes.MULTIPLE_CHOICES - ) || - status === statusCodes.BAD_REQUEST, - }) - .then((res) => { + return axios + .patch( + this.saveEndpoint, + { + variables_attributes: this.variableList.getAllData(), + }, + { + // We want to be able to process the `res.data` from a 400 error response + // and print the validation messages such as duplicate variable keys + validateStatus: status => + (status >= statusCodes.OK && status < statusCodes.MULTIPLE_CHOICES) || + status === statusCodes.BAD_REQUEST, + }, + ) + .then(res => { loadingIcon.classList.toggle('hide', true); this.variableList.toggleEnableRow(true); @@ -90,18 +89,21 @@ export default class AjaxVariableList { } updateRowsWithPersistedVariables(persistedVariables = []) { - const persistedVariableMap = [].concat(persistedVariables).reduce((variableMap, variable) => ({ - ...variableMap, - [variable.key]: variable, - }), {}); + const persistedVariableMap = [].concat(persistedVariables).reduce( + (variableMap, variable) => ({ + ...variableMap, + [variable.key]: variable, + }), + {}, + ); - this.container.querySelectorAll('.js-row').forEach((row) => { + this.container.querySelectorAll('.js-row').forEach(row => { // If we submitted a row that was destroyed, remove it so we don't try // to destroy it again which would cause a BE error const destroyInput = row.querySelector('.js-ci-variable-input-destroy'); if (convertPermissionToBoolean(destroyInput.value)) { row.remove(); - // Update the ID input so any future edits and `_destroy` will apply on the BE + // Update the ID input so any future edits and `_destroy` will apply on the BE } else { const key = row.querySelector('.js-ci-variable-input-key').value; const persistedVariable = persistedVariableMap[key]; diff --git a/app/assets/javascripts/ci_variable_list/ci_variable_list.js b/app/assets/javascripts/ci_variable_list/ci_variable_list.js index 47efb3a8cee..7bdc18ce03e 100644 --- a/app/assets/javascripts/ci_variable_list/ci_variable_list.js +++ b/app/assets/javascripts/ci_variable_list/ci_variable_list.js @@ -16,10 +16,7 @@ function createEnvironmentItem(value) { } export default class VariableList { - constructor({ - container, - formField, - }) { + constructor({ container, formField }) { this.$container = $(container); this.formField = formField; this.environmentDropdownMap = new WeakMap(); @@ -71,7 +68,7 @@ export default class VariableList { this.initRow(rowEl); }); - this.$container.on('click', '.js-row-remove-button', (e) => { + this.$container.on('click', '.js-row-remove-button', e => { e.preventDefault(); this.removeRow($(e.currentTarget).closest('.js-row')); }); @@ -81,7 +78,7 @@ export default class VariableList { .join(','); // Remove any empty rows except the last row - this.$container.on('blur', inputSelector, (e) => { + this.$container.on('blur', inputSelector, e => { const $row = $(e.currentTarget).closest('.js-row'); if ($row.is(':not(:last-child)') && !this.checkIfRowTouched($row)) { @@ -136,7 +133,7 @@ export default class VariableList { $rowClone.removeAttr('data-is-persisted'); // Reset the inputs to their defaults - Object.keys(this.inputMap).forEach((name) => { + Object.keys(this.inputMap).forEach(name => { const entry = this.inputMap[name]; $rowClone.find(entry.selector).val(entry.default); }); @@ -171,7 +168,7 @@ export default class VariableList { } checkIfRowTouched($row) { - return Object.keys(this.inputMap).some((name) => { + return Object.keys(this.inputMap).some(name => { const entry = this.inputMap[name]; const $el = $row.find(entry.selector); return $el.length && $el.val() !== entry.default; @@ -190,11 +187,14 @@ export default class VariableList { getAllData() { // Ignore the last empty row because we don't want to try persist // a blank variable and run into validation problems. - const validRows = this.$container.find('.js-row').toArray().slice(0, -1); + const validRows = this.$container + .find('.js-row') + .toArray() + .slice(0, -1); - return validRows.map((rowEl) => { + return validRows.map(rowEl => { const resultant = {}; - Object.keys(this.inputMap).forEach((name) => { + Object.keys(this.inputMap).forEach(name => { const entry = this.inputMap[name]; const $input = $(rowEl).find(entry.selector); if ($input.length) { @@ -207,11 +207,16 @@ export default class VariableList { } getEnvironmentValues() { - const valueMap = this.$container.find(this.inputMap.environment_scope.selector).toArray() - .reduce((prevValueMap, envInput) => ({ - ...prevValueMap, - [envInput.value]: envInput.value, - }), {}); + const valueMap = this.$container + .find(this.inputMap.environment_scope.selector) + .toArray() + .reduce( + (prevValueMap, envInput) => ({ + ...prevValueMap, + [envInput.value]: envInput.value, + }), + {}, + ); return Object.keys(valueMap).map(createEnvironmentItem); } diff --git a/app/assets/javascripts/ci_variable_list/native_form_variable_list.js b/app/assets/javascripts/ci_variable_list/native_form_variable_list.js index 7cd5916ac9c..e7111c666a2 100644 --- a/app/assets/javascripts/ci_variable_list/native_form_variable_list.js +++ b/app/assets/javascripts/ci_variable_list/native_form_variable_list.js @@ -2,10 +2,7 @@ import $ from 'jquery'; import VariableList from './ci_variable_list'; // Used for the variable list on scheduled pipeline edit page -export default function setupNativeFormVariableList({ - container, - formField = 'variables', -}) { +export default function setupNativeFormVariableList({ container, formField = 'variables' }) { const $container = $(container); const variableList = new VariableList({ diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue index 0452729d3ff..236bb1394c8 100644 --- a/app/assets/javascripts/clusters/components/application_row.vue +++ b/app/assets/javascripts/clusters/components/application_row.vue @@ -1,153 +1,162 @@ <script> - /* eslint-disable vue/require-default-prop */ - import { s__, sprintf } from '../../locale'; - import eventHub from '../event_hub'; - import identicon from '../../vue_shared/components/identicon.vue'; - import loadingButton from '../../vue_shared/components/loading_button.vue'; - import { - APPLICATION_STATUS, - REQUEST_LOADING, - REQUEST_SUCCESS, - REQUEST_FAILURE, - } from '../constants'; +/* eslint-disable vue/require-default-prop */ +import { s__, sprintf } from '../../locale'; +import eventHub from '../event_hub'; +import identicon from '../../vue_shared/components/identicon.vue'; +import loadingButton from '../../vue_shared/components/loading_button.vue'; +import { + APPLICATION_STATUS, + REQUEST_LOADING, + REQUEST_SUCCESS, + REQUEST_FAILURE, +} from '../constants'; - export default { - components: { - loadingButton, - identicon, +export default { + components: { + loadingButton, + identicon, + }, + props: { + id: { + type: String, + required: true, }, - props: { - id: { - type: String, - required: true, - }, - title: { - type: String, - required: true, - }, - titleLink: { - type: String, - required: false, - }, - manageLink: { - type: String, - required: false, - }, - logoUrl: { - type: String, - required: false, - default: null, - }, - disabled: { - type: Boolean, - required: false, - default: false, - }, - status: { - type: String, - required: false, - }, - statusReason: { - type: String, - required: false, - }, - requestStatus: { - type: String, - required: false, - }, - requestReason: { - type: String, - required: false, - }, - installApplicationRequestParams: { - type: Object, - required: false, - default: () => ({}), - }, + title: { + type: String, + required: true, }, - computed: { - isUnknownStatus() { - return !this.isKnownStatus && this.status !== null; - }, - isKnownStatus() { - return Object.values(APPLICATION_STATUS).includes(this.status); - }, - isInstalled() { - return ( - this.status === APPLICATION_STATUS.INSTALLED || this.status === APPLICATION_STATUS.UPDATED - ); - }, - hasLogo() { - return !!this.logoUrl; - }, - identiconId() { - // generate a deterministic integer id for the identicon background - return this.id.charCodeAt(0); - }, - rowJsClass() { - return `js-cluster-application-row-${this.id}`; - }, - installButtonLoading() { - return !this.status || - this.status === APPLICATION_STATUS.SCHEDULED || - this.status === APPLICATION_STATUS.INSTALLING || - this.requestStatus === REQUEST_LOADING; - }, - installButtonDisabled() { - // Avoid the potential for the real-time data to say APPLICATION_STATUS.INSTALLABLE but - // we already made a request to install and are just waiting for the real-time - // to sync up. - return ((this.status !== APPLICATION_STATUS.INSTALLABLE - && this.status !== APPLICATION_STATUS.ERROR) || + titleLink: { + type: String, + required: false, + }, + manageLink: { + type: String, + required: false, + }, + logoUrl: { + type: String, + required: false, + default: null, + }, + disabled: { + type: Boolean, + required: false, + default: false, + }, + status: { + type: String, + required: false, + }, + statusReason: { + type: String, + required: false, + }, + requestStatus: { + type: String, + required: false, + }, + requestReason: { + type: String, + required: false, + }, + installApplicationRequestParams: { + type: Object, + required: false, + default: () => ({}), + }, + }, + computed: { + isUnknownStatus() { + return !this.isKnownStatus && this.status !== null; + }, + isKnownStatus() { + return Object.values(APPLICATION_STATUS).includes(this.status); + }, + isInstalled() { + return ( + this.status === APPLICATION_STATUS.INSTALLED || + this.status === APPLICATION_STATUS.UPDATED || + this.status === APPLICATION_STATUS.UPDATING + ); + }, + hasLogo() { + return !!this.logoUrl; + }, + identiconId() { + // generate a deterministic integer id for the identicon background + return this.id.charCodeAt(0); + }, + rowJsClass() { + return `js-cluster-application-row-${this.id}`; + }, + installButtonLoading() { + return ( + !this.status || + this.status === APPLICATION_STATUS.SCHEDULED || + this.status === APPLICATION_STATUS.INSTALLING || + this.requestStatus === REQUEST_LOADING + ); + }, + installButtonDisabled() { + // Avoid the potential for the real-time data to say APPLICATION_STATUS.INSTALLABLE but + // we already made a request to install and are just waiting for the real-time + // to sync up. + return ( + ((this.status !== APPLICATION_STATUS.INSTALLABLE && + this.status !== APPLICATION_STATUS.ERROR) || this.requestStatus === REQUEST_LOADING || - this.requestStatus === REQUEST_SUCCESS) && this.isKnownStatus; - }, - installButtonLabel() { - let label; - if ( - this.status === APPLICATION_STATUS.NOT_INSTALLABLE || - this.status === APPLICATION_STATUS.INSTALLABLE || - this.status === APPLICATION_STATUS.ERROR || - this.isUnknownStatus - ) { - label = s__('ClusterIntegration|Install'); - } else if (this.status === APPLICATION_STATUS.SCHEDULED || - this.status === APPLICATION_STATUS.INSTALLING) { - label = s__('ClusterIntegration|Installing'); - } else if (this.status === APPLICATION_STATUS.INSTALLED || - this.status === APPLICATION_STATUS.UPDATED) { - label = s__('ClusterIntegration|Installed'); - } + this.requestStatus === REQUEST_SUCCESS) && + this.isKnownStatus + ); + }, + installButtonLabel() { + let label; + if ( + this.status === APPLICATION_STATUS.NOT_INSTALLABLE || + this.status === APPLICATION_STATUS.INSTALLABLE || + this.status === APPLICATION_STATUS.ERROR || + this.isUnknownStatus + ) { + label = s__('ClusterIntegration|Install'); + } else if ( + this.status === APPLICATION_STATUS.SCHEDULED || + this.status === APPLICATION_STATUS.INSTALLING + ) { + label = s__('ClusterIntegration|Installing'); + } else if ( + this.status === APPLICATION_STATUS.INSTALLED || + this.status === APPLICATION_STATUS.UPDATED || + this.status === APPLICATION_STATUS.UPDATING + ) { + label = s__('ClusterIntegration|Installed'); + } - return label; - }, - showManageButton() { - return this.manageLink && this.status === APPLICATION_STATUS.INSTALLED; - }, - manageButtonLabel() { - return s__('ClusterIntegration|Manage'); - }, - hasError() { - return this.status === APPLICATION_STATUS.ERROR || - this.requestStatus === REQUEST_FAILURE; - }, - generalErrorDescription() { - return sprintf( - s__('ClusterIntegration|Something went wrong while installing %{title}'), { - title: this.title, - }, - ); - }, + return label; + }, + showManageButton() { + return this.manageLink && this.status === APPLICATION_STATUS.INSTALLED; + }, + manageButtonLabel() { + return s__('ClusterIntegration|Manage'); + }, + hasError() { + return this.status === APPLICATION_STATUS.ERROR || this.requestStatus === REQUEST_FAILURE; + }, + generalErrorDescription() { + return sprintf(s__('ClusterIntegration|Something went wrong while installing %{title}'), { + title: this.title, + }); }, - methods: { - installClicked() { - eventHub.$emit('installApplication', { - id: this.id, - params: this.installApplicationRequestParams, - }); - }, + }, + methods: { + installClicked() { + eventHub.$emit('installApplication', { + id: this.id, + params: this.installApplicationRequestParams, + }); }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js index 72fc9355d82..24a49624583 100644 --- a/app/assets/javascripts/clusters/constants.js +++ b/app/assets/javascripts/clusters/constants.js @@ -6,6 +6,7 @@ export const APPLICATION_STATUS = { INSTALLING: 'installing', INSTALLED: 'installed', UPDATED: 'updated', + UPDATING: 'updating', ERROR: 'errored', }; diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js index d90db7b103c..106ac3cb516 100644 --- a/app/assets/javascripts/clusters/stores/clusters_store.js +++ b/app/assets/javascripts/clusters/stores/clusters_store.js @@ -76,12 +76,8 @@ export default class ClusterStore { this.state.status = serverState.status; this.state.statusReason = serverState.status_reason; - serverState.applications.forEach((serverAppEntry) => { - const { - name: appId, - status, - status_reason: statusReason, - } = serverAppEntry; + serverState.applications.forEach(serverAppEntry => { + const { name: appId, status, status_reason: statusReason } = serverAppEntry; this.state.applications[appId] = { ...(this.state.applications[appId] || {}), diff --git a/app/assets/javascripts/comment_type_toggle.js b/app/assets/javascripts/comment_type_toggle.js index c74184949df..a259667bb75 100644 --- a/app/assets/javascripts/comment_type_toggle.js +++ b/app/assets/javascripts/comment_type_toggle.js @@ -24,36 +24,44 @@ class CommentTypeToggle { setConfig() { const config = { - InputSetter: [{ - input: this.noteTypeInput, - valueAttribute: 'data-value', - }, - { - input: this.submitButton, - valueAttribute: 'data-submit-text', - }], + InputSetter: [ + { + input: this.noteTypeInput, + valueAttribute: 'data-value', + }, + { + input: this.submitButton, + valueAttribute: 'data-submit-text', + }, + ], }; if (this.closeButton) { - config.InputSetter.push({ - input: this.closeButton, - valueAttribute: 'data-close-text', - }, { - input: this.closeButton, - valueAttribute: 'data-close-text', - inputAttribute: 'data-alternative-text', - }); + config.InputSetter.push( + { + input: this.closeButton, + valueAttribute: 'data-close-text', + }, + { + input: this.closeButton, + valueAttribute: 'data-close-text', + inputAttribute: 'data-alternative-text', + }, + ); } if (this.reopenButton) { - config.InputSetter.push({ - input: this.reopenButton, - valueAttribute: 'data-reopen-text', - }, { - input: this.reopenButton, - valueAttribute: 'data-reopen-text', - inputAttribute: 'data-alternative-text', - }); + config.InputSetter.push( + { + input: this.reopenButton, + valueAttribute: 'data-reopen-text', + }, + { + input: this.reopenButton, + valueAttribute: 'data-reopen-text', + inputAttribute: 'data-alternative-text', + }, + ); } return config; diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js index 30d9b656fec..d4ecfa4aa93 100644 --- a/app/assets/javascripts/commit/image_file.js +++ b/app/assets/javascripts/commit/image_file.js @@ -9,44 +9,60 @@ const viewModes = ['two-up', 'swipe']; export default class ImageFile { constructor(file) { this.file = file; - this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) { - return function(deletedWidth, deletedHeight) { - return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) { - _this.initViewModes(); - - // Load two-up view after images are loaded - // so that we can display the correct width and height information - const $images = $('.two-up.view img', _this.file); - - $images.waitForImages(function() { - _this.initView('two-up'); + this.requestImageInfo( + $('.two-up.view .frame.deleted img', this.file), + (function(_this) { + return function(deletedWidth, deletedHeight) { + return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function( + width, + height, + ) { + _this.initViewModes(); + + // Load two-up view after images are loaded + // so that we can display the correct width and height information + const $images = $('.two-up.view img', _this.file); + + $images.waitForImages(function() { + _this.initView('two-up'); + }); }); - }); - }; - })(this)); + }; + })(this), + ); } initViewModes() { const viewMode = viewModes[0]; $('.view-modes', this.file).removeClass('hide'); - $('.view-modes-menu', this.file).on('click', 'li', (function(_this) { - return function(event) { - if (!$(event.currentTarget).hasClass('active')) { - return _this.activateViewMode(event.currentTarget.className); - } - }; - })(this)); + $('.view-modes-menu', this.file).on( + 'click', + 'li', + (function(_this) { + return function(event) { + if (!$(event.currentTarget).hasClass('active')) { + return _this.activateViewMode(event.currentTarget.className); + } + }; + })(this), + ); return this.activateViewMode(viewMode); } activateViewMode(viewMode) { - $('.view-modes-menu li', this.file).removeClass('active').filter("." + viewMode).addClass('active'); - return $(".view:visible:not(." + viewMode + ")", this.file).fadeOut(200, (function(_this) { - return function() { - $(".view." + viewMode, _this.file).fadeIn(200); - return _this.initView(viewMode); - }; - })(this)); + $('.view-modes-menu li', this.file) + .removeClass('active') + .filter('.' + viewMode) + .addClass('active'); + return $('.view:visible:not(.' + viewMode + ')', this.file).fadeOut( + 200, + (function(_this) { + return function() { + $('.view.' + viewMode, _this.file).fadeIn(200); + return _this.initView(viewMode); + }; + })(this), + ); } initView(viewMode) { @@ -63,135 +79,154 @@ export default class ImageFile { $body.css('user-select', 'none'); }); - $body.off('mouseup').off('mousemove').on('mouseup', function() { - dragging = false; - $body.css('user-select', ''); - }) - .on('mousemove', function(e) { - var left; - if (!dragging) return; - - left = e.pageX - ($offsetEl.offset().left + padding); - - callback(e, left); - }); + $body + .off('mouseup') + .off('mousemove') + .on('mouseup', function() { + dragging = false; + $body.css('user-select', ''); + }) + .on('mousemove', function(e) { + var left; + if (!dragging) return; + + left = e.pageX - ($offsetEl.offset().left + padding); + + callback(e, left); + }); } prepareFrames(view) { var maxHeight, maxWidth; maxWidth = 0; maxHeight = 0; - $('.frame', view).each((function(_this) { - return function(index, frame) { - var height, width; - width = $(frame).width(); - height = $(frame).height(); - maxWidth = width > maxWidth ? width : maxWidth; - return maxHeight = height > maxHeight ? height : maxHeight; - }; - })(this)).css({ - width: maxWidth, - height: maxHeight - }); + $('.frame', view) + .each( + (function(_this) { + return function(index, frame) { + var height, width; + width = $(frame).width(); + height = $(frame).height(); + maxWidth = width > maxWidth ? width : maxWidth; + return (maxHeight = height > maxHeight ? height : maxHeight); + }; + })(this), + ) + .css({ + width: maxWidth, + height: maxHeight, + }); return [maxWidth, maxHeight]; } views = { 'two-up': function() { - return $('.two-up.view .wrap', this.file).each((function(_this) { - return function(index, wrap) { - $('img', wrap).each(function() { - var currentWidth; - currentWidth = $(this).width(); - if (currentWidth > availWidth / 2) { - return $(this).width(availWidth / 2); - } - }); - return _this.requestImageInfo($('img', wrap), function(width, height) { - $('.image-info .meta-width', wrap).text(width + "px"); - $('.image-info .meta-height', wrap).text(height + "px"); - return $('.image-info', wrap).removeClass('hide'); - }); - }; - })(this)); + return $('.two-up.view .wrap', this.file).each( + (function(_this) { + return function(index, wrap) { + $('img', wrap).each(function() { + var currentWidth; + currentWidth = $(this).width(); + if (currentWidth > availWidth / 2) { + return $(this).width(availWidth / 2); + } + }); + return _this.requestImageInfo($('img', wrap), function(width, height) { + $('.image-info .meta-width', wrap).text(width + 'px'); + $('.image-info .meta-height', wrap).text(height + 'px'); + return $('.image-info', wrap).removeClass('hide'); + }); + }; + })(this), + ); }, - 'swipe': function() { + swipe() { var maxHeight, maxWidth; maxWidth = 0; maxHeight = 0; - return $('.swipe.view', this.file).each((function(_this) { - return function(index, view) { - var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref; - ref = _this.prepareFrames(view), [maxWidth, maxHeight] = ref; - $swipeFrame = $('.swipe-frame', view); - $swipeWrap = $('.swipe-wrap', view); - $swipeBar = $('.swipe-bar', view); - - $swipeFrame.css({ - width: maxWidth + 16, - height: maxHeight + 28 - }); - $swipeWrap.css({ - width: maxWidth + 1, - height: maxHeight + 2 - }); - // Set swipeBar left position to match image frame - $swipeBar.css({ - left: 1 - }); - - wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10); - - _this.initDraggable($swipeBar, wrapPadding, function(e, left) { - if (left > 0 && left < $swipeFrame.width() - (wrapPadding * 2)) { - $swipeWrap.width((maxWidth + 1) - left); - $swipeBar.css('left', left); - } - }); - }; - })(this)); + return $('.swipe.view', this.file).each( + (function(_this) { + return function(index, view) { + var $swipeWrap, $swipeBar, $swipeFrame, wrapPadding, ref; + (ref = _this.prepareFrames(view)), ([maxWidth, maxHeight] = ref); + $swipeFrame = $('.swipe-frame', view); + $swipeWrap = $('.swipe-wrap', view); + $swipeBar = $('.swipe-bar', view); + + $swipeFrame.css({ + width: maxWidth + 16, + height: maxHeight + 28, + }); + $swipeWrap.css({ + width: maxWidth + 1, + height: maxHeight + 2, + }); + // Set swipeBar left position to match image frame + $swipeBar.css({ + left: 1, + }); + + wrapPadding = parseInt($swipeWrap.css('right').replace('px', ''), 10); + + _this.initDraggable($swipeBar, wrapPadding, function(e, left) { + if (left > 0 && left < $swipeFrame.width() - wrapPadding * 2) { + $swipeWrap.width(maxWidth + 1 - left); + $swipeBar.css('left', left); + } + }); + }; + })(this), + ); }, 'onion-skin': function() { var dragTrackWidth, maxHeight, maxWidth; maxWidth = 0; maxHeight = 0; dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width(); - return $('.onion-skin.view', this.file).each((function(_this) { - return function(index, view) { - var $frame, $track, $dragger, $frameAdded, framePadding, ref, dragging = false; - ref = _this.prepareFrames(view), [maxWidth, maxHeight] = ref; - $frame = $('.onion-skin-frame', view); - $frameAdded = $('.frame.added', view); - $track = $('.drag-track', view); - $dragger = $('.dragger', $track); - - $frame.css({ - width: maxWidth + 16, - height: maxHeight + 28 - }); - $('.swipe-wrap', view).css({ - width: maxWidth + 1, - height: maxHeight + 2 - }); - $dragger.css({ - left: dragTrackWidth - }); - - $frameAdded.css('opacity', 1); - framePadding = parseInt($frameAdded.css('right').replace('px', ''), 10); - - _this.initDraggable($dragger, framePadding, function(e, left) { - var opacity = left / dragTrackWidth; - - if (opacity >= 0 && opacity <= 1) { - $dragger.css('left', left); - $frameAdded.css('opacity', opacity); - } - }); - }; - })(this)); - } - } + return $('.onion-skin.view', this.file).each( + (function(_this) { + return function(index, view) { + var $frame, + $track, + $dragger, + $frameAdded, + framePadding, + ref, + dragging = false; + (ref = _this.prepareFrames(view)), ([maxWidth, maxHeight] = ref); + $frame = $('.onion-skin-frame', view); + $frameAdded = $('.frame.added', view); + $track = $('.drag-track', view); + $dragger = $('.dragger', $track); + + $frame.css({ + width: maxWidth + 16, + height: maxHeight + 28, + }); + $('.swipe-wrap', view).css({ + width: maxWidth + 1, + height: maxHeight + 2, + }); + $dragger.css({ + left: dragTrackWidth, + }); + + $frameAdded.css('opacity', 1); + framePadding = parseInt($frameAdded.css('right').replace('px', ''), 10); + + _this.initDraggable($dragger, framePadding, function(e, left) { + var opacity = left / dragTrackWidth; + + if (opacity >= 0 && opacity <= 1) { + $dragger.css('left', left); + $frameAdded.css('opacity', opacity); + } + }); + }; + })(this), + ); + }, + }; requestImageInfo(img, callback) { const domImg = img.get(0); @@ -199,11 +234,14 @@ export default class ImageFile { if (domImg.complete) { return callback.call(this, domImg.naturalWidth, domImg.naturalHeight); } else { - return img.on('load', (function(_this) { - return function() { - return callback.call(_this, domImg.naturalWidth, domImg.naturalHeight); - }; - })(this)); + return img.on( + 'load', + (function(_this) { + return function() { + return callback.call(_this, domImg.naturalWidth, domImg.naturalHeight); + }; + })(this), + ); } } } diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js index 3d89bf1316e..340a93e4e66 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js +++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js @@ -19,11 +19,13 @@ export default () => { const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view'); if (pipelineTableViewEl) { - // Update MR and Commits tabs - pipelineTableViewEl.addEventListener('update-pipelines-count', (event) => { - if (event.detail.pipelines && + // Update MR and Commits tabs + pipelineTableViewEl.addEventListener('update-pipelines-count', event => { + if ( + event.detail.pipelines && event.detail.pipelines.count && - event.detail.pipelines.count.all) { + event.detail.pipelines.count.all + ) { const badge = document.querySelector('.js-pipelines-mr-count'); badge.textContent = event.detail.pipelines.count.all; diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue index 4849b0fa3db..a2aa3d197e3 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue @@ -1,77 +1,73 @@ <script> - import PipelinesService from '../../pipelines/services/pipelines_service'; - import PipelineStore from '../../pipelines/stores/pipelines_store'; - import pipelinesMixin from '../../pipelines/mixins/pipelines'; +import PipelinesService from '../../pipelines/services/pipelines_service'; +import PipelineStore from '../../pipelines/stores/pipelines_store'; +import pipelinesMixin from '../../pipelines/mixins/pipelines'; - export default { - mixins: [ - pipelinesMixin, - ], - props: { - endpoint: { - type: String, - required: true, - }, - helpPagePath: { - type: String, - required: true, - }, - autoDevopsHelpPath: { - type: String, - required: true, - }, - errorStateSvgPath: { - type: String, - required: true, - }, - viewType: { - type: String, - required: false, - default: 'child', - }, +export default { + mixins: [pipelinesMixin], + props: { + endpoint: { + type: String, + required: true, }, + helpPagePath: { + type: String, + required: true, + }, + autoDevopsHelpPath: { + type: String, + required: true, + }, + errorStateSvgPath: { + type: String, + required: true, + }, + viewType: { + type: String, + required: false, + default: 'child', + }, + }, - data() { - const store = new PipelineStore(); + data() { + const store = new PipelineStore(); - return { - store, - state: store.state, - }; - }, + return { + store, + state: store.state, + }; + }, - computed: { - shouldRenderTable() { - return !this.isLoading && - this.state.pipelines.length > 0 && - !this.hasError; - }, - shouldRenderErrorState() { - return this.hasError && !this.isLoading; - }, + computed: { + shouldRenderTable() { + return !this.isLoading && this.state.pipelines.length > 0 && !this.hasError; }, - created() { - this.service = new PipelinesService(this.endpoint); + shouldRenderErrorState() { + return this.hasError && !this.isLoading; }, - methods: { - successCallback(resp) { - // depending of the endpoint the response can either bring a `pipelines` key or not. - const pipelines = resp.data.pipelines || resp.data; - this.setCommonData(pipelines); + }, + created() { + this.service = new PipelinesService(this.endpoint); + }, + methods: { + successCallback(resp) { + // depending of the endpoint the response can either bring a `pipelines` key or not. + const pipelines = resp.data.pipelines || resp.data; + this.setCommonData(pipelines); - const updatePipelinesEvent = new CustomEvent('update-pipelines-count', { - detail: { - pipelines: resp.data, - }, - }); + const updatePipelinesEvent = new CustomEvent('update-pipelines-count', { + detail: { + pipelines: resp.data, + }, + }); - // notifiy to update the count in tabs - if (this.$el.parentElement) { - this.$el.parentElement.dispatchEvent(updatePipelinesEvent); - } - }, + // notifiy to update the count in tabs + if (this.$el.parentElement) { + this.$el.parentElement.dispatchEvent(updatePipelinesEvent); + } }, - }; + }, +}; </script> <template> <div class="content-list pipelines"> diff --git a/app/assets/javascripts/commit_merge_requests.js b/app/assets/javascripts/commit_merge_requests.js index 102b4ee8463..3a0ab119df6 100644 --- a/app/assets/javascripts/commit_merge_requests.js +++ b/app/assets/javascripts/commit_merge_requests.js @@ -50,7 +50,7 @@ export function createContent(mergeRequests) { if (mergeRequests.length === 0) { $content.text(s__('Commits|No related merge requests found')); } else { - mergeRequests.forEach((mergeRequest) => { + mergeRequests.forEach(mergeRequest => { const $header = createHeader($content.children().length, mergeRequests.length); const $item = createItem(mergeRequest); $content.append($header); @@ -64,8 +64,9 @@ export function createContent(mergeRequests) { export function fetchCommitMergeRequests() { const $container = $('.merge-requests'); - axios.get($container.data('projectCommitPath')) - .then((response) => { + axios + .get($container.data('projectCommitPath')) + .then(response => { const $content = createContent(response.data); $container.html($content); diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js index 9a3ea7a55b6..54e2589c707 100644 --- a/app/assets/javascripts/commits.js +++ b/app/assets/javascripts/commits.js @@ -32,22 +32,31 @@ export default class CommitsList { if (search === this.lastSearch) return Promise.resolve(); const commitsUrl = `${form.attr('action')}?${form.serialize()}`; this.content.fadeTo('fast', 0.5); - const params = form.serializeArray().reduce((acc, obj) => Object.assign(acc, { - [obj.name]: obj.value, - }), {}); + const params = form.serializeArray().reduce( + (acc, obj) => + Object.assign(acc, { + [obj.name]: obj.value, + }), + {}, + ); - return axios.get(form.attr('action'), { - params, - }) + return axios + .get(form.attr('action'), { + params, + }) .then(({ data }) => { this.lastSearch = search; this.content.html(data.html); this.content.fadeTo('fast', 1.0); // Change url so if user reload a page - search results are saved - window.history.replaceState({ - page: commitsUrl, - }, document.title, commitsUrl); + window.history.replaceState( + { + page: commitsUrl, + }, + document.title, + commitsUrl, + ); }) .catch(() => { this.content.fadeTo('fast', 1.0); @@ -75,8 +84,15 @@ export default class CommitsList { processedData = $processedData.not(`li.js-commit-header[data-day='${loadedShownDayFirst}']`); // Update commits count in the previous commits header. - commitsCount += Number($(processedData).nextUntil('li.js-commit-header').first().find('li.commit').length); - $commitsHeadersLast.find('span.commits-count').text(`${commitsCount} ${pluralize('commit', commitsCount)}`); + commitsCount += Number( + $(processedData) + .nextUntil('li.js-commit-header') + .first() + .find('li.commit').length, + ); + $commitsHeadersLast + .find('span.commits-count') + .text(`${commitsCount} ${pluralize('commit', commitsCount)}`); } localTimeAgo($processedData.find('.js-timeago')); diff --git a/app/assets/javascripts/commons/bootstrap.js b/app/assets/javascripts/commons/bootstrap.js index 50e2949ab55..fba30aea9ae 100644 --- a/app/assets/javascripts/commons/bootstrap.js +++ b/app/assets/javascripts/commons/bootstrap.js @@ -5,6 +5,14 @@ import 'bootstrap'; // custom jQuery functions $.fn.extend({ - disable() { return $(this).prop('disabled', true).addClass('disabled'); }, - enable() { return $(this).prop('disabled', false).removeClass('disabled'); }, + disable() { + return $(this) + .prop('disabled', true) + .addClass('disabled'); + }, + enable() { + return $(this) + .prop('disabled', false) + .removeClass('disabled'); + }, }); diff --git a/app/assets/javascripts/compare_autocomplete.js b/app/assets/javascripts/compare_autocomplete.js index 852d71f4e84..37a3ceb5341 100644 --- a/app/assets/javascripts/compare_autocomplete.js +++ b/app/assets/javascripts/compare_autocomplete.js @@ -40,7 +40,7 @@ export default function initCompareAutocomplete(limitTo = null, clickHandler = ( }, selectable: true, filterable: true, - filterRemote: true, + filterRemote: !!$dropdown.data('refsUrl'), fieldName: $dropdown.data('fieldName'), filterInput: 'input[type="search"]', renderRow: function(ref) { diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js index b0c85c2572e..1000c310e35 100644 --- a/app/assets/javascripts/confirm_danger_modal.js +++ b/app/assets/javascripts/confirm_danger_modal.js @@ -13,19 +13,23 @@ function openConfirmDangerModal($form, text) { $submit.disable(); $input.focus(); - $('.js-confirm-danger-input').off('input').on('input', function handleInput() { - const confirmText = rstrip($(this).val()); - if (confirmText === confirmTextMatch) { - $submit.enable(); - } else { - $submit.disable(); - } - }); - $('.js-confirm-danger-submit').off('click').on('click', () => $form.submit()); + $('.js-confirm-danger-input') + .off('input') + .on('input', function handleInput() { + const confirmText = rstrip($(this).val()); + if (confirmText === confirmTextMatch) { + $submit.enable(); + } else { + $submit.disable(); + } + }); + $('.js-confirm-danger-submit') + .off('click') + .on('click', () => $form.submit()); } export default function initConfirmDangerModal() { - $(document).on('click', '.js-confirm-danger', (e) => { + $(document).on('click', '.js-confirm-danger', e => { e.preventDefault(); const $btn = $(e.target); const $form = $btn.closest('form'); diff --git a/app/assets/javascripts/contextual_sidebar.js b/app/assets/javascripts/contextual_sidebar.js index 3a50e73ad85..dff0adba25a 100644 --- a/app/assets/javascripts/contextual_sidebar.js +++ b/app/assets/javascripts/contextual_sidebar.js @@ -20,8 +20,11 @@ export default class ContextualSidebar { } bindEvents() { - document.addEventListener('click', (e) => { - if (!e.target.closest('.nav-sidebar') && (bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md')) { + document.addEventListener('click', e => { + if ( + !e.target.closest('.nav-sidebar') && + (bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md') + ) { this.toggleCollapsedSidebar(true); } }); diff --git a/app/assets/javascripts/create_item_dropdown.js b/app/assets/javascripts/create_item_dropdown.js index 8ef9aa7f529..916b190f469 100644 --- a/app/assets/javascripts/create_item_dropdown.js +++ b/app/assets/javascripts/create_item_dropdown.js @@ -36,7 +36,7 @@ export default class CreateItemDropdown { }, selectable: true, toggleLabel(selected) { - return (selected && 'id' in selected) ? _.escape(selected.title) : this.defaultToggleLabel; + return selected && 'id' in selected ? _.escape(selected.title) : this.defaultToggleLabel; }, fieldName: this.fieldName, text(item) { @@ -46,7 +46,7 @@ export default class CreateItemDropdown { return _.escape(item.id); }, onFilter: this.toggleCreateNewButton.bind(this), - clicked: (options) => { + clicked: options => { options.e.preventDefault(); this.onSelect(); }, @@ -77,9 +77,8 @@ export default class CreateItemDropdown { getData(term, callback) { this.getDataOption(term, (data = []) => { // Ensure the selected item isn't already in the data to avoid duplicates - const alreadyHasSelectedItem = this.selectedItem && data.some(item => - item.id === this.selectedItem.id, - ); + const alreadyHasSelectedItem = + this.selectedItem && data.some(item => item.id === this.selectedItem.id); let uniqueData = data; if (!alreadyHasSelectedItem) { @@ -106,9 +105,7 @@ export default class CreateItemDropdown { if (newValue) { this.selectedItem = this.createNewItemFromValue(newValue); - this.$dropdownContainer - .find('.js-dropdown-create-new-item code') - .text(newValue); + this.$dropdownContainer.find('.js-dropdown-create-new-item code').text(newValue); } this.toggleFooter(!newValue); diff --git a/app/assets/javascripts/create_label.js b/app/assets/javascripts/create_label.js index a999c21b2e9..28ca7d97314 100644 --- a/app/assets/javascripts/create_label.js +++ b/app/assets/javascripts/create_label.js @@ -37,7 +37,7 @@ export default class CreateLabelDropdown { addBinding() { const self = this; - this.$colorSuggestions.on('click', function (e) { + this.$colorSuggestions.on('click', function(e) { const $this = $(this); self.addColorValue(e, $this); }); @@ -47,7 +47,7 @@ export default class CreateLabelDropdown { this.$dropdownBack.on('click', this.resetForm.bind(this)); - this.$cancelButton.on('click', function (e) { + this.$cancelButton.on('click', function(e) { e.preventDefault(); e.stopPropagation(); @@ -79,13 +79,9 @@ export default class CreateLabelDropdown { } resetForm() { - this.$newLabelField - .val('') - .trigger('change'); + this.$newLabelField.val('').trigger('change'); - this.$newColorField - .val('') - .trigger('change'); + this.$newColorField.val('').trigger('change'); this.$colorPreview .css('background-color', '') @@ -97,31 +93,34 @@ export default class CreateLabelDropdown { e.preventDefault(); e.stopPropagation(); - Api.newLabel(this.namespacePath, this.projectPath, { - title: this.$newLabelField.val(), - color: this.$newColorField.val(), - }, (label) => { - this.$newLabelCreateButton.enable(); - - if (label.message) { - let errors; - - if (typeof label.message === 'string') { - errors = label.message; + Api.newLabel( + this.namespacePath, + this.projectPath, + { + title: this.$newLabelField.val(), + color: this.$newColorField.val(), + }, + label => { + this.$newLabelCreateButton.enable(); + + if (label.message) { + let errors; + + if (typeof label.message === 'string') { + errors = label.message; + } else { + errors = Object.keys(label.message) + .map(key => `${humanize(key)} ${label.message[key].join(', ')}`) + .join('<br/>'); + } + + this.$newLabelError.html(errors).show(); } else { - errors = Object.keys(label.message).map(key => - `${humanize(key)} ${label.message[key].join(', ')}`, - ).join('<br/>'); - } + this.$dropdownBack.trigger('click'); - this.$newLabelError - .html(errors) - .show(); - } else { - this.$dropdownBack.trigger('click'); - - $(document).trigger('created.label', label); - } - }); + $(document).trigger('created.label', label); + } + }, + ); } } diff --git a/app/assets/javascripts/cycle_analytics/components/banner.vue b/app/assets/javascripts/cycle_analytics/components/banner.vue index 88570160f26..82b0f523d2e 100644 --- a/app/assets/javascripts/cycle_analytics/components/banner.vue +++ b/app/assets/javascripts/cycle_analytics/components/banner.vue @@ -1,28 +1,28 @@ <script> - import Icon from '~/vue_shared/components/icon.vue'; - import iconCycleAnalyticsSplash from 'icons/_icon_cycle_analytics_splash.svg'; +import Icon from '~/vue_shared/components/icon.vue'; +import iconCycleAnalyticsSplash from 'icons/_icon_cycle_analytics_splash.svg'; - export default { - components: { - Icon, +export default { + components: { + Icon, + }, + props: { + documentationLink: { + type: String, + required: true, }, - props: { - documentationLink: { - type: String, - required: true, - }, + }, + computed: { + iconCycleAnalyticsSplash() { + return iconCycleAnalyticsSplash; }, - computed: { - iconCycleAnalyticsSplash() { - return iconCycleAnalyticsSplash; - }, + }, + methods: { + dismissOverviewDialog() { + this.$emit('dismiss-overview-dialog'); }, - methods: { - dismissOverviewDialog() { - this.$emit('dismiss-overview-dialog'); - }, - }, - }; + }, +}; </script> <template> <div class="landing content-block"> diff --git a/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue b/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue index b626b187651..f6a7d9962eb 100644 --- a/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue +++ b/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue @@ -1,17 +1,17 @@ <script> - import tooltip from '../../vue_shared/directives/tooltip'; +import tooltip from '../../vue_shared/directives/tooltip'; - export default { - directives: { - tooltip, +export default { + directives: { + tooltip, + }, + props: { + count: { + type: Number, + required: true, }, - props: { - count: { - type: Number, - required: true, - }, - }, - }; + }, +}; </script> <template> <span diff --git a/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue index a71dcf78103..429fef176c3 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue +++ b/app/assets/javascripts/cycle_analytics/components/stage_code_component.vue @@ -1,25 +1,25 @@ <script> - import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; - import limitWarning from './limit_warning_component.vue'; - import totalTime from './total_time_component.vue'; +import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; +import limitWarning from './limit_warning_component.vue'; +import totalTime from './total_time_component.vue'; - export default { - components: { - userAvatarImage, - limitWarning, - totalTime, +export default { + components: { + userAvatarImage, + limitWarning, + totalTime, + }, + props: { + items: { + type: Array, + default: () => [], }, - props: { - items: { - type: Array, - default: () => [], - }, - stage: { - type: Object, - default: () => ({}), - }, + stage: { + type: Object, + default: () => ({}), }, - }; + }, +}; </script> <template> <div> diff --git a/app/assets/javascripts/cycle_analytics/components/stage_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_component.vue index 312fe75dde4..56e851fa528 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_component.vue +++ b/app/assets/javascripts/cycle_analytics/components/stage_component.vue @@ -1,25 +1,25 @@ <script> - import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; - import limitWarning from './limit_warning_component.vue'; - import totalTime from './total_time_component.vue'; +import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; +import limitWarning from './limit_warning_component.vue'; +import totalTime from './total_time_component.vue'; - export default { - components: { - userAvatarImage, - limitWarning, - totalTime, +export default { + components: { + userAvatarImage, + limitWarning, + totalTime, + }, + props: { + items: { + type: Array, + default: () => [], }, - props: { - items: { - type: Array, - default: () => [], - }, - stage: { - type: Object, - default: () => ({}), - }, + stage: { + type: Object, + default: () => ({}), }, - }; + }, +}; </script> <template> <div> @@ -73,4 +73,3 @@ </ul> </div> </template> - diff --git a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue index cee294b4ac2..54b9da4983a 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue +++ b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue @@ -1,31 +1,31 @@ <script> - import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; - import iconCommit from '../svg/icon_commit.svg'; - import limitWarning from './limit_warning_component.vue'; - import totalTime from './total_time_component.vue'; +import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; +import iconCommit from '../svg/icon_commit.svg'; +import limitWarning from './limit_warning_component.vue'; +import totalTime from './total_time_component.vue'; - export default { - components: { - userAvatarImage, - totalTime, - limitWarning, +export default { + components: { + userAvatarImage, + totalTime, + limitWarning, + }, + props: { + items: { + type: Array, + default: () => [], }, - props: { - items: { - type: Array, - default: () => [], - }, - stage: { - type: Object, - default: () => ({}), - }, + stage: { + type: Object, + default: () => ({}), }, - computed: { - iconCommit() { - return iconCommit; - }, + }, + computed: { + iconCommit() { + return iconCommit; }, - }; + }, +}; </script> <template> <div> @@ -74,4 +74,3 @@ </ul> </div> </template> - diff --git a/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue index d4735d030fc..f9c80d237d7 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue +++ b/app/assets/javascripts/cycle_analytics/components/stage_review_component.vue @@ -1,27 +1,27 @@ <script> - import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; - import limitWarning from './limit_warning_component.vue'; - import totalTime from './total_time_component.vue'; - import icon from '../../vue_shared/components/icon.vue'; +import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; +import limitWarning from './limit_warning_component.vue'; +import totalTime from './total_time_component.vue'; +import icon from '../../vue_shared/components/icon.vue'; - export default { - components: { - userAvatarImage, - totalTime, - limitWarning, - icon, +export default { + components: { + userAvatarImage, + totalTime, + limitWarning, + icon, + }, + props: { + items: { + type: Array, + default: () => [], }, - props: { - items: { - type: Array, - default: () => [], - }, - stage: { - type: Object, - default: () => ({}), - }, + stage: { + type: Object, + default: () => ({}), }, - }; + }, +}; </script> <template> <div> diff --git a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue index 22637485c01..e83b66eef86 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue +++ b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.vue @@ -1,33 +1,33 @@ <script> - import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; - import iconBranch from '../svg/icon_branch.svg'; - import limitWarning from './limit_warning_component.vue'; - import totalTime from './total_time_component.vue'; - import icon from '../../vue_shared/components/icon.vue'; +import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; +import iconBranch from '../svg/icon_branch.svg'; +import limitWarning from './limit_warning_component.vue'; +import totalTime from './total_time_component.vue'; +import icon from '../../vue_shared/components/icon.vue'; - export default { - components: { - userAvatarImage, - totalTime, - limitWarning, - icon, +export default { + components: { + userAvatarImage, + totalTime, + limitWarning, + icon, + }, + props: { + items: { + type: Array, + default: () => [], }, - props: { - items: { - type: Array, - default: () => [], - }, - stage: { - type: Object, - default: () => ({}), - }, + stage: { + type: Object, + default: () => ({}), }, - computed: { - iconBranch() { - return iconBranch; - }, + }, + computed: { + iconBranch() { + return iconBranch; }, - }; + }, +}; </script> <template> <div> diff --git a/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue index a0796f299e7..a8196dc879a 100644 --- a/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue +++ b/app/assets/javascripts/cycle_analytics/components/stage_test_component.vue @@ -1,35 +1,35 @@ <script> - import iconBuildStatus from '../svg/icon_build_status.svg'; - import iconBranch from '../svg/icon_branch.svg'; - import limitWarning from './limit_warning_component.vue'; - import totalTime from './total_time_component.vue'; - import icon from '../../vue_shared/components/icon.vue'; +import iconBuildStatus from '../svg/icon_build_status.svg'; +import iconBranch from '../svg/icon_branch.svg'; +import limitWarning from './limit_warning_component.vue'; +import totalTime from './total_time_component.vue'; +import icon from '../../vue_shared/components/icon.vue'; - export default { - components: { - totalTime, - limitWarning, - icon, +export default { + components: { + totalTime, + limitWarning, + icon, + }, + props: { + items: { + type: Array, + default: () => [], }, - props: { - items: { - type: Array, - default: () => [], - }, - stage: { - type: Object, - default: () => ({}), - }, + stage: { + type: Object, + default: () => ({}), }, - computed: { - iconBuildStatus() { - return iconBuildStatus; - }, - iconBranch() { - return iconBranch; - }, + }, + computed: { + iconBuildStatus() { + return iconBuildStatus; }, - }; + iconBranch() { + return iconBranch; + }, + }, +}; </script> <template> <div> diff --git a/app/assets/javascripts/cycle_analytics/components/total_time_component.vue b/app/assets/javascripts/cycle_analytics/components/total_time_component.vue index 7758bf0cb3f..4db50134208 100644 --- a/app/assets/javascripts/cycle_analytics/components/total_time_component.vue +++ b/app/assets/javascripts/cycle_analytics/components/total_time_component.vue @@ -1,18 +1,18 @@ <script> - export default { - props: { - time: { - type: Object, - required: false, - default: () => ({}), - }, +export default { + props: { + time: { + type: Object, + required: false, + default: () => ({}), }, - computed: { - hasData() { - return Object.keys(this.time).length; - }, + }, + computed: { + hasData() { + return Object.keys(this.time).length; }, - }; + }, +}; </script> <template> <span class="total-time"> diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js index 1c43fc3cdc7..4de425b48e7 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js @@ -18,7 +18,8 @@ Vue.use(Translate); export default () => { const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed'; - new Vue({ // eslint-disable-line no-new + // eslint-disable-next-line no-new + new Vue({ el: '#cycle-analytics', name: 'CycleAnalytics', components: { @@ -66,14 +67,17 @@ export default () => { const $dropdown = $('.js-ca-dropdown'); const $label = $dropdown.find('.dropdown-label'); - $dropdown.find('li a').off('click').on('click', (e) => { - e.preventDefault(); - const $target = $(e.currentTarget); - this.startDate = $target.data('value'); + $dropdown + .find('li a') + .off('click') + .on('click', e => { + e.preventDefault(); + const $target = $(e.currentTarget); + this.startDate = $target.data('value'); - $label.text($target.text().trim()); - this.fetchCycleAnalyticsData({ startDate: this.startDate }); - }); + $label.text($target.text().trim()); + this.fetchCycleAnalyticsData({ startDate: this.startDate }); + }); }, fetchCycleAnalyticsData(options) { const fetchOptions = options || { startDate: this.startDate }; @@ -82,7 +86,7 @@ export default () => { this.service .fetchCycleAnalyticsData(fetchOptions) - .then((response) => { + .then(response => { this.store.setCycleAnalyticsData(response); this.selectDefaultStage(); this.initDropdown(); @@ -115,7 +119,7 @@ export default () => { stage, startDate: this.startDate, }) - .then((response) => { + .then(response => { this.isEmptyStage = !response.events.length; this.store.setStageEvents(response.events, stage); this.isLoadingStage = false; diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js index 4cf416c50e5..a0426301a0a 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js @@ -18,10 +18,7 @@ export default class CycleAnalyticsService { } fetchStageData(options) { - const { - stage, - startDate, - } = options; + const { stage, startDate } = options; return this.axios .get(`events/${stage.name}.json`, { diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js index a8cd8c20f8f..18fb57c8b4f 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js @@ -5,13 +5,27 @@ import { dasherize } from '../lib/utils/text_utility'; import DEFAULT_EVENT_OBJECTS from './default_event_objects'; const EMPTY_STAGE_TEXTS = { - issue: __('The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.'), - plan: __('The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.'), - code: __('The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.'), - test: __('The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.'), - review: __('The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.'), - staging: __('The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.'), - production: __('The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.'), + issue: __( + 'The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.', + ), + plan: __( + 'The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.', + ), + code: __( + 'The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.', + ), + test: __( + 'The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.', + ), + review: __( + 'The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.', + ), + staging: __( + 'The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.', + ), + production: __( + 'The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle.', + ), }; export default { @@ -31,11 +45,11 @@ export default { newData.stages = data.stats || []; newData.summary = data.summary || []; - newData.summary.forEach((item) => { + newData.summary.forEach(item => { item.value = item.value || '-'; }); - newData.stages.forEach((item) => { + newData.stages.forEach(item => { const stageSlug = dasherize(item.name.toLowerCase()); item.active = false; item.isUserAllowed = data.permissions[stageSlug]; @@ -53,7 +67,7 @@ export default { this.state.hasError = state; }, deactivateAllStages() { - this.state.stages.forEach((stage) => { + this.state.stages.forEach(stage => { stage.active = false; }); }, @@ -67,7 +81,7 @@ export default { decorateEvents(events, stage) { const newEvents = []; - events.forEach((item) => { + events.forEach(item => { if (!item) return; const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item); diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue index aa52f120fe7..3589599986d 100644 --- a/app/assets/javascripts/deploy_keys/components/app.vue +++ b/app/assets/javascripts/deploy_keys/components/app.vue @@ -95,8 +95,10 @@ export default { .catch(() => new Flash(s__('DeployKeys|Error enabling deploy key'))); }, disableKey(deployKey, callback) { - // eslint-disable-next-line no-alert - if (window.confirm(s__('DeployKeys|You are going to remove this deploy key. Are you sure?'))) { + if ( + // eslint-disable-next-line no-alert + window.confirm(s__('DeployKeys|You are going to remove this deploy key. Are you sure?')) + ) { this.service .disableKey(deployKey.id) .then(this.fetchKeys) diff --git a/app/assets/javascripts/deploy_keys/service/index.js b/app/assets/javascripts/deploy_keys/service/index.js index 9dc3b21f6f6..268a37008c5 100644 --- a/app/assets/javascripts/deploy_keys/service/index.js +++ b/app/assets/javascripts/deploy_keys/service/index.js @@ -8,17 +8,14 @@ export default class DeployKeysService { } getKeys() { - return this.axios.get() - .then(response => response.data); + return this.axios.get().then(response => response.data); } enableKey(id) { - return this.axios.put(`${id}/enable`) - .then(response => response.data); + return this.axios.put(`${id}/enable`).then(response => response.data); } disableKey(id) { - return this.axios.put(`${id}/disable`) - .then(response => response.data); + return this.axios.put(`${id}/disable`).then(response => response.data); } } diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index a044fc1ab42..245f1a7c558 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -21,9 +21,12 @@ export default class Diff { }); const tab = document.getElementById('diffs'); - if (!tab || (tab && tab.dataset && tab.dataset.isLocked !== '')) FilesCommentButton.init($diffFile); + if (!tab || (tab && tab.dataset && tab.dataset.isLocked !== '')) + FilesCommentButton.init($diffFile); - const firstFile = $('.files').first().get(0); + const firstFile = $('.files') + .first() + .get(0); const canCreateNote = firstFile && firstFile.hasAttribute('data-can-create-note'); $diffFile.each((index, file) => imageDiffHelper.initImageDiff(file, canCreateNote)); @@ -73,9 +76,10 @@ export default class Diff { const view = file.data('view'); const params = { since, to, bottom, offset, unfold, view }; - axios.get(link, { params }) - .then(({ data }) => $target.parent().replaceWith(data)) - .catch(() => flash(__('An error occurred while loading diff'))); + axios + .get(link, { params }) + .then(({ data }) => $target.parent().replaceWith(data)) + .catch(() => flash(__('An error occurred while loading diff'))); } openAnchoredDiff(cb) { diff --git a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js index 87621761500..4ae4ceabc21 100644 --- a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js +++ b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js @@ -18,52 +18,56 @@ const CommentAndResolveBtn = Vue.extend({ }; }, computed: { - showButton: function () { + showButton: function() { if (this.discussion) { return this.discussion.isResolvable(); } else { return false; } }, - isDiscussionResolved: function () { + isDiscussionResolved: function() { return this.discussion.isResolved(); }, - buttonText: function () { + buttonText: function() { if (this.isDiscussionResolved) { if (this.textareaIsEmpty) { - return "Unresolve discussion"; + return 'Unresolve discussion'; } else { - return "Comment & unresolve discussion"; + return 'Comment & unresolve discussion'; } } else { if (this.textareaIsEmpty) { - return "Resolve discussion"; + return 'Resolve discussion'; } else { - return "Comment & resolve discussion"; + return 'Comment & resolve discussion'; } } - } + }, }, created() { if (this.discussionId) { this.discussion = CommentsStore.state[this.discussionId]; } }, - mounted: function () { + mounted: function() { if (!this.discussionId) return; - const $textarea = $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`); + const $textarea = $( + `.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`, + ); this.textareaIsEmpty = $textarea.val() === ''; $textarea.on('input.comment-and-resolve-btn', () => { this.textareaIsEmpty = $textarea.val() === ''; }); }, - destroyed: function () { + destroyed: function() { if (!this.discussionId) return; - $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off('input.comment-and-resolve-btn'); - } + $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off( + 'input.comment-and-resolve-btn', + ); + }, }); Vue.component('comment-and-resolve-btn', CommentAndResolveBtn); diff --git a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js index 5528d2a542b..5bdeaaade68 100644 --- a/app/assets/javascripts/diff_notes/components/diff_note_avatars.js +++ b/app/assets/javascripts/diff_notes/components/diff_note_avatars.js @@ -83,7 +83,11 @@ const DiffNoteAvatars = Vue.extend({ this.addNoCommentClass(); this.setDiscussionVisible(); - this.lineType = $(this.$el).closest('.diff-line-num').hasClass('old_line') ? 'old' : 'new'; + this.lineType = $(this.$el) + .closest('.diff-line-num') + .hasClass('old_line') + ? 'old' + : 'new'; }); $(document).on('toggle.comments', () => { @@ -113,20 +117,30 @@ const DiffNoteAvatars = Vue.extend({ addNoCommentClass() { const { notesCount } = this; - $(this.$el).closest('.js-avatar-container') + $(this.$el) + .closest('.js-avatar-container') .toggleClass('no-comment-btn', notesCount > 0) .nextUntil('.js-avatar-container') .toggleClass('no-comment-btn', notesCount > 0); }, toggleDiscussionsToggleState() { - const $notesHolders = $(this.$el).closest('.code').find('.notes_holder'); + const $notesHolders = $(this.$el) + .closest('.code') + .find('.notes_holder'); const $visibleNotesHolders = $notesHolders.filter(':visible'); - const $toggleDiffCommentsBtn = $(this.$el).closest('.diff-file').find('.js-toggle-diff-comments'); - - $toggleDiffCommentsBtn.toggleClass('active', $notesHolders.length === $visibleNotesHolders.length); + const $toggleDiffCommentsBtn = $(this.$el) + .closest('.diff-file') + .find('.js-toggle-diff-comments'); + + $toggleDiffCommentsBtn.toggleClass( + 'active', + $notesHolders.length === $visibleNotesHolders.length, + ); }, setDiscussionVisible() { - this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible'); + this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is( + ':visible', + ); }, getTooltipText(note) { return `${note.authorName}: ${note.noteTruncated}`; diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js index 2b78bb58735..c0c21416275 100644 --- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js +++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js @@ -14,24 +14,24 @@ const JumpToDiscussion = Vue.extend({ required: true, }, }, - data: function () { + data: function() { return { discussions: CommentsStore.state, discussion: {}, }; }, computed: { - buttonText: function () { + buttonText: function() { if (this.discussionId) { return 'Jump to next unresolved discussion'; } else { return 'Jump to first unresolved discussion'; } }, - allResolved: function () { + allResolved: function() { return this.unresolvedDiscussionCount === 0; }, - showButton: function () { + showButton: function() { if (this.discussionId) { if (this.unresolvedDiscussionCount > 1) { return true; @@ -42,7 +42,7 @@ const JumpToDiscussion = Vue.extend({ return this.unresolvedDiscussionCount >= 1; } }, - lastResolvedId: function () { + lastResolvedId: function() { let lastId; for (const discussionId in this.discussions) { const discussion = this.discussions[discussionId]; @@ -52,13 +52,13 @@ const JumpToDiscussion = Vue.extend({ } } return lastId; - } + }, }, created() { this.discussion = this.discussions[this.discussionId]; }, methods: { - jumpToNextUnresolvedDiscussion: function () { + jumpToNextUnresolvedDiscussion: function() { let discussionsSelector; let discussionIdsInScope; let firstUnresolvedDiscussionId; @@ -68,9 +68,11 @@ const JumpToDiscussion = Vue.extend({ let jumpToFirstDiscussion = !this.discussionId; const discussionIdsForElements = function(elements) { - return elements.map(function() { - return $(this).attr('data-discussion-id'); - }).toArray(); + return elements + .map(function() { + return $(this).attr('data-discussion-id'); + }) + .toArray(); }; const { discussions } = this; @@ -144,8 +146,7 @@ const JumpToDiscussion = Vue.extend({ if (!discussion.isResolved()) { nextUnresolvedDiscussionId = discussionId; break; - } - else { + } else { continue; } } @@ -175,9 +176,9 @@ const JumpToDiscussion = Vue.extend({ // Resolved discussions are hidden in the diffs tab by default. // If they are marked unresolved on the notes tab, they will still be hidden on the diffs tab. // When jumping between unresolved discussions on the diffs tab, we show them. - $target.closest(".content").show(); + $target.closest('.content').show(); - const $notesHolder = $target.closest("tr.notes_holder"); + const $notesHolder = $target.closest('tr.notes_holder'); // Image diff discussions does not use notes_holder // so we should keep original $target value in those cases @@ -194,7 +195,7 @@ const JumpToDiscussion = Vue.extend({ prevEl = $target.prev(); // If the discussion doesn't have 4 lines above it, we'll have to do with fewer. - if (!prevEl.hasClass("line_holder")) { + if (!prevEl.hasClass('line_holder')) { break; } @@ -203,9 +204,9 @@ const JumpToDiscussion = Vue.extend({ } $.scrollTo($target, { - offset: -150 + offset: -150, }); - } + }, }, }); diff --git a/app/assets/javascripts/diff_notes/components/resolve_count.js b/app/assets/javascripts/diff_notes/components/resolve_count.js index eb539c6b348..d8b056096f4 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_count.js +++ b/app/assets/javascripts/diff_notes/components/resolve_count.js @@ -13,17 +13,17 @@ window.ResolveCount = Vue.extend({ required: true, }, }, - data: function () { + data: function() { return { - discussions: CommentsStore.state + discussions: CommentsStore.state, }; }, computed: { - allResolved: function () { + allResolved: function() { return this.resolvedDiscussionCount === this.discussionCount; }, resolvedCountText() { return this.discussionCount === 1 ? 'discussion' : 'discussions'; - } - } + }, + }, }); diff --git a/app/assets/javascripts/diff_notes/mixins/discussion.js b/app/assets/javascripts/diff_notes/mixins/discussion.js index 7589f9dd6e0..dea64dca132 100644 --- a/app/assets/javascripts/diff_notes/mixins/discussion.js +++ b/app/assets/javascripts/diff_notes/mixins/discussion.js @@ -2,10 +2,10 @@ const DiscussionMixins = { computed: { - discussionCount: function () { + discussionCount: function() { return Object.keys(this.discussions).length; }, - resolvedDiscussionCount: function () { + resolvedDiscussionCount: function() { let resolvedCount = 0; for (const discussionId in this.discussions) { @@ -18,7 +18,7 @@ const DiscussionMixins = { return resolvedCount; }, - unresolvedDiscussionCount: function () { + unresolvedDiscussionCount: function() { let unresolvedCount = 0; for (const discussionId in this.discussions) { @@ -30,8 +30,8 @@ const DiscussionMixins = { } return unresolvedCount; - } - } + }, + }, }; export default DiscussionMixins; diff --git a/app/assets/javascripts/diff_notes/models/discussion.js b/app/assets/javascripts/diff_notes/models/discussion.js index 787e6d8855f..daf61e5d467 100644 --- a/app/assets/javascripts/diff_notes/models/discussion.js +++ b/app/assets/javascripts/diff_notes/models/discussion.js @@ -6,22 +6,22 @@ import Vue from 'vue'; import { localTimeAgo } from '../../lib/utils/datetime_utility'; class DiscussionModel { - constructor (discussionId) { + constructor(discussionId) { this.id = discussionId; this.notes = {}; this.loading = false; this.canResolve = false; } - createNote (noteObj) { + createNote(noteObj) { Vue.set(this.notes, noteObj.noteId, new NoteModel(this.id, noteObj)); } - deleteNote (noteId) { + deleteNote(noteId) { Vue.delete(this.notes, noteId); } - getNote (noteId) { + getNote(noteId) { return this.notes[noteId]; } @@ -29,7 +29,7 @@ class DiscussionModel { return Object.keys(this.notes).length; } - isResolved () { + isResolved() { for (const noteId in this.notes) { const note = this.notes[noteId]; @@ -40,7 +40,7 @@ class DiscussionModel { return true; } - resolveAllNotes (resolved_by) { + resolveAllNotes(resolved_by) { for (const noteId in this.notes) { const note = this.notes[noteId]; @@ -51,7 +51,7 @@ class DiscussionModel { } } - unResolveAllNotes () { + unResolveAllNotes() { for (const noteId in this.notes) { const note = this.notes[noteId]; @@ -62,7 +62,7 @@ class DiscussionModel { } } - updateHeadline (data) { + updateHeadline(data) { const discussionSelector = `.discussion[data-discussion-id="${this.id}"]`; const $discussionHeadline = $(`${discussionSelector} .js-discussion-headline`); @@ -79,7 +79,7 @@ class DiscussionModel { } } - isResolvable () { + isResolvable() { if (!this.canResolve) { return false; } diff --git a/app/assets/javascripts/diff_notes/stores/comments.js b/app/assets/javascripts/diff_notes/stores/comments.js index d012cd02d10..060bb044f78 100644 --- a/app/assets/javascripts/diff_notes/stores/comments.js +++ b/app/assets/javascripts/diff_notes/stores/comments.js @@ -5,10 +5,10 @@ import Vue from 'vue'; window.CommentsStore = { state: {}, - get: function (discussionId, noteId) { + get: function(discussionId, noteId) { return this.state[discussionId].getNote(noteId); }, - createDiscussion: function (discussionId, canResolve) { + createDiscussion: function(discussionId, canResolve) { let discussion = this.state[discussionId]; if (!this.state[discussionId]) { discussion = new DiscussionModel(discussionId); @@ -21,18 +21,18 @@ window.CommentsStore = { return discussion; }, - create: function (noteObj) { + create: function(noteObj) { const discussion = this.createDiscussion(noteObj.discussionId); discussion.createNote(noteObj); }, - update: function (discussionId, noteId, resolved, resolved_by) { + update: function(discussionId, noteId, resolved, resolved_by) { const discussion = this.state[discussionId]; const note = discussion.getNote(noteId); note.resolved = resolved; note.resolved_by = resolved_by; }, - delete: function (discussionId, noteId) { + delete: function(discussionId, noteId) { const discussion = this.state[discussionId]; discussion.deleteNote(noteId); @@ -40,7 +40,7 @@ window.CommentsStore = { Vue.delete(this.state, discussionId); } }, - unresolvedDiscussionIds: function () { + unresolvedDiscussionIds: function() { const ids = []; for (const discussionId in this.state) { @@ -52,5 +52,5 @@ window.CommentsStore = { } return ids; - } + }, }; diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index edca45f22f9..a8d615dd8f0 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -41,6 +41,11 @@ export default { required: true, }, }, + data() { + return { + assignedDiscussions: false, + }; + }, computed: { ...mapState({ isLoading: state => state.diffs.isLoading, @@ -58,9 +63,9 @@ export default { plainDiffPath: state => state.diffs.plainDiffPath, emailPatchPath: state => state.diffs.emailPatchPath, }), - ...mapState('diffs', ['showTreeList']), + ...mapState('diffs', ['showTreeList', 'isLoading']), ...mapGetters('diffs', ['isParallelView']), - ...mapGetters(['isNotesFetched', 'discussionsStructuredByLineCode']), + ...mapGetters(['isNotesFetched', 'getNoteableData']), targetBranch() { return { branchName: this.targetBranchName, @@ -147,11 +152,12 @@ export default { } }, setDiscussions() { - if (this.isNotesFetched) { + if (this.isNotesFetched && !this.assignedDiscussions && !this.isLoading) { requestIdleCallback( - () => { - this.assignDiscussionsToDiff(this.discussionsStructuredByLineCode); - }, + () => + this.assignDiscussionsToDiff().then(() => { + this.assignedDiscussions = true; + }), { timeout: 1000 }, ); } diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue index 993206b2e73..23d0bad2ecb 100644 --- a/app/assets/javascripts/diffs/components/commit_item.vue +++ b/app/assets/javascripts/diffs/components/commit_item.vue @@ -43,7 +43,9 @@ export default { return (this.commit.author && this.commit.author.name) || this.commit.authorName; }, authorUrl() { - return (this.commit.author && this.commit.author.webUrl) || `mailto:${this.commit.authorEmail}`; + return ( + (this.commit.author && this.commit.author.webUrl) || `mailto:${this.commit.authorEmail}` + ); }, authorAvatar() { return (this.commit.author && this.commit.author.avatarUrl) || this.commit.authorGravatarUrl; diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index 4e04e50c52a..958e57c5652 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -29,7 +29,7 @@ export default { }, computed: { ...mapState('diffs', ['currentDiffFileId']), - ...mapGetters(['isNotesFetched', 'discussionsStructuredByLineCode']), + ...mapGetters(['isNotesFetched']), isCollapsed() { return this.file.collapsed || false; }, @@ -46,10 +46,10 @@ export default { showExpandMessage() { return ( this.isCollapsed || - !this.file.highlightedDiffLines && - !this.isLoadingCollapsedDiff && - !this.file.tooLarge && - this.file.text + (!this.file.highlightedDiffLines && + !this.isLoadingCollapsedDiff && + !this.file.tooLarge && + this.file.text) ); }, showLoadingIcon() { @@ -79,7 +79,7 @@ export default { .then(() => { requestIdleCallback( () => { - this.assignDiscussionsToDiff(this.discussionsStructuredByLineCode); + this.assignDiscussionsToDiff(); }, { timeout: 1000 }, ); diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue index cfe4273742f..34e836a570a 100644 --- a/app/assets/javascripts/diffs/components/tree_list.vue +++ b/app/assets/javascripts/diffs/components/tree_list.vue @@ -1,17 +1,30 @@ <script> import { mapActions, mapGetters, mapState } from 'vuex'; +import { TooltipDirective as Tooltip } from '@gitlab-org/gitlab-ui'; +import { convertPermissionToBoolean } from '~/lib/utils/common_utils'; import Icon from '~/vue_shared/components/icon.vue'; import FileRow from '~/vue_shared/components/file_row.vue'; import FileRowStats from './file_row_stats.vue'; +const treeListStorageKey = 'mr_diff_tree_list'; + export default { + directives: { + Tooltip, + }, components: { Icon, FileRow, }, data() { + const treeListStored = localStorage.getItem(treeListStorageKey); + const renderTreeList = treeListStored !== null ? + convertPermissionToBoolean(treeListStored) : true; + return { search: '', + renderTreeList, + focusSearch: false, }; }, computed: { @@ -20,15 +33,35 @@ export default { filteredTreeList() { const search = this.search.toLowerCase().trim(); - if (search === '') return this.tree; + if (search === '') return this.renderTreeList ? this.tree : this.allBlobs; return this.allBlobs.filter(f => f.name.toLowerCase().indexOf(search) >= 0); }, + rowDisplayTextKey() { + if (this.renderTreeList && this.search.trim() === '') { + return 'name'; + } + + return 'path'; + }, }, methods: { ...mapActions('diffs', ['toggleTreeOpen', 'scrollToFile']), clearSearch() { this.search = ''; + this.toggleFocusSearch(false); + }, + toggleRenderTreeList(toggle) { + this.renderTreeList = toggle; + localStorage.setItem(treeListStorageKey, this.renderTreeList); + }, + toggleFocusSearch(toggle) { + this.focusSearch = toggle; + }, + blurSearch() { + if (this.search.trim() === '') { + this.toggleFocusSearch(false); + } }, }, FileRowStats, @@ -37,28 +70,67 @@ export default { <template> <div class="tree-list-holder d-flex flex-column"> - <div class="append-bottom-8 position-relative tree-list-search"> - <icon - name="search" - class="position-absolute tree-list-icon" - /> - <input - v-model="search" - :placeholder="s__('MergeRequest|Filter files')" - type="search" - class="form-control" - /> - <button - v-show="search" - :aria-label="__('Clear search')" - type="button" - class="position-absolute tree-list-icon tree-list-clear-icon border-0 p-0" - @click="clearSearch" - > + <div class="append-bottom-8 position-relative tree-list-search d-flex"> + <div class="flex-fill d-flex"> <icon - name="close" + name="search" + class="position-absolute tree-list-icon" + /> + <input + v-model="search" + :placeholder="s__('MergeRequest|Filter files')" + type="search" + class="form-control" + @focus="toggleFocusSearch(true)" + @blur="blurSearch" /> - </button> + <button + v-show="search" + :aria-label="__('Clear search')" + type="button" + class="position-absolute bg-transparent tree-list-icon tree-list-clear-icon border-0 p-0" + @click="clearSearch" + > + <icon + name="close" + /> + </button> + </div> + <div + v-show="!focusSearch" + class="btn-group prepend-left-8 tree-list-view-toggle" + > + <button + v-tooltip.hover + :aria-label="__('List view')" + :title="__('List view')" + :class="{ + active: !renderTreeList + }" + class="btn btn-default pt-0 pb-0 d-flex align-items-center" + type="button" + @click="toggleRenderTreeList(false)" + > + <icon + name="hamburger" + /> + </button> + <button + v-tooltip.hover + :aria-label="__('Tree view')" + :title="__('Tree view')" + :class="{ + active: renderTreeList + }" + class="btn btn-default pt-0 pb-0 d-flex align-items-center" + type="button" + @click="toggleRenderTreeList(true)" + > + <icon + name="file-tree" + /> + </button> + </div> </div> <div class="tree-list-scroll" @@ -72,6 +144,8 @@ export default { :hide-extra-on-tree="true" :extra-component="$options.FileRowStats" :show-changed-icon="true" + :display-text-key="rowDisplayTextKey" + :should-truncate-start="true" @toggleTreeOpen="toggleTreeOpen" @clickFile="scrollToFile" /> diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index 1e0b27b538d..ca8ae605cb4 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -5,7 +5,6 @@ import createFlash from '~/flash'; import { s__ } from '~/locale'; import { handleLocationHash, historyPushState } from '~/lib/utils/common_utils'; import { mergeUrlParams, getLocationHash } from '~/lib/utils/url_utility'; -import { reduceDiscussionsToLineCodes } from '../../notes/stores/utils'; import { getDiffPositionByLineCode, getNoteFormData } from './utils'; import * as types from './mutation_types'; import { @@ -36,18 +35,17 @@ export const fetchDiffFiles = ({ state, commit }) => { // This is adding line discussions to the actual lines in the diff tree // once for parallel and once for inline mode -export const assignDiscussionsToDiff = ({ state, commit }, allLineDiscussions) => { +export const assignDiscussionsToDiff = ( + { commit, state, rootState }, + discussions = rootState.notes.discussions, +) => { const diffPositionByLineCode = getDiffPositionByLineCode(state.diffFiles); - Object.values(allLineDiscussions).forEach(discussions => { - if (discussions.length > 0) { - const { fileHash } = discussions[0]; - commit(types.SET_LINE_DISCUSSIONS_FOR_FILE, { - fileHash, - discussions, - diffPositionByLineCode, - }); - } + discussions.filter(discussion => discussion.diff_discussion).forEach(discussion => { + commit(types.SET_LINE_DISCUSSIONS_FOR_FILE, { + discussion, + diffPositionByLineCode, + }); }); }; @@ -190,9 +188,7 @@ export const saveDiffDiscussion = ({ dispatch }, { note, formData }) => { return dispatch('saveNote', postData, { root: true }) .then(result => dispatch('updateDiscussion', result.discussion, { root: true })) - .then(discussion => - dispatch('assignDiscussionsToDiff', reduceDiscussionsToLineCodes([discussion])), - ) + .then(discussion => dispatch('assignDiscussionsToDiff', [discussion])) .catch(() => createFlash(s__('MergeRequests|Saving the comment failed'))); }; diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js index 0b4485ecdb5..5a8aebd2086 100644 --- a/app/assets/javascripts/diffs/store/mutations.js +++ b/app/assets/javascripts/diffs/store/mutations.js @@ -90,53 +90,67 @@ export default { })); }, - [types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, discussions, diffPositionByLineCode }) { - const selectedFile = state.diffFiles.find(f => f.fileHash === fileHash); - const firstDiscussion = discussions[0]; - const isDiffDiscussion = firstDiscussion.diff_discussion; - const hasLineCode = firstDiscussion.line_code; - const diffPosition = diffPositionByLineCode[firstDiscussion.line_code]; - - if ( - selectedFile && - isDiffDiscussion && - hasLineCode && - diffPosition && + [types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { discussion, diffPositionByLineCode }) { + const { latestDiff } = state; + + const discussionLineCode = discussion.line_code; + const fileHash = discussion.diff_file.file_hash; + const lineCheck = ({ lineCode }) => + lineCode === discussionLineCode && isDiscussionApplicableToLine({ - discussion: firstDiscussion, - diffPosition, - latestDiff: state.latestDiff, - }) - ) { - const targetLine = selectedFile.parallelDiffLines.find( - line => - (line.left && line.left.lineCode === firstDiscussion.line_code) || - (line.right && line.right.lineCode === firstDiscussion.line_code), - ); - if (targetLine) { - if (targetLine.left && targetLine.left.lineCode === firstDiscussion.line_code) { - Object.assign(targetLine.left, { - discussions, - }); - } else { - Object.assign(targetLine.right, { - discussions, + discussion, + diffPosition: diffPositionByLineCode[lineCode], + latestDiff, + }); + + state.diffFiles = state.diffFiles.map(diffFile => { + if (diffFile.fileHash === fileHash) { + const file = { ...diffFile }; + + if (file.highlightedDiffLines) { + file.highlightedDiffLines = file.highlightedDiffLines.map(line => { + if (lineCheck(line)) { + return { + ...line, + discussions: line.discussions.concat(discussion), + }; + } + + return line; }); } - } - - if (selectedFile.highlightedDiffLines) { - const targetInlineLine = selectedFile.highlightedDiffLines.find( - line => line.lineCode === firstDiscussion.line_code, - ); - if (targetInlineLine) { - Object.assign(targetInlineLine, { - discussions, + if (file.parallelDiffLines) { + file.parallelDiffLines = file.parallelDiffLines.map(line => { + const left = line.left && lineCheck(line.left); + const right = line.right && lineCheck(line.right); + + if (left || right) { + return { + left: { + ...line.left, + discussions: left ? line.left.discussions.concat(discussion) : [], + }, + right: { + ...line.right, + discussions: right ? line.right.discussions.concat(discussion) : [], + }, + }; + } + + return line; }); } + + if (!file.parallelDiffLines || !file.highlightedDiffLines) { + file.discussions = file.discussions.concat(discussion); + } + + return file; } - } + + return diffFile; + }); }, [types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, lineCode }) { diff --git a/app/assets/javascripts/droplab/constants.js b/app/assets/javascripts/droplab/constants.js index 868d47e91b3..6451af49d36 100644 --- a/app/assets/javascripts/droplab/constants.js +++ b/app/assets/javascripts/droplab/constants.js @@ -6,11 +6,4 @@ const IGNORE_CLASS = 'droplab-item-ignore'; // Matches `{{anything}}` and `{{ everything }}`. const TEMPLATE_REGEX = /\{\{(.+?)\}\}/g; -export { - DATA_TRIGGER, - DATA_DROPDOWN, - SELECTED_CLASS, - ACTIVE_CLASS, - TEMPLATE_REGEX, - IGNORE_CLASS, -}; +export { DATA_TRIGGER, DATA_DROPDOWN, SELECTED_CLASS, ACTIVE_CLASS, TEMPLATE_REGEX, IGNORE_CLASS }; diff --git a/app/assets/javascripts/droplab/drop_down.js b/app/assets/javascripts/droplab/drop_down.js index 3cc316c3f3e..ccb3d56ed8c 100644 --- a/app/assets/javascripts/droplab/drop_down.js +++ b/app/assets/javascripts/droplab/drop_down.js @@ -2,7 +2,7 @@ import utils from './utils'; import { SELECTED_CLASS, IGNORE_CLASS } from './constants'; class DropDown { - constructor(list, config = { }) { + constructor(list, config = {}) { this.currentIndex = 0; this.hidden = true; this.list = typeof list === 'string' ? document.querySelector(list) : list; @@ -157,7 +157,7 @@ class DropDown { static setImagesSrc(template) { const images = [...template.querySelectorAll('img[data-src]')]; - images.forEach((image) => { + images.forEach(image => { const img = image; img.src = img.getAttribute('data-src'); diff --git a/app/assets/javascripts/droplab/drop_lab.js b/app/assets/javascripts/droplab/drop_lab.js index 2a02ede72bf..1339e28d8b8 100644 --- a/app/assets/javascripts/droplab/drop_lab.js +++ b/app/assets/javascripts/droplab/drop_lab.js @@ -51,7 +51,7 @@ class DropLab { } processData(trigger, data, methodName) { - this.hooks.forEach((hook) => { + this.hooks.forEach(hook => { if (Array.isArray(trigger)) hook.list[methodName](trigger); if (hook.trigger.id === trigger) hook.list[methodName](data); @@ -78,7 +78,8 @@ class DropLab { } changeHookList(trigger, list, plugins, config) { - const availableTrigger = typeof trigger === 'string' ? document.getElementById(trigger) : trigger; + const availableTrigger = + typeof trigger === 'string' ? document.getElementById(trigger) : trigger; this.hooks.forEach((hook, i) => { const aHook = hook; diff --git a/app/assets/javascripts/droplab/keyboard.js b/app/assets/javascripts/droplab/keyboard.js index 02f1b805ce4..40837ffdf8f 100644 --- a/app/assets/javascripts/droplab/keyboard.js +++ b/app/assets/javascripts/droplab/keyboard.js @@ -2,15 +2,18 @@ import { ACTIVE_CLASS } from './constants'; -const Keyboard = function () { +const Keyboard = function() { var currentKey; var currentFocus; var isUpArrow = false; var isDownArrow = false; var removeHighlight = function removeHighlight(list) { - var itemElements = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider):not(.hidden)'), 0); + var itemElements = Array.prototype.slice.call( + list.list.querySelectorAll('li:not(.divider):not(.hidden)'), + 0, + ); var listItems = []; - for(var i = 0; i < itemElements.length; i++) { + for (var i = 0; i < itemElements.length; i++) { var listItem = itemElements[i]; listItem.classList.remove(ACTIVE_CLASS); @@ -23,13 +26,13 @@ const Keyboard = function () { var setMenuForArrows = function setMenuForArrows(list) { var listItems = removeHighlight(list); - if(list.currentIndex>0){ - if(!listItems[list.currentIndex-1]){ - list.currentIndex = list.currentIndex-1; + if (list.currentIndex > 0) { + if (!listItems[list.currentIndex - 1]) { + list.currentIndex = list.currentIndex - 1; } - if (listItems[list.currentIndex-1]) { - var el = listItems[list.currentIndex-1]; + if (listItems[list.currentIndex - 1]) { + var el = listItems[list.currentIndex - 1]; var filterDropdownEl = el.closest('.filter-dropdown'); el.classList.add(ACTIVE_CLASS); @@ -55,7 +58,7 @@ const Keyboard = function () { }; var selectItem = function selectItem(list) { var listItems = removeHighlight(list); - var currentItem = listItems[list.currentIndex-1]; + var currentItem = listItems[list.currentIndex - 1]; var listEvent = new CustomEvent('click.dl', { detail: { list: list, @@ -65,43 +68,49 @@ const Keyboard = function () { }); list.list.dispatchEvent(listEvent); list.hide(); - } + }; - var keydown = function keydown(e){ + var keydown = function keydown(e) { var typedOn = e.target; var list = e.detail.hook.list; var currentIndex = list.currentIndex; isUpArrow = false; isDownArrow = false; - if(e.detail.which){ + if (e.detail.which) { currentKey = e.detail.which; - if(currentKey === 13){ + if (currentKey === 13) { selectItem(e.detail.hook.list); return; } - if(currentKey === 38) { + if (currentKey === 38) { isUpArrow = true; } - if(currentKey === 40) { + if (currentKey === 40) { isDownArrow = true; } - } else if(e.detail.key) { + } else if (e.detail.key) { currentKey = e.detail.key; - if(currentKey === 'Enter'){ + if (currentKey === 'Enter') { selectItem(e.detail.hook.list); return; } - if(currentKey === 'ArrowUp') { + if (currentKey === 'ArrowUp') { isUpArrow = true; } - if(currentKey === 'ArrowDown') { + if (currentKey === 'ArrowDown') { isDownArrow = true; } } - if(isUpArrow){ currentIndex--; } - if(isDownArrow){ currentIndex++; } - if(currentIndex < 0){ currentIndex = 0; } + if (isUpArrow) { + currentIndex--; + } + if (isDownArrow) { + currentIndex++; + } + if (currentIndex < 0) { + currentIndex = 0; + } list.currentIndex = currentIndex; setMenuForArrows(e.detail.hook.list); }; diff --git a/app/assets/javascripts/droplab/plugins/ajax.js b/app/assets/javascripts/droplab/plugins/ajax.js index 267b53fa4f2..48b2a90c459 100644 --- a/app/assets/javascripts/droplab/plugins/ajax.js +++ b/app/assets/javascripts/droplab/plugins/ajax.js @@ -43,12 +43,12 @@ const Ajax = { return AjaxCache.retrieve(config.endpoint) .then(self.preprocessing.bind(null, config)) - .then((data) => self._loadData(data, config, self)) + .then(data => self._loadData(data, config, self)) .catch(config.onError); }, destroy: function() { this.destroyed = true; - } + }, }; export default Ajax; diff --git a/app/assets/javascripts/droplab/plugins/ajax_filter.js b/app/assets/javascripts/droplab/plugins/ajax_filter.js index 1db20227a16..66a52548417 100644 --- a/app/assets/javascripts/droplab/plugins/ajax_filter.js +++ b/app/assets/javascripts/droplab/plugins/ajax_filter.js @@ -41,8 +41,10 @@ const AjaxFilter = { if (config.searchValueFunction) { searchValue = config.searchValueFunction(); } - if (config.loadingTemplate && this.hook.list.data === undefined || - this.hook.list.data.length === 0) { + if ( + (config.loadingTemplate && this.hook.list.data === undefined) || + this.hook.list.data.length === 0 + ) { var dynamicList = this.hook.list.list.querySelector('[data-dynamic]'); var loadingTemplate = document.createElement('div'); loadingTemplate.innerHTML = config.loadingTemplate; @@ -61,7 +63,7 @@ const AjaxFilter = { params[config.searchKey] = searchValue; var url = config.endpoint + this.buildParams(params); return AjaxCache.retrieve(url) - .then((data) => { + .then(data => { this._loadData(data, config); if (config.onLoadingFinished) { config.onLoadingFinished(data); @@ -72,8 +74,7 @@ const AjaxFilter = { _loadData(data, config) { const list = this.hook.list; - if (config.loadingTemplate && list.data === undefined || - list.data.length === 0) { + if ((config.loadingTemplate && list.data === undefined) || list.data.length === 0) { const dataLoadingTemplate = list.list.querySelector('[data-loading-template]'); if (dataLoadingTemplate) { dataLoadingTemplate.outerHTML = this.listTemplate; @@ -81,7 +82,8 @@ const AjaxFilter = { } if (!this.destroyed) { var hookListChildren = list.list.children; - var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic'); + var onlyDynamicList = + hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic'); if (onlyDynamicList && data.length === 0) { list.hide(); } @@ -100,12 +102,12 @@ const AjaxFilter = { }, destroy: function destroy() { - if (this.timeout)clearTimeout(this.timeout); + if (this.timeout) clearTimeout(this.timeout); this.destroyed = true; this.hook.trigger.removeEventListener('keydown.dl', this.eventWrapper.debounceTrigger); this.hook.trigger.removeEventListener('focus', this.eventWrapper.debounceTrigger); - } + }, }; export default AjaxFilter; diff --git a/app/assets/javascripts/droplab/plugins/filter.js b/app/assets/javascripts/droplab/plugins/filter.js index 404d707cf7a..6f1dc252d24 100644 --- a/app/assets/javascripts/droplab/plugins/filter.js +++ b/app/assets/javascripts/droplab/plugins/filter.js @@ -1,7 +1,7 @@ /* eslint-disable */ const Filter = { - keydown: function(e){ + keydown: function(e) { if (this.destroyed) return; var hiddenCount = 0; @@ -14,14 +14,14 @@ const Filter = { var matches = []; var filterFunction; // will only work on dynamically set data - if(!data){ + if (!data) { return; } if (config && config.filterFunction && typeof config.filterFunction === 'function') { filterFunction = config.filterFunction; } else { - filterFunction = function(o){ + filterFunction = function(o) { // cheap string search o.droplab_hidden = o[config.template].toLowerCase().indexOf(value) === -1; return o; @@ -47,20 +47,23 @@ const Filter = { }, debounceKeydown: function debounceKeydown(e) { - if ([ - 13, // enter - 16, // shift - 17, // ctrl - 18, // alt - 20, // caps lock - 37, // left arrow - 38, // up arrow - 39, // right arrow - 40, // down arrow - 91, // left window - 92, // right window - 93, // select - ].indexOf(e.detail.which || e.detail.keyCode) > -1) return; + if ( + [ + 13, // enter + 16, // shift + 17, // ctrl + 18, // alt + 20, // caps lock + 37, // left arrow + 38, // up arrow + 39, // right arrow + 40, // down arrow + 91, // left window + 92, // right window + 93, // select + ].indexOf(e.detail.which || e.detail.keyCode) > -1 + ) + return; if (this.timeout) clearTimeout(this.timeout); this.timeout = setTimeout(this.keydown.bind(this, e), 200); @@ -87,7 +90,7 @@ const Filter = { this.hook.trigger.removeEventListener('keydown.dl', this.eventWrapper.debounceKeydown); this.hook.trigger.removeEventListener('mousedown.dl', this.eventWrapper.debounceKeydown); - } + }, }; export default Filter; diff --git a/app/assets/javascripts/droplab/plugins/input_setter.js b/app/assets/javascripts/droplab/plugins/input_setter.js index d01fbc5830d..6cfc738a1e3 100644 --- a/app/assets/javascripts/droplab/plugins/input_setter.js +++ b/app/assets/javascripts/droplab/plugins/input_setter.js @@ -36,8 +36,8 @@ const InputSetter = { const inputAttribute = config.inputAttribute; if (input.hasAttribute(inputAttribute)) return input.setAttribute(inputAttribute, newValue); - if (input.tagName === 'INPUT') return input.value = newValue; - return input.textContent = newValue; + if (input.tagName === 'INPUT') return (input.value = newValue); + return (input.textContent = newValue); }, destroy() { diff --git a/app/assets/javascripts/droplab/utils.js b/app/assets/javascripts/droplab/utils.js index bfe056a0fcc..5272778ce2d 100644 --- a/app/assets/javascripts/droplab/utils.js +++ b/app/assets/javascripts/droplab/utils.js @@ -5,7 +5,12 @@ import { DATA_TRIGGER, DATA_DROPDOWN, TEMPLATE_REGEX } from './constants'; const utils = { toCamelCase(attr) { - return this.camelize(attr.split('-').slice(1).join(' ')); + return this.camelize( + attr + .split('-') + .slice(1) + .join(' '), + ); }, template(templateString, data) { @@ -17,9 +22,11 @@ const utils = { }, camelize(str) { - return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => { - return index === 0 ? letter.toLowerCase() : letter.toUpperCase(); - }).replace(/\s+/g, ''); + return str + .replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => { + return index === 0 ? letter.toLowerCase() : letter.toUpperCase(); + }) + .replace(/\s+/g, ''); }, closest(thisTag, stopTag) { diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index d2778bcdf1c..9987fbcb6a7 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -136,7 +136,7 @@ export default function dropzoneInput(form) { // removeAllFiles(true) stops uploading files (if any) // and remove them from dropzone files queue. - $cancelButton.on('click', (e) => { + $cancelButton.on('click', e => { e.preventDefault(); e.stopPropagation(); Dropzone.forElement($formDropzone.get(0)).removeAllFiles(true); @@ -146,8 +146,10 @@ export default function dropzoneInput(form) { // clear dropzone files queue, change status of failed files to undefined, // and add that files to the dropzone files queue again. // addFile() adds file to dropzone files queue and upload it. - $retryLink.on('click', (e) => { - const dropzoneInstance = Dropzone.forElement(e.target.closest('.js-main-target-form').querySelector('.div-dropzone')); + $retryLink.on('click', e => { + const dropzoneInstance = Dropzone.forElement( + e.target.closest('.js-main-target-form').querySelector('.div-dropzone'), + ); const failedFiles = dropzoneInstance.files; e.preventDefault(); @@ -156,7 +158,7 @@ export default function dropzoneInput(form) { // uploading of files that are being uploaded at the moment. dropzoneInstance.removeAllFiles(true); - failedFiles.map((failedFile) => { + failedFiles.map(failedFile => { const file = failedFile; if (file.status === Dropzone.ERROR) { @@ -168,7 +170,7 @@ export default function dropzoneInput(form) { }); }); // eslint-disable-next-line consistent-return - handlePaste = (event) => { + handlePaste = event => { const pasteEvent = event.originalEvent; if (pasteEvent.clipboardData && pasteEvent.clipboardData.items) { const image = isImage(pasteEvent); @@ -182,7 +184,7 @@ export default function dropzoneInput(form) { } }; - isImage = (data) => { + isImage = data => { let i = 0; while (i < data.clipboardData.items.length) { const item = data.clipboardData.items[i]; @@ -203,8 +205,12 @@ export default function dropzoneInput(form) { const caretStart = textarea.selectionStart; const caretEnd = textarea.selectionEnd; const textEnd = $(child).val().length; - const beforeSelection = $(child).val().substring(0, caretStart); - const afterSelection = $(child).val().substring(caretEnd, textEnd); + const beforeSelection = $(child) + .val() + .substring(0, caretStart); + const afterSelection = $(child) + .val() + .substring(caretEnd, textEnd); $(child).val(beforeSelection + formattedText + afterSelection); textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length); textarea.style.height = `${textarea.scrollHeight}px`; @@ -212,11 +218,11 @@ export default function dropzoneInput(form) { return formTextarea.trigger('input'); }; - addFileToForm = (path) => { + addFileToForm = path => { $(form).append(`<input type="hidden" name="files[]" value="${_.escape(path)}">`); }; - getFilename = (e) => { + getFilename = e => { let value; if (window.clipboardData && window.clipboardData.getData) { value = window.clipboardData.getData('Text'); @@ -231,7 +237,7 @@ export default function dropzoneInput(form) { const closeSpinner = () => $uploadingProgressContainer.addClass('hide'); - const showError = (message) => { + const showError = message => { $uploadingErrorContainer.removeClass('hide'); $uploadingErrorMessage.html(message); }; @@ -252,14 +258,15 @@ export default function dropzoneInput(form) { showSpinner(); closeAlertMessage(); - axios.post(uploadsPath, formData) + axios + .post(uploadsPath, formData) .then(({ data }) => { const md = data.link.markdown; insertToTextArea(filename, md); closeSpinner(); }) - .catch((e) => { + .catch(e => { showError(e.response.data.message); closeSpinner(); }); @@ -267,7 +274,8 @@ export default function dropzoneInput(form) { updateAttachingMessage = (files, messageContainer) => { let attachingMessage; - const filesCount = files.filter(file => file.status === 'uploading' || file.status === 'queued').length; + const filesCount = files.filter(file => file.status === 'uploading' || file.status === 'queued') + .length; // Dinamycally change uploading files text depending on files number in // dropzone files queue. @@ -282,7 +290,10 @@ export default function dropzoneInput(form) { form.find('.markdown-selector').click(function onMarkdownClick(e) { e.preventDefault(); - $(this).closest('.gfm-form').find('.div-dropzone').click(); + $(this) + .closest('.gfm-form') + .find('.div-dropzone') + .click(); formTextarea.focus(); }); diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js index c7b5a35cc14..dbfcf8cc921 100644 --- a/app/assets/javascripts/due_date_select.js +++ b/app/assets/javascripts/due_date_select.js @@ -3,8 +3,7 @@ import Pikaday from 'pikaday'; import dateFormat from 'dateformat'; import { __ } from '~/locale'; import axios from './lib/utils/axios_utils'; -import { timeFor } from './lib/utils/datetime_utility'; -import { parsePikadayDate, pikadayToString } from './lib/utils/datefix'; +import { timeFor, parsePikadayDate, pikadayToString } from './lib/utils/datetime_utility'; import boardsStore from './boards/stores/boards_store'; class DueDateSelect { diff --git a/app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js b/app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js index e9defb62cf8..c5f9fcf6358 100644 --- a/app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js +++ b/app/assets/javascripts/emoji/support/is_emoji_unicode_supported.js @@ -13,9 +13,11 @@ const rainbowCodePoint = 127752; // parseInt('1F308', 16) function isRainbowFlagEmoji(emojiUnicode) { const characters = Array.from(emojiUnicode); // Length 4 because flags are made of 2 characters which are surrogate pairs - return emojiUnicode.length === 4 && + return ( + emojiUnicode.length === 4 && characters[0].codePointAt(0) === baseFlagCodePoint && - characters[1].codePointAt(0) === rainbowCodePoint; + characters[1].codePointAt(0) === rainbowCodePoint + ); } // Chrome <57 renders keycaps oddly @@ -26,22 +28,28 @@ function isKeycapEmoji(emojiUnicode) { } // Check for a skin tone variation emoji which aren't always supported -const tone1 = 127995;// parseInt('1F3FB', 16) -const tone5 = 127999;// parseInt('1F3FF', 16) +const tone1 = 127995; // parseInt('1F3FB', 16) +const tone5 = 127999; // parseInt('1F3FF', 16) function isSkinToneComboEmoji(emojiUnicode) { - return emojiUnicode.length > 2 && Array.from(emojiUnicode).some((char) => { - const cp = char.codePointAt(0); - return cp >= tone1 && cp <= tone5; - }); + return ( + emojiUnicode.length > 2 && + Array.from(emojiUnicode).some(char => { + const cp = char.codePointAt(0); + return cp >= tone1 && cp <= tone5; + }) + ); } // macOS supports most skin tone emoji's but // doesn't support the skin tone versions of horse racing -const horseRacingCodePoint = 127943;// parseInt('1F3C7', 16) +const horseRacingCodePoint = 127943; // parseInt('1F3C7', 16) function isHorceRacingSkinToneComboEmoji(emojiUnicode) { const firstCharacter = Array.from(emojiUnicode)[0]; - return firstCharacter && firstCharacter.codePointAt(0) === horseRacingCodePoint && - isSkinToneComboEmoji(emojiUnicode); + return ( + firstCharacter && + firstCharacter.codePointAt(0) === horseRacingCodePoint && + isSkinToneComboEmoji(emojiUnicode) + ); } // Check for `family_*`, `kiss_*`, `couple_*` @@ -52,7 +60,7 @@ const personEndCodePoint = 128105; // parseInt('1F469', 16) function isPersonZwjEmoji(emojiUnicode) { let hasPersonEmoji = false; let hasZwj = false; - Array.from(emojiUnicode).forEach((character) => { + Array.from(emojiUnicode).forEach(character => { const cp = character.codePointAt(0); if (cp === zwj) { hasZwj = true; @@ -80,10 +88,7 @@ function checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) { // in `isEmojiUnicodeSupported` logic function checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) { const isSkinToneResult = isSkinToneComboEmoji(emojiUnicode); - return ( - (unicodeSupportMap.skinToneModifier && isSkinToneResult) || - !isSkinToneResult - ); + return (unicodeSupportMap.skinToneModifier && isSkinToneResult) || !isSkinToneResult; } // Helper func so we don't have to run `isHorceRacingSkinToneComboEmoji` twice @@ -91,8 +96,7 @@ function checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) { function checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) { const isHorseRacingSkinToneResult = isHorceRacingSkinToneComboEmoji(emojiUnicode); return ( - (unicodeSupportMap.horseRacing && isHorseRacingSkinToneResult) || - !isHorseRacingSkinToneResult + (unicodeSupportMap.horseRacing && isHorseRacingSkinToneResult) || !isHorseRacingSkinToneResult ); } @@ -100,10 +104,7 @@ function checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnico // in `isEmojiUnicodeSupported` logic function checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode) { const isPersonZwjResult = isPersonZwjEmoji(emojiUnicode); - return ( - (unicodeSupportMap.personZwj && isPersonZwjResult) || - !isPersonZwjResult - ); + return (unicodeSupportMap.personZwj && isPersonZwjResult) || !isPersonZwjResult; } // Takes in a support map and determines whether @@ -111,16 +112,20 @@ function checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode) { // // Combines all the edge case tests into a one-stop shop method function isEmojiUnicodeSupported(unicodeSupportMap = {}, emojiUnicode, unicodeVersion) { - const isOlderThanChrome57 = unicodeSupportMap.meta && unicodeSupportMap.meta.isChrome && + const isOlderThanChrome57 = + unicodeSupportMap.meta && + unicodeSupportMap.meta.isChrome && unicodeSupportMap.meta.chromeVersion < 57; // For comments about each scenario, see the comments above each individual respective function - return unicodeSupportMap[unicodeVersion] && + return ( + unicodeSupportMap[unicodeVersion] && !(isOlderThanChrome57 && isKeycapEmoji(emojiUnicode)) && checkFlagEmojiSupport(unicodeSupportMap, emojiUnicode) && checkSkinToneModifierSupport(unicodeSupportMap, emojiUnicode) && checkHorseRacingSkinToneComboEmojiSupport(unicodeSupportMap, emojiUnicode) && - checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode); + checkPersonEmojiSupport(unicodeSupportMap, emojiUnicode) + ); } export { diff --git a/app/assets/javascripts/environments/components/container.vue b/app/assets/javascripts/environments/components/container.vue index 9de851c9409..00d197d294f 100644 --- a/app/assets/javascripts/environments/components/container.vue +++ b/app/assets/javascripts/environments/components/container.vue @@ -1,40 +1,40 @@ <script> - import tablePagination from '../../vue_shared/components/table_pagination.vue'; - import environmentTable from '../components/environments_table.vue'; +import tablePagination from '../../vue_shared/components/table_pagination.vue'; +import environmentTable from '../components/environments_table.vue'; - export default { - components: { - environmentTable, - tablePagination, +export default { + components: { + environmentTable, + tablePagination, + }, + props: { + isLoading: { + type: Boolean, + required: true, }, - props: { - isLoading: { - type: Boolean, - required: true, - }, - environments: { - type: Array, - required: true, - }, - pagination: { - type: Object, - required: true, - }, - canCreateDeployment: { - type: Boolean, - required: true, - }, - canReadEnvironment: { - type: Boolean, - required: true, - }, + environments: { + type: Array, + required: true, }, - methods: { - onChangePage(page) { - this.$emit('onChangePage', page); - }, + pagination: { + type: Object, + required: true, }, - }; + canCreateDeployment: { + type: Boolean, + required: true, + }, + canReadEnvironment: { + type: Boolean, + required: true, + }, + }, + methods: { + onChangePage(page) { + this.$emit('onChangePage', page); + }, + }, +}; </script> <template> diff --git a/app/assets/javascripts/environments/components/empty_state.vue b/app/assets/javascripts/environments/components/empty_state.vue index cf78f89981e..2360a52645b 100644 --- a/app/assets/javascripts/environments/components/empty_state.vue +++ b/app/assets/javascripts/environments/components/empty_state.vue @@ -1,44 +1,45 @@ <script> - export default { - name: 'EnvironmentsEmptyState', - props: { - newPath: { - type: String, - required: true, - }, - canCreateEnvironment: { - type: Boolean, - required: true, - }, - helpPath: { - type: String, - required: true, - }, +export default { + name: 'EnvironmentsEmptyState', + props: { + newPath: { + type: String, + required: true, }, - }; + canCreateEnvironment: { + type: Boolean, + required: true, + }, + helpPath: { + type: String, + required: true, + }, + }, +}; </script> <template> - <div class="blank-state-row"> - <div class="blank-state-center"> - <h2 class="blank-state-title js-blank-state-title"> - {{ s__("Environments|You don't have any environments right now.") }} - </h2> + <div class="empty-state"> + <div class="text-content"> + <h4 class="blank-state-title js-blank-state-title"> + {{ s__("Environments|You don't have any environments right now") }} + </h4> <p class="blank-state-text"> {{ s__(`Environments|Environments are places where -code gets deployed, such as staging or production.`) }} - <br /> + code gets deployed, such as staging or production.`) }} <a :href="helpPath"> {{ s__("Environments|Read more about environments") }} </a> </p> - <a - v-if="canCreateEnvironment" - :href="newPath" - class="btn btn-success js-new-environment-button" - > - {{ s__("Environments|New environment") }} - </a> + <div class="text-center"> + <a + v-if="canCreateEnvironment" + :href="newPath" + class="btn btn-success js-new-environment-button" + > + {{ s__("Environments|New environment") }} + </a> + </div> </div> </div> </template> diff --git a/app/assets/javascripts/environments/components/environment_rollback.vue b/app/assets/javascripts/environments/components/environment_rollback.vue index efbf88d0f11..9e137f79dcc 100644 --- a/app/assets/javascripts/environments/components/environment_rollback.vue +++ b/app/assets/javascripts/environments/components/environment_rollback.vue @@ -38,7 +38,9 @@ export default { computed: { title() { - return this.isLastDeployment ? s__('Environments|Re-deploy to environment') : s__('Environments|Rollback environment'); + return this.isLastDeployment + ? s__('Environments|Re-deploy to environment') + : s__('Environments|Rollback environment'); }, }, diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js index de0fbdb2e91..f044d31c776 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js +++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js @@ -5,31 +5,32 @@ import Translate from '../../vue_shared/translate'; Vue.use(Translate); -export default () => new Vue({ - el: '#environments-folder-list-view', - components: { - environmentsFolderApp, - }, - data() { - const environmentsData = document.querySelector(this.$options.el).dataset; +export default () => + new Vue({ + el: '#environments-folder-list-view', + components: { + environmentsFolderApp, + }, + data() { + const environmentsData = document.querySelector(this.$options.el).dataset; - return { - endpoint: environmentsData.endpoint, - folderName: environmentsData.folderName, - cssContainerClass: environmentsData.cssClass, - canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment), - canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment), - }; - }, - render(createElement) { - return createElement('environments-folder-app', { - props: { - endpoint: this.endpoint, - folderName: this.folderName, - cssContainerClass: this.cssContainerClass, - canCreateDeployment: this.canCreateDeployment, - canReadEnvironment: this.canReadEnvironment, - }, - }); - }, -}); + return { + endpoint: environmentsData.endpoint, + folderName: environmentsData.folderName, + cssContainerClass: environmentsData.cssClass, + canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment), + canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment), + }; + }, + render(createElement) { + return createElement('environments-folder-app', { + props: { + endpoint: this.endpoint, + folderName: this.folderName, + cssContainerClass: this.cssContainerClass, + canCreateDeployment: this.canCreateDeployment, + canReadEnvironment: this.canReadEnvironment, + }, + }); + }, + }); diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue index e69bfa0b2cc..6be4845fe4c 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.vue +++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue @@ -1,46 +1,43 @@ <script> - import environmentsMixin from '../mixins/environments_mixin'; - import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; - import StopEnvironmentModal from '../components/stop_environment_modal.vue'; +import environmentsMixin from '../mixins/environments_mixin'; +import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; +import StopEnvironmentModal from '../components/stop_environment_modal.vue'; - export default { - components: { - StopEnvironmentModal, - }, +export default { + components: { + StopEnvironmentModal, + }, - mixins: [ - environmentsMixin, - CIPaginationMixin, - ], + mixins: [environmentsMixin, CIPaginationMixin], - props: { - endpoint: { - type: String, - required: true, - }, - folderName: { - type: String, - required: true, - }, - cssContainerClass: { - type: String, - required: true, - }, - canCreateDeployment: { - type: Boolean, - required: true, - }, - canReadEnvironment: { - type: Boolean, - required: true, - }, + props: { + endpoint: { + type: String, + required: true, + }, + folderName: { + type: String, + required: true, + }, + cssContainerClass: { + type: String, + required: true, + }, + canCreateDeployment: { + type: Boolean, + required: true, + }, + canReadEnvironment: { + type: Boolean, + required: true, }, - methods: { - successCallback(resp) { - this.saveData(resp); - }, + }, + methods: { + successCallback(resp) { + this.saveData(resp); }, - }; + }, +}; </script> <template> <div :class="cssContainerClass"> diff --git a/app/assets/javascripts/environments/index.js b/app/assets/javascripts/environments/index.js index afc4aba6554..5b6833fb15d 100644 --- a/app/assets/javascripts/environments/index.js +++ b/app/assets/javascripts/environments/index.js @@ -5,35 +5,36 @@ import Translate from '../vue_shared/translate'; Vue.use(Translate); -export default () => new Vue({ - el: '#environments-list-view', - components: { - environmentsComponent, - }, - data() { - const environmentsData = document.querySelector(this.$options.el).dataset; +export default () => + new Vue({ + el: '#environments-list-view', + components: { + environmentsComponent, + }, + data() { + const environmentsData = document.querySelector(this.$options.el).dataset; - return { - endpoint: environmentsData.environmentsDataEndpoint, - newEnvironmentPath: environmentsData.newEnvironmentPath, - helpPagePath: environmentsData.helpPagePath, - cssContainerClass: environmentsData.cssClass, - canCreateEnvironment: convertPermissionToBoolean(environmentsData.canCreateEnvironment), - canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment), - canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment), - }; - }, - render(createElement) { - return createElement('environments-component', { - props: { - endpoint: this.endpoint, - newEnvironmentPath: this.newEnvironmentPath, - helpPagePath: this.helpPagePath, - cssContainerClass: this.cssContainerClass, - canCreateEnvironment: this.canCreateEnvironment, - canCreateDeployment: this.canCreateDeployment, - canReadEnvironment: this.canReadEnvironment, - }, - }); - }, -}); + return { + endpoint: environmentsData.environmentsDataEndpoint, + newEnvironmentPath: environmentsData.newEnvironmentPath, + helpPagePath: environmentsData.helpPagePath, + cssContainerClass: environmentsData.cssClass, + canCreateEnvironment: convertPermissionToBoolean(environmentsData.canCreateEnvironment), + canCreateDeployment: convertPermissionToBoolean(environmentsData.canCreateDeployment), + canReadEnvironment: convertPermissionToBoolean(environmentsData.canReadEnvironment), + }; + }, + render(createElement) { + return createElement('environments-component', { + props: { + endpoint: this.endpoint, + newEnvironmentPath: this.newEnvironmentPath, + helpPagePath: this.helpPagePath, + cssContainerClass: this.cssContainerClass, + canCreateEnvironment: this.canCreateEnvironment, + canCreateDeployment: this.canCreateDeployment, + canReadEnvironment: this.canReadEnvironment, + }, + }); + }, + }); diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js index d71964612c5..96dc1f07cb9 100644 --- a/app/assets/javascripts/environments/mixins/environments_mixin.js +++ b/app/assets/javascripts/environments/mixins/environments_mixin.js @@ -4,9 +4,7 @@ import _ from 'underscore'; import Visibility from 'visibilityjs'; import Poll from '../../lib/utils/poll'; -import { - getParameterByName, -} from '../../lib/utils/common_utils'; +import { getParameterByName } from '../../lib/utils/common_utils'; import { s__ } from '../../locale'; import Flash from '../../flash'; import eventHub from '../event_hub'; @@ -19,7 +17,6 @@ import tabs from '../../vue_shared/components/navigation_tabs.vue'; import container from '../components/container.vue'; export default { - components: { environmentTable, container, @@ -65,7 +62,8 @@ export default { updateContent(parameters) { this.updateInternalState(parameters); // fetch new data - return this.service.fetchEnvironments(this.requestData) + return this.service + .fetchEnvironments(this.requestData) .then(response => this.successCallback(response)) .then(() => { // restart polling @@ -88,7 +86,8 @@ export default { if (!this.isMakingRequest) { this.isLoading = true; - this.service.postAction(endpoint) + this.service + .postAction(endpoint) .then(() => this.fetchEnvironments()) .catch(() => { this.isLoading = false; @@ -100,7 +99,8 @@ export default { fetchEnvironments() { this.isLoading = true; - return this.service.fetchEnvironments(this.requestData) + return this.service + .fetchEnvironments(this.requestData) .then(this.successCallback) .catch(this.errorCallback); }, @@ -111,7 +111,9 @@ export default { stopEnvironment(environment) { const endpoint = environment.stop_path; - const errorMessage = s__('Environments|An error occurred while stopping the environment, please try again'); + const errorMessage = s__( + 'Environments|An error occurred while stopping the environment, please try again', + ); this.postAction({ endpoint, errorMessage }); }, }, @@ -149,7 +151,7 @@ export default { data: this.requestData, successCallback: this.successCallback, errorCallback: this.errorCallback, - notificationCallback: (isMakingRequest) => { + notificationCallback: isMakingRequest => { this.isMakingRequest = isMakingRequest; }, }); diff --git a/app/assets/javascripts/experimental_flags.js b/app/assets/javascripts/experimental_flags.js index 1d60847147b..42b3fb8c6da 100644 --- a/app/assets/javascripts/experimental_flags.js +++ b/app/assets/javascripts/experimental_flags.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import Cookies from 'js-cookie'; export default () => { - $('.js-experiment-feature-toggle').on('change', (e) => { + $('.js-experiment-feature-toggle').on('change', e => { const el = e.target; Cookies.set(el.name, el.value, { diff --git a/app/assets/javascripts/feature_highlight/feature_highlight.js b/app/assets/javascripts/feature_highlight/feature_highlight.js index 03dfa942d69..173fe7c69de 100644 --- a/app/assets/javascripts/feature_highlight/feature_highlight.js +++ b/app/assets/javascripts/feature_highlight/feature_highlight.js @@ -1,13 +1,6 @@ import $ from 'jquery'; -import { - getSelector, - inserted, -} from './feature_highlight_helper'; -import { - togglePopover, - mouseenter, - debouncedMouseleave, -} from '../shared/popover'; +import { getSelector, inserted } from './feature_highlight_helper'; +import { togglePopover, mouseenter, debouncedMouseleave } from '../shared/popover'; export function setupFeatureHighlightPopover(id, debounceTimeout = 300) { const $selector = $(getSelector(id)); @@ -41,8 +34,9 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) { export function findHighestPriorityFeature() { let priorityFeature; - const sortedFeatureEls = [].slice.call(document.querySelectorAll('.js-feature-highlight')).sort((a, b) => - (a.dataset.highlightPriority || 0) < (b.dataset.highlightPriority || 0)); + const sortedFeatureEls = [].slice + .call(document.querySelectorAll('.js-feature-highlight')) + .sort((a, b) => (a.dataset.highlightPriority || 0) < (b.dataset.highlightPriority || 0)); const [priorityFeatureEl] = sortedFeatureEls; if (priorityFeatureEl) { diff --git a/app/assets/javascripts/feature_highlight/feature_highlight_helper.js b/app/assets/javascripts/feature_highlight/feature_highlight_helper.js index d5b97ebb264..fd9433b625c 100644 --- a/app/assets/javascripts/feature_highlight/feature_highlight_helper.js +++ b/app/assets/javascripts/feature_highlight/feature_highlight_helper.js @@ -8,10 +8,17 @@ import { togglePopover } from '../shared/popover'; export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`; export function dismiss(highlightId) { - axios.post(this.attr('data-dismiss-endpoint'), { - feature_name: highlightId, - }) - .catch(() => Flash(__('An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again.'))); + axios + .post(this.attr('data-dismiss-endpoint'), { + feature_name: highlightId, + }) + .catch(() => + Flash( + __( + 'An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again.', + ), + ), + ); togglePopover.call(this, false); this.hide(); @@ -23,8 +30,7 @@ export function inserted() { const $popover = $(this); const dismissWrapper = dismiss.bind($popover, highlightId); - $(`#${popoverId} .dismiss-feature-highlight`) - .on('click', dismissWrapper); + $(`#${popoverId} .dismiss-feature-highlight`).on('click', dismissWrapper); const lazyImg = $(`#${popoverId} .feature-highlight-illustration`)[0]; if (lazyImg) { diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js index 6a4874e1ab8..3233f5c4f71 100644 --- a/app/assets/javascripts/files_comment_button.js +++ b/app/assets/javascripts/files_comment_button.js @@ -25,13 +25,15 @@ export default { if (!this.userCanCreateNote) { // data-can-create-note is an empty string when true, otherwise undefined - this.userCanCreateNote = $diffFile.closest(DIFF_CONTAINER_SELECTOR).data('canCreateNote') === ''; + this.userCanCreateNote = + $diffFile.closest(DIFF_CONTAINER_SELECTOR).data('canCreateNote') === ''; } this.isParallelView = Cookies.get('diff_view') === 'parallel'; if (this.userCanCreateNote) { - $diffFile.on('mouseover', LINE_COLUMN_CLASSES, e => this.showButton(this.isParallelView, e)) + $diffFile + .on('mouseover', LINE_COLUMN_CLASSES, e => this.showButton(this.isParallelView, e)) .on('mouseleave', LINE_COLUMN_CLASSES, e => this.hideButton(this.isParallelView, e)); } }, @@ -64,9 +66,11 @@ export default { }, validateButtonParent(buttonParentElement) { - return !buttonParentElement.classList.contains(EMPTY_CELL_CLASS) && + return ( + !buttonParentElement.classList.contains(EMPTY_CELL_CLASS) && !buttonParentElement.classList.contains(UNFOLDABLE_LINE_CLASS) && !buttonParentElement.classList.contains(NO_COMMENT_CLASS) && - !buttonParentElement.parentNode.classList.contains(DIFF_EXPANDED_CLASS); + !buttonParentElement.parentNode.classList.contains(DIFF_EXPANDED_CLASS) + ); }, }; diff --git a/app/assets/javascripts/filterable_list.js b/app/assets/javascripts/filterable_list.js index b17ba3c21db..64b09c8b62c 100644 --- a/app/assets/javascripts/filterable_list.js +++ b/app/assets/javascripts/filterable_list.js @@ -65,12 +65,15 @@ export default class FilterableList { this.isBusy = true; - return axios.get(this.getFilterEndpoint(), { - params, - }).then((res) => { - this.onFilterSuccess(res, params); - this.onFilterComplete(); - }).catch(() => this.onFilterComplete()); + return axios + .get(this.getFilterEndpoint(), { + params, + }) + .then(res => { + this.onFilterSuccess(res, params); + this.onFilterComplete(); + }) + .catch(() => this.onFilterComplete()); } onFilterSuccess(response, queryData) { @@ -81,9 +84,13 @@ export default class FilterableList { // Change url so if user reload a page - search results are saved const currentPath = this.getPagePath(queryData); - return window.history.replaceState({ - page: currentPath, - }, document.title, currentPath); + return window.history.replaceState( + { + page: currentPath, + }, + document.title, + currentPath, + ); } onFilterComplete() { diff --git a/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js index d7aa4ce597f..934375023ba 100644 --- a/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js +++ b/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js @@ -1,20 +1,23 @@ import FilteredSearchTokenKeys from './filtered_search_token_keys'; -const tokenKeys = [{ - key: 'status', - type: 'string', - param: 'status', - symbol: '', - icon: 'messages', - tag: 'status', -}, { - key: 'type', - type: 'string', - param: 'type', - symbol: '', - icon: 'cube', - tag: 'type', -}]; +const tokenKeys = [ + { + key: 'status', + type: 'string', + param: 'status', + symbol: '', + icon: 'messages', + tag: 'status', + }, + { + key: 'type', + type: 'string', + param: 'type', + symbol: '', + icon: 'cube', + tag: 'type', + }, +]; const AdminRunnersFilteredSearchTokenKeys = new FilteredSearchTokenKeys(tokenKeys); diff --git a/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue b/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue index 21b5ccdb613..b9bc5e6ed7f 100644 --- a/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue +++ b/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue @@ -21,9 +21,11 @@ export default { }, computed: { processedItems() { - return this.items.map((item) => { - const { tokens, searchToken } - = FilteredSearchTokenizer.processTokens(item, this.allowedKeys); + return this.items.map(item => { + const { tokens, searchToken } = FilteredSearchTokenizer.processTokens( + item, + this.allowedKeys, + ); const resultantTokens = tokens.map(token => ({ prefix: `${token.key}:`, diff --git a/app/assets/javascripts/filtered_search/dropdown_emoji.js b/app/assets/javascripts/filtered_search/dropdown_emoji.js index 5ddd0e5e690..af7936a92fb 100644 --- a/app/assets/javascripts/filtered_search/dropdown_emoji.js +++ b/app/assets/javascripts/filtered_search/dropdown_emoji.js @@ -24,8 +24,12 @@ export default class DropdownEmoji extends FilteredSearchDropdown { }; import(/* webpackChunkName: 'emoji' */ '~/emoji') - .then(({ glEmojiTag }) => { this.glEmojiTag = glEmojiTag; }) - .catch(() => { /* ignore error and leave emoji name in the search bar */ }); + .then(({ glEmojiTag }) => { + this.glEmojiTag = glEmojiTag; + }) + .catch(() => { + /* ignore error and leave emoji name in the search bar */ + }); this.unbindEvents(); this.bindEvents(); @@ -48,7 +52,7 @@ export default class DropdownEmoji extends FilteredSearchDropdown { } itemClicked(e) { - super.itemClicked(e, (selected) => { + super.itemClicked(e, selected => { const name = selected.querySelector('.js-data-value').innerText.trim(); return DropdownUtils.getEscapedText(name); }); @@ -64,7 +68,7 @@ export default class DropdownEmoji extends FilteredSearchDropdown { // Replace empty gl-emoji tag to real content const dropdownItems = [...this.dropdown.querySelectorAll('.filter-dropdown-item')]; - dropdownItems.forEach((dropdownItem) => { + dropdownItems.forEach(dropdownItem => { const name = dropdownItem.querySelector('.js-data-value').innerText; const emojiTag = this.glEmojiTag(name); const emojiElement = dropdownItem.querySelector('gl-emoji'); @@ -73,7 +77,6 @@ export default class DropdownEmoji extends FilteredSearchDropdown { } init() { - this.droplab - .addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init(); + this.droplab.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init(); } } diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js index c568f4e4ebf..1a1135ae929 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js @@ -41,8 +41,10 @@ export default class DropdownHint extends FilteredSearchDropdown { previousInputValues.forEach((value, index) => { searchTerms.push(value); - if (index === previousInputValues.length - 1 - && token.indexOf(value.toLowerCase()) !== -1) { + if ( + index === previousInputValues.length - 1 && + token.indexOf(value.toLowerCase()) !== -1 + ) { searchTerms.pop(); } }); @@ -64,13 +66,12 @@ export default class DropdownHint extends FilteredSearchDropdown { } renderContent() { - const dropdownData = this.tokenKeys.get() - .map(tokenKey => ({ - icon: `${gon.sprite_icons}#${tokenKey.icon}`, - hint: tokenKey.key, - tag: `:${tokenKey.tag}`, - type: tokenKey.type, - })); + const dropdownData = this.tokenKeys.get().map(tokenKey => ({ + icon: `${gon.sprite_icons}#${tokenKey.icon}`, + hint: tokenKey.key, + tag: `:${tokenKey.tag}`, + type: tokenKey.type, + })); this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config); this.droplab.setData(this.hookId, dropdownData); diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js b/app/assets/javascripts/filtered_search/dropdown_non_user.js index 2ffda7e2037..0264f934914 100644 --- a/app/assets/javascripts/filtered_search/dropdown_non_user.js +++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js @@ -29,20 +29,18 @@ export default class DropdownNonUser extends FilteredSearchDropdown { } itemClicked(e) { - super.itemClicked(e, (selected) => { + super.itemClicked(e, selected => { const title = selected.querySelector('.js-data-value').innerText.trim(); return `${this.symbol}${DropdownUtils.getEscapedText(title)}`; }); } renderContent(forceShowList = false) { - this.droplab - .changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config); + this.droplab.changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config); super.renderContent(forceShowList); } init() { - this.droplab - .addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init(); + this.droplab.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init(); } } diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js b/app/assets/javascripts/filtered_search/dropdown_utils.js index 6da6ca10008..1b79a3320c6 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js @@ -41,7 +41,7 @@ export default class DropdownUtils { // Removes the first character if it is a quotation so that we can search // with multiple words - if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) { + if ((value[0] === '"' || value[0] === "'") && title.indexOf(' ') !== -1) { value = value.slice(1); } @@ -82,11 +82,13 @@ export default class DropdownUtils { // Reduce the colors to 4 colors.length = Math.min(colors.length, 4); - const color = colors.map((c, i) => { - const percentFirst = Math.floor(spacing * i); - const percentSecond = Math.floor(spacing * (i + 1)); - return `${c} ${percentFirst}%, ${c} ${percentSecond}%`; - }).join(', '); + const color = colors + .map((c, i) => { + const percentFirst = Math.floor(spacing * i); + const percentSecond = Math.floor(spacing * (i + 1)); + return `${c} ${percentFirst}%, ${c} ${percentSecond}%`; + }) + .join(', '); return `linear-gradient(${color})`; } @@ -97,17 +99,16 @@ export default class DropdownUtils { data.forEach(DropdownUtils.mergeDuplicateLabels.bind(null, dataMap)); - Object.keys(dataMap) - .forEach((key) => { - const label = dataMap[key]; + Object.keys(dataMap).forEach(key => { + const label = dataMap[key]; - if (label.multipleColors) { - label.color = DropdownUtils.duplicateLabelColor(label.multipleColors); - label.text_color = '#000000'; - } + if (label.multipleColors) { + label.color = DropdownUtils.duplicateLabelColor(label.multipleColors); + label.text_color = '#000000'; + } - results.push(label); - }); + results.push(label); + }); results.preprocessed = true; @@ -118,8 +119,7 @@ export default class DropdownUtils { const { input, allowedKeys } = config; const updatedItem = item; const searchInput = DropdownUtils.getSearchQuery(input); - const { lastToken, tokens } = - FilteredSearchTokenizer.processTokens(searchInput, allowedKeys); + const { lastToken, tokens } = FilteredSearchTokenizer.processTokens(searchInput, allowedKeys); const lastKey = lastToken.key || lastToken || ''; const allowMultiple = item.type === 'array'; const itemInExistingTokens = tokens.some(t => t.key === item.hint); @@ -154,7 +154,10 @@ export default class DropdownUtils { static getVisualTokenValues(visualToken) { const tokenName = visualToken && visualToken.querySelector('.name').textContent.trim(); - let tokenValue = visualToken && visualToken.querySelector('.value') && visualToken.querySelector('.value').textContent.trim(); + let tokenValue = + visualToken && + visualToken.querySelector('.value') && + visualToken.querySelector('.value').textContent.trim(); if (tokenName === 'label' && tokenValue) { // remove leading symbol and wrapping quotes tokenValue = tokenValue.replace(/^~("|')?(.*)/, '$2').replace(/("|')$/, ''); @@ -174,7 +177,7 @@ export default class DropdownUtils { tokens.splice(inputIndex + 1); } - tokens.forEach((token) => { + tokens.forEach(token => { if (token.classList.contains('js-visual-token')) { const name = token.querySelector('.name'); const value = token.querySelector('.value'); @@ -194,8 +197,9 @@ export default class DropdownUtils { values.push(name.innerText); } } else if (token.classList.contains('input-token')) { - const { isLastVisualTokenValid } = - FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); + const { + isLastVisualTokenValid, + } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); const input = FilteredSearchContainer.container.querySelector('.filtered-search'); const inputValue = input && input.value; @@ -209,9 +213,7 @@ export default class DropdownUtils { } }); - return values - .map(value => value.trim()) - .join(' '); + return values.map(value => value.trim()).join(' '); } static getSearchInput(filteredSearchInput) { @@ -227,7 +229,9 @@ export default class DropdownUtils { // Replace all spaces inside quote marks with underscores // (will continue to match entire string until an end quote is found if any) // This helps with matching the beginning & end of a token:key - inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => str.replace(/\s/g, '_')); + inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => + str.replace(/\s/g, '_'), + ); // Get the right position for the word selected // Regex matches first space diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js index fb4ae1d17dd..4eb67ff7649 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js @@ -87,10 +87,12 @@ export default class FilteredSearchDropdown { dispatchInputEvent() { // Propogate input change to FilteredSearchDropdownManager // so that it can determine which dropdowns to open - this.input.dispatchEvent(new CustomEvent('input', { - bubbles: true, - cancelable: true, - })); + this.input.dispatchEvent( + new CustomEvent('input', { + bubbles: true, + cancelable: true, + }), + ); } dispatchFormSubmitEvent() { @@ -114,7 +116,7 @@ export default class FilteredSearchDropdown { if (!data) return; - const results = data.map((o) => { + const results = data.map(o => { const updated = o; updated.droplab_hidden = false; return updated; diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js index a09ad3e4758..e01dedbb57c 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js +++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js @@ -42,19 +42,21 @@ export default class FilteredSearchTokenKeys { } searchByKeyParam(keyParam) { - return this.tokenKeysWithAlternative.find((tokenKey) => { - let tokenKeyParam = tokenKey.key; + return ( + this.tokenKeysWithAlternative.find(tokenKey => { + let tokenKeyParam = tokenKey.key; - // Replace hyphen with underscore to compare keyParam with tokenKeyParam - // e.g. 'my-reaction' => 'my_reaction' - tokenKeyParam = tokenKeyParam.replace('-', '_'); + // Replace hyphen with underscore to compare keyParam with tokenKeyParam + // e.g. 'my-reaction' => 'my_reaction' + tokenKeyParam = tokenKeyParam.replace('-', '_'); - if (tokenKey.param) { - tokenKeyParam += `_${tokenKey.param}`; - } + if (tokenKey.param) { + tokenKeyParam += `_${tokenKey.param}`; + } - return keyParam === tokenKeyParam; - }) || null; + return keyParam === tokenKeyParam; + }) || null + ); } searchByConditionUrl(url) { @@ -62,8 +64,10 @@ export default class FilteredSearchTokenKeys { } searchByConditionKeyValue(key, value) { - return this.conditions - .find(condition => condition.tokenKey === key && condition.value === value) || null; + return ( + this.conditions.find(condition => condition.tokenKey === key && condition.value === value) || + null + ); } addExtraTokensForMergeRequests() { diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js index d75610f6d68..b5c4cb15aac 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js @@ -4,41 +4,48 @@ export default class FilteredSearchTokenizer { static processTokens(input, allowedKeys) { // Regex extracts `(token):(symbol)(value)` // Values that start with a double quote must end in a double quote (same for single) - const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g'); + const tokenRegex = new RegExp( + `(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, + 'g', + ); const tokens = []; const tokenIndexes = []; // stores key+value for simple search let lastToken = null; - const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => { - let tokenValue = v1 || v2 || v3; - let tokenSymbol = symbol; - let tokenIndex = ''; - - if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') { - tokenSymbol = tokenValue; - tokenValue = ''; - } - - tokenIndex = `${key}:${tokenValue}`; - - // Prevent adding duplicates - if (tokenIndexes.indexOf(tokenIndex) === -1) { - tokenIndexes.push(tokenIndex); - - tokens.push({ - key, - value: tokenValue || '', - symbol: tokenSymbol || '', - }); - } - - return ''; - }).replace(/\s{2,}/g, ' ').trim() || ''; + const searchToken = + input + .replace(tokenRegex, (match, key, symbol, v1, v2, v3) => { + let tokenValue = v1 || v2 || v3; + let tokenSymbol = symbol; + let tokenIndex = ''; + + if (tokenValue === '~' || tokenValue === '%' || tokenValue === '@') { + tokenSymbol = tokenValue; + tokenValue = ''; + } + + tokenIndex = `${key}:${tokenValue}`; + + // Prevent adding duplicates + if (tokenIndexes.indexOf(tokenIndex) === -1) { + tokenIndexes.push(tokenIndex); + + tokens.push({ + key, + value: tokenValue || '', + symbol: tokenSymbol || '', + }); + } + + return ''; + }) + .replace(/\s{2,}/g, ' ') + .trim() || ''; if (tokens.length > 0) { const last = tokens[tokens.length - 1]; const lastString = `${last.key}:${last.symbol}${last.value}`; - lastToken = input.lastIndexOf(lastString) === - input.length - lastString.length ? last : searchToken; + lastToken = + input.lastIndexOf(lastString) === input.length - lastString.length ? last : searchToken; } else { lastToken = searchToken; } diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js index 0854c1822fb..c23d4c484a5 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js +++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js @@ -13,7 +13,10 @@ export default class FilteredSearchVisualTokens { return { lastVisualToken, - isLastVisualTokenValid: lastVisualToken === null || lastVisualToken.className.indexOf('filtered-search-term') !== -1 || (lastVisualToken && lastVisualToken.querySelector('.value') !== null), + isLastVisualTokenValid: + lastVisualToken === null || + lastVisualToken.className.indexOf('filtered-search-term') !== -1 || + (lastVisualToken && lastVisualToken.querySelector('.value') !== null), }; } @@ -33,7 +36,9 @@ export default class FilteredSearchVisualTokens { } static unselectTokens() { - const otherTokens = FilteredSearchContainer.container.querySelectorAll('.js-visual-token .selectable.selected'); + const otherTokens = FilteredSearchContainer.container.querySelectorAll( + '.js-visual-token .selectable.selected', + ); [].forEach.call(otherTokens, t => t.classList.remove('selected')); } @@ -56,11 +61,7 @@ export default class FilteredSearchVisualTokens { } static createVisualTokenElementHTML(options = {}) { - const { - canEdit = true, - uppercaseTokenName = false, - capitalizeTokenValue = false, - } = options; + const { canEdit = true, uppercaseTokenName = false, capitalizeTokenValue = false } = options; return ` <div class="${canEdit ? 'selectable' : 'hidden'}" role="button"> @@ -115,15 +116,20 @@ export default class FilteredSearchVisualTokens { return AjaxCache.retrieve(labelsEndpoint) .then(FilteredSearchVisualTokens.preprocessLabel.bind(null, labelsEndpoint)) - .then((labels) => { - const matchingLabel = (labels || []).find(label => `~${DropdownUtils.getEscapedText(label.title)}` === tokenValue); + .then(labels => { + const matchingLabel = (labels || []).find( + label => `~${DropdownUtils.getEscapedText(label.title)}` === tokenValue, + ); if (!matchingLabel) { return; } - FilteredSearchVisualTokens - .setTokenStyle(tokenValueContainer, matchingLabel.color, matchingLabel.text_color); + FilteredSearchVisualTokens.setTokenStyle( + tokenValueContainer, + matchingLabel.color, + matchingLabel.text_color, + ); }) .catch(() => new Flash('An error occurred while fetching label colors.')); } @@ -134,39 +140,43 @@ export default class FilteredSearchVisualTokens { } const username = tokenValue.replace(/^@/, ''); - return UsersCache.retrieve(username) - .then((user) => { - if (!user) { - return; - } - - /* eslint-disable no-param-reassign */ - tokenValueContainer.dataset.originalValue = tokenValue; - tokenValueElement.innerHTML = ` + return ( + UsersCache.retrieve(username) + .then(user => { + if (!user) { + return; + } + + /* eslint-disable no-param-reassign */ + tokenValueContainer.dataset.originalValue = tokenValue; + tokenValueElement.innerHTML = ` <img class="avatar s20" src="${user.avatar_url}" alt=""> ${_.escape(user.name)} `; - /* eslint-enable no-param-reassign */ - }) - // ignore error and leave username in the search bar - .catch(() => { }); + /* eslint-enable no-param-reassign */ + }) + // ignore error and leave username in the search bar + .catch(() => {}) + ); } static updateEmojiTokenAppearance(tokenValueContainer, tokenValueElement, tokenValue) { const container = tokenValueContainer; const element = tokenValueElement; - return import(/* webpackChunkName: 'emoji' */ '../emoji') - .then((Emoji) => { - if (!Emoji.isEmojiNameValid(tokenValue)) { - return; - } - - container.dataset.originalValue = tokenValue; - element.innerHTML = Emoji.glEmojiTag(tokenValue); - }) - // ignore error and leave emoji name in the search bar - .catch(() => { }); + return ( + import(/* webpackChunkName: 'emoji' */ '../emoji') + .then(Emoji => { + if (!Emoji.isEmojiNameValid(tokenValue)) { + return; + } + + container.dataset.originalValue = tokenValue; + element.innerHTML = Emoji.glEmojiTag(tokenValue); + }) + // ignore error and leave emoji name in the search bar + .catch(() => {}) + ); } static renderVisualTokenValue(parentElement, tokenName, tokenValue) { @@ -177,24 +187,23 @@ export default class FilteredSearchVisualTokens { const tokenType = tokenName.toLowerCase(); if (tokenType === 'label') { FilteredSearchVisualTokens.updateLabelTokenColor(tokenValueContainer, tokenValue); - } else if ((tokenType === 'author') || (tokenType === 'assignee')) { + } else if (tokenType === 'author' || tokenType === 'assignee') { FilteredSearchVisualTokens.updateUserTokenAppearance( - tokenValueContainer, tokenValueElement, tokenValue, + tokenValueContainer, + tokenValueElement, + tokenValue, ); } else if (tokenType === 'my-reaction') { FilteredSearchVisualTokens.updateEmojiTokenAppearance( - tokenValueContainer, tokenValueElement, tokenValue, + tokenValueContainer, + tokenValueElement, + tokenValue, ); } } static addVisualTokenElement(name, value, options = {}) { - const { - isSearchTerm = false, - canEdit, - uppercaseTokenName, - capitalizeTokenValue, - } = options; + const { isSearchTerm = false, canEdit, uppercaseTokenName, capitalizeTokenValue } = options; const li = document.createElement('li'); li.classList.add('js-visual-token'); li.classList.add(isSearchTerm ? 'filtered-search-term' : 'filtered-search-token'); @@ -217,8 +226,10 @@ export default class FilteredSearchVisualTokens { } static addValueToPreviousVisualTokenElement(value) { - const { lastVisualToken, isLastVisualTokenValid } = - FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); + const { + lastVisualToken, + isLastVisualTokenValid, + } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); if (!isLastVisualTokenValid && lastVisualToken.classList.contains('filtered-search-token')) { const name = FilteredSearchVisualTokens.getLastTokenPartial(); @@ -228,13 +239,15 @@ export default class FilteredSearchVisualTokens { } } - static addFilterVisualToken(tokenName, tokenValue, { - canEdit, - uppercaseTokenName = false, - capitalizeTokenValue = false, - } = {}) { - const { lastVisualToken, isLastVisualTokenValid } - = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); + static addFilterVisualToken( + tokenName, + tokenValue, + { canEdit, uppercaseTokenName = false, capitalizeTokenValue = false } = {}, + ) { + const { + lastVisualToken, + isLastVisualTokenValid, + } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); const { addVisualTokenElement } = FilteredSearchVisualTokens; if (isLastVisualTokenValid) { @@ -308,8 +321,7 @@ export default class FilteredSearchVisualTokens { static tokenizeInput() { const input = FilteredSearchContainer.container.querySelector('.filtered-search'); - const { isLastVisualTokenValid } = - FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); + const { isLastVisualTokenValid } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); if (input.value) { if (isLastVisualTokenValid) { @@ -375,8 +387,7 @@ export default class FilteredSearchVisualTokens { FilteredSearchVisualTokens.tokenizeInput(); if (!tokenContainer.lastElementChild.isEqualNode(inputLi)) { - const { isLastVisualTokenValid } = - FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); + const { isLastVisualTokenValid } = FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); if (!isLastVisualTokenValid) { const lastPartial = FilteredSearchVisualTokens.getLastTokenPartial(); diff --git a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js index cc7291c9f59..b70125c80ca 100644 --- a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js +++ b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js @@ -1,34 +1,39 @@ import FilteredSearchTokenKeys from './filtered_search_token_keys'; -export const tokenKeys = [{ - key: 'author', - type: 'string', - param: 'username', - symbol: '@', - icon: 'pencil', - tag: '@author', -}, { - key: 'assignee', - type: 'string', - param: 'username', - symbol: '@', - icon: 'user', - tag: '@assignee', -}, { - key: 'milestone', - type: 'string', - param: 'title', - symbol: '%', - icon: 'clock', - tag: '%milestone', -}, { - key: 'label', - type: 'array', - param: 'name[]', - symbol: '~', - icon: 'labels', - tag: '~label', -}]; +export const tokenKeys = [ + { + key: 'author', + type: 'string', + param: 'username', + symbol: '@', + icon: 'pencil', + tag: '@author', + }, + { + key: 'assignee', + type: 'string', + param: 'username', + symbol: '@', + icon: 'user', + tag: '@assignee', + }, + { + key: 'milestone', + type: 'string', + param: 'title', + symbol: '%', + icon: 'clock', + tag: '%milestone', + }, + { + key: 'label', + type: 'array', + param: 'name[]', + symbol: '~', + icon: 'labels', + tag: '~label', + }, +]; if (gon.current_user_id) { // Appending tokenkeys only logged-in @@ -42,36 +47,52 @@ if (gon.current_user_id) { }); } -export const alternativeTokenKeys = [{ - key: 'label', - type: 'string', - param: 'name', - symbol: '~', -}]; +export const alternativeTokenKeys = [ + { + key: 'label', + type: 'string', + param: 'name', + symbol: '~', + }, +]; -export const conditions = [{ - url: 'assignee_id=0', - tokenKey: 'assignee', - value: 'none', -}, { - url: 'milestone_title=No+Milestone', - tokenKey: 'milestone', - value: 'none', -}, { - url: 'milestone_title=%23upcoming', - tokenKey: 'milestone', - value: 'upcoming', -}, { - url: 'milestone_title=%23started', - tokenKey: 'milestone', - value: 'started', -}, { - url: 'label_name[]=No+Label', - tokenKey: 'label', - value: 'none', -}]; +export const conditions = [ + { + url: 'assignee_id=0', + tokenKey: 'assignee', + value: 'none', + }, + { + url: 'milestone_title=No+Milestone', + tokenKey: 'milestone', + value: 'none', + }, + { + url: 'milestone_title=Any+Milestone', + tokenKey: 'milestone', + value: 'any', + }, + { + url: 'milestone_title=%23upcoming', + tokenKey: 'milestone', + value: 'upcoming', + }, + { + url: 'milestone_title=%23started', + tokenKey: 'milestone', + value: 'started', + }, + { + url: 'label_name[]=No+Label', + tokenKey: 'label', + value: 'none', + }, +]; -const IssuableFilteredSearchTokenKeys = - new FilteredSearchTokenKeys(tokenKeys, alternativeTokenKeys, conditions); +const IssuableFilteredSearchTokenKeys = new FilteredSearchTokenKeys( + tokenKeys, + alternativeTokenKeys, + conditions, +); export default IssuableFilteredSearchTokenKeys; diff --git a/app/assets/javascripts/filtered_search/recent_searches_root.js b/app/assets/javascripts/filtered_search/recent_searches_root.js index c1efa9c86f4..6c8e77a7fe5 100644 --- a/app/assets/javascripts/filtered_search/recent_searches_root.js +++ b/app/assets/javascripts/filtered_search/recent_searches_root.js @@ -3,11 +3,7 @@ import RecentSearchesDropdownContent from './components/recent_searches_dropdown import eventHub from './event_hub'; class RecentSearchesRoot { - constructor( - recentSearchesStore, - recentSearchesService, - wrapperElement, - ) { + constructor(recentSearchesStore, recentSearchesService, wrapperElement) { this.store = recentSearchesStore; this.service = recentSearchesService; this.wrapperElement = wrapperElement; @@ -35,7 +31,9 @@ class RecentSearchesRoot { components: { RecentSearchesDropdownContent, }, - data() { return state; }, + data() { + return state; + }, template: ` <recent-searches-dropdown-content :items="recentSearches" @@ -57,7 +55,6 @@ class RecentSearchesRoot { this.vm.$destroy(); } } - } export default RecentSearchesRoot; diff --git a/app/assets/javascripts/filtered_search/stores/recent_searches_store.js b/app/assets/javascripts/filtered_search/stores/recent_searches_store.js index aaa0c349d93..76d40bfdaf8 100644 --- a/app/assets/javascripts/filtered_search/stores/recent_searches_store.js +++ b/app/assets/javascripts/filtered_search/stores/recent_searches_store.js @@ -2,11 +2,14 @@ import _ from 'underscore'; class RecentSearchesStore { constructor(initialState = {}, allowedKeys) { - this.state = Object.assign({ - isLocalStorageAvailable: true, - recentSearches: [], - allowedKeys, - }, initialState); + this.state = Object.assign( + { + isLocalStorageAvailable: true, + recentSearches: [], + allowedKeys, + }, + initialState, + ); } addRecentSearch(newSearch) { diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js index a29de9ae899..749c09f897c 100644 --- a/app/assets/javascripts/flash.js +++ b/app/assets/javascripts/flash.js @@ -8,14 +8,19 @@ const hideFlash = (flashEl, fadeTransition = true) => { }); } - flashEl.addEventListener('transitionend', () => { - flashEl.remove(); - window.dispatchEvent(new Event('resize')); - if (document.body.classList.contains('flash-shown')) document.body.classList.remove('flash-shown'); - }, { - once: true, - passive: true, - }); + flashEl.addEventListener( + 'transitionend', + () => { + flashEl.remove(); + window.dispatchEvent(new Event('resize')); + if (document.body.classList.contains('flash-shown')) + document.body.classList.remove('flash-shown'); + }, + { + once: true, + passive: true, + }, + ); if (!fadeTransition) flashEl.dispatchEvent(new Event('transitionend')); }; @@ -30,12 +35,12 @@ const createAction = config => ` </a> `; -const createFlashEl = (message, type, isInContentWrapper = false) => ` +const createFlashEl = (message, type, isFixedLayout = false) => ` <div class="flash-${type}" > <div - class="flash-text ${isInContentWrapper ? 'container-fluid container-limited' : ''}" + class="flash-text ${isFixedLayout ? 'container-fluid container-limited limit-container-width' : ''}" > ${_.escape(message)} </div> @@ -69,12 +74,13 @@ const createFlash = function createFlash( addBodyClass = false, ) { const flashContainer = parent.querySelector('.flash-container'); + const navigation = parent.querySelector('.content'); if (!flashContainer) return null; - const isInContentWrapper = flashContainer.parentNode.classList.contains('content-wrapper'); + const isFixedLayout = navigation ? navigation.parentNode.classList.contains('container-limited') : true; - flashContainer.innerHTML = createFlashEl(message, type, isInContentWrapper); + flashContainer.innerHTML = createFlashEl(message, type, isFixedLayout); const flashEl = flashContainer.querySelector(`.flash-${type}`); removeFlashClickListener(flashEl, fadeTransition); @@ -83,7 +89,9 @@ const createFlash = function createFlash( flashEl.innerHTML += createAction(actionConfig); if (actionConfig.clickHandler) { - flashEl.querySelector('.flash-action').addEventListener('click', e => actionConfig.clickHandler(e)); + flashEl + .querySelector('.flash-action') + .addEventListener('click', e => actionConfig.clickHandler(e)); } } @@ -94,11 +102,5 @@ const createFlash = function createFlash( return flashContainer; }; -export { - createFlash as default, - createFlashEl, - createAction, - hideFlash, - removeFlashClickListener, -}; +export { createFlash as default, createFlashEl, createAction, hideFlash, removeFlashClickListener }; window.Flash = createFlash; diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js index f820f0dc3f0..3ac00c51df4 100644 --- a/app/assets/javascripts/fly_out_nav.js +++ b/app/assets/javascripts/fly_out_nav.js @@ -11,9 +11,13 @@ let sidebar; export const mousePos = []; -export const setSidebar = (el) => { sidebar = el; }; +export const setSidebar = el => { + sidebar = el; +}; export const getOpenMenu = () => currentOpenMenu; -export const setOpenMenu = (menu = null) => { currentOpenMenu = menu; }; +export const setOpenMenu = (menu = null) => { + currentOpenMenu = menu; +}; export const slope = (a, b) => (b.y - a.y) / (b.x - a.x); @@ -21,9 +25,10 @@ let headerHeight = 50; export const getHeaderHeight = () => headerHeight; -export const isSidebarCollapsed = () => sidebar && sidebar.classList.contains('sidebar-collapsed-desktop'); +export const isSidebarCollapsed = () => + sidebar && sidebar.classList.contains('sidebar-collapsed-desktop'); -export const canShowActiveSubItems = (el) => { +export const canShowActiveSubItems = el => { if (el.classList.contains('active') && !isSidebarCollapsed()) { return false; } @@ -31,7 +36,10 @@ export const canShowActiveSubItems = (el) => { return true; }; -export const canShowSubItems = () => bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md' || bp.getBreakpointSize() === 'lg'; +export const canShowSubItems = () => + bp.getBreakpointSize() === 'sm' || + bp.getBreakpointSize() === 'md' || + bp.getBreakpointSize() === 'lg'; export const getHideSubItemsInterval = () => { if (!currentOpenMenu || !mousePos.length) return 0; @@ -41,11 +49,12 @@ export const getHideSubItemsInterval = () => { const currentMousePosY = currentMousePos.y; const [menuTop, menuBottom] = menuCornerLocs; - if (currentMousePosY < menuTop.y || - currentMousePosY > menuBottom.y) return 0; + if (currentMousePosY < menuTop.y || currentMousePosY > menuBottom.y) return 0; - if (slope(prevMousePos, menuBottom) < slope(currentMousePos, menuBottom) && - slope(prevMousePos, menuTop) > slope(currentMousePos, menuTop)) { + if ( + slope(prevMousePos, menuBottom) < slope(currentMousePos, menuBottom) && + slope(prevMousePos, menuTop) > slope(currentMousePos, menuTop) + ) { return HIDE_INTERVAL_TIMEOUT; } @@ -56,11 +65,12 @@ export const calculateTop = (boundingRect, outerHeight) => { const windowHeight = window.innerHeight; const bottomOverflow = windowHeight - (boundingRect.top + outerHeight); - return bottomOverflow < 0 ? (boundingRect.top - outerHeight) + boundingRect.height : - boundingRect.top; + return bottomOverflow < 0 + ? boundingRect.top - outerHeight + boundingRect.height + : boundingRect.top; }; -export const hideMenu = (el) => { +export const hideMenu = el => { if (!el) return; const parentEl = el.parentNode; @@ -101,7 +111,7 @@ export const moveSubItemsToPosition = (el, subItems) => { } }; -export const showSubLevelItems = (el) => { +export const showSubLevelItems = el => { const subItems = el.querySelector('.sidebar-sub-level-items'); const isIconOnly = subItems && subItems.classList.contains('is-fly-out-only'); @@ -128,16 +138,20 @@ export const mouseEnterTopItems = (el, timeout = getHideSubItemsInterval()) => { }, timeout); }; -export const mouseLeaveTopItem = (el) => { +export const mouseLeaveTopItem = el => { const subItems = el.querySelector('.sidebar-sub-level-items'); - if (!canShowSubItems() || !canShowActiveSubItems(el) || - (subItems && subItems === currentOpenMenu)) return; + if ( + !canShowSubItems() || + !canShowActiveSubItems(el) || + (subItems && subItems === currentOpenMenu) + ) + return; el.classList.remove(IS_OVER_CLASS); }; -export const documentMouseMove = (e) => { +export const documentMouseMove = e => { mousePos.push({ x: e.clientX, y: e.clientY, @@ -146,7 +160,7 @@ export const documentMouseMove = (e) => { if (mousePos.length > 6) mousePos.shift(); }; -export const subItemsMouseLeave = (relatedTarget) => { +export const subItemsMouseLeave = relatedTarget => { clearTimeout(timeoutId); if (relatedTarget && !relatedTarget.closest(`.${IS_OVER_CLASS}`)) { @@ -174,7 +188,7 @@ export default () => { headerHeight = document.querySelector('.nav-sidebar').offsetTop; - items.forEach((el) => { + items.forEach(el => { const subItems = el.querySelector('.sidebar-sub-level-items'); if (subItems) { diff --git a/app/assets/javascripts/gl_field_error.js b/app/assets/javascripts/gl_field_error.js index 87c6e37b9fb..a5b8c357e8a 100644 --- a/app/assets/javascripts/gl_field_error.js +++ b/app/assets/javascripts/gl_field_error.js @@ -116,7 +116,8 @@ export default class GlFieldError { this.form.focusOnFirstInvalid.apply(this.form); // For UX, wait til after first invalid submission to check each keyup - this.inputElement.off('keyup.fieldValidator') + this.inputElement + .off('keyup.fieldValidator') .on('keyup.fieldValidator', this.updateValidity.bind(this)); } diff --git a/app/assets/javascripts/gl_field_errors.js b/app/assets/javascripts/gl_field_errors.js index b9c51045b1d..3764e7ab422 100644 --- a/app/assets/javascripts/gl_field_errors.js +++ b/app/assets/javascripts/gl_field_errors.js @@ -16,9 +16,12 @@ export default class GlFieldErrors { initValidators() { // register selectors here as needed const validateSelectors = [':text', ':password', '[type=email]'] - .map(selector => `input${selector}`).join(','); + .map(selector => `input${selector}`) + .join(','); - this.state.inputs = this.form.find(validateSelectors).toArray() + this.state.inputs = this.form + .find(validateSelectors) + .toArray() .filter(input => !input.classList.contains(customValidationFlag)) .map(input => new GlFieldError({ input, formErrors: this })); @@ -42,7 +45,7 @@ export default class GlFieldErrors { /* Public method for triggering validity updates manually */ updateFormValidityState() { - this.state.inputs.forEach((field) => { + this.state.inputs.forEach(field => { if (field.state.submitted) { field.updateValidity(); } @@ -50,8 +53,9 @@ export default class GlFieldErrors { } focusOnFirstInvalid() { - const firstInvalid = this.state.inputs - .filter(input => !input.inputDomElement.validity.valid)[0]; + const firstInvalid = this.state.inputs.filter( + input => !input.inputDomElement.validity.valid, + )[0]; firstInvalid.inputElement.focus(); } } diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js index e672284a2d0..f842d2d74db 100644 --- a/app/assets/javascripts/gl_form.js +++ b/app/assets/javascripts/gl_form.js @@ -39,7 +39,10 @@ export default class GLForm { this.form.find('.div-dropzone').remove(); this.form.addClass('gfm-form'); // remove notify commit author checkbox for non-commit notes - gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion')); + gl.utils.disableButtonIfEmptyField( + this.form.find('.js-note-text'), + this.form.find('.js-comment-button, .js-note-new-discussion'), + ); this.autoComplete = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources); this.autoComplete.setup(this.form.find('.js-gfm-input'), this.enableGFM); dropzoneInput(this.form); @@ -55,11 +58,9 @@ export default class GLForm { } setupAutosize() { - this.textarea.off('autosize:resized') - .on('autosize:resized', this.setHeightData.bind(this)); + this.textarea.off('autosize:resized').on('autosize:resized', this.setHeightData.bind(this)); - this.textarea.off('mouseup.autosize') - .on('mouseup.autosize', this.destroyAutosize.bind(this)); + this.textarea.off('mouseup.autosize').on('mouseup.autosize', this.destroyAutosize.bind(this)); setTimeout(() => { autosize(this.textarea); @@ -91,10 +92,14 @@ export default class GLForm { addEventListeners() { this.textarea.on('focus', function focusTextArea() { - $(this).closest('.md-area').addClass('is-focused'); + $(this) + .closest('.md-area') + .addClass('is-focused'); }); this.textarea.on('blur', function blurTextArea() { - $(this).closest('.md-area').removeClass('is-focused'); + $(this) + .closest('.md-area') + .removeClass('is-focused'); }); } } diff --git a/app/assets/javascripts/group_avatar.js b/app/assets/javascripts/group_avatar.js index beaac61e887..dcda625f587 100644 --- a/app/assets/javascripts/group_avatar.js +++ b/app/assets/javascripts/group_avatar.js @@ -7,8 +7,9 @@ export default function groupAvatar() { }); $('.js-group-avatar-input').on('change', function onChangeAvatarInput() { const form = $(this).closest('form'); - // eslint-disable-next-line no-useless-escape - const filename = $(this).val().replace(/^.*[\\\/]/, ''); + const filename = $(this) + .val() + .replace(/^.*[\\\/]/, ''); // eslint-disable-line no-useless-escape return form.find('.js-avatar-filename').text(filename); }); } diff --git a/app/assets/javascripts/group_label_subscription.js b/app/assets/javascripts/group_label_subscription.js index d33e3a37580..9b74560f914 100644 --- a/app/assets/javascripts/group_label_subscription.js +++ b/app/assets/javascripts/group_label_subscription.js @@ -23,7 +23,8 @@ export default class GroupLabelSubscription { event.preventDefault(); const url = this.$unsubscribeButtons.attr('data-url'); - axios.post(url) + axios + .post(url) .then(() => { this.toggleSubscriptionButtons(); this.$unsubscribeButtons.removeAttr('data-url'); @@ -39,7 +40,8 @@ export default class GroupLabelSubscription { this.$unsubscribeButtons.attr('data-url', url); - axios.post(url) + axios + .post(url) .then(() => GroupLabelSubscription.setNewTooltip($btn)) .then(() => this.toggleSubscriptionButtons()) .catch(() => flash(__('There was an error when subscribing to this label.'))); @@ -58,6 +60,8 @@ export default class GroupLabelSubscription { const newTitle = tooltipTitles[type]; $('.js-unsubscribe-button', $button.closest('.label-actions-list')) - .tooltip('hide').attr('title', newTitle).tooltip('_fixTitle'); + .tooltip('hide') + .attr('title', newTitle) + .tooltip('_fixTitle'); } } diff --git a/app/assets/javascripts/groups/components/item_stats.vue b/app/assets/javascripts/groups/components/item_stats.vue index 87ab5480c15..829924ba63c 100644 --- a/app/assets/javascripts/groups/components/item_stats.vue +++ b/app/assets/javascripts/groups/components/item_stats.vue @@ -1,44 +1,44 @@ <script> - import icon from '~/vue_shared/components/icon.vue'; - import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; - import { - ITEM_TYPE, - VISIBILITY_TYPE_ICON, - GROUP_VISIBILITY_TYPE, - PROJECT_VISIBILITY_TYPE, - } from '../constants'; - import itemStatsValue from './item_stats_value.vue'; +import icon from '~/vue_shared/components/icon.vue'; +import timeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; +import { + ITEM_TYPE, + VISIBILITY_TYPE_ICON, + GROUP_VISIBILITY_TYPE, + PROJECT_VISIBILITY_TYPE, +} from '../constants'; +import itemStatsValue from './item_stats_value.vue'; - export default { - components: { - icon, - timeAgoTooltip, - itemStatsValue, +export default { + components: { + icon, + timeAgoTooltip, + itemStatsValue, + }, + props: { + item: { + type: Object, + required: true, }, - props: { - item: { - type: Object, - required: true, - }, + }, + computed: { + visibilityIcon() { + return VISIBILITY_TYPE_ICON[this.item.visibility]; }, - computed: { - visibilityIcon() { - return VISIBILITY_TYPE_ICON[this.item.visibility]; - }, - visibilityTooltip() { - if (this.item.type === ITEM_TYPE.GROUP) { - return GROUP_VISIBILITY_TYPE[this.item.visibility]; - } - return PROJECT_VISIBILITY_TYPE[this.item.visibility]; - }, - isProject() { - return this.item.type === ITEM_TYPE.PROJECT; - }, - isGroup() { - return this.item.type === ITEM_TYPE.GROUP; - }, + visibilityTooltip() { + if (this.item.type === ITEM_TYPE.GROUP) { + return GROUP_VISIBILITY_TYPE[this.item.visibility]; + } + return PROJECT_VISIBILITY_TYPE[this.item.visibility]; }, - }; + isProject() { + return this.item.type === ITEM_TYPE.PROJECT; + }, + isGroup() { + return this.item.type === ITEM_TYPE.GROUP; + }, + }, +}; </script> <template> diff --git a/app/assets/javascripts/groups/components/item_stats_value.vue b/app/assets/javascripts/groups/components/item_stats_value.vue index ef9f2bca76c..c542ca946d3 100644 --- a/app/assets/javascripts/groups/components/item_stats_value.vue +++ b/app/assets/javascripts/groups/components/item_stats_value.vue @@ -1,52 +1,52 @@ <script> - import tooltip from '~/vue_shared/directives/tooltip'; - import icon from '~/vue_shared/components/icon.vue'; +import tooltip from '~/vue_shared/directives/tooltip'; +import icon from '~/vue_shared/components/icon.vue'; - export default { - components: { - icon, +export default { + components: { + icon, + }, + directives: { + tooltip, + }, + props: { + title: { + type: String, + required: false, + default: '', }, - directives: { - tooltip, + cssClass: { + type: String, + required: false, + default: '', }, - props: { - title: { - type: String, - required: false, - default: '', - }, - cssClass: { - type: String, - required: false, - default: '', - }, - iconName: { - type: String, - required: true, - }, - tooltipPlacement: { - type: String, - required: false, - default: 'bottom', - }, - /** - * value could either be number or string - * as `memberCount` is always passed as string - * while `subgroupCount` & `projectCount` - * are always number - */ - value: { - type: [Number, String], - required: false, - default: '', - }, + iconName: { + type: String, + required: true, }, - computed: { - isValuePresent() { - return this.value !== ''; - }, + tooltipPlacement: { + type: String, + required: false, + default: 'bottom', }, - }; + /** + * value could either be number or string + * as `memberCount` is always passed as string + * while `subgroupCount` & `projectCount` + * are always number + */ + value: { + type: [Number, String], + required: false, + default: '', + }, + }, + computed: { + isValuePresent() { + return this.value !== ''; + }, + }, +}; </script> <template> diff --git a/app/assets/javascripts/groups/new_group_child.js b/app/assets/javascripts/groups/new_group_child.js index a120d501e35..012177479c6 100644 --- a/app/assets/javascripts/groups/new_group_child.js +++ b/app/assets/javascripts/groups/new_group_child.js @@ -37,20 +37,22 @@ export default class NewGroupChild { getDroplabConfig() { return { - InputSetter: [{ - input: this.newGroupChildButton, - valueAttribute: 'data-value', - inputAttribute: 'data-action', - }, { - input: this.newGroupChildButton, - valueAttribute: 'data-text', - }], + InputSetter: [ + { + input: this.newGroupChildButton, + valueAttribute: 'data-value', + inputAttribute: 'data-action', + }, + { + input: this.newGroupChildButton, + valueAttribute: 'data-text', + }, + ], }; } bindEvents() { - this.newGroupChildButton - .addEventListener('click', this.onClickNewGroupChildButton.bind(this)); + this.newGroupChildButton.addEventListener('click', this.onClickNewGroupChildButton.bind(this)); } onClickNewGroupChildButton(e) { diff --git a/app/assets/javascripts/groups/store/groups_store.js b/app/assets/javascripts/groups/store/groups_store.js index 4a7569078a1..16f95d5a0cc 100644 --- a/app/assets/javascripts/groups/store/groups_store.js +++ b/app/assets/javascripts/groups/store/groups_store.js @@ -17,13 +17,14 @@ export default class GroupsStore { } setSearchedGroups(rawGroups) { - const formatGroups = groups => groups.map((group) => { - const formattedGroup = this.formatGroupItem(group); - if (formattedGroup.children && formattedGroup.children.length) { - formattedGroup.children = formatGroups(formattedGroup.children); - } - return formattedGroup; - }); + const formatGroups = groups => + groups.map(group => { + const formattedGroup = this.formatGroupItem(group); + if (formattedGroup.children && formattedGroup.children.length) { + formattedGroup.children = formatGroups(formattedGroup.children); + } + return formattedGroup; + }); if (rawGroups && rawGroups.length) { this.state.groups = formatGroups(rawGroups); @@ -62,10 +63,10 @@ export default class GroupsStore { formatGroupItem(rawGroupItem) { const groupChildren = rawGroupItem.children || []; - const groupIsOpen = (groupChildren.length > 0) || false; - const childrenCount = this.hideProjects ? - rawGroupItem.subgroup_count : - rawGroupItem.children_count; + const groupIsOpen = groupChildren.length > 0 || false; + const childrenCount = this.hideProjects + ? rawGroupItem.subgroup_count + : rawGroupItem.children_count; return { id: rawGroupItem.id, diff --git a/app/assets/javascripts/groups/transfer_dropdown.js b/app/assets/javascripts/groups/transfer_dropdown.js index e0eb118ddf7..26510fcdb2a 100644 --- a/app/assets/javascripts/groups/transfer_dropdown.js +++ b/app/assets/javascripts/groups/transfer_dropdown.js @@ -22,7 +22,7 @@ export default class TransferDropdown { search: { fields: ['text'] }, data: extraOptions.concat(this.data), text: item => item.text, - clicked: (options) => { + clicked: options => { const { e } = options; e.preventDefault(); this.assignSelected(options.selectedObj); diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js index e37fc5c4be6..b4a3037c1b7 100644 --- a/app/assets/javascripts/groups_select.js +++ b/app/assets/javascripts/groups_select.js @@ -23,7 +23,7 @@ export default function groupsSelect() { axios[params.type.toLowerCase()](params.url, { params: params.data, }) - .then((res) => { + .then(res => { const results = res.data || []; const headers = normalizeHeaders(res.headers); const currentPage = parseInt(headers['X-PAGE'], 10) || 0; @@ -36,7 +36,8 @@ export default function groupsSelect() { more, }, }); - }).catch(params.error); + }) + .catch(params.error); }, data(search, page) { return { @@ -68,7 +69,9 @@ export default function groupsSelect() { } }, formatResult(object) { - return `<div class='group-result'> <div class='group-name'>${object.full_name}</div> <div class='group-path'>${object.full_path}</div> </div>`; + return `<div class='group-result'> <div class='group-name'>${ + object.full_name + }</div> <div class='group-path'>${object.full_path}</div> </div>`; }, formatSelection(object) { return object.full_name; diff --git a/app/assets/javascripts/helpers/avatar_helper.js b/app/assets/javascripts/helpers/avatar_helper.js index d3b1d0f11fd..35ac7b2629c 100644 --- a/app/assets/javascripts/helpers/avatar_helper.js +++ b/app/assets/javascripts/helpers/avatar_helper.js @@ -19,7 +19,9 @@ export function renderIdenticon(entity, options = {}) { const bgClass = getIdenticonBackgroundClass(entity.id); const title = getIdenticonTitle(entity.name); - return `<div class="avatar identicon ${_.escape(sizeClass)} ${_.escape(bgClass)}">${_.escape(title)}</div>`; + return `<div class="avatar identicon ${_.escape(sizeClass)} ${_.escape(bgClass)}">${_.escape( + title, + )}</div>`; } export function renderAvatar(entity, options = {}) { diff --git a/app/assets/javascripts/ide/components/new_dropdown/upload.vue b/app/assets/javascripts/ide/components/new_dropdown/upload.vue index e2be805ed22..ec759043efc 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/upload.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/upload.vue @@ -25,14 +25,32 @@ export default { }, }, methods: { - createFile(target, file, isText) { + isText(content, fileType) { + const knownBinaryFileTypes = ['image/']; + const knownTextFileTypes = ['text/']; + const isKnownBinaryFileType = knownBinaryFileTypes.find(type => fileType.includes(type)); + const isKnownTextFileType = knownTextFileTypes.find(type => fileType.includes(type)); + const asciiRegex = /^[ -~\t\n\r]+$/; // tests whether a string contains ascii characters only (ranges from space to tilde, tabs and new lines) + + if (isKnownBinaryFileType) { + return false; + } + + if (isKnownTextFileType) { + return true; + } + + // if it's not a known file type, determine the type by evaluating the file contents + return asciiRegex.test(content); + }, + createFile(target, file) { const { name } = file; let { result } = target; + const encodedContent = result.split('base64,')[1]; + const rawContent = encodedContent ? atob(encodedContent) : ''; + const isText = this.isText(rawContent, file.type); - if (!isText) { - // eslint-disable-next-line prefer-destructuring - result = result.split('base64,')[1]; - } + result = isText ? rawContent : encodedContent; this.$emit('create', { name: `${this.path ? `${this.path}/` : ''}${name}`, @@ -43,15 +61,9 @@ export default { }, readFile(file) { const reader = new FileReader(); - const isText = file.type.match(/text.*/) !== null; - reader.addEventListener('load', e => this.createFile(e.target, file, isText), { once: true }); - - if (isText) { - reader.readAsText(file); - } else { - reader.readAsDataURL(file); - } + reader.addEventListener('load', e => this.createFile(e.target, file), { once: true }); + reader.readAsDataURL(file); }, openFile() { Array.from(this.$refs.fileUpload.files).forEach(file => this.readFile(file)); diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js index fab0255c378..3587f073a00 100644 --- a/app/assets/javascripts/image_diff/image_diff.js +++ b/app/assets/javascripts/image_diff/image_diff.js @@ -60,8 +60,10 @@ export default class ImageDiff { } renderBadge(discussionEl, index) { - const imageBadge = imageDiffHelper - .generateBadgeFromDiscussionDOM(this.imageFrameEl, discussionEl); + const imageBadge = imageDiffHelper.generateBadgeFromDiscussionDOM( + this.imageFrameEl, + discussionEl, + ); this.imageBadges.push(imageBadge); diff --git a/app/assets/javascripts/image_diff/init_discussion_tab.js b/app/assets/javascripts/image_diff/init_discussion_tab.js index 2f16c6ef115..dbe4c06a4e9 100644 --- a/app/assets/javascripts/image_diff/init_discussion_tab.js +++ b/app/assets/javascripts/image_diff/init_discussion_tab.js @@ -8,5 +8,6 @@ export default () => { const diffFileEls = document.querySelectorAll('.timeline-content .diff-file.js-image-file'); [...diffFileEls].forEach(diffFileEl => - imageDiffHelper.initImageDiff(diffFileEl, canCreateNote, renderCommentBadge)); + imageDiffHelper.initImageDiff(diffFileEl, canCreateNote, renderCommentBadge), + ); }; diff --git a/app/assets/javascripts/image_diff/replaced_image_diff.js b/app/assets/javascripts/image_diff/replaced_image_diff.js index 4abd13fb472..8d9e65155d8 100644 --- a/app/assets/javascripts/image_diff/replaced_image_diff.js +++ b/app/assets/javascripts/image_diff/replaced_image_diff.js @@ -26,7 +26,7 @@ export default class ReplacedImageDiff extends ImageDiff { this.imageEls = {}; const viewTypeNames = Object.getOwnPropertyNames(viewTypes); - viewTypeNames.forEach((viewType) => { + viewTypeNames.forEach(viewType => { this.imageEls[viewType] = this.imageFrameEls[viewType].querySelector('img'); }); } @@ -79,13 +79,12 @@ export default class ReplacedImageDiff extends ImageDiff { // Re-render indicator in new view if (indicator.removed) { - const normalizedIndicator = imageDiffHelper - .resizeCoordinatesToImageElement(this.imageEl, { - x: indicator.x, - y: indicator.y, - width: indicator.image.width, - height: indicator.image.height, - }); + const normalizedIndicator = imageDiffHelper.resizeCoordinatesToImageElement(this.imageEl, { + x: indicator.x, + y: indicator.y, + width: indicator.image.width, + height: indicator.image.height, + }); imageDiffHelper.showCommentIndicator(this.imageFrameEl, normalizedIndicator); } } diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js index eda8cdad908..f1beb1a8ea5 100644 --- a/app/assets/javascripts/importer_status.js +++ b/app/assets/javascripts/importer_status.js @@ -60,66 +60,71 @@ class ImporterStatus { attributes = Object.assign(repoData, attributes); } - return axios.post(this.importUrl, attributes) - .then(({ data }) => { - const job = $(`tr#repo_${id}`); - job.attr('id', `project_${data.id}`); - - job.find('.import-target').html(`<a href="${data.full_path}">${data.full_path}</a>`); - $('table.import-jobs tbody').prepend(job); - - job.addClass('table-active'); - const connectingVerb = this.ciCdOnly ? __('connecting') : __('importing'); - job.find('.import-actions').html(sprintf( - _.escape(__('%{loadingIcon} Started')), { - loadingIcon: `<i class="fa fa-spinner fa-spin" aria-label="${_.escape(connectingVerb)}"></i>`, - }, - false, - )); - }) - .catch((error) => { - let details = error; - - const $statusField = $(`#repo_${this.id} .job-status`); - $statusField.text(__('Failed')); - - if (error.response && error.response.data && error.response.data.errors) { - details = error.response.data.errors; - } - - flash(sprintf(__('An error occurred while importing project: %{details}'), { details })); - }); + return axios + .post(this.importUrl, attributes) + .then(({ data }) => { + const job = $(`tr#repo_${id}`); + job.attr('id', `project_${data.id}`); + + job.find('.import-target').html(`<a href="${data.full_path}">${data.full_path}</a>`); + $('table.import-jobs tbody').prepend(job); + + job.addClass('table-active'); + const connectingVerb = this.ciCdOnly ? __('connecting') : __('importing'); + job.find('.import-actions').html( + sprintf( + _.escape(__('%{loadingIcon} Started')), + { + loadingIcon: `<i class="fa fa-spinner fa-spin" aria-label="${_.escape( + connectingVerb, + )}"></i>`, + }, + false, + ), + ); + }) + .catch(error => { + let details = error; + + const $statusField = $(`#repo_${this.id} .job-status`); + $statusField.text(__('Failed')); + + if (error.response && error.response.data && error.response.data.errors) { + details = error.response.data.errors; + } + + flash(sprintf(__('An error occurred while importing project: %{details}'), { details })); + }); } autoUpdate() { - return axios.get(this.jobsUrl) - .then(({ data = [] }) => { - data.forEach((job) => { - const jobItem = $(`#project_${job.id}`); - const statusField = jobItem.find('.job-status'); - - const spinner = '<i class="fa fa-spinner fa-spin"></i>'; - - switch (job.import_status) { - case 'finished': - jobItem.removeClass('table-active').addClass('table-success'); - statusField.html(`<span><i class="fa fa-check"></i> ${__('Done')}</span>`); - break; - case 'scheduled': - statusField.html(`${spinner} ${__('Scheduled')}`); - break; - case 'started': - statusField.html(`${spinner} ${__('Started')}`); - break; - case 'failed': - statusField.html(__('Failed')); - break; - default: - statusField.html(job.import_status); - break; - } - }); + return axios.get(this.jobsUrl).then(({ data = [] }) => { + data.forEach(job => { + const jobItem = $(`#project_${job.id}`); + const statusField = jobItem.find('.job-status'); + + const spinner = '<i class="fa fa-spinner fa-spin"></i>'; + + switch (job.import_status) { + case 'finished': + jobItem.removeClass('table-active').addClass('table-success'); + statusField.html(`<span><i class="fa fa-check"></i> ${__('Done')}</span>`); + break; + case 'scheduled': + statusField.html(`${spinner} ${__('Scheduled')}`); + break; + case 'started': + statusField.html(`${spinner} ${__('Started')}`); + break; + case 'failed': + statusField.html(__('Failed')); + break; + default: + statusField.html(job.import_status); + break; + } }); + }); } setAutoUpdate() { @@ -141,7 +146,4 @@ function initImporterStatus() { } } -export { - initImporterStatus as default, - ImporterStatus, -}; +export { initImporterStatus as default, ImporterStatus }; diff --git a/app/assets/javascripts/init_changes_dropdown.js b/app/assets/javascripts/init_changes_dropdown.js index 5c5a6e01848..e708e5d0978 100644 --- a/app/assets/javascripts/init_changes_dropdown.js +++ b/app/assets/javascripts/init_changes_dropdown.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import { stickyMonitor } from './lib/utils/sticky'; -export default (stickyTop) => { +export default stickyTop => { stickyMonitor(document.querySelector('.js-diff-files-changed'), stickyTop); $('.js-diff-stats-dropdown').glDropdown({ diff --git a/app/assets/javascripts/init_notes.js b/app/assets/javascripts/init_notes.js index 3c71258e53b..a77828e8cf2 100644 --- a/app/assets/javascripts/init_notes.js +++ b/app/assets/javascripts/init_notes.js @@ -2,13 +2,7 @@ import Notes from './notes'; export default () => { const dataEl = document.querySelector('.js-notes-data'); - const { - notesUrl, - notesIds, - now, - diffView, - enableGFM, - } = JSON.parse(dataEl.innerHTML); + const { notesUrl, notesIds, now, diffView, enableGFM } = JSON.parse(dataEl.innerHTML); // Create a singleton so that we don't need to assign // into the window object, we can just access the current isntance with Notes.instance diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js index bd90d0eaa32..08b858305ab 100644 --- a/app/assets/javascripts/integrations/integration_settings_form.js +++ b/app/assets/javascripts/integrations/integration_settings_form.js @@ -97,7 +97,8 @@ export default class IntegrationSettingsForm { testSettings(formData) { this.toggleSubmitBtnState(true); - return axios.put(this.testEndPoint, formData) + return axios + .put(this.testEndPoint, formData) .then(({ data }) => { if (data.error) { let flashActions; @@ -105,7 +106,7 @@ export default class IntegrationSettingsForm { if (data.test_failed) { flashActions = { title: 'Save anyway', - clickHandler: (e) => { + clickHandler: e => { e.preventDefault(); this.$form.submit(); }, diff --git a/app/assets/javascripts/issuable/auto_width_dropdown_select.js b/app/assets/javascripts/issuable/auto_width_dropdown_select.js index 07cf1eff279..612c524ca1c 100644 --- a/app/assets/javascripts/issuable/auto_width_dropdown_select.js +++ b/app/assets/javascripts/issuable/auto_width_dropdown_select.js @@ -27,7 +27,10 @@ class AutoWidthDropdownSelect { // We have to look at the parent because // `offsetParent` on a `display: none;` is `null` - const offsetParentWidth = $(this).parent().offsetParent().width(); + const offsetParentWidth = $(this) + .parent() + .offsetParent() + .width(); // Reset any width to let it naturally flow $dropdown.css('width', 'auto'); if ($dropdown.outerWidth(false) > offsetParentWidth) { diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js index 9848bcc2e64..b844e4c5e5b 100644 --- a/app/assets/javascripts/issuable_bulk_update_actions.js +++ b/app/assets/javascripts/issuable_bulk_update_actions.js @@ -32,7 +32,7 @@ export default { onFormSubmitFailure() { this.form.find('[type="submit"]').enable(); - return new Flash("Issue update failed"); + return new Flash('Issue update failed'); }, getSelectedIssues() { @@ -63,7 +63,7 @@ export default { const result = []; const labelsToKeep = this.$labelDropdown.data('indeterminate'); - this.getLabelsFromSelection().forEach((id) => { + this.getLabelsFromSelection().forEach(id => { if (labelsToKeep.indexOf(id) === -1) { result.push(id); } @@ -89,8 +89,8 @@ export default { issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(), subscription_event: this.form.find('input[name="update[subscription_event]"]').val(), add_label_ids: [], - remove_label_ids: [] - } + remove_label_ids: [], + }, }; if (this.willUpdateLabels) { formData.update.add_label_ids = this.$labelDropdown.data('marked'); @@ -134,7 +134,7 @@ export default { // Collect unique label IDs for all checked issues this.getElement('.selected-issuable:checked').each((i, el) => { issuableLabels = this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels'); - issuableLabels.forEach((labelId) => { + issuableLabels.forEach(labelId => { // Store unique IDs if (uniqueIds.indexOf(labelId) === -1) { uniqueIds.push(labelId); diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js index 0140960b367..c81a2230310 100644 --- a/app/assets/javascripts/issuable_form.js +++ b/app/assets/javascripts/issuable_form.js @@ -1,6 +1,3 @@ -/* eslint-disable no-new, no-unused-vars, consistent-return, no-else-return */ -/* global GitLab */ - import $ from 'jquery'; import Pikaday from 'pikaday'; import Autosave from './autosave'; @@ -8,7 +5,7 @@ import UsersSelect from './users_select'; import GfmAutoComplete from './gfm_auto_complete'; import ZenMode from './zen_mode'; import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select'; -import { parsePikadayDate, pikadayToString } from './lib/utils/datefix'; +import { parsePikadayDate, pikadayToString } from './lib/utils/datetime_utility'; export default class IssuableForm { constructor(form) { @@ -19,9 +16,11 @@ export default class IssuableForm { this.handleSubmit = this.handleSubmit.bind(this); this.wipRegex = /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i; - new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup(); - new UsersSelect(); - new ZenMode(); + this.gfmAutoComplete = new GfmAutoComplete( + gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources, + ).setup(); + this.usersSelect = new UsersSelect(); + this.zenMode = new ZenMode(); this.titleField = this.form.find('input[name*="[title]"]'); this.descriptionField = this.form.find('textarea[name*="[description]"]'); @@ -57,8 +56,16 @@ export default class IssuableForm { } initAutosave() { - new Autosave(this.titleField, [document.location.pathname, document.location.search, 'title']); - return new Autosave(this.descriptionField, [document.location.pathname, document.location.search, 'description']); + this.autosave = new Autosave(this.titleField, [ + document.location.pathname, + document.location.search, + 'title', + ]); + return new Autosave(this.descriptionField, [ + document.location.pathname, + document.location.search, + 'description', + ]); } handleSubmit() { @@ -74,7 +81,7 @@ export default class IssuableForm { this.$wipExplanation = this.form.find('.js-wip-explanation'); this.$noWipExplanation = this.form.find('.js-no-wip-explanation'); if (!(this.$wipExplanation.length && this.$noWipExplanation.length)) { - return; + return undefined; } this.form.on('click', '.js-toggle-wip', this.toggleWip); this.titleField.on('keyup blur', this.renderWipExplanation); @@ -89,10 +96,9 @@ export default class IssuableForm { if (this.workInProgress()) { this.$wipExplanation.show(); return this.$noWipExplanation.hide(); - } else { - this.$wipExplanation.hide(); - return this.$noWipExplanation.show(); } + this.$wipExplanation.hide(); + return this.$noWipExplanation.show(); } toggleWip(event) { @@ -110,7 +116,7 @@ export default class IssuableForm { } addWip() { - this.titleField.val(`WIP: ${(this.titleField.val())}`); + this.titleField.val(`WIP: ${this.titleField.val()}`); } initTargetBranchDropdown() { diff --git a/app/assets/javascripts/issuable_index.js b/app/assets/javascripts/issuable_index.js index 06ec4546164..ffcbd7cf28c 100644 --- a/app/assets/javascripts/issuable_index.js +++ b/app/assets/javascripts/issuable_index.js @@ -26,14 +26,17 @@ export default class IssuableIndex { static resetIncomingEmailToken() { const $resetToken = $('.incoming-email-token-reset'); - $resetToken.on('click', (e) => { + $resetToken.on('click', e => { e.preventDefault(); $resetToken.text('resetting...'); - axios.put($resetToken.attr('href')) + axios + .put($resetToken.attr('href')) .then(({ data }) => { - $('#issuable_email').val(data.new_address).focus(); + $('#issuable_email') + .val(data.new_address) + .focus(); $resetToken.text('reset it'); }) diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js index 4b4e9aa48ab..94b78907d9a 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -28,7 +28,7 @@ export default class Issue { } // Listen to state changes in the Vue app - document.addEventListener('issuable_vue_app:change', (event) => { + document.addEventListener('issuable_vue_app:change', event => { this.updateTopState(event.detail.isClosed, event.detail.data); }); } @@ -55,7 +55,13 @@ export default class Issue { $(document).trigger('issuable:change', isClosed); this.toggleCloseReopenButton(isClosed); - let numProjectIssues = Number(projectIssuesCounter.first().text().trim().replace(/[^\d]/, '')); + let numProjectIssues = Number( + projectIssuesCounter + .first() + .text() + .trim() + .replace(/[^\d]/, ''), + ); numProjectIssues = isClosed ? numProjectIssues - 1 : numProjectIssues + 1; projectIssuesCounter.text(addDelimiter(numProjectIssues)); @@ -76,29 +82,34 @@ export default class Issue { initIssueBtnEventListeners() { const issueFailMessage = 'Unable to update this issue at this time.'; - return $(document).on('click', '.js-issuable-actions a.btn-close, .js-issuable-actions a.btn-reopen', (e) => { - var $button, shouldSubmit, url; - e.preventDefault(); - e.stopImmediatePropagation(); - $button = $(e.currentTarget); - shouldSubmit = $button.hasClass('btn-comment'); - if (shouldSubmit) { - Issue.submitNoteForm($button.closest('form')); - } - - this.disableCloseReopenButton($button); + return $(document).on( + 'click', + '.js-issuable-actions a.btn-close, .js-issuable-actions a.btn-reopen', + e => { + var $button, shouldSubmit, url; + e.preventDefault(); + e.stopImmediatePropagation(); + $button = $(e.currentTarget); + shouldSubmit = $button.hasClass('btn-comment'); + if (shouldSubmit) { + Issue.submitNoteForm($button.closest('form')); + } - url = $button.attr('href'); - return axios.put(url) - .then(({ data }) => { - const isClosed = $button.hasClass('btn-close'); - this.updateTopState(isClosed, data); - }) - .catch(() => flash(issueFailMessage)) - .then(() => { - this.disableCloseReopenButton($button, false); - }); - }); + this.disableCloseReopenButton($button); + + url = $button.attr('href'); + return axios + .put(url) + .then(({ data }) => { + const isClosed = $button.hasClass('btn-close'); + this.updateTopState(isClosed, data); + }) + .catch(() => flash(issueFailMessage)) + .then(() => { + this.disableCloseReopenButton($button, false); + }); + }, + ); } initCloseReopenReport() { @@ -124,7 +135,7 @@ export default class Issue { static submitNoteForm(form) { var noteText; - noteText = form.find("textarea.js-note-text").val(); + noteText = form.find('textarea.js-note-text').val(); if (noteText && noteText.trim().length > 0) { return form.submit(); } @@ -133,22 +144,26 @@ export default class Issue { static initMergeRequests() { var $container; $container = $('#merge-requests'); - return axios.get($container.data('url')) + return axios + .get($container.data('url')) .then(({ data }) => { if ('html' in data) { $container.html(data.html); } - }).catch(() => flash('Failed to load referenced merge requests')); + }) + .catch(() => flash('Failed to load referenced merge requests')); } static initRelatedBranches() { var $container; $container = $('#related-branches'); - return axios.get($container.data('url')) + return axios + .get($container.data('url')) .then(({ data }) => { if ('html' in data) { $container.html(data.html); } - }).catch(() => flash('Failed to load related branches')); + }) + .catch(() => flash('Failed to load related branches')); } } diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js deleted file mode 100644 index 854445bd2a4..00000000000 --- a/app/assets/javascripts/job.js +++ /dev/null @@ -1,190 +0,0 @@ -import $ from 'jquery'; -import _ from 'underscore'; -import { polyfillSticky } from './lib/utils/sticky'; -import axios from './lib/utils/axios_utils'; -import { visitUrl } from './lib/utils/url_utility'; -import bp from './breakpoints'; -import { numberToHumanSize } from './lib/utils/number_utils'; -import { setCiStatusFavicon } from './lib/utils/common_utils'; -import { isScrolledToBottom, scrollDown, scrollUp } from './lib/utils/scroll_utils'; -import LogOutputBehaviours from './lib/utils/logoutput_behaviours'; - -export default class Job extends LogOutputBehaviours { - constructor(options) { - super(); - this.timeout = null; - this.state = null; - this.fetchingStatusFavicon = false; - this.options = options || $('.js-build-options').data(); - - this.pagePath = this.options.pagePath; - this.buildStatus = this.options.buildStatus; - this.state = this.options.logState; - this.buildStage = this.options.buildStage; - this.$document = $(document); - this.$window = $(window); - this.logBytes = 0; - - this.$buildTrace = $('#build-trace'); - this.$buildRefreshAnimation = $('.js-build-refresh'); - this.$truncatedInfo = $('.js-truncated-info'); - this.$buildTraceOutput = $('.js-build-output'); - this.$topBar = $('.js-top-bar'); - - clearTimeout(this.timeout); - - this.initSidebar(); - this.sidebarOnResize(); - - this.$document - .off('click', '.js-sidebar-build-toggle') - .on('click', '.js-sidebar-build-toggle', this.sidebarOnClick.bind(this)); - - this.scrollThrottled = _.throttle(this.toggleScroll.bind(this), 100); - - this.$window - .off('scroll') - .on('scroll', () => { - if (!isScrolledToBottom()) { - this.toggleScrollAnimation(false); - } else if (isScrolledToBottom() && !this.isLogComplete) { - this.toggleScrollAnimation(true); - } - this.scrollThrottled(); - }); - - this.$window - .off('resize.build') - .on('resize.build', _.throttle(this.sidebarOnResize.bind(this), 100)); - - this.initAffixTopArea(); - - this.getBuildTrace(); - } - - initAffixTopArea() { - polyfillSticky(this.$topBar); - } - - scrollToBottom() { - scrollDown(); - this.hasBeenScrolled = true; - this.toggleScroll(); - } - - scrollToTop() { - scrollUp(); - this.hasBeenScrolled = true; - this.toggleScroll(); - } - - toggleScrollAnimation(toggle) { - this.$scrollBottomBtn.toggleClass('animate', toggle); - } - - initSidebar() { - this.$sidebar = $('.js-build-sidebar'); - } - - getBuildTrace() { - return axios.get(`${this.pagePath}/trace.json`, { - params: { state: this.state }, - }) - .then((res) => { - const log = res.data; - - if (!this.fetchingStatusFavicon) { - this.fetchingStatusFavicon = true; - - setCiStatusFavicon(`${this.pagePath}/status.json`) - .then(() => { - this.fetchingStatusFavicon = false; - }) - .catch(() => { - this.fetchingStatusFavicon = false; - }); - } - - if (log.state) { - this.state = log.state; - } - - this.isScrollInBottom = isScrolledToBottom(); - - if (log.append) { - this.$buildTraceOutput.append(log.html); - this.logBytes += log.size; - } else { - this.$buildTraceOutput.html(log.html); - this.logBytes = log.size; - } - - // if the incremental sum of logBytes we received is less than the total - // we need to show a message warning the user about that. - if (this.logBytes < log.total) { - // size is in bytes, we need to calculate KiB - const size = numberToHumanSize(this.logBytes); - $('.js-truncated-info-size').html(`${size}`); - this.$truncatedInfo.removeClass('hidden'); - } else { - this.$truncatedInfo.addClass('hidden'); - } - this.isLogComplete = log.complete; - - if (log.complete === false) { - this.timeout = setTimeout(() => { - this.getBuildTrace(); - }, 4000); - } else { - this.$buildRefreshAnimation.remove(); - this.toggleScrollAnimation(false); - } - - if (log.status !== this.buildStatus) { - visitUrl(this.pagePath); - } - }) - .catch(() => { - this.$buildRefreshAnimation.remove(); - }) - .then(() => { - if (this.isScrollInBottom) { - scrollDown(); - } - }) - .then(() => this.toggleScroll()); - } - // eslint-disable-next-line class-methods-use-this - shouldHideSidebarForViewport() { - const bootstrapBreakpoint = bp.getBreakpointSize(); - return bootstrapBreakpoint === 'xs'; - } - - toggleSidebar(shouldHide) { - const shouldShow = typeof shouldHide === 'boolean' ? !shouldHide : undefined; - const $toggleButton = $('.js-sidebar-build-toggle-header'); - - this.$sidebar - .toggleClass('right-sidebar-expanded', shouldShow) - .toggleClass('right-sidebar-collapsed', shouldHide); - - this.$topBar - .toggleClass('sidebar-expanded', shouldShow) - .toggleClass('sidebar-collapsed', shouldHide); - - if (this.$sidebar.hasClass('right-sidebar-expanded')) { - $toggleButton.addClass('hidden'); - } else { - $toggleButton.removeClass('hidden'); - } - } - - sidebarOnResize() { - this.toggleSidebar(this.shouldHideSidebarForViewport()); - } - - sidebarOnClick() { - if (this.shouldHideSidebarForViewport()) this.toggleSidebar(); - } - -} diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue index 4e8d3ad24cc..ac19034f69d 100644 --- a/app/assets/javascripts/jobs/components/job_app.vue +++ b/app/assets/javascripts/jobs/components/job_app.vue @@ -1,21 +1,32 @@ <script> - import { mapGetters, mapState } from 'vuex'; + import _ from 'underscore'; + import { mapGetters, mapState, mapActions } from 'vuex'; + import { isScrolledToBottom } from '~/lib/utils/scroll_utils'; + import bp from '~/breakpoints'; import CiHeader from '~/vue_shared/components/header_ci_component.vue'; import Callout from '~/vue_shared/components/callout.vue'; + import createStore from '../store'; import EmptyState from './empty_state.vue'; import EnvironmentsBlock from './environments_block.vue'; import ErasedBlock from './erased_block.vue'; + import Log from './job_log.vue'; + import LogTopBar from './job_log_controllers.vue'; import StuckBlock from './stuck_block.vue'; + import Sidebar from './sidebar.vue'; export default { name: 'JobPageApp', + store: createStore(), components: { CiHeader, Callout, EmptyState, EnvironmentsBlock, ErasedBlock, + Log, + LogTopBar, StuckBlock, + Sidebar, }, props: { runnerSettingsUrl: { @@ -23,19 +34,129 @@ required: false, default: null, }, + runnerHelpUrl: { + type: String, + required: false, + default: null, + }, + endpoint: { + type: String, + required: true, + }, + terminalPath: { + type: String, + required: false, + default: null, + }, + pagePath: { + type: String, + required: true, + }, + logState: { + type: String, + required: true, + }, }, computed: { - ...mapState(['isLoading', 'job']), + ...mapState([ + 'isLoading', + 'job', + 'isSidebarOpen', + 'trace', + 'isTraceComplete', + 'traceSize', + 'isTraceSizeVisible', + 'isScrollBottomDisabled', + 'isScrollTopDisabled', + 'isScrolledToBottomBeforeReceivingTrace', + 'hasError', + ]), ...mapGetters([ 'headerActions', 'headerTime', 'shouldRenderCalloutMessage', 'shouldRenderTriggeredLabel', 'hasEnvironment', - 'isJobStuck', 'hasTrace', 'emptyStateIllustration', + 'isScrollingDown', + 'emptyStateAction', + 'hasRunnersForProject', ]), + + shouldRenderContent() { + return !this.isLoading && !this.hasError; + } + }, + watch: { + // Once the job log is loaded, + // fetch the stages for the dropdown on the sidebar + job(newVal, oldVal) { + if (_.isEmpty(oldVal) && !_.isEmpty(newVal.pipeline)) { + this.fetchStages(); + } + }, + }, + created() { + this.throttled = _.throttle(this.toggleScrollButtons, 100); + + this.setJobEndpoint(this.endpoint); + this.setTraceOptions({ + logState: this.logState, + pagePath: this.pagePath, + }); + + this.fetchJob(); + this.fetchTrace(); + + window.addEventListener('resize', this.onResize); + window.addEventListener('scroll', this.updateScroll); + }, + + mounted() { + this.updateSidebar(); + }, + + destroyed() { + window.removeEventListener('resize', this.onResize); + window.removeEventListener('scroll', this.updateScroll); + }, + + methods: { + ...mapActions([ + 'setJobEndpoint', + 'setTraceOptions', + 'fetchJob', + 'fetchStages', + 'hideSidebar', + 'showSidebar', + 'toggleSidebar', + 'fetchTrace', + 'scrollBottom', + 'scrollTop', + 'toggleScrollButtons', + 'toggleScrollAnimation', + ]), + onResize() { + this.updateSidebar(); + this.updateScroll(); + }, + updateSidebar() { + if (bp.getBreakpointSize() === 'xs') { + this.hideSidebar(); + } else if (!this.isSidebarOpen) { + this.showSidebar(); + } + }, + updateScroll() { + if (!isScrolledToBottom()) { + this.toggleScrollAnimation(false); + } else if (this.isScrollingDown) { + this.toggleScrollAnimation(true); + } + + this.throttled(); + }, }, }; </script> @@ -44,71 +165,107 @@ <gl-loading-icon v-if="isLoading" :size="2" - class="prepend-top-20" + class="js-job-loading qa-loading-animation prepend-top-20" /> - <template v-else> - <!-- Header Section --> - <header> - <div class="js-build-header build-header top-area"> - <ci-header - :status="job.status" - :item-id="job.id" - :time="headerTime" - :user="job.user" - :actions="headerActions" - :has-sidebar-button="true" - :should-render-triggered-label="shouldRenderTriggeredLabel" - :item-name="__('Job')" + <template v-else-if="shouldRenderContent"> + <div class="js-job-content build-page"> + <!-- Header Section --> + <header> + <div class="js-build-header build-header top-area"> + <ci-header + :status="job.status" + :item-id="job.id" + :time="headerTime" + :user="job.user" + :actions="headerActions" + :has-sidebar-button="true" + :should-render-triggered-label="shouldRenderTriggeredLabel" + :item-name="__('Job')" + @clickedSidebarButton="toggleSidebar" + /> + </div> + + <callout + v-if="shouldRenderCalloutMessage" + :message="job.callout_message" + /> + </header> + <!-- EO Header Section --> + + <!-- Body Section --> + <stuck-block + v-if="job.stuck" + class="js-job-stuck" + :has-no-runners-for-project="hasRunnersForProject" + :tags="job.tags" + :runners-path="runnerSettingsUrl" + /> + + <environments-block + v-if="hasEnvironment" + class="js-job-environment" + :deployment-status="job.deployment_status" + :icon-status="job.status" + /> + + <erased-block + v-if="job.erased_at" + class="js-job-erased-block" + :user="job.erased_by" + :erased-at="job.erased_at" + /> + + <!--job log --> + <div + v-if="hasTrace" + class="build-trace-container prepend-top-default"> + <log-top-bar + :class="{ + 'sidebar-expanded': isSidebarOpen, + 'sidebar-collapsed': !isSidebarOpen + }" + :erase-path="job.erase_path" + :size="traceSize" + :raw-path="job.raw_path" + :is-scroll-bottom-disabled="isScrollBottomDisabled" + :is-scroll-top-disabled="isScrollTopDisabled" + :is-trace-size-visible="isTraceSizeVisible" + :is-scrolling-down="isScrollingDown" + @scrollJobLogTop="scrollTop" + @scrollJobLogBottom="scrollBottom" + /> + <log + :trace="trace" + :is-complete="isTraceComplete" /> </div> + <!-- EO job log --> - <callout - v-if="shouldRenderCalloutMessage" - :message="job.callout_message" + <!--empty state --> + <empty-state + v-if="!hasTrace" + class="js-job-empty-state" + :illustration-path="emptyStateIllustration.image" + :illustration-size-class="emptyStateIllustration.size" + :title="emptyStateIllustration.title" + :content="emptyStateIllustration.content" + :action="emptyStateAction" /> - </header> - <!-- EO Header Section --> - - <!-- Body Section --> - <stuck-block - v-if="isJobStuck" - class="js-job-stuck" - :has-no-runners-for-project="job.runners.available" - :tags="job.tags" - :runners-path="runnerSettingsUrl" - /> - - <environments-block - v-if="hasEnvironment" - class="js-job-environment" - :deployment-status="job.deployment_status" - :icon-status="job.status" - /> - - <erased-block - v-if="job.erased_at" - class="js-job-erased-block" - :user="job.erased_by" - :erased-at="job.erased_at" - /> - - <!--job log --> - <!-- EO job log --> - - <!--empty state --> - <empty-state - v-if="!hasTrace" - class="js-job-empty-state" - :illustration-path="emptyStateIllustration.image" - :illustration-size-class="emptyStateIllustration.size" - :title="emptyStateIllustration.title" - :content="emptyStateIllustration.content" - :action="job.status.action" - /> <!-- EO empty state --> <!-- EO Body Section --> + </div> </template> + + <sidebar + v-if="shouldRenderContent" + class="js-job-sidebar" + :class="{ + 'right-sidebar-expanded': isSidebarOpen, + 'right-sidebar-collapsed': !isSidebarOpen + }" + :runner-help-url="runnerHelpUrl" + /> </div> </template> diff --git a/app/assets/javascripts/jobs/components/job_container_item.vue b/app/assets/javascripts/jobs/components/job_container_item.vue new file mode 100644 index 00000000000..6486b25c8a7 --- /dev/null +++ b/app/assets/javascripts/jobs/components/job_container_item.vue @@ -0,0 +1,65 @@ +<script> +import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import Icon from '~/vue_shared/components/icon.vue'; +import tooltip from '~/vue_shared/directives/tooltip'; + +export default { + components: { + CiIcon, + Icon, + }, + directives: { + tooltip, + }, + props: { + job: { + type: Object, + required: true, + }, + isActive: { + type: Boolean, + required: true, + }, + }, + computed: { + tooltipText() { + return `${this.job.name} - ${this.job.status.tooltip}`; + }, + }, +}; +</script> + +<template> + <div + class="build-job" + :class="{ + retried: job.retried, + active: isActive + }" + > + <a + v-tooltip + :href="job.status.details_path" + :title="tooltipText" + data-container="body" + data-boundary="viewport" + class="js-job-link" + > + <icon + v-if="isActive" + name="arrow-right" + class="js-arrow-right icon-arrow-right" + /> + + <ci-icon :status="job.status" /> + + <span>{{ job.name ? job.name : job.id }}</span> + + <icon + v-if="job.retried" + name="retry" + class="js-retry-icon" + /> + </a> + </div> +</template> diff --git a/app/assets/javascripts/jobs/components/job_log.vue b/app/assets/javascripts/jobs/components/job_log.vue index 9d78d89239a..ffa6ada3e28 100644 --- a/app/assets/javascripts/jobs/components/job_log.vue +++ b/app/assets/javascripts/jobs/components/job_log.vue @@ -1,20 +1,48 @@ <script> -export default { - name: 'JobLog', - props: { - trace: { - type: String, - required: true, + import { mapState, mapActions } from 'vuex'; + + export default { + name: 'JobLog', + props: { + trace: { + type: String, + required: true, + }, + isComplete: { + type: Boolean, + required: true, + }, + }, + computed: { + ...mapState(['isScrolledToBottomBeforeReceivingTrace']), + }, + updated() { + this.$nextTick(() => this.handleScrollDown()); + }, + mounted() { + this.$nextTick(() => this.handleScrollDown()); }, - isComplete: { - type: Boolean, - required: true, + methods: { + ...mapActions(['scrollBottom']), + /** + * The job log is sent in HTML, which means we need to use `v-html` to render it + * Using the updated hook with $nextTick is not enough to wait for the DOM to be updated + * in this case because it runs before `v-html` has finished running, since there's no + * Vue binding. + * In order to scroll the page down after `v-html` has finished, we need to use setTimeout + */ + handleScrollDown() { + if (this.isScrolledToBottomBeforeReceivingTrace) { + setTimeout(() => { + this.scrollBottom(); + }, 0); + } + }, }, - }, -}; + }; </script> <template> - <pre class="build-trace"> + <pre class="js-build-trace build-trace qa-build-trace"> <code class="bash" v-html="trace" @@ -22,7 +50,7 @@ export default { </code> <div - v-if="isComplete" + v-if="!isComplete" class="js-log-animation build-loader-animation" > <div class="dot"></div> diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue index cc885ea8e1b..94ab1b16c84 100644 --- a/app/assets/javascripts/jobs/components/job_log_controllers.vue +++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue @@ -4,6 +4,7 @@ import Icon from '~/vue_shared/components/icon.vue'; import tooltip from '~/vue_shared/directives/tooltip'; import { numberToHumanSize } from '~/lib/utils/number_utils'; import { sprintf } from '~/locale'; +import scrollDown from '../svg/scroll_down.svg'; export default { components: { @@ -12,6 +13,7 @@ export default { directives: { tooltip, }, + scrollDown, props: { erasePath: { type: String, @@ -65,7 +67,7 @@ export default { }; </script> <template> - <div class="top-bar affix js-top-bar"> + <div class="top-bar affix"> <!-- truncate information --> <div class="js-truncated-info truncated-info d-none d-sm-block float-left"> <template v-if="isTraceSizeVisible"> @@ -100,7 +102,7 @@ export default { v-tooltip :title="s__('Job|Erase job log')" :href="erasePath" - data-confirm="__('Are you sure you want to erase this build?')" + :data-confirm="__('Are you sure you want to erase this build?')" class="js-erase-link controllers-buttons" data-container="body" data-method="post" @@ -138,8 +140,8 @@ export default { class="js-scroll-bottom btn-scroll btn-transparent btn-blank" :class="{ animate: isScrollingDown }" @click="handleScrollToBottom" + v-html="$options.scrollDown" > - <icon name="scroll_down"/> </button> </div> <!-- eo scroll buttons --> diff --git a/app/assets/javascripts/jobs/components/jobs_container.vue b/app/assets/javascripts/jobs/components/jobs_container.vue index 03f36ec5c8b..951bcb36600 100644 --- a/app/assets/javascripts/jobs/components/jobs_container.vue +++ b/app/assets/javascripts/jobs/components/jobs_container.vue @@ -1,17 +1,11 @@ <script> -import _ from 'underscore'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; -import Icon from '~/vue_shared/components/icon.vue'; -import tooltip from '~/vue_shared/directives/tooltip'; +import JobContainerItem from './job_container_item.vue'; export default { components: { - CiIcon, - Icon, - }, - directives: { - tooltip, + JobContainerItem, }, + props: { jobs: { type: Array, @@ -26,49 +20,16 @@ export default { isJobActive(currentJobId) { return this.jobId === currentJobId; }, - tooltipText(job) { - return `${_.escape(job.name)} - ${job.status.tooltip}`; - }, }, }; </script> <template> <div class="js-jobs-container builds-container"> - <div + <job-container-item v-for="job in jobs" :key="job.id" - class="build-job" - :class="{ retried: job.retried, active: isJobActive(job.id) }" - > - <a - v-tooltip - :href="job.status.details_path" - :title="tooltipText(job)" - data-container="body" - > - <icon - v-if="isJobActive(job.id)" - name="arrow-right" - class="js-arrow-right icon-arrow-right" - /> - - <ci-icon :status="job.status" /> - - <span> - <template v-if="job.name"> - {{ job.name }} - </template> - <template v-else> - {{ job.id }} - </template> - </span> - - <icon - v-if="job.retried" - name="retry" - class="js-retry-icon" - /> - </a> - </div> + :job="job" + :is-active="isJobActive(job.id)" + /> </div> </template> diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue index 8f3c6aced23..906769ee6a2 100644 --- a/app/assets/javascripts/jobs/components/sidebar.vue +++ b/app/assets/javascripts/jobs/components/sidebar.vue @@ -29,14 +29,9 @@ export default { required: false, default: '', }, - terminalPath: { - type: String, - required: false, - default: null, - }, }, computed: { - ...mapState(['job', 'isLoading', 'stages', 'jobs', 'selectedStage']), + ...mapState(['job', 'stages', 'jobs', 'selectedStage']), coverage() { return `${this.job.coverage}%`; }, @@ -64,10 +59,10 @@ export default { return ''; } - let t = this.job.metadata.timeout_human_readable; - if (this.job.metadata.timeout_source !== '') { - t += ` (from ${this.job.metadata.timeout_source})`; - } + let t = this.job.metadata.timeout_human_readable; + if (this.job.metadata.timeout_source !== '') { + t += ` (from ${this.job.metadata.timeout_source})`; + } return t; }, @@ -100,196 +95,190 @@ export default { ); }, commit() { - return this.job.pipeline.commit || {}; + return this.job.pipeline && this.job.pipeline.commit ? this.job.pipeline.commit : {}; }, }, methods: { - ...mapActions(['fetchJobsForStage']), + ...mapActions(['fetchJobsForStage', 'toggleSidebar']), }, }; </script> <template> <aside - class="js-build-sidebar right-sidebar right-sidebar-expanded build-sidebar" + class="right-sidebar build-sidebar" data-offset-top="101" data-spy="affix" > <div class="sidebar-container"> <div class="blocks-container"> - <template v-if="!isLoading"> - <div class="block"> - <strong class="inline prepend-top-8"> - {{ job.name }} - </strong> - <a - v-if="job.retry_path" - :class="retryButtonClass" - :href="job.retry_path" - data-method="post" - rel="nofollow" - > - {{ __('Retry') }} - </a> - <a - v-if="terminalPath" - :href="terminalPath" - class="js-terminal-link pull-right btn btn-primary - btn-inverted visible-md-block visible-lg-block" - target="_blank" - > - {{ __('Debug') }} - <icon name="external-link" /> - </a> - <button - :aria-label="__('Toggle Sidebar')" - type="button" - class="btn btn-blank gutter-toggle - float-right d-block d-md-none js-sidebar-build-toggle" - > - <i - aria-hidden="true" - data-hidden="true" - class="fa fa-angle-double-right" - ></i> - </button> - </div> - <div - v-if="job.retry_path || job.new_issue_path" - class="block retry-link" + <div class="block"> + <strong class="inline prepend-top-8"> + {{ job.name }} + </strong> + <a + v-if="job.retry_path" + :class="retryButtonClass" + :href="job.retry_path" + data-method="post" + rel="nofollow" > - <a - v-if="job.new_issue_path" - :href="job.new_issue_path" - class="js-new-issue btn btn-success btn-inverted" - > - {{ __('New issue') }} + {{ __('Retry') }} + </a> + <a + v-if="job.terminal_path" + :href="job.terminal_path" + class="js-terminal-link pull-right btn btn-primary + btn-inverted visible-md-block visible-lg-block" + target="_blank" + > + {{ __('Debug') }} + <icon name="external-link" /> + </a> + <button + :aria-label="__('Toggle Sidebar')" + type="button" + class="btn btn-blank gutter-toggle + float-right d-block d-md-none js-sidebar-build-toggle" + @click="toggleSidebar" + > + <i + aria-hidden="true" + data-hidden="true" + class="fa fa-angle-double-right" + ></i> + </button> + </div> + <div + v-if="job.retry_path || job.new_issue_path" + class="block retry-link" + > + <a + v-if="job.new_issue_path" + :href="job.new_issue_path" + class="js-new-issue btn btn-success btn-inverted" + > + {{ __('New issue') }} + </a> + <a + v-if="job.retry_path" + :href="job.retry_path" + class="js-retry-job btn btn-inverted-secondary" + data-method="post" + rel="nofollow" + > + {{ __('Retry') }} + </a> + </div> + <div :class="{ block : renderBlock }"> + <p + v-if="job.merge_request" + class="build-detail-row js-job-mr" + > + <span class="build-light-text"> + {{ __('Merge Request:') }} + </span> + <a :href="job.merge_request.path"> + !{{ job.merge_request.iid }} </a> + </p> + + <detail-row + v-if="job.duration" + :value="duration" + class="js-job-duration" + title="Duration" + /> + <detail-row + v-if="job.finished_at" + :value="timeFormated(job.finished_at)" + class="js-job-finished" + title="Finished" + /> + <detail-row + v-if="job.erased_at" + :value="timeFormated(job.erased_at)" + class="js-job-erased" + title="Erased" + /> + <detail-row + v-if="job.queued" + :value="queued" + class="js-job-queued" + title="Queued" + /> + <detail-row + v-if="hasTimeout" + :help-url="runnerHelpUrl" + :value="timeout" + class="js-job-timeout" + title="Timeout" + /> + <detail-row + v-if="job.runner" + :value="runnerId" + class="js-job-runner" + title="Runner" + /> + <detail-row + v-if="job.coverage" + :value="coverage" + class="js-job-coverage" + title="Coverage" + /> + <p + v-if="job.tags.length" + class="build-detail-row js-job-tags" + > + <span class="build-light-text"> + {{ __('Tags:') }} + </span> + <span + v-for="(tag, i) in job.tags" + :key="i" + class="label label-primary"> + {{ tag }} + </span> + </p> + + <div + v-if="job.cancel_path" + class="btn-group prepend-top-5" + role="group"> <a - v-if="job.retry_path" - :href="job.retry_path" - class="js-retry-job btn btn-inverted-secondary" + :href="job.cancel_path" + class="js-cancel-job btn btn-sm btn-default" data-method="post" rel="nofollow" > - {{ __('Retry') }} + {{ __('Cancel') }} </a> </div> - <div :class="{ block : renderBlock }"> - <p - v-if="job.merge_request" - class="build-detail-row js-job-mr" - > - <span class="build-light-text"> - {{ __('Merge Request:') }} - </span> - <a :href="job.merge_request.path"> - !{{ job.merge_request.iid }} - </a> - </p> - - <detail-row - v-if="job.duration" - :value="duration" - class="js-job-duration" - title="Duration" - /> - <detail-row - v-if="job.finished_at" - :value="timeFormated(job.finished_at)" - class="js-job-finished" - title="Finished" - /> - <detail-row - v-if="job.erased_at" - :value="timeFormated(job.erased_at)" - class="js-job-erased" - title="Erased" - /> - <detail-row - v-if="job.queued" - :value="queued" - class="js-job-queued" - title="Queued" - /> - <detail-row - v-if="hasTimeout" - :help-url="runnerHelpUrl" - :value="timeout" - class="js-job-timeout" - title="Timeout" - /> - <detail-row - v-if="job.runner" - :value="runnerId" - class="js-job-runner" - title="Runner" - /> - <detail-row - v-if="job.coverage" - :value="coverage" - class="js-job-coverage" - title="Coverage" - /> - <p - v-if="job.tags.length" - class="build-detail-row js-job-tags" - > - <span class="build-light-text"> - {{ __('Tags:') }} - </span> - <span - v-for="(tag, i) in job.tags" - :key="i" - class="label label-primary"> - {{ tag }} - </span> - </p> - - <div - v-if="job.cancel_path" - class="btn-group prepend-top-5" - role="group"> - <a - :href="job.cancel_path" - class="js-cancel-job btn btn-sm btn-default" - data-method="post" - rel="nofollow" - > - {{ __('Cancel') }} - </a> - </div> - </div> - <artifacts-block - v-if="hasArtifact" - :artifact="job.artifact" - /> - <trigger-block - v-if="hasTriggers" - :trigger="job.trigger" - /> - <commit-block - :is-last-block="hasStages" - :commit="commit" - :merge-request="job.merge_request" - /> + </div> - <stages-dropdown - :stages="stages" - :pipeline="job.pipeline" - :selected-stage="selectedStage" - @requestSidebarStageDropdown="fetchJobsForStage" - /> + <artifacts-block + v-if="hasArtifact" + :artifact="job.artifact" + /> + <trigger-block + v-if="hasTriggers" + :trigger="job.trigger" + /> + <commit-block + :is-last-block="hasStages" + :commit="commit" + :merge-request="job.merge_request" + /> - </template> - <gl-loading-icon - v-else - :size="2" - class="prepend-top-10" + <stages-dropdown + :stages="stages" + :pipeline="job.pipeline" + :selected-stage="selectedStage" + @requestSidebarStageDropdown="fetchJobsForStage" /> </div> <jobs-container - v-if="!isLoading && jobs.length" + v-if="jobs.length" :jobs="jobs" :job-id="job.id" /> diff --git a/app/assets/javascripts/jobs/components/stuck_block.vue b/app/assets/javascripts/jobs/components/stuck_block.vue index a60643b2c65..1d5789b175a 100644 --- a/app/assets/javascripts/jobs/components/stuck_block.vue +++ b/app/assets/javascripts/jobs/components/stuck_block.vue @@ -23,14 +23,7 @@ export default { <template> <div class="bs-callout bs-callout-warning"> <p - v-if="hasNoRunnersForProject" - class="js-stuck-no-runners append-bottom-0" - > - {{ s__(`Job|This job is stuck, because the project - doesn't have any runners online assigned to it.`) }} - </p> - <p - v-else-if="tags.length" + v-if="tags.length" class="js-stuck-with-tags append-bottom-0" > {{ s__(`This job is stuck, because you don't have @@ -44,6 +37,13 @@ export default { </span> </p> <p + v-else-if="hasNoRunnersForProject" + class="js-stuck-no-runners append-bottom-0" + > + {{ s__(`Job|This job is stuck, because the project + doesn't have any runners online assigned to it.`) }} + </p> + <p v-else class="js-stuck-no-active-runner append-bottom-0" > diff --git a/app/assets/javascripts/jobs/index.js b/app/assets/javascripts/jobs/index.js new file mode 100644 index 00000000000..ccd096a1da5 --- /dev/null +++ b/app/assets/javascripts/jobs/index.js @@ -0,0 +1,26 @@ +import Vue from 'vue'; +import JobApp from './components/job_app.vue'; + +export default () => { + const element = document.getElementById('js-job-vue-app'); + + return new Vue({ + el: element, + components: { + JobApp, + }, + render(createElement) { + return createElement('job-app', { + props: { + runnerHelpUrl: element.dataset.runnerHelpUrl, + runnerSettingsUrl: element.dataset.runnerSettingsUrl, + endpoint: element.dataset.endpoint, + pagePath: element.dataset.buildOptionsPagePath, + logState: element.dataset.buildOptionsLogState, + buildStatus: element.dataset.buildOptionsBuildStatus, + }, + }); + }, + }); +}; + diff --git a/app/assets/javascripts/jobs/job_details_bundle.js b/app/assets/javascripts/jobs/job_details_bundle.js deleted file mode 100644 index 15cd79b1c50..00000000000 --- a/app/assets/javascripts/jobs/job_details_bundle.js +++ /dev/null @@ -1,76 +0,0 @@ -import _ from 'underscore'; -import { mapState, mapActions } from 'vuex'; -import Vue from 'vue'; -import Job from '../job'; -import JobApp from './components/job_app.vue'; -import Sidebar from './components/sidebar.vue'; -import createStore from './store'; - -export default () => { - const { dataset } = document.getElementById('js-job-details-vue'); - - - - const store = createStore(); - store.dispatch('setJobEndpoint', dataset.endpoint); - - store.dispatch('fetchJob'); - - // Header - // eslint-disable-next-line no-new - new Vue({ - el: '#js-build-header-vue', - components: { - JobApp, - }, - store, - computed: { - ...mapState(['job', 'isLoading']), - }, - render(createElement) { - return createElement('job-app', { - props: { - isLoading: this.isLoading, - job: this.job, - runnerSettingsUrl: dataset.runnerSettingsUrl, - }, - }); - }, - }); - - // Sidebar information block - const detailsBlockElement = document.getElementById('js-details-block-vue'); - const detailsBlockDataset = detailsBlockElement.dataset; - // eslint-disable-next-line - new Vue({ - el: detailsBlockElement, - components: { - Sidebar, - }, - computed: { - ...mapState(['job']), - }, - watch: { - job(newVal, oldVal) { - if (_.isEmpty(oldVal) && !_.isEmpty(newVal.pipeline)) { - this.fetchStages(); - } - }, - }, - methods: { - ...mapActions(['fetchStages']), - }, - store, - render(createElement) { - return createElement('sidebar', { - props: { - runnerHelpUrl: dataset.runnerHelpUrl, - terminalPath: detailsBlockDataset.terminalPath, - }, - }); - }, - }); - - // eslint-disable-next-line no-new - new Job(); -}; diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js index d0040161dc3..54ed217572a 100644 --- a/app/assets/javascripts/jobs/store/actions.js +++ b/app/assets/javascripts/jobs/store/actions.js @@ -1,17 +1,32 @@ import Visibility from 'visibilityjs'; import * as types from './mutation_types'; -import axios from '../../lib/utils/axios_utils'; -import Poll from '../../lib/utils/poll'; -import { setCiStatusFavicon } from '../../lib/utils/common_utils'; -import flash from '../../flash'; -import { __ } from '../../locale'; +import axios from '~/lib/utils/axios_utils'; +import Poll from '~/lib/utils/poll'; +import { setFaviconOverlay, resetFavicon } from '~/lib/utils/common_utils'; +import flash from '~/flash'; +import { __ } from '~/locale'; +import { + canScroll, + isScrolledToBottom, + isScrolledToTop, + isScrolledToMiddle, + scrollDown, + scrollUp, +} from '~/lib/utils/scroll_utils'; export const setJobEndpoint = ({ commit }, endpoint) => commit(types.SET_JOB_ENDPOINT, endpoint); -export const setTraceEndpoint = ({ commit }, endpoint) => - commit(types.SET_TRACE_ENDPOINT, endpoint); -export const setStagesEndpoint = ({ commit }, endpoint) => - commit(types.SET_STAGES_ENDPOINT, endpoint); -export const setJobsEndpoint = ({ commit }, endpoint) => commit(types.SET_JOBS_ENDPOINT, endpoint); +export const setTraceOptions = ({ commit }, options) => commit(types.SET_TRACE_OPTIONS, options); + +export const hideSidebar = ({ commit }) => commit(types.HIDE_SIDEBAR); +export const showSidebar = ({ commit }) => commit(types.SHOW_SIDEBAR); + +export const toggleSidebar = ({ dispatch, state }) => { + if (state.isSidebarOpen) { + dispatch('hideSidebar'); + } else { + dispatch('showSidebar'); + } +}; let eTagPoll; @@ -62,41 +77,84 @@ export const fetchJob = ({ state, dispatch }) => { }); }; -export const receiveJobSuccess = ({ commit }, data) => { +export const receiveJobSuccess = ({ commit }, data = {}) => { commit(types.RECEIVE_JOB_SUCCESS, data); + + if (data.favicon) { + setFaviconOverlay(data.favicon); + } else { + resetFavicon(); + } }; export const receiveJobError = ({ commit }) => { commit(types.RECEIVE_JOB_ERROR); flash(__('An error occurred while fetching the job.')); + resetFavicon(); }; /** * Job's Trace */ -export const scrollTop = ({ commit }) => { - commit(types.SCROLL_TO_TOP); - window.scrollTo({ top: 0 }); +export const scrollTop = ({ dispatch }) => { + scrollUp(); + dispatch('toggleScrollButtons'); }; -export const scrollBottom = ({ commit }) => { - commit(types.SCROLL_TO_BOTTOM); - window.scrollTo({ top: document.height }); +export const scrollBottom = ({ dispatch }) => { + scrollDown(); + dispatch('toggleScrollButtons'); +}; + +/** + * Responsible for toggling the disabled state of the scroll buttons + */ +export const toggleScrollButtons = ({ dispatch }) => { + if (canScroll()) { + if (isScrolledToMiddle()) { + dispatch('enableScrollTop'); + dispatch('enableScrollBottom'); + } else if (isScrolledToTop()) { + dispatch('disableScrollTop'); + dispatch('enableScrollBottom'); + } else if (isScrolledToBottom()) { + dispatch('disableScrollBottom'); + dispatch('enableScrollTop'); + } + } else { + dispatch('disableScrollBottom'); + dispatch('disableScrollTop'); + } +}; + +export const disableScrollBottom = ({ commit }) => commit(types.DISABLE_SCROLL_BOTTOM); +export const disableScrollTop = ({ commit }) => commit(types.DISABLE_SCROLL_TOP); +export const enableScrollBottom = ({ commit }) => commit(types.ENABLE_SCROLL_BOTTOM); +export const enableScrollTop = ({ commit }) => commit(types.ENABLE_SCROLL_TOP); + +/** + * While the automatic scroll down is active, + * we show the scroll down button with an animation + */ +export const toggleScrollAnimation = ({ commit }, toggle) => + commit(types.TOGGLE_SCROLL_ANIMATION, toggle); + +/** + * Responsible to handle automatic scroll + */ +export const toggleScrollisInBottom = ({ commit }, toggle) => { + commit(types.TOGGLE_IS_SCROLL_IN_BOTTOM_BEFORE_UPDATING_TRACE, toggle); }; export const requestTrace = ({ commit }) => commit(types.REQUEST_TRACE); let traceTimeout; -export const fetchTrace = ({ dispatch, state }) => { - dispatch('requestTrace'); - +export const fetchTrace = ({ dispatch, state }) => axios .get(`${state.traceEndpoint}/trace.json`, { params: { state: state.traceState }, }) .then(({ data }) => { - if (!state.fetchingStatusFavicon) { - dispatch('fetchFavicon'); - } + dispatch('toggleScrollisInBottom', isScrolledToBottom()); dispatch('receiveTraceSuccess', data); if (!data.complete) { @@ -108,7 +166,7 @@ export const fetchTrace = ({ dispatch, state }) => { } }) .catch(() => dispatch('receiveTraceError')); -}; + export const stopPollingTrace = ({ commit }) => { commit(types.STOP_POLLING_TRACE); clearTimeout(traceTimeout); @@ -120,17 +178,6 @@ export const receiveTraceError = ({ commit }) => { flash(__('An error occurred while fetching the job log.')); }; -export const fetchFavicon = ({ state, dispatch }) => { - dispatch('requestStatusFavicon'); - setCiStatusFavicon(`${state.pagePath}/status.json`) - .then(() => dispatch('receiveStatusFaviconSuccess')) - .catch(() => dispatch('requestStatusFaviconError')); -}; -export const requestStatusFavicon = ({ commit }) => commit(types.REQUEST_STATUS_FAVICON); -export const receiveStatusFaviconSuccess = ({ commit }) => - commit(types.RECEIVE_STATUS_FAVICON_SUCCESS); -export const requestStatusFaviconError = ({ commit }) => commit(types.RECEIVE_STATUS_FAVICON_ERROR); - /** * Stages dropdown on sidebar */ diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js index 9f4f372e3d2..4de01f8e532 100644 --- a/app/assets/javascripts/jobs/store/getters.js +++ b/app/assets/javascripts/jobs/store/getters.js @@ -1,5 +1,6 @@ import _ from 'underscore'; import { __ } from '~/locale'; +import { isScrolledToBottom } from '~/lib/utils/scroll_utils'; export const headerActions = state => { if (state.job.new_issue_path) { @@ -34,20 +35,16 @@ export const hasEnvironment = state => !_.isEmpty(state.job.deployment_status); * Used to check if it should render the job log or the empty state * @returns {Boolean} */ -export const hasTrace = state => state.job.has_trace || state.job.status.group === 'running'; +export const hasTrace = state => state.job.has_trace || (!_.isEmpty(state.job.status) && state.job.status.group === 'running'); export const emptyStateIllustration = state => (state.job && state.job.status && state.job.status.illustration) || {}; -/** - * When the job is pending and there are no available runners - * we need to render the stuck block; - * - * @returns {Boolean} - */ -export const isJobStuck = state => - state.job.status.group === 'pending' && - (!_.isEmpty(state.job.runners) && state.job.runners.available === false); +export const emptyStateAction = state => (state.job && state.job.status && state.job.status.action) || {}; + +export const isScrollingDown = state => isScrolledToBottom() && !state.isTraceComplete; + +export const hasRunnersForProject = state => state.job.runners.available && !state.job.runners.online; // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/jobs/store/mutation_types.js b/app/assets/javascripts/jobs/store/mutation_types.js index e66e1d4f116..fd098f13e90 100644 --- a/app/assets/javascripts/jobs/store/mutation_types.js +++ b/app/assets/javascripts/jobs/store/mutation_types.js @@ -1,10 +1,19 @@ export const SET_JOB_ENDPOINT = 'SET_JOB_ENDPOINT'; -export const SET_TRACE_ENDPOINT = 'SET_TRACE_ENDPOINT'; -export const SET_STAGES_ENDPOINT = 'SET_STAGES_ENDPOINT'; -export const SET_JOBS_ENDPOINT = 'SET_JOBS_ENDPOINT'; +export const SET_TRACE_OPTIONS = 'SET_TRACE_OPTIONS'; + +export const HIDE_SIDEBAR = 'HIDE_SIDEBAR'; +export const SHOW_SIDEBAR = 'SHOW_SIDEBAR'; export const SCROLL_TO_TOP = 'SCROLL_TO_TOP'; export const SCROLL_TO_BOTTOM = 'SCROLL_TO_BOTTOM'; +export const DISABLE_SCROLL_BOTTOM = 'DISABLE_SCROLL_BOTTOM'; +export const DISABLE_SCROLL_TOP = 'DISABLE_SCROLL_TOP'; +export const ENABLE_SCROLL_BOTTOM = 'ENABLE_SCROLL_BOTTOM'; +export const ENABLE_SCROLL_TOP = 'ENABLE_SCROLL_TOP'; +// TODO +export const TOGGLE_SCROLL_ANIMATION = 'TOGGLE_SCROLL_ANIMATION'; + +export const TOGGLE_IS_SCROLL_IN_BOTTOM_BEFORE_UPDATING_TRACE = 'TOGGLE_IS_SCROLL_IN_BOTTOM'; export const REQUEST_JOB = 'REQUEST_JOB'; export const RECEIVE_JOB_SUCCESS = 'RECEIVE_JOB_SUCCESS'; @@ -15,10 +24,6 @@ export const STOP_POLLING_TRACE = 'STOP_POLLING_TRACE'; export const RECEIVE_TRACE_SUCCESS = 'RECEIVE_TRACE_SUCCESS'; export const RECEIVE_TRACE_ERROR = 'RECEIVE_TRACE_ERROR'; -export const REQUEST_STATUS_FAVICON = 'REQUEST_STATUS_FAVICON'; -export const RECEIVE_STATUS_FAVICON_SUCCESS = 'RECEIVE_STATUS_FAVICON_SUCCESS'; -export const RECEIVE_STATUS_FAVICON_ERROR = 'RECEIVE_STATUS_FAVICON_ERROR'; - export const REQUEST_STAGES = 'REQUEST_STAGES'; export const RECEIVE_STAGES_SUCCESS = 'RECEIVE_STAGES_SUCCESS'; export const RECEIVE_STAGES_ERROR = 'RECEIVE_STAGES_ERROR'; diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js index f00e06e1a6c..4195d787f12 100644 --- a/app/assets/javascripts/jobs/store/mutations.js +++ b/app/assets/javascripts/jobs/store/mutations.js @@ -4,14 +4,17 @@ export default { [types.SET_JOB_ENDPOINT](state, endpoint) { state.jobEndpoint = endpoint; }, - [types.REQUEST_STATUS_FAVICON](state) { - state.fetchingStatusFavicon = true; + + [types.SET_TRACE_OPTIONS](state, options = {}) { + state.traceEndpoint = options.pagePath; + state.traceState = options.logState; }, - [types.RECEIVE_STATUS_FAVICON_SUCCESS](state) { - state.fetchingStatusFavicon = false; + + [types.HIDE_SIDEBAR](state) { + state.isSidebarOpen = false; }, - [types.RECEIVE_STATUS_FAVICON_ERROR](state) { - state.fetchingStatusFavicon = false; + [types.SHOW_SIDEBAR](state) { + state.isSidebarOpen = true; }, [types.RECEIVE_TRACE_SUCCESS](state, log) { @@ -23,8 +26,12 @@ export default { state.trace += log.html; state.traceSize += log.size; } else { - state.trace = log.html; - state.traceSize = log.size; + // When the job still does not have a trace + // the trace response will not have a defined + // html or size. We keep the old value otherwise these + // will be set to `undefined` + state.trace = log.html || state.trace; + state.traceSize = log.size || state.traceSize; } if (state.traceSize < log.total) { @@ -33,25 +40,29 @@ export default { state.isTraceSizeVisible = false; } - state.isTraceComplete = log.complete; - state.hasTraceError = false; + state.isTraceComplete = log.complete || state.isTraceComplete; }, + + /** + * Will remove loading animation + */ [types.STOP_POLLING_TRACE](state) { state.isTraceComplete = true; }, - // todo_fl: check this. + + /** + * Will remove loading animation + */ [types.RECEIVE_TRACE_ERROR](state) { - state.isLoadingTrace = false; state.isTraceComplete = true; - state.hasTraceError = true; }, [types.REQUEST_JOB](state) { state.isLoading = true; }, [types.RECEIVE_JOB_SUCCESS](state, job) { - state.isLoading = false; state.hasError = false; + state.isLoading = false; state.job = job; /** @@ -66,17 +77,28 @@ export default { }, [types.RECEIVE_JOB_ERROR](state) { state.isLoading = false; - state.hasError = true; state.job = {}; + state.hasError = true; }, - [types.SCROLL_TO_TOP](state) { - state.isTraceScrolledToBottom = false; - state.hasBeenScrolled = true; + [types.ENABLE_SCROLL_TOP](state) { + state.isScrollTopDisabled = false; + }, + [types.DISABLE_SCROLL_TOP](state) { + state.isScrollTopDisabled = true; + }, + [types.ENABLE_SCROLL_BOTTOM](state) { + state.isScrollBottomDisabled = false; }, - [types.SCROLL_TO_BOTTOM](state) { - state.isTraceScrolledToBottom = true; - state.hasBeenScrolled = true; + [types.DISABLE_SCROLL_BOTTOM](state) { + state.isScrollBottomDisabled = true; + }, + [types.TOGGLE_SCROLL_ANIMATION](state, toggle) { + state.isScrollingDown = toggle; + }, + + [types.TOGGLE_IS_SCROLL_IN_BOTTOM_BEFORE_UPDATING_TRACE](state, toggle) { + state.isScrolledToBottomBeforeReceivingTrace = toggle; }, [types.REQUEST_STAGES](state) { diff --git a/app/assets/javascripts/jobs/store/state.js b/app/assets/javascripts/jobs/store/state.js index afbc959bb71..0eb269ca38f 100644 --- a/app/assets/javascripts/jobs/store/state.js +++ b/app/assets/javascripts/jobs/store/state.js @@ -4,36 +4,29 @@ export default () => ({ jobEndpoint: null, traceEndpoint: null, - // dropdown options - stagesEndpoint: null, - // list of jobs on sidebard - stageJobsEndpoint: null, + // sidebar + isSidebarOpen: true, - // job log isLoading: false, hasError: false, job: {}, - // trace - isLoadingTrace: false, - hasTraceError: false, + // scroll buttons state + isScrollBottomDisabled: true, + isScrollTopDisabled: true, - trace: '', - - isTraceScrolledToBottom: false, - hasBeenScrolled: false, + // Used to check if we should keep the automatic scroll + isScrolledToBottomBeforeReceivingTrace: true, + trace: '', isTraceComplete: false, - traceSize: 0, // todo_fl: needs to be converted into human readable format in components + traceSize: 0, isTraceSizeVisible: false, - fetchingStatusFavicon: false, - // used as a query parameter + // used as a query parameter to fetch the trace traceState: null, - // used to check if we need to redirect the user - todo_fl: check if actually needed - traceStatus: null, - // sidebar dropdown + // sidebar dropdown & list of jobs isLoadingStages: false, isLoadingJobs: false, selectedStage: __('More'), diff --git a/app/assets/javascripts/jobs/svg/scroll_down.svg b/app/assets/javascripts/jobs/svg/scroll_down.svg new file mode 100644 index 00000000000..1d22870ec09 --- /dev/null +++ b/app/assets/javascripts/jobs/svg/scroll_down.svg @@ -0,0 +1,5 @@ +<svg width="12" height="16" viewBox="0 0 12 16" xmlns="http://www.w3.org/2000/svg"> + <path class="first-triangle" d="M1.048 14.155a.508.508 0 0 0-.32.105c-.091.07-.136.154-.136.25v.71c0 .095.045.178.135.249.09.07.197.105.321.105h10.043c.124 0 .23-.035.321-.105.09-.07.136-.154.136-.25v-.71c0-.095-.045-.178-.136-.249a.508.508 0 0 0-.32-.105"/> + <path class="second-triangle" d="M.687 8.027c-.09-.087-.122-.16-.093-.22.028-.06.104-.09.228-.09h10.5c.123 0 .2.03.228.09.029.06-.002.133-.093.22L6.393 12.91a.458.458 0 0 1-.136.089h-.37a.626.626 0 0 1-.136-.09"/> + <path class="third-triangle" d="M.687 1.027C.597.94.565.867.594.807c.028-.06.104-.09.228-.09h10.5c.123 0 .2.03.228.09.029.06-.002.133-.093.22L6.393 5.91A.458.458 0 0 1 6.257 6h-.37a.626.626 0 0 1-.136-.09"/> +</svg> diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js index 2c995c5902f..062501d1d04 100644 --- a/app/assets/javascripts/label_manager.js +++ b/app/assets/javascripts/label_manager.js @@ -47,7 +47,10 @@ export default class LabelManager { } toggleEmptyState($label, $btn, action) { - this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li')); + this.emptyState.classList.toggle( + 'hidden', + !!this.prioritizedLabels[0].querySelector(':scope > li'), + ); } toggleLabelPriority($label, action, persistState) { @@ -80,16 +83,14 @@ export default class LabelManager { return; } if (action === 'remove') { - axios.delete(url) - .catch(rollbackLabelPosition); + axios.delete(url).catch(rollbackLabelPosition); // Restore empty message if (!$from.find('li').length) { $from.find('.empty-message').removeClass('hidden'); } } else { - this.savePrioritySort($label, action) - .catch(rollbackLabelPosition); + this.savePrioritySort($label, action).catch(rollbackLabelPosition); } } @@ -102,8 +103,7 @@ export default class LabelManager { } onPrioritySortUpdate() { - this.savePrioritySort() - .catch(() => flash(this.errorMessage)); + this.savePrioritySort().catch(() => flash(this.errorMessage)); } savePrioritySort() { diff --git a/app/assets/javascripts/labels.js b/app/assets/javascripts/labels.js index d85ae851706..2bc09237495 100644 --- a/app/assets/javascripts/labels.js +++ b/app/assets/javascripts/labels.js @@ -22,7 +22,7 @@ export default class Labels { updateColorPreview() { const previewColor = $('input#label_color').val(); return $('div.label-color-preview').css('background-color', previewColor); - // Updates the the preview color with the hex-color input + // Updates the the preview color with the hex-color input } // Updates the preview color with a click on a suggested color diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js index e3177188772..b8c3c237eb3 100644 --- a/app/assets/javascripts/layout_nav.js +++ b/app/assets/javascripts/layout_nav.js @@ -5,7 +5,9 @@ import initFlyOutNav from './fly_out_nav'; function hideEndFade($scrollingTabs) { $scrollingTabs.each(function scrollTabsLoop() { const $this = $(this); - $this.siblings('.fade-right').toggleClass('scrolling', Math.round($this.width()) < $this.prop('scrollWidth')); + $this + .siblings('.fade-right') + .toggleClass('scrolling', Math.round($this.width()) < $this.prop('scrollWidth')); }); } @@ -15,38 +17,42 @@ export default function initLayoutNav() { initFlyOutNav(); - $(document).on('init.scrolling-tabs', () => { - const $scrollingTabs = $('.scrolling-tabs').not('.is-initialized'); - $scrollingTabs.addClass('is-initialized'); + $(document) + .on('init.scrolling-tabs', () => { + const $scrollingTabs = $('.scrolling-tabs').not('.is-initialized'); + $scrollingTabs.addClass('is-initialized'); - $(window).on('resize.nav', () => { - hideEndFade($scrollingTabs); - }).trigger('resize.nav'); + $(window) + .on('resize.nav', () => { + hideEndFade($scrollingTabs); + }) + .trigger('resize.nav'); - $scrollingTabs.on('scroll', function tabsScrollEvent() { - const $this = $(this); - const currentPosition = $this.scrollLeft(); - const maxPosition = $this.prop('scrollWidth') - $this.outerWidth(); + $scrollingTabs.on('scroll', function tabsScrollEvent() { + const $this = $(this); + const currentPosition = $this.scrollLeft(); + const maxPosition = $this.prop('scrollWidth') - $this.outerWidth(); - $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0); - $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1); - }); + $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0); + $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1); + }); - $scrollingTabs.each(function scrollTabsEachLoop() { - const $this = $(this); - const scrollingTabWidth = $this.width(); - const $active = $this.find('.active'); - const activeWidth = $active.width(); + $scrollingTabs.each(function scrollTabsEachLoop() { + const $this = $(this); + const scrollingTabWidth = $this.width(); + const $active = $this.find('.active'); + const activeWidth = $active.width(); - if ($active.length) { - const offset = $active.offset().left + activeWidth; + if ($active.length) { + const offset = $active.offset().left + activeWidth; - if (offset > scrollingTabWidth - 30) { - const scrollLeft = (offset - (scrollingTabWidth / 2)) - (activeWidth / 2); + if (offset > scrollingTabWidth - 30) { + const scrollLeft = offset - scrollingTabWidth / 2 - activeWidth / 2; - $this.scrollLeft(scrollLeft); + $this.scrollLeft(scrollLeft); + } } - } - }); - }).trigger('init.scrolling-tabs'); + }); + }) + .trigger('init.scrolling-tabs'); } diff --git a/app/assets/javascripts/lib/utils/datefix.js b/app/assets/javascripts/lib/utils/datefix.js deleted file mode 100644 index 19e4085dbbb..00000000000 --- a/app/assets/javascripts/lib/utils/datefix.js +++ /dev/null @@ -1,28 +0,0 @@ -export const pad = (val, len = 2) => `0${val}`.slice(-len); - -/** - * Formats dates in Pickaday - * @param {String} dateString Date in yyyy-mm-dd format - * @return {Date} UTC format - */ -export const parsePikadayDate = dateString => { - const parts = dateString.split('-'); - const year = parseInt(parts[0], 10); - const month = parseInt(parts[1] - 1, 10); - const day = parseInt(parts[2], 10); - - return new Date(year, month, day); -}; - -/** - * Used `onSelect` method in pickaday - * @param {Date} date UTC format - * @return {String} Date formated in yyyy-mm-dd - */ -export const pikadayToString = date => { - const day = pad(date.getDate()); - const month = pad(date.getMonth() + 1); - const year = date.getFullYear(); - - return `${year}-${month}-${day}`; -}; diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index 833dbefd3dc..46740308f17 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -1,4 +1,5 @@ import $ from 'jquery'; +import _ from 'underscore'; import timeago from 'timeago.js'; import dateFormat from 'dateformat'; import { pluralize } from './text_utility'; @@ -46,6 +47,8 @@ const getMonthNames = abbreviated => { ]; }; +export const pad = (val, len = 2) => `0${val}`.slice(-len); + /** * Given a date object returns the day of the week in English * @param {date} date @@ -74,10 +77,10 @@ let timeagoInstance; /** * Sets a timeago Instance */ -export function getTimeago() { +export const getTimeago = () => { if (!timeagoInstance) { - const localeRemaining = function getLocaleRemaining(number, index) { - return [ + const localeRemaining = (number, index) => + [ [s__('Timeago|just now'), s__('Timeago|right now')], [s__('Timeago|%s seconds ago'), s__('Timeago|%s seconds remaining')], [s__('Timeago|1 minute ago'), s__('Timeago|1 minute remaining')], @@ -93,9 +96,9 @@ export function getTimeago() { [s__('Timeago|1 year ago'), s__('Timeago|1 year remaining')], [s__('Timeago|%s years ago'), s__('Timeago|%s years remaining')], ][index]; - }; - const locale = function getLocale(number, index) { - return [ + + const locale = (number, index) => + [ [s__('Timeago|just now'), s__('Timeago|right now')], [s__('Timeago|%s seconds ago'), s__('Timeago|in %s seconds')], [s__('Timeago|1 minute ago'), s__('Timeago|in 1 minute')], @@ -111,7 +114,6 @@ export function getTimeago() { [s__('Timeago|1 year ago'), s__('Timeago|in 1 year')], [s__('Timeago|%s years ago'), s__('Timeago|in %s years')], ][index]; - }; timeago.register(timeagoLanguageCode, locale); timeago.register(`${timeagoLanguageCode}-remaining`, localeRemaining); @@ -119,7 +121,7 @@ export function getTimeago() { } return timeagoInstance; -} +}; /** * For the given element, renders a timeago instance. @@ -184,7 +186,7 @@ export const getDayDifference = (a, b) => { * @param {Number} seconds * @return {String} */ -export function timeIntervalInWords(intervalInSeconds) { +export const timeIntervalInWords = intervalInSeconds => { const secondsInteger = parseInt(intervalInSeconds, 10); const minutes = Math.floor(secondsInteger / 60); const seconds = secondsInteger - minutes * 60; @@ -196,9 +198,9 @@ export function timeIntervalInWords(intervalInSeconds) { text = `${seconds} ${pluralize('second', seconds)}`; } return text; -} +}; -export function dateInWords(date, abbreviated = false, hideYear = false) { +export const dateInWords = (date, abbreviated = false, hideYear = false) => { if (!date) return date; const month = date.getMonth(); @@ -240,7 +242,7 @@ export function dateInWords(date, abbreviated = false, hideYear = false) { } return `${monthName} ${date.getDate()}, ${year}`; -} +}; /** * Returns month name based on provided date. @@ -391,3 +393,95 @@ export const formatTime = milliseconds => { formattedTime += remainingSeconds; return formattedTime; }; + +/** + * Formats dates in Pickaday + * @param {String} dateString Date in yyyy-mm-dd format + * @return {Date} UTC format + */ +export const parsePikadayDate = dateString => { + const parts = dateString.split('-'); + const year = parseInt(parts[0], 10); + const month = parseInt(parts[1] - 1, 10); + const day = parseInt(parts[2], 10); + + return new Date(year, month, day); +}; + +/** + * Used `onSelect` method in pickaday + * @param {Date} date UTC format + * @return {String} Date formated in yyyy-mm-dd + */ +export const pikadayToString = date => { + const day = pad(date.getDate()); + const month = pad(date.getMonth() + 1); + const year = date.getFullYear(); + + return `${year}-${month}-${day}`; +}; + +/** + * Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # } + * Seconds can be negative or positive, zero or non-zero. Can be configured for any day + * or week length. + */ +export const parseSeconds = (seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) => { + const DAYS_PER_WEEK = daysPerWeek; + const HOURS_PER_DAY = hoursPerDay; + const MINUTES_PER_HOUR = 60; + const MINUTES_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR; + const MINUTES_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR; + + const timePeriodConstraints = { + weeks: MINUTES_PER_WEEK, + days: MINUTES_PER_DAY, + hours: MINUTES_PER_HOUR, + minutes: 1, + }; + + let unorderedMinutes = Math.abs(seconds / MINUTES_PER_HOUR); + + return _.mapObject(timePeriodConstraints, minutesPerPeriod => { + const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod); + + unorderedMinutes -= periodCount * minutesPerPeriod; + + return periodCount; + }); +}; + +/** + * Accepts a timeObject (see parseSeconds) and returns a condensed string representation of it + * (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included. + */ +export const stringifyTime = timeObject => { + const reducedTime = _.reduce( + timeObject, + (memo, unitValue, unitName) => { + const isNonZero = !!unitValue; + return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo; + }, + '', + ).trim(); + return reducedTime.length ? reducedTime : '0m'; +}; + +/** + * Accepts a time string of any size (e.g. '1w 2d 3h 5m' or '1w 2d') and returns + * the first non-zero unit/value pair. + */ +export const abbreviateTime = timeStr => + timeStr.split(' ').filter(unitStr => unitStr.charAt(0) !== '0')[0]; + +/** + * Calculates the milliseconds between now and a given date string. + * The result cannot become negative. + * + * @param endDate date string that the time difference is calculated for + * @return {number} number of milliseconds remaining until the given date + */ +export const calculateRemainingMilliseconds = endDate => { + const remainingMilliseconds = new Date(endDate).getTime() - Date.now(); + return Math.max(remainingMilliseconds, 0); +}; diff --git a/app/assets/javascripts/lib/utils/pretty_time.js b/app/assets/javascripts/lib/utils/pretty_time.js deleted file mode 100644 index d92b8a7179f..00000000000 --- a/app/assets/javascripts/lib/utils/pretty_time.js +++ /dev/null @@ -1,63 +0,0 @@ -import _ from 'underscore'; - -/* - * TODO: Make these methods more configurable (e.g. stringifyTime condensed or - * non-condensed, abbreviateTimelengths) - * */ - -/* - * Accepts seconds and returns a timeObject { weeks: #, days: #, hours: #, minutes: # } - * Seconds can be negative or positive, zero or non-zero. Can be configured for any day - * or week length. -*/ - -export function parseSeconds(seconds, { daysPerWeek = 5, hoursPerDay = 8 } = {}) { - const DAYS_PER_WEEK = daysPerWeek; - const HOURS_PER_DAY = hoursPerDay; - const MINUTES_PER_HOUR = 60; - const MINUTES_PER_WEEK = DAYS_PER_WEEK * HOURS_PER_DAY * MINUTES_PER_HOUR; - const MINUTES_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR; - - const timePeriodConstraints = { - weeks: MINUTES_PER_WEEK, - days: MINUTES_PER_DAY, - hours: MINUTES_PER_HOUR, - minutes: 1, - }; - - let unorderedMinutes = Math.abs(seconds / MINUTES_PER_HOUR); - - return _.mapObject(timePeriodConstraints, minutesPerPeriod => { - const periodCount = Math.floor(unorderedMinutes / minutesPerPeriod); - - unorderedMinutes -= periodCount * minutesPerPeriod; - - return periodCount; - }); -} - -/* -* Accepts a timeObject (see parseSeconds) and returns a condensed string representation of it -* (e.g. '1w 2d 3h 1m' or '1h 30m'). Zero value units are not included. -*/ - -export function stringifyTime(timeObject) { - const reducedTime = _.reduce( - timeObject, - (memo, unitValue, unitName) => { - const isNonZero = !!unitValue; - return isNonZero ? `${memo} ${unitValue}${unitName.charAt(0)}` : memo; - }, - '', - ).trim(); - return reducedTime.length ? reducedTime : '0m'; -} - -/* -* Accepts a time string of any size (e.g. '1w 2d 3h 5m' or '1w 2d') and returns -* the first non-zero unit/value pair. -*/ - -export function abbreviateTime(timeStr) { - return timeStr.split(' ').filter(unitStr => unitStr.charAt(0) !== '0')[0]; -} diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js index d58fd63bb33..4db63c841a9 100644 --- a/app/assets/javascripts/line_highlighter.js +++ b/app/assets/javascripts/line_highlighter.js @@ -70,7 +70,7 @@ LineHighlighter.prototype.highlightHash = function(newHash) { const scrollOptions = { // Scroll to the first highlighted line on initial load // Offset -50 for the sticky top bar, and another -100 for some context - offset: -150 + offset: -150, }; if (this.options.scrollFileHolder) { $(this.options.fileHolderSelector).scrollTo(lineSelector, scrollOptions); @@ -85,7 +85,9 @@ LineHighlighter.prototype.clickHandler = function(event) { var current, lineNumber, range; event.preventDefault(); this.clearHighlight(); - lineNumber = $(event.target).closest('a').data('lineNumber'); + lineNumber = $(event.target) + .closest('a') + .data('lineNumber'); current = this.hashToRange(this._hash); if (!(current[0] && event.shiftKey)) { // If there's no current selection, or there is but Shift wasn't held, @@ -104,7 +106,7 @@ LineHighlighter.prototype.clickHandler = function(event) { }; LineHighlighter.prototype.clearHighlight = function() { - return $("." + this.highlightLineClass).removeClass(this.highlightLineClass); + return $('.' + this.highlightLineClass).removeClass(this.highlightLineClass); }; // Convert a URL hash String into line numbers @@ -135,7 +137,7 @@ LineHighlighter.prototype.hashToRange = function(hash) { // // lineNumber - Line number to highlight LineHighlighter.prototype.highlightLine = function(lineNumber) { - return $("#LC" + lineNumber).addClass(this.highlightLineClass); + return $('#LC' + lineNumber).addClass(this.highlightLineClass); }; // Highlight all lines within a range @@ -160,9 +162,9 @@ LineHighlighter.prototype.highlightRange = function(range) { LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) { var hash; if (lastLineNumber) { - hash = "#L" + firstLineNumber + "-" + lastLineNumber; + hash = '#L' + firstLineNumber + '-' + lastLineNumber; } else { - hash = "#L" + firstLineNumber; + hash = '#L' + firstLineNumber; } this._hash = hash; return this.__setLocationHash__(hash); @@ -172,11 +174,15 @@ LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) { // // This method is stubbed in tests. LineHighlighter.prototype.__setLocationHash__ = function(value) { - return window.history.pushState({ - url: value - // We're using pushState instead of assigning location.hash directly to - // prevent the page from scrolling on the hashchange event - }, document.title, value); + return window.history.pushState( + { + url: value, + // We're using pushState instead of assigning location.hash directly to + // prevent the page from scrolling on the hashchange event + }, + document.title, + value, + ); }; export default LineHighlighter; diff --git a/app/assets/javascripts/locale/sprintf.js b/app/assets/javascripts/locale/sprintf.js index 599104dcfa0..5246c49842e 100644 --- a/app/assets/javascripts/locale/sprintf.js +++ b/app/assets/javascripts/locale/sprintf.js @@ -15,7 +15,7 @@ export default (input, parameters, escapeParameters = true) => { let output = input; if (parameters) { - Object.keys(parameters).forEach((parameterName) => { + Object.keys(parameters).forEach(parameterName => { const parameterValue = parameters[parameterName]; const escapedParameterValue = escapeParameters ? _.escape(parameterValue) : parameterValue; output = output.replace(new RegExp(`%{${parameterName}}`, 'g'), escapedParameterValue); diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js index d27922a2099..0beedcacf33 100644 --- a/app/assets/javascripts/member_expiration_date.js +++ b/app/assets/javascripts/member_expiration_date.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import Pikaday from 'pikaday'; -import { parsePikadayDate, pikadayToString } from './lib/utils/datefix'; +import { parsePikadayDate, pikadayToString } from './lib/utils/datetime_utility'; // Add datepickers to all `js-access-expiration-date` elements. If those elements are // children of an element with the `clearable-input` class, and have a sibling @@ -9,7 +9,9 @@ import { parsePikadayDate, pikadayToString } from './lib/utils/datefix'; // export default function memberExpirationDate(selector = '.js-access-expiration-date') { function toggleClearInput() { - $(this).closest('.clearable-input').toggleClass('has-value', $(this).val() !== ''); + $(this) + .closest('.clearable-input') + .toggleClass('has-value', $(this).val() !== ''); } const inputs = $(selector); @@ -40,7 +42,9 @@ export default function memberExpirationDate(selector = '.js-access-expiration-d inputs.next('.js-clear-input').on('click', function clicked(event) { event.preventDefault(); - const input = $(this).closest('.clearable-input').find(selector); + const input = $(this) + .closest('.clearable-input') + .find(selector); const calendar = input.data('pikaday'); calendar.setDate(null); diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index 9b6d7d1772f..0deae478deb 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -16,26 +16,29 @@ function MergeRequest(opts) { this.opts = opts != null ? opts : {}; this.submitNoteForm = this.submitNoteForm.bind(this); this.$el = $('.merge-request'); - this.$('.show-all-commits').on('click', (function(_this) { - return function() { - return _this.showAllCommits(); - }; - })(this)); + this.$('.show-all-commits').on( + 'click', + (function(_this) { + return function() { + return _this.showAllCommits(); + }; + })(this), + ); this.initTabs(); this.initMRBtnListeners(); this.initCommitMessageListeners(); this.closeReopenReportToggle = IssuablesHelper.initCloseReopenReport(); - if ($("a.btn-close").length) { + if ($('a.btn-close').length) { this.taskList = new TaskList({ dataType: 'merge_request', fieldName: 'description', selector: '.detail-page-description', - onSuccess: (result) => { + onSuccess: result => { document.querySelector('#task_status').innerText = result.task_status; document.querySelector('#task_status_short').innerText = result.task_status_short; - } + }, }); } } @@ -84,7 +87,7 @@ MergeRequest.prototype.initMRBtnListeners = function() { MergeRequest.prototype.submitNoteForm = function(form, $button) { var noteText; - noteText = form.find("textarea.js-note-text").val(); + noteText = form.find('textarea.js-note-text').val(); if (noteText.trim().length > 0) { form.submit(); $button.data('submitted', true); @@ -122,7 +125,7 @@ MergeRequest.setStatusBoxToMerged = function() { MergeRequest.decreaseCounter = function(by = 1) { const $el = $('.js-merge-counter'); - const count = Math.max((parseInt($el.text().replace(/[^\d]/, ''), 10) - by), 0); + const count = Math.max(parseInt($el.text().replace(/[^\d]/, ''), 10) - by, 0); $el.text(addDelimiter(count)); }; diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 03f3bb42193..2950c2299ab 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -419,7 +419,7 @@ export default class MergeRequestTabs { if (this.diffViewType() === 'parallel' || removeLimited) { $wrapper.removeClass('container-limited'); } else { - $wrapper.addClass('container-limited'); + $wrapper.toggleClass('container-limited', this.fixedLayoutPref); } } diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js index 6da04020881..f211632cf24 100644 --- a/app/assets/javascripts/milestone.js +++ b/app/assets/javascripts/milestone.js @@ -15,7 +15,7 @@ export default class Milestone { } bindTabsSwitching() { - return $('a[data-toggle="tab"]').on('show.bs.tab', (e) => { + return $('a[data-toggle="tab"]').on('show.bs.tab', e => { const $target = $(e.target); window.location.hash = $target.attr('href'); @@ -36,7 +36,8 @@ export default class Milestone { const tabElId = $target.attr('href'); if (endpoint && !$target.hasClass('is-loaded')) { - axios.get(endpoint) + axios + .get(endpoint) .then(({ data }) => { $(tabElId).html(data.html); $target.addClass('is-loaded'); @@ -46,23 +47,28 @@ export default class Milestone { } static initDeprecationMessage() { - const deprecationMesssageContainer = document.querySelector('.js-milestone-deprecation-message'); + const deprecationMesssageContainer = document.querySelector( + '.js-milestone-deprecation-message', + ); if (!deprecationMesssageContainer) return; - const deprecationMessage = deprecationMesssageContainer.querySelector('.js-milestone-deprecation-message-template').innerHTML; + const deprecationMessage = deprecationMesssageContainer.querySelector( + '.js-milestone-deprecation-message-template', + ).innerHTML; const $popover = $('.js-popover-link', deprecationMesssageContainer); const hideOnScroll = togglePopover.bind($popover, false); - $popover.popover({ - content: deprecationMessage, - html: true, - placement: 'bottom', - }) - .on('mouseenter', mouseenter) - .on('mouseleave', debouncedMouseleave()) - .on('show.bs.popover', () => { - window.addEventListener('scroll', hideOnScroll, { once: true }); - }); + $popover + .popover({ + content: deprecationMessage, + html: true, + placement: 'bottom', + }) + .on('mouseenter', mouseenter) + .on('mouseleave', debouncedMouseleave()) + .on('show.bs.popover', () => { + window.addEventListener('scroll', hideOnScroll, { once: true }); + }); } } diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js b/app/assets/javascripts/mini_pipeline_graph_dropdown.js index f8257b6abab..81ab9d8be4b 100644 --- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js +++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js @@ -46,7 +46,7 @@ export default class MiniPipelineGraph { $(document).on( 'click', `${this.container} .js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item`, - (e) => { + e => { e.stopPropagation(); }, ); @@ -82,7 +82,8 @@ export default class MiniPipelineGraph { this.renderBuildsList(button, ''); this.toggleLoading(button); - axios.get(endpoint) + axios + .get(endpoint) .then(({ data }) => { this.toggleLoading(button); this.renderBuildsList(button, data.html); @@ -90,7 +91,11 @@ export default class MiniPipelineGraph { }) .catch(() => { this.toggleLoading(button); - if ($(button).parent().hasClass('open')) { + if ( + $(button) + .parent() + .hasClass('open') + ) { $(button).dropdown('toggle'); } flash('An error occurred while fetching the builds.', 'alert'); @@ -104,8 +109,8 @@ export default class MiniPipelineGraph { * @return {type} */ toggleLoading(stageContainer) { - stageContainer.parentElement.querySelector( - `${this.dropdownListSelector} .js-builds-dropdown-loading`, - ).classList.toggle('hidden'); + stageContainer.parentElement + .querySelector(`${this.dropdownListSelector} .js-builds-dropdown-loading`) + .classList.toggle('hidden'); } } diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue index ed5c8b15945..5c6e2e09e46 100644 --- a/app/assets/javascripts/monitoring/components/graph.vue +++ b/app/assets/javascripts/monitoring/components/graph.vue @@ -121,6 +121,7 @@ export default { draw() { const breakpointSize = bp.getBreakpointSize(); const query = this.graphData.queries[0]; + const svgWidth = this.$refs.baseSvg.getBoundingClientRect().width; this.margin = measurements.large.margin; if (this.smallGraph || breakpointSize === 'xs' || breakpointSize === 'sm') { this.graphHeight = 300; @@ -130,13 +131,13 @@ export default { this.unitOfDisplay = query.unit || ''; this.yAxisLabel = this.graphData.y_label || 'Values'; this.legendTitle = query.label || 'Average'; - this.graphWidth = this.$refs.baseSvg.clientWidth - this.margin.left - this.margin.right; + this.graphWidth = svgWidth - this.margin.left - this.margin.right; this.graphHeight = this.graphHeight - this.margin.top - this.margin.bottom; this.baseGraphHeight = this.graphHeight - 50; this.baseGraphWidth = this.graphWidth; // pixel offsets inside the svg and outside are not 1:1 - this.realPixelRatio = this.$refs.baseSvg.clientWidth / this.baseGraphWidth; + this.realPixelRatio = svgWidth / this.baseGraphWidth; this.renderAxesPaths(); this.formatDeployments(); diff --git a/app/assets/javascripts/mr_notes/index.js b/app/assets/javascripts/mr_notes/index.js index 8aabb840847..1c98683c597 100644 --- a/app/assets/javascripts/mr_notes/index.js +++ b/app/assets/javascripts/mr_notes/index.js @@ -4,6 +4,7 @@ import { mapActions, mapState, mapGetters } from 'vuex'; import initDiffsApp from '../diffs'; import notesApp from '../notes/components/notes_app.vue'; import discussionCounter from '../notes/components/discussion_counter.vue'; +import initDiscussionFilters from '../notes/discussion_filters'; import store from './stores'; import MergeRequest from '../merge_request'; @@ -88,5 +89,6 @@ export default function initMrNotes() { }, }); + initDiscussionFilters(store); initDiffsApp(store); } diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js index ec4c0910e92..cba6759ebf5 100644 --- a/app/assets/javascripts/namespace_select.js +++ b/app/assets/javascripts/namespace_select.js @@ -14,14 +14,14 @@ export default class NamespaceSelect { selectable: true, filterRemote: true, search: { - fields: ['path'] + fields: ['path'], }, fieldName: fieldName, toggleLabel: function(selected) { if (selected.id == null) { return selected.text; } else { - return selected.kind + ": " + selected.full_path; + return selected.kind + ': ' + selected.full_path; } }, data: function(term, dataCallback) { @@ -29,7 +29,7 @@ export default class NamespaceSelect { if (isFilter) { const anyNamespace = { text: 'Any namespace', - id: null + id: null, }; namespaces.unshift(anyNamespace); namespaces.splice(1, 0, 'divider'); @@ -41,7 +41,7 @@ export default class NamespaceSelect { if (namespace.id == null) { return namespace.text; } else { - return namespace.kind + ": " + namespace.full_path; + return namespace.kind + ': ' + namespace.full_path; } }, renderRow: this.renderRow, diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js index ad7136adb8c..d1fa9f5e2a2 100644 --- a/app/assets/javascripts/network/branch_graph.js +++ b/app/assets/javascripts/network/branch_graph.js @@ -20,7 +20,7 @@ export default (function() { this.mtime = 0; this.mspace = 0; this.parents = {}; - this.colors = ["#000"]; + this.colors = ['#000']; this.offsetX = 150; this.offsetY = 20; this.unitTime = 30; @@ -30,9 +30,10 @@ export default (function() { } BranchGraph.prototype.load = function() { - axios.get(this.options.url) + axios + .get(this.options.url) .then(({ data }) => { - $(".loading", this.element).hide(); + $('.loading', this.element).hide(); this.prepareData(data.days, data.commits); this.buildGraph(); }) @@ -71,17 +72,19 @@ export default (function() { c = ref[j]; this.mtime = Math.max(this.mtime, c.time); this.mspace = Math.max(this.mspace, c.space); - results.push((function() { - var l, len1, ref1, results1; - ref1 = c.parents; - results1 = []; - for (l = 0, len1 = ref1.length; l < len1; l += 1) { - p = ref1[l]; - this.parents[p[0]] = true; - results1.push(this.mspace = Math.max(this.mspace, p[1])); - } - return results1; - }).call(this)); + results.push( + function() { + var l, len1, ref1, results1; + ref1 = c.parents; + results1 = []; + for (l = 0, len1 = ref1.length; l < len1; l += 1) { + p = ref1[l]; + this.parents[p[0]] = true; + results1.push((this.mspace = Math.max(this.mspace, p[1]))); + } + return results1; + }.call(this), + ); } return results; }; @@ -91,11 +94,11 @@ export default (function() { k = 0; results = []; while (k < this.mspace) { - this.colors.push(Raphael.getColor(.8)); + this.colors.push(Raphael.getColor(0.8)); // Skipping a few colors in the spectrum to get more contrast between colors Raphael.getColor(); Raphael.getColor(); - results.push(k += 1); + results.push((k += 1)); } return results; }; @@ -104,12 +107,12 @@ export default (function() { var cuday, cumonth, day, j, len, mm, ref; const { r } = this; cuday = 0; - cumonth = ""; + cumonth = ''; r.rect(0, 0, 40, this.barHeight).attr({ - fill: "#222" + fill: '#222', }); r.rect(40, 0, 30, this.barHeight).attr({ - fill: "#444" + fill: '#444', }); ref = this.days; @@ -118,16 +121,16 @@ export default (function() { if (cuday !== day[0] || cumonth !== day[1]) { // Dates r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({ - font: "12px Monaco, monospace", - fill: "#BBB" + font: '12px Monaco, monospace', + fill: '#BBB', }); [cuday] = day; } if (cumonth !== day[1]) { // Months r.text(20, this.offsetY + this.unitTime * mm, day[1]).attr({ - font: "12px Monaco, monospace", - fill: "#EEE" + font: '12px Monaco, monospace', + fill: '#EEE', }); // eslint-disable-next-line prefer-destructuring @@ -173,11 +176,13 @@ export default (function() { BranchGraph.prototype.bindEvents = function() { const { element } = this; - return $(element).scroll((function(_this) { - return function(event) { - return _this.renderPartialGraph(); - }; - })(this)); + return $(element).scroll( + (function(_this) { + return function(event) { + return _this.renderPartialGraph(); + }; + })(this), + ); }; BranchGraph.prototype.scrollDown = function() { @@ -219,46 +224,53 @@ export default (function() { shortrefs = commit.refs; // Truncate if longer than 15 chars if (shortrefs.length > 17) { - shortrefs = shortrefs.substr(0, 15) + "…"; + shortrefs = shortrefs.substr(0, 15) + '…'; } text = r.text(x + 4, y, shortrefs).attr({ - "text-anchor": "start", - font: "10px Monaco, monospace", - fill: "#FFF", - title: commit.refs + 'text-anchor': 'start', + font: '10px Monaco, monospace', + fill: '#FFF', + title: commit.refs, }); textbox = text.getBBox(); // Create rectangle based on the size of the textbox rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({ - fill: "#000", - "fill-opacity": .5, - stroke: "none" + fill: '#000', + 'fill-opacity': 0.5, + stroke: 'none', }); - triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr({ - fill: "#000", - "fill-opacity": .5, - stroke: "none" + triangle = r.path(['M', x - 5, y, 'L', x - 15, y - 4, 'L', x - 15, y + 4, 'Z']).attr({ + fill: '#000', + 'fill-opacity': 0.5, + stroke: 'none', }); label = r.set(rect, text); - label.transform(["t", -rect.getBBox().width - 15, 0]); + label.transform(['t', -rect.getBBox().width - 15, 0]); // Set text to front return text.toFront(); }; BranchGraph.prototype.appendAnchor = function(x, y, commit) { const { r, top, options } = this; - const anchor = r.circle(x, y, 10).attr({ - fill: "#000", - opacity: 0, - cursor: "pointer" - }).click(function() { - return window.open(options.commit_url.replace("%s", commit.id), "_blank"); - }).hover(function() { - this.tooltip = r.commitTooltip(x + 5, y, commit); - return top.push(this.tooltip.insertBefore(this)); - }, function() { - return this.tooltip && this.tooltip.remove() && delete this.tooltip; - }); + const anchor = r + .circle(x, y, 10) + .attr({ + fill: '#000', + opacity: 0, + cursor: 'pointer', + }) + .click(function() { + return window.open(options.commit_url.replace('%s', commit.id), '_blank'); + }) + .hover( + function() { + this.tooltip = r.commitTooltip(x + 5, y, commit); + return top.push(this.tooltip.insertBefore(this)); + }, + function() { + return this.tooltip && this.tooltip.remove() && delete this.tooltip; + }, + ); return top.push(anchor); }; @@ -266,7 +278,7 @@ export default (function() { const { r } = this; r.circle(x, y, 3).attr({ fill: this.colors[commit.space], - stroke: "none" + stroke: 'none', }); const avatar_box_x = this.offsetX + this.unitSpace * this.mspace + 10; @@ -274,13 +286,15 @@ export default (function() { r.rect(avatar_box_x, avatar_box_y, 20, 20).attr({ stroke: this.colors[commit.space], - "stroke-width": 2 + 'stroke-width': 2, }); r.image(commit.author.icon, avatar_box_x, avatar_box_y, 20, 20); - return r.text(this.offsetX + this.unitSpace * this.mspace + 35, y, commit.message.split("\n")[0]).attr({ - "text-anchor": "start", - font: "14px Monaco, monospace" - }); + return r + .text(this.offsetX + this.unitSpace * this.mspace + 35, y, commit.message.split('\n')[0]) + .attr({ + 'text-anchor': 'start', + font: '14px Monaco, monospace', + }); }; BranchGraph.prototype.drawLines = function(x, y, commit) { @@ -304,30 +318,32 @@ export default (function() { // Build line shape if (parent[1] === commit.space) { offset = [0, 5]; - arrow = "l-2,5,4,0,-2,-5,0,5"; + arrow = 'l-2,5,4,0,-2,-5,0,5'; } else if (parent[1] < commit.space) { offset = [3, 3]; - arrow = "l5,0,-2,4,-3,-4,4,2"; + arrow = 'l5,0,-2,4,-3,-4,4,2'; } else { offset = [-3, 3]; - arrow = "l-5,0,2,4,3,-4,-4,2"; + arrow = 'l-5,0,2,4,3,-4,-4,2'; } // Start point - route = ["M", x + offset[0], y + offset[1]]; + route = ['M', x + offset[0], y + offset[1]]; // Add arrow if not first parent if (i > 0) { route.push(arrow); } // Circumvent if overlap if (commit.space !== parentCommit.space || commit.space !== parent[1]) { - route.push("L", parentX2, y + 10, "L", parentX2, parentY - 5); + route.push('L', parentX2, y + 10, 'L', parentX2, parentY - 5); } // End point - route.push("L", parentX1, parentY); - results.push(r.path(route).attr({ - stroke: color, - "stroke-width": 2 - })); + route.push('L', parentX1, parentY); + results.push( + r.path(route).attr({ + stroke: color, + 'stroke-width': 2, + }), + ); } return results; }; @@ -337,10 +353,10 @@ export default (function() { const { r } = this; const x = this.offsetX + this.unitSpace * (this.mspace - commit.space); const y = this.offsetY + this.unitTime * commit.time; - r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr({ - fill: "#000", - "fill-opacity": .5, - stroke: "none" + r.path(['M', x + 5, y, 'L', x + 15, y + 4, 'L', x + 15, y - 4, 'Z']).attr({ + fill: '#000', + 'fill-opacity': 0.5, + stroke: 'none', }); // Displayed in the center return this.element.scrollTop(y - this.graphHeight / 2); diff --git a/app/assets/javascripts/network/raphael.js b/app/assets/javascripts/network/raphael.js index 09dcf716148..22e06a35d91 100644 --- a/app/assets/javascripts/network/raphael.js +++ b/app/assets/javascripts/network/raphael.js @@ -49,7 +49,7 @@ Raphael.prototype.textWrap = function testWrap(t, width) { const s = []; for (let j = 0, len = words.length; j < len; j += 1) { const word = words[j]; - if (x + (word.length * letterWidth) > width) { + if (x + word.length * letterWidth > width) { s.push('\n'); x = 0; } diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js index 8f6ea9e61c1..f338dbbb0a6 100644 --- a/app/assets/javascripts/new_branch_form.js +++ b/app/assets/javascripts/new_branch_form.js @@ -30,24 +30,24 @@ export default class NewBranchForm { startsWith = { pattern: /^(\/|\.)/g, prefix: "can't start with", - conjunction: "or" + conjunction: 'or', }; endsWith = { pattern: /(\/|\.|\.lock)$/g, prefix: "can't end in", - conjunction: "or" + conjunction: 'or', }; invalid = { pattern: /(\s|~|\^|:|\?|\*|\[|\\|\.\.|@\{|\/{2,}){1}/g, prefix: "can't contain", - conjunction: ", " + conjunction: ', ', }; single = { pattern: /^@+$/g, prefix: "can't be", - conjunction: "or" + conjunction: 'or', }; - return this.restrictions = [startsWith, invalid, endsWith, single]; + return (this.restrictions = [startsWith, invalid, endsWith, single]); } validate() { @@ -73,7 +73,7 @@ export default class NewBranchForm { return "'" + value + "'"; } }); - return restriction.prefix + " " + (formatted.join(restriction.conjunction)); + return restriction.prefix + ' ' + formatted.join(restriction.conjunction); }; validator = (function(_this) { return function(errors, restriction) { @@ -88,7 +88,7 @@ export default class NewBranchForm { })(this); errors = this.restrictions.reduce(validator, []); if (errors.length > 0) { - errorMessage = $("<span/>").text(errors.join(', ')); + errorMessage = $('<span/>').text(errors.join(', ')); return this.branchNameError.append(errorMessage); } } diff --git a/app/assets/javascripts/new_commit_form.js b/app/assets/javascripts/new_commit_form.js index 17ec20f1cc1..b142f212eb0 100644 --- a/app/assets/javascripts/new_commit_form.js +++ b/app/assets/javascripts/new_commit_form.js @@ -6,9 +6,7 @@ export default class NewCommitForm { this.branchName = form.find('.js-branch-name'); this.originalBranch = form.find('.js-original-branch'); this.createMergeRequest = form.find('.js-create-merge-request'); - this.createMergeRequestContainer = form.find( - '.js-create-merge-request-container', - ); + this.createMergeRequestContainer = form.find('.js-create-merge-request-container'); this.branchName.keyup(this.renderDestination); this.renderDestination(); } diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue index ad6e7cf501d..1f80f24e045 100644 --- a/app/assets/javascripts/notes/components/discussion_counter.vue +++ b/app/assets/javascripts/notes/components/discussion_counter.vue @@ -56,10 +56,11 @@ export default { </script> <template> - <div class="line-resolve-all-container prepend-top-10"> + <div + v-if="discussionCount > 0" + class="line-resolve-all-container prepend-top-8"> <div> <div - v-if="discussionCount > 0" :class="{ 'has-next-btn': hasNextButton }" class="line-resolve-all"> <span diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue new file mode 100644 index 00000000000..27972682ca1 --- /dev/null +++ b/app/assets/javascripts/notes/components/discussion_filter.vue @@ -0,0 +1,82 @@ +<script> +import $ from 'jquery'; +import Icon from '~/vue_shared/components/icon.vue'; +import { mapGetters, mapActions } from 'vuex'; + +export default { + components: { + Icon, + }, + props: { + filters: { + type: Array, + required: true, + }, + defaultValue: { + type: Number, + default: null, + required: false, + }, + }, + data() { + return { currentValue: this.defaultValue }; + }, + computed: { + ...mapGetters([ + 'getNotesDataByProp', + ]), + currentFilter() { + if (!this.currentValue) return this.filters[0]; + return this.filters.find(filter => filter.value === this.currentValue); + }, + }, + methods: { + ...mapActions(['filterDiscussion']), + selectFilter(value) { + const filter = parseInt(value, 10); + + // close dropdown + $(this.$refs.dropdownToggle).dropdown('toggle'); + + if (filter === this.currentValue) return; + this.currentValue = filter; + this.filterDiscussion({ path: this.getNotesDataByProp('discussionsPath'), filter }); + }, + }, +}; +</script> + +<template> + <div class="discussion-filter-container d-inline-block align-bottom"> + <button + id="discussion-filter-dropdown" + ref="dropdownToggle" + class="btn btn-default" + data-toggle="dropdown" + aria-expanded="false" + > + {{ currentFilter.title }} + <icon name="chevron-down" /> + </button> + <div + class="dropdown-menu dropdown-menu-selectable dropdown-menu-right" + aria-labelledby="discussion-filter-dropdown"> + <div class="dropdown-content"> + <ul> + <li + v-for="filter in filters" + :key="filter.value" + > + <button + :class="{ 'is-active': filter.value === currentValue }" + type="button" + @click="selectFilter(filter.value)" + > + {{ filter.title }} + </button> + </li> + </ul> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index 618a1581d8f..b0faa443a18 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -50,11 +50,11 @@ export default { }, data() { return { - isLoading: true, + currentFilter: null, }; }, computed: { - ...mapGetters(['isNotesFetched', 'discussions', 'getNotesDataByProp', 'discussionCount']), + ...mapGetters(['isNotesFetched', 'discussions', 'getNotesDataByProp', 'discussionCount', 'isLoading']), noteableType() { return this.noteableData.noteableType; }, @@ -102,6 +102,7 @@ export default { }, methods: { ...mapActions({ + setLoadingState: 'setLoadingState', fetchDiscussions: 'fetchDiscussions', poll: 'poll', actionToggleAward: 'toggleAward', @@ -133,19 +134,19 @@ export default { return discussion.individual_note ? { note: discussion.notes[0] } : { discussion }; }, fetchNotes() { - return this.fetchDiscussions(this.getNotesDataByProp('discussionsPath')) + return this.fetchDiscussions({ path: this.getNotesDataByProp('discussionsPath') }) .then(() => { this.initPolling(); }) .then(() => { - this.isLoading = false; + this.setLoadingState(false); this.setNotesFetchedState(true); eventHub.$emit('fetchedNotesData'); }) .then(() => this.$nextTick()) .then(() => this.checkLocationHash()) .catch(() => { - this.isLoading = false; + this.setLoadingState(false); this.setNotesFetchedState(true); Flash('Something went wrong while fetching comments. Please try again.'); }); diff --git a/app/assets/javascripts/notes/discussion_filters.js b/app/assets/javascripts/notes/discussion_filters.js new file mode 100644 index 00000000000..012ffc4093e --- /dev/null +++ b/app/assets/javascripts/notes/discussion_filters.js @@ -0,0 +1,33 @@ +import Vue from 'vue'; +import DiscussionFilter from './components/discussion_filter.vue'; + +export default (store) => { + const discussionFilterEl = document.getElementById('js-vue-discussion-filter'); + + if (discussionFilterEl) { + const { defaultFilter, notesFilters } = discussionFilterEl.dataset; + const defaultValue = defaultFilter ? parseInt(defaultFilter, 10) : null; + const filterValues = notesFilters ? JSON.parse(notesFilters) : {}; + const filters = Object.keys(filterValues).map(entry => + ({ title: entry, value: filterValues[entry] })); + + return new Vue({ + el: discussionFilterEl, + name: 'DiscussionFilter', + components: { + DiscussionFilter, + }, + store, + render(createElement) { + return createElement('discussion-filter', { + props: { + filters, + defaultValue, + }, + }); + }, + }); + } + + return null; +}; diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js index 3aef30c608c..2f715c85fa6 100644 --- a/app/assets/javascripts/notes/index.js +++ b/app/assets/javascripts/notes/index.js @@ -1,10 +1,13 @@ import Vue from 'vue'; import notesApp from './components/notes_app.vue'; +import initDiscussionFilters from './discussion_filters'; import createStore from './stores'; document.addEventListener('DOMContentLoaded', () => { const store = createStore(); + initDiscussionFilters(store); + return new Vue({ el: '#js-vue-notes', components: { diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js index f5dce94caad..47a6f07cce2 100644 --- a/app/assets/javascripts/notes/services/notes_service.js +++ b/app/assets/javascripts/notes/services/notes_service.js @@ -5,8 +5,9 @@ import * as constants from '../constants'; Vue.use(VueResource); export default { - fetchDiscussions(endpoint) { - return Vue.http.get(endpoint); + fetchDiscussions(endpoint, filter) { + const config = filter !== undefined ? { params: { notes_filter: filter } } : null; + return Vue.http.get(endpoint, config); }, deleteNote(endpoint) { return Vue.http.delete(endpoint); diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 7ab7e5a9abb..b5dd49bc6c9 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -11,6 +11,7 @@ import loadAwardsHandler from '../../awards_handler'; import sidebarTimeTrackingEventHub from '../../sidebar/event_hub'; import { isInViewport, scrollToElement } from '../../lib/utils/common_utils'; import mrWidgetEventHub from '../../vue_merge_request_widget/event_hub'; +import { __ } from '~/locale'; let eTagPoll; @@ -36,9 +37,9 @@ export const setNotesFetchedState = ({ commit }, state) => export const toggleDiscussion = ({ commit }, data) => commit(types.TOGGLE_DISCUSSION, data); -export const fetchDiscussions = ({ commit }, path) => +export const fetchDiscussions = ({ commit }, { path, filter }) => service - .fetchDiscussions(path) + .fetchDiscussions(path, filter) .then(res => res.json()) .then(discussions => { commit(types.SET_INITIAL_DISCUSSIONS, discussions); @@ -251,7 +252,7 @@ const pollSuccessCallBack = (resp, commit, state, getters, dispatch) => { if (discussion) { commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note); } else if (note.type === constants.DIFF_NOTE) { - dispatch('fetchDiscussions', state.notesData.discussionsPath); + dispatch('fetchDiscussions', { path: state.notesData.discussionsPath }); } else { commit(types.ADD_NEW_NOTE, note); } @@ -345,5 +346,23 @@ export const updateMergeRequestWidget = () => { mrWidgetEventHub.$emit('mr.discussion.updated'); }; +export const setLoadingState = ({ commit }, data) => { + commit(types.SET_NOTES_LOADING_STATE, data); +}; + +export const filterDiscussion = ({ dispatch }, { path, filter }) => { + dispatch('setLoadingState', true); + dispatch('fetchDiscussions', { path, filter }) + .then(() => { + dispatch('setLoadingState', false); + dispatch('setNotesFetchedState', true); + }) + .catch(() => { + dispatch('setLoadingState', false); + dispatch('setNotesFetchedState', true); + Flash(__('Something went wrong while fetching comments. Please try again.')); + }); +}; + // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index a829149a17e..e4f36154fcd 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -1,6 +1,5 @@ import _ from 'underscore'; import * as constants from '../constants'; -import { reduceDiscussionsToLineCodes } from './utils'; import { collapseSystemNotes } from './collapse_utils'; export const discussions = state => collapseSystemNotes(state.discussions); @@ -11,6 +10,8 @@ export const getNotesData = state => state.notesData; export const isNotesFetched = state => state.isNotesFetched; +export const isLoading = state => state.isLoading; + export const getNotesDataByProp = state => prop => state.notesData[prop]; export const getNoteableData = state => state.noteableData; @@ -29,9 +30,6 @@ export const notesById = state => return acc; }, {}); -export const discussionsStructuredByLineCode = state => - reduceDiscussionsToLineCodes(state.discussions); - export const noteableType = state => { const { ISSUE_NOTEABLE_TYPE, MERGE_REQUEST_NOTEABLE_TYPE, EPIC_NOTEABLE_TYPE } = constants; diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js index 61dbb075586..400142668ea 100644 --- a/app/assets/javascripts/notes/stores/modules/index.js +++ b/app/assets/javascripts/notes/stores/modules/index.js @@ -11,6 +11,7 @@ export default () => ({ // View layer isToggleStateButtonLoading: false, isNotesFetched: false, + isLoading: true, // holds endpoints and permissions provided through haml notesData: { diff --git a/app/assets/javascripts/notes/stores/mutation_types.js b/app/assets/javascripts/notes/stores/mutation_types.js index 6f374f78691..2fa53aef1d4 100644 --- a/app/assets/javascripts/notes/stores/mutation_types.js +++ b/app/assets/javascripts/notes/stores/mutation_types.js @@ -14,6 +14,7 @@ export const UPDATE_NOTE = 'UPDATE_NOTE'; export const UPDATE_DISCUSSION = 'UPDATE_DISCUSSION'; export const SET_DISCUSSION_DIFF_LINES = 'SET_DISCUSSION_DIFF_LINES'; export const SET_NOTES_FETCHED_STATE = 'SET_NOTES_FETCHED_STATE'; +export const SET_NOTES_LOADING_STATE = 'SET_NOTES_LOADING_STATE'; // DISCUSSION export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION'; diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index 73e55705f39..65085452139 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -216,6 +216,10 @@ export default { Object.assign(state, { isNotesFetched: value }); }, + [types.SET_NOTES_LOADING_STATE](state, value) { + state.isLoading = value; + }, + [types.SET_DISCUSSION_DIFF_LINES](state, { discussionId, diffLines }) { const discussion = utils.findNoteObjectById(state.discussions, discussionId); diff --git a/app/assets/javascripts/notes/stores/utils.js b/app/assets/javascripts/notes/stores/utils.js index 0e41ff03d67..dd57539e4d8 100644 --- a/app/assets/javascripts/notes/stores/utils.js +++ b/app/assets/javascripts/notes/stores/utils.js @@ -25,18 +25,6 @@ export const getQuickActionText = note => { return text; }; -export const reduceDiscussionsToLineCodes = selectedDiscussions => - selectedDiscussions.reduce((acc, note) => { - if (note.diff_discussion && note.line_code) { - // For context about line notes: there might be multiple notes with the same line code - const items = acc[note.line_code] || []; - items.push(note); - - Object.assign(acc, { [note.line_code]: items }); - } - return acc; - }, {}); - export const hasQuickActions = note => REGEX_QUICK_ACTIONS.test(note); export const stripQuickActions = note => note.replace(REGEX_QUICK_ACTIONS, '').trim(); diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js index 8ff8bb446ad..c4c8cf86cb0 100644 --- a/app/assets/javascripts/notifications_dropdown.js +++ b/app/assets/javascripts/notifications_dropdown.js @@ -18,7 +18,9 @@ export default function notificationsDropdown() { $(document).on('ajax:success', '.notification-form', (e, data) => { if (data.saved) { - $(e.currentTarget).closest('.js-notification-dropdown').replaceWith(data.html); + $(e.currentTarget) + .closest('.js-notification-dropdown') + .replaceWith(data.html); } else { Flash('Failed to save new settings', 'alert'); } diff --git a/app/assets/javascripts/notifications_form.js b/app/assets/javascripts/notifications_form.js index 00e27ca0e70..45f033f2822 100644 --- a/app/assets/javascripts/notifications_form.js +++ b/app/assets/javascripts/notifications_form.js @@ -22,7 +22,8 @@ export default class NotificationsForm { // eslint-disable-next-line class-methods-use-this showCheckboxLoadingSpinner($parent) { - $parent.addClass('is-loading') + $parent + .addClass('is-loading') .find('.custom-notification-event-loading') .removeClass('fa-check') .addClass('fa-spin fa-spinner') @@ -38,9 +39,12 @@ export default class NotificationsForm { .then(({ data }) => { $checkbox.enable(); if (data.saved) { - $parent.find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done'); + $parent + .find('.custom-notification-event-loading') + .toggleClass('fa-spin fa-spinner fa-check is-done'); setTimeout(() => { - $parent.removeClass('is-loading') + $parent + .removeClass('is-loading') .find('.custom-notification-event-loading') .toggleClass('fa-spin fa-spinner fa-check is-done'); }, 2000); diff --git a/app/assets/javascripts/pager.js b/app/assets/javascripts/pager.js index 86a43b66cc8..386a9b2c740 100644 --- a/app/assets/javascripts/pager.js +++ b/app/assets/javascripts/pager.js @@ -7,14 +7,21 @@ const ENDLESS_SCROLL_BOTTOM_PX = 400; const ENDLESS_SCROLL_FIRE_DELAY_MS = 1000; export default { - init(limit = 0, preload = false, disable = false, prepareData = $.noop, callback = $.noop) { + init( + limit = 0, + preload = false, + disable = false, + prepareData = $.noop, + callback = $.noop, + container = '', + ) { this.url = $('.content_list').data('href') || removeParams(['limit', 'offset']); this.limit = limit; this.offset = parseInt(getParameterByName('offset'), 10) || this.limit; this.disable = disable; this.prepareData = prepareData; this.callback = callback; - this.loading = $('.loading').first(); + this.loading = $(`${container} .loading`).first(); if (preload) { this.offset = 0; this.getOld(); @@ -24,22 +31,25 @@ export default { getOld() { this.loading.show(); - axios.get(this.url, { - params: { - limit: this.limit, - offset: this.offset, - }, - }).then(({ data }) => { - this.append(data.count, this.prepareData(data.html)); - this.callback(); + axios + .get(this.url, { + params: { + limit: this.limit, + offset: this.offset, + }, + }) + .then(({ data }) => { + this.append(data.count, this.prepareData(data.html)); + this.callback(); - // keep loading until we've filled the viewport height - if (!this.disable && !this.isScrollable()) { - this.getOld(); - } else { - this.loading.hide(); - } - }).catch(() => this.loading.hide()); + // keep loading until we've filled the viewport height + if (!this.disable && !this.isScrollable()) { + this.getOld(); + } else { + this.loading.hide(); + } + }) + .catch(() => this.loading.hide()); }, append(count, html) { diff --git a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js index 15e737fff05..d9cf62db3f7 100644 --- a/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js +++ b/app/assets/javascripts/pages/admin/abuse_reports/abuse_reports.js @@ -31,7 +31,7 @@ export default class AbuseReports { $messageCellElement.text(originalMessage); } else { $messageCellElement.data('messageTruncated', 'true'); - $messageCellElement.text(`${originalMessage.substr(0, (MAX_MESSAGE_LENGTH - 3))}...`); + $messageCellElement.text(`${originalMessage.substr(0, MAX_MESSAGE_LENGTH - 3)}...`); } } } diff --git a/app/assets/javascripts/pages/admin/admin.js b/app/assets/javascripts/pages/admin/admin.js index ff4d6ab15f9..4616a075729 100644 --- a/app/assets/javascripts/pages/admin/admin.js +++ b/app/assets/javascripts/pages/admin/admin.js @@ -23,7 +23,7 @@ export default function adminInit() { } }); - $('body').on('click', '.js-toggle-colors-link', (e) => { + $('body').on('click', '.js-toggle-colors-link', e => { e.preventDefault(); $('.js-toggle-colors-container').toggleClass('hide'); }); @@ -33,12 +33,15 @@ export default function adminInit() { $(this).tab('show'); }); - $('.log-bottom').on('click', (e) => { + $('.log-bottom').on('click', e => { e.preventDefault(); const $visibleLog = $('.file-content:visible'); - $visibleLog.animate({ - scrollTop: $visibleLog.find('ol').height(), - }, 'fast'); + $visibleLog.animate( + { + scrollTop: $visibleLog.find('ol').height(), + }, + 'fast', + ); }); $('.change-owner-link').on('click', function changeOwnerLinkClick(e) { @@ -47,7 +50,7 @@ export default function adminInit() { modal.show(); }); - $('.change-owner-cancel-link').on('click', (e) => { + $('.change-owner-cancel-link').on('click', e => { e.preventDefault(); modal.hide(); $('.change-owner-link').show(); diff --git a/app/assets/javascripts/pages/admin/application_settings/account_and_limits.js b/app/assets/javascripts/pages/admin/application_settings/account_and_limits.js index 7281f907ec7..455c637a6b3 100644 --- a/app/assets/javascripts/pages/admin/application_settings/account_and_limits.js +++ b/app/assets/javascripts/pages/admin/application_settings/account_and_limits.js @@ -1,10 +1,14 @@ import { __ } from '~/locale'; export const PLACEHOLDER_USER_EXTERNAL_DEFAULT_TRUE = __('Regex pattern'); -export const PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE = __('To define internal users, first enable new users set to external'); +export const PLACEHOLDER_USER_EXTERNAL_DEFAULT_FALSE = __( + 'To define internal users, first enable new users set to external', +); function setUserInternalRegexPlaceholder(checkbox) { - const userInternalRegex = document.getElementById('application_setting_user_default_internal_regex'); + const userInternalRegex = document.getElementById( + 'application_setting_user_default_internal_regex', + ); if (checkbox && userInternalRegex) { if (checkbox.checked) { userInternalRegex.readOnly = false; diff --git a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js index e7ceccb6f47..d5ded3f9a79 100644 --- a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js +++ b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js @@ -17,20 +17,24 @@ export default () => { const previewPath = $('textarea#broadcast_message_message').data('previewPath'); - $('textarea#broadcast_message_message').on('input', _.debounce(function onMessageInput() { - const message = $(this).val(); - if (message === '') { - $('.js-broadcast-message-preview').text('Your message here'); - } else { - axios.post(previewPath, { - broadcast_message: { - message, - }, - }) - .then(({ data }) => { - $('.js-broadcast-message-preview').html(data.message); - }) - .catch(() => flash(__('An error occurred while rendering preview broadcast message'))); - } - }, 250)); + $('textarea#broadcast_message_message').on( + 'input', + _.debounce(function onMessageInput() { + const message = $(this).val(); + if (message === '') { + $('.js-broadcast-message-preview').text('Your message here'); + } else { + axios + .post(previewPath, { + broadcast_message: { + message, + }, + }) + .then(({ data }) => { + $('.js-broadcast-message-preview').html(data.message); + }) + .catch(() => flash(__('An error occurred while rendering preview broadcast message'))); + } + }, 250), + ); }; diff --git a/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue index bc84666779e..e2fec3c7172 100644 --- a/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue +++ b/app/assets/javascripts/pages/admin/jobs/index/components/stop_jobs_modal.vue @@ -1,39 +1,42 @@ <script> - import axios from '~/lib/utils/axios_utils'; - import createFlash from '~/flash'; - import GlModal from '~/vue_shared/components/gl_modal.vue'; - import { redirectTo } from '~/lib/utils/url_utility'; - import { s__ } from '~/locale'; +import axios from '~/lib/utils/axios_utils'; +import createFlash from '~/flash'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; +import { redirectTo } from '~/lib/utils/url_utility'; +import { s__ } from '~/locale'; - export default { - components: { - GlModal, +export default { + components: { + GlModal, + }, + props: { + url: { + type: String, + required: true, }, - props: { - url: { - type: String, - required: true, - }, + }, + computed: { + text() { + return s__( + 'AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running.', + ); }, - computed: { - text() { - return s__('AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running.'); - }, + }, + methods: { + onSubmit() { + return axios + .post(this.url) + .then(response => { + // follow the rediect to refresh the page + redirectTo(response.request.responseURL); + }) + .catch(error => { + createFlash(s__('AdminArea|Stopping jobs failed')); + throw error; + }); }, - methods: { - onSubmit() { - return axios.post(this.url) - .then((response) => { - // follow the rediect to refresh the page - redirectTo(response.request.responseURL); - }) - .catch((error) => { - createFlash(s__('AdminArea|Stopping jobs failed')); - throw error; - }); - }, - }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/pages/admin/projects/index.js b/app/assets/javascripts/pages/admin/projects/index.js index 31c96eb87af..d6b1e747aec 100644 --- a/app/assets/javascripts/pages/admin/projects/index.js +++ b/app/assets/javascripts/pages/admin/projects/index.js @@ -4,6 +4,7 @@ import NamespaceSelect from '../../../namespace_select'; document.addEventListener('DOMContentLoaded', () => { new ProjectsList(); // eslint-disable-line no-new - document.querySelectorAll('.js-namespace-select') + document + .querySelectorAll('.js-namespace-select') .forEach(dropdown => new NamespaceSelect({ dropdown })); }); diff --git a/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue b/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue index ff66d3a8ac4..3c383735f4a 100644 --- a/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue +++ b/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue @@ -1,81 +1,84 @@ <script> - import _ from 'underscore'; - import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; - import { s__, sprintf } from '~/locale'; +import _ from 'underscore'; +import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; +import { s__, sprintf } from '~/locale'; - export default { - components: { - DeprecatedModal, +export default { + components: { + DeprecatedModal, + }, + props: { + deleteProjectUrl: { + type: String, + required: false, + default: '', }, - props: { - deleteProjectUrl: { - type: String, - required: false, - default: '', - }, - projectName: { - type: String, - required: false, - default: '', - }, - csrfToken: { - type: String, - required: false, - default: '', - }, + projectName: { + type: String, + required: false, + default: '', }, - data() { - return { - enteredProjectName: '', - }; + csrfToken: { + type: String, + required: false, + default: '', }, - computed: { - title() { - return sprintf(s__('AdminProjects|Delete Project %{projectName}?'), - { - projectName: `'${_.escape(this.projectName)}'`, - }, - false, - ); - }, - text() { - return sprintf(s__(`AdminProjects| + }, + data() { + return { + enteredProjectName: '', + }; + }, + computed: { + title() { + return sprintf( + s__('AdminProjects|Delete Project %{projectName}?'), + { + projectName: `'${_.escape(this.projectName)}'`, + }, + false, + ); + }, + text() { + return sprintf( + s__(`AdminProjects| You’re about to permanently delete the project %{projectName}, its repository, and all related resources including issues, merge requests, etc.. Once you confirm and press %{strong_start}Delete project%{strong_end}, it cannot be undone or recovered.`), - { - projectName: `<strong>${_.escape(this.projectName)}</strong>`, - strong_start: '<strong>', - strong_end: '</strong>', - }, - false, - ); - }, - confirmationTextLabel() { - return sprintf(s__('AdminUsers|To confirm, type %{projectName}'), - { - projectName: `<code>${_.escape(this.projectName)}</code>`, - }, - false, - ); - }, - primaryButtonLabel() { - return s__('AdminProjects|Delete project'); - }, - canSubmit() { - return this.enteredProjectName === this.projectName; - }, + { + projectName: `<strong>${_.escape(this.projectName)}</strong>`, + strong_start: '<strong>', + strong_end: '</strong>', + }, + false, + ); + }, + confirmationTextLabel() { + return sprintf( + s__('AdminUsers|To confirm, type %{projectName}'), + { + projectName: `<code>${_.escape(this.projectName)}</code>`, + }, + false, + ); + }, + primaryButtonLabel() { + return s__('AdminProjects|Delete project'); + }, + canSubmit() { + return this.enteredProjectName === this.projectName; + }, + }, + methods: { + onCancel() { + this.enteredProjectName = ''; }, - methods: { - onCancel() { - this.enteredProjectName = ''; - }, - onSubmit() { - this.$refs.form.submit(); - this.enteredProjectName = ''; - }, + onSubmit() { + this.$refs.form.submit(); + this.enteredProjectName = ''; }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/pages/admin/projects/index/index.js b/app/assets/javascripts/pages/admin/projects/index/index.js index ddbefec87b6..6fa8760545d 100644 --- a/app/assets/javascripts/pages/admin/projects/index/index.js +++ b/app/assets/javascripts/pages/admin/projects/index/index.js @@ -28,7 +28,7 @@ document.addEventListener('DOMContentLoaded', () => { }, }); - $(document).on('shown.bs.modal', (event) => { + $(document).on('shown.bs.modal', event => { if (event.relatedTarget.classList.contains('delete-project-button')) { const buttonProps = event.relatedTarget.dataset; deleteModal.deleteProjectUrl = buttonProps.deleteProjectUrl; diff --git a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue index 8d5efcdcd96..4b33fcc759a 100644 --- a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue +++ b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue @@ -1,114 +1,119 @@ <script> - import _ from 'underscore'; - import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; - import { s__, sprintf } from '~/locale'; +import _ from 'underscore'; +import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; +import { s__, sprintf } from '~/locale'; - export default { - components: { - DeprecatedModal, +export default { + components: { + DeprecatedModal, + }, + props: { + deleteUserUrl: { + type: String, + required: false, + default: '', }, - props: { - deleteUserUrl: { - type: String, - required: false, - default: '', - }, - blockUserUrl: { - type: String, - required: false, - default: '', - }, - deleteContributions: { - type: Boolean, - required: false, - default: false, - }, - username: { - type: String, - required: false, - default: '', - }, - csrfToken: { - type: String, - required: false, - default: '', - }, + blockUserUrl: { + type: String, + required: false, + default: '', }, - data() { - return { - enteredUsername: '', - }; + deleteContributions: { + type: Boolean, + required: false, + default: false, }, - computed: { - title() { - const keepContributionsTitle = s__('AdminUsers|Delete User %{username}?'); - const deleteContributionsTitle = s__('AdminUsers|Delete User %{username} and contributions?'); + username: { + type: String, + required: false, + default: '', + }, + csrfToken: { + type: String, + required: false, + default: '', + }, + }, + data() { + return { + enteredUsername: '', + }; + }, + computed: { + title() { + const keepContributionsTitle = s__('AdminUsers|Delete User %{username}?'); + const deleteContributionsTitle = s__('AdminUsers|Delete User %{username} and contributions?'); - return sprintf( - this.deleteContributions ? deleteContributionsTitle : keepContributionsTitle, { - username: `'${_.escape(this.username)}'`, - }, false); - }, - text() { - const keepContributionsText = s__(`AdminArea| + return sprintf( + this.deleteContributions ? deleteContributionsTitle : keepContributionsTitle, + { + username: `'${_.escape(this.username)}'`, + }, + false, + ); + }, + text() { + const keepContributionsText = s__(`AdminArea| You are about to permanently delete the user %{username}. Issues, merge requests, and groups linked to them will be transferred to a system-wide "Ghost-user". To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`); - const deleteContributionsText = s__(`AdminArea| + const deleteContributionsText = s__(`AdminArea| You are about to permanently delete the user %{username}. This will delete all of the issues, merge requests, and groups linked to them. To avoid data loss, consider using the %{strong_start}block user%{strong_end} feature instead. Once you %{strong_start}Delete user%{strong_end}, it cannot be undone or recovered.`); - return sprintf(this.deleteContributions ? deleteContributionsText : keepContributionsText, - { - username: `<strong>${_.escape(this.username)}</strong>`, - strong_start: '<strong>', - strong_end: '</strong>', - }, - false, - ); - }, - confirmationTextLabel() { - return sprintf(s__('AdminUsers|To confirm, type %{username}'), - { - username: `<code>${_.escape(this.username)}</code>`, - }, - false, - ); - }, - primaryButtonLabel() { - const keepContributionsLabel = s__('AdminUsers|Delete user'); - const deleteContributionsLabel = s__('AdminUsers|Delete user and contributions'); + return sprintf( + this.deleteContributions ? deleteContributionsText : keepContributionsText, + { + username: `<strong>${_.escape(this.username)}</strong>`, + strong_start: '<strong>', + strong_end: '</strong>', + }, + false, + ); + }, + confirmationTextLabel() { + return sprintf( + s__('AdminUsers|To confirm, type %{username}'), + { + username: `<code>${_.escape(this.username)}</code>`, + }, + false, + ); + }, + primaryButtonLabel() { + const keepContributionsLabel = s__('AdminUsers|Delete user'); + const deleteContributionsLabel = s__('AdminUsers|Delete user and contributions'); - return this.deleteContributions ? deleteContributionsLabel : keepContributionsLabel; - }, - secondaryButtonLabel() { - return s__('AdminUsers|Block user'); - }, - canSubmit() { - return this.enteredUsername === this.username; - }, + return this.deleteContributions ? deleteContributionsLabel : keepContributionsLabel; }, - methods: { - onCancel() { - this.enteredUsername = ''; - }, - onSecondaryAction() { - const { form } = this.$refs; + secondaryButtonLabel() { + return s__('AdminUsers|Block user'); + }, + canSubmit() { + return this.enteredUsername === this.username; + }, + }, + methods: { + onCancel() { + this.enteredUsername = ''; + }, + onSecondaryAction() { + const { form } = this.$refs; - form.action = this.blockUserUrl; - this.$refs.method.value = 'put'; + form.action = this.blockUserUrl; + this.$refs.method.value = 'put'; - form.submit(); - }, - onSubmit() { - this.$refs.form.submit(); - this.enteredUsername = ''; - }, + form.submit(); + }, + onSubmit() { + this.$refs.form.submit(); + this.enteredUsername = ''; }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/pages/admin/users/index.js b/app/assets/javascripts/pages/admin/users/index.js index 06599c3fd5f..45046688b57 100644 --- a/app/assets/javascripts/pages/admin/users/index.js +++ b/app/assets/javascripts/pages/admin/users/index.js @@ -32,12 +32,14 @@ document.addEventListener('DOMContentLoaded', () => { }, }); - $(document).on('shown.bs.modal', (event) => { + $(document).on('shown.bs.modal', event => { if (event.relatedTarget.classList.contains('delete-user-button')) { const buttonProps = event.relatedTarget.dataset; deleteModal.deleteUserUrl = buttonProps.deleteUserUrl; deleteModal.blockUserUrl = buttonProps.blockUserUrl; - deleteModal.deleteContributions = event.relatedTarget.hasAttribute('data-delete-contributions'); + deleteModal.deleteContributions = event.relatedTarget.hasAttribute( + 'data-delete-contributions', + ); deleteModal.username = buttonProps.username; } }); diff --git a/app/assets/javascripts/pages/admin/users/new/index.js b/app/assets/javascripts/pages/admin/users/new/index.js index 58bfa8d64e7..3e6a090cb0e 100644 --- a/app/assets/javascripts/pages/admin/users/new/index.js +++ b/app/assets/javascripts/pages/admin/users/new/index.js @@ -4,7 +4,9 @@ export default class UserInternalRegexHandler { constructor() { this.regexPattern = $('[data-user-internal-regex-pattern]').data('user-internal-regex-pattern'); if (this.regexPattern && this.regexPattern !== '') { - this.regexOptions = $('[data-user-internal-regex-options]').data('user-internal-regex-options'); + this.regexOptions = $('[data-user-internal-regex-options]').data( + 'user-internal-regex-options', + ); this.external = $('#user_external'); this.warningMessage = $('#warning_external_automatically_set'); this.addListenerToEmailField(); @@ -13,7 +15,7 @@ export default class UserInternalRegexHandler { } addListenerToEmailField() { - $('#user_email').on('input', (event) => { + $('#user_email').on('input', event => { this.setExternalCheckbox(event.currentTarget.value); }); } diff --git a/app/assets/javascripts/pages/dashboard/todos/index/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js index 72f3f70b98f..1b56b97f751 100644 --- a/app/assets/javascripts/pages/dashboard/todos/index/todos.js +++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js @@ -79,7 +79,8 @@ export default class Todos { .then(({ data }) => { this.updateRowState(target); this.updateBadges(data); - }).catch(() => { + }) + .catch(() => { this.updateRowState(target, true); return flash(__('Error updating todo status.')); }); @@ -118,10 +119,12 @@ export default class Todos { axios[target.dataset.method](target.dataset.href, { ids: this.todo_ids, - }).then(({ data }) => { - this.updateAllState(target, data); - this.updateBadges(data); - }).catch(() => flash(__('Error updating status for all todos.'))); + }) + .then(({ data }) => { + this.updateAllState(target, data); + this.updateBadges(data); + }) + .catch(() => flash(__('Error updating status for all todos.'))); } updateAllState(target, data) { @@ -133,7 +136,7 @@ export default class Todos { target.removeAttribute('disabled'); target.classList.remove('disabled'); - this.todo_ids = (target === markAllDoneBtn) ? data.updated_ids : []; + this.todo_ids = target === markAllDoneBtn ? data.updated_ids : []; undoAllBtn.classList.toggle('hidden'); markAllDoneBtn.classList.toggle('hidden'); todoListContainer.classList.toggle('hidden'); diff --git a/app/assets/javascripts/pages/groups/edit/index.js b/app/assets/javascripts/pages/groups/edit/index.js index d0bce857029..32b55575f95 100644 --- a/app/assets/javascripts/pages/groups/edit/index.js +++ b/app/assets/javascripts/pages/groups/edit/index.js @@ -5,6 +5,7 @@ import initSettingsPanels from '~/settings_panels'; import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory'; import mountBadgeSettings from '~/pages/shared/mount_badge_settings'; import { GROUP_BADGE } from '~/badges/constants'; +import projectSelect from '~/project_select'; document.addEventListener('DOMContentLoaded', () => { groupAvatar(); @@ -15,4 +16,6 @@ document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('.js-general-settings-form, .js-general-permissions-form'), ); mountBadgeSettings(GROUP_BADGE); + + projectSelect(); }); diff --git a/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue b/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue index 48668562f09..a4778077bc4 100644 --- a/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue +++ b/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue @@ -1,94 +1,117 @@ <script> - import axios from '~/lib/utils/axios_utils'; +import axios from '~/lib/utils/axios_utils'; - import Flash from '~/flash'; - import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; - import { n__, s__, sprintf } from '~/locale'; - import { redirectTo } from '~/lib/utils/url_utility'; - import eventHub from '../event_hub'; +import Flash from '~/flash'; +import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; +import { n__, s__, sprintf } from '~/locale'; +import { redirectTo } from '~/lib/utils/url_utility'; +import eventHub from '../event_hub'; - export default { - components: { - DeprecatedModal, +export default { + components: { + DeprecatedModal, + }, + props: { + issueCount: { + type: Number, + required: true, }, - props: { - issueCount: { - type: Number, - required: true, - }, - mergeRequestCount: { - type: Number, - required: true, - }, - milestoneId: { - type: Number, - required: true, - }, - milestoneTitle: { - type: String, - required: true, - }, - milestoneUrl: { - type: String, - required: true, - }, + mergeRequestCount: { + type: Number, + required: true, }, - computed: { - text() { - const milestoneTitle = sprintf('<strong>%{milestoneTitle}</strong>', { milestoneTitle: this.milestoneTitle }); - - if (this.issueCount === 0 && this.mergeRequestCount === 0) { - return sprintf( - s__(`Milestones| -You’re about to permanently delete the milestone %{milestoneTitle}. -This milestone is not currently used in any issues or merge requests.`), - { - milestoneTitle, - }, - false, - ); - } + milestoneId: { + type: Number, + required: true, + }, + milestoneTitle: { + type: String, + required: true, + }, + milestoneUrl: { + type: String, + required: true, + }, + }, + computed: { + text() { + const milestoneTitle = sprintf('<strong>%{milestoneTitle}</strong>', { + milestoneTitle: this.milestoneTitle, + }); + if (this.issueCount === 0 && this.mergeRequestCount === 0) { return sprintf( s__(`Milestones| -You’re about to permanently delete the milestone %{milestoneTitle} and remove it from %{issuesWithCount} and %{mergeRequestsWithCount}. -Once deleted, it cannot be undone or recovered.`), +You’re about to permanently delete the milestone %{milestoneTitle}. +This milestone is not currently used in any issues or merge requests.`), { milestoneTitle, - issuesWithCount: n__('%d issue', '%d issues', this.issueCount), - mergeRequestsWithCount: n__('%d merge request', '%d merge requests', this.mergeRequestCount), }, false, ); - }, - title() { - return sprintf(s__('Milestones|Delete milestone %{milestoneTitle}?'), { milestoneTitle: this.milestoneTitle }); - }, - }, - methods: { - onSubmit() { - eventHub.$emit('deleteMilestoneModal.requestStarted', this.milestoneUrl); + } - return axios.delete(this.milestoneUrl) - .then((response) => { - eventHub.$emit('deleteMilestoneModal.requestFinished', { milestoneUrl: this.milestoneUrl, successful: true }); + return sprintf( + s__(`Milestones| +You’re about to permanently delete the milestone %{milestoneTitle} and remove it from %{issuesWithCount} and %{mergeRequestsWithCount}. +Once deleted, it cannot be undone or recovered.`), + { + milestoneTitle, + issuesWithCount: n__('%d issue', '%d issues', this.issueCount), + mergeRequestsWithCount: n__( + '%d merge request', + '%d merge requests', + this.mergeRequestCount, + ), + }, + false, + ); + }, + title() { + return sprintf(s__('Milestones|Delete milestone %{milestoneTitle}?'), { + milestoneTitle: this.milestoneTitle, + }); + }, + }, + methods: { + onSubmit() { + eventHub.$emit('deleteMilestoneModal.requestStarted', this.milestoneUrl); - // follow the rediect to milestones overview page - redirectTo(response.request.responseURL); - }) - .catch((error) => { - eventHub.$emit('deleteMilestoneModal.requestFinished', { milestoneUrl: this.milestoneUrl, successful: false }); + return axios + .delete(this.milestoneUrl) + .then(response => { + eventHub.$emit('deleteMilestoneModal.requestFinished', { + milestoneUrl: this.milestoneUrl, + successful: true, + }); - if (error.response && error.response.status === 404) { - Flash(sprintf(s__('Milestones|Milestone %{milestoneTitle} was not found'), { milestoneTitle: this.milestoneTitle })); - } else { - Flash(sprintf(s__('Milestones|Failed to delete milestone %{milestoneTitle}'), { milestoneTitle: this.milestoneTitle })); - } - throw error; + // follow the rediect to milestones overview page + redirectTo(response.request.responseURL); + }) + .catch(error => { + eventHub.$emit('deleteMilestoneModal.requestFinished', { + milestoneUrl: this.milestoneUrl, + successful: false, }); - }, + + if (error.response && error.response.status === 404) { + Flash( + sprintf(s__('Milestones|Milestone %{milestoneTitle} was not found'), { + milestoneTitle: this.milestoneTitle, + }), + ); + } else { + Flash( + sprintf(s__('Milestones|Failed to delete milestone %{milestoneTitle}'), { + milestoneTitle: this.milestoneTitle, + }), + ); + } + throw error; + }); }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js b/app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js index d51b5c221e3..1d559dc6e41 100644 --- a/app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js +++ b/app/assets/javascripts/pages/milestones/shared/delete_milestone_modal_init.js @@ -7,7 +7,9 @@ export default () => { Vue.use(Translate); const onRequestFinished = ({ milestoneUrl, successful }) => { - const button = document.querySelector(`.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`); + const button = document.querySelector( + `.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`, + ); if (!successful) { button.removeAttribute('disabled'); @@ -16,14 +18,16 @@ export default () => { button.querySelector('.js-loading-icon').classList.add('hidden'); }; - const onRequestStarted = (milestoneUrl) => { - const button = document.querySelector(`.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`); + const onRequestStarted = milestoneUrl => { + const button = document.querySelector( + `.js-delete-milestone-button[data-milestone-url="${milestoneUrl}"]`, + ); button.setAttribute('disabled', ''); button.querySelector('.js-loading-icon').classList.remove('hidden'); eventHub.$once('deleteMilestoneModal.requestFinished', onRequestFinished); }; - const onDeleteButtonClick = (event) => { + const onDeleteButtonClick = event => { const button = event.currentTarget; const modalProps = { milestoneId: parseInt(button.dataset.milestoneId, 10), @@ -37,12 +41,12 @@ export default () => { }; const deleteMilestoneButtons = document.querySelectorAll('.js-delete-milestone-button'); - deleteMilestoneButtons.forEach((button) => { + deleteMilestoneButtons.forEach(button => { button.addEventListener('click', onDeleteButtonClick); }); eventHub.$once('deleteMilestoneModal.mounted', () => { - deleteMilestoneButtons.forEach((button) => { + deleteMilestoneButtons.forEach(button => { button.removeAttribute('disabled'); }); }); diff --git a/app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js b/app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js index 8e79341e96a..fcc62a2b2af 100644 --- a/app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js +++ b/app/assets/javascripts/pages/milestones/shared/promote_milestone_modal_init.js @@ -7,20 +7,24 @@ Vue.use(Translate); export default () => { const onRequestFinished = ({ milestoneUrl, successful }) => { - const button = document.querySelector(`.js-promote-project-milestone-button[data-url="${milestoneUrl}"]`); + const button = document.querySelector( + `.js-promote-project-milestone-button[data-url="${milestoneUrl}"]`, + ); if (!successful) { button.removeAttribute('disabled'); } }; - const onRequestStarted = (milestoneUrl) => { - const button = document.querySelector(`.js-promote-project-milestone-button[data-url="${milestoneUrl}"]`); + const onRequestStarted = milestoneUrl => { + const button = document.querySelector( + `.js-promote-project-milestone-button[data-url="${milestoneUrl}"]`, + ); button.setAttribute('disabled', ''); eventHub.$once('promoteMilestoneModal.requestFinished', onRequestFinished); }; - const onDeleteButtonClick = (event) => { + const onDeleteButtonClick = event => { const button = event.currentTarget; const modalProps = { milestoneTitle: button.dataset.milestoneTitle, @@ -32,12 +36,12 @@ export default () => { }; const promoteMilestoneButtons = document.querySelectorAll('.js-promote-project-milestone-button'); - promoteMilestoneButtons.forEach((button) => { + promoteMilestoneButtons.forEach(button => { button.addEventListener('click', onDeleteButtonClick); }); eventHub.$once('promoteMilestoneModal.mounted', () => { - promoteMilestoneButtons.forEach((button) => { + promoteMilestoneButtons.forEach(button => { button.removeAttribute('disabled'); }); }); diff --git a/app/assets/javascripts/pages/profiles/index.js b/app/assets/javascripts/pages/profiles/index.js index 04e50963699..883be18b336 100644 --- a/app/assets/javascripts/pages/profiles/index.js +++ b/app/assets/javascripts/pages/profiles/index.js @@ -3,9 +3,12 @@ import '~/profile/gl_crop'; import Profile from '~/profile/profile'; document.addEventListener('DOMContentLoaded', () => { - $(document).on('input.ssh_key', '#key_key', function () { // eslint-disable-line func-names + // eslint-disable-next-line func-names + $(document).on('input.ssh_key', '#key_key', function() { const $title = $('#key_title'); - const comment = $(this).val().match(/^\S+ \S+ (.+)\n?$/); + const comment = $(this) + .val() + .match(/^\S+ \S+ (.+)\n?$/); // Extract the SSH Key title from its comment if (comment && comment.length > 1) { diff --git a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js index 8e8f47c21d8..417935e2ad0 100644 --- a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js +++ b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js @@ -5,7 +5,9 @@ document.addEventListener('DOMContentLoaded', () => { const twoFactorNode = document.querySelector('.js-two-factor-auth'); const skippable = twoFactorNode.dataset.twoFactorSkippable === 'true'; if (skippable) { - const button = `<a class="btn btn-sm btn-warning float-right" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a>`; + const button = `<a class="btn btn-sm btn-warning float-right" data-method="patch" href="${ + twoFactorNode.dataset.two_factor_skip_url + }">Configure it later</a>`; const flashAlert = document.querySelector('.flash-alert .container-fluid'); if (flashAlert) flashAlert.insertAdjacentHTML('beforeend', button); } diff --git a/app/assets/javascripts/pages/projects/branches/new/index.js b/app/assets/javascripts/pages/projects/branches/new/index.js index a9658fd1eb4..13ff47d53c2 100644 --- a/app/assets/javascripts/pages/projects/branches/new/index.js +++ b/app/assets/javascripts/pages/projects/branches/new/index.js @@ -1,6 +1,11 @@ import $ from 'jquery'; import NewBranchForm from '~/new_branch_form'; -document.addEventListener('DOMContentLoaded', () => ( - new NewBranchForm($('.js-create-branch-form'), JSON.parse(document.getElementById('availableRefs').innerHTML)) -)); +document.addEventListener( + 'DOMContentLoaded', + () => + new NewBranchForm( + $('.js-create-branch-form'), + JSON.parse(document.getElementById('availableRefs').innerHTML), + ), +); diff --git a/app/assets/javascripts/pages/projects/graphs/charts/index.js b/app/assets/javascripts/pages/projects/graphs/charts/index.js index 80159a82bd4..3ccad513c05 100644 --- a/app/assets/javascripts/pages/projects/graphs/charts/index.js +++ b/app/assets/javascripts/pages/projects/graphs/charts/index.js @@ -31,14 +31,16 @@ document.addEventListener('DOMContentLoaded', () => { const chartData = data => ({ labels: Object.keys(data), - datasets: [{ - fillColor: 'rgba(220,220,220,0.5)', - strokeColor: 'rgba(220,220,220,1)', - barStrokeWidth: 1, - barValueSpacing: 1, - barDatasetSpacing: 1, - data: _.values(data), - }], + datasets: [ + { + fillColor: 'rgba(220,220,220,0.5)', + strokeColor: 'rgba(220,220,220,1)', + barStrokeWidth: 1, + barValueSpacing: 1, + barDatasetSpacing: 1, + data: _.values(data), + }, + ], }); const hourData = chartData(projectChartData.hour); @@ -51,7 +53,9 @@ document.addEventListener('DOMContentLoaded', () => { responsiveChart($('#month-chart'), monthData); const data = projectChartData.languages; - const ctx = $('#languages-chart').get(0).getContext('2d'); + const ctx = $('#languages-chart') + .get(0) + .getContext('2d'); const options = { scaleOverlay: true, responsive: true, diff --git a/app/assets/javascripts/pages/projects/graphs/show/index.js b/app/assets/javascripts/pages/projects/graphs/show/index.js index 71f629fbc13..f79c386b59e 100644 --- a/app/assets/javascripts/pages/projects/graphs/show/index.js +++ b/app/assets/javascripts/pages/projects/graphs/show/index.js @@ -7,7 +7,8 @@ import ContributorsStatGraph from './stat_graph_contributors'; document.addEventListener('DOMContentLoaded', () => { const url = document.querySelector('.js-graphs-show').dataset.projectGraphPath; - axios.get(url) + axios + .get(url) .then(({ data }) => { const graph = new ContributorsStatGraph(); graph.init(data); diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js index 58bb8c5b0c8..76613394af6 100644 --- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js +++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js @@ -3,7 +3,11 @@ import $ from 'jquery'; import _ from 'underscore'; import { n__, s__, createDateTimeFormat, sprintf } from '~/locale'; -import { ContributorsGraph, ContributorsAuthorGraph, ContributorsMasterGraph } from './stat_graph_contributors_graph'; +import { + ContributorsGraph, + ContributorsAuthorGraph, + ContributorsMasterGraph, +} from './stat_graph_contributors_graph'; import ContributorsStatGraphUtil from './stat_graph_contributors_util'; export default (function() { @@ -14,7 +18,7 @@ export default (function() { ContributorsStatGraph.prototype.init = function(log) { var author_commits, total_commits; this.parsed_log = ContributorsStatGraphUtil.parse_log(log); - this.set_current_field("commits"); + this.set_current_field('commits'); total_commits = ContributorsStatGraphUtil.get_total_data(this.parsed_log, this.field); author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field); this.add_master_graph(total_commits); @@ -31,23 +35,26 @@ export default (function() { var limited_author_data; this.authors = []; limited_author_data = author_data.slice(0, 100); - return _.each(limited_author_data, (function(_this) { - return function(d) { - var author_graph, author_header; - author_header = _this.create_author_header(d); - $(".contributors-list").append(author_header); - - author_graph = new ContributorsAuthorGraph(d.dates); - _this.authors[d.author_name] = author_graph; - return author_graph.draw(); - }; - })(this)); + return _.each( + limited_author_data, + (function(_this) { + return function(d) { + var author_graph, author_header; + author_header = _this.create_author_header(d); + $('.contributors-list').append(author_header); + + author_graph = new ContributorsAuthorGraph(d.dates); + _this.authors[d.author_name] = author_graph; + return author_graph.draw(); + }; + })(this), + ); }; ContributorsStatGraph.prototype.format_author_commit_info = function(author) { var commits; commits = $('<span/>', { - "class": 'graph-author-commits-count' + class: 'graph-author-commits-count', }); commits.text(n__('%d commit', '%d commits', author.commits)); return $('<span/>').append(commits); @@ -56,13 +63,13 @@ export default (function() { ContributorsStatGraph.prototype.create_author_header = function(author) { var author_commit_info, author_commit_info_span, author_email, author_name, list_item; list_item = $('<li/>', { - "class": 'person', - style: 'display: block;' + class: 'person', + style: 'display: block;', }); author_name = $('<h4>' + author.author_name + '</h4>'); author_email = $('<p class="graph-author-email">' + author.author_email + '</p>'); author_commit_info_span = $('<span/>', { - "class": 'commits' + class: 'commits', }); author_commit_info = this.format_author_commit_info(author); author_commit_info_span.html(author_commit_info); @@ -80,37 +87,41 @@ export default (function() { }; ContributorsStatGraph.prototype.redraw_authors = function() { - $("ol").html(""); + $('ol').html(''); const { x_domain } = ContributorsGraph.prototype; - const author_commits = ContributorsStatGraphUtil.get_author_data(this.parsed_log, this.field, x_domain); - - return _.each(author_commits, (function(_this) { - return function(d) { - _this.redraw_author_commit_info(d); - if (_this.authors[d.author_name] != null) { - $(_this.authors[d.author_name].list_item).appendTo("ol"); - _this.authors[d.author_name].set_data(d.dates); - return _this.authors[d.author_name].redraw(); - } - return ''; - }; - })(this)); + const author_commits = ContributorsStatGraphUtil.get_author_data( + this.parsed_log, + this.field, + x_domain, + ); + + return _.each( + author_commits, + (function(_this) { + return function(d) { + _this.redraw_author_commit_info(d); + if (_this.authors[d.author_name] != null) { + $(_this.authors[d.author_name].list_item).appendTo('ol'); + _this.authors[d.author_name].set_data(d.dates); + return _this.authors[d.author_name].redraw(); + } + return ''; + }; + })(this), + ); }; ContributorsStatGraph.prototype.set_current_field = function(field) { - return this.field = field; + return (this.field = field); }; ContributorsStatGraph.prototype.change_date_header = function() { const { x_domain } = ContributorsGraph.prototype; - const formattedDateRange = sprintf( - s__('ContributorsPage|%{startDate} – %{endDate}'), - { - startDate: this.dateFormat.format(new Date(x_domain[0])), - endDate: this.dateFormat.format(new Date(x_domain[1])), - }, - ); + const formattedDateRange = sprintf(s__('ContributorsPage|%{startDate} – %{endDate}'), { + startDate: this.dateFormat.format(new Date(x_domain[0])), + endDate: this.dateFormat.format(new Date(x_domain[1])), + }); return $('#date_header').text(formattedDateRange); }; @@ -120,7 +131,7 @@ export default (function() { if ($author != null) { author_list_item = $(this.authors[author.author_name].list_item); author_commit_info = this.format_author_commit_info(author); - return author_list_item.find("span").html(author_commit_info); + return author_list_item.find('span').html(author_commit_info); } return ''; }; diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js index 5f91686347a..377dce6c746 100644 --- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js +++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js @@ -11,10 +11,32 @@ import { brushX } from 'd3-brush'; import { timeParse } from 'd3-time-format'; import { dateTickFormat } from '~/lib/utils/tick_formats'; -const d3 = { extent, max, select, scaleTime, scaleLinear, axisLeft, axisBottom, area, brushX, timeParse }; +const d3 = { + extent, + max, + select, + scaleTime, + scaleLinear, + axisLeft, + axisBottom, + area, + brushX, + timeParse, +}; const hasProp = {}.hasOwnProperty; -const extend = function(child, parent) { for (const key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; +const extend = function(child, parent) { + for (const key in parent) { + if (hasProp.call(parent, key)) child[key] = parent[key]; + } + function ctor() { + this.constructor = child; + } + ctor.prototype = parent.prototype; + child.prototype = new ctor(); + child.__super__ = parent.prototype; + return child; +}; export const ContributorsGraph = (function() { function ContributorsGraph() {} @@ -23,7 +45,7 @@ export const ContributorsGraph = (function() { top: 20, right: 10, bottom: 30, - left: 40 + left: 40, }; ContributorsGraph.prototype.x_domain = null; @@ -33,35 +55,39 @@ export const ContributorsGraph = (function() { ContributorsGraph.prototype.dates = []; ContributorsGraph.prototype.determine_width = function(baseWidth, $parentElement) { - const parentPaddingWidth = parseFloat($parentElement.css('padding-left')) + parseFloat($parentElement.css('padding-right')); + const parentPaddingWidth = + parseFloat($parentElement.css('padding-left')) + + parseFloat($parentElement.css('padding-right')); const marginWidth = this.MARGIN.left + this.MARGIN.right; return baseWidth - parentPaddingWidth - marginWidth; }; ContributorsGraph.set_x_domain = function(data) { - return ContributorsGraph.prototype.x_domain = data; + return (ContributorsGraph.prototype.x_domain = data); }; ContributorsGraph.set_y_domain = function(data) { - return ContributorsGraph.prototype.y_domain = [ - 0, d3.max(data, function(d) { - return d.commits = d.commits || d.additions || d.deletions; - }) - ]; + return (ContributorsGraph.prototype.y_domain = [ + 0, + d3.max(data, function(d) { + return (d.commits = d.commits || d.additions || d.deletions); + }), + ]); }; ContributorsGraph.init_x_domain = function(data) { - return ContributorsGraph.prototype.x_domain = d3.extent(data, function(d) { + return (ContributorsGraph.prototype.x_domain = d3.extent(data, function(d) { return d.date; - }); + })); }; ContributorsGraph.init_y_domain = function(data) { - return ContributorsGraph.prototype.y_domain = [ - 0, d3.max(data, function(d) { - return d.commits = d.commits || d.additions || d.deletions; - }) - ]; + return (ContributorsGraph.prototype.y_domain = [ + 0, + d3.max(data, function(d) { + return (d.commits = d.commits || d.additions || d.deletions); + }), + ]); }; ContributorsGraph.init_domain = function(data) { @@ -70,7 +96,7 @@ export const ContributorsGraph = (function() { }; ContributorsGraph.set_dates = function(data) { - return ContributorsGraph.prototype.dates = data; + return (ContributorsGraph.prototype.dates = data); }; ContributorsGraph.prototype.set_x_domain = function() { @@ -87,20 +113,33 @@ export const ContributorsGraph = (function() { }; ContributorsGraph.prototype.create_scale = function(width, height) { - this.x = d3.scaleTime().range([0, width]).clamp(true); - return this.y = d3.scaleLinear().range([height, 0]).nice(); + this.x = d3 + .scaleTime() + .range([0, width]) + .clamp(true); + return (this.y = d3 + .scaleLinear() + .range([height, 0]) + .nice()); }; ContributorsGraph.prototype.draw_x_axis = function() { - return this.svg.append("g").attr("class", "x axis").attr("transform", "translate(0, " + this.height + ")").call(this.x_axis); + return this.svg + .append('g') + .attr('class', 'x axis') + .attr('transform', 'translate(0, ' + this.height + ')') + .call(this.x_axis); }; ContributorsGraph.prototype.draw_y_axis = function() { - return this.svg.append("g").attr("class", "y axis").call(this.y_axis); + return this.svg + .append('g') + .attr('class', 'y axis') + .call(this.y_axis); }; ContributorsGraph.prototype.set_data = function(data) { - return this.data = data; + return (this.data = data); }; return ContributorsGraph; @@ -137,9 +176,9 @@ export const ContributorsMasterGraph = (function(superClass) { }; ContributorsMasterGraph.prototype.parse_dates = function(data) { - const parseDate = d3.timeParse("%Y-%m-%d"); + const parseDate = d3.timeParse('%Y-%m-%d'); return data.forEach(function(d) { - return d.date = parseDate(d.date); + return (d.date = parseDate(d.date)); }); }; @@ -148,42 +187,63 @@ export const ContributorsMasterGraph = (function(superClass) { }; ContributorsMasterGraph.prototype.create_axes = function() { - this.x_axis = d3.axisBottom() + this.x_axis = d3 + .axisBottom() .scale(this.x) .tickFormat(dateTickFormat); - return this.y_axis = d3.axisLeft().scale(this.y).ticks(5); + return (this.y_axis = d3 + .axisLeft() + .scale(this.y) + .ticks(5)); }; ContributorsMasterGraph.prototype.create_svg = function() { - this.svg = d3.select("#contributors-master") - .append("svg") - .attr("width", this.width + this.MARGIN.left + this.MARGIN.right) - .attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom) - .attr("class", "tint-box") - .append("g") - .attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")"); + this.svg = d3 + .select('#contributors-master') + .append('svg') + .attr('width', this.width + this.MARGIN.left + this.MARGIN.right) + .attr('height', this.height + this.MARGIN.top + this.MARGIN.bottom) + .attr('class', 'tint-box') + .append('g') + .attr('transform', 'translate(' + this.MARGIN.left + ',' + this.MARGIN.top + ')'); return this.svg; }; ContributorsMasterGraph.prototype.create_area = function(x, y) { - return this.area = d3.area().x(function(d) { - return x(d.date); - }).y0(this.height).y1(function(d) { - d.commits = d.commits || d.additions || d.deletions; - return y(d.commits); - }); + return (this.area = d3 + .area() + .x(function(d) { + return x(d.date); + }) + .y0(this.height) + .y1(function(d) { + d.commits = d.commits || d.additions || d.deletions; + return y(d.commits); + })); }; ContributorsMasterGraph.prototype.create_brush = function() { - return this.brush = d3.brushX(this.x).extent([[this.x.range()[0], 0], [this.x.range()[1], this.height]]).on("end", this.update_content); + return (this.brush = d3 + .brushX(this.x) + .extent([[this.x.range()[0], 0], [this.x.range()[1], this.height]]) + .on('end', this.update_content)); }; ContributorsMasterGraph.prototype.draw_path = function(data) { - return this.svg.append("path").datum(data).attr("class", "area").attr("d", this.area); + return this.svg + .append('path') + .datum(data) + .attr('class', 'area') + .attr('d', this.area); }; ContributorsMasterGraph.prototype.add_brush = function() { - return this.svg.append("g").attr("class", "selection").call(this.brush).selectAll("rect").attr("height", this.height); + return this.svg + .append('g') + .attr('class', 'selection') + .call(this.brush) + .selectAll('rect') + .attr('height', this.height); }; ContributorsMasterGraph.prototype.update_content = function() { @@ -193,7 +253,7 @@ export const ContributorsMasterGraph = (function(superClass) { } else { ContributorsGraph.set_x_domain(this.x_max_domain); } - return $("#brush_change").trigger('change'); + return $('#brush_change').trigger('change'); }; ContributorsMasterGraph.prototype.draw = function() { @@ -216,9 +276,9 @@ export const ContributorsMasterGraph = (function(superClass) { this.process_dates(this.data); ContributorsGraph.set_y_domain(this.data); this.set_y_domain(); - this.svg.select("path").datum(this.data); - this.svg.select("path").attr("d", this.area); - return this.svg.select(".y.axis").call(this.y_axis); + this.svg.select('path').datum(this.data); + this.svg.select('path').attr('d', this.area); + return this.svg.select('.y.axis').call(this.y_axis); }; return ContributorsMasterGraph; @@ -252,43 +312,58 @@ export const ContributorsAuthorGraph = (function(superClass) { }; ContributorsAuthorGraph.prototype.create_axes = function() { - this.x_axis = d3.axisBottom() + this.x_axis = d3 + .axisBottom() .scale(this.x) .ticks(8) .tickFormat(dateTickFormat); - return this.y_axis = d3.axisLeft().scale(this.y).ticks(5); + return (this.y_axis = d3 + .axisLeft() + .scale(this.y) + .ticks(5)); }; ContributorsAuthorGraph.prototype.create_area = function(x, y) { - return this.area = d3.area().x(function(d) { - const parseDate = d3.timeParse("%Y-%m-%d"); - return x(parseDate(d)); - }).y0(this.height).y1((function(_this) { - return function(d) { - if (_this.data[d] != null) { - return y(_this.data[d]); - } else { - return y(0); - } - }; - })(this)); + return (this.area = d3 + .area() + .x(function(d) { + const parseDate = d3.timeParse('%Y-%m-%d'); + return x(parseDate(d)); + }) + .y0(this.height) + .y1( + (function(_this) { + return function(d) { + if (_this.data[d] != null) { + return y(_this.data[d]); + } else { + return y(0); + } + }; + })(this), + )); }; ContributorsAuthorGraph.prototype.create_svg = function() { const persons = document.querySelectorAll('.person'); this.list_item = persons[persons.length - 1]; - this.svg = d3.select(this.list_item) - .append("svg") - .attr("width", this.width + this.MARGIN.left + this.MARGIN.right) - .attr("height", this.height + this.MARGIN.top + this.MARGIN.bottom) - .attr("class", "spark") - .append("g") - .attr("transform", "translate(" + this.MARGIN.left + "," + this.MARGIN.top + ")"); + this.svg = d3 + .select(this.list_item) + .append('svg') + .attr('width', this.width + this.MARGIN.left + this.MARGIN.right) + .attr('height', this.height + this.MARGIN.top + this.MARGIN.bottom) + .attr('class', 'spark') + .append('g') + .attr('transform', 'translate(' + this.MARGIN.left + ',' + this.MARGIN.top + ')'); return this.svg; }; ContributorsAuthorGraph.prototype.draw_path = function(data) { - return this.svg.append("path").datum(data).attr("class", "area-contributor").attr("d", this.area); + return this.svg + .append('path') + .datum(data) + .attr('class', 'area-contributor') + .attr('d', this.area); }; ContributorsAuthorGraph.prototype.draw = function() { @@ -304,10 +379,10 @@ export const ContributorsAuthorGraph = (function(superClass) { ContributorsAuthorGraph.prototype.redraw = function() { this.set_domain(); - this.svg.select("path").datum(this.dates); - this.svg.select("path").attr("d", this.area); - this.svg.select(".x.axis").call(this.x_axis); - return this.svg.select(".y.axis").call(this.y_axis); + this.svg.select('path').datum(this.dates); + this.svg.select('path').attr('d', this.area); + this.svg.select('.x.axis').call(this.x_axis); + return this.svg.select('.y.axis').call(this.y_axis); }; return ContributorsAuthorGraph; diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js index cd0e2bc023c..988ae164955 100644 --- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js +++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js @@ -26,12 +26,12 @@ export default { by_author = _.toArray(by_author); return { total: total, - by_author: by_author + by_author: by_author, }; }, add_date: function(date, collection) { collection[date] = {}; - return collection[date].date = date; + return (collection[date].date = date); }, add_author: function(author, by_author, by_email) { var data, normalized_email; @@ -49,28 +49,28 @@ export default { return this.store_deletions(entry, total, by_author); }, store_commits: function(total, by_author) { - this.add(total, "commits", 1); - return this.add(by_author, "commits", 1); + this.add(total, 'commits', 1); + return this.add(by_author, 'commits', 1); }, add: function(collection, field, value) { if (collection[field] == null) { collection[field] = 0; } - return collection[field] += value; + return (collection[field] += value); }, store_additions: function(entry, total, by_author) { if (entry.additions == null) { entry.additions = 0; } - this.add(total, "additions", entry.additions); - return this.add(by_author, "additions", entry.additions); + this.add(total, 'additions', entry.additions); + return this.add(by_author, 'additions', entry.additions); }, store_deletions: function(entry, total, by_author) { if (entry.deletions == null) { entry.deletions = 0; } - this.add(total, "deletions", entry.deletions); - return this.add(by_author, "deletions", entry.deletions); + this.add(total, 'deletions', entry.deletions); + return this.add(by_author, 'deletions', entry.deletions); }, get_total_data: function(parsed_log, field) { var log, total_data; @@ -95,15 +95,18 @@ export default { } log = parsed_log.by_author; author_data = []; - _.each(log, (function(_this) { - return function(log_entry) { - var parsed_log_entry; - parsed_log_entry = _this.parse_log_entry(log_entry, field, date_range); - if (!_.isEmpty(parsed_log_entry.dates)) { - return author_data.push(parsed_log_entry); - } - }; - })(this)); + _.each( + log, + (function(_this) { + return function(log_entry) { + var parsed_log_entry; + parsed_log_entry = _this.parse_log_entry(log_entry, field, date_range); + if (!_.isEmpty(parsed_log_entry.dates)) { + return author_data.push(parsed_log_entry); + } + }; + })(this), + ); return _.sortBy(author_data, function(d) { return d[field]; }).reverse(); @@ -120,16 +123,19 @@ export default { parsed_entry.additions = 0; parsed_entry.deletions = 0; - _.each(_.omit(log_entry, 'author_name', 'author_email'), (function(_this) { - return function(value, key) { - if (_this.in_range(value.date, date_range)) { - parsed_entry.dates[value.date] = value[field]; - parsed_entry.commits += value.commits; - parsed_entry.additions += value.additions; - return parsed_entry.deletions += value.deletions; - } - }; - })(this)); + _.each( + _.omit(log_entry, 'author_name', 'author_email'), + (function(_this) { + return function(value, key) { + if (_this.in_range(value.date, date_range)) { + parsed_entry.dates[value.date] = value[field]; + parsed_entry.commits += value.commits; + parsed_entry.additions += value.additions; + return (parsed_entry.deletions += value.deletions); + } + }; + })(this), + ); return parsed_entry; }, in_range: function(date, date_range) { @@ -139,5 +145,5 @@ export default { } else { return false; } - } + }, }; diff --git a/app/assets/javascripts/pages/projects/init_blob.js b/app/assets/javascripts/pages/projects/init_blob.js index bc08ccf3584..bd8afa2d5ba 100644 --- a/app/assets/javascripts/pages/projects/init_blob.js +++ b/app/assets/javascripts/pages/projects/init_blob.js @@ -16,7 +16,8 @@ export default () => { ); const fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url'); - const fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href'); + const fileBlobPermalinkUrl = + fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href'); new ShortcutsNavigation(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/pages/projects/init_form.js b/app/assets/javascripts/pages/projects/init_form.js index 9f20a3e4e46..019efe077f7 100644 --- a/app/assets/javascripts/pages/projects/init_form.js +++ b/app/assets/javascripts/pages/projects/init_form.js @@ -1,7 +1,7 @@ import ZenMode from '~/zen_mode'; import GLForm from '~/gl_form'; -export default function ($formEl) { +export default function($formEl) { new ZenMode(); // eslint-disable-line no-new new GLForm($formEl); // eslint-disable-line no-new } diff --git a/app/assets/javascripts/pages/projects/issues/show.js b/app/assets/javascripts/pages/projects/issues/show.js index ef65196872c..8987c8e3f47 100644 --- a/app/assets/javascripts/pages/projects/issues/show.js +++ b/app/assets/javascripts/pages/projects/issues/show.js @@ -5,7 +5,7 @@ import ZenMode from '~/zen_mode'; import '~/notes/index'; import initIssueableApp from '~/issue_show'; -export default function () { +export default function() { initIssueableApp(); new Issue(); // eslint-disable-line no-new new ShortcutsIssuable(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/pages/projects/jobs/show/index.js b/app/assets/javascripts/pages/projects/jobs/show/index.js index 3626f3ffec6..d57dbeb1242 100644 --- a/app/assets/javascripts/pages/projects/jobs/show/index.js +++ b/app/assets/javascripts/pages/projects/jobs/show/index.js @@ -1,3 +1,3 @@ -import initJobDetails from '~/jobs/job_details_bundle'; +import initJobDetails from '~/jobs'; document.addEventListener('DOMContentLoaded', initJobDetails); diff --git a/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue b/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue index 5d2247f6c6d..e8b646f3f6e 100644 --- a/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue +++ b/app/assets/javascripts/pages/projects/labels/components/promote_label_modal.vue @@ -1,72 +1,86 @@ <script> - import _ from 'underscore'; - import axios from '~/lib/utils/axios_utils'; - import createFlash from '~/flash'; - import GlModal from '~/vue_shared/components/gl_modal.vue'; - import { s__, sprintf } from '~/locale'; - import { visitUrl } from '~/lib/utils/url_utility'; - import eventHub from '../event_hub'; +import _ from 'underscore'; +import axios from '~/lib/utils/axios_utils'; +import createFlash from '~/flash'; +import GlModal from '~/vue_shared/components/gl_modal.vue'; +import { s__, sprintf } from '~/locale'; +import { visitUrl } from '~/lib/utils/url_utility'; +import eventHub from '../event_hub'; - export default { - components: { - GlModal, +export default { + components: { + GlModal, + }, + props: { + url: { + type: String, + required: true, }, - props: { - url: { - type: String, - required: true, - }, - labelTitle: { - type: String, - required: true, - }, - labelColor: { - type: String, - required: true, - }, - labelTextColor: { - type: String, - required: true, - }, - groupName: { - type: String, - required: true, - }, + labelTitle: { + type: String, + required: true, }, - computed: { - text() { - return sprintf(s__(`Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. - Existing project labels with the same title will be merged. This action cannot be reversed.`), { + labelColor: { + type: String, + required: true, + }, + labelTextColor: { + type: String, + required: true, + }, + groupName: { + type: String, + required: true, + }, + }, + computed: { + text() { + return sprintf( + s__(`Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. + Existing project labels with the same title will be merged. This action cannot be reversed.`), + { labelTitle: this.labelTitle, groupName: this.groupName, - }); - }, - title() { - const label = `<span + }, + ); + }, + title() { + const label = `<span class="label color-label" style="background-color: ${this.labelColor}; color: ${this.labelTextColor};" >${_.escape(this.labelTitle)}</span>`; - return sprintf(s__('Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>'), { + return sprintf( + s__('Labels|<span>Promote label</span> %{labelTitle} <span>to Group Label?</span>'), + { labelTitle: label, - }, false); - }, + }, + false, + ); }, - methods: { - onSubmit() { - eventHub.$emit('promoteLabelModal.requestStarted', this.url); - return axios.post(this.url, { params: { format: 'json' } }) - .then((response) => { - eventHub.$emit('promoteLabelModal.requestFinished', { labelUrl: this.url, successful: true }); - visitUrl(response.data.url); - }) - .catch((error) => { - eventHub.$emit('promoteLabelModal.requestFinished', { labelUrl: this.url, successful: false }); - createFlash(error); + }, + methods: { + onSubmit() { + eventHub.$emit('promoteLabelModal.requestStarted', this.url); + return axios + .post(this.url, { params: { format: 'json' } }) + .then(response => { + eventHub.$emit('promoteLabelModal.requestFinished', { + labelUrl: this.url, + successful: true, + }); + visitUrl(response.data.url); + }) + .catch(error => { + eventHub.$emit('promoteLabelModal.requestFinished', { + labelUrl: this.url, + successful: false, }); - }, + createFlash(error); + }); }, - }; + }, +}; </script> <template> <gl-modal diff --git a/app/assets/javascripts/pages/projects/labels/index/index.js b/app/assets/javascripts/pages/projects/labels/index/index.js index 03cfef61311..36cf485f33d 100644 --- a/app/assets/javascripts/pages/projects/labels/index/index.js +++ b/app/assets/javascripts/pages/projects/labels/index/index.js @@ -10,20 +10,24 @@ const initLabelIndex = () => { initLabels(); const onRequestFinished = ({ labelUrl, successful }) => { - const button = document.querySelector(`.js-promote-project-label-button[data-url="${labelUrl}"]`); + const button = document.querySelector( + `.js-promote-project-label-button[data-url="${labelUrl}"]`, + ); if (!successful) { button.removeAttribute('disabled'); } }; - const onRequestStarted = (labelUrl) => { - const button = document.querySelector(`.js-promote-project-label-button[data-url="${labelUrl}"]`); + const onRequestStarted = labelUrl => { + const button = document.querySelector( + `.js-promote-project-label-button[data-url="${labelUrl}"]`, + ); button.setAttribute('disabled', ''); eventHub.$once('promoteLabelModal.requestFinished', onRequestFinished); }; - const onDeleteButtonClick = (event) => { + const onDeleteButtonClick = event => { const button = event.currentTarget; const modalProps = { labelTitle: button.dataset.labelTitle, @@ -37,12 +41,12 @@ const initLabelIndex = () => { }; const promoteLabelButtons = document.querySelectorAll('.js-promote-project-label-button'); - promoteLabelButtons.forEach((button) => { + promoteLabelButtons.forEach(button => { button.addEventListener('click', onDeleteButtonClick); }); eventHub.$once('promoteLabelModal.mounted', () => { - promoteLabelButtons.forEach((button) => { + promoteLabelButtons.forEach(button => { button.removeAttribute('disabled'); }); }); diff --git a/app/assets/javascripts/pages/projects/network/network.js b/app/assets/javascripts/pages/projects/network/network.js index 70fbb3f301c..226d63f05c4 100644 --- a/app/assets/javascripts/pages/projects/network/network.js +++ b/app/assets/javascripts/pages/projects/network/network.js @@ -6,13 +6,15 @@ import BranchGraph from '../../../network/branch_graph'; export default (function() { function Network(opts) { var vph; - $("#filter_ref").click(function() { - return $(this).closest('form').submit(); + $('#filter_ref').click(function() { + return $(this) + .closest('form') + .submit(); }); - this.branch_graph = new BranchGraph($(".network-graph"), opts); + this.branch_graph = new BranchGraph($('.network-graph'), opts); vph = $(window).height() - 250; $('.network-graph').css({ - 'height': vph + 'px' + height: vph + 'px', }); } diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js b/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js index 544360dcd51..6197dc8a9db 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/index/index.js @@ -1,12 +1,16 @@ import Vue from 'vue'; import PipelineSchedulesCallout from '../shared/components/pipeline_schedules_callout.vue'; -document.addEventListener('DOMContentLoaded', () => new Vue({ - el: '#pipeline-schedules-callout', - components: { - 'pipeline-schedules-callout': PipelineSchedulesCallout, - }, - render(createElement) { - return createElement('pipeline-schedules-callout'); - }, -})); +document.addEventListener( + 'DOMContentLoaded', + () => + new Vue({ + el: '#pipeline-schedules-callout', + components: { + 'pipeline-schedules-callout': PipelineSchedulesCallout, + }, + render(createElement) { + return createElement('pipeline-schedules-callout'); + }, + }), +); diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue index ef53d67e7cb..ab6f42d928c 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue @@ -1,63 +1,63 @@ <script> - import _ from 'underscore'; +import _ from 'underscore'; - export default { - props: { - initialCronInterval: { - type: String, - required: false, - default: '', - }, - }, - data() { - return { - inputNameAttribute: 'schedule[cron]', - cronInterval: this.initialCronInterval, - cronIntervalPresets: { - everyDay: '0 4 * * *', - everyWeek: '0 4 * * 0', - everyMonth: '0 4 1 * *', - }, - cronSyntaxUrl: 'https://en.wikipedia.org/wiki/Cron', - customInputEnabled: false, - }; +export default { + props: { + initialCronInterval: { + type: String, + required: false, + default: '', }, - computed: { - intervalIsPreset() { - return _.contains(this.cronIntervalPresets, this.cronInterval); - }, - // The text input is editable when there's a custom interval, or when it's - // a preset interval and the user clicks the 'custom' radio button - isEditable() { - return !!(this.customInputEnabled || !this.intervalIsPreset); + }, + data() { + return { + inputNameAttribute: 'schedule[cron]', + cronInterval: this.initialCronInterval, + cronIntervalPresets: { + everyDay: '0 4 * * *', + everyWeek: '0 4 * * 0', + everyMonth: '0 4 1 * *', }, + cronSyntaxUrl: 'https://en.wikipedia.org/wiki/Cron', + customInputEnabled: false, + }; + }, + computed: { + intervalIsPreset() { + return _.contains(this.cronIntervalPresets, this.cronInterval); }, - watch: { - cronInterval() { - // updates field validation state when model changes, as - // glFieldError only updates on input. - this.$nextTick(() => { - gl.pipelineScheduleFieldErrors.updateFormValidityState(); - }); - }, + // The text input is editable when there's a custom interval, or when it's + // a preset interval and the user clicks the 'custom' radio button + isEditable() { + return !!(this.customInputEnabled || !this.intervalIsPreset); }, - created() { - if (this.intervalIsPreset) { - this.enableCustomInput = false; - } + }, + watch: { + cronInterval() { + // updates field validation state when model changes, as + // glFieldError only updates on input. + this.$nextTick(() => { + gl.pipelineScheduleFieldErrors.updateFormValidityState(); + }); }, - methods: { - toggleCustomInput(shouldEnable) { - this.customInputEnabled = shouldEnable; + }, + created() { + if (this.intervalIsPreset) { + this.enableCustomInput = false; + } + }, + methods: { + toggleCustomInput(shouldEnable) { + this.customInputEnabled = shouldEnable; - if (shouldEnable) { - // We need to change the value so other radios don't remain selected - // because the model (cronInterval) hasn't changed. The server trims it. - this.cronInterval = `${this.cronInterval} `; - } - }, + if (shouldEnable) { + // We need to change the value so other radios don't remain selected + // because the model (cronInterval) hasn't changed. The server trims it. + this.cronInterval = `${this.cronInterval} `; + } }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue index 77508e62cef..33fc2420e4d 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue @@ -1,31 +1,31 @@ <script> - import Vue from 'vue'; - import Cookies from 'js-cookie'; - import Translate from '../../../../../vue_shared/translate'; - import illustrationSvg from '../icons/intro_illustration.svg'; +import Vue from 'vue'; +import Cookies from 'js-cookie'; +import Translate from '../../../../../vue_shared/translate'; +import illustrationSvg from '../icons/intro_illustration.svg'; - Vue.use(Translate); +Vue.use(Translate); - const cookieKey = 'pipeline_schedules_callout_dismissed'; +const cookieKey = 'pipeline_schedules_callout_dismissed'; - export default { - name: 'PipelineSchedulesCallout', - data() { - return { - docsUrl: document.getElementById('pipeline-schedules-callout').dataset.docsUrl, - calloutDismissed: Cookies.get(cookieKey) === 'true', - }; +export default { + name: 'PipelineSchedulesCallout', + data() { + return { + docsUrl: document.getElementById('pipeline-schedules-callout').dataset.docsUrl, + calloutDismissed: Cookies.get(cookieKey) === 'true', + }; + }, + created() { + this.illustrationSvg = illustrationSvg; + }, + methods: { + dismissCallout() { + this.calloutDismissed = true; + Cookies.set(cookieKey, this.calloutDismissed, { expires: 365 }); }, - created() { - this.illustrationSvg = illustrationSvg; - }, - methods: { - dismissCallout() { - this.calloutDismissed = true; - Cookies.set(cookieKey, this.calloutDismissed, { expires: 365 }); - }, - }, - }; + }, +}; </script> <template> <div diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js index 4ef0d11dd36..0057700c1b3 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js @@ -26,8 +26,7 @@ export default class TargetBranchDropdown { } formatBranchesList() { - return this.$dropdown.data('data') - .map(val => ({ name: val })); + return this.$dropdown.data('data').map(val => ({ name: val })); } setDropdownToggle() { diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js index c3ac54733a3..4d494efef6c 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/init_form.js @@ -11,7 +11,9 @@ Vue.use(Translate); function initIntervalPatternInput() { const intervalPatternMount = document.getElementById('interval-pattern-input'); - const initialCronInterval = intervalPatternMount ? intervalPatternMount.dataset.initialInterval : ''; + const initialCronInterval = intervalPatternMount + ? intervalPatternMount.dataset.initialInterval + : ''; return new Vue({ el: intervalPatternMount, diff --git a/app/assets/javascripts/pages/projects/pipelines/charts/index.js b/app/assets/javascripts/pages/projects/pipelines/charts/index.js index 07b6992eba1..48353f3b4ef 100644 --- a/app/assets/javascripts/pages/projects/pipelines/charts/index.js +++ b/app/assets/javascripts/pages/projects/pipelines/charts/index.js @@ -7,26 +7,29 @@ const options = { maintainAspectRatio: false, }; -const buildChart = (chartScope) => { +const buildChart = chartScope => { const data = { labels: chartScope.labels, - datasets: [{ - fillColor: '#707070', - strokeColor: '#707070', - pointColor: '#707070', - pointStrokeColor: '#EEE', - data: chartScope.totalValues, - }, - { - fillColor: '#1aaa55', - strokeColor: '#1aaa55', - pointColor: '#1aaa55', - pointStrokeColor: '#fff', - data: chartScope.successValues, - }, + datasets: [ + { + fillColor: '#707070', + strokeColor: '#707070', + pointColor: '#707070', + pointStrokeColor: '#EEE', + data: chartScope.totalValues, + }, + { + fillColor: '#1aaa55', + strokeColor: '#1aaa55', + pointColor: '#1aaa55', + pointStrokeColor: '#fff', + data: chartScope.successValues, + }, ], }; - const ctx = $(`#${chartScope.scope}Chart`).get(0).getContext('2d'); + const ctx = $(`#${chartScope.scope}Chart`) + .get(0) + .getContext('2d'); new Chart(ctx).Line(data, options); }; @@ -36,14 +39,16 @@ document.addEventListener('DOMContentLoaded', () => { const chartsData = JSON.parse(document.getElementById('pipelinesChartsData').innerHTML); const data = { labels: chartTimesData.labels, - datasets: [{ - fillColor: 'rgba(220,220,220,0.5)', - strokeColor: 'rgba(220,220,220,1)', - barStrokeWidth: 1, - barValueSpacing: 1, - barDatasetSpacing: 1, - data: chartTimesData.values, - }], + datasets: [ + { + fillColor: 'rgba(220,220,220,0.5)', + strokeColor: 'rgba(220,220,220,1)', + barStrokeWidth: 1, + barValueSpacing: 1, + barDatasetSpacing: 1, + data: chartTimesData.values, + }, + ], }; if (window.innerWidth < 768) { @@ -51,7 +56,11 @@ document.addEventListener('DOMContentLoaded', () => { options.scaleFontSize = 8; } - new Chart($('#build_timesChart').get(0).getContext('2d')).Bar(data, options); + new Chart( + $('#build_timesChart') + .get(0) + .getContext('2d'), + ).Bar(data, options); chartsData.forEach(scope => buildChart(scope)); }); diff --git a/app/assets/javascripts/pages/projects/pipelines/index/index.js b/app/assets/javascripts/pages/projects/pipelines/index/index.js index a84e2790680..fc337a7609b 100644 --- a/app/assets/javascripts/pages/projects/pipelines/index/index.js +++ b/app/assets/javascripts/pages/projects/pipelines/index/index.js @@ -6,35 +6,39 @@ import { convertPermissionToBoolean } from '../../../../lib/utils/common_utils'; Vue.use(Translate); -document.addEventListener('DOMContentLoaded', () => new Vue({ - el: '#pipelines-list-vue', - components: { - pipelinesComponent, - }, - data() { - return { - store: new PipelinesStore(), - }; - }, - created() { - this.dataset = document.querySelector(this.$options.el).dataset; - }, - render(createElement) { - return createElement('pipelines-component', { - props: { - store: this.store, - endpoint: this.dataset.endpoint, - helpPagePath: this.dataset.helpPagePath, - emptyStateSvgPath: this.dataset.emptyStateSvgPath, - errorStateSvgPath: this.dataset.errorStateSvgPath, - noPipelinesSvgPath: this.dataset.noPipelinesSvgPath, - autoDevopsPath: this.dataset.helpAutoDevopsPath, - newPipelinePath: this.dataset.newPipelinePath, - canCreatePipeline: convertPermissionToBoolean(this.dataset.canCreatePipeline), - hasGitlabCi: convertPermissionToBoolean(this.dataset.hasGitlabCi), - ciLintPath: this.dataset.ciLintPath, - resetCachePath: this.dataset.resetCachePath, +document.addEventListener( + 'DOMContentLoaded', + () => + new Vue({ + el: '#pipelines-list-vue', + components: { + pipelinesComponent, }, - }); - }, -})); + data() { + return { + store: new PipelinesStore(), + }; + }, + created() { + this.dataset = document.querySelector(this.$options.el).dataset; + }, + render(createElement) { + return createElement('pipelines-component', { + props: { + store: this.store, + endpoint: this.dataset.endpoint, + helpPagePath: this.dataset.helpPagePath, + emptyStateSvgPath: this.dataset.emptyStateSvgPath, + errorStateSvgPath: this.dataset.errorStateSvgPath, + noPipelinesSvgPath: this.dataset.noPipelinesSvgPath, + autoDevopsPath: this.dataset.helpAutoDevopsPath, + newPipelinePath: this.dataset.newPipelinePath, + canCreatePipeline: convertPermissionToBoolean(this.dataset.canCreatePipeline), + hasGitlabCi: convertPermissionToBoolean(this.dataset.hasGitlabCi), + ciLintPath: this.dataset.ciLintPath, + resetCachePath: this.dataset.resetCachePath, + }, + }); + }, + }), +); diff --git a/app/assets/javascripts/pages/projects/pipelines/init_pipelines.js b/app/assets/javascripts/pages/projects/pipelines/init_pipelines.js index 94dfeb96e8c..ba4ae04ab3d 100644 --- a/app/assets/javascripts/pages/projects/pipelines/init_pipelines.js +++ b/app/assets/javascripts/pages/projects/pipelines/init_pipelines.js @@ -2,9 +2,12 @@ import Pipelines from '~/pipelines'; export default () => { const { controllerAction } = document.querySelector('.js-pipeline-container').dataset; - const pipelineStatusUrl = `${document.querySelector('.js-pipeline-tab-link a').getAttribute('href')}/status.json`; + const pipelineStatusUrl = `${document + .querySelector('.js-pipeline-tab-link a') + .getAttribute('href')}/status.json`; - new Pipelines({ // eslint-disable-line no-new + // eslint-disable-next-line no-new + new Pipelines({ initTabs: true, pipelineStatusUrl, tabsOptions: { diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue index 06101290f6c..dced839c883 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/project_feature_setting.vue @@ -1,73 +1,71 @@ <script> - import projectFeatureToggle from '../../../../../vue_shared/components/toggle_button.vue'; +import projectFeatureToggle from '../../../../../vue_shared/components/toggle_button.vue'; - export default { - components: { - projectFeatureToggle, - }, +export default { + components: { + projectFeatureToggle, + }, - model: { - prop: 'value', - event: 'change', - }, + model: { + prop: 'value', + event: 'change', + }, - props: { - name: { - type: String, - required: false, - default: '', - }, - options: { - type: Array, - required: false, - default: () => [], - }, - value: { - type: Number, - required: false, - default: 0, - }, - disabledInput: { - type: Boolean, - required: false, - default: false, - }, + props: { + name: { + type: String, + required: false, + default: '', + }, + options: { + type: Array, + required: false, + default: () => [], + }, + value: { + type: Number, + required: false, + default: 0, }, + disabledInput: { + type: Boolean, + required: false, + default: false, + }, + }, - computed: { - featureEnabled() { - return this.value !== 0; - }, + computed: { + featureEnabled() { + return this.value !== 0; + }, - displayOptions() { - if (this.featureEnabled) { - return this.options; - } - return [ - [0, 'Enable feature to choose access level'], - ]; - }, + displayOptions() { + if (this.featureEnabled) { + return this.options; + } + return [[0, 'Enable feature to choose access level']]; + }, - displaySelectInput() { - return this.disabledInput || !this.featureEnabled || this.displayOptions.length < 2; - }, + displaySelectInput() { + return this.disabledInput || !this.featureEnabled || this.displayOptions.length < 2; }, + }, - methods: { - toggleFeature(featureEnabled) { - if (featureEnabled === false || this.options.length < 1) { - this.$emit('change', 0); - } else { - const [firstOptionValue] = this.options[this.options.length - 1]; - this.$emit('change', firstOptionValue); - } - }, + methods: { + toggleFeature(featureEnabled) { + if (featureEnabled === false || this.options.length < 1) { + this.$emit('change', 0); + } else { + const [firstOptionValue] = this.options[this.options.length - 1]; + this.$emit('change', firstOptionValue); + } + }, - selectOption(e) { - this.$emit('change', Number(e.target.value)); - }, + selectOption(e) { + this.$emit('change', Number(e.target.value)); }, - }; + }, +}; </script> <template> diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue index 83437363af5..898d605463f 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/project_setting_row.vue @@ -1,23 +1,23 @@ <script> - export default { - props: { - label: { - type: String, - required: false, - default: null, - }, - helpPath: { - type: String, - required: false, - default: null, - }, - helpText: { - type: String, - required: false, - default: null, - }, +export default { + props: { + label: { + type: String, + required: false, + default: null, }, - }; + helpPath: { + type: String, + required: false, + default: null, + }, + helpText: { + type: String, + required: false, + default: null, + }, + }, +}; </script> <template> diff --git a/app/assets/javascripts/pages/projects/shared/permissions/constants.js b/app/assets/javascripts/pages/projects/shared/permissions/constants.js index ce47562f259..bc5c29d12b5 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/constants.js +++ b/app/assets/javascripts/pages/projects/shared/permissions/constants.js @@ -5,7 +5,9 @@ export const visibilityOptions = { }; export const visibilityLevelDescriptions = { - [visibilityOptions.PRIVATE]: 'The project is accessible only by members of the project. Access must be granted explicitly to each user.', + [visibilityOptions.PRIVATE]: + 'The project is accessible only by members of the project. Access must be granted explicitly to each user.', [visibilityOptions.INTERNAL]: 'The project can be accessed by any user who is logged in.', - [visibilityOptions.PUBLIC]: 'The project can be accessed by anyone, regardless of authentication.', + [visibilityOptions.PUBLIC]: + 'The project can be accessed by anyone, regardless of authentication.', }; diff --git a/app/assets/javascripts/pages/projects/shared/project_avatar.js b/app/assets/javascripts/pages/projects/shared/project_avatar.js index 447877752fe..1e69ecb481d 100644 --- a/app/assets/javascripts/pages/projects/shared/project_avatar.js +++ b/app/assets/javascripts/pages/projects/shared/project_avatar.js @@ -8,8 +8,9 @@ export default function projectAvatar() { $('.js-project-avatar-input').bind('change', function onClickAvatarInput() { const form = $(this).closest('form'); - // eslint-disable-next-line no-useless-escape - const filename = $(this).val().replace(/^.*[\\\/]/, ''); + const filename = $(this) + .val() + .replace(/^.*[\\\/]/, ''); // eslint-disable-line no-useless-escape return form.find('.js-avatar-filename').text(filename); }); } diff --git a/app/assets/javascripts/pages/projects/wikis/index.js b/app/assets/javascripts/pages/projects/wikis/index.js index c2629090f01..f5fd84d69ac 100644 --- a/app/assets/javascripts/pages/projects/wikis/index.js +++ b/app/assets/javascripts/pages/projects/wikis/index.js @@ -21,7 +21,8 @@ document.addEventListener('DOMContentLoaded', () => { const { deleteWikiUrl, pageTitle } = deleteWikiModalWrapperEl.dataset; - new Vue({ // eslint-disable-line no-new + // eslint-disable-next-line no-new + new Vue({ el: deleteWikiModalWrapperEl, data: { deleteWikiUrl: '', diff --git a/app/assets/javascripts/pages/search/show/search.js b/app/assets/javascripts/pages/search/show/search.js index e3e0ab91993..0c896c8599e 100644 --- a/app/assets/javascripts/pages/search/show/search.js +++ b/app/assets/javascripts/pages/search/show/search.js @@ -22,7 +22,7 @@ export default class Search { fields: ['full_name'], }, data(term, callback) { - return Api.groups(term, {}, (data) => { + return Api.groups(term, {}, data => { data.unshift({ full_name: 'Any', }); @@ -37,7 +37,7 @@ export default class Search { return obj.full_name; }, toggleLabel(obj) { - return `${($groupDropdown.data('defaultLabel'))} ${obj.full_name}`; + return `${$groupDropdown.data('defaultLabel')} ${obj.full_name}`; }, clicked: () => Search.submitSearch(), }); @@ -52,7 +52,7 @@ export default class Search { }, data: (term, callback) => { this.getProjectsData(term) - .then((data) => { + .then(data => { data.unshift({ name_with_namespace: 'Any', }); @@ -70,7 +70,7 @@ export default class Search { return obj.name_with_namespace; }, toggleLabel(obj) { - return `${($projectDropdown.data('defaultLabel'))} ${obj.name_with_namespace}`; + return `${$projectDropdown.data('defaultLabel')} ${obj.name_with_namespace}`; }, clicked: () => Search.submitSearch(), }); @@ -99,17 +99,24 @@ export default class Search { } clearSearchField() { - return $(this.searchInput).val('').trigger('keyup').focus(); + return $(this.searchInput) + .val('') + .trigger('keyup') + .focus(); } getProjectsData(term) { - return new Promise((resolve) => { + return new Promise(resolve => { if (this.groupId) { Api.groupProjects(this.groupId, term, {}, resolve); } else { - Api.projects(term, { - order_by: 'id', - }, resolve); + Api.projects( + term, + { + order_by: 'id', + }, + resolve, + ); } }); } diff --git a/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js b/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js index 1e7c29aefaa..2b8f1e8b0ef 100644 --- a/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js +++ b/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js @@ -20,7 +20,7 @@ export default class SigninTabsMemoizer { bootstrap() { const tabs = document.querySelectorAll(this.tabSelector); if (tabs.length > 0) { - tabs[0].addEventListener('click', (e) => { + tabs[0].addEventListener('click', e => { if (e.target && e.target.nodeName === 'A') { const anchorName = e.target.getAttribute('href'); this.saveData(anchorName); diff --git a/app/assets/javascripts/pages/sessions/new/username_validator.js b/app/assets/javascripts/pages/sessions/new/username_validator.js index d621f988d86..7a41805bada 100644 --- a/app/assets/javascripts/pages/sessions/new/username_validator.js +++ b/app/assets/javascripts/pages/sessions/new/username_validator.js @@ -22,10 +22,10 @@ export default class UsernameValidator { available: false, valid: false, pending: false, - empty: true + empty: true, }; - const debounceTimeout = _.debounce((username) => { + const debounceTimeout = _.debounce(username => { this.validateUsername(username); }, debounceTimeoutDuration); @@ -81,7 +81,8 @@ export default class UsernameValidator { this.state.pending = true; this.state.available = false; this.renderState(); - axios.get(`${gon.relative_url_root}/users/${username}/exists`) + axios + .get(`${gon.relative_url_root}/users/${username}/exists`) .then(({ data }) => this.setAvailabilityState(data.exists)) .catch(() => flash(__('An error occurred while validating username'))); } @@ -100,8 +101,7 @@ export default class UsernameValidator { clearFieldValidationState() { this.inputElement.siblings('p').hide(); - this.inputElement.removeClass(invalidInputClass) - .removeClass(successInputClass); + this.inputElement.removeClass(invalidInputClass).removeClass(successInputClass); } setUnavailableState() { diff --git a/app/assets/javascripts/pages/users/index.js b/app/assets/javascripts/pages/users/index.js index 6b1626b0161..a191df00dfa 100644 --- a/app/assets/javascripts/pages/users/index.js +++ b/app/assets/javascripts/pages/users/index.js @@ -13,10 +13,12 @@ function initUserProfile(action) { new UserTabs({ parentEl: '.user-profile', action }); // hide project limit message - $('.hide-project-limit-message').on('click', (e) => { + $('.hide-project-limit-message').on('click', e => { e.preventDefault(); Cookies.set('hide_project_limit_message', 'false'); - $(this).parents('.project-limit-message').remove(); + $(this) + .parents('.project-limit-message') + .remove(); }); } diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js index 1de9945baad..04bcb16f036 100644 --- a/app/assets/javascripts/pages/users/user_tabs.js +++ b/app/assets/javascripts/pages/users/user_tabs.js @@ -170,7 +170,7 @@ export default class UserTabs { this.loadActivityCalendar('activity'); // eslint-disable-next-line no-new - new Activities(); + new Activities('#activity'); this.loaded.activity = true; } diff --git a/app/assets/javascripts/pdf/index.vue b/app/assets/javascripts/pdf/index.vue index 2f480ecdc69..7b079fe02d9 100644 --- a/app/assets/javascripts/pdf/index.vue +++ b/app/assets/javascripts/pdf/index.vue @@ -1,57 +1,58 @@ <script> - import pdfjsLib from 'vendor/pdf'; - import workerSrc from 'vendor/pdf.worker.min'; +import pdfjsLib from 'vendor/pdf'; +import workerSrc from 'vendor/pdf.worker.min'; - import page from './page/index.vue'; +import page from './page/index.vue'; - export default { - components: { page }, - props: { - pdf: { - type: [String, Uint8Array], - required: true, - }, +export default { + components: { page }, + props: { + pdf: { + type: [String, Uint8Array], + required: true, }, - data() { - return { - loading: false, - pages: [], - }; + }, + data() { + return { + loading: false, + pages: [], + }; + }, + computed: { + document() { + return typeof this.pdf === 'string' ? this.pdf : { data: this.pdf }; }, - computed: { - document() { - return typeof this.pdf === 'string' ? this.pdf : { data: this.pdf }; - }, - hasPDF() { - return this.pdf && this.pdf.length > 0; - }, + hasPDF() { + return this.pdf && this.pdf.length > 0; }, - watch: { pdf: 'load' }, - mounted() { - pdfjsLib.PDFJS.workerSrc = workerSrc; - if (this.hasPDF) this.load(); + }, + watch: { pdf: 'load' }, + mounted() { + pdfjsLib.PDFJS.workerSrc = workerSrc; + if (this.hasPDF) this.load(); + }, + methods: { + load() { + this.pages = []; + return pdfjsLib + .getDocument(this.document) + .then(this.renderPages) + .then(() => this.$emit('pdflabload')) + .catch(error => this.$emit('pdflaberror', error)) + .then(() => { + this.loading = false; + }); }, - methods: { - load() { - this.pages = []; - return pdfjsLib.getDocument(this.document) - .then(this.renderPages) - .then(() => this.$emit('pdflabload')) - .catch(error => this.$emit('pdflaberror', error)) - .then(() => { this.loading = false; }); - }, - renderPages(pdf) { - const pagePromises = []; - this.loading = true; - for (let num = 1; num <= pdf.numPages; num += 1) { - pagePromises.push( - pdf.getPage(num).then(p => this.pages.push(p)), - ); - } - return Promise.all(pagePromises); - }, + renderPages(pdf) { + const pagePromises = []; + this.loading = true; + for (let num = 1; num <= pdf.numPages; num += 1) { + pagePromises.push(pdf.getPage(num).then(p => this.pages.push(p))); + } + return Promise.all(pagePromises); }, - }; + }, +}; </script> <template> @@ -69,9 +70,9 @@ </template> <style> - .pdf-viewer { - background: url('./assets/img/bg.gif'); - display: flex; - flex-flow: column nowrap; - } +.pdf-viewer { + background: url('./assets/img/bg.gif'); + display: flex; + flex-flow: column nowrap; +} </style> diff --git a/app/assets/javascripts/pdf/page/index.vue b/app/assets/javascripts/pdf/page/index.vue index 9f06833d560..96aadf41653 100644 --- a/app/assets/javascripts/pdf/page/index.vue +++ b/app/assets/javascripts/pdf/page/index.vue @@ -1,44 +1,47 @@ <script> - export default { - props: { - page: { - type: Object, - required: true, - }, - number: { - type: Number, - required: true, - }, +export default { + props: { + page: { + type: Object, + required: true, }, - data() { - return { - scale: 4, - rendering: false, - }; + number: { + type: Number, + required: true, + }, + }, + data() { + return { + scale: 4, + rendering: false, + }; + }, + computed: { + viewport() { + return this.page.getViewport(this.scale); }, - computed: { - viewport() { - return this.page.getViewport(this.scale); - }, - context() { - return this.$refs.canvas.getContext('2d'); - }, - renderContext() { - return { - canvasContext: this.context, - viewport: this.viewport, - }; - }, + context() { + return this.$refs.canvas.getContext('2d'); }, - mounted() { - this.$refs.canvas.height = this.viewport.height; - this.$refs.canvas.width = this.viewport.width; - this.rendering = true; - this.page.render(this.renderContext) - .then(() => { this.rendering = false; }) - .catch(error => this.$emit('pdflaberror', error)); + renderContext() { + return { + canvasContext: this.context, + viewport: this.viewport, + }; }, - }; + }, + mounted() { + this.$refs.canvas.height = this.viewport.height; + this.$refs.canvas.width = this.viewport.width; + this.rendering = true; + this.page + .render(this.renderContext) + .then(() => { + this.rendering = false; + }) + .catch(error => this.$emit('pdflaberror', error)); + }, +}; </script> <template> @@ -51,20 +54,20 @@ </template> <style> - .pdf-page { - margin: 8px auto 0 auto; - border-top: 1px #ddd solid; - border-bottom: 1px #ddd solid; - width: 100%; - } +.pdf-page { + margin: 8px auto 0 auto; + border-top: 1px #ddd solid; + border-bottom: 1px #ddd solid; + width: 100%; +} - .pdf-page:first-child { - margin-top: 0px; - border-top: 0px; - } +.pdf-page:first-child { + margin-top: 0px; + border-top: 0px; +} - .pdf-page:last-child { - margin-bottom: 0px; - border-bottom: 0px; - } +.pdf-page:last-child { + margin-bottom: 0px; + border-bottom: 0px; +} </style> diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue index 9b4ba0c1a9a..23c0be7742e 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue @@ -70,7 +70,7 @@ export default { v-for="(stage, index) in graph" :key="stage.name" :title="capitalizeStageName(stage.name)" - :jobs="stage.groups" + :groups="stage.groups" :stage-connector-class="stageConnectorClass(index, stage)" :is-first-column="isFirstColumn(index)" @refreshPipelineGraph="refreshPipelineGraph" diff --git a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue index 2ad66f4fe86..34bada533df 100644 --- a/app/assets/javascripts/pipelines/components/graph/dropdown_job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue @@ -1,31 +1,14 @@ <script> import $ from 'jquery'; -import JobNameComponent from './job_name_component.vue'; -import JobComponent from './job_component.vue'; +import CiIcon from '~/vue_shared/components/ci_icon.vue'; +import JobItem from './job_item.vue'; import tooltip from '../../../vue_shared/directives/tooltip'; /** * Renders the dropdown for the pipeline graph. * - * The following object should be provided as `job`: + * The object provided as `group` corresponds to app/serializers/job_group_entity.rb. * - * { - * "id": 4256, - * "name": "test", - * "status": { - * "icon": "status_success", - * "text": "passed", - * "label": "passed", - * "group": "success", - * "details_path": "/root/ci-mock/builds/4256", - * "action": { - * "icon": "retry", - * "title": "Retry", - * "path": "/root/ci-mock/builds/4256/retry", - * "method": "post" - * } - * } - * } */ export default { directives: { @@ -33,12 +16,12 @@ export default { }, components: { - JobComponent, - JobNameComponent, + JobItem, + CiIcon, }, props: { - job: { + group: { type: Object, required: true, }, @@ -46,7 +29,8 @@ export default { computed: { tooltipText() { - return `${this.job.name} - ${this.job.status.label}`; + const { name, status } = this.group; + return `${name} - ${status.label}`; }, }, @@ -56,7 +40,7 @@ export default { methods: { /** - * When the user right clicks or cmd/ctrl + click in the job name or the action icon + * When the user right clicks or cmd/ctrl + click in the group name or the action icon * the dropdown should not be closed so we stop propagation * of the click event inside the dropdown. * @@ -90,14 +74,14 @@ export default { data-display="static" class="dropdown-menu-toggle build-content" > + <ci-icon :status="group.status" /> - <job-name-component - :name="job.name" - :status="job.status" - /> + <span class="ci-status-text"> + {{ group.name }} + </span> <span class="dropdown-counter-badge"> - {{ job.size }} + {{ group.size }} </span> </button> @@ -105,12 +89,12 @@ export default { <li class="scrollable-menu"> <ul> <li - v-for="(item, i) in job.jobs" - :key="i" + v-for="job in group.jobs" + :key="job.id" > - <job-component - :dropdown-length="job.size" - :job="item" + <job-item + :dropdown-length="group.size" + :job="job" css-class-job-name="mini-pipeline-graph-dropdown-item" @pipelineActionRequestComplete="pipelineActionRequestComplete" /> diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_item.vue index a1504592bbc..a1504592bbc 100644 --- a/app/assets/javascripts/pipelines/components/graph/job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/job_item.vue diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue index 567ea119343..efbab51d200 100644 --- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue @@ -1,12 +1,12 @@ <script> import _ from 'underscore'; -import JobComponent from './job_component.vue'; -import DropdownJobComponent from './dropdown_job_component.vue'; +import JobItem from './job_item.vue'; +import JobGroupDropdown from './job_group_dropdown.vue'; export default { components: { - JobComponent, - DropdownJobComponent, + JobItem, + JobGroupDropdown, }, props: { title: { @@ -14,7 +14,7 @@ export default { required: true, }, - jobs: { + groups: { type: Array, required: true, }, @@ -33,12 +33,8 @@ export default { }, methods: { - firstJob(list) { - return list[0]; - }, - - jobId(job) { - return `ci-badge-${_.escape(job.name)}`; + groupId(group) { + return `ci-badge-${_.escape(group.name)}`; }, buildConnnectorClass(index) { @@ -61,25 +57,25 @@ export default { <div class="builds-container"> <ul> <li - v-for="(job, index) in jobs" - :id="jobId(job)" - :key="job.id" + v-for="(group, index) in groups" + :id="groupId(group)" + :key="group.id" :class="buildConnnectorClass(index)" class="build" > <div class="curve"></div> - <job-component - v-if="job.size === 1" - :job="job" + <job-item + v-if="group.size === 1" + :job="group.jobs[0]" css-class-job-name="build-content" @pipelineActionRequestComplete="pipelineActionRequestComplete" /> - <dropdown-job-component - v-if="job.size > 1" - :job="job" + <job-group-dropdown + v-if="group.size > 1" + :group="group" @pipelineActionRequestComplete="pipelineActionRequestComplete" /> diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue index b03438ddba1..4f89ee66023 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue @@ -64,10 +64,7 @@ export default { return []; } const { details } = this.pipeline; - return [ - ...(details.manual_actions || []), - ...(details.scheduled_actions || []), - ]; + return [...(details.manual_actions || []), ...(details.scheduled_actions || [])]; }, /** * If provided, returns the commit tag. diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue index 47c15b1a9c4..7ec55792850 100644 --- a/app/assets/javascripts/pipelines/components/stage.vue +++ b/app/assets/javascripts/pipelines/components/stage.vue @@ -18,14 +18,14 @@ import Flash from '../../flash'; import axios from '../../lib/utils/axios_utils'; import eventHub from '../event_hub'; import Icon from '../../vue_shared/components/icon.vue'; -import JobComponent from './graph/job_component.vue'; +import JobItem from './graph/job_item.vue'; import tooltip from '../../vue_shared/directives/tooltip'; import { PIPELINES_TABLE } from '../constants'; export default { components: { Icon, - JobComponent, + JobItem, }, directives: { @@ -198,7 +198,7 @@ export default { v-for="job in dropdownContent" :key="job.id" > - <job-component + <job-item :dropdown-length="dropdownContent.length" :job="job" css-class-job-name="mini-pipeline-graph-dropdown-item" diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js index 12cfa7de316..60d3d83a4b2 100644 --- a/app/assets/javascripts/project_find_file.js +++ b/app/assets/javascripts/project_find_file.js @@ -10,14 +10,14 @@ import { __ } from '~/locale'; const highlighter = function(element, text, matches) { var highlightText, j, lastIndex, len, matchIndex, matchedChars, unmatched; lastIndex = 0; - highlightText = ""; + highlightText = ''; matchedChars = []; for (j = 0, len = matches.length; j < len; j += 1) { matchIndex = matches[j]; unmatched = text.substring(lastIndex, matchIndex); if (unmatched) { if (matchedChars.length) { - element.append(matchedChars.join("").bold()); + element.append(matchedChars.join('').bold()); } matchedChars = []; element.append(document.createTextNode(unmatched)); @@ -26,7 +26,7 @@ const highlighter = function(element, text, matches) { lastIndex = matchIndex + 1; } if (matchedChars.length) { - element.append(matchedChars.join("").bold()); + element.append(matchedChars.join('').bold()); } return element.append(document.createTextNode(text.substring(lastIndex))); }; @@ -40,7 +40,7 @@ export default class ProjectFindFile { this.selectRowDown = this.selectRowDown.bind(this); this.selectRowUp = this.selectRowUp.bind(this); this.filePaths = {}; - this.inputElement = this.element.find(".file-finder-input"); + this.inputElement = this.element.find('.file-finder-input'); // init event this.initEvent(); // focus text input box @@ -50,38 +50,51 @@ export default class ProjectFindFile { } initEvent() { - this.inputElement.off("keyup"); - this.inputElement.on("keyup", (function(_this) { - return function(event) { - var oldValue, ref, target, value; - target = $(event.target); - value = target.val(); - oldValue = (ref = target.data("oldValue")) != null ? ref : ""; - if (value !== oldValue) { - target.data("oldValue", value); - _this.findFile(); - return _this.element.find("tr.tree-item").eq(0).addClass("selected").focus(); - } - }; - })(this)); + this.inputElement.off('keyup'); + this.inputElement.on( + 'keyup', + (function(_this) { + return function(event) { + var oldValue, ref, target, value; + target = $(event.target); + value = target.val(); + oldValue = (ref = target.data('oldValue')) != null ? ref : ''; + if (value !== oldValue) { + target.data('oldValue', value); + _this.findFile(); + return _this.element + .find('tr.tree-item') + .eq(0) + .addClass('selected') + .focus(); + } + }; + })(this), + ); } findFile() { var result, searchText; 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); - // find file + // find file } // files pathes load load(url) { - axios.get(url) + axios + .get(url) .then(({ data }) => { this.element.find('.loading').hide(); this.filePaths = data; 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'))); } @@ -89,7 +102,7 @@ export default class ProjectFindFile { // render result renderList(filePaths, searchText) { var blobItemUrl, filePath, html, i, len, matches, results; - this.element.find(".tree-table > tbody").empty(); + this.element.find('.tree-table > tbody').empty(); results = []; for (i = 0, len = filePaths.length; i < len; i += 1) { @@ -100,9 +113,9 @@ export default class ProjectFindFile { if (searchText) { matches = fuzzaldrinPlus.match(filePath, searchText); } - blobItemUrl = this.options.blobUrlTemplate + "/" + filePath; + blobItemUrl = this.options.blobUrlTemplate + '/' + filePath; 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; } @@ -110,52 +123,56 @@ export default class ProjectFindFile { // make tbody row html static makeHtml(filePath, matches, blobItemUrl) { 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) { - $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 { - $tr.find("a").attr("href", blobItemUrl); - $tr.find(".str-truncated").text(filePath); + $tr.find('a').attr('href', blobItemUrl); + $tr.find('.str-truncated').text(filePath); } return $tr; } selectRow(type) { var next, rows, selectedRow; - rows = this.element.find(".files-slider tr.tree-item"); - selectedRow = this.element.find(".files-slider tr.tree-item.selected"); + rows = this.element.find('.files-slider tr.tree-item'); + selectedRow = this.element.find('.files-slider tr.tree-item.selected'); if (rows && rows.length > 0) { if (selectedRow && selectedRow.length > 0) { - if (type === "UP") { + if (type === 'UP') { next = selectedRow.prev(); - } else if (type === "DOWN") { + } else if (type === 'DOWN') { next = selectedRow.next(); } if (next.length > 0) { - selectedRow.removeClass("selected"); + selectedRow.removeClass('selected'); selectedRow = next; } } else { selectedRow = rows.eq(0); } - return selectedRow.addClass("selected").focus(); + return selectedRow.addClass('selected').focus(); } } selectRowUp() { - return this.selectRow("UP"); + return this.selectRow('UP'); } selectRowDown() { - return this.selectRow("DOWN"); + return this.selectRow('DOWN'); } goToTree() { - return window.location.href = this.options.treeUrl; + return (window.location.href = this.options.treeUrl); } 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) { $link.get(0).click(); diff --git a/app/assets/javascripts/project_import.js b/app/assets/javascripts/project_import.js index 5a0d2b642eb..a51a2a2242f 100644 --- a/app/assets/javascripts/project_import.js +++ b/app/assets/javascripts/project_import.js @@ -5,4 +5,3 @@ export default function projectImport() { visitUrl(window.location.href); }, 5000); } - diff --git a/app/assets/javascripts/project_label_subscription.js b/app/assets/javascripts/project_label_subscription.js index 9049f87e037..d3c604dcee1 100644 --- a/app/assets/javascripts/project_label_subscription.js +++ b/app/assets/javascripts/project_label_subscription.js @@ -31,32 +31,35 @@ export default class ProjectLabelSubscription { $btn.addClass('disabled'); - axios.post(url).then(() => { - let newStatus; - let newAction; + axios + .post(url) + .then(() => { + let newStatus; + let newAction; - if (oldStatus === 'unsubscribed') { - [newStatus, newAction] = ['subscribed', 'Unsubscribe']; - } else { - [newStatus, newAction] = ['unsubscribed', 'Subscribe']; - } + if (oldStatus === 'unsubscribed') { + [newStatus, newAction] = ['subscribed', 'Unsubscribe']; + } else { + [newStatus, newAction] = ['unsubscribed', 'Subscribe']; + } - $btn.removeClass('disabled'); + $btn.removeClass('disabled'); - this.$buttons.attr('data-status', newStatus); - this.$buttons.find('> span').text(newAction); + this.$buttons.attr('data-status', newStatus); + this.$buttons.find('> span').text(newAction); - this.$buttons.map((i, button) => { - const $button = $(button); - const originalTitle = $button.attr('data-original-title'); + this.$buttons.map((i, button) => { + const $button = $(button); + const originalTitle = $button.attr('data-original-title'); - if (originalTitle) { - ProjectLabelSubscription.setNewTitle($button, originalTitle, newStatus, newAction); - } + if (originalTitle) { + ProjectLabelSubscription.setNewTitle($button, originalTitle, newStatus, newAction); + } - return button; - }); - }).catch(() => flash(__('There was an error subscribing to this label.'))); + return button; + }); + }) + .catch(() => flash(__('There was an error subscribing to this label.'))); } static setNewTitle($button, originalTitle, newStatus) { diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js index eaaeda8b339..f1fff173619 100644 --- a/app/assets/javascripts/project_select.js +++ b/app/assets/javascripts/project_select.js @@ -16,28 +16,28 @@ export default function projectSelect() { this.withMergeRequestsEnabled = $(select).data('withMergeRequestsEnabled'); this.allowClear = $(select).data('allowClear') || false; - placeholder = "Search for project"; + placeholder = 'Search for project'; if (this.includeGroups) { - placeholder += " or group"; + placeholder += ' or group'; } $(select).select2({ placeholder: placeholder, minimumInputLength: 0, - query: (function (_this) { - return function (query) { + query: (function(_this) { + return function(query) { var finalCallback, projectsCallback; - finalCallback = function (projects) { + finalCallback = function(projects) { var data; data = { - results: projects + results: projects, }; return query.callback(data); }; if (_this.includeGroups) { - projectsCallback = function (projects) { + projectsCallback = function(projects) { var groupsCallback; - groupsCallback = function (groups) { + groupsCallback = function(groups) { var data; data = groups.concat(projects); return finalCallback(data); @@ -48,17 +48,26 @@ export default function projectSelect() { projectsCallback = finalCallback; } if (_this.groupId) { - return Api.groupProjects(_this.groupId, query.term, { - with_issues_enabled: _this.withIssuesEnabled, - with_merge_requests_enabled: _this.withMergeRequestsEnabled, - }, projectsCallback); + return Api.groupProjects( + _this.groupId, + query.term, + { + with_issues_enabled: _this.withIssuesEnabled, + with_merge_requests_enabled: _this.withMergeRequestsEnabled, + }, + projectsCallback, + ); } else { - return Api.projects(query.term, { - order_by: _this.orderBy, - with_issues_enabled: _this.withIssuesEnabled, - with_merge_requests_enabled: _this.withMergeRequestsEnabled, - membership: !_this.allProjects, - }, projectsCallback); + return Api.projects( + query.term, + { + order_by: _this.orderBy, + with_issues_enabled: _this.withIssuesEnabled, + with_merge_requests_enabled: _this.withMergeRequestsEnabled, + membership: !_this.allProjects, + }, + projectsCallback, + ); } }; })(this), @@ -69,7 +78,7 @@ export default function projectSelect() { url: project.web_url, }); }, - text: function (project) { + text: function(project) { return project.name_with_namespace || project.name; }, @@ -79,7 +88,7 @@ export default function projectSelect() { allowClear: this.allowClear, - dropdownCssClass: "ajax-project-dropdown" + dropdownCssClass: 'ajax-project-dropdown', }); if (simpleFilter) return select; return new ProjectSelectComboButton(select); diff --git a/app/assets/javascripts/project_select_combo_button.js b/app/assets/javascripts/project_select_combo_button.js index 9b404896e86..3dbac3ff942 100644 --- a/app/assets/javascripts/project_select_combo_button.js +++ b/app/assets/javascripts/project_select_combo_button.js @@ -14,10 +14,11 @@ export default class ProjectSelectComboButton { } bindEvents() { - this.projectSelectInput.siblings('.new-project-item-select-button') + this.projectSelectInput + .siblings('.new-project-item-select-button') .on('click', e => this.openDropdown(e)); - this.newItemBtn.on('click', (e) => { + this.newItemBtn.on('click', e => { if (!this.getProjectFromLocalStorage()) { e.preventDefault(); this.openDropdown(e); @@ -31,14 +32,21 @@ export default class ProjectSelectComboButton { const localStorageIsSafe = AccessorUtilities.isLocalStorageAccessSafe(); 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(); } } // eslint-disable-next-line class-methods-use-this openDropdown(event) { - $(event.currentTarget).siblings('.project-item-select').select2('open'); + $(event.currentTarget) + .siblings('.project-item-select') + .select2('open'); } selectProject() { @@ -86,8 +94,14 @@ export default class ProjectSelectComboButton { const defaultTextPrefix = this.resourceLabel; // 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 presetTextSuffix = this.resourceType.split('_').join(' ').slice(0, -1); + const localStorageItemType = `new-${this.resourceType + .split('_') + .join('-') + .slice(0, -1)}`; + const presetTextSuffix = this.resourceType + .split('_') + .join(' ') + .slice(0, -1); return { localStorageItemType, // new-issue / new-merge-request @@ -96,4 +110,3 @@ export default class ProjectSelectComboButton { }; } } - diff --git a/app/assets/javascripts/project_visibility.js b/app/assets/javascripts/project_visibility.js index a52ac768e57..aaf6723c85c 100644 --- a/app/assets/javascripts/project_visibility.js +++ b/app/assets/javascripts/project_visibility.js @@ -7,7 +7,7 @@ function setVisibilityOptions(namespaceSelector) { const selectedNamespace = namespaceSelector.options[namespaceSelector.selectedIndex]; 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 optionValue = optionInput ? optionInput.value : 0; const optionTitle = option.querySelector('.option-title'); @@ -20,8 +20,7 @@ function setVisibilityOptions(namespaceSelector) { optionInput.disabled = true; const reason = option.querySelector('.option-disabled-reason'); if (reason) { - reason.innerHTML = - `This project cannot be ${optionName} because the visibility of + reason.innerHTML = `This project cannot be ${optionName} because the visibility of <a href="${showPath}">${name}</a> is ${visibility}. To make this project ${optionName}, you must first <a href="${editPath}">change the visibility</a> of the parent group.`; diff --git a/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js b/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js index 078ccbbbac2..8380cfb6c59 100644 --- a/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js +++ b/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js @@ -65,10 +65,14 @@ export default class PrometheusMetrics { let totalMissingEnvVarMetrics = 0; let totalExporters = 0; - metrics.forEach((metric) => { + metrics.forEach(metric => { if (metric.active_metrics > 0) { 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; if (metric.metrics_missing_requirements > 0) { this.$missingEnvVarMetricsList.append(`<li>${_.escape(metric.group)}</li>`); @@ -78,17 +82,26 @@ export default class PrometheusMetrics { }); if (totalMonitoredMetrics === 0) { - const emptyCommonMetricsText = sprintf(s__('PrometheusService|<p class="text-tertiary">No <a href="%{docsUrl}">common metrics</a> were found</p>'), { - docsUrl: this.helpMetricsPath, - }, false); + const emptyCommonMetricsText = sprintf( + s__( + 'PrometheusService|<p class="text-tertiary">No <a href="%{docsUrl}">common metrics</a> were found</p>', + ), + { + docsUrl: this.helpMetricsPath, + }, + false, + ); this.$monitoredMetricsEmpty.empty(); this.$monitoredMetricsEmpty.append(emptyCommonMetricsText); this.showMonitoringMetricsPanelState(PANEL_STATE.EMPTY); } else { - const metricsCountText = sprintf(s__('PrometheusService|%{exporters} with %{metrics} were found'), { - exporters: n__('%d exporter', '%d exporters', totalExporters), - metrics: n__('%d metric', '%d metrics', totalMonitoredMetrics), - }); + const metricsCountText = sprintf( + s__('PrometheusService|%{exporters} with %{metrics} were found'), + { + exporters: n__('%d exporter', '%d exporters', totalExporters), + metrics: n__('%d metric', '%d metrics', totalMonitoredMetrics), + }, + ); this.$monitoredMetricsCount.text(metricsCountText); this.showMonitoringMetricsPanelState(PANEL_STATE.LIST); @@ -102,7 +115,8 @@ export default class PrometheusMetrics { loadActiveMetrics() { this.showMonitoringMetricsPanelState(PANEL_STATE.LOADING); backOff((next, stop) => { - axios.get(this.activeMetricsEndpoint) + axios + .get(this.activeMetricsEndpoint) .then(({ data }) => { if (data && data.success) { stop(data); @@ -117,7 +131,7 @@ export default class PrometheusMetrics { }) .catch(stop); }) - .then((res) => { + .then(res => { if (res && res.data && res.data.length) { this.populateActiveMetrics(res.data); } else { diff --git a/app/assets/javascripts/raven/raven_config.js b/app/assets/javascripts/raven/raven_config.js index 658caeecde1..338006ce2b9 100644 --- a/app/assets/javascripts/raven/raven_config.js +++ b/app/assets/javascripts/raven/raven_config.js @@ -9,7 +9,7 @@ const IGNORE_ERRORS = [ 'canvas.contentDocument', 'MyApp_RemoveAllHighlights', 'http://tt.epicplay.com', - 'Can\'t find variable: ZiteReader', + "Can't find variable: ZiteReader", 'jigsaw is not defined', 'ComboSearch is not defined', 'http://loading.retry.widdit.com/', diff --git a/app/assets/javascripts/ref_select_dropdown.js b/app/assets/javascripts/ref_select_dropdown.js index 95c5cf7b345..75bac035aca 100644 --- a/app/assets/javascripts/ref_select_dropdown.js +++ b/app/assets/javascripts/ref_select_dropdown.js @@ -2,7 +2,8 @@ import $ from 'jquery'; class RefSelectDropdown { constructor($dropdownButton, availableRefs) { - const availableRefsValue = availableRefs || JSON.parse(document.getElementById('availableRefs').innerHTML); + const availableRefsValue = + availableRefs || JSON.parse(document.getElementById('availableRefs').innerHTML); $dropdownButton.glDropdown({ data: availableRefsValue, filterable: true, @@ -29,7 +30,7 @@ class RefSelectDropdown { const $fieldInput = $(`input[name="${$dropdownButton.data('fieldName')}"]`, $dropdownContainer); const $filterInput = $('input[type="search"]', $dropdownContainer); - $filterInput.on('keyup', (e) => { + $filterInput.on('keyup', e => { const keyCode = e.keyCode || e.which; if (keyCode !== 13) return; diff --git a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue index 9f3a41180cc..bd204503cc7 100644 --- a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue +++ b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue @@ -75,7 +75,7 @@ export default { :loading-text="groupedSummaryText" :error-text="groupedSummaryText" :has-issues="reports.length > 0" - class="mr-widget-border-top grouped-security-reports mr-report" + class="mr-widget-section grouped-security-reports mr-report" > <div slot="body" diff --git a/app/assets/javascripts/settings_panels.js b/app/assets/javascripts/settings_panels.js index b38421bdf1c..d22aca35e09 100644 --- a/app/assets/javascripts/settings_panels.js +++ b/app/assets/javascripts/settings_panels.js @@ -3,10 +3,14 @@ import { __ } from './locale'; function expandSection($section) { $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'); if (!$section.hasClass('no-animate')) { - $section.addClass('animating') + $section + .addClass('animating') .one('animationend.animateSection', () => $section.removeClass('animating')); } } @@ -16,7 +20,8 @@ function closeSection($section) { $section.find('.settings-content').on('scroll.expandSection', () => expandSection($section)); $section.removeClass('expanded'); if (!$section.hasClass('no-animate')) { - $section.addClass('animating') + $section + .addClass('animating') .one('animationend.animateSection', () => $section.removeClass('animating')); } } diff --git a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue index a7bffa81045..259858e4b46 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue @@ -1,6 +1,6 @@ <script> import { __, sprintf } from '~/locale'; -import { abbreviateTime } from '~/lib/utils/pretty_time'; +import { abbreviateTime } from '~/lib/utils/datetime_utility'; import icon from '~/vue_shared/components/icon.vue'; import tooltip from '~/vue_shared/directives/tooltip'; diff --git a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue index dc599e1b9fc..e74912d628f 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/comparison_pane.vue @@ -1,5 +1,5 @@ <script> -import { parseSeconds, stringifyTime } from '../../../lib/utils/pretty_time'; +import { parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility'; import tooltip from '../../../vue_shared/directives/tooltip'; export default { diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js index 99c93952e2a..f2b9d75dd00 100644 --- a/app/assets/javascripts/single_file_diff.js +++ b/app/assets/javascripts/single_file_diff.js @@ -10,8 +10,10 @@ import syntaxHighlight from './syntax_highlight'; const WRAPPER = '<div class="diff-content"></div>'; 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 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>'; +const ERROR_HTML = + '<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 { constructor(file) { @@ -23,23 +25,36 @@ export default class SingleFileDiff { this.isOpen = !this.diffForPath; if (this.diffForPath) { 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.collapsedContent.after(this.loadingContent); this.$toggleIcon.addClass('fa-caret-right'); } else { - this.collapsedContent = $(WRAPPER).html(COLLAPSED_HTML).hide(); + this.collapsedContent = $(WRAPPER) + .html(COLLAPSED_HTML) + .hide(); this.content.after(this.collapsedContent); this.$toggleIcon.addClass('fa-caret-down'); } - $('.js-file-title, .click-to-expand', this.file).on('click', (function (e) { - this.toggleDiff($(e.target)); - }).bind(this)); + $('.js-file-title, .click-to-expand', this.file).on( + 'click', + function(e) { + this.toggleDiff($(e.target)); + }.bind(this), + ); } 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; if (!this.isOpen && !this.hasError) { this.content.hide(); @@ -65,7 +80,8 @@ export default class SingleFileDiff { this.collapsedContent.hide(); this.loadingContent.show(); - axios.get(this.diffForPath) + axios + .get(this.diffForPath) .then(({ data }) => { this.loadingContent.hide(); if (data.html) { diff --git a/app/assets/javascripts/smart_interval.js b/app/assets/javascripts/smart_interval.js index 5e385400747..8ca590123ae 100644 --- a/app/assets/javascripts/smart_interval.js +++ b/app/assets/javascripts/smart_interval.js @@ -93,7 +93,9 @@ export default class SmartInterval { destroy() { this.cancel(); document.removeEventListener('visibilitychange', this.handleVisibilityChange); - $(document).off('visibilitychange').off('beforeunload'); + $(document) + .off('visibilitychange') + .off('beforeunload'); } /* private */ @@ -111,11 +113,12 @@ export default class SmartInterval { triggerCallback() { this.isLoading = true; - this.cfg.callback() + this.cfg + .callback() .then(() => { this.isLoading = false; }) - .catch((err) => { + .catch(err => { this.isLoading = false; throw err; }); @@ -134,9 +137,9 @@ export default class SmartInterval { handleVisibilityChange(e) { this.state.pageVisibility = e.target.visibilityState; - const intervalAction = this.isPageVisible() ? - this.onVisibilityVisible : - this.onVisibilityHidden; + const intervalAction = this.isPageVisible() + ? this.onVisibilityVisible + : this.onVisibilityHidden; intervalAction.apply(this); } @@ -162,7 +165,9 @@ export default class SmartInterval { this.setCurrentInterval(nextInterval); } - isPageVisible() { return this.state.pageVisibility === 'visible'; } + isPageVisible() { + return this.state.pageVisibility === 'visible'; + } stopTimer() { const { state } = this; @@ -170,4 +175,3 @@ export default class SmartInterval { state.intervalId = window.clearInterval(state.intervalId); } } - diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js index f5a7fdae5d7..007b83e1927 100644 --- a/app/assets/javascripts/star.js +++ b/app/assets/javascripts/star.js @@ -11,10 +11,14 @@ export default class Star { const $starSpan = $this.find('span'); const $startIcon = $this.find('svg'); - axios.post($this.data('endpoint')) + axios + .post($this.data('endpoint')) .then(({ data }) => { 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) { $starSpan.removeClass('starred').text(s__('StarProject|Star')); diff --git a/app/assets/javascripts/task_list.js b/app/assets/javascripts/task_list.js index 48782e63b9b..edefb3735d7 100644 --- a/app/assets/javascripts/task_list.js +++ b/app/assets/javascripts/task_list.js @@ -26,7 +26,11 @@ export default class TaskList { // Prevent duplicate event bindings this.disable(); $(`${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() { @@ -41,7 +45,8 @@ export default class TaskList { [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)) .catch(err => this.onError(err)); } diff --git a/app/assets/javascripts/templates/issuable_template_selector.js b/app/assets/javascripts/templates/issuable_template_selector.js index 74166313940..6065770e68d 100644 --- a/app/assets/javascripts/templates/issuable_template_selector.js +++ b/app/assets/javascripts/templates/issuable_template_selector.js @@ -31,12 +31,18 @@ export default class IssuableTemplateSelector extends TemplateSelector { requestFile(query) { this.startLoadingSpinner(); - Api.issueTemplate(this.namespacePath, this.projectPath, query.name, this.issuableType, (err, currentTemplate) => { - this.currentTemplate = currentTemplate; - this.stopLoadingSpinner(); - if (err) return; // Error handled by global AJAX error handler - this.setInputValueToTemplateContent(); - }); + Api.issueTemplate( + this.namespacePath, + this.projectPath, + query.name, + this.issuableType, + (err, currentTemplate) => { + this.currentTemplate = currentTemplate; + this.stopLoadingSpinner(); + if (err) return; // Error handled by global AJAX error handler + this.setInputValueToTemplateContent(); + }, + ); return; } diff --git a/app/assets/javascripts/terminal/terminal.js b/app/assets/javascripts/terminal/terminal.js index 74c5bbe45a4..b24aa8a3a34 100644 --- a/app/assets/javascripts/terminal/terminal.js +++ b/app/assets/javascripts/terminal/terminal.js @@ -4,10 +4,14 @@ import * as fit from 'xterm/lib/addons/fit/fit'; export default class GLTerminal { constructor(options = {}) { - this.options = Object.assign({}, { - cursorBlink: true, - screenKeys: true, - }, options); + this.options = Object.assign( + {}, + { + cursorBlink: true, + screenKeys: true, + }, + options, + ); this.container = document.querySelector(options.selector); diff --git a/app/assets/javascripts/test_utils/simulate_drag.js b/app/assets/javascripts/test_utils/simulate_drag.js index a5c18042ce7..be9ebc81c6b 100644 --- a/app/assets/javascripts/test_utils/simulate_drag.js +++ b/app/assets/javascripts/test_utils/simulate_drag.js @@ -4,15 +4,43 @@ function simulateEvent(el, type, options = {}) { if (/^mouse/.test(type)) { event = el.ownerDocument.createEvent('MouseEvents'); - event.initMouseEvent(type, 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.initMouseEvent( + type, + 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 { event = el.ownerDocument.createEvent('CustomEvent'); - event.initCustomEvent(type, 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.initCustomEvent( + type, + 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 = { data: {}, @@ -37,14 +65,16 @@ function simulateEvent(el, type, options = {}) { } 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; return children.length - 1 === target.index; } 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; return ( @@ -58,13 +88,13 @@ function getTarget(target) { function getRect(el) { const rect = el.getBoundingClientRect(); const width = rect.right - rect.left; - const height = (rect.bottom - rect.top) + 10; + const height = rect.bottom - rect.top + 10; return { x: rect.left, y: rect.top, - cx: rect.left + (width / 2), - cy: rect.top + (height / 2), + cx: rect.left + width / 2, + cy: rect.top + height / 2, w: width, h: height, hw: width / 2, @@ -112,8 +142,8 @@ export default function simulateDrag(options) { const dragInterval = setInterval(() => { const progress = (new Date().getTime() - startTime) / duration; - const x = (fromRect.cx + ((toRect.cx - fromRect.cx) * progress)); - const y = (fromRect.cy + ((toRect.cy - fromRect.cy) * progress)); + const x = fromRect.cx + (toRect.cx - fromRect.cx) * progress; + const y = fromRect.cy + (toRect.cy - fromRect.cy) * progress; const overEl = fromEl.ownerDocument.elementFromPoint(x, y); simulateEvent(overEl, 'mousemove', { diff --git a/app/assets/javascripts/test_utils/simulate_input.js b/app/assets/javascripts/test_utils/simulate_input.js index 90c1b7cb57e..c300c806e6d 100644 --- a/app/assets/javascripts/test_utils/simulate_input.js +++ b/app/assets/javascripts/test_utils/simulate_input.js @@ -12,7 +12,7 @@ export default function simulateInput(target, text) { } if (text.length > 0) { - Array.prototype.forEach.call(text, (char) => { + Array.prototype.forEach.call(text, char => { input.value += char; triggerEvents(input); }); diff --git a/app/assets/javascripts/toggle_buttons.js b/app/assets/javascripts/toggle_buttons.js index 199b14458ed..d83ffc7e211 100644 --- a/app/assets/javascripts/toggle_buttons.js +++ b/app/assets/javascripts/toggle_buttons.js @@ -49,7 +49,7 @@ function onToggleClicked(toggle, input, clickCallback) { export default function setupToggleButtons(container, clickCallback = () => {}) { const toggles = container.querySelectorAll('.js-project-feature-toggle'); - toggles.forEach((toggle) => { + toggles.forEach(toggle => { const input = toggle.querySelector('.js-project-feature-toggle-input'); const isOn = convertPermissionToBoolean(input.value); diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js index 066fd6278a7..3e659c9e7ea 100644 --- a/app/assets/javascripts/tree.js +++ b/app/assets/javascripts/tree.js @@ -8,7 +8,7 @@ export default class TreeView { this.initKeyNav(); // Code browser tree slider // 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; $clickedEl = $(e.target); path = $('.tree-item-file-name a', this).attr('href'); @@ -27,33 +27,33 @@ export default class TreeView { initKeyNav() { var li, liSelected; - li = $("tr.tree-item"); + li = $('tr.tree-item'); liSelected = null; return $('body').keydown(function(e) { 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; } if (e.which === 40) { if (liSelected) { next = liSelected.next(); if (next.length > 0) { - liSelected.removeClass("selected"); - liSelected = next.addClass("selected"); + liSelected.removeClass('selected'); + liSelected = next.addClass('selected'); } } else { - liSelected = li.eq(0).addClass("selected"); + liSelected = li.eq(0).addClass('selected'); } return $(liSelected).focus(); } else if (e.which === 38) { if (liSelected) { next = liSelected.prev(); if (next.length > 0) { - liSelected.removeClass("selected"); - liSelected = next.addClass("selected"); + liSelected.removeClass('selected'); + liSelected = next.addClass('selected'); } } else { - liSelected = li.last().addClass("selected"); + liSelected = li.last().addClass('selected'); } return $(liSelected).focus(); } else if (e.which === 13) { diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js index 78fd7ad441f..abfc81e681e 100644 --- a/app/assets/javascripts/u2f/authenticate.js +++ b/app/assets/javascripts/u2f/authenticate.js @@ -49,7 +49,7 @@ export default class U2FAuthenticate { start() { return importU2FLibrary() - .then((utils) => { + .then(utils => { this.u2fUtils = utils; this.renderInProgress(); }) @@ -57,14 +57,19 @@ export default class U2FAuthenticate { } authenticate() { - return this.u2fUtils.sign(this.appId, this.challenge, this.signRequests, - (response) => { + return this.u2fUtils.sign( + this.appId, + this.challenge, + this.signRequests, + response => { if (response.errorCode) { const error = new U2FError(response.errorCode, 'authenticate'); return this.renderError(error); } return this.renderAuthenticated(JSON.stringify(response)); - }, 10); + }, + 10, + ); } renderTemplate(name, params) { @@ -99,5 +104,4 @@ export default class U2FAuthenticate { this.container[0].classList.add('hidden'); this.fallbackUI.classList.remove('hidden'); } - } diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js index 01e259a741d..43c814c8070 100644 --- a/app/assets/javascripts/u2f/register.js +++ b/app/assets/javascripts/u2f/register.js @@ -34,7 +34,7 @@ export default class U2FRegister { start() { return importU2FLibrary() - .then((utils) => { + .then(utils => { this.u2fUtils = utils; this.renderSetup(); }) @@ -42,14 +42,19 @@ export default class U2FRegister { } register() { - return this.u2fUtils.register(this.appId, this.registerRequests, this.signRequests, - (response) => { + return this.u2fUtils.register( + this.appId, + this.registerRequests, + this.signRequests, + response => { if (response.errorCode) { const error = new U2FError(response.errorCode, 'register'); return this.renderError(error); } return this.renderRegistered(JSON.stringify(response)); - }, 10); + }, + 10, + ); } renderTemplate(name, params) { diff --git a/app/assets/javascripts/u2f/util.js b/app/assets/javascripts/u2f/util.js index 5778f00332d..b706481c02f 100644 --- a/app/assets/javascripts/u2f/util.js +++ b/app/assets/javascripts/u2f/util.js @@ -19,11 +19,10 @@ function getChromeVersion(userAgent) { export function canInjectU2fApi(userAgent) { const isSupportedChrome = isChrome(userAgent) && getChromeVersion(userAgent) >= 41; const isSupportedOpera = isOpera(userAgent) && getOperaVersion(userAgent) >= 40; - const isMobile = ( + const isMobile = userAgent.indexOf('droid') >= 0 || userAgent.indexOf('CriOS') >= 0 || - /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent) - ); + /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent); return (isSupportedChrome || isSupportedOpera) && !isMobile; } diff --git a/app/assets/javascripts/ui_development_kit.js b/app/assets/javascripts/ui_development_kit.js index 9b242ea779d..be18ac5da24 100644 --- a/app/assets/javascripts/ui_development_kit.js +++ b/app/assets/javascripts/ui_development_kit.js @@ -4,13 +4,17 @@ import Api from './api'; export default () => { $('#js-project-dropdown').glDropdown({ data: (term, callback) => { - Api.projects(term, { - order_by: 'last_activity_at', - }, (data) => { - callback(data); - }); + Api.projects( + term, + { + 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, fieldName: 'author_id', filterable: true, @@ -18,6 +22,6 @@ export default () => { fields: ['name_with_namespace'], }, id: data => data.id, - isSelected: data => (data.id === 2), + isSelected: data => data.id === 2, }); }; diff --git a/app/assets/javascripts/usage_ping_consent.js b/app/assets/javascripts/usage_ping_consent.js index ae3fde190e3..05607f09a7e 100644 --- a/app/assets/javascripts/usage_ping_consent.js +++ b/app/assets/javascripts/usage_ping_consent.js @@ -4,7 +4,7 @@ import Flash, { hideFlash } from './flash'; import { convertPermissionToBoolean } from './lib/utils/common_utils'; export default () => { - $('body').on('click', '.js-usage-consent-action', (e) => { + $('body').on('click', '.js-usage-consent-action', e => { e.preventDefault(); e.stopImmediatePropagation(); // overwrite rails listener @@ -18,7 +18,8 @@ export default () => { const hideConsentMessage = () => hideFlash(document.querySelector('.ping-consent-message')); - axios.put(url, data) + axios + .put(url, data) .then(() => { hideConsentMessage(); }) diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index e2259a8d07b..4b090212d83 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -15,8 +15,8 @@ function UsersSelect(currentUser, els, options = {}) { var $els; this.users = this.users.bind(this); this.user = this.user.bind(this); - this.usersPath = "/autocomplete/users.json"; - this.userPath = "/autocomplete/users/:id.json"; + this.usersPath = '/autocomplete/users.json'; + this.userPath = '/autocomplete/users/:id.json'; if (currentUser != null) { if (typeof currentUser === 'object') { this.currentUser = currentUser; @@ -33,156 +33,180 @@ function UsersSelect(currentUser, els, options = {}) { $els = $('.js-user-search'); } - $els.each((function(_this) { - return function(i, dropdown) { - var options = {}; - var $block, $collapsedSidebar, $dropdown, $loading, $selectbox, $value, abilityName, assignTo, assigneeTemplate, collapsedAssigneeTemplate, defaultLabel, defaultNullUser, firstUser, issueURL, selectedId, selectedIdDefault, showAnyUser, showNullUser, showMenuAbove; - $dropdown = $(dropdown); - options.projectId = $dropdown.data('projectId'); - options.groupId = $dropdown.data('groupId'); - options.showCurrentUser = $dropdown.data('currentUser'); - options.todoFilter = $dropdown.data('todoFilter'); - options.todoStateFilter = $dropdown.data('todoStateFilter'); - showNullUser = $dropdown.data('nullUser'); - defaultNullUser = $dropdown.data('nullUserDefault'); - showMenuAbove = $dropdown.data('showMenuAbove'); - showAnyUser = $dropdown.data('anyUser'); - firstUser = $dropdown.data('firstUser'); - options.authorId = $dropdown.data('authorId'); - defaultLabel = $dropdown.data('defaultLabel'); - 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; - } - - const assignYourself = function () { - const unassignedSelected = $dropdown.closest('.selectbox') - .find(`input[name='${$dropdown.data('fieldName')}'][value=0]`); - - if (unassignedSelected) { - unassignedSelected.remove(); + $els.each( + (function(_this) { + return function(i, dropdown) { + var options = {}; + var $block, + $collapsedSidebar, + $dropdown, + $loading, + $selectbox, + $value, + abilityName, + assignTo, + assigneeTemplate, + collapsedAssigneeTemplate, + defaultLabel, + defaultNullUser, + firstUser, + issueURL, + selectedId, + selectedIdDefault, + showAnyUser, + showNullUser, + showMenuAbove; + $dropdown = $(dropdown); + options.projectId = $dropdown.data('projectId'); + options.groupId = $dropdown.data('groupId'); + options.showCurrentUser = $dropdown.data('currentUser'); + options.todoFilter = $dropdown.data('todoFilter'); + options.todoStateFilter = $dropdown.data('todoStateFilter'); + showNullUser = $dropdown.data('nullUser'); + defaultNullUser = $dropdown.data('nullUserDefault'); + showMenuAbove = $dropdown.data('showMenuAbove'); + showAnyUser = $dropdown.data('anyUser'); + firstUser = $dropdown.data('firstUser'); + options.authorId = $dropdown.data('authorId'); + defaultLabel = $dropdown.data('defaultLabel'); + 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 input = document.createElement('input'); - input.type = 'hidden'; - input.name = $dropdown.data('fieldName'); + const assignYourself = function() { + const unassignedSelected = $dropdown + .closest('.selectbox') + .find(`input[name='${$dropdown.data('fieldName')}'][value=0]`); - const currentUserInfo = $dropdown.data('currentUserInfo'); - - 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')}"]`); - }; + if (unassignedSelected) { + unassignedSelected.remove(); + } - const getSelected = function() { - return getSelectedUserInputs() - .map((index, input) => parseInt(input.value, 10)) - .get(); - }; + // Save current selected user to the DOM + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = $dropdown.data('fieldName'); - const checkMaxSelect = function() { - const maxSelect = $dropdown.data('maxSelect'); - if (maxSelect) { - const selected = getSelected(); + const currentUserInfo = $dropdown.data('currentUserInfo'); - if (selected.length > maxSelect) { - const firstSelectedId = selected[0]; - const firstSelected = $dropdown.closest('.selectbox') - .find(`input[name='${$dropdown.data('fieldName')}'][value=${firstSelectedId}]`); + if (currentUserInfo) { + input.value = currentUserInfo.id; + input.dataset.meta = _.escape(currentUserInfo.name); + } else if (_this.currentUser) { + input.value = _this.currentUser.id; + } - firstSelected.remove(); - emitSidebarEvent('sidebar.removeAssignee', { - id: firstSelectedId, - }); + if ($selectbox) { + $dropdown.parent().before(input); + } else { + $dropdown.after(input); } - } - }; + }; - 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 ($block[0]) { + $block[0].addEventListener('assignYourself', assignYourself); } - }; - $('.assign-to-me-link').on('click', (e) => { - e.preventDefault(); - $(e.currentTarget).hide(); + const getSelectedUserInputs = function() { + return $selectbox.find(`input[name="${$dropdown.data('fieldName')}"]`); + }; + + 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')) { - assignYourself(); - checkMaxSelect(); + $('.assign-to-me-link').on('click', e => { + e.preventDefault(); + $(e.currentTarget).hide(); - const currentUserInfo = $dropdown.data('currentUserInfo'); - $dropdown.find('.dropdown-toggle-text').text(getMultiSelectDropdownTitle(currentUserInfo)).removeClass('is-default'); - } else { - const $input = $(`input[name="${$dropdown.data('fieldName')}"]`); - $input.val(gon.current_user_id); - selectedId = $input.val(); - $dropdown.find('.dropdown-toggle-text').text(gon.current_user_fullname).removeClass('is-default'); - } - }); - - $block.on('click', '.js-assign-yourself', (e) => { - e.preventDefault(); - return assignTo(_this.currentUser.id); - }); - - 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 }) => { + if ($dropdown.data('multiSelect')) { + assignYourself(); + checkMaxSelect(); + + const currentUserInfo = $dropdown.data('currentUserInfo'); + $dropdown + .find('.dropdown-toggle-text') + .text(getMultiSelectDropdownTitle(currentUserInfo)) + .removeClass('is-default'); + } else { + const $input = $(`input[name="${$dropdown.data('fieldName')}"]`); + $input.val(gon.current_user_id); + selectedId = $input.val(); + $dropdown + .find('.dropdown-toggle-text') + .text(gon.current_user_fullname) + .removeClass('is-default'); + } + }); + + $block.on('click', '.js-assign-yourself', e => { + e.preventDefault(); + return assignTo(_this.currentUser.id); + }); + + 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; $dropdown.trigger('loaded.gl.dropdown'); $loading.fadeOut(); @@ -190,14 +214,14 @@ function UsersSelect(currentUser, els, options = {}) { user = { name: data.assignee.name, username: data.assignee.username, - avatar: data.assignee.avatar_url + avatar: data.assignee.avatar_url, }; tooltipTitle = _.escape(user.name); } else { user = { name: 'Unassigned', username: '', - avatar: '' + avatar: '', }; tooltipTitle = __('Assignee'); } @@ -205,319 +229,341 @@ function UsersSelect(currentUser, els, options = {}) { $collapsedSidebar.attr('title', tooltipTitle).tooltip('_fixTitle'); 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> <% } %>'); - 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> <% } %>'); - return $dropdown.glDropdown({ - showMenuAbove: showMenuAbove, - data: function(term, callback) { - return _this.users(term, options, function(users) { - // GitLabDropdownFilter returns this.instance - // GitLabDropdownRemote returns this.options.instance - const glDropdown = this.instance || this.options.instance; - glDropdown.options.processData(term, users, callback); - }.bind(this)); - }, - processData: function(term, data, callback) { - let users = data; - - // Only show assigned user list when there is no search term - if ($dropdown.hasClass('js-multiselect') && term.length === 0) { - const selectedInputs = getSelectedUserInputs(); - - // Potential duplicate entries when dealing with issue board - // because issue board is also managed by vue - const selectedUsers = _.uniq(selectedInputs, false, a => a.value) - .filter((input) => { - const userId = parseInt(input.value, 10); - const inUsersArray = users.find(u => u.id === userId); - - return !inUsersArray && userId !== 0; - }) - .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, - }; - }); + }; + 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> <% } %>', + ); + 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> <% } %>', + ); + return $dropdown.glDropdown({ + showMenuAbove: showMenuAbove, + data: function(term, callback) { + return _this.users( + term, + options, + function(users) { + // GitLabDropdownFilter returns this.instance + // GitLabDropdownRemote returns this.options.instance + const glDropdown = this.instance || this.options.instance; + glDropdown.options.processData(term, users, callback); + }.bind(this), + ); + }, + processData: function(term, data, callback) { + let users = data; + + // Only show assigned user list when there is no search term + if ($dropdown.hasClass('js-multiselect') && term.length === 0) { + const selectedInputs = getSelectedUserInputs(); + + // Potential duplicate entries when dealing with issue board + // because issue board is also managed by vue + const selectedUsers = _.uniq(selectedInputs, false, a => a.value) + .filter(input => { + const userId = parseInt(input.value, 10); + const inUsersArray = users.find(u => u.id === userId); + + return !inUsersArray && userId !== 0; + }) + .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 index; - let len; - let name; - let obj; - let showDivider; - if (term.length === 0) { - showDivider = 0; - if (firstUser) { - // Move current user to the front of the list - for (index = 0, len = users.length; index < len; index += 1) { - obj = users[index]; - if (obj.username === firstUser) { - users.splice(index, 1); - users.unshift(obj); - break; + let anyUser; + let index; + let len; + let name; + let obj; + let showDivider; + if (term.length === 0) { + showDivider = 0; + if (firstUser) { + // Move current user to the front of the list + for (index = 0, len = users.length; index < len; index += 1) { + obj = users[index]; + if (obj.username === firstUser) { + users.splice(index, 1); + users.unshift(obj); + break; + } } } - } - if (showNullUser) { - showDivider += 1; - users.unshift({ - beforeDivider: true, - name: 'Unassigned', - id: 0 - }); - } - if (showAnyUser) { - showDivider += 1; - name = showAnyUser; - if (name === true) { - name = 'Any User'; + if (showNullUser) { + showDivider += 1; + users.unshift({ + beforeDivider: true, + name: 'Unassigned', + id: 0, + }); + } + if (showAnyUser) { + showDivider += 1; + name = showAnyUser; + if (name === true) { + 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) { - users.splice(showDivider, 0, 'divider'); - } + if (showDivider) { + users.splice(showDivider, 0, 'divider'); + } - if ($dropdown.hasClass('js-multiselect')) { - const selected = getSelected().filter(i => i !== 0); + if ($dropdown.hasClass('js-multiselect')) { + const selected = getSelected().filter(i => i !== 0); - if (selected.length > 0) { - if ($dropdown.data('dropdownHeader')) { - showDivider += 1; - users.splice(showDivider, 0, { - header: $dropdown.data('dropdownHeader'), - }); - } + if (selected.length > 0) { + if ($dropdown.data('dropdownHeader')) { + showDivider += 1; + users.splice(showDivider, 0, { + header: $dropdown.data('dropdownHeader'), + }); + } - const selectedUsers = users - .filter(u => selected.indexOf(u.id) !== -1) - .sort((a, b) => a.name > b.name); + const selectedUsers = users + .filter(u => selected.indexOf(u.id) !== -1) + .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) => { - showDivider += 1; - users.splice(showDivider, 0, selectedUser); - }); + selectedUsers.forEach(selectedUser => { + showDivider += 1; + users.splice(showDivider, 0, selectedUser); + }); - users.splice(showDivider + 1, 0, 'divider'); + users.splice(showDivider + 1, 0, 'divider'); + } } } - } - callback(users); - if (showMenuAbove) { - $dropdown.data('glDropdown').positionMenuAbove(); - } - }, - filterable: true, - filterRemote: true, - search: { - fields: ['name', 'username'] - }, - selectable: true, - fieldName: $dropdown.data('fieldName'), - toggleLabel: function(selected, el, glDropdown) { - const inputValue = glDropdown.filterInput.val(); - - if (this.multiSelect && inputValue === '') { - // Remove non-users from the fullData array - const users = glDropdown.filteredFullData(); - const callback = glDropdown.parseData.bind(glDropdown); - - // Update the data model - this.processData(inputValue, users, callback); - } + callback(users); + if (showMenuAbove) { + $dropdown.data('glDropdown').positionMenuAbove(); + } + }, + filterable: true, + filterRemote: true, + search: { + fields: ['name', 'username'], + }, + selectable: true, + fieldName: $dropdown.data('fieldName'), + toggleLabel: function(selected, el, glDropdown) { + const inputValue = glDropdown.filterInput.val(); + + if (this.multiSelect && inputValue === '') { + // Remove non-users from the fullData array + const users = glDropdown.filteredFullData(); + const callback = glDropdown.parseData.bind(glDropdown); + + // Update the data model + this.processData(inputValue, users, callback); + } - if (this.multiSelect) { - return getMultiSelectDropdownTitle(selected, $(el).hasClass('is-active')); - } + if (this.multiSelect) { + return getMultiSelectDropdownTitle(selected, $(el).hasClass('is-active')); + } - if (selected && 'id' in selected && $(el).hasClass('is-active')) { - $dropdown.find('.dropdown-toggle-text').removeClass('is-default'); - if (selected.text) { - return selected.text; + if (selected && 'id' in selected && $(el).hasClass('is-active')) { + $dropdown.find('.dropdown-toggle-text').removeClass('is-default'); + if (selected.text) { + return selected.text; + } else { + return selected.name; + } } 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 - $block = $selectbox.closest('.block'); - $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 (!$dropdown.data('alwaysShowSelectbox')) { + $selectbox.hide(); - 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); + // Recalculate where .value is because vue might have changed it + $block = $selectbox.closest('.block'); + $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; - // Remove unassigned selection (if it was previously selected) - const unassignedSelected = $dropdown.closest('.selectbox') - .find("input[name='" + ($dropdown.data('fieldName')) + "'][value=0]"); + 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') { + // 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) { - unassignedSelected.remove(); - } - } else { - if (previouslySelected.length === 0) { - // Select unassigned because there is no more selected users - this.addInput($dropdown.data('fieldName'), 0, {}); + // User unselected + emitSidebarEvent('sidebar.removeAssignee', user); } - // User unselected - emitSidebarEvent('sidebar.removeAssignee', user); + if (getSelected().find(u => u === gon.current_user_id)) { + $('.assign-to-me-link').hide(); + } else { + $('.assign-to-me-link').show(); + } } - if (getSelected().find(u => u === gon.current_user_id)) { - $('.assign-to-me-link').hide(); - } else { - $('.assign-to-me-link').show(); + var isIssueIndex, isMRIndex, page, selected; + page = $('body').attr('data-page'); + isIssueIndex = page === 'projects:issues:index'; + 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; - page = $('body').attr('data-page'); - isIssueIndex = page === 'projects:issues:index'; - isMRIndex = (page === page && page === 'projects:merge_requests:index'); - if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { - e.preventDefault(); + // 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'); - const isSelecting = (user.id !== selectedId); - selectedId = isSelecting ? user.id : selectedIdDefault; + function highlightSelected(id) { + $el.find(`li[data-user-id="${id}"] .dropdown-menu-user-link`).addClass('is-active'); + } - if (selectedId === gon.current_user_id) { - $('.assign-to-me-link').hide(); + if (selected.length > 0) { + getSelected().forEach(selectedId => highlightSelected(selectedId)); + } else if ($dropdown.hasClass('js-issue-board-sidebar')) { + highlightSelected(0); } else { - $('.assign-to-me-link').show(); + highlightSelected(selectedId); } - 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); - } - - // 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'); - } + }, + 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; - if (selected.length > 0) { - 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; - let selected = false; + if (this.multiSelect) { + selected = getSelected().find(u => user.id === u); - if (this.multiSelect) { - selected = getSelected().find(u => user.id === u); + const { fieldName } = this; + const field = $dropdown + .closest('.selectbox') + .find("input[name='" + fieldName + "'][value='" + user.id + "']"); - const { fieldName } = this; - const field = $dropdown.closest('.selectbox').find("input[name='" + fieldName + "'][value='" + user.id + "']"); - - if (field.length) { - selected = true; + if (field.length) { + selected = true; + } + } else { + selected = user.id === selectedId; } - } else { - selected = user.id === selectedId; - } - img = ""; - if (user.beforeDivider != null) { - `<li><a href='#' class='${selected === true ? 'is-active' : ''}'>${_.escape(user.name)}</a></li>`; - } else { - img = "<img src='" + avatar + "' class='avatar avatar-inline' width='32' />"; - } + img = ''; + if (user.beforeDivider != null) { + `<li><a href='#' class='${selected === true ? 'is-active' : ''}'>${_.escape( + user.name, + )}</a></li>`; + } else { + img = "<img src='" + avatar + "' class='avatar avatar-inline' width='32' />"; + } - return ` + return ` <li data-user-id=${user.id}> <a href='#' class='dropdown-menu-user-link ${selected === true ? 'is-active' : ''}'> ${img} @@ -528,114 +574,117 @@ function UsersSelect(currentUser, els, options = {}) { </a> </li> `; - } - }); - }; - })(this)); - $('.ajax-users-select').each((function(_this) { - return function(i, select) { - var firstUser, showAnyUser, showEmailUser, showNullUser; - var options = {}; - options.skipLdap = $(select).hasClass('skip_ldap'); - options.projectId = $(select).data('projectId'); - options.groupId = $(select).data('groupId'); - options.showCurrentUser = $(select).data('currentUser'); - options.authorId = $(select).data('authorId'); - options.skipUsers = $(select).data('skipUsers'); - showNullUser = $(select).data('nullUser'); - showAnyUser = $(select).data('anyUser'); - showEmailUser = $(select).data('emailUser'); - firstUser = $(select).data('firstUser'); - return $(select).select2({ - placeholder: "Search for a user", - multiple: $(select).hasClass('multiselect'), - minimumInputLength: 0, - query: function(query) { - return _this.users(query.term, options, function(users) { - var anyUser, data, emailUser, index, len, name, nullUser, obj, ref; - data = { - results: users - }; - if (query.term.length === 0) { - if (firstUser) { - // Move current user to the front of the list - ref = data.results; - - for (index = 0, len = ref.length; index < len; index += 1) { - obj = ref[index]; - if (obj.username === firstUser) { - data.results.splice(index, 1); - data.results.unshift(obj); - break; + }, + }); + }; + })(this), + ); + $('.ajax-users-select').each( + (function(_this) { + return function(i, select) { + var firstUser, showAnyUser, showEmailUser, showNullUser; + var options = {}; + options.skipLdap = $(select).hasClass('skip_ldap'); + options.projectId = $(select).data('projectId'); + options.groupId = $(select).data('groupId'); + options.showCurrentUser = $(select).data('currentUser'); + options.authorId = $(select).data('authorId'); + options.skipUsers = $(select).data('skipUsers'); + showNullUser = $(select).data('nullUser'); + showAnyUser = $(select).data('anyUser'); + showEmailUser = $(select).data('emailUser'); + firstUser = $(select).data('firstUser'); + return $(select).select2({ + placeholder: 'Search for a user', + multiple: $(select).hasClass('multiselect'), + minimumInputLength: 0, + query: function(query) { + return _this.users(query.term, options, function(users) { + var anyUser, data, emailUser, index, len, name, nullUser, obj, ref; + data = { + results: users, + }; + if (query.term.length === 0) { + if (firstUser) { + // Move current user to the front of the list + ref = data.results; + + for (index = 0, len = ref.length; index < len; index += 1) { + obj = ref[index]; + if (obj.username === firstUser) { + data.results.splice(index, 1); + data.results.unshift(obj); + break; + } } } - } - if (showNullUser) { - nullUser = { - name: 'Unassigned', - id: 0 - }; - data.results.unshift(nullUser); - } - if (showAnyUser) { - name = showAnyUser; - if (name === true) { - name = 'Any User'; + if (showNullUser) { + nullUser = { + name: 'Unassigned', + id: 0, + }; + data.results.unshift(nullUser); } - anyUser = { - name: name, - id: null + if (showAnyUser) { + name = showAnyUser; + 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); } - } - 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(emailUser); - } - return query.callback(data); - }); - }, - initSelection: function() { - var args; - args = 1 <= arguments.length ? [].slice.call(arguments, 0) : []; - return _this.initSelection.apply(_this, args); - }, - formatResult: function() { - var args; - args = 1 <= arguments.length ? [].slice.call(arguments, 0) : []; - return _this.formatResult.apply(_this, args); - }, - formatSelection: function() { - var args; - 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)); + return query.callback(data); + }); + }, + initSelection: function() { + var args; + args = 1 <= arguments.length ? [].slice.call(arguments, 0) : []; + return _this.initSelection.apply(_this, args); + }, + formatResult: function() { + var args; + args = 1 <= arguments.length ? [].slice.call(arguments, 0) : []; + return _this.formatResult.apply(_this, args); + }, + formatSelection: function() { + var args; + 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) { var id, nullUser; id = $(element).val(); - if (id === "0") { + if (id === '0') { nullUser = { - name: 'Unassigned' + name: 'Unassigned', }; return callback(nullUser); - } else if (id !== "") { + } else if (id !== '') { return this.user(id, callback); } }; @@ -647,7 +696,17 @@ UsersSelect.prototype.formatResult = function(user) { } else { 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) { @@ -662,10 +721,9 @@ UsersSelect.prototype.user = function(user_id, callback) { var url; url = this.buildUrl(this.userPath); url = url.replace(':id', user_id); - return axios.get(url) - .then(({ data }) => { - callback(data); - }); + return axios.get(url).then(({ data }) => { + callback(data); + }); }; // Return users list. Filtered by query @@ -682,12 +740,11 @@ UsersSelect.prototype.users = function(query, options, callback) { todo_state_filter: options.todoStateFilter || null, current_user: options.showCurrentUser || null, author_id: options.authorId || null, - skip_users: options.skipUsers || null + skip_users: options.skipUsers || null, }; - return axios.get(url, { params }) - .then(({ data }) => { - callback(data); - }); + return axios.get(url, { params }).then(({ data }) => { + callback(data); + }); }; UsersSelect.prototype.buildUrl = function(url) { diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue index 9161f703697..6c87287a4c4 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue @@ -1,6 +1,7 @@ <script> import Icon from '~/vue_shared/components/icon.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; +import FilteredSearchDropdown from '~/vue_shared/components/filtered_search_dropdown.vue'; import timeagoMixin from '../../vue_shared/mixins/timeago'; import tooltip from '../../vue_shared/directives/tooltip'; import LoadingButton from '../../vue_shared/components/loading_button.vue'; @@ -18,6 +19,7 @@ export default { StatusIcon, Icon, TooltipOnTruncate, + FilteredSearchDropdown, }, directives: { tooltip, @@ -30,8 +32,10 @@ export default { }, }, data() { + const features = window.gon.features || {}; return { isStopping: false, + enableCiEnvironmentsStatusChanges: features.ciEnvironmentsStatusChanges, }; }, computed: { @@ -118,18 +122,65 @@ export default { /> </div> <div> - <a - v-if="hasExternalUrls" - :href="deployment.external_url" - target="_blank" - rel="noopener noreferrer nofollow" - class="deploy-link js-deploy-url btn btn-default btn-sm inline" - > - <span> - View app - <icon name="external-link" /> - </span> - </a> + <template v-if="hasExternalUrls"> + <filtered-search-dropdown + v-if="enableCiEnvironmentsStatusChanges" + class="js-mr-wigdet-deployment-dropdown inline" + :items="deployment.changes" + :main-action-link="deployment.external_url" + filter-key="path" + > + <template + slot="mainAction" + slot-scope="slotProps" + > + <a + :href="deployment.external_url" + target="_blank" + rel="noopener noreferrer nofollow" + class="deploy-link js-deploy-url inline" + :class="slotProps.className" + > + <span> + {{ __('View app') }} + <icon name="external-link" /> + </span> + </a> + </template> + + <template + slot="result" + slot-scope="slotProps" + > + <a + :href="slotProps.result.external_url" + target="_blank" + rel="noopener noreferrer nofollow" + class="menu-item" + > + <strong class="str-truncated-100 append-bottom-0 d-block"> + {{ slotProps.result.path }} + </strong> + + <p class="text-secondary str-truncated-100 append-bottom-0 d-block"> + {{ slotProps.result.external_url }} + </p> + </a> + </template> + </filtered-search-dropdown> + <a + v-else + :href="deployment.external_url" + target="_blank" + rel="noopener noreferrer nofollow" + class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inline" + > + <span> + {{ __('View app') }} + <icon name="external-link" /> + </span> + </a> + </template> <loading-button v-if="deployment.stop_url" :loading="isStopping" diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue index 7ac3fedb2e3..8180f13a7cb 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue @@ -112,7 +112,8 @@ export default { eventHub.$on('mr.discussion.updated', this.checkStatus); }, mounted() { - this.handleMounted(); + this.setFaviconHelper(); + this.initDeploymentsPolling(); }, beforeDestroy() { eventHub.$off('mr.discussion.updated', this.checkStatus); @@ -250,10 +251,6 @@ export default { this.stopPolling(); }); }, - handleMounted() { - this.setFaviconHelper(); - this.initDeploymentsPolling(); - }, }, }; </script> @@ -275,12 +272,13 @@ export default { :key="deployment.id" :deployment="deployment" /> - <grouped-test-reports-app - v-if="mr.testResultsPath" - class="js-reports-container" - :endpoint="mr.testResultsPath" - /> <div class="mr-section-container"> + <grouped-test-reports-app + v-if="mr.testResultsPath" + class="js-reports-container" + :endpoint="mr.testResultsPath" + /> + <div class="mr-widget-section"> <component :is="componentName" diff --git a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue index a2518e2a611..c60052fec50 100644 --- a/app/assets/javascripts/vue_shared/components/ci_badge_link.vue +++ b/app/assets/javascripts/vue_shared/components/ci_badge_link.vue @@ -43,7 +43,7 @@ export default { computed: { cssClass() { const className = this.status.group; - return className ? `ci-status ci-${className}` : 'ci-status'; + return className ? `ci-status ci-${className} qa-status-badge` : 'ci-status qa-status-badge'; }, }, }; diff --git a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js index 19611b14be4..bffaa096210 100644 --- a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js +++ b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js @@ -549,6 +549,7 @@ const fileNameIcons = { jenkinsfile: 'jenkins', 'firebase.json': 'firebase', '.firebaserc': 'firebase', + Rakefile: 'ruby', 'rollup.config.js': 'rollup', 'rollup.config.ts': 'rollup', 'rollup-config.js': 'rollup', diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue index 36a345130c0..2d89a156117 100644 --- a/app/assets/javascripts/vue_shared/components/file_row.vue +++ b/app/assets/javascripts/vue_shared/components/file_row.vue @@ -34,10 +34,21 @@ export default { required: false, default: false, }, + displayTextKey: { + type: String, + required: false, + default: 'name', + }, + shouldTruncateStart: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { mouseOver: false, + truncateStart: 0, }; }, computed: { @@ -60,6 +71,15 @@ export default { 'is-open': this.file.opened, }; }, + outputText() { + const text = this.file[this.displayTextKey]; + + if (this.truncateStart === 0) { + return text; + } + + return `...${text.substring(this.truncateStart, text.length)}`; + }, }, watch: { 'file.active': function fileActiveWatch(active) { @@ -72,6 +92,15 @@ export default { if (this.hasPathAtCurrentRoute()) { this.scrollIntoView(true); } + + if (this.shouldTruncateStart) { + const { scrollWidth, offsetWidth } = this.$refs.textOutput; + const textOverflow = scrollWidth - offsetWidth; + + if (textOverflow > 0) { + this.truncateStart = Math.ceil(textOverflow / 5) + 3; + } + } }, methods: { toggleTreeOpen(path) { @@ -139,6 +168,7 @@ export default { class="file-row-name-container" > <span + ref="textOutput" :style="levelIndentation" class="file-row-name str-truncated" > @@ -156,7 +186,7 @@ export default { :size="16" class="append-right-5" /> - {{ file.name }} + {{ outputText }} </span> <component :is="extraComponent" @@ -175,6 +205,8 @@ export default { :hide-extra-on-tree="hideExtraOnTree" :extra-component="extraComponent" :show-changed-icon="showChangedIcon" + :display-text-key="displayTextKey" + :should-truncate-start="shouldTruncateStart" @toggleTreeOpen="toggleTreeOpen" @clickFile="clickedFile" /> diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue new file mode 100644 index 00000000000..460fa6ad72e --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/filtered_search_dropdown.vue @@ -0,0 +1,143 @@ +<script> +import $ from 'jquery'; +import Icon from '~/vue_shared/components/icon.vue'; +/** + * Renders a split dropdown with + * an input that allows to search through the given + * array of options. + */ +export default { + name: 'FilteredSearchDropdown', + components: { + Icon, + }, + props: { + title: { + type: String, + required: false, + default: '', + }, + buttonType: { + required: false, + validator: value => + ['primary', 'default', 'secondary', 'success', 'info', 'warning', 'danger'].indexOf( + value, + ) !== -1, + default: 'default', + }, + size: { + required: false, + type: String, + default: 'sm', + }, + items: { + type: Array, + required: true, + }, + visibleItems: { + type: Number, + required: false, + default: 5, + }, + filterKey: { + type: String, + required: true, + }, + }, + data() { + return { + filter: '', + }; + }, + computed: { + className() { + return `btn btn-${this.buttonType} btn-${this.size}`; + }, + filteredResults() { + if (this.filter !== '') { + return this.items.filter( + item => item[this.filterKey] && item[this.filterKey].toLowerCase().includes(this.filter.toLowerCase()), + ); + } + + return this.items.slice(0, this.visibleItems); + } + }, + mounted() { + /** + * Resets the filter every time the user closes the dropdown + */ + $(this.$el) + .on('shown.bs.dropdown', () => { + this.$nextTick(() => this.$refs.searchInput.focus()); + }) + .on('hidden.bs.dropdown', () => { + this.filter = ''; + }); + }, +}; +</script> +<template> + <div class="dropdown"> + <div class="btn-group"> + <slot + name="mainAction" + :class-name="className" + > + <button + type="button" + :class="className" + > + {{ title }} + </button> + </slot> + + <button + type="button" + :class="className" + class="dropdown-toggle dropdown-toggle-split" + data-toggle="dropdown" + aria-haspopup="true" + aria-expanded="false" + aria-label="Expand dropdown" + > + <icon + name="angle-down" + :size="12" + /> + </button> + <div class="dropdown-menu dropdown-menu-right"> + <div class="dropdown-input"> + <input + ref="searchInput" + v-model="filter" + type="search" + placeholder="Filter" + class="js-filtered-dropdown-input dropdown-input-field" + /> + <icon + class="dropdown-input-search" + name="search" + /> + </div> + + <div class="dropdown-content"> + <ul> + <li + v-for="(result, i) in filteredResults" + :key="i" + class="js-filtered-dropdown-result" + > + <slot + name="result" + :result="result" + > + {{ result[filterKey] }} + </slot> + </li> + </ul> + </div> + </div> + </div> + </div> +</template> diff --git a/app/assets/javascripts/vue_shared/components/gl_countdown.vue b/app/assets/javascripts/vue_shared/components/gl_countdown.vue new file mode 100644 index 00000000000..9327a2a4a6c --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/gl_countdown.vue @@ -0,0 +1,49 @@ +<script> +import { calculateRemainingMilliseconds, formatTime } from '~/lib/utils/datetime_utility'; + +/** + * Counts down to a given end date. + */ +export default { + props: { + endDateString: { + type: String, + required: true, + validator(value) { + return !Number.isNaN(new Date(value).getTime()); + }, + }, + }, + + data() { + return { + remainingTime: formatTime(0), + countdownUpdateIntervalId: null, + }; + }, + + mounted() { + const updateRemainingTime = () => { + const remainingMilliseconds = calculateRemainingMilliseconds(this.endDateString); + this.remainingTime = formatTime(remainingMilliseconds); + }; + + updateRemainingTime(); + this.countdownUpdateIntervalId = window.setInterval(updateRemainingTime, 1000); + }, + + beforeDestroy() { + window.clearInterval(this.countdownUpdateIntervalId); + }, +}; +</script> + +<template> + <time + v-gl-tooltip + :datetime="endDateString" + :title="endDateString" + > + {{ remainingTime }} + </time> +</template> diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue index b371b6adf7e..aee88cae48d 100644 --- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue +++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue @@ -69,6 +69,9 @@ export default { onClickAction(action) { this.$emit('actionClicked', action); }, + onClickSidebarButton() { + this.$emit('clickedSidebarButton'); + }, }, }; </script> @@ -161,21 +164,21 @@ export default { </i> </button> </template> - <button - v-if="hasSidebarButton" - id="toggleSidebar" - type="button" - class="btn btn-default d-block d-sm-none d-md-none + </section> + <button + v-if="hasSidebarButton" + id="toggleSidebar" + type="button" + class="btn btn-default d-block d-sm-none sidebar-toggle-btn js-sidebar-build-toggle js-sidebar-build-toggle-header" - aria-label="Toggle Sidebar" + @click="onClickSidebarButton" + > + <i + class="fa fa-angle-double-left" + aria-hidden="true" + aria-labelledby="toggleSidebar" > - <i - class="fa fa-angle-double-left" - aria-hidden="true" - aria-labelledby="toggleSidebar" - > - </i> - </button> - </section> + </i> + </button> </header> </template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index 704adf7864f..3ddb39730c4 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -1,16 +1,16 @@ <script> import $ from 'jquery'; -import tooltip from '../../directives/tooltip'; -import toolbarButton from './toolbar_button.vue'; -import icon from '../icon.vue'; +import Tooltip from '../../directives/tooltip'; +import ToolbarButton from './toolbar_button.vue'; +import Icon from '../icon.vue'; export default { directives: { - tooltip, + Tooltip, }, components: { - toolbarButton, - icon, + ToolbarButton, + Icon, }, props: { previewMarkdown: { @@ -68,27 +68,27 @@ export default { :class="{ active: !previewMarkdown }" class="md-header-tab" > - <a + <button class="js-write-link" - href="#md-write-holder" tabindex="-1" - @click.prevent="writeMarkdownTab($event)" + type="button" + @click="writeMarkdownTab($event)" > Write - </a> + </button> </li> <li :class="{ active: previewMarkdown }" class="md-header-tab" > - <a + <button class="js-preview-link js-md-preview-button" - href="#md-preview-holder" tabindex="-1" - @click.prevent="previewMarkdownTab($event)" + type="button" + @click="previewMarkdownTab($event)" > Preview - </a> + </button> </li> <li :class="{ active: !previewMarkdown }" diff --git a/app/assets/javascripts/vue_shared/components/pikaday.vue b/app/assets/javascripts/vue_shared/components/pikaday.vue index 782d8e3abf6..26c99aecae4 100644 --- a/app/assets/javascripts/vue_shared/components/pikaday.vue +++ b/app/assets/javascripts/vue_shared/components/pikaday.vue @@ -1,6 +1,6 @@ <script> import Pikaday from 'pikaday'; -import { parsePikadayDate, pikadayToString } from '../../lib/utils/datefix'; +import { parsePikadayDate, pikadayToString } from '~/lib/utils/datetime_utility'; export default { name: 'DatePicker', diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js index bdb2351c344..e98c4d7bf7a 100644 --- a/app/assets/javascripts/zen_mode.js +++ b/app/assets/javascripts/zen_mode.js @@ -47,16 +47,26 @@ export default class ZenMode { e.preventDefault(); return $(e.currentTarget).trigger('zen_mode:leave'); }); - $(document).on('zen_mode:enter', (function(_this) { - return function(e) { - return _this.enter($(e.target).closest('.md-area').find('.zen-backdrop')); - }; - })(this)); - $(document).on('zen_mode:leave', (function(_this) { - return function(e) { - return _this.exit(); - }; - })(this)); + $(document).on( + 'zen_mode:enter', + (function(_this) { + return function(e) { + return _this.enter( + $(e.target) + .closest('.md-area') + .find('.zen-backdrop'), + ); + }; + })(this), + ); + $(document).on( + 'zen_mode:leave', + (function(_this) { + return function(e) { + return _this.exit(); + }; + })(this), + ); $(document).on('keydown', function(e) { // Esc if (e.keyCode === 27) { @@ -93,7 +103,7 @@ export default class ZenMode { scrollTo(zen_area) { return $.scrollTo(zen_area, 0, { - offset: -150 + offset: -150, }); } } diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss index af73954bd2e..1e00aa4ff7e 100644 --- a/app/assets/stylesheets/bootstrap_migration.scss +++ b/app/assets/stylesheets/bootstrap_migration.scss @@ -238,10 +238,6 @@ h3.popover-header { } .card { - .card-title { - margin-bottom: 0; - } - &.card-without-border { @extend .border-0; } @@ -255,13 +251,6 @@ h3.popover-header { } } -.card-header { - h3.card-title, - h4.card-title { - margin-top: 0; - } -} - .nav-tabs { // Override bootstrap's default border border-bottom: 0; diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 4ffb3e9ab42..4041f2b4479 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -51,6 +51,7 @@ @import 'framework/blank'; @import 'framework/wells'; @import 'framework/page_header'; +@import 'framework/page_title'; @import 'framework/awards'; @import 'framework/images'; @import 'framework/broadcast_messages'; diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 3c9505a21d6..fa753b13e5f 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -334,6 +334,14 @@ img.emoji { } } +.outline-0 { + outline: 0; + + &:focus { + outline: 0; + } +} + /** COMMON CLASSES **/ .prepend-top-0 { margin-top: 0; } .prepend-top-2 { margin-top: 2px; } @@ -369,3 +377,5 @@ img.emoji { .flex-align-self-center { align-self: center; } .flex-grow { flex-grow: 1; } .flex-no-shrink { flex-shrink: 0; } +.mw-460 { max-width: 460px; } +.ws-initial { white-space: initial; } diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss index 50ebc6d0dd1..b8bb9e1e07b 100644 --- a/app/assets/stylesheets/framework/gitlab_theme.scss +++ b/app/assets/stylesheets/framework/gitlab_theme.scss @@ -161,6 +161,7 @@ .nav-links li { &.active a, + &.md-header-tab.active button, a.active { border-bottom: 2px solid $active-tab-border; diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index c430009bfe0..d1ce3a582bb 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -530,9 +530,6 @@ .header-user { &.show .dropdown-menu { - width: auto; - min-width: unset; - max-height: 323px; margin-top: 4px; color: $gl-text-color; left: auto; @@ -544,15 +541,19 @@ display: block; } - .user-status-emoji { + .user-status { margin-right: 0; - display: block; - vertical-align: text-top; - max-width: 148px; - font-size: 12px; + max-width: 240px; + font-size: $gl-font-size-small; gl-emoji { - font-size: $gl-font-size; + font-size: $gl-font-size-small; + } + + .user-status-emoji { + gl-emoji { + font-size: $gl-font-size; + } } } } diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 554e2b6720a..3142f94b192 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -72,6 +72,7 @@ .md-header-tab { @include media-breakpoint-down(xs) { flex: 1; + flex-direction: column; width: 100%; border-bottom: 1px solid $border-color; text-align: center; diff --git a/app/assets/stylesheets/framework/page_title.scss b/app/assets/stylesheets/framework/page_title.scss new file mode 100644 index 00000000000..e8302953a63 --- /dev/null +++ b/app/assets/stylesheets/framework/page_title.scss @@ -0,0 +1,18 @@ +.page-title-holder { + @extend .d-flex; + @extend .align-items-center; + + padding-top: $gl-padding-top; + border-bottom: 1px solid $border-color; + + .page-title { + margin: $gl-padding 0; + font-size: 1.75em; + font-weight: $gl-font-weight-bold; + color: $gl-text-color; + } + + .page-title-controls { + margin-left: auto; + } +} diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss index 5ca4d944d73..3a117106cff 100644 --- a/app/assets/stylesheets/framework/panels.scss +++ b/app/assets/stylesheets/framework/panels.scss @@ -53,8 +53,3 @@ margin-top: $gl-padding; } } - -.card-title { - font-size: inherit; - line-height: inherit; -} diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss index 8bab8cf36b1..f47dfe1b563 100644 --- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss +++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss @@ -8,15 +8,17 @@ height: auto; border-bottom: 1px solid $border-color; - li { + li:not(.md-header-toolbar) { display: flex; - a { + a, + button { padding: $gl-btn-padding; padding-bottom: 11px; font-size: 14px; line-height: 28px; color: $gl-text-color-secondary; + border: 0; border-bottom: 2px solid transparent; white-space: nowrap; @@ -33,7 +35,13 @@ } } + button { + padding-top: 0; + background-color: transparent; + } + &.active a, + &.active button, a.active { color: $black; font-weight: $gl-font-weight-bold; @@ -42,6 +50,10 @@ color: $black; } } + + &.md-header-tab button { + line-height: 19px; + } } } diff --git a/app/assets/stylesheets/framework/terms.scss b/app/assets/stylesheets/framework/terms.scss index 7cda674e5c8..3f4be8829d7 100644 --- a/app/assets/stylesheets/framework/terms.scss +++ b/app/assets/stylesheets/framework/terms.scss @@ -19,17 +19,12 @@ justify-content: space-between; line-height: $line-height-base; - .card-title { + .logo-text { + width: 55px; + height: 24px; display: flex; - align-items: center; - - .logo-text { - width: 55px; - height: 24px; - display: flex; - flex-direction: column; - justify-content: center; - } + flex-direction: column; + justify-content: center; } .navbar-collapse { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 0fde6e18cc7..ad66a0365ed 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -194,6 +194,7 @@ $well-light-text-color: #5b6169; * Text */ $gl-font-size: 14px; +$gl-font-size-small: 12px; $gl-font-weight-normal: 400; $gl-font-weight-bold: 600; $gl-text-color: #2e2e2e; diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index ed877f625b5..227f49ec595 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -117,7 +117,6 @@ .controllers { display: flex; - font-size: 15px; justify-content: center; align-items: center; @@ -179,6 +178,7 @@ .build-loader-animation { @include build-loader-animation; + float: left; } } diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 8d884ad6891..52c91266ff4 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -1027,8 +1027,12 @@ overflow-x: auto; } -.tree-list-search .form-control { - padding-left: 30px; +.tree-list-search { + flex: 0 0 34px; + + .form-control { + padding-left: 30px; + } } .tree-list-icon { @@ -1063,3 +1067,9 @@ } } } + +.tree-list-view-toggle { + svg { + top: 0; + } +} diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 0f95fb911e1..8ea34f5d19d 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -185,7 +185,17 @@ ul.related-merge-requests > li { } .new-branch-col { - padding-top: 10px; + font-size: 0; + + .discussion-filter-container { + &:not(:only-child) { + margin-right: $gl-padding-8; + } + + @include media-breakpoint-down(md) { + margin-top: $gl-padding-8; + } + } } .create-mr-dropdown-wrap { @@ -205,6 +215,10 @@ ul.related-merge-requests > li { .btn-group:not(.hidden) { display: flex; + + @include media-breakpoint-down(md) { + margin-top: $gl-padding-8; + } } .js-create-merge-request { @@ -251,7 +265,6 @@ ul.related-merge-requests > li { .new-branch-col { padding-top: 0; - text-align: right; align-self: center; } @@ -262,3 +275,9 @@ ul.related-merge-requests > li { } } } + +@include media-breakpoint-up(lg) { + .new-branch-col { + text-align: right; + } +} diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 895db89f289..fa6afbf81de 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -47,7 +47,6 @@ } } - .mr-widget-heading { position: relative; border: 1px solid $border-color; @@ -454,7 +453,7 @@ .mr-list { .merge-request { - padding: 10px 0 10px 15px; + padding: 10px 0 10px 15px; position: relative; display: -webkit-flex; display: flex; @@ -468,7 +467,6 @@ margin-bottom: 2px; .ci-status-link { - svg { height: 16px; width: 16px; @@ -698,7 +696,6 @@ .table-holder { .ci-table { - th { background-color: $white-light; color: $gl-text-color-secondary; @@ -775,7 +772,7 @@ &.affix { left: 0; - transition: right .15s; + transition: right 0.15s; @include media-breakpoint-down(xs) { right: 0; @@ -821,9 +818,17 @@ display: flex; justify-content: space-between; - @include media-breakpoint-down(xs) { + @include media-breakpoint-down(md) { flex-direction: column-reverse; } + + .discussion-filter-container { + margin-top: $gl-padding-8; + + &:not(:only-child) { + padding-right: $gl-padding-8; + } + } } .limit-container-width:not(.container-limited) { @@ -884,7 +889,7 @@ } > *:not(:last-child) { - margin-right: .3em; + margin-right: 0.3em; } svg { @@ -907,6 +912,10 @@ .btn svg { fill: $theme-gray-700; } + + .dropdown-menu { + width: 400px; + } } // Hack alert: we've rewritten `btn` class in a way that @@ -917,7 +926,7 @@ &[disabled] { cursor: not-allowed; box-shadow: none; - opacity: .65; + opacity: 0.65; &:hover { color: $gl-gray-500; diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 4268e194ed7..c60bb360a03 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -340,6 +340,8 @@ } .note-form-actions { + color: $gl-text-color; + @include media-breakpoint-down(xs) { .btn { float: none; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index bfba1bf1b2b..be535ade0a6 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -618,7 +618,6 @@ ul.notes { .line-resolve-all-container { @include notes-media('min', map-get($grid-breakpoints, sm)) { margin-right: 0; - padding-left: $gl-padding; } > div { @@ -756,3 +755,23 @@ ul.notes { margin-top: 4px; } } + +.discussion-filter-container { + + .btn > svg { + width: $gl-col-padding; + height: $gl-col-padding; + } + + .dropdown-menu { + margin-bottom: $gl-padding-4; + + @include media-breakpoint-down(md) { + margin-left: $btn-side-margin + $contextual-sidebar-collapsed-width; + } + + @include media-breakpoint-down(xs) { + margin-left: $btn-side-margin; + } + } +} |