merge master into issue_3359_3

This commit is contained in:
Felipe Artur 2016-06-22 10:47:48 -03:00
commit 2674b54860
791 changed files with 30425 additions and 5190 deletions

View File

@ -92,9 +92,7 @@ update-knapsack:
- export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export KNAPSACK_GENERATE_REPORT=true
- cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH}
- knapsack spinach "-r rerun"
# retry failed tests 3 times
- retry '[ ! -e tmp/spinach-rerun.txt ] || bin/spinach -r rerun $(cat tmp/spinach-rerun.txt)'
- knapsack spinach "-r rerun" || retry '[ ! -e tmp/spinach-rerun.txt ] || bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)'
artifacts:
paths:
- knapsack/
@ -131,56 +129,51 @@ spinach 7 10: *spinach-knapsack
spinach 8 10: *spinach-knapsack
spinach 9 10: *spinach-knapsack
# Execute all testing suites against Ruby 2.2
.ruby-22: &ruby-22
image: "ruby:2.2"
# Execute all testing suites against Ruby 2.3
.ruby-23: &ruby-23
image: "ruby:2.3"
only:
- master
cache:
key: "ruby22"
paths:
- vendor
.rspec-knapsack-ruby22: &rspec-knapsack-ruby22
.rspec-knapsack-ruby23: &rspec-knapsack-ruby23
<<: *rspec-knapsack
<<: *ruby-22
<<: *ruby-23
.spinach-knapsack-ruby22: &spinach-knapsack-ruby22
.spinach-knapsack-ruby23: &spinach-knapsack-ruby23
<<: *spinach-knapsack
<<: *ruby-22
<<: *ruby-23
rspec 0 20 ruby22: *rspec-knapsack-ruby22
rspec 1 20 ruby22: *rspec-knapsack-ruby22
rspec 2 20 ruby22: *rspec-knapsack-ruby22
rspec 3 20 ruby22: *rspec-knapsack-ruby22
rspec 4 20 ruby22: *rspec-knapsack-ruby22
rspec 5 20 ruby22: *rspec-knapsack-ruby22
rspec 6 20 ruby22: *rspec-knapsack-ruby22
rspec 7 20 ruby22: *rspec-knapsack-ruby22
rspec 8 20 ruby22: *rspec-knapsack-ruby22
rspec 9 20 ruby22: *rspec-knapsack-ruby22
rspec 10 20 ruby22: *rspec-knapsack-ruby22
rspec 11 20 ruby22: *rspec-knapsack-ruby22
rspec 12 20 ruby22: *rspec-knapsack-ruby22
rspec 13 20 ruby22: *rspec-knapsack-ruby22
rspec 14 20 ruby22: *rspec-knapsack-ruby22
rspec 15 20 ruby22: *rspec-knapsack-ruby22
rspec 16 20 ruby22: *rspec-knapsack-ruby22
rspec 17 20 ruby22: *rspec-knapsack-ruby22
rspec 18 20 ruby22: *rspec-knapsack-ruby22
rspec 19 20 ruby22: *rspec-knapsack-ruby22
rspec 0 20 ruby23: *rspec-knapsack-ruby23
rspec 1 20 ruby23: *rspec-knapsack-ruby23
rspec 2 20 ruby23: *rspec-knapsack-ruby23
rspec 3 20 ruby23: *rspec-knapsack-ruby23
rspec 4 20 ruby23: *rspec-knapsack-ruby23
rspec 5 20 ruby23: *rspec-knapsack-ruby23
rspec 6 20 ruby23: *rspec-knapsack-ruby23
rspec 7 20 ruby23: *rspec-knapsack-ruby23
rspec 8 20 ruby23: *rspec-knapsack-ruby23
rspec 9 20 ruby23: *rspec-knapsack-ruby23
rspec 10 20 ruby23: *rspec-knapsack-ruby23
rspec 11 20 ruby23: *rspec-knapsack-ruby23
rspec 12 20 ruby23: *rspec-knapsack-ruby23
rspec 13 20 ruby23: *rspec-knapsack-ruby23
rspec 14 20 ruby23: *rspec-knapsack-ruby23
rspec 15 20 ruby23: *rspec-knapsack-ruby23
rspec 16 20 ruby23: *rspec-knapsack-ruby23
rspec 17 20 ruby23: *rspec-knapsack-ruby23
rspec 18 20 ruby23: *rspec-knapsack-ruby23
rspec 19 20 ruby23: *rspec-knapsack-ruby23
spinach 0 10 ruby22: *spinach-knapsack-ruby22
spinach 1 10 ruby22: *spinach-knapsack-ruby22
spinach 2 10 ruby22: *spinach-knapsack-ruby22
spinach 3 10 ruby22: *spinach-knapsack-ruby22
spinach 4 10 ruby22: *spinach-knapsack-ruby22
spinach 5 10 ruby22: *spinach-knapsack-ruby22
spinach 6 10 ruby22: *spinach-knapsack-ruby22
spinach 7 10 ruby22: *spinach-knapsack-ruby22
spinach 8 10 ruby22: *spinach-knapsack-ruby22
spinach 9 10 ruby22: *spinach-knapsack-ruby22
spinach 0 10 ruby23: *spinach-knapsack-ruby23
spinach 1 10 ruby23: *spinach-knapsack-ruby23
spinach 2 10 ruby23: *spinach-knapsack-ruby23
spinach 3 10 ruby23: *spinach-knapsack-ruby23
spinach 4 10 ruby23: *spinach-knapsack-ruby23
spinach 5 10 ruby23: *spinach-knapsack-ruby23
spinach 6 10 ruby23: *spinach-knapsack-ruby23
spinach 7 10 ruby23: *spinach-knapsack-ruby23
spinach 8 10 ruby23: *spinach-knapsack-ruby23
spinach 9 10 ruby23: *spinach-knapsack-ruby23
# Other generic tests

128
CHANGELOG
View File

@ -3,16 +3,30 @@ v 8.10(unreleased)
- Add notifications dropdown for groups
v 8.9.0 (unreleased)
- Fix builds API response not including commit data
- Fix error when CI job variables key specified but not defined
- Fix pipeline status when there are no builds in pipeline
- Fix Error 500 when using closes_issues API with an external issue tracker
- Add more information into RSS feed for issues (Alexander Matyushentsev)
- Bulk assign/unassign labels to issues.
- Ability to prioritize labels !4009 / !3205 (Thijs Wouters)
- Show Star and Fork buttons on mobile.
- Performance improvements on RelativeLinkFilter
- Fix endless redirections when accessing user OAuth applications when they are disabled
- Allow enabling wiki page events from Webhook management UI
- Bump rouge to 1.11.0
- Fix MR-auto-close text added to description
- Fix issue with arrow keys not working in search autocomplete dropdown
- Fix an issue where note polling stopped working if a window was in the
background during a refresh.
- Pre-processing Markdown now only happens when needed
- Make EmailsOnPushWorker use Sidekiq mailers queue
- Redesign all Devise emails. !4297
- Don't show 'Leave Project' to group members
- Fix wiki page events' webhook to point to the wiki repository
- Add a border around images to differentiate them from the background.
- Don't show tags for revert and cherry-pick operations
- Show image ID on registry page
- Fix issue todo not remove when leave project !4150 (Long Nguyen)
- Allow customisable text on the 'nearly there' page after a user signs up
- Bump recaptcha gem to 3.0.0 to remove deprecated stoken support
@ -21,43 +35,74 @@ v 8.9.0 (unreleased)
- Added descriptions to notification settings dropdown
- Improve note validation to prevent errors when creating invalid note via API
- Reduce number of fog gem dependencies
- Add number of merge requests for a given milestone to the milestones view.
- Implement a fair usage of shared runners
- Remove project notification settings associated with deleted projects
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects
- Wrap code blocks on Activies and Todos page !4783 (winniehell)
- Add a metric for the number of new Redis connections created by a transaction
- Fix Error 500 when viewing a blob with binary characters after the 1024-byte mark
- Redesign navigation for project pages
- Fix images in sign-up confirmation email
- Added shortcut 'y' for copying a files content hash URL #14470
- Fix groups API to list only user's accessible projects
- Fix horizontal scrollbar for long commit message.
- GitLab Performance Monitoring now tracks the total method execution time and call count per method
- Add Environments and Deployments
- Redesign account and email confirmation emails
- Don't fail builds for projects that are deleted
- Support Docker Registry manifest v1
- `git clone https://host/namespace/project` now works, in addition to using the `.git` suffix
- Bump nokogiri to 1.6.8
- Use gitlab-shell v3.0.0
- Fixed alignment of download dropdown in merge requests
- Upgrade to jQuery 2
- Adds selected branch name to the dropdown toggle
- Add API endpoint for Sidekiq Metrics !4653
- Refactoring Award Emoji with API support for Issues and MergeRequests
- Use Knapsack to evenly distribute tests across multiple nodes
- Add notifications dropdown for groups
- Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged
- Don't allow MRs to be merged when commits were added since the last review / page load
- Add DB index on users.state
- Limit email on push diff size to 30 files / 150 KB
- Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database
- Changed the Slack build message to use the singular duration if necessary (Aran Koning)
- Fix race condition on merge when build succeeds
- Links from a wiki page to other wiki pages should be rewritten as expected
- Add option to project to only allow merge requests to be merged if the build succeeds (Rui Santos)
- Added navigation shortcuts to the project pipelines, milestones, builds and forks page. !4393
- Fix issues filter when ordering by milestone
- Disable SAML account unlink feature
- Added artifacts:when to .gitlab-ci.yml - this requires GitLab Runner 1.3
- Bamboo Service: Fix missing credentials & URL handling when base URL contains a path (Benjamin Schmid)
- TeamCity Service: Fix URL handling when base URL contains a path
- Todos will display target state if issuable target is 'Closed' or 'Merged'
- Validate only and except regexp
- Fix bug when sorting issues by milestone due date and filtering by two or more labels
- POST to API /projects/:id/runners/:runner_id would give 409 if the runner was already enabled for this project
- Add support for using Yubikeys (U2F) for two-factor authentication
- Link to blank group icon doesn't throw a 404 anymore
- Remove 'main language' feature
- Toggle whitespace button now available for compare branches diffs #17881
- Pipelines can be canceled only when there are running builds
- Allow authentication using personal access tokens
- Use downcased path to container repository as this is expected path by Docker
- Customized notification settings for projects
- Allow to use CI token to fetch LFS objects
- Custom notification settings
- Projects pending deletion will render a 404 page
- Measure queue duration between gitlab-workhorse and Rails
- Added Gfm autocomplete for labels
- Added edit note 'up' shortcut documentation to the help panel and docs screenshot #18114
- Make Omniauth providers specs to not modify global configuration
- Remove unused JiraIssue class and replace references with ExternalIssue. !4659 (Ilan Shamir)
- Make authentication service for Container Registry to be compatible with < Docker 1.11
- Make it possible to lock a runner from being enabled for other projects
- Add Application Setting to configure Container Registry token expire delay (default 5min)
- Cache assigned issue and merge request counts in sidebar nav
- Use Knapsack only in CI environment
- Cache project build count in sidebar nav
- Add milestone expire date to the right sidebar
- Manually mark a issue or merge request as a todo
- Fix markdown_spec to use before instead of before(:all) to properly cleanup database after testing
- Reduce number of queries needed to render issue labels in the sidebar
- Improve error handling importing projects
@ -67,27 +112,66 @@ v 8.9.0 (unreleased)
- Replace Colorize with Rainbow for coloring console output in Rake tasks.
- Add workhorse controller and API helpers
- An indicator is now displayed at the top of the comment field for confidential issues.
- Show categorised search queries in the search autocomplete
- RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented
- Dropdown for `.gitlab-ci.yml` templates
- Improve issuables APIs performance when accessing notes !4471
- Add sorting dropdown to tags page !4423
- External links now open in a new tab
- Prevent default actions of disabled buttons and links
- Markdown editor now correctly resets the input value on edit cancellation !4175
- Toggling a task list item in a issue/mr description does not creates a Todo for mentions
- Improved UX of date pickers on issue & milestone forms
- Cache on the database if a project has an active external issue tracker.
- Put project Labels and Milestones pages links under Issues and Merge Requests tabs as subnav
- GitLab project import and export functionality
- All classes in the Banzai::ReferenceParser namespace are now instrumented
- Remove deprecated issues_tracker and issues_tracker_id from project model
- Allow users to create confidential issues in private projects
- Measure CPU time for instrumented methods
- Instrument private methods and private instance methods by default instead just public methods
- Only show notes through JSON on confidential issues that the user has access to
- Updated the allocations Gem to version 1.0.5
- The background sampler now ignores classes without names
- Update design for `Close` buttons
- New custom icons for navigation
- Horizontally scrolling navigation on project, group, and profile settings pages
- Hide global side navigation by default
- Fix project Star/Unstar project button tooltip
- Remove tanuki logo from side navigation; center on top nav
- Include user relationships when retrieving award_emoji
- Various associations are now eager loaded when parsing issue references to reduce the number of queries executed
- Set inverse_of for Project/Service association to reduce the number of queries
- Update tanuki logo highlight/loading colors
- Remove explicit Gitlab::Metrics.action assignments, are already automatic.
- Use Git cached counters for branches and tags on project page
- Cache participable participants in an instance variable.
- Filter parameters for request_uri value on instrumented transactions.
- Remove duplicated keys add UNIQUE index to keys fingerprint column
- ExtractsPath get ref_names from repository cache, if not there access git.
- Cache user todo counts from TodoService
- Ensure Todos counters doesn't count Todos for projects pending delete
- Add left/right arrows horizontal navigation
- Add tooltip to pin/unpin navbar
- Add new sub nav style to Wiki and Graphs sub navigation
v 8.8.5 (unreleased)
- Ensure branch cleanup regardless of whether the GitHub import process succeeds
- Fix todos page throwing errors when you have a project pending deletion
- Reduce number of SQL queries when rendering user references
- Import GitHub repositories respecting the API rate limit
- Fix importer for GitHub comments on diff
- Disable Webhooks before proceeding with the GitHub import
- Fix incremental trace upload API when using multi-byte UTF-8 chars in trace
v 8.8.5
- Import GitHub repositories respecting the API rate limit !4166
- Fix todos page throwing errors when you have a project pending deletion !4300
- Disable Webhooks before proceeding with the GitHub import !4470
- Fix importer for GitHub comments on diff !4488
- Adjust the SAML control flow to allow LDAP identities to be added to an existing SAML user !4498
- Fix incremental trace upload API when using multi-byte UTF-8 chars in trace !4541
- Prevent unauthorized access for projects build traces
- Forbid scripting for wiki files
- Only show notes through JSON on confidential issues that the user has access to
- Banzai::Filter::UploadLinkFilter use XPath instead CSS expressions
- Banzai::Filter::ExternalLinkFilter use XPath instead CSS expressions
v 8.8.4
- Fix LDAP-based login for users with 2FA enabled. !4493
- Added descriptions to notification settings dropdown
- Due date can be removed from milestones
v 8.8.3
- Fix 404 page when viewing TODOs that contain milestones or labels in different projects. !4312
@ -203,6 +287,9 @@ v 8.8.0
v 8.7.7
- Fix import by `Any Git URL` broken if the URL contains a space
- Prevent unauthorized access to other projects build traces
- Forbid scripting for wiki files
- Only show notes through JSON on confidential issues that the user has access to
v 8.7.6
- Fix links on wiki pages for relative url setups. !4131 (Artem Sidorenko)
@ -365,6 +452,11 @@ v 8.7.0
- Add RAW build trace output and button on build page
- Add incremental build trace update into CI API
v 8.6.9
- Prevent unauthorized access to other projects build traces
- Forbid scripting for wiki files
- Only show notes through JSON on confidential issues that the user has access to
v 8.6.8
- Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API
@ -519,6 +611,10 @@ v 8.6.0
- Trigger a todo for mentions on commits page
- Let project owners and admins soft delete issues and merge requests
v 8.5.13
- Prevent unauthorized access to other projects build traces
- Forbid scripting for wiki files
v 8.5.12
- Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API
@ -680,6 +776,10 @@ v 8.5.0
- Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul)
- Add Todos
v 8.4.11
- Prevent unauthorized access to other projects build traces
- Forbid scripting for wiki files
v 8.4.10
- Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API
@ -816,6 +916,10 @@ v 8.4.0
- Add IP check against DNSBLs at account sign-up
- Added cache:key to .gitlab-ci.yml allowing to fine tune the caching
v 8.3.10
- Prevent unauthorized access to other projects build traces
- Forbid scripting for wiki files
v 8.3.9
- Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API
@ -934,6 +1038,10 @@ v 8.3.0
- Expose Git's version in the admin area
- Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye)
v 8.2.6
- Prevent unauthorized access to other projects build traces
- Forbid scripting for wiki files
v 8.2.5
- Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API

