diff options
author | Lin Jen-Shin <godfat@godfat.org> | 2016-12-14 19:24:31 +0800 |
---|---|---|
committer | Lin Jen-Shin <godfat@godfat.org> | 2016-12-14 19:24:31 +0800 |
commit | 367024f1707ebbf986e5f25ac208f24e35746389 (patch) | |
tree | 8ff45ed585720aa4eb2f5a6c772a2a20f89b946f /app | |
parent | 101cde38cf6d5506ea37c5f912fb4c37af50c541 (diff) | |
parent | 3a90612660ab90225907ec6d79032905885c2507 (diff) | |
download | gitlab-ce-367024f1707ebbf986e5f25ac208f24e35746389.tar.gz |
Merge remote-tracking branch 'upstream/master' into show-commit-status-from-latest-pipeline
* upstream/master: (557 commits)
Fix wrong error message expectation in API::Commits spec
Move admin settings spinach feature to rspec
Encode when migrating ProcessCommitWorker jobs
Prevent overflow with vertical scroll when we have space to show content
Make rubocop happy
API: Ability to cherry-pick a commit
Be smarter when finding a sudoed user in API::Helpers
Backport hooks on group policies for the EE-specific implementation
API: Ability to get group's project in simple representation
Add AddLowerPathIndexToRoutes to setup_postgresql.rake
For single line git commit messages, the close quote should be on the same line as the open quote
added border-radius and padding to labels
Allow all alphanumeric characters in file names (!8002)
Add failing test for #20190
Don't allow blank MR titles in API
Replace static fixture for awards_handler_spec (!7661)
Crontab typo '* */6' -> '0 */6' (4x/day not 1x-per-min-for-1h 4x/day)
Fix test
Tweak style and add back wording
Clean up commit copy to clipboard and make consistent
...
Diffstat (limited to 'app')
438 files changed, 5994 insertions, 4074 deletions
diff --git a/app/assets/fonts/OFL.txt b/app/assets/fonts/OFL.txt deleted file mode 100644 index df187637e18..00000000000 --- a/app/assets/fonts/OFL.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2010, 2012, 2014 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. - -This Font Software is licensed under the SIL Open Font License, Version 1.1. - -This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/app/assets/fonts/SourceSansPro-Black.ttf.woff b/app/assets/fonts/SourceSansPro-Black.ttf.woff Binary files differdeleted file mode 100644 index b7e86200927..00000000000 --- a/app/assets/fonts/SourceSansPro-Black.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-Black.ttf.woff2 b/app/assets/fonts/SourceSansPro-Black.ttf.woff2 Binary files differdeleted file mode 100644 index c90d078406c..00000000000 --- a/app/assets/fonts/SourceSansPro-Black.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff b/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff Binary files differdeleted file mode 100644 index c3314b1ef06..00000000000 --- a/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff2 Binary files differdeleted file mode 100644 index b87e22c41b5..00000000000 --- a/app/assets/fonts/SourceSansPro-BlackIt.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-Bold.ttf.woff b/app/assets/fonts/SourceSansPro-Bold.ttf.woff Binary files differdeleted file mode 100644 index d1d40f840f8..00000000000 --- a/app/assets/fonts/SourceSansPro-Bold.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-Bold.ttf.woff2 b/app/assets/fonts/SourceSansPro-Bold.ttf.woff2 Binary files differdeleted file mode 100644 index 0f46f3e833a..00000000000 --- a/app/assets/fonts/SourceSansPro-Bold.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff b/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff Binary files differdeleted file mode 100644 index ef6ff514d3a..00000000000 --- a/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff2 Binary files differdeleted file mode 100644 index 8007df6df32..00000000000 --- a/app/assets/fonts/SourceSansPro-BoldIt.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff b/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff Binary files differdeleted file mode 100644 index 1e6c94d9eb3..00000000000 --- a/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff2 b/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff2 Binary files differdeleted file mode 100644 index b715f274082..00000000000 --- a/app/assets/fonts/SourceSansPro-ExtraLight.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff b/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff Binary files differdeleted file mode 100644 index 7a408b1ec73..00000000000 --- a/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff2 Binary files differdeleted file mode 100644 index d8f9d29d4aa..00000000000 --- a/app/assets/fonts/SourceSansPro-ExtraLightIt.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-It.ttf.woff b/app/assets/fonts/SourceSansPro-It.ttf.woff Binary files differdeleted file mode 100644 index 4d54bc95718..00000000000 --- a/app/assets/fonts/SourceSansPro-It.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-It.ttf.woff2 b/app/assets/fonts/SourceSansPro-It.ttf.woff2 Binary files differdeleted file mode 100644 index a00852641f8..00000000000 --- a/app/assets/fonts/SourceSansPro-It.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-Light.ttf.woff b/app/assets/fonts/SourceSansPro-Light.ttf.woff Binary files differdeleted file mode 100644 index 1706d57d3c5..00000000000 --- a/app/assets/fonts/SourceSansPro-Light.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-Light.ttf.woff2 b/app/assets/fonts/SourceSansPro-Light.ttf.woff2 Binary files differdeleted file mode 100644 index d8b610ad76e..00000000000 --- a/app/assets/fonts/SourceSansPro-Light.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-LightIt.ttf.woff b/app/assets/fonts/SourceSansPro-LightIt.ttf.woff Binary files differdeleted file mode 100644 index 87378d6c609..00000000000 --- a/app/assets/fonts/SourceSansPro-LightIt.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-LightIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-LightIt.ttf.woff2 Binary files differdeleted file mode 100644 index e0eebac8273..00000000000 --- a/app/assets/fonts/SourceSansPro-LightIt.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-Regular.ttf.woff b/app/assets/fonts/SourceSansPro-Regular.ttf.woff Binary files differdeleted file mode 100644 index 460ab12a638..00000000000 --- a/app/assets/fonts/SourceSansPro-Regular.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-Regular.ttf.woff2 b/app/assets/fonts/SourceSansPro-Regular.ttf.woff2 Binary files differdeleted file mode 100644 index 0dd3464c74b..00000000000 --- a/app/assets/fonts/SourceSansPro-Regular.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-Semibold.ttf.woff b/app/assets/fonts/SourceSansPro-Semibold.ttf.woff Binary files differdeleted file mode 100644 index 43379631b2d..00000000000 --- a/app/assets/fonts/SourceSansPro-Semibold.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-Semibold.ttf.woff2 b/app/assets/fonts/SourceSansPro-Semibold.ttf.woff2 Binary files differdeleted file mode 100644 index 2526d2e1b60..00000000000 --- a/app/assets/fonts/SourceSansPro-Semibold.ttf.woff2 +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff b/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff Binary files differdeleted file mode 100644 index 232c2048ae7..00000000000 --- a/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff +++ /dev/null diff --git a/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff2 b/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff2 Binary files differdeleted file mode 100644 index 606935af089..00000000000 --- a/app/assets/fonts/SourceSansPro-SemiboldIt.ttf.woff2 +++ /dev/null diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 76f3c6506ed..b7c4673c8e3 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -56,33 +56,18 @@ /*= require es6-promise.auto */ (function () { - document.addEventListener('page:fetch', gl.utils.cleanupBeforeFetch); - window.addEventListener('hashchange', gl.utils.shiftWindow); - - // automatically adjust scroll position for hash urls taking the height of the navbar into account - // https://github.com/twitter/bootstrap/issues/1768 - window.adjustScroll = function() { - var navbar = document.querySelector('.navbar-gitlab'); - var subnav = document.querySelector('.layout-nav'); - var fixedTabs = document.querySelector('.js-tabs-affix'); - - adjustment = 0; - if (navbar) adjustment -= navbar.offsetHeight; - if (subnav) adjustment -= subnav.offsetHeight; - if (fixedTabs) adjustment -= fixedTabs.offsetHeight; - - return scrollBy(0, adjustment); - }; + document.addEventListener('page:fetch', function () { + // Unbind scroll events + $(document).off('scroll'); + // Close any open tooltips + $('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy'); + }); - window.addEventListener("hashchange", adjustScroll); - - window.onload = function () { - // Scroll the window to avoid the topnav bar - // https://github.com/twitter/bootstrap/issues/1768 - if (location.hash) { - return setTimeout(adjustScroll, 100); - } - }; + window.addEventListener('hashchange', gl.utils.handleLocationHash); + window.addEventListener('load', function onLoad() { + window.removeEventListener('load', onLoad, false); + gl.utils.handleLocationHash(); + }, false); $(function () { var $body = $('body'); @@ -97,7 +82,15 @@ // Set the default path for all cookies to GitLab's root directory Cookies.defaults.path = gon.relative_url_root || '/'; - gl.utils.preventDisabledButtons(); + // prevent default action for disabled buttons + $('.btn').click(function(e) { + if ($(this).hasClass('disabled')) { + e.preventDefault(); + e.stopImmediatePropagation(); + return false; + } + }); + $('.nav-sidebar').niceScroll({ cursoropacitymax: '0.4', cursorcolor: '#FFF', diff --git a/app/assets/javascripts/blob/template_selector.js.es6 b/app/assets/javascripts/blob/template_selector.js.es6 index 5434a19bcec..0ff5c0fab05 100644 --- a/app/assets/javascripts/blob/template_selector.js.es6 +++ b/app/assets/javascripts/blob/template_selector.js.es6 @@ -70,6 +70,8 @@ // e.g. // Api.gitignoreText item.name, @requestFileSuccess.bind(@) requestFileSuccess(file, { skipFocus } = {}) { + if (!file) return; + const oldValue = this.editor.getValue(); let newValue = file.content; diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index 8e91cbfac75..43ebeef39c4 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -80,6 +80,7 @@ }, mounted () { const options = gl.issueBoards.getBoardSortableDefaultOptions({ + scroll: document.querySelectorAll('.boards-list')[0], group: 'issues', sort: false, disabled: this.disabled, @@ -88,13 +89,13 @@ const card = this.$refs.issue[e.oldIndex]; card.showDetail = false; - Store.moving.issue = card.issue; Store.moving.list = card.list; + Store.moving.issue = Store.moving.list.findIssue(+e.item.dataset.issueId); gl.issueBoards.onStart(); }, onAdd: (e) => { - gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue); + gl.issueBoards.BoardsStore.moveIssueToList(Store.moving.list, this.list, Store.moving.issue, e.newIndex); this.$nextTick(() => { e.item.remove(); diff --git a/app/assets/javascripts/boards/components/board_sidebar.js.es6 b/app/assets/javascripts/boards/components/board_sidebar.js.es6 index d5cb6164e0b..1644a772737 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js.es6 +++ b/app/assets/javascripts/boards/components/board_sidebar.js.es6 @@ -47,7 +47,7 @@ new gl.DueDateSelectors(); new LabelsSelect(); new Sidebar(); - new Subscription('.subscription'); + gl.Subscription.bindAll('.subscription'); } }); })(); diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6 index 8a7dc67409e..429bd27c3fb 100644 --- a/app/assets/javascripts/boards/models/list.js.es6 +++ b/app/assets/javascripts/boards/models/list.js.es6 @@ -106,9 +106,13 @@ class List { }); } - addIssue (issue, listFrom) { + addIssue (issue, listFrom, newIndex) { if (!this.findIssue(issue.id)) { - this.issues.push(issue); + if (newIndex !== undefined) { + this.issues.splice(newIndex, 0, issue); + } else { + this.issues.push(issue); + } if (this.label) { issue.addLabel(this.label); diff --git a/app/assets/javascripts/boards/stores/boards_store.js.es6 b/app/assets/javascripts/boards/stores/boards_store.js.es6 index 6bc95aa60f2..bb2a4de8b18 100644 --- a/app/assets/javascripts/boards/stores/boards_store.js.es6 +++ b/app/assets/javascripts/boards/stores/boards_store.js.es6 @@ -89,14 +89,14 @@ }); listFrom.update(); }, - moveIssueToList (listFrom, listTo, issue) { + moveIssueToList (listFrom, listTo, issue, newIndex) { const issueTo = listTo.findIssue(issue.id), issueLists = issue.getLists(), listLabels = issueLists.map( listIssue => listIssue.label ); // Add to new lists issues if it doesn't already exist if (!issueTo) { - listTo.addIssue(issue, listFrom); + listTo.addIssue(issue, listFrom, newIndex); } if (listTo.type === 'done' && listFrom.type !== 'backlog') { diff --git a/app/assets/javascripts/commit/file.js b/app/assets/javascripts/commit/file.js index 3f29826fa9b..600bac8834a 100644 --- a/app/assets/javascripts/commit/file.js +++ b/app/assets/javascripts/commit/file.js @@ -3,7 +3,7 @@ this.CommitFile = (function() { function CommitFile(file) { if ($('.image', file).length) { - new ImageFile(file); + new gl.ImageFile(file); } } diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js index 4c2ae595319..fd8910e916f 100644 --- a/app/assets/javascripts/commit/image_file.js +++ b/app/assets/javascripts/commit/image_file.js @@ -1,6 +1,6 @@ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-use-before-define, prefer-arrow-callback, no-else-return, consistent-return, prefer-template, quotes, one-var, one-var-declaration-per-line, no-unused-vars, no-return-assign, comma-dangle, quote-props, no-unused-expressions, no-sequences, object-shorthand, padded-blocks, max-len */ (function() { - this.ImageFile = (function() { + gl.ImageFile = (function() { var prepareFrames; // Width where images must fits in, for 2-up this gets divided by 2 diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js index 951fb338f9d..3627aaf5080 100644 --- a/app/assets/javascripts/commits.js +++ b/app/assets/javascripts/commits.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-undef, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-undef, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, padded-blocks, max-len, prefer-arrow-callback */ (function() { this.CommitsList = (function() { function CommitsList() {} @@ -13,7 +13,9 @@ return false; } }); - Pager.init(limit, false); + Pager.init(limit, false, false, function() { + gl.utils.localTimeAgo($('.js-timeago')); + }); this.content = $("#commits-list"); this.searchField = $("#commits-search"); return this.initSearch(); diff --git a/app/assets/javascripts/copy_to_clipboard.js b/app/assets/javascripts/copy_to_clipboard.js index 1cc34e490c2..efa228a75d9 100644 --- a/app/assets/javascripts/copy_to_clipboard.js +++ b/app/assets/javascripts/copy_to_clipboard.js @@ -6,7 +6,7 @@ var genericError, genericSuccess, showTooltip; genericSuccess = function(e) { - showTooltip(e.trigger, 'Copied!'); + showTooltip(e.trigger, 'Copied'); // Clear the selection and blur the trigger so it loses its border e.clearSelection(); return $(e.trigger).blur(); @@ -31,7 +31,7 @@ var originalTitle = $target.data('original-title'); $target - .attr('title', 'Copied!') + .attr('title', 'Copied') .tooltip('fixTitle') .tooltip('show') .attr('title', originalTitle) diff --git a/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 index b9675f50e31..0d85e1a4678 100644 --- a/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 +++ b/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 @@ -10,10 +10,15 @@ }, template: ` <span class="total-time"> - <template v-if="time.days">{{ time.days }} <span>{{ time.days === 1 ? 'day' : 'days' }}</span></template> - <template v-if="time.hours">{{ time.hours }} <span>hr</span></template> - <template v-if="time.mins && !time.days">{{ time.mins }} <span>mins</span></template> - <template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>s</span></template> + <template v-if="Object.keys(time).length"> + <template v-if="time.days">{{ time.days }} <span>{{ time.days === 1 ? 'day' : 'days' }}</span></template> + <template v-if="time.hours">{{ time.hours }} <span>hr</span></template> + <template v-if="time.mins && !time.days">{{ time.mins }} <span>mins</span></template> + <template v-if="time.seconds && Object.keys(time).length === 1 || time.seconds === 0">{{ time.seconds }} <span>s</span></template> + </template> + <template v-else> + -- + </template> </span> `, }); 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 9b905874167..be732971c7f 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6 +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6 @@ -62,9 +62,11 @@ this.state.events = this.decorateEvents(events); }, decorateEvents(events) { - const newEvents = events; + const newEvents = []; + + events.forEach((item) => { + if (!item) return; - newEvents.forEach((item) => { item.totalTime = item.total_time; item.author.webUrl = item.author.web_url; item.author.avatarUrl = item.author.avatar_url; @@ -79,6 +81,8 @@ delete item.created_at; delete item.short_sha; delete item.commit_url; + + newEvents.push(item); }); return newEvents; diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js deleted file mode 100644 index 00da5f17f9f..00000000000 --- a/app/assets/javascripts/diff.js +++ /dev/null @@ -1,73 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, max-len, one-var, camelcase, one-var-declaration-per-line, no-unused-vars, no-unused-expressions, no-sequences, object-shorthand, comma-dangle, prefer-arrow-callback, semi, radix, padded-blocks, max-len */ -(function() { - this.Diff = (function() { - var UNFOLD_COUNT; - - UNFOLD_COUNT = 20; - - function Diff() { - $('.files .diff-file').singleFileDiff(); - this.filesCommentButton = $('.files .diff-file').filesCommentButton(); - if (this.diffViewType() === 'parallel') { - $('.content-wrapper .container-fluid').removeClass('container-limited'); - } - $(document).off('click', '.js-unfold'); - $(document).on('click', '.js-unfold', (function(_this) { - return function(event) { - var line_number, link, file, offset, old_line, params, prev_new_line, prev_old_line, ref, ref1, since, target, to, unfold, unfoldBottom; - target = $(event.target); - unfoldBottom = target.hasClass('js-unfold-bottom'); - unfold = true; - ref = _this.lineNumbers(target.parent()), old_line = ref[0], line_number = ref[1]; - offset = line_number - old_line; - if (unfoldBottom) { - line_number += 1; - since = line_number; - to = line_number + UNFOLD_COUNT; - } else { - ref1 = _this.lineNumbers(target.parent().prev()), prev_old_line = ref1[0], prev_new_line = ref1[1]; - line_number -= 1; - to = line_number; - if (line_number - UNFOLD_COUNT > prev_new_line + 1) { - since = line_number - UNFOLD_COUNT; - } else { - since = prev_new_line + 1; - unfold = false; - } - } - file = target.parents('.diff-file'); - link = file.data('blob-diff-path'); - params = { - since: since, - to: to, - bottom: unfoldBottom, - offset: offset, - unfold: unfold, - view: file.data('view') - }; - return $.get(link, params, function(response) { - return target.parent().replaceWith(response); - }); - }; - })(this)); - } - - Diff.prototype.diffViewType = function() { - return $('.inline-parallel-buttons a.active').data('view-type'); - } - - Diff.prototype.lineNumbers = function(line) { - if (!line.children().length) { - return [0, 0]; - } - - return line.find('.diff-line-num').map(function() { - return parseInt($(this).data('linenumber')); - }); - }; - - return Diff; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/diff.js.es6 b/app/assets/javascripts/diff.js.es6 new file mode 100644 index 00000000000..9cf33e62958 --- /dev/null +++ b/app/assets/javascripts/diff.js.es6 @@ -0,0 +1,112 @@ +/* eslint-disable class-methods-use-this */ + +(() => { + const UNFOLD_COUNT = 20; + + class Diff { + constructor() { + const $diffFile = $('.files .diff-file'); + $diffFile.singleFileDiff(); + $diffFile.filesCommentButton(); + + $diffFile.each((index, file) => new gl.ImageFile(file)); + + if (this.diffViewType() === 'parallel') { + $('.content-wrapper .container-fluid').removeClass('container-limited'); + } + + $(document) + .off('click', '.js-unfold, .diff-line-num a') + .on('click', '.js-unfold', this.handleClickUnfold.bind(this)) + .on('click', '.diff-line-num a', this.handleClickLineNum.bind(this)); + + this.highlighSelectedLine(); + } + + handleClickUnfold(e) { + const $target = $(e.target); + // current babel config relies on iterators implementation, so we cannot simply do: + // const [oldLineNumber, newLineNumber] = this.lineNumbers($target.parent()); + const ref = this.lineNumbers($target.parent()); + const oldLineNumber = ref[0]; + const newLineNumber = ref[1]; + const offset = newLineNumber - oldLineNumber; + const bottom = $target.hasClass('js-unfold-bottom'); + let since; + let to; + let unfold = true; + + if (bottom) { + const lineNumber = newLineNumber + 1; + since = lineNumber; + to = lineNumber + UNFOLD_COUNT; + } else { + const lineNumber = newLineNumber - 1; + since = lineNumber - UNFOLD_COUNT; + to = lineNumber; + + // make sure we aren't loading more than we need + const prevNewLine = this.lineNumbers($target.parent().prev())[1]; + if (since <= prevNewLine + 1) { + since = prevNewLine + 1; + unfold = false; + } + } + + const file = $target.parents('.diff-file'); + const link = file.data('blob-diff-path'); + const view = file.data('view'); + + const params = { since, to, bottom, offset, unfold, view }; + $.get(link, params, response => $target.parent().replaceWith(response)); + } + + openAnchoredDiff(anchoredDiff, cb) { + const diffTitle = $(`#file-path-${anchoredDiff}`); + const diffFile = diffTitle.closest('.diff-file'); + const nothingHereBlock = $('.nothing-here-block:visible', diffFile); + if (nothingHereBlock.length) { + diffFile.singleFileDiff(true, cb); + } else { + cb(); + } + } + + handleClickLineNum(e) { + const hash = $(e.currentTarget).attr('href'); + e.preventDefault(); + if (window.history.pushState) { + window.history.pushState(null, null, hash); + } else { + window.location.hash = hash; + } + this.highlighSelectedLine(); + } + + diffViewType() { + return $('.inline-parallel-buttons a.active').data('view-type'); + } + + lineNumbers(line) { + if (!line.children().length) { + return [0, 0]; + } + return line.find('.diff-line-num').map((i, elm) => parseInt($(elm).data('linenumber'), 10)); + } + + highlighSelectedLine() { + const $diffFiles = $('.diff-file'); + $diffFiles.find('.hll').removeClass('hll'); + + if (window.location.hash !== '') { + const hash = window.location.hash.replace('#', ''); + $diffFiles + .find(`tr#${hash}:not(.match) td, td#${hash}, td[data-line-code="${hash}"]`) + .addClass('hll'); + } + } + } + + window.gl = window.gl || {}; + window.gl.Diff = Diff; +})(); diff --git a/app/assets/javascripts/diff_notes/models/discussion.js.es6 b/app/assets/javascripts/diff_notes/models/discussion.js.es6 index 439f55520ef..badcdccc840 100644 --- a/app/assets/javascripts/diff_notes/models/discussion.js.es6 +++ b/app/assets/javascripts/diff_notes/models/discussion.js.es6 @@ -57,14 +57,17 @@ class DiscussionModel { } updateHeadline (data) { - const $discussionHeadline = $(`.discussion[data-discussion-id="${this.id}"] .js-discussion-headline`); + const discussionSelector = `.discussion[data-discussion-id="${this.id}"]`; + const $discussionHeadline = $(`${discussionSelector} .js-discussion-headline`); if (data.discussion_headline_html) { if ($discussionHeadline.length) { $discussionHeadline.replaceWith(data.discussion_headline_html); } else { - $(`.discussion[data-discussion-id="${this.id}"] .discussion-header`).append(data.discussion_headline_html); + $(`${discussionSelector} .discussion-header`).append(data.discussion_headline_html); } + + gl.utils.localTimeAgo($('.js-timeago', `${discussionSelector}`)); } else { $discussionHeadline.remove(); } @@ -74,7 +77,7 @@ class DiscussionModel { if (!this.canResolve) { return false; } - + for (const noteId in this.notes) { const note = this.notes[noteId]; diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index c2d4670b7e9..413117c2226 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -24,6 +24,7 @@ switch (page) { case 'sessions:new': new UsernameValidator(); + new ActiveTabMemoizer(); break; case 'projects:boards:show': case 'projects:boards:index': @@ -61,7 +62,7 @@ new ZenMode(); break; case 'projects:compare:show': - new Diff(); + new gl.Diff(); break; case 'projects:issues:new': case 'projects:issues:edit': @@ -74,7 +75,7 @@ break; case 'projects:merge_requests:new': case 'projects:merge_requests:edit': - new Diff(); + new gl.Diff(); shortcut_handler = new ShortcutsNavigation(); new GLForm($('.merge-request-form')); new IssuableForm($('.merge-request-form')); @@ -91,7 +92,7 @@ new GLForm($('.release-form')); break; case 'projects:merge_requests:show': - new Diff(); + new gl.Diff(); shortcut_handler = new ShortcutsIssuable(true); new ZenMode(); new MergedButtons(); @@ -101,7 +102,7 @@ new MergedButtons(); break; case "projects:merge_requests:diffs": - new Diff(); + new gl.Diff(); new ZenMode(); new MergedButtons(); break; @@ -117,7 +118,7 @@ break; case 'projects:commit:show': new Commit(); - new Diff(); + new gl.Diff(); new ZenMode(); shortcut_handler = new ShortcutsNavigation(); break; @@ -135,8 +136,18 @@ new TreeView(); } break; + case 'projects:pipelines:builds': case 'projects:pipelines:show': - new gl.Pipelines(); + const { controllerAction } = document.querySelector('.js-pipeline-container').dataset; + + new gl.Pipelines({ + initTabs: true, + tabsOptions: { + action: controllerAction, + defaultAction: 'pipelines', + parentEl: '.pipelines-tabs', + }, + }); break; case 'groups:activity': new gl.Activities(); @@ -208,6 +219,9 @@ new gl.ProtectedBranchCreate(); new gl.ProtectedBranchEditList(); break; + case 'projects:variables:index': + new gl.ProjectVariables(); + break; } switch (path.first()) { case 'admin': @@ -259,7 +273,7 @@ new NotificationsDropdown(); break; case 'wikis': - new Wikis(); + new gl.Wikis(); shortcut_handler = new ShortcutsNavigation(); new ZenMode(); new GLForm($('.wiki-form')); diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6 index 35e183a9086..1db29dd47fb 100644 --- a/app/assets/javascripts/environments/components/environment.js.es6 +++ b/app/assets/javascripts/environments/components/environment.js.es6 @@ -74,6 +74,8 @@ projectStoppedEnvironmentsPath: environmentsData.projectStoppedEnvironmentsPath, newEnvironmentPath: environmentsData.newEnvironmentPath, helpPagePath: environmentsData.helpPagePath, + commitIconSvg: environmentsData.commitIconSvg, + playIconSvg: environmentsData.playIconSvg, }; }, @@ -181,7 +183,7 @@ <div class="environments-container"> <div class="environments-list-loading text-center" v-if="isLoading"> - <i class="fa fa-spinner spin"></i> + <i class="fa fa-spinner fa-spin"></i> </div> <div class="blank-state blank-state-no-icon" @@ -227,7 +229,9 @@ :model="model" :toggleRow="toggleRow.bind(model)" :can-create-deployment="canCreateDeploymentParsed" - :can-read-environment="canReadEnvironmentParsed"></tr> + :can-read-environment="canReadEnvironmentParsed" + :play-icon-svg="playIconSvg" + :commit-icon-svg="commitIconSvg"></tr> <tr v-if="model.isOpen && model.children && model.children.length > 0" is="environment-item" @@ -235,7 +239,9 @@ :model="children" :toggleRow="toggleRow.bind(children)" :can-create-deployment="canCreateDeploymentParsed" - :can-read-environment="canReadEnvironmentParsed"> + :can-read-environment="canReadEnvironmentParsed" + :play-icon-svg="playIconSvg" + :commit-icon-svg="commitIconSvg"> </tr> </template> diff --git a/app/assets/javascripts/environments/components/environment_actions.js.es6 b/app/assets/javascripts/environments/components/environment_actions.js.es6 index d149a446e0b..7c743705d51 100644 --- a/app/assets/javascripts/environments/components/environment_actions.js.es6 +++ b/app/assets/javascripts/environments/components/environment_actions.js.es6 @@ -12,38 +12,18 @@ required: false, default: () => [], }, - }, - - /** - * Appends the svg icon that were render in the index page. - * In order to reuse the svg instead of copy and paste in this template - * we need to render it outside this component using =custom_icon partial. - * - * TODO: Remove this when webpack is merged. - * - */ - mounted() { - const playIcon = document.querySelector('.play-icon-svg.hidden svg'); - - const dropdownContainer = this.$el.querySelector('.dropdown-play-icon-container'); - const actionContainers = this.$el.querySelectorAll('.action-play-icon-container'); - // Phantomjs does not have support to iterate a nodelist. - const actionsArray = [].slice.call(actionContainers); - - if (playIcon && actionsArray && dropdownContainer) { - dropdownContainer.appendChild(playIcon.cloneNode(true)); - actionsArray.forEach((element) => { - element.appendChild(playIcon.cloneNode(true)); - }); - } + playIconSvg: { + type: String, + required: false, + }, }, template: ` <div class="inline"> <div class="dropdown"> <a class="dropdown-new btn btn-default" data-toggle="dropdown"> - <span class="dropdown-play-icon-container"></span> + <span class="js-dropdown-play-icon-container" v-html="playIconSvg"></span> <i class="fa fa-caret-down"></i> </a> @@ -53,7 +33,9 @@ data-method="post" rel="nofollow" class="js-manual-action-link"> - <span class="action-play-icon-container"></span> + + <span class="js-action-play-icon-container" v-html="playIconSvg"></span> + <span> {{action.name}} </span> diff --git a/app/assets/javascripts/environments/components/environment_external_url.js.es6 b/app/assets/javascripts/environments/components/environment_external_url.js.es6 index 79cd5ded5bd..aed65b33c04 100644 --- a/app/assets/javascripts/environments/components/environment_external_url.js.es6 +++ b/app/assets/javascripts/environments/components/environment_external_url.js.es6 @@ -7,14 +7,14 @@ window.gl.environmentsList.ExternalUrlComponent = Vue.component('external-url-component', { props: { - external_url: { + externalUrl: { type: String, default: '', }, }, template: ` - <a class="btn external_url" :href="external_url" target="_blank"> + <a class="btn external_url" :href="externalUrl" target="_blank"> <i class="fa fa-external-link"></i> </a> `, diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js.es6 index 07f49cce3dc..2e046a60146 100644 --- a/app/assets/javascripts/environments/components/environment_item.js.es6 +++ b/app/assets/javascripts/environments/components/environment_item.js.es6 @@ -23,6 +23,7 @@ window.gl = window.gl || {}; window.gl.environmentsList = window.gl.environmentsList || {}; + window.gl.environmentsList.timeagoInstance = new timeago(); // eslint-disable-line gl.environmentsList.EnvironmentItem = Vue.component('environment-item', { @@ -57,6 +58,16 @@ required: false, default: false, }, + + commitIconSvg: { + type: String, + required: false, + }, + + playIconSvg: { + type: String, + required: false, + }, }, data() { @@ -148,14 +159,25 @@ }, /** + * Verifies if the date to be shown is present. + * + * @returns {Boolean|Undefined} + */ + canShowDate() { + return this.model.last_deployment && + this.model.last_deployment.deployable && + this.model.last_deployment.deployable !== undefined; + }, + + /** * Human readable date. * * @returns {String} */ createdDate() { - const timeagoInstance = new timeago(); // eslint-disable-line - - return timeagoInstance.format(this.model.created_at); + return window.gl.environmentsList.timeagoInstance.format( + this.model.last_deployment.deployable.created_at, + ); }, /** @@ -439,11 +461,12 @@ <div v-if="!isFolder && hasLastDeploymentKey" class="js-commit-component"> <commit-component :tag="commitTag" - :ref="commitRef" - :commit_url="commitUrl" - :short_sha="commitShortSha" + :commit-ref="commitRef" + :commit-url="commitUrl" + :short-sha="commitShortSha" :title="commitTitle" - :author="commitAuthor"> + :author="commitAuthor" + :commit-icon-svg="commitIconSvg"> </commit-component> </div> <p v-if="!isFolder && !hasLastDeploymentKey" class="commit-title"> @@ -453,7 +476,7 @@ <td> <span - v-if="!isFolder && model.last_deployment" + v-if="!isFolder && canShowDate" class="environment-created-date-timeago"> {{createdDate}} </span> @@ -464,6 +487,7 @@ <div v-if="hasManualActions && canCreateDeployment" class="inline js-manual-actions-container"> <actions-component + :play-icon-svg="playIconSvg" :actions="manualActions"> </actions-component> </div> @@ -471,22 +495,22 @@ <div v-if="model.external_url && canReadEnvironment" class="inline js-external-url-container"> <external-url-component - :external_url="model.external_url"> - </external_url-component> + :external-url="model.external_url"> + </external-url-component> </div> <div v-if="isStoppable && canCreateDeployment" class="inline js-stop-component-container"> <stop-component - :stop_url="model.stop_path"> + :stop-url="model.stop_path"> </stop-component> </div> <div v-if="canRetry && canCreateDeployment" class="inline js-rollback-component-container"> <rollback-component - :is_last_deployment="isLastDeployment" - :retry_url="retryUrl"> + :is-last-deployment="isLastDeployment" + :retry-url="retryUrl"> </rollback-component> </div> </div> diff --git a/app/assets/javascripts/environments/components/environment_rollback.js.es6 b/app/assets/javascripts/environments/components/environment_rollback.js.es6 index 55e5c826e07..6d4e8fad604 100644 --- a/app/assets/javascripts/environments/components/environment_rollback.js.es6 +++ b/app/assets/javascripts/environments/components/environment_rollback.js.es6 @@ -7,19 +7,20 @@ window.gl.environmentsList.RollbackComponent = Vue.component('rollback-component', { props: { - retry_url: { + retryUrl: { type: String, default: '', }, - is_last_deployment: { + + isLastDeployment: { type: Boolean, default: true, }, }, template: ` - <a class="btn" :href="retry_url" data-method="post" rel="nofollow"> - <span v-if="is_last_deployment"> + <a class="btn" :href="retryUrl" data-method="post" rel="nofollow"> + <span v-if="isLastDeployment"> Re-deploy </span> <span v-else> diff --git a/app/assets/javascripts/environments/components/environment_stop.js.es6 b/app/assets/javascripts/environments/components/environment_stop.js.es6 index e6d66a0148c..7292f924e5c 100644 --- a/app/assets/javascripts/environments/components/environment_stop.js.es6 +++ b/app/assets/javascripts/environments/components/environment_stop.js.es6 @@ -7,7 +7,7 @@ window.gl.environmentsList.StopComponent = Vue.component('stop-component', { props: { - stop_url: { + stopUrl: { type: String, default: '', }, @@ -15,7 +15,7 @@ template: ` <a class="btn stop-env-link" - :href="stop_url" + :href="stopUrl" data-confirm="Are you sure you want to stop this environment?" data-method="post" rel="nofollow"> diff --git a/app/assets/javascripts/extensions/array.js b/app/assets/javascripts/extensions/array.js deleted file mode 100644 index fc6c130113d..00000000000 --- a/app/assets/javascripts/extensions/array.js +++ /dev/null @@ -1,8 +0,0 @@ -/* eslint-disable no-extend-native, func-names, space-before-function-paren, semi, space-infix-ops, max-len */ -Array.prototype.first = function() { - return this[0]; -} - -Array.prototype.last = function() { - return this[this.length-1]; -} diff --git a/app/assets/javascripts/extensions/array.js.es6 b/app/assets/javascripts/extensions/array.js.es6 new file mode 100644 index 00000000000..717566a4715 --- /dev/null +++ b/app/assets/javascripts/extensions/array.js.es6 @@ -0,0 +1,24 @@ +/* eslint-disable no-extend-native, func-names, space-before-function-paren, semi, space-infix-ops, max-len */ +Array.prototype.first = function() { + return this[0]; +} + +Array.prototype.last = function() { + return this[this.length-1]; +} + +Array.prototype.find = Array.prototype.find || function(predicate, ...args) { + if (!this) throw new TypeError('Array.prototype.find called on null or undefined'); + if (typeof predicate !== 'function') throw new TypeError('predicate must be a function'); + + const list = Object(this); + const thisArg = args[1]; + let value = {}; + + for (let i = 0; i < list.length; i += 1) { + value = list[i]; + if (predicate.call(thisArg, value, i, list)) return value; + } + + return undefined; +}; diff --git a/app/assets/javascripts/extensions/element.js.es6 b/app/assets/javascripts/extensions/element.js.es6 index 6d9b0c4bc3e..3f12ad9ff9f 100644 --- a/app/assets/javascripts/extensions/element.js.es6 +++ b/app/assets/javascripts/extensions/element.js.es6 @@ -1,9 +1,20 @@ /* global Element */ -/* eslint-disable consistent-return, max-len */ - -Element.prototype.matches = Element.prototype.matches || Element.prototype.msMatchesSelector; +/* eslint-disable consistent-return, max-len, no-empty, no-plusplus, func-names */ Element.prototype.closest = Element.prototype.closest || function closest(selector, selectedElement = this) { if (!selectedElement) return; return selectedElement.matches(selector) ? selectedElement : Element.prototype.closest(selector, selectedElement.parentElement); }; + +Element.prototype.matches = Element.prototype.matches || + Element.prototype.matchesSelector || + Element.prototype.mozMatchesSelector || + Element.prototype.msMatchesSelector || + Element.prototype.oMatchesSelector || + Element.prototype.webkitMatchesSelector || + function (s) { + const matches = (this.document || this.ownerDocument).querySelectorAll(s); + let i = matches.length; + while (--i >= 0 && matches.item(i) !== this) {} + return i > -1; + }; diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index 5d9ac4d350a..2f3da745119 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -5,6 +5,10 @@ window.GitLab = {}; } + function sanitize(str) { + return str.replace(/<(?:.|\n)*?>/gm, ''); + } + GitLab.GfmAutoComplete = { dataLoading: false, dataLoaded: false, @@ -48,11 +52,36 @@ return $.fn.atwho["default"].callbacks.filter(query, data, searchKey); }, beforeInsert: function(value) { + if (value && !this.setting.skipSpecialCharacterTest) { + var withoutAt = value.substring(1); + if (withoutAt && /[^\w\d]/.test(withoutAt)) value = value.charAt() + '"' + withoutAt + '"'; + } if (!GitLab.GfmAutoComplete.dataLoaded) { return this.at; } else { return value; } + }, + matcher: function (flag, subtext) { + // The below is taken from At.js source + // Tweaked to commands to start without a space only if char before is a non-word character + // https://github.com/ichord/At.js + var _a, _y, regexp, match; + subtext = subtext.split(' ').pop(); + flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + + _a = decodeURI("%C3%80"); + _y = decodeURI("%C3%BF"); + + regexp = new RegExp("(?:\\B|\\W|\\s)" + flag + "(?!\\W)([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]*)|([^\\x00-\\xff]*)$", 'gi'); + + match = regexp.exec(subtext); + + if (match) { + return match[2] || match[1]; + } else { + return null; + } } }, setup: _.debounce(function(input) { @@ -91,10 +120,13 @@ })(this), insertTpl: ':${name}:', data: ['loading'], + startWithSpace: false, + skipSpecialCharacterTest: true, callbacks: { sorter: this.DefaultOptions.sorter, filter: this.DefaultOptions.filter, - beforeInsert: this.DefaultOptions.beforeInsert + beforeInsert: this.DefaultOptions.beforeInsert, + matcher: this.DefaultOptions.matcher } }); // Team Members @@ -112,11 +144,14 @@ insertTpl: '${atwho-at}${username}', searchKey: 'search', data: ['loading'], + startWithSpace: false, alwaysHighlightFirst: true, + skipSpecialCharacterTest: true, callbacks: { sorter: this.DefaultOptions.sorter, filter: this.DefaultOptions.filter, beforeInsert: this.DefaultOptions.beforeInsert, + matcher: this.DefaultOptions.matcher, beforeSave: function(members) { return $.map(members, function(m) { let title = ''; @@ -135,8 +170,8 @@ return { username: m.username, avatarTag: autoCompleteAvatar.length === 1 ? txtAvatar : imgAvatar, - title: gl.utils.sanitize(title), - search: gl.utils.sanitize(m.username + " " + m.name) + title: sanitize(title), + search: sanitize(m.username + " " + m.name) }; }); } @@ -157,10 +192,12 @@ })(this), data: ['loading'], insertTpl: '${atwho-at}${id}', + startWithSpace: false, callbacks: { sorter: this.DefaultOptions.sorter, filter: this.DefaultOptions.filter, beforeInsert: this.DefaultOptions.beforeInsert, + matcher: this.DefaultOptions.matcher, beforeSave: function(issues) { return $.map(issues, function(i) { if (i.title == null) { @@ -168,7 +205,7 @@ } return { id: i.iid, - title: gl.utils.sanitize(i.title), + title: sanitize(i.title), search: i.iid + " " + i.title }; }); @@ -188,10 +225,13 @@ } }; })(this), - insertTpl: '${atwho-at}"${title}"', + insertTpl: '${atwho-at}${title}', data: ['loading'], + startWithSpace: false, callbacks: { + matcher: this.DefaultOptions.matcher, sorter: this.DefaultOptions.sorter, + beforeInsert: this.DefaultOptions.beforeInsert, beforeSave: function(milestones) { return $.map(milestones, function(m) { if (m.title == null) { @@ -199,7 +239,7 @@ } return { id: m.iid, - title: gl.utils.sanitize(m.title), + title: sanitize(m.title), search: "" + m.title }; }); @@ -220,11 +260,13 @@ }; })(this), data: ['loading'], + startWithSpace: false, insertTpl: '${atwho-at}${id}', callbacks: { sorter: this.DefaultOptions.sorter, filter: this.DefaultOptions.filter, beforeInsert: this.DefaultOptions.beforeInsert, + matcher: this.DefaultOptions.matcher, beforeSave: function(merges) { return $.map(merges, function(m) { if (m.title == null) { @@ -232,7 +274,7 @@ } return { id: m.iid, - title: gl.utils.sanitize(m.title), + title: sanitize(m.title), search: m.iid + " " + m.title }; }); @@ -245,20 +287,15 @@ searchKey: 'search', displayTpl: this.Labels.template, insertTpl: '${atwho-at}${title}', + startWithSpace: false, callbacks: { + matcher: this.DefaultOptions.matcher, sorter: this.DefaultOptions.sorter, + beforeInsert: this.DefaultOptions.beforeInsert, beforeSave: function(merges) { - var sanitizeLabelTitle; - sanitizeLabelTitle = function(title) { - if (/[\w\?&]+\s+[\w\?&]+/g.test(title)) { - return "\"" + (gl.utils.sanitize(title)) + "\""; - } else { - return gl.utils.sanitize(title); - } - }; return $.map(merges, function(m) { return { - title: sanitizeLabelTitle(m.title), + title: sanitize(m.title), color: m.color, search: "" + m.title }; @@ -271,6 +308,7 @@ at: '/', alias: 'commands', searchKey: 'search', + skipSpecialCharacterTest: true, displayTpl: function(value) { var tpl = '<li>/${name}'; if (value.aliases.length > 0) { diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 969778dded7..9a91018a8e4 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -650,6 +650,11 @@ } else if(value) { field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']"); } + + if (this.options.isSelectable && !this.options.isSelectable(selectedObject, el)) { + return; + } + if (el.hasClass(ACTIVE_CLASS)) { el.removeClass(ACTIVE_CLASS); if (field && field.length) { diff --git a/app/assets/javascripts/graphs/stat_graph_contributors_util.js b/app/assets/javascripts/graphs/stat_graph_contributors_util.js index 051ff98c774..1982f4af939 100644 --- a/app/assets/javascripts/graphs/stat_graph_contributors_util.js +++ b/app/assets/javascripts/graphs/stat_graph_contributors_util.js @@ -2,7 +2,7 @@ (function() { window.ContributorsStatGraphUtil = { parse_log: function(log) { - var by_author, by_email, data, entry, i, len, total; + var by_author, by_email, data, entry, i, len, total, normalized_email; total = {}; by_author = {}; by_email = {}; @@ -11,7 +11,8 @@ if (total[entry.date] == null) { this.add_date(entry.date, total); } - data = by_author[entry.author_name] || by_email[entry.author_email]; + normalized_email = entry.author_email.toLowerCase(); + data = by_author[entry.author_name] || by_email[normalized_email]; if (data == null) { data = this.add_author(entry, by_author, by_email); } @@ -32,12 +33,14 @@ return collection[date].date = date; }, add_author: function(author, by_author, by_email) { - var data; + var data, normalized_email; data = {}; data.author_name = author.author_name; data.author_email = author.author_email; + normalized_email = author.author_email.toLowerCase(); by_author[author.author_name] = data; - return by_email[author.author_email] = data; + by_email[normalized_email] = data; + return data; }, store_data: function(entry, total, by_author) { this.store_commits(total, by_author); diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 812d5cde685..f334f35594d 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, no-undef, semi, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread, padded-blocks, max-len */ +/* eslint-disable no-useless-return, func-names, space-before-function-paren, wrap-iife, no-var, no-underscore-dangle, prefer-arrow-callback, max-len, one-var, no-unused-vars, one-var-declaration-per-line, prefer-template, no-new, consistent-return, object-shorthand, comma-dangle, no-shadow, no-param-reassign, brace-style, vars-on-top, quotes, no-lonely-if, no-else-return, no-undef, semi, dot-notation, no-empty, no-return-assign, camelcase, prefer-spread, padded-blocks, max-len */ (function() { this.LabelsSelect = (function() { function LabelsSelect() { diff --git a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6 b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6 new file mode 100644 index 00000000000..e810ee85bd3 --- /dev/null +++ b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6 @@ -0,0 +1,113 @@ +/** + * Linked Tabs + * + * Handles persisting and restores the current tab selection and content. + * Reusable component for static content. + * + * ### Example Markup + * + * <ul class="nav-links tab-links"> + * <li class="active"> + * <a data-action="tab1" data-target="#tab1" data-toggle="tab" href="/path/tab1"> + * Tab 1 + * </a> + * </li> + * <li class="groups-tab"> + * <a data-action="tab2" data-target="#tab2" data-toggle="tab" href="/path/tab2"> + * Tab 2 + * </a> + * </li> + * + * + * <div class="tab-content"> + * <div class="tab-pane" id="tab1"> + * Tab 1 Content + * </div> + * <div class="tab-pane" id="tab2"> + * Tab 2 Content + * </div> + * </div> + * + * + * ### How to use + * + * new window.gl.LinkedTabs({ + * action: "#{controller.action_name}", + * defaultAction: 'tab1', + * parentEl: '.tab-links' + * }); + */ + +(() => { + window.gl = window.gl || {}; + + window.gl.LinkedTabs = class LinkedTabs { + /** + * Binds the events and activates de default tab. + * + * @param {Object} options + */ + constructor(options) { + this.options = options || {}; + + this.defaultAction = this.options.defaultAction; + this.action = this.options.action || this.defaultAction; + + if (this.action === 'show') { + this.action = this.defaultAction; + } + + this.currentLocation = window.location; + + const tabSelector = `${this.options.parentEl} a[data-toggle="tab"]`; + + // since this is a custom event we need jQuery :( + $(document) + .off('shown.bs.tab', tabSelector) + .on('shown.bs.tab', tabSelector, e => this.tabShown(e)); + + this.activateTab(this.action); + } + + /** + * Handles the `shown.bs.tab` event to set the currect url action. + * + * @param {type} evt + * @return {Function} + */ + tabShown(evt) { + const source = evt.target.getAttribute('href'); + + return this.setCurrentAction(source); + } + + /** + * Updates the URL with the path that matched the given action. + * + * @param {String} source + * @return {String} + */ + setCurrentAction(source) { + const copySource = source; + + copySource.replace(/\/+$/, ''); + + const newState = `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`; + + history.replaceState({ + turbolinks: true, + url: newState, + }, document.title, newState); + return newState; + } + + /** + * Given the current action activates the correct tab. + * http://getbootstrap.com/javascript/#tab-show + * Note: Will trigger `shown.bs.tab` + */ + activateTab() { + return $(`${this.options.parentEl} a[data-action='${this.action}']`).tab('show'); + } + }; +})(); diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index d83c41fae9d..8fa80502d92 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-param-reassign, no-else-return, quotes, object-shorthand, comma-dangle, camelcase, one-var, vars-on-top, one-var-declaration-per-line, no-return-assign, consistent-return, padded-blocks, max-len */ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-unused-expressions, no-param-reassign, no-else-return, quotes, object-shorthand, comma-dangle, camelcase, one-var, vars-on-top, one-var-declaration-per-line, no-return-assign, consistent-return, padded-blocks, max-len, prefer-template */ (function() { (function(w) { var base; @@ -33,10 +33,6 @@ }); }; - w.gl.utils.split = function(val) { - return val.split(/,\s*/); - }; - w.gl.utils.extractLast = function(term) { return this.split(term).pop(); }; @@ -67,63 +63,51 @@ }); }; - w.gl.utils.disableButtonIfAnyEmptyField = function(form, form_selector, button_selector) { - var closest_submit, updateButtons; - closest_submit = form.find(button_selector); - updateButtons = function() { - var filled; - filled = true; - form.find('input').filter(form_selector).each(function() { - return filled = this.rstrip($(this).val()) !== "" || !$(this).attr('required'); - }); - if (filled) { - return closest_submit.enable(); - } else { - return closest_submit.disable(); - } - }; - updateButtons(); - return form.keyup(updateButtons); - }; - - w.gl.utils.sanitize = function(str) { - return str.replace(/<(?:.|\n)*?>/gm, ''); - }; - - w.gl.utils.unbindEvents = function() { - return $(document).off('scroll'); - }; + // automatically adjust scroll position for hash urls taking the height of the navbar into account + // https://github.com/twitter/bootstrap/issues/1768 + w.gl.utils.handleLocationHash = function() { + var hash = w.gl.utils.getLocationHash(); + if (!hash) return; - w.gl.utils.shiftWindow = function() { - return w.scrollBy(0, -100); - }; + var navbar = document.querySelector('.navbar-gitlab'); + var subnav = document.querySelector('.layout-nav'); + var fixedTabs = document.querySelector('.js-tabs-affix'); + var adjustment = 0; + if (navbar) adjustment -= navbar.offsetHeight; + if (subnav) adjustment -= subnav.offsetHeight; - gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) { - return $tooltipEl.tooltip('destroy').attr('title', newTitle).tooltip('fixTitle'); - }; - gl.utils.preventDisabledButtons = function() { - return $('.btn').click(function(e) { - if ($(this).hasClass('disabled')) { - e.preventDefault(); - e.stopImmediatePropagation(); - return false; + // scroll to user-generated markdown anchor if we cannot find a match + if (document.getElementById(hash) === null) { + var target = document.getElementById('user-content-' + hash); + if (target && target.scrollIntoView) { + target.scrollIntoView(true); + window.scrollBy(0, adjustment); } - }); + } else { + // only adjust for fixedTabs when not targeting user-generated content + if (fixedTabs) { + adjustment -= fixedTabs.offsetHeight; + } + window.scrollBy(0, adjustment); + } }; + gl.utils.getPagePath = function() { return $('body').data('page').split(':')[0]; }; + gl.utils.parseUrl = function (url) { var parser = document.createElement('a'); parser.href = url; return parser; }; - gl.utils.cleanupBeforeFetch = function() { - // Unbind scroll events - $(document).off('scroll'); - // Close any open tooltips - $('.has-tooltip, [data-toggle="tooltip"]').tooltip('destroy'); + + gl.utils.parseUrlPathname = function (url) { + var parsedUrl = gl.utils.parseUrl(url); + // parsedUrl.pathname will return an absolute path for Firefox and a relative path for IE11 + // We have to make sure we always have an absolute path. + return parsedUrl.pathname.charAt(0) === '/' ? parsedUrl.pathname : '/' + parsedUrl.pathname; }; gl.utils.isMetaKey = function(e) { diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index 963d2851e5f..e8e502694d6 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -29,7 +29,7 @@ setTimeago = true; } - $timeagoEls.each(function() { + $timeagoEls.filter(':not([data-timeago-rendered])').each(function() { var $el = $(this); $el.attr('title', gl.utils.formatDate($el.attr('datetime'))); @@ -39,6 +39,8 @@ template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>' }); } + + $el.attr('data-timeago-rendered', true); gl.utils.renderTimeago($el); }); }; diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js.es6 index 895bc10784f..e3f367a11eb 100644 --- a/app/assets/javascripts/members.js.es6 +++ b/app/assets/javascripts/members.js.es6 @@ -1,38 +1,81 @@ -/* eslint-disable */ -((w) => { - w.gl = w.gl || {}; +/* eslint-disable class-methods-use-this */ +(() => { + window.gl = window.gl || {}; class Members { constructor() { this.addListeners(); + this.initGLDropdown(); } addListeners() { $('.project_member, .group_member').off('ajax:success').on('ajax:success', this.removeRow); - $('.js-member-update-control').off('change').on('change', this.formSubmit); - $('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess); + $('.js-member-update-control').off('change').on('change', this.formSubmit.bind(this)); + $('.js-edit-member-form').off('ajax:success').on('ajax:success', this.formSuccess.bind(this)); gl.utils.disableButtonIfEmptyField('#user_ids', 'input[name=commit]', 'change'); } + initGLDropdown() { + $('.js-member-permissions-dropdown').each((i, btn) => { + const $btn = $(btn); + + $btn.glDropdown({ + selectable: true, + isSelectable(selected, $el) { + return !$el.hasClass('is-active'); + }, + fieldName: $btn.data('field-name'), + id(selected, $el) { + return $el.data('id'); + }, + toggleLabel(selected, $el) { + return $el.text(); + }, + clicked: (selected, $link) => { + this.formSubmit(null, $link); + }, + }); + }); + } + removeRow(e) { const $target = $(e.target); if ($target.hasClass('btn-remove')) { $target.closest('.member') - .fadeOut(function () { + .fadeOut(function fadeOutMemberRow() { $(this).remove(); }); } } - formSubmit() { - $(this).closest('form').trigger("submit.rails").end().disable(); + formSubmit(e, $el = null) { + const $this = e ? $(e.currentTarget) : $el; + const { $toggle, $dateInput } = this.getMemberListItems($this); + + $this.closest('form').trigger('submit.rails'); + + $toggle.disable(); + $dateInput.disable(); } - formSuccess() { - $(this).find('.js-member-update-control').enable(); + formSuccess(e) { + const { $toggle, $dateInput } = this.getMemberListItems($(e.currentTarget).closest('.member')); + + $toggle.enable(); + $dateInput.enable(); + } + + getMemberListItems($el) { + const $memberListItem = $el.is('.member') ? $el : $(`#${$el.data('el-id')}`); + + return { + $memberListItem, + $toggle: $memberListItem.find('.dropdown-menu-toggle'), + $dateInput: $memberListItem.find('.js-access-expiration-date'), + }; } } gl.Members = Members; -})(window); +})(); diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js index a4b4db14db8..88c3636be6c 100644 --- a/app/assets/javascripts/merge_request.js +++ b/app/assets/javascripts/merge_request.js @@ -40,7 +40,7 @@ if (window.mrTabs) { window.mrTabs.unbindEvents(); } - window.mrTabs = new MergeRequestTabs(this.opts); + window.mrTabs = new gl.MergeRequestTabs(this.opts); }; MergeRequest.prototype.showAllCommits = function() { diff --git a/app/assets/javascripts/merge_request_tabs.js b/app/assets/javascripts/merge_request_tabs.js deleted file mode 100644 index b1928f8d279..00000000000 --- a/app/assets/javascripts/merge_request_tabs.js +++ /dev/null @@ -1,442 +0,0 @@ -/* eslint-disable max-len, func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, no-underscore-dangle, no-undef, one-var, one-var-declaration-per-line, quotes, comma-dangle, consistent-return, prefer-template, no-param-reassign, camelcase, vars-on-top, space-in-parens, curly, prefer-arrow-callback, no-unused-vars, no-return-assign, semi, object-shorthand, operator-assignment, padded-blocks, max-len */ -// MergeRequestTabs -// -// Handles persisting and restoring the current tab selection and lazily-loading -// content on the MergeRequests#show page. -// -/*= require js.cookie */ - -// -// ### Example Markup -// -// <ul class="nav-links merge-request-tabs"> -// <li class="notes-tab active"> -// <a data-action="notes" data-target="#notes" data-toggle="tab" href="/foo/bar/merge_requests/1"> -// Discussion -// </a> -// </li> -// <li class="commits-tab"> -// <a data-action="commits" data-target="#commits" data-toggle="tab" href="/foo/bar/merge_requests/1/commits"> -// Commits -// </a> -// </li> -// <li class="diffs-tab"> -// <a data-action="diffs" data-target="#diffs" data-toggle="tab" href="/foo/bar/merge_requests/1/diffs"> -// Diffs -// </a> -// </li> -// </ul> -// -// <div class="tab-content"> -// <div class="notes tab-pane active" id="notes"> -// Notes Content -// </div> -// <div class="commits tab-pane" id="commits"> -// Commits Content -// </div> -// <div class="diffs tab-pane" id="diffs"> -// Diffs Content -// </div> -// </div> -// -// <div class="mr-loading-status"> -// <div class="loading"> -// Loading Animation -// </div> -// </div> -// -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.MergeRequestTabs = (function() { - MergeRequestTabs.prototype.diffsLoaded = false; - - MergeRequestTabs.prototype.buildsLoaded = false; - - MergeRequestTabs.prototype.pipelinesLoaded = false; - - MergeRequestTabs.prototype.commitsLoaded = false; - - MergeRequestTabs.prototype.fixedLayoutPref = null; - - function MergeRequestTabs(opts) { - this.opts = opts != null ? opts : {}; - this.opts.setUrl = this.opts.setUrl !== undefined ? this.opts.setUrl : true; - - this.buildsLoaded = this.opts.buildsLoaded || false; - - this.setCurrentAction = bind(this.setCurrentAction, this); - this.tabShown = bind(this.tabShown, this); - this.showTab = bind(this.showTab, this); - // Store the `location` object, allowing for easier stubbing in tests - this._location = location; - this.bindEvents(); - this.activateTab(this.opts.action); - this.initAffix(); - } - - MergeRequestTabs.prototype.bindEvents = function() { - $(document).on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown); - $(document).on('click', '.js-show-tab', this.showTab); - }; - - MergeRequestTabs.prototype.unbindEvents = function() { - $(document).off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown); - $(document).off('click', '.js-show-tab', this.showTab); - }; - - MergeRequestTabs.prototype.showTab = function(event) { - event.preventDefault(); - return this.activateTab($(event.target).data('action')); - }; - - MergeRequestTabs.prototype.tabShown = function(event) { - var $target, action, navBarHeight; - $target = $(event.target); - action = $target.data('action'); - if (action === 'commits') { - this.loadCommits($target.attr('href')); - this.expandView(); - this.resetViewContainer(); - } else if (this.isDiffAction(action)) { - this.loadDiff($target.attr('href')); - if ((typeof bp !== "undefined" && bp !== null) && bp.getBreakpointSize() !== 'lg') { - this.shrinkView(); - } - if (this.diffViewType() === 'parallel') { - this.expandViewContainer(); - } - navBarHeight = $('.navbar-gitlab').outerHeight(); - $.scrollTo(".merge-request-details .merge-request-tabs", { - offset: -navBarHeight - }); - } else if (action === 'builds') { - this.loadBuilds($target.attr('href')); - this.expandView(); - this.resetViewContainer(); - } else if (action === 'pipelines') { - this.loadPipelines($target.attr('href')); - this.expandView(); - this.resetViewContainer(); - } else { - this.expandView(); - this.resetViewContainer(); - } - if (this.opts.setUrl) { - this.setCurrentAction(action); - } - }; - - MergeRequestTabs.prototype.scrollToElement = function(container) { - var $el, navBarHeight; - if (window.location.hash) { - navBarHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight() + document.querySelector('.js-tabs-affix').offsetHeight; - $el = $(container + " " + window.location.hash + ":not(.match)"); - if ($el.length) { - return $.scrollTo(container + " " + window.location.hash + ":not(.match)", { - offset: -navBarHeight - }); - } - } - }; - - // Activate a tab based on the current action - MergeRequestTabs.prototype.activateTab = function(action) { - if (action === 'show') { - action = 'notes'; - } - // important note: the .tab('show') method triggers 'shown.bs.tab' event itself - $(".merge-request-tabs a[data-action='" + action + "']").tab('show'); - }; - - // Replaces the current Merge Request-specific action in the URL with a new one - // - // If the action is "notes", the URL is reset to the standard - // `MergeRequests#show` route. - // - // Examples: - // - // location.pathname # => "/namespace/project/merge_requests/1" - // setCurrentAction('diffs') - // location.pathname # => "/namespace/project/merge_requests/1/diffs" - // - // location.pathname # => "/namespace/project/merge_requests/1/diffs" - // setCurrentAction('notes') - // location.pathname # => "/namespace/project/merge_requests/1" - // - // location.pathname # => "/namespace/project/merge_requests/1/diffs" - // setCurrentAction('commits') - // location.pathname # => "/namespace/project/merge_requests/1/commits" - // - // Returns the new URL String - MergeRequestTabs.prototype.setCurrentAction = function(action) { - var new_state; - // Normalize action, just to be safe - if (action === 'show') { - action = 'notes'; - } - this.currentAction = action; - // Remove a trailing '/commits' '/diffs' '/builds' '/pipelines' '/new' '/new/diffs' - new_state = this._location.pathname.replace(/\/(commits|diffs|builds|pipelines|new|new\/diffs)(\.html)?\/?$/, ''); - - // Append the new action if we're on a tab other than 'notes' - if (action !== 'notes') { - new_state += "/" + action; - } - // Ensure parameters and hash come along for the ride - new_state += this._location.search + this._location.hash; - history.replaceState({ - turbolinks: true, - url: new_state - // Replace the current history state with the new one without breaking - // Turbolinks' history. - // - // See https://github.com/rails/turbolinks/issues/363 - }, document.title, new_state); - return new_state; - }; - - MergeRequestTabs.prototype.loadCommits = function(source) { - if (this.commitsLoaded) { - return; - } - return this._get({ - url: source + ".json", - success: (function(_this) { - return function(data) { - document.querySelector("div#commits").innerHTML = data.html; - gl.utils.localTimeAgo($('.js-timeago', 'div#commits')); - _this.commitsLoaded = true; - return _this.scrollToElement("#commits"); - }; - })(this) - }); - }; - - MergeRequestTabs.prototype.loadDiff = function(source) { - if (this.diffsLoaded) { - return; - } - - // We extract pathname for the current Changes tab anchor href - // some pages like MergeRequestsController#new has query parameters on that anchor - var url = gl.utils.parseUrl(source); - - return this._get({ - url: (url.pathname + ".json") + this._location.search, - success: (function(_this) { - return function(data) { - $('#diffs').html(data.html); - - if (typeof gl.diffNotesCompileComponents !== 'undefined') { - gl.diffNotesCompileComponents(); - } - - gl.utils.localTimeAgo($('.js-timeago', 'div#diffs')); - $('#diffs .js-syntax-highlight').syntaxHighlight(); - $('#diffs .diff-file').singleFileDiff(); - if (_this.diffViewType() === 'parallel' && (_this.isDiffAction(_this.currentAction)) ) { - _this.expandViewContainer(); - } - _this.diffsLoaded = true; - var anchoredDiff = gl.utils.getLocationHash(); - if (anchoredDiff) _this.openAnchoredDiff(anchoredDiff, function() { - _this.scrollToElement("#diffs"); - _this.highlighSelectedLine(); - }); - _this.filesCommentButton = $('.files .diff-file').filesCommentButton(); - return $(document).off('click', '.diff-line-num a').on('click', '.diff-line-num a', function(e) { - e.preventDefault(); - window.location.hash = $(e.currentTarget).attr('href'); - _this.highlighSelectedLine(); - return _this.scrollToElement("#diffs"); - }); - }; - })(this) - }); - }; - - MergeRequestTabs.prototype.openAnchoredDiff = function(anchoredDiff, cb) { - var diffTitle = $('#file-path-' + anchoredDiff); - var diffFile = diffTitle.closest('.diff-file'); - var nothingHereBlock = $('.nothing-here-block:visible', diffFile); - if (nothingHereBlock.length) { - diffFile.singleFileDiff(true, cb); - } else { - cb(); - } - }; - - MergeRequestTabs.prototype.highlighSelectedLine = function() { - var $diffLine, diffLineTop, hashClassString, locationHash, navBarHeight; - $('.hll').removeClass('hll'); - locationHash = window.location.hash; - if (locationHash !== '') { - dataLineString = '[data-line-code="' + locationHash.replace('#', '') + '"]'; - $diffLine = $(locationHash + ":not(.match)", $('#diffs')); - if (!$diffLine.is('tr')) { - $diffLine = $('#diffs').find("td" + locationHash + ", td" + dataLineString); - } else { - $diffLine = $diffLine.find('td'); - } - if ($diffLine.length) { - $diffLine.addClass('hll'); - diffLineTop = $diffLine.offset().top; - return navBarHeight = $('.navbar-gitlab').outerHeight(); - } - } - }; - - MergeRequestTabs.prototype.loadBuilds = function(source) { - if (this.buildsLoaded) { - return; - } - return this._get({ - url: source + ".json", - success: (function(_this) { - return function(data) { - document.querySelector("div#builds").innerHTML = data.html; - gl.utils.localTimeAgo($('.js-timeago', 'div#builds')); - _this.buildsLoaded = true; - if (!this.pipelines) this.pipelines = new gl.Pipelines(); - return _this.scrollToElement("#builds"); - }; - })(this) - }); - }; - - MergeRequestTabs.prototype.loadPipelines = function(source) { - if (this.pipelinesLoaded) { - return; - } - return this._get({ - url: source + ".json", - success: function(data) { - $('#pipelines').html(data.html); - gl.utils.localTimeAgo($('.js-timeago', '#pipelines')); - this.pipelinesLoaded = true; - return this.scrollToElement("#pipelines"); - }.bind(this) - }); - }; - - // Show or hide the loading spinner - // - // status - Boolean, true to show, false to hide - MergeRequestTabs.prototype.toggleLoading = function(status) { - return $('.mr-loading-status .loading').toggle(status); - }; - - MergeRequestTabs.prototype._get = function(options) { - var defaults; - defaults = { - beforeSend: (function(_this) { - return function() { - return _this.toggleLoading(true); - }; - })(this), - complete: (function(_this) { - return function() { - return _this.toggleLoading(false); - }; - })(this), - dataType: 'json', - type: 'GET' - }; - options = $.extend({}, defaults, options); - return $.ajax(options); - }; - - MergeRequestTabs.prototype.diffViewType = function() { - return $('.inline-parallel-buttons a.active').data('view-type'); - }; - - MergeRequestTabs.prototype.isDiffAction = function(action) { - return action === 'diffs' || action === 'new/diffs' - }; - - MergeRequestTabs.prototype.expandViewContainer = function() { - var $wrapper = $('.content-wrapper .container-fluid'); - if (this.fixedLayoutPref === null) { - this.fixedLayoutPref = $wrapper.hasClass('container-limited'); - } - $wrapper.removeClass('container-limited'); - }; - - MergeRequestTabs.prototype.resetViewContainer = function() { - if (this.fixedLayoutPref !== null) { - $('.content-wrapper .container-fluid') - .toggleClass('container-limited', this.fixedLayoutPref); - } - }; - - MergeRequestTabs.prototype.shrinkView = function() { - var $gutterIcon; - $gutterIcon = $('.js-sidebar-toggle i:visible'); - return setTimeout(function() { - if ($gutterIcon.is('.fa-angle-double-right')) { - return $gutterIcon.closest('a').trigger('click', [true]); - } - // Wait until listeners are set - // Only when sidebar is expanded - }, 0); - }; - - MergeRequestTabs.prototype.expandView = function() { - var $gutterIcon; - if (Cookies.get('collapsed_gutter') === 'true') { - return; - } - $gutterIcon = $('.js-sidebar-toggle i:visible'); - return setTimeout(function() { - if ($gutterIcon.is('.fa-angle-double-left')) { - return $gutterIcon.closest('a').trigger('click', [true]); - } - }, 0); - // Expand the issuable sidebar unless the user explicitly collapsed it - // Wait until listeners are set - // Only when sidebar is collapsed - }; - - MergeRequestTabs.prototype.initAffix = function () { - var $tabs = $('.js-tabs-affix'); - - // Screen space on small screens is usually very sparse - // So we dont affix the tabs on these - if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return; - - var $diffTabs = $('#diff-notes-app'), - $fixedNav = $('.navbar-fixed-top'), - $layoutNav = $('.layout-nav'); - - $tabs.off('affix.bs.affix affix-top.bs.affix') - .affix({ - offset: { - top: function () { - var tabsTop = $diffTabs.offset().top - $tabs.height(); - tabsTop = tabsTop - ($fixedNav.height() + $layoutNav.height()); - - return tabsTop; - } - } - }).on('affix.bs.affix', function () { - $diffTabs.css({ - marginTop: $tabs.height() - }); - }).on('affix-top.bs.affix', function () { - $diffTabs.css({ - marginTop: '' - }); - }); - - // Fix bug when reloading the page already scrolling - if ($tabs.hasClass('affix')) { - $tabs.trigger('affix.bs.affix'); - } - }; - - return MergeRequestTabs; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/merge_request_tabs.js.es6 b/app/assets/javascripts/merge_request_tabs.js.es6 new file mode 100644 index 00000000000..3ec0f1fd613 --- /dev/null +++ b/app/assets/javascripts/merge_request_tabs.js.es6 @@ -0,0 +1,389 @@ +/* eslint-disable no-new, class-methods-use-this */ +/* global Breakpoints */ +/* global Cookies */ +/* global DiffNotesApp */ +/* global Flash */ + +/*= require js.cookie */ +/*= require breakpoints */ + +/* eslint-disable max-len */ +// MergeRequestTabs +// +// Handles persisting and restoring the current tab selection and lazily-loading +// content on the MergeRequests#show page. +// +// ### Example Markup +// +// <ul class="nav-links merge-request-tabs"> +// <li class="notes-tab active"> +// <a data-action="notes" data-target="#notes" data-toggle="tab" href="/foo/bar/merge_requests/1"> +// Discussion +// </a> +// </li> +// <li class="commits-tab"> +// <a data-action="commits" data-target="#commits" data-toggle="tab" href="/foo/bar/merge_requests/1/commits"> +// Commits +// </a> +// </li> +// <li class="diffs-tab"> +// <a data-action="diffs" data-target="#diffs" data-toggle="tab" href="/foo/bar/merge_requests/1/diffs"> +// Diffs +// </a> +// </li> +// </ul> +// +// <div class="tab-content"> +// <div class="notes tab-pane active" id="notes"> +// Notes Content +// </div> +// <div class="commits tab-pane" id="commits"> +// Commits Content +// </div> +// <div class="diffs tab-pane" id="diffs"> +// Diffs Content +// </div> +// </div> +// +// <div class="mr-loading-status"> +// <div class="loading"> +// Loading Animation +// </div> +// </div> +// +/* eslint-enable max-len */ + +(() => { + // Store the `location` object, allowing for easier stubbing in tests + let location = window.location; + + class MergeRequestTabs { + + constructor({ action, setUrl, buildsLoaded, stubLocation } = {}) { + this.diffsLoaded = false; + this.buildsLoaded = false; + this.pipelinesLoaded = false; + this.commitsLoaded = false; + this.fixedLayoutPref = null; + + this.setUrl = setUrl !== undefined ? setUrl : true; + this.buildsLoaded = buildsLoaded || false; + + this.setCurrentAction = this.setCurrentAction.bind(this); + this.tabShown = this.tabShown.bind(this); + this.showTab = this.showTab.bind(this); + + if (stubLocation) { + location = stubLocation; + } + + this.bindEvents(); + this.activateTab(action); + this.initAffix(); + } + + bindEvents() { + $(document) + .on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown) + .on('click', '.js-show-tab', this.showTab); + } + + unbindEvents() { + $(document) + .off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown) + .off('click', '.js-show-tab', this.showTab); + } + + showTab(e) { + e.preventDefault(); + this.activateTab($(e.target).data('action')); + } + + tabShown(e) { + const $target = $(e.target); + const action = $target.data('action'); + + if (action === 'commits') { + this.loadCommits($target.attr('href')); + this.expandView(); + this.resetViewContainer(); + } else if (this.isDiffAction(action)) { + this.loadDiff($target.attr('href')); + if (Breakpoints.get().getBreakpointSize() !== 'lg') { + this.shrinkView(); + } + if (this.diffViewType() === 'parallel') { + this.expandViewContainer(); + } + const navBarHeight = $('.navbar-gitlab').outerHeight(); + $.scrollTo('.merge-request-details .merge-request-tabs', { + offset: -navBarHeight, + }); + } else if (action === 'builds') { + this.loadBuilds($target.attr('href')); + this.expandView(); + this.resetViewContainer(); + } else if (action === 'pipelines') { + this.loadPipelines($target.attr('href')); + this.expandView(); + this.resetViewContainer(); + } else { + this.expandView(); + this.resetViewContainer(); + } + if (this.setUrl) { + this.setCurrentAction(action); + } + } + + scrollToElement(container) { + if (location.hash) { + const offset = 0 - ( + $('.navbar-gitlab').outerHeight() + + $('.layout-nav').outerHeight() + + $('.js-tabs-affix').outerHeight() + ); + const $el = $(`${container} ${location.hash}:not(.match)`); + if ($el.length) { + $.scrollTo($el[0], { offset }); + } + } + } + + // Activate a tab based on the current action + activateTab(action) { + const activate = action === 'show' ? 'notes' : action; + // important note: the .tab('show') method triggers 'shown.bs.tab' event itself + $(`.merge-request-tabs a[data-action='${activate}']`).tab('show'); + } + + // Replaces the current Merge Request-specific action in the URL with a new one + // + // If the action is "notes", the URL is reset to the standard + // `MergeRequests#show` route. + // + // Examples: + // + // location.pathname # => "/namespace/project/merge_requests/1" + // setCurrentAction('diffs') + // location.pathname # => "/namespace/project/merge_requests/1/diffs" + // + // location.pathname # => "/namespace/project/merge_requests/1/diffs" + // setCurrentAction('notes') + // location.pathname # => "/namespace/project/merge_requests/1" + // + // location.pathname # => "/namespace/project/merge_requests/1/diffs" + // setCurrentAction('commits') + // location.pathname # => "/namespace/project/merge_requests/1/commits" + // + // Returns the new URL String + setCurrentAction(action) { + this.currentAction = action === 'show' ? 'notes' : action; + + // Remove a trailing '/commits' '/diffs' '/builds' '/pipelines' '/new' '/new/diffs' + let newState = location.pathname.replace(/\/(commits|diffs|builds|pipelines|new|new\/diffs)(\.html)?\/?$/, ''); + + // Append the new action if we're on a tab other than 'notes' + if (this.currentAction !== 'notes') { + newState += `/${this.currentAction}`; + } + + // Ensure parameters and hash come along for the ride + newState += location.search + location.hash; + + // Replace the current history state with the new one without breaking + // Turbolinks' history. + // + // See https://github.com/rails/turbolinks/issues/363 + window.history.replaceState({ + turbolinks: true, + url: newState, + }, document.title, newState); + + return newState; + } + + loadCommits(source) { + if (this.commitsLoaded) { + return; + } + this.ajaxGet({ + url: `${source}.json`, + success: (data) => { + document.querySelector('div#commits').innerHTML = data.html; + gl.utils.localTimeAgo($('.js-timeago', 'div#commits')); + this.commitsLoaded = true; + this.scrollToElement('#commits'); + }, + }); + } + + loadDiff(source) { + if (this.diffsLoaded) { + return; + } + + // We extract pathname for the current Changes tab anchor href + // some pages like MergeRequestsController#new has query parameters on that anchor + const urlPathname = gl.utils.parseUrlPathname(source); + + this.ajaxGet({ + url: `${urlPathname}.json${location.search}`, + success: (data) => { + $('#diffs').html(data.html); + + if (typeof gl.diffNotesCompileComponents !== 'undefined') { + gl.diffNotesCompileComponents(); + } + + gl.utils.localTimeAgo($('.js-timeago', 'div#diffs')); + $('#diffs .js-syntax-highlight').syntaxHighlight(); + + if (this.diffViewType() === 'parallel' && this.isDiffAction(this.currentAction)) { + this.expandViewContainer(); + } + this.diffsLoaded = true; + + const diffPage = new gl.Diff(); + + const locationHash = gl.utils.getLocationHash(); + const anchoredDiff = locationHash && locationHash.split('_')[0]; + if (anchoredDiff) { + diffPage.openAnchoredDiff(anchoredDiff, () => this.scrollToElement('#diffs')); + } + }, + }); + } + + loadBuilds(source) { + if (this.buildsLoaded) { + return; + } + this.ajaxGet({ + url: `${source}.json`, + success: (data) => { + document.querySelector('div#builds').innerHTML = data.html; + gl.utils.localTimeAgo($('.js-timeago', 'div#builds')); + this.buildsLoaded = true; + new gl.Pipelines(); + this.scrollToElement('#builds'); + }, + }); + } + + loadPipelines(source) { + if (this.pipelinesLoaded) { + return; + } + this.ajaxGet({ + url: `${source}.json`, + success: (data) => { + $('#pipelines').html(data.html); + gl.utils.localTimeAgo($('.js-timeago', '#pipelines')); + this.pipelinesLoaded = true; + this.scrollToElement('#pipelines'); + }, + }); + } + + // Show or hide the loading spinner + // + // status - Boolean, true to show, false to hide + toggleLoading(status) { + $('.mr-loading-status .loading').toggle(status); + } + + ajaxGet(options) { + const defaults = { + beforeSend: () => this.toggleLoading(true), + error: () => new Flash('An error occurred while fetching this tab.', 'alert'), + complete: () => this.toggleLoading(false), + dataType: 'json', + type: 'GET', + }; + $.ajax($.extend({}, defaults, options)); + } + + diffViewType() { + return $('.inline-parallel-buttons a.active').data('view-type'); + } + + isDiffAction(action) { + return action === 'diffs' || action === 'new/diffs'; + } + + expandViewContainer() { + const $wrapper = $('.content-wrapper .container-fluid'); + if (this.fixedLayoutPref === null) { + this.fixedLayoutPref = $wrapper.hasClass('container-limited'); + } + $wrapper.removeClass('container-limited'); + } + + resetViewContainer() { + if (this.fixedLayoutPref !== null) { + $('.content-wrapper .container-fluid') + .toggleClass('container-limited', this.fixedLayoutPref); + } + } + + shrinkView() { + const $gutterIcon = $('.js-sidebar-toggle i:visible'); + + // Wait until listeners are set + setTimeout(() => { + // Only when sidebar is expanded + if ($gutterIcon.is('.fa-angle-double-right')) { + $gutterIcon.closest('a').trigger('click', [true]); + } + }, 0); + } + + // Expand the issuable sidebar unless the user explicitly collapsed it + expandView() { + if (Cookies.get('collapsed_gutter') === 'true') { + return; + } + const $gutterIcon = $('.js-sidebar-toggle i:visible'); + + // Wait until listeners are set + setTimeout(() => { + // Only when sidebar is collapsed + if ($gutterIcon.is('.fa-angle-double-left')) { + $gutterIcon.closest('a').trigger('click', [true]); + } + }, 0); + } + + initAffix() { + const $tabs = $('.js-tabs-affix'); + + // Screen space on small screens is usually very sparse + // So we dont affix the tabs on these + if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return; + + const $diffTabs = $('#diff-notes-app'); + const $fixedNav = $('.navbar-fixed-top'); + const $layoutNav = $('.layout-nav'); + + $tabs.off('affix.bs.affix affix-top.bs.affix') + .affix({ + offset: { + top: () => ( + $diffTabs.offset().top - $tabs.height() - $fixedNav.height() - $layoutNav.height() + ), + }, + }) + .on('affix.bs.affix', () => $diffTabs.css({ marginTop: $tabs.height() })) + .on('affix-top.bs.affix', () => $diffTabs.css({ marginTop: '' })); + + // Fix bug when reloading the page already scrolling + if ($tabs.hasClass('affix')) { + $tabs.trigger('affix.bs.affix'); + } + } + } + + window.gl = window.gl || {}; + window.gl.MergeRequestTabs = MergeRequestTabs; +})(); diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index a55fe9df0b3..7022aa1263b 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -40,19 +40,26 @@ $('#modal_merge_info').modal({ show: false }); - this.firstCICheck = true; - this.readyForCICheck = false; - this.readyForCIEnvironmentCheck = false; - this.cancel = false; - clearInterval(this.fetchBuildStatusInterval); - clearInterval(this.fetchBuildEnvironmentStatusInterval); this.clearEventListeners(); this.addEventListeners(); this.getCIStatus(false); - this.getCIEnvironmentsStatus(); this.retrieveSuccessIcon(); - this.pollCIStatus(); - this.pollCIEnvironmentsStatus(); + + this.ciStatusInterval = new global.SmartInterval({ + callback: this.getCIStatus.bind(this, true), + startingInterval: 10000, + maxInterval: 30000, + hiddenInterval: 120000, + incrementByFactorOf: 5000, + }); + this.ciEnvironmentStatusInterval = new global.SmartInterval({ + callback: this.getCIEnvironmentsStatus.bind(this), + startingInterval: 30000, + maxInterval: 120000, + hiddenInterval: 240000, + incrementByFactorOf: 15000, + immediateExecution: true, + }); notifyPermissions(); } @@ -60,10 +67,6 @@ return $(document).off('page:change.merge_request'); }; - MergeRequestWidget.prototype.cancelPolling = function() { - return this.cancel = true; - }; - MergeRequestWidget.prototype.addEventListeners = function() { var allowedPages; allowedPages = ['show', 'commits', 'builds', 'pipelines', 'changes']; @@ -72,9 +75,6 @@ var page; page = $('body').data('page').split(':').last(); if (allowedPages.indexOf(page) < 0) { - clearInterval(_this.fetchBuildStatusInterval); - clearInterval(_this.fetchBuildEnvironmentStatusInterval); - _this.cancelPolling(); return _this.clearEventListeners(); } }; @@ -101,7 +101,7 @@ urlSuffix = deleteSourceBranch ? '?deleted_source_branch=true' : ''; return window.location.href = window.location.pathname + urlSuffix; } else if (data.merge_error) { - return this.$widgetBody.html("<h4>" + data.merge_error + "</h4>"); + return _this.$widgetBody.html("<h4>" + data.merge_error + "</h4>"); } else { callback = function() { return merge_request_widget.mergeInProgress(deleteSourceBranch); @@ -114,6 +114,11 @@ }); }; + MergeRequestWidget.prototype.cancelPolling = function () { + this.ciStatusInterval.cancel(); + this.ciEnvironmentStatusInterval.cancel(); + }; + MergeRequestWidget.prototype.getMergeStatus = function() { return $.get(this.opts.merge_check_url, function(data) { return $('.mr-state-widget').replaceWith(data); @@ -131,18 +136,6 @@ } }; - MergeRequestWidget.prototype.pollCIStatus = function() { - return this.fetchBuildStatusInterval = setInterval(((function(_this) { - return function() { - if (!_this.readyForCICheck) { - return; - } - _this.getCIStatus(true); - return _this.readyForCICheck = false; - }; - })(this)), 10000); - }; - MergeRequestWidget.prototype.getCIStatus = function(showNotification) { var _this; _this = this; @@ -150,23 +143,17 @@ return $.getJSON(this.opts.ci_status_url, (function(_this) { return function(data) { var message, status, title; - if (_this.cancel) { - return; - } - _this.readyForCICheck = true; if (data.status === '') { return; } if (data.environments && data.environments.length) _this.renderEnvironments(data.environments); - if (_this.firstCICheck || data.status !== _this.opts.ci_status && (data.status != null)) { + if (data.status !== _this.opts.ci_status && (data.status != null)) { _this.opts.ci_status = data.status; _this.showCIStatus(data.status); if (data.coverage) { _this.showCICoverage(data.coverage); } - // The first check should only update the UI, a notification - // should only be displayed on status changes - if (showNotification && !_this.firstCICheck) { + if (showNotification) { status = _this.ciLabelForStatus(data.status); if (status === "preparing") { title = _this.opts.ci_title.preparing; @@ -184,24 +171,13 @@ return Turbolinks.visit(_this.opts.builds_path); }); } - return _this.firstCICheck = false; } }; })(this)); }; - MergeRequestWidget.prototype.pollCIEnvironmentsStatus = function() { - this.fetchBuildEnvironmentStatusInterval = setInterval(() => { - if (!this.readyForCIEnvironmentCheck) return; - this.getCIEnvironmentsStatus(); - this.readyForCIEnvironmentCheck = false; - }, 300000); - }; - MergeRequestWidget.prototype.getCIEnvironmentsStatus = function() { $.getJSON(this.opts.ci_environments_status_url, (environments) => { - if (this.cancel) return; - this.readyForCIEnvironmentCheck = true; if (environments && environments.length) this.renderEnvironments(environments); }); }; @@ -212,11 +188,11 @@ if ($(`.mr-state-widget #${ environment.id }`).length) return; const $template = $(DEPLOYMENT_TEMPLATE); if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove(); - + if (!environment.stop_url) { $('.js-stop-env-link', $template).remove(); } - + if (environment.deployed_at && environment.deployed_at_formatted) { environment.deployed_at = gl.utils.getTimeago().format(environment.deployed_at, 'gl_en') + '.'; } else { diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index a84c514dac7..0ca0e255595 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, no-undef, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, semi, indent, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape, radix, padded-blocks, max-len */ +/* eslint-disable no-restricted-properties, func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, no-undef, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, semi, indent, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape, radix, padded-blocks, max-len */ /*= require autosave */ /*= require autosize */ @@ -333,7 +333,7 @@ gl.diffNotesCompileComponents(); } - gl.utils.localTimeAgo($('.js-timeago', note_html), false); + gl.utils.localTimeAgo($('.js-timeago'), false); return this.updateNotesCount(1); }; diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js.es6 index a84db9c0233..fb95648e1c7 100644 --- a/app/assets/javascripts/pipelines.js.es6 +++ b/app/assets/javascripts/pipelines.js.es6 @@ -1,15 +1,24 @@ +//= require lib/utils/bootstrap_linked_tabs + /* eslint-disable */ ((global) => { class Pipelines { - constructor() { + constructor(options = {}) { + + if (options.initTabs && options.tabsOptions) { + new global.LinkedTabs(options.tabsOptions); + } + this.addMarginToBuildColumns(); } addMarginToBuildColumns() { - this.pipelineGraph = document.querySelector('.pipeline-graph'); - const secondChildBuildNodes = document.querySelector('.pipeline-graph').querySelectorAll('.build:nth-child(2)'); - for (buildNodeIndex in secondChildBuildNodes) { + this.pipelineGraph = document.querySelector('.js-pipeline-graph'); + + const secondChildBuildNodes = this.pipelineGraph.querySelectorAll('.build:nth-child(2)'); + + for (const buildNodeIndex in secondChildBuildNodes) { const buildNode = secondChildBuildNodes[buildNodeIndex]; const firstChildBuildNode = buildNode.previousElementSibling; if (!firstChildBuildNode || !firstChildBuildNode.matches('.build')) continue; @@ -21,6 +30,7 @@ const columnBuilds = previousColumn.querySelectorAll('.build'); if (columnBuilds.length === 1) previousColumn.classList.add('no-margin'); } + this.pipelineGraph.classList.remove('hidden'); } } diff --git a/app/assets/javascripts/project_variables.js.es6 b/app/assets/javascripts/project_variables.js.es6 new file mode 100644 index 00000000000..4ee2e49306d --- /dev/null +++ b/app/assets/javascripts/project_variables.js.es6 @@ -0,0 +1,43 @@ +(() => { + const HIDDEN_VALUE_TEXT = '******'; + + class ProjectVariables { + constructor() { + this.$revealBtn = $('.js-btn-toggle-reveal-values'); + this.$revealBtn.on('click', this.toggleRevealState.bind(this)); + } + + toggleRevealState(e) { + e.preventDefault(); + + const oldStatus = this.$revealBtn.attr('data-status'); + let newStatus = 'hidden'; + let newAction = 'Reveal Values'; + + if (oldStatus === 'hidden') { + newStatus = 'revealed'; + newAction = 'Hide Values'; + } + + this.$revealBtn.attr('data-status', newStatus); + + const $variables = $('.variable-value'); + + $variables.each((_, variable) => { + const $variable = $(variable); + let newText = HIDDEN_VALUE_TEXT; + + if (newStatus === 'revealed') { + newText = $variable.attr('data-value'); + } + + $variable.text(newText); + }); + + this.$revealBtn.text(newAction); + } + } + + window.gl = window.gl || {}; + window.gl.ProjectVariables = ProjectVariables; +})(); diff --git a/app/assets/javascripts/signin_tabs_memoizer.js.es6 b/app/assets/javascripts/signin_tabs_memoizer.js.es6 new file mode 100644 index 00000000000..d811d1cd53a --- /dev/null +++ b/app/assets/javascripts/signin_tabs_memoizer.js.es6 @@ -0,0 +1,49 @@ +/* eslint no-param-reassign: ["error", { "props": false }]*/ +/* eslint no-new: "off" */ +((global) => { + /** + * Memorize the last selected tab after reloading a page. + * Does that setting the current selected tab in the localStorage + */ + class ActiveTabMemoizer { + constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) { + this.currentTabKey = currentTabKey; + this.tabSelector = tabSelector; + this.bootstrap(); + } + + bootstrap() { + const tabs = document.querySelectorAll(this.tabSelector); + if (tabs.length > 0) { + tabs[0].addEventListener('click', (e) => { + if (e.target && e.target.nodeName === 'A') { + const anchorName = e.target.getAttribute('href'); + this.saveData(anchorName); + } + }); + } + + this.showTab(); + } + + showTab() { + const anchorName = this.readData(); + if (anchorName) { + const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`); + if (tab) { + tab.click(); + } + } + } + + saveData(val) { + localStorage.setItem(this.currentTabKey, val); + } + + readData() { + return localStorage.getItem(this.currentTabKey); + } + } + + global.ActiveTabMemoizer = ActiveTabMemoizer; +})(window); diff --git a/app/assets/javascripts/single_file_diff.js b/app/assets/javascripts/single_file_diff.js index 2767849e673..0d48e69cce9 100644 --- a/app/assets/javascripts/single_file_diff.js +++ b/app/assets/javascripts/single_file_diff.js @@ -14,6 +14,7 @@ COLLAPSED_HTML = '<div class="nothing-here-block diff-collapsed">This diff is collapsed. <a class="click-to-expand">Click to expand it.</a></div>'; function SingleFileDiff(file, forceLoad, cb) { + var clickTarget; this.file = file; this.toggleDiff = bind(this.toggleDiff, this); this.content = $('.diff-content', this.file); @@ -31,9 +32,9 @@ this.content.after(this.collapsedContent); this.$toggleIcon.addClass('fa-caret-down'); } - $('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff); + clickTarget = $('.file-title, .click-to-expand', this.file).on('click', this.toggleDiff); if (forceLoad) { - this.toggleDiff(null, cb); + this.toggleDiff({ target: clickTarget }, cb); } } diff --git a/app/assets/javascripts/smart_interval.js.es6 b/app/assets/javascripts/smart_interval.js.es6 index 5eb15dba79b..40f67637c7c 100644 --- a/app/assets/javascripts/smart_interval.js.es6 +++ b/app/assets/javascripts/smart_interval.js.es6 @@ -7,24 +7,31 @@ (() => { class SmartInterval { /** - * @param { function } callback Function to be called on each iteration (required) - * @param { milliseconds } startingInterval `currentInterval` is set to this initially - * @param { milliseconds } maxInterval `currentInterval` will be incremented to this - * @param { integer } incrementByFactorOf `currentInterval` is incremented by this factor - * @param { boolean } lazyStart Configure if timer is initialized on instantiation or lazily + * @param { function } opts.callback Function to be called on each iteration (required) + * @param { milliseconds } opts.startingInterval `currentInterval` is set to this initially + * @param { milliseconds } opts.maxInterval `currentInterval` will be incremented to this + * @param { milliseconds } opts.hiddenInterval `currentInterval` is set to this + * when the page is hidden + * @param { integer } opts.incrementByFactorOf `currentInterval` is incremented by this factor + * @param { boolean } opts.lazyStart Configure if timer is initialized on + * instantiation or lazily + * @param { boolean } opts.immediateExecution Configure if callback should + * be executed before the first interval. */ - constructor({ callback, startingInterval, maxInterval, incrementByFactorOf, lazyStart }) { + constructor(opts = {}) { this.cfg = { - callback, - startingInterval, - maxInterval, - incrementByFactorOf, - lazyStart, + callback: opts.callback, + startingInterval: opts.startingInterval, + maxInterval: opts.maxInterval, + hiddenInterval: opts.hiddenInterval, + incrementByFactorOf: opts.incrementByFactorOf, + lazyStart: opts.lazyStart, + immediateExecution: opts.immediateExecution, }; this.state = { intervalId: null, - currentInterval: startingInterval, + currentInterval: this.cfg.startingInterval, pageVisibility: 'visible', }; @@ -36,6 +43,11 @@ const cfg = this.cfg; const state = this.state; + if (cfg.immediateExecution) { + cfg.immediateExecution = false; + cfg.callback(); + } + state.intervalId = window.setInterval(() => { cfg.callback(); @@ -54,14 +66,29 @@ this.stopTimer(); } + onVisibilityHidden() { + if (this.cfg.hiddenInterval) { + this.setCurrentInterval(this.cfg.hiddenInterval); + this.resume(); + } else { + this.cancel(); + } + } + // start a timer, using the existing interval resume() { this.stopTimer(); // stop exsiting timer, in case timer was not previously stopped this.start(); } + onVisibilityVisible() { + this.cancel(); + this.start(); + } + destroy() { this.cancel(); + document.removeEventListener('visibilitychange', this.handleVisibilityChange); $(document).off('visibilitychange').off('page:before-unload'); } @@ -80,11 +107,7 @@ initVisibilityChangeHandling() { // cancel interval when tab no longer shown (prevents cached pages from polling) - $(document) - .off('visibilitychange').on('visibilitychange', (e) => { - this.state.pageVisibility = e.target.visibilityState; - this.handleVisibilityChange(); - }); + document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this)); } initPageUnloadHandling() { @@ -92,10 +115,11 @@ $(document).on('page:before-unload', () => this.cancel()); } - handleVisibilityChange() { - const state = this.state; - - const intervalAction = state.pageVisibility === 'hidden' ? this.cancel : this.resume; + handleVisibilityChange(e) { + this.state.pageVisibility = e.target.visibilityState; + const intervalAction = this.isPageVisible() ? + this.onVisibilityVisible : + this.onVisibilityHidden; intervalAction.apply(this); } @@ -111,6 +135,7 @@ incrementInterval() { const cfg = this.cfg; const currentInterval = this.getCurrentInterval(); + if (cfg.hiddenInterval && !this.isPageVisible()) return; let nextInterval = currentInterval * cfg.incrementByFactorOf; if (nextInterval > cfg.maxInterval) { @@ -120,6 +145,8 @@ this.setCurrentInterval(nextInterval); } + isPageVisible() { return this.state.pageVisibility === 'visible'; } + stopTimer() { const state = this.state; diff --git a/app/assets/javascripts/subscription.js b/app/assets/javascripts/subscription.js deleted file mode 100644 index 6d75688deeb..00000000000 --- a/app/assets/javascripts/subscription.js +++ /dev/null @@ -1,52 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, vars-on-top, no-unused-vars, one-var, one-var-declaration-per-line, camelcase, consistent-return, no-undef, padded-blocks, max-len */ -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.Subscription = (function() { - function Subscription(container) { - this.toggleSubscription = bind(this.toggleSubscription, this); - var $container; - this.$container = $(container); - this.url = this.$container.attr('data-url'); - this.subscribe_button = this.$container.find('.js-subscribe-button'); - this.subscription_status = this.$container.find('.subscription-status'); - this.subscribe_button.unbind('click').click(this.toggleSubscription); - } - - Subscription.prototype.toggleSubscription = function(event) { - var action, btn, current_status; - btn = $(event.currentTarget); - action = btn.find('span').text(); - current_status = this.subscription_status.attr('data-status'); - btn.addClass('disabled'); - - if ($('html').hasClass('issue-boards-page')) { - this.url = this.$container.attr('data-url'); - } - - return $.post(this.url, (function(_this) { - return function() { - var status; - btn.removeClass('disabled'); - - if ($('html').hasClass('issue-boards-page')) { - Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'subscribed', !gl.issueBoards.BoardsStore.detail.issue.subscribed); - } else { - status = current_status === 'subscribed' ? 'unsubscribed' : 'subscribed'; - _this.subscription_status.attr('data-status', status); - action = status === 'subscribed' ? 'Unsubscribe' : 'Subscribe'; - btn.find('span').text(action); - _this.subscription_status.find('>div').toggleClass('hidden'); - if (btn.attr('data-original-title')) { - return btn.tooltip('hide').attr('data-original-title', action).tooltip('fixTitle'); - } - } - }; - })(this)); - }; - - return Subscription; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/subscription.js.es6 b/app/assets/javascripts/subscription.js.es6 new file mode 100644 index 00000000000..62d1604fe9e --- /dev/null +++ b/app/assets/javascripts/subscription.js.es6 @@ -0,0 +1,50 @@ +/* global Vue */ + +(() => { + class Subscription { + constructor(containerElm) { + this.containerElm = containerElm; + + const subscribeButton = containerElm.querySelector('.js-subscribe-button'); + if (subscribeButton) { + // remove class so we don't bind twice + subscribeButton.classList.remove('js-subscribe-button'); + subscribeButton.addEventListener('click', this.toggleSubscription.bind(this)); + } + } + + toggleSubscription(event) { + const button = event.currentTarget; + const buttonSpan = button.querySelector('span'); + if (!buttonSpan || button.classList.contains('disabled')) { + return; + } + button.classList.add('disabled'); + + const isSubscribed = buttonSpan.innerHTML.trim().toLowerCase() !== 'subscribe'; + const toggleActionUrl = this.containerElm.dataset.url; + + $.post(toggleActionUrl, () => { + button.classList.remove('disabled'); + + // hack to allow this to work with the issue boards Vue object + if (document.querySelector('html').classList.contains('issue-boards-page')) { + Vue.set( + gl.issueBoards.BoardsStore.detail.issue, + 'subscribed', + !gl.issueBoards.BoardsStore.detail.issue.subscribed, + ); + } else { + buttonSpan.innerHTML = isSubscribed ? 'Subscribe' : 'Unsubscribe'; + } + }); + } + + static bindAll(selector) { + [].forEach.call(document.querySelectorAll(selector), elm => new Subscription(elm)); + } + } + + window.gl = window.gl || {}; + window.gl.Subscription = Subscription; +})(); diff --git a/app/assets/javascripts/vue_common_component/commit.js.es6 b/app/assets/javascripts/vue_common_component/commit.js.es6 index 1bc68c1ba2f..62a22e39a3b 100644 --- a/app/assets/javascripts/vue_common_component/commit.js.es6 +++ b/app/assets/javascripts/vue_common_component/commit.js.es6 @@ -23,7 +23,7 @@ * name * ref_url */ - ref: { + commitRef: { type: Object, required: false, default: () => ({}), @@ -32,16 +32,16 @@ /** * Used to link to the commit sha. */ - commit_url: { + commitUrl: { type: String, required: false, default: '', }, /** - * Used to show the commit short_sha that links to the commit url. + * Used to show the commit short sha that links to the commit url. */ - short_sha: { + shortSha: { type: String, required: false, default: '', @@ -68,6 +68,11 @@ required: false, default: () => ({}), }, + + commitIconSvg: { + type: String, + required: false, + }, }, computed: { @@ -79,8 +84,8 @@ * * @returns {Boolean} */ - hasRef() { - return this.ref && this.ref.name && this.ref.ref_url; + hasCommitRef() { + return this.commitRef && this.commitRef.name && this.commitRef.ref_url; }, /** @@ -110,43 +115,25 @@ }, }, - /** - * In order to reuse the svg instead of copy and paste in this template - * we need to render it outside this component using =custom_icon partial. - * Make sure it has this structure: - * .commit-icon-svg.hidden - * svg - * - * TODO: Find a better way to include SVG - */ - mounted() { - const commitIconContainer = this.$el.querySelector('.commit-icon-container'); - const commitIcon = document.querySelector('.commit-icon-svg.hidden svg'); - - if (commitIconContainer && commitIcon) { - commitIconContainer.appendChild(commitIcon.cloneNode(true)); - } - }, - template: ` <div class="branch-commit"> - <div v-if="hasRef" class="icon-container"> + <div v-if="hasCommitRef" class="icon-container"> <i v-if="tag" class="fa fa-tag"></i> <i v-if="!tag" class="fa fa-code-fork"></i> </div> - <a v-if="hasRef" + <a v-if="hasCommitRef" class="monospace branch-name" - :href="ref.ref_url"> - {{ref.name}} + :href="commitRef.ref_url"> + {{commitRef.name}} </a> - <div class="icon-container commit-icon commit-icon-container"></div> + <div v-html="commitIconSvg" class="commit-icon js-commit-icon"></div> <a class="commit-id monospace" - :href="commit_url"> - {{short_sha}} + :href="commitUrl"> + {{shortSha}} </a> <p class="commit-title"> @@ -162,7 +149,7 @@ </a> <a class="commit-row-message" - :href="commit_url"> + :href="commitUrl"> {{title}} </a> </span> diff --git a/app/assets/javascripts/wikis.js b/app/assets/javascripts/wikis.js deleted file mode 100644 index 5dd853389c2..00000000000 --- a/app/assets/javascripts/wikis.js +++ /dev/null @@ -1,38 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, space-before-blocks, prefer-rest-params, wrap-iife, consistent-return, one-var, one-var-declaration-per-line, no-undef, prefer-template, padded-blocks, max-len */ - -/*= require latinise */ - -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.Wikis = (function() { - function Wikis() { - this.slugify = bind(this.slugify, this); - $('.new-wiki-page').on('submit', (function(_this) { - return function(e) { - var field, path, slug; - $('[data-error~=slug]').addClass('hidden'); - field = $('#new_wiki_path'); - slug = _this.slugify(field.val()); - if (slug.length > 0) { - path = field.attr('data-wikis-path'); - location.href = path + '/' + slug; - return e.preventDefault(); - } - }; - })(this)); - } - - Wikis.prototype.dasherize = function(value) { - return value.replace(/[_\s]+/g, '-'); - }; - - Wikis.prototype.slugify = function(value) { - return this.dasherize(value.trim().toLowerCase().latinise()); - }; - - return Wikis; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/wikis.js.es6 b/app/assets/javascripts/wikis.js.es6 new file mode 100644 index 00000000000..ecff5fd5bf4 --- /dev/null +++ b/app/assets/javascripts/wikis.js.es6 @@ -0,0 +1,73 @@ +/* eslint-disable no-param-reassign */ +/* global Breakpoints */ + +/*= require latinise */ +/*= require breakpoints */ +/*= require 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(); + this.sidebarEl = document.querySelector('.js-wiki-sidebar'); + this.sidebarExpanded = false; + $(this.sidebarEl).niceScroll(); + + const sidebarToggles = document.querySelectorAll('.js-sidebar-wiki-toggle'); + for (let i = 0; i < sidebarToggles.length; i += 1) { + sidebarToggles[i].addEventListener('click', e => this.handleToggleSidebar(e)); + } + + this.newWikiForm = document.querySelector('form.new-wiki-page'); + if (this.newWikiForm) { + this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e)); + } + + window.addEventListener('resize', () => this.renderSidebar()); + this.renderSidebar(); + } + + handleNewWikiSubmit(e) { + if (!this.newWikiForm) return; + + const slugInput = this.newWikiForm.querySelector('#new_wiki_path'); + const slug = slugify(slugInput.value); + + if (slug.length > 0) { + const wikisPath = slugInput.getAttribute('data-wikis-path'); + window.location.href = `${wikisPath}/${slug}`; + e.preventDefault(); + } + } + + handleToggleSidebar(e) { + e.preventDefault(); + this.sidebarExpanded = !this.sidebarExpanded; + this.renderSidebar(); + } + + sidebarCanCollapse() { + const bootstrapBreakpoint = this.bp.getBreakpointSize(); + return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm'; + } + + renderSidebar() { + if (!this.sidebarEl) return; + const { classList } = this.sidebarEl; + if (this.sidebarExpanded || !this.sidebarCanCollapse()) { + if (!classList.contains('right-sidebar-expanded')) { + classList.remove('right-sidebar-collapsed'); + classList.add('right-sidebar-expanded'); + } + } else if (classList.contains('right-sidebar-expanded')) { + classList.add('right-sidebar-collapsed'); + classList.remove('right-sidebar-expanded'); + } + } + } + + global.Wikis = Wikis; +})(window.gl || (window.gl = {})); diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 7c7f991dd87..c82a9a2b9e3 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -1,4 +1,3 @@ -@import "framework/fonts"; @import "framework/variables"; @import "framework/mixins"; @import 'framework/tw_bootstrap_variables'; @@ -7,6 +6,7 @@ @import "framework/animations.scss"; @import "framework/avatar.scss"; +@import "framework/asciidoctor.scss"; @import "framework/blocks.scss"; @import "framework/buttons.scss"; @import "framework/calendar.scss"; @@ -41,3 +41,6 @@ @import "framework/blank"; @import "framework/wells.scss"; @import "framework/page-header.scss"; +@import "framework/awards.scss"; +@import "framework/images.scss"; +@import "framework/broadcast-messages"; diff --git a/app/assets/stylesheets/framework/asciidoctor.scss b/app/assets/stylesheets/framework/asciidoctor.scss new file mode 100644 index 00000000000..62493c32833 --- /dev/null +++ b/app/assets/stylesheets/framework/asciidoctor.scss @@ -0,0 +1,27 @@ +.admonitionblock td.icon { + width: 1%; + + [class^="fa icon-"] { + @extend .fa-2x; + } + + .icon-note { + @extend .fa-thumb-tack; + } + + .icon-tip { + @extend .fa-lightbulb-o; + } + + .icon-warning { + @extend .fa-exclamation-triangle; + } + + .icon-caution { + @extend .fa-fire; + } + + .icon-important { + @extend .fa-exclamation-circle; + } +} diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index ad0d387067f..000e591e09c 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -8,7 +8,7 @@ float: left; margin-right: 15px; border-radius: $avatar_radius; - border: 1px solid rgba(0, 0, 0, .1); + border: 1px solid $avatar-border; &.s16 { @include avatar-size(16px, 6px); } &.s20 { @include avatar-size(20px, 7px); } &.s24 { @include avatar-size(24px, 8px); } @@ -80,6 +80,7 @@ border-radius: 0; border: none; height: auto; + width: 100%; margin: 0; align-self: center; } diff --git a/app/assets/stylesheets/pages/awards.scss b/app/assets/stylesheets/framework/awards.scss index 486ad16ea26..dece5c3202b 100644 --- a/app/assets/stylesheets/pages/awards.scss +++ b/app/assets/stylesheets/framework/awards.scss @@ -1,7 +1,7 @@ .awards { .emoji-icon { - width: 19px; - height: 19px; + width: 20px; + height: 20px; } } @@ -15,7 +15,7 @@ background-color: $award-emoji-menu-bg; border: 1px solid $award-emoji-menu-border; border-radius: $border-radius-base; - box-shadow: 0 6px 12px rgba(0,0,0,.175); + box-shadow: 0 6px 12px $award-emoji-menu-shadow; pointer-events: none; opacity: 0; transform: scale(.2); @@ -127,7 +127,7 @@ .award-control-icon { float: left; margin-right: 5px; - font-size: 19px; + font-size: 18px; } .award-control-icon-loading { @@ -136,5 +136,6 @@ .award-control-icon { color: $award-emoji-new-btn-icon-color; + margin-top: 1px; } } diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss index 540718197e0..a2fa2e7769b 100644 --- a/app/assets/stylesheets/framework/blank.scss +++ b/app/assets/stylesheets/framework/blank.scss @@ -32,14 +32,14 @@ .blank-state-title { margin-top: 0; margin-bottom: 5px; - font-size: 19px; + font-size: 18px; font-weight: normal; } .blank-state-text { margin-top: 0; margin-bottom: $gl-padding; - font-size: 15px; + font-size: 14px; > strong { font-weight: 600; diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 77ae9e9a6e7..95c02499271 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -1,8 +1,3 @@ -.light-well { - background-color: $background-color; - padding: 15px; -} - .centered-light-block { text-align: center; color: $gl-gray; @@ -41,7 +36,7 @@ } &.white { - background-color: white; + background-color: $white-light; } &.top-block { @@ -158,7 +153,7 @@ p { padding: 0 $gl-padding; - color: #5c5d5e; + color: $gl-text-color-dark; } } @@ -274,6 +269,10 @@ } } + .emoji-icon { + display: inline-block; + } + @media(max-width: $screen-xs-max) { margin-top: 50px; text-align: center; diff --git a/app/assets/stylesheets/framework/broadcast-messages.scss b/app/assets/stylesheets/framework/broadcast-messages.scss new file mode 100644 index 00000000000..9b54fb94cdc --- /dev/null +++ b/app/assets/stylesheets/framework/broadcast-messages.scss @@ -0,0 +1,21 @@ +.broadcast-message { + @extend .alert-warning; + padding: 10px; + text-align: center; + + div, + p { + display: inline; + margin: 0; + + a { + color: inherit; + text-decoration: underline; + } + } +} + +.broadcast-message-preview { + @extend .broadcast-message; + margin-bottom: 20px; +} diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index 4a9aa0f8717..1c7b2f4df7c 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -1,7 +1,7 @@ @mixin btn-default { border-radius: 3px; font-size: $gl-font-size; - font-weight: 500; + font-weight: 400; padding: $gl-vert-padding $gl-btn-padding; &:focus, @@ -15,7 +15,7 @@ @include btn-default; } -@mixin btn-outline($background, $text, $border, $hover-background, $hover-text, $hover-border) { +@mixin btn-outline($background, $text, $border, $hover-background, $hover-text, $hover-border, $active-background, $active-border) { background-color: $background; color: $text; border-color: $border; @@ -23,8 +23,14 @@ &:hover, &:focus { background-color: $hover-background; - color: $hover-text; border-color: $hover-border; + color: $hover-text; + } + + &:active { + background-color: $active-background; + border-color: $active-border; + color: $hover-text; } } @@ -62,31 +68,31 @@ } @mixin btn-green { - @include btn-color($green-light, $border-green-light, $green-normal, $border-green-normal, $green-dark, $border-green-dark, #fff); + @include btn-color($green-light, $border-green-light, $green-normal, $border-green-normal, $green-dark, $border-green-dark, $white-light); } @mixin btn-blue { - @include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, #fff); + @include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, $white-light); } @mixin btn-blue-medium { - @include btn-color($blue-medium-light, $border-blue-light, $blue-medium, $border-blue-normal, $blue-medium-dark, $border-blue-dark, #fff); + @include btn-color($blue-medium-light, $border-blue-light, $blue-medium, $border-blue-normal, $blue-medium-dark, $border-blue-dark, $white-light); } @mixin btn-orange { - @include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, #fff); + @include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, $white-light); } @mixin btn-red { - @include btn-color($red-light, $border-red-light, $red-normal, $border-red-normal, $red-dark, $border-red-dark, #fff); + @include btn-color($red-light, $border-red-light, $red-normal, $border-red-normal, $red-dark, $border-red-dark, $white-light); } @mixin btn-gray { - @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, $gl-gray-dark); + @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-normal, $gray-dark, $border-gray-dark, $gl-gray-dark); } @mixin btn-white { - @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $btn-white-active); + @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $gl-text-color); } @mixin btn-with-margin { @@ -139,11 +145,11 @@ &.btn-new, &.btn-create, &.btn-save { - @include btn-outline($white-light, $green-normal, $green-normal, $green-light, $white-light, $green-light); + @include btn-outline($white-light, $border-green-light, $border-green-light, $green-light, $white-light, $border-green-light, $green-normal, $border-green-normal); } &.btn-remove { - @include btn-outline($white-light, $red-normal, $red-normal, $red-light, $white-light, $red-light); + @include btn-outline($white-light, $border-red-light, $border-red-light, $red-light, $white-light, $border-red-light, $red-normal, $border-red-normal); } } @@ -165,11 +171,11 @@ } &.btn-close { - @include btn-outline($white-light, $orange-normal, $orange-normal, $orange-light, $white-light, $orange-light); + @include btn-outline($white-light, $border-orange-light, $border-orange-light, $orange-light, $white-light, $border-orange-light, $orange-normal, $border-orange-normal); } &.btn-spam { - @include btn-outline($white-light, $red-normal, $red-normal, $red-light, $white-light, $red-light); + @include btn-outline($white-light, $border-red-light, $border-red-light, $red-light, $white-light, $border-red-light, $red-normal, $border-red-normal); } &.btn-danger, @@ -199,7 +205,7 @@ } .fa-caret-down, - .fa-caret-up { + .fa-chevron-down { margin-left: 5px; } @@ -283,8 +289,8 @@ .active { box-shadow: $gl-btn-active-background; - border: 1px solid #c6cacf !important; - background-color: #e4e7ed !important; + border: 1px solid $border-white-dark !important; + background-color: $btn-active-gray-light !important; } } @@ -339,19 +345,19 @@ .btn-static { background-color: $background-color !important; - border: 1px solid lightgrey; + border: 1px solid $border-gray-light; cursor: default; &:active { - -moz-box-shadow: inset 0 0 0 white; - -webkit-box-shadow: inset 0 0 0 white; - box-shadow: inset 0 0 0 white; + -moz-box-shadow: inset 0 0 0 $white-light; + -webkit-box-shadow: inset 0 0 0 $white-light; + box-shadow: inset 0 0 0 $white-light; } } .btn-inverted { &-secondary { - @include btn-outline($white-light, $blue-normal, $blue-normal, $blue-light, $white-light, $blue-light); + @include btn-outline($white-light, $border-blue-light, $border-blue-light, $blue-light, $white-light, $border-blue-light, $blue-normal, $border-blue-normal); } } diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss index 8642b7530e2..ef921a8c6a9 100644 --- a/app/assets/stylesheets/framework/calendar.scss +++ b/app/assets/stylesheets/framework/calendar.scss @@ -2,7 +2,7 @@ padding-left: 0; padding-right: 0; - @media (min-width: $screen-sm-min) and (max-width: $screen-lg-min) { + @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) { overflow-x: scroll; } } @@ -28,13 +28,13 @@ .user-contrib-cell { &:hover { cursor: pointer; - stroke: #000; + stroke: $black; } } .user-contrib-text { font-size: 12px; - fill: #959494; + fill: $calendar-user-contrib-text; } .calendar-hint { diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss index f3b6ad88ad6..2a100980aca 100644 --- a/app/assets/stylesheets/framework/callout.scss +++ b/app/assets/stylesheets/framework/callout.scss @@ -25,25 +25,25 @@ /* Variations */ .bs-callout-danger { - background-color: #fdf7f7; - border-color: #eed3d7; - color: #b94a48; + background-color: $callout-danger-bg; + border-color: $callout-danger-border; + color: $callout-danger-color; } .bs-callout-warning { - background-color: #faf8f0; - border-color: #faebcc; - color: #8a6d3b; + background-color: $callout-warning-bg; + border-color: $callout-warning-border; + color: $callout-warning-color; } .bs-callout-info { - background-color: #f4f8fa; - border-color: #bce8f1; - color: #34789a; + background-color: $callout-info-bg; + border-color: $callout-info-border; + color: $callout-info-color; } .bs-callout-success { - background-color: #dff0d8; - border-color: #5ca64d; - color: #3c763d; + background-color: $callout-success-bg; + border-color: $callout-success-border; + color: $callout-success-color; } diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index b24fce6f0c2..251e43d2edd 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -1,9 +1,9 @@ /** COLORS **/ -.cgray { color: $gl-gray; } -.clgray { color: #bbb; } -.cred { color: $gl-text-red; } -.cgreen { color: $gl-text-green; } -.cdark { color: #444; } +.cgray { color: $common-gray; } +.clgray { color: $common-gray-light; } +.cred { color: $common-red; } +.cgreen { color: $common-green; } +.cdark { color: $common-gray-dark; } /** COMMON CLASSES **/ .prepend-top-0 { margin-top: 0; } @@ -28,12 +28,12 @@ .center { text-align: center; } .underlined-link { text-decoration: underline; } -.hint { font-style: italic; color: #999; } -.light { color: $gl-gray; } +.hint { font-style: italic; color: $hint-color; } +.light { color: $common-gray; } .slead { - color: $gl-gray; - font-size: 15px; + color: $common-gray; + font-size: 14px; margin-bottom: 12px; font-weight: normal; line-height: 24px; @@ -52,10 +52,10 @@ pre { } &.well-pre { - border: 1px solid #eee; + border: 1px solid $well-pre-bg; background: $gray-light; border-radius: 0; - color: #555; + color: $well-pre-color; } } @@ -87,14 +87,14 @@ table a code { .loading { margin: 20px auto; height: 40px; - color: #555; + color: $loading-color; font-size: 32px; text-align: center; } span.update-author { display: block; - color: #999; + color: $update-author-color; font-weight: normal; font-style: italic; @@ -105,7 +105,7 @@ span.update-author { } .user-mention { - color: #2fa0bb; + color: $user-mention-color; font-weight: bold; } @@ -114,7 +114,7 @@ span.update-author { } p.time { - color: #999; + color: $time-color; font-size: 90%; margin: 30px 3px 3px 2px; } @@ -150,7 +150,7 @@ li.note { .project_member_show { td:first-child { - color: #aaa; + color: $project-member-show-color; } } @@ -176,7 +176,7 @@ li.note { margin-top: 40px; pre { - background: white; + background: $white-light; border: none; font-size: 12px; } @@ -184,12 +184,12 @@ li.note { .error-message { padding: 10px; - background: #c67; + background: $error-bg; margin: 0; - color: #fff; + color: $white-light; a { - color: #fff; + color: $white-light; text-decoration: underline; } } @@ -197,22 +197,22 @@ li.note { .browser-alert { padding: 10px; text-align: center; - background: #c67; - color: #fff; + background: $error-bg; + color: $white-light; font-weight: bold; a { - color: #fff; + color: $white-light; text-decoration: underline; } } .warning_message { - border-left: 4px solid #ed9; - color: #b90; + border-left: 4px solid $warning-message-border; + color: $warning-message-color; padding: 10px; margin-bottom: 10px; - background: #ffffe6; + background: $warning-message-bg; padding-left: 20px; &.centered { @@ -222,7 +222,7 @@ li.note { .gitlab-promo { a { - color: #aaa; + color: $gl-promo-color; margin-right: 30px; } } @@ -245,7 +245,7 @@ li.note { position: relative; top: 2px; left: 5px; - color: #666; + color: $control-group-descr-color; } } } @@ -255,6 +255,7 @@ img.emoji { height: 20px; vertical-align: top; width: 20px; + margin-top: 1px; } .chart { @@ -270,7 +271,7 @@ img.emoji { table { td.permission-x { - background: #d9edf7 !important; + background: $table-permission-x-bg !important; text-align: center; } } @@ -323,13 +324,13 @@ table { .username { font-size: 18px; - color: #666; + color: $username-color; margin-top: 8px; } .description { font-size: $gl-font-size; - color: #666; + color: $description-color; margin-top: 8px; } } @@ -339,7 +340,7 @@ table { .profiler-button, .profiler-controls { - border-color: #eee !important; + border-color: $profiler-border !important; } } @@ -379,7 +380,9 @@ table { border-top: 1px solid $border-color; } -.hide-bottom-border { border-bottom: none !important; } +.hide-bottom-border { + border-bottom: none !important; +} .gl-accessibility { &:focus { @@ -396,3 +399,13 @@ table { z-index: 1; } } + +.str-truncated { + &-60 { + @include str-truncated(60%); + } + + &-100 { + @include str-truncated(100%); + } +} diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 583c17e4a83..d5914b900e2 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -8,6 +8,12 @@ } } +@mixin chevron-active { + .fa-chevron-down { + color: $dropdown-toggle-hover-icon-color; + } +} + .open { .dropdown-menu, .dropdown-menu-nav { @@ -19,53 +25,32 @@ } } + .dropdown-toggle, .dropdown-menu-toggle { + @include chevron-active; border-color: $dropdown-toggle-hover-border-color; - - .fa { - color: $dropdown-toggle-hover-icon-color; - } } } -.dropdown-menu-toggle { - position: relative; - width: 160px; - padding: 6px 20px 6px 10px; +.dropdown-toggle { + padding: 6px 8px 6px 10px; background-color: $dropdown-toggle-bg; color: $dropdown-toggle-color; - font-size: 15px; + font-size: 14px; text-align: left; border: 1px solid $border-color; border-radius: $border-radius-base; - text-overflow: ellipsis; white-space: nowrap; - overflow: hidden; - .fa { - position: absolute; - top: 10px; - right: 8px; - color: $dropdown-toggle-icon-color; - - &.fa-spinner { - font-size: 16px; - margin-top: -8px; - } + &[disabled] { + background-color: $input-bg-disabled; + cursor: not-allowed; } &.no-outline { outline: 0; } - &:hover, { - border-color: $dropdown-toggle-hover-border-color; - - .fa { - color: $dropdown-toggle-hover-icon-color; - } - } - &.large { width: 200px; } @@ -86,6 +71,51 @@ max-width: 100%; padding-right: 25px; } + + .fa { + color: $dropdown-toggle-icon-color; + } + + .fa-chevron-down { + font-size: $dropdown-chevron-size; + position: relative; + top: -3px; + margin-left: 5px; + } + + &:hover { + @include chevron-active; + border-color: $dropdown-toggle-hover-border-color; + } + + &:focus:active { + @include chevron-active; + border-color: $dropdown-toggle-active-border-color; + } +} + +.dropdown-menu-toggle { + @extend .dropdown-toggle; + padding-right: 20px; + position: relative; + width: 160px; + text-overflow: ellipsis; + overflow: hidden; + + .fa { + position: absolute; + + &.fa-spinner { + font-size: 16px; + margin-top: -8px; + } + } + + .fa-chevron-down { + position: absolute; + top: 11px; + right: 8px; + } } .dropdown-menu, @@ -98,7 +128,7 @@ width: 240px; margin-top: 2px; margin-bottom: 0; - font-size: 15px; + font-size: 14px; font-weight: normal; padding: 8px 0; background-color: $dropdown-bg; @@ -351,7 +381,7 @@ position: absolute; top: 10px; right: 20px; - color: #c7c7c7; + color: $dropdown-input-fa-color; font-size: 12px; pointer-events: none; } @@ -504,7 +534,7 @@ .ui-datepicker-calendar { .ui-state-hover, .ui-state-active { - color: #fff; + color: $white-light; border: 0; } } @@ -564,7 +594,7 @@ .ui-datepicker-title { color: $gl-gray; - font-size: 15px; + font-size: 14px; line-height: 1; font-weight: normal; } diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index f49d7b92a00..ab0b81f77f7 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -59,10 +59,10 @@ } .file-content { - background: #fff; + background: $white-light; &.image_file { - background: #eee; + background: $file-image-bg; text-align: center; img { @@ -84,8 +84,8 @@ } &.blob-no-preview { - background: #eee; - text-shadow: 0 1px 2px #fff; + background: $blob-bg; + text-shadow: 0 1px 2px $white-light; padding: 100px 0; } @@ -99,7 +99,7 @@ } tr { - border-bottom: 1px solid #eee; + border-bottom: 1px solid $blame-border; } td { @@ -120,7 +120,7 @@ td.line-numbers { float: none; - border-left: 1px solid #ddd; + border-left: 1px solid $blame-line-numbers-border; i { float: none; @@ -134,7 +134,7 @@ } &.logs { - background: #eee; + background: $logs-bg; max-height: 700px; overflow-y: auto; @@ -143,14 +143,14 @@ padding: 10px 0; border-left: 1px solid $border-color; margin-bottom: 0; - background: white; + background: $white-light; li { - color: #888; + color: $logs-li-color; p { margin: 0; - color: #333; + color: $logs-p-color; line-height: 24px; padding-left: 10px; } diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index a9006de6d3e..eadb9409fee 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -38,7 +38,7 @@ } } -@media (max-width: $screen-md-min) { +@media (max-width: $screen-sm-max) { ul.notes { .flash-container.timeline-content { margin-left: 0; diff --git a/app/assets/stylesheets/framework/fonts.scss b/app/assets/stylesheets/framework/fonts.scss deleted file mode 100644 index 5f9685bc71a..00000000000 --- a/app/assets/stylesheets/framework/fonts.scss +++ /dev/null @@ -1,45 +0,0 @@ -// Disabling "SpaceAfterPropertyColon" linter because the linter doesn't like -// the way the `src` property is formatted in this file. -// scss-lint:disable SpaceAfterPropertyColon - -/* latin-ext */ -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 300; - src: - local('Source Sans Pro Light'), - local('SourceSansPro-Light'), - font-url('SourceSansPro-Light.ttf.woff2') format('woff2'), - font-url('SourceSansPro-Light.ttf.woff') format('woff'); -} -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 400; - src: - local('Source Sans Pro'), - local('SourceSansPro-Regular'), - font-url('SourceSansPro-Regular.ttf.woff2') format('woff2'), - font-url('SourceSansPro-Regular.ttf.woff') format('woff'); -} -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 600; - src: - local('Source Sans Pro Semibold'), - local('SourceSansPro-Semibold'), - font-url('SourceSansPro-Semibold.ttf.woff2') format('woff2'), - font-url('SourceSansPro-Semibold.ttf.woff') format('woff'); -} -@font-face { - font-family: 'Source Sans Pro'; - font-style: normal; - font-weight: 700; - src: - local('Source Sans Pro Bold'), - local('SourceSansPro-Bold'), - font-url('SourceSansPro-Bold.ttf.woff2') format('woff2'), - font-url('SourceSansPro-Bold.ttf.woff') format('woff'); -} diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index e83a1f7ad68..25a2b38baaa 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -7,9 +7,9 @@ input { } input[type='text'].danger { - background: #f2dede!important; - border-color: #d66; - text-shadow: 0 1px 1px #fff; + background: $input-danger-bg !important; + border-color: $input-danger-border; + text-shadow: 0 1px 1px $white-light; } .datetime-controls { @@ -98,7 +98,7 @@ label { } } - @media(max-width: $screen-sm-min) { + @media(max-width: $screen-xs-max) { padding: 0 $gl-padding; .control-label, @@ -159,7 +159,7 @@ label { } .input-group-addon { - background-color: #f7f8fa; + background-color: $input-group-addon-bg; } .input-group-addon:not(:first-child):not(:last-child) { @@ -181,7 +181,7 @@ label { border: 1px solid $green-normal; &:focus { - box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 $green-normal; + box-shadow: 0 0 0 1px $green-normal inset, 0 1px 1px $gl-field-focus-shadow inset, 0 0 4px 0 $green-normal; border: 0 none; } } @@ -190,7 +190,7 @@ label { border: 1px solid $red-normal; &:focus { - box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 4px 0 rgba(210, 40, 82, 0.6); + box-shadow: 0 0 0 1px $red-normal inset, 0 1px 1px $gl-field-focus-shadow inset, 0 0 4px 0 $gl-field-focus-shadow-error; border: 0 none; } } diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss index 91ab1503439..5cd242af91d 100644 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -21,7 +21,6 @@ background: $color-darker; } - .sidebar-header, .sidebar-action-buttons { color: $color-light; background-color: lighten($color-darker, 5%); @@ -86,37 +85,57 @@ } $theme-charcoal: #3d454d; +$theme-charcoal-light: #485157; $theme-charcoal-dark: #383f45; $theme-charcoal-text: #b9bbbe; +$theme-blue-light: #becde9; $theme-blue: #2980b9; +$theme-blue-dark: #1970a9; +$theme-blue-darker: #096099; + +$theme-graphite-lighter: #ccc; +$theme-graphite-light: #777; $theme-graphite: #666; +$theme-graphite-dark: #555; + +$theme-gray-light: #979797; $theme-gray: #373737; +$theme-gray-dark: #272727; +$theme-gray-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(#becde9, $theme-blue, #1970a9, #096099); + @include gitlab-theme($theme-blue-light, $theme-blue, $theme-blue-dark, $theme-blue-darker); } &.ui_charcoal { - @include gitlab-theme($theme-charcoal-text, #485157, $theme-charcoal, $theme-charcoal-dark); + @include gitlab-theme($theme-charcoal-text, $theme-charcoal-light, $theme-charcoal, $theme-charcoal-dark); } &.ui_graphite { - @include gitlab-theme(#ccc, #777, $theme-graphite, #555); + @include gitlab-theme($theme-graphite-lighter, $theme-graphite-light, $theme-graphite, $theme-graphite-dark); } &.ui_gray { - @include gitlab-theme(#979797, $theme-gray, #272727, #222); + @include gitlab-theme($theme-gray-light, $theme-gray, $theme-gray-dark, $theme-gray-darker); } &.ui_green { - @include gitlab-theme(#adc, $theme-green, #018865, #017855); + @include gitlab-theme($theme-green-light, $theme-green, $theme-green-dark, $theme-green-darker); } &.ui_violet { - @include gitlab-theme(#98c, $theme-violet, #436, #325); + @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 16ecf466931..cc2286038c0 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -8,7 +8,7 @@ header { &.navbar-empty { height: $header-height; - background: #fff; + background: $white-light; border-bottom: 1px solid $btn-gray-hover; .center-logo { @@ -71,12 +71,12 @@ header { } .fa-caret-down { - font-size: 15px; + font-size: 14px; } } .navbar-toggle { - color: #666; + color: $nav-toggle-gray; margin: 6px 0; border-radius: 0; position: absolute; @@ -156,7 +156,7 @@ header { position: relative; padding-right: 20px; margin: 0; - font-size: 19px; + font-size: 18px; max-width: 385px; display: inline-block; line-height: $header-height; @@ -228,7 +228,7 @@ header { } .page-sidebar-pinned.right-sidebar-expanded { - @media (max-width: $screen-lg-min) { + @media (max-width: $screen-md-max) { .header-content .title { width: 300px; } diff --git a/app/assets/stylesheets/pages/appearances.scss b/app/assets/stylesheets/framework/images.scss index 878f44116ba..878f44116ba 100644 --- a/app/assets/stylesheets/pages/appearances.scss +++ b/app/assets/stylesheets/framework/images.scss diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index ff6f316d576..44834a84234 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -20,7 +20,7 @@ display: block; float: left; margin-right: 10px; - color: #fff; + color: $white-light; font-size: $gl-font-size; line-height: 25px; @@ -37,10 +37,10 @@ } &.status-box-expired { - background: #cea61b; + background-color: $issue-status-expired; } &.status-box-upcoming { - background: #8f8f8f; + background: $issue-box-upcoming-bg; } } diff --git a/app/assets/stylesheets/framework/jquery.scss b/app/assets/stylesheets/framework/jquery.scss index 30a5b837d69..18f2f316f02 100644 --- a/app/assets/stylesheets/framework/jquery.scss +++ b/app/assets/stylesheets/framework/jquery.scss @@ -4,13 +4,13 @@ &.ui-datepicker, &.ui-datepicker-inline { - border: 1px solid #ddd; + border: 1px solid $jq-ui-border; padding: 10px; width: 270px; .ui-datepicker-header { - background: #fff; - border-color: #ddd; + background: $white-light; + border-color: $jq-ui-border; .ui-datepicker-prev, .ui-datepicker-next { @@ -39,7 +39,7 @@ } &.ui-autocomplete { - border-color: #ddd; + border-color: $jq-ui-border; padding: 0; margin-top: 2px; z-index: 1001; @@ -50,9 +50,9 @@ } .ui-state-default { - border: 1px solid #fff; - background: #fff; - color: #777; + border: 1px solid $white-light; + background: $white-light; + color: $jq-ui-default-color; } .ui-state-highlight { @@ -66,7 +66,7 @@ .ui-state-focus { border: 1px solid $gl-primary; background: $gl-primary; - color: #fff; + color: $white-light; } } } diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index 7baa4296abf..dfaf2f7f1d3 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -6,7 +6,7 @@ html { body { &.navless { - background-color: white !important; + background-color: $white-light !important; } } diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index bc0610cc417..ed4b60faf92 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -11,8 +11,8 @@ > li { padding: 10px 15px; min-height: 20px; - border-bottom: 1px solid #eee; - border-bottom: 1px solid rgba(0, 0, 0, 0.05); + border-bottom: 1px solid $list-border-light; + border-bottom: 1px solid $list-border; &::after { content: " "; @@ -21,7 +21,7 @@ } &.disabled { - color: #888; + color: $list-text-disabled-color; } &.unstyled { @@ -31,9 +31,9 @@ } &.warning-row { - background-color: #fcf8e3; - border-color: #faebcc; - color: #8a6d3b; + background-color: $list-warning-row-bg; + border-color: $list-warning-row-border; + color: $list-warning-row-color; } &.smoke { background-color: $background-color; } @@ -106,13 +106,13 @@ ul.task-list { } } +// Generic content list ul.content-list { @include basic-list; - margin: 0; padding: 0; - > li { + li { border-color: $table-border-color; font-size: $list-font-size; color: $list-text-color; @@ -193,6 +193,41 @@ ul.content-list { } } +// Content list using flexbox +.flex-list { + .flex-row { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + white-space: nowrap; + } + + .row-main-content { + flex: 1 1 auto; + overflow: hidden; + padding-right: 8px; + } + + .row-title { + font-weight: 600; + } + + .row-second-line { + display: block; + } + + .dropdown { + .btn-block { + margin-bottom: 0; + line-height: inherit; + } + } + + .label-default { + color: $btn-transparent-color; + } +} + .panel > .content-list > li { padding: $gl-padding-top $gl-padding; diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index 42087c91530..59a30d31ac7 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -56,16 +56,9 @@ .md-header { .nav-links { - .active { - a { - border-bottom-color: #000; - } - } - a { padding-top: 0; line-height: 19px; - border-bottom: 1px solid $border-color; &.btn.btn-xs { padding: 2px 5px; @@ -80,7 +73,7 @@ } .referenced-users { - color: #4c4e54; + color: $gl-header-color; padding-top: 10px; } @@ -92,8 +85,8 @@ .markdown-area { border-radius: 0; - background: #fff; - border: 1px solid #ddd; + background: $white-light; + border: 1px solid $md-area-border; min-height: 140px; max-height: 500px; padding: 5px; @@ -115,7 +108,7 @@ hr { // Darken 'whitesmoke' a bit to make it more visible in note bodies - border-color: darken(#f5f5f5, 8%); + border-color: darken($gray-normal, 8%); margin: 10px 0; } @@ -142,7 +135,7 @@ .toolbar-btn { float: left; padding: 0 5px; - color: #959494; + color: $note-toolbar-color; background: transparent; border: 0; outline: 0; diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index f84ca36d10f..4f2ac77f228 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -24,7 +24,7 @@ @include clearfix; padding: 10px 0; - border-bottom: 1px solid #eee; + border-bottom: 1px solid $list-border-light; display: block; margin: 0; @@ -67,8 +67,8 @@ } @mixin dark-diff-match-line { - color: rgba(255, 255, 255, 0.3); - background: rgba(255, 255, 255, 0.1); + color: $dark-diff-match-bg; + background: $dark-diff-match-color; } @mixin webkit-prefix($property, $value) { diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 9391661a595..abfdd7a759d 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -133,9 +133,9 @@ right: 0; top: 30%; padding: 5px 15px; - background: #eee; + background: $show-aside-bg; font-size: 20px; - color: #777; + color: $show-aside-color; z-index: 100; - box-shadow: 0 1px 2px #ddd; + box-shadow: 0 1px 2px $show-aside-shadow; } diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index ce864c2de5e..ea77348633d 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -49,26 +49,37 @@ padding: $gl-btn-padding; padding-bottom: 11px; margin-bottom: -1px; - font-size: 15px; + font-size: 14px; line-height: 28px; - color: #959494; + color: $note-toolbar-color; border-bottom: 2px solid transparent; &:hover, &:active, &:focus { text-decoration: none; + border-bottom: 2px solid $gray-darkest; + color: $black; + + .badge { + color: $black; + } } } &.active a { border-bottom: 2px solid $link-underline-blue; color: $black; + font-weight: 600; + + .badge { + color: $black; + } } .badge { font-weight: normal; - background-color: #eee; + background-color: $nav-badge-bg; color: $btn-transparent-color; vertical-align: baseline; } @@ -85,14 +96,20 @@ li { + &.active a { + border-bottom: none; + color: $link-underline-blue; + } + a { margin: 0; padding: 11px 10px 9px; - } - &.active a { - border-bottom: none; - color: $link-underline-blue; + &:hover, + &:active, + &:focus { + border-bottom: none; + } } } } @@ -110,7 +127,7 @@ line-height: 28px; /* Small devices (phones, tablets, 768px and lower) */ - @media (max-width: $screen-sm-min) { + @media (max-width: $screen-xs-max) { width: 100%; } } @@ -255,6 +272,16 @@ width: auto; } } + + &.multi-line { + .nav-text { + line-height: 20px; + } + + .nav-controls { + padding: 17px 0; + } + } } .layout-nav { @@ -310,37 +337,9 @@ height: 51px; li { - a { padding-top: 10px; } - - a, - i { - color: $layout-link-gray; - } - - &.active { - - a, - i { - color: $black; - } - - svg { - path, - polygon { - fill: $black; - } - } - } - - &:hover { - a, - i { - color: $black; - } - } } } } diff --git a/app/assets/stylesheets/framework/pagination.scss b/app/assets/stylesheets/framework/pagination.scss index cb2c351c368..b37c1d0d670 100644 --- a/app/assets/stylesheets/framework/pagination.scss +++ b/app/assets/stylesheets/framework/pagination.scss @@ -43,7 +43,7 @@ /** * Small screen pagination */ -@media (max-width: $screen-xs) { +@media (max-width: $screen-xs-min) { .gl-pagination { .pagination li a { padding: 6px 10px; @@ -62,7 +62,7 @@ /** * Medium screen pagination */ -@media (min-width: $screen-xs) and (max-width: $screen-md-max) { +@media (min-width: $screen-xs-min) and (max-width: $screen-md-max) { .gl-pagination { .page { display: none; diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index 920ce249b9a..fde1431b13e 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -6,7 +6,7 @@ .select2-container, .select2-container.select2-drop-above { .select2-choice { - background: #fff; + background: $white-light; border-color: $input-border; height: 35px; padding: $gl-vert-padding $gl-input-padding; @@ -47,7 +47,7 @@ } .select2-drop { - box-shadow: rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0; + box-shadow: $select2-drop-shadow1 0 0 1px 0, $select2-drop-shadow2 0 2px 18px 0; border-radius: $border-radius-default; border: none; min-width: 175px; @@ -59,7 +59,7 @@ } .select2-drop { - color: #7f8fa4; + color: $gl-grayish-blue; } .select2-highlighted { @@ -156,7 +156,7 @@ .select2-search input { padding: 2px 25px 2px 5px; - background: #fff image-url('select2.png'); + background: $white-light image-url('select2.png'); background-repeat: no-repeat; background-position: right 0 bottom 6px; border: 1px solid $input-border; @@ -169,7 +169,7 @@ } .select2-search input.select2-active { - background-color: #fff; + background-color: $white-light; background-image: image-url('select2-spinner.gif') !important; background-repeat: no-repeat; background-position: right 5px center !important; @@ -206,7 +206,7 @@ .select2-highlighted { .group-result { .group-path { - color: #fff; + color: $white-light; } } } @@ -221,7 +221,7 @@ } .group-path { - color: #999; + color: $group-path-color; } } @@ -241,7 +241,7 @@ .namespace-result { .namespace-kind { - color: #aaa; + color: $namespace-kind-color; font-weight: normal; } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 44c445c0543..0aa609b8dd5 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -36,7 +36,7 @@ transition: padding $sidebar-transition-duration; .container-fluid { - background: #fff; + background: $white-light; padding: 0 $gl-padding; &.container-blank { @@ -59,11 +59,6 @@ padding: 0 !important; } - .sidebar-header { - padding: 11px 22px 12px; - font-size: 20px; - } - li { &.separate-item { padding-top: 10px; @@ -220,7 +215,7 @@ header.header-sidebar-pinned { padding-right: 0; @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) { - &:not(.build-sidebar) { + &:not(.build-sidebar):not(.wiki-sidebar) { padding-right: $sidebar_collapsed_width; } } diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss index 9a90d3794fd..5d0ca63ea08 100644 --- a/app/assets/stylesheets/framework/tables.scss +++ b/app/assets/stylesheets/framework/tables.scss @@ -14,11 +14,11 @@ table { .warning, .danger, .info { - color: #fff; + color: $white-light; a:not(.btn) { text-decoration: underline; - color: #fff; + color: $white-light; } } @@ -34,6 +34,10 @@ table { background-color: $background-color; font-weight: normal; border-bottom: none; + + &.wide { + width: 55%; + } } td { @@ -42,3 +46,16 @@ table { } } } + +.responsive-table { + @media (max-width: $screen-sm-max) { + th { + width: 100%; + } + + td { + width: 100%; + float: left; + } + } +} diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index 59f4594bb83..55bc325b858 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -97,13 +97,13 @@ display: inline-block; &.label-gray { - background-color: #f8fafc; + background-color: $label-gray-bg; color: $gl-gray; text-shadow: none; } &.label-inverse { - background-color: #333; + background-color: $label-inverse-bg; } } @@ -158,7 +158,7 @@ font-weight: normal; a { - color: #777; + color: $panel-heading-link-color; } } } @@ -172,7 +172,7 @@ .alert { a:not(.btn) { @extend .alert-link; - color: #fff; + color: $white-light; text-decoration: underline; } } diff --git a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss index 44fe37d3a4a..c731a8f222f 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap_variables.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap_variables.scss @@ -48,7 +48,7 @@ $font-size-base: $gl-font-size; $padding-base-vertical: $gl-vert-padding; $padding-base-horizontal: $gl-padding; -$component-active-color: #fff; +$component-active-color: $white-light; $component-active-bg: $brand-info; //== Forms @@ -66,7 +66,7 @@ $legend-color: $text-color; //## $pagination-color: $gl-gray; -$pagination-bg: #fff; +$pagination-bg: $white-light; $pagination-border: $border-color; $pagination-hover-color: $gl-gray; @@ -74,7 +74,7 @@ $pagination-hover-bg: $row-hover; $pagination-hover-border: $border-color; $pagination-active-color: $blue-dark; -$pagination-active-bg: #fff; +$pagination-active-bg: $white-light; $pagination-active-border: $border-color; $pagination-disabled-color: #cdcdcd; @@ -86,19 +86,19 @@ $pagination-disabled-border: $border-color; // //## Define colors for form feedback states and, by default, alerts. -$state-success-text: #fff; +$state-success-text: $white-light; $state-success-bg: $brand-success; $state-success-border: $brand-success; -$state-info-text: #fff; +$state-info-text: $white-light; $state-info-bg: $brand-info; $state-info-border: $brand-info; -$state-warning-text: #fff; +$state-warning-text: $white-light; $state-warning-bg: $brand-warning; $state-warning-border: $brand-warning; -$state-danger-text: #fff; +$state-danger-text: $white-light; $state-danger-bg: $brand-danger; $state-danger-border: $brand-danger; @@ -135,14 +135,14 @@ $well-border: #eee; $code-color: #c7254e; $code-bg: #f9f2f4; -$kbd-color: #fff; +$kbd-color: $white-light; $kbd-bg: #333; //== Buttons // //## $btn-default-color: $gl-text-color; -$btn-default-bg: #fff; +$btn-default-bg: $white-light; $btn-default-border: #e7e9ed; //== Nav diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 070e42d63d2..d906d26bba9 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -33,15 +33,15 @@ padding: 3px 5px; font-size: 11px; line-height: 10px; - color: #555; + color: $kdb-color; vertical-align: middle; - background-color: #fcfcfc; + background-color: $kdb-bg; border-width: 1px; border-style: solid; - border-color: #ccc #ccc #bbb; + border-color: $kdb-border $kdb-border $kdb-border-bottom; border-image: none; border-radius: 3px; - box-shadow: 0 -1px 0 #bbb inset; + box-shadow: 0 -1px 0 $kdb-shadow inset; } h1 { @@ -81,7 +81,7 @@ } blockquote { - color: #7f8fa4; + color: $gl-grayish-blue; font-size: inherit; padding: 8px 21px; margin: 12px 0; @@ -94,13 +94,13 @@ } blockquote p { - color: #7f8fa4 !important; + color: $gl-grayish-blue !important; font-size: inherit; line-height: 1.5; } p { - color: #5c5d5e; + color: $gl-text-color-dark; margin: 6px 0 0; } @@ -108,10 +108,10 @@ @extend .table; @extend .table-bordered; margin: 12px 0; - color: #5c5d5e; + color: $gl-text-color-dark; th { - background: #f8fafc; + background: $label-gray-bg; } } @@ -182,6 +182,7 @@ left: -16px; position: absolute; text-decoration: none; + outline: none; &::after { content: image-url('icon_anchor.svg'); @@ -201,7 +202,7 @@ * */ body { - -webkit-text-shadow: rgba(255,255,255,0.01) 0 0 1px; + -webkit-text-shadow: $body-text-shadow 0 0 1px; } .page-title { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 750d99ebabe..a1d5f6427f4 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -12,67 +12,71 @@ $sidebar-breakpoint: 1024px; /* * Color schema */ +$darken-normal-factor: 7%; +$darken-dark-factor: 10%; +$darken-border-factor: 5%; + $white-light: #fff; -$white-normal: #ededed; -$white-dark: #ececec; +$white-normal: darken($white-light, $darken-normal-factor); +$white-dark: darken($white-light, $darken-dark-factor); $gray-lightest: #fdfdfd; $gray-light: #fafafa; $gray-lighter: #f9f9f9; -$gray-normal: #f5f5f5; -$gray-dark: #ededed; +$gray-normal: darken($gray-light, $darken-normal-factor); +$gray-dark: darken($gray-light, $darken-dark-factor); $gray-darker: #eee; $gray-darkest: #c9c9c9; -$green-light: #38ae67; -$green-normal: #2faa60; -$green-dark: #2ca05b; +$green-light: #3cbd70; +$green-normal: darken($green-light, $darken-normal-factor); +$green-dark: darken($green-light, $darken-dark-factor); $blue-light: #2ea8e5; -$blue-normal: #2d9fd8; -$blue-dark: #2897ce; +$blue-normal: darken($blue-light, $darken-normal-factor); +$blue-dark: darken($blue-light, $darken-dark-factor); $blue-medium-light: #3498cb; -$blue-medium: #2f8ebf; -$blue-medium-dark: #2d86b4; +$blue-medium: darken($blue-medium-light, $darken-normal-factor); +$blue-medium-dark: darken($blue-medium-light, $darken-dark-factor); $blue-light-transparent: rgba(44, 159, 216, 0.05); $orange-light: #fc8a51; -$orange-normal: #e75e40; -$orange-dark: #ce5237; +$orange-normal: darken($orange-light, $darken-normal-factor); +$orange-dark: darken($orange-light, $darken-dark-factor); $red-light: #e52c5a; -$red-normal: #d22852; -$red-dark: darken($red-normal, 5%); +$red-normal: darken($red-light, $darken-normal-factor); +$red-dark: darken($red-light, $darken-dark-factor); $black: #000; $black-transparent: rgba(0, 0, 0, 0.3); -$border-white-light: #f1f2f4; -$border-white-normal: #d6dae2; -$border-white-dark: #c6cacf; +$border-white-light: darken($white-light, $darken-border-factor); +$border-white-normal: darken($white-normal, $darken-border-factor); +$border-white-dark: darken($white-dark, $darken-border-factor); -$border-gray-light: #dcdcdc; -$border-gray-normal: #d7d7d7; -$border-gray-dark: #c6cacf; +$border-gray-light: darken($gray-light, $darken-border-factor); +$border-gray-normal: darken($gray-normal, $darken-border-factor); +$border-gray-dark: darken($gray-dark, $darken-border-factor); $border-green-extra-light: #9adb84; -$border-green-light: #2faa60; -$border-green-normal: #2ca05b; -$border-green-dark: #279654; +$border-green-light: darken($green-light, $darken-border-factor); +$border-green-normal: darken($green-normal, $darken-border-factor); +$border-green-dark: darken($green-dark, $darken-border-factor); -$border-blue-light: #2d9fd8; -$border-blue-normal: #2897ce; -$border-blue-dark: #258dc1; +$border-blue-light: darken($blue-light, $darken-border-factor); +$border-blue-normal: darken($blue-normal, $darken-border-factor); +$border-blue-dark: darken($blue-dark, $darken-border-factor); -$border-orange-light: #fc6d26; -$border-orange-normal: #ce5237; -$border-orange-dark: #c14e35; +$border-orange-light: darken($orange-light, $darken-border-factor); +$border-orange-normal: darken($orange-normal, $darken-border-factor); +$border-orange-dark: darken($orange-dark, $darken-border-factor); -$border-red-light: #d22852; -$border-red-normal: #ca264f; -$border-red-dark: darken($border-red-normal, 5%); +$border-red-light: darken($red-light, $darken-border-factor); +$border-red-normal: darken($red-normal, $darken-border-factor); +$border-red-dark: darken($red-dark, $darken-border-factor); $help-well-bg: $gray-light; $help-well-border: #e5e5e5; @@ -92,18 +96,22 @@ $dark-background-color: #f5f5f5; $table-text-gray: #8f8f8f; $well-expand-item: #e8f2f7; $well-inner-border: #eef0f2; +$well-light-border: #f1f1f1; +$well-light-text-color: #5b6169; /* * Text */ -$gl-font-size: 15px; +$gl-font-size: 14px; $gl-title-color: #333; $gl-text-color: #5c5c5c; +$gl-text-color-dark: #5c5d5e; $gl-text-color-light: #8c8c8c; $gl-text-green: #4a2; $gl-text-red: #d12f19; $gl-text-orange: #d90; $gl-link-color: #3777b0; +$gl-diff-text-color: #555; $gl-dark-link-color: #333; $gl-placeholder-color: #8f8f8f; $gl-icon-color: $gl-placeholder-color; @@ -119,13 +127,20 @@ $gl-header-color: #4c4e54; $list-font-size: $gl-font-size; $list-title-color: $gl-title-color; $list-text-color: $gl-text-color; +$list-text-disabled-color: #888; +$list-border-light: #eee; +$list-border: rgba(0, 0, 0, 0.05); $list-text-height: 42px; +$list-warning-row-bg: #fcf8e3; +$list-warning-row-border: #faebcc; +$list-warning-row-color: #8a6d3b; /* * Markdown */ $md-text-color: $gl-text-color; $md-link-color: $gl-link-color; +$md-area-border: #ddd; /* * Code @@ -149,10 +164,8 @@ $gl-sidebar-padding: 22px; $row-hover: #f7faff; $row-hover-border: #b2d7ff; $progress-color: #c0392b; -$avatar_radius: 50%; $header-height: 50px; $fixed-layout-width: 1280px; -$gl-avatar-size: 40px; $error-exclamation-point: #e62958; $border-radius-default: 2px; $btn-transparent-color: #8f8f8f; @@ -162,10 +175,47 @@ $provider-btn-not-active-color: #4688f1; $link-underline-blue: #4a8bee; $active-item-blue: #4a8bee; $layout-link-gray: #7e7c7c; -$todo-alert-blue: #428bca; $btn-side-margin: 10px; $btn-sm-side-margin: 7px; $btn-xs-side-margin: 5px; +$issue-status-expired: #cea61b; +$issuable-sidebar-color: #999; +$issuable-avatar-hover-border: #999; +$issuable-clipboard-color: #999; +$show-aside-bg: #eee; +$show-aside-color: #777; +$show-aside-shadow: #ddd; +$group-path-color: #999; +$namespace-kind-color: #aaa; +$panel-heading-link-color: #777; +$graph-author-email-color: #777; +$count-arrow-border: #dce0e5; +$save-project-loader-color: #555; +$divergence-graph-bar-bg: #ccc; +$divergence-graph-separator-bg: #ccc; +$issue-box-upcoming-bg: #8f8f8f; + +/* +* Common component specific colors +*/ +$hint-color: #999; +$well-pre-bg: #eee; +$well-pre-color: #555; +$loading-color: #555; +$update-author-color: #999; +$user-mention-color: #2fa0bb; +$time-color: #999; +$project-member-show-color: #aaa; +$gl-promo-color: #aaa; +$error-bg: #c67; +$warning-message-bg: #ffffe6; +$warning-message-border: #ed9; +$warning-message-color: #b90; +$control-group-descr-color: #666; +$table-permission-x-bg: #d9edf7; +$username-color: #666; +$description-color: #666; +$profiler-border: #eee; /* tanuki logo colors */ $tanuki-red: #e24329; @@ -201,12 +251,22 @@ $table-border-gray: #f0f0f0; $line-target-blue: #f6faff; $line-select-yellow: #fcf8e7; $line-select-yellow-dark: #f0e2bd; +$dark-diff-match-bg: rgba(255, 255, 255, 0.3); +$dark-diff-match-color: rgba(255, 255, 255, 0.1); +$file-mode-changed: #777; +$file-mode-changed: #777; +$diff-image-bg: #ddd; +$diff-image-info-color: grey; +$diff-image-img-bg: #e5e5e5; +$diff-swipe-border: #999; +$diff-view-modes-color: grey; +$diff-view-modes-border: #c1c1c1; /* * Fonts */ $monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace; -$regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif; +$regular_font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; /* * Dropdowns @@ -216,27 +276,31 @@ $dropdown-bg: #fff; $dropdown-link-color: #555; $dropdown-link-hover-bg: $row-hover; $dropdown-empty-row-bg: rgba(#000, .04); -$dropdown-border-color: rgba(#000, .1); +$dropdown-border-color: $border-color; $dropdown-shadow-color: rgba(#000, .1); $dropdown-divider-color: rgba(#000, .1); $dropdown-header-color: #959494; $dropdown-title-btn-color: #bfbfbf; $dropdown-input-color: #555; +$dropdown-input-fa-color: #c7c7c7; $dropdown-input-focus-border: $focus-border-color; $dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4); $dropdown-loading-bg: rgba(#fff, .6); +$dropdown-chevron-size: 10px; $dropdown-toggle-bg: #fff; -$dropdown-toggle-color: #626262; -$dropdown-toggle-border-color: #eaeaea; -$dropdown-toggle-hover-border-color: darken($dropdown-toggle-border-color, 15%); +$dropdown-toggle-color: #5c5c5c; +$dropdown-toggle-border-color: #e5e5e5; +$dropdown-toggle-hover-border-color: darken($dropdown-toggle-border-color, 13%); +$dropdown-toggle-active-border-color: darken($dropdown-toggle-border-color, 14%); $dropdown-toggle-icon-color: #c4c4c4; -$dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color; +$dropdown-toggle-hover-icon-color: darken($dropdown-toggle-icon-color, 7%); /* * Buttons */ $btn-active-gray: #ececec; +$btn-active-gray-light: e4e7ed; $btn-placeholder-gray: #c7c7c7; $btn-white-active: #848484; $btn-gray-hover: #eee; @@ -246,6 +310,7 @@ $btn-gray-hover: #eee; */ $award-emoji-menu-bg: #fff; $award-emoji-menu-border: #f1f2f4; +$award-emoji-menu-shadow: rgba(0,0,0,.175); $award-emoji-new-btn-icon-color: #dcdcdc; /* @@ -255,7 +320,7 @@ $search-input-border-color: rgba(#4688f1, .8); $search-input-focus-shadow-color: $dropdown-input-focus-shadow; $search-input-width: 220px; $location-badge-color: #aaa; -$location-badge-bg: $gray-normal; +$location-badge-bg: $dark-background-color; $location-badge-active-bg: #4f91f8; $location-icon-color: #e7e9ed; $location-icon-active-color: #807e7e; @@ -267,17 +332,28 @@ $notes-light-color: #8e8e8e; $notes-action-color: #c3c3c3; $notes-role-color: #8e8e8e; $notes-role-border-color: #e4e4e4; - $note-disabled-comment-color: #b2b2b2; $note-form-border-color: #e5e5e5; $note-toolbar-color: #959494; +$note-targe3-outside: #fffff0; +$note-targe3-inside: #ffffd3; +$note-line2-border: #ddd; + +/* +* Zen +*/ +$zen-control-color: #555; $zen-control-hover-color: #111; +/* +* Calendar +*/ $calendar-header-color: #b8b8b8; $calendar-hover-bg: #ecf3fe; $calendar-border-color: rgba(#000, .1); $calendar-unselectable-bg: $gray-light; +$calendar-user-contrib-text: #959494; /* * Cycle Analytics @@ -287,13 +363,212 @@ $cycle-analytics-box-text-color: #8c8c8c; $cycle-analytics-big-font: 19px; $cycle-analytics-dark-text: $gl-title-color; $cycle-analytics-light-gray: #bfbfbf; +$cycle-analytics-dismiss-icon-color: #b2b2b2; /* * Personal Access Tokens */ $personal-access-tokens-disabled-label-color: #bbb; +/* +* CI +*/ $ci-output-bg: #1d1f21; $ci-text-color: #c5c8c6; +$ci-skipped-color: #888; + +/* +* Boards +*/ +$issue-boards-font-size: 14px; +$issue-boards-card-shadow: rgba(186, 186, 186, 0.5); + +/* +* Avatar +*/ +$avatar_radius: 50%; +$avatar-border: rgba(0, 0, 0, .1); +$gl-avatar-size: 40px; + +/* +* Builds +*/ +$builds-trace-bg: #111; + +/* +* Callout +*/ +$callout-danger-bg: #fdf7f7; +$callout-danger-border: #eed3d7; +$callout-danger-color: #b94a48; +$callout-warning-bg: #faf8f0; +$callout-warning-border: #faebcc; +$callout-warning-color: #8a6d3b; +$callout-info-bg: #f4f8fa; +$callout-info-border: #bce8f1; +$callout-info-color: #34789a; +$callout-success-bg: #dff0d8; +$callout-success-border: #5ca64d; +$callout-success-color: #3c763d; + +/* +* Commit Page +*/ +$commit-committer-color: #999; +$commit-max-width-marker-color: rgba(0, 0, 0, 0.0); +$commit-message-text-area-bg: rgba(0, 0, 0, 0.0); + +/* +* Common +*/ +$common-gray: $gl-gray; +$common-gray-light: #bbb; +$common-gray-dark: #444; +$common-red: $gl-text-red; +$common-green: $gl-text-green; + +/* +* Editor +*/ +$editor-cancel-color: #b94a48; + +/* +* Events +*/ +$events-pre-color: #777; +$events-note-icon-color: #777; +$events-body-border: #ddd; + +/* +* Files +*/ +$file-image-bg: #eee; +$blob-bg: #eee; +$blame-border: #eee; +$blame-line-numbers-border: #ddd; +$logs-bg: #eee; +$logs-li-color: #888; +$logs-p-color: #333; + +/* +* Forms +*/ +$input-danger-bg: #f2dede; +$input-danger-border: #d66; +$input-group-addon-bg: #f7f8fa; +$gl-field-focus-shadow: rgba(0, 0, 0, 0.075); +$gl-field-focus-shadow-error: rgba(210, 40, 82, 0.6); -$issue-boards-font-size: 15px; +/* +* Help +*/ +$document-index-color: #888; +$help-shortcut-color: #999; +$help-shortcut-mapping-color: #555; +$help-shortcut-header-color: #333; + +/* +* Issues +*/ +$issues-border: #e5e5e5; +$issues-today-bg: #f3fff2; +$issues-today-border: #e1e8d5; + +/* +* jQuery UI +*/ +$jq-ui-border: #ddd; +$jq-ui-default-color: #777; + +/* +* Label +*/ +$label-gray-bg: #f8fafc; +$label-inverse-bg: #333; +$label-remove-border: rgba(0, 0, 0, .1); +$label-border-radius: 14px; + +/* +* Lint +*/ +$lint-incorrect-color: red; +$lint-correct-color: #47a447; + +/* +* Login +*/ +$login-brand-holder-color: #888; +$login-devise-error-color: #a00; + +/* +* Nav +*/ +$nav-link-gray: #959494; +$nav-badge-bg: #eee; +$nav-toggle-gray: #666; + +/* +* Notify +*/ +$notify-details: #777; +$notify-footer: #777; +$notify-new-file: #090; +$notify-deleted-file: #b00; + +/* +* Projects +*/ +$project-option-descr-color: #54565b; +$project-breadcrumb-color: #999; +$project-private-forks-notice-odd: #2aa056; +$project-network-controls-color: #888; +$project-limit-message-bg: #f28d35; + +/* +* Runners +*/ +$runner-state-shared-bg: #32b186; +$runner-state-specific-bg: #3498db; +$runner-status-online-color: green; +$runner-status-offline-color: gray; +$runner-status-paused-color: red; + +/* +Stat Graph +*/ +$stat-graph-common-bg: #f3f3f3; +$stat-graph-area-fill: #1db34f; +$stat-graph-axis-fill: #aaa; +$stat-graph-orange-fill: #f17f49; +$stat-graph-selection-fill: #333; +$stat-graph-selection-stroke: #333; + +/* +* Selects +*/ +$select2-drop-shadow1: rgba(76, 86, 103, 0.247059); +$select2-drop-shadow2: rgba(31, 37, 50, 0.317647); + + +/* +* Todo +*/ +$todo-alert-blue: #428bca; +$todo-body-pre-color: #777; +$todo-body-border: #ddd; + +/* +* Typography +*/ +$kdb-bg: #fcfcfc; +$kdb-color: #555; +$kdb-border: #ccc; +$kdb-border-bottom: #bbb; +$kdb-shadow: #bbb; +$body-text-shadow: rgba(255,255,255,0.01); + +/* +* UI Dev Kit +*/ +$ui-dev-kit-example-color: #bbb; +$ui-dev-kit-example-border: #ddd; diff --git a/app/assets/stylesheets/framework/wells.scss b/app/assets/stylesheets/framework/wells.scss index 192939f4527..f2860dfe84d 100644 --- a/app/assets/stylesheets/framework/wells.scss +++ b/app/assets/stylesheets/framework/wells.scss @@ -43,3 +43,16 @@ background-color: $well-expand-item; } } + +.light-well { + background-color: $background-color; + padding: 15px; +} + +.well-centered { + h1 { + font-weight: normal; + text-align: center; + font-size: 48px; + } +} diff --git a/app/assets/stylesheets/framework/zen.scss b/app/assets/stylesheets/framework/zen.scss index ff02ebdd34c..e5c7d70d45a 100644 --- a/app/assets/stylesheets/framework/zen.scss +++ b/app/assets/stylesheets/framework/zen.scss @@ -1,6 +1,6 @@ .zen-backdrop { &.fullscreen { - background-color: white; + background-color: $white-light; position: fixed; top: 0; bottom: 0; @@ -12,7 +12,7 @@ border: none; box-shadow: none; border-radius: 0; - color: #000; + color: $black; font-size: 20px; line-height: 26px; padding: 30px; @@ -34,7 +34,7 @@ .zen-control { padding: 0; - color: #555; + color: $zen-control-color; background: none; border: 0; } diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index d22d9b01495..cb923166b25 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -1,27 +1,109 @@ /* https://github.com/MozMorris/tomorrow-pygments */ + +/* +* Dark syntax colors +*/ +$dark-new-bg: rgba(51, 255, 51, 0.1); +$dark-new-idiff: rgba(51, 255, 51, 0.2); +$dark-old-bg: rgba(255, 51, 51, 0.2); +$dark-old-idiff: rgba(255, 51, 51, 0.25); +$dark-border: #808080; +$dark-code-border: #666; +$dark-main-bg: #1d1f21; +$dark-main-color: #1d1f21; +$dark-line-color: #c5c8c6; +$dark-line-num-color: rgba(255, 255, 255, 0.3); +$dark-diff-not-empty-bg: #557; +$dark-highlight-bg: #ffe792; +$dark-highlight-color: $black; +$dark-pre-hll-bg: #373b41; +$dark-hll-bg: #373b41; +$dark-c: #969896; +$dark-err: #c66; +$dark-k: #b294bb; +$dark-l: #de935f; +$dark-n: #c5c8c6; +$dark-o: #8abeb7; +$dark-p: #c5c8c6; +$dark-cm: #969896; +$dark-cp: #969896; +$dark-c1: #969896; +$dark-cs: #969896; +$dark-gd: #c66; +$dark-gh: #c5c8c6; +$dark-gi: #b5bd68; +$dark-gp: #969896; +$dark-gu: #8abeb7; +$dark-kc: #b294bb; +$dark-kd: #b294bb; +$dark-kn: #8abeb7; +$dark-kp: #b294bb; +$dark-kr: #b294bb; +$dark-kt: #f0c674; +$dark-ld: #b5bd68; +$dark-m: #de935f; +$dark-s: #b5bd68; +$dark-na: #81a2be; +$dark-nb: #c5c8c6; +$dark-nc: #f0c674; +$dark-no: #c66; +$dark-nd: #8abeb7; +$dark-ni: #c5c8c6; +$dark-ne: #c66; +$dark-nf: #81a2be; +$dark-nl: #c5c8c6; +$dark-nn: #f0c674; +$dark-nx: #81a2be; +$dark-py: #c5c8c6; +$dark-nt: #8abeb7; +$dark-nv: #c66; +$dark-ow: #8abeb7; +$dark-w: #c5c8c6; +$dark-mf: #de935f; +$dark-mh: #de935f; +$dark-mi: #de935f; +$dark-mo: #de935f; +$dark-sb: #b5bd68; +$dark-sc: #c5c8c6; +$dark-sd: #969896; +$dark-s2: #b5bd68; +$dark-se: #de935f; +$dark-sh: #b5bd68; +$dark-si: #de935f; +$dark-sx: #b5bd68; +$dark-sr: #b5bd68; +$dark-s1: #b5bd68; +$dark-ss: #b5bd68; +$dark-bp: #c5c8c6; +$dark-vc: #c66; +$dark-vg: #c66; +$dark-vi: #c66; +$dark-il: #de935f; + .code.dark { // Line numbers .line-numbers, .diff-line-num { - background-color: #1d1f21; + background-color: $dark-main-bg; } .diff-line-num, .diff-line-num a { - color: rgba(255, 255, 255, 0.3); + color: $dark-main-color; + color: $dark-line-num-color; } // Code itself pre.code, .diff-line-num { - border-color: #666; + border-color: $dark-code-border; } &, pre.code, .line_holder .line_content { - background-color: #1d1f21; - color: #c5c8c6; + background-color: $dark-main-bg; + color: $dark-line-color; } // Diff line @@ -32,18 +114,18 @@ td.diff-line-num.hll:not(.empty-cell), td.line_content.hll:not(.empty-cell) { - background-color: #557; - border-color: darken(#557, 15%); + background-color: $dark-diff-not-empty-bg; + border-color: darken($dark-diff-not-empty-bg, 15%); } .diff-line-num.new, .line_content.new { - @include diff_background(rgba(51, 255, 51, 0.1), rgba(51, 255, 51, 0.2), #808080); + @include diff_background($dark-new-bg, $dark-new-idiff, $dark-border); } .diff-line-num.old, .line_content.old { - @include diff_background(rgba(255, 51, 51, 0.2), rgba(255, 51, 51, 0.25), #808080); + @include diff_background($dark-old-bg, $dark-old-idiff, $dark-border); } .line_content.match { @@ -53,77 +135,77 @@ // highlight line via anchor pre .hll { - background-color: #557 !important; + background-color: $dark-pre-hll-bg !important; } // Search result highlight span.highlight_word { - background-color: #ffe792 !important; - color: #000 !important; + background-color: $dark-highlight-bg !important; + color: $dark-highlight-color !important; } - .hll { background-color: #373b41; } - .c { color: #969896; } /* Comment */ - .err { color: #c66; } /* Error */ - .k { color: #b294bb; } /* Keyword */ - .l { color: #de935f; } /* Literal */ - .n { color: #c5c8c6; } /* Name */ - .o { color: #8abeb7; } /* Operator */ - .p { color: #c5c8c6; } /* Punctuation */ - .cm { color: #969896; } /* Comment.Multiline */ - .cp { color: #969896; } /* Comment.Preproc */ - .c1 { color: #969896; } /* Comment.Single */ - .cs { color: #969896; } /* Comment.Special */ - .gd { color: #c66; } /* Generic.Deleted */ + .hll { background-color: $dark-hll-bg; } + .c { color: $dark-c; } /* Comment */ + .err { color: $dark-err; } /* Error */ + .k { color: $dark-k; } /* Keyword */ + .l { color: $dark-l; } /* Literal */ + .n { color: $dark-n; } /* Name */ + .o { color: $dark-o; } /* Operator */ + .p { color: $dark-p; } /* Punctuation */ + .cm { color: $dark-cm; } /* Comment.Multiline */ + .cp { color: $dark-cp; } /* Comment.Preproc */ + .c1 { color: $dark-c1; } /* Comment.Single */ + .cs { color: $dark-cs; } /* Comment.Special */ + .gd { color: $dark-gd; } /* Generic.Deleted */ .ge { font-style: italic; } /* Generic.Emph */ - .gh { color: #c5c8c6; font-weight: bold; } /* Generic.Heading */ - .gi { color: #b5bd68; } /* Generic.Inserted */ - .gp { color: #969896; font-weight: bold; } /* Generic.Prompt */ + .gh { color: $dark-gh; font-weight: bold; } /* Generic.Heading */ + .gi { color: $dark-gi; } /* Generic.Inserted */ + .gp { color: $dark-gp; font-weight: bold; } /* Generic.Prompt */ .gs { font-weight: bold; } /* Generic.Strong */ - .gu { color: #8abeb7; font-weight: bold; } /* Generic.Subheading */ - .kc { color: #b294bb; } /* Keyword.Constant */ - .kd { color: #b294bb; } /* Keyword.Declaration */ - .kn { color: #8abeb7; } /* Keyword.Namespace */ - .kp { color: #b294bb; } /* Keyword.Pseudo */ - .kr { color: #b294bb; } /* Keyword.Reserved */ - .kt { color: #f0c674; } /* Keyword.Type */ - .ld { color: #b5bd68; } /* Literal.Date */ - .m { color: #de935f; } /* Literal.Number */ - .s { color: #b5bd68; } /* Literal.String */ - .na { color: #81a2be; } /* Name.Attribute */ - .nb { color: #c5c8c6; } /* Name.Builtin */ - .nc { color: #f0c674; } /* Name.Class */ - .no { color: #c66; } /* Name.Constant */ - .nd { color: #8abeb7; } /* Name.Decorator */ - .ni { color: #c5c8c6; } /* Name.Entity */ - .ne { color: #c66; } /* Name.Exception */ - .nf { color: #81a2be; } /* Name.Function */ - .nl { color: #c5c8c6; } /* Name.Label */ - .nn { color: #f0c674; } /* Name.Namespace */ - .nx { color: #81a2be; } /* Name.Other */ - .py { color: #c5c8c6; } /* Name.Property */ - .nt { color: #8abeb7; } /* Name.Tag */ - .nv { color: #c66; } /* Name.Variable */ - .ow { color: #8abeb7; } /* Operator.Word */ - .w { color: #c5c8c6; } /* Text.Whitespace */ - .mf { color: #de935f; } /* Literal.Number.Float */ - .mh { color: #de935f; } /* Literal.Number.Hex */ - .mi { color: #de935f; } /* Literal.Number.Integer */ - .mo { color: #de935f; } /* Literal.Number.Oct */ - .sb { color: #b5bd68; } /* Literal.String.Backtick */ - .sc { color: #c5c8c6; } /* Literal.String.Char */ - .sd { color: #969896; } /* Literal.String.Doc */ - .s2 { color: #b5bd68; } /* Literal.String.Double */ - .se { color: #de935f; } /* Literal.String.Escape */ - .sh { color: #b5bd68; } /* Literal.String.Heredoc */ - .si { color: #de935f; } /* Literal.String.Interpol */ - .sx { color: #b5bd68; } /* Literal.String.Other */ - .sr { color: #b5bd68; } /* Literal.String.Regex */ - .s1 { color: #b5bd68; } /* Literal.String.Single */ - .ss { color: #b5bd68; } /* Literal.String.Symbol */ - .bp { color: #c5c8c6; } /* Name.Builtin.Pseudo */ - .vc { color: #c66; } /* Name.Variable.Class */ - .vg { color: #c66; } /* Name.Variable.Global */ - .vi { color: #c66; } /* Name.Variable.Instance */ - .il { color: #de935f; } /* Literal.Number.Integer.Long */ + .gu { color: $dark-gu; font-weight: bold; } /* Generic.Subheading */ + .kc { color: $dark-kc; } /* Keyword.Constant */ + .kd { color: $dark-kd; } /* Keyword.Declaration */ + .kn { color: $dark-kn; } /* Keyword.Namespace */ + .kp { color: $dark-kp; } /* Keyword.Pseudo */ + .kr { color: $dark-kr; } /* Keyword.Reserved */ + .kt { color: $dark-kt; } /* Keyword.Type */ + .ld { color: $dark-ld; } /* Literal.Date */ + .m { color: $dark-m; } /* Literal.Number */ + .s { color: $dark-s; } /* Literal.String */ + .na { color: $dark-na; } /* Name.Attribute */ + .nb { color: $dark-nb; } /* Name.Builtin */ + .nc { color: $dark-nc; } /* Name.Class */ + .no { color: $dark-no; } /* Name.Constant */ + .nd { color: $dark-nd; } /* Name.Decorator */ + .ni { color: $dark-ni; } /* Name.Entity */ + .ne { color: $dark-ne; } /* Name.Exception */ + .nf { color: $dark-nf; } /* Name.Function */ + .nl { color: $dark-nl; } /* Name.Label */ + .nn { color: $dark-nn; } /* Name.Namespace */ + .nx { color: $dark-nx; } /* Name.Other */ + .py { color: $dark-py; } /* Name.Property */ + .nt { color: $dark-nt; } /* Name.Tag */ + .nv { color: $dark-nv; } /* Name.Variable */ + .ow { color: $dark-ow; } /* Operator.Word */ + .w { color: $dark-w; } /* Text.Whitespace */ + .mf { color: $dark-mf; } /* Literal.Number.Float */ + .mh { color: $dark-mh; } /* Literal.Number.Hex */ + .mi { color: $dark-mi; } /* Literal.Number.Integer */ + .mo { color: $dark-mo; } /* Literal.Number.Oct */ + .sb { color: $dark-sb; } /* Literal.String.Backtick */ + .sc { color: $dark-sc; } /* Literal.String.Char */ + .sd { color: $dark-sd; } /* Literal.String.Doc */ + .s2 { color: $dark-s2; } /* Literal.String.Double */ + .se { color: $dark-se; } /* Literal.String.Escape */ + .sh { color: $dark-sh; } /* Literal.String.Heredoc */ + .si { color: $dark-si; } /* Literal.String.Interpol */ + .sx { color: $dark-sx; } /* Literal.String.Other */ + .sr { color: $dark-sr; } /* Literal.String.Regex */ + .s1 { color: $dark-s1; } /* Literal.String.Single */ + .ss { color: $dark-ss; } /* Literal.String.Symbol */ + .bp { color: $dark-bp; } /* Name.Builtin.Pseudo */ + .vc { color: $dark-vc; } /* Name.Variable.Class */ + .vg { color: $dark-vg; } /* Name.Variable.Global */ + .vi { color: $dark-vi; } /* Name.Variable.Instance */ + .il { color: $dark-il; } /* Literal.Number.Integer.Long */ } diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index db8da8aab10..d8510baad8a 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -1,27 +1,108 @@ /* https://github.com/richleland/pygments-css/blob/master/monokai.css */ + +/* +* Monokai Colors +*/ +$monokai-bg: #272822; +$monokai-border: #555; +$monokai-text-color: #f8f8f2; +$monokai-line-num-color: rgba(255, 255, 255, 0.3); +$monokai-line-empty-bg: #49483e; +$monokai-line-empty-border: darken($monokai-line-empty-bg, 15%); +$monokai-diff-border: #808080; +$monokai-highlight-bg: #ffe792; + +$monokai-new-bg: rgba(166, 226, 46, 0.1); +$monokai-new-idiff: rgba(166, 226, 46, 0.15); + +$monokai-old-bg: rgba(254, 147, 140, 0.15); +$monokai-old-idiff: rgba(254, 147, 140, 0.2); + +$monokai-hll: #49483e; +$monokai-c: #75715e; +$monokai-err-color: #960050; +$monokai-err-bg: #1e0010; +$monokai-k: #66d9ef; +$monokai-l: #ae81ff; +$monokai-n: #f8f8f2; +$monokai-o: #f92672; +$monokai-p: #f8f8f2; +$monokai-cm: #75715e; +$monokai-cp: #75715e; +$monokai-c1: #75715e; +$monokai-cs: #75715e; +$monokai-kc: #66d9ef; +$monokai-kd: #66d9ef; +$monokai-kn: #f92672; +$monokai-kp: #66d9ef; +$monokai-kr: #66d9ef; +$monokai-kt: #66d9ef; +$monokai-ld: #e6db74; +$monokai-m: #ae81ff; +$monokai-s: #e6db74; +$monokai-na: #a6e22e; +$monokai-nb: #f8f8f2; +$monokai-nc: #a6e22e; +$monokai-no: #66d9ef; +$monokai-nd: #a6e22e; +$monokai-ni: #f8f8f2; +$monokai-ne: #a6e22e; +$monokai-nf: #a6e22e; +$monokai-nl: #f8f8f2; +$monokai-nn: #f8f8f2; +$monokai-nx: #a6e22e; +$monokai-py: #f8f8f2; +$monokai-nt: #f92672; +$monokai-nv: #f8f8f2; +$monokai-ow: #f92672; +$monokai-w: #f8f8f2; +$monokai-mf: #ae81ff; +$monokai-mh: #ae81ff; +$monokai-mi: #ae81ff; +$monokai-mo: #ae81ff; +$monokai-sb: #e6db74; +$monokai-sc: #e6db74; +$monokai-sd: #e6db74; +$monokai-s2: #e6db74; +$monokai-se: #ae81ff; +$monokai-sh: #e6db74; +$monokai-si: #e6db74; +$monokai-sx: #e6db74; +$monokai-sr: #e6db74; +$monokai-s1: #e6db74; +$monokai-ss: #e6db74; +$monokai-bp: #f8f8f2; +$monokai-vc: #f8f8f2; +$monokai-vg: #f8f8f2; +$monokai-vi: #f8f8f2; +$monokai-il: #ae81ff; +$monokai-gu: #75715e; +$monokai-gd: #f92672; +$monokai-gi: #a6e22e; + .code.monokai { // Line numbers .line-numbers, .diff-line-num { - background-color: #272822; + background-color: $monokai-bg; } .diff-line-num, .diff-line-num a { - color: rgba(255, 255, 255, 0.3); + color: $monokai-line-num-color; } // Code itself pre.code, .diff-line-num { - border-color: #555; + border-color: $monokai-border; } &, pre.code, .line_holder .line_content { - background-color: #272822; - color: #f8f8f2; + background-color: $monokai-bg; + color: $monokai-text-color; } // Diff line @@ -32,18 +113,18 @@ td.diff-line-num.hll:not(.empty-cell), td.line_content.hll:not(.empty-cell) { - background-color: #49483e; - border-color: darken(#49483e, 15%); + background-color: $monokai-line-empty-bg; + border-color: $monokai-line-empty-border; } .diff-line-num.new, .line_content.new { - @include diff_background(rgba(166, 226, 46, 0.1), rgba(166, 226, 46, 0.15), #808080); + @include diff_background($monokai-new-bg, $monokai-new-idiff, $monokai-diff-border); } .diff-line-num.old, .line_content.old { - @include diff_background(rgba(254, 147, 140, 0.15), rgba(254, 147, 140, 0.2), #808080); + @include diff_background($monokai-old-bg, $monokai-old-idiff, $monokai-diff-border); } .line_content.match { @@ -53,75 +134,75 @@ // highlight line via anchor pre .hll { - background-color: #49483e !important; + background-color: $monokai-hll !important; } // Search result highlight span.highlight_word { - background-color: #ffe792 !important; - color: #000 !important; + background-color: $monokai-highlight-bg !important; + color: $black !important; } - .hll { background-color: #49483e; } - .c { color: #75715e; } /* Comment */ - .err { color: #960050; background-color: #1e0010; } /* Error */ - .k { color: #66d9ef; } /* Keyword */ - .l { color: #ae81ff; } /* Literal */ - .n { color: #f8f8f2; } /* Name */ - .o { color: #f92672; } /* Operator */ - .p { color: #f8f8f2; } /* Punctuation */ - .cm { color: #75715e; } /* Comment.Multiline */ - .cp { color: #75715e; } /* Comment.Preproc */ - .c1 { color: #75715e; } /* Comment.Single */ - .cs { color: #75715e; } /* Comment.Special */ + .hll { background-color: $monokai-hll; } + .c { color: $monokai-c; } /* Comment */ + .err { color: $monokai-err-color; background-color: $monokai-err-bg; } /* Error */ + .k { color: $monokai-k; } /* Keyword */ + .l { color: $monokai-l; } /* Literal */ + .n { color: $monokai-n; } /* Name */ + .o { color: $monokai-o; } /* Operator */ + .p { color: $monokai-p; } /* Punctuation */ + .cm { color: $monokai-cm; } /* Comment.Multiline */ + .cp { color: $monokai-cp; } /* Comment.Preproc */ + .c1 { color: $monokai-c1; } /* Comment.Single */ + .cs { color: $monokai-cs; } /* Comment.Special */ .ge { font-style: italic; } /* Generic.Emph */ .gs { font-weight: bold; } /* Generic.Strong */ - .kc { color: #66d9ef; } /* Keyword.Constant */ - .kd { color: #66d9ef; } /* Keyword.Declaration */ - .kn { color: #f92672; } /* Keyword.Namespace */ - .kp { color: #66d9ef; } /* Keyword.Pseudo */ - .kr { color: #66d9ef; } /* Keyword.Reserved */ - .kt { color: #66d9ef; } /* Keyword.Type */ - .ld { color: #e6db74; } /* Literal.Date */ - .m { color: #ae81ff; } /* Literal.Number */ - .s { color: #e6db74; } /* Literal.String */ - .na { color: #a6e22e; } /* Name.Attribute */ - .nb { color: #f8f8f2; } /* Name.Builtin */ - .nc { color: #a6e22e; } /* Name.Class */ - .no { color: #66d9ef; } /* Name.Constant */ - .nd { color: #a6e22e; } /* Name.Decorator */ - .ni { color: #f8f8f2; } /* Name.Entity */ - .ne { color: #a6e22e; } /* Name.Exception */ - .nf { color: #a6e22e; } /* Name.Function */ - .nl { color: #f8f8f2; } /* Name.Label */ - .nn { color: #f8f8f2; } /* Name.Namespace */ - .nx { color: #a6e22e; } /* Name.Other */ - .py { color: #f8f8f2; } /* Name.Property */ - .nt { color: #f92672; } /* Name.Tag */ - .nv { color: #f8f8f2; } /* Name.Variable */ - .ow { color: #f92672; } /* Operator.Word */ - .w { color: #f8f8f2; } /* Text.Whitespace */ - .mf { color: #ae81ff; } /* Literal.Number.Float */ - .mh { color: #ae81ff; } /* Literal.Number.Hex */ - .mi { color: #ae81ff; } /* Literal.Number.Integer */ - .mo { color: #ae81ff; } /* Literal.Number.Oct */ - .sb { color: #e6db74; } /* Literal.String.Backtick */ - .sc { color: #e6db74; } /* Literal.String.Char */ - .sd { color: #e6db74; } /* Literal.String.Doc */ - .s2 { color: #e6db74; } /* Literal.String.Double */ - .se { color: #ae81ff; } /* Literal.String.Escape */ - .sh { color: #e6db74; } /* Literal.String.Heredoc */ - .si { color: #e6db74; } /* Literal.String.Interpol */ - .sx { color: #e6db74; } /* Literal.String.Other */ - .sr { color: #e6db74; } /* Literal.String.Regex */ - .s1 { color: #e6db74; } /* Literal.String.Single */ - .ss { color: #e6db74; } /* Literal.String.Symbol */ - .bp { color: #f8f8f2; } /* Name.Builtin.Pseudo */ - .vc { color: #f8f8f2; } /* Name.Variable.Class */ - .vg { color: #f8f8f2; } /* Name.Variable.Global */ - .vi { color: #f8f8f2; } /* Name.Variable.Instance */ - .il { color: #ae81ff; } /* Literal.Number.Integer.Long */ - .gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */ - .gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */ - .gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */ + .kc { color: $monokai-kc; } /* Keyword.Constant */ + .kd { color: $monokai-kd; } /* Keyword.Declaration */ + .kn { color: $monokai-kn; } /* Keyword.Namespace */ + .kp { color: $monokai-kp; } /* Keyword.Pseudo */ + .kr { color: $monokai-kr; } /* Keyword.Reserved */ + .kt { color: $monokai-kt; } /* Keyword.Type */ + .ld { color: $monokai-ld; } /* Literal.Date */ + .m { color: $monokai-m; } /* Literal.Number */ + .s { color: $monokai-s; } /* Literal.String */ + .na { color: $monokai-na; } /* Name.Attribute */ + .nb { color: $monokai-nb; } /* Name.Builtin */ + .nc { color: $monokai-nc; } /* Name.Class */ + .no { color: $monokai-no; } /* Name.Constant */ + .nd { color: $monokai-nd; } /* Name.Decorator */ + .ni { color: $monokai-ni; } /* Name.Entity */ + .ne { color: $monokai-ne; } /* Name.Exception */ + .nf { color: $monokai-nf; } /* Name.Function */ + .nl { color: $monokai-nl; } /* Name.Label */ + .nn { color: $monokai-nn; } /* Name.Namespace */ + .nx { color: $monokai-nx; } /* Name.Other */ + .py { color: $monokai-py; } /* Name.Property */ + .nt { color: $monokai-nt; } /* Name.Tag */ + .nv { color: $monokai-nv; } /* Name.Variable */ + .ow { color: $monokai-ow; } /* Operator.Word */ + .w { color: $monokai-w; } /* Text.Whitespace */ + .mf { color: $monokai-mf; } /* Literal.Number.Float */ + .mh { color: $monokai-mh; } /* Literal.Number.Hex */ + .mi { color: $monokai-mi; } /* Literal.Number.Integer */ + .mo { color: $monokai-mo; } /* Literal.Number.Oct */ + .sb { color: $monokai-sb; } /* Literal.String.Backtick */ + .sc { color: $monokai-sc; } /* Literal.String.Char */ + .sd { color: $monokai-sd; } /* Literal.String.Doc */ + .s2 { color: $monokai-s2; } /* Literal.String.Double */ + .se { color: $monokai-se; } /* Literal.String.Escape */ + .sh { color: $monokai-sh; } /* Literal.String.Heredoc */ + .si { color: $monokai-si; } /* Literal.String.Interpol */ + .sx { color: $monokai-sx; } /* Literal.String.Other */ + .sr { color: $monokai-sr; } /* Literal.String.Regex */ + .s1 { color: $monokai-s1; } /* Literal.String.Single */ + .ss { color: $monokai-ss; } /* Literal.String.Symbol */ + .bp { color: $monokai-bp; } /* Name.Builtin.Pseudo */ + .vc { color: $monokai-vc; } /* Name.Variable.Class */ + .vg { color: $monokai-vg; } /* Name.Variable.Global */ + .vi { color: $monokai-vi; } /* Name.Variable.Instance */ + .il { color: $monokai-il; } /* Literal.Number.Integer.Long */ + .gu { color: $monokai-gu; } /* Generic.Subheading & Diff Unified/Comment? */ + .gd { color: $monokai-gd; } /* Generic.Deleted & Diff Deleted */ + .gi { color: $monokai-gi; } /* Generic.Inserted & Diff Inserted */ } diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index a87333146de..874aecb5e16 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -1,27 +1,112 @@ /* https://gist.github.com/qguv/7936275 */ + +/* +* Solarized dark colors +*/ +$solarized-dark-new-bg: rgba(133, 153, 0, 0.15); +$solarized-dark-new-idiff: rgba(133, 153, 0, 0.25); +$solarized-dark-old-bg: rgba(220, 50, 47, 0.3); +$solarized-dark-old-idiff: rgba(220, 50, 47, 0.25); +$solarized-dark-border: #113b46; +$solarized-dark-pre-bg: #002b36; +$solarized-dark-pre-color: #93a1a1; +$solarized-dark-pre-border: #113b46; +$solarized-dark-line-bg: #002b36; +$solarized-dark-line-color: rgba(255, 255, 255, 0.3); +$solarized-dark-highlight: #094554; +$solarized-dark-hll-bg: #174652; +$solarized-dark-c: #586e75; +$solarized-dark-err: #93a1a1; +$solarized-dark-g: #93a1a1; +$solarized-dark-k: #859900; +$solarized-dark-l: #93a1a1; +$solarized-dark-n: #93a1a1; +$solarized-dark-o: #859900; +$solarized-dark-x: #cb4b16; +$solarized-dark-p: #93a1a1; +$solarized-dark-cm: #586e75; +$solarized-dark-cp: #859900; +$solarized-dark-c1: #586e75; +$solarized-dark-cs: #859900; +$solarized-dark-gd: #2aa198; +$solarized-dark-ge: #93a1a1; +$solarized-dark-gr: #dc322f; +$solarized-dark-gh: #cb4b16; +$solarized-dark-gi: #859900; +$solarized-dark-go: #93a1a1; +$solarized-dark-gp: #93a1a1; +$solarized-dark-gs: #93a1a1; +$solarized-dark-gu: #cb4b16; +$solarized-dark-gt: #93a1a1; +$solarized-dark-kc: #cb4b16; +$solarized-dark-kd: #268bd2; +$solarized-dark-kn: #859900; +$solarized-dark-kp: #859900; +$solarized-dark-kr: #268bd2; +$solarized-dark-kt: #dc322f; +$solarized-dark-ld: #93a1a1; +$solarized-dark-m: #2aa198; +$solarized-dark-s: #2aa198; +$solarized-dark-na: #93a1a1; +$solarized-dark-nb: #b58900; +$solarized-dark-nc: #268bd2; +$solarized-dark-no: #cb4b16; +$solarized-dark-nd: #268bd2; +$solarized-dark-ni: #cb4b16; +$solarized-dark-ne: #cb4b16; +$solarized-dark-nf: #268bd2; +$solarized-dark-nl: #93a1a1; +$solarized-dark-nn: #93a1a1; +$solarized-dark-nx: #93a1a1; +$solarized-dark-py: #93a1a1; +$solarized-dark-nt: #268bd2; +$solarized-dark-nv: #268bd2; +$solarized-dark-ow: #859900; +$solarized-dark-w: #93a1a1; +$solarized-dark-mf: #2aa198; +$solarized-dark-mh: #2aa198; +$solarized-dark-mi: #2aa198; +$solarized-dark-mo: #2aa198; +$solarized-dark-sb: #586e75; +$solarized-dark-sc: #2aa198; +$solarized-dark-sd: #93a1a1; +$solarized-dark-s2: #2aa198; +$solarized-dark-se: #cb4b16; +$solarized-dark-sh: #93a1a1; +$solarized-dark-si: #2aa198; +$solarized-dark-sx: #2aa198; +$solarized-dark-sr: #dc322f; +$solarized-dark-s1: #2aa198; +$solarized-dark-ss: #2aa198; +$solarized-dark-bp: #268bd2; +$solarized-dark-vc: #268bd2; +$solarized-dark-vg: #268bd2; +$solarized-dark-vi: #268bd2; +$solarized-dark-il: #2aa198; + .code.solarized-dark { // Line numbers .line-numbers, .diff-line-num { - background-color: #002b36; + background-color: $solarized-dark-line-bg; } .diff-line-num, .diff-line-num a { - color: rgba(255, 255, 255, 0.3); + color: $solarized-dark-line-color; } // Code itself pre.code, .diff-line-num { - border-color: #113b46; + border-color: $solarized-dark-pre-border; } &, pre.code, .line_holder .line_content { - background-color: #002b36; - color: #93a1a1; + background-color: $solarized-dark-pre-bg; + color: $solarized-dark-pre-color; } // Diff line @@ -32,18 +117,18 @@ td.diff-line-num.hll:not(.empty-cell), td.line_content.hll:not(.empty-cell) { - background-color: #174652; - border-color: darken(#174652, 15%); + background-color: $solarized-dark-hll-bg; + border-color: darken($solarized-dark-hll-bg, 15%); } .diff-line-num.new, .line_content.new { - @include diff_background(rgba(133, 153, 0, 0.15), rgba(133, 153, 0, 0.25), #113b46); + @include diff_background($solarized-dark-new-bg, $solarized-dark-new-idiff, $solarized-dark-border); } .diff-line-num.old, .line_content.old { - @include diff_background(rgba(220, 50, 47, 0.3), rgba(220, 50, 47, 0.25), #113b46); + @include diff_background($solarized-dark-old-bg, $solarized-dark-old-idiff, $solarized-dark-border); } .line_content.match { @@ -53,12 +138,12 @@ // highlight line via anchor pre .hll { - background-color: #174652 !important; + background-color: $solarized-dark-hll-bg !important; } // Search result highlight span.highlight_word { - background-color: #094554 !important; + background-color: $solarized-dark-highlight !important; } /* Solarized Dark @@ -79,72 +164,72 @@ green #859900 operators, other keywords */ - .c { color: #586e75; } /* Comment */ - .err { color: #93a1a1; } /* Error */ - .g { color: #93a1a1; } /* Generic */ - .k { color: #859900; } /* Keyword */ - .l { color: #93a1a1; } /* Literal */ - .n { color: #93a1a1; } /* Name */ - .o { color: #859900; } /* Operator */ - .x { color: #cb4b16; } /* Other */ - .p { color: #93a1a1; } /* Punctuation */ - .cm { color: #586e75; } /* Comment.Multiline */ - .cp { color: #859900; } /* Comment.Preproc */ - .c1 { color: #586e75; } /* Comment.Single */ - .cs { color: #859900; } /* Comment.Special */ - .gd { color: #2aa198; } /* Generic.Deleted */ - .ge { color: #93a1a1; font-style: italic; } /* Generic.Emph */ - .gr { color: #dc322f; } /* Generic.Error */ - .gh { color: #cb4b16; } /* Generic.Heading */ - .gi { color: #859900; } /* Generic.Inserted */ - .go { color: #93a1a1; } /* Generic.Output */ - .gp { color: #93a1a1; } /* Generic.Prompt */ - .gs { color: #93a1a1; font-weight: bold; } /* Generic.Strong */ - .gu { color: #cb4b16; } /* Generic.Subheading */ - .gt { color: #93a1a1; } /* Generic.Traceback */ - .kc { color: #cb4b16; } /* Keyword.Constant */ - .kd { color: #268bd2; } /* Keyword.Declaration */ - .kn { color: #859900; } /* Keyword.Namespace */ - .kp { color: #859900; } /* Keyword.Pseudo */ - .kr { color: #268bd2; } /* Keyword.Reserved */ - .kt { color: #dc322f; } /* Keyword.Type */ - .ld { color: #93a1a1; } /* Literal.Date */ - .m { color: #2aa198; } /* Literal.Number */ - .s { color: #2aa198; } /* Literal.String */ - .na { color: #93a1a1; } /* Name.Attribute */ - .nb { color: #b58900; } /* Name.Builtin */ - .nc { color: #268bd2; } /* Name.Class */ - .no { color: #cb4b16; } /* Name.Constant */ - .nd { color: #268bd2; } /* Name.Decorator */ - .ni { color: #cb4b16; } /* Name.Entity */ - .ne { color: #cb4b16; } /* Name.Exception */ - .nf { color: #268bd2; } /* Name.Function */ - .nl { color: #93a1a1; } /* Name.Label */ - .nn { color: #93a1a1; } /* Name.Namespace */ - .nx { color: #93a1a1; } /* Name.Other */ - .py { color: #93a1a1; } /* Name.Property */ - .nt { color: #268bd2; } /* Name.Tag */ - .nv { color: #268bd2; } /* Name.Variable */ - .ow { color: #859900; } /* Operator.Word */ - .w { color: #93a1a1; } /* Text.Whitespace */ - .mf { color: #2aa198; } /* Literal.Number.Float */ - .mh { color: #2aa198; } /* Literal.Number.Hex */ - .mi { color: #2aa198; } /* Literal.Number.Integer */ - .mo { color: #2aa198; } /* Literal.Number.Oct */ - .sb { color: #586e75; } /* Literal.String.Backtick */ - .sc { color: #2aa198; } /* Literal.String.Char */ - .sd { color: #93a1a1; } /* Literal.String.Doc */ - .s2 { color: #2aa198; } /* Literal.String.Double */ - .se { color: #cb4b16; } /* Literal.String.Escape */ - .sh { color: #93a1a1; } /* Literal.String.Heredoc */ - .si { color: #2aa198; } /* Literal.String.Interpol */ - .sx { color: #2aa198; } /* Literal.String.Other */ - .sr { color: #dc322f; } /* Literal.String.Regex */ - .s1 { color: #2aa198; } /* Literal.String.Single */ - .ss { color: #2aa198; } /* Literal.String.Symbol */ - .bp { color: #268bd2; } /* Name.Builtin.Pseudo */ - .vc { color: #268bd2; } /* Name.Variable.Class */ - .vg { color: #268bd2; } /* Name.Variable.Global */ - .vi { color: #268bd2; } /* Name.Variable.Instance */ - .il { color: #2aa198; } /* Literal.Number.Integer.Long */ + .c { color: $solarized-dark-c; } /* Comment */ + .err { color: $solarized-dark-err; } /* Error */ + .g { color: $solarized-dark-g; } /* Generic */ + .k { color: $solarized-dark-k; } /* Keyword */ + .l { color: $solarized-dark-l; } /* Literal */ + .n { color: $solarized-dark-n; } /* Name */ + .o { color: $solarized-dark-o; } /* Operator */ + .x { color: $solarized-dark-x; } /* Other */ + .p { color: $solarized-dark-p; } /* Punctuation */ + .cm { color: $solarized-dark-cm; } /* Comment.Multiline */ + .cp { color: $solarized-dark-cp; } /* Comment.Preproc */ + .c1 { color: $solarized-dark-c1; } /* Comment.Single */ + .cs { color: $solarized-dark-cs; } /* Comment.Special */ + .gd { color: $solarized-dark-gd; } /* Generic.Deleted */ + .ge { color: $solarized-dark-ge; font-style: italic; } /* Generic.Emph */ + .gr { color: $solarized-dark-gr; } /* Generic.Error */ + .gh { color: $solarized-dark-gh; } /* Generic.Heading */ + .gi { color: $solarized-dark-gi; } /* Generic.Inserted */ + .go { color: $solarized-dark-go; } /* Generic.Output */ + .gp { color: $solarized-dark-gp; } /* Generic.Prompt */ + .gs { color: $solarized-dark-gs; font-weight: bold; } /* Generic.Strong */ + .gu { color: $solarized-dark-gu; } /* Generic.Subheading */ + .gt { color: $solarized-dark-gt; } /* Generic.Traceback */ + .kc { color: $solarized-dark-kc; } /* Keyword.Constant */ + .kd { color: $solarized-dark-kd; } /* Keyword.Declaration */ + .kn { color: $solarized-dark-kn; } /* Keyword.Namespace */ + .kp { color: $solarized-dark-kp; } /* Keyword.Pseudo */ + .kr { color: $solarized-dark-kr; } /* Keyword.Reserved */ + .kt { color: $solarized-dark-kt; } /* Keyword.Type */ + .ld { color: $solarized-dark-ld; } /* Literal.Date */ + .m { color: $solarized-dark-m; } /* Literal.Number */ + .s { color: $solarized-dark-s; } /* Literal.String */ + .na { color: $solarized-dark-na; } /* Name.Attribute */ + .nb { color: $solarized-dark-nb; } /* Name.Builtin */ + .nc { color: $solarized-dark-nc; } /* Name.Class */ + .no { color: $solarized-dark-no; } /* Name.Constant */ + .nd { color: $solarized-dark-nd; } /* Name.Decorator */ + .ni { color: $solarized-dark-ni; } /* Name.Entity */ + .ne { color: $solarized-dark-ne; } /* Name.Exception */ + .nf { color: $solarized-dark-nf; } /* Name.Function */ + .nl { color: $solarized-dark-nl; } /* Name.Label */ + .nn { color: $solarized-dark-nn; } /* Name.Namespace */ + .nx { color: $solarized-dark-nx; } /* Name.Other */ + .py { color: $solarized-dark-py; } /* Name.Property */ + .nt { color: $solarized-dark-nt; } /* Name.Tag */ + .nv { color: $solarized-dark-nv; } /* Name.Variable */ + .ow { color: $solarized-dark-ow; } /* Operator.Word */ + .w { color: $solarized-dark-w; } /* Text.Whitespace */ + .mf { color: $solarized-dark-mf; } /* Literal.Number.Float */ + .mh { color: $solarized-dark-mh; } /* Literal.Number.Hex */ + .mi { color: $solarized-dark-mi; } /* Literal.Number.Integer */ + .mo { color: $solarized-dark-mo; } /* Literal.Number.Oct */ + .sb { color: $solarized-dark-sb; } /* Literal.String.Backtick */ + .sc { color: $solarized-dark-sc; } /* Literal.String.Char */ + .sd { color: $solarized-dark-sd; } /* Literal.String.Doc */ + .s2 { color: $solarized-dark-s2; } /* Literal.String.Double */ + .se { color: $solarized-dark-se; } /* Literal.String.Escape */ + .sh { color: $solarized-dark-sh; } /* Literal.String.Heredoc */ + .si { color: $solarized-dark-si; } /* Literal.String.Interpol */ + .sx { color: $solarized-dark-sx; } /* Literal.String.Other */ + .sr { color: $solarized-dark-sr; } /* Literal.String.Regex */ + .s1 { color: $solarized-dark-s1; } /* Literal.String.Single */ + .ss { color: $solarized-dark-ss; } /* Literal.String.Symbol */ + .bp { color: $solarized-dark-bp; } /* Name.Builtin.Pseudo */ + .vc { color: $solarized-dark-vc; } /* Name.Variable.Class */ + .vg { color: $solarized-dark-vg; } /* Name.Variable.Global */ + .vi { color: $solarized-dark-vi; } /* Name.Variable.Instance */ + .il { color: $solarized-dark-il; } /* Literal.Number.Integer.Long */ } diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index faff353ded7..499a1c108b8 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -1,15 +1,99 @@ /* https://gist.github.com/qguv/7936275 */ +/* +* Solarized light syntax colors +*/ +$solarized-light-matchline-bg: rgba(255, 255, 255, 0.4); +$solarized-light-new-bg: rgba(133, 153, 0, 0.2); +$solarized-light-new-idiff: rgba(133, 153, 0, 0.25); +$solarized-light-old-bg: rgba(220, 50, 47, 0.2); +$solarized-light-old-idiff: rgba(220, 50, 47, 0.25); +$solarized-light-border: #c5d0d4; +$solarized-light-pre-bg: #002b36; +$solarized-light-pre-bg: #fdf6e3; +$solarized-light-pre-color: #586e75; +$solarized-light-line-bg: #fdf6e3; +$solarized-light-highlight: #eee8d5; +$solarized-light-hll-bg: #ddd8c5; +$solarized-light-c: #93a1a1; +$solarized-light-err: #586e75; +$solarized-light-g: #586e75; +$solarized-light-k: #859900; +$solarized-light-l: #586e75; +$solarized-light-n: #586e75; +$solarized-light-o: #859900; +$solarized-light-x: #cb4b16; +$solarized-light-p: #586e75; +$solarized-light-cm: #93a1a1; +$solarized-light-cp: #859900; +$solarized-light-c1: #93a1a1; +$solarized-light-cs: #859900; +$solarized-light-gd: #2aa198; +$solarized-light-ge: #586e75; +$solarized-light-gr: #dc322f; +$solarized-light-gh: #cb4b16; +$solarized-light-gi: #859900; +$solarized-light-go: #586e75; +$solarized-light-gp: #586e75; +$solarized-light-gs: #586e75; +$solarized-light-gu: #cb4b16; +$solarized-light-gt: #586e75; +$solarized-light-kc: #cb4b16; +$solarized-light-kd: #268bd2; +$solarized-light-kn: #859900; +$solarized-light-kp: #859900; +$solarized-light-kr: #268bd2; +$solarized-light-kt: #dc322f; +$solarized-light-ld: #586e75; +$solarized-light-m: #2aa198; +$solarized-light-s: #2aa198; +$solarized-light-na: #586e75; +$solarized-light-nb: #b58900; +$solarized-light-nc: #268bd2; +$solarized-light-no: #cb4b16; +$solarized-light-nd: #268bd2; +$solarized-light-ni: #cb4b16; +$solarized-light-ne: #cb4b16; +$solarized-light-nf: #268bd2; +$solarized-light-nl: #586e75; +$solarized-light-nn: #586e75; +$solarized-light-nx: #586e75; +$solarized-light-py: #586e75; +$solarized-light-nt: #268bd2; +$solarized-light-nv: #268bd2; +$solarized-light-ow: #859900; +$solarized-light-w: #586e75; +$solarized-light-mf: #2aa198; +$solarized-light-mh: #2aa198; +$solarized-light-mi: #2aa198; +$solarized-light-mo: #2aa198; +$solarized-light-sb: #93a1a1; +$solarized-light-sc: #2aa198; +$solarized-light-sd: #586e75; +$solarized-light-s2: #2aa198; +$solarized-light-se: #cb4b16; +$solarized-light-sh: #586e75; +$solarized-light-si: #2aa198; +$solarized-light-sx: #2aa198; +$solarized-light-sr: #dc322f; +$solarized-light-s1: #2aa198; +$solarized-light-ss: #2aa198; +$solarized-light-bp: #268bd2; +$solarized-light-vc: #268bd2; +$solarized-light-vg: #268bd2; +$solarized-light-vi: #268bd2; +$solarized-light-il: #2aa198; + @mixin matchLine { color: $black-transparent; - background: rgba(255, 255, 255, 0.4); + background: $solarized-light-matchline-bg; } .code.solarized-light { // Line numbers .line-numbers, .diff-line-num { - background-color: #fdf6e3; + background-color: $solarized-light-line-bg; } .diff-line-num, @@ -20,14 +104,14 @@ // Code itself pre.code, .diff-line-num { - border-color: #c5d0d4; + border-color: $solarized-light-border; } &, pre.code, .line_holder .line_content { - background-color: #fdf6e3; - color: #586e75; + background-color: $solarized-light-pre-bg; + color: $solarized-light-pre-color; } // Diff line @@ -38,18 +122,19 @@ td.diff-line-num.hll:not(.empty-cell), td.line_content.hll:not(.empty-cell) { - background-color: #ddd8c5; - border-color: darken(#ddd8c5, 15%); + background-color: $solarized-light-hll-bg; + border-color: darken($solarized-light-hll-bg, 15%); } .diff-line-num.new, .line_content.new { - @include diff_background(rgba(133, 153, 0, 0.2), rgba(133, 153, 0, 0.25), #c5d0d4); + @include diff_background($solarized-light-new-bg, + $solarized-light-new-idiff, $solarized-light-border); } .diff-line-num.old, .line_content.old { - @include diff_background(rgba(220, 50, 47, 0.2), rgba(220, 50, 47, 0.25), #c5d0d4); + @include diff_background($solarized-light-old-bg, $solarized-light-old-idiff, $solarized-light-border); } .line_content.match { @@ -59,12 +144,12 @@ // highlight line via anchor pre .hll { - background-color: #ddd8c5 !important; + background-color: $solarized-light-hll-bg !important; } // Search result highlight span.highlight_word { - background-color: #eee8d5 !important; + background-color: $solarized-light-highlight !important; } /* Solarized Light @@ -85,72 +170,72 @@ green #859900 operators, other keywords */ - .c { color: #93a1a1; } /* Comment */ - .err { color: #586e75; } /* Error */ - .g { color: #586e75; } /* Generic */ - .k { color: #859900; } /* Keyword */ - .l { color: #586e75; } /* Literal */ - .n { color: #586e75; } /* Name */ - .o { color: #859900; } /* Operator */ - .x { color: #cb4b16; } /* Other */ - .p { color: #586e75; } /* Punctuation */ - .cm { color: #93a1a1; } /* Comment.Multiline */ - .cp { color: #859900; } /* Comment.Preproc */ - .c1 { color: #93a1a1; } /* Comment.Single */ - .cs { color: #859900; } /* Comment.Special */ - .gd { color: #2aa198; } /* Generic.Deleted */ - .ge { color: #586e75; font-style: italic; } /* Generic.Emph */ - .gr { color: #dc322f; } /* Generic.Error */ - .gh { color: #cb4b16; } /* Generic.Heading */ - .gi { color: #859900; } /* Generic.Inserted */ - .go { color: #586e75; } /* Generic.Output */ - .gp { color: #586e75; } /* Generic.Prompt */ - .gs { color: #586e75; font-weight: bold; } /* Generic.Strong */ - .gu { color: #cb4b16; } /* Generic.Subheading */ - .gt { color: #586e75; } /* Generic.Traceback */ - .kc { color: #cb4b16; } /* Keyword.Constant */ - .kd { color: #268bd2; } /* Keyword.Declaration */ - .kn { color: #859900; } /* Keyword.Namespace */ - .kp { color: #859900; } /* Keyword.Pseudo */ - .kr { color: #268bd2; } /* Keyword.Reserved */ - .kt { color: #dc322f; } /* Keyword.Type */ - .ld { color: #586e75; } /* Literal.Date */ - .m { color: #2aa198; } /* Literal.Number */ - .s { color: #2aa198; } /* Literal.String */ - .na { color: #586e75; } /* Name.Attribute */ - .nb { color: #b58900; } /* Name.Builtin */ - .nc { color: #268bd2; } /* Name.Class */ - .no { color: #cb4b16; } /* Name.Constant */ - .nd { color: #268bd2; } /* Name.Decorator */ - .ni { color: #cb4b16; } /* Name.Entity */ - .ne { color: #cb4b16; } /* Name.Exception */ - .nf { color: #268bd2; } /* Name.Function */ - .nl { color: #586e75; } /* Name.Label */ - .nn { color: #586e75; } /* Name.Namespace */ - .nx { color: #586e75; } /* Name.Other */ - .py { color: #586e75; } /* Name.Property */ - .nt { color: #268bd2; } /* Name.Tag */ - .nv { color: #268bd2; } /* Name.Variable */ - .ow { color: #859900; } /* Operator.Word */ - .w { color: #586e75; } /* Text.Whitespace */ - .mf { color: #2aa198; } /* Literal.Number.Float */ - .mh { color: #2aa198; } /* Literal.Number.Hex */ - .mi { color: #2aa198; } /* Literal.Number.Integer */ - .mo { color: #2aa198; } /* Literal.Number.Oct */ - .sb { color: #93a1a1; } /* Literal.String.Backtick */ - .sc { color: #2aa198; } /* Literal.String.Char */ - .sd { color: #586e75; } /* Literal.String.Doc */ - .s2 { color: #2aa198; } /* Literal.String.Double */ - .se { color: #cb4b16; } /* Literal.String.Escape */ - .sh { color: #586e75; } /* Literal.String.Heredoc */ - .si { color: #2aa198; } /* Literal.String.Interpol */ - .sx { color: #2aa198; } /* Literal.String.Other */ - .sr { color: #dc322f; } /* Literal.String.Regex */ - .s1 { color: #2aa198; } /* Literal.String.Single */ - .ss { color: #2aa198; } /* Literal.String.Symbol */ - .bp { color: #268bd2; } /* Name.Builtin.Pseudo */ - .vc { color: #268bd2; } /* Name.Variable.Class */ - .vg { color: #268bd2; } /* Name.Variable.Global */ - .vi { color: #268bd2; } /* Name.Variable.Instance */ - .il { color: #2aa198; } /* Literal.Number.Integer.Long */ + .c { color: $solarized-light-c; } /* Comment */ + .err { color: $solarized-light-err; } /* Error */ + .g { color: $solarized-light-g; } /* Generic */ + .k { color: $solarized-light-k; } /* Keyword */ + .l { color: $solarized-light-l; } /* Literal */ + .n { color: $solarized-light-n; } /* Name */ + .o { color: $solarized-light-o; } /* Operator */ + .x { color: $solarized-light-x; } /* Other */ + .p { color: $solarized-light-p; } /* Punctuation */ + .cm { color: $solarized-light-cm; } /* Comment.Multiline */ + .cp { color: $solarized-light-cp; } /* Comment.Preproc */ + .c1 { color: $solarized-light-c1; } /* Comment.Single */ + .cs { color: $solarized-light-cs; } /* Comment.Special */ + .gd { color: $solarized-light-gd; } /* Generic.Deleted */ + .ge { color: $solarized-light-ge; font-style: italic; } /* Generic.Emph */ + .gr { color: $solarized-light-gr; } /* Generic.Error */ + .gh { color: $solarized-light-gh; } /* Generic.Heading */ + .gi { color: $solarized-light-gi; } /* Generic.Inserted */ + .go { color: $solarized-light-go; } /* Generic.Output */ + .gp { color: $solarized-light-gp; } /* Generic.Prompt */ + .gs { color: $solarized-light-gs; font-weight: bold; } /* Generic.Strong */ + .gu { color: $solarized-light-gu; } /* Generic.Subheading */ + .gt { color: $solarized-light-gt; } /* Generic.Traceback */ + .kc { color: $solarized-light-kc; } /* Keyword.Constant */ + .kd { color: $solarized-light-kd; } /* Keyword.Declaration */ + .kn { color: $solarized-light-kn; } /* Keyword.Namespace */ + .kp { color: $solarized-light-kp; } /* Keyword.Pseudo */ + .kr { color: $solarized-light-kr; } /* Keyword.Reserved */ + .kt { color: $solarized-light-kt; } /* Keyword.Type */ + .ld { color: $solarized-light-ld; } /* Literal.Date */ + .m { color: $solarized-light-m; } /* Literal.Number */ + .s { color: $solarized-light-s; } /* Literal.String */ + .na { color: $solarized-light-na; } /* Name.Attribute */ + .nb { color: $solarized-light-nb; } /* Name.Builtin */ + .nc { color: $solarized-light-nc; } /* Name.Class */ + .no { color: $solarized-light-no; } /* Name.Constant */ + .nd { color: $solarized-light-nd; } /* Name.Decorator */ + .ni { color: $solarized-light-ni; } /* Name.Entity */ + .ne { color: $solarized-light-ne; } /* Name.Exception */ + .nf { color: $solarized-light-nf; } /* Name.Function */ + .nl { color: $solarized-light-nl; } /* Name.Label */ + .nn { color: $solarized-light-nn; } /* Name.Namespace */ + .nx { color: $solarized-light-nx; } /* Name.Other */ + .py { color: $solarized-light-py; } /* Name.Property */ + .nt { color: $solarized-light-nt; } /* Name.Tag */ + .nv { color: $solarized-light-nv; } /* Name.Variable */ + .ow { color: $solarized-light-ow; } /* Operator.Word */ + .w { color: $solarized-light-w; } /* Text.Whitespace */ + .mf { color: $solarized-light-mf; } /* Literal.Number.Float */ + .mh { color: $solarized-light-mh; } /* Literal.Number.Hex */ + .mi { color: $solarized-light-mi; } /* Literal.Number.Integer */ + .mo { color: $solarized-light-mo; } /* Literal.Number.Oct */ + .sb { color: $solarized-light-sb; } /* Literal.String.Backtick */ + .sc { color: $solarized-light-sc; } /* Literal.String.Char */ + .sd { color: $solarized-light-sd; } /* Literal.String.Doc */ + .s2 { color: $solarized-light-s2; } /* Literal.String.Double */ + .se { color: $solarized-light-se; } /* Literal.String.Escape */ + .sh { color: $solarized-light-sh; } /* Literal.String.Heredoc */ + .si { color: $solarized-light-si; } /* Literal.String.Interpol */ + .sx { color: $solarized-light-sx; } /* Literal.String.Other */ + .sr { color: $solarized-light-sr; } /* Literal.String.Regex */ + .s1 { color: $solarized-light-s1; } /* Literal.String.Single */ + .ss { color: $solarized-light-ss; } /* Literal.String.Symbol */ + .bp { color: $solarized-light-bp; } /* Name.Builtin.Pseudo */ + .vc { color: $solarized-light-vc; } /* Name.Variable.Class */ + .vg { color: $solarized-light-vg; } /* Name.Variable.Global */ + .vi { color: $solarized-light-vi; } /* Name.Variable.Instance */ + .il { color: $solarized-light-il; } /* Literal.Number.Integer.Long */ } diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index d5367d5f3f0..1adab3ffd94 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -1,5 +1,72 @@ /* https://github.com/aahan/pygments-github-style */ +/* +* White Syntax Colors +*/ +$white-code-color: #333; +$white-highlight: #fafe3d; +$white-pre-hll-bg: #f8eec7; +$white-hll-bg: #f8f8f8; +$white-c: #998; +$white-err: #a61717; +$white-err-bg: #e3d2d2; +$white-cm: #998; +$white-cp: #999; +$white-c1: #998; +$white-cs: #999; +$white-gd: $black; +$white-gd-bg: #fdd; +$white-gd-x: $black; +$white-gd-x-bg: #faa; +$white-gr: #a00; +$white-gh: #999; +$white-gi: $black; +$white-gi-bg: #dfd; +$white-gi-x: $black; +$white-gi-x-bg: #afa; +$white-go: #888; +$white-gp: #555; +$white-gu: #800080; +$white-gt: #a00; +$white-kt: #458; +$white-m: #099; +$white-s: #d14; +$white-n: #333; +$white-na: teal; +$white-nb: #0086b3; +$white-nc: #458; +$white-no: teal; +$white-ni: purple; +$white-ne: #900; +$white-nf: #900; +$white-nn: #555; +$white-nt: navy; +$white-nv: teal; +$white-w: #bbb; +$white-mf: #099; +$white-mh: #099; +$white-mi: #099; +$white-mo: #099; +$white-sb: #d14; +$white-sc: #d14; +$white-sd: #d14; +$white-s2: #d14; +$white-se: #d14; +$white-sh: #d14; +$white-si: #d14; +$white-sx: #d14; +$white-sr: #009926; +$white-s1: #d14; +$white-ss: #990073; +$white-bp: #999; +$white-vc: teal; +$white-vg: teal; +$white-vi: teal; +$white-il: #099; +$white-gc-color: #999; +$white-gc-bg: #eaf2f5; + + @mixin matchLine { color: $black-transparent; background-color: $match-line; @@ -26,8 +93,8 @@ &, pre.code, .line_holder .line_content { - background-color: #fff; - color: #333; + background-color: $white-light; + color: $white-code-color; } // Diff line @@ -83,75 +150,75 @@ // highlight line via anchor pre .hll { - background-color: #f8eec7 !important; + background-color: $white-pre-hll-bg !important; } // Search result highlight span.highlight_word { - background-color: #fafe3d !important; + background-color: $white-highlight !important; } - .hll { background-color: #f8f8f8; } - .c { color: #998; font-style: italic; } - .err { color: #a61717; background-color: #e3d2d2; } + .hll { background-color: $white-hll-bg; } + .c { color: $white-c; font-style: italic; } + .err { color: $white-err; background-color: $white-err-bg; } .k { font-weight: bold; } .o { font-weight: bold; } - .cm { color: #998; font-style: italic; } - .cp { color: #999; font-weight: bold; } - .c1 { color: #998; font-style: italic; } - .cs { color: #999; font-weight: bold; font-style: italic; } - .gd { color: #000; background-color: #fdd; } - .gd .x { color: #000; background-color: #faa; } + .cm { color: $white-cm; font-style: italic; } + .cp { color: $white-cp; font-weight: bold; } + .c1 { color: $white-c1; font-style: italic; } + .cs { color: $white-cs; font-weight: bold; font-style: italic; } + .gd { color: $white-gd; background-color: $white-gd-bg; } + .gd .x { color: $white-gd-x; background-color: $white-gd-x-bg; } .ge { font-style: italic; } - .gr { color: #a00; } - .gh { color: #999; } - .gi { color: #000; background-color: #dfd; } - .gi .x { color: #000; background-color: #afa; } - .go { color: #888; } - .gp { color: #555; } + .gr { color: $white-gr; } + .gh { color: $white-gh; } + .gi { color: $white-gi; background-color: $white-gi-bg; } + .gi .x { color: $white-gi-x; background-color: $white-gi-x-bg; } + .go { color: $white-go; } + .gp { color: $white-gp; } .gs { font-weight: bold; } - .gu { color: #800080; font-weight: bold; } - .gt { color: #a00; } + .gu { color: $white-gu; font-weight: bold; } + .gt { color: $white-gt; } .kc { font-weight: bold; } .kd { font-weight: bold; } .kn { font-weight: bold; } .kp { font-weight: bold; } .kr { font-weight: bold; } - .kt { color: #458; font-weight: bold; } - .m { color: #099; } - .s { color: #d14; } - .n { color: #333; } - .na { color: teal; } - .nb { color: #0086b3; } - .nc { color: #458; font-weight: bold; } - .no { color: teal; } - .ni { color: purple; } - .ne { color: #900; font-weight: bold; } - .nf { color: #900; font-weight: bold; } - .nn { color: #555; } - .nt { color: navy; } - .nv { color: teal; } + .kt { color: $white-kt; font-weight: bold; } + .m { color: $white-m; } + .s { color: $white-s; } + .n { color: $white-n; } + .na { color: $white-na; } + .nb { color: $white-nb; } + .nc { color: $white-nc; font-weight: bold; } + .no { color: $white-no; } + .ni { color: $white-ni; } + .ne { color: $white-ne; font-weight: bold; } + .nf { color: $white-nf; font-weight: bold; } + .nn { color: $white-nn; } + .nt { color: $white-nt; } + .nv { color: $white-nv; } .ow { font-weight: bold; } - .w { color: #bbb; } - .mf { color: #099; } - .mh { color: #099; } - .mi { color: #099; } - .mo { color: #099; } - .sb { color: #d14; } - .sc { color: #d14; } - .sd { color: #d14; } - .s2 { color: #d14; } - .se { color: #d14; } - .sh { color: #d14; } - .si { color: #d14; } - .sx { color: #d14; } - .sr { color: #009926; } - .s1 { color: #d14; } - .ss { color: #990073; } - .bp { color: #999; } - .vc { color: teal; } - .vg { color: teal; } - .vi { color: teal; } - .il { color: #099; } - .gc { color: #999; background-color: #eaf2f5; } + .w { color: $white-w; } + .mf { color: $white-mf; } + .mh { color: $white-mh; } + .mi { color: $white-mi; } + .mo { color: $white-mo; } + .sb { color: $white-sb; } + .sc { color: $white-sc; } + .sd { color: $white-sd; } + .s2 { color: $white-s2; } + .se { color: $white-se; } + .sh { color: $white-sh; } + .si { color: $white-si; } + .sx { color: $white-sx; } + .sr { color: $white-sr; } + .s1 { color: $white-s1; } + .ss { color: $white-ss; } + .bp { color: $white-bp; } + .vc { color: $white-vc; } + .vg { color: $white-vg; } + .vi { color: $white-vi; } + .il { color: $white-il; } + .gc { color: $white-gc-color; background-color: $white-gc-bg; } } diff --git a/app/assets/stylesheets/mailers/devise.scss b/app/assets/stylesheets/mailers/devise.scss index b2bce482fde..9f613710cf4 100644 --- a/app/assets/stylesheets/mailers/devise.scss +++ b/app/assets/stylesheets/mailers/devise.scss @@ -1,3 +1,5 @@ +@import "framework/variables"; + // NOTE: This stylesheet is for the exclusive use of the `devise_mailer` layout // used for Devise email templates, and _should not_ be included in any // application stylesheets. @@ -46,7 +48,7 @@ table { &#body { background-color: $message-background-color; - border: 1px solid #000; + border: 1px solid $black; border-radius: 4px; margin: 0 auto; width: 600px; diff --git a/app/assets/stylesheets/mailers/highlighted_diff_email.scss b/app/assets/stylesheets/mailers/highlighted_diff_email.scss new file mode 100644 index 00000000000..024b4df6bd0 --- /dev/null +++ b/app/assets/stylesheets/mailers/highlighted_diff_email.scss @@ -0,0 +1,207 @@ +@import "framework/variables"; + +// This file is largely copied from `highlight/white.scss`, but modified to +// avoid all descendant selectors (`table td`). This is because the CSS inlining +// we use performs dramatically worse on descendant selectors than the +// alternatives. +// <https://gitlab.com/gitlab-org/gitlab-ee/issues/490#note_12283632> +// +// DO NOT ADD ANY DESCENDANT SELECTORS TO THIS FILE. Instead, use (in order of +// preference): plain class selectors, type (element name) selectors, or +// explicit child selectors. + +/* +* Highlighted Diff Email Syntax Colors +*/ +$highlighted-highlight-word: #fafe3d; +$highlighted-hll-bg: #f8f8f8; +$highlighted-c: #998; +$highlighted-err: #a61717; +$highlighted-err-bg: #e3d2d2; +$highlighted-cm: #998; +$highlighted-cp: #999; +$highlighted-c1: #998; +$highlighted-cs: #999; +$highlighted-gd: #000; +$highlighted-gd-bg: #fdd; +$highlighted-gd-x: #000; +$highlighted-gd-x-bg: #faa; +$highlighted-gr: #a00; +$highlighted-gh: #999; +$highlighted-gi: #000; +$highlighted-gi-bg: #dfd; +$highlighted-gi-x: #000; +$highlighted-gi-x-bg: #afa; +$highlighted-go: #888; +$highlighted-gp: #555; +$highlighted-gu: #800080; +$highlighted-gt: #a00; +$highlighted-kt: #458; +$highlighted-m: #099; +$highlighted-s: #d14; +$highlighted-n: #333; +$highlighted-na: teal; +$highlighted-nb: #0086b3; +$highlighted-nc: #458; +$highlighted-no: teal; +$highlighted-ni: purple; +$highlighted-ne: #900; +$highlighted-nf: #900; +$highlighted-nn: #555; +$highlighted-nt: navy; +$highlighted-nv: teal; +$highlighted-w: #bbb; +$highlighted-mf: #099; +$highlighted-mh: #099; +$highlighted-mi: #099; +$highlighted-mo: #099; +$highlighted-sb: #d14; +$highlighted-sc: #d14; +$highlighted-sd: #d14; +$highlighted-s2: #d14; +$highlighted-se: #d14; +$highlighted-sh: #d14; +$highlighted-si: #d14; +$highlighted-sx: #d14; +$highlighted-sr: #009926; +$highlighted-s1: #d14; +$highlighted-ss: #990073; +$highlighted-bp: #999; +$highlighted-vc: teal; +$highlighted-vg: teal; +$highlighted-vi: teal; +$highlighted-il: #099; +$highlighted-gc: #999; +$highlighted-gc-bg: #eaf2f5; + +.code { + background-color: $white-light; + font-family: monospace; + font-size: $code_font_size; + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + + > tr { + line-height: $code_line_height; + } +} + +.diff-line-num { + padding: 0 5px; + text-align: right; + width: 35px; + background-color: $background-color; + color: $black-transparent; + border-right: 1px solid $table-border-gray; + + &.old { + background-color: $line-number-old; + border-right-color: $line-removed-dark; + } + + &.new { + background-color: $line-number-new; + border-right-color: $line-added-dark; + } +} + +.line_content { + padding-left: 0.5em; + padding-right: 0.5em; + + &.old { + background-color: $line-removed; + + > .line > span.idiff, + > .line > span > span.idiff { + background-color: $line-removed-dark; + } + } + + &.new { + background-color: $line-added; + + > .line > span.idiff, + > .line > span > span.idiff { + background-color: $line-added-dark; + } + } + + &.match { + color: $black-transparent; + background-color: $match-line; + } +} + +pre { + margin: 0; +} + +span.highlight_word { + background-color: $highlighted-highlight-word !important; +} + +.hll { background-color: $highlighted-hll-bg; } +.c { color: $highlighted-c; font-style: italic; } +.err { color: $highlighted-err; background-color: $highlighted-err-bg; } +.k { font-weight: bold; } +.o { font-weight: bold; } +.cm { color: $highlighted-cm; font-style: italic; } +.cp { color: $highlighted-cp; font-weight: bold; } +.c1 { color: $highlighted-c1; font-style: italic; } +.cs { color: $highlighted-cs; font-weight: bold; font-style: italic; } +.gd { color: $highlighted-gd; background-color: $highlighted-gd-bg; } +.gd .x { color: $highlighted-gd; background-color: $highlighted-gd-x-bg; } +.ge { font-style: italic; } +.gr { color: $highlighted-gr; } +.gh { color: $highlighted-gh; } +.gi { color: $highlighted-gi; background-color: $highlighted-gi-bg; } +.gi .x { color: $highlighted-gi; background-color: $highlighted-gi-x-bg; } +.go { color: $highlighted-go; } +.gp { color: $highlighted-gp; } +.gs { font-weight: bold; } +.gu { color: $highlighted-gu; font-weight: bold; } +.gt { color: $highlighted-gt; } +.kc { font-weight: bold; } +.kd { font-weight: bold; } +.kn { font-weight: bold; } +.kp { font-weight: bold; } +.kr { font-weight: bold; } +.kt { color: $highlighted-kt; font-weight: bold; } +.m { color: $highlighted-m; } +.s { color: $highlighted-s; } +.n { color: $highlighted-n; } +.na { color: $highlighted-na; } +.nb { color: $highlighted-nb; } +.nc { color: $highlighted-nc; font-weight: bold; } +.no { color: $highlighted-no; } +.ni { color: $highlighted-ni; } +.ne { color: $highlighted-ne; font-weight: bold; } +.nf { color: $highlighted-nf; font-weight: bold; } +.nn { color: $highlighted-nn; } +.nt { color: $highlighted-nt; } +.nv { color: $highlighted-nv; } +.ow { font-weight: bold; } +.w { color: $highlighted-w; } +.mf { color: $highlighted-mf; } +.mh { color: $highlighted-mh; } +.mi { color: $highlighted-mi; } +.mo { color: $highlighted-mo; } +.sb { color: $highlighted-sb; } +.sc { color: $highlighted-sc; } +.sd { color: $highlighted-sd; } +.s2 { color: $highlighted-s2; } +.se { color: $highlighted-se; } +.sh { color: $highlighted-sh; } +.si { color: $highlighted-si; } +.sx { color: $highlighted-sx; } +.sr { color: $highlighted-sr; } +.s1 { color: $highlighted-s1; } +.ss { color: $highlighted-ss; } +.bp { color: $highlighted-bp; } +.vc { color: $highlighted-vc; } +.vg { color: $highlighted-vg; } +.vi { color: $highlighted-vi; } +.il { color: $highlighted-il; } +.gc { color: $highlighted-gc; background-color: $highlighted-gc-bg; } diff --git a/app/assets/stylesheets/mailers/repository_push_email.scss b/app/assets/stylesheets/mailers/repository_push_email.scss deleted file mode 100644 index 8d1a6020ca4..00000000000 --- a/app/assets/stylesheets/mailers/repository_push_email.scss +++ /dev/null @@ -1,143 +0,0 @@ -@import "framework/variables"; - -// This file is largely copied from `highlight/white.scss`, but modified to -// avoid all descendant selectors (`table td`). This is because the CSS inlining -// we use performs dramatically worse on descendant selectors than the -// alternatives. -// <https://gitlab.com/gitlab-org/gitlab-ee/issues/490#note_12283632> -// -// DO NOT ADD ANY DESCENDANT SELECTORS TO THIS FILE. Instead, use (in order of -// preference): plain class selectors, type (element name) selectors, or -// explicit child selectors. - -.code { - background-color: #fff; - font-family: monospace; - font-size: $code_font_size; - -premailer-cellpadding: 0; - -premailer-cellspacing: 0; - -premailer-width: 100%; - - > tr { - line-height: $code_line_height; - } -} - -.diff-line-num { - padding: 0 5px; - text-align: right; - width: 35px; - background-color: $background-color; - color: $black-transparent; - border-right: 1px solid $table-border-gray; - - &.old { - background-color: $line-number-old; - border-right-color: $line-removed-dark; - } - - &.new { - background-color: $line-number-new; - border-right-color: $line-added-dark; - } -} - -.line_content { - padding-left: 0.5em; - padding-right: 0.5em; - - &.old { - background-color: $line-removed; - - > .line > span.idiff, - > .line > span > span.idiff { - background-color: $line-removed-dark; - } - } - - &.new { - background-color: $line-added; - - > .line > span.idiff, - > .line > span > span.idiff { - background-color: $line-added-dark; - } - } - - &.match { - color: $black-transparent; - background-color: $match-line; - } -} - -pre { - margin: 0; -} - -span.highlight_word { - background-color: #fafe3d !important; -} - -.hll { background-color: #f8f8f8; } -.c { color: #998; font-style: italic; } -.err { color: #a61717; background-color: #e3d2d2; } -.k { font-weight: bold; } -.o { font-weight: bold; } -.cm { color: #998; font-style: italic; } -.cp { color: #999; font-weight: bold; } -.c1 { color: #998; font-style: italic; } -.cs { color: #999; font-weight: bold; font-style: italic; } -.gd { color: #000; background-color: #fdd; } -.gd .x { color: #000; background-color: #faa; } -.ge { font-style: italic; } -.gr { color: #a00; } -.gh { color: #999; } -.gi { color: #000; background-color: #dfd; } -.gi .x { color: #000; background-color: #afa; } -.go { color: #888; } -.gp { color: #555; } -.gs { font-weight: bold; } -.gu { color: #800080; font-weight: bold; } -.gt { color: #a00; } -.kc { font-weight: bold; } -.kd { font-weight: bold; } -.kn { font-weight: bold; } -.kp { font-weight: bold; } -.kr { font-weight: bold; } -.kt { color: #458; font-weight: bold; } -.m { color: #099; } -.s { color: #d14; } -.n { color: #333; } -.na { color: teal; } -.nb { color: #0086b3; } -.nc { color: #458; font-weight: bold; } -.no { color: teal; } -.ni { color: purple; } -.ne { color: #900; font-weight: bold; } -.nf { color: #900; font-weight: bold; } -.nn { color: #555; } -.nt { color: navy; } -.nv { color: teal; } -.ow { font-weight: bold; } -.w { color: #bbb; } -.mf { color: #099; } -.mh { color: #099; } -.mi { color: #099; } -.mo { color: #099; } -.sb { color: #d14; } -.sc { color: #d14; } -.sd { color: #d14; } -.s2 { color: #d14; } -.se { color: #d14; } -.sh { color: #d14; } -.si { color: #d14; } -.sx { color: #d14; } -.sr { color: #009926; } -.s1 { color: #d14; } -.ss { color: #990073; } -.bp { color: #999; } -.vc { color: teal; } -.vg { color: teal; } -.vi { color: teal; } -.il { color: #099; } -.gc { color: #999; background-color: #eaf2f5; } diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss index ced8c4a9907..ddc382362f7 100644 --- a/app/assets/stylesheets/notify.scss +++ b/app/assets/stylesheets/notify.scss @@ -1,3 +1,5 @@ +@import "framework/variables"; + img { max-width: 100%; height: auto; @@ -5,12 +7,12 @@ img { p.details { font-style: italic; - color: #777; + color: $notify-details; } .footer > p { font-size: small; - color: #777; + color: $notify-footer; } pre.commit-message { @@ -21,10 +23,10 @@ pre.commit-message { text-decoration: none; > .new-file { - color: #090; + color: $notify-new-file; } > .deleted-file { - color: #b00; + color: $notify-deleted-file; } } diff --git a/app/assets/stylesheets/pages/admin.scss b/app/assets/stylesheets/pages/admin.scss deleted file mode 100644 index 14812e171fd..00000000000 --- a/app/assets/stylesheets/pages/admin.scss +++ /dev/null @@ -1,168 +0,0 @@ -/** - * Admin area - * - */ -.admin-dashboard { - .data { - a { - h1 { - line-height: 48px; - font-size: 48px; - padding: 20px; - text-align: center; - font-weight: normal; - } - } - } - - .str-truncated { - max-width: 60%; - } -} - -.admin-filter form { - .select2-container { - width: 100%; - } - - .controls { - margin-left: 130px; - } - - .form-actions { - padding-left: 130px; - background: #fff; - } - - .visibility-levels { - .controls { - margin-bottom: 9px; - } - - i { - color: inherit; - } - } -} - -.broadcast-messages { - .message { - line-height: 2; - } -} - -.broadcast-message { - @extend .alert-warning; - padding: 10px; - text-align: center; - - > div, - p { - display: inline; - margin: 0; - - a { - color: inherit; - text-decoration: underline; - } - } -} - -.broadcast-message-preview { - @extend .broadcast-message; - margin-bottom: 20px; -} - -// Users List - -.users-list { - .user-row { - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - white-space: nowrap; - } - - .user-details { - flex: 1 1 auto; - overflow: hidden; - padding-right: 8px; - } - - .user-name { - display: inline-block; - font-weight: 600; - } - - .user-name, - .user-email { - overflow: hidden; - text-overflow: ellipsis; - } - - .dropdown { - .btn-block { - margin-bottom: 0; - line-height: inherit; - } - } - - .label-default { - color: $btn-transparent-color; - } -} - -.abuse-reports { - .table { - table-layout: fixed; - } - - .subheading { - padding-bottom: $gl-padding; - } - - .message { - word-wrap: break-word; - } - - .btn { - white-space: normal; - padding: $gl-btn-padding; - } - - th { - width: 15%; - - &.wide { - width: 55%; - } - } - - @media (max-width: $screen-sm-max) { - th { - width: 100%; - } - - td { - width: 100%; - float: left; - } - } - - .no-reports { - .emoji-icon { - margin-left: $btn-side-margin; - margin-top: 3px; - } - - span { - font-size: 19px; - } - } -} - -.admin-builds-table { - .ci-table td:last-child { - min-width: 120px; - } -} diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index 4327f8bf640..0d9cf679e7c 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -145,7 +145,7 @@ .board-blank-state { height: calc(100% - 49px); padding: $gl-padding; - background-color: #fff; + background-color: $white-light; } .board-blank-state-list { @@ -191,9 +191,9 @@ .card { position: relative; padding: 10px $gl-padding; - background: #fff; + background: $white-light; border-radius: $border-radius-default; - box-shadow: 0 1px 2px rgba(186, 186, 186, 0.5); + box-shadow: 0 1px 2px $issue-boards-card-shadow; list-style: none; &:not(:last-child) { @@ -325,7 +325,6 @@ } .issuable-header-text { - width: 100%; padding-right: 35px; > strong { diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 48f11eb2552..dcc13f6d74a 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -1,7 +1,7 @@ .build-page { pre.trace { - background: #111; - color: #fff; + background: $builds-trace-bg; + color: $white-light; font-family: $monospace_font; white-space: pre-wrap; overflow: auto; @@ -64,6 +64,7 @@ @media (max-width: $screen-sm-max) { padding-right: 40px; + margin-top: 6px; .btn-inverted { display: none; diff --git a/app/assets/stylesheets/pages/ci_projects.scss b/app/assets/stylesheets/pages/ci_projects.scss index 87c453a7a27..d1cd1e5d848 100644 --- a/app/assets/stylesheets/pages/ci_projects.scss +++ b/app/assets/stylesheets/pages/ci_projects.scss @@ -1,7 +1,7 @@ .ci-body { .project-title { margin: 0; - color: #444; + color: $common-gray-dark; font-size: 20px; line-height: 1.5; } diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index ddc9d0e2b1a..bf656d0e28e 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -5,7 +5,7 @@ .commit-author, .commit-committer { display: block; - color: #999; + color: $commit-committer-color; font-weight: normal; font-style: italic; } @@ -113,17 +113,17 @@ overflow: hidden; // See https://gitlab.com/gitlab-org/gitlab-ce/issues/13987 .max-width-marker { width: 72ch; - color: rgba(0, 0, 0, 0.0); + color: $commit-max-width-marker-color; font-family: inherit; left: $left; height: 100%; - border-right: 1px solid mix($input-border, white); + border-right: 1px solid mix($input-border, $white-light); position: absolute; z-index: 1; } > textarea { - background-color: rgba(0, 0, 0, 0.0); + background-color: $commit-message-text-area-bg; font-family: inherit; padding-left: $left; position: relative; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 83ffa0e1d39..c29b5fdea78 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -8,8 +8,8 @@ .commit-header { padding: 5px 10px; background-color: $background-color; - border-top: 1px solid #eee; - border-bottom: 1px solid #eee; + border-top: 1px solid $gray-darker; + border-bottom: 1px solid $gray-darker; font-size: 14px; &:first-child { @@ -94,7 +94,7 @@ } &:not(:last-child) { - border-bottom: 1px solid #eee; + border-bottom: 1px solid $gray-darker; } a, @@ -201,7 +201,7 @@ .bar { position: absolute; height: 4px; - background-color: #ccc; + background-color: $divergence-graph-bar-bg; } .bar-behind { @@ -218,7 +218,7 @@ padding-top: 6px; padding-bottom: 0; font-size: 12px; - color: #333; + color: $gl-title-color; display: block; } @@ -239,6 +239,6 @@ height: 18px; margin: 5px 0 0; float: left; - background-color: #ccc; + background-color: $divergence-graph-separator-bg; } } diff --git a/app/assets/stylesheets/pages/confirmation.scss b/app/assets/stylesheets/pages/confirmation.scss deleted file mode 100644 index 81e5cee240d..00000000000 --- a/app/assets/stylesheets/pages/confirmation.scss +++ /dev/null @@ -1,32 +0,0 @@ -.well-confirmation { - margin-bottom: 20px; - border-bottom: 1px solid #eee; - - > h1, - h2, - h3, - h4, - h5, - h6 { - font-weight: 400; - } - - .lead { - margin-bottom: 20px; - } - - ul, - ol { - padding-left: 0; - } - - li { - list-style-type: none; - } -} - -.confirmation-content { - a { - color: $md-link-color; - } -} diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index 498a8f68e49..57146e1fccd 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -53,7 +53,7 @@ border-bottom: none; position: relative; - @media (max-width: $screen-sm-min) { + @media (max-width: $screen-xs-max) { padding: 6px 0 24px; } } @@ -61,7 +61,7 @@ .column { text-align: center; - @media (max-width: $screen-sm-min) { + @media (max-width: $screen-xs-max) { padding: 15px 0; } @@ -78,7 +78,7 @@ } &:last-child { - @media (max-width: $screen-sm-min) { + @media (max-width: $screen-xs-max) { text-align: center; } } @@ -109,7 +109,7 @@ &.title { line-height: 19px; - font-size: 15px; + font-size: 14px; font-weight: 600; color: $gl-title-color; } @@ -141,9 +141,9 @@ .dismiss-icon { position: absolute; - right: $cycle-analytics-box-padding; + right: $cycle-analytics-dismiss-icon-color; cursor: pointer; - color: #b2b2b2; + color: $cycle-analytics-dismiss-icon-color; } .svg-container { @@ -156,7 +156,7 @@ } .inner-content { - @media (max-width: $screen-sm-min) { + @media (max-width: $screen-xs-max) { padding: 0 28px; text-align: center; } diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss deleted file mode 100644 index 016bab104eb..00000000000 --- a/app/assets/stylesheets/pages/dashboard.scss +++ /dev/null @@ -1,43 +0,0 @@ -.dashboard { - .side { - .panel { - .panel-heading { - background: $background-color; - border-top-left-radius: 0; - } - - border-top-left-radius: 0; - } - } -} - -.dashboard-search-filter { - padding: 5px; - - .search-text-input { - float: left; - @extend .col-md-2; - } - - .btn { - margin-left: 5px; - float: left; - } -} - -.project-access-icon { - margin-left: 10px; - float: left; - margin-right: 15px; - margin-bottom: 15px; - - i { - color: #888; - } -} - -.dash-project-access-icon { - float: left; - margin-right: 5px; - width: 16px; -} diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 0f0c0abe7ae..80baebd5ea3 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -1,16 +1,16 @@ .detail-page-header { padding: $gl-padding-top 0; border-bottom: 1px solid $border-color; - color: #5c5d5e; + color: $gl-text-color-dark; font-size: 16px; line-height: 34px; .author { - color: #5c5d5e; + color: $gl-text-color-dark; } .identifier { - color: #5c5d5e; + color: $gl-text-color-dark; } .issue_created_ago, diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 99fdea15218..737f6e0f4be 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -14,7 +14,7 @@ background: $background-color; border-bottom: 1px solid $border-color; padding: 10px 16px; - color: #555; + color: $gl-diff-text-color; z-index: 10; border-radius: 3px 3px 0 0; @@ -24,7 +24,7 @@ display: block; .file-mode { - color: #777; + color: $file-mode-changed; } } @@ -49,8 +49,8 @@ .diff-content { overflow: auto; overflow-y: hidden; - background: #fff; - color: #333; + background: $white-light; + color: $gl-title-color; border-radius: 0 0 3px 3px; .unfold { @@ -59,7 +59,7 @@ .file-mode-changed { padding: 10px; - color: #777; + color: $file-mode-changed; } .suppressed-container { @@ -172,7 +172,7 @@ } .image { - background: #ddd; + background: $diff-image-bg; text-align: center; padding: 30px; @@ -182,13 +182,13 @@ .frame { display: inline-block; - background-color: #fff; + background-color: $white-light; line-height: 0; img { - border: 1px solid #fff; - background-image: linear-gradient(45deg, #e5e5e5 25%, transparent 25%, transparent 75%, #e5e5e5 75%, #e5e5e5 100%), - linear-gradient(45deg, #e5e5e5 25%, transparent 25%, transparent 75%, #e5e5e5 75%, #e5e5e5 100%); + border: 1px solid $white-light; + background-image: linear-gradient(45deg, $diff-image-img-bg 25%, transparent 25%, transparent 75%, $diff-image-img-bg 75%, $diff-image-img-bg 100%), + linear-gradient(45deg, $diff-image-img-bg 25%, transparent 25%, transparent 75%, $diff-image-img-bg 75%, $diff-image-img-bg 100%); background-size: 10px 10px; background-position: 0 0, 5px 5px; max-width: 100%; @@ -206,7 +206,7 @@ .image-info { font-size: 12px; margin: 5px 0 0; - color: grey; + color: $diff-image-info-color; } .view.swipe { @@ -220,7 +220,7 @@ .swipe-wrap { overflow: hidden; - border-left: 1px solid #999; + border-left: 1px solid $diff-swipe-border; position: absolute; display: block; top: 13px; @@ -350,7 +350,7 @@ .view-modes { padding: 10px; text-align: center; - background: #eee; + background: $gray-darker; ul, li { @@ -361,8 +361,8 @@ } li { - color: grey; - border-left: 1px solid #c1c1c1; + color: $diff-view-modes-color; + border-left: 1px solid $diff-view-modes-border; padding: 0 12px 0 16px; cursor: pointer; @@ -380,7 +380,7 @@ } cursor: default; - color: #333; + color: $gl-title-color; } &.disabled { diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index 778126bcfb7..6cde9c592de 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -14,10 +14,10 @@ } .cancel-btn { - color: #b94a48; + color: $editor-cancel-color; &:hover { - color: #b94a48; + color: $editor-cancel-color; } } diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index 4b382e8adaf..e716f24c8e2 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -1,6 +1,8 @@ -.deployments-container { - width: 100%; - overflow: auto; +@media (max-width: $screen-md-max) { + .deployments-container { + width: 100%; + overflow: auto; + } } .environments-list-loading { @@ -8,7 +10,7 @@ font-size: 34px; } -@media (max-width: $screen-sm-min) { +@media (max-width: $screen-xs-max) { .environments-container { width: 100%; overflow: auto; diff --git a/app/assets/stylesheets/pages/errors.scss b/app/assets/stylesheets/pages/errors.scss deleted file mode 100644 index 11309817d31..00000000000 --- a/app/assets/stylesheets/pages/errors.scss +++ /dev/null @@ -1,16 +0,0 @@ -.error-page { - max-width: 400px; - margin: 0 auto; - - h1, - h2, - h3 { - text-align: center; - } - - h1 { - font-size: 56px; - line-height: 100px; - font-weight: 300; - } -} diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 3004959ff7b..dc67d411c71 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -62,7 +62,7 @@ border: none; background: $gray-light; border-radius: 0; - color: #777; + color: $events-pre-color; margin: 0 20px; overflow: hidden; } @@ -80,7 +80,7 @@ } .event-note-icon { - color: #777; + color: $events-pre-color; float: left; font-size: $gl-font-size; line-height: 16px; @@ -91,7 +91,7 @@ .event_icon { position: relative; float: right; - border: 1px solid #eee; + border: 1px solid $gray-darker; padding: 5px; border-radius: 5px; background: $gray-light; @@ -170,7 +170,7 @@ .event-body { margin: 0; - border-left: 2px solid #ddd; + border-left: 2px solid $events-body-border; padding-left: 10px; } @@ -186,4 +186,3 @@ display: none; } } - diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss index f7f9a9bb770..84da9180f93 100644 --- a/app/assets/stylesheets/pages/graph.scss +++ b/app/assets/stylesheets/pages/graph.scss @@ -2,15 +2,15 @@ border: 1px solid $border-color; .controls { - color: #888; + color: $project-network-controls-color; font-size: 14px; padding: 5px; border-bottom: 1px solid $border-color; - background: #eee; + background: $gray-darker; } .network-graph { - background: #fff; + background: $white-light; height: 500px; overflow-y: scroll; overflow-x: hidden; @@ -20,15 +20,14 @@ .graphs { .graph-author-email { float: right; - color: #777; + color: $graph-author-email-color; } .graph-additions { - color: #4a2; + color: $gl-text-green; } .graph-deletions { - color: #d12f19; + color: $gl-text-red; } } - diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 57d028cec8c..a9af7af59e2 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -49,14 +49,14 @@ padding: 50px 100px; overflow: hidden; - @media (max-width: $screen-md-min) { + @media (max-width: $screen-sm-max) { padding: 50px 0; } svg { float: right; - @media (max-width: $screen-md-min) { + @media (max-width: $screen-sm-max) { float: none; display: block; width: 250px; @@ -71,7 +71,7 @@ width: 460px; margin-top: 120px; - @media (max-width: $screen-md-min) { + @media (max-width: $screen-sm-max) { float: none; margin-top: 60px; width: auto; diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss index a48b4c65db8..e2e644dc23b 100644 --- a/app/assets/stylesheets/pages/help.scss +++ b/app/assets/stylesheets/pages/help.scss @@ -9,7 +9,7 @@ li { line-height: 24px; - color: #888; + color: $document-index-color; a { margin-right: 3px; @@ -20,7 +20,7 @@ .shortcut-mappings { font-size: 12px; - color: #555; + color: $help-shortcut-mapping-color; tbody:first-child tr:first-child { padding-top: 0; @@ -29,7 +29,7 @@ th { padding-top: 15px; line-height: 1.5; - color: #333; + color: $help-shortcut-header-color; text-align: left; } @@ -42,7 +42,7 @@ .shortcut { padding-right: 10px; - color: #999; + color: $help-shortcut-color; text-align: right; white-space: nowrap; } diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 773155fe80a..407c0afbac8 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -30,6 +30,7 @@ .color-label { padding: 6px 10px; + border-radius: $label-border-radius; } } @@ -132,7 +133,7 @@ display: none; } - .btn-clipboard { + .btn-clipboard:hover { color: $gl-gray; } } @@ -233,7 +234,11 @@ width: 100%; text-align: center; padding-bottom: 10px; - color: #999; + color: $issuable-sidebar-color; + + &:hover { + color: $gl-gray; + } span { display: block; @@ -244,15 +249,17 @@ display: none; } + .avatar:hover { + border-color: $issuable-avatar-hover-border; + } + .btn-clipboard { border: none; + color: $issuable-clipboard-color; &:hover { background: transparent; - } - - i { - color: #999; + color: $gl-gray; } } } diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index eb171195309..3b47f99df2c 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -82,18 +82,18 @@ ul.related-merge-requests > li { .merge-request, .issue { &.today { - background: #f3fff2; - border-color: #e1e8d5; + background: $issues-today-bg; + border-color: $issues-today-border; } &.closed { background: $gray-light; - border-color: #e5e5e5; + border-color: $issues-border; } &.merged { background: $gray-light; - border-color: #e5e5e5; + border-color: $issues-border; } } diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index e39ce19f846..25c91203ff4 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -104,7 +104,8 @@ } .color-label { - padding: 3px 4px; + padding: 3px 7px; + border-radius: $label-border-radius; } .dropdown-labels-error { @@ -198,7 +199,7 @@ } .label-remove { - border-left: 1px solid rgba(0, 0, 0, .1); + border-left: 1px solid $label-remove-border; z-index: 3; } diff --git a/app/assets/stylesheets/pages/lint.scss b/app/assets/stylesheets/pages/lint.scss index 8290519dc25..a7c80dce424 100644 --- a/app/assets/stylesheets/pages/lint.scss +++ b/app/assets/stylesheets/pages/lint.scss @@ -1,11 +1,11 @@ .ci-body { .incorrect-syntax { - font-size: 19px; - color: red; + font-size: 18px; + color: $lint-incorrect-color; } .correct-syntax { - font-size: 19px; - color: #47a447; + font-size: 18px; + color: $lint-correct-color; } } diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 54c89d75e94..dd27a06fcd2 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -18,7 +18,7 @@ p { font-size: 18px; - color: #888; + color: $login-brand-holder-color; } h1:first-child { @@ -174,7 +174,7 @@ .form-control { &:active, &:focus { - background-color: #fff; + background-color: $white-light; } } @@ -195,7 +195,7 @@ h2 { margin-top: 0; font-size: 14px; - color: #a00; + color: $login-devise-error-color; } } } @@ -254,4 +254,3 @@ } } } - diff --git a/app/assets/stylesheets/pages/members.scss b/app/assets/stylesheets/pages/members.scss index 756efa9c7fa..5f3bbb40ba0 100644 --- a/app/assets/stylesheets/pages/members.scss +++ b/app/assets/stylesheets/pages/members.scss @@ -54,6 +54,10 @@ @media (min-width: $screen-sm-min) { width: 50%; } + + .dropdown-menu-toggle { + width: 100%; + } } .member-access-text { diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index 19ab198c2e7..7a90713dd3f 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -1,3 +1,5 @@ +// Disabled to use the color map for creating color schemes +// scss-lint:disable ColorVariable $colors: ( white_header_head_neutral : #e1fad7, white_line_head_neutral : #effdec, @@ -98,6 +100,7 @@ $colors: ( solarized_dark_header_not_chosen : rgba(#839496, .25), solarized_dark_line_not_chosen : rgba(#839496, .15) ); +// scss-lint:enable ColorVariable @mixin color-scheme($color) { @@ -228,14 +231,15 @@ $colors: ( position: absolute; right: 10px; padding: 0; - color: #fff; + outline: none; + color: $white-light; width: 75px; // static width to make 2 buttons have same width height: 19px; } } .btn-success .fa-spinner { - color: #fff; + color: $white-light; } .editor-wrap { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index da1187af41c..6234779ac19 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -86,7 +86,7 @@ } .normal { - color: #5c5d5e; + color: $gl-text-color-dark; } .js-deployment-link { @@ -116,7 +116,7 @@ @media (max-width: $screen-xs-max) { h4 { - font-size: 15px; + font-size: 14px; } p { @@ -129,6 +129,11 @@ margin-bottom: 4px; } + .btn-grouped { + float: none; + margin-right: 0; + } + .accept-action { width: 100%; text-align: center; @@ -143,7 +148,7 @@ } .mr-widget-footer { - border-top: 1px solid #eee; + border-top: 1px solid $gray-darker; } .ci-coverage { diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index 8843d1463db..dfc6079bd15 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -123,7 +123,7 @@ padding: 20px 0; } -@media (max-width: $screen-sm-min) { +@media (max-width: $screen-xs-max) { .milestone-actions { @include clearfix(); padding-top: $gl-vert-padding; diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 16ddef481bd..c35d71f9e7b 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -111,7 +111,7 @@ text-align: center; font-size: 13px; - @media (max-width: $screen-md-min) { + @media (max-width: $screen-sm-max) { // On smaller devices the warning becomes the fourth item in the list, // rather than centering, and grows to span the full width of the // comment area. @@ -129,10 +129,10 @@ .note-edit-form { display: none; - font-size: 15px; + font-size: 14px; .md-area { - background-color: #fff; + background-color: $white-light; } } diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index e66c1f8d072..10eb3d4203e 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -3,9 +3,9 @@ */ @-webkit-keyframes targe3-note { - from { background: #fffff0; } - 50% { background: #ffffd3; } - to { background: #fffff0; } + from { background: $note-targe3-outside; } + 50% { background: $note-targe3-inside; } + to { background: $note-targe3-outside; } } ul.notes { @@ -90,14 +90,14 @@ ul.notes { } &.system-note-commit-list { - max-height: 63px; + max-height: 70px; overflow: hidden; display: block; ul { - margin: 3px 0 3px 15px !important; + margin: 3px 0 3px 16px !important; - li { + .gfm-commit { font-family: $monospace_font; font-size: 12px; } @@ -124,7 +124,7 @@ ul.notes { position: absolute; left: 0; bottom: 0; - background: linear-gradient(rgba($gray-light, 0.1) -100px, $white-light 100%); + background: linear-gradient(rgba($white-light, 0.1) -100px, $white-light 100%); } &.hide-shade { @@ -172,6 +172,10 @@ ul.notes { &.timeline-entry { padding: 14px 10px; } + + .system-note { + padding: 0; + } } &.is-editting { @@ -249,7 +253,7 @@ ul.notes { } .page-sidebar-pinned.right-sidebar-expanded { - @media (max-width: $screen-lg-min) { + @media (max-width: $screen-md-max) { .note-header { .note-headline-light { display: block; @@ -301,7 +305,7 @@ ul.notes { &.notes_line2 { text-align: center; padding: 10px 0; - border-left: 1px solid #ddd !important; + border-left: 1px solid $note-line2-border !important; } &.notes_content { @@ -409,7 +413,6 @@ ul.notes { .fa { color: $notes-action-color; position: relative; - top: 1px; font-size: 17px; } @@ -467,7 +470,7 @@ ul.notes { .add-diff-note { margin-top: -4px; border-radius: 40px; - background: #fff; + background: $white-light; padding: 4px; font-size: 16px; color: $gl-link-color; @@ -480,7 +483,7 @@ ul.notes { &:hover { background: $gl-info; - color: #fff; + color: $white-light; @include show-add-diff-note; } } diff --git a/app/assets/stylesheets/pages/notifications.scss b/app/assets/stylesheets/pages/notifications.scss index 94fbbef3c77..7d61390a439 100644 --- a/app/assets/stylesheets/pages/notifications.scss +++ b/app/assets/stylesheets/pages/notifications.scss @@ -1,5 +1,9 @@ .notification-list-item { line-height: 34px; + + .dropdown-menu { + @extend .dropdown-menu-align-right; + } } .notification { diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 0027d2caf22..6822f916cc5 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -37,12 +37,13 @@ } } -.content-list { - - &.pipelines, - &.builds-content-list { - width: 100%; - overflow: auto; +@media (max-width: $screen-md-max) { + .content-list { + &.pipelines, + &.builds-content-list { + width: 100%; + overflow: auto; + } } } @@ -280,6 +281,12 @@ } } +.admin-builds-table { + .ci-table td:last-child { + min-width: 120px; + } +} + // Pipeline visualization .toggle-pipeline-btn { @@ -660,10 +667,6 @@ min-width: 900px; } - .content-list.pipelines { - overflow: auto; - } - .stage { max-width: 100px; width: 100px; diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 6fab97a71aa..f8677f93fe0 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -180,7 +180,7 @@ .modal-dialog { width: 380px; - @media (max-width: $screen-sm-min) { + @media (max-width: $screen-xs-max) { width: auto; } @@ -261,4 +261,4 @@ table.u2f-registrations { td:not(:last-child) { border-right: solid 1px transparent; } -}
\ No newline at end of file +} diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 19a7a97ea0d..6e0f6b1cd81 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -8,7 +8,7 @@ .no-ssh-key-message, .project-limit-message { - background-color: #f28d35; + background-color: $project-limit-message-bg; margin-bottom: 0; } @@ -76,7 +76,7 @@ &.static-namespace { height: 35px; border-radius: 3px; - border: 1px solid #e5e5e5; + border: 1px solid $border-color; } &+ .select2 a { @@ -188,8 +188,12 @@ margin-left: 10px; } + .notification-dropdown .dropdown-menu { + @extend .dropdown-menu-align-right; + } + .download-button { - @media (max-width: $screen-lg-min) { + @media (max-width: $screen-md-max) { margin-left: 0; } } @@ -225,7 +229,7 @@ left: 0; margin-top: -6px; border-width: 7px 5px 7px 0; - border-right-color: #dce0e5; + border-right-color: $count-arrow-border; pointer-events: none; } @@ -240,7 +244,7 @@ left: 1px; margin-top: -9px; border-width: 10px 7px 10px 0; - border-right-color: #fff; + border-right-color: $white-light; pointer-events: none; } } @@ -248,7 +252,7 @@ .count { @include btn-gray; display: inline-block; - background: white; + background: $white-light; border-radius: 2px; border-width: 1px; border-style: solid; @@ -270,7 +274,7 @@ } &:hover { - background: #fff; + background: $white-light; } } } @@ -302,7 +306,7 @@ .option-descr { margin-left: 29px; - color: #54565b; + color: $project-option-descr-color; } } } @@ -310,7 +314,7 @@ .save-project-loader { margin-top: 50px; margin-bottom: 50px; - color: #555; + color: $save-project-loader-color; } .transfer-project .select2-container { @@ -373,7 +377,7 @@ a.deploy-project-label { > li + li::before { padding: 0 3px; - color: #999; + color: $project-breadcrumb-color; } a { @@ -521,7 +525,7 @@ a.deploy-project-label { .nav > li > a { padding: 0; background-color: transparent; - font-size: 15px; + font-size: 14px; line-height: 29px; color: $notes-light-color; @@ -549,20 +553,20 @@ a.deploy-project-label { } pre.light-well { - border-color: #f1f1f1; + border-color: $well-light-border; } .git-empty { margin: 0 7px 7px; h5 { - color: #5c5d5e; + color: $gl-text-color-dark; } .light-well { border-radius: 2px; - color: #5b6169; + color: $well-light-text-color; font-size: 13px; line-height: 1.6em; } @@ -716,7 +720,7 @@ pre.light-well { .form-control { @extend .monospace; - background: #fff; + background: $white-light; font-size: 14px; margin-left: -1px; cursor: auto; @@ -726,17 +730,17 @@ pre.light-well { .cannot-be-merged, .cannot-be-merged:hover { - color: #e62958; + color: $error-exclamation-point; margin-top: 2px; } .private-forks-notice .private-fork-icon { i:nth-child(1) { - color: #2aa056; + color: $project-private-forks-notice-odd; } i:nth-child(2) { - color: #fff; + color: $white-light; } } @@ -876,3 +880,11 @@ pre.light-well { pointer-events: none; } } + +.variables-table { + table-layout: fixed; + + .variable-key { + width: 30%; + } +} diff --git a/app/assets/stylesheets/pages/runners.scss b/app/assets/stylesheets/pages/runners.scss index 7b3878c91df..9b6ff237557 100644 --- a/app/assets/stylesheets/pages/runners.scss +++ b/app/assets/stylesheets/pages/runners.scss @@ -1,27 +1,27 @@ .runner-state { padding: 6px 12px; margin-right: 10px; - color: #fff; + color: $white-light; &.runner-state-shared { - background: #32b186; + background: $runner-state-shared-bg; } &.runner-state-specific { - background: #3498db; + background: $runner-state-specific-bg; } } .runner-status-online { - color: green; + color: $runner-status-online-color; } .runner-status-offline { - color: gray; + color: $runner-status-offline-color; } .runner-status-paused { - color: red; + color: $runner-status-paused-color; } .runner { diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss index 857eb76131a..ff13b86acf0 100644 --- a/app/assets/stylesheets/pages/snippets.scss +++ b/app/assets/stylesheets/pages/snippets.scss @@ -1,3 +1,13 @@ +.snippet-row { + .title { + margin-bottom: 2px; + } + + .snippet-filename { + padding: 0 2px; + } +} + .snippet-form-holder .file-holder .file-title { padding: 2px; } @@ -24,11 +34,17 @@ padding-bottom: $gl-padding; } +.snippet-header { + padding: $gl-padding 0; +} + .snippet-title { font-size: 24px; font-weight: 600; - padding: $gl-padding; - padding-left: 0; +} + +.snippet-edited-ago { + color: $gray-darkest; } .snippet-actions { diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss index 69288b31cc4..dfa4d033fb8 100644 --- a/app/assets/stylesheets/pages/stat_graph.scss +++ b/app/assets/stylesheets/pages/stat_graph.scss @@ -1,16 +1,16 @@ .tint-box { - background: #f3f3f3; + background: $stat-graph-common-bg; position: relative; margin-bottom: 10px; } .area { - fill: #1db34f; + fill: $stat-graph-area-fill; fill-opacity: 0.5; } .axis { - fill: #aaa; + fill: $stat-graph-axis-fill; font-size: 10px; } @@ -37,26 +37,26 @@ @include make-md-column(6); margin-top: 10px; - @media (max-width: $screen-sm-min) { + @media (max-width: $screen-xs-max) { width: 100%; } } .person .spark { display: block; - background: #f3f3f3; + background: $stat-graph-common-bg; width: 100%; } .person .area-contributor { - fill: #f17f49; + fill: $stat-graph-orange-fill; } } .selection rect { - fill: #333; + fill: $stat-graph-selection-fill; fill-opacity: 0.1; - stroke: #333; + stroke: $stat-graph-selection-stroke; stroke-width: 1px; stroke-opacity: 0.4; shape-rendering: crispedges; diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 4c258bae1f4..5084b466722 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -2,7 +2,7 @@ .ci-status { padding: 2px 7px; margin-right: 10px; - border: 1px solid #eee; + border: 1px solid $gray-darker; white-space: nowrap; border-radius: 4px; diff --git a/app/assets/stylesheets/pages/tags.scss b/app/assets/stylesheets/pages/tags.scss deleted file mode 100644 index 24ebd3e7cfa..00000000000 --- a/app/assets/stylesheets/pages/tags.scss +++ /dev/null @@ -1,7 +0,0 @@ -.tag-buttons { - line-height: 40px; - - .btn:not(.dropdown-toggle) { - margin-left: 10px; - } -} diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index b3aef2fdd32..508b30f3947 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -11,7 +11,7 @@ background: $todo-alert-blue; margin-left: -17px; font-size: 11px; - color: white; + color: $white-light; padding: 3px; padding-top: 1px; padding-bottom: 1px; @@ -81,7 +81,7 @@ word-wrap: break-word; .md { - color: #7f8fa4; + color: $gl-grayish-blue; font-size: $gl-font-size; .label { @@ -90,7 +90,7 @@ } p { - color: #5c5d5e; + color: $gl-text-color-dark; } } @@ -102,7 +102,7 @@ border: none; background: $gray-light; border-radius: 0; - color: #777; + color: $todo-body-pre-color; margin: 0 20px; overflow: hidden; } @@ -146,7 +146,7 @@ .todo-body { margin: 0; - border-left: 2px solid #ddd; + border-left: 2px solid $todo-body-border; padding-left: 10px; } } diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 2b836fa1f4a..20ad63be045 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -31,7 +31,7 @@ .last-commit { @include str-truncated(506px); - @media (min-width: $screen-sm-max) and (max-width: $screen-md-max) { + @media (min-width: $screen-md-min) and (max-width: $screen-md-max) { @include str-truncated(450px); } diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss index e73cecc92be..8c87bc3cafd 100644 --- a/app/assets/stylesheets/pages/ui_dev_kit.scss +++ b/app/assets/stylesheets/pages/ui_dev_kit.scss @@ -7,11 +7,11 @@ .example { &::before { content: "Example"; - color: #bbb; + color: $ui-dev-kit-example-color; } padding: 15px; - border: 1px dashed #ddd; + border: 1px dashed $ui-dev-kit-example-border; margin-bottom: 15px; } } diff --git a/app/assets/stylesheets/pages/votes.scss b/app/assets/stylesheets/pages/votes.scss deleted file mode 100644 index dc9a7d71e8b..00000000000 --- a/app/assets/stylesheets/pages/votes.scss +++ /dev/null @@ -1,4 +0,0 @@ -.votes-inline { - display: inline-block; - margin: 0 8px; -} diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss index dfaeba41cf6..b9f81533150 100644 --- a/app/assets/stylesheets/pages/wiki.scss +++ b/app/assets/stylesheets/pages/wiki.scss @@ -4,3 +4,128 @@ margin-right: auto; padding-right: 7px; } + +.wiki-page-header { + @extend .top-area; + position: relative; + + .wiki-page-title { + margin: 0; + font-size: 22px; + } + + .wiki-last-edit-by { + color: $gl-gray-light; + + strong { + color: $gl-text-color; + } + } + + .light { + font-weight: normal; + color: $gl-gray-light; + } + + .git-access-header { + padding: 16px 40px 11px 0; + line-height: 28px; + font-size: 18px; + } + + .git-clone-holder { + width: 100%; + padding-bottom: 40px; + } + + button.sidebar-toggle { + position: absolute; + right: 0; + top: 11px; + display: block; + } + + @media (min-width: $screen-sm-min) { + &.has-sidebar-toggle { + padding-right: 40px; + } + + .git-clone-holder { + width: 480px; + } + + .nav-controls { + width: auto; + min-width: 50%; + white-space: nowrap; + } + } + + @media (min-width: $screen-md-min) { + &.has-sidebar-toggle { + padding-right: 0; + } + + button.sidebar-toggle { + display: none; + } + } +} + +.wiki-git-access { + margin: $gl-padding 0; + + h3 { + font-size: 22px; + font-weight: normal; + margin-top: 1.4em; + } +} + +.right-sidebar.wiki-sidebar { + padding: $gl-padding 0; + + &.right-sidebar-collapsed { + display: none; + } + + .blocks-container { + padding: 0 $gl-padding; + } + + .block { + width: 100%; + } + + a { + color: $layout-link-gray; + + &:hover, + &.active { + color: $black; + } + } + + .active > a { + color: $black; + } + + ul.wiki-pages, + ul.wiki-pages li { + list-style: none; + padding: 0; + margin: 0; + } + + ul.wiki-pages li { + margin: 5px 0 10px; + } + + .wiki-sidebar-header { + padding: 0 $gl-padding $gl-padding; + + .gutter-toggle { + margin-top: 0; + } + } +} diff --git a/app/assets/stylesheets/pages/xterm.scss b/app/assets/stylesheets/pages/xterm.scss index 3fa7fa3d7e3..9f9d630978a 100644 --- a/app/assets/stylesheets/pages/xterm.scss +++ b/app/assets/stylesheets/pages/xterm.scss @@ -20,6 +20,266 @@ $l-cyan: #8abeb7; $l-white: $ci-text-color; + /* + * xterm colors + */ + $xterm-fg-0: $black; + $xterm-fg-1: #800000; + $xterm-fg-2: #008000; + $xterm-fg-3: #808000; + $xterm-fg-4: #000080; + $xterm-fg-5: #800080; + $xterm-fg-6: #008080; + $xterm-fg-7: #c0c0c0; + $xterm-fg-8: #808080; + $xterm-fg-9: #f00; + $xterm-fg-10: #0f0; + $xterm-fg-11: #ff0; + $xterm-fg-12: #00f; + $xterm-fg-13: #f0f; + $xterm-fg-14: #0ff; + $xterm-fg-15: $white-light; + $xterm-fg-16: $black; + $xterm-fg-17: #00005f; + $xterm-fg-18: #000087; + $xterm-fg-19: #0000af; + $xterm-fg-20: #0000d7; + $xterm-fg-21: #00f; + $xterm-fg-22: #005f00; + $xterm-fg-23: #005f5f; + $xterm-fg-24: #005f87; + $xterm-fg-25: #005faf; + $xterm-fg-26: #005fd7; + $xterm-fg-27: #005fff; + $xterm-fg-28: #008700; + $xterm-fg-29: #00875f; + $xterm-fg-30: #008787; + $xterm-fg-31: #0087af; + $xterm-fg-32: #0087d7; + $xterm-fg-33: #0087ff; + $xterm-fg-34: #00af00; + $xterm-fg-35: #00af5f; + $xterm-fg-36: #00af87; + $xterm-fg-37: #00afaf; + $xterm-fg-38: #00afd7; + $xterm-fg-39: #00afff; + $xterm-fg-40: #00d700; + $xterm-fg-41: #00d75f; + $xterm-fg-42: #00d787; + $xterm-fg-43: #00d7af; + $xterm-fg-44: #00d7d7; + $xterm-fg-45: #00d7ff; + $xterm-fg-46: #0f0; + $xterm-fg-47: #00ff5f; + $xterm-fg-48: #00ff87; + $xterm-fg-49: #00ffaf; + $xterm-fg-50: #00ffd7; + $xterm-fg-51: #0ff; + $xterm-fg-52: #5f0000; + $xterm-fg-53: #5f005f; + $xterm-fg-54: #5f0087; + $xterm-fg-55: #5f00af; + $xterm-fg-56: #5f00d7; + $xterm-fg-57: #5f00ff; + $xterm-fg-58: #5f5f00; + $xterm-fg-59: #5f5f5f; + $xterm-fg-60: #5f5f87; + $xterm-fg-61: #5f5faf; + $xterm-fg-62: #5f5fd7; + $xterm-fg-63: #5f5fff; + $xterm-fg-64: #5f8700; + $xterm-fg-65: #5f875f; + $xterm-fg-66: #5f8787; + $xterm-fg-67: #5f87af; + $xterm-fg-68: #5f87d7; + $xterm-fg-69: #5f87ff; + $xterm-fg-70: #5faf00; + $xterm-fg-71: #5faf5f; + $xterm-fg-72: #5faf87; + $xterm-fg-73: #5fafaf; + $xterm-fg-74: #5fafd7; + $xterm-fg-75: #5fafff; + $xterm-fg-76: #5fd700; + $xterm-fg-77: #5fd75f; + $xterm-fg-78: #5fd787; + $xterm-fg-79: #5fd7af; + $xterm-fg-80: #5fd7d7; + $xterm-fg-81: #5fd7ff; + $xterm-fg-82: #5fff00; + $xterm-fg-83: #5fff5f; + $xterm-fg-84: #5fff87; + $xterm-fg-85: #5fffaf; + $xterm-fg-86: #5fffd7; + $xterm-fg-87: #5fffff; + $xterm-fg-88: #870000; + $xterm-fg-89: #87005f; + $xterm-fg-90: #870087; + $xterm-fg-91: #8700af; + $xterm-fg-92: #8700d7; + $xterm-fg-93: #8700ff; + $xterm-fg-94: #875f00; + $xterm-fg-95: #875f5f; + $xterm-fg-96: #875f87; + $xterm-fg-97: #875faf; + $xterm-fg-98: #875fd7; + $xterm-fg-99: #875fff; + $xterm-fg-100: #878700; + $xterm-fg-101: #87875f; + $xterm-fg-102: #878787; + $xterm-fg-103: #8787af; + $xterm-fg-104: #8787d7; + $xterm-fg-105: #8787ff; + $xterm-fg-106: #87af00; + $xterm-fg-107: #87af5f; + $xterm-fg-108: #87af87; + $xterm-fg-109: #87afaf; + $xterm-fg-110: #87afd7; + $xterm-fg-111: #87afff; + $xterm-fg-112: #87d700; + $xterm-fg-113: #87d75f; + $xterm-fg-114: #87d787; + $xterm-fg-115: #87d7af; + $xterm-fg-116: #87d7d7; + $xterm-fg-117: #87d7ff; + $xterm-fg-118: #87ff00; + $xterm-fg-119: #87ff5f; + $xterm-fg-120: #87ff87; + $xterm-fg-121: #87ffaf; + $xterm-fg-122: #87ffd7; + $xterm-fg-123: #87ffff; + $xterm-fg-124: #af0000; + $xterm-fg-125: #af005f; + $xterm-fg-126: #af0087; + $xterm-fg-127: #af00af; + $xterm-fg-128: #af00d7; + $xterm-fg-129: #af00ff; + $xterm-fg-130: #af5f00; + $xterm-fg-131: #af5f5f; + $xterm-fg-132: #af5f87; + $xterm-fg-133: #af5faf; + $xterm-fg-134: #af5fd7; + $xterm-fg-135: #af5fff; + $xterm-fg-136: #af8700; + $xterm-fg-137: #af875f; + $xterm-fg-138: #af8787; + $xterm-fg-139: #af87af; + $xterm-fg-140: #af87d7; + $xterm-fg-141: #af87ff; + $xterm-fg-142: #afaf00; + $xterm-fg-143: #afaf5f; + $xterm-fg-144: #afaf87; + $xterm-fg-145: #afafaf; + $xterm-fg-146: #afafd7; + $xterm-fg-147: #afafff; + $xterm-fg-148: #afd700; + $xterm-fg-149: #afd75f; + $xterm-fg-150: #afd787; + $xterm-fg-151: #afd7af; + $xterm-fg-152: #afd7d7; + $xterm-fg-153: #afd7ff; + $xterm-fg-154: #afff00; + $xterm-fg-155: #afff5f; + $xterm-fg-156: #afff87; + $xterm-fg-157: #afffaf; + $xterm-fg-158: #afffd7; + $xterm-fg-159: #afffff; + $xterm-fg-160: #d70000; + $xterm-fg-161: #d7005f; + $xterm-fg-162: #d70087; + $xterm-fg-163: #d700af; + $xterm-fg-164: #d700d7; + $xterm-fg-165: #d700ff; + $xterm-fg-166: #d75f00; + $xterm-fg-167: #d75f5f; + $xterm-fg-168: #d75f87; + $xterm-fg-169: #d75faf; + $xterm-fg-170: #d75fd7; + $xterm-fg-171: #d75fff; + $xterm-fg-172: #d78700; + $xterm-fg-173: #d7875f; + $xterm-fg-174: #d78787; + $xterm-fg-175: #d787af; + $xterm-fg-176: #d787d7; + $xterm-fg-177: #d787ff; + $xterm-fg-178: #d7af00; + $xterm-fg-179: #d7af5f; + $xterm-fg-180: #d7af87; + $xterm-fg-181: #d7afaf; + $xterm-fg-182: #d7afd7; + $xterm-fg-183: #d7afff; + $xterm-fg-184: #d7d700; + $xterm-fg-185: #d7d75f; + $xterm-fg-186: #d7d787; + $xterm-fg-187: #d7d7af; + $xterm-fg-188: #d7d7d7; + $xterm-fg-189: #d7d7ff; + $xterm-fg-190: #d7ff00; + $xterm-fg-191: #d7ff5f; + $xterm-fg-192: #d7ff87; + $xterm-fg-193: #d7ffaf; + $xterm-fg-194: #d7ffd7; + $xterm-fg-195: #d7ffff; + $xterm-fg-196: #f00; + $xterm-fg-197: #ff005f; + $xterm-fg-198: #ff0087; + $xterm-fg-199: #ff00af; + $xterm-fg-200: #ff00d7; + $xterm-fg-201: #f0f; + $xterm-fg-202: #ff5f00; + $xterm-fg-203: #ff5f5f; + $xterm-fg-204: #ff5f87; + $xterm-fg-205: #ff5faf; + $xterm-fg-206: #ff5fd7; + $xterm-fg-207: #ff5fff; + $xterm-fg-208: #ff8700; + $xterm-fg-209: #ff875f; + $xterm-fg-210: #ff8787; + $xterm-fg-211: #ff87af; + $xterm-fg-212: #ff87d7; + $xterm-fg-213: #ff87ff; + $xterm-fg-214: #ffaf00; + $xterm-fg-215: #ffaf5f; + $xterm-fg-216: #ffaf87; + $xterm-fg-217: #ffafaf; + $xterm-fg-218: #ffafd7; + $xterm-fg-219: #ffafff; + $xterm-fg-220: #ffd700; + $xterm-fg-221: #ffd75f; + $xterm-fg-222: #ffd787; + $xterm-fg-223: #ffd7af; + $xterm-fg-224: #ffd7d7; + $xterm-fg-225: #ffd7ff; + $xterm-fg-226: #ff0; + $xterm-fg-227: #ffff5f; + $xterm-fg-228: #ffff87; + $xterm-fg-229: #ffffaf; + $xterm-fg-230: #ffffd7; + $xterm-fg-231: $white-light; + $xterm-fg-232: #080808; + $xterm-fg-233: #121212; + $xterm-fg-234: #1c1c1c; + $xterm-fg-235: #262626; + $xterm-fg-236: #303030; + $xterm-fg-237: #3a3a3a; + $xterm-fg-238: #444; + $xterm-fg-239: #4e4e4e; + $xterm-fg-240: #585858; + $xterm-fg-241: #626262; + $xterm-fg-242: #6c6c6c; + $xterm-fg-243: #767676; + $xterm-fg-244: #808080; + $xterm-fg-245: #8a8a8a; + $xterm-fg-246: #949494; + $xterm-fg-247: #9e9e9e; + $xterm-fg-248: #a8a8a8; + $xterm-fg-249: #b2b2b2; + $xterm-fg-250: #bcbcbc; + $xterm-fg-251: #c6c6c6; + $xterm-fg-252: #d0d0d0; + $xterm-fg-253: #dadada; + $xterm-fg-254: #e4e4e4; + $xterm-fg-255: #eee; + .term-bold { font-weight: bold; } @@ -169,1026 +429,1026 @@ } .xterm-fg-0 { - color: #000; + color: $xterm-fg-0; } .xterm-fg-1 { - color: #800000; + color: $xterm-fg-1; } .xterm-fg-2 { - color: #008000; + color: $xterm-fg-2; } .xterm-fg-3 { - color: #808000; + color: $xterm-fg-3; } .xterm-fg-4 { - color: #000080; + color: $xterm-fg-4; } .xterm-fg-5 { - color: #800080; + color: $xterm-fg-5; } .xterm-fg-6 { - color: #008080; + color: $xterm-fg-6; } .xterm-fg-7 { - color: #c0c0c0; + color: $xterm-fg-7; } .xterm-fg-8 { - color: #808080; + color: $xterm-fg-8; } .xterm-fg-9 { - color: #f00; + color: $xterm-fg-9; } .xterm-fg-10 { - color: #0f0; + color: $xterm-fg-10; } .xterm-fg-11 { - color: #ff0; + color: $xterm-fg-11; } .xterm-fg-12 { - color: #00f; + color: $xterm-fg-12; } .xterm-fg-13 { - color: #f0f; + color: $xterm-fg-13; } .xterm-fg-14 { - color: #0ff; + color: $xterm-fg-14; } .xterm-fg-15 { - color: #fff; + color: $white-light; } .xterm-fg-16 { - color: #000; + color: $black; } .xterm-fg-17 { - color: #00005f; + color: $xterm-fg-17; } .xterm-fg-18 { - color: #000087; + color: $xterm-fg-18; } .xterm-fg-19 { - color: #0000af; + color: $xterm-fg-19; } .xterm-fg-20 { - color: #0000d7; + color: $xterm-fg-20; } .xterm-fg-21 { - color: #00f; + color: $xterm-fg-21; } .xterm-fg-22 { - color: #005f00; + color: $xterm-fg-22; } .xterm-fg-23 { - color: #005f5f; + color: $xterm-fg-23; } .xterm-fg-24 { - color: #005f87; + color: $xterm-fg-24; } .xterm-fg-25 { - color: #005faf; + color: $xterm-fg-25; } .xterm-fg-26 { - color: #005fd7; + color: $xterm-fg-26; } .xterm-fg-27 { - color: #005fff; + color: $xterm-fg-27; } .xterm-fg-28 { - color: #008700; + color: $xterm-fg-28; } .xterm-fg-29 { - color: #00875f; + color: $xterm-fg-29; } .xterm-fg-30 { - color: #008787; + color: $xterm-fg-30; } .xterm-fg-31 { - color: #0087af; + color: $xterm-fg-31; } .xterm-fg-32 { - color: #0087d7; + color: $xterm-fg-32; } .xterm-fg-33 { - color: #0087ff; + color: $xterm-fg-33; } .xterm-fg-34 { - color: #00af00; + color: $xterm-fg-34; } .xterm-fg-35 { - color: #00af5f; + color: $xterm-fg-35; } .xterm-fg-36 { - color: #00af87; + color: $xterm-fg-36; } .xterm-fg-37 { - color: #00afaf; + color: $xterm-fg-37; } .xterm-fg-38 { - color: #00afd7; + color: $xterm-fg-38; } .xterm-fg-39 { - color: #00afff; + color: $xterm-fg-39; } .xterm-fg-40 { - color: #00d700; + color: $xterm-fg-40; } .xterm-fg-41 { - color: #00d75f; + color: $xterm-fg-41; } .xterm-fg-42 { - color: #00d787; + color: $xterm-fg-42; } .xterm-fg-43 { - color: #00d7af; + color: $xterm-fg-43; } .xterm-fg-44 { - color: #00d7d7; + color: $xterm-fg-44; } .xterm-fg-45 { - color: #00d7ff; + color: $xterm-fg-45; } .xterm-fg-46 { - color: #0f0; + color: $xterm-fg-46; } .xterm-fg-47 { - color: #00ff5f; + color: $xterm-fg-47; } .xterm-fg-48 { - color: #00ff87; + color: $xterm-fg-48; } .xterm-fg-49 { - color: #00ffaf; + color: $xterm-fg-49; } .xterm-fg-50 { - color: #00ffd7; + color: $xterm-fg-50; } .xterm-fg-51 { - color: #0ff; + color: $xterm-fg-51; } .xterm-fg-52 { - color: #5f0000; + color: $xterm-fg-52; } .xterm-fg-53 { - color: #5f005f; + color: $xterm-fg-53; } .xterm-fg-54 { - color: #5f0087; + color: $xterm-fg-54; } .xterm-fg-55 { - color: #5f00af; + color: $xterm-fg-55; } .xterm-fg-56 { - color: #5f00d7; + color: $xterm-fg-56; } .xterm-fg-57 { - color: #5f00ff; + color: $xterm-fg-57; } .xterm-fg-58 { - color: #5f5f00; + color: $xterm-fg-58; } .xterm-fg-59 { - color: #5f5f5f; + color: $xterm-fg-59; } .xterm-fg-60 { - color: #5f5f87; + color: $xterm-fg-60; } .xterm-fg-61 { - color: #5f5faf; + color: $xterm-fg-61; } .xterm-fg-62 { - color: #5f5fd7; + color: $xterm-fg-62; } .xterm-fg-63 { - color: #5f5fff; + color: $xterm-fg-63; } .xterm-fg-64 { - color: #5f8700; + color: $xterm-fg-64; } .xterm-fg-65 { - color: #5f875f; + color: $xterm-fg-65; } .xterm-fg-66 { - color: #5f8787; + color: $xterm-fg-66; } .xterm-fg-67 { - color: #5f87af; + color: $xterm-fg-67; } .xterm-fg-68 { - color: #5f87d7; + color: $xterm-fg-68; } .xterm-fg-69 { - color: #5f87ff; + color: $xterm-fg-69; } .xterm-fg-70 { - color: #5faf00; + color: $xterm-fg-70; } .xterm-fg-71 { - color: #5faf5f; + color: $xterm-fg-71; } .xterm-fg-72 { - color: #5faf87; + color: $xterm-fg-72; } .xterm-fg-73 { - color: #5fafaf; + color: $xterm-fg-73; } .xterm-fg-74 { - color: #5fafd7; + color: $xterm-fg-74; } .xterm-fg-75 { - color: #5fafff; + color: $xterm-fg-75; } .xterm-fg-76 { - color: #5fd700; + color: $xterm-fg-76; } .xterm-fg-77 { - color: #5fd75f; + color: $xterm-fg-77; } .xterm-fg-78 { - color: #5fd787; + color: $xterm-fg-78; } .xterm-fg-79 { - color: #5fd7af; + color: $xterm-fg-79; } .xterm-fg-80 { - color: #5fd7d7; + color: $xterm-fg-80; } .xterm-fg-81 { - color: #5fd7ff; + color: $xterm-fg-81; } .xterm-fg-82 { - color: #5fff00; + color: $xterm-fg-82; } .xterm-fg-83 { - color: #5fff5f; + color: $xterm-fg-83; } .xterm-fg-84 { - color: #5fff87; + color: $xterm-fg-84; } .xterm-fg-85 { - color: #5fffaf; + color: $xterm-fg-85; } .xterm-fg-86 { - color: #5fffd7; + color: $xterm-fg-86; } .xterm-fg-87 { - color: #5fffff; + color: $xterm-fg-87; } .xterm-fg-88 { - color: #870000; + color: $xterm-fg-88; } .xterm-fg-89 { - color: #87005f; + color: $xterm-fg-89; } .xterm-fg-90 { - color: #870087; + color: $xterm-fg-90; } .xterm-fg-91 { - color: #8700af; + color: $xterm-fg-91; } .xterm-fg-92 { - color: #8700d7; + color: $xterm-fg-92; } .xterm-fg-93 { - color: #8700ff; + color: $xterm-fg-93; } .xterm-fg-94 { - color: #875f00; + color: $xterm-fg-94; } .xterm-fg-95 { - color: #875f5f; + color: $xterm-fg-95; } .xterm-fg-96 { - color: #875f87; + color: $xterm-fg-96; } .xterm-fg-97 { - color: #875faf; + color: $xterm-fg-97; } .xterm-fg-98 { - color: #875fd7; + color: $xterm-fg-98; } .xterm-fg-99 { - color: #875fff; + color: $xterm-fg-99; } .xterm-fg-100 { - color: #878700; + color: $xterm-fg-100; } .xterm-fg-101 { - color: #87875f; + color: $xterm-fg-101; } .xterm-fg-102 { - color: #878787; + color: $xterm-fg-102; } .xterm-fg-103 { - color: #8787af; + color: $xterm-fg-103; } .xterm-fg-104 { - color: #8787d7; + color: $xterm-fg-104; } .xterm-fg-105 { - color: #8787ff; + color: $xterm-fg-105; } .xterm-fg-106 { - color: #87af00; + color: $xterm-fg-106; } .xterm-fg-107 { - color: #87af5f; + color: $xterm-fg-107; } .xterm-fg-108 { - color: #87af87; + color: $xterm-fg-108; } .xterm-fg-109 { - color: #87afaf; + color: $xterm-fg-109; } .xterm-fg-110 { - color: #87afd7; + color: $xterm-fg-110; } .xterm-fg-111 { - color: #87afff; + color: $xterm-fg-111; } .xterm-fg-112 { - color: #87d700; + color: $xterm-fg-112; } .xterm-fg-113 { - color: #87d75f; + color: $xterm-fg-113; } .xterm-fg-114 { - color: #87d787; + color: $xterm-fg-114; } .xterm-fg-115 { - color: #87d7af; + color: $xterm-fg-115; } .xterm-fg-116 { - color: #87d7d7; + color: $xterm-fg-116; } .xterm-fg-117 { - color: #87d7ff; + color: $xterm-fg-117; } .xterm-fg-118 { - color: #87ff00; + color: $xterm-fg-118; } .xterm-fg-119 { - color: #87ff5f; + color: $xterm-fg-119; } .xterm-fg-120 { - color: #87ff87; + color: $xterm-fg-120; } .xterm-fg-121 { - color: #87ffaf; + color: $xterm-fg-121; } .xterm-fg-122 { - color: #87ffd7; + color: $xterm-fg-122; } .xterm-fg-123 { - color: #87ffff; + color: $xterm-fg-123; } .xterm-fg-124 { - color: #af0000; + color: $xterm-fg-124; } .xterm-fg-125 { - color: #af005f; + color: $xterm-fg-125; } .xterm-fg-126 { - color: #af0087; + color: $xterm-fg-126; } .xterm-fg-127 { - color: #af00af; + color: $xterm-fg-127; } .xterm-fg-128 { - color: #af00d7; + color: $xterm-fg-128; } .xterm-fg-129 { - color: #af00ff; + color: $xterm-fg-129; } .xterm-fg-130 { - color: #af5f00; + color: $xterm-fg-130; } .xterm-fg-131 { - color: #af5f5f; + color: $xterm-fg-131; } .xterm-fg-132 { - color: #af5f87; + color: $xterm-fg-132; } .xterm-fg-133 { - color: #af5faf; + color: $xterm-fg-133; } .xterm-fg-134 { - color: #af5fd7; + color: $xterm-fg-134; } .xterm-fg-135 { - color: #af5fff; + color: $xterm-fg-135; } .xterm-fg-136 { - color: #af8700; + color: $xterm-fg-136; } .xterm-fg-137 { - color: #af875f; + color: $xterm-fg-137; } .xterm-fg-138 { - color: #af8787; + color: $xterm-fg-138; } .xterm-fg-139 { - color: #af87af; + color: $xterm-fg-139; } .xterm-fg-140 { - color: #af87d7; + color: $xterm-fg-140; } .xterm-fg-141 { - color: #af87ff; + color: $xterm-fg-141; } .xterm-fg-142 { - color: #afaf00; + color: $xterm-fg-142; } .xterm-fg-143 { - color: #afaf5f; + color: $xterm-fg-143; } .xterm-fg-144 { - color: #afaf87; + color: $xterm-fg-144; } .xterm-fg-145 { - color: #afafaf; + color: $xterm-fg-145; } .xterm-fg-146 { - color: #afafd7; + color: $xterm-fg-146; } .xterm-fg-147 { - color: #afafff; + color: $xterm-fg-147; } .xterm-fg-148 { - color: #afd700; + color: $xterm-fg-148; } .xterm-fg-149 { - color: #afd75f; + color: $xterm-fg-149; } .xterm-fg-150 { - color: #afd787; + color: $xterm-fg-150; } .xterm-fg-151 { - color: #afd7af; + color: $xterm-fg-151; } .xterm-fg-152 { - color: #afd7d7; + color: $xterm-fg-152; } .xterm-fg-153 { - color: #afd7ff; + color: $xterm-fg-153; } .xterm-fg-154 { - color: #afff00; + color: $xterm-fg-154; } .xterm-fg-155 { - color: #afff5f; + color: $xterm-fg-155; } .xterm-fg-156 { - color: #afff87; + color: $xterm-fg-156; } .xterm-fg-157 { - color: #afffaf; + color: $xterm-fg-157; } .xterm-fg-158 { - color: #afffd7; + color: $xterm-fg-158; } .xterm-fg-159 { - color: #afffff; + color: $xterm-fg-159; } .xterm-fg-160 { - color: #d70000; + color: $xterm-fg-160; } .xterm-fg-161 { - color: #d7005f; + color: $xterm-fg-161; } .xterm-fg-162 { - color: #d70087; + color: $xterm-fg-162; } .xterm-fg-163 { - color: #d700af; + color: $xterm-fg-163; } .xterm-fg-164 { - color: #d700d7; + color: $xterm-fg-164; } .xterm-fg-165 { - color: #d700ff; + color: $xterm-fg-165; } .xterm-fg-166 { - color: #d75f00; + color: $xterm-fg-166; } .xterm-fg-167 { - color: #d75f5f; + color: $xterm-fg-167; } .xterm-fg-168 { - color: #d75f87; + color: $xterm-fg-168; } .xterm-fg-169 { - color: #d75faf; + color: $xterm-fg-169; } .xterm-fg-170 { - color: #d75fd7; + color: $xterm-fg-170; } .xterm-fg-171 { - color: #d75fff; + color: $xterm-fg-171; } .xterm-fg-172 { - color: #d78700; + color: $xterm-fg-172; } .xterm-fg-173 { - color: #d7875f; + color: $xterm-fg-173; } .xterm-fg-174 { - color: #d78787; + color: $xterm-fg-174; } .xterm-fg-175 { - color: #d787af; + color: $xterm-fg-175; } .xterm-fg-176 { - color: #d787d7; + color: $xterm-fg-176; } .xterm-fg-177 { - color: #d787ff; + color: $xterm-fg-177; } .xterm-fg-178 { - color: #d7af00; + color: $xterm-fg-178; } .xterm-fg-179 { - color: #d7af5f; + color: $xterm-fg-179; } .xterm-fg-180 { - color: #d7af87; + color: $xterm-fg-180; } .xterm-fg-181 { - color: #d7afaf; + color: $xterm-fg-181; } .xterm-fg-182 { - color: #d7afd7; + color: $xterm-fg-182; } .xterm-fg-183 { - color: #d7afff; + color: $xterm-fg-183; } .xterm-fg-184 { - color: #d7d700; + color: $xterm-fg-184; } .xterm-fg-185 { - color: #d7d75f; + color: $xterm-fg-185; } .xterm-fg-186 { - color: #d7d787; + color: $xterm-fg-186; } .xterm-fg-187 { - color: #d7d7af; + color: $xterm-fg-187; } .xterm-fg-188 { - color: #d7d7d7; + color: $xterm-fg-188; } .xterm-fg-189 { - color: #d7d7ff; + color: $xterm-fg-189; } .xterm-fg-190 { - color: #d7ff00; + color: $xterm-fg-190; } .xterm-fg-191 { - color: #d7ff5f; + color: $xterm-fg-191; } .xterm-fg-192 { - color: #d7ff87; + color: $xterm-fg-192; } .xterm-fg-193 { - color: #d7ffaf; + color: $xterm-fg-193; } .xterm-fg-194 { - color: #d7ffd7; + color: $xterm-fg-194; } .xterm-fg-195 { - color: #d7ffff; + color: $xterm-fg-195; } .xterm-fg-196 { - color: #f00; + color: $xterm-fg-196; } .xterm-fg-197 { - color: #ff005f; + color: $xterm-fg-197; } .xterm-fg-198 { - color: #ff0087; + color: $xterm-fg-198; } .xterm-fg-199 { - color: #ff00af; + color: $xterm-fg-199; } .xterm-fg-200 { - color: #ff00d7; + color: $xterm-fg-200; } .xterm-fg-201 { - color: #f0f; + color: $xterm-fg-201; } .xterm-fg-202 { - color: #ff5f00; + color: $xterm-fg-202; } .xterm-fg-203 { - color: #ff5f5f; + color: $xterm-fg-203; } .xterm-fg-204 { - color: #ff5f87; + color: $xterm-fg-204; } .xterm-fg-205 { - color: #ff5faf; + color: $xterm-fg-205; } .xterm-fg-206 { - color: #ff5fd7; + color: $xterm-fg-206; } .xterm-fg-207 { - color: #ff5fff; + color: $xterm-fg-207; } .xterm-fg-208 { - color: #ff8700; + color: $xterm-fg-208; } .xterm-fg-209 { - color: #ff875f; + color: $xterm-fg-209; } .xterm-fg-210 { - color: #ff8787; + color: $xterm-fg-210; } .xterm-fg-211 { - color: #ff87af; + color: $xterm-fg-211; } .xterm-fg-212 { - color: #ff87d7; + color: $xterm-fg-212; } .xterm-fg-213 { - color: #ff87ff; + color: $xterm-fg-213; } .xterm-fg-214 { - color: #ffaf00; + color: $xterm-fg-214; } .xterm-fg-215 { - color: #ffaf5f; + color: $xterm-fg-215; } .xterm-fg-216 { - color: #ffaf87; + color: $xterm-fg-216; } .xterm-fg-217 { - color: #ffafaf; + color: $xterm-fg-217; } .xterm-fg-218 { - color: #ffafd7; + color: $xterm-fg-218; } .xterm-fg-219 { - color: #ffafff; + color: $xterm-fg-219; } .xterm-fg-220 { - color: #ffd700; + color: $xterm-fg-220; } .xterm-fg-221 { - color: #ffd75f; + color: $xterm-fg-221; } .xterm-fg-222 { - color: #ffd787; + color: $xterm-fg-222; } .xterm-fg-223 { - color: #ffd7af; + color: $xterm-fg-223; } .xterm-fg-224 { - color: #ffd7d7; + color: $xterm-fg-224; } .xterm-fg-225 { - color: #ffd7ff; + color: $xterm-fg-225; } .xterm-fg-226 { - color: #ff0; + color: $xterm-fg-226; } .xterm-fg-227 { - color: #ffff5f; + color: $xterm-fg-227; } .xterm-fg-228 { - color: #ffff87; + color: $xterm-fg-228; } .xterm-fg-229 { - color: #ffffaf; + color: $xterm-fg-229; } .xterm-fg-230 { - color: #ffffd7; + color: $xterm-fg-230; } .xterm-fg-231 { - color: #fff; + color: $xterm-fg-231; } .xterm-fg-232 { - color: #080808; + color: $xterm-fg-232; } .xterm-fg-233 { - color: #121212; + color: $xterm-fg-233; } .xterm-fg-234 { - color: #1c1c1c; + color: $xterm-fg-234; } .xterm-fg-235 { - color: #262626; + color: $xterm-fg-235; } .xterm-fg-236 { - color: #303030; + color: $xterm-fg-236; } .xterm-fg-237 { - color: #3a3a3a; + color: $xterm-fg-237; } .xterm-fg-238 { - color: #444; + color: $xterm-fg-238; } .xterm-fg-239 { - color: #4e4e4e; + color: $xterm-fg-239; } .xterm-fg-240 { - color: #585858; + color: $xterm-fg-240; } .xterm-fg-241 { - color: #626262; + color: $xterm-fg-241; } .xterm-fg-242 { - color: #6c6c6c; + color: $xterm-fg-242; } .xterm-fg-243 { - color: #767676; + color: $xterm-fg-243; } .xterm-fg-244 { - color: #808080; + color: $xterm-fg-244; } .xterm-fg-245 { - color: #8a8a8a; + color: $xterm-fg-245; } .xterm-fg-246 { - color: #949494; + color: $xterm-fg-246; } .xterm-fg-247 { - color: #9e9e9e; + color: $xterm-fg-247; } .xterm-fg-248 { - color: #a8a8a8; + color: $xterm-fg-248; } .xterm-fg-249 { - color: #b2b2b2; + color: $xterm-fg-249; } .xterm-fg-250 { - color: #bcbcbc; + color: $xterm-fg-250; } .xterm-fg-251 { - color: #c6c6c6; + color: $xterm-fg-251; } .xterm-fg-252 { - color: #d0d0d0; + color: $xterm-fg-252; } .xterm-fg-253 { - color: #dadada; + color: $xterm-fg-253; } .xterm-fg-254 { - color: #e4e4e4; + color: $xterm-fg-254; } .xterm-fg-255 { - color: #eee; + color: $xterm-fg-255; } } diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index b81842e319b..c2bb8464824 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -112,6 +112,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :koding_enabled, :koding_url, :email_author_in_body, + :html_emails_enabled, :repository_checks_enabled, :metrics_packet_size, :send_user_confirmation_email, diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index aa7570cd896..1e3d194e9f9 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -56,7 +56,7 @@ class Admin::GroupsController < Admin::ApplicationController private def group - @group ||= Group.find_by(path: params[:id]) + @group ||= Group.find_by_full_path(params[:id]) end def group_params diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 5c44637fdee..5f13353baa1 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -11,7 +11,7 @@ class AutocompleteController < ApplicationController @users = @users.reorder(:name) @users = @users.page(params[:page]) - if params[:todo_filter].present? + if params[:todo_filter].present? && current_user @users = @users.todo_authors(current_user.id, params[:todo_state_filter]) end diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index dacb5679dd3..936d9bab57e 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -81,10 +81,8 @@ module CreatesCommit def merge_request_exists? return @merge_request if defined?(@merge_request) - @merge_request = @mr_target_project.merge_requests.opened.find_by( - source_branch: @mr_source_branch, - target_branch: @mr_target_branch - ) + @merge_request = MergeRequestsFinder.new(current_user, project_id: @mr_target_project.id).execute.opened. + find_by(source_branch: @mr_source_branch, target_branch: @mr_target_branch) end def different_project? diff --git a/app/helpers/lfs_helper.rb b/app/controllers/concerns/lfs_request.rb index 2425c3a8bc8..ed22b1e5470 100644 --- a/app/helpers/lfs_helper.rb +++ b/app/controllers/concerns/lfs_request.rb @@ -1,5 +1,21 @@ -module LfsHelper - include Gitlab::Routing.url_helpers +# This concern assumes: +# - a `#project` accessor +# - a `#user` accessor +# - a `#authentication_result` accessor +# - a `#can?(object, action, subject)` method +# - a `#ci?` method +# - a `#download_request?` method +# - a `#upload_request?` method +# - a `#has_authentication_ability?(ability)` method +module LfsRequest + extend ActiveSupport::Concern + + included do + before_action :require_lfs_enabled! + before_action :lfs_check_access! + end + + private def require_lfs_enabled! return if Gitlab.config.lfs.enabled @@ -17,35 +33,15 @@ module LfsHelper return if download_request? && lfs_download_access? return if upload_request? && lfs_upload_access? - if project.public? || (user && user.can?(:read_project, project)) - render_lfs_forbidden + if project.public? || can?(user, :read_project, project) + lfs_forbidden! else render_lfs_not_found end end - def lfs_download_access? - return false unless project.lfs_enabled? - - ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code? - end - - def objects - @objects ||= (params[:objects] || []).to_a - end - - def user_can_download_code? - has_authentication_ability?(:download_code) && can?(user, :download_code, project) - end - - def build_can_download_code? - has_authentication_ability?(:build_download_code) && can?(user, :build_download_code, project) - end - - def lfs_upload_access? - return false unless project.lfs_enabled? - - has_authentication_ability?(:push_code) && can?(user, :push_code, project) + def lfs_forbidden! + render_lfs_forbidden end def render_lfs_forbidden @@ -70,6 +66,30 @@ module LfsHelper ) end + def lfs_download_access? + return false unless project.lfs_enabled? + + ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code? + end + + def lfs_upload_access? + return false unless project.lfs_enabled? + + has_authentication_ability?(:push_code) && can?(user, :push_code, project) + end + + def lfs_deploy_token? + authentication_result.lfs_deploy_token?(project) + end + + def user_can_download_code? + has_authentication_ability?(:download_code) && can?(user, :download_code, project) + end + + def build_can_download_code? + has_authentication_ability?(:build_download_code) && can?(user, :build_download_code, project) + end + def storage_project @storage_project ||= begin result = project @@ -82,4 +102,8 @@ module LfsHelper result end end + + def objects + @objects ||= (params[:objects] || []).to_a + end end diff --git a/app/controllers/concerns/merge_requests_action.rb b/app/controllers/concerns/merge_requests_action.rb index 6546a07b41c..fdb05bb3228 100644 --- a/app/controllers/concerns/merge_requests_action.rb +++ b/app/controllers/concerns/merge_requests_action.rb @@ -6,7 +6,12 @@ module MergeRequestsAction @label = merge_requests_finder.labels.first @merge_requests = merge_requests_collection - .non_archived .page(params[:page]) end + + private + + def filter_params + super.merge(non_archived: true) + end end diff --git a/app/controllers/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb index 3717c49f272..fbf9a026b10 100644 --- a/app/controllers/concerns/toggle_award_emoji.rb +++ b/app/controllers/concerns/toggle_award_emoji.rb @@ -1,11 +1,8 @@ module ToggleAwardEmoji extend ActiveSupport::Concern - included do - before_action :authenticate_user!, only: [:toggle_award_emoji] - end - def toggle_award_emoji + authenticate_user! name = params.require(:name) if awardable.user_can_award?(current_user, name) diff --git a/app/controllers/concerns/workhorse_request.rb b/app/controllers/concerns/workhorse_request.rb new file mode 100644 index 00000000000..43c0f1b173c --- /dev/null +++ b/app/controllers/concerns/workhorse_request.rb @@ -0,0 +1,13 @@ +module WorkhorseRequest + extend ActiveSupport::Concern + + included do + before_action :verify_workhorse_api! + end + + private + + def verify_workhorse_api! + Gitlab::Workhorse.verify_api_request!(request.headers) + end +end diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb index 949b4a6c25a..c411c21bb80 100644 --- a/app/controllers/groups/application_controller.rb +++ b/app/controllers/groups/application_controller.rb @@ -9,7 +9,7 @@ class Groups::ApplicationController < ApplicationController def group unless @group id = params[:group_id] || params[:id] - @group = Group.find_by(path: id) + @group = Group.find_by_full_path(id) unless @group && can?(current_user, :read_group, @group) @group = nil diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index 4b3c71874be..37feff79999 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -6,9 +6,11 @@ class HelpController < ApplicationController def index @help_index = File.read(Rails.root.join('doc', 'README.md')) - # Prefix Markdown links with `help/` unless they already have been - # See http://rubular.com/r/ie2MlpdUMq - @help_index.gsub!(/(\]\()(\/?help\/)?([^\)\(]+\))/, '\1/help/\3') + # Prefix Markdown links with `help/` unless they are external links + # See http://rubular.com/r/X3baHTbPO2 + @help_index.gsub!(%r{(?<delim>\]\()(?!.+://)(?!/)(?<link>[^\)\(]+\))}) do + "#{$~[:delim]}#{Gitlab.config.gitlab.relative_url_root}/help/#{$~[:link]}" + end end def show diff --git a/app/controllers/profiles/avatars_controller.rb b/app/controllers/profiles/avatars_controller.rb index f193adb46b4..daa51ae41df 100644 --- a/app/controllers/profiles/avatars_controller.rb +++ b/app/controllers/profiles/avatars_controller.rb @@ -4,7 +4,6 @@ class Profiles::AvatarsController < Profiles::ApplicationController @user.remove_avatar! @user.save - @user.reset_events_cache redirect_to profile_path end diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb index ada7db3c552..53788687076 100644 --- a/app/controllers/projects/avatars_controller.rb +++ b/app/controllers/projects/avatars_controller.rb @@ -20,7 +20,6 @@ class Projects::AvatarsController < Projects::ApplicationController @project.remove_avatar! @project.save - @project.reset_events_cache redirect_to edit_project_path(@project) end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 56ced786311..9940263ae24 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -13,7 +13,6 @@ class Projects::BlobController < Projects::ApplicationController before_action :assign_blob_vars before_action :commit, except: [:new, :create] before_action :blob, except: [:new, :create] - before_action :from_merge_request, only: [:edit, :update] before_action :require_branch_head, only: [:edit, :update] before_action :editor_variables, except: [:show, :preview, :diff] before_action :validate_diff_params, only: :diff @@ -39,14 +38,6 @@ class Projects::BlobController < Projects::ApplicationController def update @path = params[:file_path] if params[:file_path].present? - after_edit_path = - if from_merge_request && @target_branch == @ref - diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + - "##{hexdigest(@path)}" - else - namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path)) - end - create_commit(Files::UpdateService, success_path: after_edit_path, failure_view: :edit, failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) @@ -124,9 +115,14 @@ class Projects::BlobController < Projects::ApplicationController render_404 end - def from_merge_request - # If blob edit was initiated from merge request page - @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id]) + def after_edit_path + from_merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:from_merge_request_iid]) + if from_merge_request && @target_branch == @ref + diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + + "##{hexdigest(@path)}" + else + namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path)) + end end def editor_variables diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 6b9f37983c4..89d84809e3a 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -36,7 +36,7 @@ class Projects::BranchesController < Projects::ApplicationController execute(branch_name, ref) if params[:issue_iid] - issue = @project.issues.find_by(iid: params[:issue_iid]) + issue = IssuesFinder.new(current_user, project_id: @project.id).find_by(iid: params[:issue_iid]) SystemNoteService.new_issue_branch(issue, @project, current_user, branch_name) if issue end diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index cdfc1ba7b92..8197d9e4c99 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -65,7 +65,7 @@ class Projects::CommitController < Projects::ApplicationController return render_404 if @target_branch.blank? - create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title} has been successfully reverted.", + create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully reverted.", success_path: successful_change_path, failure_path: failed_change_path) end @@ -74,26 +74,24 @@ class Projects::CommitController < Projects::ApplicationController return render_404 if @target_branch.blank? - create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title} has been successfully cherry-picked.", + create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully cherry-picked.", success_path: successful_change_path, failure_path: failed_change_path) end private def successful_change_path - return referenced_merge_request_url if @commit.merged_merge_request - - namespace_project_commits_url(@project.namespace, @project, @target_branch) + referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @target_branch) end def failed_change_path - return referenced_merge_request_url if @commit.merged_merge_request - - namespace_project_commit_url(@project.namespace, @project, params[:id]) + referenced_merge_request_url || namespace_project_commit_url(@project.namespace, @project, params[:id]) end def referenced_merge_request_url - namespace_project_merge_request_url(@project.namespace, @project, @commit.merged_merge_request) + if merge_request = @commit.merged_merge_request(current_user) + namespace_project_merge_request_url(@project.namespace, @project, merge_request) + end end def commit diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index aba87b6144b..ad92f05a42d 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -21,7 +21,7 @@ class Projects::CommitsController < Projects::ApplicationController @note_counts = project.notes.where(commit_id: @commits.map(&:id)). group(:commit_id).count - @merge_request = @project.merge_requests.opened. + @merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened. find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref) respond_to do |format| diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index bee3d56076c..ec02fc15d35 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -53,7 +53,7 @@ class Projects::CompareController < Projects::ApplicationController end def merge_request - @merge_request ||= @project.merge_requests.opened. + @merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened. find_by(source_project: @project, source_branch: @head_ref, target_branch: @start_ref) end end diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb index fd263960b93..ac639ef015b 100644 --- a/app/controllers/projects/cycle_analytics_controller.rb +++ b/app/controllers/projects/cycle_analytics_controller.rb @@ -6,7 +6,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController before_action :authorize_read_cycle_analytics! def show - @cycle_analytics = ::CycleAnalytics.new(@project, from: start_date(cycle_analytics_params)) + @cycle_analytics = ::CycleAnalytics.new(@project, current_user, from: start_date(cycle_analytics_params)) stats_values, cycle_analytics_json = generate_cycle_analytics_data diff --git a/app/controllers/projects/discussions_controller.rb b/app/controllers/projects/discussions_controller.rb index d174e1145a7..1349b015a63 100644 --- a/app/controllers/projects/discussions_controller.rb +++ b/app/controllers/projects/discussions_controller.rb @@ -5,9 +5,7 @@ class Projects::DiscussionsController < Projects::ApplicationController before_action :authorize_resolve_discussion! def resolve - discussion.resolve!(current_user) - - MergeRequests::ResolvedDiscussionNotificationService.new(project, current_user).execute(merge_request) + Discussions::ResolveService.new(project, current_user, merge_request: merge_request).execute(discussion) render json: { resolved_by: discussion.resolved_by.try(:name), @@ -26,7 +24,7 @@ class Projects::DiscussionsController < Projects::ApplicationController private def merge_request - @merge_request ||= @project.merge_requests.find_by!(iid: params[:merge_request_id]) + @merge_request ||= MergeRequestsFinder.new(current_user, project_id: @project.id).find_by!(iid: params[:merge_request_id]) end def discussion diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index 3f41916e6d3..8714349e27f 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -18,6 +18,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController private + def download_request? + raise NotImplementedError + end + + def upload_request? + raise NotImplementedError + end + def authenticate_user @authentication_result = Gitlab::Auth::Result.new @@ -130,10 +138,6 @@ class Projects::GitHttpClientController < Projects::ApplicationController authentication_result.ci?(project) end - def lfs_deploy_token? - authentication_result.lfs_deploy_token?(project) - end - def authentication_has_download_access? has_authentication_ability?(:download_code) || has_authentication_ability?(:build_download_code) end @@ -149,8 +153,4 @@ class Projects::GitHttpClientController < Projects::ApplicationController def authentication_project authentication_result.project end - - def verify_workhorse_api! - Gitlab::Workhorse.verify_api_request!(request.headers) - end end diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb index 13caeb42d40..9184dcccac5 100644 --- a/app/controllers/projects/git_http_controller.rb +++ b/app/controllers/projects/git_http_controller.rb @@ -1,7 +1,5 @@ -# This file should be identical in GitLab Community Edition and Enterprise Edition - class Projects::GitHttpController < Projects::GitHttpClientController - before_action :verify_workhorse_api! + include WorkhorseRequest # GET /foo/bar.git/info/refs?service=git-upload-pack (git pull) # GET /foo/bar.git/info/refs?service=git-receive-pack (git push) @@ -67,14 +65,18 @@ class Projects::GitHttpController < Projects::GitHttpClientController end def render_denied - if user && user.can?(:read_project, project) - render plain: 'Access denied', status: :forbidden + if user && can?(user, :read_project, project) + render plain: access_denied_message, status: :forbidden else # Do not leak information about project existence render_not_found end end + def access_denied_message + 'Access denied' + end + def upload_pack_allowed? return false unless Gitlab.config.gitlab_shell.upload_pack diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 4aea7bb62c4..4f66e01e0f7 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -46,8 +46,9 @@ class Projects::IssuesController < Projects::ApplicationController params[:issue] ||= ActionController::Parameters.new( assignee_id: "" ) + build_params = issue_params.merge(merge_request_for_resolving_discussions: merge_request_for_resolving_discussions) + @issue = @noteable = Issues::BuildService.new(project, current_user, build_params).execute - @issue = @noteable = @project.issues.new(issue_params) respond_with(@issue) end @@ -75,7 +76,9 @@ class Projects::IssuesController < Projects::ApplicationController end def create - @issue = Issues::CreateService.new(project, current_user, issue_params.merge(request: request)).execute + extra_params = { request: request, + merge_request_for_resolving_discussions: merge_request_for_resolving_discussions } + @issue = Issues::CreateService.new(project, current_user, issue_params.merge(extra_params)).execute respond_to do |format| format.html do @@ -169,6 +172,14 @@ class Projects::IssuesController < Projects::ApplicationController alias_method :awardable, :issue alias_method :spammable, :issue + def merge_request_for_resolving_discussions + return unless merge_request_iid = params[:merge_request_for_resolving_discussions] + + @merge_request_for_resolving_discussions ||= MergeRequestsFinder.new(current_user, project_id: project.id). + execute. + find_by(iid: merge_request_iid) + end + def authorize_read_issue! return render_404 unless can?(current_user, :read_issue, @issue) end diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb index 2d493276941..440259b643c 100644 --- a/app/controllers/projects/lfs_api_controller.rb +++ b/app/controllers/projects/lfs_api_controller.rb @@ -1,8 +1,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController - include LfsHelper + include LfsRequest - before_action :require_lfs_enabled! - before_action :lfs_check_access!, except: [:deprecated] + skip_before_action :lfs_check_access!, only: [:deprecated] def batch unless objects.present? @@ -31,6 +30,14 @@ class Projects::LfsApiController < Projects::GitHttpClientController private + def download_request? + params[:operation] == 'download' + end + + def upload_request? + params[:operation] == 'upload' + end + def existing_oids @existing_oids ||= begin storage_project.lfs_objects.where(oid: objects.map { |o| o['oid'].to_s }).pluck(:oid) @@ -79,12 +86,4 @@ class Projects::LfsApiController < Projects::GitHttpClientController } } end - - def download_request? - params[:operation] == 'download' - end - - def upload_request? - params[:operation] == 'upload' - end end diff --git a/app/controllers/projects/lfs_storage_controller.rb b/app/controllers/projects/lfs_storage_controller.rb index 9005b104e90..32759672b6c 100644 --- a/app/controllers/projects/lfs_storage_controller.rb +++ b/app/controllers/projects/lfs_storage_controller.rb @@ -1,9 +1,8 @@ class Projects::LfsStorageController < Projects::GitHttpClientController - include LfsHelper + include LfsRequest + include WorkhorseRequest - before_action :require_lfs_enabled! - before_action :lfs_check_access! - before_action :verify_workhorse_api!, only: [:upload_authorize] + skip_before_action :verify_workhorse_api!, only: [:download, :upload_finalize] def download lfs_object = LfsObject.find_by_oid(oid) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index e24a670631f..f0cb5a9d4b4 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -302,9 +302,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def cancel_merge_when_build_succeeds - return access_denied! unless @merge_request.can_cancel_merge_when_build_succeeds?(current_user) + unless @merge_request.can_cancel_merge_when_build_succeeds?(current_user) + return access_denied! + end - MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user).cancel(@merge_request) + MergeRequests::MergeWhenPipelineSucceedsService + .new(@project, current_user) + .cancel(@merge_request) end def merge @@ -325,16 +329,18 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.update(merge_error: nil) if params[:merge_when_build_succeeds].present? - unless @merge_request.pipeline + unless @merge_request.head_pipeline @status = :failed return end - if @merge_request.pipeline.active? - MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params) - .execute(@merge_request) + if @merge_request.head_pipeline.active? + MergeRequests::MergeWhenPipelineSucceedsService + .new(@project, current_user, merge_params) + .execute(@merge_request) + @status = :merge_when_build_succeeds - elsif @merge_request.pipeline.success? + elsif @merge_request.head_pipeline.success? # This can be triggered when a user clicks the auto merge button while # the tests finish at about the same time MergeWorker.perform_async(@merge_request.id, current_user.id, params) @@ -398,7 +404,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def ci_status - pipeline = @merge_request.pipeline + pipeline = @merge_request.head_pipeline + if pipeline status = pipeline.status coverage = pipeline.try(:coverage) @@ -491,7 +498,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def validates_merge_request # Show git not found page # if there is no saved commits between source & target branch - if @merge_request.commits.blank? + if @merge_request.has_no_commits? # and if target branch doesn't exist return invalid_mr unless @merge_request.target_branch_exists? end @@ -499,7 +506,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def define_show_vars @noteable = @merge_request - @commits_count = @merge_request.commits.count + @commits_count = @merge_request.commits_count if @merge_request.locked_long_ago? @merge_request.unlock_mr @@ -534,7 +541,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def define_widget_vars - @pipeline = @merge_request.pipeline + @pipeline = @merge_request.head_pipeline end def define_commit_vars @@ -563,8 +570,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController def define_pipelines_vars @pipelines = @merge_request.all_pipelines - @pipeline = @merge_request.pipeline - @statuses = @pipeline.statuses.relevant if @pipeline.present? + @pipeline = @merge_request.head_pipeline + @statuses_count = @pipeline.present? ? @pipeline.statuses.relevant.count : 0 end def define_new_vars @@ -631,7 +638,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def merge_when_build_succeeds_active? params[:merge_when_build_succeeds].present? && - @merge_request.pipeline && @merge_request.pipeline.active? + @merge_request.head_pipeline && @merge_request.head_pipeline.active? end def build_merge_request diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index f029fde2a2f..15ca080c696 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -197,6 +197,7 @@ class Projects::NotesController < Projects::ApplicationController ) end + attrs[:commands_changes] = note.commands_changes unless attrs[:award] attrs end diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 533af80aee0..85188cfdd4c 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -1,6 +1,6 @@ class Projects::PipelinesController < Projects::ApplicationController before_action :pipeline, except: [:index, :new, :create] - before_action :commit, only: [:show] + before_action :commit, only: [:show, :builds] before_action :authorize_read_pipeline! before_action :authorize_create_pipeline!, only: [:new, :create] before_action :authorize_update_pipeline!, only: [:retry, :cancel] @@ -32,6 +32,14 @@ class Projects::PipelinesController < Projects::ApplicationController def show end + def builds + respond_to do |format| + format.html do + render 'show' + end + end + end + def retry pipeline.retry_failed(current_user) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 699a56ae2f8..53308948f62 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -10,14 +10,37 @@ class Projects::ProjectMembersController < Projects::ApplicationController @project_members = @project.project_members @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) + group = @project.group + + if group + # We need `.where.not(user_id: nil)` here otherwise when a group has an + # invitee, it would make the following query return 0 rows since a NULL + # user_id would be present in the subquery + # See http://stackoverflow.com/questions/129077/not-in-clause-and-null-values + # FIXME: This whole logic should be moved to a finder! + non_null_user_ids = @project_members.where.not(user_id: nil).select(:user_id) + group_members = group.group_members.where.not(user_id: non_null_user_ids) + group_members = group_members.non_invite unless can?(current_user, :admin_group, @group) + end + if params[:search].present? - users = @project.users.search(params[:search]).to_a - @project_members = @project_members.where(user_id: users) + user_ids = @project.users.search(params[:search]).select(:id) + @project_members = @project_members.where(user_id: user_ids) + + if group_members + user_ids = group.users.search(params[:search]).select(:id) + group_members = group_members.where(user_id: user_ids) + end @group_links = @project.project_group_links.where(group_id: @project.invited_groups.search(params[:search]).select(:id)) end - @project_members = @project_members.order(access_level: :desc).page(params[:page]) + wheres = ["id IN (#{@project_members.select(:id).to_sql})"] + wheres << "id IN (#{group_members.select(:id).to_sql})" if group_members + + @project_members = Member. + where(wheres.join(' OR ')). + order(access_level: :desc).page(params[:page]) @requesters = AccessRequestsFinder.new(@project).execute(current_user) diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb index 0825a4311cb..2c097cb4d8d 100644 --- a/app/controllers/projects/releases_controller.rb +++ b/app/controllers/projects/releases_controller.rb @@ -10,7 +10,14 @@ class Projects::ReleasesController < Projects::ApplicationController end def update - release.update_attributes(release_params) + # Release belongs to Tag which is not active record object, + # it exists only to save a description to each Tag. + # If description is empty we should destroy the existing record. + if release_params[:description].present? + release.update_attributes(release_params) + else + release.destroy + end redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name) end diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index e290a0eadda..0720be2e55d 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -19,10 +19,12 @@ class Projects::SnippetsController < Projects::ApplicationController respond_to :html def index - @snippets = SnippetsFinder.new.execute(current_user, { + @snippets = SnippetsFinder.new.execute( + current_user, filter: :by_project, - project: @project - }) + project: @project, + scope: params[:scope] + ) @snippets = @snippets.page(params[:page]) end diff --git a/app/controllers/projects/todos_controller.rb b/app/controllers/projects/todos_controller.rb index 5685d0f4e7c..a41fcb85c40 100644 --- a/app/controllers/projects/todos_controller.rb +++ b/app/controllers/projects/todos_controller.rb @@ -16,15 +16,9 @@ class Projects::TodosController < Projects::ApplicationController @issuable ||= begin case params[:issuable_type] when "issue" - issue = @project.issues.find(params[:issuable_id]) - - if can?(current_user, :read_issue, issue) - issue - else - render_404 - end + IssuesFinder.new(current_user, project_id: @project.id).find(params[:issuable_id]) when "merge_request" - @project.merge_requests.find(params[:issuable_id]) + MergeRequestsFinder.new(current_user, project_id: @project.id).find(params[:issuable_id]) end end end diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 177ccf5eec9..c3353446fd1 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -115,6 +115,8 @@ class Projects::WikisController < Projects::ApplicationController # Call #wiki to make sure the Wiki Repo is initialized @project_wiki.wiki + + @sidebar_wiki_pages = @project_wiki.pages.first(15) rescue ProjectWiki::CouldNotCreateWikiError flash[:notice] = "Could not create Wiki Repository at this time. Please try again later." redirect_to project_path(@project) diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 3327f4f2b87..c45196cc3e9 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -27,7 +27,10 @@ class RegistrationsController < Devise::RegistrationsController DeleteUserService.new(current_user).execute(current_user) respond_to do |format| - format.html { redirect_to new_user_session_path, notice: "Account successfully removed." } + format.html do + session.try(:destroy) + redirect_to new_user_session_path, notice: "Account successfully removed." + end end end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 5d7ecfeacf4..8c698695202 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -31,10 +31,18 @@ class SessionsController < Devise::SessionsController resource.update_attributes(reset_password_token: nil, reset_password_sent_at: nil) end + # hide the signed-in notification + flash[:notice] = nil log_audit_event(current_user, with: authentication_method) end end + def destroy + super + # hide the signed_out notice + flash[:notice] = nil + end + private # Handle an "initial setup" state, where there's only one user, it's an admin, diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 6297b2db369..b4c14d05eaf 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -7,7 +7,7 @@ # current_user - which user use # params: # scope: 'created-by-me' or 'assigned-to-me' or 'all' -# state: 'open' or 'closed' or 'all' +# state: 'opened' or 'closed' or 'all' # group_id: integer # project_id: integer # milestone_title: string @@ -15,15 +15,14 @@ # search: string # label_name: string # sort: string +# non_archived: boolean # -require_relative 'projects_finder' - class IssuableFinder NONE = '0' attr_accessor :current_user, :params - def initialize(current_user, params) + def initialize(current_user, params = {}) @current_user = current_user @params = params end @@ -40,9 +39,48 @@ class IssuableFinder items = by_author(items) items = by_label(items) items = by_due_date(items) + items = by_non_archived(items) sort(items) end + def find(*params) + execute.find(*params) + end + + def find_by(*params) + execute.find_by(*params) + end + + # We often get counts for each state by running a query per state, and + # counting those results. This is typically slower than running one query + # (even if that query is slower than any of the individual state queries) and + # grouping and counting within that query. + # + def count_by_state + count_params = params.merge(state: nil, sort: nil) + labels_count = label_names.any? ? label_names.count : 1 + finder = self.class.new(current_user, count_params) + counts = Hash.new(0) + + # Searching by label includes a GROUP BY in the query, but ours will be last + # because it is added last. Searching by multiple labels also includes a row + # per issuable, so we have to count those in Ruby - which is bad, but still + # better than performing multiple queries. + # + finder.execute.reorder(nil).group(:state).count.each do |key, value| + counts[Array(key).last.to_sym] += value / labels_count + end + + counts[:all] = counts.values.sum + counts[:opened] += counts[:reopened] + + counts + end + + def find_by!(*params) + execute.find_by!(*params) + end + def group return @group if defined?(@group) @@ -175,10 +213,13 @@ class IssuableFinder end def by_state(items) - params[:state] ||= 'all' - - if items.respond_to?(params[:state]) - items.public_send(params[:state]) + case params[:state].to_s + when 'closed' + items.closed + when 'merged' + items.respond_to?(:merged) ? items.merged : items.closed + when 'opened' + items.opened else items end @@ -321,6 +362,10 @@ class IssuableFinder end end + def by_non_archived(items) + params[:non_archived].present? ? items.non_archived : items + end + def current_user_related? params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' end diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb index 3b254e7d9d5..8b82255445e 100644 --- a/app/finders/merge_requests_finder.rb +++ b/app/finders/merge_requests_finder.rb @@ -14,6 +14,7 @@ # search: string # label_name: string # sort: string +# non_archived: boolean # class MergeRequestsFinder < IssuableFinder def klass diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb index 0b7832e6583..2484339e3a4 100644 --- a/app/finders/notes_finder.rb +++ b/app/finders/notes_finder.rb @@ -12,9 +12,9 @@ class NotesFinder when "commit" project.notes.for_commit_id(target_id).non_diff_notes when "issue" - project.issues.visible_to_user(current_user).find(target_id).notes.inc_author + IssuesFinder.new(current_user, project_id: project.id).find(target_id).notes.inc_author when "merge_request" - project.merge_requests.find(target_id).mr_and_commit_notes.inc_author + MergeRequestsFinder.new(current_user, project_id: project.id).find(target_id).mr_and_commit_notes.inc_author when "snippet", "project_snippet" project.snippets.find(target_id).notes else diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index 00ff1611039..da6e6e87a6f 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -1,14 +1,17 @@ class SnippetsFinder def execute(current_user, params = {}) filter = params[:filter] + user = params.fetch(:user, current_user) case filter when :all then snippets(current_user).fresh + when :public then + Snippet.are_public.fresh when :by_user then - by_user(current_user, params[:user], params[:scope]) + by_user(current_user, user, params[:scope]) when :by_project - by_project(current_user, params[:project]) + by_project(current_user, params[:project], params[:scope]) end end @@ -29,35 +32,35 @@ class SnippetsFinder def by_user(current_user, user, scope) snippets = user.snippets.fresh - return snippets.are_public unless current_user - - if user == current_user - case scope - when 'are_internal' then - snippets.are_internal - when 'are_private' then - snippets.are_private - when 'are_public' then - snippets.are_public - else - snippets - end + if current_user + include_private = user == current_user + by_scope(snippets, scope, include_private) else - snippets.public_and_internal + snippets.are_public end end - def by_project(current_user, project) + def by_project(current_user, project, scope) snippets = project.snippets.fresh if current_user - if project.team.member?(current_user) || current_user.admin? - snippets - else - snippets.public_and_internal - end + include_private = project.team.member?(current_user) || current_user.admin? + by_scope(snippets, scope, include_private) else snippets.are_public end end + + def by_scope(snippets, scope = nil, include_private = false) + case scope.to_s + when 'are_private' + include_private ? snippets.are_private : Snippet.none + when 'are_internal' + snippets.are_internal + when 'are_public' + snippets.are_public + else + include_private ? snippets : snippets.public_and_internal + end + end end diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index dee3c78df47..4c7c16d694c 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -16,7 +16,7 @@ module ButtonHelper # See http://clipboardjs.com/#usage def clipboard_button(data = {}) css_class = data[:class] || 'btn-clipboard btn-transparent' - title = data[:title] || 'Copy to Clipboard' + title = data[:title] || 'Copy to clipboard' data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) content_tag :button, icon('clipboard'), diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index abcf84b4d15..8e19752a8a1 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -5,8 +5,9 @@ module CiStatusHelper end def ci_status_with_icon(status, target = nil) - content = ci_icon_for_status(status) + ci_label_for_status(status) + content = ci_icon_for_status(status) + ci_text_for_status(status) klass = "ci-status ci-#{status}" + if target link_to content, target, class: klass else @@ -14,7 +15,19 @@ module CiStatusHelper end end + def ci_text_for_status(status) + if detailed_status?(status) + status.text + else + status + end + end + def ci_label_for_status(status) + if detailed_status?(status) + return status.label + end + case status when 'success' 'passed' @@ -31,6 +44,10 @@ module CiStatusHelper end def ci_icon_for_status(status) + if detailed_status?(status) + return custom_icon(status.icon) + end + icon_name = case status when 'success' @@ -94,4 +111,10 @@ module CiStatusHelper class: klass, title: title, data: data end end + + def detailed_status?(status) + status.respond_to?(:text) && + status.respond_to?(:label) && + status.respond_to?(:icon) + end end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index ed402b698fb..66a720a9426 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -130,7 +130,7 @@ module CommitsHelper def revert_commit_link(commit, continue_to_path, btn_class: nil, has_tooltip: true) return unless current_user - tooltip = "Revert this #{commit.change_type_title} in a new merge request" if has_tooltip + tooltip = "Revert this #{commit.change_type_title(current_user)} in a new merge request" if has_tooltip if can_collaborate_with_project? btn_class = "btn btn-warning btn-#{btn_class}" unless btn_class.nil? @@ -154,7 +154,7 @@ module CommitsHelper def cherry_pick_commit_link(commit, continue_to_path, btn_class: nil, has_tooltip: true) return unless current_user - tooltip = "Cherry-pick this #{commit.change_type_title} in a new merge request" + tooltip = "Cherry-pick this #{commit.change_type_title(current_user)} in a new merge request" if can_collaborate_with_project? btn_class = "btn btn-default btn-#{btn_class}" unless btn_class.nil? diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index f489f9aa0d6..c35d6611ab0 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -55,7 +55,9 @@ module DiffHelper if line.blank? " ".html_safe else - line.sub(/^[\-+ ]/, '').html_safe + # We can't use `sub` because the HTML-safeness of `line` will not survive. + line[0] = '' if line.start_with?('+', '-', ' ') + line end end diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index cbab1fd5967..81e0b6bb5ae 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -43,7 +43,7 @@ module DropdownsHelper default_label = data_attr[:default_label] content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}") - output << icon('caret-down') + output << icon('chevron-down') output.html_safe end end diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb index 27975b7ddb7..ff8550439d0 100644 --- a/app/helpers/environment_helper.rb +++ b/app/helpers/environment_helper.rb @@ -14,10 +14,12 @@ module EnvironmentHelper end end - def deployment_link(deployment) + def deployment_link(deployment, text: nil) return unless deployment - link_to "##{deployment.iid}", [deployment.project.namespace.becomes(Namespace), deployment.project, deployment.deployable] + link_label = text ? text : "##{deployment.iid}" + + link_to link_label, [deployment.project.namespace.becomes(Namespace), deployment.project, deployment.deployable] end def last_deployment_link_for_environment_build(project, build) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index f1a0b929d82..362046c0270 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -45,6 +45,12 @@ module EventsHelper @project.feature_available?(feature_key, current_user) end + def comments_visible? + event_filter_visible(:repository) || + event_filter_visible(:merge_requests) || + event_filter_visible(:issues) + end + def event_preposition(event) if event.push? || event.commented? || event.target "at" diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 0772d848289..eb435cc1783 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -174,7 +174,7 @@ module GitlabMarkdownHelper # Returns a String def cross_project_reference(project, entity) if entity.respond_to?(:to_reference) - "#{project.to_reference}#{entity.to_reference}" + entity.to_reference(project) else '' end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb index af9087d8326..99db73c9ee0 100644 --- a/app/helpers/gitlab_routing_helper.rb +++ b/app/helpers/gitlab_routing_helper.rb @@ -159,6 +159,11 @@ module GitlabRoutingHelper resend_invite_namespace_project_project_member_path(project_member.source.namespace, project_member.source, project_member) end + # Snippets + def personal_snippet_url(snippet, *args) + snippet_url(snippet) + end + # Groups ## Members diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 75cd9eece5c..f6d4ea4659a 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -5,14 +5,10 @@ module GroupsHelper def group_icon(group) if group.is_a?(String) - group = Group.find_by(path: group) + group = Group.find_by_full_path(group) end - if group && group.avatar.present? - group.avatar.url - else - image_path('no_group_avatar.png') - end + group.try(:avatar_url) || image_path('no_group_avatar.png') end def group_title(group, name = nil, url = nil) diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 8bebda07787..8231f8fa334 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -143,6 +143,20 @@ module IssuablesHelper end end + def issuable_filter_params + [ + :search, + :author_id, + :assignee_id, + :milestone_title, + :label_name + ] + end + + def issuable_filter_present? + issuable_filter_params.any? { |k| params.key?(k) } + end + private def assigned_issuables_count(assignee, issuable_type, state) @@ -165,17 +179,10 @@ module IssuablesHelper end end - def issuable_filters_present - params[:search] || params[:author_id] || params[:assignee_id] || params[:milestone_title] || params[:label_name] - end - def issuables_count_for_state(issuable_type, state) - issuables_finder = public_send("#{issuable_type}_finder") - - params = issuables_finder.params.merge(state: state) - finder = issuables_finder.class.new(issuables_finder.current_user, params) - - finder.execute.page(1).total_count + @counts ||= {} + @counts[issuable_type] ||= public_send("#{issuable_type}_finder").count_by_state + @counts[issuable_type][state] end IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page] @@ -185,6 +192,7 @@ module IssuablesHelper opts = params.with_indifferent_access opts[:state] = state opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY) + opts.delete_if { |_, value| value.blank? } hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-')) end diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 4f180456b16..e5b1e6e8bc7 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -82,12 +82,6 @@ module LabelsHelper span.html_safe end - def render_colored_cross_project_label(label, source_project = nil, tooltip: true) - label_suffix = source_project ? source_project.name_with_namespace : label.project.name_with_namespace - label_suffix = " <i>in #{escape_once(label_suffix)}</i>" - render_colored_label(label, label_suffix, tooltip: tooltip) - end - def suggested_colors [ '#0033CC', @@ -166,6 +160,5 @@ module LabelsHelper end # Required for Banzai::Filter::LabelReferenceFilter - module_function :render_colored_label, :render_colored_cross_project_label, - :text_color_for_bg, :escape_once + module_function :render_colored_label, :text_color_for_bg, :escape_once end diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index df87fac132d..a3331dc80cb 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -20,6 +20,11 @@ module NavHelper end elsif current_path?('builds#show') "page-gutter build-sidebar right-sidebar-expanded" + elsif current_path?('wikis#show') || + current_path?('wikis#edit') || + current_path?('wikis#history') || + current_path?('wikis#git_access') + "page-gutter wiki-sidebar right-sidebar-expanded" end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 898ce6a3af7..9cda3b78761 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -390,21 +390,7 @@ module ProjectsHelper "success" end end - - def new_readme_path - ref = @repository.root_ref if @repository - ref ||= 'master' - - namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'README.md') - end - - def new_license_path - ref = @repository.root_ref if @repository - ref ||= 'master' - - namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'LICENSE') - end - + def readme_cache_key sha = @project.commit.try(:sha) || 'nil' [@project.path_with_namespace, sha, "readme"].join('-') diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb index 7e33a562077..8c02b4061ca 100644 --- a/app/helpers/snippets_helper.rb +++ b/app/helpers/snippets_helper.rb @@ -8,6 +8,17 @@ module SnippetsHelper end end + # Return the path of a snippets index for a user or for a project + # + # @returns String, path to snippet index + def subject_snippets_path(subject = nil, opts = nil) + if subject.is_a?(Project) + namespace_project_snippets_path(subject.namespace, subject, opts) + else # assume subject === User + dashboard_snippets_path(opts) + end + end + # Get an array of line numbers surrounding a matching # line, bounded by min/max. # diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index 96116e916dd..0d20c9092c4 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -4,6 +4,7 @@ module Emails setup_note_mail(note_id, recipient_id) @commit = @note.noteable + @discussion = @note.to_discussion if @note.diff_note? @target_url = namespace_project_commit_url(*note_target_url_options) mail_answer_thread(@commit, @@ -24,6 +25,7 @@ module Emails setup_note_mail(note_id, recipient_id) @merge_request = @note.noteable + @discussion = @note.to_discussion if @note.diff_note? @target_url = namespace_project_merge_request_url(*note_target_url_options) mail_answer_thread(@merge_request, note_thread_options(recipient_id)) end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index e7d33bd26db..88c46076df6 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -203,7 +203,7 @@ module Ci .reorder(iid: :asc) merge_requests.find do |merge_request| - merge_request.commits.any? { |ci| ci.id == pipeline.sha } + merge_request.commits_sha.include?(pipeline.sha) end end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index d1ce43570ac..c40201652a9 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -21,8 +21,6 @@ module Ci after_create :keep_around_commits, unless: :importing? - delegate :stages, to: :statuses - state_machine :status, initial: :created do event :enqueue do transition created: :pending @@ -108,17 +106,35 @@ module Ci sha[0...8] end - def self.stages - # We use pluck here due to problems with MySQL which doesn't allow LIMIT/OFFSET in queries - CommitStatus.where(pipeline: pluck(:id)).stages - end - def self.total_duration where.not(duration: nil).sum(:duration) end - def stages_with_latest_statuses - statuses.latest.includes(project: :namespace).order(:stage_idx).group_by(&:stage) + def stages_count + statuses.select(:stage).distinct.count + end + + def stages_name + statuses.order(:stage_idx).distinct. + pluck(:stage, :stage_idx).map(&:first) + end + + def stages + status_sql = statuses.latest.where('stage=sg.stage').status_sql + + stages_query = statuses.group('stage').select(:stage) + .order('max(stage_idx)') + + stages_with_statuses = CommitStatus.from(stages_query, :sg). + pluck('sg.stage', status_sql) + + stages_with_statuses.map do |stage| + Ci::Stage.new(self, name: stage.first, status: stage.last) + end + end + + def artifacts + builds.latest.with_artifacts_not_expired end def project_id @@ -171,23 +187,27 @@ module Ci end def retryable? - builds.latest.any? do |build| - (build.failed? || build.canceled?) && build.retryable? - end + builds.latest.failed_or_canceled.any?(&:retryable?) end def cancelable? - builds.running_or_pending.any? + statuses.cancelable.any? end def cancel_running - builds.running_or_pending.each(&:cancel) + Gitlab::OptimisticLocking.retry_lock( + statuses.cancelable) do |cancelable| + cancelable.each(&:cancel) + end end def retry_failed(user) - builds.latest.failed.select(&:retryable?).each do |build| - Ci::Build.retry(build, user) - end + 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 end def mark_as_processable_after_stage(stage_idx) @@ -323,7 +343,11 @@ module Ci def merge_requests @merge_requests ||= project.merge_requests .where(source_branch: self.ref) - .select { |merge_request| merge_request.pipeline.try(:id) == self.id } + .select { |merge_request| merge_request.head_pipeline.try(:id) == self.id } + end + + def detailed_status + Gitlab::Ci::Status::Pipeline::Factory.new(self).fabricate! end private diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb new file mode 100644 index 00000000000..d2a37c0a827 --- /dev/null +++ b/app/models/ci/stage.rb @@ -0,0 +1,37 @@ +module Ci + # Currently this is artificial object, constructed dynamically + # We should migrate this object to actual database record in the future + class Stage + include StaticModel + + attr_reader :pipeline, :name + + delegate :project, to: :pipeline + + def initialize(pipeline, name:, status: nil) + @pipeline = pipeline + @name = name + @status = status + end + + def to_param + name + end + + def status + @status ||= statuses.latest.status + end + + def detailed_status + Gitlab::Ci::Status::Stage::Factory.new(self).fabricate! + end + + def statuses + @statuses ||= pipeline.statuses.where(stage: name) + end + + def builds + @builds ||= pipeline.builds.where(stage: name) + end + end +end diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index 94d9e2b3208..2c8698d8b5d 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -4,10 +4,10 @@ module Ci belongs_to :project, foreign_key: :gl_project_id - validates_uniqueness_of :key, scope: :gl_project_id validates :key, presence: true, - length: { within: 0..255 }, + uniqueness: { scope: :gl_project_id }, + length: { maximum: 255 }, format: { with: /\A[a-zA-Z0-9_]+\z/, message: "can contain only letters, digits and '_'." } diff --git a/app/models/commit.rb b/app/models/commit.rb index 946bfc4712c..91c7970fca6 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -48,6 +48,10 @@ class Commit max_lines: DIFF_HARD_LIMIT_LINES, } end + + def from_hash(hash, project) + new(Gitlab::Git::Commit.new(hash), project) + end end attr_accessor :raw @@ -88,19 +92,11 @@ class Commit end def to_reference(from_project = nil) - if cross_project_reference?(from_project) - project.to_reference + self.class.reference_prefix + self.id - else - self.id - end + commit_reference(from_project, id) end def reference_link_text(from_project = nil) - if cross_project_reference?(from_project) - project.to_reference + self.class.reference_prefix + self.short_id - else - self.short_id - end + commit_reference(from_project, short_id) end def diff_line_count @@ -245,44 +241,47 @@ class Commit project.repository.next_branch("cherry-pick-#{short_id}", mild: true) end - def revert_description - if merged_merge_request - "This reverts merge request #{merged_merge_request.to_reference}" + def revert_description(user) + if merged_merge_request?(user) + "This reverts merge request #{merged_merge_request(user).to_reference}" else "This reverts commit #{sha}" end end - def revert_message - %Q{Revert "#{title.strip}"\n\n#{revert_description}} + def revert_message(user) + %Q{Revert "#{title.strip}"\n\n#{revert_description(user)}} end - def reverts_commit?(commit) - description? && description.include?(commit.revert_description) + def reverts_commit?(commit, user) + description? && description.include?(commit.revert_description(user)) end def merge_commit? parents.size > 1 end - def merged_merge_request - return @merged_merge_request if defined?(@merged_merge_request) - - @merged_merge_request = project.merge_requests.find_by(merge_commit_sha: id) if merge_commit? + def merged_merge_request(current_user) + # Memoize with per-user access check + @merged_merge_request_hash ||= Hash.new do |hash, user| + hash[user] = merged_merge_request_no_cache(user) + end + + @merged_merge_request_hash[current_user] end - def has_been_reverted?(current_user = nil, noteable = self) + def has_been_reverted?(current_user, noteable = self) ext = all_references(current_user) noteable.notes_with_associations.system.each do |note| note.all_references(current_user, extractor: ext) end - ext.commits.any? { |commit_ref| commit_ref.reverts_commit?(self) } + ext.commits.any? { |commit_ref| commit_ref.reverts_commit?(self, current_user) } end - def change_type_title - merged_merge_request ? 'merge request' : 'commit' + def change_type_title(user) + merged_merge_request?(user) ? 'merge request' : 'commit' end # Get the URI type of the given path @@ -321,6 +320,16 @@ class Commit private + def commit_reference(from_project, referable_commit_id) + reference = project.to_reference(from_project) + + if reference.present? + "#{reference}#{self.class.reference_prefix}#{referable_commit_id}" + else + referable_commit_id + end + end + def find_author_by_any_email User.find_by_any_email(author_email.downcase) end @@ -340,4 +349,12 @@ class Commit changes end + + def merged_merge_request?(user) + !!merged_merge_request(user) + end + + def merged_merge_request_no_cache(user) + MergeRequestsFinder.new(user, project_id: project.id).find_by(merge_commit_sha: id) if merge_commit? + end end diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index ac2477fd973..d9af7f6c139 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -90,21 +90,24 @@ class CommitRange alias_method :id, :to_s def to_reference(from_project = nil) - if cross_project_reference?(from_project) - project.to_reference + self.class.reference_prefix + self.id + project_reference = project.to_reference(from_project) + + if project_reference.present? + project_reference + self.class.reference_prefix + self.id else self.id end end def reference_link_text(from_project = nil) - reference = ref_from + notation + ref_to + project_reference = project.to_reference(from_project) + reference = ref_from + notation + ref_to - if cross_project_reference?(from_project) - reference = project.to_reference + self.class.reference_prefix + reference + if project_reference.present? + project_reference + self.class.reference_prefix + reference + else + reference end - - reference end # Return a Hash of parameters for passing to a URL helper diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index c345bf293c9..cf90475f4d4 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -31,18 +31,13 @@ class CommitStatus < ActiveRecord::Base end scope :exclude_ignored, -> do - quoted_when = connection.quote_column_name('when') # We want to ignore failed_but_allowed jobs where("allow_failure = ? OR status IN (?)", - false, all_state_names - [:failed, :canceled]). - # We want to ignore skipped manual jobs - where("#{quoted_when} <> ? OR status <> ?", 'manual', 'skipped'). - # We want to ignore skipped on_failure - where("#{quoted_when} <> ? OR status <> ?", 'on_failure', 'skipped') + false, all_state_names - [:failed, :canceled]) end - scope :latest_ci_stages, -> { latest.ordered.includes(project: :namespace) } - scope :retried_ci_stages, -> { retried.ordered.includes(project: :namespace) } + scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) } + scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) } state_machine :status do event :enqueue do @@ -117,20 +112,6 @@ class CommitStatus < ActiveRecord::Base name.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip end - def self.stages - # We group by stage name, but order stages by theirs' index - unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage') - end - - def self.stages_status - # We execute subquery for each stage to calculate a stage status - statuses = unscoped.from(all, :sg).group('stage').pluck('sg.stage', all.where('stage=sg.stage').status_sql) - statuses.inject({}) do |h, k| - h[k.first] = k.last - h - end - end - def failed_but_allowed? allow_failure? && (failed? || canceled?) end diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index ef3e73a4072..90432fc4050 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -4,7 +4,7 @@ module HasStatus AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped] STARTED_STATUSES = %w[running success failed skipped] ACTIVE_STATUSES = %w[pending running] - COMPLETED_STATUSES = %w[success failed canceled] + COMPLETED_STATUSES = %w[success failed canceled skipped] ORDERED_STATUSES = %w[failed pending running canceled success skipped] class_methods do @@ -23,9 +23,10 @@ module HasStatus canceled = scope.canceled.select('count(*)').to_sql "(CASE + WHEN (#{builds})=(#{skipped}) THEN 'skipped' WHEN (#{builds})=(#{success}) THEN 'success' WHEN (#{builds})=(#{created}) THEN 'created' - WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'skipped' + WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success' WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled' WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending' WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running' @@ -73,6 +74,11 @@ module HasStatus scope :skipped, -> { where(status: 'skipped') } scope :running_or_pending, -> { where(status: [:running, :pending]) } scope :finished, -> { where(status: [:success, :failed, :canceled]) } + scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) } + + scope :cancelable, -> do + where(status: [:running, :pending, :created]) + end end def started? diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 69d8afc45da..0ea7b1b1098 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -41,7 +41,7 @@ module Issuable has_one :metrics validates :author, presence: true - validates :title, presence: true, length: { within: 0..255 } + validates :title, presence: true, length: { maximum: 255 } scope :authored, ->(user) { where(author_id: user) } scope :assigned_to, ->(u) { where(assignee_id: u.id)} diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb index e65fc9eaa09..875e9834487 100644 --- a/app/models/concerns/milestoneish.rb +++ b/app/models/concerns/milestoneish.rb @@ -1,17 +1,17 @@ module Milestoneish - def closed_items_count(user = nil) + def closed_items_count(user) issues_visible_to_user(user).closed.size + merge_requests.closed_and_merged.size end - def total_items_count(user = nil) + def total_items_count(user) issues_visible_to_user(user).size + merge_requests.size end - def complete?(user = nil) + def complete?(user) total_items_count(user) > 0 && total_items_count(user) == closed_items_count(user) end - def percent_complete(user = nil) + def percent_complete(user) ((closed_items_count(user) * 100) / total_items_count(user)).abs rescue ZeroDivisionError 0 @@ -29,7 +29,7 @@ module Milestoneish (Date.today - start_date).to_i end - def issues_visible_to_user(user = nil) + def issues_visible_to_user(user) issues.visible_to_user(user) end diff --git a/app/models/concerns/protected_branch_access.rb b/app/models/concerns/protected_branch_access.rb index 7fd0905ee81..9dd4d9c6f24 100644 --- a/app/models/concerns/protected_branch_access.rb +++ b/app/models/concerns/protected_branch_access.rb @@ -2,6 +2,9 @@ module ProtectedBranchAccess extend ActiveSupport::Concern included do + belongs_to :protected_branch + delegate :project, to: :protected_branch + scope :master, -> { where(access_level: Gitlab::Access::MASTER) } scope :developer, -> { where(access_level: Gitlab::Access::DEVELOPER) } end @@ -9,4 +12,10 @@ module ProtectedBranchAccess def humanize self.class.human_access_levels[self.access_level] end + + def check_access(user) + return true if user.is_admin? + + project.team.max_member_access(user.id) >= access_level + end end diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb index dee940a3f88..8ba009fe04f 100644 --- a/app/models/concerns/referable.rb +++ b/app/models/concerns/referable.rb @@ -72,17 +72,4 @@ module Referable }x end end - - private - - # Check if a reference is being done cross-project - # - # from_project - Refering Project object - def cross_project_reference?(from_project) - if self.is_a?(Project) - self != from_project - else - from_project && self.project && self.project != from_project - end - end end diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb new file mode 100644 index 00000000000..1108a64c59e --- /dev/null +++ b/app/models/concerns/routable.rb @@ -0,0 +1,71 @@ +# Store object full path in separate table for easy lookup and uniq validation +# Object must have path db field and respond to full_path and full_path_changed? methods. +module Routable + extend ActiveSupport::Concern + + included do + has_one :route, as: :source, autosave: true, dependent: :destroy + + validates_associated :route + validates :route, presence: true + + before_validation :update_route_path, if: :full_path_changed? + end + + class_methods do + # Finds a single object by full path match in routes table. + # + # Usage: + # + # Klass.find_by_full_path('gitlab-org/gitlab-ce') + # + # Returns a single object, or nil. + def find_by_full_path(path) + # On MySQL we want to ensure the ORDER BY uses a case-sensitive match so + # any literal matches come first, for this we have to use "BINARY". + # Without this there's still no guarantee in what order MySQL will return + # rows. + binary = Gitlab::Database.mysql? ? 'BINARY' : '' + + order_sql = "(CASE WHEN #{binary} routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)" + + where_full_path_in([path]).reorder(order_sql).take + end + + # Builds a relation to find multiple objects by their full paths. + # + # Usage: + # + # Klass.where_full_path_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee}) + # + # Returns an ActiveRecord::Relation. + def where_full_path_in(paths) + wheres = [] + cast_lower = Gitlab::Database.postgresql? + + paths.each do |path| + path = connection.quote(path) + where = "(routes.path = #{path})" + + if cast_lower + where = "(#{where} OR (LOWER(routes.path) = LOWER(#{path})))" + end + + wheres << where + end + + if wheres.empty? + none + else + joins(:route).where(wheres.join(' OR ')) + end + end + end + + private + + def update_route_path + route || build_route(source: self) + route.path = full_path + end +end diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb index cb8e088d21d..ba4ee6fcf9d 100644 --- a/app/models/cycle_analytics.rb +++ b/app/models/cycle_analytics.rb @@ -1,14 +1,15 @@ class CycleAnalytics STAGES = %i[issue plan code test review staging production].freeze - def initialize(project, from:) + def initialize(project, current_user, from:) @project = project + @current_user = current_user @from = from @fetcher = Gitlab::CycleAnalytics::MetricsFetcher.new(project: project, from: from, branch: nil) end def summary - @summary ||= Summary.new(@project, from: @from) + @summary ||= Summary.new(@project, @current_user, from: @from) end def permissions(user:) diff --git a/app/models/cycle_analytics/summary.rb b/app/models/cycle_analytics/summary.rb index b46db449bf3..82f53d17ddd 100644 --- a/app/models/cycle_analytics/summary.rb +++ b/app/models/cycle_analytics/summary.rb @@ -1,12 +1,13 @@ class CycleAnalytics class Summary - def initialize(project, from:) + def initialize(project, current_user, from:) @project = project + @current_user = current_user @from = from end def new_issues - @project.issues.created_after(@from).count + IssuesFinder.new(@current_user, project_id: @project.id).execute.created_after(@from).count end def commits diff --git a/app/models/discussion.rb b/app/models/discussion.rb index de06c13481a..bbe813db823 100644 --- a/app/models/discussion.rb +++ b/app/models/discussion.rb @@ -25,7 +25,12 @@ class Discussion to: :last_resolved_note, allow_nil: true - delegate :blob, :highlighted_diff_lines, to: :diff_file, allow_nil: true + delegate :blob, + :highlighted_diff_lines, + :diff_lines, + + to: :diff_file, + allow_nil: true def self.for_notes(notes) notes.group_by(&:discussion_id).values.map { |notes| new(notes) } @@ -83,6 +88,10 @@ class Discussion @first_note ||= @notes.first end + def first_note_to_resolve + @first_note_to_resolve ||= notes.detect(&:to_be_resolved?) + end + def last_note @last_note ||= @notes.last end @@ -159,10 +168,11 @@ class Discussion end # Returns an array of at most 16 highlighted lines above a diff note - def truncated_diff_lines + def truncated_diff_lines(highlight: true) + lines = highlight ? highlighted_diff_lines : diff_lines prev_lines = [] - highlighted_diff_lines.each do |line| + lines.each do |line| if line.meta? prev_lines.clear else diff --git a/app/models/environment.rb b/app/models/environment.rb index a7f4156fc2e..96700143ddd 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -9,7 +9,7 @@ class Environment < ActiveRecord::Base validates :name, presence: true, uniqueness: { scope: :project_id }, - length: { within: 0..255 }, + length: { maximum: 255 }, format: { with: Gitlab::Regex.environment_name_regex, message: Gitlab::Regex.environment_name_regex_message } diff --git a/app/models/event.rb b/app/models/event.rb index 21eaca917b8..2662f170765 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -43,12 +43,6 @@ class Event < ActiveRecord::Base scope :for_milestone_id, ->(milestone_id) { where(target_type: "Milestone", target_id: milestone_id) } class << self - def reset_event_cache_for(target) - Event.where(target_id: target.id, target_type: target.class.to_s). - order('id DESC').limit(100). - update_all(updated_at: Time.now) - end - # Update Gitlab::ContributionsCalendar#activity_dates if this changes def contributions where("action = ? OR (target_type in (?) AND action in (?))", @@ -353,6 +347,10 @@ class Event < ActiveRecord::Base update_all(last_activity_at: created_at) end + def authored_by?(user) + user ? author_id == user.id : false + end + private def recent_update? diff --git a/app/models/issue.rb b/app/models/issue.rb index dd0cb75f9a8..7fe92051037 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -153,11 +153,7 @@ class Issue < ActiveRecord::Base def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{iid}" - if cross_project_reference?(from_project) - reference = project.to_reference + reference - end - - reference + "#{project.to_reference(from_project)}#{reference}" end def referenced_merge_requests(current_user = nil) @@ -182,18 +178,6 @@ class Issue < ActiveRecord::Base branches_with_iid - branches_with_merge_request end - # Reset issue events cache - # - # Since we do cache @event we need to reset cache in special cases: - # * when an issue is updated - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.reset_event_cache_for(self) - end - # To allow polymorphism with MergeRequest. def source_project project diff --git a/app/models/key.rb b/app/models/key.rb index ff8dda2dc89..a5d25409730 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -8,10 +8,18 @@ class Key < ActiveRecord::Base before_validation :generate_fingerprint - validates :title, presence: true, length: { within: 0..255 } - validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ } - validates :key, format: { without: /\n|\r/, message: 'should be a single line' } - validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' } + validates :title, + presence: true, + length: { maximum: 255 } + validates :key, + presence: true, + length: { maximum: 5000 }, + format: { with: /\A(ssh|ecdsa)-.*\Z/ } + validates :key, + format: { without: /\n|\r/, message: 'should be a single line' } + validates :fingerprint, + uniqueness: true, + presence: { message: 'cannot be generated' } delegate :name, :email, to: :user, prefix: true diff --git a/app/models/label.rb b/app/models/label.rb index d9287f2dc29..d38c37344c9 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -144,9 +144,10 @@ class Label < ActiveRecord::Base # # Examples: # - # Label.first.to_reference # => "~1" - # Label.first.to_reference(format: :name) # => "~\"bug\"" - # Label.first.to_reference(project1, project2) # => "gitlab-org/gitlab-ce~1" + # Label.first.to_reference # => "~1" + # Label.first.to_reference(format: :name) # => "~\"bug\"" + # Label.first.to_reference(project, same_namespace_project) # => "gitlab-ce~1" + # Label.first.to_reference(project, another_namespace_project) # => "gitlab-org/gitlab-ce~1" # # Returns a String # @@ -154,8 +155,8 @@ class Label < ActiveRecord::Base format_reference = label_format_reference(format) reference = "#{self.class.reference_prefix}#{format_reference}" - if cross_project_reference?(source_project, target_project) - source_project.to_reference + reference + if source_project + "#{source_project.to_reference(target_project)}#{reference}" else reference end @@ -169,10 +170,6 @@ class Label < ActiveRecord::Base private - def cross_project_reference?(source_project, target_project) - source_project && target_project && source_project != target_project - end - def issues_count(user, params = {}) params.merge!(subject_foreign_key => subject.id, label_name: title, scope: 'all') IssuesFinder.new(user, params.with_indifferent_access).execute.count diff --git a/app/models/member.rb b/app/models/member.rb index df93aaee847..3b65587c66b 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -63,6 +63,7 @@ class Member < ActiveRecord::Base after_create :send_request, if: :request?, unless: :importing? after_create :create_notification_setting, unless: [:pending?, :importing?] after_create :post_create_hook, unless: [:pending?, :importing?] + after_create :refresh_member_authorized_projects, if: :importing? after_update :post_update_hook, unless: [:pending?, :importing?] after_destroy :post_destroy_hook, unless: :pending? diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index fdf54cc8a7e..ea3cf1cdaac 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -22,7 +22,8 @@ class MergeRequest < ActiveRecord::Base after_create :ensure_merge_request_diff, unless: :importing? after_update :reload_diff_if_branch_changed - delegate :commits, :real_size, to: :merge_request_diff, prefix: nil + delegate :commits, :real_size, :commits_sha, :commits_count, + to: :merge_request_diff, prefix: nil # When this attribute is true some MR validation is ignored # It allows us to close or modify broken merge requests @@ -100,7 +101,9 @@ class MergeRequest < ActiveRecord::Base validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?] validate :validate_fork, unless: :closed_without_fork? - scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) } + scope :by_source_or_target_branch, ->(branch_name) do + where("source_branch = :branch OR target_branch = :branch", branch: branch_name) + end scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } scope :by_milestone, ->(milestone) { where(milestone_id: milestone) } scope :of_projects, ->(ids) { where(target_project_id: ids) } @@ -175,11 +178,7 @@ class MergeRequest < ActiveRecord::Base def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{iid}" - if cross_project_reference?(from_project) - reference = project.to_reference + reference - end - - reference + "#{project.to_reference(from_project)}#{reference}" end def first_commit @@ -479,6 +478,14 @@ class MergeRequest < ActiveRecord::Base @diff_discussions ||= self.notes.diff_notes.discussions end + def resolvable_discussions + @resolvable_discussions ||= diff_discussions.select(&:to_be_resolved?) + end + + def discussions_can_be_resolved_by?(user) + resolvable_discussions.all? { |discussion| discussion.can_resolve?(user) } + end + def find_diff_discussion(discussion_id) notes = self.notes.diff_notes.where(discussion_id: discussion_id).fresh.to_a return if notes.empty? @@ -605,18 +612,6 @@ class MergeRequest < ActiveRecord::Base self.target_project.repository.branch_names.include?(self.target_branch) end - # Reset merge request events cache - # - # Since we do cache @event we need to reset cache in special cases: - # * when a merge request is updated - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.reset_event_cache_for(self) - end - def merge_commit_message message = "Merge branch '#{source_branch}' into '#{target_branch}'\n\n" message << "#{title}\n\n" @@ -674,7 +669,7 @@ class MergeRequest < ActiveRecord::Base end def broken? - self.commits.blank? || branch_missing? || cannot_be_merged? + has_no_commits? || branch_missing? || cannot_be_merged? end def can_be_merged_by?(user) @@ -690,7 +685,7 @@ class MergeRequest < ActiveRecord::Base def mergeable_ci_state? return true unless project.only_allow_merge_if_build_succeeds? - !pipeline || pipeline.success? || pipeline.skipped? + !head_pipeline || head_pipeline.success? || head_pipeline.skipped? end def environments @@ -782,18 +777,14 @@ class MergeRequest < ActiveRecord::Base diverged_commits_count > 0 end - def commits_sha - commits.map(&:sha) - end - - def pipeline + def head_pipeline return unless diff_head_sha && source_project - @pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha) + @head_pipeline ||= source_project.pipeline_for(source_branch, diff_head_sha) end def all_pipelines - return unless source_project + return Ci::Pipeline.none unless source_project @all_pipelines ||= source_project.pipelines .where(sha: all_commits_sha, ref: source_branch) @@ -816,7 +807,7 @@ class MergeRequest < ActiveRecord::Base @merge_commit ||= project.commit(merge_commit_sha) if merge_commit_sha end - def can_be_reverted?(current_user = nil) + def can_be_reverted?(current_user) merge_commit && !merge_commit.has_been_reverted?(current_user, self) end @@ -883,4 +874,12 @@ class MergeRequest < ActiveRecord::Base @conflicts_can_be_resolved_in_ui = false end end + + def has_commits? + commits_count > 0 + end + + def has_no_commits? + !has_commits? + end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 58a24eb84cb..b8f36a2c958 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -127,11 +127,7 @@ class MergeRequestDiff < ActiveRecord::Base end def commits_sha - if @commits - commits.map(&:sha) - else - st_commits.map { |commit| commit[:id] } - end + st_commits.map { |commit| commit[:id] } end def diff_refs @@ -176,6 +172,10 @@ class MergeRequestDiff < ActiveRecord::Base CompareService.new.execute(project, head_commit_sha, project, sha, straight: straight) end + def commits_count + st_commits.count + end + private # Old GitLab implementations may have generated diffs as ["--broken-diff"]. diff --git a/app/models/milestone.rb b/app/models/milestone.rb index c774e69080c..45ca97adad1 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -113,19 +113,16 @@ class Milestone < ActiveRecord::Base # # Examples: # - # Milestone.first.to_reference # => "%1" - # Milestone.first.to_reference(format: :name) # => "%\"goal\"" - # Milestone.first.to_reference(project) # => "gitlab-org/gitlab-ce%1" + # Milestone.first.to_reference # => "%1" + # Milestone.first.to_reference(format: :name) # => "%\"goal\"" + # Milestone.first.to_reference(cross_namespace_project) # => "gitlab-org/gitlab-ce%1" + # Milestone.first.to_reference(same_namespace_project) # => "gitlab-ce%1" # def to_reference(from_project = nil, format: :iid) format_reference = milestone_format_reference(format) reference = "#{self.class.reference_prefix}#{format_reference}" - if cross_project_reference?(from_project) - project.to_reference + reference - else - reference - end + "#{project.to_reference(from_project)}#{reference}" end def reference_link_text(from_project = nil) diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 891dffac648..37374044551 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -4,25 +4,29 @@ class Namespace < ActiveRecord::Base include CacheMarkdownField include Sortable include Gitlab::ShellAdapter + include Routable cache_markdown_field :description, pipeline: :description has_many :projects, dependent: :destroy belongs_to :owner, class_name: "User" + belongs_to :parent, class_name: "Namespace" + has_many :children, class_name: "Namespace", foreign_key: :parent_id + validates :owner, presence: true, unless: ->(n) { n.type == "Group" } validates :name, - length: { within: 0..255 }, - namespace_name: true, presence: true, - uniqueness: true + uniqueness: true, + length: { maximum: 255 }, + namespace_name: true - validates :description, length: { within: 0..255 } + validates :description, length: { maximum: 255 } validates :path, - length: { within: 1..255 }, - namespace: true, presence: true, - uniqueness: { case_sensitive: false } + uniqueness: { case_sensitive: false }, + length: { maximum: 255 }, + namespace: true delegate :name, to: :owner, allow_nil: true, prefix: true @@ -86,7 +90,7 @@ class Namespace < ActiveRecord::Base end def to_param - path + full_path end def human_name @@ -150,6 +154,14 @@ class Namespace < ActiveRecord::Base Gitlab.config.lfs.enabled end + def full_path + if parent + parent.full_path + '/' + path + else + path + end + end + private def repository_storage_paths @@ -185,4 +197,8 @@ class Namespace < ActiveRecord::Base where(projects: { namespace_id: id }). find_each(&:refresh_members_authorized_projects) end + + def full_path_changed? + path_changed? || parent_id_changed? + end end diff --git a/app/models/note.rb b/app/models/note.rb index 9ff5e308ed2..08bd08743ef 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -19,6 +19,9 @@ class Note < ActiveRecord::Base # Banzai::ObjectRenderer attr_accessor :user_visible_reference_count + # Attribute used to store the attributes that have ben changed by slash commands. + attr_accessor :commands_changes + default_value_for :system, false attr_mentionable :note, pipeline: :note @@ -96,7 +99,7 @@ class Note < ActiveRecord::Base end def discussions - Discussion.for_notes(all) + Discussion.for_notes(fresh) end def grouped_diff_discussions @@ -198,19 +201,6 @@ class Note < ActiveRecord::Base super(noteable_type.to_s.classify.constantize.base_class.to_s) end - # Reset notes events cache - # - # Since we do cache @event we need to reset cache in special cases: - # * when a note is updated - # * when a note is removed - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.reset_event_cache_for(self) - end - def editable? !system? end diff --git a/app/models/project.rb b/app/models/project.rb index 9256e9ddd95..77d740081c6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -14,6 +14,7 @@ class Project < ActiveRecord::Base include TokenAuthenticatable include ProjectFeaturesCompatibility include SelectForProjectAuthorization + include Routable extend Gitlab::ConfigHelper @@ -172,13 +173,13 @@ class Project < ActiveRecord::Base validates :description, length: { maximum: 2000 }, allow_blank: true validates :name, presence: true, - length: { within: 0..255 }, + length: { maximum: 255 }, format: { with: Gitlab::Regex.project_name_regex, message: Gitlab::Regex.project_name_regex_message } validates :path, presence: true, project_path: true, - length: { within: 0..255 }, + length: { maximum: 255 }, format: { with: Gitlab::Regex.project_path_regex, message: Gitlab::Regex.project_path_regex_message } validates :namespace, presence: true @@ -324,87 +325,6 @@ class Project < ActiveRecord::Base non_archived.where(table[:name].matches(pattern)) end - # Finds a single project for the given path. - # - # path - The full project path (including namespace path). - # - # Returns a Project, or nil if no project could be found. - def find_with_namespace(path) - namespace_path, project_path = path.split('/', 2) - - return unless namespace_path && project_path - - namespace_path = connection.quote(namespace_path) - project_path = connection.quote(project_path) - - # On MySQL we want to ensure the ORDER BY uses a case-sensitive match so - # any literal matches come first, for this we have to use "BINARY". - # Without this there's still no guarantee in what order MySQL will return - # rows. - binary = Gitlab::Database.mysql? ? 'BINARY' : '' - - order_sql = "(CASE WHEN #{binary} namespaces.path = #{namespace_path} " \ - "AND #{binary} projects.path = #{project_path} THEN 0 ELSE 1 END)" - - where_paths_in([path]).reorder(order_sql).take - end - - # Builds a relation to find multiple projects by their full paths. - # - # Each path must be in the following format: - # - # namespace_path/project_path - # - # For example: - # - # gitlab-org/gitlab-ce - # - # Usage: - # - # Project.where_paths_in(%w{gitlab-org/gitlab-ce gitlab-org/gitlab-ee}) - # - # This would return the projects with the full paths matching the values - # given. - # - # paths - An Array of full paths (namespace path + project path) for which - # to find the projects. - # - # Returns an ActiveRecord::Relation. - def where_paths_in(paths) - wheres = [] - cast_lower = Gitlab::Database.postgresql? - - paths.each do |path| - namespace_path, project_path = path.split('/', 2) - - next unless namespace_path && project_path - - namespace_path = connection.quote(namespace_path) - project_path = connection.quote(project_path) - - where = "(namespaces.path = #{namespace_path} - AND projects.path = #{project_path})" - - if cast_lower - where = "( - #{where} - OR ( - LOWER(namespaces.path) = LOWER(#{namespace_path}) - AND LOWER(projects.path) = LOWER(#{project_path}) - ) - )" - end - - wheres << where - end - - if wheres.empty? - none - else - joins(:namespace).where(wheres.join(' OR ')) - end - end - def visibility_levels Gitlab::VisibilityLevel.options end @@ -419,7 +339,11 @@ class Project < ActiveRecord::Base def reference_pattern name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR - %r{(?<project>#{name_pattern}/#{name_pattern})} + + %r{ + ((?<namespace>#{name_pattern})\/)? + (?<project>#{name_pattern}) + }x end def trending @@ -436,6 +360,10 @@ class Project < ActiveRecord::Base def group_ids joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id) end + + # Add alias for Routable method for compatibility with old code. + # In future all calls `find_with_namespace` should be replaced with `find_by_full_path` + alias_method :find_with_namespace, :find_by_full_path end def lfs_enabled? @@ -650,8 +578,20 @@ class Project < ActiveRecord::Base end end - def to_reference(_from_project = nil) - path_with_namespace + def to_reference(from_project = nil) + if cross_namespace_reference?(from_project) + path_with_namespace + elsif cross_project_reference?(from_project) + path + end + end + + def to_human_reference(from_project = nil) + if cross_namespace_reference?(from_project) + name_with_namespace + elsif cross_project_reference?(from_project) + name + end end def web_url @@ -687,9 +627,9 @@ class Project < ActiveRecord::Base self.id end - def get_issue(issue_id) + def get_issue(issue_id, current_user) if default_issues_tracker? - issues.find_by(iid: issue_id) + IssuesFinder.new(current_user, project_id: id).find_by(iid: issue_id) else ExternalIssue.new(issue_id, self) end @@ -863,13 +803,14 @@ class Project < ActiveRecord::Base end alias_method :human_name, :name_with_namespace - def path_with_namespace - if namespace - namespace.path + '/' + path + def full_path + if namespace && path + namespace.full_path + '/' + path else path end end + alias_method :path_with_namespace, :full_path def execute_hooks(data, hooks_scope = :push_hooks) hooks.send(hooks_scope).each do |hook| @@ -976,7 +917,6 @@ class Project < ActiveRecord::Base begin gitlab_shell.mv_repository(repository_storage_path, "#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki") send_move_instructions(old_path_with_namespace) - reset_events_cache @old_path_with_namespace = old_path_with_namespace @@ -1043,22 +983,6 @@ class Project < ActiveRecord::Base attrs end - # Reset events cache related to this project - # - # Since we do cache @event we need to reset cache in special cases: - # * when project was moved - # * when project was renamed - # * when the project avatar changes - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.where(project_id: self.id). - order('id DESC').limit(100). - update_all(updated_at: Time.now) - end - def project_member(user) project_members.find_by(user_id: user) end @@ -1344,10 +1268,21 @@ class Project < ActiveRecord::Base private + # Check if a reference is being done cross-project + # + # from_project - Refering Project object + def cross_project_reference?(from_project) + from_project && self != from_project + end + def pushes_since_gc_redis_key "projects/#{id}/pushes_since_gc" end + def cross_namespace_reference?(from_project) + from_project && namespace != from_project.namespace + end + def default_branch_protected? current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL || current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE @@ -1363,4 +1298,8 @@ class Project < ActiveRecord::Base def validate_board_limit(board) raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS end + + def full_path_changed? + path_changed? || namespace_id_changed? + end end diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb index 86a06321e21..fe6d7aabb22 100644 --- a/app/models/project_services/buildkite_service.rb +++ b/app/models/project_services/buildkite_service.rb @@ -3,7 +3,8 @@ require "addressable/uri" class BuildkiteService < CiService ENDPOINT = "https://buildkite.com" - prop_accessor :project_url, :token, :enable_ssl_verification + prop_accessor :project_url, :token + boolean_accessor :enable_ssl_verification validates :project_url, presence: true, url: true, if: :activated? validates :token, presence: true, if: :activated? diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb index 5e4dd101c53..adc78a427ee 100644 --- a/app/models/project_services/drone_ci_service.rb +++ b/app/models/project_services/drone_ci_service.rb @@ -1,5 +1,6 @@ class DroneCiService < CiService - prop_accessor :drone_url, :token, :enable_ssl_verification + prop_accessor :drone_url, :token + boolean_accessor :enable_ssl_verification validates :drone_url, presence: true, url: true, if: :activated? validates :token, presence: true, if: :activated? diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index e0083c43adb..79285cbd26d 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -1,6 +1,6 @@ class EmailsOnPushService < Service - prop_accessor :send_from_committer_email - prop_accessor :disable_diffs + boolean_accessor :send_from_committer_email + boolean_accessor :disable_diffs prop_accessor :recipients validates :recipients, presence: true, if: :activated? @@ -24,20 +24,20 @@ class EmailsOnPushService < Service return unless supported_events.include?(push_data[:object_kind]) EmailsOnPushWorker.perform_async( - project_id, - recipients, - push_data, - send_from_committer_email: send_from_committer_email?, - disable_diffs: disable_diffs? + project_id, + recipients, + push_data, + send_from_committer_email: send_from_committer_email?, + disable_diffs: disable_diffs? ) end def send_from_committer_email? - self.send_from_committer_email == "1" + Gitlab::Utils.to_boolean(self.send_from_committer_email) end def disable_diffs? - self.disable_diffs == "1" + Gitlab::Utils.to_boolean(self.disable_diffs) end def fields diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 660a8ae3421..915f6fed74c 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -8,8 +8,8 @@ class HipchatService < Service ul ol li dl dt dd ] - prop_accessor :token, :room, :server, :notify, :color, :api_version - boolean_accessor :notify_only_broken_builds + prop_accessor :token, :room, :server, :color, :api_version + boolean_accessor :notify_only_broken_builds, :notify validates :token, presence: true, if: :activated? def initialize_properties @@ -75,7 +75,7 @@ class HipchatService < Service end def message_options(data = nil) - { notify: notify.present? && notify == '1', color: message_color(data) } + { notify: notify.present? && Gitlab::Utils.to_boolean(notify), color: message_color(data) } end def create_message(data) diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index ce7d1c5d5b1..7355918feab 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -2,7 +2,8 @@ require 'uri' class IrkerService < Service prop_accessor :server_host, :server_port, :default_irc_uri - prop_accessor :colorize_messages, :recipients, :channels + prop_accessor :recipients, :channels + boolean_accessor :colorize_messages validates :recipients, presence: true, if: :activated? before_validation :get_channels diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 70bbbbcda85..2d969d2fcb6 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -9,6 +9,9 @@ class JiraService < IssueTrackerService before_update :reset_password + # This is confusing, but JiraService does not really support these events. + # The values here are required to display correct options in the service + # configuration screen. def supported_events %w(commit merge_request) end @@ -105,18 +108,29 @@ class JiraService < IssueTrackerService "#{url}/secure/CreateIssue.jspa" end - def execute(push, issue = nil) - if issue.nil? - # No specific issue, that means - # we just want to test settings - test_settings - else - jira_issue = jira_request { client.Issue.find(issue.iid) } + def execute(push) + # This method is a no-op, because currently JiraService does not + # support any events. + end - return false unless jira_issue.present? + def close_issue(entity, external_issue) + issue = jira_request { client.Issue.find(external_issue.iid) } - close_issue(push, jira_issue) - end + return if issue.nil? || issue.resolution.present? || !jira_issue_transition_id.present? + + commit_id = if entity.is_a?(Commit) + entity.id + elsif entity.is_a?(MergeRequest) + entity.diff_head_sha + end + + commit_url = build_entity_url(:commit, commit_id) + + # Depending on the JIRA project's workflow, a comment during transition + # may or may not be allowed. Refresh the issue after transition and check + # if it is closed, so we don't have one comment for every commit. + issue = jira_request { client.Issue.find(issue.key) } if transition_issue(issue) + add_issue_solved_comment(issue, commit_id, commit_url) if issue.resolution end def create_cross_reference_note(mentioned, noteable, author) @@ -156,6 +170,11 @@ class JiraService < IssueTrackerService "Please fill in Password and Username." end + def test(_) + result = test_settings + { success: result.present?, result: result } + end + def can_test? username.present? && password.present? end @@ -182,24 +201,6 @@ class JiraService < IssueTrackerService end end - def close_issue(entity, issue) - return if issue.nil? || issue.resolution.present? || !jira_issue_transition_id.present? - - commit_id = if entity.is_a?(Commit) - entity.id - elsif entity.is_a?(MergeRequest) - entity.diff_head_sha - end - - commit_url = build_entity_url(:commit, commit_id) - - # Depending on the JIRA project's workflow, a comment during transition - # may or may not be allowed. Refresh the issue after transition and check - # if it is closed, so we don't have one comment for every commit. - issue = jira_request { client.Issue.find(issue.key) } if transition_issue(issue) - add_issue_solved_comment(issue, commit_id, commit_url) if issue.resolution - end - def transition_issue(issue) issue.transitions.build.save(transition: { id: jira_issue_transition_id }) end @@ -219,7 +220,7 @@ class JiraService < IssueTrackerService entity_title = data[:entity][:title] project_name = data[:project][:name] - message = "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title}'" + message = "[#{user_name}|#{user_url}] mentioned this issue in [a #{entity_name} of #{project_name}|#{entity_url}]:\n'#{entity_title.chomp}'" link_title = "GitLab: Mentioned on #{entity_name} - #{entity_title}" link_props = build_remote_link_props(url: entity_url, title: link_title) diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb index 806b3ccd275..771e3376613 100644 --- a/app/models/protected_branch/merge_access_level.rb +++ b/app/models/protected_branch/merge_access_level.rb @@ -1,9 +1,6 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base include ProtectedBranchAccess - belongs_to :protected_branch - delegate :project, to: :protected_branch - validates :access_level, presence: true, inclusion: { in: [Gitlab::Access::MASTER, Gitlab::Access::DEVELOPER] } @@ -13,10 +10,4 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base Gitlab::Access::DEVELOPER => "Developers + Masters" }.with_indifferent_access end - - def check_access(user) - return true if user.is_admin? - - project.team.max_member_access(user.id) >= access_level - end end diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb index 92e9c51d883..14610cb42b7 100644 --- a/app/models/protected_branch/push_access_level.rb +++ b/app/models/protected_branch/push_access_level.rb @@ -1,9 +1,6 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base include ProtectedBranchAccess - belongs_to :protected_branch - delegate :project, to: :protected_branch - validates :access_level, presence: true, inclusion: { in: [Gitlab::Access::MASTER, Gitlab::Access::DEVELOPER, Gitlab::Access::NO_ACCESS] } @@ -18,8 +15,7 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base def check_access(user) return false if access_level == Gitlab::Access::NO_ACCESS - return true if user.is_admin? - project.team.max_member_access(user.id) >= access_level + super end end diff --git a/app/models/repository.rb b/app/models/repository.rb index bf136ccdb6c..1ccabdb7c1f 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -85,11 +85,7 @@ class Repository # This method return true if repository contains some content visible in project page. # def has_visible_content? - return @has_visible_content unless @has_visible_content.nil? - - @has_visible_content = cache.fetch(:has_visible_content?) do - branch_count > 0 - end + branch_count > 0 end def commit(ref = 'HEAD') @@ -196,18 +192,12 @@ class Repository options = { message: message, tagger: user_to_committer(user) } if message - rugged.tags.create(tag_name, target, options) - tag = find_tag(tag_name) - - GitHooksService.new.execute(user, path_to_repo, oldrev, tag.target, ref) do - # we already created a tag, because we need tag SHA to pass correct - # values to hooks + GitHooksService.new.execute(user, path_to_repo, oldrev, target, ref) do |service| + raw_tag = rugged.tags.create(tag_name, target, options) + service.newrev = raw_tag.target_id end - tag - rescue GitHooksService::PreReceiveError - rugged.tags.delete(tag_name) - raise + find_tag(tag_name) end def rm_branch(user, branch_name) @@ -380,12 +370,6 @@ class Repository return unless empty? expire_method_caches(%i(empty?)) - expire_has_visible_content_cache - end - - def expire_has_visible_content_cache - cache.expire(:has_visible_content?) - @has_visible_content = nil end def lookup_cache @@ -473,7 +457,6 @@ class Repository # Runs code after a new branch has been created. def after_create_branch expire_branches_cache - expire_has_visible_content_cache repository_event(:push_branch) end @@ -487,7 +470,6 @@ class Repository # Runs code after an existing branch has been removed. def after_remove_branch - expire_has_visible_content_cache expire_branches_cache end @@ -968,7 +950,7 @@ class Repository update_branch_with_hooks(user, base_branch) do committer = user_to_committer(user) source_sha = Rugged::Commit.create(rugged, - message: commit.revert_message, + message: commit.revert_message(user), author: committer, committer: committer, tree: revert_tree_id, diff --git a/app/models/route.rb b/app/models/route.rb new file mode 100644 index 00000000000..d40214b9da6 --- /dev/null +++ b/app/models/route.rb @@ -0,0 +1,22 @@ +class Route < ActiveRecord::Base + belongs_to :source, polymorphic: true + + validates :source, presence: true + + validates :path, + length: { within: 1..255 }, + presence: true, + uniqueness: { case_sensitive: false } + + after_update :rename_children, if: :path_changed? + + def rename_children + # We update each row separately because MySQL does not have regexp_replace. + # rubocop:disable Rails/FindEach + Route.where('path LIKE ?', "#{path_was}%").each do |route| + # Note that update column skips validation and callbacks. + # We need this to avoid recursive call of rename_children method + route.update_column(:path, route.path.sub(path_was, path)) + end + end +end diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 8ff4e7ae718..98ccf5f331f 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -27,9 +27,9 @@ class Snippet < ActiveRecord::Base delegate :name, :email, to: :author, prefix: true, allow_nil: true validates :author, presence: true - validates :title, presence: true, length: { within: 0..255 } + validates :title, presence: true, length: { maximum: 255 } validates :file_name, - length: { within: 0..255 }, + length: { maximum: 255 }, format: { with: Gitlab::Regex.file_name_regex, message: Gitlab::Regex.file_name_regex_message } @@ -67,11 +67,11 @@ class Snippet < ActiveRecord::Base def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{id}" - if cross_project_reference?(from_project) - reference = project.to_reference + reference + if project.present? + "#{project.to_reference(from_project)}#{reference}" + else + reference end - - reference end def self.content_types @@ -94,6 +94,10 @@ class Snippet < ActiveRecord::Base 0 end + def file_name + super.to_s + end + # alias for compatibility with blobs and highlighting def path file_name diff --git a/app/models/user.rb b/app/models/user.rb index 513a19d81d2..1bd28203523 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -304,10 +304,6 @@ class User < ActiveRecord::Base personal_access_token.user if personal_access_token end - def by_username_or_id(name_or_id) - find_by('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i) - end - # Returns a user for the given SSH key. def find_by_ssh_key_id(key_id) find_by(id: Key.unscoped.select(:user_id).where(id: key_id)) @@ -445,27 +441,21 @@ class User < ActiveRecord::Base end def refresh_authorized_projects - loop do - begin - Gitlab::Database.serialized_transaction do - project_authorizations.delete_all - - # project_authorizations_union can return multiple records for the same project/user with - # different access_level so we take row with the maximum access_level - project_authorizations.connection.execute <<-SQL - INSERT INTO project_authorizations (user_id, project_id, access_level) - SELECT user_id, project_id, MAX(access_level) AS access_level - FROM (#{project_authorizations_union.to_sql}) sub - GROUP BY user_id, project_id - SQL - - update_column(:authorized_projects_populated, true) unless authorized_projects_populated - end - - break - # In the event of a concurrent modification Rails raises StatementInvalid. - # In this case we want to keep retrying until the transaction succeeds - rescue ActiveRecord::StatementInvalid + transaction do + project_authorizations.delete_all + + # project_authorizations_union can return multiple records for the same + # project/user with different access_level so we take row with the maximum + # access_level + project_authorizations.connection.execute <<-SQL + INSERT INTO project_authorizations (user_id, project_id, access_level) + SELECT user_id, project_id, MAX(access_level) AS access_level + FROM (#{project_authorizations_union.to_sql}) sub + GROUP BY user_id, project_id + SQL + + unless authorized_projects_populated + update_column(:authorized_projects_populated, true) end end end @@ -518,7 +508,7 @@ class User < ActiveRecord::Base end def require_ssh_key? - keys.count == 0 + keys.count == 0 && Gitlab::ProtocolAccess.allowed?('ssh') end def require_password? @@ -708,20 +698,6 @@ class User < ActiveRecord::Base project.project_member(self) end - # Reset project events cache related to this user - # - # Since we do cache @event we need to reset cache in special cases: - # * when the user changes their avatar - # Events cache stored like events/23-20130109142513. - # The cache key includes updated_at timestamp. - # Thus it will automatically generate a new fragment - # when the event is updated because the key changes. - def reset_events_cache - Event.where(author_id: id). - order('id DESC').limit(1000). - update_all(updated_at: Time.now) - end - def full_website_url return "http://#{website_url}" if website_url !~ /\Ahttps?:\/\// diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb index 8b25332b73c..7b1752df0e1 100644 --- a/app/policies/ci/build_policy.rb +++ b/app/policies/ci/build_policy.rb @@ -1,6 +1,8 @@ module Ci class BuildPolicy < CommitStatusPolicy def rules + can! :read_build if @subject.project.public_builds? + super # If we can't read build we should also not have that diff --git a/app/policies/group_member_policy.rb b/app/policies/group_member_policy.rb index 62335527654..5a3fe814b77 100644 --- a/app/policies/group_member_policy.rb +++ b/app/policies/group_member_policy.rb @@ -15,5 +15,11 @@ class GroupMemberPolicy < BasePolicy elsif @user == target_user can! :destroy_group_member end + + additional_rules! + end + + def additional_rules! + # This is meant to be overriden in EE end end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index b65fb68cd88..6f943feb2a7 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -33,6 +33,8 @@ class GroupPolicy < BasePolicy if globally_viewable && @subject.request_access_enabled && !member can! :request_access end + + additional_rules!(master) end def can_read_group? @@ -43,4 +45,8 @@ class GroupPolicy < BasePolicy GroupProjectsFinder.new(@subject).execute(@user).any? end + + def additional_rules!(master) + # This is meant to be overriden in EE + end end diff --git a/app/policies/personal_snippet_policy.rb b/app/policies/personal_snippet_policy.rb index 46c5aa1a5be..d3913986cd8 100644 --- a/app/policies/personal_snippet_policy.rb +++ b/app/policies/personal_snippet_policy.rb @@ -6,9 +6,14 @@ class PersonalSnippetPolicy < BasePolicy if @subject.author == @user can! :read_personal_snippet can! :update_personal_snippet + can! :destroy_personal_snippet can! :admin_personal_snippet end + unless @user.external? + can! :create_personal_snippet + end + if @subject.internal? && !@user.external? can! :read_personal_snippet end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 1ee31023e26..d5aadfce76a 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -12,9 +12,6 @@ class ProjectPolicy < BasePolicy guest_access! public_access! - # Allow to read builds for internal projects - can! :read_build if project.public_builds? - if project.request_access_enabled && !(owner || user.admin? || project.team.member?(user) || project_group_member?(user)) can! :request_access @@ -46,10 +43,16 @@ class ProjectPolicy < BasePolicy can! :create_note can! :upload_file can! :read_cycle_analytics + + if project.public_builds? + can! :read_pipeline + can! :read_build + end end def reporter_access! can! :download_code + can! :download_wiki_code can! :fork_project can! :create_project_snippet can! :update_issue @@ -187,6 +190,7 @@ class ProjectPolicy < BasePolicy unless project.feature_available?(:wiki, user) || project.has_external_wiki? cannot!(*named_abilities(:wiki)) + cannot!(:download_wiki_code) end unless project.feature_available?(:builds, user) && repository_enabled @@ -226,6 +230,7 @@ class ProjectPolicy < BasePolicy can! :read_commit_status can! :read_container_image can! :download_code + can! :download_wiki_code can! :read_cycle_analytics # NOTE: may be overridden by IssuePolicy diff --git a/app/serializers/analytics_build_entity.rb b/app/serializers/analytics_build_entity.rb index abefcd5cc02..a0db5b8f0f4 100644 --- a/app/serializers/analytics_build_entity.rb +++ b/app/serializers/analytics_build_entity.rb @@ -13,7 +13,7 @@ class AnalyticsBuildEntity < Grape::Entity end expose :duration, as: :total_time do |build| - distance_of_time_as_hash(build.duration.to_f) + build.duration ? distance_of_time_as_hash(build.duration.to_f) : {} end expose :branch do diff --git a/app/serializers/build_entity.rb b/app/serializers/build_entity.rb index cf1c418a88e..b5384e6462b 100644 --- a/app/serializers/build_entity.rb +++ b/app/serializers/build_entity.rb @@ -16,6 +16,9 @@ class BuildEntity < Grape::Entity path_to(:play_namespace_project_build, build) end + expose :created_at + expose :updated_at + private def path_to(route, build) diff --git a/app/serializers/entity_date_helper.rb b/app/serializers/entity_date_helper.rb index 918abba8d99..9607ad55a8b 100644 --- a/app/serializers/entity_date_helper.rb +++ b/app/serializers/entity_date_helper.rb @@ -2,6 +2,8 @@ module EntityDateHelper include ActionView::Helpers::DateHelper def interval_in_words(diff) + return 'Not started' unless diff + "#{distance_of_time_in_words(Time.now, diff)} ago" end diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index cde856b0186..e3bc9847200 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -45,9 +45,15 @@ module Ci return error('No builds for this pipeline.') end - pipeline.save - pipeline.process! - pipeline + Ci::Pipeline.transaction do + pipeline.save + + Ci::CreatePipelineBuildsService + .new(project, current_user) + .execute(pipeline) + end + + pipeline.tap(&:process!) end private diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index 8face432d97..79eb97b7b55 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -5,10 +5,7 @@ module Ci def execute(pipeline) @pipeline = pipeline - # This method will ensure that our pipeline does have all builds for all stages created - if created_builds.empty? - create_builds! - end + ensure_created_builds! # TODO, remove me in 9.0 new_builds = stage_indexes_of_created_builds.map do |index| @@ -22,10 +19,6 @@ module Ci private - def create_builds! - Ci::CreatePipelineBuildsService.new(project, current_user).execute(pipeline) - end - def process_stage(index) current_status = status_for_prior_stages(index) @@ -51,11 +44,11 @@ module Ci def valid_statuses_for_when(value) case value when 'on_success' - %w[success] + %w[success skipped] when 'on_failure' %w[failed] when 'always' - %w[success failed] + %w[success failed skipped] else [] end @@ -76,5 +69,18 @@ module Ci def created_builds pipeline.builds.created end + + # This method is DEPRECATED and should be removed in 9.0. + # + # We need it to maintain backwards compatibility with previous versions + # when builds were not created within one transaction with the pipeline. + # + def ensure_created_builds! + return if created_builds.any? + + Ci::CreatePipelineBuildsService + .new(project, current_user) + .execute(pipeline) + end end end diff --git a/app/services/commits/change_service.rb b/app/services/commits/change_service.rb index 1c82599c579..4d410f66c55 100644 --- a/app/services/commits/change_service.rb +++ b/app/services/commits/change_service.rb @@ -34,8 +34,8 @@ module Commits repository.public_send(action, current_user, @commit, into, tree_id) success else - error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title} automatically. - It may have already been #{action.to_s.dasherize}, or a more recent commit may have updated some of its content." + error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title(current_user)} automatically. + A #{action.to_s.dasherize} may have already been performed with this #{@commit.change_type_title(current_user)}, or a more recent commit may have updated some of its content." raise ChangeError, error_msg end end diff --git a/app/services/destroy_group_service.rb b/app/services/destroy_group_service.rb index a880952e274..2316c57bf1e 100644 --- a/app/services/destroy_group_service.rb +++ b/app/services/destroy_group_service.rb @@ -20,6 +20,10 @@ class DestroyGroupService ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute end + group.children.each do |group| + DestroyGroupService.new(group, current_user).async_execute + end + group.really_destroy! end end diff --git a/app/services/discussions/base_service.rb b/app/services/discussions/base_service.rb new file mode 100644 index 00000000000..e4dfe6e71bb --- /dev/null +++ b/app/services/discussions/base_service.rb @@ -0,0 +1,4 @@ +module Discussions + class BaseService < ::BaseService + end +end diff --git a/app/services/discussions/resolve_service.rb b/app/services/discussions/resolve_service.rb new file mode 100644 index 00000000000..0437195f588 --- /dev/null +++ b/app/services/discussions/resolve_service.rb @@ -0,0 +1,24 @@ +module Discussions + class ResolveService < Discussions::BaseService + def execute(one_or_more_discussions) + Array(one_or_more_discussions).each { |discussion| resolve_discussion(discussion) } + end + + def resolve_discussion(discussion) + return unless discussion.can_resolve?(current_user) + + discussion.resolve!(current_user) + + MergeRequests::ResolvedDiscussionNotificationService.new(project, current_user).execute(merge_request) + SystemNoteService.discussion_continued_in_issue(discussion, project, current_user, follow_up_issue) if follow_up_issue + end + + def merge_request + params[:merge_request] + end + + def follow_up_issue + params[:follow_up_issue] + end + end +end diff --git a/app/services/git_hooks_service.rb b/app/services/git_hooks_service.rb index 172bd85dade..6cd3908d43a 100644 --- a/app/services/git_hooks_service.rb +++ b/app/services/git_hooks_service.rb @@ -1,6 +1,8 @@ class GitHooksService PreReceiveError = Class.new(StandardError) + attr_accessor :oldrev, :newrev, :ref + def execute(user, repo_path, oldrev, newrev, ref) @repo_path = repo_path @user = Gitlab::GlId.gl_id(user) @@ -16,7 +18,7 @@ class GitHooksService end end - yield + yield self run_hook('post-receive') end @@ -25,6 +27,6 @@ class GitHooksService def run_hook(name) hook = Gitlab::Git::Hook.new(name, @repo_path) - hook.trigger(@user, @oldrev, @newrev, @ref) + hook.trigger(@user, oldrev, newrev, ref) end end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 647930d555c..185556c12cc 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -135,7 +135,7 @@ class GitPushService < BaseService @push_commits.each do |commit| ProcessCommitWorker. - perform_async(project.id, current_user.id, commit.id, default) + perform_async(project.id, current_user.id, commit.to_hash, default) end end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 575795788de..b5f63cc5a1a 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -85,14 +85,15 @@ class IssuableBaseService < BaseService def find_or_create_label_ids labels = params.delete(:labels) + return unless labels - params[:label_ids] = labels.split(',').map do |label_name| + params[:label_ids] = labels.split(",").map do |label_name| service = Labels::FindOrCreateService.new(current_user, project, title: label_name.strip) label = service.execute - label.id - end + label.try(:id) + end.compact end def process_label_ids(attributes, existing_label_ids: nil) @@ -119,9 +120,10 @@ class IssuableBaseService < BaseService def merge_slash_commands_into_params!(issuable) description, command_params = SlashCommands::InterpretService.new(project, current_user). - execute(params[:description], issuable) + execute(params[:description], issuable) - params[:description] = description + # Avoid a description already set on an issuable to be overwritten by a nil + params[:description] = description if params.has_key?(:description) params.merge!(command_params) end @@ -140,6 +142,7 @@ class IssuableBaseService < BaseService params.delete(:state_event) params[:author] ||= current_user + label_ids = process_label_ids(params) issuable.assign_attributes(params) @@ -184,8 +187,6 @@ class IssuableBaseService < BaseService params[:label_ids] = process_label_ids(params, existing_label_ids: issuable.label_ids) if params.present? && update_issuable(issuable, params) - issuable.reset_events_cache - # 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) diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 9ea3ce084ba..742e834df97 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -1,5 +1,13 @@ module Issues class BaseService < ::IssuableBaseService + attr_reader :merge_request_for_resolving_discussions + + def initialize(*args) + super + + @merge_request_for_resolving_discussions ||= params.delete(:merge_request_for_resolving_discussions) + end + def hook_data(issue, action) issue_data = issue.to_hook_data(current_user) issue_url = Gitlab::UrlBuilder.build(issue) diff --git a/app/services/issues/build_service.rb b/app/services/issues/build_service.rb new file mode 100644 index 00000000000..a63982f60c8 --- /dev/null +++ b/app/services/issues/build_service.rb @@ -0,0 +1,50 @@ +module Issues + class BuildService < Issues::BaseService + def execute + @issue = project.issues.new(issue_params) + end + + def issue_params_with_info_from_merge_request + return {} unless merge_request_for_resolving_discussions + + { title: title_from_merge_request, description: description_from_merge_request } + end + + def title_from_merge_request + "Follow-up from \"#{merge_request_for_resolving_discussions.title}\"" + end + + def description_from_merge_request + if merge_request_for_resolving_discussions.resolvable_discussions.empty? + return "There are no unresolved discussions. "\ + "Review the conversation in #{merge_request_for_resolving_discussions.to_reference}" + end + + description = "The following discussions from #{merge_request_for_resolving_discussions.to_reference} should be addressed:" + [description, *items_for_discussions].join("\n\n") + end + + def items_for_discussions + merge_request_for_resolving_discussions.resolvable_discussions.map { |discussion| item_for_discussion(discussion) } + end + + def item_for_discussion(discussion) + first_note = discussion.first_note_to_resolve + other_note_count = discussion.notes.size - 1 + creation_time = first_note.created_at.to_s(:medium) + note_url = Gitlab::UrlBuilder.build(first_note) + + discussion_info = "- [ ] #{first_note.author.to_reference} commented in a discussion on [#{creation_time}](#{note_url}): " + discussion_info << " (+#{other_note_count} #{'comment'.pluralize(other_note_count)})" if other_note_count > 0 + + note_without_block_quotes = Banzai::Filter::BlockquoteFenceFilter.new(first_note.note).call + quote = ">>>\n#{note_without_block_quotes}\n>>>" + + [discussion_info, quote].join("\n\n") + end + + def issue_params + @issue_params ||= issue_params_with_info_from_merge_request.merge(params.slice(:title, :description)) + end + end +end diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb index ab4c51386a4..f1030912c68 100644 --- a/app/services/issues/close_service.rb +++ b/app/services/issues/close_service.rb @@ -17,7 +17,7 @@ module Issues # allowed to close the given issue. def close_issue(issue, commit: nil, notifications: true, system_note: true) if project.jira_tracker? && project.jira_service.active - project.jira_service.execute(commit, issue) + project.jira_service.close_issue(commit, issue) todo_service.close_issue(issue, current_user) return issue end diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb index ea1690f3e38..d2eb46ac41b 100644 --- a/app/services/issues/create_service.rb +++ b/app/services/issues/create_service.rb @@ -4,7 +4,8 @@ module Issues @request = params.delete(:request) @api = params.delete(:api) - @issue = project.issues.new + issue_attributes = params.merge(merge_request_for_resolving_discussions: merge_request_for_resolving_discussions) + @issue = BuildService.new(project, current_user, issue_attributes).execute create(@issue) end @@ -18,6 +19,17 @@ module Issues notification_service.new_issue(issuable, current_user) todo_service.new_issue(issuable, current_user) user_agent_detail_service.create + + if merge_request_for_resolving_discussions.try(:discussions_can_be_resolved_by?, current_user) + resolve_discussions_in_merge_request(issuable) + end + end + + def resolve_discussions_in_merge_request(issue) + Discussions::ResolveService.new(project, current_user, + merge_request: merge_request_for_resolving_discussions, + follow_up_issue: issue). + execute(merge_request_for_resolving_discussions.resolvable_discussions) end private diff --git a/app/services/labels/find_or_create_service.rb b/app/services/labels/find_or_create_service.rb index d622f9edd33..cf4f7606c94 100644 --- a/app/services/labels/find_or_create_service.rb +++ b/app/services/labels/find_or_create_service.rb @@ -22,9 +22,14 @@ module Labels ).execute(skip_authorization: skip_authorization) end + # Only creates the label if current_user can do so, if the label does not exist + # and the user can not create the label, nil is returned def find_or_create_label new_label = available_labels.find_by(title: title) - new_label ||= project.labels.create(params) + + if new_label.nil? && (skip_authorization || Ability.allowed?(current_user, :admin_label, project)) + new_label = project.labels.create(params) + end new_label end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 58f69a41e14..800fd39c424 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -55,7 +55,7 @@ module MergeRequests def pipeline_merge_requests(pipeline) merge_requests_for(pipeline.ref).each do |merge_request| - next unless pipeline == merge_request.pipeline + next unless pipeline == merge_request.head_pipeline yield merge_request end @@ -63,7 +63,7 @@ module MergeRequests def commit_status_merge_requests(commit_status) merge_requests_for(commit_status.ref).each do |merge_request| - pipeline = merge_request.pipeline + pipeline = merge_request.head_pipeline next unless pipeline next unless pipeline.sha == commit_status.sha diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index dd0d738674e..bebfca7537b 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -81,7 +81,7 @@ module MergeRequests commit = commits.first merge_request.title = commit.title merge_request.description ||= commit.description.try(:strip) - elsif iid && (issue = merge_request.target_project.get_issue(iid)) && !issue.try(:confidential?) + elsif iid && issue = merge_request.target_project.get_issue(iid, current_user) case issue when Issue merge_request.title = "Resolve \"#{issue.title}\"" diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb index dc159de0058..5616edf8b4a 100644 --- a/app/services/merge_requests/merge_when_build_succeeds_service.rb +++ b/app/services/merge_requests/merge_when_pipeline_succeeds_service.rb @@ -1,5 +1,5 @@ module MergeRequests - class MergeWhenBuildSucceedsService < MergeRequests::BaseService + class MergeWhenPipelineSucceedsService < MergeRequests::BaseService # Marks the passed `merge_request` to be merged when the build succeeds or # updates the params for the automatic merge def execute(merge_request) diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 22596b4014a..0a9563ed7e7 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -55,15 +55,16 @@ module MergeRequests # Refresh merge request diff if we push to source or target branch of merge request # Note: we should update merge requests from forks too def reload_merge_requests - merge_requests = @project.merge_requests.opened.by_branch(@branch_name).to_a - merge_requests += fork_merge_requests.by_branch(@branch_name).to_a + merge_requests = @project.merge_requests.opened. + by_source_or_target_branch(@branch_name).to_a + merge_requests += fork_merge_requests merge_requests = filter_merge_requests(merge_requests) merge_requests.each do |merge_request| if merge_request.source_branch == @branch_name || force_push? merge_request.reload_diff else - mr_commit_ids = merge_request.commits.map(&:id) + mr_commit_ids = merge_request.commits_sha push_commit_ids = @commits.map(&:id) matches = mr_commit_ids & push_commit_ids merge_request.reload_diff if matches.any? @@ -123,7 +124,7 @@ module MergeRequests return unless @commits.present? merge_requests_for_source_branch.each do |merge_request| - mr_commit_ids = Set.new(merge_request.commits.map(&:id)) + mr_commit_ids = Set.new(merge_request.commits_sha) new_commits, existing_commits = @commits.partition do |commit| mr_commit_ids.include?(commit.id) @@ -157,13 +158,14 @@ module MergeRequests def merge_requests_for_source_branch @source_merge_requests ||= begin merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a - merge_requests += fork_merge_requests.where(source_branch: @branch_name).to_a + merge_requests += fork_merge_requests filter_merge_requests(merge_requests) end end def fork_merge_requests - @fork_merge_requests ||= @project.fork_merge_requests.opened + @fork_merge_requests ||= @project.fork_merge_requests.opened. + where(source_branch: @branch_name).to_a end def branch_added? diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 7935fabe2da..d75592e31f3 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -43,6 +43,8 @@ module Notes if only_commands note.errors.add(:commands_only, 'Your commands have been executed!') end + + note.commands_changes = command_params.keys end note diff --git a/app/services/notes/delete_service.rb b/app/services/notes/delete_service.rb index 7f1b30ec84e..a673e8e9dde 100644 --- a/app/services/notes/delete_service.rb +++ b/app/services/notes/delete_service.rb @@ -2,7 +2,6 @@ module Notes class DeleteService < BaseService def execute(note) note.destroy - note.reset_events_cache end end end diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb index 1361b1e0300..75a4b3ed826 100644 --- a/app/services/notes/update_service.rb +++ b/app/services/notes/update_service.rb @@ -5,7 +5,6 @@ module Notes note.update_attributes(params.merge(updated_by: current_user)) note.create_new_cross_references!(current_user) - note.reset_events_cache if note.previous_changes.include?('note') TodoService.new.update_note(note, current_user) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index ecdcbf08ee1..9a7af5730d2 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -171,7 +171,6 @@ class NotificationService return true unless note.noteable_type.present? # ignore gitlab service messages - return true if note.note.start_with?('Status changed to closed') return true if note.cross_reference? && note.system? target = note.noteable diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 28470f59807..34ec575e808 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -61,9 +61,6 @@ module Projects # Move missing group labels to project Labels::TransferService.new(current_user, old_group, project).execute - # clear project cached events - project.reset_events_cache - # Move uploads Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 1ce66d50368..8b48d90f60b 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -21,7 +21,7 @@ module SystemNoteService total_count = new_commits.length + existing_commits.length commits_text = "#{total_count} commit".pluralize(total_count) - body = "Added #{commits_text}:\n\n" + body = "added #{commits_text}\n\n" body << existing_commit_summary(noteable, existing_commits, oldrev) body << new_commit_summary(new_commits).join("\n") body << "\n\n[Compare with previous version](#{diff_comparison_url(noteable, project, oldrev)})" @@ -38,13 +38,13 @@ module SystemNoteService # # Example Note text: # - # "Assignee removed" + # "removed assignee" # - # "Reassigned to @rspeicher" + # "assigned to @rspeicher" # # Returns the created Note object def change_assignee(noteable, project, author, assignee) - body = assignee.nil? ? 'Assignee removed' : "Reassigned to #{assignee.to_reference}" + body = assignee.nil? ? 'removed assignee' : "assigned to #{assignee.to_reference}" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -59,11 +59,11 @@ module SystemNoteService # # Example Note text: # - # "Added ~1 and removed ~2 ~3 labels" + # "added ~1 and removed ~2 ~3 labels" # - # "Added ~4 label" + # "added ~4 label" # - # "Removed ~5 label" + # "removed ~5 label" # # Returns the created Note object def change_label(noteable, project, author, added_labels, removed_labels) @@ -85,7 +85,6 @@ module SystemNoteService end body << ' ' << 'label'.pluralize(labels_count) - body = body.capitalize create_note(noteable: noteable, project: project, author: author, note: body) end @@ -99,14 +98,13 @@ module SystemNoteService # # Example Note text: # - # "Milestone removed" + # "removed milestone" # - # "Miletone changed to 7.11" + # "changed milestone to 7.11" # # Returns the created Note object def change_milestone(noteable, project, author, milestone) - body = 'Milestone ' - body += milestone.nil? ? 'removed' : "changed to #{milestone.to_reference(project)}" + body = milestone.nil? ? 'removed milestone' : "changed milestone to #{milestone.to_reference(project)}" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -121,50 +119,58 @@ module SystemNoteService # # Example Note text: # - # "Status changed to merged" + # "merged" # - # "Status changed to closed by bc17db76" + # "closed via bc17db76" # # Returns the created Note object def change_status(noteable, project, author, status, source) - body = "Status changed to #{status}" - body << " by #{source.gfm_reference(project)}" if source + body = status.dup + body << " via #{source.gfm_reference(project)}" if source create_note(noteable: noteable, project: project, author: author, note: body) end - # Called when 'merge when build succeeds' is executed + # Called when 'merge when pipeline succeeds' is executed def merge_when_build_succeeds(noteable, project, author, last_commit) - body = "Enabled an automatic merge when the build for #{last_commit.to_reference(project)} succeeds" + body = "enabled an automatic merge when the pipeline for #{last_commit.to_reference(project)} succeeds" create_note(noteable: noteable, project: project, author: author, note: body) end - # Called when 'merge when build succeeds' is canceled + # Called when 'merge when pipeline succeeds' is canceled def cancel_merge_when_build_succeeds(noteable, project, author) - body = 'Canceled the automatic merge' + body = 'canceled the automatic merge' create_note(noteable: noteable, project: project, author: author, note: body) end def remove_merge_request_wip(noteable, project, author) - body = 'Unmarked this merge request as a Work In Progress' + body = 'unmarked as a Work In Progress' create_note(noteable: noteable, project: project, author: author, note: body) end def add_merge_request_wip(noteable, project, author) - body = 'Marked this merge request as a **Work In Progress**' + body = 'marked as a **Work In Progress**' create_note(noteable: noteable, project: project, author: author, note: body) end def self.resolve_all_discussions(merge_request, project, author) - body = "Resolved all discussions" + body = "resolved all discussions" create_note(noteable: merge_request, project: project, author: author, note: body) end + def discussion_continued_in_issue(discussion, project, author, issue) + body = "Added #{issue.to_reference} to continue this discussion" + note_attributes = discussion.reply_attributes.merge(project: project, author: author, note: body) + note_attributes[:type] = note_attributes.delete(:note_type) + + create_note(note_attributes) + end + # Called when the title of a Noteable is changed # # noteable - Noteable object that responds to `title` @@ -174,7 +180,7 @@ module SystemNoteService # # Example Note text: # - # "Title changed from **Old** to **New**" + # "changed title from **Old** to **New**" # # Returns the created Note object def change_title(noteable, project, author, old_title) @@ -185,7 +191,7 @@ module SystemNoteService marked_old_title = Gitlab::Diff::InlineDiffMarker.new(old_title).mark(old_diffs, mode: :deletion, markdown: true) marked_new_title = Gitlab::Diff::InlineDiffMarker.new(new_title).mark(new_diffs, mode: :addition, markdown: true) - body = "Changed title: **#{marked_old_title}** โ **#{marked_new_title}**" + body = "changed title from **#{marked_old_title}** to **#{marked_new_title}**" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -197,11 +203,11 @@ module SystemNoteService # # Example Note text: # - # "Made the issue confidential" + # "made the issue confidential" # # Returns the created Note object def change_issue_confidentiality(issue, project, author) - body = issue.confidential ? 'Made the issue confidential' : 'Made the issue visible' + body = issue.confidential ? 'made the issue confidential' : 'made the issue visible to everyone' create_note(noteable: issue, project: project, author: author, note: body) end @@ -216,11 +222,11 @@ module SystemNoteService # # Example Note text: # - # "Target branch changed from `Old` to `New`" + # "changed target branch from `Old` to `New`" # # Returns the created Note object def change_branch(noteable, project, author, branch_type, old_branch, new_branch) - body = "#{branch_type} branch changed from `#{old_branch}` to `#{new_branch}`".capitalize + body = "changed #{branch_type} branch from `#{old_branch}` to `#{new_branch}`" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -235,7 +241,7 @@ module SystemNoteService # # Example Note text: # - # "Restored target branch `feature`" + # "restored target branch `feature`" # # Returns the created Note object def change_branch_presence(noteable, project, author, branch_type, branch, presence) @@ -246,18 +252,18 @@ module SystemNoteService 'deleted' end - body = "#{verb} #{branch_type} branch `#{branch}`".capitalize + body = "#{verb} #{branch_type} branch `#{branch}`" create_note(noteable: noteable, project: project, author: author, note: body) end # Called when a branch is created from the 'new branch' button on a issue # Example note text: # - # "Started branch `201-issue-branch-button`" + # "created branch `201-issue-branch-button`" def new_issue_branch(issue, project, author, branch) link = url_helpers.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch) - body = "Started branch [`#{branch}`](#{link})" + body = "created branch [`#{branch}`](#{link})" create_note(noteable: issue, project: project, author: author, note: body) end @@ -269,11 +275,11 @@ module SystemNoteService # # Example Note text: # - # "Mentioned in #1" + # "mentioned in #1" # - # "Mentioned in !2" + # "mentioned in !2" # - # "Mentioned in 54f7727c" + # "mentioned in 54f7727c" # # See cross_reference_note_content. # @@ -303,12 +309,12 @@ module SystemNoteService end def cross_reference?(note_text) - note_text.start_with?(cross_reference_note_prefix) + note_text =~ /\A#{cross_reference_note_prefix}/i end # Check if a cross-reference is disallowed # - # This method prevents adding a "Mentioned in !1" note on every single commit + # This method prevents adding a "mentioned in !1" note on every single commit # in a merge request. Additionally, it prevents the creation of references to # external issues (which would fail). # @@ -370,12 +376,12 @@ module SystemNoteService # # Example Note text: # - # "Soandso marked the task Whatever as completed." + # "marked the task Whatever as completed." # # Returns the created Note object def change_task_status(noteable, project, author, new_task) status_label = new_task.complete? ? Taskable::COMPLETED : Taskable::INCOMPLETE - body = "Marked the task **#{new_task.source}** as #{status_label}" + body = "marked the task **#{new_task.source}** as #{status_label}" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -388,7 +394,7 @@ module SystemNoteService # # Example Note text: # - # "Moved to some_namespace/project_new#11" + # "moved to some_namespace/project_new#11" # # Returns the created Note object def noteable_moved(noteable, project, noteable_ref, author, direction:) @@ -397,7 +403,7 @@ module SystemNoteService end cross_reference = noteable_ref.to_reference(project) - body = "Moved #{direction} #{cross_reference}" + body = "moved #{direction} #{cross_reference}" create_note(noteable: noteable, project: project, author: author, note: body) end @@ -405,10 +411,12 @@ module SystemNoteService def notes_for_mentioner(mentioner, noteable, notes) if mentioner.is_a?(Commit) - notes.where('note LIKE ?', "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}") + text = "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}" + notes.where('(note LIKE ? OR note LIKE ?)', text, text.capitalize) else gfm_reference = mentioner.gfm_reference(noteable.project) - notes.where(note: cross_reference_note_content(gfm_reference)) + text = cross_reference_note_content(gfm_reference) + notes.where(note: [text, text.capitalize]) end end @@ -417,7 +425,7 @@ module SystemNoteService end def cross_reference_note_prefix - 'Mentioned in ' + 'mentioned in ' end def cross_reference_note_content(gfm_reference) diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb index 71ff14a3f20..38683fdf6d7 100644 --- a/app/uploaders/avatar_uploader.rb +++ b/app/uploaders/avatar_uploader.rb @@ -3,16 +3,10 @@ class AvatarUploader < CarrierWave::Uploader::Base storage :file - after :store, :reset_events_cache - def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end - def reset_events_cache(file) - model.reset_events_cache if model.is_a?(User) - end - def exists? model.avatar.file && model.avatar.file.exists? end diff --git a/app/uploaders/uploader_helper.rb b/app/uploaders/uploader_helper.rb index b10ad71d052..fbaea2744a3 100644 --- a/app/uploaders/uploader_helper.rb +++ b/app/uploaders/uploader_helper.rb @@ -1,6 +1,6 @@ # Extra methods for uploader module UploaderHelper - IMAGE_EXT = %w[png jpg jpeg gif bmp tiff] + IMAGE_EXT = %w[png jpg jpeg gif bmp tiff svg] # We recommend using the .mp4 format over .mov. Videos in .mov format can # still be used but you really need to make sure they are served with the # proper MIME type video/mp4 and not video/quicktime or your videos won't play diff --git a/app/views/admin/abuse_reports/index.html.haml b/app/views/admin/abuse_reports/index.html.haml index 7bbc75db9ff..c4b748d0ab8 100644 --- a/app/views/admin/abuse_reports/index.html.haml +++ b/app/views/admin/abuse_reports/index.html.haml @@ -4,7 +4,7 @@ .abuse-reports - if @abuse_reports.present? .table-holder - %table.table + %table.table.responsive-table %thead.hidden-sm.hidden-xs %tr %th User @@ -13,8 +13,6 @@ %th Action = render @abuse_reports - else - .no-reports - %span.pull-left - There are no abuse reports! - .pull-left - = emoji_icon 'tada' + .empty-state + .text-center + %h4 There are no abuse reports! #{emoji_icon 'tada'} diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index ce803f329f9..7accd2529af 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -443,7 +443,16 @@ Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead. - + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :html_emails_enabled do + = f.check_box :html_emails_enabled + Enable HTML emails + .help-block + By default GitLab sends emails in HTML and plain text formats so mail + clients can choose what format to use. Disable this option if you only + want to send emails in plain text format. %fieldset %legend Automatic Git repository housekeeping .form-group diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 1db2150f336..e51f4ac1d93 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -113,7 +113,7 @@ %hr .row .col-sm-4 - .light-well + .light-well.well-centered %h4 Projects .data = link_to admin_namespaces_projects_path do @@ -121,7 +121,7 @@ %hr = link_to('New Project', new_project_path, class: "btn btn-new") .col-sm-4 - .light-well + .light-well.well-centered %h4 Users .data = link_to admin_users_path do @@ -129,7 +129,7 @@ %hr = link_to 'New User', new_admin_user_path, class: "btn btn-new" .col-sm-4 - .light-well + .light-well.well-centered %h4 Groups .data = link_to admin_groups_path do @@ -143,7 +143,7 @@ %hr - @projects.each do |project| %p - = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated' + = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60' %span.light.pull-right #{time_ago_with_tooltip(project.created_at)} @@ -152,7 +152,7 @@ %hr - @users.each do |user| %p - = link_to [:admin, user], class: 'str-truncated' do + = link_to [:admin, user], class: 'str-truncated-60' do = user.name %span.light.pull-right #{time_ago_with_tooltip(user.created_at)} @@ -162,7 +162,7 @@ %hr - @groups.each do |group| %p - = link_to [:admin, group], class: 'str-truncated' do + = link_to [:admin, group], class: 'str-truncated-60' do = group.name %span.light.pull-right #{time_ago_with_tooltip(group.created_at)} diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml index 4bf1c9cde3c..2d9588f9d27 100644 --- a/app/views/admin/users/_user.html.haml +++ b/app/views/admin/users/_user.html.haml @@ -1,8 +1,8 @@ -%li.user-row +%li.flex-row .user-avatar = image_tag avatar_icon(user), class: "avatar", alt: '' - .user-details - .user-name + .row-main-content + .user-name.row-title.str-truncated-100 = link_to user.name, [:admin, user] - if user.blocked? %span.label.label-danger blocked @@ -12,7 +12,7 @@ %span.label.label-default External - if user == current_user %span It's you! - .user-email + .row-second-line.str-truncated-100 = mail_to user.email, user.email .controls = link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: 'btn' diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index d3038ae644f..4dc44225d49 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -68,7 +68,7 @@ %small.badge= number_with_delimiter(User.without_projects.count) .fade-right - %ul.users-list.content-list + %ul.flex-list.content-list - if @users.empty? %li .nothing-here-block No users found. diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml index 84b9ceb23b3..dd6b7303493 100644 --- a/app/views/admin/users/projects.html.haml +++ b/app/views/admin/users/projects.html.haml @@ -7,7 +7,7 @@ %ul.well-list - @user.groups.each do |group| %li - %strong= group.name + %strong= link_to group.name, admin_group_path(group) – access to #{pluralize(group.projects.count, 'project')} diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml index f7abad54286..48b0fd504f4 100644 --- a/app/views/dashboard/_projects_head.html.haml +++ b/app/views/dashboard/_projects_head.html.haml @@ -4,13 +4,13 @@ %ul.nav-links = nav_link(page: [dashboard_projects_path, root_path]) do = link_to dashboard_projects_path, title: 'Home', class: 'shortcuts-activity', data: {placement: 'right'} do - Your Projects + Your projects = nav_link(page: starred_dashboard_projects_path) do = link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do - Starred Projects + Starred projects = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do - Explore Projects + Explore projects .nav-controls = form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml index b25e8ea1f0c..02e90bbfa55 100644 --- a/app/views/dashboard/_snippets_head.html.haml +++ b/app/views/dashboard/_snippets_head.html.haml @@ -1,7 +1,13 @@ -%ul.nav-links - = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do - = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do - Your Snippets - = nav_link(page: explore_snippets_path) do - = link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do - Explore Snippets +.top-area + %ul.nav-links + = nav_link(page: dashboard_snippets_path, html_options: {class: 'home'}) do + = link_to dashboard_snippets_path, title: 'Your snippets', data: {placement: 'right'} do + Your Snippets + = nav_link(page: explore_snippets_path) do + = link_to explore_snippets_path, title: 'Explore snippets', data: {placement: 'right'} do + Explore Snippets + + - if current_user + .nav-controls.hidden-xs + = link_to new_snippet_path, class: "btn btn-new", title: "New snippet" do + New snippet diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml index fdea834ff45..4a55aac0df6 100644 --- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml @@ -4,6 +4,18 @@ Welcome to GitLab %p.blank-state-text Code, test, and deploy together + +- if current_user.can_create_group? + .blank-state + .blank-state-icon + = custom_icon("group", size: 50) + %h3.blank-state-title + You can create a group for several dependent projects. + %p.blank-state-text + Groups are the best way to manage projects and members. + = link_to new_group_path, class: "btn btn-new" do + New group + .blank-state .blank-state-icon = custom_icon("project", size: 50) @@ -21,17 +33,6 @@ = link_to new_project_path, class: "btn btn-new" do New project -- if current_user.can_create_group? - .blank-state - .blank-state-icon - = custom_icon("group", size: 50) - %h3.blank-state-title - You can create a group for several dependent projects. - %p.blank-state-text - Groups are the best way to manage projects and members. - = link_to new_group_path, class: "btn btn-new" do - New group - -if publicish_project_count > 0 .blank-state .blank-state-icon diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml index b2af438ea57..85cbe0bf0e6 100644 --- a/app/views/dashboard/snippets/index.html.haml +++ b/app/views/dashboard/snippets/index.html.haml @@ -2,41 +2,11 @@ - header_title "Snippets", dashboard_snippets_path = render 'dashboard/snippets_head' += render partial: 'snippets/snippets_scope_menu', locals: { include_private: true } -.nav-block - .controls.hidden-xs - = link_to new_snippet_path, class: "btn btn-new", title: "New snippet" do - = icon('plus') - New snippet +.visible-xs + + = link_to new_snippet_path, class: "btn btn-new btn-block", title: "New snippet" do + New snippet - .nav-links.snippet-scope-menu - %li{ class: ("active" unless params[:scope]) } - = link_to dashboard_snippets_path do - All - %span.badge - = current_user.snippets.count - - %li{ class: ("active" if params[:scope] == "are_private") } - = link_to dashboard_snippets_path(scope: 'are_private') do - Private - %span.badge - = current_user.snippets.are_private.count - - %li{ class: ("active" if params[:scope] == "are_internal") } - = link_to dashboard_snippets_path(scope: 'are_internal') do - Internal - %span.badge - = current_user.snippets.are_internal.count - - %li{ class: ("active" if params[:scope] == "are_public") } - = link_to dashboard_snippets_path(scope: 'are_public') do - Public - %span.badge - = current_user.snippets.are_public.count - - .visible-xs - = link_to new_snippet_path, class: "btn btn-new btn-block", title: "New snippet" do - = icon('plus') - New snippet - -= render 'snippets/snippets' += render partial: 'snippets/snippets', locals: { link_project: true } diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 472d698486b..62f52086be4 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -50,13 +50,13 @@ data: { data: todo_actions_options }}) .pull-right .dropdown.inline.prepend-left-10 - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.light - if @sort.present? = sort_options_hash[@sort] - else = sort_title_recently_created - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort %li = link_to todos_filter_path(sort: sort_value_priority) do diff --git a/app/views/devise/confirmations/almost_there.haml b/app/views/devise/confirmations/almost_there.haml index 20cd7b0179d..fb70d158096 100644 --- a/app/views/devise/confirmations/almost_there.haml +++ b/app/views/devise/confirmations/almost_there.haml @@ -1,12 +1,13 @@ -.well-confirmation.text-center +.well-confirmation.text-center.append-bottom-20 %h1.prepend-top-0 Almost there... - %p.lead + %p.lead.append-bottom-20 Please check your email to confirm your account + %hr - if current_application_settings.after_sign_up_text.present? .well-confirmation.text-center = markdown_field(current_application_settings, :after_sign_up_text) -%p.confirmation-content.text-center +%p.text-center No confirmation email received? Please check your spam folder or .append-bottom-20.prepend-top-20.text-center %a.btn.btn-lg.btn-success{ href: new_user_confirmation_path } diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index fa8e7979461..af87129e49e 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -1,4 +1,5 @@ - page_title "Sign in" + %div - if form_based_providers.any? = render 'devise/shared/tabs_ldap' diff --git a/app/views/discussions/_discussion.html.haml b/app/views/discussions/_discussion.html.haml index e4b4ea675d2..2bce2780484 100644 --- a/app/views/discussions/_discussion.html.haml +++ b/app/views/discussions/_discussion.html.haml @@ -32,6 +32,7 @@ an outdated diff = time_ago_with_tooltip(discussion.created_at, placement: "bottom", html_class: "note-created-ago") + = render "discussions/headline", discussion: discussion .discussion-body.js-toggle-content{ class: ("hide" unless expanded) } - if discussion.diff_discussion? && discussion.diff_file diff --git a/app/views/errors/access_denied.html.haml b/app/views/errors/access_denied.html.haml index c034bbe430e..8bddbef3562 100644 --- a/app/views/errors/access_denied.html.haml +++ b/app/views/errors/access_denied.html.haml @@ -1,6 +1,10 @@ -- page_title "Access Denied" -%h1 403 -%h3 Access Denied -%hr -%p You are not allowed to access this page. -%p Read more about project permissions #{link_to "here", help_page_path("user/permissions"), class: "vlink"} +- content_for(:title, 'Access Denied') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 403 +.container + %h3 Access Denied + %hr + %p You are not allowed to access this page. + %p Read more about project permissions #{link_to "here", help_page_path("user/permissions"), class: "vlink"} diff --git a/app/views/errors/encoding.html.haml b/app/views/errors/encoding.html.haml index 90cfbebfcc6..064ff14ad2c 100644 --- a/app/views/errors/encoding.html.haml +++ b/app/views/errors/encoding.html.haml @@ -1,5 +1,9 @@ -- page_title "Encoding Error" -%h1 500 -%h3 Encoding Error -%hr -%p Page can't be loaded because of an encoding error. +- content_for(:title, 'Encoding Error') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 500 +.container + %h3 Encoding Error + %hr + %p Page can't be loaded because of an encoding error. diff --git a/app/views/errors/git_not_found.html.haml b/app/views/errors/git_not_found.html.haml index ff5d4cc1506..c5c12a410ac 100644 --- a/app/views/errors/git_not_found.html.haml +++ b/app/views/errors/git_not_found.html.haml @@ -1,7 +1,10 @@ -- page_title "Git Resource Not Found" -%h1 404 -%h3 Git Resource Not found -%hr -%p - Application can't get access to some branch or commit in your repository. It - may have been moved. +- content_for(:title, 'Git Resource Not Found') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 404 +.container + %h3 Git Resource Not found + %hr + %p Application can't get access to some branch or commit in your repository. It + may have been moved diff --git a/app/views/errors/not_found.html.haml b/app/views/errors/not_found.html.haml index 3756b98ebb2..50a54a93cb5 100644 --- a/app/views/errors/not_found.html.haml +++ b/app/views/errors/not_found.html.haml @@ -1,5 +1,9 @@ -- page_title "Not Found" -%h1 404 -%h3 The resource you were looking for doesn't exist. -%hr -%p You may have mistyped the address or the page may have moved. +- content_for(:title, 'Not Found') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 404 +.container + %h3 The resource you were looking for doesn't exist. + %hr + %p You may have mistyped the address or the page may have moved. diff --git a/app/views/errors/omniauth_error.html.haml b/app/views/errors/omniauth_error.html.haml index 3e70e98a24c..d91f1878cb6 100644 --- a/app/views/errors/omniauth_error.html.haml +++ b/app/views/errors/omniauth_error.html.haml @@ -1,9 +1,13 @@ -- page_title "Auth Error" -%h1 422 -%h3 Sign-in using #{@provider} auth failed -%hr -%p Sign-in failed because #{@error}. -%p There are couple of steps you can take: +- content_for(:title, 'Auth Error') +%img{:alt => "GitLab Logo", + :src => image_path('logo.svg')} + %h1 + 422 +.container + %h3 Sign-in using #{@provider} auth failed + %hr + %p Sign-in failed because #{@error}. + %p There are couple of steps you can take: %ul %li Try logging in using your email diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index 5c318cd3b8b..a0bd14df209 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -3,14 +3,13 @@ .event-item-timestamp #{time_ago_with_tooltip(event.created_at)} - = cache [event, current_application_settings, "v2.2"] do - = author_avatar(event, size: 40) + = author_avatar(event, size: 40) - - if event.created_project? - = render "events/event/created_project", event: event - - elsif event.push? - = render "events/event/push", event: event - - elsif event.commented? - = render "events/event/note", event: event - - else - = render "events/event/common", event: event + - if event.created_project? + = render "events/event/created_project", event: event + - elsif event.push? + = render "events/event/push", event: event + - elsif event.commented? + = render "events/event/note", event: event + - else + = render "events/event/common", event: event diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml index 44fff49d99c..64ca3c32e01 100644 --- a/app/views/events/event/_push.html.haml +++ b/app/views/events/event/_push.html.haml @@ -18,7 +18,7 @@ - few_commits.each do |commit| = render "events/commit", commit: commit, project: project, event: event - - create_mr = event.new_ref? && create_mr_button?(project.default_branch, event.ref_name, project) + - create_mr = event.new_ref? && create_mr_button?(project.default_branch, event.ref_name, project) && event.authored_by?(current_user) - if event.commits_count > 1 %li.commits-stat - if event.commits_count > 2 @@ -35,12 +35,12 @@ Compare #{from_label}...#{truncate_sha(event.commit_to)} - if create_mr - %span{"data-user-is" => event.author_id, "data-display" => "inline"} + %span or = link_to create_mr_path(project.default_branch, event.ref_name, project) do create a merge request - elsif create_mr - %li.commits-stat{"data-user-is" => event.author_id} + %li.commits-stat = link_to create_mr_path(project.default_branch, event.ref_name, project) do Create Merge Request - elsif event.rm_ref? diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index a1b39d9e1a0..4e5d965ccbe 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -17,13 +17,13 @@ .pull-right .dropdown.inline - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.light - if @sort.present? = sort_options_hash[@sort] - else = sort_title_recently_created - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to explore_groups_path(sort: sort_value_recently_created) do diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index 4cff14b096b..5ea154c36b4 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -1,13 +1,13 @@ - if current_user .dropdown - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %button.dropdown-toggle{href: '#', "data-toggle" => "dropdown"} = icon('globe') %span.light Visibility: - if params[:visibility_level].present? = visibility_level_label(params[:visibility_level].to_i) - else Any - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_projects_path(visibility_level: nil) do @@ -20,14 +20,14 @@ - if @tags.present? .dropdown - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %button.dropdown-toggle{href: '#', "data-toggle" => "dropdown"} = icon('tags') %span.light Tags: - if params[:tag].present? = params[:tag] - else Any - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_projects_path(tag: nil) do diff --git a/app/views/explore/snippets/index.html.haml b/app/views/explore/snippets/index.html.haml index 7def9eacdc9..e5706d04736 100644 --- a/app/views/explore/snippets/index.html.haml +++ b/app/views/explore/snippets/index.html.haml @@ -6,12 +6,4 @@ - else = render 'explore/head' -.row-content-block - - if current_user - = link_to new_snippet_path, class: "btn btn-new btn-wide-on-sm pull-right", title: "New snippet" do - New snippet - - .oneline - Public snippets created by you and other users are listed here - -= render 'snippets/snippets' += render partial: 'snippets/snippets', locals: { link_project: true } diff --git a/app/views/groups/group_members/update.js.haml b/app/views/groups/group_members/update.js.haml index de8f53b6b52..9d05bff6c4e 100644 --- a/app/views/groups/group_members/update.js.haml +++ b/app/views/groups/group_members/update.js.haml @@ -1,3 +1,4 @@ :plain var $listItem = $('#{escape_javascript(render('shared/members/member', member: @group_member))}'); $("##{dom_id(@group_member)} .list-item-name").replaceWith($listItem.find('.list-item-name')); + gl.utils.localTimeAgo($('.js-timeago'), $("##{dom_id(@group_member)}")); diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 324a116a50e..b4aa4f24d9e 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -6,13 +6,13 @@ - if group_issues(@group).exists? .top-area = render 'shared/issuable/nav', type: :issues - .nav-controls - - if current_user + - if current_user + .nav-controls = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn' do = icon('rss') %span.icon-label Subscribe - = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" + = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" = render 'shared/issuable/filter', type: :issues diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index e6953d94531..dbbdb583a24 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -2,8 +2,9 @@ .top-area = render 'shared/issuable/nav', type: :merge_requests - .nav-controls - = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request" + - if current_user + .nav-controls + = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request" = render 'shared/issuable/filter', type: :merge_requests diff --git a/app/views/help/show.html.haml b/app/views/help/show.html.haml index be257b51b9e..f6ebd76af9d 100644 --- a/app/views/help/show.html.haml +++ b/app/views/help/show.html.haml @@ -1,3 +1,3 @@ - page_title @path.split("/").reverse.map(&:humanize) .documentation.wiki - = markdown @markdown.gsub('$your_email', current_user.try(:email) || "email@example.com") + = markdown @markdown diff --git a/app/views/invites/show.html.haml b/app/views/invites/show.html.haml index 2fd4859c1c6..882fdf1317d 100644 --- a/app/views/invites/show.html.haml +++ b/app/views/invites/show.html.haml @@ -6,7 +6,7 @@ - if inviter = @member.created_by by = link_to inviter.name, user_url(inviter) - to join + to join - case @member.source - when Project - project = @member.source @@ -20,11 +20,18 @@ = link_to group.name, group_url(group) as #{@member.human_access}. -- if @member.source.users.include?(current_user) +- is_member = @member.source.users.include?(current_user) + +- if is_member %p However, you are already a member of this #{@member.source.is_a?(Group) ? "group" : "project"}. Sign in using a different account to accept the invitation. -- else + +- if @member.invite_email != current_user.email + %p + Note that this invitation was sent to #{mail_to @member.invite_email}, but you are signed in as #{link_to current_user.to_reference, user_url(current_user)} with email #{mail_to current_user.email}. + +- unless is_member .actions = link_to "Accept invitation", accept_invite_url(@token), method: :post, class: "btn btn-success" = link_to "Decline", decline_invite_url(@token), method: :post, class: "btn btn-danger prepend-left-10" diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 757de92d6d4..3e488cf73b9 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -56,5 +56,3 @@ = render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id') = render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id') = render 'layouts/bootlint' if Rails.env.development? - - = render 'layouts/user_styles' diff --git a/app/views/layouts/_user_styles.html.haml b/app/views/layouts/_user_styles.html.haml deleted file mode 100644 index b76b3cb5510..00000000000 --- a/app/views/layouts/_user_styles.html.haml +++ /dev/null @@ -1,24 +0,0 @@ -:css - [data-user-is] { - display: none !important; - } - - [data-user-is="#{current_user.try(:id)}"] { - display: block !important; - } - - [data-user-is="#{current_user.try(:id)}"][data-display="inline"] { - display: inline !important; - } - - [data-user-is-not] { - display: block !important; - } - - [data-user-is-not][data-display="inline"] { - display: inline !important; - } - - [data-user-is-not="#{current_user.try(:id)}"] { - display: none !important; - } diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml index 7fbe065df00..a3b925f6afd 100644 --- a/app/views/layouts/errors.html.haml +++ b/app/views/layouts/errors.html.haml @@ -1,10 +1,59 @@ !!! 5 %html{ lang: "en"} - = render "layouts/head" - %body{class: "#{user_application_theme} application navless"} - = Gon::Base.render_data - = render "layouts/header/empty" - .container.navless-container - = render "layouts/flash" - .error-page - = yield + %head + %meta{:content => "width=device-width, initial-scale=1, maximum-scale=1", :name => "viewport"} + %title= yield(:title) + :css + body { + color: #666; + text-align: center; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + margin: auto; + font-size: 14px; + } + + h1 { + font-size: 56px; + line-height: 100px; + font-weight: normal; + color: #456; + } + + h2 { + font-size: 24px; + color: #666; + line-height: 1.5em; + } + + h3 { + color: #456; + font-size: 20px; + font-weight: normal; + line-height: 28px; + } + + hr { + max-width: 800px; + margin: 18px auto; + border: 0; + border-top: 1px solid #EEE; + border-bottom: 1px solid white; + } + + img { + max-width: 40vw; + display: block; + margin: 40px auto; + } + + .container { + margin: auto 20px; + } + + ul { + margin: auto; + text-align: left; + display:inline-block; + } +%body + = yield diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 2a6d9cda379..817e4bebb05 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -1,5 +1,4 @@ .nav-sidebar - .sidebar-header Across GitLab %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 diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index f7edb47b666..f3539fd372d 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -31,7 +31,7 @@ = link_to merge_requests_group_path(@group), title: 'Merge Requests' do %span Merge Requests - - merge_requests = MergeRequestsFinder.new(current_user, group_id: @group.id, state: 'opened').execute + - 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 diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml index c0328fe8842..1579d8f1662 100644 --- a/app/views/layouts/nav/_group_settings.html.haml +++ b/app/views/layouts/nav/_group_settings.html.haml @@ -1,10 +1,8 @@ - if current_user - can_admin_group = can?(current_user, :admin_group, @group) - can_edit = can?(current_user, :admin_group, @group) - - member = @group.members.find_by(user_id: current_user.id) - - can_leave = member && can?(current_user, :destroy_group_member, member) - - if can_admin_group || can_edit || can_leave + - if can_admin_group || can_edit .controls .dropdown.group-settings-dropdown %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'} @@ -14,13 +12,7 @@ - if can_admin_group = nav_link(path: 'groups#projects') do = link_to 'Projects', projects_group_path(@group), title: 'Projects' - - if (can_edit || can_leave) && can_admin_group + - if can_edit && can_admin_group %li.divider - - if can_edit %li = link_to 'Edit Group', edit_group_path(@group) - - if can_leave - %li - = link_to polymorphic_path([:leave, @group, :members]), - data: { confirm: leave_confirmation_message(@group) }, method: :delete, title: 'Leave group' do - Leave Group diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 99a58bbb676..904d11c2cf4 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -6,23 +6,14 @@ = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right - can_edit = can?(current_user, :admin_project, @project) - -# We don't use @project.team.find_member because it searches for group members too... - - member = @project.members.find_by(user_id: current_user.id) - - can_leave = member && can?(current_user, :destroy_project_member, member) = render 'layouts/nav/project_settings', can_edit: can_edit - - if can_edit || can_leave + - if can_edit %li.divider - - if can_edit - %li - = link_to edit_project_path(@project) do - Edit Project - - if can_leave - %li - = link_to polymorphic_path([:leave, @project, :members]), - data: { confirm: leave_confirmation_message(@project) }, method: :delete, title: 'Leave project' do - Leave Project + %li + = link_to edit_project_path(@project) do + Edit Project .scrolling-tabs-container{ class: nav_control_class } .fade-left @@ -70,14 +61,14 @@ %span Issues - if @project.default_issues_tracker? - %span.badge.count.issue_counter= number_with_delimiter(@project.issues.visible_to_user(current_user).opened.count) + %span.badge.count.issue_counter= number_with_delimiter(IssuesFinder.new(current_user, project_id: @project.id).execute.opened.count) - if project_nav_tab? :merge_requests = nav_link(controller: :merge_requests) do = link_to namespace_project_merge_requests_path(@project.namespace, @project), title: 'Merge Requests', class: 'shortcuts-merge_requests' do %span Merge Requests - %span.badge.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count) + %span.badge.count.merge_counter= number_with_delimiter(MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.count) - if project_nav_tab? :wiki = nav_link(controller: :wikis) do diff --git a/app/views/notify/_note_message.text.erb b/app/views/notify/_note_message.text.erb new file mode 100644 index 00000000000..f82cbc9a3fc --- /dev/null +++ b/app/views/notify/_note_message.text.erb @@ -0,0 +1,5 @@ +<% if current_application_settings.email_author_in_body %> + <%= @note.author_name %> wrote: +<% end -%> + +<%= @note.note %> diff --git a/app/views/notify/_note_mr_or_commit_email.html.haml b/app/views/notify/_note_mr_or_commit_email.html.haml new file mode 100644 index 00000000000..edf8dfe7e9e --- /dev/null +++ b/app/views/notify/_note_mr_or_commit_email.html.haml @@ -0,0 +1,18 @@ += content_for :head do + = stylesheet_link_tag 'mailers/highlighted_diff_email' + +New comment + +- if @discussion && @discussion.diff_file + on + = link_to @note.diff_file.file_path, @target_url, class: 'details' + \: + %table + = render partial: "projects/diffs/line", + collection: @discussion.truncated_diff_lines, + as: :line, + locals: { diff_file: @note.diff_file, + plain: true, + email: true } + += render 'note_message' diff --git a/app/views/notify/_note_mr_or_commit_email.text.erb b/app/views/notify/_note_mr_or_commit_email.text.erb new file mode 100644 index 00000000000..b4fcdf6b1e9 --- /dev/null +++ b/app/views/notify/_note_mr_or_commit_email.text.erb @@ -0,0 +1,8 @@ +<% if @discussion && @discussion.diff_file -%> + on <%= @note.diff_file.file_path -%> +<% end -%>: + +<%= url %> + +<%= render 'simple_diff' if @discussion -%> +<%= render 'note_message' %> diff --git a/app/views/notify/_simple_diff.text.erb b/app/views/notify/_simple_diff.text.erb new file mode 100644 index 00000000000..c28d1cc34d3 --- /dev/null +++ b/app/views/notify/_simple_diff.text.erb @@ -0,0 +1,3 @@ +<% @discussion.truncated_diff_lines(highlight: false).each do |line| %> +> <%= line.text %> +<% end %> diff --git a/app/views/notify/note_commit_email.html.haml b/app/views/notify/note_commit_email.html.haml index 1d961e4424c..0a650e3b2ca 100644 --- a/app/views/notify/note_commit_email.html.haml +++ b/app/views/notify/note_commit_email.html.haml @@ -1,2 +1,2 @@ -= render 'note_message' - +%p.details + = render 'note_mr_or_commit_email' diff --git a/app/views/notify/note_commit_email.text.erb b/app/views/notify/note_commit_email.text.erb index aaeaf5fdf73..6aa085a172e 100644 --- a/app/views/notify/note_commit_email.text.erb +++ b/app/views/notify/note_commit_email.text.erb @@ -1,9 +1,2 @@ -New comment for Commit <%= @commit.short_id %> - -<%= url_for(namespace_project_commit_url(@note.project.namespace, @note.project, id: @commit.id, anchor: "note_#{@note.id}")) %> - - -Author: <%= @note.author_name %> - -<%= @note.note %> - +New comment for Commit <%= @commit.short_id -%> +<%= render partial: 'note_mr_or_commit_email', locals: { url: @target_url } %> diff --git a/app/views/notify/note_merge_request_email.html.haml b/app/views/notify/note_merge_request_email.html.haml index ea7e3d199fd..0a650e3b2ca 100644 --- a/app/views/notify/note_merge_request_email.html.haml +++ b/app/views/notify/note_merge_request_email.html.haml @@ -1,7 +1,2 @@ -- if @note.diff_note? && @note.diff_file - %p.details - New comment on diff for - = link_to @note.diff_file.file_path, @target_url - \: - -= render 'note_message' +%p.details + = render 'note_mr_or_commit_email' diff --git a/app/views/notify/note_merge_request_email.text.erb b/app/views/notify/note_merge_request_email.text.erb index 8cdab63829e..2ce64c494cf 100644 --- a/app/views/notify/note_merge_request_email.text.erb +++ b/app/views/notify/note_merge_request_email.text.erb @@ -1,9 +1,2 @@ -New comment for Merge Request <%= @merge_request.to_reference %> - -<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %> - - -<%= @note.author_name %> - -<%= @note.note %> - +New comment for Merge Request <%= @merge_request.to_reference -%> +<%= render partial: 'note_mr_or_commit_email', locals: { url: @target_url }%> diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index 697c8d19257..56c1949ab2b 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -133,7 +133,7 @@ %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.size + - stage_count = @pipeline.stages_count Pipeline %a{href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;"} = "\##{@pipeline.id}" diff --git a/app/views/notify/pipeline_success_email.text.erb b/app/views/notify/pipeline_success_email.text.erb index ae22d474f2c..40e5e306426 100644 --- a/app/views/notify/pipeline_success_email.text.erb +++ b/app/views/notify/pipeline_success_email.text.erb @@ -16,7 +16,7 @@ Commit Author: <%= commit.author_name %> <% end -%> <% build_count = @pipeline.statuses.latest.size -%> -<% stage_count = @pipeline.stages.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 %>. diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index 307c5a11206..25883de257c 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -1,5 +1,5 @@ = content_for :head do - = stylesheet_link_tag 'mailers/repository_push_email' + = stylesheet_link_tag 'mailers/highlighted_diff_email' %h3 #{@message.author_name} #{@message.action_name} #{@message.ref_type} #{@message.ref_name} diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 844fce59704..d79a1a9f368 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -30,7 +30,7 @@ %br .clearfix .form-group.pull-left.global-notification-setting - = render 'shared/notifications/button', notification_setting: @global_notification_setting, left_align: true + = render 'shared/notifications/button', notification_setting: @global_notification_setting .clearfix diff --git a/app/views/profiles/update_username.js.haml b/app/views/profiles/update_username.js.haml index de1337a2a24..5307e0b48cb 100644 --- a/app/views/profiles/update_username.js.haml +++ b/app/views/profiles/update_username.js.haml @@ -2,5 +2,6 @@ :plain new Flash("Username successfully changed", "notice") - else + - error = @user.errors.full_messages.first :plain - new Flash("Username change failed - #{@user.errors.full_messages.first}", "alert") + new Flash("Username change failed - #{escape_javascript error.html_safe}", "alert") diff --git a/app/views/projects/_merge_request_merge_settings.html.haml b/app/views/projects/_merge_request_merge_settings.html.haml new file mode 100644 index 00000000000..afe2fd7fd7b --- /dev/null +++ b/app/views/projects/_merge_request_merge_settings.html.haml @@ -0,0 +1,15 @@ +- form = local_assigns.fetch(:form) + +.form-group + .checkbox.builds-feature + = form.label :only_allow_merge_if_build_succeeds do + = form.check_box :only_allow_merge_if_build_succeeds + %strong Only allow merge requests to be merged if the build succeeds + %br + %span.descr + Builds need to be configured to enable this feature. + = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds') + .checkbox + = form.label :only_allow_merge_if_all_discussions_are_resolved do + = form.check_box :only_allow_merge_if_all_discussions_are_resolved + %strong Only allow merge requests to be merged if all discussions are resolved diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml index 6e143c4b570..818010bc7d3 100644 --- a/app/views/projects/_merge_request_settings.html.haml +++ b/app/views/projects/_merge_request_settings.html.haml @@ -1,18 +1,8 @@ -.merge-requests-feature - %fieldset.builds-feature - %hr - %h5.prepend-top-0 - Merge Requests - .form-group - .checkbox - = f.label :only_allow_merge_if_build_succeeds do - = f.check_box :only_allow_merge_if_build_succeeds - %strong Only allow merge requests to be merged if the build succeeds - %br - %span.descr - Builds need to be configured to enable this feature. - = link_to icon('question-circle'), help_page_path('user/project/merge_requests/merge_when_build_succeeds', anchor: 'only-allow-merge-requests-to-be-merged-if-the-build-succeeds') - .checkbox - = f.label :only_allow_merge_if_all_discussions_are_resolved do - = f.check_box :only_allow_merge_if_all_discussions_are_resolved - %strong Only allow merge requests to be merged if all discussions are resolved +- form = local_assigns.fetch(:form) + +%fieldset.features.merge-requests-feature.append-bottom-default + %hr + %h5.prepend-top-0 + Merge Requests + + = render 'projects/merge_request_merge_settings', form: form diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml index 369a847e7d4..b6fb08b68e9 100644 --- a/app/views/projects/_readme.html.haml +++ b/app/views/projects/_readme.html.haml @@ -18,5 +18,5 @@ distributed with computer software, forming part of its documentation. %p We recommend you to - = link_to "add a README", new_readme_path, class: 'underlined-link' + = link_to "add a README", add_special_file_path(@project, file_name: 'README.md'), class: 'underlined-link' file to the repository and GitLab will render it here instead of this message. diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index cadfe5a3e30..f63802ac88b 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -31,7 +31,7 @@ .light = commit_author_link(commit, avatar: false) - authored + committed #{time_ago_with_tooltip(commit.committed_date)} %td.line-numbers - line_count = blame_group[:lines].count diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 2a0352a71b7..a5dcd93f42e 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -27,5 +27,5 @@ = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" = hidden_field_tag 'last_commit_sha', @last_commit_sha = hidden_field_tag 'content', '', id: "file-content" - = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] + = hidden_field_tag 'from_merge_request_iid', params[:from_merge_request_iid] = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_blob_path(@project.namespace, @project, @id) diff --git a/app/views/projects/boards/components/_card.html.haml b/app/views/projects/boards/components/_card.html.haml index 34effac17b2..1f31496e73f 100644 --- a/app/views/projects/boards/components/_card.html.haml +++ b/app/views/projects/boards/components/_card.html.haml @@ -1,5 +1,6 @@ %li.card{ ":class" => '{ "user-can-drag": !disabled && issue.id, "is-disabled": disabled || !issue.id, "is-active": issueDetailVisible }', ":index" => "index", + ":data-issue-id" => "issue.id", "@mousedown" => "mouseDown", "@mousemove" => "mouseMove", "@mouseup" => "showIssue($event)" } diff --git a/app/views/projects/boards/components/sidebar/_notifications.html.haml b/app/views/projects/boards/components/sidebar/_notifications.html.haml index 21c9563e9db..a08c7f2af09 100644 --- a/app/views/projects/boards/components/sidebar/_notifications.html.haml +++ b/app/views/projects/boards/components/sidebar/_notifications.html.haml @@ -1,11 +1,7 @@ - if current_user .block.light.subscription{ ":data-url" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '/toggle_subscription'" } - .title + %span.issuable-header-text.hide-collapsed.pull-left Notifications - %button.btn.btn-block.btn-default.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" } - {{ issue.subscribed ? 'Unsubscribe' : 'Subscribe' }} - .subscription-status{ ":data-status" => "issue.subscribed ? 'subscribed' : 'unsubscribed'" } - .unsubscribed{ "v-show" => "!issue.subscribed" } - You're not receiving notifications from this thread. - .subscribed{ "v-show" => "issue.subscribed" } - You're receiving notifications because you're subscribed to this thread. + %button.btn.btn-default.pull-right.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" } + %span + {{issue.subscribed ? 'Unsubscribe' : 'Subscribe'}} diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 2246316b540..5fd664c7a93 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -12,10 +12,10 @@ = search_field_tag :search, params[:search], { placeholder: 'Filter by branch name', id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false } .dropdown.inline - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.light = projects_sort_options_hash[@sort] - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li = link_to filter_branches_path(sort: sort_value_name) do diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index f5562046953..ce8b66b1945 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -111,16 +111,16 @@ %span.label.label-primary = tag - - if @build.pipeline.stages.many? + - if @build.pipeline.stages_count > 1 .dropdown.build-dropdown .title Stage %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.stage-selection More - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu - @build.pipeline.stages.each do |stage| %li - %a.stage-item= stage + %a.stage-item= stage.name .builds-container - HasStatus::ORDERED_STATUSES.each do |build_status| diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index d8cbfd7173a..cdeb81372ee 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -40,15 +40,13 @@ This build is the most recent deployment to #{environment_link_for_build(@build.project, @build)}. - else This build is an out-of-date deployment to #{environment_link_for_build(@build.project, @build)}. - - if environment.last_deployment - View the most recent deployment #{deployment_link(environment.last_deployment)}. + View the most recent deployment #{deployment_link(environment.last_deployment)}. - elsif @build.complete? && !@build.success? The deployment of this build to #{environment_link_for_build(@build.project, @build)} did not succeed. - else This build is creating a deployment to #{environment_link_for_build(@build.project, @build)} - - if environment.last_deployment - and will overwrite the - = link_to 'latest deployment', deployment_link(environment.last_deployment) + - if environment.try(:last_deployment) + and will overwrite the #{deployment_link(environment.last_deployment, text: 'latest deployment')} .prepend-top-default - if @build.erased? diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 7b995bd8735..40bfa01a45a 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -1,42 +1,41 @@ - if !project.empty_repo? && can?(current_user, :download_code, project) - %span{class: 'download-button'} - .dropdown.inline - %button.btn{ 'data-toggle' => 'dropdown' } - = icon('download') - = icon("caret-down") - %span.sr-only - Select Archive Format - %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } - %li.dropdown-header Source code - %li - = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do - %i.fa.fa-download - %span Download zip - %li - = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do - %i.fa.fa-download - %span Download tar.gz - %li - = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do - %i.fa.fa-download - %span Download tar.bz2 - %li - = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow' do - %i.fa.fa-download - %span Download tar + .dropdown.inline.download-button + %button.btn{ 'data-toggle' => 'dropdown' } + = icon('download') + = icon("caret-down") + %span.sr-only + Select Archive Format + %ul.dropdown-menu.dropdown-menu-align-right{ role: 'menu' } + %li.dropdown-header Source code + %li + = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'zip'), rel: 'nofollow' do + %i.fa.fa-download + %span Download zip + %li + = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do + %i.fa.fa-download + %span Download tar.gz + %li + = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do + %i.fa.fa-download + %span Download tar.bz2 + %li + = link_to archive_namespace_project_repository_path(project.namespace, project, ref: ref, format: 'tar'), rel: 'nofollow' do + %i.fa.fa-download + %span Download tar - - pipeline = project.pipelines.latest_successful_for(ref) - - if pipeline - - artifacts = pipeline.builds.latest.with_artifacts - - if artifacts.any? - %li.dropdown-header Artifacts - - unless pipeline.latest? - - latest_pipeline = project.pipeline_for(ref) - %li - .unclickable= ci_status_for_statuseable(latest_pipeline) - %li.dropdown-header Previous Artifacts - - artifacts.each do |job| - %li - = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{ref}/download", job: job.name), rel: 'nofollow' do - %i.fa.fa-download - %span Download '#{job.name}' + - pipeline = project.pipelines.latest_successful_for(ref) + - if pipeline + - artifacts = pipeline.builds.latest.with_artifacts + - if artifacts.any? + %li.dropdown-header Artifacts + - unless pipeline.latest? + - latest_pipeline = project.pipeline_for(ref) + %li + .unclickable= ci_status_for_statuseable(latest_pipeline) + %li.dropdown-header Previous Artifacts + - artifacts.each do |job| + %li + = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{ref}/download", job: job.name), rel: 'nofollow' do + %i.fa.fa-download + %span Download '#{job.name}' diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 8d9c15d0dc6..18b3b04154f 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -66,8 +66,6 @@ %td - if build.project = link_to build.project.name_with_namespace, admin_namespace_project_path(build.project.namespace, build.project) - - - if admin %td - if build.try(:runner) = runner_link(build.runner) @@ -93,9 +91,8 @@ %span #{time_ago_with_tooltip(build.finished_at)} %td.coverage - - if coverage - - if build.try(:coverage) - #{build.coverage}% + - if coverage && build.try(:coverage) + #{build.coverage}% %td .pull-right @@ -107,9 +104,9 @@ = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do = icon('remove', class: 'cred') - elsif allow_retry - - if build.retryable? - = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do - = icon('repeat') - - elsif build.playable? && !admin + - if build.playable? && !admin = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do = custom_icon('icon_play') + - elsif build.retryable? + = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do + = icon('repeat') diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index 423a1282eb2..ad1a7360a8b 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -1,10 +1,10 @@ - is_playable = subject.playable? && can?(current_user, :update_build, @project) - if is_playable - = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, data: { toggle: 'tooltip', title: "#{subject.name} - play", container: '.pipeline-graph', placement: 'bottom' } do + = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, data: { toggle: 'tooltip', title: "#{subject.name} - play", container: '.js-pipeline-graph', placement: 'bottom' } do = ci_icon_for_status('play') .ci-status-text= subject.name - elsif can?(current_user, :read_build, @project) - = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject), data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.pipeline-graph', placement: 'bottom' } do + = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject), data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.js-pipeline-graph', placement: 'bottom' } do %span{class: "ci-status-icon ci-status-icon-#{subject.status}"} = ci_icon_for_status(subject.status) .ci-status-text= subject.name diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 4c7b14a04db..b58dceb58c9 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -1,12 +1,13 @@ - status = pipeline.status +- detailed_status = pipeline.detailed_status - show_commit = local_assigns.fetch(:show_commit, true) - show_branch = local_assigns.fetch(:show_branch, true) %tr.commit %td.commit-link - = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "ci-status ci-#{status}" do - = ci_icon_for_status(status) - = ci_label_for_status(status) + = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "ci-status ci-#{detailed_status}" do + = ci_icon_for_status(detailed_status) + = ci_text_for_status(detailed_status) %td = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id) do @@ -42,15 +43,13 @@ - else Cant find HEAD commit for this branch - - stages_status = pipeline.statuses.latest.stages_status %td.stage-cell - - stages.each do |stage| - - status = stages_status[stage] - - tooltip = "#{stage.titleize}: #{status || 'not found'}" - - if status + - pipeline.stages.each do |stage| + - if stage.status + - tooltip = "#{stage.name.titleize}: #{stage.status || 'not found'}" .stage-container - = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do - = ci_icon_for_status(status) + = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage.name), class: "has-tooltip ci-status-icon-#{stage.status}", title: tooltip do + = ci_icon_for_status(stage.status) %td - if pipeline.duration diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml index e4cd55b9f7a..782f558e8b0 100644 --- a/app/views/projects/commit/_change.html.haml +++ b/app/views/projects/commit/_change.html.haml @@ -11,9 +11,9 @@ .modal-content .modal-header %a.close{href: "#", "data-dismiss" => "modal"} ร - %h3.page-title== #{label} this #{commit.change_type_title} + %h3.page-title== #{label} this #{commit.change_type_title(current_user)} .modal-body - = form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: 'form-horizontal js-#{type}-form js-requires-input' do + = form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: "form-horizontal js-#{type}-form js-requires-input" do .form-group.branch = label_tag 'target_branch', target_label, class: 'control-label' .col-sm-10 @@ -23,12 +23,11 @@ - if can?(current_user, :push_code, @project) .js-create-merge-request-container .checkbox - - nonce = SecureRandom.hex - = label_tag "create_merge_request-#{nonce}" do - = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}" + = label_tag do + = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: nil Start a <strong>new merge request</strong> with these changes - else - = hidden_field_tag 'create_merge_request', 1 + = hidden_field_tag 'create_merge_request', 1, id: nil .form-actions = submit_tag label, class: 'btn btn-create' = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" diff --git a/app/views/projects/commit/_ci_stage.html.haml b/app/views/projects/commit/_ci_stage.html.haml deleted file mode 100644 index 3a3d750439f..00000000000 --- a/app/views/projects/commit/_ci_stage.html.haml +++ /dev/null @@ -1,15 +0,0 @@ -%tr - %th{colspan: 10} - %strong - %a{name: stage} - - status = statuses.latest.status - %span{class: "ci-status-link ci-status-icon-#{status}"} - = ci_icon_for_status(status) - - if stage - - = stage.titleize - = render statuses.latest_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, allow_retry: true - = render statuses.retried_ci_stages, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, retried: true -%tr - %td{colspan: 10} - diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 65151ac3a56..c08ed8f6c16 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -1,13 +1,8 @@ .page-content-header .header-main-content - %strong Commit - %strong.monospace.js-details-short= @commit.short_id - = link_to("#", class: "js-details-expand hidden-xs hidden-sm") do - %span.text-expander - \... - %span.js-details-content.hide - %strong.monospace.commit-hash-full= @commit.id + %strong = clipboard_button(clipboard_text: @commit.id) + = @commit.short_id %span.hidden-xs authored #{time_ago_with_tooltip(@commit.authored_date)} %span by diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 1174158eb65..08d3443b3d0 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -24,20 +24,8 @@ in = time_interval_in_words pipeline.duration - .row-content-block.build-content.middle-block.pipeline-graph.hidden - .pipeline-visualization - %ul.stage-column-list - - stages = pipeline.stages_with_latest_statuses - - stages.each do |stage, statuses| - %li.stage-column - .stage-name - %a{name: stage} - - if stage - = stage.titleize - .builds-container - %ul - = render "projects/commit/pipeline_stage", statuses: statuses - + .row-content-block.build-content.middle-block.js-pipeline-graph.hidden + = render "projects/pipelines/graph", pipeline: pipeline - if pipeline.yaml_errors.present? .bs-callout.bs-callout-danger @@ -62,5 +50,4 @@ - if pipeline.project.build_coverage_enabled? %th Coverage %th - - pipeline.statuses.relevant.stages.each do |stage| - = render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.relevant.where(stage: stage) + = render partial: "projects/stage/stage", collection: pipeline.stages, as: :stage diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml deleted file mode 100644 index f9a9c8707f5..00000000000 --- a/app/views/projects/commit/_pipeline_stage.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -- status_groups = statuses.sort_by(&:name).group_by(&:group_name) -- status_groups.each do |group_name, grouped_statuses| - - if grouped_statuses.one? - - status = grouped_statuses.first - - is_playable = status.playable? && can?(current_user, :update_build, @project) - %li.build{ class: ("playable" if is_playable) } - .curve - .build-content - = render "projects/#{status.to_partial_path}_pipeline", subject: status - - else - %li.build - .curve - .dropdown.inline.build-content - = render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml index 2dc91a9b762..7f42fde0fea 100644 --- a/app/views/projects/commit/_pipelines_list.haml +++ b/app/views/projects/commit/_pipelines_list.haml @@ -12,4 +12,4 @@ %th Stages %th %th - = render pipelines, commit_sha: true, stage: true, allow_retry: true, stages: pipelines.stages, show_commit: false + = render pipelines, commit_sha: true, stage: true, allow_retry: true, show_commit: false diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 12096941209..a940515fadf 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -37,5 +37,5 @@ = preserve(markdown(commit.description, pipeline: :single_line, author: commit.author)) = commit_author_link(commit, avatar: false, size: 24) - authored + committed #{time_ago_with_tooltip(commit.committed_date)} diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 120ba9ffcd2..6c33d80becd 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -9,7 +9,7 @@ = icon('comment') \ - if editable_diff?(diff_file) - - link_opts = @merge_request.id ? { from_merge_request_id: @merge_request.id } : {} + - link_opts = @merge_request.persisted? ? { from_merge_request_iid: @merge_request.iid } : {} = edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path, blob: blob, link_opts: link_opts) diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml index a3e4b5b777e..16c96b66714 100644 --- a/app/views/projects/diffs/_line.html.haml +++ b/app/views/projects/diffs/_line.html.haml @@ -25,7 +25,7 @@ %a{href: "##{line_code}", data: { linenumber: link_text }} %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, diff_file.position(line), type) unless plain) }< - if email - %pre= diff_line_content(line.text) + %pre= line.text - else = diff_line_content(line.text) diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 3a5af2723c6..38e7fc4279c 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -112,7 +112,8 @@ %span.descr Enable Container Registry for this project = link_to icon('question-circle'), help_page_path('user/project/container_registry'), target: '_blank' - = render 'merge_request_settings', f: f + = render 'merge_request_settings', form: f + %hr %fieldset.features.append-bottom-default %h5.prepend-top-0 @@ -145,7 +146,7 @@ such as compressing file revisions and removing unreachable objects. .col-lg-9 = link_to 'Housekeeping', housekeeping_namespace_project_path(@project.namespace, @project), - method: :post, class: "btn btn-save" + method: :post, class: "btn btn-default" %hr .row.prepend-top-default .col-lg-3 diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 7a39064adc5..c0a83091c8c 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -16,7 +16,7 @@ %p Otherwise you can start with adding a = succeed ',' do - = link_to "README", new_readme_path, class: 'underlined-link' + = link_to "README", add_special_file_path(@project, file_name: 'README.md'), class: 'underlined-link' a = succeed ',' do = link_to "LICENSE", add_special_file_path(@project, file_name: 'LICENSE'), class: 'underlined-link' diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index a9235d6af35..a65a630f2d0 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -17,4 +17,6 @@ "project-stopped-environments-path" => project_environments_path(@project, scope: :stopped), "new-environment-path" => new_namespace_project_environment_path(@project.namespace, @project), "help-page-path" => help_page_path("ci/environments"), - "css-class" => container_class}} + "css-class" => container_class, + "commit-icon-svg" => custom_icon("icon_commit"), + "play-icon-svg" => custom_icon("icon_play")}} diff --git a/app/views/projects/forks/error.html.haml b/app/views/projects/forks/error.html.haml index 3d0ab5b85d6..98d81308407 100644 --- a/app/views/projects/forks/error.html.haml +++ b/app/views/projects/forks/error.html.haml @@ -13,7 +13,11 @@ - if @forked_project && @forked_project.errors.any? %p – - = @forked_project.errors.full_messages.first + - error = @forked_project.errors.full_messages.first + - if error.include?("already been taken") + Name has already been taken + - else + = error %p = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml index abf4f697f86..5ee3979c7e7 100644 --- a/app/views/projects/forks/index.html.haml +++ b/app/views/projects/forks/index.html.haml @@ -9,13 +9,13 @@ spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' } .dropdown - %button.dropdown-toggle.btn.sort-forks{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'} %span.light sort: - if @sort.present? = sort_options_hash[@sort] - else = sort_title_recently_created - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li - excluded_filters = [:state, :scope, :label_name, :milestone_id, :assignee_id, :author_id] diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index 0b99e9f8756..7f751d9ae2e 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -1,4 +1,12 @@ -%tr.generic_commit_status +- admin = local_assigns.fetch(:admin, false) +- ref = local_assigns.fetch(:ref, nil) +- commit_sha = local_assigns.fetch(:commit_sha, nil) +- retried = local_assigns.fetch(:retried, false) +- pipeline_link = local_assigns.fetch(:pipeline_link, false) +- stage = local_assigns.fetch(:stage, false) +- coverage = local_assigns.fetch(:coverage, false) + +%tr.generic_commit_status{class: ('retried' if retried)} %td.status - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url = ci_status_with_icon(generic_commit_status.status, generic_commit_status.target_url) @@ -8,14 +16,35 @@ %td.generic_commit_status-link - if can?(current_user, :read_commit_status, generic_commit_status) && generic_commit_status.target_url = link_to generic_commit_status.target_url do - %strong ##{generic_commit_status.id} + %span.build-link ##{generic_commit_status.id} - else - %strong ##{generic_commit_status.id} + %span.build-link ##{generic_commit_status.id} - - if defined?(retried) && retried + - if ref + - if generic_commit_status.ref + .icon-container + = generic_commit_status.tags.any? ? icon('tag') : icon('code-fork') + = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref) + - else + .light none + .icon-container.commit-icon + = custom_icon("icon_commit") + + - if commit_sha + = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "commit-id monospace" + + - if retried = icon('warning', class: 'text-warning has-tooltip', title: 'Status was retried.') - - if defined?(pipeline_link) && pipeline_link + .label-container + - if generic_commit_status.tags.any? + - generic_commit_status.tags.each do |tag| + %span.label.label-primary + = tag + - if retried + %span.label.label-warning retried + + - if pipeline_link %td = link_to pipeline_path(generic_commit_status.pipeline) do %span.pipeline-id ##{generic_commit_status.pipeline.id} @@ -25,25 +54,17 @@ - else %span.monospace API - - if defined?(commit_sha) && commit_sha - %td - = link_to generic_commit_status.short_sha, namespace_project_commit_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.sha), class: "monospace" - - - if defined?(ref) && ref + - if admin %td - - if generic_commit_status.ref - = link_to generic_commit_status.ref, namespace_project_commits_path(generic_commit_status.project.namespace, generic_commit_status.project, generic_commit_status.ref) - - else - .light none - - - if defined?(runner) && runner + - if generic_commit_status.project + = link_to generic_commit_status.project.name_with_namespace, admin_namespace_project_path(generic_commit_status.project.namespace, generic_commit_status.project) %td - if generic_commit_status.try(:runner) = runner_link(generic_commit_status.runner) - else .light none - - if defined?(stage) && stage + - if stage %td = generic_commit_status.stage @@ -51,24 +72,19 @@ = generic_commit_status.name %td - - if generic_commit_status.tags.any? - - generic_commit_status.tags.each do |tag| - %span.label.label-primary - = tag - - if defined?(retried) && retried - %span.label.label-warning retried - - %td.duration - if generic_commit_status.duration - = icon("clock-o") - = time_interval_in_words(generic_commit_status.duration) + %p.duration + = custom_icon("icon_timer") + = duration_in_numbers(generic_commit_status.duration) - %td.timestamp - if generic_commit_status.finished_at - = icon("calendar") - %span #{time_ago_with_tooltip(generic_commit_status.finished_at)} + %p.finished-at + = icon("calendar") + %span #{time_ago_with_tooltip(generic_commit_status.finished_at)} - - if defined?(coverage) && coverage - %td.coverage - - if generic_commit_status.try(:coverage) - #{generic_commit_status.coverage}% + %td.coverage + - if coverage && generic_commit_status.try(:coverage) + #{generic_commit_status.coverage}% + + %td + -# empty column to match number of columns in ci/builds/_build.html.haml diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml index 7b82d913d29..1bba0443154 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml @@ -1,4 +1,4 @@ -%a{ data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.pipeline-graph', placement: 'bottom' } } +%a{ data: { toggle: 'tooltip', title: "#{subject.name} - #{subject.status}", container: '.js-pipeline-graph', placement: 'bottom' } } - if subject.target_url = link_to subject.target_url do %span{class: "ci-status-icon ci-status-icon-#{subject.status}"} diff --git a/app/views/projects/group_links/update.js.haml b/app/views/projects/group_links/update.js.haml index af9a5b19060..55520fda494 100644 --- a/app/views/projects/group_links/update.js.haml +++ b/app/views/projects/group_links/update.js.haml @@ -1,3 +1,4 @@ :plain var $listItem = $('#{escape_javascript(render('shared/members/group', group_link: @group_link))}'); $("#group_member_#{@group_link.id} .list-item-name").replaceWith($listItem.find('.list-item-name')); + gl.utils.localTimeAgo($('.js-timeago'), $("#group_member_#{@group_link.id}")); diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml index 747bfa554cb..d48923b422a 100644 --- a/app/views/projects/issues/_merge_requests.html.haml +++ b/app/views/projects/issues/_merge_requests.html.haml @@ -2,12 +2,12 @@ %h2.merge-requests-title = pluralize(@merge_requests.count, 'Related Merge Request') %ul.unstyled-list.related-merge-requests - - has_any_ci = @merge_requests.any?(&:pipeline) + - has_any_ci = @merge_requests.any?(&:head_pipeline) - @merge_requests.each do |merge_request| %li %span.merge-request-ci-status - - if merge_request.pipeline - = render_pipeline_status(merge_request.pipeline) + - if merge_request.head_pipeline + = render_pipeline_status(merge_request.head_pipeline) - elsif has_any_ci = icon('blank fw') %span.merge-request-id diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 9ffcc48eb80..fa189ae62d8 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -15,9 +15,9 @@ = icon('ban') CLOSED - - if merge_request.pipeline + - if merge_request.head_pipeline %li - = render_pipeline_status(merge_request.pipeline) + = render_pipeline_status(merge_request.head_pipeline) - if merge_request.open? && merge_request.broken? %li diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 9c6f562f7db..4a08ed045f4 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -34,10 +34,11 @@ = link_to url_for(params), data: {target: 'div#pipelines', action: 'pipelines', toggle: 'tab'} do Pipelines %span.badge= @pipelines.size + - if @pipeline.present? %li.builds-tab = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do Builds - %span.badge= @statuses.size + %span.badge= @statuses_count %li.diffs-tab = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do Changes @@ -48,9 +49,10 @@ = render "projects/merge_requests/show/commits" #diffs.diffs.tab-pane - # This tab is always loaded via AJAX - - if @pipelines.any? + - if @pipeline.present? #builds.builds.tab-pane = render "projects/merge_requests/show/builds" + - if @pipelines.any? #pipelines.pipelines.tab-pane = render "projects/merge_requests/show/pipelines" @@ -65,5 +67,5 @@ :javascript var merge_request = new MergeRequest({ action: "#{(@show_changes_tab ? 'new/diffs' : 'new')}", - buildsLoaded: "#{@pipelines.any? ? 'true' : 'false'}" + buildsLoaded: "#{@pipeline.present? ? 'true' : 'false'}" }); diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index a497f418c7c..896f10104fa 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -31,7 +31,7 @@ %span.label-branch= source_branch_with_namespace(@merge_request) %span into %span.label-branch - = link_to @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch) + = link_to_if @merge_request.target_branch_exists?, @merge_request.target_branch, namespace_project_commits_path(@project.namespace, @project, @merge_request.target_branch) - if @merge_request.open? && @merge_request.diverged_from_target_branch? %span (#{pluralize(@merge_request.diverged_commits_count, 'commit')} behind) @@ -59,15 +59,16 @@ = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#commits', action: 'commits', toggle: 'tab' } do Commits %span.badge= @commits_count - - if @pipeline + - if @pipelines.any? %li.pipelines-tab = link_to pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#pipelines', action: 'pipelines', toggle: 'tab' } do Pipelines %span.badge= @pipelines.size + - if @pipeline.present? %li.builds-tab = link_to builds_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: '#builds', action: 'builds', toggle: 'tab' } do Builds - %span.badge= @statuses.size + %span.badge= @statuses_count %li.diffs-tab = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#diffs', action: 'diffs', toggle: 'tab' } do Changes diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index eab48b78cb3..5cc92595fe0 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -27,7 +27,7 @@ version #{version_index(merge_request_diff)} .monospace #{short_sha(merge_request_diff.head_commit_sha)} %small - #{number_with_delimiter(merge_request_diff.commits.count)} #{'commit'.pluralize(merge_request_diff.commits.count)}, + #{number_with_delimiter(merge_request_diff.commits_count)} #{'commit'.pluralize(merge_request_diff.commits_count)}, = time_ago_with_tooltip(merge_request_diff.created_at) - if @merge_request_diff.base_commit_sha diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 18c72ed875c..9ab7971b56c 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -1,21 +1,20 @@ - if @pipeline .mr-widget-heading - %w[success success_with_warnings skipped canceled failed running pending].each do |status| - .ci_widget{ class: "ci-status-icon-#{status}", style: ("display:none" unless @pipeline.status == status) } + .ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: ("display:none" unless @pipeline.status == status) } = ci_icon_for_status(status) %span Pipeline = link_to "##{@pipeline.id}", namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'pipeline' = ci_label_for_status(status) for - - commit = @merge_request.diff_head_commit = succeed "." do = link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace" %span.ci-coverage - elsif @merge_request.has_ci? - # Compatibility with old CI integrations (ex jenkins) when you request status from CI server via AJAX - - # Remove in later versions when services like Jenkins will set CI status via Commit status API + - # TODO, remove in later versions when services like Jenkins will set CI status via Commit status API .mr-widget-heading - %w[success skipped canceled failed running pending].each do |status| .ci_widget{class: "ci-#{status}", style: "display:none"} diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml index 20c93930abc..eee711dc5af 100644 --- a/app/views/projects/merge_requests/widget/_open.html.haml +++ b/app/views/projects/merge_requests/widget/_open.html.haml @@ -11,7 +11,7 @@ = render 'projects/merge_requests/widget/open/archived' - elsif @merge_request.branch_missing? = render 'projects/merge_requests/widget/open/missing_branch' - - elsif @merge_request.commits.blank? + - elsif @merge_request.has_no_commits? = render 'projects/merge_requests/widget/open/nothing' - elsif @merge_request.unchecked? = render 'projects/merge_requests/widget/open/check' diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index 608fdf1c5f5..a8918c85dde 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -14,7 +14,7 @@ ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", ci_environments_status_url: "#{ci_environments_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", gitlab_icon: "#{asset_path 'gitlab_logo.png'}", - ci_status: "#{@merge_request.pipeline ? @merge_request.pipeline.status : ''}", + ci_status: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.status : ''}", ci_message: { normal: "Build {{status}} for \"{{title}}\"", preparing: "{{status}} build for \"{{title}}\"" diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml index ce43ca3a286..435fe835fae 100644 --- a/app/views/projects/merge_requests/widget/open/_accept.html.haml +++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml @@ -9,7 +9,7 @@ - if @pipeline && @pipeline.active? %span.btn-group = button_tag class: "btn btn-create js-merge-button merge_when_build_succeeds" do - Merge When Build Succeeds + Merge When Pipeline Succeeds - unless @project.only_allow_merge_if_build_succeeds? = button_tag class: "btn btn-success dropdown-toggle", 'data-toggle' => 'dropdown' do = icon('caret-down') @@ -19,7 +19,7 @@ %li = link_to "#", class: "merge_when_build_succeeds" do = icon('check fw') - Merge When Build Succeeds + Merge When Pipeline Succeeds %li = link_to "#", class: "accept_merge_request" do = icon('warning fw') diff --git a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml index 1aeb12e4661..072d01d144e 100644 --- a/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml +++ b/app/views/projects/merge_requests/widget/open/_merge_when_build_succeeds.html.haml @@ -1,6 +1,6 @@ %h4 Set by #{link_to_member(@project, @merge_request.merge_user, avatar: true)} - to be merged automatically when the build succeeds. + to be merged automatically when the pipeline succeeds. %div %p = succeed '.' do diff --git a/app/views/projects/merge_requests/widget/open/_unresolved_discussions.html.haml b/app/views/projects/merge_requests/widget/open/_unresolved_discussions.html.haml index 35d5677ee37..e094f97f3b6 100644 --- a/app/views/projects/merge_requests/widget/open/_unresolved_discussions.html.haml +++ b/app/views/projects/merge_requests/widget/open/_unresolved_discussions.html.haml @@ -3,4 +3,8 @@ This merge request has unresolved discussions %p - Please resolve these discussions to allow this merge request to be merged.
\ No newline at end of file + Please resolve these discussions + - if @project.issues_enabled? && can?(current_user, :create_issue, @project) + or + = link_to "open an issue to resolve them later", new_namespace_project_issue_path(@project.namespace, @project, merge_request_for_resolving_discussions: @merge_request.iid) + to allow this merge request to be merged. diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index ba8895438c5..778a32e6345 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -65,7 +65,7 @@ .note-text.md = preserve do = note.redacted_note_html - = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true) + = edited_time_ago_with_tooltip(note, placement: 'bottom', html_class: 'note_edited_ago', include_author: true) - if note_editable = render 'projects/notes/edit_form', note: note .note-awards diff --git a/app/views/projects/pipelines/_graph.html.haml b/app/views/projects/pipelines/_graph.html.haml new file mode 100644 index 00000000000..0202833c0bf --- /dev/null +++ b/app/views/projects/pipelines/_graph.html.haml @@ -0,0 +1,4 @@ +- pipeline = local_assigns.fetch(:pipeline) +.pipeline-visualization.pipeline-graph + %ul.stage-column-list + = render partial: "projects/stage/graph", collection: pipeline.stages, as: :stage diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index 095bd254d6b..229bdfb0e8d 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -1,6 +1,6 @@ .page-content-header .header-main-content - = ci_status_with_icon(@pipeline.status) + = ci_status_with_icon(@pipeline.detailed_status) %strong Pipeline ##{@commit.pipelines.last.id} triggered #{time_ago_with_tooltip(@commit.authored_date)} by = author_avatar(@commit, size: 24) diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml index 718314701f9..88af41aa835 100644 --- a/app/views/projects/pipelines/_with_tabs.html.haml +++ b/app/views/projects/pipelines/_with_tabs.html.haml @@ -1,27 +1,19 @@ .tabs-holder - %ul.nav-links.no-top.no-bottom - %li.active - = link_to "Pipeline", "#js-tab-pipeline", data: { target: '#js-tab-pipeline', action: 'pipeline', toggle: 'tab' }, class: 'pipeline-tab' - %li - = link_to "#js-tab-builds", data: { target: '#js-tab-builds', action: 'build', toggle: 'tab' }, class: 'builds-tab' do + %ul.pipelines-tabs.nav-links.no-top.no-bottom + %li.js-pipeline-tab-link + = link_to namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: { target: 'div#js-tab-pipeline', action: 'pipelines', toggle: 'tab' }, class: 'pipeline-tab' do + Pipeline + %li.js-builds-tab-link + = link_to builds_namespace_project_pipeline_path(@project.namespace, @project, @pipeline), data: {target: 'div#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do Builds - %span.badge= pipeline.statuses.count + %span.badge.js-builds-counter= pipeline.statuses.count + + .tab-content - #js-tab-pipeline.tab-pane.active - .build-content.middle-block.pipeline-graph - .pipeline-visualization - %ul.stage-column-list - - stages = pipeline.stages_with_latest_statuses - - stages.each do |stage, statuses| - %li.stage-column - .stage-name - %a{name: stage} - - if stage - = stage.titleize - .builds-container - %ul - = render "projects/commit/pipeline_stage", statuses: statuses + #js-tab-pipeline.tab-pane + .build-content.middle-block.js-pipeline-graph + = render "projects/pipelines/graph", pipeline: pipeline #js-tab-builds.tab-pane - if pipeline.yaml_errors.present? @@ -47,5 +39,4 @@ - if pipeline.project.build_coverage_enabled? %th Coverage %th - - pipeline.statuses.relevant.stages.each do |stage| - = render 'projects/commit/ci_stage', stage: stage, statuses: pipeline.statuses.relevant.where(stage: stage) + = render partial: "projects/stage/stage", collection: pipeline.stages, as: :stage diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index 4bc49072f35..e1e787dbde4 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -37,7 +37,6 @@ %span CI Lint %div.content-list.pipelines - - stages = @pipelines.stages - if @pipelines.blank? %div .nothing-here-block No pipelines to show @@ -51,6 +50,6 @@ %th Stages %th %th.hidden-xs - = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages + = render @pipelines, commit_sha: true, stage: true, allow_retry: true = paginate @pipelines, theme: 'gitlab' diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml index 8c6652a5f90..29a41bc664b 100644 --- a/app/views/projects/pipelines/show.html.haml +++ b/app/views/projects/pipelines/show.html.haml @@ -2,7 +2,7 @@ - page_title "Pipeline" = render "projects/pipelines/head" -%div{ class: container_class } +%div.js-pipeline-container{ class: container_class, data: { controller_action: "#{controller.action_name}" } } - if @commit = render "projects/pipelines/info" diff --git a/app/views/projects/pipelines_settings/_badge.html.haml b/app/views/projects/pipelines_settings/_badge.html.haml index 7b7fa56d993..22a3b884520 100644 --- a/app/views/projects/pipelines_settings/_badge.html.haml +++ b/app/views/projects/pipelines_settings/_badge.html.haml @@ -1,4 +1,4 @@ -.row{ class: badge.title.gsub(' ', '-') } +%div{ class: badge.title.gsub(' ', '-') } .col-lg-3.profile-settings-sidebar %h4.prepend-top-0 = badge.title.capitalize diff --git a/app/views/projects/pipelines_settings/show.html.haml b/app/views/projects/pipelines_settings/show.html.haml index 96221a20502..1f698558bce 100644 --- a/app/views/projects/pipelines_settings/show.html.haml +++ b/app/views/projects/pipelines_settings/show.html.haml @@ -86,6 +86,9 @@ %li tap --coverage-report=text-summary (NodeJS) - %code ^Statements\s*:\s*([^%]+) + %li + excoveralls (Elixir) - + %code \[TOTAL\]\s+(\d+\.\d+)% = f.submit 'Save changes', class: "btn btn-save" diff --git a/app/views/projects/project_members/update.js.haml b/app/views/projects/project_members/update.js.haml index 91927181efb..d15f4310ff5 100644 --- a/app/views/projects/project_members/update.js.haml +++ b/app/views/projects/project_members/update.js.haml @@ -1,3 +1,4 @@ :plain var $listItem = $('#{escape_javascript(render('shared/members/member', member: @project_member))}'); $("##{dom_id(@project_member)} .list-item-name").replaceWith($listItem.find('.list-item-name')); + gl.utils.localTimeAgo($('.js-timeago'), $("##{dom_id(@project_member)}")); diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml index 32e1f8a21b0..068a6610350 100644 --- a/app/views/projects/snippets/_actions.html.haml +++ b/app/views/projects/snippets/_actions.html.haml @@ -1,13 +1,13 @@ .hidden-xs - - if can?(current_user, :create_project_snippet, @project) - = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New snippet" do - New snippet - - if can?(current_user, :update_project_snippet, @snippet) - = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do - Delete - if can?(current_user, :update_project_snippet, @snippet) - = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do + = link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped" do Edit + - if can?(current_user, :update_project_snippet, @snippet) + = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do + Delete + - 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 can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet) .visible-xs-block.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index e77e1b026f6..84e05cd6d88 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -1,11 +1,19 @@ - page_title "Snippets" -.sub-header-block - - if can?(current_user, :create_project_snippet, @project) - = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-wide-on-sm pull-right", title: "New snippet" do - New snippet +- if current_user + .top-area + - include_private = @project.team.member?(current_user) || current_user.admin? + = render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private } + + .nav-controls.hidden-xs + - if can?(current_user, :create_project_snippet, @project) + = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New snippet" do + New snippet - .oneline - Share code pastes with others out of git repository +- if can?(current_user, :create_project_snippet, @project) + .visible-xs + + = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new btn-block", title: "New snippet" do + New snippet = render 'snippets/snippets' diff --git a/app/views/projects/stage/_graph.html.haml b/app/views/projects/stage/_graph.html.haml new file mode 100644 index 00000000000..1d8fa10db0c --- /dev/null +++ b/app/views/projects/stage/_graph.html.haml @@ -0,0 +1,22 @@ +- stage = local_assigns.fetch(:stage) +- statuses = stage.statuses.latest +- status_groups = statuses.sort_by(&:name).group_by(&:group_name) +%li.stage-column + .stage-name + %a{ name: stage.name } + = stage.name.titleize + .builds-container + %ul + - status_groups.each do |group_name, grouped_statuses| + - if grouped_statuses.one? + - status = grouped_statuses.first + - is_playable = status.playable? && can?(current_user, :update_build, @project) + %li.build{ class: ("playable" if is_playable) } + .curve + .build-content + = render "projects/#{status.to_partial_path}_pipeline", subject: status + - else + %li.build + .curve + .dropdown.inline.build-content + = render "projects/stage/in_stage_group", name: group_name, subject: grouped_statuses diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/stage/_in_stage_group.html.haml index 2b26ad9d6fa..2b26ad9d6fa 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/stage/_in_stage_group.html.haml diff --git a/app/views/projects/stage/_stage.html.haml b/app/views/projects/stage/_stage.html.haml new file mode 100644 index 00000000000..1684e02fbad --- /dev/null +++ b/app/views/projects/stage/_stage.html.haml @@ -0,0 +1,13 @@ +%tr + %th{colspan: 10} + %strong + %a{ name: stage.name } + %span{class: "ci-status-link ci-status-icon-#{stage.status}"} + = ci_icon_for_status(stage.status) + + = stage.name.titleize += render stage.statuses.latest_ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, allow_retry: true += render stage.statuses.retried_ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, retried: true +%tr + %td{colspan: 10} + diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 7a0d9dcc94f..1d39f3a7534 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -10,15 +10,16 @@ .nav-controls = form_tag(filter_tags_path, method: :get) do = search_field_tag :search, params[:search], { placeholder: 'Filter by tag name', id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false } + .dropdown.inline - %button.dropdown-toggle.btn{ type: 'button', data: { toggle: 'dropdown'} } + %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown'} } %span.light - = @sort.humanize - = icon('caret-down') + = projects_sort_options_hash[@sort] + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li - = link_to filter_tags_path(sort: nil) do - Name + = link_to filter_tags_path(sort: sort_value_name) do + = sort_title_name = link_to filter_tags_path(sort: sort_value_recently_updated) do = sort_title_recently_updated = link_to filter_tags_path(sort: sort_value_oldest_updated) do diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index 155af755759..12facb6eb73 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -3,8 +3,16 @@ = render "projects/commits/head" %div{ class: container_class } - .sub-header-block - .pull-right.tag-buttons + .top-area.multi-line + .nav-text + .title + %span.item-title= @tag.name + - if @commit + = render 'projects/branches/commit', commit: @commit, project: @project + - else + Cant find HEAD commit for this tag + + .nav-controls - if can?(current_user, :push_code, @project) = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Edit release notes' do = icon("pencil") @@ -15,15 +23,8 @@ = render 'projects/buttons/download', project: @project, ref: @tag.name - if can?(current_user, :admin_project, @project) .pull-right - = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do + = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do %i.fa.fa-trash-o - .tag-info.append-bottom-10 - .title - %span.item-title= @tag.name - - if @commit - = render 'projects/branches/commit', commit: @commit, project: @project - - else - Cant find HEAD commit for this tag - if @tag.message.present? %pre.body = strip_gpg_signature(@tag.message) diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml index 21e378b8735..d37c376c36b 100644 --- a/app/views/projects/tree/_tree_content.html.haml +++ b/app/views/projects/tree/_tree_content.html.haml @@ -5,14 +5,11 @@ %tr %th Name %th.hidden-xs - .pull-left Last Commit + .pull-left Last commit .last-commit.hidden-sm.pull-left - - %i.fa.fa-angle-right - - %small.light + %small.light + = clipboard_button(clipboard_text: @commit.id) = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" - – = time_ago_with_tooltip(@commit.committed_date) = @commit.full_title %small.commit-history-link-spacer | diff --git a/app/views/projects/variables/_table.html.haml b/app/views/projects/variables/_table.html.haml index 07cee86ba4c..c7cebf45160 100644 --- a/app/views/projects/variables/_table.html.haml +++ b/app/views/projects/variables/_table.html.haml @@ -12,8 +12,8 @@ - @project.variables.order_key_asc.each do |variable| - if variable.id? %tr - %td= variable.key - %td= variable.value + %td.variable-key= variable.key + %td.variable-value{ "data-value" => variable.value }****** %td = link_to namespace_project_variable_path(@project.namespace, @project, variable), class: "btn btn-transparent btn-variable-edit" do %span.sr-only diff --git a/app/views/projects/variables/index.html.haml b/app/views/projects/variables/index.html.haml index 09bb54600af..39303700131 100644 --- a/app/views/projects/variables/index.html.haml +++ b/app/views/projects/variables/index.html.haml @@ -15,3 +15,4 @@ No variables found, add one with the form above. - else = render "table" + %button.btn.btn-info.js-btn-toggle-reveal-values{"data-status" => 'hidden'} Reveal Values diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 4e41a15d9f4..c52527332bc 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -34,9 +34,6 @@ - if @page && @page.persisted? = f.submit 'Save changes', class: "btn-save btn" .pull-right - - if can?(current_user, :admin_wiki, @project) - = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger btn-grouped" do - Delete = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-cancel btn-grouped" - else = f.submit 'Create page', class: "btn-create btn" diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml deleted file mode 100644 index afdef70e1cf..00000000000 --- a/app/views/projects/wikis/_nav.html.haml +++ /dev/null @@ -1,16 +0,0 @@ -= 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(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do - = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) - - = nav_link(path: 'wikis#pages') do - = link_to 'Pages', namespace_project_wikis_pages_path(@project.namespace, @project) - - = nav_link(path: 'wikis#git_access') do - = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do - Git Access - - = render 'projects/wikis/new' diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml new file mode 100644 index 00000000000..cad9c15a49e --- /dev/null +++ b/app/views/projects/wikis/_sidebar.html.haml @@ -0,0 +1,23 @@ +%aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar + .block.wiki-sidebar-header.append-bottom-default + %a.gutter-toggle.pull-right.visible-xs-block.visible-sm-block.js-sidebar-wiki-toggle{ href: "#" } + = icon('angle-double-right') + + - git_access_url = namespace_project_wikis_git_access_path(@project.namespace, @project) + = link_to git_access_url, class: active_nav_link?(path: 'wikis#git_access') ? 'active' : '' do + = succeed ' ' do + = icon('cloud-download') + Clone repository + + .blocks-container + .block.block-first + %ul.wiki-pages + - @sidebar_wiki_pages.each do |wiki_page| + %li{ class: params[:id] == wiki_page.slug ? 'active' : '' } + = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_page) do + = wiki_page.title.capitalize + .block + = link_to namespace_project_wikis_pages_path(@project.namespace, @project), class: 'btn btn-block' do + More Pages + += render 'projects/wikis/new' diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml index 679d6018bef..8cf018da1b7 100644 --- a/app/views/projects/wikis/edit.html.haml +++ b/app/views/projects/wikis/edit.html.haml @@ -1,23 +1,35 @@ - @no_container = true - page_title "Edit", @page.title.capitalize, "Wiki" -= render 'nav' %div{ class: container_class } - .top-area + .wiki-page-header.has-sidebar-toggle + %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } + = icon('angle-double-left') + .nav-text - %strong + %h2.wiki-page-title - if @page.persisted? = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) - else = @page.title.capitalize - %span.light - · - Edit Page + %span.light + · + - if @page.persisted? + Edit Page + - else + Create Page .nav-controls - - if !(@page && @page.persisted?) - - if can?(current_user, :create_wiki, @project) - = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do - New Page + - if can?(current_user, :create_wiki, @project) + = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do + New Page + - if @page.persisted? + = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn" do + Page History + - if can?(current_user, :admin_wiki, @project) + = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-danger" do + Delete = render 'form' + += render 'sidebar' diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index b8811a28dd6..e25d6a48573 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -1,34 +1,36 @@ - @no_container = true - page_title "Git Access", "Wiki" -= render 'nav' %div{ class: container_class } - .sub-header-block - %span.oneline - Git access for + .wiki-page-header.has-sidebar-toggle + %button.btn.btn-default.visible-xs.visible-sm.pull-right.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } + = icon('angle-double-left') + + .git-access-header + Clone repository %strong= @project_wiki.path_with_namespace - .pull-right - = render "shared/clone_panel", project: @project_wiki + = render "shared/clone_panel", project: @project_wiki + + .wiki-git-access + %h3 Install Gollum + %pre.dark + :preserve + gem install gollum - .prepend-top-default - %fieldset - %legend Install Gollum: - %pre.dark - :preserve - gem install gollum + %h3 Clone your wiki + %pre.dark + :preserve + git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')} + cd #{h @project_wiki.path} - %legend Clone Your Wiki: - %pre.dark - :preserve - git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')} - cd #{h @project_wiki.path} + %h3 Start Gollum and edit locally + %pre.dark + :preserve + gollum + == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin + >> Thin web server (v1.5.0 codename Knife) + >> Maximum connections set to 1024 + >> Listening on 0.0.0.0:4567, CTRL+C to stop - %legend Start Gollum And Edit Locally: - %pre.dark - :preserve - gollum - == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin - >> Thin web server (v1.5.0 codename Knife) - >> Maximum connections set to 1024 - >> Listening on 0.0.0.0:4567, CTRL+C to stop += render 'sidebar' diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml index 4c0b14e2c42..dd7213622c1 100644 --- a/app/views/projects/wikis/history.html.haml +++ b/app/views/projects/wikis/history.html.haml @@ -1,13 +1,16 @@ - page_title "History", @page.title.capitalize, "Wiki" -= render 'nav' + %div{ class: container_class } - .top-area + .wiki-page-header.has-sidebar-toggle + %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } + = icon('angle-double-left') + .nav-text - %strong + %h2.wiki-page-title = link_to @page.title.capitalize, namespace_project_wiki_path(@project.namespace, @project, @page) - %span.light - · - History + %span.light + · + History .table-holder %table.table @@ -35,3 +38,5 @@ %td %strong = @page.page.wiki.page(@page.page.name, commit.id).try(:format) + += render 'sidebar' diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml index 9c10acd4cb6..e1eaffc6884 100644 --- a/app/views/projects/wikis/pages.html.haml +++ b/app/views/projects/wikis/pages.html.haml @@ -1,9 +1,18 @@ - @no_container = true - page_title "Pages", "Wiki" -= render 'nav' - %div{ class: container_class } + .wiki-page-header + + .nav-text + %h2.wiki-page-title + Wiki Pages + + .nav-controls + = link_to namespace_project_wikis_git_access_path(@project.namespace, @project), class: 'btn' do + = icon('cloud-download') + Clone repository + %ul.content-list - @wiki_pages.each do |wiki_page| %li diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index 5cebb538cf5..1b6dceee241 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -1,15 +1,19 @@ - @no_container = true - page_title @page.title.capitalize, "Wiki" -= render 'nav' %div{ class: container_class } - .top-area + .wiki-page-header.has-sidebar-toggle + %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } + = icon('angle-double-left') + .nav-text - %strong= @page.title.capitalize + %h2.wiki-page-title= @page.title.capitalize %span.wiki-last-edit-by - · - last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)} + Last edited by + %strong + #{@page.commit.author.name} + #{time_ago_with_tooltip(@page.commit.authored_date)} .nav-controls = render 'main_links' @@ -19,8 +23,9 @@ This is an old version of this page. You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", namespace_project_wiki_history_path(@project.namespace, @project, @page)}. - .wiki-holder.prepend-top-default.append-bottom-default .wiki = preserve do = render_wiki_content(@page) + += render 'sidebar' diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml index ef1c0296d49..938be20c7cf 100644 --- a/app/views/search/_filter.html.haml +++ b/app/views/search/_filter.html.haml @@ -3,7 +3,7 @@ - if params[:project_id].present? = hidden_field_tag :project_id, params[:project_id] .dropdown - %button.dropdown-menu-toggle.btn.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Group:" } } + %button.dropdown-menu-toggle.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Group:" } } %span.dropdown-toggle-text Group: - if @group.present? @@ -18,7 +18,7 @@ = dropdown_loading .dropdown.project-filter - %button.dropdown-menu-toggle.btn.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Project:" } } + %button.dropdown-menu-toggle.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Project:" } } %span.dropdown-toggle-text Project: - if @project.present? diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml index c414acb6a11..027d42396b4 100644 --- a/app/views/search/results/_snippet_title.html.haml +++ b/app/views/search/results/_snippet_title.html.haml @@ -14,7 +14,7 @@ = link_to snippet_title.project.name_with_namespace, namespace_project_path(snippet_title.project.namespace, snippet_title.project) .snippet-info - = "##{snippet_title.id}" + = snippet_title.to_reference %span by = link_to user_snippets_path(snippet_title.author) do diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml index c367ae336db..e50ab5fea09 100644 --- a/app/views/shared/_event_filter.html.haml +++ b/app/views/shared/_event_filter.html.haml @@ -5,5 +5,7 @@ - if event_filter_visible(:merge_requests) = event_filter_link EventFilter.merged, 'Merge events' - if event_filter_visible(:issues) + = event_filter_link EventFilter.issue, 'Issue events' + - if comments_visible? = event_filter_link EventFilter.comments, 'Comments' = event_filter_link EventFilter.team, 'Team' diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml index ba25e09d638..0bc851b4256 100644 --- a/app/views/shared/_group_form.html.haml +++ b/app/views/shared/_group_form.html.haml @@ -13,8 +13,9 @@ .input-group-addon = root_url = f.text_field :path, placeholder: 'open-source', class: 'form-control', - autofocus: local_assigns[:autofocus] || false, pattern: "[a-zA-Z0-9-_]+", - required: true, title: 'Please choose a group name with no special characters.' + autofocus: local_assigns[:autofocus] || false, required: true, + pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_SIMPLE, + title: 'Please choose a group name with no special characters.' - if @group.persisted? .alert.alert-warning.prepend-top-10 diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml index baa6d5f8206..26b349e8a62 100644 --- a/app/views/shared/_issues.html.haml +++ b/app/views/shared/_issues.html.haml @@ -1,4 +1,4 @@ -- if @issues.reorder(nil).any? +- if @issues.to_a.any? - @issues.group_by(&:project).each do |group| .panel.panel-default.panel-small - project = group[0] diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml index ca3178395c1..2f3605b4d27 100644 --- a/app/views/shared/_merge_requests.html.haml +++ b/app/views/shared/_merge_requests.html.haml @@ -1,4 +1,4 @@ -- if @merge_requests.reorder(nil).any? +- if @merge_requests.to_a.any? - @merge_requests.group_by(&:target_project).each do |group| .panel.panel-default.panel-small - project = group[0] diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml index 68e05cb72e1..ede3c7090d7 100644 --- a/app/views/shared/_sort_dropdown.html.haml +++ b/app/views/shared/_sort_dropdown.html.haml @@ -1,11 +1,11 @@ .dropdown.inline.prepend-left-10 - %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle{type: 'button', data: {toggle: 'dropdown'}} %span.light - if @sort.present? = sort_options_hash[@sort] - else = sort_title_recently_created - = icon('caret-down') + = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort %li = link_to page_filter_path(sort: sort_value_priority, label: true) do diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index b7e5e928993..e3503981afe 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -29,9 +29,9 @@ .filter-item.inline.labels-filter = render "shared/issuable/label_dropdown", selected: finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" } - - if issuable_filters_present + - if issuable_filter_present? .filter-item.inline.reset-filters - %a{href: page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :search])} Reset filters + %a{href: page_filter_path(without: issuable_filter_params)} Reset filters .pull-right - if boards_page diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 9b9ad510444..bdb00bfa33c 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -16,20 +16,9 @@ = render 'shared/issuable/form/template_selector', issuable: issuable = render 'shared/issuable/form/title', issuable: issuable, form: form -.form-group.detail-page-description - = form.label :description, 'Description', class: 'control-label' - .col-sm-10 += render 'shared/issuable/form/description', issuable: issuable, form: form - = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do - = render 'projects/zen', f: form, attr: :description, - classes: 'note-textarea', - placeholder: "Write a comment or drag your files here...", - supports_slash_commands: !issuable.persisted? - = render 'projects/notes/hints', supports_slash_commands: !issuable.persisted? - .clearfix - .error-alert - -- if issuable.is_a?(Issue) +- if issuable.respond_to?(:confidential) .form-group .col-sm-offset-2.col-sm-10 .checkbox @@ -37,38 +26,7 @@ = form.check_box :confidential This issue is confidential and should only be visible to team members with at least Reporter access. -- if can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project) - - has_due_date = issuable.has_attribute?(:due_date) - %hr - .row - %div{ class: (has_due_date ? "col-lg-6" : "col-sm-12") } - .form-group.issue-assignee - = form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}" - .col-sm-10{ class: ("col-lg-8" if has_due_date) } - .issuable-form-select-holder - - if issuable.assignee_id - = form.hidden_field :assignee_id - = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", - placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} }) - .form-group.issue-milestone - = form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" - .col-sm-10{ class: ("col-lg-8" if has_due_date) } - .issuable-form-select-holder - = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" - .form-group - - has_labels = @labels && @labels.any? - = form.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" - = form.hidden_field :label_ids, multiple: true, value: '' - .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } - .issuable-form-select-holder - = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false}, dropdown_title: "Select label" - - if has_due_date - .col-lg-6 - .form-group - = form.label :due_date, "Due date", class: "control-label" - .col-sm-10 - .issuable-form-select-holder - = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date" += render 'shared/issuable/form/metadata', issuable: issuable, form: form - if issuable.can_move?(current_user) %hr @@ -82,30 +40,22 @@ title: 'Moving an issue will copy the discussion to a different project and close it here. All participants will be notified of the new location.' } = icon('question-circle') -- if issuable.is_a?(MergeRequest) && !issuable.closed_without_fork? - %hr - - if @merge_request.new_record? - .form-group - = form.label :source_branch, class: 'control-label' - .col-sm-10 - .issuable-form-select-holder - = form.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true }) += render 'shared/issuable/form/branch_chooser', issuable: issuable, form: form + +- if @merge_request_for_resolving_discussions .form-group - = form.label :target_branch, class: 'control-label' - .col-sm-10 - .issuable-form-select-holder - = form.select(:target_branch, @merge_request.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record?, data: {placeholder: "Select branch"} }) - - if @merge_request.new_record? - - = link_to 'Change branches', mr_change_branches_path(@merge_request) - - if @merge_request.can_remove_source_branch?(current_user) - .form-group - .col-sm-10.col-sm-offset-2 - .checkbox - = label_tag 'merge_request[force_remove_source_branch]' do - = hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil - = check_box_tag 'merge_request[force_remove_source_branch]', '1', @merge_request.force_remove_source_branch? - Remove source branch when merge request is accepted. + .col-sm-10.col-sm-offset-2 + - if @merge_request_for_resolving_discussions.discussions_can_be_resolved_by?(current_user) + = icon('exclamation-triangle') + Creating this issue will mark all discussions in + = link_to @merge_request_for_resolving_discussions.to_reference, merge_request_path(@merge_request_for_resolving_discussions) + as resolved. + = hidden_field_tag 'merge_request_for_resolving_discussions', @merge_request_for_resolving_discussions.iid + - else + = icon('exclamation-triangle') + You can't automatically mark all discussions in + = link_to @merge_request_for_resolving_discussions.to_reference, merge_request_path(@merge_request_for_resolving_discussions) + as resolved. Ask someone with sufficient rights to resolve the them. - is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?) .row-content-block{class: (is_footer ? "footer-block" : "middle-block")} diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 1d778bc88de..22b5a6aa11b 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -22,7 +22,7 @@ %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} %span.dropdown-toggle-text{ class: ("is-default" if selected.nil? || selected.empty?) } = multi_label_name(selected, "Labels") - = icon('caret-down') + = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable = render partial: "shared/issuable/label_page_default", locals: { title: dropdown_title, show_footer: show_footer, show_create: show_create } - if show_create && project && can?(current_user, :admin_label, project) diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml index 5527a2f889a..0af92b59584 100644 --- a/app/views/shared/issuable/_nav.html.haml +++ b/app/views/shared/issuable/_nav.html.haml @@ -4,22 +4,22 @@ %ul.nav-links.issues-state-filters %li{class: ("active" if params[:state] == 'opened')} - = link_to page_filter_path(state: 'opened', label: true), title: "Filter by #{page_context_word} that are currently opened." do + = link_to page_filter_path(state: 'opened', label: true), id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened." do #{issuables_state_counter_text(type, :opened)} - if type == :merge_requests %li{class: ("active" if params[:state] == 'merged')} - = link_to page_filter_path(state: 'merged', label: true), title: 'Filter by merge requests that are currently merged.' do + = link_to page_filter_path(state: 'merged', label: true), id: 'state-merged', title: 'Filter by merge requests that are currently merged.' do #{issuables_state_counter_text(type, :merged)} %li{class: ("active" if params[:state] == 'closed')} - = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by merge requests that are currently closed and unmerged.' do + = link_to page_filter_path(state: 'closed', label: true), id: 'state-closed', title: 'Filter by merge requests that are currently closed and unmerged.' do #{issuables_state_counter_text(type, :closed)} - else %li{class: ("active" if params[:state] == 'closed')} - = link_to page_filter_path(state: 'closed', label: true), title: 'Filter by issues that are currently closed.' do + = link_to page_filter_path(state: 'closed', label: true), id: 'state-all', title: 'Filter by issues that are currently closed.' do #{issuables_state_counter_text(type, :closed)} %li{class: ("active" if params[:state] == 'all')} - = link_to page_filter_path(state: 'all', label: true), title: "Show all #{page_context_word}." do + = link_to page_filter_path(state: 'all', label: true), id: 'state-all', title: "Show all #{page_context_word}." do #{issuables_state_counter_text(type, :all)} diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index f166fac105d..958f8413e1d 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -9,12 +9,12 @@ %a.gutter-toggle.pull-right.js-sidebar-toggle{ role: "button", href: "#", aria: { label: "Toggle sidebar" } } = sidebar_gutter_toggle_icon - if current_user - %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", aria: { label: (todo.nil? ? "Add Todo" : "Mark Done") }, data: { todo_text: "Add Todo", mark_text: "Mark Done", issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project), delete_path: (dashboard_todo_path(todo) if todo) } } + %button.btn.btn-default.issuable-header-btn.pull-right.js-issuable-todo{ type: "button", aria: { label: (todo.nil? ? "Add todo" : "Mark done") }, data: { todo_text: "Add todo", mark_text: "Mark done", issuable_id: issuable.id, issuable_type: issuable.class.name.underscore, url: namespace_project_todos_path(@project.namespace, @project), delete_path: (dashboard_todo_path(todo) if todo) } } %span.js-issuable-todo-text - if todo - Mark Done + Mark done - else - Add Todo + Add todo = icon('spin spinner', class: 'hidden js-issuable-todo-loading') = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| @@ -144,16 +144,11 @@ .block.light.subscription{data: {url: toggle_subscription_path(issuable)}} .sidebar-collapsed-icon = icon('rss') - .title.hide-collapsed + %span.issuable-header-text.hide-collapsed.pull-left Notifications - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed' - %button.btn.btn-block.btn-default.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" } + %button.btn.btn-default.pull-right.js-subscribe-button.issuable-subscribe-button.hide-collapsed{ type: "button" } %span= subscribed ? 'Unsubscribe' : 'Subscribe' - .subscription-status.hide-collapsed{data: {status: subscribtion_status}} - .unsubscribed{class: ( 'hidden' if subscribed )} - You're not receiving notifications from this thread. - .subscribed{class: ( 'hidden' unless subscribed )} - You're receiving notifications because you're subscribed to this thread. - project_ref = cross_project_reference(@project, issuable) .block.project-reference @@ -170,6 +165,6 @@ new MilestoneSelect('{"namespace":"#{@project.namespace.path}","path":"#{@project.path}"}'); new LabelsSelect(); new IssuableContext('#{escape_javascript(current_user.to_json(only: [:username, :id, :name]))}'); - new Subscription('.subscription') + gl.Subscription.bindAll('.subscription'); new gl.DueDateSelectors(); sidebar = new Sidebar(); diff --git a/app/views/shared/issuable/form/_branch_chooser.html.haml b/app/views/shared/issuable/form/_branch_chooser.html.haml new file mode 100644 index 00000000000..b757893ea04 --- /dev/null +++ b/app/views/shared/issuable/form/_branch_chooser.html.haml @@ -0,0 +1,30 @@ +- issuable = local_assigns.fetch(:issuable) +- form = local_assigns.fetch(:form) + +- return unless issuable.is_a?(MergeRequest) +- return if issuable.closed_without_fork? + +%hr +- if issuable.new_record? + .form-group + = form.label :source_branch, class: 'control-label' + .col-sm-10 + .issuable-form-select-holder + = form.select(:source_branch, [issuable.source_branch], {}, { class: 'source_branch select2 span2', disabled: true }) +.form-group + = form.label :target_branch, class: 'control-label' + .col-sm-10 + .issuable-form-select-holder + = form.select(:target_branch, issuable.target_branches, { include_blank: true }, { class: 'target_branch select2 span2', disabled: issuable.new_record?, data: { placeholder: "Select branch" }}) + - if issuable.new_record? + + = link_to 'Change branches', mr_change_branches_path(issuable) + +- if issuable.can_remove_source_branch?(current_user) + .form-group + .col-sm-10.col-sm-offset-2 + .checkbox + = label_tag 'merge_request[force_remove_source_branch]' do + = hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil + = check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch? + Remove source branch when merge request is accepted. diff --git a/app/views/shared/issuable/form/_description.html.haml b/app/views/shared/issuable/form/_description.html.haml new file mode 100644 index 00000000000..dbace9ce401 --- /dev/null +++ b/app/views/shared/issuable/form/_description.html.haml @@ -0,0 +1,15 @@ +- issuable = local_assigns.fetch(:issuable) +- form = local_assigns.fetch(:form) + +.form-group.detail-page-description + = form.label :description, 'Description', class: 'control-label' + .col-sm-10 + + = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do + = render 'projects/zen', f: form, attr: :description, + classes: 'note-textarea', + placeholder: "Write a comment or drag your files here...", + supports_slash_commands: !issuable.persisted? + = render 'projects/notes/hints', supports_slash_commands: !issuable.persisted? + .clearfix + .error-alert diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml new file mode 100644 index 00000000000..a47085230b8 --- /dev/null +++ b/app/views/shared/issuable/form/_metadata.html.haml @@ -0,0 +1,38 @@ +- issuable = local_assigns.fetch(:issuable) + +- return unless can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project) + +- has_due_date = issuable.has_attribute?(:due_date) +- has_labels = @labels && @labels.any? +- form = local_assigns.fetch(:form) + +%hr +.row + %div{ class: (has_due_date ? "col-lg-6" : "col-sm-12") } + .form-group.issue-assignee + = form.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}" + .col-sm-10{ class: ("col-lg-8" if has_due_date) } + .issuable-form-select-holder + - if issuable.assignee_id + = form.hidden_field :assignee_id + = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", + placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: issuable.project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee"} }) + .form-group.issue-milestone + = form.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" + .col-sm-10{ class: ("col-lg-8" if has_due_date) } + .issuable-form-select-holder + = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" + .form-group + - has_labels = @labels && @labels.any? + = form.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" + = form.hidden_field :label_ids, multiple: true, value: '' + .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } + .issuable-form-select-holder + = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false}, dropdown_title: "Select label" + - if has_due_date + .col-lg-6 + .form-group + = form.label :due_date, "Due date", class: "control-label" + .col-sm-10 + .issuable-form-select-holder + = form.text_field :due_date, id: "issuable-due-date", class: "datepicker form-control", placeholder: "Select due date" diff --git a/app/views/shared/members/_access_request_buttons.html.haml b/app/views/shared/members/_access_request_buttons.html.haml index eff914398bb..e166dfab710 100644 --- a/app/views/shared/members/_access_request_buttons.html.haml +++ b/app/views/shared/members/_access_request_buttons.html.haml @@ -1,10 +1,16 @@ -- if can?(current_user, :request_access, source) - - if requester = source.requesters.find_by(user_id: current_user.id) - = link_to 'Withdraw Access Request', polymorphic_path([:leave, source, :members]), - method: :delete, - data: { confirm: remove_member_message(requester) }, - class: 'btn' - - else - = link_to 'Request Access', polymorphic_path([:request_access, source, :members]), - method: :post, - class: 'btn' +- model_name = source.model_name.to_s.downcase + +- if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id)) + = link_to "Leave #{model_name}", polymorphic_path([:leave, source, :members]), + method: :delete, + data: { confirm: leave_confirmation_message(source) }, + class: 'btn' +- elsif requester = source.requesters.find_by(user_id: current_user.id) + = link_to 'Withdraw Access Request', polymorphic_path([:leave, source, :members]), + method: :delete, + data: { confirm: remove_member_message(requester) }, + class: 'btn' +- elsif source.request_access_enabled && can?(current_user, :request_access, source) + = link_to 'Request Access', polymorphic_path([:request_access, source, :members]), + method: :post, + class: 'btn' diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 1c0346bbc78..8928de9097b 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -1,7 +1,8 @@ - group_link = local_assigns[:group_link] - group = group_link.group - can_admin_member = can?(current_user, :admin_project_member, @project) -%li.member.group_member{ id: "group_member_#{group_link.id}" } +- dom_id = "group_member_#{group_link.id}" +%li.member.group_member{ id: dom_id } %span{ class: "list-item-name" } = image_tag group_icon(group), class: "avatar s40", alt: '' %strong @@ -14,7 +15,23 @@ Expires in #{distance_of_time_in_words_to_now(group_link.expires_at)} .controls.member-controls = form_tag namespace_project_group_link_path(@project.namespace, @project, group_link), method: :put, remote: true, class: 'form-horizontal js-edit-member-form' do - = select_tag 'group_link[group_access]', options_for_select(ProjectGroupLink.access_options, group_link.group_access), class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{group.id}", disabled: !can_admin_member + = hidden_field_tag "group_link[group_access]", group_link.group_access + .member-form-control.dropdown.append-right-5 + %button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button", + disabled: !can_admin_member, + data: { toggle: "dropdown", field_name: "group_link[group_access]" } } + %span.dropdown-toggle-text + = group_link.human_access + = icon("chevron-down") + .dropdown-menu.dropdown-select.dropdown-menu-align-right.dropdown-menu-selectable + = dropdown_title("Change permissions") + .dropdown-content + %ul + - Gitlab::Access.options.each do |role, role_id| + %li + = link_to role, "javascript:void(0)", + class: ("is-active" if group_link.group_access == role_id), + data: { id: role_id, el_id: dom_id } .prepend-left-5.clearable-input.member-form-control = text_field_tag 'group_link[expires_at]', group_link.expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{group.id}", disabled: !can_admin_member %i.clear-icon.js-clear-input diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 432047a1c4e..659d4c905fc 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -20,8 +20,8 @@ %strong Blocked - if source.instance_of?(Group) && !@group - = link_to source, class: "member-group-link prepend-left-5" do - = "ยท #{source.name}" + · + = link_to source.name, source, class: "member-group-link" .hidden-xs.cgray - if member.request? @@ -45,12 +45,28 @@ = time_ago_with_tooltip(member.created_at) - if show_roles .controls.member-controls - - if show_controls + - if show_controls && (member.respond_to?(:group) && @group) || (member.respond_to?(:project) && @project) - if user != current_user = form_for member, remote: true, html: { class: 'form-horizontal js-edit-member-form' } do |f| - = f.select :access_level, options_for_select(member.class.access_level_roles, member.access_level), {}, class: 'form-control member-form-control append-right-5 js-member-update-control', id: "member_access_level_#{member.id}", disabled: !can_admin_member + = f.hidden_field :access_level + .member-form-control.dropdown.append-right-5 + %button.dropdown-menu-toggle.js-member-permissions-dropdown{ type: "button", + disabled: !can_admin_member, + data: { toggle: "dropdown", field_name: "#{f.object_name}[access_level]" } } + %span.dropdown-toggle-text + = member.human_access + = icon("chevron-down") + .dropdown-menu.dropdown-select.dropdown-menu-align-right.dropdown-menu-selectable + = dropdown_title("Change permissions") + .dropdown-content + %ul + - Gitlab::Access.options.each do |role, role_id| + %li + = link_to role, "javascript:void(0)", + class: ("is-active" if member.access_level == role_id), + data: { id: role_id, el_id: dom_id(member) } .prepend-left-5.clearable-input.member-form-control - = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can_admin_member + = f.text_field :expires_at, class: 'form-control js-access-expiration-date js-member-update-control', placeholder: 'Expiration date', id: "member_expires_at_#{member.id}", disabled: !can_admin_member, data: { el_id: dom_id(member) } %i.clear-icon.js-clear-input - else %span.member-access-text= member.human_access diff --git a/app/views/shared/milestones/_summary.html.haml b/app/views/shared/milestones/_summary.html.haml index 0a237136959..d27fba805a3 100644 --- a/app/views/shared/milestones/_summary.html.haml +++ b/app/views/shared/milestones/_summary.html.haml @@ -25,8 +25,10 @@ %span.milestone-stat %strong== #{milestone.percent_complete(current_user)}% complete - %span.milestone-stat - %span.remaining-days= milestone_remaining_days(milestone) + - remaining_days = milestone_remaining_days(milestone) + - if remaining_days.present? + %span.milestone-stat + %span.remaining-days= remaining_days .milestone-progress-buttons %span.tab-issues-buttons diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml index 1f7df0bcd19..fbad0d05de3 100644 --- a/app/views/shared/notifications/_button.html.haml +++ b/app/views/shared/notifications/_button.html.haml @@ -1,4 +1,3 @@ -- left_align = local_assigns[:left_align] - if notification_setting .dropdown.notification-dropdown = form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f| @@ -19,7 +18,7 @@ = notification_title(notification_setting.level) = icon("caret-down") - = render "shared/notifications/notification_dropdown", notification_setting: notification_setting, left_align: left_align + = render "shared/notifications/notification_dropdown", notification_setting: notification_setting = content_for :scripts_body do = render "shared/notifications/custom_notifications", notification_setting: notification_setting diff --git a/app/views/shared/notifications/_notification_dropdown.html.haml b/app/views/shared/notifications/_notification_dropdown.html.haml index d3258ee64cb..85ad74f9a39 100644 --- a/app/views/shared/notifications/_notification_dropdown.html.haml +++ b/app/views/shared/notifications/_notification_dropdown.html.haml @@ -1,5 +1,4 @@ -- left_align = local_assigns[:left_align] -%ul.dropdown-menu.dropdown-menu-no-wrap.dropdown-menu-selectable.dropdown-menu-large{ role: "menu", class: [notifications_menu_identifier("dropdown", notification_setting), ("dropdown-menu-align-right" unless left_align)] } +%ul.dropdown-menu.dropdown-menu-no-wrap.dropdown-menu-selectable.dropdown-menu-large{ role: "menu", class: [notifications_menu_identifier("dropdown", notification_setting)] } - NotificationSetting.levels.each_key do |level| - next if level == "custom" - next if level == "global" && notification_setting.source.nil? diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml index d7506e07ff6..d084f5e9684 100644 --- a/app/views/shared/snippets/_header.html.haml +++ b/app/views/shared/snippets/_header.html.haml @@ -8,10 +8,6 @@ %span.creator authored = time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago') - - if @snippet.updated_at != @snippet.created_at - %span - = icon('edit', title: 'edited') - = time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago') by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title", avatar_class: "hidden-xs")} .snippet-actions @@ -20,5 +16,9 @@ - else = render "snippets/actions" -%h2.snippet-title.prepend-top-0.append-bottom-0 - = markdown_field(@snippet, :title) +.snippet-header + %h2.snippet-title.prepend-top-0.append-bottom-0 + = markdown_field(@snippet, :title) + + - if @snippet.updated_at != @snippet.created_at + = edited_time_ago_with_tooltip(@snippet, placement: 'bottom', html_class: 'snippet-edited-ago') diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml index ea17bec8677..5d2d2317f22 100644 --- a/app/views/shared/snippets/_snippet.html.haml +++ b/app/views/shared/snippets/_snippet.html.haml @@ -1,17 +1,16 @@ +- link_project = local_assigns.fetch(:link_project, false) + %li.snippet-row = image_tag avatar_icon(snippet.author_email), class: "avatar s40 hidden-xs", alt: '' .title = link_to reliable_snippet_path(snippet) do = snippet.title - - if snippet.private? - %span.label.label-gray.hidden-xs - = icon('lock') - private - %span.monospace.pull-right.hidden-xs - = snippet.file_name + - if snippet.file_name + %span.snippet-filename.monospace.hidden-xs + = snippet.file_name - %ul.controls.visible-xs + %ul.controls %li - note_count = snippet.notes.user.count = link_to reliable_snippet_path(snippet, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do @@ -22,11 +21,17 @@ = visibility_level_label(snippet.visibility_level) = visibility_level_icon(snippet.visibility_level, fw: false) - %small.pull-right.cgray.hidden-xs - - if snippet.project_id? - = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project) - - .snippet-info.hidden-xs + .snippet-info + #{snippet.to_reference} · + authored #{time_ago_with_tooltip(snippet.created_at, placement: 'bottom', html_class: 'snippet-created-ago')} + by = link_to user_snippets_path(snippet.author) do = snippet.author_name - authored #{time_ago_with_tooltip(snippet.created_at)} + - if link_project && snippet.project_id? + %span.hidden-xs + in + = link_to namespace_project_path(snippet.project.namespace, snippet.project) do + = snippet.project.name_with_namespace + + .pull-right.snippet-updated-at + %span updated #{time_ago_with_tooltip(snippet.updated_at, placement: 'bottom')} diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml index 1d0e549ed3d..95fc7198104 100644 --- a/app/views/snippets/_actions.html.haml +++ b/app/views/snippets/_actions.html.haml @@ -1,13 +1,13 @@ .hidden-xs - - if current_user - = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New snippet" do - New snippet - - if can?(current_user, :admin_personal_snippet, @snippet) - = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do - Delete - if can?(current_user, :update_personal_snippet, @snippet) - = link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do + = link_to edit_snippet_path(@snippet), class: "btn btn-grouped" do Edit + - if can?(current_user, :admin_personal_snippet, @snippet) + = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do + Delete + - if current_user + = link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do + New snippet - if current_user .visible-xs-block.dropdown %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml index 77b66ca74b6..ac3701233ad 100644 --- a/app/views/snippets/_snippets.html.haml +++ b/app/views/snippets/_snippets.html.haml @@ -1,8 +1,9 @@ - remote = local_assigns.fetch(:remote, false) +- link_project = local_assigns.fetch(:link_project, false) .snippets-list-holder %ul.content-list - = render partial: 'shared/snippets/snippet', collection: @snippets + = render partial: 'shared/snippets/snippet', collection: @snippets, locals: { link_project: link_project } - if @snippets.empty? %li .nothing-here-block Nothing here. diff --git a/app/views/snippets/_snippets_scope_menu.html.haml b/app/views/snippets/_snippets_scope_menu.html.haml new file mode 100644 index 00000000000..2dda5fed647 --- /dev/null +++ b/app/views/snippets/_snippets_scope_menu.html.haml @@ -0,0 +1,31 @@ +- subject = local_assigns.fetch(:subject, current_user) +- include_private = local_assigns.fetch(:include_private, false) + +.nav-links.snippet-scope-menu + %li{ class: ("active" unless params[:scope]) } + = link_to subject_snippets_path(subject) do + All + %span.badge + - if include_private + = subject.snippets.count + - else + = subject.snippets.public_and_internal.count + + - if include_private + %li{ class: ("active" if params[:scope] == "are_private") } + = link_to subject_snippets_path(subject, scope: 'are_private') do + Private + %span.badge + = subject.snippets.are_private.count + + %li{ class: ("active" if params[:scope] == "are_internal") } + = link_to subject_snippets_path(subject, scope: 'are_internal') do + Internal + %span.badge + = subject.snippets.are_internal.count + + %li{ class: ("active" if params[:scope] == "are_public") } + = link_to subject_snippets_path(subject, scope: 'are_public') do + Public + %span.badge + = subject.snippets.are_public.count diff --git a/app/workers/authorized_projects_worker.rb b/app/workers/authorized_projects_worker.rb index 331727ba9d8..fccddb70d18 100644 --- a/app/workers/authorized_projects_worker.rb +++ b/app/workers/authorized_projects_worker.rb @@ -2,14 +2,33 @@ class AuthorizedProjectsWorker include Sidekiq::Worker include DedicatedSidekiqQueue + LEASE_TIMEOUT = 1.minute.to_i + def self.bulk_perform_async(args_list) Sidekiq::Client.push_bulk('class' => self, 'args' => args_list) end def perform(user_id) user = User.find_by(id: user_id) - return unless user - user.refresh_authorized_projects + refresh(user) if user + end + + def refresh(user) + lease_key = "refresh_authorized_projects:#{user.id}" + lease = Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT) + + until uuid = lease.try_obtain + # Keep trying until we obtain the lease. If we don't do so we may end up + # not updating the list of authorized projects properly. To prevent + # hammering Redis too much we'll wait for a bit between retries. + sleep(1) + end + + begin + user.refresh_authorized_projects + ensure + Gitlab::ExclusiveLease.cancel(lease_key, uuid) + end end end diff --git a/app/workers/pipeline_success_worker.rb b/app/workers/pipeline_success_worker.rb index 2aa6fff24da..cc0eb708cf9 100644 --- a/app/workers/pipeline_success_worker.rb +++ b/app/workers/pipeline_success_worker.rb @@ -4,7 +4,7 @@ class PipelineSuccessWorker def perform(pipeline_id) Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline| - MergeRequests::MergeWhenBuildSucceedsService + MergeRequests::MergeWhenPipelineSucceedsService .new(pipeline.project, nil) .trigger(pipeline) end diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb index 071741fbacd..e9a5bd7f24e 100644 --- a/app/workers/process_commit_worker.rb +++ b/app/workers/process_commit_worker.rb @@ -10,9 +10,10 @@ class ProcessCommitWorker # project_id - The ID of the project this commit belongs to. # user_id - The ID of the user that pushed the commit. - # commit_sha - The SHA1 of the commit to process. + # commit_hash - Hash containing commit details to use for constructing a + # Commit object without having to use the Git repository. # default - The data was pushed to the default branch. - def perform(project_id, user_id, commit_sha, default = false) + def perform(project_id, user_id, commit_hash, default = false) project = Project.find_by(id: project_id) return unless project @@ -21,10 +22,7 @@ class ProcessCommitWorker return unless user - commit = find_commit(project, commit_sha) - - return unless commit - + commit = build_commit(project, commit_hash) author = commit.author || user process_commit_message(project, commit, user, author, default) @@ -59,9 +57,18 @@ class ProcessCommitWorker update_all(first_mentioned_in_commit_at: commit.committed_date) end - private + def build_commit(project, hash) + date_suffix = '_date' + + # When processing Sidekiq payloads various timestamps are stored as Strings. + # Commit in turn expects Time-like instances upon input, so we have to + # manually parse these values. + hash.each do |key, value| + if key.to_s.end_with?(date_suffix) && value.is_a?(String) + hash[key] = Time.parse(value) + end + end - def find_commit(project, sha) - project.commit(sha) + Commit.from_hash(hash, project) end end |