diff options
author | Jose Vargas <jvargas@gitlab.com> | 2018-09-25 11:22:38 -0500 |
---|---|---|
committer | Jose Vargas <jvargas@gitlab.com> | 2018-09-25 11:22:38 -0500 |
commit | efedd968fb35a1113414885b48798c2723c7578d (patch) | |
tree | a417e431a24caa67ef2ac56373c3a0f869fefaa4 /app/assets | |
parent | d0f81b60e45a0eed8eb863aa9fc9070d1f416f14 (diff) | |
parent | 45b365b0e5abdb86ff6ac23b00578d5398134af9 (diff) | |
download | gitlab-ce-efedd968fb35a1113414885b48798c2723c7578d.tar.gz |
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into jivl-fix-monitoring-dashboard-resizing-navbar
Diffstat (limited to 'app/assets')
239 files changed, 1753 insertions, 1559 deletions
diff --git a/app/assets/images/cluster_app_logos/elasticsearch.png b/app/assets/images/cluster_app_logos/elasticsearch.png Binary files differnew file mode 100644 index 00000000000..96e9e0ff934 --- /dev/null +++ b/app/assets/images/cluster_app_logos/elasticsearch.png diff --git a/app/assets/images/cluster_app_logos/gitlab.png b/app/assets/images/cluster_app_logos/gitlab.png Binary files differnew file mode 100644 index 00000000000..cb2195fc6a2 --- /dev/null +++ b/app/assets/images/cluster_app_logos/gitlab.png diff --git a/app/assets/images/cluster_app_logos/helm.png b/app/assets/images/cluster_app_logos/helm.png Binary files differnew file mode 100644 index 00000000000..2989cae7b93 --- /dev/null +++ b/app/assets/images/cluster_app_logos/helm.png diff --git a/app/assets/images/cluster_app_logos/jeager.png b/app/assets/images/cluster_app_logos/jeager.png Binary files differnew file mode 100644 index 00000000000..be5bf2a0c9c --- /dev/null +++ b/app/assets/images/cluster_app_logos/jeager.png diff --git a/app/assets/images/cluster_app_logos/jupyterhub.png b/app/assets/images/cluster_app_logos/jupyterhub.png Binary files differnew file mode 100644 index 00000000000..80c7343067f --- /dev/null +++ b/app/assets/images/cluster_app_logos/jupyterhub.png diff --git a/app/assets/images/cluster_app_logos/kubernetes.png b/app/assets/images/cluster_app_logos/kubernetes.png Binary files differnew file mode 100644 index 00000000000..4d774909c10 --- /dev/null +++ b/app/assets/images/cluster_app_logos/kubernetes.png diff --git a/app/assets/images/cluster_app_logos/meltano.png b/app/assets/images/cluster_app_logos/meltano.png Binary files differnew file mode 100644 index 00000000000..7a2d82fbe27 --- /dev/null +++ b/app/assets/images/cluster_app_logos/meltano.png diff --git a/app/assets/images/cluster_app_logos/prometheus.png b/app/assets/images/cluster_app_logos/prometheus.png Binary files differnew file mode 100644 index 00000000000..a8663449b88 --- /dev/null +++ b/app/assets/images/cluster_app_logos/prometheus.png diff --git a/app/assets/javascripts/badges/components/badge.vue b/app/assets/javascripts/badges/components/badge.vue index 155c348286c..97232d7f783 100644 --- a/app/assets/javascripts/badges/components/badge.vue +++ b/app/assets/javascripts/badges/components/badge.vue @@ -1,13 +1,11 @@ <script> import Icon from '~/vue_shared/components/icon.vue'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; import Tooltip from '~/vue_shared/directives/tooltip'; export default { name: 'Badge', components: { Icon, - LoadingIcon, Tooltip, }, directives: { @@ -80,7 +78,7 @@ export default { /> </a> - <loading-icon + <gl-loading-icon v-show="isLoading" :inline="true" /> @@ -105,8 +103,8 @@ export default { </div> <button - v-tooltip v-show="hasError" + v-tooltip :title="s__('Badges|Reload badge image')" class="btn btn-transparent btn-sm text-primary" type="button" diff --git a/app/assets/javascripts/badges/components/badge_form.vue b/app/assets/javascripts/badges/components/badge_form.vue index b3f25da87ce..aff7c4180e3 100644 --- a/app/assets/javascripts/badges/components/badge_form.vue +++ b/app/assets/javascripts/badges/components/badge_form.vue @@ -4,7 +4,6 @@ import { mapActions, mapState } from 'vuex'; import createFlash from '~/flash'; import { s__, sprintf } from '~/locale'; import LoadingButton from '~/vue_shared/components/loading_button.vue'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; import createEmptyBadge from '../empty_badge'; import Badge from './badge.vue'; @@ -15,7 +14,6 @@ export default { components: { Badge, LoadingButton, - LoadingIcon, }, props: { isEditing: { @@ -207,7 +205,7 @@ export default { :link-url="renderedLinkUrl" /> <p v-show="isRendering"> - <loading-icon + <gl-loading-icon :inline="true" /> </p> diff --git a/app/assets/javascripts/badges/components/badge_list.vue b/app/assets/javascripts/badges/components/badge_list.vue index d2ec0fbb2c0..359d3e10380 100644 --- a/app/assets/javascripts/badges/components/badge_list.vue +++ b/app/assets/javascripts/badges/components/badge_list.vue @@ -1,6 +1,5 @@ <script> import { mapState } from 'vuex'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; import BadgeListRow from './badge_list_row.vue'; import { GROUP_BADGE } from '../constants'; @@ -8,7 +7,6 @@ export default { name: 'BadgeList', components: { BadgeListRow, - LoadingIcon, }, computed: { ...mapState(['badges', 'isLoading', 'kind']), @@ -31,10 +29,10 @@ export default { class="badge badge-pill" >{{ badges.length }}</span> </div> - <loading-icon + <gl-loading-icon v-show="isLoading" + :size="2" class="card-body" - size="2" /> <div v-if="hasNoBadges" diff --git a/app/assets/javascripts/badges/components/badge_list_row.vue b/app/assets/javascripts/badges/components/badge_list_row.vue index 712d81d0430..5d16ba3ce6d 100644 --- a/app/assets/javascripts/badges/components/badge_list_row.vue +++ b/app/assets/javascripts/badges/components/badge_list_row.vue @@ -2,7 +2,6 @@ import { mapActions, mapState } from 'vuex'; import { s__ } from '~/locale'; import Icon from '~/vue_shared/components/icon.vue'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; import { PROJECT_BADGE } from '../constants'; import Badge from './badge.vue'; @@ -11,7 +10,6 @@ export default { components: { Badge, Icon, - LoadingIcon, }, props: { badge: { @@ -79,7 +77,7 @@ export default { name="remove" /> </button> - <loading-icon + <gl-loading-icon v-show="badge.isDeleting" :inline="true" /> diff --git a/app/assets/javascripts/behaviors/index.js b/app/assets/javascripts/behaviors/index.js index 84fef4d8b4f..8c4eccc34a3 100644 --- a/app/assets/javascripts/behaviors/index.js +++ b/app/assets/javascripts/behaviors/index.js @@ -1,15 +1,19 @@ import './autosize'; import './bind_in_out'; import './markdown/render_gfm'; +import initGFMInput from './markdown/gfm_auto_complete'; import initCopyAsGFM from './markdown/copy_as_gfm'; import initCopyToClipboard from './copy_to_clipboard'; import './details_behavior'; import installGlEmojiElement from './gl_emoji'; import './quick_submit'; import './requires_input'; +import initPageShortcuts from './shortcuts'; import './toggler_behavior'; -import '../preview_markdown'; +import './preview_markdown'; installGlEmojiElement(); +initGFMInput(); initCopyAsGFM(); initCopyToClipboard(); +initPageShortcuts(); diff --git a/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js b/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js new file mode 100644 index 00000000000..a303e504cc7 --- /dev/null +++ b/app/assets/javascripts/behaviors/markdown/gfm_auto_complete.js @@ -0,0 +1,19 @@ +import $ from 'jquery'; +import { convertPermissionToBoolean } from '~/lib/utils/common_utils'; +import GfmAutoComplete from '~/gfm_auto_complete'; + +export default function initGFMInput() { + $('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => { + const gfm = new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources); + const enableGFM = convertPermissionToBoolean(el.dataset.supportsAutocomplete); + + gfm.setup($(el), { + emojis: true, + members: enableGFM, + issues: enableGFM, + milestones: enableGFM, + mergeRequests: enableGFM, + labels: enableGFM, + }); + }); +} diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js index dbff2bd4b10..429455f97ec 100644 --- a/app/assets/javascripts/behaviors/markdown/render_gfm.js +++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js @@ -3,7 +3,7 @@ import syntaxHighlight from '~/syntax_highlight'; import renderMath from './render_math'; import renderMermaid from './render_mermaid'; -// Render Gitlab flavoured Markdown +// Render GitLab flavoured Markdown // // Delegates to syntax highlight and render math & mermaid diagrams. // diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/behaviors/preview_markdown.js index 0964baf8954..0964baf8954 100644 --- a/app/assets/javascripts/preview_markdown.js +++ b/app/assets/javascripts/behaviors/preview_markdown.js diff --git a/app/assets/javascripts/behaviors/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts.js new file mode 100644 index 00000000000..7987a533ae5 --- /dev/null +++ b/app/assets/javascripts/behaviors/shortcuts.js @@ -0,0 +1,35 @@ +import Shortcuts from './shortcuts/shortcuts'; + +export default function initPageShortcuts() { + const { page } = document.body.dataset; + const pagesWithCustomShortcuts = [ + 'projects:activity', + 'projects:artifacts:browse', + 'projects:artifacts:file', + 'projects:blame:show', + 'projects:blob:show', + 'projects:commit:show', + 'projects:commits:show', + 'projects:find_file:show', + 'projects:issues:edit', + 'projects:issues:index', + 'projects:issues:new', + 'projects:issues:show', + 'projects:merge_requests:creations:diffs', + 'projects:merge_requests:creations:new', + 'projects:merge_requests:edit', + 'projects:merge_requests:index', + 'projects:merge_requests:show', + 'projects:network:show', + 'projects:show', + 'projects:tree:show', + 'groups:show', + ]; + + // the pages above have their own shortcuts sub-classes instantiated elsewhere + // TODO: replace this whitelist with something more automated/maintainable + if (page && !pagesWithCustomShortcuts.includes(page)) { + return new Shortcuts(); + } + return false; +} diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js index 99c71d6524a..6719bfd6d22 100644 --- a/app/assets/javascripts/shortcuts.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js @@ -1,9 +1,9 @@ import $ from 'jquery'; import Cookies from 'js-cookie'; import Mousetrap from 'mousetrap'; -import axios from './lib/utils/axios_utils'; -import { refreshCurrentPage, visitUrl } from './lib/utils/url_utility'; -import findAndFollowLink from './shortcuts_dashboard_navigation'; +import axios from '../../lib/utils/axios_utils'; +import { refreshCurrentPage, visitUrl } from '../../lib/utils/url_utility'; +import findAndFollowLink from '../../lib/utils/navigation_utility'; const defaultStopCallback = Mousetrap.stopCallback; Mousetrap.stopCallback = (e, element, combo) => { diff --git a/app/assets/javascripts/shortcuts_blob.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js index 908b9cab93d..052e33b4a2b 100644 --- a/app/assets/javascripts/shortcuts_blob.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js @@ -1,5 +1,5 @@ import Mousetrap from 'mousetrap'; -import { getLocationHash, visitUrl } from './lib/utils/url_utility'; +import { getLocationHash, visitUrl } from '../../lib/utils/url_utility'; import Shortcuts from './shortcuts'; const defaults = { diff --git a/app/assets/javascripts/shortcuts_find_file.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js index 8658081c6c2..8658081c6c2 100644 --- a/app/assets/javascripts/shortcuts_find_file.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js index e9451be31fd..5e48bf5a35c 100644 --- a/app/assets/javascripts/shortcuts_issuable.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js @@ -1,9 +1,9 @@ import $ from 'jquery'; import Mousetrap from 'mousetrap'; import _ from 'underscore'; -import Sidebar from './right_sidebar'; +import Sidebar from '../../right_sidebar'; import Shortcuts from './shortcuts'; -import { CopyAsGFM } from './behaviors/markdown/copy_as_gfm'; +import { CopyAsGFM } from '../markdown/copy_as_gfm'; export default class ShortcutsIssuable extends Shortcuts { constructor(isMergeRequest) { diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js index 6b595764bc5..fa9b2c9f755 100644 --- a/app/assets/javascripts/shortcuts_navigation.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js @@ -1,5 +1,5 @@ import Mousetrap from 'mousetrap'; -import findAndFollowLink from './shortcuts_dashboard_navigation'; +import findAndFollowLink from '../../lib/utils/navigation_utility'; import Shortcuts from './shortcuts'; export default class ShortcutsNavigation extends Shortcuts { diff --git a/app/assets/javascripts/shortcuts_network.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js index a88c280fa3b..a88c280fa3b 100644 --- a/app/assets/javascripts/shortcuts_network.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js diff --git a/app/assets/javascripts/shortcuts_wiki.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js index 41865dcf4ba..8b7e6a56d25 100644 --- a/app/assets/javascripts/shortcuts_wiki.js +++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js @@ -1,6 +1,6 @@ import Mousetrap from 'mousetrap'; import ShortcutsNavigation from './shortcuts_navigation'; -import findAndFollowLink from './shortcuts_dashboard_navigation'; +import findAndFollowLink from '../../lib/utils/navigation_utility'; export default class ShortcutsWiki extends ShortcutsNavigation { constructor() { diff --git a/app/assets/javascripts/blob/3d_viewer/index.js b/app/assets/javascripts/blob/3d_viewer/index.js index 68d4ddad551..1bdf1aeb76c 100644 --- a/app/assets/javascripts/blob/3d_viewer/index.js +++ b/app/assets/javascripts/blob/3d_viewer/index.js @@ -29,12 +29,12 @@ export default class Renderer { this.scene.add(this.camera); - // Setup the viewer + // Set up the viewer this.setupRenderer(); this.setupGrid(); this.setupLight(); - // Setup OrbitControls + // Set up OrbitControls this.controls = new OrbitControls( this.camera, this.renderer.domElement, diff --git a/app/assets/javascripts/boards/components/board_blank_state.vue b/app/assets/javascripts/boards/components/board_blank_state.vue index 286529b4d13..cde22725a89 100644 --- a/app/assets/javascripts/boards/components/board_blank_state.vue +++ b/app/assets/javascripts/boards/components/board_blank_state.vue @@ -83,7 +83,7 @@ export default { right on the way to making the most of your board. </p> <button - class="btn btn-create btn-inverted btn-block" + class="btn btn-success btn-inverted btn-block" type="button" @click.stop="addDefaultLists"> Add default lists diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index bfc8d9b03ad..7ddb22ad824 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -3,7 +3,6 @@ import Sortable from 'sortablejs'; import boardNewIssue from './board_new_issue.vue'; import boardCard from './board_card.vue'; import eventHub from '../eventhub'; -import loadingIcon from '../../vue_shared/components/loading_icon.vue'; const Store = gl.issueBoards.BoardsStore; @@ -12,7 +11,6 @@ export default { components: { boardCard, boardNewIssue, - loadingIcon, }, props: { groupId: { @@ -217,7 +215,7 @@ export default { v-if="loading" class="board-list-loading text-center" aria-label="Loading issues"> - <loading-icon /> + <gl-loading-icon /> </div> <board-new-issue v-if="list.type !== 'closed' && showIssueForm" @@ -233,19 +231,19 @@ export default { <board-card v-for="(issue, index) in issues" ref="issue" + :key="issue.id" :index="index" :list="list" :issue="issue" :issue-link-base="issueLinkBase" :group-id="groupId" :root-path="rootPath" - :disabled="disabled" - :key="issue.id" /> + :disabled="disabled" /> <li v-if="showCount" class="board-list-count text-center" data-issue-id="-1"> - <loading-icon + <gl-loading-icon v-show="list.loadingMore" label="Loading more issues" /> diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue index 1e3cd43d1f0..f248f53fa51 100644 --- a/app/assets/javascripts/boards/components/board_new_issue.vue +++ b/app/assets/javascripts/boards/components/board_new_issue.vue @@ -110,9 +110,9 @@ export default { Title </label> <input + :id="list.id + '-title'" ref="input" v-model="title" - :id="list.id + '-title'" class="form-control" type="text" name="issue_title" diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue index d50641dc3a9..f56d3fe000c 100644 --- a/app/assets/javascripts/boards/components/issue_card_inner.vue +++ b/app/assets/javascripts/boards/components/issue_card_inner.vue @@ -170,8 +170,8 @@ tooltip-placement="bottom" /> <span - v-tooltip v-if="shouldRenderCounter" + v-tooltip :title="assigneeCounterTooltip" class="avatar-counter" > @@ -184,10 +184,10 @@ class="board-card-footer" > <button - v-tooltip v-for="label in issue.labels" v-if="showLabel(label)" :key="label.id" + v-tooltip :style="labelStyle(label)" :title="label.description" class="badge color-label" diff --git a/app/assets/javascripts/boards/components/modal/index.vue b/app/assets/javascripts/boards/components/modal/index.vue index 7b33a7573e7..0c4c709324d 100644 --- a/app/assets/javascripts/boards/components/modal/index.vue +++ b/app/assets/javascripts/boards/components/modal/index.vue @@ -1,7 +1,6 @@ <script> /* global ListIssue */ import { urlParamsToObject } from '~/lib/utils/common_utils'; - import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import ModalHeader from './header.vue'; import ModalList from './list.vue'; import ModalFooter from './footer.vue'; @@ -14,7 +13,6 @@ ModalHeader, ModalList, ModalFooter, - loadingIcon, }, props: { newIssuePath: { @@ -167,7 +165,7 @@ class="add-issues-list text-center" > <div class="add-issues-list-loading"> - <loading-icon /> + <gl-loading-icon /> </div> </section> <modal-footer/> diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue index ef9844d5562..d4676914e02 100644 --- a/app/assets/javascripts/boards/components/project_select.vue +++ b/app/assets/javascripts/boards/components/project_select.vue @@ -2,14 +2,10 @@ import $ from 'jquery'; import _ from 'underscore'; import eventHub from '../eventhub'; -import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import Api from '../../api'; export default { name: 'BoardProjectSelect', - components: { - loadingIcon, - }, props: { groupId: { type: Number, @@ -119,7 +115,7 @@ export default { </div> <div class="dropdown-content"></div> <div class="dropdown-loading"> - <loading-icon /> + <gl-loading-icon /> </div> </div> </div> diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index bc263cbbfea..662363a6f26 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -9,7 +9,7 @@ import '~/vue_shared/models/assignee'; import FilteredSearchBoards from './filtered_search_boards'; import eventHub from './eventhub'; -import sidebarEventHub from '~/sidebar/event_hub'; // eslint-disable-line import/first +import sidebarEventHub from '~/sidebar/event_hub'; import './models/issue'; import './models/list'; import './models/milestone'; @@ -24,7 +24,7 @@ import './components/board'; import './components/board_sidebar'; import './components/new_list_dropdown'; import BoardAddIssuesModal from './components/modal/index.vue'; -import '~/vue_shared/vue_resource_interceptor'; // eslint-disable-line import/first +import '~/vue_shared/vue_resource_interceptor'; export default () => { const $boardApp = document.getElementById('board-app'); @@ -229,7 +229,7 @@ export default () => { template: ` <div class="board-extra-actions"> <button - class="btn btn-create prepend-left-10" + class="btn btn-success prepend-left-10" type="button" data-placement="bottom" ref="addIssuesButton" diff --git a/app/assets/javascripts/build_variables.js b/app/assets/javascripts/build_variables.js deleted file mode 100644 index d398e4a4c83..00000000000 --- a/app/assets/javascripts/build_variables.js +++ /dev/null @@ -1,10 +0,0 @@ -import $ from 'jquery'; - -export default function handleRevealVariables() { - $('.js-reveal-variables') - .off('click') - .on('click', function click() { - $('.js-build-variables').toggle(); - $(this).hide(); - }); -} diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue index 651f3b50236..0452729d3ff 100644 --- a/app/assets/javascripts/clusters/components/application_row.vue +++ b/app/assets/javascripts/clusters/components/application_row.vue @@ -2,6 +2,7 @@ /* 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, @@ -13,6 +14,7 @@ export default { components: { loadingButton, + identicon, }, props: { id: { @@ -31,6 +33,16 @@ type: String, required: false, }, + logoUrl: { + type: String, + required: false, + default: null, + }, + disabled: { + type: Boolean, + required: false, + default: false, + }, status: { type: String, required: false, @@ -60,6 +72,18 @@ 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}`; }, @@ -128,37 +152,81 @@ <template> <div - :class="rowJsClass" - class="gl-responsive-table-row gl-responsive-table-row-col-span" + :class="[ + rowJsClass, + isInstalled && 'cluster-application-installed', + disabled && 'cluster-application-disabled' + ]" + class="cluster-application-row gl-responsive-table-row gl-responsive-table-row-col-span" > <div class="gl-responsive-table-row-layout" role="row" > - <a - v-if="titleLink" - :href="titleLink" - target="blank" - rel="noopener noreferrer" + <div + class="table-section append-right-8 section-align-top" role="gridcell" - class="table-section section-15 section-align-top js-cluster-application-title" > - {{ title }} - </a> - <span - v-else - class="table-section section-15 section-align-top js-cluster-application-title" - > - {{ title }} - </span> + <img + v-if="hasLogo" + :src="logoUrl" + :alt="`${title} logo`" + class="cluster-application-logo avatar s40" + /> + <identicon + v-else + :entity-id="identiconId" + :entity-name="title" + size-class="s40" + /> + </div> <div - class="table-section section-wrap" + class="table-section cluster-application-description section-wrap" role="gridcell" > + <strong> + <a + v-if="titleLink" + :href="titleLink" + target="blank" + rel="noopener noreferrer" + class="js-cluster-application-title" + > + {{ title }} + </a> + <span + v-else + class="js-cluster-application-title" + > + {{ title }} + </span> + </strong> <slot name="description"></slot> + <div + v-if="hasError || isUnknownStatus" + class="cluster-application-error text-danger prepend-top-10" + > + <p class="js-cluster-application-general-error-message append-bottom-0"> + {{ generalErrorDescription }} + </p> + <ul v-if="statusReason || requestReason"> + <li + v-if="statusReason" + class="js-cluster-application-status-error-message" + > + {{ statusReason }} + </li> + <li + v-if="requestReason" + class="js-cluster-application-request-error-message" + > + {{ requestReason }} + </li> + </ul> + </div> </div> <div - :class="{ 'section-20': showManageButton, 'section-15': !showManageButton }" + :class="{ 'section-25': showManageButton, 'section-15': !showManageButton }" class="table-section table-button-footer section-align-top" role="gridcell" > @@ -168,6 +236,7 @@ > <a :href="manageLink" + :class="{ disabled: disabled }" class="btn" > {{ manageButtonLabel }} @@ -176,7 +245,7 @@ <div class="btn-group table-action-buttons"> <loading-button :loading="installButtonLoading" - :disabled="installButtonDisabled" + :disabled="disabled || installButtonDisabled" :label="installButtonLabel" class="js-cluster-application-install-button" @click="installClicked" @@ -184,35 +253,5 @@ </div> </div> </div> - <div - v-if="hasError || isUnknownStatus" - class="gl-responsive-table-row-layout" - role="row" - > - <div - class="alert alert-danger alert-block append-bottom-0 clusters-error-alert" - role="gridcell" - > - <div> - <p class="js-cluster-application-general-error-message"> - {{ generalErrorDescription }} - </p> - <ul v-if="statusReason || requestReason"> - <li - v-if="statusReason" - class="js-cluster-application-status-error-message" - > - {{ statusReason }} - </li> - <li - v-if="requestReason" - class="js-cluster-application-request-error-message" - > - {{ requestReason }} - </li> - </ul> - </div> - </div> - </div> </div> </template> diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index d708a9e595a..a1069985178 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -1,5 +1,14 @@ <script> import _ from 'underscore'; +import helmInstallIllustration from '@gitlab-org/gitlab-svgs/illustrations/kubernetes-installation.svg'; +import elasticsearchLogo from 'images/cluster_app_logos/elasticsearch.png'; +import gitlabLogo from 'images/cluster_app_logos/gitlab.png'; +import helmLogo from 'images/cluster_app_logos/helm.png'; +import jeagerLogo from 'images/cluster_app_logos/jeager.png'; +import jupyterhubLogo from 'images/cluster_app_logos/jupyterhub.png'; +import kubernetesLogo from 'images/cluster_app_logos/kubernetes.png'; +import meltanoLogo from 'images/cluster_app_logos/meltano.png'; +import prometheusLogo from 'images/cluster_app_logos/prometheus.png'; import { s__, sprintf } from '../../locale'; import applicationRow from './application_row.vue'; import clipboardButton from '../../vue_shared/components/clipboard_button.vue'; @@ -37,21 +46,21 @@ export default { default: '', }, }, + data: () => ({ + elasticsearchLogo, + gitlabLogo, + helmLogo, + jeagerLogo, + jupyterhubLogo, + kubernetesLogo, + meltanoLogo, + prometheusLogo, + }), computed: { - generalApplicationDescription() { - return sprintf( - _.escape( - s__( - `ClusterIntegration|Install applications on your Kubernetes cluster. - Read more about %{helpLink}`, - ), - ), - { - helpLink: `<a href="${this.helpPath}"> - ${_.escape(s__('ClusterIntegration|installing applications'))} - </a>`, - }, - false, + helmInstalled() { + return ( + this.applications.helm.status === APPLICATION_STATUS.INSTALLED || + this.applications.helm.status === APPLICATION_STATUS.UPDATED ); }, ingressId() { @@ -128,224 +137,240 @@ export default { return this.applications.jupyter.hostname; }, }, + created() { + this.helmInstallIllustration = helmInstallIllustration; + }, }; </script> <template> - <section - id="cluster-applications" - class="settings no-animate expanded" - > - <div class="settings-header"> - <h4> - {{ s__('ClusterIntegration|Applications') }} - </h4> - <p - class="append-bottom-0" - v-html="generalApplicationDescription" - > - </p> - </div> + <section id="cluster-applications"> + <h4> + {{ s__('ClusterIntegration|Applications') }} + </h4> + <p class="append-bottom-0"> + {{ s__(`ClusterIntegration|Choose which applications to install on your Kubernetes cluster. + Helm Tiller is required to install any of the following applications.`) }} + <a :href="helpPath"> + {{ __('More information') }} + </a> + </p> - <div class="settings-content"> - <div class="append-bottom-20"> - <application-row - id="helm" - :title="applications.helm.title" - :status="applications.helm.status" - :status-reason="applications.helm.statusReason" - :request-status="applications.helm.requestStatus" - :request-reason="applications.helm.requestReason" - title-link="https://docs.helm.sh/" - > - <div slot="description"> - {{ s__(`ClusterIntegration|Helm streamlines installing - and managing Kubernetes applications. - Tiller runs inside of your Kubernetes Cluster, - and manages releases of your charts.`) }} - </div> - </application-row> - <application-row - :id="ingressId" - :title="applications.ingress.title" - :status="applications.ingress.status" - :status-reason="applications.ingress.statusReason" - :request-status="applications.ingress.requestStatus" - :request-reason="applications.ingress.requestReason" - title-link="https://kubernetes.io/docs/concepts/services-networking/ingress/" + <div class="cluster-application-list prepend-top-10"> + <application-row + id="helm" + :logo-url="helmLogo" + :title="applications.helm.title" + :status="applications.helm.status" + :status-reason="applications.helm.statusReason" + :request-status="applications.helm.requestStatus" + :request-reason="applications.helm.requestReason" + class="rounded-top" + title-link="https://docs.helm.sh/" + > + <div slot="description"> + {{ s__(`ClusterIntegration|Helm streamlines installing + and managing Kubernetes applications. + Tiller runs inside of your Kubernetes Cluster, + and manages releases of your charts.`) }} + </div> + </application-row> + <div + v-show="!helmInstalled" + class="cluster-application-warning" + > + <div + class="svg-container" + v-html="helmInstallIllustration" > - <div slot="description"> - <p> - {{ s__(`ClusterIntegration|Ingress gives you a way to route - requests to services based on the request host or path, - centralizing a number of services into a single entrypoint.`) }} - </p> + </div> + {{ s__(`ClusterIntegration|You must first install Helm Tiller before + installing the applications below`) }} + </div> + <application-row + :id="ingressId" + :logo-url="kubernetesLogo" + :title="applications.ingress.title" + :status="applications.ingress.status" + :status-reason="applications.ingress.statusReason" + :request-status="applications.ingress.requestStatus" + :request-reason="applications.ingress.requestReason" + :disabled="!helmInstalled" + title-link="https://kubernetes.io/docs/concepts/services-networking/ingress/" + > + <div slot="description"> + <p> + {{ s__(`ClusterIntegration|Ingress gives you a way to route + requests to services based on the request host or path, + centralizing a number of services into a single entrypoint.`) }} + </p> - <template v-if="ingressInstalled"> - <div class="form-group"> - <label for="ingress-ip-address"> - {{ s__('ClusterIntegration|Ingress IP Address') }} - </label> - <div - v-if="ingressExternalIp" - class="input-group" - > - <input - id="ingress-ip-address" - :value="ingressExternalIp" - type="text" - class="form-control js-ip-address" - readonly - /> - <span class="input-group-append"> - <clipboard-button - :text="ingressExternalIp" - :title="s__('ClusterIntegration|Copy Ingress IP Address to clipboard')" - class="input-group-text js-clipboard-btn" - /> - </span> - </div> + <template v-if="ingressInstalled"> + <div class="form-group"> + <label for="ingress-ip-address"> + {{ s__('ClusterIntegration|Ingress IP Address') }} + </label> + <div + v-if="ingressExternalIp" + class="input-group" + > <input - v-else + id="ingress-ip-address" + :value="ingressExternalIp" type="text" class="form-control js-ip-address" readonly - value="?" /> + <span class="input-group-append"> + <clipboard-button + :text="ingressExternalIp" + :title="s__('ClusterIntegration|Copy Ingress IP Address to clipboard')" + class="input-group-text js-clipboard-btn" + /> + </span> </div> + <input + v-else + type="text" + class="form-control js-ip-address" + readonly + value="?" + /> + </div> - <p - v-if="!ingressExternalIp" - class="settings-message js-no-ip-message" - > - {{ s__(`ClusterIntegration|The IP address is in - the process of being assigned. Please check your Kubernetes - cluster or Quotas on Google Kubernetes Engine if it takes a long time.`) }} + <p + v-if="!ingressExternalIp" + class="settings-message js-no-ip-message" + > + {{ s__(`ClusterIntegration|The IP address is in + the process of being assigned. Please check your Kubernetes + cluster or Quotas on Google Kubernetes Engine if it takes a long time.`) }} - <a - :href="ingressHelpPath" - target="_blank" - rel="noopener noreferrer" - > - {{ __('More information') }} - </a> - </p> + <a + :href="ingressHelpPath" + target="_blank" + rel="noopener noreferrer" + > + {{ __('More information') }} + </a> + </p> - <p> - {{ s__(`ClusterIntegration|Point a wildcard DNS to this - generated IP address in order to access - your application after it has been deployed.`) }} - <a - :href="ingressDnsHelpPath" - target="_blank" - rel="noopener noreferrer" - > - {{ __('More information') }} - </a> - </p> + <p> + {{ s__(`ClusterIntegration|Point a wildcard DNS to this + generated IP address in order to access + your application after it has been deployed.`) }} + <a + :href="ingressDnsHelpPath" + target="_blank" + rel="noopener noreferrer" + > + {{ __('More information') }} + </a> + </p> - </template> - <div - v-else - v-html="ingressDescription" - > - </div> - </div> - </application-row> - <application-row - id="prometheus" - :title="applications.prometheus.title" - :manage-link="managePrometheusPath" - :status="applications.prometheus.status" - :status-reason="applications.prometheus.statusReason" - :request-status="applications.prometheus.requestStatus" - :request-reason="applications.prometheus.requestReason" - title-link="https://prometheus.io/docs/introduction/overview/" - > + </template> <div - slot="description" - v-html="prometheusDescription" + v-html="ingressDescription" > </div> - </application-row> - <application-row - id="runner" - :title="applications.runner.title" - :status="applications.runner.status" - :status-reason="applications.runner.statusReason" - :request-status="applications.runner.requestStatus" - :request-reason="applications.runner.requestReason" - title-link="https://docs.gitlab.com/runner/" - > - <div slot="description"> - {{ s__(`ClusterIntegration|GitLab Runner connects to this - project's repository and executes CI/CD jobs, - pushing results back and deploying, - applications to production.`) }} - </div> - </application-row> - <application-row - id="jupyter" - :title="applications.jupyter.title" - :status="applications.jupyter.status" - :status-reason="applications.jupyter.statusReason" - :request-status="applications.jupyter.requestStatus" - :request-reason="applications.jupyter.requestReason" - :install-application-request-params="{ hostname: applications.jupyter.hostname }" - title-link="https://jupyterhub.readthedocs.io/en/stable/" + </div> + </application-row> + <application-row + id="prometheus" + :logo-url="prometheusLogo" + :title="applications.prometheus.title" + :manage-link="managePrometheusPath" + :status="applications.prometheus.status" + :status-reason="applications.prometheus.statusReason" + :request-status="applications.prometheus.requestStatus" + :request-reason="applications.prometheus.requestReason" + :disabled="!helmInstalled" + title-link="https://prometheus.io/docs/introduction/overview/" + > + <div + slot="description" + v-html="prometheusDescription" > - <div slot="description"> - <p> - {{ s__(`ClusterIntegration|JupyterHub, a multi-user Hub, spawns, - manages, and proxies multiple instances of the single-user - Jupyter notebook server. JupyterHub can be used to serve - notebooks to a class of students, a corporate data science group, - or a scientific research group.`) }} - </p> + </div> + </application-row> + <application-row + id="runner" + :logo-url="gitlabLogo" + :title="applications.runner.title" + :status="applications.runner.status" + :status-reason="applications.runner.statusReason" + :request-status="applications.runner.requestStatus" + :request-reason="applications.runner.requestReason" + :disabled="!helmInstalled" + title-link="https://docs.gitlab.com/runner/" + > + <div slot="description"> + {{ s__(`ClusterIntegration|GitLab Runner connects to this + project's repository and executes CI/CD jobs, + pushing results back and deploying, + applications to production.`) }} + </div> + </application-row> + <application-row + id="jupyter" + :logo-url="jupyterhubLogo" + :title="applications.jupyter.title" + :status="applications.jupyter.status" + :status-reason="applications.jupyter.statusReason" + :request-status="applications.jupyter.requestStatus" + :request-reason="applications.jupyter.requestReason" + :install-application-request-params="{ hostname: applications.jupyter.hostname }" + :disabled="!helmInstalled" + class="hide-bottom-border rounded-bottom" + title-link="https://jupyterhub.readthedocs.io/en/stable/" + > + <div slot="description"> + <p> + {{ s__(`ClusterIntegration|JupyterHub, a multi-user Hub, spawns, + manages, and proxies multiple instances of the single-user + Jupyter notebook server. JupyterHub can be used to serve + notebooks to a class of students, a corporate data science group, + or a scientific research group.`) }} + </p> - <template v-if="ingressExternalIp"> - <div class="form-group"> - <label for="jupyter-hostname"> - {{ s__('ClusterIntegration|Jupyter Hostname') }} - </label> + <template v-if="ingressExternalIp"> + <div class="form-group"> + <label for="jupyter-hostname"> + {{ s__('ClusterIntegration|Jupyter Hostname') }} + </label> - <div class="input-group"> - <input - v-model="applications.jupyter.hostname" - :readonly="jupyterInstalled" - type="text" - class="form-control js-hostname" + <div class="input-group"> + <input + v-model="applications.jupyter.hostname" + :readonly="jupyterInstalled" + type="text" + class="form-control js-hostname" + /> + <span + class="input-group-btn" + > + <clipboard-button + :text="jupyterHostname" + :title="s__('ClusterIntegration|Copy Jupyter Hostname to clipboard')" + class="js-clipboard-btn" /> - <span - class="input-group-btn" - > - <clipboard-button - :text="jupyterHostname" - :title="s__('ClusterIntegration|Copy Jupyter Hostname to clipboard')" - class="js-clipboard-btn" - /> - </span> - </div> + </span> </div> - <p v-if="ingressInstalled"> - {{ s__(`ClusterIntegration|Replace this with your own hostname if you want. - If you do so, point hostname to Ingress IP Address from above.`) }} - <a - :href="ingressDnsHelpPath" - target="_blank" - rel="noopener noreferrer" - > - {{ __('More information') }} - </a> - </p> - </template> - </div> - </application-row> - <!-- - NOTE: Don't forget to update `clusters.scss` - min-height for this block and uncomment `application_spec` tests - --> - </div> + </div> + <p v-if="ingressInstalled"> + {{ s__(`ClusterIntegration|Replace this with your own hostname if you want. + If you do so, point hostname to Ingress IP Address from above.`) }} + <a + :href="ingressDnsHelpPath" + target="_blank" + rel="noopener noreferrer" + > + {{ __('More information') }} + </a> + </p> + </template> + </div> + </application-row> </div> </section> </template> diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue index 95c4be64d35..4849b0fa3db 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue @@ -76,10 +76,10 @@ <template> <div class="content-list pipelines"> - <loading-icon + <gl-loading-icon v-if="isLoading" :label="s__('Pipelines|Loading Pipelines')" - size="3" + :size="3" class="prepend-top-20" /> diff --git a/app/assets/javascripts/commons/gitlab_ui.js b/app/assets/javascripts/commons/gitlab_ui.js index 14c2db24205..aed26adfa5c 100644 --- a/app/assets/javascripts/commons/gitlab_ui.js +++ b/app/assets/javascripts/commons/gitlab_ui.js @@ -1,12 +1,16 @@ import Vue from 'vue'; +import Pagination from '@gitlab-org/gitlab-ui/dist/components/base/pagination'; import progressBar from '@gitlab-org/gitlab-ui/dist/components/base/progress_bar'; import modal from '@gitlab-org/gitlab-ui/dist/components/base/modal'; +import loadingIcon from '@gitlab-org/gitlab-ui/dist/components/base/loading_icon'; import dModal from '@gitlab-org/gitlab-ui/dist/directives/modal'; import dTooltip from '@gitlab-org/gitlab-ui/dist/directives/tooltip'; +Vue.component('gl-pagination', Pagination); Vue.component('gl-progress-bar', progressBar); Vue.component('gl-ui-modal', modal); +Vue.component('gl-loading-icon', loadingIcon); Vue.directive('gl-modal', dModal); Vue.directive('gl-tooltip', dTooltip); diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js index 742cf490ad2..539d0d29e0d 100644 --- a/app/assets/javascripts/commons/polyfills.js +++ b/app/assets/javascripts/commons/polyfills.js @@ -14,10 +14,10 @@ import 'core-js/es6/map'; import 'core-js/es6/weak-map'; // Browser polyfills -import 'classlist-polyfill'; import 'formdata-polyfill'; import './polyfills/custom_event'; import './polyfills/element'; import './polyfills/event'; import './polyfills/nodelist'; import './polyfills/request_idle_callback'; +import './polyfills/svg'; diff --git a/app/assets/javascripts/commons/polyfills/element.js b/app/assets/javascripts/commons/polyfills/element.js index b593bde6aa2..dde5e8f54f9 100644 --- a/app/assets/javascripts/commons/polyfills/element.js +++ b/app/assets/javascripts/commons/polyfills/element.js @@ -1,12 +1,17 @@ -Element.prototype.closest = Element.prototype.closest || +// polyfill Element.classList and DOMTokenList with classList.js +import 'classlist-polyfill'; + +Element.prototype.closest = + Element.prototype.closest || function closest(selector, selectedElement = this) { if (!selectedElement) return null; - return selectedElement.matches(selector) ? - selectedElement : - Element.prototype.closest(selector, selectedElement.parentElement); + return selectedElement.matches(selector) + ? selectedElement + : Element.prototype.closest(selector, selectedElement.parentElement); }; -Element.prototype.matches = Element.prototype.matches || +Element.prototype.matches = + Element.prototype.matches || Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || @@ -15,13 +20,15 @@ Element.prototype.matches = Element.prototype.matches || function matches(selector) { const elms = (this.document || this.ownerDocument).querySelectorAll(selector); let i = elms.length - 1; - while (i >= 0 && elms.item(i) !== this) { i -= 1; } + while (i >= 0 && elms.item(i) !== this) { + i -= 1; + } return i > -1; }; // From the polyfill on MDN, https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove#Polyfill -((arr) => { - arr.forEach((item) => { +(arr => { + arr.forEach(item => { if (Object.prototype.hasOwnProperty.call(item, 'remove')) { return; } diff --git a/app/assets/javascripts/commons/polyfills/svg.js b/app/assets/javascripts/commons/polyfills/svg.js new file mode 100644 index 00000000000..8648a568f6f --- /dev/null +++ b/app/assets/javascripts/commons/polyfills/svg.js @@ -0,0 +1,5 @@ +import svg4everybody from 'svg4everybody'; + +// polyfill support for external SVG file references via <use xlink:href> +// @see https://css-tricks.com/svg-use-external-source/ +svg4everybody(); diff --git a/app/assets/javascripts/deploy_keys/components/action_btn.vue b/app/assets/javascripts/deploy_keys/components/action_btn.vue index 7399fc97d45..10548da8ec5 100644 --- a/app/assets/javascripts/deploy_keys/components/action_btn.vue +++ b/app/assets/javascripts/deploy_keys/components/action_btn.vue @@ -1,11 +1,7 @@ <script> -import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import eventHub from '../eventhub'; export default { - components: { - loadingIcon, - }, props: { deployKey: { type: Object, @@ -45,7 +41,7 @@ export default { class="btn" @click="doAction"> <slot></slot> - <loading-icon + <gl-loading-icon v-if="isLoading" :inline="true" /> diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue index d91e4809126..aa52f120fe7 100644 --- a/app/assets/javascripts/deploy_keys/components/app.vue +++ b/app/assets/javascripts/deploy_keys/components/app.vue @@ -1,7 +1,6 @@ <script> import { s__ } from '~/locale'; import Flash from '~/flash'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue'; import eventHub from '../eventhub'; import DeployKeysService from '../service'; @@ -11,7 +10,6 @@ import KeysPanel from './keys_panel.vue'; export default { components: { KeysPanel, - LoadingIcon, NavigationTabs, }, props: { @@ -114,10 +112,10 @@ export default { <template> <div class="append-bottom-default deploy-keys"> - <loading-icon + <gl-loading-icon v-if="isLoading && !hasKeys" :label="s__('DeployKeys|Loading deploy keys')" - size="2" + :size="2" /> <template v-else-if="hasKeys"> <div class="top-area scrolling-tabs-container inner-page-scroll-tabs"> diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue index f66ca070445..c05b9b1de79 100644 --- a/app/assets/javascripts/deploy_keys/components/key.vue +++ b/app/assets/javascripts/deploy_keys/components/key.vue @@ -145,8 +145,8 @@ export default { <icon :name="firstProject.can_push ? 'lock-open' : 'lock'"/> </a> <a - v-tooltip v-if="isExpandable" + v-tooltip :title="restProjectsTooltip" class="label deploy-project-label" @click="toggleExpanded" @@ -154,10 +154,10 @@ export default { <span>{{ restProjectsLabel }}</span> </a> <a - v-tooltip v-for="deployKeysProject in restProjects" v-else-if="isExpanded" :key="deployKeysProject.project.full_path" + v-tooltip :href="deployKeysProject.project.full_path" :title="projectTooltipTitle(deployKeysProject)" class="label deploy-project-label" @@ -198,8 +198,8 @@ export default { {{ __('Enable') }} </action-btn> <a - v-tooltip v-if="deployKey.can_edit" + v-tooltip :href="editDeployKeyPath" :title="__('Edit')" class="btn btn-default text-secondary" @@ -208,8 +208,8 @@ export default { <icon name="pencil"/> </a> <action-btn - v-tooltip v-if="isRemovable" + v-tooltip :deploy-key="deployKey" :title="__('Remove')" btn-css-class="btn-danger" @@ -219,8 +219,8 @@ export default { <icon name="remove"/> </action-btn> <action-btn - v-tooltip v-else-if="isEnabled" + v-tooltip :deploy-key="deployKey" :title="__('Disable')" btn-css-class="btn-warning" diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 4261a99c52b..bfb992340bc 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -4,7 +4,6 @@ import Icon from '~/vue_shared/components/icon.vue'; import { __ } from '~/locale'; import createFlash from '~/flash'; import eventHub from '../../notes/event_hub'; -import LoadingIcon from '../../vue_shared/components/loading_icon.vue'; import CompareVersions from './compare_versions.vue'; import ChangedFiles from './changed_files.vue'; import DiffFile from './diff_file.vue'; @@ -15,7 +14,6 @@ export default { name: 'DiffsApp', components: { Icon, - LoadingIcon, CompareVersions, ChangedFiles, DiffFile, @@ -168,7 +166,7 @@ export default { v-if="isLoading" class="loading" > - <loading-icon /> + <gl-loading-icon /> </div> <div v-else diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index 67e85c4eee3..bcbe374a90c 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -3,7 +3,6 @@ import { mapActions, mapGetters } from 'vuex'; import _ from 'underscore'; import { __, sprintf } from '~/locale'; import createFlash from '~/flash'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; import DiffFileHeader from './diff_file_header.vue'; import DiffContent from './diff_content.vue'; @@ -11,7 +10,6 @@ export default { components: { DiffFileHeader, DiffContent, - LoadingIcon, }, props: { file: { @@ -46,7 +44,7 @@ export default { }, showExpandMessage() { return ( - !this.isCollapsed && + this.isCollapsed || !this.file.highlightedDiffLines && !this.isLoadingCollapsedDiff && !this.file.tooLarge && @@ -144,7 +142,7 @@ export default { :class="{ hidden: isCollapsed || file.tooLarge }" :diff-file="file" /> - <loading-icon + <gl-loading-icon v-if="showLoadingIcon" class="diff-content loading" /> diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue index d3ffbe0415a..517fbf400e8 100644 --- a/app/assets/javascripts/diffs/components/diff_file_header.vue +++ b/app/assets/javascripts/diffs/components/diff_file_header.vue @@ -181,8 +181,8 @@ export default { </span> <strong - v-tooltip v-else + v-tooltip :title="filePath" class="file-title-name" data-container="body" @@ -255,8 +255,8 @@ export default { </a> <a - v-tooltip v-if="diffFile.externalUrl" + v-tooltip :href="diffFile.externalUrl" :title="`View on ${diffFile.formattedExternalUrl}`" target="_blank" diff --git a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue index a02c41f39ab..1b59777f901 100644 --- a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue +++ b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue @@ -87,8 +87,8 @@ export default { @click.native="toggleDiscussions" /> <span - v-gl-tooltip v-if="moreText" + v-gl-tooltip :title="moreText" class="diff-comments-more-count js-diff-comment-avatar js-diff-comment-plus" data-container="body" diff --git a/app/assets/javascripts/diffs/components/diff_line_note_form.vue b/app/assets/javascripts/diffs/components/diff_line_note_form.vue index a0dc381ccc7..bb9bb821de3 100644 --- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue +++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue @@ -1,12 +1,9 @@ <script> import { mapState, mapGetters, mapActions } from 'vuex'; -import createFlash from '~/flash'; import { s__ } from '~/locale'; import noteForm from '../../notes/components/note_form.vue'; -import { getNoteFormData } from '../store/utils'; import autosave from '../../notes/mixins/autosave'; import { DIFF_NOTE_TYPE } from '../constants'; -import { reduceDiscussionsToLineCodes } from '../../notes/stores/utils'; export default { components: { @@ -22,7 +19,7 @@ export default { type: Object, required: true, }, - position: { + linePosition: { type: String, required: false, default: '', @@ -39,6 +36,16 @@ export default { }), ...mapGetters('diffs', ['getDiffFileByHash']), ...mapGetters(['isLoggedIn', 'noteableType', 'getNoteableData', 'getNotesDataByProp']), + formData() { + return { + noteableData: this.noteableData, + noteableType: this.noteableType, + noteTargetLine: this.noteTargetLine, + diffViewType: this.diffViewType, + diffFile: this.getDiffFileByHash(this.diffFileHash), + linePosition: this.linePosition, + }; + }, }, mounted() { if (this.isLoggedIn) { @@ -53,8 +60,7 @@ export default { } }, methods: { - ...mapActions('diffs', ['cancelCommentForm', 'assignDiscussionsToDiff']), - ...mapActions(['saveNote', 'refetchDiscussionById']), + ...mapActions('diffs', ['cancelCommentForm', 'assignDiscussionsToDiff', 'saveDiffDiscussion']), handleCancelCommentForm(shouldConfirm, isDirty) { if (shouldConfirm && isDirty) { const msg = s__('Notes|Are you sure you want to cancel creating this comment?'); @@ -73,35 +79,9 @@ export default { }); }, handleSaveNote(note) { - const selectedDiffFile = this.getDiffFileByHash(this.diffFileHash); - const postData = getNoteFormData({ - note, - noteableData: this.noteableData, - noteableType: this.noteableType, - noteTargetLine: this.noteTargetLine, - diffViewType: this.diffViewType, - diffFile: selectedDiffFile, - linePosition: this.position, - }); - - this.saveNote(postData) - .then(result => { - const endpoint = this.getNotesDataByProp('discussionsPath'); - - this.refetchDiscussionById({ path: endpoint, discussionId: result.discussion_id }) - .then(selectedDiscussion => { - const lineCodeDiscussions = reduceDiscussionsToLineCodes([selectedDiscussion]); - this.assignDiscussionsToDiff(lineCodeDiscussions); - - this.handleCancelCommentForm(); - }) - .catch(() => { - createFlash(s__('MergeRequests|Updating discussions failed')); - }); - }) - .catch(() => { - createFlash(s__('MergeRequests|Saving the comment failed')); - }); + return this.saveDiffDiscussion({ note, formData: this.formData }).then(() => + this.handleCancelCommentForm(), + ); }, }, }; diff --git a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue index 0e306f39a9f..62fa34e835a 100644 --- a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue +++ b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue @@ -1,5 +1,5 @@ <script> -import { mapGetters } from 'vuex'; +import { mapGetters, mapActions } from 'vuex'; import DiffTableCell from './diff_table_cell.vue'; import { NEW_LINE_TYPE, @@ -63,7 +63,11 @@ export default { this.linePositionLeft = LINE_POSITION_LEFT; this.linePositionRight = LINE_POSITION_RIGHT; }, + mounted() { + this.scrollToLineIfNeededInline(this.line); + }, methods: { + ...mapActions('diffs', ['scrollToLineIfNeededInline']), handleMouseMove(e) { // To show the comment icon on the gutter we need to know if we hover the line. // Current table structure doesn't allow us to do this with CSS in both of the diff view types diff --git a/app/assets/javascripts/diffs/components/inline_diff_view.vue b/app/assets/javascripts/diffs/components/inline_diff_view.vue index 947e7c98fae..fbf9e77ac07 100644 --- a/app/assets/javascripts/diffs/components/inline_diff_view.vue +++ b/app/assets/javascripts/diffs/components/inline_diff_view.vue @@ -43,18 +43,18 @@ export default { v-for="(line, index) in diffLines" > <inline-diff-table-row + :key="line.lineCode" :file-hash="diffFile.fileHash" :context-lines-path="diffFile.contextLinesPath" :line="line" :is-bottom="index + 1 === diffLinesLength" - :key="line.lineCode" /> <inline-diff-comment-row v-if="shouldRenderInlineCommentRow(line)" + :key="index" :diff-file-hash="diffFile.fileHash" :line="line" :line-index="index" - :key="index" /> </template> </tbody> diff --git a/app/assets/javascripts/diffs/components/no_changes.vue b/app/assets/javascripts/diffs/components/no_changes.vue index d817157fbcd..6905630ad8c 100644 --- a/app/assets/javascripts/diffs/components/no_changes.vue +++ b/app/assets/javascripts/diffs/components/no_changes.vue @@ -38,7 +38,7 @@ export default { <div class="text-center"> <a :href="newBlobPath" - class="btn btn-save" + class="btn btn-success" > {{ __('Create commit') }} </a> diff --git a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue index 26417c350cb..3339c56cbb6 100644 --- a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue +++ b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue @@ -92,7 +92,7 @@ export default { :diff-file-hash="diffFileHash" :line="line.left" :note-target-line="line.left" - position="left" + line-position="left" /> </td> <td class="notes_line new"></td> @@ -111,7 +111,7 @@ export default { :diff-file-hash="diffFileHash" :line="line.right" :note-target-line="line.right" - position="right" + line-position="right" /> </td> </tr> diff --git a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue index fb68d191091..fcc3b3e9117 100644 --- a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue +++ b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue @@ -1,4 +1,5 @@ <script> +import { mapActions } from 'vuex'; import $ from 'jquery'; import DiffTableCell from './diff_table_cell.vue'; import { @@ -64,7 +65,11 @@ export default { this.oldLineType = OLD_LINE_TYPE; this.parallelDiffViewType = PARALLEL_DIFF_VIEW_TYPE; }, + mounted() { + this.scrollToLineIfNeededParallel(this.line); + }, methods: { + ...mapActions('diffs', ['scrollToLineIfNeededParallel']), handleMouseMove(e) { const isHover = e.type === 'mouseover'; const hoveringCell = e.target.closest('td'); diff --git a/app/assets/javascripts/diffs/components/parallel_diff_view.vue b/app/assets/javascripts/diffs/components/parallel_diff_view.vue index 501bd4450d8..3452f0d2b00 100644 --- a/app/assets/javascripts/diffs/components/parallel_diff_view.vue +++ b/app/assets/javascripts/diffs/components/parallel_diff_view.vue @@ -45,11 +45,11 @@ export default { v-for="(line, index) in diffLines" > <parallel-diff-table-row + :key="index" :file-hash="diffFile.fileHash" :context-lines-path="diffFile.contextLinesPath" :line="line" :is-bottom="index + 1 === diffLinesLength" - :key="index" /> <parallel-diff-comment-row v-if="shouldRenderParallelCommentRow(line)" diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js index f68afa44837..2795dddfc48 100644 --- a/app/assets/javascripts/diffs/constants.js +++ b/app/assets/javascripts/diffs/constants.js @@ -7,6 +7,7 @@ export const CONTEXT_LINE_TYPE = 'context'; export const EMPTY_CELL_TYPE = 'empty-cell'; export const COMMENT_FORM_TYPE = 'commentForm'; export const DIFF_NOTE_TYPE = 'DiffNote'; +export const LEGACY_DIFF_NOTE_TYPE = 'LegacyDiffNote'; export const NOTE_TYPE = 'Note'; export const NEW_LINE_TYPE = 'new'; export const OLD_LINE_TYPE = 'old'; diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index 027df2ec841..98d8d5943f9 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -1,9 +1,12 @@ import Vue from 'vue'; import axios from '~/lib/utils/axios_utils'; import Cookies from 'js-cookie'; +import createFlash from '~/flash'; +import { s__ } from '~/locale'; import { handleLocationHash, historyPushState } from '~/lib/utils/common_utils'; -import { mergeUrlParams } from '~/lib/utils/url_utility'; -import { getDiffPositionByLineCode } from './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 { PARALLEL_DIFF_VIEW_TYPE, @@ -120,6 +123,25 @@ export const loadMoreLines = ({ commit }, options) => { }); }; +export const scrollToLineIfNeededInline = (_, line) => { + const hash = getLocationHash(); + + if (hash && line.lineCode === hash) { + handleLocationHash(); + } +}; + +export const scrollToLineIfNeededParallel = (_, line) => { + const hash = getLocationHash(); + + if ( + hash && + ((line.left && line.left.lineCode === hash) || (line.right && line.right.lineCode === hash)) + ) { + handleLocationHash(); + } +}; + export const loadCollapsedDiff = ({ commit }, file) => axios.get(file.loadCollapsedDiffUrl).then(res => { commit(types.ADD_COLLAPSED_DIFFS, { @@ -159,5 +181,19 @@ export const toggleFileDiscussions = ({ getters, dispatch }, diff) => { }); }; +export const saveDiffDiscussion = ({ dispatch }, { note, formData }) => { + const postData = getNoteFormData({ + note, + ...formData, + }); + + return dispatch('saveNote', postData, { root: true }) + .then(result => dispatch('updateDiscussion', result.discussion, { root: true })) + .then(discussion => + dispatch('assignDiscussionsToDiff', reduceDiscussionsToLineCodes([discussion])), + ) + .catch(() => createFlash(s__('MergeRequests|Saving the comment failed'))); +}; + // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js index 39d90a64aab..eb596b251c1 100644 --- a/app/assets/javascripts/diffs/store/modules/diff_state.js +++ b/app/assets/javascripts/diffs/store/modules/diff_state.js @@ -11,8 +11,10 @@ export default () => ({ endpoint: '', basePath: '', commit: null, + startVersion: null, diffFiles: [], mergeRequestDiffs: [], + mergeRequestDiff: null, diffLineCommentForms: {}, diffViewType: viewTypeFromQueryString || viewTypeFromCookie || defaultViewType, }); diff --git a/app/assets/javascripts/diffs/store/modules/index.js b/app/assets/javascripts/diffs/store/modules/index.js index 20d1ebbe049..6860e24db6b 100644 --- a/app/assets/javascripts/diffs/store/modules/index.js +++ b/app/assets/javascripts/diffs/store/modules/index.js @@ -3,10 +3,10 @@ import * as getters from '../getters'; import mutations from '../mutations'; import createState from './diff_state'; -export default { +export default () => ({ namespaced: true, state: createState(), getters, actions, mutations, -}; +}); diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js index 6dc5bf16c65..59a2c09e54f 100644 --- a/app/assets/javascripts/diffs/store/mutations.js +++ b/app/assets/javascripts/diffs/store/mutations.js @@ -90,16 +90,18 @@ export default { const firstDiscussion = discussions[0]; const isDiffDiscussion = firstDiscussion.diff_discussion; const hasLineCode = firstDiscussion.line_code; - const isResolvable = firstDiscussion.resolvable; const diffPosition = diffPositionByLineCode[firstDiscussion.line_code]; if ( selectedFile && isDiffDiscussion && hasLineCode && - isResolvable && diffPosition && - isDiscussionApplicableToLine(firstDiscussion, diffPosition) + isDiscussionApplicableToLine({ + discussion: firstDiscussion, + diffPosition, + latestDiff: state.latestDiff, + }) ) { const targetLine = selectedFile.parallelDiffLines.find( line => diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js index b7e52a8f37f..631e3de311e 100644 --- a/app/assets/javascripts/diffs/store/utils.js +++ b/app/assets/javascripts/diffs/store/utils.js @@ -4,6 +4,7 @@ import { LINE_POSITION_LEFT, LINE_POSITION_RIGHT, TEXT_DIFF_POSITION_TYPE, + LEGACY_DIFF_NOTE_TYPE, DIFF_NOTE_TYPE, NEW_LINE_TYPE, OLD_LINE_TYPE, @@ -54,13 +55,17 @@ export function getNoteFormData(params) { note_project_id: '', target_type: noteableData.targetType, target_id: noteableData.id, + return_discussion: true, note: { note, position, noteable_type: noteableType, noteable_id: noteableData.id, commit_id: '', - type: DIFF_NOTE_TYPE, + type: + diffFile.diffRefs.startSha && diffFile.diffRefs.headSha + ? DIFF_NOTE_TYPE + : LEGACY_DIFF_NOTE_TYPE, line_code: noteTargetLine.lineCode, }, }; @@ -230,7 +235,16 @@ export function getDiffPositionByLineCode(diffFiles) { const { lineCode, oldLine, newLine } = line; if (lineCode) { - acc[lineCode] = { baseSha, headSha, startSha, newPath, oldPath, oldLine, newLine }; + acc[lineCode] = { + baseSha, + headSha, + startSha, + newPath, + oldPath, + oldLine, + newLine, + lineCode, + }; } }); } @@ -241,9 +255,15 @@ export function getDiffPositionByLineCode(diffFiles) { // This method will check whether the discussion is still applicable // to the diff line in question regarding different versions of the MR -export function isDiscussionApplicableToLine(discussion, diffPosition) { - const originalRefs = convertObjectPropsToCamelCase(discussion.original_position.formatter); - const refs = convertObjectPropsToCamelCase(discussion.position.formatter); +export function isDiscussionApplicableToLine({ discussion, diffPosition, latestDiff }) { + const { lineCode, ...diffPositionCopy } = diffPosition; - return _.isEqual(refs, diffPosition) || _.isEqual(originalRefs, diffPosition); + if (discussion.original_position && discussion.position) { + const originalRefs = convertObjectPropsToCamelCase(discussion.original_position.formatter); + const refs = convertObjectPropsToCamelCase(discussion.position.formatter); + + return _.isEqual(refs, diffPositionCopy) || _.isEqual(originalRefs, diffPositionCopy); + } + + return latestDiff && discussion.active && lineCode === discussion.line_code; } diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js deleted file mode 100644 index a5af37e80b6..00000000000 --- a/app/assets/javascripts/dispatcher.js +++ /dev/null @@ -1,89 +0,0 @@ -/* eslint-disable consistent-return, no-new */ - -import $ from 'jquery'; -import GfmAutoComplete from './gfm_auto_complete'; -import { convertPermissionToBoolean } from './lib/utils/common_utils'; -import GlFieldErrors from './gl_field_errors'; -import Shortcuts from './shortcuts'; -import SearchAutocomplete from './search_autocomplete'; -import performanceBar from './performance_bar'; - -function initSearch() { - // Only when search form is present - if ($('.search').length) { - return new SearchAutocomplete(); - } -} - -function initFieldErrors() { - $('.gl-show-field-errors').each((i, form) => { - new GlFieldErrors(form); - }); -} - -function initPageShortcuts(page) { - const pagesWithCustomShortcuts = [ - 'projects:activity', - 'projects:artifacts:browse', - 'projects:artifacts:file', - 'projects:blame:show', - 'projects:blob:show', - 'projects:commit:show', - 'projects:commits:show', - 'projects:find_file:show', - 'projects:issues:edit', - 'projects:issues:index', - 'projects:issues:new', - 'projects:issues:show', - 'projects:merge_requests:creations:diffs', - 'projects:merge_requests:creations:new', - 'projects:merge_requests:edit', - 'projects:merge_requests:index', - 'projects:merge_requests:show', - 'projects:network:show', - 'projects:show', - 'projects:tree:show', - 'groups:show', - ]; - - if (pagesWithCustomShortcuts.indexOf(page) === -1) { - new Shortcuts(); - } -} - -function initGFMInput() { - $('.js-gfm-input:not(.js-vue-textarea)').each((i, el) => { - const gfm = new GfmAutoComplete( - gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources, - ); - const enableGFM = convertPermissionToBoolean( - el.dataset.supportsAutocomplete, - ); - gfm.setup($(el), { - emojis: true, - members: enableGFM, - issues: enableGFM, - milestones: enableGFM, - mergeRequests: enableGFM, - labels: enableGFM, - }); - }); -} - -function initPerformanceBar() { - if (document.querySelector('#js-peek')) { - performanceBar({ container: '#js-peek' }); - } -} - -export default () => { - initSearch(); - initFieldErrors(); - - const page = $('body').attr('data-page'); - if (page) { - initPageShortcuts(page); - initGFMInput(); - initPerformanceBar(); - } -}; diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index ff969bb94a4..d2778bcdf1c 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import Dropzone from 'dropzone'; import _ from 'underscore'; -import './preview_markdown'; +import './behaviors/preview_markdown'; import csrf from './lib/utils/csrf'; import axios from './lib/utils/axios_utils'; diff --git a/app/assets/javascripts/environments/components/container.vue b/app/assets/javascripts/environments/components/container.vue index 9aa224fa407..9de851c9409 100644 --- a/app/assets/javascripts/environments/components/container.vue +++ b/app/assets/javascripts/environments/components/container.vue @@ -1,12 +1,10 @@ <script> - import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import tablePagination from '../../vue_shared/components/table_pagination.vue'; import environmentTable from '../components/environments_table.vue'; export default { components: { environmentTable, - loadingIcon, tablePagination, }, props: { @@ -42,11 +40,11 @@ <template> <div class="environments-container"> - <loading-icon + <gl-loading-icon v-if="isLoading" + :size="3" class="prepend-top-default" label="Loading environments" - size="3" /> <slot name="emptyState"></slot> diff --git a/app/assets/javascripts/environments/components/empty_state.vue b/app/assets/javascripts/environments/components/empty_state.vue index 00e63c3467a..cf78f89981e 100644 --- a/app/assets/javascripts/environments/components/empty_state.vue +++ b/app/assets/javascripts/environments/components/empty_state.vue @@ -35,7 +35,7 @@ code gets deployed, such as staging or production.`) }} <a v-if="canCreateEnvironment" :href="newPath" - class="btn btn-create js-new-environment-button" + class="btn btn-success js-new-environment-button" > {{ s__("Environments|New environment") }} </a> diff --git a/app/assets/javascripts/environments/components/environment_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue index 63d83e307ee..e1f9248bc4c 100644 --- a/app/assets/javascripts/environments/components/environment_actions.vue +++ b/app/assets/javascripts/environments/components/environment_actions.vue @@ -1,7 +1,6 @@ <script> import Icon from '~/vue_shared/components/icon.vue'; import eventHub from '../event_hub'; -import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import tooltip from '../../vue_shared/directives/tooltip'; export default { @@ -9,7 +8,6 @@ export default { tooltip, }, components: { - loadingIcon, Icon, }, props: { @@ -67,7 +65,7 @@ export default { aria-hidden="true" > </i> - <loading-icon v-if="isLoading" /> + <gl-loading-icon v-if="isLoading" /> </span> </button> diff --git a/app/assets/javascripts/environments/components/environment_rollback.vue b/app/assets/javascripts/environments/components/environment_rollback.vue index 4deeef4beb9..efbf88d0f11 100644 --- a/app/assets/javascripts/environments/components/environment_rollback.vue +++ b/app/assets/javascripts/environments/components/environment_rollback.vue @@ -9,12 +9,10 @@ import { s__ } from '~/locale'; import Icon from '~/vue_shared/components/icon.vue'; import tooltip from '~/vue_shared/directives/tooltip'; import eventHub from '../event_hub'; -import LoadingIcon from '../../vue_shared/components/loading_icon.vue'; export default { components: { Icon, - LoadingIcon, }, directives: { @@ -70,6 +68,6 @@ export default { v-else name="redo"/> - <loading-icon v-if="isLoading" /> + <gl-loading-icon v-if="isLoading" /> </button> </template> diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue index 8efdfb8abe0..e2ecf426e64 100644 --- a/app/assets/javascripts/environments/components/environments_app.vue +++ b/app/assets/javascripts/environments/components/environments_app.vue @@ -107,7 +107,7 @@ > <a :href="newEnvironmentPath" - class="btn btn-create" + class="btn btn-success" > {{ s__("Environments|New environment") }} </a> diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue index 016e9f7c7b3..16abafebbc0 100644 --- a/app/assets/javascripts/environments/components/environments_table.vue +++ b/app/assets/javascripts/environments/components/environments_table.vue @@ -2,13 +2,11 @@ /** * Render environments table. */ -import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import environmentItem from './environment_item.vue'; export default { components: { environmentItem, - loadingIcon, }, props: { @@ -85,10 +83,10 @@ export default { :model="model"> <div is="environment-item" + :key="`environment-item-${i}`" :model="model" :can-create-deployment="canCreateDeployment" :can-read-environment="canReadEnvironment" - :key="`environment-item-${i}`" /> <template @@ -97,17 +95,17 @@ export default { <div v-if="model.isLoadingFolderContent" :key="`loading-item-${i}`"> - <loading-icon size="2" /> + <gl-loading-icon :size="2" /> </div> <template v-else> <div is="environment-item" v-for="(children, index) in model.children" + :key="`env-item-${i}-${index}`" :model="children" :can-create-deployment="canCreateDeployment" :can-read-environment="canReadEnvironment" - :key="`env-item-${i}-${index}`" /> <div :key="`sub-div-${i}`"> diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js index d88624f7f8d..d71964612c5 100644 --- a/app/assets/javascripts/environments/mixins/environments_mixin.js +++ b/app/assets/javascripts/environments/mixins/environments_mixin.js @@ -13,7 +13,6 @@ import eventHub from '../event_hub'; import EnvironmentsStore from '../stores/environments_store'; import EnvironmentsService from '../services/environments_service'; -import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import tablePagination from '../../vue_shared/components/table_pagination.vue'; import environmentTable from '../components/environments_table.vue'; import tabs from '../../vue_shared/components/navigation_tabs.vue'; @@ -24,7 +23,6 @@ export default { components: { environmentTable, container, - loadingIcon, tabs, tablePagination, }, diff --git a/app/assets/javascripts/feature_highlight/feature_highlight.js b/app/assets/javascripts/feature_highlight/feature_highlight.js index 2f27c9351bc..03dfa942d69 100644 --- a/app/assets/javascripts/feature_highlight/feature_highlight.js +++ b/app/assets/javascripts/feature_highlight/feature_highlight.js @@ -16,7 +16,7 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) { const hideOnScroll = togglePopover.bind($selector, false); $selector - // Setup popover + // Set up popover .data('content', $popoverContent.prop('outerHTML')) .popover({ html: true, 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 new file mode 100644 index 00000000000..b4588cc1318 --- /dev/null +++ b/app/assets/javascripts/filtered_search/admin_runners_filtered_search_token_keys.js @@ -0,0 +1,14 @@ +import FilteredSearchTokenKeys from './filtered_search_token_keys'; + +const tokenKeys = [{ + key: 'status', + type: 'string', + param: 'status', + symbol: '', + icon: 'messages', + tag: 'status', +}]; + +const AdminRunnersFilteredSearchTokenKeys = new FilteredSearchTokenKeys(tokenKeys); + +export default AdminRunnersFilteredSearchTokenKeys; 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 a8eb8d94be3..21b5ccdb613 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 @@ -72,8 +72,8 @@ export default { @click="onItemActivated(item.text)"> <span> <span - v-for="(token, index) in item.tokens" - :key="`dropdown-token-${index}`" + v-for="(token, tokenIndex) in item.tokens" + :key="`dropdown-token-${tokenIndex}`" class="filtered-search-history-dropdown-token" > <span class="name">{{ token.prefix }}</span> diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js b/app/assets/javascripts/filtered_search/dropdown_hint.js index 184b34b7b5e..8aecf9725e6 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js @@ -62,7 +62,7 @@ export default class DropdownHint extends FilteredSearchDropdown { renderContent() { const dropdownData = this.tokenKeys.get() .map(tokenKey => ({ - icon: `fa-${tokenKey.icon}`, + icon: `${gon.sprite_icons}#${tokenKey.icon}`, hint: tokenKey.key, tag: `:${tokenKey.tag}`, type: tokenKey.type, diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js index 296571606d6..a750647f8be 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js @@ -7,6 +7,7 @@ import DropdownHint from './dropdown_hint'; import DropdownEmoji from './dropdown_emoji'; import DropdownNonUser from './dropdown_non_user'; import DropdownUser from './dropdown_user'; +import NullDropdown from './null_dropdown'; import FilteredSearchVisualTokens from './filtered_search_visual_tokens'; export default class FilteredSearchDropdownManager { @@ -90,6 +91,11 @@ export default class FilteredSearchDropdownManager { gl: DropdownEmoji, element: this.container.querySelector('#js-dropdown-my-reaction'), }, + status: { + reference: null, + gl: NullDropdown, + element: this.container.querySelector('#js-dropdown-admin-runner-status'), + }, }; supportedTokens.forEach((type) => { diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index 81286c54c4c..d25f6f95b22 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -3,10 +3,10 @@ import { getParameterByName, getUrlParamsArray, } from '~/lib/utils/common_utils'; +import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; import { visitUrl } from '../lib/utils/url_utility'; import Flash from '../flash'; import FilteredSearchContainer from './container'; -import FilteredSearchTokenKeys from './filtered_search_token_keys'; import RecentSearchesRoot from './recent_searches_root'; import RecentSearchesStore from './stores/recent_searches_store'; import RecentSearchesService from './services/recent_searches_service'; @@ -23,7 +23,7 @@ export default class FilteredSearchManager { isGroup = false, isGroupAncestor = true, isGroupDecendent = false, - filteredSearchTokenKeys = FilteredSearchTokenKeys, + filteredSearchTokenKeys = IssuableFilteredSearchTokenKeys, stateFiltersSelector = '.issues-state-filters', }) { this.isGroup = isGroup; 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 087ef5cd6f2..5d131b396a0 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js +++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js @@ -1,103 +1,38 @@ -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-o', - tag: '%milestone', -}, { - key: 'label', - type: 'array', - param: 'name[]', - symbol: '~', - icon: 'tag', - tag: '~label', -}]; - -if (gon.current_user_id) { - // Appending tokenkeys only logged-in - tokenKeys.push({ - key: 'my-reaction', - type: 'string', - param: 'emoji', - symbol: '', - icon: 'thumbs-up', - tag: 'emoji', - }); -} - -const alternativeTokenKeys = [{ - key: 'label', - type: 'string', - param: 'name', - symbol: '~', -}]; - -const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys); +export default class FilteredSearchTokenKeys { + constructor(tokenKeys = [], alternativeTokenKeys = [], conditions = []) { + this.tokenKeys = tokenKeys; + this.alternativeTokenKeys = alternativeTokenKeys; + this.conditions = conditions; -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', -}]; + this.tokenKeysWithAlternative = this.tokenKeys.concat(this.alternativeTokenKeys); + } -export default class FilteredSearchTokenKeys { - static get() { - return tokenKeys; + get() { + return this.tokenKeys; } - static getKeys() { - return tokenKeys.map(i => i.key); + getKeys() { + return this.tokenKeys.map(i => i.key); } - static getAlternatives() { - return alternativeTokenKeys; + getAlternatives() { + return this.alternativeTokenKeys; } - static getConditions() { - return conditions; + getConditions() { + return this.conditions; } - static searchByKey(key) { - return tokenKeys.find(tokenKey => tokenKey.key === key) || null; + searchByKey(key) { + return this.tokenKeys.find(tokenKey => tokenKey.key === key) || null; } - static searchBySymbol(symbol) { - return tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null; + searchBySymbol(symbol) { + return this.tokenKeys.find(tokenKey => tokenKey.symbol === symbol) || null; } - static searchByKeyParam(keyParam) { - return tokenKeysWithAlternative.find((tokenKey) => { + searchByKeyParam(keyParam) { + return this.tokenKeysWithAlternative.find((tokenKey) => { let tokenKeyParam = tokenKey.key; // Replace hyphen with underscore to compare keyParam with tokenKeyParam @@ -112,12 +47,12 @@ export default class FilteredSearchTokenKeys { }) || null; } - static searchByConditionUrl(url) { - return conditions.find(condition => condition.url === url) || null; + searchByConditionUrl(url) { + return this.conditions.find(condition => condition.url === url) || null; } - static searchByConditionKeyValue(key, value) { - return conditions + searchByConditionKeyValue(key, value) { + return this.conditions .find(condition => condition.tokenKey === key && condition.value === value) || null; } } 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 new file mode 100644 index 00000000000..cc7291c9f59 --- /dev/null +++ b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js @@ -0,0 +1,77 @@ +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', +}]; + +if (gon.current_user_id) { + // Appending tokenkeys only logged-in + tokenKeys.push({ + key: 'my-reaction', + type: 'string', + param: 'emoji', + symbol: '', + icon: 'thumb-up', + tag: 'emoji', + }); +} + +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', +}]; + +const IssuableFilteredSearchTokenKeys = + new FilteredSearchTokenKeys(tokenKeys, alternativeTokenKeys, conditions); + +export default IssuableFilteredSearchTokenKeys; diff --git a/app/assets/javascripts/filtered_search/null_dropdown.js b/app/assets/javascripts/filtered_search/null_dropdown.js new file mode 100644 index 00000000000..4cfce2a5beb --- /dev/null +++ b/app/assets/javascripts/filtered_search/null_dropdown.js @@ -0,0 +1,9 @@ +import FilteredSearchDropdown from './filtered_search_dropdown'; + +export default class NullDropdown extends FilteredSearchDropdown { + renderContent(forceShowList = false) { + this.droplab.changeHookList(this.hookId, this.dropdown, [], this.config); + + super.renderContent(forceShowList); + } +} diff --git a/app/assets/javascripts/frequent_items/components/app.vue b/app/assets/javascripts/frequent_items/components/app.vue index 2f030de8967..70a8838b772 100644 --- a/app/assets/javascripts/frequent_items/components/app.vue +++ b/app/assets/javascripts/frequent_items/components/app.vue @@ -1,6 +1,5 @@ <script> import { mapState, mapActions, mapGetters } from 'vuex'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; import AccessorUtilities from '~/lib/utils/accessor'; import eventHub from '../event_hub'; import store from '../store/'; @@ -13,7 +12,6 @@ import frequentItemsMixin from './frequent_items_mixin'; export default { store, components: { - LoadingIcon, FrequentItemsSearchInput, FrequentItemsList, }, @@ -98,11 +96,11 @@ export default { <frequent-items-search-input :namespace="namespace" /> - <loading-icon + <gl-loading-icon v-if="isLoadingItems" :label="translations.loadingMessage" + :size="2" class="loading-animation prepend-top-20" - size="2" /> <div v-if="!isLoadingItems && !hasSearchQuery" diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue index 1f1665ff7fe..2399ee15332 100644 --- a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue +++ b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue @@ -1,5 +1,5 @@ <script> -/* eslint-disable vue/require-default-prop, vue/require-prop-types */ +/* eslint-disable vue/require-default-prop */ import Identicon from '../../vue_shared/components/identicon.vue'; export default { diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js index c74de7ac34d..e672284a2d0 100644 --- a/app/assets/javascripts/gl_form.js +++ b/app/assets/javascripts/gl_form.js @@ -18,7 +18,7 @@ export default class GLForm { }); // Before we start, we should clean up any previous data for this form this.destroy(); - // Setup the form + // Set up the form this.setupForm(); this.form.data('glForm', this); } diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue index 69f192ac75e..a032f291546 100644 --- a/app/assets/javascripts/groups/components/app.vue +++ b/app/assets/javascripts/groups/components/app.vue @@ -3,7 +3,6 @@ import $ from 'jquery'; import { s__, sprintf } from '~/locale'; -import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; import { HIDDEN_CLASS } from '~/lib/utils/constants'; import { getParameterByName } from '~/lib/utils/common_utils'; @@ -15,7 +14,6 @@ import groupsComponent from './groups.vue'; export default { components: { - loadingIcon, DeprecatedModal, groupsComponent, }, @@ -241,11 +239,11 @@ export default { <template> <div> - <loading-icon + <gl-loading-icon v-if="isLoading" :label="s__('GroupsTree|Loading groups')" + :size="2" class="loading-animation prepend-top-20" - size="2" /> <groups-component v-if="!isLoading" diff --git a/app/assets/javascripts/groups/components/groups.vue b/app/assets/javascripts/groups/components/groups.vue index a1beb222950..81b2e5ea37b 100644 --- a/app/assets/javascripts/groups/components/groups.vue +++ b/app/assets/javascripts/groups/components/groups.vue @@ -1,11 +1,11 @@ <script> -import tablePagination from '~/vue_shared/components/table_pagination.vue'; +import PaginationLinks from '~/vue_shared/components/pagination_links.vue'; import eventHub from '../event_hub'; import { getParameterByName } from '../../lib/utils/common_utils'; export default { components: { - tablePagination, + PaginationLinks, }, props: { groups: { @@ -49,15 +49,18 @@ export default { > {{ searchEmptyMessage }} </div> - <group-folder - v-if="!searchEmpty" - :groups="groups" - :action="action" - /> - <table-pagination - v-if="!searchEmpty" - :change="change" - :page-info="pageInfo" - /> + <template + v-else + > + <group-folder + :groups="groups" + :action="action" + /> + <pagination-links + :change="change" + :page-info="pageInfo" + class="d-flex justify-content-center prepend-top-default" + /> + </template> </div> </template> diff --git a/app/assets/javascripts/groups/components/item_actions.vue b/app/assets/javascripts/groups/components/item_actions.vue index 6e700b8bf8a..c1783d5ce25 100644 --- a/app/assets/javascripts/groups/components/item_actions.vue +++ b/app/assets/javascripts/groups/components/item_actions.vue @@ -46,8 +46,8 @@ export default { <template> <div class="controls"> <a - v-tooltip v-if="group.canEdit" + v-tooltip :href="group.editPath" :title="editBtnTitle" :aria-label="editBtnTitle" @@ -57,8 +57,8 @@ export default { <icon name="settings"/> </a> <a - v-tooltip v-if="group.canLeave" + v-tooltip :href="group.leavePath" :title="leaveBtnTitle" :aria-label="leaveBtnTitle" diff --git a/app/assets/javascripts/ide/components/branches/search_list.vue b/app/assets/javascripts/ide/components/branches/search_list.vue index 6db7b9d6b0e..52ccc537c9d 100644 --- a/app/assets/javascripts/ide/components/branches/search_list.vue +++ b/app/assets/javascripts/ide/components/branches/search_list.vue @@ -1,13 +1,11 @@ <script> import { mapActions, mapState } from 'vuex'; import _ from 'underscore'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; import Icon from '~/vue_shared/components/icon.vue'; import Item from './item.vue'; export default { components: { - LoadingIcon, Item, Icon, }, @@ -62,8 +60,8 @@ export default { <div class="position-relative"> <input ref="searchInput" - :placeholder="__('Search branches')" v-model="search" + :placeholder="__('Search branches')" type="search" class="form-control dropdown-input-field" @input="searchBranches" @@ -76,10 +74,10 @@ export default { </div> </div> <div class="dropdown-content ide-merge-requests-dropdown-content d-flex"> - <loading-icon + <gl-loading-icon v-if="isLoading" + :size="2" class="mt-3 mb-3 align-self-center ml-auto mr-auto" - size="2" /> <ul v-else diff --git a/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue b/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue index c3ca147e850..3aca38399fb 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/editor_header.vue @@ -52,6 +52,7 @@ export default { </strong> <changed-file-icon :file="activeFile" + class="ml-0" /> <div class="ml-auto"> <button diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue index 3fdd35ad228..3e3539e364b 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/list.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/list.vue @@ -113,8 +113,8 @@ export default { </strong> <div class="d-flex ml-auto"> <button - v-tooltip ref="actionBtn" + v-tooltip :title="actionBtnText" :aria-label="actionBtnText" :disabled="!filesLength" @@ -135,8 +135,8 @@ export default { /> </button> <button - v-tooltip v-if="!stagedList" + v-tooltip :title="__('Discard all changes')" :aria-label="__('Discard all changes')" :disabled="!filesLength" diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue index 10c78a80302..ee0e72cd05f 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue @@ -120,10 +120,6 @@ export default { :css-classes="iconClass" /> </div> - <component - :is="actionComponent" - :path="file.path" - /> </div> </div> </div> diff --git a/app/assets/javascripts/ide/components/error_message.vue b/app/assets/javascripts/ide/components/error_message.vue index acbc98b7a7b..a20dc0a7006 100644 --- a/app/assets/javascripts/ide/components/error_message.vue +++ b/app/assets/javascripts/ide/components/error_message.vue @@ -1,11 +1,7 @@ <script> import { mapActions } from 'vuex'; -import LoadingIcon from '../../vue_shared/components/loading_icon.vue'; export default { - components: { - LoadingIcon, - }, props: { message: { type: Object, @@ -59,7 +55,7 @@ export default { @click.stop.prevent="clickAction" > {{ message.actionText }} - <loading-icon + <gl-loading-icon v-show="isLoading" inline /> diff --git a/app/assets/javascripts/ide/components/file_finder/index.vue b/app/assets/javascripts/ide/components/file_finder/index.vue index 0ba33053717..760ed8654ee 100644 --- a/app/assets/javascripts/ide/components/file_finder/index.vue +++ b/app/assets/javascripts/ide/components/file_finder/index.vue @@ -174,8 +174,8 @@ export default { <div class="dropdown-input"> <input ref="searchInput" - :placeholder="__('Search files')" v-model="searchText" + :placeholder="__('Search files')" type="search" class="dropdown-input-field" autocomplete="off" diff --git a/app/assets/javascripts/ide/components/file_finder/item.vue b/app/assets/javascripts/ide/components/file_finder/item.vue index f5252ce7706..a612739d641 100644 --- a/app/assets/javascripts/ide/components/file_finder/item.vue +++ b/app/assets/javascripts/ide/components/file_finder/item.vue @@ -78,10 +78,10 @@ export default { class="diff-changed-file-name" > <span - v-for="(char, index) in file.name.split('')" - :key="index + char" + v-for="(char, charIndex) in file.name.split('')" + :key="charIndex + char" :class="{ - highlighted: nameSearchTextOccurences.indexOf(index) >= 0, + highlighted: nameSearchTextOccurences.indexOf(charIndex) >= 0, }" v-text="char" > @@ -91,10 +91,10 @@ export default { class="diff-changed-file-path prepend-top-5" > <span - v-for="(char, index) in pathWithEllipsis.split('')" - :key="index + char" + v-for="(char, charIndex) in pathWithEllipsis.split('')" + :key="charIndex + char" :class="{ - highlighted: pathSearchTextOccurences.indexOf(index) >= 0, + highlighted: pathSearchTextOccurences.indexOf(charIndex) >= 0, }" v-text="char" > diff --git a/app/assets/javascripts/ide/components/file_row_extra.vue b/app/assets/javascripts/ide/components/file_row_extra.vue new file mode 100644 index 00000000000..44a360ab909 --- /dev/null +++ b/app/assets/javascripts/ide/components/file_row_extra.vue @@ -0,0 +1,104 @@ +<script> +import { mapGetters } from 'vuex'; +import { n__, __, sprintf } from '~/locale'; +import tooltip from '~/vue_shared/directives/tooltip'; +import Icon from '~/vue_shared/components/icon.vue'; +import NewDropdown from './new_dropdown/index.vue'; +import ChangedFileIcon from './changed_file_icon.vue'; +import MrFileIcon from './mr_file_icon.vue'; + +export default { + name: 'FileRowExtra', + directives: { + tooltip, + }, + components: { + Icon, + NewDropdown, + ChangedFileIcon, + MrFileIcon, + }, + props: { + file: { + type: Object, + required: true, + }, + mouseOver: { + type: Boolean, + required: true, + }, + }, + computed: { + ...mapGetters([ + 'getChangesInFolder', + 'getUnstagedFilesCountForPath', + 'getStagedFilesCountForPath', + ]), + folderUnstagedCount() { + return this.getUnstagedFilesCountForPath(this.file.path); + }, + folderStagedCount() { + return this.getStagedFilesCountForPath(this.file.path); + }, + changesCount() { + return this.getChangesInFolder(this.file.path); + }, + folderChangesTooltip() { + if (this.changesCount === 0) return undefined; + + if (this.folderUnstagedCount > 0 && this.folderStagedCount === 0) { + return n__('%d unstaged change', '%d unstaged changes', this.folderUnstagedCount); + } else if (this.folderUnstagedCount === 0 && this.folderStagedCount > 0) { + return n__('%d staged change', '%d staged changes', this.folderStagedCount); + } + + return sprintf(__('%{unstaged} unstaged and %{staged} staged changes'), { + unstaged: this.folderUnstagedCount, + staged: this.folderStagedCount, + }); + }, + showTreeChangesCount() { + return this.file.type === 'tree' && this.changesCount > 0 && !this.file.opened; + }, + showChangedFileIcon() { + return this.file.changed || this.file.tempFile || this.file.staged; + }, + }, +}; +</script> + +<template> + <div class="float-right ide-file-icon-holder"> + <mr-file-icon + v-if="file.mrChange" + /> + <span + v-if="showTreeChangesCount" + class="ide-tree-changes" + > + {{ changesCount }} + <icon + v-tooltip + :title="folderChangesTooltip" + :size="12" + data-container="body" + data-placement="right" + name="file-modified" + css-classes="prepend-left-5 ide-file-modified" + /> + </span> + <changed-file-icon + v-else-if="showChangedFileIcon" + :file="file" + :show-tooltip="true" + :show-staged-icon="true" + :force-modified-icon="true" + /> + <new-dropdown + :type="file.type" + :path="file.path" + :mouse-over="mouseOver" + class="prepend-left-8" + /> + </div> +</template> diff --git a/app/assets/javascripts/ide/components/file_templates/dropdown.vue b/app/assets/javascripts/ide/components/file_templates/dropdown.vue index 13059937f85..ef1f6de3a86 100644 --- a/app/assets/javascripts/ide/components/file_templates/dropdown.vue +++ b/app/assets/javascripts/ide/components/file_templates/dropdown.vue @@ -1,13 +1,11 @@ <script> import $ from 'jquery'; import { mapActions, mapState } from 'vuex'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue'; export default { components: { DropdownButton, - LoadingIcon, }, props: { data: { @@ -102,9 +100,9 @@ export default { ></i> </div> <div class="dropdown-content"> - <loading-icon + <gl-loading-icon v-if="showLoading" - size="2" + :size="2" /> <ul v-else> <li diff --git a/app/assets/javascripts/ide/components/ide_tree_list.vue b/app/assets/javascripts/ide/components/ide_tree_list.vue index 00ae5ea2c15..e658d1bf956 100644 --- a/app/assets/javascripts/ide/components/ide_tree_list.vue +++ b/app/assets/javascripts/ide/components/ide_tree_list.vue @@ -2,15 +2,16 @@ import { mapActions, mapGetters, mapState } from 'vuex'; import Icon from '~/vue_shared/components/icon.vue'; import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; -import RepoFile from './repo_file.vue'; +import FileRow from '~/vue_shared/components/file_row.vue'; import NavDropdown from './nav_dropdown.vue'; +import FileRowExtra from './file_row_extra.vue'; export default { components: { Icon, - RepoFile, SkeletonLoadingContainer, NavDropdown, + FileRow, }, props: { viewerType: { @@ -34,8 +35,9 @@ export default { this.updateViewer(this.viewerType); }, methods: { - ...mapActions(['updateViewer']), + ...mapActions(['updateViewer', 'toggleTreeOpen']), }, + FileRowExtra, }; </script> @@ -63,11 +65,13 @@ export default { <div class="ide-tree-body h-100" > - <repo-file + <file-row v-for="file in currentTree.tree" :key="file.key" :file="file" :level="0" + :extra-component="$options.FileRowExtra" + @toggleTreeOpen="toggleTreeOpen" /> </div> </template> diff --git a/app/assets/javascripts/ide/components/jobs/list.vue b/app/assets/javascripts/ide/components/jobs/list.vue index 3b16b860ecd..acd37605d16 100644 --- a/app/assets/javascripts/ide/components/jobs/list.vue +++ b/app/assets/javascripts/ide/components/jobs/list.vue @@ -1,11 +1,9 @@ <script> import { mapActions } from 'vuex'; -import LoadingIcon from '../../../vue_shared/components/loading_icon.vue'; import Stage from './stage.vue'; export default { components: { - LoadingIcon, Stage, }, props: { @@ -26,10 +24,10 @@ export default { <template> <div> - <loading-icon + <gl-loading-icon v-if="loading && !stages.length" + :size="2" class="prepend-top-default" - size="2" /> <template v-else> <stage diff --git a/app/assets/javascripts/ide/components/jobs/stage.vue b/app/assets/javascripts/ide/components/jobs/stage.vue index 15e881b7bc8..ec168d36b9e 100644 --- a/app/assets/javascripts/ide/components/jobs/stage.vue +++ b/app/assets/javascripts/ide/components/jobs/stage.vue @@ -2,7 +2,6 @@ import tooltip from '../../../vue_shared/directives/tooltip'; import Icon from '../../../vue_shared/components/icon.vue'; import CiIcon from '../../../vue_shared/components/ci_icon.vue'; -import LoadingIcon from '../../../vue_shared/components/loading_icon.vue'; import Item from './item.vue'; export default { @@ -12,7 +11,6 @@ export default { components: { Icon, CiIcon, - LoadingIcon, Item, }, props: { @@ -71,8 +69,8 @@ export default { :size="24" /> <strong - v-tooltip="showTooltip" ref="stageTitle" + v-tooltip="showTooltip" :title="showTooltip ? stage.name : null" data-container="body" class="prepend-left-8 ide-stage-title" @@ -96,7 +94,7 @@ export default { v-show="!stage.isCollapsed" class="card-body" > - <loading-icon + <gl-loading-icon v-if="showLoadingIcon" /> <template v-else> diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue index fc612956688..c8343e77860 100644 --- a/app/assets/javascripts/ide/components/merge_requests/list.vue +++ b/app/assets/javascripts/ide/components/merge_requests/list.vue @@ -3,7 +3,6 @@ import { mapActions, mapState } from 'vuex'; import _ from 'underscore'; import { __ } from '~/locale'; import Icon from '~/vue_shared/components/icon.vue'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; import Item from './item.vue'; import TokenedInput from '../shared/tokened_input.vue'; @@ -14,7 +13,6 @@ const SEARCH_TYPES = [ export default { components: { - LoadingIcon, TokenedInput, Item, Icon, @@ -98,10 +96,10 @@ export default { </div> </div> <div class="dropdown-content ide-merge-requests-dropdown-content d-flex"> - <loading-icon + <gl-loading-icon v-if="isLoading" + :size="2" class="mt-3 mb-3 align-self-center ml-auto mr-auto" - size="2" /> <template v-else> <ul diff --git a/app/assets/javascripts/ide/components/pipelines/list.vue b/app/assets/javascripts/ide/components/pipelines/list.vue index 5757dfdc925..0a2681b7a1e 100644 --- a/app/assets/javascripts/ide/components/pipelines/list.vue +++ b/app/assets/javascripts/ide/components/pipelines/list.vue @@ -2,7 +2,6 @@ import { mapActions, mapGetters, mapState } from 'vuex'; import _ from 'underscore'; import { sprintf, __ } from '../../../locale'; -import LoadingIcon from '../../../vue_shared/components/loading_icon.vue'; import Icon from '../../../vue_shared/components/icon.vue'; import CiIcon from '../../../vue_shared/components/ci_icon.vue'; import Tabs from '../../../vue_shared/components/tabs/tabs'; @@ -12,7 +11,6 @@ import JobsList from '../jobs/list.vue'; export default { components: { - LoadingIcon, Icon, CiIcon, Tabs, @@ -50,10 +48,10 @@ export default { <template> <div class="ide-pipeline"> - <loading-icon + <gl-loading-icon v-if="showLoadingIcon" + :size="2" class="prepend-top-default" - size="2" /> <template v-else-if="latestPipeline !== null"> <header diff --git a/app/assets/javascripts/ide/components/preview/clientside.vue b/app/assets/javascripts/ide/components/preview/clientside.vue index 39a1bd1f61b..37a8ad36507 100644 --- a/app/assets/javascripts/ide/components/preview/clientside.vue +++ b/app/assets/javascripts/ide/components/preview/clientside.vue @@ -3,14 +3,12 @@ import { mapActions, mapGetters, mapState } from 'vuex'; import _ from 'underscore'; import { Manager } from 'smooshpack'; import { listen } from 'codesandbox-api'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; import Navigator from './navigator.vue'; import { packageJsonPath } from '../../constants'; import { createPathWithExt } from '../../utils'; export default { components: { - LoadingIcon, Navigator, }, data() { @@ -177,9 +175,9 @@ export default { {{ s__('IDE|Get started with Live Preview') }} </a> </div> - <loading-icon + <gl-loading-icon v-else - size="2" + :size="2" class="align-self-center mt-auto mb-auto" /> </div> diff --git a/app/assets/javascripts/ide/components/preview/navigator.vue b/app/assets/javascripts/ide/components/preview/navigator.vue index 4bf346946b6..42f23801692 100644 --- a/app/assets/javascripts/ide/components/preview/navigator.vue +++ b/app/assets/javascripts/ide/components/preview/navigator.vue @@ -1,12 +1,10 @@ <script> import { listen } from 'codesandbox-api'; import Icon from '~/vue_shared/components/icon.vue'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; export default { components: { Icon, - LoadingIcon, }, props: { manager: { @@ -138,7 +136,7 @@ export default { class="ide-navigator-location form-control bg-white" readonly /> - <loading-icon + <gl-loading-icon v-if="loading" class="position-absolute ide-preview-loading-icon" /> diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue deleted file mode 100644 index 110eda83bb4..00000000000 --- a/app/assets/javascripts/ide/components/repo_file.vue +++ /dev/null @@ -1,227 +0,0 @@ -<script> -import { mapActions, mapGetters } from 'vuex'; -import { n__, __, sprintf } from '~/locale'; -import tooltip from '~/vue_shared/directives/tooltip'; -import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue'; -import Icon from '~/vue_shared/components/icon.vue'; -import FileIcon from '~/vue_shared/components/file_icon.vue'; -import router from '../ide_router'; -import NewDropdown from './new_dropdown/index.vue'; -import FileStatusIcon from './repo_file_status_icon.vue'; -import ChangedFileIcon from './changed_file_icon.vue'; -import MrFileIcon from './mr_file_icon.vue'; - -export default { - name: 'RepoFile', - directives: { - tooltip, - }, - components: { - SkeletonLoadingContainer, - NewDropdown, - FileStatusIcon, - FileIcon, - ChangedFileIcon, - MrFileIcon, - Icon, - }, - props: { - file: { - type: Object, - required: true, - }, - level: { - type: Number, - required: true, - }, - }, - data() { - return { - mouseOver: false, - }; - }, - computed: { - ...mapGetters([ - 'getChangesInFolder', - 'getUnstagedFilesCountForPath', - 'getStagedFilesCountForPath', - ]), - folderUnstagedCount() { - return this.getUnstagedFilesCountForPath(this.file.path); - }, - folderStagedCount() { - return this.getStagedFilesCountForPath(this.file.path); - }, - changesCount() { - return this.getChangesInFolder(this.file.path); - }, - folderChangesTooltip() { - if (this.changesCount === 0) return undefined; - - if (this.folderUnstagedCount > 0 && this.folderStagedCount === 0) { - return n__('%d unstaged change', '%d unstaged changes', this.folderUnstagedCount); - } else if (this.folderUnstagedCount === 0 && this.folderStagedCount > 0) { - return n__('%d staged change', '%d staged changes', this.folderStagedCount); - } - - return sprintf(__('%{unstaged} unstaged and %{staged} staged changes'), { - unstaged: this.folderUnstagedCount, - staged: this.folderStagedCount, - }); - }, - isTree() { - return this.file.type === 'tree'; - }, - isBlob() { - return this.file.type === 'blob'; - }, - levelIndentation() { - return { - marginLeft: `${this.level * 16}px`, - }; - }, - fileClass() { - return { - 'file-open': this.isBlob && this.file.opened, - 'file-active': this.isBlob && this.file.active, - folder: this.isTree, - 'is-open': this.file.opened, - }; - }, - showTreeChangesCount() { - return this.isTree && this.changesCount > 0 && !this.file.opened; - }, - showChangedFileIcon() { - return this.file.changed || this.file.tempFile || this.file.staged; - }, - }, - watch: { - 'file.active': function fileActiveWatch(active) { - if (this.file.type === 'blob' && active) { - this.scrollIntoView(); - } - }, - }, - mounted() { - if (this.hasPathAtCurrentRoute()) { - this.scrollIntoView(true); - } - }, - methods: { - ...mapActions(['toggleTreeOpen']), - clickFile() { - // Manual Action if a tree is selected/opened - if (this.isTree && this.hasUrlAtCurrentRoute()) { - this.toggleTreeOpen(this.file.path); - } - - router.push(`/project${this.file.url}`); - }, - scrollIntoView(isInit = false) { - const block = isInit && this.isTree ? 'center' : 'nearest'; - - this.$el.scrollIntoView({ - behavior: 'smooth', - block, - }); - }, - hasPathAtCurrentRoute() { - if (!this.$router || !this.$router.currentRoute) { - return false; - } - - // - strip route up to "/-/" and ending "/" - const routePath = this.$router.currentRoute.path - .replace(/^.*?[/]-[/]/g, '') - .replace(/[/]$/g, ''); - - // - strip ending "/" - const filePath = this.file.path.replace(/[/]$/g, ''); - - return filePath === routePath; - }, - hasUrlAtCurrentRoute() { - return this.$router.currentRoute.path === `/project${this.file.url}`; - }, - toggleHover(over) { - this.mouseOver = over; - }, - }, -}; -</script> - -<template> - <div> - <div - :class="fileClass" - class="file" - role="button" - @click="clickFile" - @mouseover="toggleHover(true)" - @mouseout="toggleHover(false)" - > - <div - class="file-name" - > - <span - :style="levelIndentation" - class="ide-file-name str-truncated" - > - <file-icon - :file-name="file.name" - :loading="file.loading" - :folder="isTree" - :opened="file.opened" - :size="16" - /> - {{ file.name }} - <file-status-icon - :file="file" - /> - </span> - <span class="float-right ide-file-icon-holder"> - <mr-file-icon - v-if="file.mrChange" - /> - <span - v-if="showTreeChangesCount" - class="ide-tree-changes" - > - {{ changesCount }} - <icon - v-tooltip - :title="folderChangesTooltip" - :size="12" - data-container="body" - data-placement="right" - name="file-modified" - css-classes="prepend-left-5 ide-file-modified" - /> - </span> - <changed-file-icon - v-else-if="showChangedFileIcon" - :file="file" - :show-tooltip="true" - :show-staged-icon="true" - :force-modified-icon="true" - class="float-right" - /> - </span> - <new-dropdown - :type="file.type" - :path="file.path" - :mouse-over="mouseOver" - class="float-right prepend-left-8" - /> - </div> - </div> - <template v-if="file.opened"> - <repo-file - v-for="childFile in file.tree" - :key="childFile.key" - :file="childFile" - :level="level + 1" - /> - </template> - </div> -</template> diff --git a/app/assets/javascripts/ide/components/repo_file_status_icon.vue b/app/assets/javascripts/ide/components/repo_file_status_icon.vue index 76a3333be50..97589e116c5 100644 --- a/app/assets/javascripts/ide/components/repo_file_status_icon.vue +++ b/app/assets/javascripts/ide/components/repo_file_status_icon.vue @@ -26,8 +26,8 @@ export default { <template> <span - v-tooltip v-if="file.file_lock" + v-tooltip :title="lockTooltip" data-container="body" > diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js index 79e38ae911e..c90f8694326 100644 --- a/app/assets/javascripts/ide/index.js +++ b/app/assets/javascripts/ide/index.js @@ -8,9 +8,21 @@ import { convertPermissionToBoolean } from '../lib/utils/common_utils'; Vue.use(Translate); -export function initIde(el) { +/** + * Initialize the IDE on the given element. + * + * @param {Element} el - The element that will contain the IDE. + * @param {Object} options - Extra options for the IDE (Used by EE). + * @param {(e:Element) => Object} options.extraInitialData - + * Function that returns extra properties to seed initial data. + */ +export function initIde(el, options = {}) { if (!el) return null; + const { + extraInitialData = () => ({}), + } = options; + return new Vue({ el, store, @@ -32,6 +44,7 @@ export function initIde(el) { }); this.setInitialData({ clientsidePreviewEnabled: convertPermissionToBoolean(el.dataset.clientsidePreviewEnabled), + ...extraInitialData(el), }); }, methods: { @@ -52,3 +65,18 @@ export function resetServiceWorkersPublicPath() { const webpackAssetPath = `${relativeRootPath}/assets/webpack/`; __webpack_public_path__ = webpackAssetPath; // eslint-disable-line camelcase } + +/** + * Start the IDE. + * + * @param {Objects} options - Extra options for the IDE (Used by EE). + */ +export function startIde(options) { + document.addEventListener('DOMContentLoaded', () => { + const ideElement = document.getElementById('ide'); + if (ideElement) { + resetServiceWorkersPublicPath(); + initIde(ideElement, options); + } + }); +} diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js index 35eaf21a836..9e848699163 100644 --- a/app/assets/javascripts/issuable_bulk_update_actions.js +++ b/app/assets/javascripts/issuable_bulk_update_actions.js @@ -36,7 +36,7 @@ export default { }, getSelectedIssues() { - return this.issues.has('.selected_issue:checked'); + return this.issues.has('.selected-issuable:checked'); }, getLabelsFromSelection() { @@ -110,7 +110,7 @@ export default { getOriginalCommonIds() { const labelIds = []; - this.getElement('.selected_issue:checked').each((i, el) => { + this.getElement('.selected-issuable:checked').each((i, el) => { labelIds.push(this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels')); }); return _.intersection.apply(this, labelIds); @@ -119,7 +119,7 @@ export default { // From issuable's initial bulk selection getOriginalMarkedIds() { const labelIds = []; - this.getElement('.selected_issue:checked').each((i, el) => { + this.getElement('.selected-issuable:checked').each((i, el) => { labelIds.push(this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels')); }); return _.intersection.apply(this, labelIds); @@ -132,7 +132,7 @@ export default { let issuableLabels = []; // Collect unique label IDs for all checked issues - this.getElement('.selected_issue:checked').each((i, el) => { + this.getElement('.selected-issuable:checked').each((i, el) => { issuableLabels = this.getElement(`#${this.prefixId}${el.dataset.id}`).data('labels'); issuableLabels.forEach((labelId) => { // Store unique IDs diff --git a/app/assets/javascripts/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable_bulk_update_sidebar.js index 2307c8e0d85..74150ce3a8b 100644 --- a/app/assets/javascripts/issuable_bulk_update_sidebar.js +++ b/app/assets/javascripts/issuable_bulk_update_sidebar.js @@ -30,7 +30,7 @@ export default class IssuableBulkUpdateSidebar { this.$otherFilters = $('.issues-other-filters'); this.$checkAllContainer = $('.check-all-holder'); this.$issueChecks = $('.issue-check'); - this.$issuesList = $('.selected_issue'); + this.$issuesList = $('.selected-issuable'); this.$issuableIdsInput = $('#update_issuable_ids'); } @@ -55,7 +55,7 @@ export default class IssuableBulkUpdateSidebar { } updateFormState() { - const noCheckedIssues = !$('.selected_issue:checked').length; + const noCheckedIssues = !$('.selected-issuable:checked').length; this.toggleSubmitButtonDisabled(noCheckedIssues); this.updateSelectedIssuableIds(); @@ -123,7 +123,7 @@ export default class IssuableBulkUpdateSidebar { } static getCheckedIssueIds() { - const $checkedIssues = $('.selected_issue:checked'); + const $checkedIssues = $('.selected-issuable:checked'); if ($checkedIssues.length > 0) { return $.map($checkedIssues, value => $(value).data('id')); diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue index ad928484952..c6ad3aa3e0d 100644 --- a/app/assets/javascripts/issue_show/components/app.vue +++ b/app/assets/javascripts/issue_show/components/app.vue @@ -293,6 +293,7 @@ :show-delete-button="showDeleteButton" :can-attach-file="canAttachFile" :enable-autocomplete="enableAutocomplete" + :issuable-type="issuableType" /> <recaptcha-modal diff --git a/app/assets/javascripts/issue_show/components/edit_actions.vue b/app/assets/javascripts/issue_show/components/edit_actions.vue index 597c6d69a81..bcf8686afcc 100644 --- a/app/assets/javascripts/issue_show/components/edit_actions.vue +++ b/app/assets/javascripts/issue_show/components/edit_actions.vue @@ -1,7 +1,13 @@ <script> + import { __, sprintf } from '~/locale'; import updateMixin from '../mixins/update'; import eventHub from '../event_hub'; + const issuableTypes = { + issue: __('Issue'), + epic: __('Epic'), + }; + export default { mixins: [updateMixin], props: { @@ -18,6 +24,10 @@ required: false, default: true, }, + issuableType: { + type: String, + required: true, + }, }, data() { return { @@ -37,8 +47,11 @@ eventHub.$emit('close.form'); }, deleteIssuable() { + const confirmMessage = sprintf(__('%{issuableType} will be removed! Are you sure?'), { + issuableType: issuableTypes[this.issuableType], + }); // eslint-disable-next-line no-alert - if (window.confirm('Issue will be removed! Are you sure?')) { + if (window.confirm(confirmMessage)) { this.deleteLoading = true; eventHub.$emit('delete.issuable'); @@ -53,7 +66,7 @@ <button :class="{ disabled: formState.updateLoading || !isSubmitEnabled }" :disabled="formState.updateLoading || !isSubmitEnabled" - class="btn btn-save float-left" + class="btn btn-success float-left" type="submit" @click.prevent="updateIssuable"> Save changes diff --git a/app/assets/javascripts/issue_show/components/form.vue b/app/assets/javascripts/issue_show/components/form.vue index e509bb52f7d..03d8d0ec67c 100644 --- a/app/assets/javascripts/issue_show/components/form.vue +++ b/app/assets/javascripts/issue_show/components/form.vue @@ -27,6 +27,10 @@ required: false, default: () => [], }, + issuableType: { + type: String, + required: true, + }, markdownPreviewPath: { type: String, required: true, @@ -110,6 +114,7 @@ :form-state="formState" :can-destroy="canDestroy" :show-delete-button="showDeleteButton" + :issuable-type="issuableType" /> </form> </template> diff --git a/app/assets/javascripts/issue_show/components/title.vue b/app/assets/javascripts/issue_show/components/title.vue index b5e8e0ea44b..cf99e9a9cd8 100644 --- a/app/assets/javascripts/issue_show/components/title.vue +++ b/app/assets/javascripts/issue_show/components/title.vue @@ -76,8 +76,8 @@ export default { > </h2> <button - v-tooltip v-if="showInlineEditButton && canUpdate" + v-tooltip type="button" class="btn btn-default btn-edit btn-svg js-issuable-edit" title="Edit title and description" diff --git a/app/assets/javascripts/jobs/components/artifacts_block.vue b/app/assets/javascripts/jobs/components/artifacts_block.vue index 525c5eec91a..d5866f9b9f1 100644 --- a/app/assets/javascripts/jobs/components/artifacts_block.vue +++ b/app/assets/javascripts/jobs/components/artifacts_block.vue @@ -1,40 +1,27 @@ <script> - import TimeagoTooltiop from '~/vue_shared/components/time_ago_tooltip.vue'; + import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; + import timeagoMixin from '~/vue_shared/mixins/timeago'; export default { components: { - TimeagoTooltiop, + TimeagoTooltip, }, + mixins: [ + timeagoMixin, + ], props: { - // @build.artifacts_expired? - haveArtifactsExpired: { - type: Boolean, + artifact: { + type: Object, required: true, }, - // @build.has_expiring_artifacts? - willArtifactsExpire: { - type: Boolean, - required: true, - }, - expireAt: { - type: String, - required: false, - default: null, - }, - keepArtifactsPath: { - type: String, - required: false, - default: null, - }, - downloadArtifactsPath: { - type: String, - required: false, - default: null, + }, + computed: { + isExpired() { + return this.artifact.expired; }, - browseArtifactsPath: { - type: String, - required: false, - default: null, + // Only when the key is `false` we can render this block + willExpire() { + return this.artifact.expired === false; }, }, }; @@ -46,21 +33,22 @@ </div> <p - v-if="haveArtifactsExpired" + v-if="isExpired" class="js-artifacts-removed build-detail-row" > {{ s__('Job|The artifacts were removed') }} </p> + <p - v-else-if="willArtifactsExpire" + v-else-if="willExpire" class="js-artifacts-will-be-removed build-detail-row" > - {{ s__('Job|The artifacts will be removed') }} + {{ s__('Job|The artifacts will be removed in') }} </p> - <timeago-tooltiop - v-if="expireAt" - :time="expireAt" + <timeago-tooltip + v-if="artifact.expire_at" + :time="artifact.expire_at" /> <div @@ -68,8 +56,8 @@ role="group" > <a - v-if="keepArtifactsPath" - :href="keepArtifactsPath" + v-if="artifact.keep_path" + :href="artifact.keep_path" class="js-keep-artifacts btn btn-sm btn-default" data-method="post" > @@ -77,8 +65,8 @@ </a> <a - v-if="downloadArtifactsPath" - :href="downloadArtifactsPath" + v-if="artifact.download_path" + :href="artifact.download_path" class="js-download-artifacts btn btn-sm btn-default" download rel="nofollow" @@ -87,8 +75,8 @@ </a> <a - v-if="browseArtifactsPath" - :href="browseArtifactsPath" + v-if="artifact.browse_path" + :href="artifact.browse_path" class="js-browse-artifacts btn btn-sm btn-default" > {{ s__('Job|Browse') }} diff --git a/app/assets/javascripts/jobs/components/commit_block.vue b/app/assets/javascripts/jobs/components/commit_block.vue index 7f485295513..39a4ff159e2 100644 --- a/app/assets/javascripts/jobs/components/commit_block.vue +++ b/app/assets/javascripts/jobs/components/commit_block.vue @@ -1,64 +1,56 @@ <script> -import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; + import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; -export default { - components: { - ClipboardButton, - }, - props: { - pipelineShortSha: { - type: String, - required: true, + export default { + components: { + ClipboardButton, }, - pipelineShaPath: { - type: String, - required: true, + props: { + commit: { + type: Object, + required: true, + }, + mergeRequest: { + type: Object, + required: false, + default: null, + }, + isLastBlock: { + type: Boolean, + required: true, + }, }, - mergeRequestReference: { - type: String, - required: false, - default: null, - }, - mergeRequestPath: { - type: String, - required: false, - default: null, - }, - gitCommitTitlte: { - type: String, - required: true, - }, - }, -}; + }; </script> <template> - <div class="block"> + <div + :class="{ + 'block-last': isLastBlock, + block: !isLastBlock + }"> <p> {{ __('Commit') }} <a - :href="pipelineShaPath" + :href="commit.commit_path" class="js-commit-sha commit-sha link-commit" - > - {{ pipelineShortSha }} - </a> + >{{ commit.short_id }}</a> <clipboard-button - :text="pipelineShortSha" + :text="commit.short_id" :title="__('Copy commit SHA to clipboard')" + css-class="btn btn-clipboard btn-transparent" /> <a - v-if="mergeRequestPath && mergeRequestReference" - :href="mergeRequestPath" + v-if="mergeRequest" + :href="mergeRequest.path" class="js-link-commit link-commit" - > - {{ mergeRequestReference }} - </a> + >{{ mergeRequest.iid }}</a> </p> <p class="build-light-text append-bottom-0"> - {{ gitCommitTitlte }} + {{ commit.title }} </p> </div> </template> diff --git a/app/assets/javascripts/jobs/components/header.vue b/app/assets/javascripts/jobs/components/header.vue index 1e7f4b2c3f7..63324e68d68 100644 --- a/app/assets/javascripts/jobs/components/header.vue +++ b/app/assets/javascripts/jobs/components/header.vue @@ -1,13 +1,11 @@ <script> import ciHeader from '../../vue_shared/components/header_ci_component.vue'; -import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import callout from '../../vue_shared/components/callout.vue'; export default { name: 'JobHeaderSection', components: { ciHeader, - loadingIcon, callout, }, props: { @@ -59,7 +57,7 @@ export default { actions.push({ label: 'New issue', path: this.job.new_issue_path, - cssClass: 'js-new-issue btn btn-new btn-inverted d-none d-md-block d-lg-block d-xl-block', + cssClass: 'js-new-issue btn btn-success btn-inverted d-none d-md-block d-lg-block d-xl-block', type: 'link', }); } @@ -82,9 +80,9 @@ export default { :should-render-triggered-label="jobStarted" item-name="Job" /> - <loading-icon + <gl-loading-icon v-if="isLoading" - size="2" + :size="2" class="prepend-top-default append-bottom-default" /> </div> diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue index 513851e376f..2cbf0f85266 100644 --- a/app/assets/javascripts/jobs/components/job_log_controllers.vue +++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue @@ -78,8 +78,8 @@ <div class="controllers float-right"> <!-- links --> <a - v-tooltip v-if="rawTracePath" + v-tooltip :title="s__('Job|Show complete raw')" :href="rawTracePath" class="js-raw-link-controller controllers-buttons" @@ -89,8 +89,8 @@ </a> <button - v-tooltip v-if="canEraseJob" + v-tooltip :title="s__('Job|Erase job log')" type="button" class="js-erase-link controllers-buttons" diff --git a/app/assets/javascripts/jobs/components/jobs_container.vue b/app/assets/javascripts/jobs/components/jobs_container.vue index b81109bdd06..93e2292ff84 100644 --- a/app/assets/javascripts/jobs/components/jobs_container.vue +++ b/app/assets/javascripts/jobs/components/jobs_container.vue @@ -25,9 +25,9 @@ class="build-job" > <a - v-tooltip v-for="job in jobs" :key="job.id" + v-tooltip :href="job.path" :title="job.tooltip" :class="{ active: job.active, retried: job.retried }" diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue index 36d4a3e2bc9..a591fcfb482 100644 --- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue +++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue @@ -1,91 +1,113 @@ <script> -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; -import timeagoMixin from '~/vue_shared/mixins/timeago'; -import { timeIntervalInWords } from '~/lib/utils/datetime_utility'; -import Icon from '~/vue_shared/components/icon.vue'; -import DetailRow from './sidebar_detail_row.vue'; + import _ from 'underscore'; + import timeagoMixin from '~/vue_shared/mixins/timeago'; + import { timeIntervalInWords } from '~/lib/utils/datetime_utility'; + import Icon from '~/vue_shared/components/icon.vue'; + import DetailRow from './sidebar_detail_row.vue'; + import ArtifactsBlock from './artifacts_block.vue'; + import TriggerBlock from './trigger_block.vue'; + import CommitBlock from './commit_block.vue'; -export default { - name: 'SidebarDetailsBlock', - components: { - DetailRow, - LoadingIcon, - Icon, - }, - mixins: [timeagoMixin], - props: { - job: { - type: Object, - required: true, + export default { + name: 'SidebarDetailsBlock', + components: { + ArtifactsBlock, + CommitBlock, + DetailRow, + Icon, + TriggerBlock, }, - isLoading: { - type: Boolean, - required: true, + mixins: [timeagoMixin], + props: { + job: { + type: Object, + required: true, + }, + isLoading: { + type: Boolean, + required: true, + }, + runnerHelpUrl: { + type: String, + required: false, + default: '', + }, + terminalPath: { + type: String, + required: false, + default: null, + }, }, - runnerHelpUrl: { - type: String, - required: false, - default: '', - }, - terminalPath: { - type: String, - required: false, - default: null, - }, - }, - computed: { - shouldRenderContent() { - return !this.isLoading && Object.keys(this.job).length > 0; - }, - coverage() { - return `${this.job.coverage}%`; - }, - duration() { - return timeIntervalInWords(this.job.duration); - }, - queued() { - return timeIntervalInWords(this.job.queued); - }, - runnerId() { - return `${this.job.runner.description} (#${this.job.runner.id})`; - }, - retryButtonClass() { - let className = - 'js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block'; - className += - this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary'; - return className; - }, - hasTimeout() { - return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null; - }, - timeout() { - if (this.job.metadata == null) { - return ''; - } + computed: { + shouldRenderContent() { + return !this.isLoading && Object.keys(this.job).length > 0; + }, + coverage() { + return `${this.job.coverage}%`; + }, + duration() { + return timeIntervalInWords(this.job.duration); + }, + queued() { + return timeIntervalInWords(this.job.queued); + }, + runnerId() { + return `${this.job.runner.description} (#${this.job.runner.id})`; + }, + retryButtonClass() { + let className = + 'js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block'; + className += + this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary'; + return className; + }, + hasTimeout() { + return this.job.metadata != null && this.job.metadata.timeout_human_readable !== null; + }, + timeout() { + if (this.job.metadata == null) { + 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; - }, - renderBlock() { - return ( - this.job.merge_request || - this.job.duration || - this.job.finished_data || - this.job.erased_at || - this.job.queued || - this.job.runner || - this.job.coverage || - this.job.tags.length || - this.job.cancel_path - ); + return t; + }, + renderBlock() { + return ( + this.job.merge_request || + this.job.duration || + this.job.finished_data || + this.job.erased_at || + this.job.queued || + this.job.runner || + this.job.coverage || + this.job.tags.length || + this.job.cancel_path + ); + }, + hasArtifact() { + return !_.isEmpty(this.job.artifact); + }, + hasTriggers() { + return !_.isEmpty(this.job.trigger); + }, + hasStages() { + return ( + this.job && + this.job.pipeline && + this.job.pipeline.stages && + this.job.pipeline.stages.length > 0 + ) || false; + }, + commit() { + return this.job.pipeline.commit || {}; + }, }, - }, -}; + }; </script> <template> <div> @@ -132,7 +154,7 @@ export default { <a v-if="job.new_issue_path" :href="job.new_issue_path" - class="js-new-issue btn btn-new btn-inverted" + class="js-new-issue btn btn-success btn-inverted" > {{ __('New issue') }} </a> @@ -231,11 +253,24 @@ export default { </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" + /> </template> - <loading-icon + <gl-loading-icon v-if="isLoading" + :size="2" class="prepend-top-10" - size="2" /> </div> </template> diff --git a/app/assets/javascripts/jobs/components/trigger_block.vue b/app/assets/javascripts/jobs/components/trigger_block.vue index 8a88e5da6aa..d7b3c4fcb5b 100644 --- a/app/assets/javascripts/jobs/components/trigger_block.vue +++ b/app/assets/javascripts/jobs/components/trigger_block.vue @@ -1,16 +1,9 @@ <script> export default { props: { - shortToken: { - type: String, - required: false, - default: null, - }, - - variables: { + trigger: { type: Object, - required: false, - default: () => ({}), + required: true, }, }, data() { @@ -20,7 +13,7 @@ }, computed: { hasVariables() { - return Object.keys(this.variables).length > 0; + return this.trigger.variables && this.trigger.variables.length > 0; }, }, methods: { @@ -38,17 +31,18 @@ </h4> <p - v-if="shortToken" + v-if="trigger.short_token" class="js-short-token" > <span class="build-light-text"> {{ __('Token') }} </span> - {{ shortToken }} + {{ trigger.short_token }} </p> <p v-if="hasVariables"> <button + v-if="!areVariablesVisible" type="button" class="btn btn-default group js-reveal-variables" @click="revealVariables" @@ -63,20 +57,20 @@ class="js-build-variables trigger-build-variables" > <template - v-for="(value, key) in variables" + v-for="variable in trigger.variables" > <dt - :key="`${key}-variable`" + :key="`${variable.key}-variable`" class="js-build-variable trigger-build-variable" > - {{ key }} + {{ variable.key }} </dt> <dd - :key="`${key}-value`" + :key="`${variable.key}-value`" class="js-build-value trigger-build-value" > - {{ value }} + {{ variable.value }} </dd> </template> </dl> diff --git a/app/assets/javascripts/jobs/job_details_mediator.js b/app/assets/javascripts/jobs/job_details_mediator.js index 89019da9d1e..073e518baa0 100644 --- a/app/assets/javascripts/jobs/job_details_mediator.js +++ b/app/assets/javascripts/jobs/job_details_mediator.js @@ -4,7 +4,6 @@ import Poll from '../lib/utils/poll'; import JobStore from './stores/job_store'; import JobService from './services/job_service'; import Job from '../job'; -import handleRevealVariables from '../build_variables'; export default class JobMediator { constructor(options = {}) { @@ -20,7 +19,6 @@ export default class JobMediator { initBuildClass() { this.build = new Job(); - handleRevealVariables(); } fetchJob() { diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 6499b919787..1c7bca78df3 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -449,11 +449,11 @@ export default class LabelsSelect { } bindEvents() { - return $('body').on('change', '.selected_issue', this.onSelectCheckboxIssue); + return $('body').on('change', '.selected-issuable', this.onSelectCheckboxIssue); } // eslint-disable-next-line class-methods-use-this onSelectCheckboxIssue() { - if ($('.selected_issue:checked').length) { + if ($('.selected-issuable:checked').length) { return; } return $('.issues-bulk-update .labels-filter .dropdown-toggle-text').text('Label'); diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 0849d97bc1a..30925940807 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -56,7 +56,8 @@ export const rstrip = val => { return val; }; -export const updateTooltipTitle = ($tooltipEl, newTitle) => $tooltipEl.attr('title', newTitle).tooltip('_fixTitle'); +export const updateTooltipTitle = ($tooltipEl, newTitle) => + $tooltipEl.attr('title', newTitle).tooltip('_fixTitle'); export const disableButtonIfEmptyField = (fieldSelector, buttonSelector, eventName = 'input') => { const field = $(fieldSelector); @@ -86,6 +87,7 @@ export const handleLocationHash = () => { const fixedTabs = document.querySelector('.js-tabs-affix'); const fixedDiffStats = document.querySelector('.js-diff-files-changed'); const fixedNav = document.querySelector('.navbar-gitlab'); + const performanceBar = document.querySelector('#js-peek'); let adjustment = 0; if (fixedNav) adjustment -= fixedNav.offsetHeight; @@ -102,6 +104,10 @@ export const handleLocationHash = () => { adjustment -= fixedDiffStats.offsetHeight; } + if (performanceBar) { + adjustment -= performanceBar.offsetHeight; + } + window.scrollBy(0, adjustment); }; @@ -131,21 +137,20 @@ export const parseUrlPathname = url => { return parsedUrl.pathname.charAt(0) === '/' ? parsedUrl.pathname : `/${parsedUrl.pathname}`; }; -const splitPath = (path = '') => path - .replace(/^\?/, '') - .split('&'); +const splitPath = (path = '') => path.replace(/^\?/, '').split('&'); -export const urlParamsToArray = (path = '') => splitPath(path) - .filter(param => param.length > 0) - .map(param => { - const split = param.split('='); - return [decodeURI(split[0]), split[1]].join('='); - }); +export const urlParamsToArray = (path = '') => + splitPath(path) + .filter(param => param.length > 0) + .map(param => { + const split = param.split('='); + return [decodeURI(split[0]), split[1]].join('='); + }); export const getUrlParamsArray = () => urlParamsToArray(window.location.search); -export const urlParamsToObject = (path = '') => splitPath(path) - .reduce((dataParam, filterParam) => { +export const urlParamsToObject = (path = '') => + splitPath(path).reduce((dataParam, filterParam) => { if (filterParam === '') { return dataParam; } @@ -216,7 +221,7 @@ export const getParameterByName = (name, urlToParse) => { return decodeURIComponent(results[2].replace(/\+/g, ' ')); }; -const handleSelectedRange = (range) => { +const handleSelectedRange = range => { const container = range.commonAncestorContainer; // add context to fragment if needed if (container.tagName === 'OL') { @@ -453,7 +458,7 @@ export const backOff = (fn, timeout = 60000) => { export const createOverlayIcon = (iconPath, overlayPath) => { const faviconImage = document.createElement('img'); - return new Promise((resolve) => { + return new Promise(resolve => { faviconImage.onload = () => { const size = 32; @@ -464,13 +469,29 @@ export const createOverlayIcon = (iconPath, overlayPath) => { const context = canvas.getContext('2d'); context.clearRect(0, 0, size, size); context.drawImage( - faviconImage, 0, 0, faviconImage.width, faviconImage.height, 0, 0, size, size, + faviconImage, + 0, + 0, + faviconImage.width, + faviconImage.height, + 0, + 0, + size, + size, ); const overlayImage = document.createElement('img'); overlayImage.onload = () => { context.drawImage( - overlayImage, 0, 0, overlayImage.width, overlayImage.height, 0, 0, size, size, + overlayImage, + 0, + 0, + overlayImage.width, + overlayImage.height, + 0, + 0, + size, + size, ); const faviconWithOverlayUrl = canvas.toDataURL(); @@ -483,17 +504,21 @@ export const createOverlayIcon = (iconPath, overlayPath) => { }); }; -export const setFaviconOverlay = (overlayPath) => { +export const setFaviconOverlay = overlayPath => { const faviconEl = document.getElementById('favicon'); - if (!faviconEl) { return null; } + if (!faviconEl) { + return null; + } const iconPath = faviconEl.getAttribute('data-original-href'); - return createOverlayIcon(iconPath, overlayPath).then(faviconWithOverlayUrl => faviconEl.setAttribute('href', faviconWithOverlayUrl)); + return createOverlayIcon(iconPath, overlayPath).then(faviconWithOverlayUrl => + faviconEl.setAttribute('href', faviconWithOverlayUrl), + ); }; -export const setFavicon = (faviconPath) => { +export const setFavicon = faviconPath => { const faviconEl = document.getElementById('favicon'); if (faviconEl && faviconPath) { faviconEl.setAttribute('href', faviconPath); @@ -518,7 +543,7 @@ export const setCiStatusFavicon = pageUrl => } return resetFavicon(); }) - .catch((error) => { + .catch(error => { resetFavicon(); throw error; }); diff --git a/app/assets/javascripts/shortcuts_dashboard_navigation.js b/app/assets/javascripts/lib/utils/navigation_utility.js index 9f69f110d06..1579b225e44 100644 --- a/app/assets/javascripts/shortcuts_dashboard_navigation.js +++ b/app/assets/javascripts/lib/utils/navigation_utility.js @@ -1,4 +1,4 @@ -import { visitUrl } from './lib/utils/url_utility'; +import { visitUrl } from './url_utility'; /** * Helper function that finds the href of the fiven selector and updates the location. diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js index c5a5f64abac..e8aac51a299 100644 --- a/app/assets/javascripts/main.js +++ b/app/assets/javascripts/main.js @@ -2,7 +2,6 @@ import jQuery from 'jquery'; import Cookies from 'js-cookie'; -import svg4everybody from 'svg4everybody'; // bootstrap webpack, common libs, polyfills, and behaviors import './webpack'; @@ -25,11 +24,12 @@ import initLayoutNav from './layout_nav'; import './feature_highlight/feature_highlight_options'; import LazyLoader from './lazy_loader'; import initLogoAnimation from './logo'; -import './milestone_select'; import './frequent_items'; import initBreadcrumbs from './breadcrumb'; -import initDispatcher from './dispatcher'; import initUsagePingConsent from './usage_ping_consent'; +import initPerformanceBar from './performance_bar'; +import initSearchAutocomplete from './search_autocomplete'; +import GlFieldErrors from './gl_field_errors'; // expose jQuery as global (TODO: remove these) window.jQuery = jQuery; @@ -41,8 +41,6 @@ if (process.env.NODE_ENV !== 'production' && gon && gon.test_env) { import(/* webpackMode: "eager" */ './test_utils/'); } -svg4everybody(); - document.addEventListener('beforeunload', () => { // Unbind scroll events $(document).off('scroll'); @@ -81,6 +79,9 @@ document.addEventListener('DOMContentLoaded', () => { initLogoAnimation(); initUsagePingConsent(); + if (document.querySelector('.search')) initSearchAutocomplete(); + if (document.querySelector('#js-peek')) initPerformanceBar({ container: '#js-peek' }); + // Set the default path for all cookies to GitLab's root directory Cookies.defaults.path = gon.relative_url_root || '/'; @@ -270,5 +271,6 @@ document.addEventListener('DOMContentLoaded', () => { }); } - initDispatcher(); + // initialize field errors + $('.gl-show-field-errors').each((i, form) => new GlFieldErrors(form)); }); diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js index 53d7504de35..763429d7242 100644 --- a/app/assets/javascripts/merge_request_tabs.js +++ b/app/assets/javascripts/merge_request_tabs.js @@ -115,8 +115,9 @@ export default class MergeRequestTabs { this.mergeRequestTabs && this.mergeRequestTabs.querySelector(`a[data-action='${action}']`) && this.mergeRequestTabs.querySelector(`a[data-action='${action}']`).click - ) + ) { this.mergeRequestTabs.querySelector(`a[data-action='${action}']`).click(); + } this.initAffix(); } diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 524f4d1f568..228eff006f7 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -220,8 +220,8 @@ export default { :show-panels="showPanels" > <graph - v-for="(graphData, index) in groupData.metrics" - :key="index" + v-for="(graphData, graphIndex) in groupData.metrics" + :key="graphIndex" :graph-data="graphData" :hover-data="hoverData" :deployment-data="store.deploymentData" diff --git a/app/assets/javascripts/monitoring/components/graph/legend.vue b/app/assets/javascripts/monitoring/components/graph/legend.vue index 3276f3a1ceb..ef18ae5c2c8 100644 --- a/app/assets/javascripts/monitoring/components/graph/legend.vue +++ b/app/assets/javascripts/monitoring/components/graph/legend.vue @@ -58,8 +58,8 @@ export default { </td> <template v-for="(track, trackIndex) in series.tracksLegend"> <track-line - :track="track" - :key="`track-line-${trackIndex}`"/> + :key="`track-line-${trackIndex}`" + :track="track"/> <td :key="`track-info-${trackIndex}`"> <track-info :track="track" diff --git a/app/assets/javascripts/mr_notes/stores/index.js b/app/assets/javascripts/mr_notes/stores/index.js index dd2019001db..446eb477efc 100644 --- a/app/assets/javascripts/mr_notes/stores/index.js +++ b/app/assets/javascripts/mr_notes/stores/index.js @@ -9,7 +9,7 @@ Vue.use(Vuex); export default new Vuex.Store({ modules: { page: mrPageModule, - notes: notesModule, - diffs: diffsModule, + notes: notesModule(), + diffs: diffsModule(), }, }); diff --git a/app/assets/javascripts/notebook/index.vue b/app/assets/javascripts/notebook/index.vue index e2e3b08c77f..f241df9620d 100644 --- a/app/assets/javascripts/notebook/index.vue +++ b/app/assets/javascripts/notebook/index.vue @@ -51,10 +51,10 @@ <template> <div v-if="hasNotebook"> <component - v-for="(cell, index) in cells" :is="cellType(cell.cell_type)" - :cell="cell" + v-for="(cell, index) in cells" :key="index" + :cell="cell" :code-css-class="codeCssClass" /> </div> </template> diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 8b1d8f6055e..0c966e0808a 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -631,7 +631,7 @@ export default class Notes { * * deactivates the submit button when text is empty * hides the preview button when text is empty - * setup GFM auto complete + * set up GFM auto complete * show the form */ setupNoteForm(form, enableGFM = defaultAutocompleteConfig) { @@ -954,7 +954,7 @@ export default class Notes { * Note: dataHolder must have the "discussionId" and "lineCode" data attributes set. */ setupDiscussionNoteForm(dataHolder, form) { - // setup note target + // set up note target let diffFileData = dataHolder.closest('.text-file'); if (diffFileData.length === 0) { @@ -1036,7 +1036,7 @@ export default class Notes { $diffFile[0].dispatchEvent(clickEvent); - // Setup comment form + // Set up comment form let newForm; const $noteContainer = $link.closest('.diff-viewer').find('.note-container'); const $form = $noteContainer.find('> .discussion-form'); diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index 6612bc44e0b..7735133c470 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -374,7 +374,7 @@ js-gfm-input js-autosize markdown-area js-vue-textarea" append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"> <button :disabled="isSubmitButtonDisabled" - class="btn btn-create comment-btn js-comment-button js-comment-submit-button" + class="btn btn-success comment-btn js-comment-button js-comment-submit-button" type="submit" @click.prevent="handleSave()"> {{ __(commentButtonTitle) }} diff --git a/app/assets/javascripts/notes/components/diff_file_header.vue b/app/assets/javascripts/notes/components/diff_file_header.vue index fc7b52be241..4fd93304a03 100644 --- a/app/assets/javascripts/notes/components/diff_file_header.vue +++ b/app/assets/javascripts/notes/components/diff_file_header.vue @@ -41,8 +41,8 @@ export default { </div> <template v-else> <component - ref="titleWrapper" :is="titleTag" + ref="titleWrapper" :href="diffFile.discussionPath" > <span v-html="diffFile.blobIcon"></span> diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue index 87fc002fcbc..beb53da0e6d 100644 --- a/app/assets/javascripts/notes/components/note_actions.vue +++ b/app/assets/javascripts/notes/components/note_actions.vue @@ -7,7 +7,6 @@ import editSvg from 'icons/_icon_pencil.svg'; import resolveDiscussionSvg from 'icons/_icon_resolve_discussion.svg'; import resolvedDiscussionSvg from 'icons/_icon_status_success_solid.svg'; import ellipsisSvg from 'icons/_ellipsis_v.svg'; -import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import tooltip from '~/vue_shared/directives/tooltip'; export default { @@ -15,9 +14,6 @@ export default { directives: { tooltip, }, - components: { - loadingIcon, - }, props: { authorId: { type: Number, @@ -153,9 +149,9 @@ export default { v-else v-html="resolveDiscussionSvg"></div> </template> - <loading-icon + <gl-loading-icon v-else - :inline="true" + inline /> </button> </div> @@ -172,7 +168,7 @@ export default { href="#" title="Add reaction" > - <loading-icon :inline="true" /> + <gl-loading-icon inline/> <span class="link-highlight award-control-icon-neutral" v-html="emojiSmiling"> diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue index 051b17e9aa9..c68860d98ae 100644 --- a/app/assets/javascripts/notes/components/note_awards_list.vue +++ b/app/assets/javascripts/notes/components/note_awards_list.vue @@ -182,9 +182,9 @@ export default { <div class="note-awards"> <div class="awards js-awards-block"> <button - v-tooltip v-for="(awardList, awardName, index) in groupedAwards" :key="index" + v-tooltip :class="getAwardClassBindings(awardList)" :title="awardTitle(awardList)" class="btn award-control" diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue index c41ed070383..2d47d55f33c 100644 --- a/app/assets/javascripts/notes/components/note_form.vue +++ b/app/assets/javascripts/notes/components/note_form.vue @@ -171,8 +171,8 @@ export default { id="note_note" ref="textarea" slot="textarea" - :data-supports-quick-actions="!isEditing" v-model="updatedNoteBody" + :data-supports-quick-actions="!isEditing" name="note[note]" class="note-textarea js-gfm-input js-note-text js-autosize markdown-area js-vue-issue-note-form js-vue-textarea" @@ -188,7 +188,7 @@ js-autosize markdown-area js-vue-issue-note-form js-vue-textarea" <button :disabled="isDisabled" type="button" - class="js-vue-issue-save btn btn-save js-comment-button " + class="js-vue-issue-save btn btn-success js-comment-button " @click="handleUpdate()"> {{ saveButtonTitle }} </button> diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index afe86911230..6ede7562edf 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -348,10 +348,10 @@ Please check your network connection and try again.`; <div class="discussion-notes"> <ul class="notes"> <component - v-for="note in discussion.notes" :is="componentName(note)" - :note="componentData(note)" + v-for="note in discussion.notes" :key="note.id" + :note="componentData(note)" @handleDeleteNote="deleteNoteHandler" /> </ul> diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index 7f9d23b211b..d8e8efb982a 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -10,7 +10,6 @@ import systemNote from '../../vue_shared/components/notes/system_note.vue'; import commentForm from './comment_form.vue'; import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue'; import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue'; -import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import skeletonLoadingContainer from '../../vue_shared/components/notes/skeleton_note.vue'; export default { @@ -20,7 +19,6 @@ export default { noteableDiscussion, systemNote, commentForm, - loadingIcon, placeholderNote, placeholderSystemNote, }, @@ -189,10 +187,10 @@ export default { class="notes main-notes-list timeline" > <component - v-for="discussion in allDiscussions" :is="getComponentName(discussion)" - v-bind="getComponentData(discussion)" + v-for="discussion in allDiscussions" :key="discussion.id" + v-bind="getComponentData(discussion)" /> </ul> diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 9a2ec15debd..320dfa47d5a 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -10,6 +10,7 @@ import service from '../services/notes_service'; 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'; let eTagPoll; @@ -43,27 +44,17 @@ export const fetchDiscussions = ({ commit }, path) => commit(types.SET_INITIAL_DISCUSSIONS, discussions); }); -export const refetchDiscussionById = ({ commit, state }, { path, discussionId }) => - new Promise(resolve => { - service - .fetchDiscussions(path) - .then(res => res.json()) - .then(discussions => { - const selectedDiscussion = discussions.find(discussion => discussion.id === discussionId); - if (selectedDiscussion) { - commit(types.UPDATE_DISCUSSION, selectedDiscussion); - // We need to refetch as it is now the transformed one in state - const discussion = utils.findNoteObjectById(state.discussions, discussionId); - - resolve(discussion); - } - }) - .catch(() => {}); - }); +export const updateDiscussion = ({ commit, state }, discussion) => { + commit(types.UPDATE_DISCUSSION, discussion); -export const deleteNote = ({ commit }, note) => + return utils.findNoteObjectById(state.discussions, discussion.id); +}; + +export const deleteNote = ({ commit, dispatch }, note) => service.deleteNote(note.path).then(() => { commit(types.DELETE_NOTE, note); + + dispatch('updateMergeRequestWidget'); }); export const updateNote = ({ commit }, { endpoint, note }) => @@ -84,20 +75,22 @@ export const replyToDiscussion = ({ commit }, { endpoint, data }) => return res; }); -export const createNewNote = ({ commit }, { endpoint, data }) => +export const createNewNote = ({ commit, dispatch }, { endpoint, data }) => service .createNewNote(endpoint, data) .then(res => res.json()) .then(res => { if (!res.errors) { commit(types.ADD_NEW_NOTE, res); + + dispatch('updateMergeRequestWidget'); } return res; }); export const removePlaceholderNotes = ({ commit }) => commit(types.REMOVE_PLACEHOLDER_NOTES); -export const toggleResolveNote = ({ commit }, { endpoint, isResolved, discussion }) => +export const toggleResolveNote = ({ commit, dispatch }, { endpoint, isResolved, discussion }) => service .toggleResolveNote(endpoint, isResolved) .then(res => res.json()) @@ -105,6 +98,8 @@ export const toggleResolveNote = ({ commit }, { endpoint, isResolved, discussion const mutationType = discussion ? types.UPDATE_DISCUSSION : types.UPDATE_NOTE; commit(mutationType, res); + + dispatch('updateMergeRequestWidget'); }); export const closeIssue = ({ commit, dispatch, state }) => { @@ -333,5 +328,9 @@ export const fetchDiscussionDiffLines = ({ commit }, discussion) => }); }); +export const updateMergeRequestWidget = () => { + mrWidgetEventHub.$emit('mr.discussion.updated'); +}; + // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/notes/stores/index.js b/app/assets/javascripts/notes/stores/index.js index 0f48b8880f4..f105b7d0d11 100644 --- a/app/assets/javascripts/notes/stores/index.js +++ b/app/assets/javascripts/notes/stores/index.js @@ -1,16 +1,8 @@ import Vue from 'vue'; import Vuex from 'vuex'; -import * as actions from './actions'; -import * as getters from './getters'; -import mutations from './mutations'; -import module from './modules'; +import notesModule from './modules'; Vue.use(Vuex); export default () => - new Vuex.Store({ - state: module.state, - actions, - getters, - mutations, - }); + new Vuex.Store(notesModule()); diff --git a/app/assets/javascripts/notes/stores/modules/index.js b/app/assets/javascripts/notes/stores/modules/index.js index b4cb9267e0f..61dbb075586 100644 --- a/app/assets/javascripts/notes/stores/modules/index.js +++ b/app/assets/javascripts/notes/stores/modules/index.js @@ -2,7 +2,7 @@ import * as actions from '../actions'; import * as getters from '../getters'; import mutations from '../mutations'; -export default { +export default () => ({ state: { discussions: [], targetNoteHash: null, @@ -24,4 +24,4 @@ export default { actions, getters, mutations, -}; +}); diff --git a/app/assets/javascripts/notes/stores/mutations.js b/app/assets/javascripts/notes/stores/mutations.js index 2c04bfea122..73e55705f39 100644 --- a/app/assets/javascripts/notes/stores/mutations.js +++ b/app/assets/javascripts/notes/stores/mutations.js @@ -4,7 +4,8 @@ import * as constants from '../constants'; import { isInMRPage } from '../../lib/utils/common_utils'; export default { - [types.ADD_NEW_NOTE](state, note) { + [types.ADD_NEW_NOTE](state, data) { + const note = data.discussion ? data.discussion.notes[0] : data; const { discussion_id, type } = note; const [exists] = state.discussions.filter(n => n.id === note.discussion_id); const isDiscussion = type === constants.DISCUSSION_NOTE || type === constants.DIFF_NOTE; @@ -100,7 +101,10 @@ export default { discussionsData.forEach(discussion => { if (discussion.diff_file) { - Object.assign(discussion, { fileHash: discussion.diff_file.file_hash }); + Object.assign(discussion, { + fileHash: discussion.diff_file.file_hash, + truncated_diff_lines: discussion.truncated_diff_lines || [], + }); } // To support legacy notes, should be very rare case. @@ -214,12 +218,7 @@ export default { [types.SET_DISCUSSION_DIFF_LINES](state, { discussionId, diffLines }) { const discussion = utils.findNoteObjectById(state.discussions, discussionId); - const index = state.discussions.indexOf(discussion); - - const discussionWithDiffLines = Object.assign({}, discussion, { - truncated_diff_lines: diffLines, - }); - state.discussions.splice(index, 1, discussionWithDiffLines); + discussion.truncated_diff_lines = diffLines; }, }; diff --git a/app/assets/javascripts/notes/stores/utils.js b/app/assets/javascripts/notes/stores/utils.js index 8ccbdb4c130..0e41ff03d67 100644 --- a/app/assets/javascripts/notes/stores/utils.js +++ b/app/assets/javascripts/notes/stores/utils.js @@ -27,7 +27,7 @@ export const getQuickActionText = note => { export const reduceDiscussionsToLineCodes = selectedDiscussions => selectedDiscussions.reduce((acc, note) => { - if (note.diff_discussion && note.line_code && note.resolvable) { + 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); diff --git a/app/assets/javascripts/pages/admin/application_settings/index.js b/app/assets/javascripts/pages/admin/application_settings/index.js index 069f8ce55d0..47bd70537f1 100644 --- a/app/assets/javascripts/pages/admin/application_settings/index.js +++ b/app/assets/javascripts/pages/admin/application_settings/index.js @@ -1,13 +1,8 @@ import initSettingsPanels from '~/settings_panels'; import projectSelect from '~/project_select'; -import UsagePingPayload from './usage_ping_payload'; document.addEventListener('DOMContentLoaded', () => { // Initialize expandable settings panels initSettingsPanels(); projectSelect(); - new UsagePingPayload( - document.querySelector('.js-usage-ping-payload-trigger'), - document.querySelector('.js-usage-ping-payload'), - ).init(); }); diff --git a/app/assets/javascripts/pages/admin/application_settings/metrics_and_profiling/index.js b/app/assets/javascripts/pages/admin/application_settings/metrics_and_profiling/index.js new file mode 100644 index 00000000000..c40503603be --- /dev/null +++ b/app/assets/javascripts/pages/admin/application_settings/metrics_and_profiling/index.js @@ -0,0 +1,8 @@ +import UsagePingPayload from './../usage_ping_payload'; + +document.addEventListener('DOMContentLoaded', () => { + new UsagePingPayload( + document.querySelector('.js-usage-ping-payload-trigger'), + document.querySelector('.js-usage-ping-payload'), + ).init(); +}); diff --git a/app/assets/javascripts/pages/admin/runners/index.js b/app/assets/javascripts/pages/admin/runners/index.js new file mode 100644 index 00000000000..ce8fd18b6a2 --- /dev/null +++ b/app/assets/javascripts/pages/admin/runners/index.js @@ -0,0 +1,10 @@ +import initFilteredSearch from '~/pages/search/init_filtered_search'; +import AdminRunnersFilteredSearchTokenKeys from '~/filtered_search/admin_runners_filtered_search_token_keys'; +import { FILTERED_SEARCH } from '~/pages/constants'; + +document.addEventListener('DOMContentLoaded', () => { + initFilteredSearch({ + page: FILTERED_SEARCH.ADMIN_RUNNERS, + filteredSearchTokenKeys: AdminRunnersFilteredSearchTokenKeys, + }); +}); 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 d6aa4bb95d2..8d5efcdcd96 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 @@ -155,10 +155,7 @@ /> </form> </template> - <template - slot="secondary-button" - slot-scope="props" - > + <template slot="secondary-button"> <button :disabled="!canSubmit" type="button" diff --git a/app/assets/javascripts/pages/constants.js b/app/assets/javascripts/pages/constants.js index 328b6541636..5e119454ce1 100644 --- a/app/assets/javascripts/pages/constants.js +++ b/app/assets/javascripts/pages/constants.js @@ -3,4 +3,5 @@ export const FILTERED_SEARCH = { MERGE_REQUESTS: 'merge_requests', ISSUES: 'issues', + ADMIN_RUNNERS: 'admin/runners', }; diff --git a/app/assets/javascripts/pages/groups/boards/index.js b/app/assets/javascripts/pages/groups/boards/index.js index 5cfe8723204..79c3be771d0 100644 --- a/app/assets/javascripts/pages/groups/boards/index.js +++ b/app/assets/javascripts/pages/groups/boards/index.js @@ -1,5 +1,5 @@ import UsersSelect from '~/users_select'; -import ShortcutsNavigation from '~/shortcuts_navigation'; +import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import initBoards from '~/boards'; document.addEventListener('DOMContentLoaded', () => { diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js index 914f804fdd3..736c6a62610 100644 --- a/app/assets/javascripts/pages/groups/issues/index.js +++ b/app/assets/javascripts/pages/groups/issues/index.js @@ -1,11 +1,13 @@ import projectSelect from '~/project_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; import { FILTERED_SEARCH } from '~/pages/constants'; +import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; document.addEventListener('DOMContentLoaded', () => { initFilteredSearch({ page: FILTERED_SEARCH.ISSUES, isGroupDecendent: true, + filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys, }); projectSelect(); }); diff --git a/app/assets/javascripts/pages/groups/merge_requests/index.js b/app/assets/javascripts/pages/groups/merge_requests/index.js index 1600faa3611..b798a254459 100644 --- a/app/assets/javascripts/pages/groups/merge_requests/index.js +++ b/app/assets/javascripts/pages/groups/merge_requests/index.js @@ -1,11 +1,13 @@ import projectSelect from '~/project_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; +import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; import { FILTERED_SEARCH } from '~/pages/constants'; document.addEventListener('DOMContentLoaded', () => { initFilteredSearch({ page: FILTERED_SEARCH.MERGE_REQUESTS, isGroupDecendent: true, + filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys, }); projectSelect(); }); diff --git a/app/assets/javascripts/pages/groups/show/index.js b/app/assets/javascripts/pages/groups/show/index.js index 5b8c2ae7e81..3a45fd70d02 100644 --- a/app/assets/javascripts/pages/groups/show/index.js +++ b/app/assets/javascripts/pages/groups/show/index.js @@ -6,7 +6,7 @@ import NewGroupChild from '~/groups/new_group_child'; import notificationsDropdown from '~/notifications_dropdown'; import NotificationsForm from '~/notifications_form'; import ProjectsList from '~/projects_list'; -import ShortcutsNavigation from '~/shortcuts_navigation'; +import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import GroupTabs from './group_tabs'; document.addEventListener('DOMContentLoaded', () => { diff --git a/app/assets/javascripts/pages/ide/index.js b/app/assets/javascripts/pages/ide/index.js index efadf6967aa..d192df3561e 100644 --- a/app/assets/javascripts/pages/ide/index.js +++ b/app/assets/javascripts/pages/ide/index.js @@ -1,9 +1,3 @@ -import { initIde, resetServiceWorkersPublicPath } from '~/ide/index'; +import { startIde } from '~/ide/index'; -document.addEventListener('DOMContentLoaded', () => { - const ideElement = document.getElementById('ide'); - if (ideElement) { - resetServiceWorkersPublicPath(); - initIde(ideElement); - } -}); +startIde(); diff --git a/app/assets/javascripts/pages/projects/activity/index.js b/app/assets/javascripts/pages/projects/activity/index.js index 5543ad82428..d39ea3d10bf 100644 --- a/app/assets/javascripts/pages/projects/activity/index.js +++ b/app/assets/javascripts/pages/projects/activity/index.js @@ -1,5 +1,5 @@ import Activities from '~/activities'; -import ShortcutsNavigation from '~/shortcuts_navigation'; +import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; document.addEventListener('DOMContentLoaded', () => { new Activities(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/pages/projects/artifacts/browse/index.js b/app/assets/javascripts/pages/projects/artifacts/browse/index.js index ea7458fe9b8..26dc90a56d7 100644 --- a/app/assets/javascripts/pages/projects/artifacts/browse/index.js +++ b/app/assets/javascripts/pages/projects/artifacts/browse/index.js @@ -1,5 +1,5 @@ import BuildArtifacts from '~/build_artifacts'; -import ShortcutsNavigation from '~/shortcuts_navigation'; +import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; document.addEventListener('DOMContentLoaded', () => { new ShortcutsNavigation(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/pages/projects/artifacts/file/index.js b/app/assets/javascripts/pages/projects/artifacts/file/index.js index 8484e5e9848..249900d6cb7 100644 --- a/app/assets/javascripts/pages/projects/artifacts/file/index.js +++ b/app/assets/javascripts/pages/projects/artifacts/file/index.js @@ -1,5 +1,5 @@ import BlobViewer from '~/blob/viewer/index'; -import ShortcutsNavigation from '~/shortcuts_navigation'; +import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; document.addEventListener('DOMContentLoaded', () => { new ShortcutsNavigation(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/pages/projects/boards/index.js b/app/assets/javascripts/pages/projects/boards/index.js index 5cfe8723204..79c3be771d0 100644 --- a/app/assets/javascripts/pages/projects/boards/index.js +++ b/app/assets/javascripts/pages/projects/boards/index.js @@ -1,5 +1,5 @@ import UsersSelect from '~/users_select'; -import ShortcutsNavigation from '~/shortcuts_navigation'; +import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import initBoards from '~/boards'; document.addEventListener('DOMContentLoaded', () => { diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js index 2e23cce11ce..f477424811d 100644 --- a/app/assets/javascripts/pages/projects/commit/show/index.js +++ b/app/assets/javascripts/pages/projects/commit/show/index.js @@ -3,7 +3,7 @@ import $ from 'jquery'; import Diff from '~/diff'; import ZenMode from '~/zen_mode'; -import ShortcutsNavigation from '~/shortcuts_navigation'; +import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown'; import initNotes from '~/init_notes'; import initChangesDropdown from '~/init_changes_dropdown'; diff --git a/app/assets/javascripts/pages/projects/commits/show/index.js b/app/assets/javascripts/pages/projects/commits/show/index.js index 3682020579b..ad671ce9351 100644 --- a/app/assets/javascripts/pages/projects/commits/show/index.js +++ b/app/assets/javascripts/pages/projects/commits/show/index.js @@ -1,6 +1,6 @@ import CommitsList from '~/commits'; import GpgBadges from '~/gpg_badges'; -import ShortcutsNavigation from '~/shortcuts_navigation'; +import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; document.addEventListener('DOMContentLoaded', () => { new CommitsList(document.querySelector('.js-project-commits-show').dataset.commitsLimit); // eslint-disable-line no-new diff --git a/app/assets/javascripts/pages/projects/find_file/show/index.js b/app/assets/javascripts/pages/projects/find_file/show/index.js index 24630c2aa05..388d7d7bdda 100644 --- a/app/assets/javascripts/pages/projects/find_file/show/index.js +++ b/app/assets/javascripts/pages/projects/find_file/show/index.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import ProjectFindFile from '~/project_find_file'; -import ShortcutsFindFile from '~/shortcuts_find_file'; +import ShortcutsFindFile from '~/behaviors/shortcuts/shortcuts_find_file'; document.addEventListener('DOMContentLoaded', () => { const findElement = document.querySelector('.js-file-finder'); diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js index 9c074b74c3b..5659e13981a 100644 --- a/app/assets/javascripts/pages/projects/index.js +++ b/app/assets/javascripts/pages/projects/index.js @@ -1,7 +1,7 @@ import initDismissableCallout from '~/dismissable_callout'; import initGkeDropdowns from '~/projects/gke_cluster_dropdowns'; import Project from './project'; -import ShortcutsNavigation from '../../shortcuts_navigation'; +import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation'; document.addEventListener('DOMContentLoaded', () => { const { page } = document.body.dataset; diff --git a/app/assets/javascripts/pages/projects/init_blob.js b/app/assets/javascripts/pages/projects/init_blob.js index 56ab3fcdfcb..bc08ccf3584 100644 --- a/app/assets/javascripts/pages/projects/init_blob.js +++ b/app/assets/javascripts/pages/projects/init_blob.js @@ -1,7 +1,7 @@ import LineHighlighter from '~/line_highlighter'; import BlobLinePermalinkUpdater from '~/blob/blob_line_permalink_updater'; -import ShortcutsNavigation from '~/shortcuts_navigation'; -import ShortcutsBlob from '~/shortcuts_blob'; +import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; +import ShortcutsBlob from '~/behaviors/shortcuts/shortcuts_blob'; import BlobForkSuggestion from '~/blob/blob_fork_suggestion'; import initBlobBundle from '~/blob_edit/blob_bundle'; diff --git a/app/assets/javascripts/pages/projects/issues/form.js b/app/assets/javascripts/pages/projects/issues/form.js index b2b8e5d2300..197bfa8a394 100644 --- a/app/assets/javascripts/pages/projects/issues/form.js +++ b/app/assets/javascripts/pages/projects/issues/form.js @@ -5,7 +5,7 @@ import GLForm from '~/gl_form'; import IssuableForm from '~/issuable_form'; import LabelsSelect from '~/labels_select'; import MilestoneSelect from '~/milestone_select'; -import ShortcutsNavigation from '~/shortcuts_navigation'; +import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import IssuableTemplateSelectors from '~/templates/issuable_template_selectors'; export default () => { diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js index 70fdb0ef40d..a56c0bb6be8 100644 --- a/app/assets/javascripts/pages/projects/issues/index/index.js +++ b/app/assets/javascripts/pages/projects/issues/index/index.js @@ -1,15 +1,17 @@ /* eslint-disable no-new */ import IssuableIndex from '~/issuable_index'; -import ShortcutsNavigation from '~/shortcuts_navigation'; +import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import UsersSelect from '~/users_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; +import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; import { FILTERED_SEARCH } from '~/pages/constants'; import { ISSUABLE_INDEX } from '~/pages/projects/constants'; document.addEventListener('DOMContentLoaded', () => { initFilteredSearch({ page: FILTERED_SEARCH.ISSUES, + filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys, }); new IssuableIndex(ISSUABLE_INDEX.ISSUE); diff --git a/app/assets/javascripts/pages/projects/issues/show.js b/app/assets/javascripts/pages/projects/issues/show.js index 500fbd27340..74b3a515e84 100644 --- a/app/assets/javascripts/pages/projects/issues/show.js +++ b/app/assets/javascripts/pages/projects/issues/show.js @@ -1,6 +1,6 @@ import initIssuableSidebar from '~/init_issuable_sidebar'; import Issue from '~/issue'; -import ShortcutsIssuable from '~/shortcuts_issuable'; +import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable'; import ZenMode from '~/zen_mode'; import '~/notes/index'; import '~/issue_show/index'; diff --git a/app/assets/javascripts/pages/projects/merge_requests/index/index.js b/app/assets/javascripts/pages/projects/merge_requests/index/index.js index a7aa616319f..3647048a872 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/index/index.js +++ b/app/assets/javascripts/pages/projects/merge_requests/index/index.js @@ -1,13 +1,15 @@ import IssuableIndex from '~/issuable_index'; -import ShortcutsNavigation from '~/shortcuts_navigation'; +import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import UsersSelect from '~/users_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; +import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; import { FILTERED_SEARCH } from '~/pages/constants'; import { ISSUABLE_INDEX } from '~/pages/projects/constants'; document.addEventListener('DOMContentLoaded', () => { initFilteredSearch({ page: FILTERED_SEARCH.MERGE_REQUESTS, + filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys, }); new IssuableIndex(ISSUABLE_INDEX.MERGE_REQUEST); // eslint-disable-line no-new new ShortcutsNavigation(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js index 3a3c21f2202..e3971618da5 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js +++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import Diff from '~/diff'; -import ShortcutsNavigation from '~/shortcuts_navigation'; +import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import GLForm from '~/gl_form'; import IssuableForm from '~/issuable_form'; import LabelsSelect from '~/labels_select'; diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js index 26ead75cec4..7bfb83a2204 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js +++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js @@ -1,6 +1,6 @@ import ZenMode from '~/zen_mode'; import initIssuableSidebar from '~/init_issuable_sidebar'; -import ShortcutsIssuable from '~/shortcuts_issuable'; +import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable'; import { handleLocationHash } from '~/lib/utils/common_utils'; import howToMerge from '~/how_to_merge'; import initPipelines from '~/commit/pipelines/pipelines_bundle'; diff --git a/app/assets/javascripts/pages/projects/network/show/index.js b/app/assets/javascripts/pages/projects/network/show/index.js index a0b14fed10f..9f05f63b742 100644 --- a/app/assets/javascripts/pages/projects/network/show/index.js +++ b/app/assets/javascripts/pages/projects/network/show/index.js @@ -1,5 +1,5 @@ import $ from 'jquery'; -import ShortcutsNetwork from '../../../../shortcuts_network'; +import ShortcutsNetwork from '~/behaviors/shortcuts/shortcuts_network'; import Network from '../network'; document.addEventListener('DOMContentLoaded', () => { 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 0d05668b285..ef53d67e7cb 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 @@ -147,8 +147,8 @@ <div class="cron-interval-input-wrapper"> <input id="schedule_cron" - :placeholder="__('Define a custom pattern with cron syntax')" v-model="cronInterval" + :placeholder="__('Define a custom pattern with cron syntax')" :name="inputNameAttribute" :disabled="!isEditable" class="form-control inline cron-interval-input" diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue index ae88b765abf..875f6928bed 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue @@ -240,8 +240,8 @@ help-text="Lightweight issue tracking system for this project" > <project-feature-setting - :options="featureAccessLevelOptions" v-model="issuesAccessLevel" + :options="featureAccessLevelOptions" name="project[project_feature_attributes][issues_access_level]" /> </project-setting-row> @@ -250,8 +250,8 @@ help-text="View and edit files in this project" > <project-feature-setting - :options="featureAccessLevelOptions" v-model="repositoryAccessLevel" + :options="featureAccessLevelOptions" name="project[project_feature_attributes][repository_access_level]" /> </project-setting-row> @@ -261,8 +261,8 @@ help-text="Submit changes to be merged upstream" > <project-feature-setting - :options="repoFeatureAccessLevelOptions" v-model="mergeRequestsAccessLevel" + :options="repoFeatureAccessLevelOptions" :disabled-input="!repositoryEnabled" name="project[project_feature_attributes][merge_requests_access_level]" /> @@ -272,8 +272,8 @@ help-text="Build, test, and deploy your changes" > <project-feature-setting - :options="repoFeatureAccessLevelOptions" v-model="buildsAccessLevel" + :options="repoFeatureAccessLevelOptions" :disabled-input="!repositoryEnabled" name="project[project_feature_attributes][builds_access_level]" /> @@ -308,8 +308,8 @@ help-text="Pages for project documentation" > <project-feature-setting - :options="featureAccessLevelOptions" v-model="wikiAccessLevel" + :options="featureAccessLevelOptions" name="project[project_feature_attributes][wiki_access_level]" /> </project-setting-row> @@ -318,8 +318,8 @@ help-text="Share code pastes with others out of Git repository" > <project-feature-setting - :options="featureAccessLevelOptions" v-model="snippetsAccessLevel" + :options="featureAccessLevelOptions" name="project[project_feature_attributes][snippets_access_level]" /> </project-setting-row> diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js index 0507f67843f..7302c1ab202 100644 --- a/app/assets/javascripts/pages/projects/show/index.js +++ b/app/assets/javascripts/pages/projects/show/index.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import initBlob from '~/blob_edit/blob_bundle'; -import ShortcutsNavigation from '~/shortcuts_navigation'; +import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import NotificationsForm from '~/notifications_form'; import UserCallout from '~/user_callout'; import TreeView from '~/tree'; diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js index 33d69d891d8..400aed35e32 100644 --- a/app/assets/javascripts/pages/projects/tree/show/index.js +++ b/app/assets/javascripts/pages/projects/tree/show/index.js @@ -4,7 +4,7 @@ import initBlob from '~/blob_edit/blob_bundle'; import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue'; import GpgBadges from '~/gpg_badges'; import TreeView from '../../../../tree'; -import ShortcutsNavigation from '../../../../shortcuts_navigation'; +import ShortcutsNavigation from '../../../../behaviors/shortcuts/shortcuts_navigation'; import BlobViewer from '../../../../blob/viewer'; import NewCommitForm from '../../../../new_commit_form'; import { ajaxGet } from '../../../../lib/utils/common_utils'; diff --git a/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue b/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue index 42c37bc8cd8..75cb6374ad5 100644 --- a/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue +++ b/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue @@ -1,12 +1,8 @@ <script> import _ from 'underscore'; -import GlModal from '~/vue_shared/components/gl_modal.vue'; import { s__, sprintf } from '~/locale'; export default { - components: { - GlModal, - }, props: { deleteWikiUrl: { type: String, diff --git a/app/assets/javascripts/pages/projects/wikis/index.js b/app/assets/javascripts/pages/projects/wikis/index.js index b0a323a71cd..c2629090f01 100644 --- a/app/assets/javascripts/pages/projects/wikis/index.js +++ b/app/assets/javascripts/pages/projects/wikis/index.js @@ -2,8 +2,8 @@ import $ from 'jquery'; import Vue from 'vue'; import Translate from '~/vue_shared/translate'; import csrf from '~/lib/utils/csrf'; +import ShortcutsWiki from '~/behaviors/shortcuts/shortcuts_wiki'; import Wikis from './wikis'; -import ShortcutsWiki from '../../../shortcuts_wiki'; import ZenMode from '../../../zen_mode'; import GLForm from '../../../gl_form'; import deleteWikiModal from './components/delete_wiki_modal.vue'; diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue index 0fdb0a080cf..7836d4f3b09 100644 --- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue +++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue @@ -130,8 +130,8 @@ export default { </div> <simple-metric v-for="metric in $options.simpleMetrics" - :current-request="currentRequest" :key="metric" + :current-request="currentRequest" :metric="metric" /> <div diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue index 1952dd453f4..9b4ba0c1a9a 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue @@ -1,12 +1,10 @@ <script> import _ from 'underscore'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; import StageColumnComponent from './stage_column_component.vue'; export default { components: { StageColumnComponent, - LoadingIcon, }, props: { isLoading: { @@ -59,9 +57,9 @@ export default { <div class="build-content middle-block js-pipeline-graph"> <div class="pipeline-visualization pipeline-graph pipeline-tab-content"> <div class="text-center"> - <loading-icon + <gl-loading-icon v-if="isLoading" - size="3" + :size="3" /> </div> @@ -70,9 +68,9 @@ export default { class="stage-column-list"> <stage-column-component v-for="(stage, index) in graph" + :key="stage.name" :title="capitalizeStageName(stage.name)" :jobs="stage.groups" - :key="stage.name" :stage-connector-class="stageConnectorClass(index, stage)" :is-first-column="isFirstColumn(index)" @refreshPipelineGraph="refreshPipelineGraph" diff --git a/app/assets/javascripts/pipelines/components/graph/job_component.vue b/app/assets/javascripts/pipelines/components/graph/job_component.vue index 9ac16b7e541..a1504592bbc 100644 --- a/app/assets/javascripts/pipelines/components/graph/job_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/job_component.vue @@ -98,8 +98,8 @@ export default { <template> <div class="ci-job-component"> <a - v-tooltip v-if="status.has_details" + v-tooltip :href="status.details_path" :title="tooltipText" :class="cssClassJobName" @@ -115,8 +115,8 @@ export default { </a> <div - v-tooltip v-else + v-tooltip :title="tooltipText" :class="cssClassJobName" class="js-job-component-tooltip non-details-job-component" 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 e7b2de52f76..567ea119343 100644 --- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue @@ -62,9 +62,9 @@ export default { <ul> <li v-for="(job, index) in jobs" + :id="jobId(job)" :key="job.id" :class="buildConnnectorClass(index)" - :id="jobId(job)" class="build" > diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue index 001eaeaa065..1f9187c3d65 100644 --- a/app/assets/javascripts/pipelines/components/header_component.vue +++ b/app/assets/javascripts/pipelines/components/header_component.vue @@ -1,13 +1,11 @@ <script> import ciHeader from '../../vue_shared/components/header_ci_component.vue'; import eventHub from '../event_hub'; -import loadingIcon from '../../vue_shared/components/loading_icon.vue'; export default { name: 'PipelineHeaderSection', components: { ciHeader, - loadingIcon, }, props: { pipeline: { @@ -89,9 +87,9 @@ export default { item-name="Pipeline" @actionClicked="postAction" /> - <loading-icon + <gl-loading-icon v-if="isLoading" - size="2" + :size="2" class="prepend-top-default append-bottom-default" /> </div> diff --git a/app/assets/javascripts/pipelines/components/nav_controls.vue b/app/assets/javascripts/pipelines/components/nav_controls.vue index 9501afb7493..efb80d3a3c0 100644 --- a/app/assets/javascripts/pipelines/components/nav_controls.vue +++ b/app/assets/javascripts/pipelines/components/nav_controls.vue @@ -43,7 +43,7 @@ export default { <a v-if="newPipelinePath" :href="newPipelinePath" - class="btn btn-create js-run-pipeline" + class="btn btn-success js-run-pipeline" > {{ s__('Pipelines|Run Pipeline') }} </a> diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue index 75db1e9ae7c..40df07650c9 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_url.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue @@ -67,29 +67,29 @@ export default { </span> <div class="label-container"> <span - v-tooltip v-if="pipeline.flags.latest" + v-tooltip class="js-pipeline-url-latest badge badge-success" title="Latest pipeline for this branch"> latest </span> <span - v-tooltip v-if="pipeline.flags.yaml_errors" + v-tooltip :title="pipeline.yaml_errors" class="js-pipeline-url-yaml badge badge-danger"> yaml invalid </span> <span - v-tooltip v-if="pipeline.flags.failure_reason" + v-tooltip :title="pipeline.failure_reason" class="js-pipeline-url-failure badge badge-danger"> error </span> <a - v-popover="popoverOptions" v-if="pipeline.flags.auto_devops" + v-popover="popoverOptions" tabindex="0" class="js-pipeline-url-autodevops badge badge-info autodevops-badge" role="button"> diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue index c9d2dc3a3c5..ea526cf1309 100644 --- a/app/assets/javascripts/pipelines/components/pipelines.vue +++ b/app/assets/javascripts/pipelines/components/pipelines.vue @@ -319,10 +319,10 @@ export default { <div class="content-list pipelines"> - <loading-icon + <gl-loading-icon v-if="stateToRender === $options.stateMap.loading" :label="s__('Pipelines|Loading Pipelines')" - size="3" + :size="3" class="prepend-top-20" /> diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue index 1c8d7303c52..017dd560621 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue @@ -1,6 +1,5 @@ <script> import eventHub from '../event_hub'; -import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import icon from '../../vue_shared/components/icon.vue'; import tooltip from '../../vue_shared/directives/tooltip'; @@ -9,7 +8,6 @@ export default { tooltip, }, components: { - loadingIcon, icon, }, props: { @@ -60,7 +58,7 @@ export default { class="fa fa-caret-down" aria-hidden="true"> </i> - <loading-icon v-if="isLoading" /> + <gl-loading-icon v-if="isLoading" /> </button> <ul class="dropdown-menu dropdown-menu-right"> diff --git a/app/assets/javascripts/pipelines/components/stage.vue b/app/assets/javascripts/pipelines/components/stage.vue index c7df69c69ed..47c15b1a9c4 100644 --- a/app/assets/javascripts/pipelines/components/stage.vue +++ b/app/assets/javascripts/pipelines/components/stage.vue @@ -18,14 +18,12 @@ 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 LoadingIcon from '../../vue_shared/components/loading_icon.vue'; import JobComponent from './graph/job_component.vue'; import tooltip from '../../vue_shared/directives/tooltip'; import { PIPELINES_TABLE } from '../constants'; export default { components: { - LoadingIcon, Icon, JobComponent, }, @@ -157,9 +155,9 @@ export default { <template> <div class="dropdown"> <button - v-tooltip id="stageDropdown" ref="dropdown" + v-tooltip :class="triggerButtonClass" :title="stage.title" class="mini-pipeline-graph-dropdown-toggle js-builds-dropdown-button" @@ -191,7 +189,7 @@ export default { class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container" aria-labelledby="stageDropdown" > - <loading-icon v-if="isLoading"/> + <gl-loading-icon v-if="isLoading"/> <ul v-else class="js-builds-dropdown-list scrollable-menu" diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js index 2cb558b0dec..8929b397f6c 100644 --- a/app/assets/javascripts/pipelines/mixins/pipelines.js +++ b/app/assets/javascripts/pipelines/mixins/pipelines.js @@ -4,7 +4,6 @@ import Flash from '../../flash'; import Poll from '../../lib/utils/poll'; import EmptyState from '../components/empty_state.vue'; import SvgBlankState from '../components/blank_state.vue'; -import LoadingIcon from '../../vue_shared/components/loading_icon.vue'; import PipelinesTableComponent from '../components/pipelines_table.vue'; import eventHub from '../event_hub'; import { CANCEL_REQUEST } from '../constants'; @@ -14,7 +13,6 @@ export default { PipelinesTableComponent, SvgBlankState, EmptyState, - LoadingIcon, }, data() { return { diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_dropdown_mixin.js b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_dropdown_mixin.js index c15d8ba49e1..d5266544307 100644 --- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_dropdown_mixin.js +++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_dropdown_mixin.js @@ -1,5 +1,4 @@ import _ from 'underscore'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; import DropdownSearchInput from '~/vue_shared/components/dropdown/dropdown_search_input.vue'; import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue'; import DropdownButton from '~/vue_shared/components/dropdown/dropdown_button.vue'; @@ -9,7 +8,6 @@ import store from '../store'; export default { store, components: { - LoadingIcon, DropdownButton, DropdownSearchInput, DropdownHiddenInput, diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue index d4497924ad8..2c02f436b69 100644 --- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue +++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue @@ -126,7 +126,7 @@ export default { </ul> </div> <div class="dropdown-loading"> - <loading-icon /> + <gl-loading-icon /> </div> </div> </div> diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue index 08d0a122579..fc17e2fab49 100644 --- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue +++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue @@ -187,7 +187,7 @@ export default { </ul> </div> <div class="dropdown-loading"> - <loading-icon /> + <gl-loading-icon /> </div> </div> </div> diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue index b5476684c6a..ca7c79f75f0 100644 --- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue +++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue @@ -100,7 +100,7 @@ export default { </ul> </div> <div class="dropdown-loading"> - <loading-icon /> + <gl-loading-icon /> </div> </div> </div> diff --git a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue index 1c1e17563a1..120b4fc2f2b 100644 --- a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue +++ b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue @@ -1,7 +1,6 @@ <script> import Visibility from 'visibilityjs'; import ciIcon from '~/vue_shared/components/ci_icon.vue'; -import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import Poll from '~/lib/utils/poll'; import Flash from '~/flash'; import { s__, sprintf } from '~/locale'; @@ -14,7 +13,6 @@ export default { }, components: { ciIcon, - loadingIcon, }, props: { endpoint: { @@ -100,10 +98,10 @@ export default { </script> <template> <div class="ci-status-link"> - <loading-icon + <gl-loading-icon v-if="isLoading" + :size="3" label="Loading pipeline status" - size="3" /> <a v-else diff --git a/app/assets/javascripts/registry/components/app.vue b/app/assets/javascripts/registry/components/app.vue index 31f88675912..7e2287ac4db 100644 --- a/app/assets/javascripts/registry/components/app.vue +++ b/app/assets/javascripts/registry/components/app.vue @@ -1,7 +1,6 @@ <script> import { mapGetters, mapActions } from 'vuex'; import Flash from '../../flash'; - import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import store from '../stores'; import collapsibleContainer from './collapsible_container.vue'; import { errorMessages, errorMessagesTypes } from '../constants'; @@ -10,7 +9,6 @@ name: 'RegistryListApp', components: { collapsibleContainer, - loadingIcon, }, props: { endpoint: { @@ -42,9 +40,9 @@ </script> <template> <div> - <loading-icon + <gl-loading-icon v-if="isLoading" - size="3" + :size="3" /> <collapsible-container diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue index cea409aa130..d9bf41924d1 100644 --- a/app/assets/javascripts/registry/components/collapsible_container.vue +++ b/app/assets/javascripts/registry/components/collapsible_container.vue @@ -2,7 +2,6 @@ import { mapActions } from 'vuex'; import Flash from '../../flash'; import clipboardButton from '../../vue_shared/components/clipboard_button.vue'; - import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import tooltip from '../../vue_shared/directives/tooltip'; import tableRegistry from './table_registry.vue'; import { errorMessages, errorMessagesTypes } from '../constants'; @@ -12,7 +11,6 @@ name: 'CollapsibeContainerRegisty', components: { clipboardButton, - loadingIcon, tableRegistry, }, directives: { @@ -90,8 +88,8 @@ <div class="controls d-none d-sm-block float-right"> <button - v-tooltip v-if="repo.canDelete" + v-tooltip :title="s__('ContainerRegistry|Remove repository')" :aria-label="s__('ContainerRegistry|Remove repository')" type="button" @@ -107,10 +105,10 @@ </div> </div> - <loading-icon + <gl-loading-icon v-if="repo.isLoading" + :size="2" class="append-bottom-20" - size="2" /> <div diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue index 9f4973c3490..fafb35bd69a 100644 --- a/app/assets/javascripts/registry/components/table_registry.vue +++ b/app/assets/javascripts/registry/components/table_registry.vue @@ -118,8 +118,8 @@ <td class="content"> <button - v-tooltip v-if="item.canDelete" + v-tooltip :title="s__('ContainerRegistry|Remove tag')" :aria-label="s__('ContainerRegistry|Remove tag')" type="button" 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 7b37f4e9a97..fb8c6402d02 100644 --- a/app/assets/javascripts/reports/components/grouped_test_reports_app.vue +++ b/app/assets/javascripts/reports/components/grouped_test_reports_app.vue @@ -92,16 +92,16 @@ v-for="(report, i) in reports" > <summary-row + :key="`summary-row-${i}`" :summary="reportText(report)" :status-icon="getReportIcon(report)" - :key="`summary-row-${i}`" /> <issues-list v-if="shouldRenderIssuesList(report)" + :key="`issues-list-${i}`" :unresolved-issues="report.existing_failures" :new-issues="report.new_failures" :resolved-issues="report.resolved_failures" - :key="`issues-list-${i}`" :component="$options.componentNames.TestIssueBody" class="report-block-group-list" /> diff --git a/app/assets/javascripts/reports/components/report_issues.vue b/app/assets/javascripts/reports/components/report_issues.vue index c553a374f66..a2a03945ae3 100644 --- a/app/assets/javascripts/reports/components/report_issues.vue +++ b/app/assets/javascripts/reports/components/report_issues.vue @@ -37,8 +37,8 @@ export default { <ul class="report-block-list"> <li v-for="(issue, index) in issues" - :class="{ 'is-dismissed': issue.isDismissed }" :key="index" + :class="{ 'is-dismissed': issue.isDismissed }" class="report-block-list-issue" > <issue-status-icon @@ -47,8 +47,8 @@ export default { /> <component - v-if="component" :is="component" + v-if="component" :issue="issue" :status="issue.status || status" :is-new="isNew" diff --git a/app/assets/javascripts/reports/components/summary_row.vue b/app/assets/javascripts/reports/components/summary_row.vue index 4456d84c968..51188981bed 100644 --- a/app/assets/javascripts/reports/components/summary_row.vue +++ b/app/assets/javascripts/reports/components/summary_row.vue @@ -1,6 +1,5 @@ <script> import CiIcon from '~/vue_shared/components/ci_icon.vue'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; import Popover from '~/vue_shared/components/help_popover.vue'; /** @@ -15,7 +14,6 @@ export default { name: 'ReportSummaryRow', components: { CiIcon, - LoadingIcon, Popover, }, props: { @@ -46,7 +44,7 @@ export default { <template> <div class="report-block-list-issue report-block-list-issue-parent"> <div class="report-block-list-icon append-right-10 prepend-left-5"> - <loading-icon + <gl-loading-icon v-if="statusIcon === 'loading'" css-class="report-block-list-loading-icon" /> diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index aec09b8bc0a..50dd3c12382 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -68,7 +68,7 @@ function setSearchOptions() { } } -export default class SearchAutocomplete { +export class SearchAutocomplete { constructor({ wrap, optsEl, autocompletePath, projectId, projectRef } = {}) { setSearchOptions(); this.bindEventContext(); @@ -499,3 +499,7 @@ export default class SearchAutocomplete { this.dropdownMenu.toggleClass('fade-out', !this.isScrolledUp()); } } + +export default function initSearchAutocomplete(opts) { + return new SearchAutocomplete(opts); +} diff --git a/app/assets/javascripts/sidebar/components/participants/participants.vue b/app/assets/javascripts/sidebar/components/participants/participants.vue index 56d57f6aac8..286a16f7bbf 100644 --- a/app/assets/javascripts/sidebar/components/participants/participants.vue +++ b/app/assets/javascripts/sidebar/components/participants/participants.vue @@ -1,7 +1,6 @@ <script> import { __, n__, sprintf } from '~/locale'; import tooltip from '~/vue_shared/directives/tooltip'; - import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import userAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue'; export default { @@ -9,7 +8,6 @@ tooltip, }, components: { - loadingIcon, userAvatarImage, }, props: { @@ -93,7 +91,7 @@ aria-hidden="true" > </i> - <loading-icon + <gl-loading-icon v-if="loading" class="js-participants-collapsed-loading-icon" /> @@ -105,7 +103,7 @@ </span> </div> <div class="title hide-collapsed"> - <loading-icon + <gl-loading-icon v-if="loading" :inline="true" class="js-participants-expanded-loading-icon" diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue index ca3b9338c29..2ee3e1f322e 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue @@ -19,19 +19,23 @@ export default { TimeTrackingHelpState, }, props: { + // eslint-disable-next-line vue/prop-name-casing time_estimate: { type: Number, required: true, }, + // eslint-disable-next-line vue/prop-name-casing time_spent: { type: Number, required: true, }, + // eslint-disable-next-line vue/prop-name-casing human_time_estimate: { type: String, required: false, default: '', }, + // eslint-disable-next-line vue/prop-name-casing human_time_spent: { type: String, required: false, diff --git a/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue b/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue index ffaed9c7193..a6b3a674952 100644 --- a/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue +++ b/app/assets/javascripts/sidebar/components/todo_toggle/todo.vue @@ -3,7 +3,6 @@ import { __ } from '~/locale'; import tooltip from '~/vue_shared/directives/tooltip'; import Icon from '~/vue_shared/components/icon.vue'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; const MARK_TEXT = __('Mark todo as done'); const TODO_TEXT = __('Add todo'); @@ -14,7 +13,6 @@ export default { }, components: { Icon, - LoadingIcon, }, props: { issuableId: { @@ -90,7 +88,7 @@ export default { > {{ buttonLabel }} </span> - <loading-icon + <gl-loading-icon v-show="isActionActive" :inline="true" /> 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 d530ab2767b..70518ad73e8 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue @@ -106,8 +106,8 @@ export default { </tooltip-on-truncate> </template> <span - v-tooltip v-if="hasDeploymentTime" + v-tooltip :title="deployment.deployed_at_formatted" class="js-deploy-time" > diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue index 9aff95dcfec..035ae791a1d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue @@ -1,11 +1,9 @@ <script> import ciIcon from '../../vue_shared/components/ci_icon.vue'; - import loadingIcon from '../../vue_shared/components/loading_icon.vue'; export default { components: { ciIcon, - loadingIcon, }, props: { status: { @@ -37,7 +35,7 @@ v-if="isLoading" class="mr-widget-icon" > - <loading-icon /> + <gl-loading-icon /> </div> <ci-icon diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue index 2133124347c..01294d5b40c 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_auto_merge_failed.vue @@ -1,5 +1,4 @@ <script> - import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import eventHub from '../../event_hub'; import statusIcon from '../mr_widget_status_icon.vue'; @@ -7,7 +6,6 @@ name: 'MRWidgetAutoMergeFailed', components: { statusIcon, - loadingIcon, }, props: { mr: { @@ -44,7 +42,7 @@ class="btn btn-sm btn-default" @click="refreshWidget" > - <loading-icon + <gl-loading-icon v-if="isRefreshing" :inline="true" /> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue index 1a444c04a1d..8184ef33022 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue @@ -1,7 +1,6 @@ <script> import Flash from '~/flash'; import tooltip from '~/vue_shared/directives/tooltip'; - import loadingIcon from '~/vue_shared/components/loading_icon.vue'; import { s__, __ } from '~/locale'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import MrWidgetAuthorTime from '../../components/mr_widget_author_time.vue'; @@ -15,7 +14,6 @@ }, components: { MrWidgetAuthorTime, - loadingIcon, statusIcon, ClipboardButton, }, @@ -116,8 +114,8 @@ :date-readable="mr.metrics.readableMergedAt" /> <a - v-tooltip v-if="mr.canRevertInCurrentMR" + v-tooltip :title="revertTitle" class="btn btn-close btn-sm" href="#modal-revert-commit" @@ -127,8 +125,8 @@ {{ revertLabel }} </a> <a - v-tooltip v-else-if="mr.revertInForkPath" + v-tooltip :href="mr.revertInForkPath" :title="revertTitle" class="btn btn-close btn-sm" @@ -137,8 +135,8 @@ {{ revertLabel }} </a> <a - v-tooltip v-if="mr.canCherryPickInCurrentMR" + v-tooltip :title="cherryPickTitle" class="btn btn-default btn-sm" href="#modal-cherry-pick-commit" @@ -148,8 +146,8 @@ {{ cherryPickLabel }} </a> <a - v-tooltip v-else-if="mr.cherryPickInForkPath" + v-tooltip :href="mr.cherryPickInForkPath" :title="cherryPickTitle" class="btn btn-default btn-sm" @@ -195,7 +193,7 @@ </button> </p> <p v-if="shouldShowSourceBranchRemoving"> - <loading-icon :inline="true" /> + <gl-loading-icon :inline="true" /> <span> {{ s__("mrWidget|The source branch is being removed") }} </span> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue index 2d8c3d6be87..f31c7a3edb8 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue @@ -2,14 +2,12 @@ import simplePoll from '../../../lib/utils/simple_poll'; import eventHub from '../../event_hub'; import statusIcon from '../mr_widget_status_icon.vue'; - import loadingIcon from '../../../vue_shared/components/loading_icon.vue'; import Flash from '../../../flash'; export default { name: 'MRWidgetRebase', components: { statusIcon, - loadingIcon, }, props: { mr: { @@ -115,7 +113,7 @@ js-toggle-container accept-action media space-children" class="btn btn-sm btn-reopen btn-success qa-mr-rebase-button" @click="rebase" > - <loading-icon v-if="isMakingRequest" /> + <gl-loading-icon v-if="isMakingRequest" /> Rebase </button> <span diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue index 25c1044fe2b..25ad329e196 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue @@ -37,8 +37,8 @@ export default { <div class="accept-control inline"> <label class="merge-param-checkbox"> <input - :disabled="isMergeButtonDisabled" v-model="squashBeforeMerge" + :disabled="isMergeButtonDisabled" type="checkbox" name="squash" class="qa-squash-checkbox" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue index 086dbabe77e..e73b7e410d5 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue @@ -37,7 +37,7 @@ export default { <a v-if="mr.newBlobPath" :href="mr.newBlobPath" - class="btn btn-inverted btn-save"> + class="btn btn-inverted btn-success"> Create file </a> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue index a5ca7b719a1..23c3284cd21 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue @@ -255,7 +255,7 @@ export default { data-toggle="dropdown" aria-label="Select merge moment"> <i - class="fa fa-chevron-down" + class="fa fa-chevron-down qa-merge-moment-dropdown" aria-hidden="true" ></i> </button> @@ -265,7 +265,7 @@ export default { role="menu"> <li> <a - class="merge_when_pipeline_succeeds" + class="merge_when_pipeline_succeeds qa-merge-when-pipeline-succeeds-option" href="#" @click.prevent="handleMergeButtonClick(true)"> <span class="media"> @@ -279,7 +279,7 @@ export default { </li> <li> <a - class="accept-merge-request" + class="accept-merge-request qa-merge-immediately-option" href="#" @click.prevent="handleMergeButtonClick(false, true)"> <span class="media"> diff --git a/app/assets/javascripts/vue_merge_request_widget/index.js b/app/assets/javascripts/vue_merge_request_widget/index.js index 69a9132a2da..cc6e620f365 100644 --- a/app/assets/javascripts/vue_merge_request_widget/index.js +++ b/app/assets/javascripts/vue_merge_request_widget/index.js @@ -1,7 +1,4 @@ -import { - Vue, - mrWidgetOptions, -} from './dependencies'; +import { Vue, mrWidgetOptions } from './dependencies'; import Translate from '../vue_shared/translate'; Vue.use(Translate); 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 dc6be025f11..0e445a29de4 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 @@ -107,10 +107,16 @@ export default { created() { this.initPolling(); this.bindEventHubListeners(); + eventHub.$on('mr.discussion.updated', this.checkStatus); }, mounted() { this.handleMounted(); }, + beforeDestroy() { + eventHub.$off('mr.discussion.updated', this.checkStatus); + this.pollingInterval.destroy(); + this.deploymentsInterval.destroy(); + }, methods: { createService(store) { const endpoints = { diff --git a/app/assets/javascripts/vue_shared/components/bar_chart.vue b/app/assets/javascripts/vue_shared/components/bar_chart.vue index 3ced4eb691a..33af7a7f1df 100644 --- a/app/assets/javascripts/vue_shared/components/bar_chart.vue +++ b/app/assets/javascripts/vue_shared/components/bar_chart.vue @@ -291,8 +291,8 @@ export default { <template v-for="(data, index) in graphData"> <rect - v-tooltip :key="index" + v-tooltip :width="xScale.bandwidth()" :x="xScale(data.name)" :y="yScale(data.value)" diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue index d3cbe3c7e74..cfc5343217c 100644 --- a/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue @@ -46,7 +46,7 @@ export default { } }, basePath() { - // We might get the project path from rails with the relative url already setup + // We might get the project path from rails with the relative url already set up return this.projectPath.indexOf('/') === 0 ? '' : `${gon.relative_url_root}/`; }, fullOldPath() { diff --git a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue index af5ebcdc40a..31087017968 100644 --- a/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue +++ b/app/assets/javascripts/vue_shared/components/dropdown/dropdown_button.vue @@ -1,11 +1,7 @@ <script> import { __ } from '~/locale'; -import LoadingIcon from '~/vue_shared/components/loading_icon.vue'; export default { - components: { - LoadingIcon, - }, props: { isDisabled: { type: Boolean, @@ -34,7 +30,7 @@ export default { data-toggle="dropdown" aria-expanded="false" > - <loading-icon + <gl-loading-icon v-show="isLoading" :inline="true" /> diff --git a/app/assets/javascripts/vue_shared/components/file_icon.vue b/app/assets/javascripts/vue_shared/components/file_icon.vue index 878c805ada5..408f7d7965f 100644 --- a/app/assets/javascripts/vue_shared/components/file_icon.vue +++ b/app/assets/javascripts/vue_shared/components/file_icon.vue @@ -1,6 +1,5 @@ <script> import getIconForFile from './file_icon/file_icon_map'; -import loadingIcon from '../../vue_shared/components/loading_icon.vue'; import icon from '../../vue_shared/components/icon.vue'; /* This is a re-usable vue component for rendering a svg sprite @@ -17,7 +16,6 @@ import icon from '../../vue_shared/components/icon.vue'; */ export default { components: { - loadingIcon, icon, }, props: { @@ -84,7 +82,7 @@ export default { :size="size" css-classes="folder-icon" /> - <loading-icon + <gl-loading-icon v-if="loading" :inline="true" /> diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue new file mode 100644 index 00000000000..c797ad62a5d --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/file_row.vue @@ -0,0 +1,210 @@ +<script> +import Icon from '~/vue_shared/components/icon.vue'; +import FileIcon from '~/vue_shared/components/file_icon.vue'; + +export default { + name: 'FileRow', + components: { + FileIcon, + Icon, + }, + props: { + file: { + type: Object, + required: true, + }, + level: { + type: Number, + required: true, + }, + extraComponent: { + type: Object, + required: false, + default: null, + }, + }, + data() { + return { + mouseOver: false, + }; + }, + computed: { + isTree() { + return this.file.type === 'tree'; + }, + isBlob() { + return this.file.type === 'blob'; + }, + levelIndentation() { + return { + marginLeft: `${this.level * 16}px`, + }; + }, + fileClass() { + return { + 'file-open': this.isBlob && this.file.opened, + 'is-active': this.isBlob && this.file.active, + folder: this.isTree, + 'is-open': this.file.opened, + }; + }, + }, + watch: { + 'file.active': function fileActiveWatch(active) { + if (this.file.type === 'blob' && active) { + this.scrollIntoView(); + } + }, + }, + mounted() { + if (this.hasPathAtCurrentRoute()) { + this.scrollIntoView(true); + } + }, + methods: { + toggleTreeOpen(path) { + this.$emit('toggleTreeOpen', path); + }, + clickFile() { + // Manual Action if a tree is selected/opened + if (this.isTree && this.hasUrlAtCurrentRoute()) { + this.toggleTreeOpen(this.file.path); + } + + if (this.$router) this.$router.push(`/project${this.file.url}`); + }, + scrollIntoView(isInit = false) { + const block = isInit && this.isTree ? 'center' : 'nearest'; + + this.$el.scrollIntoView({ + behavior: 'smooth', + block, + }); + }, + hasPathAtCurrentRoute() { + if (!this.$router || !this.$router.currentRoute) { + return false; + } + + // - strip route up to "/-/" and ending "/" + const routePath = this.$router.currentRoute.path + .replace(/^.*?[/]-[/]/g, '') + .replace(/[/]$/g, ''); + + // - strip ending "/" + const filePath = this.file.path.replace(/[/]$/g, ''); + + return filePath === routePath; + }, + hasUrlAtCurrentRoute() { + if (!this.$router || !this.$router.currentRoute) return true; + + return this.$router.currentRoute.path === `/project${this.file.url}`; + }, + toggleHover(over) { + this.mouseOver = over; + }, + }, +}; +</script> + +<template> + <div> + <div + :class="fileClass" + class="file-row" + role="button" + @click="clickFile" + @mouseover="toggleHover(true)" + @mouseout="toggleHover(false)" + > + <div + class="file-row-name-container" + > + <span + :style="levelIndentation" + class="file-row-name str-truncated" + > + <file-icon + :file-name="file.name" + :loading="file.loading" + :folder="isTree" + :opened="file.opened" + :size="16" + /> + {{ file.name }} + </span> + <component + :is="extraComponent" + v-if="extraComponent" + :file="file" + :mouse-over="mouseOver" + /> + </div> + </div> + <template v-if="file.opened"> + <file-row + v-for="childFile in file.tree" + :key="childFile.key" + :file="childFile" + :level="level + 1" + :extra-component="extraComponent" + @toggleTreeOpen="toggleTreeOpen" + /> + </template> + </div> +</template> + +<style> +.file-row { + display: flex; + align-items: center; + height: 32px; + padding: 4px 8px; + margin-left: -8px; + margin-right: -8px; + border-radius: 3px; + text-align: left; + cursor: pointer; +} + +.file-row:hover, +.file-row:focus { + background: #f2f2f2; +} + +.file-row:active { + background: #dfdfdf; +} + +.file-row.is-active { + background: #f2f2f2; +} + +.file-row-name-container { + display: flex; + width: 100%; + align-items: center; + overflow: visible; +} + +.file-row-name { + display: inline-block; + flex: 1; + max-width: inherit; + height: 18px; + line-height: 16px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.file-row-name svg { + margin-right: 2px; + vertical-align: middle; +} + +.file-row-name .loading-container { + display: inline-block; + margin-right: 4px; +} +</style> 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 49fbce75110..b371b6adf7e 100644 --- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue +++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue @@ -1,6 +1,5 @@ <script> import CiIconBadge from './ci_badge_link.vue'; -import LoadingIcon from './loading_icon.vue'; import TimeagoTooltip from './time_ago_tooltip.vue'; import tooltip from '../directives/tooltip'; import UserAvatarImage from './user_avatar/user_avatar_image.vue'; @@ -15,7 +14,6 @@ import UserAvatarImage from './user_avatar/user_avatar_image.vue'; export default { components: { CiIconBadge, - LoadingIcon, TimeagoTooltip, UserAvatarImage, }, @@ -128,18 +126,18 @@ export default { > <a v-if="action.type === 'link'" + :key="i" :href="action.path" :class="action.cssClass" - :key="i" > {{ action.label }} </a> <a v-else-if="action.type === 'ujs-link'" + :key="i" :href="action.path" :class="action.cssClass" - :key="i" data-method="post" rel="nofollow" > @@ -148,9 +146,9 @@ export default { <button v-else-if="action.type === 'button'" + :key="i" :disabled="action.isLoading" :class="action.cssClass" - :key="i" type="button" @click="onClickAction(action)" > diff --git a/app/assets/javascripts/vue_shared/components/loading_button.vue b/app/assets/javascripts/vue_shared/components/loading_button.vue index 2ff0c056b9c..4cbd3e6429d 100644 --- a/app/assets/javascripts/vue_shared/components/loading_button.vue +++ b/app/assets/javascripts/vue_shared/components/loading_button.vue @@ -17,12 +17,7 @@ */ - import loadingIcon from './loading_icon.vue'; - export default { - components: { - loadingIcon, - }, props: { loading: { type: Boolean, @@ -60,7 +55,7 @@ @click="onClick" > <transition name="fade"> - <loading-icon + <gl-loading-icon v-if="loading" :inline="true" :class="{ diff --git a/app/assets/javascripts/vue_shared/components/loading_icon.vue b/app/assets/javascripts/vue_shared/components/loading_icon.vue deleted file mode 100644 index db22c5f02cd..00000000000 --- a/app/assets/javascripts/vue_shared/components/loading_icon.vue +++ /dev/null @@ -1,45 +0,0 @@ -<script> - export default { - props: { - label: { - type: String, - required: false, - default: 'Loading', - }, - - size: { - type: String, - required: false, - default: '1', - }, - - inline: { - type: Boolean, - required: false, - default: false, - }, - }, - - computed: { - rootElementType() { - return this.inline ? 'span' : 'div'; - }, - cssClass() { - return `fa-${this.size}x`; - }, - }, - }; -</script> -<template> - <component - :is="rootElementType" - class="loading-container text-center"> - <i - :class="cssClass" - :aria-label="label" - class="fa fa-spin fa-spinner" - aria-hidden="true" - > - </i> - </component> -</template> diff --git a/app/assets/javascripts/vue_shared/components/pagination_links.vue b/app/assets/javascripts/vue_shared/components/pagination_links.vue new file mode 100644 index 00000000000..1f2a679c145 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/pagination_links.vue @@ -0,0 +1,34 @@ +<script> +import { s__ } from '../../locale'; + +export default { + props: { + change: { + type: Function, + required: true, + }, + pageInfo: { + type: Object, + required: true, + }, + }, + firstText: s__('Pagination|« First'), + prevText: s__('Pagination|Prev'), + nextText: s__('Pagination|Next'), + lastText: s__('Pagination|Last »'), +}; +</script> + +<template> + <gl-pagination + v-bind="$attrs" + :change="change" + :page="pageInfo.page" + :per-page="pageInfo.perPage" + :total-items="pageInfo.total" + :first-text="$options.firstText" + :prev-text="$options.prevText" + :next-text="$options.nextText" + :last-text="$options.lastText" + /> +</template> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue index 74998a4787d..9d757b27edc 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue @@ -1,6 +1,5 @@ <script> import datePicker from '../pikaday.vue'; - import loadingIcon from '../loading_icon.vue'; import toggleSidebar from './toggle_sidebar.vue'; import collapsedCalendarIcon from './collapsed_calendar_icon.vue'; import { dateInWords } from '../../../lib/utils/datetime_utility'; @@ -10,7 +9,6 @@ components: { datePicker, toggleSidebar, - loadingIcon, collapsedCalendarIcon, }, props: { @@ -112,7 +110,7 @@ /> <div class="title"> {{ label }} - <loading-icon + <gl-loading-icon v-if="isLoading" :inline="true" /> diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue index a3fc358130f..3df286de129 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue @@ -3,7 +3,6 @@ import $ from 'jquery'; import { __ } from '~/locale'; import LabelsSelect from '~/labels_select'; import DropdownHiddenInput from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue'; -import LoadingIcon from '../../loading_icon.vue'; import DropdownTitle from './dropdown_title.vue'; import DropdownValue from './dropdown_value.vue'; @@ -16,7 +15,6 @@ import DropdownCreateLabel from './dropdown_create_label.vue'; export default { components: { - LoadingIcon, DropdownTitle, DropdownValue, DropdownValueCollapsed, @@ -164,7 +162,7 @@ dropdown-menu-labels dropdown-menu-selectable" <dropdown-search-input/> <div class="dropdown-content"></div> <div class="dropdown-loading"> - <loading-icon /> + <gl-loading-icon /> </div> <dropdown-footer v-if="showCreate" diff --git a/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue b/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue index 78fde463507..cd3ee544344 100644 --- a/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue +++ b/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue @@ -99,8 +99,8 @@ export default { {{ __("Not available") }} </span> <span - v-tooltip v-if="successPercent" + v-tooltip :title="successTooltip" :style="successBarStyle" class="status-green" @@ -109,8 +109,8 @@ export default { {{ successPercent }}% </span> <span - v-tooltip v-if="neutralPercent" + v-tooltip :title="neutralTooltip" :style="neutralBarStyle" class="status-neutral" @@ -119,8 +119,8 @@ export default { {{ neutralPercent }}% </span> <span - v-tooltip v-if="failurePercent" + v-tooltip :title="failureTooltip" :style="failureBarStyle" class="status-red" diff --git a/app/assets/javascripts/vue_shared/components/toggle_button.vue b/app/assets/javascripts/vue_shared/components/toggle_button.vue index a897300b62b..5b9c51786d6 100644 --- a/app/assets/javascripts/vue_shared/components/toggle_button.vue +++ b/app/assets/javascripts/vue_shared/components/toggle_button.vue @@ -1,7 +1,6 @@ <script> import { s__ } from '../../locale'; import icon from './icon.vue'; - import loadingIcon from './loading_icon.vue'; const ICON_ON = 'status_success_borderless'; const ICON_OFF = 'status_failed_borderless'; @@ -11,7 +10,6 @@ export default { components: { icon, - loadingIcon, }, model: { @@ -78,7 +76,7 @@ class="project-feature-toggle" @click="toggleFeature" > - <loadingIcon class="loading-icon" /> + <gl-loading-icon class="loading-icon" /> <span class="toggle-icon"> <icon :name="toggleIcon" diff --git a/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue b/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue index 125826da6c3..d5b58574123 100644 --- a/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue +++ b/app/assets/javascripts/vue_shared/components/tooltip_on_truncate.vue @@ -51,8 +51,8 @@ export default { <template> <span - v-tooltip v-if="showTooltip" + v-tooltip :title="title" :data-placement="placement" class="js-show-tooltip" diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue index 01c36fec41a..08e102e57c3 100644 --- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue +++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_link.vue @@ -94,8 +94,8 @@ export default { :tooltip-text="avatarTooltipText" :tooltip-placement="tooltipPlacement" /><span - v-tooltip v-if="shouldShowUsername" + v-tooltip :title="tooltipText" :tooltip-placement="tooltipPlacement" >{{ username }}</span> diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss index c91f5e279ea..af73954bd2e 100644 --- a/app/assets/stylesheets/bootstrap_migration.scss +++ b/app/assets/stylesheets/bootstrap_migration.scss @@ -93,7 +93,6 @@ hr { } .form-group.row .col-form-label { - padding-top: 0; // Bootstrap 4 aligns labels to the left // for horizontal forms @include media-breakpoint-up(md) { diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index ab62ca07573..686ce0c63a4 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -147,18 +147,12 @@ } &.btn-success, - &.btn-new, - &.btn-create, - &.btn-save, &.btn-register { @include btn-green; } &.btn-inverted { - &.btn-success, - &.btn-new, - &.btn-create, - &.btn-save { + &.btn-success { @include btn-outline($white-light, $green-600, $green-500, $green-500, $white-light, $green-600, $green-600, $green-700); } diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index e9b074236cc..d5693a5d1a1 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -389,9 +389,8 @@ .btn { text-overflow: ellipsis; - .fa { - width: 15px; - line-height: $line-height-base; + svg { + margin-right: $gl-padding-8; } .dropdown-label-box { diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index fdc0454d837..d9d4a210f5f 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -111,6 +111,7 @@ ul.content-list { border-color: $white-normal; font-size: $gl-font-size; color: $gl-text-color; + word-break: break-word; &.no-description { .title { diff --git a/app/assets/stylesheets/framework/responsive_tables.scss b/app/assets/stylesheets/framework/responsive_tables.scss index 764bebd82c6..fc185ccfaad 100644 --- a/app/assets/stylesheets/framework/responsive_tables.scss +++ b/app/assets/stylesheets/framework/responsive_tables.scss @@ -39,7 +39,7 @@ .table-section { white-space: nowrap; - $section-widths: 10 15 20 25 30 40 50 100; + $section-widths: 5 10 15 20 25 30 40 50 100; @each $width in $section-widths { &.section-#{$width} { flex: 0 0 #{$width + '%'}; diff --git a/app/assets/stylesheets/framework/snippets.scss b/app/assets/stylesheets/framework/snippets.scss index 7152ef9bcfd..36ab38f1c9d 100644 --- a/app/assets/stylesheets/framework/snippets.scss +++ b/app/assets/stylesheets/framework/snippets.scss @@ -45,7 +45,7 @@ } } -.snippet-scope-menu .btn-new { +.snippet-scope-menu .btn-success { margin-top: 15px; } diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss index 45df8391f9a..65f0a0d18e2 100644 --- a/app/assets/stylesheets/page_bundles/ide.scss +++ b/app/assets/stylesheets/page_bundles/ide.scss @@ -53,83 +53,9 @@ $ide-commit-header-height: 48px; flex: 1; min-height: 0; // firefox fix - .file { - height: 32px; - cursor: pointer; - - &.file-active { - background: $theme-gray-100; - } - - .ide-file-name { - flex: 1; - white-space: nowrap; - text-overflow: ellipsis; - max-width: inherit; - line-height: 16px; - display: inline-block; - height: 18px; - - svg { - vertical-align: middle; - margin-right: 2px; - } - - .loading-container { - margin-right: 4px; - display: inline-block; - } - } - - .ide-file-icon-holder { - display: flex; - align-items: center; - color: $theme-gray-700; - } - - .ide-file-changed-icon { - margin-left: auto; - - > svg { - display: block; - } - } - - .ide-new-btn { - display: none; - - .btn { - padding: 2px 5px; - } - } - - &:hover, - &:focus { - .ide-new-btn { - display: block; - } - } - - .folder-icon { - fill: $gl-text-color-secondary; - } - } - a { color: $gl-text-color; } - - th { - position: sticky; - top: 0; - } -} - -.file-name { - display: flex; - overflow: visible; - align-items: center; - width: 100%; } .multi-file-loading-container { @@ -625,8 +551,7 @@ $ide-commit-header-height: 48px; } } -.multi-file-commit-list-path, -.ide-file-list .file { +.multi-file-commit-list-path { display: flex; align-items: center; margin-left: -$grid-size; @@ -634,28 +559,14 @@ $ide-commit-header-height: 48px; padding: $grid-size / 2 $grid-size; border-radius: $border-radius-default; text-align: left; - - &:hover, - &:focus { - background: $theme-gray-100; - } - - &:active { - background: $theme-gray-200; - } -} - -.multi-file-commit-list-path { cursor: pointer; height: $ide-commit-row-height; padding-right: 0; - &.is-active { - background-color: $white-normal; - } - &:hover, &:focus { + background: $theme-gray-100; + outline: 0; .multi-file-discard-btn { @@ -665,6 +576,14 @@ $ide-commit-header-height: 48px; } } + &:active { + background: $theme-gray-200; + } + + &.is-active { + background-color: $white-normal; + } + svg { min-width: 16px; vertical-align: middle; @@ -1398,9 +1317,17 @@ $ide-commit-header-height: 48px; } } -.ide-new-btn .dropdown.show .ide-entry-dropdown-toggle { - color: $white-normal; - background-color: $blue-500; +.ide-new-btn { + display: none; + + .btn { + padding: 2px 5px; + } + + .dropdown.show .ide-entry-dropdown-toggle { + color: $white-normal; + background-color: $blue-500; + } } .ide-preview-header { @@ -1465,3 +1392,28 @@ $ide-commit-header-height: 48px; width: $ide-commit-row-height; height: $ide-commit-row-height; } + +.ide-file-icon-holder { + display: flex; + align-items: center; + color: $theme-gray-700; +} + +.ide-file-changed-icon { + margin-left: auto; + + > svg { + display: block; + } +} + +.file-row:hover, +.file-row:focus { + .ide-new-btn { + display: block; + } + + .folder-icon { + fill: $gl-text-color-secondary; + } +} diff --git a/app/assets/stylesheets/pages/xterm.scss b/app/assets/stylesheets/page_bundles/xterm.scss index 7d40c61da26..7f040ac9b96 100644 --- a/app/assets/stylesheets/pages/xterm.scss +++ b/app/assets/stylesheets/page_bundles/xterm.scss @@ -1,3 +1,5 @@ +@import 'framework/variables'; + .build-page { // color codes are based on http://en.wikipedia.org/wiki/File:Xterm_256color_chart.svg // see also: https://gist.github.com/jasonm23/2868981 diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 69d7de886b4..48db91a5786 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -136,15 +136,23 @@ right: 0; bottom: 0; left: 0; + + button { + display: none; + } } .board-title { padding: 0; border-bottom: 0; + justify-content: center; > span { + width: 100%; + margin-top: -12px; display: block; - transform: rotate(90deg) translate(35px, 10px); + transform: rotate(90deg) translate(35px, 0); + overflow: initial; } } diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss index 0f22fe21143..71a3fd544f2 100644 --- a/app/assets/stylesheets/pages/clusters.scss +++ b/app/assets/stylesheets/pages/clusters.scss @@ -4,9 +4,60 @@ } } -.cluster-applications-table { - // Wait for the Vue to kick-in and render the applications block - min-height: 628px; +.cluster-application-row { + background: $gray-lighter; + + &.cluster-application-installed { + background: none; + } + + .settings-message { + padding: $gl-vert-padding $gl-padding-8; + } +} + +@media (min-width: map-get($grid-breakpoints, md)) { + .cluster-application-list { + border: 1px solid $border-color; + border-radius: $border-radius-default; + } + + .cluster-application-row { + border-bottom: 1px solid $border-color; + padding: $gl-padding; + } +} + +.cluster-application-logo { + border: 3px solid $white-light; + box-shadow: 0 0 0 1px $gray-normal; + + &.avatar:hover { + border-color: $white-light; + } +} + +.cluster-application-warning { + font-weight: bold; + text-align: center; + padding: $gl-padding; + border-bottom: 1px solid $white-normal; + + .svg-container { + display: inline-block; + vertical-align: middle; + margin-right: $gl-padding-8; + width: 40px; + height: 40px; + } +} + +.cluster-application-description { + flex: 1; +} + +.cluster-application-disabled { + opacity: 0.5; } .clusters-dropdown-menu { diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 394c99268be..fe792a53b44 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -106,7 +106,7 @@ &, .dropdown, .dropdown .dropdown-toggle, - .btn-new { + .btn-success { display: block; } @@ -118,7 +118,7 @@ .group-filter-form, .dropdown .dropdown-toggle, - .btn-new { + .btn-success { width: 100%; } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index cb29b5d4313..62a9f97caa9 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -703,7 +703,8 @@ text-overflow: ellipsis; .user-status-emoji { - margin: 0 $gl-padding-8 0 $gl-padding-4; + margin-left: $gl-padding-4; + margin-right: 0; } } @@ -725,13 +726,13 @@ display: flex; } - .issue-info-container { + .issuable-info-container { -webkit-flex: 1; flex: 1; display: flex; padding-right: $gl-padding; - .issue-main-info { + .issuable-main-info { flex: 1 auto; margin-right: 10px; } @@ -767,7 +768,7 @@ margin-bottom: 10px; min-width: 15px; - .selected_issue { + .selected-issuable { vertical-align: text-top; } } @@ -799,7 +800,7 @@ } .issuable-list li, -.issue-info-container .controls { +.issuable-info-container .controls { .avatar-counter { display: inline-block; vertical-align: middle; diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 5fdb2b4a90a..99609a96976 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -4,7 +4,7 @@ } .users-project-form { - .btn-create { + .btn-success { margin-right: 10px; } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 9d46c2cf4fa..97b131687d3 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -460,7 +460,7 @@ display: -webkit-flex; display: flex; - .issue-info-container { + .issuable-info-container { -webkit-flex: 1; flex: 1; } diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index ac7b701c2e2..4268e194ed7 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -2,7 +2,7 @@ * Note Form */ .comment-btn { - @extend .btn-create; + @extend .btn-success; } .diff-file .diff-content { diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 17f34319050..caa839c32a5 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -279,6 +279,10 @@ table.u2f-registrations { } } +.codes { + padding-top: 14px; +} + .oauth-application-show { .scope-name { font-weight: $gl-font-weight-bold; diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index 77119aea9e2..04151b1cd59 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -218,7 +218,7 @@ input[type='checkbox']:hover { } .btn-search, - .btn-new { + .btn-success { width: 100%; margin-top: 5px; |