12
Gemfile
View File

@ -48,11 +48,11 @@ gem 'attr_encrypted', '~> 3.0.0'
gem 'u2f', '~> 0.2.1'
# Browser detection
gem "browser", '~> 2.0.3'
gem "browser", '~> 2.2'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
gem "gitlab_git", '~> 10.0'
gem "gitlab_git", '~> 10.2'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
@ -210,6 +210,9 @@ gem 'mousetrap-rails', '~> 1.4.6'
# Detect and convert string character encoding
gem 'charlock_holmes', '~> 0.7.3'
# Parse duration
gem 'chronic_duration', '~> 0.10.6'
gem "sass-rails", '~> 5.0.0'
gem "coffee-rails", '~> 4.1.0'
gem "uglifier", '~> 2.7.2'
@ -218,13 +221,12 @@ gem 'jquery-turbolinks', '~> 2.1.0'
gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.3.0'
gem 'font-awesome-rails', '~> 4.2'
gem 'font-awesome-rails', '~> 4.6.1'
gem 'gitlab_emoji', '~> 0.3.0'
gem 'gon', '~> 6.0.1'
gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.1.0'
gem 'jquery-ui-rails', '~> 5.0.0'
gem 'raphael-rails', '~> 2.1.2'
gem 'request_store', '~> 1.3.0'
gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
@ -328,7 +330,7 @@ gem "newrelic_rpm", '~> 3.14'
gem 'octokit', '~> 4.3.0'
gem "mail_room", "~> 0.7"
gem "mail_room", "~> 0.8"
gem 'email_reply_parser', '~> 0.5.8'

View File

@ -50,7 +50,7 @@ GEM
after_commit_queue (1.3.0)
activerecord (>= 3.0)
akismet (2.0.0)
allocations (1.0.4)
allocations (1.0.5)
arel (6.0.3)
asana (0.4.0)
faraday (~> 0.9)
@ -98,7 +98,7 @@ GEM
autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4)
brakeman (3.3.2)
browser (2.0.3)
browser (2.2.0)
builder (3.2.2)
bullet (5.0.0)
activesupport (>= 3.0.0)
@ -124,6 +124,8 @@ GEM
mime-types (>= 1.16)
cause (0.1)
charlock_holmes (0.7.3)
chronic_duration (0.10.6)
numerizer (~> 0.1.1)
chunky_png (1.3.5)
cliver (0.3.2)
coderay (1.1.0)
@ -244,7 +246,7 @@ GEM
fog-xml (0.1.2)
fog-core
nokogiri (~> 1.5, >= 1.5.11)
font-awesome-rails (4.5.0.1)
font-awesome-rails (4.6.1.0)
railties (>= 3.2, < 5.1)
foreman (0.78.0)
thor (~> 0.19.1)
@ -275,7 +277,7 @@ GEM
posix-spawn (~> 0.3)
gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1)
gitlab_git (10.1.0)
gitlab_git (10.2.0)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
@ -396,9 +398,9 @@ GEM
systemu (~> 2.6.2)
mail (2.6.4)
mime-types (>= 1.16, < 4)
mail_room (0.7.0)
mail_room (0.8.0)
method_source (0.8.2)
mime-types (2.99.1)
mime-types (2.99.2)
mimemagic (0.3.0)
mini_portile2 (2.1.0)
minitest (5.7.0)
@ -414,6 +416,7 @@ GEM
nokogiri (1.6.8)
mini_portile2 (~> 2.1.0)
pkg-config (~> 1.1.7)
numerizer (0.1.1)
oauth (0.4.7)
oauth2 (1.0.0)
faraday (>= 0.8, < 0.10)
@ -553,7 +556,6 @@ GEM
rainbow (2.1.0)
raindrops (0.15.0)
rake (10.5.0)
raphael-rails (2.1.2)
rb-fsevent (0.9.6)
rb-inotify (0.9.5)
ffi (>= 0.5.0)
@ -831,7 +833,7 @@ DEPENDENCIES
binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0)
brakeman (~> 3.3.0)
browser (~> 2.0.3)
browser (~> 2.2)
bullet
bundler-audit
byebug
@ -839,6 +841,7 @@ DEPENDENCIES
capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.10.0)
charlock_holmes (~> 0.7.3)
chronic_duration (~> 0.10.6)
coffee-rails (~> 4.1.0)
connection_pool (~> 2.0)
coveralls (~> 0.8.2)
@ -863,7 +866,7 @@ DEPENDENCIES
fog-google (~> 0.3)
fog-local (~> 0.3)
fog-openstack (~> 0.1)
font-awesome-rails (~> 4.2)
font-awesome-rails (~> 4.6.1)
foreman
fuubar (~> 2.0.0)
gemnasium-gitlab-service (~> 0.2)
@ -871,7 +874,7 @@ DEPENDENCIES
github-markup (~> 1.3.1)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab_emoji (~> 0.3.0)
gitlab_git (~> 10.0)
gitlab_git (~> 10.2)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.1.0)
@ -896,7 +899,7 @@ DEPENDENCIES
license_finder
licensee (~> 8.0.0)
loofah (~> 2.0.3)
mail_room (~> 0.7)
mail_room (~> 0.8)
method_source (~> 0.8)
minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6)
@ -934,7 +937,6 @@ DEPENDENCIES
rails (= 4.2.6)
rails-deprecated_sanitizer (~> 1.0.3)
rainbow (~> 2.1.0)
raphael-rails (~> 2.1.2)
rblineprof
rdoc (~> 3.6)
recaptcha (~> 3.0)

View File

@ -27,6 +27,11 @@ class @LabelManager
$btn = $(e.currentTarget)
$label = $("##{$btn.data('domId')}")
action = if $btn.parents('.js-prioritized-labels').length then 'remove' else 'add'
# Make sure tooltip will hide
$tooltip = $ "##{$btn.find('.has-tooltip:visible').attr('aria-describedby')}"
$tooltip.tooltip 'destroy'
_this.toggleLabelPriority($label, action)
toggleLabelPriority: ($label, action, persistState = true) ->
@ -42,10 +47,10 @@ class @LabelManager
$from = @prioritizedLabels
if $from.find('li').length is 1
$from.find('.empty-message').show()
$from.find('.empty-message').removeClass('hidden')
if not $target.find('li').length
$target.find('.empty-message').hide()
$target.find('.empty-message').addClass('hidden')
$label.detach().appendTo($target)
@ -54,6 +59,9 @@ class @LabelManager
if action is 'remove'
xhr = $.ajax url: url, type: 'DELETE'
# Restore empty message
$from.find('.empty-message').removeClass('hidden') unless $from.find('li').length
else
xhr = @savePrioritySort($label, action)

View File

@ -7,6 +7,7 @@
labelsPath: "/api/:version/projects/:id/labels"
licensePath: "/api/:version/licenses/:key"
gitignorePath: "/api/:version/gitignores/:key"
gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key"
group: (group_id, callback) ->
url = Api.buildUrl(Api.groupPath)
@ -110,6 +111,12 @@
$.get url, (gitignore) ->
callback(gitignore)
gitlabCiYml: (key, callback) ->
url = Api.buildUrl(Api.gitlabCiYmlPath).replace(':key', key)
$.get url, (file) ->
callback(file)
buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version)

View File

