diff options
author | Phil Hughes <me@iamphill.com> | 2017-09-21 09:01:51 +0000 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2017-09-21 09:01:51 +0000 |
commit | 598b1a17a0442f0038e208f1abfc1198112282f3 (patch) | |
tree | 379289b3e94e1ded82a92291a1bd9ebecc79f4ef /app/assets/javascripts | |
parent | ac044ba0d53f17833f0aa97db404e9b49c9b219a (diff) | |
parent | 8784d362abd46c91791e783629621e4d3eaa7f08 (diff) | |
download | gitlab-ce-598b1a17a0442f0038e208f1abfc1198112282f3.tar.gz |
Merge branch '37220-es-modules' into 'master'
Use modules in common utils
See merge request gitlab-org/gitlab-ce!14074
Diffstat (limited to 'app/assets/javascripts')
35 files changed, 563 insertions, 534 deletions
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 22fa1f2a609..ec5be8664b2 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -2,6 +2,7 @@ /* global Flash */ import _ from 'underscore'; import Cookies from 'js-cookie'; +import { isInIssuePage, updateTooltipTitle } from './lib/utils/common_utils'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd'; @@ -237,7 +238,7 @@ class AwardsHandler { addAward(votesBlock, awardUrl, emoji, checkMutuality, callback) { const isMainAwardsBlock = votesBlock.closest('.js-issue-note-awards').length; - if (gl.utils.isInIssuePage() && !isMainAwardsBlock) { + if (isInIssuePage() && !isMainAwardsBlock) { const id = votesBlock.attr('id').replace('note_', ''); $('.emoji-menu').removeClass('is-visible'); @@ -288,7 +289,7 @@ class AwardsHandler { } getVotesBlock() { - if (gl.utils.isInIssuePage()) { + if (isInIssuePage()) { const $el = $('.js-add-award.is-active').closest('.note.timeline-entry'); if ($el.length) { @@ -452,11 +453,11 @@ class AwardsHandler { userAuthored($emojiButton) { const oldTitle = this.getAwardTooltip($emojiButton); const newTitle = 'You cannot vote on your own issue, MR and note'; - gl.utils.updateTooltipTitle($emojiButton, newTitle).tooltip('show'); + updateTooltipTitle($emojiButton, newTitle).tooltip('show'); // Restore tooltip back to award list return setTimeout(() => { $emojiButton.tooltip('hide'); - gl.utils.updateTooltipTitle($emojiButton, oldTitle); + updateTooltipTitle($emojiButton, oldTitle); }, 2800); } diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js index 79702c54852..2cf8f4fa935 100644 --- a/app/assets/javascripts/behaviors/quick_submit.js +++ b/app/assets/javascripts/behaviors/quick_submit.js @@ -1,4 +1,5 @@ import '../commons/bootstrap'; +import { isInIssuePage } from '../lib/utils/common_utils'; // Quick Submit behavior // @@ -45,7 +46,7 @@ $(document).on('keydown.quick_submit', '.js-quick-submit', (e) => { if (!$submitButton.attr('disabled')) { $submitButton.trigger('click', [e]); - if (!gl.utils.isInIssuePage()) { + if (!isInIssuePage()) { $submitButton.disable(); } } diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js index 187fab084fd..e0b73f13d36 100644 --- a/app/assets/javascripts/blob/viewer/index.js +++ b/app/assets/javascripts/blob/viewer/index.js @@ -1,4 +1,6 @@ /* global Flash */ +import { handleLocationHash } from '../../lib/utils/common_utils'; + export default class BlobViewer { constructor() { BlobViewer.initAuxiliaryViewer(); @@ -114,7 +116,7 @@ export default class BlobViewer { $(viewer).renderGFM(); this.$fileHolder.trigger('highlight:line'); - gl.utils.handleLocationHash(); + handleLocationHash(); this.toggleCopyButtonState(); }) diff --git a/app/assets/javascripts/boards/stores/boards_store.js b/app/assets/javascripts/boards/stores/boards_store.js index 43928e602d6..ea82958e80d 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js +++ b/app/assets/javascripts/boards/stores/boards_store.js @@ -2,6 +2,7 @@ /* global List */ import _ from 'underscore'; import Cookies from 'js-cookie'; +import { getUrlParamsArray } from '../../lib/utils/common_utils'; window.gl = window.gl || {}; window.gl.issueBoards = window.gl.issueBoards || {}; @@ -21,7 +22,7 @@ gl.issueBoards.BoardsStore = { }, create () { this.state.lists = []; - this.filter.path = gl.utils.getUrlParamsArray().join('&'); + this.filter.path = getUrlParamsArray().join('&'); this.detail = { issue: {} }; }, addList (listObj, defaultAvatar) { diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index ae1a23132a7..286a758b8a9 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -3,6 +3,7 @@ consistent-return, prefer-rest-params */ import _ from 'underscore'; import bp from './breakpoints'; import { bytesToKiB } from './lib/utils/number_utils'; +import { setCiStatusFavicon } from './lib/utils/common_utils'; window.Build = (function () { Build.timeout = null; @@ -169,7 +170,7 @@ window.Build = (function () { data: this.state, }) .done((log) => { - gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`); + setCiStatusFavicon(`${this.pageUrl}/status.json`); if (log.state) { this.state = log.state; diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js index b375b61202e..eae4a7eab55 100644 --- a/app/assets/javascripts/confirm_danger_modal.js +++ b/app/assets/javascripts/confirm_danger_modal.js @@ -1,4 +1,5 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, camelcase, one-var-declaration-per-line, no-else-return, max-len */ +import { rstrip } from './lib/utils/common_utils'; window.ConfirmDangerModal = (function() { function ConfirmDangerModal(form, text) { @@ -12,7 +13,7 @@ window.ConfirmDangerModal = (function() { submit.disable(); $('.js-confirm-danger-input').off('input'); $('.js-confirm-danger-input').on('input', function() { - if (gl.utils.rstrip($(this).val()) === project_path) { + if (rstrip($(this).val()) === project_path) { return submit.enable(); } else { return submit.disable(); diff --git a/app/assets/javascripts/copy_as_gfm.js b/app/assets/javascripts/copy_as_gfm.js index 13ba4a57293..e3e2c798570 100644 --- a/app/assets/javascripts/copy_as_gfm.js +++ b/app/assets/javascripts/copy_as_gfm.js @@ -1,6 +1,6 @@ /* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */ import _ from 'underscore'; -import './lib/utils/common_utils'; +import { insertText, getSelectedFragment, nodeMatchesSelector } from './lib/utils/common_utils'; import { placeholderImage } from './lazy_loader'; const gfmRules = { @@ -295,7 +295,7 @@ class CopyAsGFM { const clipboardData = e.originalEvent.clipboardData; if (!clipboardData) return; - const documentFragment = window.gl.utils.getSelectedFragment(); + const documentFragment = getSelectedFragment(); if (!documentFragment) return; const el = transformer(documentFragment.cloneNode(true)); @@ -412,7 +412,7 @@ class CopyAsGFM { for (const selector in rules) { const func = rules[selector]; - if (!window.gl.utils.nodeMatchesSelector(node, selector)) continue; + if (!nodeMatchesSelector(node, selector)) continue; let result; if (func.length === 2) { diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index f3b537c83e2..31214818496 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -77,6 +77,7 @@ import initProjectVisibilitySelector from './project_visibility'; import GpgBadges from './gpg_badges'; import UserFeatureHelper from './helpers/user_feature_helper'; import initChangesDropdown from './init_changes_dropdown'; +import { ajaxGet, convertPermissionToBoolean } from './lib/utils/common_utils'; (function() { var Dispatcher; @@ -100,7 +101,7 @@ import initChangesDropdown from './init_changes_dropdown'; $('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => { const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources); - const enableGFM = gl.utils.convertPermissionToBoolean(el.dataset.supportsAutocomplete); + const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete); gfm.setup($(el), { emojis: true, members: enableGFM, @@ -351,7 +352,7 @@ import initChangesDropdown from './init_changes_dropdown'; if ($('.blob-viewer').length) new BlobViewer(); if ($('.project-show-activity').length) new gl.Activities(); $('#tree-slider').waitForImages(function() { - gl.utils.ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); + ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); }); break; case 'projects:edit': @@ -427,7 +428,7 @@ import initChangesDropdown from './init_changes_dropdown'; new NewCommitForm($('.js-create-dir-form')); new UserCallout({ setCalloutPerProject: true }); $('#tree-slider').waitForImages(function() { - gl.utils.ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); + ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); }); break; case 'projects:find_file:show': diff --git a/app/assets/javascripts/environments/components/environment.vue b/app/assets/javascripts/environments/components/environment.vue index f54d573db6e..14fde1afb16 100644 --- a/app/assets/javascripts/environments/components/environment.vue +++ b/app/assets/javascripts/environments/components/environment.vue @@ -6,7 +6,7 @@ import environmentTable from './environments_table.vue'; import EnvironmentsStore from '../stores/environments_store'; import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import tablePagination from '../../vue_shared/components/table_pagination.vue'; -import '../../lib/utils/common_utils'; +import { convertPermissionToBoolean, getParameterByName, setParamInURL } from '../../lib/utils/common_utils'; import eventHub from '../event_hub'; import Poll from '../../lib/utils/poll'; import environmentsMixin from '../mixins/environments_mixin'; @@ -51,19 +51,19 @@ export default { computed: { scope() { - return gl.utils.getParameterByName('scope'); + return getParameterByName('scope'); }, canReadEnvironmentParsed() { - return gl.utils.convertPermissionToBoolean(this.canReadEnvironment); + return convertPermissionToBoolean(this.canReadEnvironment); }, canCreateDeploymentParsed() { - return gl.utils.convertPermissionToBoolean(this.canCreateDeployment); + return convertPermissionToBoolean(this.canCreateDeployment); }, canCreateEnvironmentParsed() { - return gl.utils.convertPermissionToBoolean(this.canCreateEnvironment); + return convertPermissionToBoolean(this.canCreateEnvironment); }, }, @@ -72,8 +72,8 @@ export default { * Toggles loading property. */ created() { - const scope = gl.utils.getParameterByName('scope') || this.visibility; - const page = gl.utils.getParameterByName('page') || this.pageNumber; + const scope = getParameterByName('scope') || this.visibility; + const page = getParameterByName('page') || this.pageNumber; this.service = new EnvironmentsService(this.endpoint); @@ -126,15 +126,15 @@ export default { * @return {String} */ changePage(pageNumber) { - const param = gl.utils.setParamInURL('page', pageNumber); + const param = setParamInURL('page', pageNumber); gl.utils.visitUrl(param); return param; }, fetchEnvironments() { - const scope = gl.utils.getParameterByName('scope') || this.visibility; - const page = gl.utils.getParameterByName('page') || this.pageNumber; + const scope = getParameterByName('scope') || this.visibility; + const page = getParameterByName('page') || this.pageNumber; this.isLoading = true; diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue index 925503a01c4..35891240239 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.vue +++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue @@ -9,7 +9,7 @@ import tablePagination from '../../vue_shared/components/table_pagination.vue'; import Poll from '../../lib/utils/poll'; import eventHub from '../event_hub'; import environmentsMixin from '../mixins/environments_mixin'; -import '../../lib/utils/common_utils'; +import { convertPermissionToBoolean, getParameterByName, setParamInURL } from '../../lib/utils/common_utils'; export default { components: { @@ -47,15 +47,15 @@ export default { computed: { scope() { - return gl.utils.getParameterByName('scope'); + return getParameterByName('scope'); }, canReadEnvironmentParsed() { - return gl.utils.convertPermissionToBoolean(this.canReadEnvironment); + return convertPermissionToBoolean(this.canReadEnvironment); }, canCreateDeploymentParsed() { - return gl.utils.convertPermissionToBoolean(this.canCreateDeployment); + return convertPermissionToBoolean(this.canCreateDeployment); }, /** @@ -82,8 +82,8 @@ export default { * Toggles loading property. */ created() { - const scope = gl.utils.getParameterByName('scope') || this.visibility; - const page = gl.utils.getParameterByName('page') || this.pageNumber; + const scope = getParameterByName('scope') || this.visibility; + const page = getParameterByName('page') || this.pageNumber; this.service = new EnvironmentsService(this.endpoint); @@ -125,15 +125,15 @@ export default { * @param {Number} pageNumber desired page to go to. */ changePage(pageNumber) { - const param = gl.utils.setParamInURL('page', pageNumber); + const param = setParamInURL('page', pageNumber); gl.utils.visitUrl(param); return param; }, fetchEnvironments() { - const scope = gl.utils.getParameterByName('scope') || this.visibility; - const page = gl.utils.getParameterByName('page') || this.pageNumber; + const scope = getParameterByName('scope') || this.visibility; + const page = getParameterByName('page') || this.pageNumber; this.isLoading = true; diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js index 038c149be2d..aff8227c38c 100644 --- a/app/assets/javascripts/environments/stores/environments_store.js +++ b/app/assets/javascripts/environments/stores/environments_store.js @@ -1,4 +1,4 @@ -import '~/lib/utils/common_utils'; +import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; /** * Environments Store. * @@ -66,8 +66,8 @@ export default class EnvironmentsStore { } setPagination(pagination = {}) { - const normalizedHeaders = gl.utils.normalizeHeaders(pagination); - const paginationInformation = gl.utils.parseIntPagination(normalizedHeaders); + const normalizedHeaders = normalizeHeaders(pagination); + const paginationInformation = parseIntPagination(normalizedHeaders); this.state.paginationInformation = paginationInformation; return paginationInformation; diff --git a/app/assets/javascripts/groups/components/groups.vue b/app/assets/javascripts/groups/components/groups.vue index 36a04d4202f..d17a43b048a 100644 --- a/app/assets/javascripts/groups/components/groups.vue +++ b/app/assets/javascripts/groups/components/groups.vue @@ -1,6 +1,7 @@ <script> import tablePagination from '~/vue_shared/components/table_pagination.vue'; import eventHub from '../event_hub'; +import { getParameterByName } from '../../lib/utils/common_utils'; export default { props: { @@ -18,8 +19,8 @@ export default { }, methods: { change(page) { - const filterGroupsParam = gl.utils.getParameterByName('filter_groups'); - const sortParam = gl.utils.getParameterByName('sort'); + const filterGroupsParam = getParameterByName('filter_groups'); + const sortParam = getParameterByName('sort'); eventHub.$emit('fetchPage', page, filterGroupsParam, sortParam); }, }, diff --git a/app/assets/javascripts/groups/groups_filterable_list.js b/app/assets/javascripts/groups/groups_filterable_list.js index 439a931ddad..83b102764ba 100644 --- a/app/assets/javascripts/groups/groups_filterable_list.js +++ b/app/assets/javascripts/groups/groups_filterable_list.js @@ -1,5 +1,6 @@ import FilterableList from '~/filterable_list'; import eventHub from './event_hub'; +import { getParameterByName } from '../lib/utils/common_utils'; export default class GroupFilterableList extends FilterableList { constructor({ form, filter, holder, filterEndpoint, pagePath }) { @@ -54,7 +55,7 @@ export default class GroupFilterableList extends FilterableList { e.preventDefault(); const queryData = {}; - const sortParam = gl.utils.getParameterByName('sort', e.currentTarget.href); + const sortParam = getParameterByName('sort', e.currentTarget.href); if (sortParam) { queryData.sort = sortParam; diff --git a/app/assets/javascripts/groups/index.js b/app/assets/javascripts/groups/index.js index 00e1bd94c9c..9ad8e5c6052 100644 --- a/app/assets/javascripts/groups/index.js +++ b/app/assets/javascripts/groups/index.js @@ -8,6 +8,7 @@ import GroupItem from './components/group_item.vue'; import GroupsStore from './stores/groups_store'; import GroupsService from './services/groups_service'; import eventHub from './event_hub'; +import { getParameterByName } from '../lib/utils/common_utils'; document.addEventListener('DOMContentLoaded', () => { const el = document.getElementById('dashboard-group-app'); @@ -58,17 +59,17 @@ document.addEventListener('DOMContentLoaded', () => { this.isLoading = true; } - pageParam = gl.utils.getParameterByName('page'); + pageParam = getParameterByName('page'); if (pageParam) { page = pageParam; } - filterGroupsParam = gl.utils.getParameterByName('filter_groups'); + filterGroupsParam = getParameterByName('filter_groups'); if (filterGroupsParam) { filterGroups = filterGroupsParam; } - sortParam = gl.utils.getParameterByName('sort'); + sortParam = getParameterByName('sort'); if (sortParam) { sort = sortParam; } diff --git a/app/assets/javascripts/groups/stores/groups_store.js b/app/assets/javascripts/groups/stores/groups_store.js index 6eab6083e8f..f59ec677603 100644 --- a/app/assets/javascripts/groups/stores/groups_store.js +++ b/app/assets/javascripts/groups/stores/groups_store.js @@ -1,4 +1,5 @@ import Vue from 'vue'; +import { parseIntPagination, normalizeHeaders } from '../../lib/utils/common_utils'; export default class GroupsStore { constructor() { @@ -30,8 +31,8 @@ export default class GroupsStore { let paginationInfo; if (Object.keys(pagination).length) { - const normalizedHeaders = gl.utils.normalizeHeaders(pagination); - paginationInfo = gl.utils.parseIntPagination(normalizedHeaders); + const normalizedHeaders = normalizeHeaders(pagination); + paginationInfo = parseIntPagination(normalizedHeaders); } else { paginationInfo = pagination; } diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js index 4d629bc6326..90ca70289ab 100644 --- a/app/assets/javascripts/groups_select.js +++ b/app/assets/javascripts/groups_select.js @@ -4,6 +4,7 @@ prefer-rest-params, prefer-spread, no-unused-vars, prefer-template, promise/catch-or-return */ import Api from './api'; +import { normalizeCRLFHeaders } from './lib/utils/common_utils'; var slice = [].slice; @@ -30,7 +31,7 @@ window.GroupsSelect = (function() { $.ajax(params).then((data, status, xhr) => { const results = data || []; - const headers = gl.utils.normalizeCRLFHeaders(xhr.getAllResponseHeaders()); + const headers = normalizeCRLFHeaders(xhr.getAllResponseHeaders()); const currentPage = parseInt(headers['X-PAGE'], 10) || 0; const totalPages = parseInt(headers['X-TOTAL-PAGES'], 10) || 0; const more = currentPage < totalPages; diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index b8bebe1894f..ead1b8f99d3 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -1,437 +1,437 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-param-reassign, no-else-return, quotes, object-shorthand, comma-dangle, camelcase, one-var, vars-on-top, one-var-declaration-per-line, no-return-assign, consistent-return, max-len, prefer-template */ -(function() { - (function(w) { - var base; - const faviconEl = document.getElementById('favicon'); - const originalFavicon = faviconEl ? faviconEl.getAttribute('href') : null; - w.gl || (w.gl = {}); - (base = w.gl).utils || (base.utils = {}); - w.gl.utils.isInGroupsPage = function() { - return gl.utils.getPagePath() === 'groups'; - }; - w.gl.utils.isInProjectPage = function() { - return gl.utils.getPagePath() === 'projects'; - }; - w.gl.utils.getProjectSlug = function() { - if (this.isInProjectPage()) { - return $('body').data('project'); - } else { - return null; - } - }; - w.gl.utils.getGroupSlug = function() { - if (this.isInGroupsPage()) { - return $('body').data('group'); - } else { - return null; - } - }; - - w.gl.utils.isInIssuePage = () => { - const page = gl.utils.getPagePath(1); - const action = gl.utils.getPagePath(2); - - return page === 'issues' && action === 'show'; - }; - w.gl.utils.ajaxGet = function(url) { - return $.ajax({ - type: "GET", - url: url, - dataType: "script" - }); - }; - - w.gl.utils.ajaxPost = function(url, data) { - return $.ajax({ - type: 'POST', - url: url, - data: data, - }); - }; - - w.gl.utils.extractLast = function(term) { - return this.split(term).pop(); - }; - - w.gl.utils.rstrip = function rstrip(val) { - if (val) { - return val.replace(/\s+$/, ''); +export const getPagePath = (index = 0) => $('body').data('page').split(':')[index]; + +export const isInGroupsPage = () => getPagePath() === 'groups'; + +export const isInProjectPage = () => getPagePath() === 'projects'; + +export const getProjectSlug = () => { + if (isInProjectPage()) { + return $('body').data('project'); + } + return null; +}; + +export const getGroupSlug = () => { + if (isInGroupsPage()) { + return $('body').data('group'); + } + return null; +}; + +export const isInIssuePage = () => { + const page = getPagePath(1); + const action = getPagePath(2); + + return page === 'issues' && action === 'show'; +}; + +export const ajaxGet = url => $.ajax({ + type: 'GET', + url, + dataType: 'script', +}); + +export const ajaxPost = (url, data) => $.ajax({ + type: 'POST', + url, + data, +}); + +export const rstrip = (val) => { + if (val) { + return val.replace(/\s+$/, ''); + } + return val; +}; + +export const updateTooltipTitle = ($tooltipEl, newTitle) => $tooltipEl.attr('title', newTitle).tooltip('fixTitle'); + +export const disableButtonIfEmptyField = (fieldSelector, buttonSelector, eventName = 'input') => { + const field = $(fieldSelector); + const closestSubmit = field.closest('form').find(buttonSelector); + if (rstrip(field.val()) === '') { + closestSubmit.disable(); + } + // eslint-disable-next-line func-names + return field.on(eventName, function () { + if (rstrip($(this).val()) === '') { + return closestSubmit.disable(); + } + return closestSubmit.enable(); + }); +}; + +// automatically adjust scroll position for hash urls taking the height of the navbar into account +// https://github.com/twitter/bootstrap/issues/1768 +export const handleLocationHash = () => { + let hash = window.gl.utils.getLocationHash(); + if (!hash) return; + + // This is required to handle non-unicode characters in hash + hash = decodeURIComponent(hash); + + const fixedTabs = document.querySelector('.js-tabs-affix'); + const fixedDiffStats = document.querySelector('.js-diff-files-changed.is-stuck'); + const fixedNav = document.querySelector('.navbar-gitlab'); + + let adjustment = 0; + if (fixedNav) adjustment -= fixedNav.offsetHeight; + + // scroll to user-generated markdown anchor if we cannot find a match + if (document.getElementById(hash) === null) { + const target = document.getElementById(`user-content-${hash}`); + if (target && target.scrollIntoView) { + target.scrollIntoView(true); + window.scrollBy(0, adjustment); + } + } else { + // only adjust for fixedTabs when not targeting user-generated content + if (fixedTabs) { + adjustment -= fixedTabs.offsetHeight; + } + + if (fixedDiffStats) { + adjustment -= fixedDiffStats.offsetHeight; + } + + window.scrollBy(0, adjustment); + } +}; + +// Check if element scrolled into viewport from above or below +// Courtesy http://stackoverflow.com/a/7557433/414749 +export const isInViewport = (el) => { + const rect = el.getBoundingClientRect(); + + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= window.innerHeight && + rect.right <= window.innerWidth + ); +}; + +export const parseUrl = (url) => { + const parser = document.createElement('a'); + parser.href = url; + return parser; +}; + +export const parseUrlPathname = (url) => { + const parsedUrl = parseUrl(url); + // parsedUrl.pathname will return an absolute path for Firefox and a relative path for IE11 + // We have to make sure we always have an absolute path. + return parsedUrl.pathname.charAt(0) === '/' ? parsedUrl.pathname : `/${parsedUrl.pathname}`; +}; + +// We can trust that each param has one & since values containing & will be encoded +// Remove the first character of search as it is always ? +export const getUrlParamsArray = () => window.location.search.slice(1).split('&').map((param) => { + const split = param.split('='); + return [decodeURI(split[0]), split[1]].join('='); +}); + +export const isMetaKey = e => e.metaKey || e.ctrlKey || e.altKey || e.shiftKey; + +// Identify following special clicks +// 1) Cmd + Click on Mac (e.metaKey) +// 2) Ctrl + Click on PC (e.ctrlKey) +// 3) Middle-click or Mouse Wheel Click (e.which is 2) +export const isMetaClick = e => e.metaKey || e.ctrlKey || e.which === 2; + +export const scrollToElement = ($el) => { + const top = $el.offset().top; + const mrTabsHeight = $('.merge-request-tabs').height() || 0; + const headerHeight = $('.navbar-gitlab').height() || 0; + + return $('body, html').animate({ + scrollTop: top - mrTabsHeight - headerHeight, + }, 200); +}; + +/** + this will take in the `name` of the param you want to parse in the url + if the name does not exist this function will return `null` + otherwise it will return the value of the param key provided +*/ +export const getParameterByName = (name, urlToParse) => { + const url = urlToParse || window.location.href; + const parsedName = name.replace(/[[\]]/g, '\\$&'); + const regex = new RegExp(`[?&]${parsedName}(=([^&#]*)|&|#|$)`); + const results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, ' ')); +}; + +export const getSelectedFragment = () => { + const selection = window.getSelection(); + if (selection.rangeCount === 0) return null; + const documentFragment = document.createDocumentFragment(); + for (let i = 0; i < selection.rangeCount; i += 1) { + documentFragment.appendChild(selection.getRangeAt(i).cloneContents()); + } + if (documentFragment.textContent.length === 0) return null; + + return documentFragment; +}; + +// TODO: Update this name, there is a gl.text.insertText function. +export const insertText = (target, text) => { + // Firefox doesn't support `document.execCommand('insertText', false, text)` on textareas + const selectionStart = target.selectionStart; + const selectionEnd = target.selectionEnd; + const value = target.value; + + const textBefore = value.substring(0, selectionStart); + const textAfter = value.substring(selectionEnd, value.length); + + const insertedText = text instanceof Function ? text(textBefore, textAfter) : text; + const newText = textBefore + insertedText + textAfter; + + // eslint-disable-next-line no-param-reassign + target.value = newText; + // eslint-disable-next-line no-param-reassign + target.selectionStart = target.selectionEnd = selectionStart + insertedText.length; + + // Trigger autosave + $(target).trigger('input'); + + // Trigger autosize + const event = document.createEvent('Event'); + event.initEvent('autosize:update', true, false); + target.dispatchEvent(event); +}; + +export const nodeMatchesSelector = (node, selector) => { + const matches = Element.prototype.matches || + Element.prototype.matchesSelector || + Element.prototype.mozMatchesSelector || + Element.prototype.msMatchesSelector || + Element.prototype.oMatchesSelector || + Element.prototype.webkitMatchesSelector; + + if (matches) { + return matches.call(node, selector); + } + + // IE11 doesn't support `node.matches(selector)` + + let parentNode = node.parentNode; + if (!parentNode) { + parentNode = document.createElement('div'); + // eslint-disable-next-line no-param-reassign + node = node.cloneNode(true); + parentNode.appendChild(node); + } + + const matchingNodes = parentNode.querySelectorAll(selector); + return Array.prototype.indexOf.call(matchingNodes, node) !== -1; +}; + +/** + this will take in the headers from an API response and normalize them + this way we don't run into production issues when nginx gives us lowercased header keys +*/ +export const normalizeHeaders = (headers) => { + const upperCaseHeaders = {}; + + Object.keys(headers).forEach((e) => { + upperCaseHeaders[e.toUpperCase()] = headers[e]; + }); + + return upperCaseHeaders; +}; + +/** + this will take in the getAllResponseHeaders result and normalize them + this way we don't run into production issues when nginx gives us lowercased header keys +*/ +export const normalizeCRLFHeaders = (headers) => { + const headersObject = {}; + const headersArray = headers.split('\n'); + + headersArray.forEach((header) => { + const keyValue = header.split(': '); + headersObject[keyValue[0]] = keyValue[1]; + }); + + return normalizeHeaders(headersObject); +}; + +/** + * Parses pagination object string values into numbers. + * + * @param {Object} paginationInformation + * @returns {Object} + */ +export const parseIntPagination = paginationInformation => ({ + perPage: parseInt(paginationInformation['X-PER-PAGE'], 10), + page: parseInt(paginationInformation['X-PAGE'], 10), + total: parseInt(paginationInformation['X-TOTAL'], 10), + totalPages: parseInt(paginationInformation['X-TOTAL-PAGES'], 10), + nextPage: parseInt(paginationInformation['X-NEXT-PAGE'], 10), + previousPage: parseInt(paginationInformation['X-PREV-PAGE'], 10), +}); + +/** + * Updates the search parameter of a URL given the parameter and value provided. + * + * If no search params are present we'll add it. + * If param for page is already present, we'll update it + * If there are params but not for the given one, we'll add it at the end. + * Returns the new search parameters. + * + * @param {String} param + * @param {Number|String|Undefined|Null} value + * @return {String} + */ +export const setParamInURL = (param, value) => { + let search; + const locationSearch = window.location.search; + + if (locationSearch.length) { + const parameters = locationSearch.substring(1, locationSearch.length) + .split('&') + .reduce((acc, element) => { + const val = element.split('='); + // eslint-disable-next-line no-param-reassign + acc[val[0]] = decodeURIComponent(val[1]); + return acc; + }, {}); + + parameters[param] = value; + + const toString = Object.keys(parameters) + .map(val => `${val}=${encodeURIComponent(parameters[val])}`) + .join('&'); + + search = `?${toString}`; + } else { + search = `?${param}=${value}`; + } + + return search; +}; + +/** + * Converts permission provided as strings to booleans. + * + * @param {String} string + * @returns {Boolean} + */ +export const convertPermissionToBoolean = permission => permission === 'true'; + +/** + * Back Off exponential algorithm + * backOff :: (Function<next, stop>, Number) -> Promise<Any, Error> + * + * @param {Function<next, stop>} fn function to be called + * @param {Number} timeout + * @return {Promise<Any, Error>} + * @example + * ``` + * backOff(function (next, stop) { + * // Let's perform this function repeatedly for 60s or for the timeout provided. + * + * ourFunction() + * .then(function (result) { + * // continue if result is not what we need + * next(); + * + * // when result is what we need let's stop with the repetions and jump out of the cycle + * stop(result); + * }) + * .catch(function (error) { + * // if there is an error, we need to stop this with an error. + * stop(error); + * }) + * }, 60000) + * .then(function (result) {}) + * .catch(function (error) { + * // deal with errors passed to stop() + * }) + * ``` + */ +export const backOff = (fn, timeout = 60000) => { + const maxInterval = 32000; + let nextInterval = 2000; + let timeElapsed = 0; + + return new Promise((resolve, reject) => { + const stop = arg => ((arg instanceof Error) ? reject(arg) : resolve(arg)); + + const next = () => { + if (timeElapsed < timeout) { + setTimeout(() => fn(next, stop), nextInterval); + timeElapsed += nextInterval; + nextInterval = Math.min(nextInterval + nextInterval, maxInterval); } else { - return val; - } - }; - - gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) { - return $tooltipEl.attr('title', newTitle).tooltip('fixTitle'); - }; - - w.gl.utils.disableButtonIfEmptyField = function(field_selector, button_selector, event_name) { - event_name = event_name || 'input'; - var closest_submit, field, that; - that = this; - field = $(field_selector); - closest_submit = field.closest('form').find(button_selector); - if (this.rstrip(field.val()) === "") { - closest_submit.disable(); + reject(new Error('BACKOFF_TIMEOUT')); } - return field.on(event_name, function() { - if (that.rstrip($(this).val()) === "") { - return closest_submit.disable(); - } else { - return closest_submit.enable(); - } - }); }; - // automatically adjust scroll position for hash urls taking the height of the navbar into account - // https://github.com/twitter/bootstrap/issues/1768 - w.gl.utils.handleLocationHash = function() { - var hash = w.gl.utils.getLocationHash(); - if (!hash) return; - - // This is required to handle non-unicode characters in hash - hash = decodeURIComponent(hash); - - const fixedTabs = document.querySelector('.js-tabs-affix'); - const fixedDiffStats = document.querySelector('.js-diff-files-changed.is-stuck'); - const fixedNav = document.querySelector('.navbar-gitlab'); - - var adjustment = 0; - if (fixedNav) adjustment -= fixedNav.offsetHeight; - - // scroll to user-generated markdown anchor if we cannot find a match - if (document.getElementById(hash) === null) { - var target = document.getElementById('user-content-' + hash); - if (target && target.scrollIntoView) { - target.scrollIntoView(true); - window.scrollBy(0, adjustment); - } + fn(next, stop); + }); +}; + +export const setFavicon = (faviconPath) => { + const faviconEl = document.getElementById('favicon'); + if (faviconEl && faviconPath) { + faviconEl.setAttribute('href', faviconPath); + } +}; + +export const resetFavicon = () => { + const faviconEl = document.getElementById('favicon'); + const originalFavicon = faviconEl ? faviconEl.getAttribute('href') : null; + if (faviconEl) { + faviconEl.setAttribute('href', originalFavicon); + } +}; + +export const setCiStatusFavicon = (pageUrl) => { + $.ajax({ + url: pageUrl, + dataType: 'json', + success: (data) => { + if (data && data.favicon) { + setFavicon(data.favicon); } else { - // only adjust for fixedTabs when not targeting user-generated content - if (fixedTabs) { - adjustment -= fixedTabs.offsetHeight; - } - - if (fixedDiffStats) { - adjustment -= fixedDiffStats.offsetHeight; - } - - window.scrollBy(0, adjustment); + resetFavicon(); } - }; - - // Check if element scrolled into viewport from above or below - // Courtesy http://stackoverflow.com/a/7557433/414749 - w.gl.utils.isInViewport = function(el) { - var rect = el.getBoundingClientRect(); - - return ( - rect.top >= 0 && - rect.left >= 0 && - rect.bottom <= window.innerHeight && - rect.right <= window.innerWidth - ); - }; - - gl.utils.getPagePath = function(index) { - index = index || 0; - return $('body').data('page').split(':')[index]; - }; - - gl.utils.parseUrl = function (url) { - var parser = document.createElement('a'); - parser.href = url; - return parser; - }; - - gl.utils.parseUrlPathname = function (url) { - var parsedUrl = gl.utils.parseUrl(url); - // parsedUrl.pathname will return an absolute path for Firefox and a relative path for IE11 - // We have to make sure we always have an absolute path. - return parsedUrl.pathname.charAt(0) === '/' ? parsedUrl.pathname : '/' + parsedUrl.pathname; - }; - - gl.utils.getUrlParamsArray = function () { - // We can trust that each param has one & since values containing & will be encoded - // Remove the first character of search as it is always ? - return window.location.search.slice(1).split('&').map((param) => { - const split = param.split('='); - return [decodeURI(split[0]), split[1]].join('='); - }); - }; - - gl.utils.isMetaKey = function(e) { - return e.metaKey || e.ctrlKey || e.altKey || e.shiftKey; - }; - - gl.utils.isMetaClick = function(e) { - // Identify following special clicks - // 1) Cmd + Click on Mac (e.metaKey) - // 2) Ctrl + Click on PC (e.ctrlKey) - // 3) Middle-click or Mouse Wheel Click (e.which is 2) - return e.metaKey || e.ctrlKey || e.which === 2; - }; - - gl.utils.scrollToElement = function($el) { - const top = $el.offset().top; - const mrTabsHeight = $('.merge-request-tabs').height() || 0; - const headerHeight = $('.navbar-gitlab').height() || 0; - - return $('body, html').animate({ - scrollTop: top - mrTabsHeight - headerHeight, - }, 200); - }; - - /** - this will take in the `name` of the param you want to parse in the url - if the name does not exist this function will return `null` - otherwise it will return the value of the param key provided - */ - w.gl.utils.getParameterByName = (name, parseUrl) => { - const url = parseUrl || window.location.href; - name = name.replace(/[[\]]/g, '\\$&'); - const regex = new RegExp(`[?&]${name}(=([^&#]*)|&|#|$)`); - const results = regex.exec(url); - if (!results) return null; - if (!results[2]) return ''; - return decodeURIComponent(results[2].replace(/\+/g, ' ')); - }; - - w.gl.utils.getSelectedFragment = () => { - const selection = window.getSelection(); - if (selection.rangeCount === 0) return null; - const documentFragment = document.createDocumentFragment(); - for (let i = 0; i < selection.rangeCount; i += 1) { - documentFragment.appendChild(selection.getRangeAt(i).cloneContents()); - } - if (documentFragment.textContent.length === 0) return null; - - return documentFragment; - }; - - w.gl.utils.insertText = (target, text) => { - // Firefox doesn't support `document.execCommand('insertText', false, text)` on textareas - - const selectionStart = target.selectionStart; - const selectionEnd = target.selectionEnd; - const value = target.value; - - const textBefore = value.substring(0, selectionStart); - const textAfter = value.substring(selectionEnd, value.length); - - const insertedText = text instanceof Function ? text(textBefore, textAfter) : text; - const newText = textBefore + insertedText + textAfter; - - target.value = newText; - target.selectionStart = target.selectionEnd = selectionStart + insertedText.length; - - // Trigger autosave - $(target).trigger('input'); - - // Trigger autosize - var event = document.createEvent('Event'); - event.initEvent('autosize:update', true, false); - target.dispatchEvent(event); - }; - - w.gl.utils.nodeMatchesSelector = (node, selector) => { - const matches = Element.prototype.matches || - Element.prototype.matchesSelector || - Element.prototype.mozMatchesSelector || - Element.prototype.msMatchesSelector || - Element.prototype.oMatchesSelector || - Element.prototype.webkitMatchesSelector; - - if (matches) { - return matches.call(node, selector); - } - - // IE11 doesn't support `node.matches(selector)` - - let parentNode = node.parentNode; - if (!parentNode) { - parentNode = document.createElement('div'); - node = node.cloneNode(true); - parentNode.appendChild(node); - } - - const matchingNodes = parentNode.querySelectorAll(selector); - return Array.prototype.indexOf.call(matchingNodes, node) !== -1; - }; - - /** - this will take in the headers from an API response and normalize them - this way we don't run into production issues when nginx gives us lowercased header keys - */ - w.gl.utils.normalizeHeaders = (headers) => { - const upperCaseHeaders = {}; - - Object.keys(headers).forEach((e) => { - upperCaseHeaders[e.toUpperCase()] = headers[e]; - }); - - return upperCaseHeaders; - }; - - /** - this will take in the getAllResponseHeaders result and normalize them - this way we don't run into production issues when nginx gives us lowercased header keys - */ - w.gl.utils.normalizeCRLFHeaders = (headers) => { - const headersObject = {}; - const headersArray = headers.split('\n'); - - headersArray.forEach((header) => { - const keyValue = header.split(': '); - headersObject[keyValue[0]] = keyValue[1]; - }); - - return w.gl.utils.normalizeHeaders(headersObject); - }; - - /** - * Parses pagination object string values into numbers. - * - * @param {Object} paginationInformation - * @returns {Object} - */ - w.gl.utils.parseIntPagination = paginationInformation => ({ - perPage: parseInt(paginationInformation['X-PER-PAGE'], 10), - page: parseInt(paginationInformation['X-PAGE'], 10), - total: parseInt(paginationInformation['X-TOTAL'], 10), - totalPages: parseInt(paginationInformation['X-TOTAL-PAGES'], 10), - nextPage: parseInt(paginationInformation['X-NEXT-PAGE'], 10), - previousPage: parseInt(paginationInformation['X-PREV-PAGE'], 10), - }); - - /** - * Updates the search parameter of a URL given the parameter and value provided. - * - * If no search params are present we'll add it. - * If param for page is already present, we'll update it - * If there are params but not for the given one, we'll add it at the end. - * Returns the new search parameters. - * - * @param {String} param - * @param {Number|String|Undefined|Null} value - * @return {String} - */ - w.gl.utils.setParamInURL = (param, value) => { - let search; - const locationSearch = window.location.search; - - if (locationSearch.length) { - const parameters = locationSearch.substring(1, locationSearch.length) - .split('&') - .reduce((acc, element) => { - const val = element.split('='); - acc[val[0]] = decodeURIComponent(val[1]); - return acc; - }, {}); - - parameters[param] = value; - - const toString = Object.keys(parameters) - .map(val => `${val}=${encodeURIComponent(parameters[val])}`) - .join('&'); - - search = `?${toString}`; - } else { - search = `?${param}=${value}`; - } - - return search; - }; - - /** - * Converts permission provided as strings to booleans. - * - * @param {String} string - * @returns {Boolean} - */ - w.gl.utils.convertPermissionToBoolean = permission => permission === 'true'; - - /** - * Back Off exponential algorithm - * backOff :: (Function<next, stop>, Number) -> Promise<Any, Error> - * - * @param {Function<next, stop>} fn function to be called - * @param {Number} timeout - * @return {Promise<Any, Error>} - * @example - * ``` - * backOff(function (next, stop) { - * // Let's perform this function repeatedly for 60s or for the timeout provided. - * - * ourFunction() - * .then(function (result) { - * // continue if result is not what we need - * next(); - * - * // when result is what we need let's stop with the repetions and jump out of the cycle - * stop(result); - * }) - * .catch(function (error) { - * // if there is an error, we need to stop this with an error. - * stop(error); - * }) - * }, 60000) - * .then(function (result) {}) - * .catch(function (error) { - * // deal with errors passed to stop() - * }) - * ``` - */ - w.gl.utils.backOff = (fn, timeout = 60000) => { - const maxInterval = 32000; - let nextInterval = 2000; - let timeElapsed = 0; - - return new Promise((resolve, reject) => { - const stop = arg => ((arg instanceof Error) ? reject(arg) : resolve(arg)); - - const next = () => { - if (timeElapsed < timeout) { - setTimeout(() => fn(next, stop), nextInterval); - timeElapsed += nextInterval; - nextInterval = Math.min(nextInterval + nextInterval, maxInterval); - } else { - reject(new Error('BACKOFF_TIMEOUT')); - } - }; - - fn(next, stop); - }); - }; - - w.gl.utils.setFavicon = (faviconPath) => { - if (faviconEl && faviconPath) { - faviconEl.setAttribute('href', faviconPath); - } - }; - - w.gl.utils.resetFavicon = () => { - if (faviconEl) { - faviconEl.setAttribute('href', originalFavicon); - } - }; - - w.gl.utils.setCiStatusFavicon = (pageUrl) => { - $.ajax({ - url: pageUrl, - dataType: 'json', - success: function(data) { - if (data && data.favicon) { - gl.utils.setFavicon(data.favicon); - } else { - gl.utils.resetFavicon(); - } - }, - error: function() { - gl.utils.resetFavicon(); - } - }); - }; - })(window); -}).call(window); + }, + error: () => { + resetFavicon(); + }, + }); +}; + +window.gl = window.gl || {}; +window.gl.utils = { + ...(window.gl.utils || {}), + getPagePath, + isInGroupsPage, + isInProjectPage, + getProjectSlug, + getGroupSlug, + isInIssuePage, + ajaxGet, + ajaxPost, + rstrip, + updateTooltipTitle, + disableButtonIfEmptyField, + handleLocationHash, + isInViewport, + parseUrl, + parseUrlPathname, + getUrlParamsArray, + isMetaKey, + isMetaClick, + scrollToElement, + getParameterByName, + getSelectedFragment, + insertText, + nodeMatchesSelector, +}; diff --git a/app/assets/javascripts/lib/utils/poll.js b/app/assets/javascripts/lib/utils/poll.js index 97666e13ebe..1485e900945 100644 --- a/app/assets/javascripts/lib/utils/poll.js +++ b/app/assets/javascripts/lib/utils/poll.js @@ -1,4 +1,5 @@ import httpStatusCodes from './http_status'; +import { normalizeHeaders } from './common_utils'; /** * Polling utility for handling realtime updates. @@ -57,7 +58,7 @@ export default class Poll { } checkConditions(response) { - const headers = gl.utils.normalizeHeaders(response.headers); + const headers = normalizeHeaders(response.headers); const pollInterval = parseInt(headers[this.intervalHeader], 10); if (pollInterval > 0 && response.status === httpStatusCodes.OK && this.canPoll) { diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index c2a104df749..58d877f73fe 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -40,7 +40,7 @@ import './commit/image_file'; // lib/utils import './lib/utils/bootstrap_linked_tabs'; -import './lib/utils/common_utils'; +import { handleLocationHash } from './lib/utils/common_utils'; import './lib/utils/datetime_utility'; import './lib/utils/pretty_time'; import './lib/utils/text_utility'; @@ -160,10 +160,10 @@ document.addEventListener('beforeunload', function () { $('[data-toggle="popover"]').popover('destroy'); }); -window.addEventListener('hashchange', gl.utils.handleLocationHash); +window.addEventListener('hashchange', handleLocationHash); window.addEventListener('load', function onLoad() { window.removeEventListener('load', onLoad, false); - gl.utils.handleLocationHash(); + handleLocationHash(); }, false); gl.lazyLoader = new LazyLoader({ @@ -189,7 +189,7 @@ $(function () { $body.on('click', 'a[href^="#"]', function() { var href = this.getAttribute('href'); if (href.substr(1) === gl.utils.getLocationHash()) { - setTimeout(gl.utils.handleLocationHash, 1); + setTimeout(handleLocationHash, 1); } }); diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 0c3c877ff15..f71a59aaa84 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -7,6 +7,11 @@ import './flash'; import BlobForkSuggestion from './blob/blob_fork_suggestion'; import initChangesDropdown from './init_changes_dropdown'; import bp from './breakpoints'; +import { + parseUrlPathname, + handleLocationHash, + isMetaClick, +} from './lib/utils/common_utils'; /* eslint-disable max-len */ // MergeRequestTabs @@ -114,7 +119,7 @@ import bp from './breakpoints'; } clickTab(e) { - if (e.currentTarget && gl.utils.isMetaClick(e)) { + if (e.currentTarget && isMetaClick(e)) { const targetLink = e.currentTarget.getAttribute('href'); e.stopImmediatePropagation(); e.preventDefault(); @@ -260,7 +265,7 @@ import bp from './breakpoints'; // We extract pathname for the current Changes tab anchor href // some pages like MergeRequestsController#new has query parameters on that anchor - const urlPathname = gl.utils.parseUrlPathname(source); + const urlPathname = parseUrlPathname(source); this.ajaxGet({ url: `${urlPathname}.json${location.search}`, @@ -309,7 +314,7 @@ import bp from './breakpoints'; forceShow: true, }); anchor[0].scrollIntoView(); - window.gl.utils.handleLocationHash(); + handleLocationHash(); // We have multiple elements on the page with `#note_xxx` // (discussion and diff tabs) and `:target` only applies to the first anchor.addClass('target'); diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 5d96b193fce..192473b7dd1 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -7,6 +7,7 @@ import EmptyState from './empty_state.vue'; import MonitoringStore from '../stores/monitoring_store'; import eventHub from '../event_hub'; + import { convertPermissionToBoolean } from '../../lib/utils/common_utils'; export default { @@ -17,7 +18,7 @@ return { store, state: 'gettingStarted', - hasMetrics: gl.utils.convertPermissionToBoolean(metricsData.hasMetrics), + hasMetrics: convertPermissionToBoolean(metricsData.hasMetrics), documentationPath: metricsData.documentationPath, settingsPath: metricsData.settingsPath, metricsEndpoint: metricsData.additionalMetrics, diff --git a/app/assets/javascripts/monitoring/services/monitoring_service.js b/app/assets/javascripts/monitoring/services/monitoring_service.js index 4ed651d5740..fed884d5c94 100644 --- a/app/assets/javascripts/monitoring/services/monitoring_service.js +++ b/app/assets/javascripts/monitoring/services/monitoring_service.js @@ -1,6 +1,7 @@ import Vue from 'vue'; import VueResource from 'vue-resource'; import statusCodes from '../../lib/utils/http_status'; +import { backOff } from '../../lib/utils/common_utils'; Vue.use(VueResource); @@ -8,7 +9,7 @@ const MAX_REQUESTS = 3; function backOffRequest(makeRequestCallback) { let requestCounter = 0; - return gl.utils.backOff((next, stop) => { + return backOff((next, stop) => { makeRequestCallback().then((resp) => { if (resp.status === statusCodes.NO_CONTENT) { requestCounter += 1; diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index f5f7bb4653d..93aa29454a0 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -23,6 +23,7 @@ import loadAwardsHandler from './awards_handler'; import './autosave'; import './dropzone_input'; import TaskList from './task_list'; +import { ajaxPost, isInViewport, getPagePath, scrollToElement, isMetaKey } from './lib/utils/common_utils'; window.autosize = autosize; window.Dropzone = Dropzone; @@ -81,7 +82,7 @@ export default class Notes { this.setViewType(view); // We are in the Merge Requests page so we need another edit form for Changes tab - if (gl.utils.getPagePath(1) === 'merge_requests') { + if (getPagePath(1) === 'merge_requests') { $('.note-edit-form').clone() .addClass('mr-note-edit-form').insertAfter('.note-edit-form'); } @@ -175,7 +176,7 @@ export default class Notes { keydownNoteText(e) { var $textarea, discussionNoteForm, editNote, myLastNote, myLastNoteEditBtn, newText, originalText; - if (gl.utils.isMetaKey(e)) { + if (isMetaKey(e)) { return; } @@ -644,10 +645,10 @@ export default class Notes { } else { var $buttons = $el.find('.note-form-actions'); - var isWidgetVisible = gl.utils.isInViewport($el.get(0)); + var isWidgetVisible = isInViewport($el.get(0)); if (!isWidgetVisible) { - gl.utils.scrollToElement($el); + scrollToElement($el); } $el.find('.js-finish-edit-warning').show(); @@ -1188,7 +1189,7 @@ export default class Notes { } static checkMergeRequestStatus() { - if (gl.utils.getPagePath(1) === 'merge_requests') { + if (getPagePath(1) === 'merge_requests') { gl.mrWidget.checkStatus(); } } @@ -1326,7 +1327,7 @@ export default class Notes { * 2) Identify comment type; a) Main thread b) Discussion thread c) Discussion resolve * 3) Build temporary placeholder element (using `createPlaceholderNote`) * 4) Show placeholder note on UI - * 5) Perform network request to submit the note using `gl.utils.ajaxPost` + * 5) Perform network request to submit the note using `ajaxPost` * a) If request is successfully completed * 1. Remove placeholder element * 2. Show submitted Note element @@ -1408,7 +1409,7 @@ export default class Notes { /* eslint-disable promise/catch-or-return */ // Make request to submit comment on server - gl.utils.ajaxPost(formAction, formData) + ajaxPost(formAction, formData) .then((note) => { // Submission successful! remove placeholder $notesContainer.find(`#${noteUniqueId}`).remove(); @@ -1481,7 +1482,7 @@ export default class Notes { * * 1) Get Form metadata * 2) Update note element with new content - * 3) Perform network request to submit the updated note using `gl.utils.ajaxPost` + * 3) Perform network request to submit the updated note using `ajaxPost` * a) If request is successfully completed * 1. Show submitted Note element * b) If request failed @@ -1510,7 +1511,7 @@ export default class Notes { /* eslint-disable promise/catch-or-return */ // Make request to update comment on server - gl.utils.ajaxPost(formAction, formData) + ajaxPost(formAction, formData) .then((note) => { // Submission successful! render final note element this.updateNote(note, $editingNote); diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 13cd74bfa1c..923611bda9a 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -7,6 +7,7 @@ import * as constants from '../constants'; import service from '../services/issue_notes_service'; import loadAwardsHandler from '../../awards_handler'; import sidebarTimeTrackingEventHub from '../../sidebar/event_hub'; +import { isInViewport, scrollToElement } from '../../lib/utils/common_utils'; let eTagPoll; @@ -211,7 +212,7 @@ export const toggleAwardRequest = ({ commit, getters, dispatch }, data) => { }; export const scrollToNoteIfNeeded = (context, el) => { - if (!gl.utils.isInViewport(el[0])) { - gl.utils.scrollToElement(el); + if (!isInViewport(el[0])) { + scrollToElement(el); } }; diff --git a/app/assets/javascripts/pager.js b/app/assets/javascripts/pager.js index 01110420cca..e3fc1e2fc2f 100644 --- a/app/assets/javascripts/pager.js +++ b/app/assets/javascripts/pager.js @@ -1,4 +1,4 @@ -import '~/lib/utils/common_utils'; +import { getParameterByName } from '~/lib/utils/common_utils'; import '~/lib/utils/url_utility'; (() => { @@ -9,7 +9,7 @@ import '~/lib/utils/url_utility'; init(limit = 0, preload = false, disable = false, prepareData = $.noop, callback = $.noop) { this.url = $('.content_list').data('href') || gl.utils.removeParams(['limit', 'offset']); this.limit = limit; - this.offset = parseInt(gl.utils.getParameterByName('offset'), 10) || this.limit; + this.offset = parseInt(getParameterByName('offset'), 10) || this.limit; this.disable = disable; this.prepareData = prepareData; this.callback = callback; diff --git a/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js b/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js index 644efd10509..9e0e5cacb11 100644 --- a/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js +++ b/app/assets/javascripts/pipeline_schedules/setup_pipeline_variable_list.js @@ -1,3 +1,5 @@ +import { convertPermissionToBoolean } from '../lib/utils/common_utils'; + function insertRow($row) { const $rowClone = $row.clone(); $rowClone.removeAttr('data-is-persisted'); @@ -6,7 +8,7 @@ function insertRow($row) { } function removeRow($row) { - const isPersisted = gl.utils.convertPermissionToBoolean($row.attr('data-is-persisted')); + const isPersisted = convertPermissionToBoolean($row.attr('data-is-persisted')); if (isPersisted) { $row.hide(); diff --git a/app/assets/javascripts/pipelines.js b/app/assets/javascripts/pipelines.js index 26a36ad54d1..07abe714367 100644 --- a/app/assets/javascripts/pipelines.js +++ b/app/assets/javascripts/pipelines.js @@ -1,4 +1,5 @@ import LinkedTabs from './lib/utils/bootstrap_linked_tabs'; +import { setCiStatusFavicon } from './lib/utils/common_utils'; export default class Pipelines { constructor(options = {}) { @@ -8,7 +9,7 @@ export default class Pipelines { } if (options.pipelineStatusUrl) { - gl.utils.setCiStatusFavicon(options.pipelineStatusUrl); + setCiStatusFavicon(options.pipelineStatusUrl); } } } diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue index 5e6d6b2fbdc..4c53e2541fc 100644 --- a/app/assets/javascripts/pipelines/components/pipelines.vue +++ b/app/assets/javascripts/pipelines/components/pipelines.vue @@ -4,6 +4,7 @@ import tablePagination from '../../vue_shared/components/table_pagination.vue'; import navigationTabs from './navigation_tabs.vue'; import navigationControls from './nav_controls.vue'; + import { convertPermissionToBoolean, getParameterByName, setParamInURL } from '../../lib/utils/common_utils'; export default { props: { @@ -44,10 +45,10 @@ }, computed: { canCreatePipelineParsed() { - return gl.utils.convertPermissionToBoolean(this.canCreatePipeline); + return convertPermissionToBoolean(this.canCreatePipeline); }, scope() { - const scope = gl.utils.getParameterByName('scope'); + const scope = getParameterByName('scope'); return scope === null ? 'all' : scope; }, @@ -105,10 +106,10 @@ }; }, pageParameter() { - return gl.utils.getParameterByName('page') || this.pagenum; + return getParameterByName('page') || this.pagenum; }, scopeParameter() { - return gl.utils.getParameterByName('scope') || this.apiScope; + return getParameterByName('scope') || this.apiScope; }, }, created() { @@ -122,7 +123,7 @@ * @param {Number} pageNumber desired page to go to. */ change(pageNumber) { - const param = gl.utils.setParamInURL('page', pageNumber); + const param = setParamInURL('page', pageNumber); gl.utils.visitUrl(param); return param; diff --git a/app/assets/javascripts/pipelines/stores/pipelines_store.js b/app/assets/javascripts/pipelines/stores/pipelines_store.js index ffefe0192f2..651251d2623 100644 --- a/app/assets/javascripts/pipelines/stores/pipelines_store.js +++ b/app/assets/javascripts/pipelines/stores/pipelines_store.js @@ -1,3 +1,5 @@ +import { parseIntPagination, normalizeHeaders } from '../../lib/utils/common_utils'; + export default class PipelinesStore { constructor() { this.state = {}; @@ -19,8 +21,8 @@ export default class PipelinesStore { let paginationInfo; if (Object.keys(pagination).length) { - const normalizedHeaders = gl.utils.normalizeHeaders(pagination); - paginationInfo = gl.utils.parseIntPagination(normalizedHeaders); + const normalizedHeaders = normalizeHeaders(pagination); + paginationInfo = parseIntPagination(normalizedHeaders); } else { paginationInfo = pagination; } diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js index 4ccea0624ee..3deb242bc1f 100644 --- a/app/assets/javascripts/profile/profile.js +++ b/app/assets/javascripts/profile/profile.js @@ -1,5 +1,6 @@ /* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */ /* global Flash */ +import { getPagePath } from '../lib/utils/common_utils'; ((global) => { class Profile { @@ -93,7 +94,7 @@ return $title.val(comment[1]).change(); } }); - if (global.utils.getPagePath() === 'profiles') { + if (getPagePath() === 'profiles') { return new Profile(); } }); diff --git a/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js b/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js index ef4d6df5138..a4d50a52315 100644 --- a/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js +++ b/app/assets/javascripts/prometheus_metrics/prometheus_metrics.js @@ -1,4 +1,5 @@ import PANEL_STATE from './constants'; +import { backOff } from '../lib/utils/common_utils'; export default class PrometheusMetrics { constructor(wrapperSelector) { @@ -79,7 +80,7 @@ export default class PrometheusMetrics { loadActiveMetrics() { this.showMonitoringMetricsPanelState(PANEL_STATE.LOADING); - gl.utils.backOff((next, stop) => { + backOff((next, stop) => { $.getJSON(this.activeMetricsEndpoint) .done((res) => { if (res && res.success) { diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index 003a15592f3..38c9a71dd20 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -1,4 +1,5 @@ /* eslint-disable comma-dangle, no-return-assign, one-var, no-var, no-underscore-dangle, one-var-declaration-per-line, no-unused-vars, no-cond-assign, consistent-return, object-shorthand, prefer-arrow-callback, func-names, space-before-function-paren, prefer-template, quotes, class-methods-use-this, no-unused-expressions, no-sequences, wrap-iife, no-lonely-if, no-else-return, no-param-reassign, vars-on-top, max-len */ +import { isInGroupsPage, isInProjectPage, getGroupSlug, getProjectSlug } from './lib/utils/common_utils'; ((global) => { const KEYCODE = { @@ -146,14 +147,14 @@ } getCategoryContents() { - var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, userName, utils; + var dashboardOptions, groupOptions, issuesPath, items, mrPath, name, options, projectOptions, userId, userName; userId = gon.current_user_id; userName = gon.current_username; - utils = gl.utils, projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions; - if (utils.isInGroupsPage() && groupOptions) { - options = groupOptions[utils.getGroupSlug()]; - } else if (utils.isInProjectPage() && projectOptions) { - options = projectOptions[utils.getProjectSlug()]; + projectOptions = gl.projectOptions, groupOptions = gl.groupOptions, dashboardOptions = gl.dashboardOptions; + if (isInGroupsPage() && groupOptions) { + options = groupOptions[getGroupSlug()]; + } else if (isInProjectPage() && projectOptions) { + options = projectOptions[getProjectSlug()]; } else if (dashboardOptions) { options = dashboardOptions; } diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js index a606852c22c..2fffe09c74e 100644 --- a/app/assets/javascripts/todos.js +++ b/app/assets/javascripts/todos.js @@ -1,6 +1,7 @@ /* eslint-disable class-methods-use-this, no-unneeded-ternary, quote-props */ import UsersSelect from './users_select'; +import { isMetaClick } from './lib/utils/common_utils'; export default class Todos { constructor() { @@ -137,22 +138,17 @@ export default class Todos { goToTodoUrl(e) { const todoLink = this.dataset.url; - if (!todoLink) { + if (!todoLink || e.target.tagName === 'A' || e.target.tagName === 'IMG') { return; } - if (gl.utils.isMetaClick(e)) { + e.stopPropagation(); + e.preventDefault(); + + if (isMetaClick(e)) { const windowTarget = '_blank'; - const selected = e.target; - e.stopPropagation(); - e.preventDefault(); - - if (selected.tagName === 'IMG') { - const avatarUrl = selected.parentElement.getAttribute('href'); - window.open(avatarUrl, windowTarget); - } else { - window.open(todoLink, windowTarget); - } + + window.open(todoLink, windowTarget); } else { gl.utils.visitUrl(todoLink); } diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js index a4e34116c33..a8c686e5065 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_memory_usage.js @@ -1,6 +1,6 @@ import statusCodes from '../../lib/utils/http_status'; import { bytesToMiB } from '../../lib/utils/number_utils'; - +import { backOff } from '../../lib/utils/common_utils'; import MemoryGraph from '../../vue_shared/components/memory_graph'; import MRWidgetService from '../services/mr_widget_service'; @@ -84,7 +84,7 @@ export default { } }, loadMetrics() { - gl.utils.backOff((next, stop) => { + backOff((next, stop) => { MRWidgetService.fetchMetrics(this.metricsUrl) .then((res) => { if (res.status === statusCodes.NO_CONTENT) { diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js index 2f237262028..044b664484b 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js @@ -31,6 +31,7 @@ import { SquashBeforeMerge, notify, } from './dependencies'; +import { setFavicon } from '../lib/utils/common_utils'; export default { el: '#js-vue-mr-widget', @@ -86,7 +87,7 @@ export default { .then((res) => { this.handleNotification(res); this.mr.setData(res); - this.setFavicon(); + this.setFaviconHelper(); if (cb) { cb.call(null, res); @@ -115,9 +116,9 @@ export default { immediateExecution: true, }); }, - setFavicon() { + setFaviconHelper() { if (this.mr.ciStatusFaviconPath) { - gl.utils.setFavicon(this.mr.ciStatusFaviconPath); + setFavicon(this.mr.ciStatusFaviconPath); } }, fetchDeployments() { @@ -193,7 +194,7 @@ export default { }); }, handleMounted() { - this.setFavicon(); + this.setFaviconHelper(); this.initDeploymentsPolling(); }, }, |