diff options
author | Filipa Lacerda <filipa@gitlab.com> | 2017-02-23 11:48:53 +0000 |
---|---|---|
committer | Filipa Lacerda <filipa@gitlab.com> | 2017-02-23 11:48:53 +0000 |
commit | 05c66406ca7e7f29b9b210fda9f31a60528917f1 (patch) | |
tree | 18d7f553a61fea3c6a9655be16a4842478990284 /app | |
parent | 0f36cfd7f58977becea9d3ecf410d3669440fbe9 (diff) | |
parent | f106ad513546c8d77b88a0a061a0b6a7e7ee26ed (diff) | |
download | gitlab-ce-26900-pipelines-tabs.tar.gz |
Merge branch 'master' into 26900-pipelines-tabs26900-pipelines-tabs
* master: (361 commits)
Code style improvements
remove require.context from network_bundle
remove require.context from graphs_bundle
remove require.context from filtered_search_bundle
Ignore two Rails CVEs in bundler:audit job
Remove Pages readme
Change Pages redirect
Add missing index.md to Pages docs
Added double newline after file upload markdown insert
Reorder main index items in Pages overview
remove html comments
remove <>
wrapping text - part 3
wrapping text - part 2 [ci skip]
fix link
wrap text - part 1 - [ci skip]
typo
fix spelling, add intermediate cert link
Improve `Gitlab::EeCompatCheck` by using the `git apply --3way` flag
remove link to unfinished video
...
Diffstat (limited to 'app')
286 files changed, 1751 insertions, 1830 deletions
diff --git a/app/assets/images/favicon-blue.ico b/app/assets/images/favicon-blue.ico Binary files differindex 71acdf670ab..156fcf07588 100644..100755 --- a/app/assets/images/favicon-blue.ico +++ b/app/assets/images/favicon-blue.ico diff --git a/app/assets/images/icon-merge-request-unmerged.svg b/app/assets/images/icon-merge-request-unmerged.svg new file mode 100644 index 00000000000..c4d8e65122d --- /dev/null +++ b/app/assets/images/icon-merge-request-unmerged.svg @@ -0,0 +1 @@ +<svg width="12" height="15" viewBox="0 0 12 15" xmlns="http://www.w3.org/2000/svg"><path d="M10.267 11.028V5.167c-.028-.728-.318-1.372-.878-1.923-.56-.55-1.194-.85-1.922-.877h-.934V.5l-2.8 2.8 2.8 2.8V4.233h.934a.976.976 0 0 1 .644.29.88.88 0 0 1 .289.644v5.861a1.86 1.86 0 0 0 .933 3.472 1.86 1.86 0 0 0 .934-3.472zM3.733 3.3a1.86 1.86 0 0 0-1.866-1.867 1.86 1.86 0 0 0-.934 3.472v6.123a1.86 1.86 0 0 0 .933 3.472 1.86 1.86 0 0 0 .934-3.472V4.905c.55-.317.933-.914.933-1.605z" fill-rule="nonzero"/></svg> diff --git a/app/assets/images/mailers/gitlab_footer_logo.gif b/app/assets/images/mailers/gitlab_footer_logo.gif Binary files differnew file mode 100644 index 00000000000..3f4ef31947b --- /dev/null +++ b/app/assets/images/mailers/gitlab_footer_logo.gif diff --git a/app/assets/images/mailers/gitlab_header_logo.gif b/app/assets/images/mailers/gitlab_header_logo.gif Binary files differnew file mode 100644 index 00000000000..387628f831c --- /dev/null +++ b/app/assets/images/mailers/gitlab_header_logo.gif diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js index 424dc719c78..aaed74d6073 100644 --- a/app/assets/javascripts/admin.js +++ b/app/assets/javascripts/admin.js @@ -61,4 +61,4 @@ return Admin; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 84bbe90f3b1..86e0ad89431 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -147,4 +147,4 @@ }; window.Api = Api; -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 4b5c9686cab..8e468faedbf 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -101,11 +101,6 @@ require('es6-promise').polyfill(); } }); - $('.nav-sidebar').niceScroll({ - cursoropacitymax: '0.4', - cursorcolor: '#FFF', - cursorborder: '1px solid #FFF' - }); $('.js-select-on-focus').on('focusin', function () { return $(this).select().one('mouseup', function (e) { return e.preventDefault(); @@ -245,9 +240,7 @@ require('es6-promise').polyfill(); }); gl.awardsHandler = new AwardsHandler(); new Aside(); - // bind sidebar events - new gl.Sidebar(); gl.utils.initTimeagoTimeout(); }); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/aside.js b/app/assets/javascripts/aside.js index 8438de6cdf1..448e6e2cc78 100644 --- a/app/assets/javascripts/aside.js +++ b/app/assets/javascripts/aside.js @@ -22,4 +22,4 @@ return Aside; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/autosave.js b/app/assets/javascripts/autosave.js index b16a2c0f73a..e55405135fb 100644 --- a/app/assets/javascripts/autosave.js +++ b/app/assets/javascripts/autosave.js @@ -59,4 +59,4 @@ return Autosave; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 9d776b74965..a4ccb30e447 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -377,4 +377,4 @@ var emojiAliases = require('emoji-aliases'); return AwardsHandler; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js index a489523b802..f7f41d55b52 100644 --- a/app/assets/javascripts/behaviors/autosize.js +++ b/app/assets/javascripts/behaviors/autosize.js @@ -25,4 +25,4 @@ var autosize = require('vendor/autosize'); autosize.update($fields); return $fields.css('resize', 'vertical'); }); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/behaviors/details_behavior.js b/app/assets/javascripts/behaviors/details_behavior.js index 6af8f593872..fd0840fa117 100644 --- a/app/assets/javascripts/behaviors/details_behavior.js +++ b/app/assets/javascripts/behaviors/details_behavior.js @@ -23,4 +23,4 @@ return e.preventDefault(); }); }); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/behaviors/quick_submit.js b/app/assets/javascripts/behaviors/quick_submit.js index 7747306688c..a7e68ae5cb9 100644 --- a/app/assets/javascripts/behaviors/quick_submit.js +++ b/app/assets/javascripts/behaviors/quick_submit.js @@ -74,4 +74,4 @@ require('../extensions/jquery'); return $this.tooltip('hide'); }); }); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/behaviors/requires_input.js b/app/assets/javascripts/behaviors/requires_input.js index 6276933e93e..6b21695d082 100644 --- a/app/assets/javascripts/behaviors/requires_input.js +++ b/app/assets/javascripts/behaviors/requires_input.js @@ -59,4 +59,4 @@ require('../extensions/jquery'); return hideOrShowHelpBlock($form); }); }); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js index 04bfe363929..5f14ff40eee 100644 --- a/app/assets/javascripts/blob/blob_file_dropzone.js +++ b/app/assets/javascripts/blob/blob_file_dropzone.js @@ -63,4 +63,4 @@ return BlobFileDropzone; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/blob/blob_gitignore_selector.js b/app/assets/javascripts/blob/blob_gitignore_selector.js index 1d0bcf6471f..de20eab9cd1 100644 --- a/app/assets/javascripts/blob/blob_gitignore_selector.js +++ b/app/assets/javascripts/blob/blob_gitignore_selector.js @@ -20,4 +20,4 @@ require('./template_selector'); return BlobGitignoreSelector; })(gl.TemplateSelector); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/blob/blob_gitignore_selectors.js b/app/assets/javascripts/blob/blob_gitignore_selectors.js index 8236457f0f1..43e5c0a5641 100644 --- a/app/assets/javascripts/blob/blob_gitignore_selectors.js +++ b/app/assets/javascripts/blob/blob_gitignore_selectors.js @@ -23,4 +23,4 @@ return BlobGitignoreSelectors; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/blob/blob_license_selector.js b/app/assets/javascripts/blob/blob_license_selector.js index 1d5672d4c48..b582052a76e 100644 --- a/app/assets/javascripts/blob/blob_license_selector.js +++ b/app/assets/javascripts/blob/blob_license_selector.js @@ -25,4 +25,4 @@ require('./template_selector'); return BlobLicenseSelector; })(gl.TemplateSelector); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/blob_edit/blob_edit_bundle.js b/app/assets/javascripts/blob_edit/blob_edit_bundle.js index 9e0754819fa..0436bbb0eaf 100644 --- a/app/assets/javascripts/blob_edit/blob_edit_bundle.js +++ b/app/assets/javascripts/blob_edit/blob_edit_bundle.js @@ -12,4 +12,4 @@ require('./edit_blob'); var blob = new EditBlob(url, $('.js-edit-blob-form').data('blob-language')); new NewCommitForm($('.js-edit-blob-form')); }); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/blob_edit/edit_blob.js b/app/assets/javascripts/blob_edit/edit_blob.js index 079445e8278..a1127b9e30e 100644 --- a/app/assets/javascripts/blob_edit/edit_blob.js +++ b/app/assets/javascripts/blob_edit/edit_blob.js @@ -85,4 +85,4 @@ return EditBlob; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6 index 8f30900198e..55d13be6e5f 100644 --- a/app/assets/javascripts/boards/boards_bundle.js.es6 +++ b/app/assets/javascripts/boards/boards_bundle.js.es6 @@ -1,16 +1,20 @@ -/* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren, import/newline-after-import, no-multi-spaces, max-len */ +/* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */ /* global Vue */ /* global BoardService */ -function requireAll(context) { return context.keys().map(context); } - window.Vue = require('vue'); window.Vue.use(require('vue-resource')); -requireAll(require.context('./models', true, /^\.\/.*\.(js|es6)$/)); -requireAll(require.context('./stores', true, /^\.\/.*\.(js|es6)$/)); -requireAll(require.context('./services', true, /^\.\/.*\.(js|es6)$/)); -requireAll(require.context('./mixins', true, /^\.\/.*\.(js|es6)$/)); -requireAll(require.context('./filters', true, /^\.\/.*\.(js|es6)$/)); +require('./models/issue'); +require('./models/label'); +require('./models/list'); +require('./models/milestone'); +require('./models/user'); +require('./stores/boards_store'); +require('./stores/modal_store'); +require('./services/board_service'); +require('./mixins/modal_mixins'); +require('./mixins/sortable_default_options'); +require('./filters/due_date_filters'); require('./components/board'); require('./components/board_sidebar'); require('./components/new_list_dropdown'); @@ -93,17 +97,53 @@ $(() => { modal: ModalStore.store, store: Store.state, }, + watch: { + disabled() { + this.updateTooltip(); + }, + }, computed: { disabled() { - return Store.shouldAddBlankState(); + return !this.store.lists.filter(list => list.type !== 'blank' && list.type !== 'done').length; + }, + tooltipTitle() { + if (this.disabled) { + return 'Please add a list to your board first'; + } + + return ''; }, }, + methods: { + updateTooltip() { + const $tooltip = $(this.$el); + + this.$nextTick(() => { + if (this.disabled) { + $tooltip.tooltip(); + } else { + $tooltip.tooltip('destroy'); + } + }); + }, + openModal() { + if (!this.disabled) { + this.toggleModal(true); + } + }, + }, + mounted() { + this.updateTooltip(); + }, template: ` <button - class="btn btn-create pull-right prepend-left-10 has-tooltip" + class="btn btn-create pull-right prepend-left-10" type="button" - :disabled="disabled" - @click="toggleModal(true)"> + data-placement="bottom" + :class="{ 'disabled': disabled }" + :title="tooltipTitle" + :aria-disabled="disabled" + @click="openModal"> Add issues </button> `, diff --git a/app/assets/javascripts/breakpoints.js b/app/assets/javascripts/breakpoints.js index f8dac1ff56e..22e93328548 100644 --- a/app/assets/javascripts/breakpoints.js +++ b/app/assets/javascripts/breakpoints.js @@ -69,4 +69,4 @@ })(this)); window.Breakpoints = Breakpoints; -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/broadcast_message.js b/app/assets/javascripts/broadcast_message.js index dbdadc73c3f..e8531c43b4b 100644 --- a/app/assets/javascripts/broadcast_message.js +++ b/app/assets/javascripts/broadcast_message.js @@ -31,4 +31,4 @@ } }); }); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index c5a962dd199..8fa1aceddff 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -275,4 +275,4 @@ return Build; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/build_artifacts.js b/app/assets/javascripts/build_artifacts.js index 083448552b6..cae9a0ffca4 100644 --- a/app/assets/javascripts/build_artifacts.js +++ b/app/assets/javascripts/build_artifacts.js @@ -23,4 +23,4 @@ return BuildArtifacts; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/commit.js b/app/assets/javascripts/commit.js index c656ae4e241..566b322eb49 100644 --- a/app/assets/javascripts/commit.js +++ b/app/assets/javascripts/commit.js @@ -11,4 +11,4 @@ return Commit; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/commit/file.js b/app/assets/javascripts/commit/file.js index 184b4561d2e..ee087c978dd 100644 --- a/app/assets/javascripts/commit/file.js +++ b/app/assets/javascripts/commit/file.js @@ -11,4 +11,4 @@ return CommitFile; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js index f09a6b1e676..49bb64a3472 100644 --- a/app/assets/javascripts/commit/image_file.js +++ b/app/assets/javascripts/commit/image_file.js @@ -173,4 +173,4 @@ return ImageFile; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js index c6fdfbcaa10..ccd895f3bf4 100644 --- a/app/assets/javascripts/commits.js +++ b/app/assets/javascripts/commits.js @@ -65,4 +65,4 @@ return CommitsList; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/compare.js b/app/assets/javascripts/compare.js index 9591df70e9c..15df105d4cc 100644 --- a/app/assets/javascripts/compare.js +++ b/app/assets/javascripts/compare.js @@ -88,4 +88,4 @@ return Compare; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/compare_autocomplete.js.es6 b/app/assets/javascripts/compare_autocomplete.js.es6 index 3587431ab69..1eca973e069 100644 --- a/app/assets/javascripts/compare_autocomplete.js.es6 +++ b/app/assets/javascripts/compare_autocomplete.js.es6 @@ -66,4 +66,4 @@ return CompareAutocomplete; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js index 35d98492012..a1c1b721228 100644 --- a/app/assets/javascripts/confirm_danger_modal.js +++ b/app/assets/javascripts/confirm_danger_modal.js @@ -28,4 +28,4 @@ return ConfirmDangerModal; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js index 0029c59e550..615f485e18a 100644 --- a/app/assets/javascripts/copy_to_clipboard.js +++ b/app/assets/javascripts/copy_to_clipboard.js @@ -46,4 +46,4 @@ window.Clipboard = require('vendor/clipboard'); clipboard.on('success', genericSuccess); return clipboard.on('error', genericError); }); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/create_label.js.es6 b/app/assets/javascripts/create_label.js.es6 index 947c129d5b5..85384d98126 100644 --- a/app/assets/javascripts/create_label.js.es6 +++ b/app/assets/javascripts/create_label.js.es6 @@ -107,9 +107,9 @@ if (typeof label.message === 'string') { errors = label.message; } else { - errors = label.message.map(function (value, key) { - return key + " " + value[0]; - }).join("<br/>"); + errors = Object.keys(label.message).map(key => + `${gl.text.humanize(key)} ${label.message[key].join(', ')}` + ).join("<br/>"); } this.$newLabelError diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6 b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6 index dbdb01c8c68..411ac7b24b2 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6 +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6 @@ -4,10 +4,20 @@ window.Vue = require('vue'); window.Cookies = require('js-cookie'); - -function requireAll(context) { return context.keys().map(context); } -requireAll(require.context('./svg', false, /^\.\/.*\.(js|es6)$/)); -requireAll(require.context('.', true, /^\.\/(?!cycle_analytics_bundle).*\.(js|es6)$/)); +require('./svg/icon_branch'); +require('./svg/icon_build_status'); +require('./svg/icon_commit'); +require('./components/stage_code_component'); +require('./components/stage_issue_component'); +require('./components/stage_plan_component'); +require('./components/stage_production_component'); +require('./components/stage_review_component'); +require('./components/stage_staging_component'); +require('./components/stage_test_component'); +require('./components/total_time_component'); +require('./cycle_analytics_service'); +require('./cycle_analytics_store'); +require('./default_event_objects'); $(() => { const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed'; @@ -97,7 +107,7 @@ $(() => { } this.isLoadingStage = true; - cycleAnalyticsStore.setStageEvents([]); + cycleAnalyticsStore.setStageEvents([], stage); cycleAnalyticsStore.setActiveStage(stage); cycleAnalyticsService @@ -107,7 +117,7 @@ $(() => { }) .done((response) => { this.isEmptyStage = !response.events.length; - cycleAnalyticsStore.setStageEvents(response.events); + cycleAnalyticsStore.setStageEvents(response.events, stage); }) .error(() => { this.isEmptyStage = true; diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6 b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6 index be732971c7f..3efeb141008 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6 +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6 @@ -1,4 +1,8 @@ /* eslint-disable no-param-reassign */ + +require('../lib/utils/text_utility'); +const DEFAULT_EVENT_OBJECTS = require('./default_event_objects'); + ((global) => { global.cycleAnalytics = global.cycleAnalytics || {}; @@ -34,11 +38,12 @@ }); newData.stages.forEach((item) => { - const stageName = item.title.toLowerCase(); + const stageSlug = gl.text.dasherize(item.title.toLowerCase()); item.active = false; - item.isUserAllowed = data.permissions[stageName]; - item.emptyStageText = EMPTY_STAGE_TEXTS[stageName]; - item.component = `stage-${stageName}-component`; + item.isUserAllowed = data.permissions[stageSlug]; + item.emptyStageText = EMPTY_STAGE_TEXTS[stageSlug]; + item.component = `stage-${stageSlug}-component`; + item.slug = stageSlug; }); newData.analytics = data; return newData; @@ -58,31 +63,33 @@ this.deactivateAllStages(); stage.active = true; }, - setStageEvents(events) { - this.state.events = this.decorateEvents(events); + setStageEvents(events, stage) { + this.state.events = this.decorateEvents(events, stage); }, - decorateEvents(events) { + decorateEvents(events, stage) { const newEvents = []; events.forEach((item) => { if (!item) return; - item.totalTime = item.total_time; - item.author.webUrl = item.author.web_url; - item.author.avatarUrl = item.author.avatar_url; + const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item); + + eventItem.totalTime = eventItem.total_time; + eventItem.author.webUrl = eventItem.author.web_url; + eventItem.author.avatarUrl = eventItem.author.avatar_url; - if (item.created_at) item.createdAt = item.created_at; - if (item.short_sha) item.shortSha = item.short_sha; - if (item.commit_url) item.commitUrl = item.commit_url; + if (eventItem.created_at) eventItem.createdAt = eventItem.created_at; + if (eventItem.short_sha) eventItem.shortSha = eventItem.short_sha; + if (eventItem.commit_url) eventItem.commitUrl = eventItem.commit_url; - delete item.author.web_url; - delete item.author.avatar_url; - delete item.total_time; - delete item.created_at; - delete item.short_sha; - delete item.commit_url; + delete eventItem.author.web_url; + delete eventItem.author.avatar_url; + delete eventItem.total_time; + delete eventItem.created_at; + delete eventItem.short_sha; + delete eventItem.commit_url; - newEvents.push(item); + newEvents.push(eventItem); }); return newEvents; diff --git a/app/assets/javascripts/cycle_analytics/default_event_objects.js.es6 b/app/assets/javascripts/cycle_analytics/default_event_objects.js.es6 new file mode 100644 index 00000000000..cfaf9835bf8 --- /dev/null +++ b/app/assets/javascripts/cycle_analytics/default_event_objects.js.es6 @@ -0,0 +1,98 @@ +module.exports = { + issue: { + created_at: '', + url: '', + iid: '', + title: '', + total_time: {}, + author: { + avatar_url: '', + id: '', + name: '', + web_url: '', + }, + }, + plan: { + title: '', + commit_url: '', + short_sha: '', + total_time: {}, + author: { + name: '', + id: '', + avatar_url: '', + web_url: '', + }, + }, + code: { + title: '', + iid: '', + created_at: '', + url: '', + total_time: {}, + author: { + name: '', + id: '', + avatar_url: '', + web_url: '', + }, + }, + test: { + name: '', + id: '', + date: '', + url: '', + short_sha: '', + commit_url: '', + total_time: {}, + branch: { + name: '', + url: '', + }, + }, + review: { + title: '', + iid: '', + created_at: '', + url: '', + state: '', + total_time: {}, + author: { + name: '', + id: '', + avatar_url: '', + web_url: '', + }, + }, + staging: { + id: '', + short_sha: '', + date: '', + url: '', + commit_url: '', + total_time: {}, + author: { + name: '', + id: '', + avatar_url: '', + web_url: '', + }, + branch: { + name: '', + url: '', + }, + }, + production: { + title: '', + created_at: '', + url: '', + iid: '', + total_time: {}, + author: { + name: '', + id: '', + avatar_url: '', + web_url: '', + }, + }, +}; diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6 b/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6 index 190461451d5..cadf8b96b87 100644 --- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6 +++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6 @@ -1,14 +1,18 @@ -/* eslint-disable func-names, comma-dangle, new-cap, no-new, import/newline-after-import, no-multi-spaces, max-len */ +/* eslint-disable func-names, comma-dangle, new-cap, no-new, max-len */ /* global Vue */ /* global ResolveCount */ -function requireAll(context) { return context.keys().map(context); } const Vue = require('vue'); -requireAll(require.context('./models', false, /^\.\/.*\.(js|es6)$/)); -requireAll(require.context('./stores', false, /^\.\/.*\.(js|es6)$/)); -requireAll(require.context('./services', false, /^\.\/.*\.(js|es6)$/)); -requireAll(require.context('./mixins', false, /^\.\/.*\.(js|es6)$/)); -requireAll(require.context('./components', false, /^\.\/.*\.(js|es6)$/)); +require('./models/discussion'); +require('./models/note'); +require('./stores/comments'); +require('./services/resolve'); +require('./mixins/discussion'); +require('./components/comment_resolve_btn'); +require('./components/jump_to_discussion'); +require('./components/resolve_btn'); +require('./components/resolve_count'); +require('./components/resolve_discussion_btn'); $(() => { const projectPath = document.querySelector('.merge-request').dataset.projectPath; diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index 7eec2d39a9c..f55db02f0fd 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -74,7 +74,7 @@ const ShortcutsBlob = require('./shortcuts_blob'); case 'projects:merge_requests:index': case 'projects:issues:index': if (gl.FilteredSearchManager) { - new gl.FilteredSearchManager(); + new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests'); } Issuable.init(); new gl.IssuableBulkActions({ @@ -118,6 +118,7 @@ const ShortcutsBlob = require('./shortcuts_blob'); new gl.IssuableTemplateSelectors(); break; case 'projects:merge_requests:new': + case 'projects:merge_requests:new_diffs': case 'projects:merge_requests:edit': new gl.Diff(); shortcut_handler = new ShortcutsNavigation(); @@ -382,4 +383,4 @@ const ShortcutsBlob = require('./shortcuts_blob'); return Dispatcher; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js index a510eebae1a..646f836aff0 100644 --- a/app/assets/javascripts/dropzone_input.js +++ b/app/assets/javascripts/dropzone_input.js @@ -126,13 +126,14 @@ require('./preview_markdown'); }; pasteText = function(text) { var afterSelection, beforeSelection, caretEnd, caretStart, textEnd; + var formattedText = text + "\n\n"; caretStart = $(child)[0].selectionStart; caretEnd = $(child)[0].selectionEnd; textEnd = $(child).val().length; beforeSelection = $(child).val().substring(0, caretStart); afterSelection = $(child).val().substring(caretEnd, textEnd); - $(child).val(beforeSelection + text + afterSelection); - child.get(0).setSelectionRange(caretStart + text.length, caretEnd + text.length); + $(child).val(beforeSelection + formattedText + afterSelection); + child.get(0).setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length); return form_textarea.trigger("input"); }; getFilename = function(e) { @@ -216,4 +217,4 @@ require('./preview_markdown'); return DropzoneInput; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 0cbf952ea5c..4b700a39d44 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -1,13 +1,14 @@ /* eslint-disable no-param-reassign, no-new */ /* global Flash */ -const Vue = require('vue'); -Vue.use(require('vue-resource')); +const Vue = window.Vue = require('vue'); +window.Vue.use(require('vue-resource')); const EnvironmentsService = require('../services/environments_service'); const EnvironmentTable = require('./environments_table'); const EnvironmentsStore = require('../stores/environments_store'); require('../../vue_shared/components/table_pagination'); require('../../lib/utils/common_utils'); +require('../../vue_shared/vue_resource_interceptor'); module.exports = Vue.component('environment-component', { diff --git a/app/assets/javascripts/environments/components/environment_actions.js.es6 b/app/assets/javascripts/environments/components/environment_actions.js.es6 index c5a714d9673..978d4dd8b6b 100644 --- a/app/assets/javascripts/environments/components/environment_actions.js.es6 +++ b/app/assets/javascripts/environments/components/environment_actions.js.es6 @@ -15,29 +15,29 @@ module.exports = Vue.component('actions-component', { }, template: ` - <div class="inline"> - <div class="dropdown"> - <a class="dropdown-new btn btn-default" data-toggle="dropdown"> + <div class="btn-group" role="group"> + <button class="dropdown btn btn-default dropdown-new" data-toggle="dropdown"> + <span> <span class="js-dropdown-play-icon-container" v-html="playIconSvg"></span> <i class="fa fa-caret-down"></i> - </a> + </span> - <ul class="dropdown-menu dropdown-menu-align-right"> - <li v-for="action in actions"> - <a :href="action.play_path" - data-method="post" - rel="nofollow" - class="js-manual-action-link"> + <ul class="dropdown-menu dropdown-menu-align-right"> + <li v-for="action in actions"> + <a :href="action.play_path" + data-method="post" + rel="nofollow" + class="js-manual-action-link"> - <span class="js-action-play-icon-container" v-html="playIconSvg"></span> + <span class="js-action-play-icon-container" v-html="playIconSvg"></span> - <span> - {{action.name}} - </span> - </a> - </li> - </ul> - </div> - </div> + <span> + {{action.name}} + </span> + </a> + </li> + </ul> + </button> + </div> `, }); diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index 24fd58a301a..ad9d1d21a79 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -505,39 +505,26 @@ module.exports = Vue.component('environment-item', { <td class="hidden-xs"> <div v-if="!model.isFolder"> - <div v-if="hasManualActions && canCreateDeployment" - class="inline js-manual-actions-container"> - <actions-component + <div class="btn-group" role="group"> + <actions-component v-if="hasManualActions && canCreateDeployment" :play-icon-svg="playIconSvg" :actions="manualActions"> </actions-component> - </div> - <div v-if="externalURL && canReadEnvironment" - class="inline js-external-url-container"> - <external-url-component + <external-url-component v-if="externalURL && canReadEnvironment" :external-url="externalURL"> </external-url-component> - </div> - <div v-if="hasStopAction && canCreateDeployment" - class="inline js-stop-component-container"> - <stop-component + <stop-component v-if="hasStopAction && canCreateDeployment" :stop-url="model.stop_path"> </stop-component> - </div> - <div v-if="model && model.terminal_path" - class="inline js-terminal-button-container"> - <terminal-button-component + <terminal-button-component v-if="model && model.terminal_path" :terminal-icon-svg="terminalIconSvg" :terminal-path="model.terminal_path"> </terminal-button-component> - </div> - <div v-if="canRetry && canCreateDeployment" - class="inline js-rollback-component-container"> - <rollback-component + <rollback-component v-if="canRetry && canCreateDeployment" :is-last-deployment="isLastDeployment" :retry-url="retryUrl"> </rollback-component> diff --git a/app/assets/javascripts/environments/environments_bundle.js.es6 b/app/assets/javascripts/environments/environments_bundle.js.es6 index 867eba1d384..7bbba91bc10 100644 --- a/app/assets/javascripts/environments/environments_bundle.js.es6 +++ b/app/assets/javascripts/environments/environments_bundle.js.es6 @@ -1,5 +1,4 @@ const EnvironmentsComponent = require('./components/environment'); -require('../vue_shared/vue_resource_interceptor'); $(() => { window.gl = window.gl || {}; diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6 b/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6 index 29f704c1a37..d2ca465351a 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6 +++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6 @@ -1,5 +1,4 @@ const EnvironmentsFolderComponent = require('./environments_folder_view'); -require('../../vue_shared/vue_resource_interceptor'); $(() => { window.gl = window.gl || {}; diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.js.es6 b/app/assets/javascripts/environments/folder/environments_folder_view.js.es6 index 0b1204559da..53d52965758 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.js.es6 +++ b/app/assets/javascripts/environments/folder/environments_folder_view.js.es6 @@ -1,13 +1,14 @@ /* eslint-disable no-param-reassign, no-new */ /* global Flash */ -const Vue = require('vue'); -Vue.use(require('vue-resource')); +const Vue = window.Vue = require('vue'); +window.Vue.use(require('vue-resource')); const EnvironmentsService = require('../services/environments_service'); const EnvironmentTable = require('../components/environments_table'); const EnvironmentsStore = require('../stores/environments_store'); require('../../vue_shared/components/table_pagination'); require('../../lib/utils/common_utils'); +require('../../vue_shared/vue_resource_interceptor'); module.exports = Vue.component('environment-folder-view', { diff --git a/app/assets/javascripts/extensions/jquery.js b/app/assets/javascripts/extensions/jquery.js index d3b58b2707a..1a489b859e8 100644 --- a/app/assets/javascripts/extensions/jquery.js +++ b/app/assets/javascripts/extensions/jquery.js @@ -13,4 +13,4 @@ return $(this).removeAttr('disabled').removeClass('disabled'); } }); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/files_comment_button.js b/app/assets/javascripts/files_comment_button.js index 895a872568d..698870d0ce1 100644 --- a/app/assets/javascripts/files_comment_button.js +++ b/app/assets/javascripts/files_comment_button.js @@ -144,4 +144,4 @@ } }); }; -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 index 572c221929a..9e92d544bef 100644 --- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 @@ -37,23 +37,18 @@ require('./filtered_search_dropdown'); } renderContent() { - const dropdownData = [{ - icon: 'fa-pencil', - hint: 'author:', - tag: '<@author>', - }, { - icon: 'fa-user', - hint: 'assignee:', - tag: '<@assignee>', - }, { - icon: 'fa-clock-o', - hint: 'milestone:', - tag: '<%milestone>', - }, { - icon: 'fa-tag', - hint: 'label:', - tag: '<~label>', - }]; + const dropdownData = []; + + [].forEach.call(this.input.parentElement.querySelectorAll('.dropdown-menu'), (dropdownMenu) => { + const { icon, hint, tag } = dropdownMenu.dataset; + if (icon && hint && tag) { + dropdownData.push({ + icon: `fa-${icon}`, + hint, + tag: `<${tag}>`, + }); + } + }); this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config); this.droplab.setData(this.hookId, dropdownData); diff --git a/app/assets/javascripts/filtered_search/filtered_search_bundle.js b/app/assets/javascripts/filtered_search/filtered_search_bundle.js index 392f1835966..faaba994f46 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_bundle.js +++ b/app/assets/javascripts/filtered_search/filtered_search_bundle.js @@ -1,3 +1,9 @@ -function requireAll(context) { return context.keys().map(context); } - -requireAll(require.context('./', true, /^\.\/(?!filtered_search_bundle).*\.(js|es6)$/)); +require('./dropdown_hint'); +require('./dropdown_non_user'); +require('./dropdown_user'); +require('./dropdown_utils'); +require('./filtered_search_dropdown_manager'); +require('./filtered_search_dropdown'); +require('./filtered_search_manager'); +require('./filtered_search_token_keys'); +require('./filtered_search_tokenizer'); diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 index e8c2df03a46..fbc72a3001a 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 @@ -52,8 +52,9 @@ } renderContent(forceShowList = false) { - if (forceShowList && this.getCurrentHook().list.hidden) { - this.getCurrentHook().list.show(); + const currentHook = this.getCurrentHook(); + if (forceShowList && currentHook && currentHook.list.hidden) { + currentHook.list.show(); } } @@ -92,18 +93,24 @@ } hideDropdown() { - this.getCurrentHook().list.hide(); + const currentHook = this.getCurrentHook(); + if (currentHook) { + currentHook.list.hide(); + } } resetFilters() { const hook = this.getCurrentHook(); - const data = hook.list.data; - const results = data.map((o) => { - const updated = o; - updated.droplab_hidden = false; - return updated; - }); - hook.list.render(results); + + if (hook) { + const data = hook.list.data; + const results = data.map((o) => { + const updated = o; + updated.droplab_hidden = false; + return updated; + }); + hook.list.render(results); + } } } diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 index 8ce4cf4fc36..cecd3518ce3 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 @@ -2,10 +2,12 @@ (() => { class FilteredSearchDropdownManager { - constructor(baseEndpoint = '') { + constructor(baseEndpoint = '', page) { this.baseEndpoint = baseEndpoint.replace(/\/$/, ''); this.tokenizer = gl.FilteredSearchTokenizer; + this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys; this.filteredSearchInput = document.querySelector('.filtered-search'); + this.page = page; this.setupMapping(); @@ -150,7 +152,7 @@ this.droplab = new DropLab(); } - const match = gl.FilteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase()); + const match = this.filteredSearchTokenKeys.searchByKey(dropdownName.toLowerCase()); const shouldOpenFilterDropdown = match && this.currentDropdown !== match.key && this.mapping[match.key]; const shouldOpenHintDropdown = !match && this.currentDropdown !== 'hint'; diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index ffc7d29e4c5..bbafead0305 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -1,12 +1,13 @@ (() => { class FilteredSearchManager { - constructor() { + constructor(page) { this.filteredSearchInput = document.querySelector('.filtered-search'); this.clearSearchButton = document.querySelector('.clear-search'); + this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys; if (this.filteredSearchInput) { this.tokenizer = gl.FilteredSearchTokenizer; - this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || ''); + this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', page); this.bindEvents(); this.loadSearchParamsFromURL(); @@ -117,8 +118,8 @@ const keyParam = decodeURIComponent(split[0]); const value = split[1]; - // Check if it matches edge conditions listed in gl.FilteredSearchTokenKeys - const condition = gl.FilteredSearchTokenKeys.searchByConditionUrl(p); + // Check if it matches edge conditions listed in this.filteredSearchTokenKeys + const condition = this.filteredSearchTokenKeys.searchByConditionUrl(p); if (condition) { inputValues.push(`${condition.tokenKey}:${condition.value}`); @@ -126,7 +127,7 @@ // Sanitize value since URL converts spaces into + // Replace before decode so that we know what was originally + versus the encoded + const sanitizedValue = value ? decodeURIComponent(value.replace(/\+/g, ' ')) : value; - const match = gl.FilteredSearchTokenKeys.searchByKeyParam(keyParam); + const match = this.filteredSearchTokenKeys.searchByKeyParam(keyParam); if (match) { const indexOf = keyParam.indexOf('_'); @@ -171,9 +172,9 @@ paths.push(`state=${currentState}`); tokens.forEach((token) => { - const condition = gl.FilteredSearchTokenKeys + const condition = this.filteredSearchTokenKeys .searchByConditionKeyValue(token.key, token.value.toLowerCase()); - const { param } = gl.FilteredSearchTokenKeys.searchByKey(token.key); + const { param } = this.filteredSearchTokenKeys.searchByKey(token.key) || {}; const keyParam = param ? `${token.key}_${param}` : token.key; let tokenPath = ''; diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 index cf53845a48b..9bf1b1ced88 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 @@ -1,9 +1,12 @@ +require('./filtered_search_token_keys'); + (() => { class FilteredSearchTokenizer { static processTokens(input) { + const allowedKeys = gl.FilteredSearchTokenKeys.get().map(i => i.key); // Regex extracts `(token):(symbol)(value)` // Values that start with a double quote must end in a double quote (same for single) - const tokenRegex = /(\w+):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\S+))/g; + const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g'); const tokens = []; let lastToken = null; const searchToken = input.replace(tokenRegex, (match, key, symbol, v1, v2, v3) => { diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js index 249fe23d4cb..730104b89f9 100644 --- a/app/assets/javascripts/flash.js +++ b/app/assets/javascripts/flash.js @@ -39,4 +39,4 @@ return Flash; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index 7f1f2a5d278..60d6658dc16 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -83,12 +83,12 @@ _a = decodeURI("%C3%80"); _y = decodeURI("%C3%BF"); - regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?![" + atSymbolsWithBar + "])(([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]|[^\\x00-\\x7a])*)$", 'gi'); + regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?!" + atSymbolsWithBar + ")((?:[A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]|[^\\x00-\\x7a])*)$", 'gi'); match = regexp.exec(subtext); if (match) { - return (match[1] || match[1] === "") ? match[1] : match[2]; + return match[1]; } else { return null; } @@ -103,6 +103,9 @@ this.input.each((i, input) => { const $input = $(input); $input.off('focus.setupAtWho').on('focus.setupAtWho', this.setupAtWho.bind(this, $input)); + // This triggers at.js again + // Needed for slash commands with suffixes (ex: /label ~) + $input.on('inserted-commands.atwho', $input.trigger.bind($input, 'keyup')); }); }, setupAtWho: function($input) { @@ -377,4 +380,4 @@ (dataToInspect === loadingState || dataToInspect.name === loadingState); } }; -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 0d618caf350..a01662e2f9e 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -47,9 +47,10 @@ } // Only filter asynchronously only if option remote is set if (this.options.remote) { - $inputContainer.parent().addClass('is-loading'); clearTimeout(timeout); return timeout = setTimeout(function() { + $inputContainer.parent().addClass('is-loading'); + return this.options.query(this.input.val(), function(data) { $inputContainer.parent().removeClass('is-loading'); return this.options.callback(data); @@ -846,4 +847,4 @@ } }); }; -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/graphs/graphs_bundle.js b/app/assets/javascripts/graphs/graphs_bundle.js index 4f7777aa5bc..086dcb34571 100644 --- a/app/assets/javascripts/graphs/graphs_bundle.js +++ b/app/assets/javascripts/graphs/graphs_bundle.js @@ -1,3 +1,4 @@ -// require everything else in this directory -function requireAll(context) { return context.keys().map(context); } -requireAll(require.context('.', false, /^\.\/(?!graphs_bundle).*\.(js|es6)$/)); +require('./stat_graph_contributors_graph'); +require('./stat_graph_contributors_util'); +require('./stat_graph_contributors'); +require('./stat_graph'); diff --git a/app/assets/javascripts/graphs/stat_graph.js b/app/assets/javascripts/graphs/stat_graph.js index 2e6da5750de..75a53aae33c 100644 --- a/app/assets/javascripts/graphs/stat_graph.js +++ b/app/assets/javascripts/graphs/stat_graph.js @@ -15,4 +15,4 @@ return StatGraph; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/graphs/stat_graph_contributors.js b/app/assets/javascripts/graphs/stat_graph_contributors.js index d06a1a5dae4..bbfb467ad50 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors.js @@ -113,4 +113,4 @@ window.d3 = require('d3'); return ContributorsStatGraph; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js index 241249fae63..228771da4ee 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors_graph.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors_graph.js @@ -273,4 +273,4 @@ window.d3 = require('d3'); return ContributorsAuthorGraph; })(ContributorsGraph); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_util.js b/app/assets/javascripts/graphs/stat_graph_contributors_util.js index 29c3163328f..7954c583598 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors_util.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors_util.js @@ -135,4 +135,4 @@ } } }; -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/group_avatar.js b/app/assets/javascripts/group_avatar.js index 10dfd05fe3c..c5cb273c5b2 100644 --- a/app/assets/javascripts/group_avatar.js +++ b/app/assets/javascripts/group_avatar.js @@ -17,4 +17,4 @@ return GroupAvatar; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js index bc88dc2d092..6b937e7fa0f 100644 --- a/app/assets/javascripts/groups_select.js +++ b/app/assets/javascripts/groups_select.js @@ -68,4 +68,4 @@ return GroupsSelect; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js index fa85f9a6c86..a853c3aeb1f 100644 --- a/app/assets/javascripts/header.js +++ b/app/assets/javascripts/header.js @@ -2,7 +2,7 @@ (function() { $(document).on('todo:toggle', function(e, count) { var $todoPendingCount = $('.todos-pending-count'); - $todoPendingCount.text(gl.text.addDelimiter(count)); + $todoPendingCount.text(gl.text.highCountTrim(count)); $todoPendingCount.toggleClass('hidden', count === 0); }); })(); diff --git a/app/assets/javascripts/importer_status.js b/app/assets/javascripts/importer_status.js index 9390136d3d8..34e4a257ff9 100644 --- a/app/assets/javascripts/importer_status.js +++ b/app/assets/javascripts/importer_status.js @@ -78,4 +78,4 @@ new window.ImporterStatus(jobsImportPath, importPath); } }); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js index c77fbb6a1c7..115312d4b83 100644 --- a/app/assets/javascripts/issuable_context.js +++ b/app/assets/javascripts/issuable_context.js @@ -76,4 +76,4 @@ return IssuableContext; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js index c7c744ef61f..de184ab2675 100644 --- a/app/assets/javascripts/issuable_form.js +++ b/app/assets/javascripts/issuable_form.js @@ -156,4 +156,4 @@ return IssuableForm; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js index 1776b3d61f6..52457f70d90 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -3,7 +3,7 @@ require('./flash'); require('vendor/jquery.waitforimages'); -require('vendor/task_list'); +require('./task_list'); (function() { var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; @@ -11,10 +11,16 @@ require('vendor/task_list'); this.Issue = (function() { function Issue() { this.submitNoteForm = bind(this.submitNoteForm, this); - // Prevent duplicate event bindings - this.disableTaskList(); if ($('a.btn-close').length) { - this.initTaskList(); + this.taskList = new gl.TaskList({ + dataType: 'issue', + fieldName: 'description', + selector: '.detail-page-description', + onSuccess: (result) => { + document.querySelector('#task_status').innerText = result.task_status; + document.querySelector('#task_status_short').innerText = result.task_status_short; + } + }); this.initIssueBtnEventListeners(); } this.initMergeRequests(); @@ -22,11 +28,6 @@ require('vendor/task_list'); this.initCanCreateBranch(); } - Issue.prototype.initTaskList = function() { - $('.detail-page-description .js-task-list-container').taskList('enable'); - return $(document).on('tasklist:changed', '.detail-page-description .js-task-list-container', this.updateTaskList); - }; - Issue.prototype.initIssueBtnEventListeners = function() { var _this, issueFailMessage; _this = this; @@ -85,30 +86,6 @@ require('vendor/task_list'); } }; - Issue.prototype.disableTaskList = function() { - $('.detail-page-description .js-task-list-container').taskList('disable'); - return $(document).off('tasklist:changed', '.detail-page-description .js-task-list-container'); - }; - - Issue.prototype.updateTaskList = function() { - var patchData; - patchData = {}; - patchData['issue'] = { - 'description': $('.js-task-list-field', this).val() - }; - return $.ajax({ - type: 'PATCH', - url: $('form.js-issuable-update').attr('action'), - data: patchData, - success: function(issue) { - document.querySelector('#task_status').innerText = issue.task_status; - document.querySelector('#task_status_short').innerText = issue.task_status_short; - } - }); - // TODO (rspeicher): Make the issue description inline-editable like a note so - // that we can re-use its form here - }; - Issue.prototype.initMergeRequests = function() { var $container; $container = $('#merge-requests'); @@ -155,4 +132,4 @@ require('vendor/task_list'); return Issue; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/issue_status_select.js b/app/assets/javascripts/issue_status_select.js index 1d6eff11403..b2cfd3ef2a3 100644 --- a/app/assets/javascripts/issue_status_select.js +++ b/app/assets/javascripts/issue_status_select.js @@ -31,4 +31,4 @@ return IssueStatusSelect; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/labels.js b/app/assets/javascripts/labels.js index 40ad6fc348e..17a3fc1b1e4 100644 --- a/app/assets/javascripts/labels.js +++ b/app/assets/javascripts/labels.js @@ -43,4 +43,4 @@ return Labels; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index e4cf9057e6d..9e2d14c7f87 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -504,4 +504,4 @@ return LabelsSelect; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js index 1c0ea317c1a..08ca9e4fa4d 100644 --- a/app/assets/javascripts/layout_nav.js +++ b/app/assets/javascripts/layout_nav.js @@ -44,4 +44,4 @@ } }); }); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/lib/cropper.js b/app/assets/javascripts/lib/cropper.js index 5221f85ba7a..7862c6797c3 100644 --- a/app/assets/javascripts/lib/cropper.js +++ b/app/assets/javascripts/lib/cropper.js @@ -4,4 +4,4 @@ (function() { -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/lib/raphael.js b/app/assets/javascripts/lib/raphael.js index 5a9a501efe3..ebe1e2ae98d 100644 --- a/app/assets/javascripts/lib/raphael.js +++ b/app/assets/javascripts/lib/raphael.js @@ -6,4 +6,4 @@ (function() { -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/lib/utils/animate.js b/app/assets/javascripts/lib/utils/animate.js index ce090a2e4fd..d93c1d0da59 100644 --- a/app/assets/javascripts/lib/utils/animate.js +++ b/app/assets/javascripts/lib/utils/animate.js @@ -46,4 +46,4 @@ return dfd.promise(); }; })(window); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6 index 764aff51fee..45a1d90a9d9 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js.es6 +++ b/app/assets/javascripts/lib/utils/common_utils.js.es6 @@ -297,4 +297,4 @@ */ w.gl.utils.convertPermissionToBoolean = permission => permission === 'true'; })(window); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js.es6 b/app/assets/javascripts/lib/utils/datetime_utility.js.es6 index f41fa15b147..82dcbdc26c8 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js.es6 +++ b/app/assets/javascripts/lib/utils/datetime_utility.js.es6 @@ -123,4 +123,4 @@ window.dateFormat = require('vendor/date.format'); return Math.floor((date2 - date1) / millisecondsPerDay); }; })(window); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/lib/utils/notify.js b/app/assets/javascripts/lib/utils/notify.js index 6d5979603b9..66f39122a66 100644 --- a/app/assets/javascripts/lib/utils/notify.js +++ b/app/assets/javascripts/lib/utils/notify.js @@ -44,4 +44,4 @@ w.notify = notifyMe; return w.notifyPermissions = notifyPermissions; })(window); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index d9370db0cf2..579d322e3fb 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -1,5 +1,7 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len */ +require('vendor/latinise'); + (function() { (function(w) { var base; @@ -12,6 +14,9 @@ gl.text.addDelimiter = function(text) { return text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : text; }; + gl.text.highCountTrim = function(count) { + return count > 99 ? '99+' : count; + }; gl.text.randomString = function() { return Math.random().toString(36).substring(7); }; @@ -164,8 +169,14 @@ gl.text.pluralize = function(str, count) { return str + (count > 1 || count === 0 ? 's' : ''); }; - return gl.text.truncate = function(string, maxLength) { + gl.text.truncate = function(string, maxLength) { return string.substr(0, (maxLength - 3)) + '...'; }; + gl.text.dasherize = function(str) { + return str.replace(/[_\s]+/g, '-'); + }; + gl.text.slugify = function(str) { + return str.trim().toLowerCase().latinise(); + }; })(window); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/lib/utils/type_utility.js b/app/assets/javascripts/lib/utils/type_utility.js index 6d813d61601..db62e0be324 100644 --- a/app/assets/javascripts/lib/utils/type_utility.js +++ b/app/assets/javascripts/lib/utils/type_utility.js @@ -12,4 +12,4 @@ return (obj != null) && (obj.constructor === Object); }; })(window); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/lib/utils/url_utility.js.es6 b/app/assets/javascripts/lib/utils/url_utility.js.es6 index a1558b371f0..1bc81d2e4a4 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js.es6 +++ b/app/assets/javascripts/lib/utils/url_utility.js.es6 @@ -83,4 +83,4 @@ document.location.href = url; }; })(window); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js index d7137ec63e4..966fcd8ec47 100644 --- a/app/assets/javascripts/line_highlighter.js +++ b/app/assets/javascripts/line_highlighter.js @@ -179,4 +179,4 @@ require('vendor/jquery.scrollTo'); return LineHighlighter; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/logo.js b/app/assets/javascripts/logo.js index 1b0d0768db8..729baa2e1a7 100644 --- a/app/assets/javascripts/logo.js +++ b/app/assets/javascripts/logo.js @@ -4,4 +4,4 @@ window.addEventListener('beforeunload', function() { $('.tanuki-logo').addClass('animate'); }); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/member_expiration_date.js.es6 b/app/assets/javascripts/member_expiration_date.js.es6 index efe7c78a8ec..129d2dc5f0a 100644 --- a/app/assets/javascripts/member_expiration_date.js.es6 +++ b/app/assets/javascripts/member_expiration_date.js.es6 @@ -49,4 +49,4 @@ inputs.each(toggleClearInput); }; -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index e65378cd610..5e01aacf2ba 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -2,7 +2,7 @@ /* global MergeRequestTabs */ require('vendor/jquery.waitforimages'); -require('vendor/task_list'); +require('./task_list'); require('./merge_request_tabs'); (function() { @@ -24,12 +24,18 @@ require('./merge_request_tabs'); }; })(this)); this.initTabs(); - // Prevent duplicate event bindings - this.disableTaskList(); this.initMRBtnListeners(); this.initCommitMessageListeners(); if ($("a.btn-close").length) { - this.initTaskList(); + this.taskList = new gl.TaskList({ + dataType: 'merge_request', + fieldName: 'description', + selector: '.detail-page-description', + onSuccess: (result) => { + document.querySelector('#task_status').innerText = result.task_status; + document.querySelector('#task_status_short').innerText = result.task_status_short; + } + }); } } @@ -50,11 +56,6 @@ require('./merge_request_tabs'); return this.$('.all-commits').removeClass('hide'); }; - MergeRequest.prototype.initTaskList = function() { - $('.detail-page-description .js-task-list-container').taskList('enable'); - return $(document).on('tasklist:changed', '.detail-page-description .js-task-list-container', this.updateTaskList); - }; - MergeRequest.prototype.initMRBtnListeners = function() { var _this; _this = this; @@ -85,30 +86,6 @@ require('./merge_request_tabs'); } }; - MergeRequest.prototype.disableTaskList = function() { - $('.detail-page-description .js-task-list-container').taskList('disable'); - return $(document).off('tasklist:changed', '.detail-page-description .js-task-list-container'); - }; - - MergeRequest.prototype.updateTaskList = function() { - var patchData; - patchData = {}; - patchData['merge_request'] = { - 'description': $('.js-task-list-field', this).val() - }; - return $.ajax({ - type: 'PATCH', - url: $('form.js-issuable-update').attr('action'), - data: patchData, - success: function(mergeRequest) { - document.querySelector('#task_status').innerText = mergeRequest.task_status; - document.querySelector('#task_status_short').innerText = mergeRequest.task_status_short; - } - }); - // TODO (rspeicher): Make the merge request description inline-editable like a - // note so that we can re-use its form here - }; - MergeRequest.prototype.initCommitMessageListeners = function() { $(document).on('click', 'a.js-with-description-link', function(e) { var textarea = $('textarea.js-commit-message'); @@ -131,4 +108,4 @@ require('./merge_request_tabs'); return MergeRequest; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index 4ab33420e59..88f08bbaa34 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -252,7 +252,6 @@ require('./smart_interval'); $('.ci_widget.ci-error').show(); this.setMergeButtonClass('btn-danger'); } - this.initMiniPipelineGraph(); }; MergeRequestWidget.prototype.showCICoverage = function(coverage) { diff --git a/app/assets/javascripts/merged_buttons.js b/app/assets/javascripts/merged_buttons.js index 527cdc9b698..9548a98f499 100644 --- a/app/assets/javascripts/merged_buttons.js +++ b/app/assets/javascripts/merged_buttons.js @@ -42,4 +42,4 @@ return MergedButtons; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/milestone.js b/app/assets/javascripts/milestone.js index 051cb9fe5c5..7fbaeec7882 100644 --- a/app/assets/javascripts/milestone.js +++ b/app/assets/javascripts/milestone.js @@ -172,4 +172,4 @@ return Milestone; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 2f08aa7fe8b..8df1c8e7f94 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -199,4 +199,4 @@ return MilestoneSelect; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 b/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 index 919fcd0a07b..2145e531331 100644 --- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 +++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 @@ -28,7 +28,7 @@ * All dropdown events are fired at the .dropdown-menu's parent element. */ bindEvents() { - $(document).on('shown.bs.dropdown', this.container, this.getBuildsList); + $(document).off('shown.bs.dropdown', this.container).on('shown.bs.dropdown', this.container, this.getBuildsList); } /** diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js index 2ae5617206e..b98e6121967 100644 --- a/app/assets/javascripts/namespace_select.js +++ b/app/assets/javascripts/namespace_select.js @@ -83,4 +83,4 @@ return NamespaceSelects; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js index a7ccd03b60c..43dc9838977 100644 --- a/app/assets/javascripts/network/branch_graph.js +++ b/app/assets/javascripts/network/branch_graph.js @@ -421,4 +421,4 @@ y: h }); }; -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/network/network.js b/app/assets/javascripts/network/network.js index 37bf6436fd1..8e7027b44e7 100644 --- a/app/assets/javascripts/network/network.js +++ b/app/assets/javascripts/network/network.js @@ -17,4 +17,4 @@ return Network; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/network/network_bundle.js b/app/assets/javascripts/network/network_bundle.js index b4491354472..e5947586583 100644 --- a/app/assets/javascripts/network/network_bundle.js +++ b/app/assets/javascripts/network/network_bundle.js @@ -2,9 +2,8 @@ /* global Network */ /* global ShortcutsNetwork */ -// require everything else in this directory -function requireAll(context) { return context.keys().map(context); } -requireAll(require.context('.', false, /^\.\/(?!network_bundle).*\.(js|es6)$/)); +require('./branch_graph'); +require('./network'); (function() { $(function() { @@ -19,4 +18,4 @@ requireAll(require.context('.', false, /^\.\/(?!network_bundle).*\.(js|es6)$/)); }); return new ShortcutsNetwork(network_graph.branch_graph); }); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js index 7f763c13b50..cb24f212c66 100644 --- a/app/assets/javascripts/new_branch_form.js +++ b/app/assets/javascripts/new_branch_form.js @@ -100,4 +100,4 @@ return NewBranchForm; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/new_commit_form.js b/app/assets/javascripts/new_commit_form.js index 41eea78a3e6..747f693726e 100644 --- a/app/assets/javascripts/new_commit_form.js +++ b/app/assets/javascripts/new_commit_form.js @@ -30,4 +30,4 @@ return NewCommitForm; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index 0464b895d6d..03504255bda 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -11,7 +11,7 @@ require('./dropzone_input'); require('./gfm_auto_complete'); require('vendor/jquery.caret'); // required by jquery.atwho require('vendor/jquery.atwho'); -require('vendor/task_list'); +require('./task_list'); (function() { var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; @@ -51,7 +51,11 @@ require('vendor/task_list'); this.addBinding(); this.setPollingInterval(); this.setupMainTargetNoteForm(); - this.initTaskList(); + this.taskList = new gl.TaskList({ + dataType: 'note', + fieldName: 'note', + selector: '.notes' + }); this.collapseLongCommitList(); // We are in the Merge Requests page so we need another edit form for Changes tab @@ -125,8 +129,6 @@ require('vendor/task_list'); $(document).off("keydown", ".js-note-text"); $(document).off('click', '.js-comment-resolve-button'); $(document).off("click", '.system-note-commit-list-toggler'); - $('.note .js-task-list-container').taskList('disable'); - return $(document).off('tasklist:changed', '.note .js-task-list-container'); }; Notes.prototype.keydownNoteText = function(e) { @@ -286,7 +288,7 @@ require('vendor/task_list'); // Update datetime format on the recent note gl.utils.localTimeAgo($notesList.find("#note_" + note.id + " .js-timeago"), false); this.collapseLongCommitList(); - this.initTaskList(); + this.taskList.init(); this.refresh(); return this.updateNotesCount(1); } @@ -863,15 +865,6 @@ require('vendor/task_list'); } }; - Notes.prototype.initTaskList = function() { - this.enableTaskList(); - return $(document).on('tasklist:changed', '.note .js-task-list-container', this.updateTaskList.bind(this)); - }; - - Notes.prototype.enableTaskList = function() { - return $('.note .js-task-list-container').taskList('enable'); - }; - Notes.prototype.putEditFormInPlace = function($el) { var $editForm = $(this.getEditFormSelector($el)); var $note = $el.closest('.note'); @@ -896,17 +889,6 @@ require('vendor/task_list'); $editForm.find('.referenced-users').hide(); }; - Notes.prototype.updateTaskList = function(e) { - var $target = $(e.target); - var $list = $target.closest('.js-task-list-container'); - var $editForm = $(this.getEditFormSelector($target)); - var $note = $list.closest('.note'); - - this.putEditFormInPlace($list); - $editForm.find('#note_note').val($note.find('.original-task-list').val()); - $('form', $list).submit(); - }; - Notes.prototype.updateNotesCount = function(updateCount) { return this.notesCountBadge.text(parseInt(this.notesCountBadge.text(), 10) + updateCount); }; @@ -955,4 +937,4 @@ require('vendor/task_list'); return Notes; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js index 926dc35fee8..838356133cd 100644 --- a/app/assets/javascripts/notifications_dropdown.js +++ b/app/assets/javascripts/notifications_dropdown.js @@ -28,4 +28,4 @@ return NotificationsDropdown; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/notifications_form.js b/app/assets/javascripts/notifications_form.js index c3d7cc0adfb..5005af90d48 100644 --- a/app/assets/javascripts/notifications_form.js +++ b/app/assets/javascripts/notifications_form.js @@ -54,4 +54,4 @@ return NotificationsForm; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/project.js b/app/assets/javascripts/project.js index 71719917d0c..7c03c8b72d4 100644 --- a/app/assets/javascripts/project.js +++ b/app/assets/javascripts/project.js @@ -126,4 +126,4 @@ return Project; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/project_avatar.js b/app/assets/javascripts/project_avatar.js index a6d3ba9eb86..aabdfbf65e2 100644 --- a/app/assets/javascripts/project_avatar.js +++ b/app/assets/javascripts/project_avatar.js @@ -17,4 +17,4 @@ return ProjectAvatar; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js index 04fe84683f3..e01668eabef 100644 --- a/app/assets/javascripts/project_find_file.js +++ b/app/assets/javascripts/project_find_file.js @@ -168,4 +168,4 @@ return ProjectFindFile; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/project_fork.js b/app/assets/javascripts/project_fork.js index 208f25a0e33..47197db39d3 100644 --- a/app/assets/javascripts/project_fork.js +++ b/app/assets/javascripts/project_fork.js @@ -10,4 +10,4 @@ return ProjectFork; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/project_import.js b/app/assets/javascripts/project_import.js index d7943959238..08334bf1ec5 100644 --- a/app/assets/javascripts/project_import.js +++ b/app/assets/javascripts/project_import.js @@ -10,4 +10,4 @@ return ProjectImport; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/project_label_subscription.js.es6 b/app/assets/javascripts/project_label_subscription.js.es6 index 8365f7118d5..0a811627600 100644 --- a/app/assets/javascripts/project_label_subscription.js.es6 +++ b/app/assets/javascripts/project_label_subscription.js.es6 @@ -38,13 +38,15 @@ this.$buttons.attr('data-status', newStatus); this.$buttons.find('> span').text(newAction); - for (const button of this.$buttons) { + this.$buttons.map((button) => { const $button = $(button); if ($button.attr('data-original-title')) { $button.tooltip('hide').attr('data-original-title', newAction).tooltip('fixTitle'); } - } + + return button; + }); }); } } diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js index 3aa6f6771ce..e9927c1bf51 100644 --- a/app/assets/javascripts/project_new.js +++ b/app/assets/javascripts/project_new.js @@ -101,4 +101,4 @@ return ProjectNew; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js index 7b5e9953598..f80e765ce30 100644 --- a/app/assets/javascripts/project_select.js +++ b/app/assets/javascripts/project_select.js @@ -101,4 +101,4 @@ return ProjectSelect; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/project_show.js b/app/assets/javascripts/project_show.js index aad130cf267..3a51c1f26ac 100644 --- a/app/assets/javascripts/project_show.js +++ b/app/assets/javascripts/project_show.js @@ -6,6 +6,6 @@ return ProjectShow; })(); -}).call(this); +}).call(window); // I kept class for future diff --git a/app/assets/javascripts/projects_list.js b/app/assets/javascripts/projects_list.js index 69a11dfaf39..acdf9b7eb5a 100644 --- a/app/assets/javascripts/projects_list.js +++ b/app/assets/javascripts/projects_list.js @@ -47,4 +47,4 @@ }); } }; -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/render_gfm.js b/app/assets/javascripts/render_gfm.js index bdbad93ad04..48cae8a4fa9 100644 --- a/app/assets/javascripts/render_gfm.js +++ b/app/assets/javascripts/render_gfm.js @@ -12,4 +12,4 @@ $(document).on('ready load', function() { return $('body').renderGFM(); }); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/render_math.js b/app/assets/javascripts/render_math.js index 6cef449babf..76c61c001ba 100644 --- a/app/assets/javascripts/render_math.js +++ b/app/assets/javascripts/render_math.js @@ -51,4 +51,4 @@ }); } }; -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index 76a0f993ea0..903862cac6b 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -21,11 +21,16 @@ }; Sidebar.prototype.addEventListeners = function() { + const $document = $(document); + const throttledSetSidebarHeight = _.throttle(this.setSidebarHeight, 10); + this.sidebar.on('click', '.sidebar-collapsed-icon', this, this.sidebarCollapseClicked); $('.dropdown').on('hidden.gl.dropdown', this, this.onSidebarDropdownHidden); $('.dropdown').on('loading.gl.dropdown', this.sidebarDropdownLoading); $('.dropdown').on('loaded.gl.dropdown', this.sidebarDropdownLoaded); - $(document).on('click', '.js-sidebar-toggle', function(e, triggered) { + $(window).on('resize', () => throttledSetSidebarHeight()); + $document.on('scroll', () => throttledSetSidebarHeight()); + $document.on('click', '.js-sidebar-toggle', function(e, triggered) { var $allGutterToggleIcons, $this, $thisIcon; e.preventDefault(); $this = $(this); @@ -191,6 +196,17 @@ } }; + Sidebar.prototype.setSidebarHeight = function() { + const $navHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight(); + const $rightSidebar = $('.js-right-sidebar'); + const diff = $navHeight - $('body').scrollTop(); + if (diff > 0) { + $rightSidebar.outerHeight($(window).height() - diff); + } else { + $rightSidebar.outerHeight('100%'); + } + }; + Sidebar.prototype.isOpen = function() { return this.sidebar.is('.right-sidebar-expanded'); }; @@ -201,4 +217,4 @@ return Sidebar; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js index b1c0dc37b4d..e66418beeab 100644 --- a/app/assets/javascripts/search.js +++ b/app/assets/javascripts/search.js @@ -97,4 +97,4 @@ return Search; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js.es6 index 6250e75d407..6fd5345a0a6 100644 --- a/app/assets/javascripts/search_autocomplete.js.es6 +++ b/app/assets/javascripts/search_autocomplete.js.es6 @@ -169,10 +169,10 @@ url: issuesPath + "/?author_username=" + userName }, 'separator', { text: 'Merge requests assigned to me', - url: mrPath + "/?assignee_id=" + userId + url: mrPath + "/?assignee_username=" + userName }, { text: "Merge requests I've created", - url: mrPath + "/?author_id=" + userId + url: mrPath + "/?author_username=" + userName } ]; if (!name) { diff --git a/app/assets/javascripts/shortcuts.js b/app/assets/javascripts/shortcuts.js index c6d9b007ad1..81766f4bd55 100644 --- a/app/assets/javascripts/shortcuts.js +++ b/app/assets/javascripts/shortcuts.js @@ -97,4 +97,4 @@ } }; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/shortcuts_dashboard_navigation.js b/app/assets/javascripts/shortcuts_dashboard_navigation.js index 7378b322426..e7baea894f6 100644 --- a/app/assets/javascripts/shortcuts_dashboard_navigation.js +++ b/app/assets/javascripts/shortcuts_dashboard_navigation.js @@ -37,4 +37,4 @@ require('./shortcuts'); return ShortcutsDashboardNavigation; })(Shortcuts); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/shortcuts_find_file.js b/app/assets/javascripts/shortcuts_find_file.js index 36e379d634d..a27ac264a5c 100644 --- a/app/assets/javascripts/shortcuts_find_file.js +++ b/app/assets/javascripts/shortcuts_find_file.js @@ -35,4 +35,4 @@ require('./shortcuts_navigation'); return ShortcutsFindFile; })(ShortcutsNavigation); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js index b841abb754d..fe58e98cee5 100644 --- a/app/assets/javascripts/shortcuts_issuable.js +++ b/app/assets/javascripts/shortcuts_issuable.js @@ -89,4 +89,4 @@ require('./shortcuts_navigation'); return ShortcutsIssuable; })(ShortcutsNavigation); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/shortcuts_navigation.js b/app/assets/javascripts/shortcuts_navigation.js index cb5f2c53ea6..542cd586df0 100644 --- a/app/assets/javascripts/shortcuts_navigation.js +++ b/app/assets/javascripts/shortcuts_navigation.js @@ -65,4 +65,4 @@ require('./shortcuts'); return ShortcutsNavigation; })(Shortcuts); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/shortcuts_network.js b/app/assets/javascripts/shortcuts_network.js index 651957f5325..4c2bf8bf001 100644 --- a/app/assets/javascripts/shortcuts_network.js +++ b/app/assets/javascripts/shortcuts_network.js @@ -25,4 +25,4 @@ require('./shortcuts_navigation'); return ShortcutsNetwork; })(ShortcutsNavigation); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/sidebar.js.es6 b/app/assets/javascripts/sidebar.js.es6 deleted file mode 100644 index 33e4b7db681..00000000000 --- a/app/assets/javascripts/sidebar.js.es6 +++ /dev/null @@ -1,111 +0,0 @@ -/* eslint-disable arrow-parens, class-methods-use-this, no-param-reassign */ -/* global Cookies */ - -(() => { - const pinnedStateCookie = 'pin_nav'; - const sidebarBreakpoint = 1024; - - const pageSelector = '.page-with-sidebar'; - const navbarSelector = '.navbar-gitlab'; - const sidebarWrapperSelector = '.sidebar-wrapper'; - const sidebarContentSelector = '.nav-sidebar'; - - const pinnedToggleSelector = '.js-nav-pin'; - const sidebarToggleSelector = '.toggle-nav-collapse, .side-nav-toggle'; - - const pinnedPageClass = 'page-sidebar-pinned'; - const expandedPageClass = 'page-sidebar-expanded'; - - const pinnedNavbarClass = 'header-sidebar-pinned'; - const expandedNavbarClass = 'header-sidebar-expanded'; - - class Sidebar { - constructor() { - if (!Sidebar.singleton) { - Sidebar.singleton = this; - Sidebar.singleton.init(); - } - - return Sidebar.singleton; - } - - init() { - this.isPinned = Cookies.get(pinnedStateCookie) === 'true'; - this.isExpanded = ( - window.innerWidth >= sidebarBreakpoint && - $(pageSelector).hasClass(expandedPageClass) - ); - $(window).on('resize', () => this.setSidebarHeight()); - $(document) - .on('click', sidebarToggleSelector, () => this.toggleSidebar()) - .on('click', pinnedToggleSelector, () => this.togglePinnedState()) - .on('click', 'html, body, a, button', (e) => this.handleClickEvent(e)) - .on('DOMContentLoaded', () => this.renderState()) - .on('scroll', () => this.setSidebarHeight()) - .on('todo:toggle', (e, count) => this.updateTodoCount(count)); - this.renderState(); - this.setSidebarHeight(); - } - - handleClickEvent(e) { - if (this.isExpanded && (!this.isPinned || window.innerWidth < sidebarBreakpoint)) { - const $target = $(e.target); - const targetIsToggle = $target.closest(sidebarToggleSelector).length > 0; - const targetIsSidebar = $target.closest(sidebarWrapperSelector).length > 0; - if (!targetIsToggle && (!targetIsSidebar || $target.closest('a'))) { - this.toggleSidebar(); - } - } - } - - updateTodoCount(count) { - $('.js-todos-count').text(gl.text.addDelimiter(count)); - } - - toggleSidebar() { - this.isExpanded = !this.isExpanded; - this.renderState(); - } - - setSidebarHeight() { - const $navHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight(); - const diff = $navHeight - $('body').scrollTop(); - if (diff > 0) { - $('.js-right-sidebar').outerHeight($(window).height() - diff); - } else { - $('.js-right-sidebar').outerHeight('100%'); - } - } - - togglePinnedState() { - this.isPinned = !this.isPinned; - if (!this.isPinned) { - this.isExpanded = false; - } - Cookies.set(pinnedStateCookie, this.isPinned ? 'true' : 'false', { expires: 3650 }); - this.renderState(); - } - - renderState() { - $(pageSelector) - .toggleClass(pinnedPageClass, this.isPinned && this.isExpanded) - .toggleClass(expandedPageClass, this.isExpanded); - $(navbarSelector) - .toggleClass(pinnedNavbarClass, this.isPinned && this.isExpanded) - .toggleClass(expandedNavbarClass, this.isExpanded); - - const $pinnedToggle = $(pinnedToggleSelector); - const tooltipText = this.isPinned ? 'Unpin navigation' : 'Pin navigation'; - const tooltipState = $pinnedToggle.attr('aria-describedby') && this.isExpanded ? 'show' : 'hide'; - $pinnedToggle.attr('title', tooltipText).tooltip('fixTitle').tooltip(tooltipState); - - if (this.isExpanded) { - const sidebarContent = $(sidebarContentSelector); - setTimeout(() => { sidebarContent.niceScroll().updateScrollBar(); }, 200); - } - } - } - - window.gl = window.gl || {}; - gl.Sidebar = Sidebar; -})(); diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js index 3ee0c73a8d2..294d087554e 100644 --- a/app/assets/javascripts/single_file_diff.js +++ b/app/assets/javascripts/single_file_diff.js @@ -95,4 +95,4 @@ } }); }; -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/snippet/snippet_bundle.js b/app/assets/javascripts/snippet/snippet_bundle.js index 64f9065be42..89822246bb8 100644 --- a/app/assets/javascripts/snippet/snippet_bundle.js +++ b/app/assets/javascripts/snippet/snippet_bundle.js @@ -13,4 +13,4 @@ requireAll(require.context('.', false, /^\.\/(?!snippet_bundle).*\.(js|es6)$/)); $(".snippet-file-content").val(editor.getValue()); }); }); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js index 531fd0e9c32..c75b44cc2fd 100644 --- a/app/assets/javascripts/star.js +++ b/app/assets/javascripts/star.js @@ -27,4 +27,4 @@ return Star; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/subscription_select.js b/app/assets/javascripts/subscription_select.js index 187356f0bf9..8b25f43ffc7 100644 --- a/app/assets/javascripts/subscription_select.js +++ b/app/assets/javascripts/subscription_select.js @@ -31,4 +31,4 @@ return SubscriptionSelect; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/syntax_highlight.js b/app/assets/javascripts/syntax_highlight.js index 115716bff6a..7c063fae045 100644 --- a/app/assets/javascripts/syntax_highlight.js +++ b/app/assets/javascripts/syntax_highlight.js @@ -24,4 +24,4 @@ } } }; -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/task_list.js b/app/assets/javascripts/task_list.js new file mode 100644 index 00000000000..dfe24d1fb33 --- /dev/null +++ b/app/assets/javascripts/task_list.js @@ -0,0 +1,40 @@ +require('vendor/task_list'); + +class TaskList { + constructor(options = {}) { + this.selector = options.selector; + this.dataType = options.dataType; + this.fieldName = options.fieldName; + this.onSuccess = options.onSuccess || (() => {}); + this.init(); + } + + init() { + // Prevent duplicate event bindings + this.disable(); + $(`${this.selector} .js-task-list-container`).taskList('enable'); + $(document).on('tasklist:changed', `${this.selector} .js-task-list-container`, this.update.bind(this)); + } + + disable() { + $(`${this.selector} .js-task-list-container`).taskList('disable'); + $(document).off('tasklist:changed', `${this.selector} .js-task-list-container`); + } + + update(e) { + const $target = $(e.target); + const patchData = {}; + patchData[this.dataType] = { + [this.fieldName]: $target.val(), + }; + return $.ajax({ + type: 'PATCH', + url: $target.data('update-url') || $('form.js-issuable-update').attr('action'), + data: patchData, + success: this.onSuccess, + }); + } +} + +window.gl = window.gl || {}; +window.gl.TaskList = TaskList; diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6 index ded683f2ca1..e9513725d9d 100644 --- a/app/assets/javascripts/todos.js.es6 +++ b/app/assets/javascripts/todos.js.es6 @@ -1,28 +1,34 @@ -/* eslint-disable class-methods-use-this, no-new, func-names, prefer-template, no-unneeded-ternary, object-shorthand, space-before-function-paren, comma-dangle, quote-props, consistent-return, no-else-return, no-param-reassign, max-len */ +/* eslint-disable class-methods-use-this, no-new, func-names, no-unneeded-ternary, object-shorthand, quote-props, no-param-reassign, max-len */ /* global UsersSelect */ ((global) => { class Todos { - constructor({ el } = {}) { - this.allDoneClicked = this.allDoneClicked.bind(this); - this.doneClicked = this.doneClicked.bind(this); - this.el = el || $('.js-todos-options'); - this.perPage = this.el.data('perPage'); - this.clearListeners(); - this.initBtnListeners(); + constructor() { this.initFilters(); + this.bindEvents(); + + this.cleanupWrapper = this.cleanup.bind(this); + document.addEventListener('beforeunload', this.cleanupWrapper); } - clearListeners() { - $('.done-todo').off('click'); - $('.js-todos-mark-all').off('click'); - return $('.todo').off('click'); + cleanup() { + this.unbindEvents(); + document.removeEventListener('beforeunload', this.cleanupWrapper); } - initBtnListeners() { - $('.done-todo').on('click', this.doneClicked); - $('.js-todos-mark-all').on('click', this.allDoneClicked); - return $('.todo').on('click', this.goToTodoUrl); + unbindEvents() { + $('.js-done-todo, .js-undo-todo').off('click', this.updateStateClickedWrapper); + $('.js-todos-mark-all').off('click', this.allDoneClickedWrapper); + $('.todo').off('click', this.goToTodoUrl); + } + + bindEvents() { + this.updateStateClickedWrapper = this.updateStateClicked.bind(this); + this.allDoneClickedWrapper = this.allDoneClicked.bind(this); + + $('.js-done-todo, .js-undo-todo').on('click', this.updateStateClickedWrapper); + $('.js-todos-mark-all').on('click', this.allDoneClickedWrapper); + $('.todo').on('click', this.goToTodoUrl); } initFilters() { @@ -33,7 +39,7 @@ $('form.filter-form').on('submit', function (event) { event.preventDefault(); - gl.utils.visitUrl(this.action + '&' + $(this).serialize()); + gl.utils.visitUrl(`${this.action}&${$(this).serialize()}`); }); } @@ -44,105 +50,72 @@ filterable: searchFields ? true : false, search: { fields: searchFields }, data: $dropdown.data('data'), - clicked: function() { + clicked: function () { return $dropdown.closest('form.filter-form').submit(); - } + }, }); } - doneClicked(e) { + updateStateClicked(e) { e.preventDefault(); - e.stopImmediatePropagation(); - const $target = $(e.currentTarget); - $target.disable(); - return $.ajax({ + const target = e.target; + target.setAttribute('disabled', ''); + target.classList.add('disabled'); + $.ajax({ type: 'POST', - url: $target.attr('href'), + url: target.getAttribute('href'), dataType: 'json', data: { - '_method': 'delete' + '_method': target.getAttribute('data-method'), }, success: (data) => { - this.redirectIfNeeded(data.count); - this.clearDone($target.closest('li')); - return this.updateBadges(data); - } + this.updateState(target); + this.updateBadges(data); + }, }); } allDoneClicked(e) { e.preventDefault(); - e.stopImmediatePropagation(); const $target = $(e.currentTarget); $target.disable(); - return $.ajax({ + $.ajax({ type: 'POST', url: $target.attr('href'), dataType: 'json', data: { - '_method': 'delete' + '_method': 'delete', }, success: (data) => { $target.remove(); $('.js-todos-all').html('<div class="nothing-here-block">You\'re all done!</div>'); - return this.updateBadges(data); - } + this.updateBadges(data); + }, }); } - clearDone($row) { - const $ul = $row.closest('ul'); - $row.remove(); - if (!$ul.find('li').length) { - return $ul.parents('.panel').remove(); + updateState(target) { + const row = target.closest('li'); + const restoreBtn = row.querySelector('.js-undo-todo'); + const doneBtn = row.querySelector('.js-done-todo'); + + target.removeAttribute('disabled'); + target.classList.remove('disabled'); + target.classList.add('hidden'); + + if (target === doneBtn) { + row.classList.add('done-reversible'); + restoreBtn.classList.remove('hidden'); + } else { + row.classList.remove('done-reversible'); + doneBtn.classList.remove('hidden'); } } updateBadges(data) { $(document).trigger('todo:toggle', data.count); $('.todos-pending .badge').text(data.count); - return $('.todos-done .badge').text(data.done_count); - } - - getTotalPages() { - return this.el.data('totalPages'); - } - - getCurrentPage() { - return this.el.data('currentPage'); - } - - getTodosPerPage() { - return this.el.data('perPage'); - } - - redirectIfNeeded(total) { - const currPages = this.getTotalPages(); - const currPage = this.getCurrentPage(); - - // Refresh if no remaining Todos - if (!total) { - window.location.reload(); - return; - } - // Do nothing if no pagination - if (!currPages) { - return; - } - - const newPages = Math.ceil(total / this.getTodosPerPage()); - let url = location.href; - - if (newPages !== currPages) { - // Redirect to previous page if there's one available - if (currPages > 1 && currPage === currPages) { - const pageParams = { - page: currPages - 1 - }; - url = gl.utils.mergeUrlParams(pageParams, url); - } - return gl.utils.visitUrl(url); - } + $('.todos-done .badge').text(data.done_count); } goToTodoUrl(e) { @@ -159,12 +132,12 @@ if (selected.tagName === 'IMG') { const avatarUrl = selected.parentElement.getAttribute('href'); - return window.open(avatarUrl, windowTarget); + window.open(avatarUrl, windowTarget); } else { - return window.open(todoLink, windowTarget); + window.open(todoLink, windowTarget); } } else { - return gl.utils.visitUrl(todoLink); + gl.utils.visitUrl(todoLink); } } } diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js index b1b35fdbd6c..76a821c7a17 100644 --- a/app/assets/javascripts/tree.js +++ b/app/assets/javascripts/tree.js @@ -65,4 +65,4 @@ return TreeView; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/u2f/error.js b/app/assets/javascripts/u2f/error.js index 86b459e1866..fd1829efe18 100644 --- a/app/assets/javascripts/u2f/error.js +++ b/app/assets/javascripts/u2f/error.js @@ -24,4 +24,4 @@ return U2FError; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/u2f/register.js b/app/assets/javascripts/u2f/register.js index 69d1ff3a39e..17631f2908d 100644 --- a/app/assets/javascripts/u2f/register.js +++ b/app/assets/javascripts/u2f/register.js @@ -95,4 +95,4 @@ return U2FRegister; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/u2f/util.js b/app/assets/javascripts/u2f/util.js index 34e88220b12..813d363db00 100644 --- a/app/assets/javascripts/u2f/util.js +++ b/app/assets/javascripts/u2f/util.js @@ -9,4 +9,4 @@ return U2FUtil; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js index 6e40dfdf3d8..5111b260e1c 100644 --- a/app/assets/javascripts/users/calendar.js +++ b/app/assets/javascripts/users/calendar.js @@ -221,4 +221,4 @@ return Calendar; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index d4b24d13299..de33a31b411 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -432,4 +432,4 @@ return UsersSelect; })(); -}).call(this); +}).call(window); diff --git a/app/assets/javascripts/version_check_image.js.es6 b/app/assets/javascripts/version_check_image.js.es6 index 1fa2b5ac399..d4f716acb72 100644 --- a/app/assets/javascripts/version_check_image.js.es6 +++ b/app/assets/javascripts/version_check_image.js.es6 @@ -1,10 +1,10 @@ -(() => { - class VersionCheckImage { - static bindErrorEvent(imageElement) { - imageElement.off('error').on('error', () => imageElement.hide()); - } +class VersionCheckImage { + static bindErrorEvent(imageElement) { + imageElement.off('error').on('error', () => imageElement.hide()); } +} - window.gl = window.gl || {}; - gl.VersionCheckImage = VersionCheckImage; -})(); +window.gl = window.gl || {}; +gl.VersionCheckImage = VersionCheckImage; + +module.exports = VersionCheckImage; diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 83e045c6d3d..9d66d28cc62 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -29,7 +29,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s }, props: ['scope', 'store', 'svgs'], created() { - const pagenum = gl.utils.getParameterByName('p'); + const pagenum = gl.utils.getParameterByName('page'); const scope = gl.utils.getParameterByName('scope'); if (pagenum) this.pagenum = pagenum; if (scope) this.apiScope = scope; @@ -44,7 +44,6 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s }, methods: { - /** * Changes the URL according to the pagination component. * @@ -57,7 +56,7 @@ const CommitPipelinesStoreWithTimeAgo = require('../commit/pipelines/pipelines_s */ change(pagenum, apiScope) { if (!apiScope) apiScope = 'all'; - gl.utils.visitUrl(`?scope=${apiScope}&p=${pagenum}`); + gl.utils.visitUrl(`?scope=${apiScope}&page=${pagenum}`); }, }, template: ` diff --git a/app/assets/javascripts/wikis.js.es6 b/app/assets/javascripts/wikis.js.es6 index ef99b2e92f0..75fd1394a03 100644 --- a/app/assets/javascripts/wikis.js.es6 +++ b/app/assets/javascripts/wikis.js.es6 @@ -1,14 +1,10 @@ /* eslint-disable no-param-reassign */ /* global Breakpoints */ -require('vendor/latinise'); require('./breakpoints'); require('vendor/jquery.nicescroll'); ((global) => { - const dasherize = str => str.replace(/[_\s]+/g, '-'); - const slugify = str => dasherize(str.trim().toLowerCase().latinise()); - class Wikis { constructor() { this.bp = Breakpoints.get(); @@ -34,7 +30,7 @@ require('vendor/jquery.nicescroll'); if (!this.newWikiForm) return; const slugInput = this.newWikiForm.querySelector('#new_wiki_path'); - const slug = slugify(slugInput.value); + const slug = gl.text.slugify(slugInput.value); if (slug.length > 0) { const wikisPath = slugInput.getAttribute('data-wikis-path'); diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js index d9261cda1b1..ce626cf7b46 100644 --- a/app/assets/javascripts/zen_mode.js +++ b/app/assets/javascripts/zen_mode.js @@ -94,4 +94,4 @@ require('mousetrap/plugins/pause/mousetrap-pause'); return ZenMode; })(); -}).call(this); +}).call(window); diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 08f203a1bf6..39cf3b5f8ae 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -19,7 +19,6 @@ @import "framework/flash.scss"; @import "framework/forms.scss"; @import "framework/gfm.scss"; -@import "framework/gitlab-theme.scss"; @import "framework/header.scss"; @import "framework/highlight.scss"; @import "framework/issue_box.scss"; diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss index 0ca5a9343f7..90935b9616b 100644 --- a/app/assets/stylesheets/framework/animations.scss +++ b/app/assets/stylesheets/framework/animations.scss @@ -116,7 +116,7 @@ } .btn, -.side-nav-toggle { +.global-dropdown-toggle { @include transition(background-color, border-color, color, box-shadow); } @@ -140,7 +140,6 @@ a { @include transition(background-color, box-shadow); } -.nav-sidebar a, .dropdown-menu a, .dropdown-menu button, .dropdown-menu-nav a { diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss deleted file mode 100644 index d6566dc4ec9..00000000000 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Styles the GitLab application with a specific color theme - * - * $color-light - - * $color - - * $color-darker - - * $color-dark - - */ -@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) { - .page-with-sidebar { - .toggle-nav-collapse, - .pin-nav-btn { - color: $color-light; - - &:hover { - color: $white-light; - } - } - - .sidebar-wrapper { - background: $color-darker; - } - - .sidebar-action-buttons { - color: $color-light; - background-color: lighten($color-darker, 5%); - } - - .nav-sidebar { - li { - a { - color: $color-light; - - &:hover, - &:focus, - &:active { - background: $color-dark; - } - - i { - color: $color-light; - } - - path, - polygon { - fill: $color-light; - } - - .count { - color: $color-light; - background: $color-dark; - } - - svg { - position: relative; - top: 3px; - } - } - - &.separate-item { - border-top: 1px solid $color; - } - - &.active a { - color: $white-light; - background: $color-dark; - - &.no-highlight { - border: none; - } - - i { - color: $white-light; - } - - path, - polygon { - fill: $white-light; - } - } - } - - .about-gitlab { - color: $color-light; - } - } - } -} - -$theme-charcoal-light: #b9bbbe; -$theme-charcoal: #485157; -$theme-charcoal-dark: #3d454d; -$theme-charcoal-darker: #383f45; - -$theme-blue-light: #becde9; -$theme-blue: #2980b9; -$theme-blue-dark: #1970a9; -$theme-blue-darker: #096099; - -$theme-graphite-light: #ccc; -$theme-graphite: #777; -$theme-graphite-dark: #666; -$theme-graphite-darker: #555; - -$theme-black-light: #979797; -$theme-black: #373737; -$theme-black-dark: #272727; -$theme-black-darker: #222; - -$theme-green-light: #adc; -$theme-green: #019875; -$theme-green-dark: #018865; -$theme-green-darker: #017855; - -$theme-violet-light: #98c; -$theme-violet: #548; -$theme-violet-dark: #436; -$theme-violet-darker: #325; - -body { - &.ui_blue { - @include gitlab-theme($theme-blue-light, $theme-blue, $theme-blue-dark, $theme-blue-darker); - } - - &.ui_charcoal { - @include gitlab-theme($theme-charcoal-light, $theme-charcoal, $theme-charcoal-dark, $theme-charcoal-darker); - } - - &.ui_graphite { - @include gitlab-theme($theme-graphite-light, $theme-graphite, $theme-graphite-dark, $theme-graphite-darker); - } - - &.ui_black { - @include gitlab-theme($theme-black-light, $theme-black, $theme-black-dark, $theme-black-darker); - } - - &.ui_green { - @include gitlab-theme($theme-green-light, $theme-green, $theme-green-dark, $theme-green-darker); - } - - &.ui_violet { - @include gitlab-theme($theme-violet-light, $theme-violet, $theme-violet-dark, $theme-violet-darker); - } -} diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 34e010e0e8a..3945a789c82 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -100,23 +100,40 @@ header { } } } + } - .side-nav-toggle { - position: absolute; - left: -10px; - margin: 7px 0; - font-size: 18px; - padding: 6px 10px; - border: none; - background-color: $gray-light; + .global-dropdown { + position: absolute; + left: -10px; - &:hover { - background-color: $white-normal; - color: $gl-header-nav-hover-color; + .badge { + font-size: 11px; + } + + li { + &.active a { + font-weight: bold; } } } + .global-dropdown-toggle { + margin: 7px 0; + font-size: 18px; + padding: 6px 10px; + border: none; + background-color: $gray-light; + + &:hover { + background-color: $white-normal; + } + + &:focus { + outline: none; + background-color: $white-normal; + } + } + .header-content { position: relative; height: $header-height; diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 2bfdb9f9601..55ed4b7b06c 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -96,16 +96,6 @@ ul.unstyled-list > li { border-bottom: none; } -ul.task-list { - li.task-list-item { - list-style-type: none; - } - - ul:not(.task-list) { - padding-left: 1.3em; - } -} - // Generic content list ul.content-list { @include basic-list; diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 1acd06122a3..df78bbdea51 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -76,6 +76,13 @@ #{$property}: $value; } +/* http://phrappe.com/css/conditional-css-for-webkit-based-browsers/ */ +@mixin on-webkit-only { + @media screen and (-webkit-min-device-pixel-ratio:0) { + @content; + } +} + @mixin keyframes($animation-name) { @-webkit-keyframes #{$animation-name} { @content; diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 20bcb1eeb23..040a7ce0c16 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -1,36 +1,3 @@ -.page-with-sidebar { - padding-bottom: 25px; - transition: padding $sidebar-transition-duration; - - &.page-sidebar-pinned { - .sidebar-wrapper { - box-shadow: none; - } - } - - .sidebar-wrapper { - position: fixed; - top: 0; - bottom: 0; - left: 0; - height: 100%; - width: 0; - overflow: hidden; - transition: width $sidebar-transition-duration; - box-shadow: 2px 0 16px 0 $black-transparent; - } -} - -.sidebar-wrapper { - z-index: 1000; - background: $gray-light; - - .nicescroll-rails-hr { - // TODO: Figure out why nicescroll doesn't hide horizontal bar - display: none!important; - } -} - .content-wrapper { width: 100%; transition: padding $sidebar-transition-duration; @@ -47,105 +14,6 @@ } } -.nav-sidebar { - position: absolute; - top: 50px; - bottom: 0; - width: $sidebar_width; - overflow-y: auto; - overflow-x: hidden; - - &.navbar-collapse { - padding: 0 !important; - } - - li { - &.separate-item { - padding-top: 10px; - margin-top: 10px; - } - - .icon-container { - width: 34px; - display: inline-block; - text-align: center; - } - - a { - padding: 7px $gl-sidebar-padding; - font-size: $gl-font-size; - line-height: 24px; - display: block; - text-decoration: none; - font-weight: normal; - - &:hover, - &:active, - &:focus { - text-decoration: none; - } - - i { - font-size: 16px; - } - - i, - svg { - margin-right: 13px; - } - } - } - - .count { - float: right; - padding: 0 8px; - border-radius: 6px; - } - - .about-gitlab { - padding: 7px $gl-sidebar-padding; - font-size: $gl-font-size; - line-height: 24px; - display: block; - text-decoration: none; - font-weight: normal; - position: absolute; - bottom: 10px; - } -} - -.sidebar-action-buttons { - width: $sidebar_width; - position: absolute; - top: 0; - left: 0; - min-height: 50px; - padding: 5px 0; - font-size: 18px; - line-height: 30px; - - .toggle-nav-collapse { - left: 0; - } - - .pin-nav-btn { - right: 0; - display: none; - - @media (min-width: $sidebar-breakpoint) { - display: block; - } - - .fa { - transition: transform .15s; - - .page-sidebar-pinned & { - transform: rotate(90deg); - } - } - } -} - .nav-header-btn { padding: 10px $gl-sidebar-padding; color: inherit; @@ -161,46 +29,9 @@ } } -.page-sidebar-expanded { - .sidebar-wrapper { - width: $sidebar_width; - } -} - -.page-sidebar-pinned { - .content-wrapper, - .layout-nav { - @media (min-width: $sidebar-breakpoint) { - padding-left: $sidebar_width; - } - } - - .merge-request-tabs-holder.affix { - @media (min-width: $sidebar-breakpoint) { - left: $sidebar_width; - } - } - - &.right-sidebar-expanded { - .line-resolve-all-container { - @media (min-width: $sidebar-breakpoint) { - display: none; - } - } - } -} - -header.header-sidebar-pinned { - @media (min-width: $sidebar-breakpoint) { - padding-left: ($sidebar_width + $gl-padding); - - .side-nav-toggle { - display: none; - } - - .header-content { - padding-left: 0; - } +@media (min-width: $screen-sm-min) { + .content-wrapper { + padding-right: $gutter_collapsed_width; } } @@ -208,12 +39,8 @@ header.header-sidebar-pinned { padding-right: 0; @media (min-width: $screen-sm-min) { - .content-wrapper { - padding-right: $sidebar_collapsed_width; - } - .merge-request-tabs-holder.affix { - right: $sidebar_collapsed_width; + right: $gutter_collapsed_width; } } @@ -229,12 +56,6 @@ header.header-sidebar-pinned { .right-sidebar-expanded { padding-right: 0; - @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { - &:not(.build-sidebar):not(.wiki-sidebar) { - padding-right: $sidebar_collapsed_width; - } - } - @media (min-width: $screen-md-min) { .content-wrapper { padding-right: $gutter_width; @@ -245,12 +66,12 @@ header.header-sidebar-pinned { } &.with-overlay .merge-request-tabs-holder.affix { - right: $sidebar_collapsed_width; + right: $gutter_collapsed_width; } } &.with-overlay { - padding-right: $sidebar_collapsed_width; + padding-right: $gutter_collapsed_width; } } diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 54958973f15..db5e2c51fe7 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -134,7 +134,7 @@ ul, ol { padding: 0; - margin: 3px 0 3px 28px !important; + margin: 3px 0 !important; } ul:dir(rtl), @@ -144,6 +144,29 @@ li { line-height: 1.6em; + margin-left: 25px; + padding-left: 3px; + + /* Normalize the bullet position on webkit. */ + @include on-webkit-only { + margin-left: 28px; + padding-left: 0; + } + } + + ul.task-list { + li.task-list-item { + list-style-type: none; + position: relative; + padding-left: 28px; + margin-left: 0 !important; + + input.task-list-item-checkbox { + position: absolute; + left: 8px; + top: 5px; + } + } } a[href*="/uploads/"], diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 7809d4866f1..ba0af072716 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -1,8 +1,6 @@ /* * Layout */ -$sidebar_collapsed_width: 62px; -$sidebar_width: 220px; $gutter_collapsed_width: 62px; $gutter_width: 290px; $gutter_inner_width: 250px; @@ -541,4 +539,4 @@ Pipeline Graph */ $stage-hover-bg: #eaf3fc; $stage-hover-border: #d1e7fc; -$action-icon-color: #d6d6d6;
\ No newline at end of file +$action-icon-color: #d6d6d6; diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 181dcb7721f..f789ae1ccd3 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -35,7 +35,6 @@ display: table-cell; } - .environments-name, .environments-commit, .environments-actions { width: 20%; @@ -45,6 +44,7 @@ width: 10%; } + .environments-name, .environments-deploy, .environments-build { width: 15%; @@ -62,6 +62,22 @@ } } + .btn-group { + + > a { + color: $gl-text-color-secondary; + } + + svg path { + fill: $gl-text-color-secondary; + } + + .dropdown { + outline: none; + } + } + + .commit-title { margin: 0; } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index a53cc27fac9..4426169ef5a 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -253,11 +253,11 @@ display: block; } - width: $sidebar_collapsed_width; + width: $gutter_collapsed_width; padding-top: 0; .block { - width: $sidebar_collapsed_width - 2px; + width: $gutter_collapsed_width - 2px; margin-left: -19px; padding: 15px 0 0; border-bottom: none; diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 80b0c9493d8..b595480561b 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -10,6 +10,11 @@ .issue-labels { display: inline-block; } + + .icon-merge-request-unmerged { + height: 13px; + margin-bottom: 3px; + } } } diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index 3da1150f89b..27c47d36818 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -30,6 +30,26 @@ word-wrap: break-word; } } + + .panel-heading { + line-height: $line-height-base; + padding: 14px 16px; + display: -webkit-flex; + display: flex; + + .title { + -webkit-flex: 1; + -webkit-flex-grow: 1; + flex: 1; + flex-grow: 2; + } + + .counter { + -webkit-flex: 1; + flex: 0; + padding-left: 16px; + } + } } .milestone-summary { diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 00eb5b30fd5..3fe1eef307e 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -222,6 +222,11 @@ } } + .dropdown-menu { + max-height: 250px; + overflow-y: auto; + } + .dropdown-toggle, .dropdown-menu { color: $gl-text-color-secondary; diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss index 100ace41f2a..305feaacaa1 100644 --- a/app/assets/stylesheets/pages/profiles/preferences.scss +++ b/app/assets/stylesheets/pages/profiles/preferences.scss @@ -1,42 +1,3 @@ -.application-theme { - label { - margin-right: 20px; - text-align: center; - - .preview { - border-radius: 4px; - - height: 80px; - margin-bottom: 10px; - width: 160px; - - &.ui_blue { - background: $theme-blue; - } - - &.ui_charcoal { - background: $theme-charcoal; - } - - &.ui_graphite { - background: $theme-graphite; - } - - &.ui_black { - background: $theme-black; - } - - &.ui_green { - background: $theme-green; - } - - &.ui_violet { - background: $theme-violet; - } - } - } -} - .syntax-theme { label { margin-right: 20px; diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 6b05e5bb4aa..67110813abb 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -268,6 +268,13 @@ } } +.project-repo-buttons { + .project-action-button .dropdown-menu { + max-height: 250px; + overflow-y: auto; + } +} + .split-one { display: inline-table; margin-right: 12px; diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index 0d5604aae69..af9ddb9ff80 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -6,6 +6,8 @@ .navbar-nav { li { .badge.todos-pending-count { + position: inherit; + top: -6px; margin-top: -5px; font-weight: normal; background: $todo-alert-blue; @@ -43,6 +45,12 @@ } } + .todo-avatar, + .todo-actions { + -webkit-flex: 0 0 auto; + flex: 0 0 auto; + } + .todo-actions { display: -webkit-flex; display: flex; @@ -55,15 +63,49 @@ } .todo-item { - -webkit-flex: auto; - flex: auto; + -webkit-flex: 0 1 100%; + flex: 0 1 100%; + min-width: 0; + } +} + +.todos-list > .todo.todo-pending.done-reversible { + background-color: $gray-light; + + &:hover { + border-color: $border-color; + } + + .title { + font-weight: normal; } } .todo-item { .todo-title { - @include str-truncated(calc(100% - 174px)); - overflow: visible; + display: flex; + + & > .title-item { + -webkit-flex: 0 0 auto; + flex: 0 0 auto; + margin: 0 2px; + + &:first-child { + margin-left: 0; + } + + &:last-child { + margin-right: 0; + } + } + + .todo-label { + -webkit-flex: 0 1 auto; + flex: 0 1 auto; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } .status-box { @@ -142,10 +184,12 @@ .todo-item { .todo-title { - white-space: normal; - overflow: visible; - max-width: 100%; + flex-flow: row wrap; margin-bottom: 10px; + + .todo-label { + white-space: normal; + } } .todo-body { diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 948921efc0b..e4487dbcb87 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -149,7 +149,7 @@ } .commit-actions { - width: 200px; + width: 260px; } } diff --git a/app/assets/stylesheets/print.scss b/app/assets/stylesheets/print.scss index 0ff3c3f5472..6cc1cc8e263 100644 --- a/app/assets/stylesheets/print.scss +++ b/app/assets/stylesheets/print.scss @@ -31,7 +31,6 @@ nav.navbar-collapse.collapse, .blob-commit-info, .file-title, .file-holder, -.sidebar-wrapper, .nav, .btn, ul.notes-form, diff --git a/app/controllers/admin/background_jobs_controller.rb b/app/controllers/admin/background_jobs_controller.rb index 338496013a0..c09095b9849 100644 --- a/app/controllers/admin/background_jobs_controller.rb +++ b/app/controllers/admin/background_jobs_controller.rb @@ -2,5 +2,6 @@ class Admin::BackgroundJobsController < Admin::ApplicationController def show ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Gitlab.config.gitlab.user} -o pid,pcpu,pmem,stat,start,command)) @sidekiq_processes = ps_output.split("\n").grep(/sidekiq/) + @concurrency = Sidekiq.options[:concurrency] end end diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb index 7345c91f67d..348641e5ecb 100644 --- a/app/controllers/admin/runners_controller.rb +++ b/app/controllers/admin/runners_controller.rb @@ -13,7 +13,7 @@ class Admin::RunnersController < Admin::ApplicationController end def update - if @runner.update_attributes(runner_params) + if Ci::UpdateRunnerService.new(@runner).update(runner_params) respond_to do |format| format.js format.html { redirect_to admin_runner_path(@runner) } @@ -31,7 +31,7 @@ class Admin::RunnersController < Admin::ApplicationController end def resume - if @runner.update_attributes(active: true) + if Ci::UpdateRunnerService.new(@runner).update(active: true) redirect_to admin_runners_path, notice: 'Runner was successfully updated.' else redirect_to admin_runners_path, alert: 'Runner was not updated.' @@ -39,7 +39,7 @@ class Admin::RunnersController < Admin::ApplicationController end def pause - if @runner.update_attributes(active: false) + if Ci::UpdateRunnerService.new(@runner).update(active: false) redirect_to admin_runners_path, notice: 'Runner was successfully updated.' else redirect_to admin_runners_path, alert: 'Runner was not updated.' diff --git a/app/controllers/admin/system_info_controller.rb b/app/controllers/admin/system_info_controller.rb index ca04a17caa1..1330399a836 100644 --- a/app/controllers/admin/system_info_controller.rb +++ b/app/controllers/admin/system_info_controller.rb @@ -21,6 +21,7 @@ class Admin::SystemInfoController < Admin::ApplicationController 'mqueue', 'proc', 'pstore', + 'rpc_pipefs', 'securityfs', 'sysfs', 'tmpfs', diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 1cd50852e89..7ffde71c3b1 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -194,7 +194,6 @@ class Admin::UsersController < Admin::ApplicationController :provider, :remember_me, :skype, - :theme_id, :twitter, :username, :website_url diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index bf6be3d516b..5e7af3bff0d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -74,7 +74,7 @@ class ApplicationController < ActionController::Base def authenticate_user!(*args) if redirect_to_home_page_url? - redirect_to current_application_settings.home_page_url and return + return redirect_to current_application_settings.home_page_url end super(*args) @@ -131,7 +131,7 @@ class ApplicationController < ActionController::Base headers['X-UA-Compatible'] = 'IE=edge' headers['X-Content-Type-Options'] = 'nosniff' # Enabling HSTS for non-standard ports would send clients to the wrong port - if Gitlab.config.gitlab.https and Gitlab.config.gitlab.port == 443 + if Gitlab.config.gitlab.https && Gitlab.config.gitlab.port == 443 headers['Strict-Transport-Security'] = 'max-age=31536000' end end @@ -152,7 +152,7 @@ class ApplicationController < ActionController::Base def check_password_expiration if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user? - redirect_to new_profile_password_path and return + return redirect_to new_profile_password_path end end @@ -218,7 +218,7 @@ class ApplicationController < ActionController::Base def require_email if current_user && current_user.temp_oauth_email? && session[:impersonator_id].nil? - redirect_to profile_path, notice: 'Please complete your profile with email address' and return + return redirect_to profile_path, notice: 'Please complete your profile with email address' end end diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 6286d67d30c..88d180fcc2e 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -104,23 +104,15 @@ module CreatesCommit if can?(current_user, :push_code, @project) # Edit file in this project @mr_source_project = @project - - if @project.forked? - # Merge request from this project to fork origin - @mr_target_project = @project.forked_from_project - @mr_target_branch = @mr_target_project.repository.root_ref - else - # Merge request to this project - @mr_target_project = @project - @mr_target_branch = @ref || @target_branch - end else # Merge request from fork to this project @mr_source_project = current_user.fork_of(@project) - @mr_target_project = @project - @mr_target_branch = @ref || @target_branch end + # Merge request to this project + @mr_target_project = @project + @mr_target_branch = @ref || @target_branch + @mr_source_branch = guess_mr_source_branch end diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index a6e158ebae6..85ae4985e58 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -9,24 +9,32 @@ module IssuableCollections private - def issuable_meta_data(issuable_collection) + def issuable_meta_data(issuable_collection, collection_type) # map has to be used here since using pluck or select will # throw an error when ordering issuables by priority which inserts # a new order into the collection. # We cannot use reorder to not mess up the paginated collection. - issuable_ids = issuable_collection.map(&:id) - issuable_note_count = Note.count_for_collection(issuable_ids, @collection_type) + issuable_ids = issuable_collection.map(&:id) + issuable_note_count = Note.count_for_collection(issuable_ids, @collection_type) issuable_votes_count = AwardEmoji.votes_for_collection(issuable_ids, @collection_type) + issuable_merge_requests_count = + if collection_type == 'Issue' + MergeRequestsClosingIssues.count_for_collection(issuable_ids) + else + [] + end issuable_ids.each_with_object({}) do |id, issuable_meta| downvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.downvote? } - upvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.upvote? } - notes = issuable_note_count.find { |notes| notes.noteable_id == id } + upvotes = issuable_votes_count.find { |votes| votes.awardable_id == id && votes.upvote? } + notes = issuable_note_count.find { |notes| notes.noteable_id == id } + merge_requests = issuable_merge_requests_count.find { |mr| mr.first == id } issuable_meta[id] = Issuable::IssuableMeta.new( upvotes.try(:count).to_i, downvotes.try(:count).to_i, - notes.try(:count).to_i + notes.try(:count).to_i, + merge_requests.try(:last).to_i ) end end diff --git a/app/controllers/concerns/issues_action.rb b/app/controllers/concerns/issues_action.rb index fb5edb34370..b17c138d5c7 100644 --- a/app/controllers/concerns/issues_action.rb +++ b/app/controllers/concerns/issues_action.rb @@ -10,7 +10,7 @@ module IssuesAction .page(params[:page]) @collection_type = "Issue" - @issuable_meta_data = issuable_meta_data(@issues) + @issuable_meta_data = issuable_meta_data(@issues, @collection_type) respond_to do |format| format.html diff --git a/app/controllers/concerns/merge_requests_action.rb b/app/controllers/concerns/merge_requests_action.rb index 6229759dcf1..d3c8e4888bc 100644 --- a/app/controllers/concerns/merge_requests_action.rb +++ b/app/controllers/concerns/merge_requests_action.rb @@ -9,7 +9,7 @@ module MergeRequestsAction .page(params[:page]) @collection_type = "MergeRequest" - @issuable_meta_data = issuable_meta_data(@merge_requests) + @issuable_meta_data = issuable_meta_data(@merge_requests, @collection_type) end private diff --git a/app/controllers/concerns/snippets_actions.rb b/app/controllers/concerns/snippets_actions.rb new file mode 100644 index 00000000000..ca6dffe1cc5 --- /dev/null +++ b/app/controllers/concerns/snippets_actions.rb @@ -0,0 +1,21 @@ +module SnippetsActions + extend ActiveSupport::Concern + + def edit + end + + def raw + send_data( + convert_line_endings(@snippet.content), + type: 'text/plain; charset=utf-8', + disposition: 'inline', + filename: @snippet.sanitized_file_name + ) + end + + private + + def convert_line_endings(content) + params[:line_ending] == 'raw' ? content : content.gsub(/\r\n/, "\n") + end +end diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb index a6891149bfa..da225d8f1c7 100644 --- a/app/controllers/concerns/spammable_actions.rb +++ b/app/controllers/concerns/spammable_actions.rb @@ -17,13 +17,31 @@ module SpammableActions private - def recaptcha_params - return {} unless params[:recaptcha_verification] && Gitlab::Recaptcha.load_configurations! && verify_recaptcha + def recaptcha_check_with_fallback(&fallback) + if spammable.valid? + redirect_to spammable + elsif render_recaptcha? + if params[:recaptcha_verification] + flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.' + end + + render :verify + else + fallback.call + end + end + + def spammable_params + default_params = { request: request } + + recaptcha_check = params[:recaptcha_verification] && + Gitlab::Recaptcha.load_configurations! && + verify_recaptcha + + return default_params unless recaptcha_check - { - recaptcha_verified: true, - spam_log_id: params[:spam_log_id] - } + { recaptcha_verified: true, + spam_log_id: params[:spam_log_id] }.merge(default_params) end def spammable diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index e3933e3d7b1..5848ca62777 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -1,4 +1,6 @@ class Dashboard::TodosController < Dashboard::ApplicationController + include ActionView::Helpers::NumberHelper + before_action :find_todos, only: [:index, :destroy_all] def index @@ -29,6 +31,17 @@ class Dashboard::TodosController < Dashboard::ApplicationController end end + def restore + TodoService.new.mark_todos_as_pending_by_ids([params[:id]], current_user) + + render json: todos_counts + end + + # Used in TodosHelper also + def self.todos_count_format(count) + count >= 100 ? '99+' : count + end + private def find_todos @@ -37,8 +50,8 @@ class Dashboard::TodosController < Dashboard::ApplicationController def todos_counts { - count: current_user.todos_pending_count, - done_count: current_user.todos_done_count + count: number_with_delimiter(current_user.todos_pending_count), + done_count: number_with_delimiter(current_user.todos_done_count) } end end diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb index 99b10b2f9b3..5df6bd34185 100644 --- a/app/controllers/import/fogbugz_controller.rb +++ b/app/controllers/import/fogbugz_controller.rb @@ -29,7 +29,7 @@ class Import::FogbugzController < Import::BaseController unless user_map.is_a?(Hash) && user_map.all? { |k, v| !v[:name].blank? } flash.now[:alert] = 'All users must have a name.' - render 'new_user_map' and return + return render 'new_user_map' end session[:fogbugz_user_map] = user_map diff --git a/app/controllers/import/google_code_controller.rb b/app/controllers/import/google_code_controller.rb index 8d0de158f98..7d7f13ce5d5 100644 --- a/app/controllers/import/google_code_controller.rb +++ b/app/controllers/import/google_code_controller.rb @@ -44,13 +44,13 @@ class Import::GoogleCodeController < Import::BaseController rescue flash.now[:alert] = "The entered user map is not a valid JSON user map." - render "new_user_map" and return + return render "new_user_map" end unless user_map.is_a?(Hash) && user_map.all? { |k, v| k.is_a?(String) && v.is_a?(String) } flash.now[:alert] = "The entered user map is not a valid JSON user map." - render "new_user_map" and return + return render "new_user_map" end # This is the default, so let's not save it into the database. diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index 58964a0e65d..7625187c7be 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -42,9 +42,7 @@ class InvitesController < ApplicationController @token = params[:id] @member = Member.find_by_invite_token(@token) - unless @member - render_404 and return - end + return render_404 unless @member @member end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index f54c79c2e37..58d50ad647b 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -78,6 +78,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController handle_omniauth end + def authentiq + if params['sid'] + handle_service_ticket oauth['provider'], params['sid'] + end + handle_omniauth + end + private def handle_omniauth @@ -115,7 +122,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController else error_message = @user.errors.full_messages.to_sentence - redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return + return redirect_to omniauth_error_path(oauth['provider'], error: error_message) end end diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index 830e0b9591b..c8663a3c38e 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -45,13 +45,13 @@ class Profiles::KeysController < Profiles::ApplicationController if user.present? render text: user.all_ssh_keys.join("\n"), content_type: "text/plain" else - render_404 and return + return render_404 end rescue => e render text: e.message end else - render_404 and return + return render_404 end end diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index a9a06ecc808..0d891ef4004 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -34,7 +34,6 @@ class Profiles::PreferencesController < Profiles::ApplicationController :layout, :dashboard, :project_view, - :theme_id ) end end diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index db33b60b229..e2f81b09adc 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -83,7 +83,6 @@ class Projects::ApplicationController < ApplicationController end def apply_diff_view_cookie! - @show_changes_tab = params[:view].present? cookies.permanent[:diff_view] = params.delete(:view) if params[:view].present? end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index a1db856dcfb..39ba815cfca 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -95,7 +95,7 @@ class Projects::BlobController < Projects::ApplicationController else if tree = @repository.tree(@commit.id, @path) if tree.entries.any? - redirect_to namespace_project_tree_path(@project.namespace, @project, File.join(@ref, @path)) and return + return redirect_to namespace_project_tree_path(@project.namespace, @project, File.join(@ref, @path)) end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 744a4af1c51..ca5e81100da 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -26,7 +26,7 @@ class Projects::IssuesController < Projects::ApplicationController @collection_type = "Issue" @issues = issues_collection @issues = @issues.page(params[:page]) - @issuable_meta_data = issuable_meta_data(@issues) + @issuable_meta_data = issuable_meta_data(@issues, @collection_type) if @issues.out_of_range? && @issues.total_pages != 0 return redirect_to url_for(params.merge(page: @issues.total_pages)) @@ -94,15 +94,15 @@ class Projects::IssuesController < Projects::ApplicationController end def create - extra_params = { request: request, - merge_request_for_resolving_discussions: merge_request_for_resolving_discussions } - extra_params.merge!(recaptcha_params) + create_params = issue_params + .merge(merge_request_for_resolving_discussions: merge_request_for_resolving_discussions) + .merge(spammable_params) - @issue = Issues::CreateService.new(project, current_user, issue_params.merge(extra_params)).execute + @issue = Issues::CreateService.new(project, current_user, create_params).execute respond_to do |format| format.html do - html_response_create + recaptcha_check_with_fallback { render :new } end format.js do @link = @issue.attachment.url.to_js @@ -111,7 +111,9 @@ class Projects::IssuesController < Projects::ApplicationController end def update - @issue = Issues::UpdateService.new(project, current_user, issue_params).execute(issue) + update_params = issue_params.merge(spammable_params) + + @issue = Issues::UpdateService.new(project, current_user, update_params).execute(issue) if params[:move_to_project_id].to_i > 0 new_project = Project.find(params[:move_to_project_id]) @@ -123,11 +125,7 @@ class Projects::IssuesController < Projects::ApplicationController respond_to do |format| format.html do - if @issue.valid? - redirect_to issue_path(@issue) - else - render :edit - end + recaptcha_check_with_fallback { render :edit } end format.json do @@ -179,20 +177,6 @@ class Projects::IssuesController < Projects::ApplicationController protected - def html_response_create - if @issue.valid? - redirect_to issue_path(@issue) - elsif render_recaptcha? - if params[:recaptcha_verification] - flash[:alert] = 'There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.' - end - - render :verify - else - render :new - end - end - def issue # The Sortable default scope causes performance issues when used with find_by @noteable = @issue ||= @project.issues.where(iid: params[:id]).reorder(nil).take || redirect_old diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 63b5bcbb586..365c49a20d4 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -39,7 +39,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @collection_type = "MergeRequest" @merge_requests = merge_requests_collection @merge_requests = @merge_requests.page(params[:page]) - @issuable_meta_data = issuable_meta_data(@merge_requests) + @issuable_meta_data = issuable_meta_data(@merge_requests, @collection_type) if @merge_requests.out_of_range? && @merge_requests.total_pages != 0 return redirect_to url_for(params.merge(page: @merge_requests.total_pages)) @@ -50,6 +50,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController @labels = LabelsFinder.new(current_user, labels_params).execute end + @users = [] + if params[:assignee_id].present? + assignee = User.find_by_id(params[:assignee_id]) + @users.push(assignee) if assignee + end + + if params[:author_id].present? + author = User.find_by_id(params[:author_id]) + @users.push(author) if author + end + respond_to do |format| format.html format.json do @@ -245,6 +256,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController respond_to do |format| format.html do define_new_vars + @show_changes_tab = true render "new" end format.json do @@ -616,6 +628,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController @labels = LabelsFinder.new(current_user, project_id: @project.id).execute + @show_changes_tab = params[:show_changes].present? + define_pipelines_vars end diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb index 74c54037ba9..8b50ea207a5 100644 --- a/app/controllers/projects/runners_controller.rb +++ b/app/controllers/projects/runners_controller.rb @@ -12,7 +12,7 @@ class Projects::RunnersController < Projects::ApplicationController end def update - if @runner.update_attributes(runner_params) + if Ci::UpdateRunnerService.new(@runner).update(runner_params) redirect_to runner_path(@runner), notice: 'Runner was successfully updated.' else render 'edit' @@ -28,7 +28,7 @@ class Projects::RunnersController < Projects::ApplicationController end def resume - if @runner.update_attributes(active: true) + if Ci::UpdateRunnerService.new(@runner).update(active: true) redirect_to runner_path(@runner), notice: 'Runner was successfully updated.' else redirect_to runner_path(@runner), alert: 'Runner was not updated.' @@ -36,7 +36,7 @@ class Projects::RunnersController < Projects::ApplicationController end def pause - if @runner.update_attributes(active: false) + if Ci::UpdateRunnerService.new(@runner).update(active: false) redirect_to runner_path(@runner), notice: 'Runner was successfully updated.' else redirect_to runner_path(@runner), alert: 'Runner was not updated.' diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 5d193f26a8e..ea1a97b7cf0 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -1,6 +1,7 @@ class Projects::SnippetsController < Projects::ApplicationController include ToggleAwardEmoji include SpammableActions + include SnippetsActions before_action :module_enabled before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam] @@ -37,27 +38,19 @@ class Projects::SnippetsController < Projects::ApplicationController end def create - create_params = snippet_params.merge(request: request) - @snippet = CreateSnippetService.new(@project, current_user, create_params).execute + create_params = snippet_params.merge(spammable_params) - if @snippet.valid? - respond_with(@snippet, - location: namespace_project_snippet_path(@project.namespace, - @project, @snippet)) - else - render :new - end - end + @snippet = CreateSnippetService.new(@project, current_user, create_params).execute - def edit + recaptcha_check_with_fallback { render :new } end def update - UpdateSnippetService.new(project, current_user, @snippet, - snippet_params).execute - respond_with(@snippet, - location: namespace_project_snippet_path(@project.namespace, - @project, @snippet)) + update_params = snippet_params.merge(spammable_params) + + UpdateSnippetService.new(project, current_user, @snippet, update_params).execute + + recaptcha_check_with_fallback { render :edit } end def show @@ -74,15 +67,6 @@ class Projects::SnippetsController < Projects::ApplicationController redirect_to namespace_project_snippets_path(@project.namespace, @project) end - def raw - send_data( - @snippet.content, - type: 'text/plain; charset=utf-8', - disposition: 'inline', - filename: @snippet.sanitized_file_name - ) - end - protected def snippet diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index cb3ed0f6f9c..4f094146348 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -15,10 +15,10 @@ class Projects::TreeController < Projects::ApplicationController if tree.entries.empty? if @repository.blob_at(@commit.id, @path) - redirect_to( + return redirect_to( namespace_project_blob_path(@project.namespace, @project, File.join(@ref, @path)) - ) and return + ) elsif @path.present? return render_404 end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index b169d993688..2d26718873f 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -1,6 +1,7 @@ class SnippetsController < ApplicationController include ToggleAwardEmoji include SpammableActions + include SnippetsActions before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download] @@ -22,7 +23,7 @@ class SnippetsController < ApplicationController if params[:username].present? @user = User.find_by(username: params[:username]) - render_404 and return unless @user + return render_404 unless @user @snippets = SnippetsFinder.new.execute(current_user, { filter: :by_user, @@ -41,19 +42,19 @@ class SnippetsController < ApplicationController end def create - create_params = snippet_params.merge(request: request) - @snippet = CreateSnippetService.new(nil, current_user, create_params).execute + create_params = snippet_params.merge(spammable_params) - respond_with @snippet.becomes(Snippet) - end + @snippet = CreateSnippetService.new(nil, current_user, create_params).execute - def edit + recaptcha_check_with_fallback { render :new } end def update - UpdateSnippetService.new(nil, current_user, @snippet, - snippet_params).execute - respond_with @snippet.becomes(Snippet) + update_params = snippet_params.merge(spammable_params) + + UpdateSnippetService.new(nil, current_user, @snippet, update_params).execute + + recaptcha_check_with_fallback { render :edit } end def show @@ -67,18 +68,9 @@ class SnippetsController < ApplicationController redirect_to snippets_path end - def raw - send_data( - @snippet.content, - type: 'text/plain; charset=utf-8', - disposition: 'inline', - filename: @snippet.sanitized_file_name - ) - end - def download send_data( - @snippet.content, + convert_line_endings(@snippet.content), type: 'text/plain; charset=utf-8', filename: @snippet.sanitized_file_name ) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 1576fc80a6b..206c92fe82a 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -16,6 +16,7 @@ # label_name: string # sort: string # non_archived: boolean +# iids: integer[] # class IssuableFinder NONE = '0' @@ -40,6 +41,7 @@ class IssuableFinder items = by_label(items) items = by_due_date(items) items = by_non_archived(items) + items = by_iids(items) sort(items) end @@ -266,16 +268,11 @@ class IssuableFinder end def by_search(items) - if search - items = - if search =~ iid_pattern - items.where(iid: $~[:iid]) - else - items.full_search(search) - end - end + search ? items.full_search(search) : items + end - items + def by_iids(items) + params[:iids].present? ? items.where(iid: params[:iids]) : items end def sort(items) diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index 707eddd4d29..f542f72a386 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -26,10 +26,6 @@ class IssuesFinder < IssuableFinder IssuesFinder.not_restricted_by_confidentiality(current_user) end - def iid_pattern - @iid_pattern ||= %r{\A#{Regexp.escape(Issue.reference_prefix)}(?<iid>\d+)\z} - end - def self.not_restricted_by_confidentiality(user) return Issue.where('issues.confidential IS NULL OR issues.confidential IS FALSE') if user.blank? diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb index 8b82255445e..b76ca389f38 100644 --- a/app/finders/merge_requests_finder.rb +++ b/app/finders/merge_requests_finder.rb @@ -20,14 +20,4 @@ class MergeRequestsFinder < IssuableFinder def klass MergeRequest end - - private - - def iid_pattern - @iid_pattern ||= %r{\A[ - #{Regexp.escape(MergeRequest.reference_prefix)} - #{Regexp.escape(Issue.reference_prefix)} - ](?<iid>\d+)\z - }x - end end diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb index 2843ad96efa..a6d9e37ac76 100644 --- a/app/helpers/emails_helper.rb +++ b/app/helpers/emails_helper.rb @@ -1,4 +1,6 @@ module EmailsHelper + include AppearancesHelper + # Google Actions # https://developers.google.com/gmail/markup/reference/go-to-action def email_action(url) @@ -49,4 +51,19 @@ module EmailsHelper msg = "This link is valid for #{password_reset_token_valid_time}. " msg << "After it expires, you can #{link_tag}." end + + def header_logo + if brand_item && brand_item.header_logo? + image_tag( + brand_item.header_logo, + style: 'height: 50px' + ) + else + image_tag( + image_url('mailers/gitlab_header_logo.gif'), + size: "55x50", + alt: "GitLab" + ) + end + end end diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index 0676767d910..dc5ae8edbb2 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -1,4 +1,8 @@ module NamespacesHelper + def namespace_id_from(params) + params.dig(:project, :namespace_id) || params[:namespace_id] + end + def namespaces_options(selected = :current_user, display_path: false, extra_group: nil) groups = current_user.owned_groups + current_user.masters_groups diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index e21178c7377..c1523b4dabf 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -1,10 +1,4 @@ module NavHelper - def page_sidebar_class - if pinned_nav? - "page-sidebar-expanded page-sidebar-pinned" - end - end - def page_gutter_class if current_path?('merge_requests#show') || current_path?('merge_requests#diffs') || @@ -32,10 +26,6 @@ module NavHelper class_name = '' class_name << " with-horizontal-nav" if defined?(nav) && nav - if pinned_nav? - class_name << " header-sidebar-expanded header-sidebar-pinned" - end - class_name end @@ -46,8 +36,4 @@ module NavHelper def nav_control_class "nav-control" if current_user end - - def pinned_nav? - cookies[:pin_nav] == 'true' - end end diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb index dd0a4ea03f0..c3a08d76318 100644 --- a/app/helpers/preferences_helper.rb +++ b/app/helpers/preferences_helper.rb @@ -41,10 +41,6 @@ module PreferencesHelper ] end - def user_application_theme - Gitlab::Themes.for_user(current_user).css_class - end - def user_color_scheme Gitlab::ColorSchemes.for_user(current_user).css_class end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 845f1a0e840..c52afd6db1c 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -3,6 +3,10 @@ module TodosHelper @todos_pending_count ||= current_user.todos_pending_count end + def todos_count_format(count) + count > 99 ? '99+' : count + end + def todos_done_count @todos_done_count ||= current_user.todos_done_count end diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb index 9460a6cd2be..f9f45ab987b 100644 --- a/app/mailers/emails/pipelines.rb +++ b/app/mailers/emails/pipelines.rb @@ -22,8 +22,8 @@ module Emails mail(bcc: recipients, subject: pipeline_subject(status), skip_premailer: true) do |format| - format.html { render layout: false } - format.text + format.html { render layout: 'mailer' } + format.text { render layout: 'mailer' } end end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 8c1b076c2d7..e018f8e7c4e 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -62,33 +62,10 @@ module Ci new_build.save end - def retry(build, user = nil) - new_build = Ci::Build.create( - ref: build.ref, - tag: build.tag, - options: build.options, - commands: build.commands, - tag_list: build.tag_list, - project: build.project, - pipeline: build.pipeline, - name: build.name, - allow_failure: build.allow_failure, - stage: build.stage, - stage_idx: build.stage_idx, - trigger_request: build.trigger_request, - yaml_variables: build.yaml_variables, - when: build.when, - user: user, - environment: build.environment, - status_event: 'enqueue' - ) - - MergeRequests::AddTodoWhenBuildFailsService - .new(build.project, nil) - .close(new_build) - - build.pipeline.mark_as_processable_after_stage(build.stage_idx) - new_build + def retry(build, current_user) + Ci::RetryBuildService + .new(build.project, current_user) + .execute(build) end end @@ -136,7 +113,7 @@ module Ci project.builds_enabled? && commands.present? && manual? && skipped? end - def play(current_user = nil) + def play(current_user) # Try to queue a current build if self.enqueue self.update(user: current_user) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index bbc358adb83..dc4590a9923 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -214,21 +214,17 @@ module Ci def cancel_running Gitlab::OptimisticLocking.retry_lock( statuses.cancelable) do |cancelable| - cancelable.each(&:cancel) + cancelable.find_each(&:cancel) end end - def retry_failed(user) - Gitlab::OptimisticLocking.retry_lock( - builds.latest.failed_or_canceled) do |failed_or_canceled| - failed_or_canceled.select(&:retryable?).each do |build| - Ci::Build.retry(build, user) - end - end + def retry_failed(current_user) + Ci::RetryPipelineService.new(project, current_user) + .execute(self) end def mark_as_processable_after_stage(stage_idx) - builds.skipped.where('stage_idx > ?', stage_idx).find_each(&:process) + builds.skipped.after_stage(stage_idx).find_each(&:process) end def latest? diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index ed1843ba005..07a086b0aca 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -22,8 +22,6 @@ module Ci scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) } scope :ordered, ->() { order(id: :desc) } - after_save :tick_runner_queue, if: :form_editable_changed? - scope :owned_or_shared, ->(project_id) do joins('LEFT JOIN ci_runner_projects ON ci_runner_projects.runner_id = ci_runners.id') .where("ci_runner_projects.gl_project_id = :project_id OR ci_runners.is_shared = true", project_id: project_id) @@ -40,6 +38,8 @@ module Ci acts_as_taggable + after_destroy :cleanup_runner_queue + # Searches for runners matching the given query. # # This method uses ILIKE on PostgreSQL and LIKE on MySQL. @@ -147,14 +147,14 @@ module Ci private - def runner_queue_key - "runner:build_queue:#{self.token}" + def cleanup_runner_queue + Gitlab::Redis.with do |redis| + redis.del(runner_queue_key) + end end - def form_editable_changed? - FORM_EDITABLE.any? do |editable| - public_send("#{editable}_changed?") - end + def runner_queue_key + "runner:build_queue:#{self.token}" end def tag_constraints diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 9547c57b2ae..99a6326309d 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -23,9 +23,6 @@ class CommitStatus < ActiveRecord::Base where(id: max_id.group(:name, :commit_id)) end - scope :retried, -> { where.not(id: latest) } - scope :ordered, -> { order(:name) } - scope :failed_but_allowed, -> do where(allow_failure: true, status: [:failed, :canceled]) end @@ -36,8 +33,11 @@ class CommitStatus < ActiveRecord::Base false, all_state_names - [:failed, :canceled]) end + scope :retried, -> { where.not(id: latest) } + scope :ordered, -> { order(:name) } scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) } scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) } + scope :after_stage, -> (index) { where('stage_idx > ?', index) } state_machine :status do event :enqueue do diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 5f53c48fc88..c9c6bd24d75 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -16,9 +16,9 @@ module Issuable include TimeTrackable # This object is used to gather issuable meta data for displaying - # upvotes, downvotes and notes count for issues and merge requests + # upvotes, downvotes, notes and closing merge requests count for issues and merge requests # lists avoiding n+1 queries and improving performance. - IssuableMeta = Struct.new(:upvotes, :downvotes, :notes_count) + IssuableMeta = Struct.new(:upvotes, :downvotes, :notes_count, :merge_requests_count) included do cache_markdown_field :title, pipeline: :single_line diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb index 423ae98a60e..107e6764ba2 100644 --- a/app/models/concerns/spammable.rb +++ b/app/models/concerns/spammable.rb @@ -13,7 +13,7 @@ module Spammable attr_accessor :spam attr_accessor :spam_log - after_validation :check_for_spam, on: :create + after_validation :check_for_spam, on: [:create, :update] cattr_accessor :spammable_attrs, instance_accessor: false do [] @@ -22,6 +22,10 @@ module Spammable delegate :ip_address, :user_agent, to: :user_agent_detail, allow_nil: true end + def submittable_as_spam_by?(current_user) + current_user && current_user.admin? && submittable_as_spam? + end + def submittable_as_spam? if user_agent_detail user_agent_detail.submittable? && current_application_settings.akismet_enabled diff --git a/app/models/merge_requests_closing_issues.rb b/app/models/merge_requests_closing_issues.rb index ab597c37947..daafb137be4 100644 --- a/app/models/merge_requests_closing_issues.rb +++ b/app/models/merge_requests_closing_issues.rb @@ -4,4 +4,12 @@ class MergeRequestsClosingIssues < ActiveRecord::Base validates :merge_request_id, uniqueness: { scope: :issue_id }, presence: true validates :issue_id, presence: true + + class << self + def count_for_collection(ids) + group(:issue_id). + where(issue_id: ids). + pluck('issue_id', 'COUNT(*) as count') + end + end end diff --git a/app/models/project.rb b/app/models/project.rb index fc5b1a66910..411299eef63 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -552,7 +552,7 @@ class Project < ActiveRecord::Base end def check_limit - unless creator.can_create_project? or namespace.kind == 'group' + unless creator.can_create_project? || namespace.kind == 'group' projects_limit = creator.projects_limit if projects_limit == 0 diff --git a/app/models/project_services/chat_message/base_message.rb b/app/models/project_services/chat_message/base_message.rb index a03605d01fb..86d271a3f69 100644 --- a/app/models/project_services/chat_message/base_message.rb +++ b/app/models/project_services/chat_message/base_message.rb @@ -30,5 +30,9 @@ module ChatMessage def attachment_color '#345' end + + def link(text, url) + "[#{text}](#{url})" + end end end diff --git a/app/models/project_services/chat_message/build_message.rb b/app/models/project_services/chat_message/build_message.rb index 53e35cb21bf..c776e0a20c4 100644 --- a/app/models/project_services/chat_message/build_message.rb +++ b/app/models/project_services/chat_message/build_message.rb @@ -7,7 +7,11 @@ module ChatMessage attr_reader :project_name attr_reader :project_url attr_reader :user_name + attr_reader :user_url attr_reader :duration + attr_reader :stage + attr_reader :build_id + attr_reader :build_name def initialize(params) @sha = params[:sha] @@ -17,7 +21,11 @@ module ChatMessage @project_url = params[:project_url] @status = params[:commit][:status] @user_name = params[:commit][:author_name] + @user_url = params[:commit][:author_url] @duration = params[:commit][:duration] + @stage = params[:build_stage] + @build_name = params[:build_name] + @build_id = params[:build_id] end def pretext @@ -35,7 +43,19 @@ module ChatMessage private def message - "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status} in #{duration} #{'second'.pluralize(duration)}" + "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_link} #{humanized_status} on build #{build_link} of stage #{stage} in #{duration} #{'second'.pluralize(duration)}" + end + + def build_url + "#{project_url}/builds/#{build_id}" + end + + def build_link + link(build_name, build_url) + end + + def user_link + link(user_name, user_url) end def format(string) @@ -64,11 +84,11 @@ module ChatMessage end def branch_link - "[#{ref}](#{branch_url})" + link(ref, branch_url) end def project_link - "[#{project_name}](#{project_url})" + link(project_name, project_url) end def commit_url @@ -76,7 +96,7 @@ module ChatMessage end def commit_link - "[#{Commit.truncate_sha(sha)}](#{commit_url})" + link(Commit.truncate_sha(sha), commit_url) end end end diff --git a/app/models/project_services/chat_message/issue_message.rb b/app/models/project_services/chat_message/issue_message.rb index 14fd64e5332..b96aca47e65 100644 --- a/app/models/project_services/chat_message/issue_message.rb +++ b/app/models/project_services/chat_message/issue_message.rb @@ -55,11 +55,11 @@ module ChatMessage end def project_link - "[#{project_name}](#{project_url})" + link(project_name, project_url) end def issue_link - "[#{issue_title}](#{issue_url})" + link(issue_title, issue_url) end def issue_title diff --git a/app/models/project_services/chat_message/merge_message.rb b/app/models/project_services/chat_message/merge_message.rb index ab5e8b24167..5e5efca7bec 100644 --- a/app/models/project_services/chat_message/merge_message.rb +++ b/app/models/project_services/chat_message/merge_message.rb @@ -42,7 +42,7 @@ module ChatMessage end def project_link - "[#{project_name}](#{project_url})" + link(project_name, project_url) end def merge_request_message @@ -50,7 +50,7 @@ module ChatMessage end def merge_request_link - "[merge request !#{merge_request_id}](#{merge_request_url})" + link("merge request !#{merge_request_id}", merge_request_url) end def merge_request_url diff --git a/app/models/project_services/chat_message/note_message.rb b/app/models/project_services/chat_message/note_message.rb index ca1d7207034..552113bac29 100644 --- a/app/models/project_services/chat_message/note_message.rb +++ b/app/models/project_services/chat_message/note_message.rb @@ -3,10 +3,9 @@ module ChatMessage attr_reader :message attr_reader :user_name attr_reader :project_name - attr_reader :project_link + attr_reader :project_url attr_reader :note attr_reader :note_url - attr_reader :title def initialize(params) params = HashWithIndifferentAccess.new(params) @@ -69,15 +68,15 @@ module ChatMessage end def description_message - [{ text: format(@note), color: attachment_color }] + [{ text: format(note), color: attachment_color }] end def project_link - "[#{@project_name}](#{@project_url})" + link(project_name, project_url) end def commented_on_message(target, title) - @message = "#{@user_name} [commented on #{target}](#{@note_url}) in #{project_link}: *#{title}*" + @message = "#{user_name} #{link('commented on ' + target, note_url)} in #{project_link}: *#{title}*" end end end diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb index 942ec9371e5..1ad9efac196 100644 --- a/app/models/project_services/drone_ci_service.rb +++ b/app/models/project_services/drone_ci_service.rb @@ -52,7 +52,7 @@ class DroneCiService < CiService response = HTTParty.get(commit_status_path(sha, ref), verify: enable_ssl_verification) status = - if response.code == 200 and response['status'] + if response.code == 200 && response['status'] case response['status'] when 'killed' :canceled diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index 5d93064f9b3..5d6862d9faa 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -96,7 +96,7 @@ class IrkerService < Service rescue URI::InvalidURIError end - unless uri.present? and default_irc_uri.nil? + unless uri.present? && default_irc_uri.nil? begin new_recipient = URI.join(default_irc_uri, '/', recipient).to_s uri = consider_uri(URI.parse(new_recipient)) diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb index 9bb456eee24..25b5d777641 100644 --- a/app/models/project_snippet.rb +++ b/app/models/project_snippet.rb @@ -9,8 +9,4 @@ class ProjectSnippet < Snippet participant :author participant :notes_with_associations - - def check_for_spam? - super && project.public? - end end diff --git a/app/models/user.rb b/app/models/user.rb index ad997ce2b13..f614eb66e1f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -21,7 +21,6 @@ class User < ActiveRecord::Base default_value_for :can_create_team, false default_value_for :hide_no_ssh_key, false default_value_for :hide_no_password, false - default_value_for :theme_id, gitlab_config.default_theme attr_encrypted :otp_secret, key: Gitlab::Application.secrets.otp_key_base, diff --git a/app/serializers/analytics_stage_entity.rb b/app/serializers/analytics_stage_entity.rb index a559d0850c4..69bf693de8d 100644 --- a/app/serializers/analytics_stage_entity.rb +++ b/app/serializers/analytics_stage_entity.rb @@ -2,6 +2,7 @@ class AnalyticsStageEntity < Grape::Entity include EntityDateHelper expose :title + expose :legend expose :description expose :median, as: :value do |stage| diff --git a/app/services/base_service.rb b/app/services/base_service.rb index 1a2bad77a02..fa45506317e 100644 --- a/app/services/base_service.rb +++ b/app/services/base_service.rb @@ -1,4 +1,5 @@ class BaseService + include Gitlab::Allowable include Gitlab::CurrentSettings attr_accessor :project, :current_user, :params @@ -7,10 +8,6 @@ class BaseService @project, @current_user, @params = project, user, params.dup end - def can?(object, action, subject) - Ability.allowed?(object, action, subject) - end - def notification_service NotificationService.new end diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index e3bc9847200..38a85e9fc42 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -59,7 +59,8 @@ module Ci private def skip_ci? - pipeline.git_commit_message =~ /\[(ci skip|skip ci)\]/i if pipeline.git_commit_message + return false unless pipeline.git_commit_message + pipeline.git_commit_message =~ /\[(ci[ _-]skip|skip[ _-]ci)\]/i end def commit diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb new file mode 100644 index 00000000000..4b47ee489cf --- /dev/null +++ b/app/services/ci/retry_build_service.rb @@ -0,0 +1,42 @@ +module Ci + class RetryBuildService < ::BaseService + CLONE_ATTRIBUTES = %i[pipeline ref tag options commands tag_list name + allow_failure stage stage_idx trigger_request + yaml_variables when environment coverage_regex] + .freeze + + REJECT_ATTRIBUTES = %i[id status user token coverage trace runner + artifacts_file artifacts_metadata artifacts_size + created_at updated_at started_at finished_at + queued_at erased_by erased_at].freeze + + IGNORE_ATTRIBUTES = %i[trace type lock_version project target_url + deploy job_id description].freeze + + def execute(build) + reprocess(build).tap do |new_build| + build.pipeline.mark_as_processable_after_stage(build.stage_idx) + + new_build.enqueue! + + MergeRequests::AddTodoWhenBuildFailsService + .new(project, current_user) + .close(new_build) + end + end + + def reprocess(build) + unless can?(current_user, :update_build, build) + raise Gitlab::Access::AccessDeniedError + end + + attributes = CLONE_ATTRIBUTES.map do |attribute| + [attribute, build.send(attribute)] + end + + attributes.push([:user, current_user]) + + project.builds.create(Hash[attributes]) + end + end +end diff --git a/app/services/ci/retry_pipeline_service.rb b/app/services/ci/retry_pipeline_service.rb new file mode 100644 index 00000000000..2c5e130e5aa --- /dev/null +++ b/app/services/ci/retry_pipeline_service.rb @@ -0,0 +1,22 @@ +module Ci + class RetryPipelineService < ::BaseService + def execute(pipeline) + unless can?(current_user, :update_pipeline, pipeline) + raise Gitlab::Access::AccessDeniedError + end + + pipeline.builds.failed_or_canceled.find_each do |build| + next unless build.retryable? + + Ci::RetryBuildService.new(project, current_user) + .reprocess(build) + end + + MergeRequests::AddTodoWhenBuildFailsService + .new(project, current_user) + .close_all(pipeline) + + pipeline.process! + end + end +end diff --git a/app/services/ci/update_runner_service.rb b/app/services/ci/update_runner_service.rb new file mode 100644 index 00000000000..450ee7da1c9 --- /dev/null +++ b/app/services/ci/update_runner_service.rb @@ -0,0 +1,15 @@ +module Ci + class UpdateRunnerService + attr_reader :runner + + def initialize(runner) + @runner = runner + end + + def update(params) + runner.update(params).tap do |updated| + runner.tick_runner_queue if updated + end + end + end +end diff --git a/app/services/create_snippet_service.rb b/app/services/create_snippet_service.rb index 14f5ba064ff..40286dbf3bf 100644 --- a/app/services/create_snippet_service.rb +++ b/app/services/create_snippet_service.rb @@ -1,7 +1,8 @@ class CreateSnippetService < BaseService + include SpamCheckService + def execute - request = params.delete(:request) - api = params.delete(:api) + filter_spam_check_params snippet = if project project.snippets.build(params) @@ -15,10 +16,11 @@ class CreateSnippetService < BaseService end snippet.author = current_user - snippet.spam = SpamService.new(snippet, request).check(api) + + spam_check(snippet, current_user) if snippet.save - UserAgentDetailService.new(snippet, request).create + UserAgentDetailService.new(snippet, @request).create end snippet diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb index 6ba868df04d..af6da5b9d56 100644 --- a/app/services/files/multi_service.rb +++ b/app/services/files/multi_service.rb @@ -55,7 +55,7 @@ module Files file_path = action[:file_path] file_path = action[:previous_path] if action[:action] == :move - blob = repository.blob_at_branch(params[:branch_name], file_path) + blob = repository.blob_at_branch(params[:branch], file_path) unless blob raise_error("File to be #{action[:action]}d `#{file_path}` does not exist.") @@ -89,7 +89,7 @@ module Files def validate_create(action) return if project.empty_repo? - if repository.blob_at_branch(params[:branch_name], action[:file_path]) + if repository.blob_at_branch(params[:branch], action[:file_path]) raise_error("Your changes could not be committed because a file with the name `#{action[:file_path]}` already exists.") end end @@ -102,14 +102,14 @@ module Files raise_error("You must supply the original file path when moving file `#{action[:file_path]}`.") end - blob = repository.blob_at_branch(params[:branch_name], action[:file_path]) + blob = repository.blob_at_branch(params[:branch], action[:file_path]) if blob raise_error("Move destination `#{action[:file_path]}` already exists.") end if action[:content].nil? - blob = repository.blob_at_branch(params[:branch_name], action[:previous_path]) + blob = repository.blob_at_branch(params[:branch], action[:previous_path]) blob.load_all_data!(repository) if blob.truncated? params[:actions][index][:content] = blob.data end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 5f3ced49665..9500faf2862 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -191,14 +191,12 @@ class IssuableBaseService < BaseService # To be overridden by subclasses end - def after_update(issuable) + def before_update(issuable) # To be overridden by subclasses end - def update_issuable(issuable, attributes) - issuable.with_transaction_returning_status do - issuable.update(attributes.merge(updated_by: current_user)) - end + def after_update(issuable) + # To be overridden by subclasses end def update(issuable) @@ -212,16 +210,22 @@ class IssuableBaseService < BaseService label_ids = process_label_ids(params, existing_label_ids: issuable.label_ids) params[:label_ids] = label_ids if labels_changing?(issuable.label_ids, label_ids) - if params.present? && update_issuable(issuable, params) - # We do not touch as it will affect a update on updated_at field - ActiveRecord::Base.no_touching do - handle_common_system_notes(issuable, old_labels: old_labels) - end + if params.present? + issuable.assign_attributes(params.merge(updated_by: current_user)) + + before_update(issuable) - handle_changes(issuable, old_labels: old_labels, old_mentioned_users: old_mentioned_users) - after_update(issuable) - issuable.create_new_cross_references!(current_user) - execute_hooks(issuable, 'update') + if issuable.with_transaction_returning_status { issuable.save } + # We do not touch as it will affect a update on updated_at field + ActiveRecord::Base.no_touching do + handle_common_system_notes(issuable, old_labels: old_labels) + end + + handle_changes(issuable, old_labels: old_labels, old_mentioned_users: old_mentioned_users) + after_update(issuable) + issuable.create_new_cross_references!(current_user) + execute_hooks(issuable, 'update') + end end issuable diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb index 961605a1005..366b3572738 100644 --- a/app/services/issues/create_service.rb +++ b/app/services/issues/create_service.rb @@ -1,10 +1,9 @@ module Issues class CreateService < Issues::BaseService + include SpamCheckService + def execute - @request = params.delete(:request) - @api = params.delete(:api) - @recaptcha_verified = params.delete(:recaptcha_verified) - @spam_log_id = params.delete(:spam_log_id) + filter_spam_check_params issue_attributes = params.merge(merge_request_for_resolving_discussions: merge_request_for_resolving_discussions) @issue = BuildService.new(project, current_user, issue_attributes).execute @@ -12,14 +11,8 @@ module Issues create(@issue) end - def before_create(issuable) - if @recaptcha_verified - spam_log = current_user.spam_logs.find_by(id: @spam_log_id, title: issuable.title) - spam_log&.update!(recaptcha_verified: true) - else - issuable.spam = spam_service.check(@api) - issuable.spam_log = spam_service.spam_log - end + def before_create(issue) + spam_check(issue, current_user) end def after_create(issuable) @@ -42,10 +35,6 @@ module Issues private - def spam_service - @spam_service ||= SpamService.new(@issue, @request) - end - def user_agent_detail_service UserAgentDetailService.new(@issue, @request) end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index 78cbf94ec69..22e32b13259 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -1,9 +1,17 @@ module Issues class UpdateService < Issues::BaseService + include SpamCheckService + def execute(issue) + filter_spam_check_params + update(issue) end + def before_update(issue) + spam_check(issue, current_user) + end + def handle_changes(issue, old_labels: [], old_mentioned_users: []) if has_changes?(issue, old_labels: old_labels) todo_service.mark_pending_todos_as_done(issue, current_user) diff --git a/app/services/merge_requests/add_todo_when_build_fails_service.rb b/app/services/merge_requests/add_todo_when_build_fails_service.rb index 12a8415d9a5..727768b1a39 100644 --- a/app/services/merge_requests/add_todo_when_build_fails_service.rb +++ b/app/services/merge_requests/add_todo_when_build_fails_service.rb @@ -18,5 +18,11 @@ module MergeRequests todo_service.merge_request_build_retried(merge_request) end end + + def close_all(pipeline) + pipeline_merge_requests(pipeline) do |merge_request| + todo_service.merge_request_build_retried(merge_request) + end + end end end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index f4d52e3ebbd..9d4739e37bb 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -2,18 +2,14 @@ module MergeRequests class BuildService < MergeRequests::BaseService def execute self.merge_request = MergeRequest.new(params) - merge_request.can_be_created = true merge_request.compare_commits = [] merge_request.source_project = find_source_project merge_request.target_project = find_target_project merge_request.target_branch = find_target_branch + merge_request.can_be_created = branches_valid? && source_branch_specified? && target_branch_specified? - if branches_specified? && branches_valid? - compare_branches - assign_title_and_description - else - merge_request.can_be_created = false - end + compare_branches if branches_present? + assign_title_and_description if merge_request.can_be_created merge_request end @@ -37,11 +33,17 @@ module MergeRequests target_branch || target_project.default_branch end - def branches_specified? - params[:source_branch] && params[:target_branch] + def source_branch_specified? + params[:source_branch].present? + end + + def target_branch_specified? + params[:target_branch].present? end def branches_valid? + return false unless source_branch_specified? || target_branch_specified? + validate_branches errors.blank? end @@ -55,8 +57,10 @@ module MergeRequests target_branch ) - merge_request.compare_commits = compare.commits - merge_request.compare = compare + if compare + merge_request.compare_commits = compare.commits + merge_request.compare = compare + end end def validate_branches diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 177b714b734..3da1b657a41 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -18,7 +18,7 @@ module MergeRequests @source = find_merge_source unless @source - log_merge_error('No source for merge', save_message_on_model: true) + return log_merge_error('No source for merge', save_message_on_model: true) end merge_request.in_locked_state do diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index a08c6fcd94b..9716a1780a9 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -17,8 +17,6 @@ module Projects def execute return false unless can?(current_user, :remove_project, project) - project.team.truncate - repo_path = project.path_with_namespace wiki_path = repo_path + '.wiki' @@ -30,6 +28,7 @@ module Projects Projects::UnlinkForkService.new(project, current_user).execute Project.transaction do + project.team.truncate project.destroy! unless remove_registry_tags diff --git a/app/services/projects/upload_service.rb b/app/services/projects/upload_service.rb index 012e82a7704..be34d4fa9b8 100644 --- a/app/services/projects/upload_service.rb +++ b/app/services/projects/upload_service.rb @@ -5,7 +5,7 @@ module Projects end def execute - return nil unless @file and @file.size <= max_attachment_size + return nil unless @file && @file.size <= max_attachment_size uploader = FileUploader.new(@project) uploader.store!(@file) diff --git a/app/services/spam_check_service.rb b/app/services/spam_check_service.rb new file mode 100644 index 00000000000..023e0824e85 --- /dev/null +++ b/app/services/spam_check_service.rb @@ -0,0 +1,24 @@ +# SpamCheckService +# +# Provide helper methods for checking if a given spammable object has +# potential spam data. +# +# Dependencies: +# - params with :request +# +module SpamCheckService + def filter_spam_check_params + @request = params.delete(:request) + @api = params.delete(:api) + @recaptcha_verified = params.delete(:recaptcha_verified) + @spam_log_id = params.delete(:spam_log_id) + end + + def spam_check(spammable, user) + spam_service = SpamService.new(spammable, @request) + + spam_service.when_recaptcha_verified(@recaptcha_verified, @api) do + user.spam_logs.find_by(id: @spam_log_id)&.update!(recaptcha_verified: true) + end + end +end diff --git a/app/services/spam_service.rb b/app/services/spam_service.rb index 024a7c19d33..3e65b7d31a3 100644 --- a/app/services/spam_service.rb +++ b/app/services/spam_service.rb @@ -17,15 +17,6 @@ class SpamService end end - def check(api = false) - return false unless request && check_for_spam? - - return false unless akismet.is_spam? - - create_spam_log(api) - true - end - def mark_as_spam! return false unless spammable.submittable_as_spam? @@ -36,8 +27,30 @@ class SpamService end end + def when_recaptcha_verified(recaptcha_verified, api = false) + # In case it's a request which is already verified through recaptcha, yield + # block. + if recaptcha_verified + yield + else + # Otherwise, it goes to Akismet and check if it's a spam. If that's the + # case, it assigns spammable record as "spam" and create a SpamLog record. + spammable.spam = check(api) + spammable.spam_log = spam_log + end + end + private + def check(api) + return false unless request && check_for_spam? + + return false unless akismet.is_spam? + + create_spam_log(api) + true + end + def akismet @akismet ||= AkismetService.new( spammable_owner, diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 8ab943f4639..ad86b4f9f42 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -170,16 +170,20 @@ class TodoService # When user marks some todos as done def mark_todos_as_done(todos, current_user) - mark_todos_as_done_by_ids(todos.select(&:id), current_user) + update_todos_state_by_ids(todos.select(&:id), current_user, :done) end def mark_todos_as_done_by_ids(ids, current_user) - todos = current_user.todos.where(id: ids) + update_todos_state_by_ids(ids, current_user, :done) + end - # Only return those that are not really on that state - marked_todos = todos.where.not(state: :done).update_all(state: :done) - current_user.update_todos_count_cache - marked_todos + # When user marks some todos as pending + def mark_todos_as_pending(todos, current_user) + update_todos_state_by_ids(todos.select(&:id), current_user, :pending) + end + + def mark_todos_as_pending_by_ids(ids, current_user) + update_todos_state_by_ids(ids, current_user, :pending) end # When user marks an issue as todo @@ -194,6 +198,15 @@ class TodoService private + def update_todos_state_by_ids(ids, current_user, state) + todos = current_user.todos.where(id: ids) + + # Only return those that are not really on that state + marked_todos = todos.where.not(state: state).update_all(state: state) + current_user.update_todos_count_cache + marked_todos + end + def create_todos(users, attributes) Array(users).map do |user| next if pending_todos(user, attributes).exists? diff --git a/app/services/update_snippet_service.rb b/app/services/update_snippet_service.rb index a6bb36821c3..358bca73aec 100644 --- a/app/services/update_snippet_service.rb +++ b/app/services/update_snippet_service.rb @@ -1,4 +1,6 @@ class UpdateSnippetService < BaseService + include SpamCheckService + attr_accessor :snippet def initialize(project, user, snippet, params) @@ -9,7 +11,7 @@ class UpdateSnippetService < BaseService def execute # check that user is allowed to set specified visibility_level new_visibility = params[:visibility_level] - + if new_visibility && new_visibility.to_i != snippet.visibility_level unless Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) deny_visibility_level(snippet, new_visibility) @@ -17,6 +19,10 @@ class UpdateSnippetService < BaseService end end - snippet.update_attributes(params) + filter_spam_check_params + snippet.assign_attributes(params) + spam_check(snippet, current_user) + + snippet.save end end diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb index 2d11305be13..bc0653cb634 100644 --- a/app/services/users/destroy_service.rb +++ b/app/services/users/destroy_service.rb @@ -7,6 +7,10 @@ module Users end def execute(user, options = {}) + unless current_user.admin? || current_user == user + raise Gitlab::Access::AccessDeniedError, "#{current_user} tried to destroy user #{user}!" + end + if !options[:delete_solo_owned_groups] && user.solo_owned_groups.present? user.errors[:base] << 'You must transfer ownership or delete groups before you can remove user' return user diff --git a/app/views/admin/abuse_reports/index.html.haml b/app/views/admin/abuse_reports/index.html.haml index c4b748d0ab8..6c48328da4f 100644 --- a/app/views/admin/abuse_reports/index.html.haml +++ b/app/views/admin/abuse_reports/index.html.haml @@ -12,6 +12,7 @@ %th.wide Message %th Action = render @abuse_reports + = paginate @abuse_reports, theme: 'gitlab' - else .empty-state .text-center diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 816035ec442..749c74b8110 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -192,7 +192,7 @@ = f.label :max_pages_size, 'Maximum size of pages (MB)', class: 'control-label col-sm-2' .col-sm-10 = f.number_field :max_pages_size, class: 'form-control' - .help-block Zero for unlimited + .help-block 0 for unlimited %fieldset %legend Continuous Integration @@ -525,7 +525,7 @@ = f.number_field :terminal_max_session_time, class: 'form-control' .help-block Maximum time for web terminal websocket connection (in seconds). - Set to 0 for unlimited time. + 0 for unlimited. .form-actions = f.submit 'Save', class: 'btn btn-save' diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml index 4f982a6e369..ac36bb5bb17 100644 --- a/app/views/admin/background_jobs/show.html.haml +++ b/app/views/admin/background_jobs/show.html.haml @@ -35,7 +35,7 @@ .clearfix %p %i.fa.fa-exclamation-circle - If '[25 of 25 busy]' is shown, restart GitLab with 'sudo service gitlab reload'. + If '[#{@concurrency} of #{@concurrency} busy]' is shown, restart GitLab with 'sudo service gitlab reload'. %p %i.fa.fa-exclamation-circle If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{gitlab_config.user} -f sidekiq) and restart GitLab. diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 721bc77cc2f..d725e477044 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -56,7 +56,7 @@ = submit_tag 'Search', class: 'btn' .pull-right.light - Runners with last contact less than a minute ago: #{@active_runners_cnt} + Runners with last contact more than a minute ago: #{@active_runners_cnt} %br diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index 605bfd0cf8d..a3993d5ef16 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -1,28 +1,33 @@ %li{ class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo), data: { url: todo_target_path(todo) } } - = author_avatar(todo, size: 40) + .todo-avatar + = author_avatar(todo, size: 40) .todo-item.todo-block .todo-title.title - unless todo.build_failed? || todo.unmergeable? = todo_target_state_pill(todo) - %span.author-name + .title-item.author-name - if todo.author = link_to_author(todo) - else (removed) - %span.action-name + .title-item.action-name = todo_action_name(todo) - %span.todo-label + .title-item.todo-label - if todo.target = todo_target_link(todo) - else (removed) - · #{time_ago_with_tooltip(todo.created_at)} - = todo_due_date(todo) + .title-item + · + + .title-item + #{time_ago_with_tooltip(todo.created_at)} + = todo_due_date(todo) .todo-body .todo-note @@ -31,6 +36,9 @@ - if todo.pending? .todo-actions - = link_to [:dashboard, todo], method: :delete, class: 'btn btn-loading done-todo' do + = link_to [:dashboard, todo], method: :delete, class: 'btn btn-loading js-done-todo' do Done = icon('spinner spin') + = link_to restore_dashboard_todo_path(todo), method: :patch, class: 'btn btn-loading js-undo-todo hidden' do + Undo + = icon('spinner spin') diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml index 64ca3c32e01..efd13aabf20 100644 --- a/app/views/events/event/_push.html.haml +++ b/app/views/events/event/_push.html.haml @@ -3,11 +3,9 @@ .event-title %span.author_name= link_to_author event %span.pushed #{event.action_name} #{event.ref_type} - - if event.rm_ref? - %strong= event.ref_name - - else - %strong - = link_to event.ref_name, namespace_project_commits_path(project.namespace, project, event.ref_name), title: h(event.target_title) + %strong + - commits_link = namespace_project_commits_path(project.namespace, project, event.ref_name) + = link_to_if project.repository.branch_exists?(event.ref_name), event.ref_name, commits_link = render "events/event_scope", event: event diff --git a/app/views/groups/_head.html.haml b/app/views/groups/_head.html.haml new file mode 100644 index 00000000000..6b296ea8dea --- /dev/null +++ b/app/views/groups/_head.html.haml @@ -0,0 +1,19 @@ += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: container_class } + = nav_link(path: 'groups#show', html_options: { class: 'home' }) do + = link_to group_path(@group), title: 'Group Home' do + %span + Home + + = nav_link(path: 'groups#activity') do + = link_to activity_group_path(@group), title: 'Activity' do + %span + Activity + + = nav_link(path: 'group_members#index') do + = link_to group_group_members_path(@group), title: 'Members' do + %span + Members diff --git a/app/views/groups/_head_issues.html.haml b/app/views/groups/_head_issues.html.haml new file mode 100644 index 00000000000..d554bc23743 --- /dev/null +++ b/app/views/groups/_head_issues.html.haml @@ -0,0 +1,19 @@ += content_for :sub_nav do + .scrolling-tabs-container.sub-nav-scroll + = render 'shared/nav_scroll' + .nav-links.sub-nav.scrolling-tabs + %ul{ class: container_class } + = nav_link(path: 'groups#issues', html_options: { class: 'home' }) do + = link_to issues_group_path(@group), title: 'List' do + %span + List + + = nav_link(path: 'labels#index') do + = link_to group_labels_path(@group), title: 'Labels' do + %span + Labels + + = nav_link(path: 'milestones#index') do + = link_to group_milestones_path(@group), title: 'Milestones' do + %span + Milestones diff --git a/app/views/groups/activity.html.haml b/app/views/groups/activity.html.haml index aaad265b3ee..d7375b23524 100644 --- a/app/views/groups/activity.html.haml +++ b/app/views/groups/activity.html.haml @@ -2,7 +2,8 @@ - if current_user = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") -- page_title "Activity" +- page_title "Activity" += render 'groups/head' %section.activities = render 'activities' diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index 2e4e4511bb6..8cb56443191 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -1,4 +1,5 @@ - page_title "Members" += render 'groups/head' .project-members-page.prepend-top-default %h4 diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 83edb719692..939bddf3fe9 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -1,4 +1,5 @@ - page_title "Issues" += render "head_issues" = content_for :meta_tags do - if current_user = auto_discovery_link_tag(:atom, url_for(params.merge(format: :atom, private_token: current_user.private_token)), title: "#{@group.name} issues") diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index 45325d6bc4b..2bc00fb16c8 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -1,4 +1,5 @@ - page_title 'Labels' += render "groups/head_issues" .top-area.adjust .nav-text diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index cd5388fe402..644895c56a1 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -1,4 +1,5 @@ - page_title "Milestones" += render "groups/head_issues" .top-area = render 'shared/milestones_filter' diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index b040f404ac4..3d7b469660a 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -4,6 +4,7 @@ - if current_user = auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity") += render 'groups/head' = render 'groups/home_panel' diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 54d02ee8e4b..a35a918d501 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -1,21 +1,4 @@ -.page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" } - .sidebar-wrapper.nicescroll - .sidebar-action-buttons - .nav-header-btn.toggle-nav-collapse{ title: "Open/Close" } - %span.sr-only Toggle navigation - = icon('bars') - - %div{ class: "nav-header-btn pin-nav-btn has-tooltip #{'is-active' if pinned_nav?} js-nav-pin", title: pinned_nav? ? "Unpin navigation" : "Pin Navigation", data: { placement: 'right', container: 'body' } } - %span.sr-only Toggle navigation pinning - = icon('fw thumb-tack') - - - if defined?(sidebar) && sidebar - = render "layouts/nav/#{sidebar}" - - elsif current_user - = render 'layouts/nav/dashboard' - - else - = render 'layouts/nav/explore' - +.page-with-sidebar{ class: page_gutter_class } - if defined?(nav) && nav .layout-nav .container-fluid diff --git a/app/views/layouts/_recaptcha_verification.html.haml b/app/views/layouts/_recaptcha_verification.html.haml new file mode 100644 index 00000000000..77c77dc6754 --- /dev/null +++ b/app/views/layouts/_recaptcha_verification.html.haml @@ -0,0 +1,23 @@ +- humanized_resource_name = spammable.class.model_name.human.downcase +- resource_name = spammable.class.model_name.singular + +%h3.page-title + Anti-spam verification +%hr + +%p + #{"We detected potential spam in the #{humanized_resource_name}. Please solve the reCAPTCHA to proceed."} + += form_for form do |f| + .recaptcha + - params[resource_name].each do |field, value| + = hidden_field(resource_name, field, value: value) + = hidden_field_tag(:spam_log_id, spammable.spam_log.id) + = hidden_field_tag(:recaptcha_verification, true) + = recaptcha_tags + + -# Yields a block with given extra params. + = yield + + .row-content-block.footer-block + = f.submit "Submit #{humanized_resource_name}", class: 'btn btn-create' diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 248d439cd05..19bd9b6d5c9 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: "en", class: "#{page_class}" } = render "layouts/head" - %body{ class: "#{user_application_theme}", data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } } + %body{ data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } } = Gon::Base.render_data = render "layouts/header/default", title: header_title diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 59082ce5fd5..0b8388cbff3 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -2,9 +2,15 @@ %a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content .container-fluid .header-content - %button.side-nav-toggle{ type: 'button', "aria-label" => "Toggle global navigation" } - %span.sr-only Toggle navigation - = icon('bars') + .dropdown.global-dropdown + %button.global-dropdown-toggle{ type: 'button', 'data-toggle' => 'dropdown' } + %span.sr-only Toggle navigation + = icon('bars') + .dropdown-menu-nav.global-dropdown-menu + - if current_user + = render 'layouts/nav/dashboard' + - else + = render 'layouts/nav/explore' %button.navbar-toggle{ type: 'button' } %span.sr-only Toggle navigation = icon('ellipsis-v') @@ -29,7 +35,7 @@ = link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('bell fw') %span.badge.todos-pending-count{ class: ("hidden" if todos_pending_count == 0) } - = todos_pending_count + = todos_count_format(todos_pending_count) - if Gitlab::Sherlock.enabled? %li = link_to sherlock_transactions_path, title: 'Sherlock Transactions', @@ -55,7 +61,6 @@ %div = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success' - %h1.title= title .header-logo diff --git a/app/views/layouts/mailer.html.haml b/app/views/layouts/mailer.html.haml new file mode 100644 index 00000000000..53268cc22f8 --- /dev/null +++ b/app/views/layouts/mailer.html.haml @@ -0,0 +1,72 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +%html{ lang: "en" } + %head + %meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }/ + %meta{ content: "width=device-width, initial-scale=1", name: "viewport" }/ + %meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }/ + %title= message.subject + :css + /* CLIENT-SPECIFIC STYLES */ + body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } + table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; } + img { -ms-interpolation-mode: bicubic; } + + /* iOS BLUE LINKS */ + a[x-apple-data-detectors] { + color: inherit !important; + text-decoration: none !important; + font-size: inherit !important; + font-family: inherit !important; + font-weight: inherit !important; + line-height: inherit !important; + } + + /* ANDROID MARGIN HACK */ + body { margin:0 !important; } + div[style*="margin: 16px 0"] { margin:0 !important; } + + @media only screen and (max-width: 639px) { + body, #body { + min-width: 320px !important; + } + table.wrapper { + width: 100% !important; + min-width: 320px !important; + } + table.wrapper > tbody > tr > td { + border-left: 0 !important; + border-right: 0 !important; + border-radius: 0 !important; + padding-left: 10px !important; + padding-right: 10px !important; + } + } + %body{ style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" } + %table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" } + %tbody + %tr.line + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" } + %tr.header + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } + = header_logo + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" } + %table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;" } + %tbody + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" } + %table.content{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" } + %tbody + = yield + + %tr.footer + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } + %img{ alt: "GitLab", height: "33", src: image_url('mailers/gitlab_footer_logo.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/ + %div + %a{ href: profile_notifications_url, style: "color:#3777b0;text-decoration:none;" } Manage all notifications + · + %a{ href: help_url, style: "color:#3777b0;text-decoration:none;" } Help + %div + You're receiving this email because of your account on + = succeed "." do + %a{ href: root_url, style: "color:#3777b0;text-decoration:none;" }= Gitlab.config.gitlab.host diff --git a/app/views/layouts/mailer.text.haml b/app/views/layouts/mailer.text.haml new file mode 100644 index 00000000000..6a9c6ced9cc --- /dev/null +++ b/app/views/layouts/mailer.text.haml @@ -0,0 +1,5 @@ += yield + +You're receiving this email because of your account on #{Gitlab.config.gitlab.host}. +Manage all notifications: #{profile_notifications_url} +Help: #{help_url} diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 205d23178d2..5d4178f03d7 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -1,41 +1,39 @@ -.nav-sidebar - %ul.nav - = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do - = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do - %span - Projects - = nav_link(path: 'dashboard#activity') do - = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do - %span - Activity - - if koding_enabled? - = nav_link(controller: :koding) do - = link_to koding_path, title: 'Koding' do - %span - Koding - = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do - = link_to dashboard_groups_path, title: 'Groups' do - %span - Groups - = nav_link(controller: 'dashboard/milestones') do - = link_to dashboard_milestones_path, title: 'Milestones' do - %span - Milestones - = nav_link(path: 'dashboard#issues') do - = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do - %span - Issues - %span.count= number_with_delimiter(cached_assigned_issuables_count(current_user, :issues, :opened)) - = nav_link(path: 'dashboard#merge_requests') do - = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do - %span - Merge Requests - %span.count= number_with_delimiter(cached_assigned_issuables_count(current_user, :merge_requests, :opened)) - = nav_link(controller: 'dashboard/snippets') do - = link_to dashboard_snippets_path, title: 'Snippets' do +%ul + = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: {class: "#{project_tab_class} home"}) do + = link_to dashboard_projects_path, title: 'Projects', class: 'dashboard-shortcuts-projects' do + %span + Projects + = nav_link(path: 'dashboard#activity') do + = link_to activity_dashboard_path, class: 'dashboard-shortcuts-activity', title: 'Activity' do + %span + Activity + - if koding_enabled? + = nav_link(controller: :koding) do + = link_to koding_path, title: 'Koding' do %span - Snippets - - = link_to help_path, title: 'About GitLab CE', class: 'about-gitlab' do + Koding + = nav_link(controller: [:groups, 'groups/milestones', 'groups/group_members']) do + = link_to dashboard_groups_path, title: 'Groups' do + %span + Groups + = nav_link(controller: 'dashboard/milestones') do + = link_to dashboard_milestones_path, title: 'Milestones' do + %span + Milestones + = nav_link(path: 'dashboard#issues') do + = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'dashboard-shortcuts-issues' do + %span + Issues + (#{number_with_delimiter(cached_assigned_issuables_count(current_user, :issues, :opened))}) + = nav_link(path: 'dashboard#merge_requests') do + = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'dashboard-shortcuts-merge_requests' do + %span + Merge Requests + (#{number_with_delimiter(cached_assigned_issuables_count(current_user, :merge_requests, :opened))}) + = nav_link(controller: 'dashboard/snippets') do + = link_to dashboard_snippets_path, title: 'Snippets' do %span - About GitLab CE + Snippets + %li.divider + %li + = link_to "About GitLab CE", help_path, title: 'About GitLab CE', class: 'about-gitlab' diff --git a/app/views/layouts/nav/_explore.html.haml b/app/views/layouts/nav/_explore.html.haml index e5bda7b3a6f..3a1fcd00e9c 100644 --- a/app/views/layouts/nav/_explore.html.haml +++ b/app/views/layouts/nav/_explore.html.haml @@ -1,4 +1,4 @@ -%ul.nav.nav-sidebar +%ul = nav_link(path: ['dashboard#show', 'root#show', 'projects#trending', 'projects#starred', 'projects#index'], html_options: {class: 'home'}) do = link_to explore_root_path, title: 'Projects' do %span diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index f3539fd372d..e0742d70fac 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -5,23 +5,11 @@ .fade-right = icon('angle-right') %ul.nav-links.scrolling-tabs - = nav_link(path: 'groups#show', html_options: {class: 'home'}) do + = nav_link(path: ['groups#show', 'groups#activity', 'group_members#index'], html_options: { class: 'home' }) do = link_to group_path(@group), title: 'Home' do %span Group - = nav_link(path: 'groups#activity') do - = link_to activity_group_path(@group), title: 'Activity' do - %span - Activity - = nav_link(controller: [:group, :labels]) do - = link_to group_labels_path(@group), title: 'Labels' do - %span - Labels - = nav_link(controller: [:group, :milestones]) do - = link_to group_milestones_path(@group), title: 'Milestones' do - %span - Milestones - = nav_link(path: 'groups#issues') do + = nav_link(path: ['groups#issues', 'labels#index', 'milestones#index']) do = link_to issues_group_path(@group), title: 'Issues' do %span Issues @@ -33,7 +21,3 @@ Merge Requests - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened', non_archived: true).execute %span.badge.count= number_with_delimiter(merge_requests.count) - = nav_link(controller: [:group_members]) do - = link_to group_group_members_path(@group), title: 'Members' do - %span - Members diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index d9ebbaa2704..85a1aea3a61 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -1,179 +1,109 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -%html{ lang: "en" } - %head - %meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }/ - %meta{ content: "width=device-width, initial-scale=1", name: "viewport" }/ - %meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }/ - %title= message.subject - :css - /* CLIENT-SPECIFIC STYLES */ - body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } - table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; } - img { -ms-interpolation-mode: bicubic; } - - /* iOS BLUE LINKS */ - a[x-apple-data-detectors] { - color: inherit !important; - text-decoration: none !important; - font-size: inherit !important; - font-family: inherit !important; - font-weight: inherit !important; - line-height: inherit !important; - } - - /* ANDROID MARGIN HACK */ - body { margin:0 !important; } - div[style*="margin: 16px 0"] { margin:0 !important; } - - @media only screen and (max-width: 639px) { - body, #body { - min-width: 320px !important; - } - table.wrapper { - width: 100% !important; - min-width: 320px !important; - } - table.wrapper > tbody > tr > td { - border-left: 0 !important; - border-right: 0 !important; - border-radius: 0 !important; - padding-left: 10px !important; - padding-right: 10px !important; - } - } - %body{ style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" } - %table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" } +%tr.alert + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;background-color:#d22f57;color:#ffffff;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" } %tbody - %tr.line - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" } - %tr.header - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } - %img{ alt: "GitLab", height: "50", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo.gif'), width: "55" }/ %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" } - %table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;" } + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" } + %img{ alt: "x", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif'), style: "display:block;", width: "13" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" } + Your pipeline has failed. +%tr.spacer + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" } + +%tr.section + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" } + %table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" } + %tbody + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" } Project + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" } + - namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name + - namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner) + %a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" } + = namespace_name + \/ + %a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" } + = @project.name + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Branch + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } + %tbody + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } + %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } + %a.muted{ href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;" } + = @pipeline.ref + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Commit + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %tbody %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" } - %table.content{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" } - %tbody - %tr.alert - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;background-color:#d22f57;color:#ffffff;" } - %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" } - %tbody - %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" } - %img{ alt: "x", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif'), style: "display:block;", width: "13" }/ - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" } - Your pipeline has failed. - %tr.spacer - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" } - - %tr.section - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" } - %table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" } - %tbody - %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" } Project - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" } - - namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name - - namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner) - %a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" } - = namespace_name - \/ - %a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" } - = @project.name - %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Branch - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } - %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } - %tbody - %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } - %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }/ - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } - %a.muted{ href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;" } - = @pipeline.ref - %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Commit - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } - %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } - %tbody - %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } - %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "Commit icon" }/ - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } - %a{ href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } - = @pipeline.short_sha - - if @merge_request - in - %a{ href: merge_request_url(@merge_request), style: "color:#3777b0;text-decoration:none;" } - = @merge_request.to_reference - .commit{ style: "color:#5c5c5c;font-weight:300;" } - = @pipeline.git_commit_message.truncate(50) - %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Author - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } - %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } - %tbody - %tr - - commit = @pipeline.commit - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } - %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } - - if commit.author - %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" } - = commit.author.name - - else - %span - = commit.author_name - %tr.spacer - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" } - - - failed = @pipeline.statuses.latest.failed - %tr.pre-section - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 0;" } - Pipeline - %a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } - = "\##{@pipeline.id}" - had - = failed.size - failed - #{'build'.pluralize(failed.size)}. - %tr.warning - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border:1px solid #ededed;border-bottom:0;border-radius:3px 3px 0 0;overflow:hidden;background-color:#fdf4f6;color:#d22852;font-size:14px;line-height:1.4;text-align:center;padding:8px 15px;" } - Logs may contain sensitive data. Please consider before forwarding this email. - %tr.section - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;border-top:0;border-radius:0 0 3px 3px;" } - %table.builds{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:collapse;" } - %tbody - - failed.each do |build| - %tr.build-state - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;" } - %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } - %tbody - %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;padding-right:5px;" } - %img{ alt: "x", height: "10", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red.gif'), style: "display:block;", width: "10" }/ - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;" } - = build.stage - %td{ align: "right", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;" } - = render "notify/links/#{build.to_partial_path}", pipeline: @pipeline, build: build - %tr.build-log - - if build.has_trace? - %td{ colspan: "2", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 0 15px;" } - %pre{ style: "font-family:Monaco,'Lucida Console','Courier New',Courier,monospace;background-color:#fafafa;border-radius:3px;overflow:hidden;white-space:pre-wrap;word-break:break-all;font-size:13px;line-height:1.4;padding:12px;color:#333333;margin:0;" } - = build.trace_html(last_lines: 10).html_safe - - else - %td{ colspan: "2" } - %tr.footer - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } - %img{ alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/ - %div - %a{ href: profile_notifications_url, style: "color:#3777b0;text-decoration:none;" } Manage all notifications - · - %a{ href: help_url, style: "color:#3777b0;text-decoration:none;" } Help - %div - You're receiving this email because of your account on - = succeed "." do - %a{ href: root_url, style: "color:#3777b0;text-decoration:none;" }= Gitlab.config.gitlab.host + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } + %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "Commit icon" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } + %a{ href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } + = @pipeline.short_sha + - if @merge_request + in + %a{ href: merge_request_url(@merge_request), style: "color:#3777b0;text-decoration:none;" } + = @merge_request.to_reference + .commit{ style: "color:#5c5c5c;font-weight:300;" } + = @pipeline.git_commit_message.truncate(50) + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Author + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } + %tbody + %tr + - commit = @pipeline.commit + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } + %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } + - if commit.author + %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" } + = commit.author.name + - else + %span + = commit.author_name +%tr.spacer + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" } + +- failed = @pipeline.statuses.latest.failed +%tr.pre-section + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 0;" } + Pipeline + %a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } + = "\##{@pipeline.id}" + had + = failed.size + failed + #{'build'.pluralize(failed.size)}. +%tr.warning + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border:1px solid #ededed;border-bottom:0;border-radius:3px 3px 0 0;overflow:hidden;background-color:#fdf4f6;color:#d22852;font-size:14px;line-height:1.4;text-align:center;padding:8px 15px;" } + Logs may contain sensitive data. Please consider before forwarding this email. +%tr.section + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;border-top:0;border-radius:0 0 3px 3px;" } + %table.builds{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:collapse;" } + %tbody + - failed.each do |build| + %tr.build-state + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } + %tbody + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;padding-right:5px;" } + %img{ alt: "x", height: "10", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red.gif'), style: "display:block;", width: "10" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;" } + = build.stage + %td{ align: "right", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;" } + = render "notify/links/#{build.to_partial_path}", pipeline: @pipeline, build: build + %tr.build-log + - if build.has_trace? + %td{ colspan: "2", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 0 15px;" } + %pre{ style: "font-family:Monaco,'Lucida Console','Courier New',Courier,monospace;background-color:#fafafa;border-radius:3px;overflow:hidden;white-space:pre-wrap;word-break:break-all;font-size:13px;line-height:1.4;padding:12px;color:#333333;margin:0;" } + = build.trace_html(last_lines: 10).html_safe + - else + %td{ colspan: "2" } diff --git a/app/views/notify/pipeline_failed_email.text.erb b/app/views/notify/pipeline_failed_email.text.erb index ab91c7ef350..520a2fc7d68 100644 --- a/app/views/notify/pipeline_failed_email.text.erb +++ b/app/views/notify/pipeline_failed_email.text.erb @@ -27,7 +27,3 @@ Trace: <%= build.trace_with_state(last_lines: 10)[:text] %> <% end -%> <% end -%> - -You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>. -Manage all notifications: <%= profile_notifications_url %> -Help: <%= help_url %> diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index 8add2e18206..19d4add06f5 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -1,154 +1,84 @@ -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -%html{ lang: "en" } - %head - %meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }/ - %meta{ content: "width=device-width, initial-scale=1", name: "viewport" }/ - %meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }/ - %title= message.subject - :css - /* CLIENT-SPECIFIC STYLES */ - body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } - table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; } - img { -ms-interpolation-mode: bicubic; } - - /* iOS BLUE LINKS */ - a[x-apple-data-detectors] { - color: inherit !important; - text-decoration: none !important; - font-size: inherit !important; - font-family: inherit !important; - font-weight: inherit !important; - line-height: inherit !important; - } - - /* ANDROID MARGIN HACK */ - body { margin:0 !important; } - div[style*="margin: 16px 0"] { margin:0 !important; } - - @media only screen and (max-width: 639px) { - body, #body { - min-width: 320px !important; - } - table.wrapper { - width: 100% !important; - min-width: 320px !important; - } - table.wrapper > tbody > tr > td { - border-left: 0 !important; - border-right: 0 !important; - border-radius: 0 !important; - padding-left: 10px !important; - padding-right: 10px !important; - } - } - %body{ style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" } - %table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" } +%tr.success + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#31af64;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" } %tbody - %tr.line - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" } - %tr.header - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } - %img{ alt: "GitLab", height: "50", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo.gif'), width: "55" }/ %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" } - %table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;" } + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" } + %img{ alt: "✓", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif'), style: "display:block;", width: "13" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" } + Your pipeline has passed. +%tr.spacer + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" } + +%tr.section + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" } + %table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" } + %tbody + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" } Project + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" } + - namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name + - namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner) + %a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" } + = namespace_name + \/ + %a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" } + = @project.name + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Branch + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } + %tbody + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } + %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } + %a.muted{ href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;" } + = @pipeline.ref + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Commit + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } + %tbody + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } + %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "Commit icon" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } + %a{ href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } + = @pipeline.short_sha + - if @merge_request + in + %a{ href: merge_request_url(@merge_request), style: "color:#3777b0;text-decoration:none;" } + = @merge_request.to_reference + .commit{ style: "color:#5c5c5c;font-weight:300;" } + = @pipeline.git_commit_message.truncate(50) + %tr + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Author + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %tbody %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" } - %table.content{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" } - %tbody - %tr.success - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#31af64;" } - %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" } - %tbody - %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" } - %img{ alt: "✓", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif'), style: "display:block;", width: "13" }/ - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" } - Your pipeline has passed. - %tr.spacer - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" } - - %tr.section - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" } - %table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" } - %tbody - %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" } Project - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" } - - namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name - - namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner) - %a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" } - = namespace_name - \/ - %a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" } - = @project.name - %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Branch - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } - %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } - %tbody - %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } - %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }/ - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } - %a.muted{ href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;" } - = @pipeline.ref - %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Commit - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } - %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } - %tbody - %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } - %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "Commit icon" }/ - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } - %a{ href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } - = @pipeline.short_sha - - if @merge_request - in - %a{ href: merge_request_url(@merge_request), style: "color:#3777b0;text-decoration:none;" } - = @merge_request.to_reference - .commit{ style: "color:#5c5c5c;font-weight:300;" } - = @pipeline.git_commit_message.truncate(50) - %tr - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Author - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } - %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } - %tbody - %tr - - commit = @pipeline.commit - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } - %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } - - if commit.author - %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" } - = commit.author.name - - else - %span - = commit.author_name - %tr.spacer - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" } - - %tr.success-message - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;" } - - build_count = @pipeline.statuses.latest.size - - stage_count = @pipeline.stages_count - Pipeline - %a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } - = "\##{@pipeline.id}" - successfully completed - #{build_count} #{'build'.pluralize(build_count)} - in - #{stage_count} #{'stage'.pluralize(stage_count)}. - %tr.footer - %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } - %img{ alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/ - %div - %a{ href: profile_notifications_url, style: "color:#3777b0;text-decoration:none;" } Manage all notifications - · - %a{ href: help_url, style: "color:#3777b0;text-decoration:none;" } Help - %div - You're receiving this email because of your account on - = succeed "." do - %a{ href: root_url, style: "color:#3777b0;text-decoration:none;" }= Gitlab.config.gitlab.host + - commit = @pipeline.commit + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } + %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } + - if commit.author + %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" } + = commit.author.name + - else + %span + = commit.author_name +%tr.spacer + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" } + +%tr.success-message + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;" } + - build_count = @pipeline.statuses.latest.size + - stage_count = @pipeline.stages_count + Pipeline + %a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } + = "\##{@pipeline.id}" + successfully completed + #{build_count} #{'build'.pluralize(build_count)} + in + #{stage_count} #{'stage'.pluralize(stage_count)}. diff --git a/app/views/notify/pipeline_success_email.text.erb b/app/views/notify/pipeline_success_email.text.erb index 40e5e306426..0970a3a4e09 100644 --- a/app/views/notify/pipeline_success_email.text.erb +++ b/app/views/notify/pipeline_success_email.text.erb @@ -18,7 +18,3 @@ Commit Author: <%= commit.author_name %> <% build_count = @pipeline.statuses.latest.size -%> <% stage_count = @pipeline.stages_count -%> Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) successfully completed <%= build_count %> <%= 'build'.pluralize(build_count) %> in <%= stage_count %> <%= 'stage'.pluralize(stage_count) %>. - -You're receiving this email because of your account on <%= Gitlab.config.gitlab.host %>. -Manage all notifications: <%= profile_notifications_url %> -Help: <%= help_url %> diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index feadd863b00..df0a0212f3d 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -4,19 +4,6 @@ = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f| .col-lg-3.profile-settings-sidebar %h4.prepend-top-0 - Application theme - %p - This setting allows you to customize the appearance of the site, e.g. the sidebar. - .col-lg-9.application-theme - - Gitlab::Themes.each do |theme| - = label_tag do - .preview{ class: theme.css_class } - = f.radio_button :theme_id, theme.id - = theme.name - .col-sm-12 - %hr - .col-lg-3.profile-settings-sidebar - %h4.prepend-top-0 Syntax highlighting theme %p This setting allow you to customize the appearance of the syntax. diff --git a/app/views/profiles/preferences/update.js.erb b/app/views/profiles/preferences/update.js.erb index 8966dd3fd86..431ab9d052b 100644 --- a/app/views/profiles/preferences/update.js.erb +++ b/app/views/profiles/preferences/update.js.erb @@ -1,7 +1,3 @@ -// Remove body class for any previous theme, re-add current one -$('body').removeClass('<%= Gitlab::Themes.body_classes %>') -$('body').addClass('<%= user_application_theme %>') - // Toggle container-fluid class if ('<%= current_user.layout %>' === 'fluid') { $('.content-wrapper .container-fluid').removeClass('container-limited') diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml index 3b1a2e54ec2..d1f7f65bf53 100644 --- a/app/views/projects/blob/diff.html.haml +++ b/app/views/projects/blob/diff.html.haml @@ -25,6 +25,6 @@ = link_to raw(line_new), "##{line_new}" = line_content - - if @form.unfold? && @form.bottom? && @form.to < @blob.loc + - if @form.unfold? && @form.bottom? && @form.to < @blob.lines.size %tr.line_holder{ id: @form.to, class: line_class } = diff_match_line @form.to - @form.offset, @form.to, text: @match_line, view: diff_view, bottom: true diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 6abff6aaf95..c2b32a22170 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -3,7 +3,7 @@ .pull-right - if can?(current_user, :update_pipeline, pipeline.project) - if pipeline.builds.latest.failed.any?(&:retryable?) - = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post + = link_to "Retry", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'js-retry-button btn btn-grouped btn-primary', method: :post - if pipeline.builds.running_or_pending.any? = link_to "Cancel running", cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml index b87b79b170e..5c38b5ad9c0 100644 --- a/app/views/projects/diffs/_content.html.haml +++ b/app/views/projects/diffs/_content.html.haml @@ -15,10 +15,13 @@ %a.click-to-expand Click to expand it. - elsif diff_file.diff_lines.length > 0 + - total_lines = 0 + - if blob.lines.any? + - total_lines = blob.lines.last.chomp == '' ? blob.lines.size - 1 : blob.lines.size - if diff_view == :parallel - = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob + = render "projects/diffs/parallel_view", diff_file: diff_file, total_lines: total_lines - else - = render "projects/diffs/text_file", diff_file: diff_file + = render "projects/diffs/text_file", diff_file: diff_file, total_lines: total_lines - else - if diff_file.mode_changed? .nothing-here-block File mode changed diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml index 074f1f634ae..997bf0fc560 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -43,7 +43,8 @@ - discussion_left, discussion_right = parallel_diff_discussions(left, right, diff_file) - if discussion_left || discussion_right = render "discussions/parallel_diff_discussion", discussion_left: discussion_left, discussion_right: discussion_right - - if !diff_file.new_file && diff_file.diff_lines.any? + - if !diff_file.new_file && !diff_file.deleted_file && diff_file.diff_lines.any? - last_line = diff_file.diff_lines.last - %tr.line_holder.parallel - = diff_match_line last_line.old_pos, last_line.new_pos, bottom: true, view: :parallel + - if last_line.new_pos < total_lines + %tr.line_holder.parallel + = diff_match_line last_line.old_pos, last_line.new_pos, bottom: true, view: :parallel diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml index 2eea1db169a..ebd1a914ee7 100644 --- a/app/views/projects/diffs/_text_file.html.haml +++ b/app/views/projects/diffs/_text_file.html.haml @@ -10,7 +10,8 @@ as: :line, locals: { diff_file: diff_file, discussions: discussions } - - if !diff_file.new_file && diff_file.highlighted_diff_lines.any? + - if !diff_file.new_file && !diff_file.deleted_file && diff_file.highlighted_diff_lines.any? - last_line = diff_file.highlighted_diff_lines.last - %tr.line_holder - = diff_match_line last_line.old_pos, last_line.new_pos, bottom: true + - if last_line.new_pos < total_lines + %tr.line_holder + = diff_match_line last_line.old_pos, last_line.new_pos, bottom: true diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index d3eb3b7055b..069f3d97943 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -40,7 +40,7 @@ = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' %li = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue) - - if @issue.submittable_as_spam? && current_user.admin? + - if @issue.submittable_as_spam_by?(current_user) %li = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam' @@ -50,7 +50,7 @@ - if can?(current_user, :update_issue, @issue) = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' - - if @issue.submittable_as_spam? && current_user.admin? + - if @issue.submittable_as_spam_by?(current_user) = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam' = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit' diff --git a/app/views/projects/issues/verify.html.haml b/app/views/projects/issues/verify.html.haml index 1934b18c086..09aa401e44a 100644 --- a/app/views/projects/issues/verify.html.haml +++ b/app/views/projects/issues/verify.html.haml @@ -1,20 +1,4 @@ -- page_title "Anti-spam verification" +- form = [@project.namespace.becomes(Namespace), @project, @issue] -%h3.page-title - Anti-spam verification -%hr - -%p - We detected potential spam in the issue description. Please verify that you are not a robot to submit the issue. - -= form_for [@project.namespace.becomes(Namespace), @project, @issue] do |f| - .recaptcha - - params[:issue].each do |field, value| - = hidden_field(:issue, field, value: value) - = hidden_field_tag(:merge_request_for_resolving_discussions, params[:merge_request_for_resolving_discussions]) - = hidden_field_tag(:spam_log_id, @issue.spam_log.id) - = hidden_field_tag(:recaptcha_verification, true) - = recaptcha_tags - - .row-content-block.footer-block - = f.submit "Submit #{@issue.class.model_name.human.downcase}", class: 'btn btn-create' += render layout: 'layouts/recaptcha_verification', locals: { spammable: @issue, form: form } do + = hidden_field_tag(:merge_request_for_resolving_discussions, params[:merge_request_for_resolving_discussions]) diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 144b3a9c8c8..83e6c026ba7 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -5,18 +5,19 @@ = render "projects/issues/head" = render 'projects/last_push' +- content_for :page_specific_javascripts do + = page_specific_javascript_bundle_tag('filtered_search') + %div{ class: container_class } .top-area = render 'shared/issuable/nav', type: :merge_requests .nav-controls - = render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project) - - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project)) - if merge_project = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New Merge Request" do New Merge Request - = render 'shared/issuable/filter', type: :merge_requests + = render 'shared/issuable/search_bar', type: :merge_requests .merge-requests-holder = render 'merge_requests' diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index a07885537b9..2a98bba05ee 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -22,7 +22,7 @@ - if current_user.can_select_namespace? .input-group-addon = root_url - = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user, display_path: true), {}, {class: 'select2 js-select-namespace', tabindex: 1} + = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true), {}, {class: 'select2 js-select-namespace', tabindex: 1} - else .input-group-addon.static-namespace diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 1b08165c14c..a73e8f345e0 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -71,7 +71,7 @@ - if note_editable .original-note-content.hidden{ data: { post_url: namespace_project_note_path(@project.namespace, @project, note), target_id: note.noteable.id, target_type: note.noteable.class.name.underscore } } #{note.note} - %textarea.hidden.js-task-list-field.original-task-list= note.note + %textarea.hidden.js-task-list-field.original-task-list{ data: {update_url: namespace_project_note_path(@project.namespace, @project, note) } }= note.note .note-awards = render 'award_emoji/awards_block', awardable: note, inline: false - if note.system diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index a6cd2d83bd5..0605af4fcd3 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -7,9 +7,9 @@ = commit_author_link(@commit) .header-action-buttons - if can?(current_user, :update_pipeline, @pipeline.project) - - if @pipeline.builds.latest.failed.any?(&:retryable?) - = link_to "Retry failed", retry_namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'btn btn-inverted-secondary', method: :post - - if @pipeline.builds.running_or_pending.any? + - if @pipeline.retryable? + = link_to "Retry", retry_namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'js-retry-button btn btn-inverted-secondary', method: :post + - if @pipeline.cancelable? = link_to "Cancel running", cancel_namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post - if @commit diff --git a/app/views/projects/pipelines_settings/_badge.html.haml b/app/views/projects/pipelines_settings/_badge.html.haml index 22a3b884520..43bbd735059 100644 --- a/app/views/projects/pipelines_settings/_badge.html.haml +++ b/app/views/projects/pipelines_settings/_badge.html.haml @@ -25,3 +25,10 @@ HTML .col-md-10.code.js-syntax-highlight = highlight('.html', badge.to_html) + .row + %hr + .row + .col-md-2.text-center + AsciiDoc + .col-md-10.code.js-syntax-highlight + = highlight('.adoc', badge.to_asciidoc) diff --git a/app/views/projects/pipelines_settings/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml index 8024fb8979d..132f6372e40 100644 --- a/app/views/projects/pipelines_settings/_show.html.haml +++ b/app/views/projects/pipelines_settings/_show.html.haml @@ -60,7 +60,7 @@ = f.label :build_coverage_regex, "Test coverage parsing", class: 'label-light' .input-group %span.input-group-addon / - = f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered' + = f.text_field :build_coverage_regex, class: 'form-control', placeholder: 'Regular expression' %span.input-group-addon / %p.help-block A regular expression that will be used to find the test coverage diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml index dde2e2b644d..34ee4ff1937 100644 --- a/app/views/projects/snippets/_actions.html.haml +++ b/app/views/projects/snippets/_actions.html.haml @@ -10,7 +10,7 @@ - if can?(current_user, :create_project_snippet, @project) = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do New snippet - - if @snippet.submittable_as_spam? && current_user.admin? + - if @snippet.submittable_as_spam_by?(current_user) = link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam' - if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet) .visible-xs-block.dropdown @@ -31,6 +31,6 @@ %li = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do Edit - - if @snippet.submittable_as_spam? && current_user.admin? + - if @snippet.submittable_as_spam_by?(current_user) %li = link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post diff --git a/app/views/projects/snippets/verify.html.haml b/app/views/projects/snippets/verify.html.haml new file mode 100644 index 00000000000..eb56f03b3f4 --- /dev/null +++ b/app/views/projects/snippets/verify.html.haml @@ -0,0 +1,4 @@ +- form = [@project.namespace.becomes(Namespace), @project, @snippet.becomes(Snippet)] + += render 'layouts/recaptcha_verification', spammable: @snippet, form: form + diff --git a/app/views/projects/variables/_form.html.haml b/app/views/projects/variables/_form.html.haml index a5bae83e0ce..1ae86d258af 100644 --- a/app/views/projects/variables/_form.html.haml +++ b/app/views/projects/variables/_form.html.haml @@ -6,5 +6,5 @@ = f.text_field :key, class: "form-control", placeholder: "PROJECT_VARIABLE", required: true .form-group = f.label :value, "Value", class: "label-light" - = f.text_area :value, class: "form-control", placeholder: "PROJECT_VARIABLE", required: true + = f.text_area :value, class: "form-control", placeholder: "PROJECT_VARIABLE" = f.submit btn_text, class: "btn btn-save" diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml index 7fe2bce3e7c..22004ecacbc 100644 --- a/app/views/search/_results.html.haml +++ b/app/views/search/_results.html.haml @@ -11,7 +11,7 @@ .results.prepend-top-10 - if @scope == 'commits' - %ul.list-unstyled + %ul.content-list.commit-list.table-list.table-wide = render partial: "search/results/commit", collection: @search_objects - else .search-results diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml index e977c1f1698..f84be600df8 100644 --- a/app/views/search/results/_snippet_blob.html.haml +++ b/app/views/search/results/_snippet_blob.html.haml @@ -12,41 +12,34 @@ %span.light= time_ago_with_tooltip(snippet.created_at) %h4.snippet-title - snippet_path = reliable_snippet_path(snippet) - = link_to snippet_path do - .file-holder - .js-file-title.file-title + .file-holder + .js-file-title.file-title + = link_to snippet_path do %i.fa.fa-file %strong= snippet.file_name - - if markup?(snippet.file_name) - .file-content.wiki + - if markup?(snippet.file_name) + .file-content.wiki + - snippet_chunks.each do |chunk| + - unless chunk[:data].empty? + = render_markup(snippet.file_name, chunk[:data]) + - else + .file-content.code + .nothing-here-block Empty file + - else + .file-content.code.js-syntax-highlight + .line-numbers - snippet_chunks.each do |chunk| - unless chunk[:data].empty? - = render_markup(snippet.file_name, chunk[:data]) + - Gitlab::Git::Util.count_lines(chunk[:data]).times do |index| + - offset = defined?(chunk[:start_line]) ? chunk[:start_line] : 1 + - i = index + offset + = link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}", class: "diff-line-num" do + %i.fa.fa-link + = i + .blob-content + - snippet_chunks.each do |chunk| + - unless chunk[:data].empty? + = highlight(snippet.file_name, chunk[:data], repository: nil, plain: snippet.no_highlighting?) - else .file-content.code .nothing-here-block Empty file - - else - .file-content.code.js-syntax-highlight - .line-numbers - - snippet_chunks.each do |chunk| - - unless chunk[:data].empty? - - Gitlab::Git::Util.count_lines(chunk[:data]).times do |index| - - offset = defined?(chunk[:start_line]) ? chunk[:start_line] : 1 - - i = index + offset - = link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}", class: "diff-line-num" do - %i.fa.fa-link - = i - - unless snippet == snippet_chunks.last - %a.diff-line-num - = "." - %pre.code - %code - - snippet_chunks.each do |chunk| - - unless chunk[:data].empty? - = chunk[:data] - - unless chunk == snippet_chunks.last - %a - = "..." - - else - .file-content.code - .nothing-here-block Empty file diff --git a/app/views/shared/_issuable_meta_data.html.haml b/app/views/shared/_issuable_meta_data.html.haml index 1264e524d86..66310da5cd6 100644 --- a/app/views/shared/_issuable_meta_data.html.haml +++ b/app/views/shared/_issuable_meta_data.html.haml @@ -2,6 +2,12 @@ - issue_votes = @issuable_meta_data[issuable.id] - upvotes, downvotes = issue_votes.upvotes, issue_votes.downvotes - issuable_url = @collection_type == "Issue" ? issue_path(issuable, anchor: 'notes') : merge_request_path(issuable, anchor: 'notes') +- issuable_mr = @issuable_meta_data[issuable.id].merge_requests_count + +- if issuable_mr > 0 + %li + = image_tag('icon-merge-request-unmerged', class: 'icon-merge-request-unmerged') + = issuable_mr - if upvotes > 0 %li diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index ead9b84b991..1744a597c51 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -1,6 +1,4 @@ - label_css_id = dom_id(label) -- open_issues_count = label.open_issues_count(current_user) -- open_merge_requests_count = label.open_merge_requests_count(current_user) - status = label_subscription_status(label, @project).inquiry if current_user - subject = local_assigns[:subject] @@ -15,10 +13,10 @@ %ul %li = link_to_label(label, subject: subject, type: :merge_request) do - = pluralize open_merge_requests_count, 'merge request' + view merge requests %li = link_to_label(label, subject: subject) do - = pluralize open_issues_count, 'open issue' + view open issues - if current_user && defined?(@project) %li.label-subscription - if label.is_a?(ProjectLabel) @@ -40,9 +38,9 @@ .pull-right.hidden-xs.hidden-sm.hidden-md = link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action') do - = pluralize open_merge_requests_count, 'merge request' + view merge requests = link_to_label(label, subject: subject, css_class: 'btn btn-transparent btn-action') do - = pluralize open_issues_count, 'open issue' + view open issues - if current_user && defined?(@project) .label-subscription.inline diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index 6e417aa2251..8e04b50bb8a 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -32,7 +32,7 @@ {{hint}} %span.js-filter-tag.dropdown-light-content {{tag}} - #js-dropdown-author.dropdown-menu + #js-dropdown-author.dropdown-menu{ data: { icon: 'pencil', hint: 'author', tag: '@author' } } %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item %button.btn.btn-link.dropdown-user @@ -42,7 +42,7 @@ {{name}} %span.dropdown-light-content @{{username}} - #js-dropdown-assignee.dropdown-menu + #js-dropdown-assignee.dropdown-menu{ data: { icon: 'user', hint: 'assignee', tag: '@assignee' } } %ul{ 'data-dropdown' => true } %li.filter-dropdown-item{ 'data-value' => 'none' } %button.btn.btn-link @@ -57,7 +57,7 @@ {{name}} %span.dropdown-light-content @{{username}} - #js-dropdown-milestone.dropdown-menu{ 'data-dropdown' => true } + #js-dropdown-milestone.dropdown-menu{ data: { icon: 'clock-o', hint: 'milestone', tag: '%milestone' } } %ul{ 'data-dropdown' => true } %li.filter-dropdown-item{ 'data-value' => 'none' } %button.btn.btn-link @@ -70,7 +70,7 @@ %li.filter-dropdown-item %button.btn.btn-link.js-data-value {{title}} - #js-dropdown-label.dropdown-menu{ 'data-dropdown' => true } + #js-dropdown-label.dropdown-menu{ data: { icon: 'tag', hint: 'label', tag: '~label' } } %ul{ 'data-dropdown' => true } %li.filter-dropdown-item{ 'data-value' => 'none' } %button.btn.btn-link diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 239387fc9fa..8e721c9c8dd 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -61,7 +61,7 @@ = dropdown_title("Change permissions") .dropdown-content %ul - - Gitlab::Access.options.each do |role, role_id| + - member.class.access_level_roles.each do |role, role_id| %li = link_to role, "javascript:void(0)", class: ("is-active" if member.access_level == role_id), diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml index 31eb07ca666..a93cbd1041f 100644 --- a/app/views/shared/milestones/_issuables.html.haml +++ b/app/views/shared/milestones/_issuables.html.haml @@ -3,11 +3,11 @@ - panel_class = primary ? 'panel-primary' : 'panel-default' .panel{ class: panel_class } - .panel-heading.split - .left + .panel-heading + .title = title - if show_counter - .right + .counter = number_with_delimiter(issuables.size) - class_prefix = dom_class(issuables).pluralize diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml index 855a995afa9..a7f118d3f7d 100644 --- a/app/views/snippets/_actions.html.haml +++ b/app/views/snippets/_actions.html.haml @@ -9,7 +9,7 @@ Delete = link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do New snippet - - if @snippet.submittable_as_spam? && current_user.admin? + - if @snippet.submittable_as_spam_by?(current_user) = link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam' .visible-xs-block.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } @@ -28,6 +28,6 @@ %li = link_to edit_snippet_path(@snippet) do Edit - - if @snippet.submittable_as_spam? && current_user.admin? + - if @snippet.submittable_as_spam_by?(current_user) %li = link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post diff --git a/app/views/snippets/verify.html.haml b/app/views/snippets/verify.html.haml new file mode 100644 index 00000000000..cb623ccab57 --- /dev/null +++ b/app/views/snippets/verify.html.haml @@ -0,0 +1,4 @@ +- form = [@snippet.becomes(Snippet)] + += render 'layouts/recaptcha_verification', spammable: @snippet, form: form + diff --git a/app/workers/delete_user_worker.rb b/app/workers/delete_user_worker.rb index 5483bbb210b..3340a7be4fe 100644 --- a/app/workers/delete_user_worker.rb +++ b/app/workers/delete_user_worker.rb @@ -7,5 +7,7 @@ class DeleteUserWorker current_user = User.find(current_user_id) Users::DestroyService.new(current_user).execute(delete_user, options.symbolize_keys) + rescue Gitlab::Access::AccessDeniedError => e + Rails.logger.warn("User could not be destroyed: #{e}") end end |