@ -32,10 +32,6 @@
#= require bootstrap/tooltip
#= require bootstrap/popover
#= require select2
#= require raphael
#= require g.raphael
#= require g.bar
#= require branch-graph
#= require ace/ace
#= require ace/ext-searchbox
#= require underscore
@ -125,9 +121,15 @@ window.onload = ->
setTimeout shiftWindow, 100
$ ->
$document = $(document)
$window = $(window)
$body = $('body')
gl.utils.preventDisabledButtons()
bootstrapBreakpoint = bp.getBreakpointSize()
$(".nicescroll").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF")
$(".nav-sidebar").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF")
# Click a .js-select-on-focus field, select the contents
$(".js-select-on-focus").on "focusin", ->
@ -155,7 +157,7 @@ $ ->
), 1
# Initialize tooltips
$('body').tooltip(
$body.tooltip(
selector: '.has-tooltip, [data-toggle="tooltip"]'
placement: (_, el) ->
$el = $(el)
@ -174,7 +176,7 @@ $ ->
flash.show()
# Disable form buttons while a form is submitting
$('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
$body.on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
buttons = $('[type="submit"]', @)
switch e.type
@ -187,7 +189,7 @@ $ ->
$('.account-box').hover -> $(@).toggleClass('hover')
# Commit show suppressed diff
$(document).on 'click', '.diff-content .js-show-suppressed-diff', ->
$document.on 'click', '.diff-content .js-show-suppressed-diff', ->
$container = $(@).parent()
$container.next('table').show()
$container.remove()
@ -200,13 +202,13 @@ $ ->
$('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
# Show/hide comments on diff
$("body").on "click", ".js-toggle-diff-comments", (e) ->
$body.on "click", ".js-toggle-diff-comments", (e) ->
$(@).toggleClass('active')
$(@).closest(".diff-file").find(".notes_holder").toggle()
e.preventDefault()
$(document).off "click", '.js-confirm-danger'
$(document).on "click", '.js-confirm-danger', (e) ->
$document.off "click", '.js-confirm-danger'
$document.on "click", '.js-confirm-danger', (e) ->
e.preventDefault()
btn = $(e.target)
text = btn.data("confirm-danger-message")
@ -214,7 +216,7 @@ $ ->
new ConfirmDangerModal(form, text)
$(document).on 'click', 'button', ->
$document.on 'click', 'button', ->
$(this).blur()
$('input[type="search"]').each ->
@ -222,7 +224,7 @@ $ ->
$this.attr 'value', $this.val()
return
$(document)
$document
.off 'keyup', 'input[type="search"]'
.on 'keyup', 'input[type="search"]' , (e) ->
$this = $(this)
@ -230,7 +232,7 @@ $ ->
$sidebarGutterToggle = $('.js-sidebar-toggle')
$(document)
$document
.off 'breakpoint:change'
.on 'breakpoint:change', (e, breakpoint) ->
if breakpoint is 'sm' or breakpoint is 'xs'
@ -242,14 +244,14 @@ $ ->
oldBootstrapBreakpoint = bootstrapBreakpoint
bootstrapBreakpoint = bp.getBreakpointSize()
if bootstrapBreakpoint != oldBootstrapBreakpoint
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
$document.trigger('breakpoint:change', [bootstrapBreakpoint])
checkInitialSidebarSize = ->
bootstrapBreakpoint = bp.getBreakpointSize()
if bootstrapBreakpoint is "xs" or "sm"
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
$document.trigger('breakpoint:change', [bootstrapBreakpoint])
$(window)
$window
.off "resize.app"
.on "resize.app", (e) ->
fitSidebarForSize()
@ -257,3 +259,47 @@ $ ->
gl.awardsHandler = new AwardsHandler()
checkInitialSidebarSize()
new Aside()
# Sidenav pinning
if $window.width() < 1440 and $.cookie('pin_nav') is 'true'
$.cookie('pin_nav', 'false', { path: '/' })
$('.page-with-sidebar')
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
.removeClass('page-sidebar-pinned')
$('.navbar-fixed-top').removeClass('header-pinned-nav')
$document
.off 'click', '.js-nav-pin'
.on 'click', '.js-nav-pin', (e) ->
e.preventDefault()
$pinBtn = $(e.currentTarget)
$page = $ '.page-with-sidebar'
$topNav = $ '.navbar-fixed-top'
$tooltip = $ "##{$pinBtn.attr('aria-describedby')}"
doPinNav = not $page.is('.page-sidebar-pinned')
tooltipText = 'Pin navigation'
$(this).toggleClass 'is-active'
if doPinNav
$page.addClass('page-sidebar-pinned')
$topNav.addClass('header-pinned-nav')
else
$tooltip.remove() # Remove it immediately when collapsing the sidebar
$page.removeClass('page-sidebar-pinned')
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
$topNav.removeClass('header-pinned-nav')
.toggleClass('header-collapsed header-expanded')
# Save settings
$.cookie 'pin_nav', doPinNav, { path: '/' }
if $.cookie('pin_nav') is 'true' or doPinNav
tooltipText = 'Unpin navigation'
# Update tooltip text immediately
$tooltip.find('.tooltip-inner').text(tooltipText)
# Persist tooltip title
$pinBtn.attr('title', tooltipText).tooltip('fixTitle')

View File

@ -40,7 +40,7 @@ class @AwardsHandler
$menu = $ '.emoji-menu'
if $addBtn.hasClass 'js-note-emoji'
$addBtn.parents('.note').find('.js-awards-block').addClass 'current'
$addBtn.closest('.note').find('.js-awards-block').addClass 'current'
else
$addBtn.closest('.js-awards-block').addClass 'current'

View File

@ -0,0 +1,23 @@
#= require blob/template_selector
class @BlobCiYamlSelector extends TemplateSelector
requestFile: (query) ->
Api.gitlabCiYml query.name, @requestFileSuccess.bind(@)
class @BlobCiYamlSelectors
constructor: (opts) ->
{
@$dropdowns = $('.js-gitlab-ci-yml-selector')
@editor
} = opts
@$dropdowns.each (i, dropdown) =>
$dropdown = $(dropdown)
new BlobCiYamlSelector(
pattern: /(.gitlab-ci.yml)/,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-gitlab-ci-yml-selector-wrap'),
dropdown: $dropdown,
editor: @editor
)

View File

@ -1,58 +1,5 @@
class @BlobGitignoreSelector
constructor: (opts) ->
{
@dropdown
@editor
@$wrapper = @dropdown.closest('.gitignore-selector')
@$filenameInput = $('#file_name')
@data = @dropdown.data('filenames')
} = opts
#= require blob/template_selector
@dropdown.glDropdown(
data: @data,
filterable: true,
selectable: true,
search:
fields: ['name']
clicked: @onClick
text: (gitignore) ->
gitignore.name
)
@toggleGitignoreSelector()
@bindEvents()
bindEvents: ->
@$filenameInput
.on 'keyup blur', (e) =>
@toggleGitignoreSelector()
toggleGitignoreSelector: ->
filename = @$filenameInput.val() or $('.editor-file-name').text().trim()
@$wrapper.toggleClass 'hidden', filename isnt '.gitignore'
onClick: (item, el, e) =>
e.preventDefault()
@requestIgnoreFile(item.name)
requestIgnoreFile: (name) ->
Api.gitignoreText name, @requestIgnoreFileSuccess.bind(@)
requestIgnoreFileSuccess: (gitignore) ->
@editor.setValue(gitignore.content, 1)
@editor.focus()
class @BlobGitignoreSelectors
constructor: (opts) ->
{
@$dropdowns = $('.js-gitignore-selector')
@editor
} = opts
@$dropdowns.each (i, dropdown) =>
$dropdown = $(dropdown)
new BlobGitignoreSelector(
dropdown: $dropdown,
editor: @editor
)
class @BlobGitignoreSelector extends TemplateSelector
requestFile: (query) ->
Api.gitignoreText query.name, @requestFileSuccess.bind(@)

View File

@ -0,0 +1,17 @@
class @BlobGitignoreSelectors
constructor: (opts) ->
{
@$dropdowns = $('.js-gitignore-selector')
@editor
} = opts
@$dropdowns.each (i, dropdown) =>
$dropdown = $(dropdown)
new BlobGitignoreSelector(
pattern: /(.gitignore)/,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-gitignore-selector-wrap'),
dropdown: $dropdown,
editor: @editor
)

View File

@ -1,30 +1,9 @@
class @BlobLicenseSelector
licenseRegex: /^(.+\/)?(licen[sc]e|copying)($|\.)/i
#= require blob/template_selector
constructor: (editor) ->
@$licenseSelector = $('.js-license-selector')
$fileNameInput = $('#file_name')
class @BlobLicenseSelector extends TemplateSelector
requestFile: (query) ->
data =
project: @dropdown.data('project')
fullname: @dropdown.data('fullname')
initialFileNameValue = if $fileNameInput.length
$fileNameInput.val()
else if $('.editor-file-name').length
$('.editor-file-name').text().trim()
@toggleLicenseSelector(initialFileNameValue)
if $fileNameInput
$fileNameInput.on 'keyup blur', (e) =>
@toggleLicenseSelector($(e.target).val())
$('select.license-select').on 'change', (e) ->
data =
project: $(this).data('project')
fullname: $(this).data('fullname')
Api.licenseText $(this).val(), data, (license) ->
editor.setValue(license.content, -1)
toggleLicenseSelector: (fileName) =>
if @licenseRegex.test(fileName)
@$licenseSelector.show()
else
@$licenseSelector.hide()
Api.licenseText query.id, data, @requestFileSuccess.bind(@)

View File

@ -0,0 +1,17 @@
class @BlobLicenseSelectors
constructor: (opts) ->
{
@$dropdowns = $('.js-license-selector')
@editor
} = opts
@$dropdowns.each (i, dropdown) =>
$dropdown = $(dropdown)
new BlobLicenseSelector(
pattern: /^(.+\/)?(licen[sc]e|copying)($|\.)/i,
data: $dropdown.data('data'),
wrapper: $dropdown.closest('.js-license-selector-wrap'),
dropdown: $dropdown,
editor: @editor
)

View File

@ -12,8 +12,10 @@ class @EditBlob
$("#file-content").val(@editor.getValue())
@initModePanesAndLinks()
new BlobLicenseSelector(@editor)
new BlobGitignoreSelectors(editor: @editor)
new BlobLicenseSelectors { @editor }
new BlobGitignoreSelectors { @editor }
new BlobCiYamlSelectors { @editor }
initModePanesAndLinks: ->
@$editModePanes = $(".js-edit-mode-pane")

View File

@ -0,0 +1,56 @@
class @TemplateSelector
constructor: (opts = {}) ->
{
@dropdown,
@data,
@pattern,
@wrapper,
@editor,
@fileEndpoint,
@$input = $('#file_name')
} = opts
@buildDropdown()
@bindEvents()
@onFilenameUpdate()
buildDropdown: ->
@dropdown.glDropdown(
data: @data,
filterable: true,
selectable: true,
search:
fields: ['name']
clicked: @onClick
text: (item) ->
item.name
)
bindEvents: ->
@$input.on('keyup blur', (e) =>
@onFilenameUpdate()
)
onFilenameUpdate: ->
return unless @$input.length
filenameMatches = @pattern.test(@$input.val().trim())
if not filenameMatches
@wrapper.addClass('hidden')
return
@wrapper.removeClass('hidden')
onClick: (item, el, e) =>
e.preventDefault()
@requestFile(item)
requestFile: (item) ->
# To be implemented on the extending class
# e.g.
# Api.gitignoreText item.name, @requestFileSuccess.bind(@)
requestFileSuccess: (file) ->
@editor.setValue(file.content, 1)
@editor.focus()

View File

@ -17,6 +17,8 @@ class @CiBuild
.off 'resize.build'
.on 'resize.build', @hideSidebar
@updateArtifactRemoveDate()
if $('#build-trace').length
@getInitialBuildTrace()
@initScrollButtonAffix()
@ -103,3 +105,10 @@ class @CiBuild
$('.js-build-sidebar')
.removeClass 'right-sidebar-collapsed'
.addClass 'right-sidebar-expanded'
updateArtifactRemoveDate: ->
$date = $('.js-artifacts-remove')
if $date.length
date = $date.text()
$date.text $.timefor(new Date(date), ' ')

View File

@ -29,6 +29,7 @@ class Dispatcher
new Todos()
when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode()
new DueDateSelect()
new GLForm($('.milestone-form'))
when 'groups:milestones:new'
new ZenMode()
@ -53,9 +54,13 @@ class Dispatcher
new Diff()
shortcut_handler = new ShortcutsIssuable(true)
new ZenMode()
new MergedButtons()
when 'projects:merge_requests:commits', 'projects:merge_requests:builds'
new MergedButtons()
when "projects:merge_requests:diffs"
new Diff()
new ZenMode()
new MergedButtons()
when 'projects:merge_requests:index'
shortcut_handler = new ShortcutsNavigation()
Issuable.init()
@ -68,9 +73,7 @@ class Dispatcher
new Diff()
new ZenMode()
shortcut_handler = new ShortcutsNavigation()
when 'projects:commits:show'
shortcut_handler = new ShortcutsNavigation()
when 'projects:activity'
when 'projects:commits:show', 'projects:activity'
shortcut_handler = new ShortcutsNavigation()
when 'projects:show'
shortcut_handler = new ShortcutsNavigation()
@ -98,6 +101,7 @@ class Dispatcher
when 'projects:blob:show', 'projects:blame:show'
new LineHighlighter()
shortcut_handler = new ShortcutsNavigation()
new ShortcutsBlob true
when 'projects:labels:new', 'projects:labels:edit'
new Labels()
when 'projects:labels:index'
@ -133,14 +137,13 @@ class Dispatcher
new Project()
new ProjectAvatar()
switch path[1]
when 'compare'
shortcut_handler = new ShortcutsNavigation()
when 'edit'
shortcut_handler = new ShortcutsNavigation()
new ProjectNew()
when 'new'
new ProjectNew()
when 'show'
new ProjectNew()
new ProjectShow()
new NotificationsDropdown()
when 'wikis'
@ -151,9 +154,9 @@ class Dispatcher
when 'snippets'
shortcut_handler = new ShortcutsNavigation()
new ZenMode() if path[2] == 'show'
when 'labels', 'graphs'
shortcut_handler = new ShortcutsNavigation()
when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
when 'labels', 'graphs', 'compare', 'pipelines', 'forks', \
'milestones', 'project_members', 'deploy_keys', 'builds', \
'hooks', 'services', 'protected_branches'
shortcut_handler = new ShortcutsNavigation()
# If we haven't installed a custom shortcut handler, install the default one

View File

@ -1,5 +1,21 @@
class @DueDateSelect
constructor: ->
# Milestone edit/new form
$datePicker = $('.datepicker')
if $datePicker.length
$dueDate = $('#milestone_due_date')
$datePicker.datepicker
dateFormat: 'yy-mm-dd'
onSelect: (dateText, inst) ->
$dueDate.val(dateText)
.datepicker('setDate', $.datepicker.parseDate('yy-mm-dd', $dueDate.val()))
$('.js-clear-due-date').on 'click', (e) ->
e.preventDefault()
$.datepicker._clearDate($datePicker)
# Issuable sidebar
$loading = $('.js-issuable-update .due_date')
.find('.block-loading')
.hide()
@ -32,7 +48,7 @@ class @DueDateSelect
date = new Date value.replace(new RegExp('-', 'g'), ',')
mediumDate = $.datepicker.formatDate 'M d, yy', date
else
mediumDate = 'None'
mediumDate = 'No due date'
data = {}
data[abilityName] = {}
@ -50,7 +66,8 @@ class @DueDateSelect
$selectbox.hide()
$value.css('display', '')
$valueContent.html(mediumDate)
cssClass = if Date.parse(mediumDate) then 'bold' else 'no-value'
$valueContent.html("<span class='#{cssClass}'>#{mediumDate}</span>")
$sidebarValue.html(mediumDate)
if value isnt ''

View File

@ -15,6 +15,9 @@ GitLab.GfmAutoComplete =
Members:
template: '<li>${username} <small>${title}</small></li>'
Labels:
template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>'
# Issues and MergeRequests
Issues:
template: '<li><small>${id}</small> ${title}</li>'
@ -176,6 +179,25 @@ GitLab.GfmAutoComplete =
title: sanitize(m.title)
search: "#{m.iid} #{m.title}"
@input.atwho
at: '~'
alias: 'labels'
searchKey: 'search'
displayTpl: @Labels.template
insertTpl: '${atwho-at}${title}'
callbacks:
beforeSave: (merges) ->
sanitizeLabelTitle = (title)->
if /\w+\s+\w+/g.test(title)
"\"#{sanitize(title)}\""
else
sanitize(title)
$.map merges, (m) ->
title: sanitizeLabelTitle(m.title)
color: m.color
search: "#{m.title}"
destroyAtWho: ->
@input.atwho('destroy')
@ -195,6 +217,8 @@ GitLab.GfmAutoComplete =
@input.atwho 'load', 'mergerequests', data.mergerequests
# load emojis
@input.atwho 'load', ':', data.emojis
# load labels
@input.atwho 'load', '~', data.labels
# This trigger at.js again
# otherwise we would be stuck with loading until the user types

View File

@ -58,7 +58,7 @@ class GitLabDropdownFilter
filter: (search_text) ->
data = @options.data()
if data?
if data? and not @options.filterByText
results = data
if search_text isnt ''
@ -102,10 +102,11 @@ class GitLabDropdownFilter
$el = $(@)
matches = fuzzaldrinPlus.match($el.text().trim(), search_text)
if matches.length
$el.show()
else
$el.hide()
unless $el.is('.dropdown-header')
if matches.length
$el.show()
else
$el.hide()
else
elements.show()
@ -191,6 +192,7 @@ class GitLabDropdown
if @options.filterable
@filter = new GitLabDropdownFilter @filterInput,
filterInputBlur: @filterInputBlur
filterByText: @options.filterByText
remote: @options.filterRemote
query: @options.data
keys: searchFields
@ -302,6 +304,9 @@ class GitLabDropdown
if @options.setIndeterminateIds
@options.setIndeterminateIds.call(@)
if @options.setActiveIds
@options.setActiveIds.call(@)
# Makes indeterminate items effective
if @fullData and @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
@parseData @fullData

View File

@ -34,6 +34,8 @@ class @GLForm
# form and textarea event listeners
@addEventListeners()
gl.text.init(@form)
# hide discard button
@form.find('.js-note-discard').hide()
@ -42,6 +44,7 @@ class @GLForm
clearEventListeners: ->
@textarea.off 'focus'
@textarea.off 'blur'
gl.text.removeListeners(@form)
addEventListeners: ->
@textarea.on 'focus', ->

View File

@ -121,7 +121,11 @@ class @ContributorsMasterGraph extends ContributorsGraph
class @ContributorsAuthorGraph extends ContributorsGraph
constructor: (@data) ->
@width = $('.content').width()/2 - 100
# Don't split graph size in half for mobile devices.
if $(window).width() < 768
@width = $('.content').width() - 80
else
@width = ($('.content').width() / 2) - 100
@height = 200
@x = null
@y = null

View File

@ -56,13 +56,6 @@ issuable_created = false
Issuable.filterResults $('.filter-form')
$('.js-label-select').trigger('update.label')
toggleLabelFilters: ->
$filteredLabels = $('.filtered-labels')
if $filteredLabels.find('.label-row').length > 0
$filteredLabels.removeClass('hidden')
else
$filteredLabels.addClass('hidden')
filterResults: (form) =>
formData = form.serialize()
@ -71,58 +64,16 @@ issuable_created = false
issuesUrl = formAction
issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}")
issuesUrl += formData
$.ajax
type: 'GET'
url: formAction
data: formData
complete: ->
$('.issues-holder, .merge-requests-holder').css('opacity', '1.0')
success: (data) ->
$('.issues-holder, .merge-requests-holder').html(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: issuesUrl}, document.title, issuesUrl
Issuable.reload()
Issuable.updateStateFilters()
$filteredLabels = $('.filtered-labels')
if typeof Issuable.labelRow is 'function'
$filteredLabels.html(Issuable.labelRow(data))
Issuable.toggleLabelFilters()
dataType: "json"
reload: ->
if Issuable.created
Issuable.initChecks()
$('#filter_issue_search').val($('#issue_search').val())
Turbolinks.visit(issuesUrl);
initChecks: ->
$('.check_all_issues').on 'click', ->
$('.check_all_issues').off('click').on('click', ->
$('.selected_issue').prop('checked', @checked)
Issuable.checkChanged()
)
$('.selected_issue').on 'change', Issuable.checkChanged
updateStateFilters: ->
stateFilters = $('.issues-state-filters, .dropdown-menu-sort')
newParams = {}
paramKeys = ['author_id', 'milestone_title', 'assignee_id', 'issue_search', 'issue_search']
for paramKey in paramKeys
newParams[paramKey] = gl.utils.getParameterValues(paramKey)[0] or ''
if stateFilters.length
stateFilters.find('a').each ->
initialUrl = gl.utils.removeParamQueryString($(this).attr('href'), 'label_name[]')
labelNameValues = gl.utils.getParameterValues('label_name[]')
if labelNameValues
labelNameQueryString = ("label_name[]=#{value}" for value in labelNameValues).join('&')
newUrl = "#{gl.utils.mergeUrlParams(newParams, initialUrl)}&#{labelNameQueryString}"
else
newUrl = gl.utils.mergeUrlParams(newParams, initialUrl)
$(this).attr 'href', newUrl
$('.selected_issue').off('change').on('change', Issuable.checkChanged)
checkChanged: ->
checked_issues = $('.selected_issue:checked')

View File

@ -102,6 +102,10 @@ class @IssuableForm
return {
results: data
}
data: (query) ->
{
search: query
}
formatResult: (project) ->
project.name_with_namespace
formatSelection: (project) ->

View File

@ -9,6 +9,9 @@ class @IssuableBulkActions
@bindEvents()
# Fixes bulk-assign not working when navigating through pages
Issuable.initChecks();
getElement: (selector) ->
@container.find selector

View File

@ -39,7 +39,7 @@ class @LabelsSelect
</a>
<% }); %>'
)
labelNoneHTMLTemplate = _.template('<div class="light">None</div>')
labelNoneHTMLTemplate = '<span class="no-value">None</span>'
if newLabelField.length
@ -145,7 +145,7 @@ class @LabelsSelect
template = labelHTMLTemplate(data)
labelCount = data.labels.length
else
template = labelNoneHTMLTemplate()
template = labelNoneHTMLTemplate
$value
.removeAttr('style')
.html(template)
@ -210,9 +210,21 @@ class @LabelsSelect
if $dropdown.hasClass('js-filter-bulk-update')
indeterminate = instance.indeterminateIds
active = instance.activeIds
if indeterminate.indexOf(label.id) isnt -1
selectedClass.push 'is-indeterminate'
if active.indexOf(label.id) isnt -1
# Remove is-indeterminate class if the item will be marked as active
i = selectedClass.indexOf 'is-indeterminate'
selectedClass.splice i, 1 unless i is -1
selectedClass.push 'is-active'
# Add input manually
instance.addInput @fieldName, label.id
if $form.find("input[type='hidden']\
[name='#{$dropdown.data('fieldName')}']\
[value='#{this.id(label)}']").length
@ -328,6 +340,10 @@ class @LabelsSelect
setIndeterminateIds: ->
if @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
@indeterminateIds = _this.getIndeterminateIds()
setActiveIds: ->
if @dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')
@activeIds = _this.getActiveIds()
)
@bindEvents()
@ -352,3 +368,12 @@ class @LabelsSelect
label_ids.push $("#issue_#{issue_id}").data('labels')
_.flatten(label_ids)
getActiveIds: ->
label_ids = []
$('.selected_issue:checked').each (i, el) ->
issue_id = $(el).data('id')
label_ids.push $("#issue_#{issue_id}").data('labels')
_.intersection.apply _, label_ids

View File

@ -1,14 +1,25 @@
class @LayoutNav
$ ->
$('.fade-left').addClass('end-scroll')
$('.scrolling-tabs').on 'scroll', (event) ->
$this = $(this)
$el = $(event.target)
currentPosition = $this.scrollLeft()
size = bp.getBreakpointSize()
controlBtnWidth = $('.controls').width()
maxPosition = $this.get(0).scrollWidth - $this.parent().width()
maxPosition += controlBtnWidth if size isnt 'xs' and $('.nav-control').length
hideEndFade = ($scrollingTabs) ->
$scrollingTabs.each ->
$this = $(@)
$el.find('.fade-left').toggleClass('end-scroll', currentPosition is 0)
$el.find('.fade-right').toggleClass('end-scroll', currentPosition is maxPosition)
$this
.find('.fade-right')
.toggleClass('end-scroll', $this.width() is $this.prop('scrollWidth'))
$ ->
$('.fade-left').addClass('end-scroll')
hideEndFade($('.scrolling-tabs'))
$(window)
.off 'resize.nav'
.on 'resize.nav', ->
hideEndFade($('.scrolling-tabs'))
$('.scrolling-tabs').on 'scroll', (event) ->
$this = $(this)
currentPosition = $this.scrollLeft()
maxPosition = $this.prop('scrollWidth') - $this.outerWidth()
$this.find('.fade-left').toggleClass('end-scroll', currentPosition is 0)
$this.find('.fade-right').toggleClass('end-scroll', currentPosition is maxPosition)

View File

@ -1,5 +1,46 @@
((w) ->
w.gl or= {}
w.gl.utils or= {}
w.gl.utils.isInGroupsPage = ->
return $('body').data('page').split(':')[0] is 'groups'
w.gl.utils.isInProjectPage = ->
return $('body').data('page').split(':')[0] is 'projects'
w.gl.utils.getProjectSlug = ->
return if @isInProjectPage() then $('body').data 'project' else null
w.gl.utils.getGroupSlug = ->
return if @isInGroupsPage() then $('body').data 'group' else null
gl.utils.updateTooltipTitle = ($tooltipEl, newTitle) ->
$tooltipEl
.tooltip 'destroy'
.attr 'title', newTitle
.tooltip 'fixTitle'
gl.utils.preventDisabledButtons = ->
$('.btn').click (e) ->
if $(this).hasClass 'disabled'
e.preventDefault()
e.stopImmediatePropagation()
return false
jQuery.timefor = (time, suffix, expiredLabel) ->
return '' unless time

View File

@ -0,0 +1,79 @@
((w) ->
w.gl ?= {}
w.gl.text ?= {}
gl.text.randomString = -> Math.random().toString(36).substring(7)
gl.text.replaceRange = (s, start, end, substitute) ->
s.substring(0, start) + substitute + s.substring(end);
gl.text.selectedText = (text, textarea) ->
text.substring(textarea.selectionStart, textarea.selectionEnd)
gl.text.insertText = (textArea, text, tag, selected, wrap) ->
selectedSplit = selected.split('\n')
startChar = if not wrap and textArea.selectionStart > 0 then '\n' else ''
if selectedSplit.length > 1 and not wrap
insertText = selectedSplit.map((val) ->
if val.indexOf(tag) is 0
"#{val.replace(tag, '')}"
else
"#{tag}#{val}"
).join('\n')
else
insertText = "#{startChar}#{tag}#{selected}#{if wrap then tag else ' '}"
if document.queryCommandSupported('insertText')
document.execCommand 'insertText', false, insertText
else
try
document.execCommand("ms-beginUndoUnit")
textArea.value = @replaceRange(
text,
textArea.selectionStart,
textArea.selectionEnd,
insertText)
try
document.execCommand("ms-endUndoUnit")
@moveCursor(textArea, tag, wrap)
gl.text.moveCursor = (textArea, tag, wrapped) ->
return unless textArea.setSelectionRange
if textArea.selectionStart is textArea.selectionEnd
if wrapped
pos = textArea.selectionStart - tag.length
else
pos = textArea.selectionStart
textArea.setSelectionRange pos, pos
gl.text.updateText = (textArea, tag, wrap) ->
$textArea = $(textArea)
oldVal = $textArea.val()
textArea = $textArea.get(0)
text = $textArea.val()
selected = @selectedText(text, textArea)
$textArea.focus()
@insertText(textArea, text, tag, selected, wrap)
gl.text.init = (form) ->
self = @
$('.js-md', form)
.off 'click'
.on 'click', ->
$this = $(@)
self.updateText(
$this.closest('.md-area').find('textarea'),
$this.data('md-tag'),
not $this.data('md-prepend')
)
gl.text.removeListeners = (form) ->
$('.js-md', form).off()
) window

View File

@ -42,9 +42,3 @@ work = ->
$(document).on('page:fetch', start)
$(document).on('page:change', stop)
$ ->
# Make logo clickable as part of a workaround for Safari visited
# link behaviour (See !2690).
$('#logo').on 'click', ->
Turbolinks.visit('/')

View File

@ -9,7 +9,7 @@ class @MergeRequest
# Options:
# action - String, current controller action
#
constructor: (@opts) ->
constructor: (@opts = {}) ->
this.$el = $('.merge-request')
this.$('.show-all-commits').on 'click', =>

View File

@ -88,7 +88,7 @@ class @MergeRequestTabs
scrollToElement: (container) ->
if window.location.hash
navBarHeight = $('.navbar-gitlab').outerHeight()
navBarHeight = $('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight()
$el = $("#{container} #{window.location.hash}:not(.match)")
$.scrollTo("#{container} #{window.location.hash}:not(.match)", offset: -navBarHeight) if $el.length

View File

@ -0,0 +1,30 @@
class @MergedButtons
constructor: ->
@$removeBranchWidget = $('.remove_source_branch_widget')
@$removeBranchProgress = $('.remove_source_branch_in_progress')
@$removeBranchFailed = $('.remove_source_branch_widget.failed')
@cleanEventListeners()
@initEventListeners()
cleanEventListeners: ->
$(document).off 'click', '.remove_source_branch'
$(document).off 'ajax:success', '.remove_source_branch'
$(document).off 'ajax:error', '.remove_source_branch'
initEventListeners: ->
$(document).on 'click', '.remove_source_branch', @removeSourceBranch
$(document).on 'ajax:success', '.remove_source_branch', @removeBranchSuccess
$(document).on 'ajax:error', '.remove_source_branch', @removeBranchError
removeSourceBranch: =>
@$removeBranchWidget.hide()
@$removeBranchProgress.show()
removeBranchSuccess: ->
location.reload()
removeBranchError: ->
@$removeBranchWidget.hide()
@$removeBranchProgress.hide()
@$removeBranchFailed.show()

View File

@ -24,14 +24,10 @@ class @MilestoneSelect
if issueUpdateURL
milestoneLinkTemplate = _.template(
'<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>">
<span class="has-tooltip" data-container="body" title="<%= remaining %>">
<%= _.escape(title) %>
</span>
</a>'
'<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>" class="bold has-tooltip" data-container="body" title="<%= remaining %>"><%= _.escape(title) %></a>'
)
milestoneLinkNoneTemplate = '<div class="light">None</div>'
milestoneLinkNoneTemplate = '<span class="no-value">None</span>'
collapsedSidebarLabelTemplate = _.template(
'<span class="has-tooltip" data-container="body" title="<%= remaining %>" data-placement="left">
@ -116,7 +112,7 @@ class @MilestoneSelect
.val()
data = {}
data[abilityName] = {}
data[abilityName].milestone_id = selected
data[abilityName].milestone_id = if selected? then selected else null
$loading
.fadeIn()
$dropdown.trigger('loading.gl.dropdown')

View File

@ -0,0 +1,20 @@
# This is a manifest file that'll be compiled into including all the files listed below.
# Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
# be included in the compiled file accessible from http://example.com/assets/application.js
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
#= require raphael
#= require g.raphael
#= require g.bar
#= require_tree .
$ ->
network_graph = new Network({
url: $(".network-graph").attr('data-url'),
commit_url: $(".network-graph").attr('data-commit-url'),
ref: $(".network-graph").attr('data-ref'),
commit_id: $(".network-graph").attr('data-commit-id')
})
new ShortcutsNetwork(network_graph.branch_graph)

View File

@ -102,12 +102,15 @@ class @Notes
keydownNoteText: (e) ->
$this = $(this)
if $this.val() is '' and e.which is 38 #aka the up key
if $this.val() is '' and e.which is 38 and not isMetaKey e
myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last")
if myLastNote.length
myLastNoteEditBtn = myLastNote.find('.js-note-edit')
myLastNoteEditBtn.trigger('click', [true, myLastNote])
isMetaKey = (e) ->
(e.metaKey or e.ctrlKey or e.altKey or e.shiftKey)
initRefresh: ->
clearInterval(Notes.interval)
Notes.interval = setInterval =>
@ -115,12 +118,14 @@ class @Notes
, @pollingInterval
refresh: =>
return if @refreshing is true
@refreshing = true
if not document.hidden and document.URL.indexOf(@noteable_url) is 0
@getContent()
getContent: ->
return if @refreshing
@refreshing = true
$.ajax
url: @notes_url
data: "last_fetched_at=" + @last_fetched_at

View File

@ -1,21 +1,25 @@
class @NotificationsDropdown
$ ->
constructor: ->
$(document)
.off 'click', '.update-notification'
.on 'click', '.update-notification', (e) ->
e.preventDefault()
return if $(this).is('.is-active') and $(this).data('notification-level') is 'custom'
notificationLevel = $(@).data 'notification-level'
label = $(@).data 'notification-title'
form = $(this).parents('form:first')
form = $(this).parents('.notification-form:first')
form.find('.js-notification-loading').toggleClass 'fa-bell fa-spin fa-spinner'
form.find('#notification_setting_level').val(notificationLevel)
form.submit();
form.submit()
$(document)
.off 'ajax:success', '#notification-form'
.on 'ajax:success', '#notification-form', (e, data) ->
.off 'ajax:success', '.notification-form'
.on 'ajax:success', '.notification-form', (e, data) ->
if data.saved
new Flash('Notification settings saved', 'notice')
$(e.currentTarget).closest('.notification-dropdown').replaceWith(data.html)
$(e.currentTarget)
.closest('.notification-dropdown')
.replaceWith(data.html)
else
new Flash('Failed to save new settings', 'alert')

View File

@ -12,7 +12,6 @@ class @NotificationsForm
toggleCheckbox: (e) =>
$checkbox = $(e.currentTarget)
$parent = $checkbox.closest('.checkbox')
console.log($parent)
@saveEvent($checkbox, $parent)
showCheckboxLoadingSpinner: ($parent) ->
@ -25,13 +24,13 @@ class @NotificationsForm
saveEvent: ($checkbox, $parent) ->
form = $parent.parents('form:first')
console.log(form)
$.ajax(
url: form.attr('action')
method: form.attr('method')
dataType: 'json'
data: form.serialize()
beforeSend: =>
@showCheckboxLoadingSpinner($parent)
).done (data) ->

View File

@ -19,6 +19,7 @@ class @Project
$('.clone').text(url)
# Ref switcher
@initRefSwitcher()
$('.project-refs-select').on 'change', ->
$(@).parents('form').submit()
@ -34,7 +35,6 @@ class @Project
$(@).parents('.no-password-message').remove()
e.preventDefault()
@projectSelectDropdown()
projectSelectDropdown: ->
@ -50,3 +50,39 @@ class @Project
changeProject: (url) ->
window.location = url
initRefSwitcher: ->
$('.js-project-refs-dropdown').each ->
$dropdown = $(@)
selected = $dropdown.data('selected')
$dropdown.glDropdown(
data: (term, callback) ->
$.ajax(
url: $dropdown.data('refs-url')
data:
ref: $dropdown.data('ref')
).done (refs) ->
callback(refs)
selectable: true
filterable: true
filterByText: true
fieldName: 'ref'
renderRow: (ref) ->
if ref.header?
"<li class='dropdown-header'>#{ref.header}</li>"
else
isActiveClass = if ref is selected then 'is-active' else ''
"<li>
<a href='#' data-ref='#{escape(ref)}' class='#{isActiveClass}'>
#{ref}
</a>
</li>"
id: (obj, $el) ->
$el.data('ref')
toggleLabel: (obj, $el) ->
$el.text().trim()
clicked: (e) ->
$dropdown.closest('form').submit()
)

View File

@ -43,6 +43,59 @@ class @Sidebar
$('.right-sidebar')
.hasClass('right-sidebar-collapsed'), { path: '/' })
$(document)
.off 'click', '.js-issuable-todo'
.on 'click', '.js-issuable-todo', @toggleTodo
toggleTodo: (e) =>
$this = $(e.currentTarget)
$todoLoading = $('.js-issuable-todo-loading')
$btnText = $('.js-issuable-todo-text', $this)
ajaxType = if $this.attr('data-delete-path') then 'DELETE' else 'POST'
if $this.attr('data-delete-path')
url = "#{$this.attr('data-delete-path')}"
else
url = "#{$this.data('url')}"
$.ajax(
url: url
type: ajaxType
dataType: 'json'
data:
issuable_id: $this.data('issuable-id')
issuable_type: $this.data('issuable-type')
beforeSend: =>
@beforeTodoSend($this, $todoLoading)
).done (data) =>
@todoUpdateDone(data, $this, $btnText, $todoLoading)
beforeTodoSend: ($btn, $todoLoading) ->
$btn.disable()
$todoLoading.removeClass 'hidden'
todoUpdateDone: (data, $btn, $btnText, $todoLoading) ->
$todoPendingCount = $('.todos-pending-count')
$todoPendingCount.text data.count
$btn.enable()
$todoLoading.addClass 'hidden'
if data.count is 0
$todoPendingCount.addClass 'hidden'
else
$todoPendingCount.removeClass 'hidden'
if data.delete_path?
$btn
.attr 'aria-label', $btn.data('mark-text')
.attr 'data-delete-path', data.delete_path
$btnText.text $btn.data('mark-text')
else
$btn
.attr 'aria-label', $btn.data('todo-text')
.removeAttr 'data-delete-path'
$btnText.text $btn.data('todo-text')
sidebarDropdownLoading: (e) ->
$sidebarCollapsedIcon = $(@).closest('.block').find('.sidebar-collapsed-icon')
@ -117,5 +170,3 @@ class @Sidebar
getBlock: (name) ->
@sidebar.find(".block.#{name}")

View File

@ -67,8 +67,12 @@ class @SearchAutocomplete
getData: (term, callback) ->
_this = @
# Do not trigger request if input is empty
return if @searchInput.val() is ''
unless term
if contents = @getCategoryContents()
@searchInput.data('glDropdown').filter.options.callback contents
@enableAutocomplete()
return
# Prevent multiple ajax calls
return if @loadingSuggestions
@ -122,6 +126,37 @@ class @SearchAutocomplete
).always ->
_this.loadingSuggestions = false
getCategoryContents: ->
userId = gon.current_user_id
{ utils, projectOptions, groupOptions, dashboardOptions } = gl
if utils.isInGroupsPage() and groupOptions
options = groupOptions[utils.getGroupSlug()]
else if utils.isInProjectPage() and projectOptions
options = projectOptions[utils.getProjectSlug()]
else if dashboardOptions
options = dashboardOptions
{ issuesPath, mrPath, name } = options
items = [
{ header: "#{name}" }
{ text: 'Issues assigned to me', url: "#{issuesPath}/?assignee_id=#{userId}" }
{ text: "Issues I've created", url: "#{issuesPath}/?author_id=#{userId}" }
'separator'
{ text: 'Merge requests assigned to me', url: "#{mrPath}/?assignee_id=#{userId}" }
{ text: "Merge requests I've created", url: "#{mrPath}/?author_id=#{userId}" }
]
items.splice 0, 1 unless name
return items
serializeState: ->
{
# Search Criteria
@ -209,6 +244,12 @@ class @SearchAutocomplete
@isFocused = true
@wrap.addClass('search-active')
@getData() if @getValue() is ''
getValue: -> return @searchInput.val()
onClearInputClick: (e) =>
e.preventDefault()
@searchInput.val('').focus()
@ -229,6 +270,10 @@ class @SearchAutocomplete
@locationBadgeEl.text(badgeText).show()
@wrap.addClass('has-location-badge')
hasLocationBadge: -> return @wrap.is '.has-location-badge'
restoreOriginalState: ->
inputs = Object.keys @originalState
@ -257,13 +302,14 @@ class @SearchAutocomplete
@getElement("##{input}").val('')
removeLocationBadge: ->
@locationBadgeEl.hide()
# Reset state
@resetSearchState()
@wrap.removeClass('has-location-badge')
@disableAutocomplete()
disableAutocomplete: ->
@searchInput.addClass('disabled')

View File

@ -1,7 +1,7 @@
class @Shortcuts
constructor: ->
constructor: (skipResetBindings) ->
@enabledHelp = []
Mousetrap.reset()
Mousetrap.reset() if not skipResetBindings
Mousetrap.bind('?', @onToggleHelp)
Mousetrap.bind('s', Shortcuts.focusSearch)
Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview)

View File

@ -0,0 +1,10 @@
#= require shortcuts
class @ShortcutsBlob extends Shortcuts
constructor: (skipResetBindings) ->
super skipResetBindings
Mousetrap.bind('y', ShortcutsBlob.copyToClipboard)
@copyToClipboard: ->
clipboardButton = $('.btn-clipboard')
clipboardButton.click() if clipboardButton

View File

@ -3,13 +3,33 @@ expanded = 'page-sidebar-expanded'
toggleSidebar = ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded")
$('.navbar-fixed-top').toggleClass("header-collapsed header-expanded")
if $.cookie('pin_nav') is 'true'
$('.navbar-fixed-top').toggleClass('header-pinned-nav')
$('.page-with-sidebar').toggleClass('page-sidebar-pinned')
setTimeout ( ->
niceScrollBars = $('.nicescroll').niceScroll();
niceScrollBars = $('.nav-sidebar').niceScroll();
niceScrollBars.updateScrollBar();
), 300
$(document)
.off 'click', 'body'
.on 'click', 'body', (e) ->
unless $.cookie('pin_nav') is 'true'
$target = $(e.target)
$nav = $target.closest('.sidebar-wrapper')
pageExpanded = $('.page-with-sidebar').hasClass('page-sidebar-expanded')
$toggle = $target.closest('.toggle-nav-collapse, .side-nav-toggle')
if $nav.length is 0 and pageExpanded and $toggle.length is 0
$('.page-with-sidebar')
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
$('.navbar-fixed-top')
.toggleClass('header-collapsed header-expanded')
$(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', (e) ->
e.preventDefault()

View File

@ -9,9 +9,11 @@ class @Star
$this.parent().find('.star-count').text data.star_count
if isStarred
$starSpan.removeClass('starred').text 'Star'
gl.utils.updateTooltipTitle $this, 'Star project'
$starIcon.removeClass('fa-star').addClass 'fa-star-o'
else
$starSpan.addClass('starred').text 'Unstar'
gl.utils.updateTooltipTitle $this, 'Unstar project'
$starIcon.removeClass('fa-star-o').addClass 'fa-star'
return

View File

@ -6,12 +6,6 @@ class @Calendar
@daySizeWithSpace = @daySize + (@daySpace * 2)
@monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
@months = []
@highestValue = 0
# Get the highest value from the timestampes
_.each timestamps, (count) =>
if count > @highestValue
@highestValue = count
# Loop through the timestamps to create a group of objects
# The group of objects will be grouped based on the day of the week they are
@ -39,8 +33,8 @@ class @Calendar
i++
# Init color functions
@color = @initColor()
@colorKey = @initColorKey()
@color = @initColor()
# Init the svg element
@renderSvg(group)
@ -104,7 +98,7 @@ class @Calendar
.attr 'class', 'user-contrib-cell js-tooltip'
.attr 'fill', (stamp) =>
if stamp.count isnt 0
@color(stamp.count)
@color(Math.min(stamp.count, 40))
else
'#ededed'
.attr 'data-container', 'body'
@ -164,10 +158,11 @@ class @Calendar
color
initColor: ->
colorRange = ['#ededed', @colorKey(0), @colorKey(1), @colorKey(2), @colorKey(3)]
d3.scale
.linear()
.range(['#acd5f2', '#254e77'])
.domain([0, @highestValue])
.threshold()
.domain([0, 10, 20, 30])
.range(colorRange)
initColorKey: ->
d3.scale

View File

@ -31,7 +31,7 @@ class @UsersSelect
assignTo = (selected) ->
data = {}
data[abilityName] = {}
data[abilityName].assignee_id = selected
data[abilityName].assignee_id = if selected? then selected else null
$loading
.fadeIn()
$dropdown.trigger('loading.gl.dropdown')
@ -72,7 +72,7 @@ class @UsersSelect
assigneeTemplate = _.template(
'<% if (username) { %>
<a class="author_link " href="/u/<%= username %>">
<a class="author_link bold" href="/u/<%= username %>">
<% if( avatar ) { %>
<img width="32" class="avatar avatar-inline s32" alt="" src="<%= avatar %>">
<% } %>
@ -82,7 +82,7 @@ class @UsersSelect
</span>
</a>
<% } else { %>
<span class="assign-yourself">
<span class="no-value assign-yourself">
No assignee -
<a href="#" class="js-assign-yourself">
assign yourself

View File

@ -37,3 +37,4 @@
@import "framework/timeline.scss";
@import "framework/typography.scss";
@import "framework/zen.scss";
@import "framework/blank";

View File

@ -0,0 +1,23 @@
.blank-state {
padding-top: 20px;
padding-bottom: 20px;
text-align: center;
}
.blank-state-no-icon {
padding-top: 40px;
padding-bottom: 40px;
}
.blank-state-title {
margin-top: 0;
margin-bottom: 5px;
font-size: 19px;
font-weight: normal;
}
.blank-state-text {
margin-top: 0;
margin-bottom: $gl-padding;
font-size: 15px;
}

View File

@ -91,6 +91,26 @@
background-color: $white-light;
border-top: none;
}
&.top-block .container-fluid {
background-color: inherit;
}
}
.sub-header-block {
background-color: $white-light;
border-bottom: 1px solid $white-dark;
padding: 11px 0;
margin-bottom: 11px;
.oneline {
line-height: 35px;
}
&.no-bottom-space {
border-bottom: 0;
margin-bottom: 0;
}
}
.cover-block {

View File

@ -461,10 +461,12 @@
}
}
.ui-state-active,
.ui-state-hover {
color: $md-link-color;
background-color: $calendar-hover-bg;
.ui-datepicker-calendar {
.ui-state-hover,
.ui-state-active {
color: #fff;
border: 0;
}
}
.ui-datepicker-prev,

View File

@ -8,8 +8,8 @@
*/
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
.page-with-sidebar {
.collapse-nav a {
.toggle-nav-collapse,
.pin-nav-btn {
color: $color-light;
background: $color;

View File

@ -2,8 +2,19 @@
* Application Header
*
*/
@mixin tanuki-logo-colors($path-color) {
fill: $path-color;
transition: all 0.8s;
&:hover,
&.highlight {
fill: lighten($path-color, 25%);
transition: all 0.1s;
}
}
header {
transition-duration: .3s;
transition: padding $sidebar-transition-duration;
&.navbar-empty {
height: $header-height;
@ -79,14 +90,9 @@ header {
&.header-collapsed {
padding: 0 16px;
.side-nav-toggle {
display: block;
}
}
.side-nav-toggle {
display: none;
position: absolute;
left: -10px;
margin: 6px 0;
@ -108,9 +114,7 @@ header {
.header-content {
position: relative;
height: $header-height;
padding-right: 40px;
padding-left: 30px;
transition-duration: .3s;
@media (min-width: $screen-sm-min) {
padding-right: 0;
@ -198,25 +202,24 @@ header {
}
}
.header-collapsed {
margin-left: 0;
#tanuki-logo {
.header-content {
@media (min-width: $screen-sm-max) {
padding-left: 30px;
transition-duration: .3s;
}
#tanuki-left-ear,
#tanuki-right-ear,
#tanuki-nose {
@include tanuki-logo-colors($tanuki-red);
}
}
.tanuki-shape {
transition: all 0.8s;
&:hover, &.highlight {
fill: rgb(255, 255, 255);
transition: all 0.1s;
#tanuki-left-eye,
#tanuki-right-eye {
@include tanuki-logo-colors($tanuki-orange);
}
#tanuki-left-cheek,
#tanuki-right-cheek {
@include tanuki-logo-colors($tanuki-yellow);
}
}
@media (max-width: $screen-xs-max) {

View File

@ -159,7 +159,7 @@ ul.content-list {
background-color: $gray-light;
border: dotted 1px $gray-dark;
margin: 1px 0;
min-height: 30px;
min-height: 52px;
}
}
}

View File

@ -65,6 +65,11 @@
a {
padding-top: 0;
line-height: 1;
border-bottom: 1px solid $border-color;
&.btn.btn-xs {
padding: 2px 5px;
}
}
}
}
@ -97,5 +102,30 @@
white-space: pre-wrap;
word-break: keep-all;
}
@include bulleted-list;
}
}
.toolbar-group {
float: left;
margin-right: -5px;
margin-left: $gl-padding;
&:first-child {
margin-left: 0;
}
}
.toolbar-btn {
float: left;
padding: 0 5px;
color: #959494;
background: transparent;
border: 0;
outline: 0;
&:hover {
color: $gl-link-color;
}
}

View File

@ -110,3 +110,17 @@
font-size: 16px;
line-height: 24px;
}
@mixin bulleted-list {
> ul {
list-style-type: disc;
ul {
list-style-type: circle;
ul {
list-style-type: square;
}
}
}
}

View File

@ -52,6 +52,19 @@
.git-clone-holder {
display: none;
}
// Display Star and Fork buttons without counters on mobile.
.project-action-buttons {
display: block;
.count-buttons .btn {
margin: 0 10px;
}
.count-buttons .count-with-arrow {
display: none;
}
}
}
.project-stats {

View File

@ -18,6 +18,13 @@
opacity: 0;
transition-duration: .3s;
}
.fa {
position: relative;
top: 3px;
font-size: 13px;
color: $btn-placeholder-gray;
}
}
@mixin scrolling-links() {
@ -74,6 +81,7 @@
.container-fluid {
background-color: $background-color;
margin-bottom: 0;
}
li {
@ -103,10 +111,6 @@
width: 50%;
line-height: 28px;
&.wiki-page {
padding: 16px 10px 11px;
}
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-min) {
width: 100%;
@ -135,7 +139,7 @@
}
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-max) {
@media (max-width: $screen-xs-max) {
width: 100%;
}
}
@ -219,6 +223,7 @@
form {
display: block;
height: auto;
margin-bottom: 14px;
input {
width: 100%;
@ -241,6 +246,12 @@
}
}
}
&.adjust {
.nav-text, .nav-controls {
width: auto;
}
}
}
.layout-nav {
@ -250,7 +261,7 @@
z-index: 11;
background: $background-color;
border-bottom: 1px solid $border-color;
transition-duration: .3s;
transition: padding $sidebar-transition-duration;
text-align: center;
.container-fluid {
@ -280,11 +291,10 @@
}
.dropdown {
margin-left: 7px;
@media (max-width: $screen-xs-min) {
margin-left: 0;
}
position: absolute;
top: 7px;
right: 15px;
z-index: 2;
li.active {
font-weight: bold;
@ -313,11 +323,19 @@
.fade-right {
@include fade(left, rgba(250, 250, 250, 0.4), $background-color);
right: 0;
.fa {
right: -7px;
}
}
.fade-left {
@include fade(right, rgba(250, 250, 250, 0.4), $background-color);
left: 0;
.fa {
left: -7px;
}
}
li {
@ -347,6 +365,12 @@
.badge {
color: $gl-icon-color;
}
&:hover {
a, i {
color: $black;
}
}
}
}

View File

@ -9,6 +9,10 @@
margin-top: -2px;
float: right;
}
.dropdown-menu-toggle {
line-height: 20px;
}
}
.panel-body {

View File

@ -165,11 +165,6 @@
background-size: 16px 16px !important;
}
/** Branch/tag selector **/
.project-refs-form .select2-container {
width: 160px !important;
}
.select2-results .select2-no-results,
.select2-results .select2-searching,
.select2-results .select2-ajax-error,

View File

@ -1,26 +1,31 @@
.page-with-sidebar {
padding-top: $header-height;
transition-duration: .3s;
transition: padding $sidebar-transition-duration;
.sidebar-wrapper {
position: fixed;
top: 0;
bottom: 0;
overflow-y: auto;
overflow-x: hidden;
left: 0;
height: 100%;
transition-duration: .3s;
overflow: hidden;
transition: width $sidebar-transition-duration;
}
}
.sidebar-wrapper {
z-index: 1000;
background: $background-color;
.nicescroll-rails-hr {
// TODO: Figure out why nicescroll doesn't hide horizontal bar
display: none!important;
}
}
.content-wrapper {
width: 100%;
transition: padding $sidebar-transition-duration;
.container-fluid {
background: #fff;
@ -34,58 +39,52 @@
}
}
.sidebar-wrapper {
.sidebar-user {
padding: 15px;
position: absolute;
left: 0;
bottom: 0;
width: $sidebar_width;
overflow: hidden;
font-size: 16px;
line-height: 36px;
transition: width $sidebar-transition-duration, padding $sidebar-transition-duration;
.sidebar-user {
padding: 15px 22px;
position: fixed;
bottom: 0;
width: $sidebar_width;
overflow: hidden;
transition-duration: .3s;
.username {
margin-left: 10px;
width: $sidebar_width - 2 * 10px;
font-size: 16px;
line-height: 34px;
}
@media (min-width: $sidebar-breakpoint) {
bottom: 50px;
}
}
.tanuki-shape {
transition: all 0.8s;
&:hover, &.highlight {
fill: rgb(255, 255, 255);
transition: all 0.1s;
}
}
.nav-sidebar {
margin-top: 22 + $header-height;
margin-bottom: 116px;
transition-duration: .3s;
list-style: none;
overflow: hidden;
position: absolute;
top: 50px;
bottom: 65px;
width: $sidebar_width;
overflow-y: auto;
overflow-x: hidden;
@media (min-width: $sidebar-breakpoint) {
bottom: 115px;
}
&.navbar-collapse {
padding: 0 !important;
}
li {
width: $sidebar_width;
&.separate-item {
padding-top: 10px;
margin-top: 10px;
}
.icon-container {
width: 34px;
display: inline-block;
text-align: center;
}
a {
width: $sidebar_width;
padding: 7px 15px 7px 23px;
padding: 7px 15px 7px 12px;
font-size: $gl-font-size;
line-height: 24px;
display: block;
@ -93,11 +92,9 @@
font-weight: normal;
outline: none;
&:hover {
text-decoration: none;
}
&:active, &:focus {
&:hover,
&:active,
&:focus {
text-decoration: none;
}
@ -109,10 +106,6 @@
svg {
margin-right: 13px;
}
&.back-link i {
transition-duration: .3s;
}
}
}
@ -123,37 +116,50 @@
}
}
.sidebar-subnav {
margin-left: 0;
padding-left: 0;
li {
list-style: none;
}
}
.collapse-nav a {
.toggle-nav-collapse {
width: $sidebar_width;
position: fixed;
position: absolute;
top: 0;
left: 0;
min-height: 50px;
padding: 5px 0;
font-size: 18px;
background: transparent;
height: 50px;
text-align: center;
line-height: 40px;
transition-duration: .3s;
outline: none;
line-height: 30px;
}
&:hover {
.nav-header-btn {
padding: 10px 5px;
color: inherit;
transition-duration: .3s;
&:hover,
&:focus {
color: $white-light;
text-decoration: none;
}
}
.sidebar-wrapper {
&.hidden-nav {
width: 0;
.pin-nav-btn {
display: none;
position: absolute;
left: 0;
bottom: 0;
height: 50px;
width: $sidebar_width;
line-height: 30px;
@media (min-width: $sidebar-breakpoint) {
display: block;
}
.fa {
transition: transform .15s;
}
&.is-active {
.fa {
transform: rotate(90deg);
}
}
}
@ -162,62 +168,34 @@
.sidebar-wrapper {
width: 0;
.nav-sidebar {
width: 0;
li {
width: auto;
a {
span {
display: none;
}
}
}
}
.collapse-nav a {
width: 0;
i {
display: none;
}
}
.sidebar-user {
width: 0;
padding-left: 0;
padding-right: 0;
.username {
display: none;
}
}
}
}
.page-sidebar-expanded {
@media (max-width: $screen-sm-max) {
padding-left: 0;
}
.sidebar-wrapper {
width: $sidebar_width;
}
}
.nav-sidebar {
width: $sidebar_width;
.page-sidebar-pinned {
.content-wrapper,
.layout-nav {
@media (min-width: $sidebar-breakpoint) {
padding-left: $sidebar_width;
}
}
}
header.header-pinned-nav {
@media (min-width: $sidebar-breakpoint) {
padding-left: ($sidebar_width + $gl-padding);
.side-nav-toggle {
display: none;
}
.nav-sidebar li a {
width: $sidebar_width;
&.back-link {
i {
opacity: 0;
}
}
.header-content {
padding-left: 0;
}
}
}

View File

@ -6,6 +6,8 @@ $sidebar_width: 220px;
$gutter_collapsed_width: 62px;
$gutter_width: 290px;
$gutter_inner_width: 258px;
$sidebar-transition-duration: .15s;
$sidebar-breakpoint: 1440px;
/*
* UI elements
@ -154,6 +156,11 @@ $warning-message-border: #f0e2bb;
/* header */
$light-grey-header: #faf9f9;
/* tanuki logo colors */
$tanuki-red: #e24329;
$tanuki-orange: #fc6d26;
$tanuki-yellow: #fca326;
/*
* State colors:
*/
@ -261,5 +268,10 @@ $calendar-hover-bg: #ecf3fe;
$calendar-border-color: rgba(#000, .1);
$calendar-unselectable-bg: #faf9f9;
/*
* Personal Access Tokens
*/
$personal-access-tokens-disabled-label-color: #bbb;
$ci-output-bg: #1d1f21;
$ci-text-color: #c5c8c6;

View File

@ -38,6 +38,10 @@ table {
margin: 0 auto;
text-align: left;
width: 600px;
& > td {
text-align: center;
}
}
&#body {

View File

@ -26,6 +26,8 @@
.commit-info-row {
margin-bottom: 10px;
line-height: 24px;
padding-top: 6px;
&.commit-info-row-header {
line-height: 34px;

View File

@ -7,84 +7,119 @@
margin-right: 9px;
}
.lists-separator {
margin: 10px 0;
border-color: #ddd;
}
.commit-header {
padding: 5px 10px;
background-color: $background-color;
border-top: 1px solid #eee;
border-bottom: 1px solid #eee;
font-size: 14px;
.commits-row {
ul {
margin: 0;
li.commit {
padding: 8px 0;
}
}
.commits-row-date {
font-size: 15px;
line-height: 20px;
margin-bottom: 5px;
&:first-child {
border-top-width: 0;
}
}
li.commit {
list-style: none;
.commit-row-title {
line-height: 1;
margin-bottom: 7px;
.commit-row-title {
font-size: $list-font-size;
line-height: 20px;
margin-bottom: 2px;
.notes_count {
float: right;
margin-right: 10px;
}
.btn-clipboard {
margin-top: -1px;
.str-truncated {
max-width: 70%;
}
.commit-row-message {
color: $gl-dark-link-color;
}
.text-expander {
display: inline-block;
background: $gray-light;
color: $gl-placeholder-color;
padding: 0 5px;
cursor: pointer;
border: 1px solid $border-gray-dark;
border-radius: $border-radius-default;
margin-left: 5px;
&:hover {
background-color: darken($gray-light, 10%);
text-decoration: none;
}
}
}
.notes_count {
float: right;
margin-right: 10px;
.commit-actions {
@media (min-width: $screen-sm-min) {
float: right;
margin-left: $gl-padding;
margin-top: 2px;
font-size: 0;
}
.btn-transparent {
padding-left: 0;
padding-right: 0;
}
.btn {
&:not(:first-child) {
margin-left: $gl-padding;
}
}
}
.commit_short_id {
min-width: 65px;
color: $gl-dark-link-color;
font-family: $monospace_font;
.commit-short-id {
font-family: $monospace_font;
font-weight: 600;
}
.commit {
padding: 10px 0;
position: relative;
@media (min-width: $screen-sm-min) {
padding-left: 20px;
.commit-info-block {
padding-left: 44px;
}
}
.str-truncated {
max-width: 70%;
}
&:not(:last-child) {
border-bottom: 1px solid #eee;
}
.commit-row-message {
color: $gl-dark-link-color;
a,
button {
color: $gl-dark-link-color;
vertical-align: baseline;
}
&:hover {
text-decoration: underline;
}
}
.text-expander {
background: #eee;
color: #555;
padding: 0 5px;
cursor: pointer;
margin-left: 4px;
&:hover {
background-color: #ddd;
}
}
.avatar {
position: absolute;
top: 10px;
left: 16px;
}
.item-title {
display: inline-block;
max-width: 70%;
@media (min-width: $screen-sm-min) {
max-width: 70%;
}
}
.commit-row-description {
font-size: 14px;
border-left: 1px solid #eee;
padding: 10px 15px;
margin: 5px 0 10px 5px;
margin: 10px 0;
background: #f9f9f9;
display: none;
@ -93,6 +128,7 @@ li.commit {
background: inherit;
padding: 0;
margin: 0;
white-space: pre-wrap;
}
a {
@ -102,7 +138,7 @@ li.commit {
.commit-row-info {
color: $gl-gray;
line-height: 24px;
line-height: 1;
a {
color: $gl-gray;
@ -111,10 +147,6 @@ li.commit {
.avatar {
margin-right: 8px;
}
.committed_ago {
display: inline-block;
}
}
&.inline-commit {

View File

@ -4,6 +4,11 @@
margin-bottom: $gl-padding;
border-radius: 3px;
.commit-short-id {
font-family: $regular_font;
font-weight: 400;
}
.diff-header {
position: relative;
background: $background-color;

View File

@ -60,14 +60,14 @@
.encoding-selector,
.license-selector,
.gitignore-selector {
.gitignore-selector,
.gitlab-ci-yml-selector {
display: inline-block;
vertical-align: top;
font-family: $regular_font;
}
.gitignore-selector {
.gitignore-selector, .license-selector, .gitlab-ci-yml-selector {
.dropdown {
line-height: 21px;
}
@ -77,4 +77,10 @@
width: 220px;
}
}
.gitlab-ci-yml-selector {
.dropdown-menu-toggle {
width: 250px;
}
}
}

View File

@ -0,0 +1,5 @@
.environments {
.commit-title {
margin: 0;
}
}

View File

@ -54,6 +54,10 @@
}
}
code {
white-space: pre-wrap;
}
pre {
border: none;
background: #f9f9f9;
@ -136,9 +140,10 @@
.event-last-push {
overflow: auto;
width: 100%;
.event-last-push-text {
@include str-truncated(100%);
padding: 5px 0;
padding: 4px 0;
font-size: 13px;
float: left;
margin-right: -150px;

View File

@ -39,3 +39,20 @@
}
}
}
.groups-cover-block {
.container-fluid {
position: relative;
}
.access-request-button {
@include btn-gray;
position: absolute;
right: 16px;
bottom: 32px;
padding: 3px 10px;
text-transform: none;
background-color: $background-color;
}
}

View File

@ -57,4 +57,11 @@
.documentation {
padding: 7px;
// Border around images in the help pages.
img:not(.emoji) {
border: 1px solid $table-border-gray;
padding: 5px;
margin: 5px;
}
}

View File

@ -4,6 +4,13 @@
margin-right: 1px;
}
}
// Border around images in issue and MR descriptions.
.description img:not(.emoji) {
border: 1px solid $table-border-gray;
padding: 5px;
margin: 5px;
}
}
.issuable-filter-count {
@ -34,6 +41,10 @@
color: inherit;
}
.issuable-header-text {
margin-top: 7px;
}
.block {
@include clearfix;
padding: $gl-padding 0;
@ -60,10 +71,6 @@
margin-top: 0;
}
.issuable-count {
margin-top: 7px;
}
.gutter-toggle {
margin-left: 20px;
padding-left: 10px;
@ -145,7 +152,6 @@
.assign-yourself {
margin-top: 10px;
font-weight: normal;
display: block;
}
}
@ -158,6 +164,10 @@
font-weight: normal;
}
.no-value {
color: $gl-placeholder-color;
}
.sidebar-collapsed-icon {
display: none;
}
@ -248,11 +258,16 @@
padding-bottom: 0;
margin-bottom: 10px;
}
.issuable-header-btn {
display: none;
}
}
.issuable-pager {
.issuable-header-btn {
background: $gray-normal;
border: 1px solid $border-gray-normal;
&:hover {
background: $gray-dark;
border: 1px solid $border-gray-dark;
@ -263,7 +278,7 @@
}
}
a:not(.issuable-pager) {
a {
&:hover {
color: $md-link-color;
text-decoration: none;
@ -322,7 +337,7 @@
margin-left: 5px;
a {
color: #8c8c8c;
color: $gl-placeholder-color;
}
}

View File

@ -50,11 +50,10 @@
.label-row {
.label-name {
display: block;
display: inline-block;
margin-bottom: 10px;
@media (min-width: $screen-sm-min) {
display: inline-block;
width: 200px;
margin-bottom: 0;
}
@ -63,6 +62,7 @@
.label-description {
display: block;
margin-bottom: 10px;
margin-left: 50px;
@media (min-width: $screen-sm-min) {
display: inline-block;
@ -115,6 +115,13 @@
}
}
.draggable-handler {
display: inline-block;
opacity: 0;
transition: opacity .3s;
color: $gray-darkest;
}
.prioritized-labels {
margin-bottom: 30px;
@ -122,6 +129,13 @@
display: none;
color: $gray-light;
}
li:hover {
.draggable-handler {
display: inline-block;
opacity: 1;
}
}
}
.other-labels {

View File

@ -119,7 +119,12 @@
margin-bottom: 0;
}
@media (max-width: $screen-sm-max) {
.btn-grouped {
margin-left: 0;
margin-right: 7px;
}
@media (max-width: $screen-xs-max) {
h4 {
font-size: 15px;
}
@ -131,10 +136,14 @@
.btn,
.btn-group,
.accept-action {
width: 100%;
margin-bottom: 4px;
}
.accept-action {
width: 100%;
text-align: center;
}
.accept-control {
width: 100%;
text-align: center;
@ -244,6 +253,10 @@
.panel-footer {
padding: 5px 10px;
.btn {
min-width: auto;
}
}
.commit {
@ -252,9 +265,7 @@
}
.avatar {
width: 20px;
height: 20px;
margin-right: 5px;
margin-left: 0;
}
.commit-row-info {
@ -282,7 +293,7 @@
margin-bottom: 0;
}
@media (min-width: $screen-sm-min) {
@media (min-width: $screen-xs-min) {
float: left;
width: 50%;
margin-bottom: 0;
@ -313,3 +324,13 @@
}
}
}
.merged-buttons {
.btn {
float: left;
&:not(:last-child) {
margin-right: 10px;
}
}
}

View File

@ -179,6 +179,10 @@
border-top: 1px solid $border-color;
}
.md-helper {
padding-top: 10px;
}
.toolbar-button {
padding: 0;
background: none;
@ -219,3 +223,16 @@
float: left;
}
}
.note-form-actions {
@media (max-width: $screen-xs-max) {
.btn {
float: none;
width: 100%;
&:not(:last-child) {
margin-bottom: 10px;
}
}
}
}

View File

@ -84,24 +84,14 @@ ul.notes {
word-wrap: break-word;
@include md-typography;
// Reset ul style types since we're nested inside a ul already
@include bulleted-list;
// On diffs code should wrap nicely and not overflow
code {
white-space: pre-wrap;
}
// Reset ul style types since we're nested inside a ul already
& > ul {
list-style-type: disc;
ul {
list-style-type: circle;
ul {
list-style-type: square;
}
}
}
ul.task-list {
ul:not(.task-list) {
padding-left: 1.3em;
@ -117,6 +107,13 @@ ul.notes {
code {
word-break: keep-all;
}
// Border around images in issue and MR comments.
img:not(.emoji) {
border: 1px solid $table-border-gray;
padding: 5px;
margin: 5px 0;
}
}
}
@ -139,6 +136,12 @@ ul.notes {
@media (min-width: $screen-sm-min) {
padding-right: 0;
}
@media (max-width: $screen-xs-min) {
.inline {
display: block;
}
}
}
.note-emoji-button {
@ -258,7 +261,11 @@ ul.notes {
position: absolute;
right: 0;
top: 0;
.note-action-button {
margin-left: 10px;
}
@media (min-width: $screen-sm-min) {
position: relative;
}

View File

@ -192,6 +192,25 @@
}
}
.personal-access-tokens-never-expires-label {
color: $personal-access-tokens-disabled-label-color;
}
.datepicker.personal-access-tokens-expires-at .ui-state-disabled span {
text-align: center;
}
.created-personal-access-token-container {
#created-personal-access-token {
width: 90%;
display: inline;
}
.btn-clipboard {
margin-left: 5px;
}
}
.user-profile {
@media (max-width: $screen-xs-max) {
.cover-block {

View File

@ -5,10 +5,12 @@
font-weight: normal;
}
}
.no-ssh-key-message, .project-limit-message {
background-color: #f28d35;
margin-bottom: 0;
}
.new_project,
.edit-project {
fieldset.features {
@ -18,13 +20,6 @@
}
}
.project-name-holder {
.help-inline {
vertical-align: top;
padding: 7px;
}
}
.project-home-panel {
background: $white-light;
text-align: left;
@ -33,7 +28,7 @@
.container-fluid {
position: relative;
@media (min-width: $screen-md-max) {
@media (min-width: $screen-lg-min) {
.row {
display: flex;
-ms-flex-align: center;
@ -106,7 +101,8 @@
.notifications-btn {
.fa-bell {
.fa-bell,
.fa-spinner {
margin-right: 6px;
}
@ -224,13 +220,20 @@
right: 16px;
bottom: 0;
.btn {
padding: 3px 10px;
background-color: $background-color;
@media (max-width: $screen-md-max) {
top: 0;
}
@media (max-width: 1304px) {
top: 0;
.access-request-button {
position: absolute;
right: 0;
bottom: 61px;
@media (max-width: $screen-md-max) {
position: relative;
bottom: 0;
margin-right: 10px;
}
}
}
@ -281,10 +284,6 @@
color: #555;
}
.project_member_row form {
margin: 0;
}
.transfer-project .select2-container {
min-width: 200px;
}
@ -368,13 +367,14 @@ a.deploy-project-label {
.project-import .btn {
float: left;
margin-bottom: 10px;
margin-right: 10px;
}
.project-stats {
margin-top: $gl-padding;
margin-bottom: 0;
padding: 16px 0;
padding: 0;
background-color: $white-light;
font-size: 0;
@ -383,13 +383,14 @@ a.deploy-project-label {
}
.nav li {
display: inline;
display: inline-block;
margin: 16px 0;
margin-right: 16px;
}
.nav > li > a {
background-color: transparent;
margin-right: 12px;
padding: 0 10px;
padding: 5px 10px;
font-size: 15px;
color: $notes-light-color;
}
@ -403,12 +404,17 @@ a.deploy-project-label {
font-size: 17px;
}
li.missing a {
color: #5a6069;
border: 1px dashed #dce0e5;
li.missing {
border: 1px dashed $border-gray-light;
border-radius: $border-radius-default;
a {
color: $notes-light-color;
display: block;
}
&:hover {
background-color: #f0f2f5;
background-color: $gray-normal;
}
}
@ -495,7 +501,8 @@ pre.light-well {
.activity-filter-block {
.controls {
padding-bottom: 10px;
padding-bottom: 7px;
margin-top: 8px;
border-bottom: 1px solid $border-color;
}
}
@ -616,3 +623,9 @@ pre.light-well {
color: $gl-text-green;
}
}
.project-refs-form {
.dropdown-menu {
width: 300px;
}
}

View File

@ -14,24 +14,38 @@
font-size: 10px;
}
#contributors-master {
@include make-md-column(12);
svg {
width: 100%;
}
}
#contributors {
.contributors-list {
margin: 0 0 10px;
list-style: none;
padding: 0;
svg {
width: 100%;
}
}
.person {
&:nth-child(even) {
float: right;
}
float: left;
@include make-md-column(6);
margin-top: 10px;
@media (max-width: $screen-sm-min) {
width: 100%;
}
}
.person .spark {
display: block;
background: #f3f3f3;
width: 100%;
}
.person .area-contributor {

View File

@ -62,6 +62,10 @@
}
}
code {
white-space: pre-wrap;
}
pre {
border: none;
background: #f9f9f9;

View File

@ -101,7 +101,7 @@
margin: 0;
.commit {
padding: 0;
padding: 0 0 0 55px;
.commit-row-title {
.commit-row-message {
@ -129,4 +129,6 @@
.tree-controls {
float: right;
margin-top: 11px;
position: relative;
z-index: 2;
}

View File

@ -5,6 +5,7 @@ class Admin::AppearancesController < Admin::ApplicationController
end
def preview
render 'preview', layout: 'devise'
end
def create

View File

@ -1,15 +1,14 @@
class Admin::RunnerProjectsController < Admin::ApplicationController
before_action :project, only: [:create]
def index
@runner_projects = project.runner_projects.all
@runner_project = project.runner_projects.new
end
def create
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
if @runner.assign_to(@project, current_user)
return head(403) if @runner.is_shared? || @runner.locked?
runner_project = @runner.assign_to(@project, current_user)
if runner_project.persisted?
redirect_to admin_runner_path(@runner)
else
redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project'

View File

@ -8,7 +8,7 @@ class ApplicationController < ActionController::Base
include PageLayoutHelper
include WorkhorseHelper
before_action :authenticate_user_from_token!
before_action :authenticate_user_from_private_token!
before_action :authenticate_user!
before_action :validate_user_service_ticket!
before_action :reject_blocked!
@ -24,7 +24,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
helper_method :abilities, :can?, :current_application_settings
helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?
helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?, :gitlab_project_import_enabled?
rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception)
@ -36,6 +36,10 @@ class ApplicationController < ActionController::Base
render_404
end
rescue_from Gitlab::Access::AccessDeniedError do |exception|
render_403
end
def redirect_back_or_default(default: root_path, options: {})
redirect_to request.referer.present? ? :back : default, options
end
@ -64,17 +68,10 @@ class ApplicationController < ActionController::Base
end
end
# From https://github.com/plataformatec/devise/wiki/How-To:-Simple-Token-Authentication-Example
# https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
def authenticate_user_from_token!
user_token = if params[:authenticity_token].presence
params[:authenticity_token].presence
elsif params[:private_token].presence
params[:private_token].presence
elsif request.headers['PRIVATE-TOKEN'].present?
request.headers['PRIVATE-TOKEN']
end
user = user_token && User.find_by_authentication_token(user_token.to_s)
# This filter handles both private tokens and personal access tokens
def authenticate_user_from_private_token!
token_string = params[:private_token].presence || request.headers['PRIVATE-TOKEN'].presence
user = User.find_by_authentication_token(token_string) || User.find_by_personal_access_token(token_string)
if user
# Notice we are passing store false, so the user is not
@ -326,6 +323,10 @@ class ApplicationController < ActionController::Base
current_application_settings.import_sources.include?('git')
end
def gitlab_project_import_enabled?
current_application_settings.import_sources.include?('gitlab_project')
end
def two_factor_authentication_required?
current_application_settings.require_two_factor_authentication
end

View File

@ -35,6 +35,7 @@ class AutocompleteController < ApplicationController
project = Project.find_by_id(params[:project_id])
projects = current_user.authorized_projects
projects = projects.search(params[:search]) if params[:search].present?
projects = projects.select do |project|
current_user.can?(:admin_issue, project)
end

View File

@ -0,0 +1,43 @@
module MembershipActions
extend ActiveSupport::Concern
include MembersHelper
def request_access
membershipable.request_access(current_user)
redirect_to polymorphic_path(membershipable),
notice: 'Your request for access has been queued for review.'
end
def approve_access_request
@member = membershipable.members.request.find(params[:id])
return render_403 unless can?(current_user, action_member_permission(:update, @member), @member)
@member.accept_request
redirect_to polymorphic_url([membershipable, :members])
end
def leave
@member = membershipable.members.find_by(user_id: current_user)
Members::DestroyService.new(@member, current_user).execute
source_type = @member.real_source_type.humanize(capitalize: false)
notice =
if @member.request?
"Your access request to the #{source_type} has been withdrawn."
else
"You left the \"#{@member.source.human_name}\" #{source_type}."
end
redirect_path = @member.request? ? @member.source : [:dashboard, @member.real_source_type.tableize]
redirect_to redirect_path, notice: notice
end
protected
def membershipable
raise NotImplementedError
end
end

View File

@ -1,44 +1,39 @@
class Dashboard::TodosController < Dashboard::ApplicationController
before_action :find_todos, only: [:index, :destroy, :destroy_all]
include TodosHelper
before_action :find_todos, only: [:index, :destroy_all]
def index
@todos = @todos.page(params[:page])
end
def destroy
todo.done
todo_notice = 'Todo was successfully marked as done.'
TodoService.new.mark_todos_as_done([todo], current_user)
respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: todo_notice }
format.html { redirect_to dashboard_todos_path, notice: 'Todo was successfully marked as done.' }
format.js { head :ok }
format.json do
render json: { count: @todos.size, done_count: current_user.todos.done.count }
end
format.json { render json: { count: todos_pending_count, done_count: todos_done_count } }
end
end
def destroy_all
@todos.each(&:done)
TodoService.new.mark_todos_as_done(@todos, current_user)
respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' }
format.js { head :ok }
format.json do
find_todos
render json: { count: @todos.size, done_count: current_user.todos.done.count }
end
format.json { render json: { count: todos_pending_count, done_count: todos_done_count } }
end
end
private
def todo
@todo ||= current_user.todos.find(params[:id])
@todo ||= find_todos.find(params[:id])
end
def find_todos
@todos = TodosFinder.new(current_user, params).execute
@todos ||= TodosFinder.new(current_user, params).execute
end
end

View File

@ -1,11 +1,13 @@
class Groups::GroupMembersController < Groups::ApplicationController
include MembershipActions
# Authorize
before_action :authorize_admin_group_member!, except: [:index, :leave]
before_action :authorize_admin_group_member!, except: [:index, :leave, :request_access]
def index
@project = @group.projects.find(params[:project_id]) if params[:project_id]
@members = @group.group_members
@members = @members.non_invite unless can?(current_user, :admin_group, @group)
@members = @members.non_pending unless can?(current_user, :admin_group, @group)
if params[:search].present?
users = @group.users.search(params[:search]).to_a
@ -34,9 +36,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
def destroy
@group_member = @group.group_members.find(params[:id])
return render_403 unless can?(current_user, :destroy_group_member, @group_member)
@group_member.destroy
Members::DestroyService.new(@group_member, current_user).execute
respond_to do |format|
format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' }
@ -58,25 +58,12 @@ class Groups::GroupMembersController < Groups::ApplicationController
end
end
def leave
@group_member = @group.group_members.find_by(user_id: current_user)
if can?(current_user, :destroy_group_member, @group_member)
@group_member.destroy
redirect_to(dashboard_groups_path, notice: "You left #{group.name} group.")
else
if @group.last_owner?(current_user)
redirect_to(dashboard_groups_path, alert: "You can not leave #{group.name} group because you're the last owner. Transfer or delete the group.")
else
return render_403
end
end
end
protected
def member_params
params.require(:group_member).permit(:access_level, :user_id)
end
# MembershipActions concern
alias_method :membershipable, :group
end

View File

@ -0,0 +1,48 @@
class Import::GitlabProjectsController < Import::BaseController
before_action :verify_gitlab_project_import_enabled
def new
@namespace_id = project_params[:namespace_id]
@namespace_name = Namespace.find(project_params[:namespace_id]).name
@path = project_params[:path]
end
def create
unless file_is_valid?
return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." })
end
@project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id],
current_user,
File.expand_path(project_params[:file].path),
project_params[:path]).execute
if @project.saved?
redirect_to(
project_path(@project),
notice: "Project '#{@project.name}' is being imported."
)
else
redirect_to(
new_import_gitlab_project_path,
alert: "Project could not be imported: #{@project.errors.full_messages.join(', ')}"
)
end
end
private
def file_is_valid?
project_params[:file] && project_params[:file].respond_to?(:read)
end
def verify_gitlab_project_import_enabled
render_404 unless gitlab_project_import_enabled?
end
def project_params
params.permit(
:path, :namespace_id, :file
)
end
end

View File

@ -39,7 +39,7 @@ class NotificationSettingsController < ApplicationController
def render_response
render json: {
html: view_to_html_string("notifications/buttons/_notifications", notification_setting: @notification_setting),
html: view_to_html_string("shared/notifications/_button", notification_setting: @notification_setting),
saved: @saved
}
end

View File

@ -5,7 +5,7 @@ class Profiles::AccountsController < Profiles::ApplicationController
def unlink
provider = params[:provider]
current_user.identities.find_by(provider: provider).destroy
current_user.identities.find_by(provider: provider).destroy unless provider.to_s == 'saml'
redirect_to profile_account_path
end
end

View File

@ -0,0 +1,42 @@
class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
before_action :load_personal_access_tokens, only: :index
def index
@personal_access_token = current_user.personal_access_tokens.build
end
def create
@personal_access_token = current_user.personal_access_tokens.generate(personal_access_token_params)
if @personal_access_token.save
flash[:personal_access_token] = @personal_access_token.token
redirect_to profile_personal_access_tokens_path, notice: "Your new personal access token has been created."
else
load_personal_access_tokens
render :index
end
end
def revoke
@personal_access_token = current_user.personal_access_tokens.find(params[:id])
if @personal_access_token.revoke!
flash[:notice] = "Revoked personal access token #{@personal_access_token.name}!"
else
flash[:alert] = "Could not revoke personal access token #{@personal_access_token.name}."
end
redirect_to profile_personal_access_tokens_path
end
private
def personal_access_token_params
params.require(:personal_access_token).permit(:name, :expires_at)
end
def load_personal_access_tokens
@active_personal_access_tokens = current_user.personal_access_tokens.active.order(:expires_at)
@inactive_personal_access_tokens = current_user.personal_access_tokens.inactive
end
end

View File

@ -74,7 +74,7 @@ class Projects::ApplicationController < ApplicationController
end
def require_branch_head
unless @repository.branch_names.include?(@ref)
unless @repository.branch_exists?(@ref)
redirect_to(
namespace_project_tree_path(@project.namespace, @project, @ref),
notice: "This action is not allowed unless you are on a branch"

View File

@ -1,22 +1,18 @@
class Projects::ArtifactsController < Projects::ApplicationController
layout 'project'
before_action :authorize_read_build!
before_action :authorize_update_build!, only: [:keep]
before_action :validate_artifacts!
def download
unless artifacts_file.file_storage?
return redirect_to artifacts_file.url
end
unless artifacts_file.exists?
return render_404
end
send_file artifacts_file.path, disposition: 'attachment'
end
def browse
return render_404 unless build.artifacts?
directory = params[:path] ? "#{params[:path]}/" : ''
@entry = build.artifacts_metadata_entry(directory)
@ -34,8 +30,17 @@ class Projects::ArtifactsController < Projects::ApplicationController
end
end
def keep
build.keep_artifacts!
redirect_to namespace_project_build_path(project.namespace, project, build)
end
private
def validate_artifacts!
render_404 unless build.artifacts?
end
def build
@build ||= project.builds.find_by!(id: params[:build_id])
end

View File

@ -51,7 +51,7 @@ class Projects::BuildsController < Projects::ApplicationController
return render_404
end
build = Ci::Build.retry(@build)
build = Ci::Build.retry(@build, current_user)
redirect_to build_path(build)
end

View File

@ -46,7 +46,7 @@ class Projects::CommitController < Projects::ApplicationController
def retry_builds
ci_builds.latest.failed.each do |build|
if build.retryable?
Ci::Build.retry(build)
Ci::Build.retry(build, current_user)
end
end

View File

@ -0,0 +1,49 @@
class Projects::EnvironmentsController < Projects::ApplicationController
layout 'project'
before_action :authorize_read_environment!
before_action :authorize_create_environment!, only: [:new, :create]
before_action :authorize_update_environment!, only: [:destroy]
before_action :environment, only: [:show, :destroy]
def index
@environments = project.environments
end
def show
@deployments = environment.deployments.order(id: :desc).page(params[:page])
end
def new
@environment = project.environments.new
end
def create
@environment = project.environments.create(create_params)
if @environment.persisted?
redirect_to namespace_project_environment_path(project.namespace, project, @environment)
else
render 'new'
end
end
def destroy
if @environment.destroy
flash[:notice] = 'Environment was successfully removed.'
else
flash[:alert] = 'Failed to remove environment.'
end
redirect_to namespace_project_environments_path(project.namespace, project)
end
private
def create_params
params.require(:environment).permit(:name)
end
def environment
@environment ||= project.environments.find(params[:id])
end
end

View File

@ -204,10 +204,19 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.update(merge_error: nil)
if params[:merge_when_build_succeeds].present? && @merge_request.pipeline && @merge_request.pipeline.active?
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
.execute(@merge_request)
@status = :merge_when_build_succeeds
if params[:merge_when_build_succeeds].present?
if @merge_request.pipeline && @merge_request.pipeline.active?
MergeRequests::MergeWhenBuildSucceedsService.new(@project, current_user, merge_params)
.execute(@merge_request)
@status = :merge_when_build_succeeds
elsif @merge_request.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)
@status = :success
else
@status = :failed
end
else
MergeWorker.perform_async(@merge_request.id, current_user.id, params)
@status = :success

Some files were not shown because too many files have changed in this diff Show More