mirror of
https://github.com/wahyd4/gitlabhq.git
synced 2026-08-22 02:56:06 +10:00
Merge remote-tracking branch 'origin/master' into ci-lfs-fetch
This commit is contained in:
+40
-46
@@ -7,7 +7,8 @@ services:
|
||||
cache:
|
||||
key: "ruby21"
|
||||
paths:
|
||||
- vendor
|
||||
- vendor/apt
|
||||
- vendor/ruby
|
||||
|
||||
variables:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
|
||||
@@ -91,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/
|
||||
@@ -130,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
|
||||
|
||||
|
||||
+1
-1
@@ -349,7 +349,7 @@ Style/MultilineArrayBraceLayout:
|
||||
|
||||
# Avoid multi-line chains of blocks.
|
||||
Style/MultilineBlockChain:
|
||||
Enabled: false
|
||||
Enabled: true
|
||||
|
||||
# Ensures newlines after multiline block do statements.
|
||||
Style/MultilineBlockLayout:
|
||||
|
||||
@@ -1,84 +1,156 @@
|
||||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
v 8.9.0 (unreleased)
|
||||
- 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.
|
||||
- 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 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.
|
||||
- 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
|
||||
- Don't show tags for revert and cherry-pick operations
|
||||
- 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
|
||||
- Fix SVG sanitizer to allow more elements
|
||||
- Allow forking projects with restricted visibility level
|
||||
- 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
|
||||
- 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 `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
|
||||
- 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
|
||||
- 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
|
||||
- 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
|
||||
- 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
|
||||
- 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
|
||||
- Remove duplicated notification settings
|
||||
- Put project Files and Commits tabs under Code tab
|
||||
- Decouple global notification level from user model
|
||||
- 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
|
||||
- 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
|
||||
- Use Git cached counters for branches and tags on project page
|
||||
- Filter parameters for request_uri value on instrumented transactions.
|
||||
- Cache user todo counts from TodoService
|
||||
- Ensure Todos counters doesn't count Todos for projects pending delete
|
||||
|
||||
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
|
||||
@@ -194,6 +266,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)
|
||||
@@ -356,6 +431,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
|
||||
@@ -510,6 +590,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
|
||||
@@ -671,6 +755,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
|
||||
@@ -807,6 +895,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
|
||||
@@ -925,6 +1017,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
|
||||
|
||||
@@ -52,7 +52,7 @@ gem "browser", '~> 2.0.3'
|
||||
|
||||
# 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'
|
||||
@@ -245,7 +247,7 @@ end
|
||||
|
||||
group :development do
|
||||
gem "foreman"
|
||||
gem 'brakeman', '~> 3.2.0', require: false
|
||||
gem 'brakeman', '~> 3.3.0', require: false
|
||||
|
||||
gem 'letter_opener_web', '~> 1.3.0'
|
||||
gem 'quiet_assets', '~> 1.0.2'
|
||||
|
||||
+15
-31
@@ -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)
|
||||
@@ -97,16 +97,7 @@ GEM
|
||||
bootstrap-sass (3.3.6)
|
||||
autoprefixer-rails (>= 5.2.1)
|
||||
sass (>= 3.3.4)
|
||||
brakeman (3.2.1)
|
||||
erubis (~> 2.6)
|
||||
haml (>= 3.0, < 5.0)
|
||||
highline (>= 1.6.20, < 2.0)
|
||||
ruby2ruby (~> 2.3.0)
|
||||
ruby_parser (~> 3.8.1)
|
||||
safe_yaml (>= 1.0)
|
||||
sass (~> 3.0)
|
||||
slim (>= 1.3.6, < 4.0)
|
||||
terminal-table (~> 1.4)
|
||||
brakeman (3.3.2)
|
||||
browser (2.0.3)
|
||||
builder (3.2.2)
|
||||
bullet (5.0.0)
|
||||
@@ -133,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)
|
||||
@@ -253,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)
|
||||
@@ -284,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)
|
||||
@@ -338,7 +331,6 @@ GEM
|
||||
hashie (3.4.3)
|
||||
health_check (1.5.1)
|
||||
rails (>= 2.3.0)
|
||||
highline (1.7.8)
|
||||
hipchat (1.5.2)
|
||||
httparty
|
||||
mimemagic
|
||||
@@ -408,7 +400,7 @@ GEM
|
||||
mime-types (>= 1.16, < 4)
|
||||
mail_room (0.7.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)
|
||||
@@ -424,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)
|
||||
@@ -563,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)
|
||||
@@ -642,10 +634,7 @@ GEM
|
||||
ruby-saml (1.1.2)
|
||||
nokogiri (>= 1.5.10)
|
||||
uuid (~> 2.3)
|
||||
ruby2ruby (2.3.0)
|
||||
ruby_parser (~> 3.1)
|
||||
sexp_processor (~> 4.0)
|
||||
ruby_parser (3.8.1)
|
||||
ruby_parser (3.8.2)
|
||||
sexp_processor (~> 4.1)
|
||||
rubyntlm (0.5.2)
|
||||
rubypants (0.2.0)
|
||||
@@ -655,7 +644,7 @@ GEM
|
||||
safe_yaml (1.0.4)
|
||||
sanitize (2.1.0)
|
||||
nokogiri (>= 1.4.4)
|
||||
sass (3.4.21)
|
||||
sass (3.4.22)
|
||||
sass-rails (5.0.4)
|
||||
railties (>= 4.0.0, < 5.0)
|
||||
sass (~> 3.1)
|
||||
@@ -704,9 +693,6 @@ GEM
|
||||
tilt (>= 1.3, < 3)
|
||||
six (0.2.0)
|
||||
slack-notifier (1.2.1)
|
||||
slim (3.0.6)
|
||||
temple (~> 0.7.3)
|
||||
tilt (>= 1.3.3, < 2.1)
|
||||
slop (3.6.0)
|
||||
spinach (0.8.10)
|
||||
colorize
|
||||
@@ -747,10 +733,8 @@ GEM
|
||||
railties (>= 3.2.5, < 6)
|
||||
teaspoon-jasmine (2.2.0)
|
||||
teaspoon (>= 1.0.0)
|
||||
temple (0.7.6)
|
||||
term-ansicolor (1.3.2)
|
||||
tins (~> 1.0)
|
||||
terminal-table (1.5.2)
|
||||
test_after_commit (0.4.2)
|
||||
activerecord (>= 3.2)
|
||||
thin (1.6.4)
|
||||
@@ -759,7 +743,7 @@ GEM
|
||||
rack (~> 1.0)
|
||||
thor (0.19.1)
|
||||
thread_safe (0.3.5)
|
||||
tilt (2.0.2)
|
||||
tilt (2.0.5)
|
||||
timecop (0.8.1)
|
||||
timfel-krb5-auth (0.8.3)
|
||||
tinder (1.10.1)
|
||||
@@ -848,7 +832,7 @@ DEPENDENCIES
|
||||
better_errors (~> 1.0.1)
|
||||
binding_of_caller (~> 0.7.2)
|
||||
bootstrap-sass (~> 3.3.0)
|
||||
brakeman (~> 3.2.0)
|
||||
brakeman (~> 3.3.0)
|
||||
browser (~> 2.0.3)
|
||||
bullet
|
||||
bundler-audit
|
||||
@@ -857,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)
|
||||
@@ -881,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)
|
||||
@@ -889,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)
|
||||
@@ -952,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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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,10 @@ window.onload = ->
|
||||
setTimeout shiftWindow, 100
|
||||
|
||||
$ ->
|
||||
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", ->
|
||||
@@ -162,19 +159,6 @@ $ ->
|
||||
$el.data('placement') || 'bottom'
|
||||
)
|
||||
|
||||
$('.header-logo .home').tooltip(
|
||||
placement: (_, el) ->
|
||||
$el = $(el)
|
||||
if $('.page-with-sidebar').hasClass('page-sidebar-collapsed') then 'right' else 'bottom'
|
||||
container: 'body'
|
||||
)
|
||||
|
||||
$('.page-with-sidebar').tooltip(
|
||||
selector: '.sidebar-collapsed .nav-sidebar a, .sidebar-collapsed a.sidebar-user'
|
||||
placement: 'right'
|
||||
container: 'body'
|
||||
)
|
||||
|
||||
# Form submitter
|
||||
$('.trigger-submit').on 'change', ->
|
||||
$(@).parents('form').submit()
|
||||
@@ -207,6 +191,7 @@ $ ->
|
||||
|
||||
$('.navbar-toggle').on 'click', ->
|
||||
$('.header-content .title').toggle()
|
||||
$('.header-content .header-logo').toggle()
|
||||
$('.header-content .navbar-collapse').toggle()
|
||||
$('.navbar-toggle').toggleClass('active')
|
||||
$('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
|
||||
@@ -241,7 +226,6 @@ $ ->
|
||||
$this.attr 'value', $this.val()
|
||||
|
||||
$sidebarGutterToggle = $('.js-sidebar-toggle')
|
||||
$navIconToggle = $('.toggle-nav-collapse')
|
||||
|
||||
$(document)
|
||||
.off 'breakpoint:change'
|
||||
@@ -251,10 +235,6 @@ $ ->
|
||||
if $gutterIcon.hasClass('fa-angle-double-right')
|
||||
$sidebarGutterToggle.trigger('click')
|
||||
|
||||
$navIcon = $navIconToggle.find('.fa')
|
||||
if $navIcon.hasClass('fa-angle-left')
|
||||
$navIconToggle.trigger('click')
|
||||
|
||||
fitSidebarForSize = ->
|
||||
oldBootstrapBreakpoint = bootstrapBreakpoint
|
||||
bootstrapBreakpoint = bp.getBreakpointSize()
|
||||
@@ -274,3 +254,31 @@ $ ->
|
||||
gl.awardsHandler = new AwardsHandler()
|
||||
checkInitialSidebarSize()
|
||||
new Aside()
|
||||
|
||||
# Sidenav pinning
|
||||
if $(window).width() < 1440 and $.cookie('pin_nav') is 'true'
|
||||
$.cookie('pin_nav', 'false')
|
||||
$('.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()
|
||||
|
||||
$(this).toggleClass 'is-active'
|
||||
|
||||
if $.cookie('pin_nav') is 'true'
|
||||
$.cookie 'pin_nav', 'false'
|
||||
$('.page-with-sidebar')
|
||||
.removeClass('page-sidebar-pinned')
|
||||
.toggleClass('page-sidebar-collapsed page-sidebar-expanded')
|
||||
$('.navbar-fixed-top')
|
||||
.removeClass('header-pinned-nav')
|
||||
.toggleClass('header-collapsed header-expanded')
|
||||
else
|
||||
$.cookie 'pin_nav', 'true'
|
||||
$('.page-with-sidebar').addClass('page-sidebar-pinned')
|
||||
$('.navbar-fixed-top').addClass('header-pinned-nav')
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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(@)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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(@)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -12,8 +12,9 @@ class @EditBlob
|
||||
$("#file-content").val(@editor.getValue())
|
||||
|
||||
@initModePanesAndLinks()
|
||||
new BlobLicenseSelector(@editor)
|
||||
new BlobGitignoreSelectors(editor: @editor)
|
||||
|
||||
new BlobLicenseSelectors { @editor }
|
||||
new BlobGitignoreSelectors { @editor }
|
||||
|
||||
initModePanesAndLinks: ->
|
||||
@$editModePanes = $(".js-edit-mode-pane")
|
||||
|
||||
@@ -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()
|
||||
@@ -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), ' ')
|
||||
|
||||
@@ -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,13 +73,12 @@ 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()
|
||||
|
||||
new NotificationsForm()
|
||||
new TreeView() if $('#tree-slider').length
|
||||
when 'groups:activity'
|
||||
new Activities()
|
||||
@@ -96,6 +100,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'
|
||||
@@ -125,19 +130,21 @@ class Dispatcher
|
||||
shortcut_handler = new ShortcutsDashboardNavigation()
|
||||
when 'profiles'
|
||||
new Profile()
|
||||
new NotificationsForm()
|
||||
new NotificationsDropdown()
|
||||
when 'projects'
|
||||
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'
|
||||
new Wikis()
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
@@ -146,9 +153,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
|
||||
|
||||
@@ -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 ''
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -302,6 +302,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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -102,6 +102,10 @@ class @IssuableForm
|
||||
return {
|
||||
results: data
|
||||
}
|
||||
data: (query) ->
|
||||
{
|
||||
search: query
|
||||
}
|
||||
formatResult: (project) ->
|
||||
project.name_with_namespace
|
||||
formatSelection: (project) ->
|
||||
|
||||
@@ -9,6 +9,9 @@ class @IssuableBulkActions
|
||||
|
||||
@bindEvents()
|
||||
|
||||
# Fixes bulk-assign not working when navigating through pages
|
||||
Issuable.initChecks();
|
||||
|
||||
getElement: (selector) ->
|
||||
@container.find selector
|
||||
|
||||
@@ -97,13 +100,22 @@ class @IssuableBulkActions
|
||||
$labels = @form.find('.labels-filter input[name="update[label_ids][]"]')
|
||||
|
||||
$labels.each (k, label) ->
|
||||
labelIds.push $(label).val() if label
|
||||
labelIds.push parseInt($(label).val()) if label
|
||||
|
||||
labelIds
|
||||
|
||||
###*
|
||||
* Just an alias of @getUnmarkedIndeterminedLabels
|
||||
* @return {Array} Array of labels
|
||||
* Returns Label IDs that will be removed from issue selection
|
||||
* @return {Array} Array of labels IDs
|
||||
###
|
||||
getLabelsToRemove: ->
|
||||
@getUnmarkedIndeterminedLabels()
|
||||
result = []
|
||||
indeterminatedLabels = @getUnmarkedIndeterminedLabels()
|
||||
labelsToApply = @getLabelsToApply()
|
||||
|
||||
indeterminatedLabels.map (id) ->
|
||||
# We need to exclude label IDs that will be applied
|
||||
# By not doing this will cause issues from selection to not add labels at all
|
||||
result.push(id) if labelsToApply.indexOf(id) is -1
|
||||
|
||||
result
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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', ->
|
||||
$('#js-shortcuts-home').get(0).click()
|
||||
|
||||
@@ -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', =>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
@@ -115,12 +115,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
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
class @NotificationsDropdown
|
||||
$ ->
|
||||
$(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('.notification-form:first')
|
||||
form.find('.js-notification-loading').toggleClass 'fa-bell fa-spin fa-spinner'
|
||||
form.find('#notification_setting_level').val(notificationLevel)
|
||||
form.submit()
|
||||
|
||||
$(document)
|
||||
.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)
|
||||
else
|
||||
new Flash('Failed to save new settings', 'alert')
|
||||
@@ -0,0 +1,49 @@
|
||||
class @NotificationsForm
|
||||
constructor: ->
|
||||
@removeEventListeners()
|
||||
@initEventListeners()
|
||||
|
||||
removeEventListeners: ->
|
||||
$(document).off 'change', '.js-custom-notification-event'
|
||||
|
||||
initEventListeners: ->
|
||||
$(document).on 'change', '.js-custom-notification-event', @toggleCheckbox
|
||||
|
||||
toggleCheckbox: (e) =>
|
||||
$checkbox = $(e.currentTarget)
|
||||
$parent = $checkbox.closest('.checkbox')
|
||||
@saveEvent($checkbox, $parent)
|
||||
|
||||
showCheckboxLoadingSpinner: ($parent) ->
|
||||
$parent
|
||||
.addClass 'is-loading'
|
||||
.find '.custom-notification-event-loading'
|
||||
.removeClass 'fa-check'
|
||||
.addClass 'fa-spin fa-spinner'
|
||||
.removeClass 'is-done'
|
||||
|
||||
saveEvent: ($checkbox, $parent) ->
|
||||
form = $parent.parents('form:first')
|
||||
|
||||
$.ajax(
|
||||
url: form.attr('action')
|
||||
method: form.attr('method')
|
||||
dataType: 'json'
|
||||
data: form.serialize()
|
||||
|
||||
beforeSend: =>
|
||||
@showCheckboxLoadingSpinner($parent)
|
||||
).done (data) ->
|
||||
$checkbox.enable()
|
||||
|
||||
if data.saved
|
||||
$parent
|
||||
.find '.custom-notification-event-loading'
|
||||
.toggleClass 'fa-spin fa-spinner fa-check is-done'
|
||||
|
||||
setTimeout(->
|
||||
$parent
|
||||
.removeClass 'is-loading'
|
||||
.find '.custom-notification-event-loading'
|
||||
.toggleClass 'fa-spin fa-spinner fa-check is-done'
|
||||
, 2000)
|
||||
@@ -8,6 +8,10 @@ class @Profile
|
||||
$('.js-preferences-form').on 'change.preference', 'input[type=radio]', ->
|
||||
$(this).parents('form').submit()
|
||||
|
||||
# Automatically submit email form when it changes
|
||||
$('#user_notification_email').on 'change', ->
|
||||
$(this).parents('form').submit()
|
||||
|
||||
$('.update-username').on 'ajax:before', ->
|
||||
$('.loading-username').show()
|
||||
$(this).find('.update-success').hide()
|
||||
|
||||
@@ -34,22 +34,6 @@ class @Project
|
||||
$(@).parents('.no-password-message').remove()
|
||||
e.preventDefault()
|
||||
|
||||
$('.update-notification').on 'click', (e) ->
|
||||
e.preventDefault()
|
||||
notification_level = $(@).data 'notification-level'
|
||||
label = $(@).data 'notification-title'
|
||||
$('#notification_setting_level').val(notification_level)
|
||||
$('#notification-form').submit()
|
||||
$('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>")
|
||||
$(@).parents('ul').find('li.active').removeClass 'active'
|
||||
$(@).parent().addClass 'active'
|
||||
|
||||
$('#notification-form').on 'ajax:success', (e, data) ->
|
||||
if data.saved
|
||||
new Flash("Notification settings saved", "notice")
|
||||
else
|
||||
new Flash("Failed to save new settings", "alert")
|
||||
|
||||
|
||||
@projectSelectDropdown()
|
||||
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -3,24 +3,35 @@ expanded = 'page-sidebar-expanded'
|
||||
|
||||
toggleSidebar = ->
|
||||
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
|
||||
$('header').toggleClass("header-collapsed header-expanded")
|
||||
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
|
||||
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
|
||||
$('.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()
|
||||
|
||||
toggleSidebar()
|
||||
)
|
||||
|
||||
$ ->
|
||||
size = bp.getBreakpointSize()
|
||||
|
||||
if size is "xs" or size is "sm"
|
||||
if $('.page-with-sidebar').hasClass(expanded)
|
||||
toggleSidebar()
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -91,6 +91,10 @@
|
||||
background-color: $white-light;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
&.top-block .container-fluid {
|
||||
background-color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.cover-block {
|
||||
|
||||
@@ -8,34 +8,16 @@
|
||||
*/
|
||||
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
|
||||
.page-with-sidebar {
|
||||
.header-logo {
|
||||
background: $color-darker;
|
||||
|
||||
a {
|
||||
color: $color-light;
|
||||
|
||||
h3 {
|
||||
color: $color-light;
|
||||
}
|
||||
}
|
||||
.toggle-nav-collapse,
|
||||
.pin-nav-btn {
|
||||
color: $color-light;
|
||||
background: $color;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-dark;
|
||||
a {
|
||||
color: $white-light;
|
||||
|
||||
h3 {
|
||||
color: $white-light;
|
||||
}
|
||||
}
|
||||
color: $white-light;
|
||||
}
|
||||
}
|
||||
|
||||
.collapse-nav a {
|
||||
color: $white-light;
|
||||
background: $color;
|
||||
}
|
||||
|
||||
.sidebar-wrapper {
|
||||
background: $color-darker;
|
||||
|
||||
|
||||
@@ -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,11 +114,7 @@ header {
|
||||
.header-content {
|
||||
position: relative;
|
||||
height: $header-height;
|
||||
padding-right: 40px;
|
||||
|
||||
@media (max-width: $screen-xs-min) {
|
||||
padding-left: 40px;
|
||||
}
|
||||
padding-left: 30px;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
padding-right: 0;
|
||||
@@ -122,9 +124,29 @@ header {
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
.header-logo {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
margin-left: -18px;
|
||||
top: 7px;
|
||||
transition-duration: .3s;
|
||||
z-index: 999;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
right: 25px;
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 19px;
|
||||
max-width: 400px;
|
||||
display: inline-block;
|
||||
line-height: $header-height;
|
||||
font-weight: normal;
|
||||
color: $gl-text-color;
|
||||
@@ -133,6 +155,10 @@ header {
|
||||
vertical-align: top;
|
||||
white-space: nowrap;
|
||||
|
||||
@media (max-width: $screen-sm-max) {
|
||||
max-width: 190px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $gl-text-color;
|
||||
&:hover {
|
||||
@@ -160,6 +186,10 @@ header {
|
||||
.navbar-collapse {
|
||||
float: right;
|
||||
border-top: none;
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,22 +202,24 @@ header {
|
||||
}
|
||||
}
|
||||
|
||||
.header-collapsed {
|
||||
margin-left: 0;
|
||||
#tanuki-logo {
|
||||
|
||||
.header-content {
|
||||
padding-left: 30px;
|
||||
transition-duration: .3s;
|
||||
#tanuki-left-ear,
|
||||
#tanuki-right-ear,
|
||||
#tanuki-nose {
|
||||
@include tanuki-logo-colors($tanuki-red);
|
||||
}
|
||||
}
|
||||
|
||||
.header-expanded {
|
||||
margin-left: 0;
|
||||
|
||||
.header-content {
|
||||
margin-left: $sidebar_width;
|
||||
transition-duration: .3s;
|
||||
#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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@mixin fade($gradient-direction, $rgba, $gradient-color) {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
width: 43px;
|
||||
@@ -68,10 +69,12 @@
|
||||
}
|
||||
|
||||
&.sub-nav {
|
||||
text-align: center;
|
||||
background-color: $background-color;
|
||||
|
||||
.container-fluid {
|
||||
background-color: $background-color;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
@@ -171,7 +174,6 @@
|
||||
> form {
|
||||
display: inline-block;
|
||||
margin-top: -1px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.icon-label {
|
||||
@@ -240,6 +242,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.adjust {
|
||||
.nav-text, .nav-controls {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-nav {
|
||||
@@ -249,7 +257,8 @@
|
||||
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 {
|
||||
position: relative;
|
||||
@@ -278,11 +287,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;
|
||||
@@ -345,6 +353,12 @@
|
||||
.badge {
|
||||
color: $gl-icon-color;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
a, i {
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,7 +366,7 @@
|
||||
|
||||
.fade-right {
|
||||
@media (min-width: $screen-xs-max) {
|
||||
right: 67px;
|
||||
right: 68px;
|
||||
}
|
||||
@media (max-width: $screen-xs-min) {
|
||||
right: 0;
|
||||
|
||||
@@ -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,83 +39,63 @@
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-wrapper {
|
||||
.header-logo {
|
||||
height: $header-height;
|
||||
padding: 8px 26px;
|
||||
width: $sidebar_width;
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
overflow: hidden;
|
||||
transition-duration: .3s;
|
||||
.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;
|
||||
|
||||
&:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-user {
|
||||
padding: 15px 22px;
|
||||
position: fixed;
|
||||
bottom: 40px;
|
||||
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;
|
||||
}
|
||||
|
||||
a {
|
||||
.icon-container {
|
||||
width: 34px;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
a {
|
||||
padding: 7px 15px 7px 12px;
|
||||
font-size: $gl-font-size;
|
||||
color: $gray;
|
||||
line-height: 24px;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
font-weight: normal;
|
||||
outline: none;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:active, &:focus {
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -118,49 +103,64 @@
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.nav-link-text {
|
||||
margin-top: 3px;
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
&.back-link i {
|
||||
transition-duration: .3s;
|
||||
i,
|
||||
svg {
|
||||
margin-right: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-subnav {
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
.count {
|
||||
float: right;
|
||||
padding: 0 8px;
|
||||
@include border-radius(6px);
|
||||
}
|
||||
}
|
||||
|
||||
.collapse-nav a {
|
||||
.toggle-nav-collapse {
|
||||
width: $sidebar_width;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-size: 13px;
|
||||
background: transparent;
|
||||
height: 40px;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
transition-duration: .3s;
|
||||
outline: none;
|
||||
min-height: 50px;
|
||||
padding: 5px 0;
|
||||
font-size: 18px;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,90 +169,34 @@
|
||||
|
||||
.sidebar-wrapper {
|
||||
width: 0;
|
||||
|
||||
.header-logo {
|
||||
width: 0;
|
||||
padding: 8px 0;
|
||||
|
||||
a {
|
||||
padding-left: ($sidebar_collapsed_width - 36) / 2;
|
||||
|
||||
.gitlab-text-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-sidebar {
|
||||
width: $sidebar_collapsed_width;
|
||||
|
||||
li {
|
||||
width: auto;
|
||||
|
||||
a {
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.collapse-nav a {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.sidebar-user {
|
||||
width: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
|
||||
.username {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-sidebar-expanded {
|
||||
padding-left: $sidebar_width;
|
||||
|
||||
@media (max-width: $screen-xs-min) {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.sidebar-wrapper {
|
||||
width: $sidebar_width;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-sidebar {
|
||||
width: $sidebar_width;
|
||||
}
|
||||
|
||||
.nav-sidebar li a {
|
||||
width: $sidebar_width;
|
||||
|
||||
&.back-link {
|
||||
i {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
.page-sidebar-pinned {
|
||||
.content-wrapper,
|
||||
.layout-nav {
|
||||
@media (min-width: $sidebar-breakpoint) {
|
||||
padding-left: $sidebar_width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-nav {
|
||||
@media (max-width: $screen-xs-min) {
|
||||
padding-right: 0;
|
||||
header.header-pinned-nav {
|
||||
@media (min-width: $sidebar-breakpoint) {
|
||||
padding-left: ($sidebar_width + $gl-padding);
|
||||
|
||||
.side-nav-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (min-width: $screen-xs-min) and (max-width: $screen-md-min) {
|
||||
padding-right: 90px;
|
||||
}
|
||||
|
||||
@media (min-width: $screen-md-min) {
|
||||
padding-right: $sidebar_width;
|
||||
.header-content {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
* Layout
|
||||
*/
|
||||
$sidebar_collapsed_width: 62px;
|
||||
$sidebar_width: 90px;
|
||||
$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;
|
||||
|
||||
@@ -38,6 +38,10 @@ table {
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
width: 600px;
|
||||
|
||||
& > td {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&#body {
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
.commit-info-row {
|
||||
margin-bottom: 10px;
|
||||
line-height: 24px;
|
||||
padding-top: 6px;
|
||||
|
||||
&.commit-info-row-header {
|
||||
line-height: 34px;
|
||||
|
||||
@@ -7,84 +7,111 @@
|
||||
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;
|
||||
}
|
||||
|
||||
.str-truncated {
|
||||
max-width: 70%;
|
||||
}
|
||||
.commit {
|
||||
padding: 10px 0;
|
||||
|
||||
.commit-row-message {
|
||||
color: $gl-dark-link-color;
|
||||
@media (min-width: $screen-sm-min) {
|
||||
padding-left: 46px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.text-expander {
|
||||
background: #eee;
|
||||
color: #555;
|
||||
padding: 0 5px;
|
||||
cursor: pointer;
|
||||
margin-left: 4px;
|
||||
&:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
}
|
||||
a,
|
||||
button {
|
||||
color: $gl-dark-link-color;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin-left: -46px;
|
||||
}
|
||||
|
||||
.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 +120,7 @@ li.commit {
|
||||
background: inherit;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -102,7 +130,7 @@ li.commit {
|
||||
|
||||
.commit-row-info {
|
||||
color: $gl-gray;
|
||||
line-height: 24px;
|
||||
line-height: 1;
|
||||
|
||||
a {
|
||||
color: $gl-gray;
|
||||
@@ -111,10 +139,6 @@ li.commit {
|
||||
.avatar {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.committed_ago {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
&.inline-commit {
|
||||
|
||||
@@ -66,8 +66,7 @@
|
||||
font-family: $regular_font;
|
||||
}
|
||||
|
||||
.gitignore-selector {
|
||||
|
||||
.gitignore-selector, .license-selector {
|
||||
.dropdown {
|
||||
line-height: 21px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
.environments {
|
||||
.commit-title {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@@ -136,9 +136,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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.issuable-header-text {
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.block {
|
||||
@include clearfix;
|
||||
padding: $gl-padding 0;
|
||||
@@ -60,10 +64,6 @@
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.issuable-count {
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.gutter-toggle {
|
||||
margin-left: 20px;
|
||||
padding-left: 10px;
|
||||
@@ -145,7 +145,6 @@
|
||||
|
||||
.assign-yourself {
|
||||
margin-top: 10px;
|
||||
font-weight: normal;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -158,6 +157,10 @@
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.no-value {
|
||||
color: $gl-placeholder-color;
|
||||
}
|
||||
|
||||
.sidebar-collapsed-icon {
|
||||
display: none;
|
||||
}
|
||||
@@ -248,11 +251,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 +271,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
a:not(.issuable-pager) {
|
||||
a {
|
||||
&:hover {
|
||||
color: $md-link-color;
|
||||
text-decoration: none;
|
||||
@@ -322,7 +330,7 @@
|
||||
margin-left: 5px;
|
||||
|
||||
a {
|
||||
color: #8c8c8c;
|
||||
color: $gl-placeholder-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -244,6 +244,10 @@
|
||||
|
||||
.panel-footer {
|
||||
padding: 5px 10px;
|
||||
|
||||
.btn {
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.commit {
|
||||
@@ -252,9 +256,7 @@
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-right: 5px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.commit-row-info {
|
||||
@@ -313,3 +315,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.merged-buttons {
|
||||
.btn {
|
||||
float: left;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +139,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 +264,11 @@ ul.notes {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
||||
|
||||
.note-action-button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
@@ -133,11 +128,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.btn-group:not(:first-child):not(:last-child) > .btn {
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
|
||||
form {
|
||||
margin-left: 10px;
|
||||
}
|
||||
@@ -229,13 +219,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,10 +283,6 @@
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.project_member_row form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.transfer-project .select2-container {
|
||||
min-width: 200px;
|
||||
}
|
||||
@@ -373,6 +366,7 @@ a.deploy-project-label {
|
||||
|
||||
.project-import .btn {
|
||||
float: left;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@@ -500,7 +494,8 @@ pre.light-well {
|
||||
|
||||
.activity-filter-block {
|
||||
.controls {
|
||||
padding-bottom: 10px;
|
||||
padding-bottom: 7px;
|
||||
margin-top: 8px;
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
}
|
||||
@@ -604,3 +599,20 @@ pre.light-well {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-notifications-form {
|
||||
.is-loading {
|
||||
.custom-notification-event-loading {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-notification-event-loading {
|
||||
display: none;
|
||||
margin-left: 5px;
|
||||
|
||||
&.is-done {
|
||||
color: $gl-text-green;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -64,17 +64,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 +319,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
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)
|
||||
return render_403 unless @member
|
||||
|
||||
source_type = @member.real_source_type.humanize(capitalize: false)
|
||||
|
||||
if can?(current_user, action_member_permission(:destroy, @member), @member)
|
||||
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
|
||||
@member.destroy
|
||||
|
||||
redirect_to [:dashboard, @member.real_source_type.tableize], notice: notice
|
||||
else
|
||||
if cannot_leave?
|
||||
alert = "You can not leave the \"#{@member.source.human_name}\" #{source_type}."
|
||||
alert << " Transfer or delete the #{source_type}."
|
||||
redirect_to polymorphic_url(membershipable), alert: alert
|
||||
else
|
||||
render_403
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def membershipable
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def cannot_leave?
|
||||
raise NotImplementedError
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -58,25 +60,16 @@ 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
|
||||
|
||||
def cannot_leave?
|
||||
@group.last_owner?(current_user)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
class Groups::NotificationSettingsController < Groups::ApplicationController
|
||||
before_action :authenticate_user!
|
||||
|
||||
def update
|
||||
notification_setting = current_user.notification_settings_for(group)
|
||||
saved = notification_setting.update_attributes(notification_setting_params)
|
||||
|
||||
render json: { saved: saved }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def notification_setting_params
|
||||
params.require(:notification_setting).permit(:level)
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
@@ -42,7 +42,7 @@ class JwtController < ApplicationController
|
||||
end
|
||||
|
||||
def authenticate_user(login, password)
|
||||
user = Gitlab::Auth.find_in_gitlab_or_ldap(login, password)
|
||||
user = Gitlab::Auth.find_with_user_password(login, password)
|
||||
Gitlab::Auth.rate_limit!(request.ip, success: user.present?, login: login)
|
||||
user
|
||||
end
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
class NotificationSettingsController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
|
||||
def create
|
||||
project = Project.find(params[:project][:id])
|
||||
|
||||
return render_404 unless can?(current_user, :read_project, project)
|
||||
|
||||
@notification_setting = current_user.notification_settings_for(project)
|
||||
@saved = @notification_setting.update_attributes(notification_setting_params)
|
||||
|
||||
render_response
|
||||
end
|
||||
|
||||
def update
|
||||
@notification_setting = current_user.notification_settings.find(params[:id])
|
||||
@saved = @notification_setting.update_attributes(notification_setting_params)
|
||||
|
||||
render_response
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render_response
|
||||
render json: {
|
||||
html: view_to_html_string("shared/notifications/_button", notification_setting: @notification_setting),
|
||||
saved: @saved
|
||||
}
|
||||
end
|
||||
|
||||
def notification_setting_params
|
||||
allowed_fields = NotificationSetting::EMAIL_EVENTS.dup
|
||||
allowed_fields << :level
|
||||
params.require(:notification_setting).permit(allowed_fields)
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
class Profiles::NotificationsController < Profiles::ApplicationController
|
||||
def show
|
||||
@user = current_user
|
||||
@group_notifications = current_user.notification_settings.for_groups
|
||||
@project_notifications = current_user.notification_settings.for_projects
|
||||
@user = current_user
|
||||
@group_notifications = current_user.notification_settings.for_groups.order(:id)
|
||||
@project_notifications = current_user.notification_settings.for_projects.order(:id)
|
||||
@global_notification_setting = current_user.global_notification_setting
|
||||
end
|
||||
|
||||
def update
|
||||
@@ -16,6 +17,6 @@ class Profiles::NotificationsController < Profiles::ApplicationController
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:notification_email, :notification_level)
|
||||
params.require(:user).permit(:notification_email)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -43,7 +43,7 @@ class Projects::GitHttpController < Projects::ApplicationController
|
||||
return if project && project.public? && upload_pack?
|
||||
|
||||
authenticate_or_request_with_http_basic do |login, password|
|
||||
auth_result = Gitlab::Auth.find(login, password, project: project, ip: request.ip)
|
||||
auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
|
||||
|
||||
if auth_result.type == :ci && upload_pack?
|
||||
@ci = true
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
class Projects::NotificationSettingsController < Projects::ApplicationController
|
||||
before_action :authenticate_user!
|
||||
|
||||
def update
|
||||
notification_setting = current_user.notification_settings_for(project)
|
||||
saved = notification_setting.update_attributes(notification_setting_params)
|
||||
|
||||
render json: { saved: saved }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def notification_setting_params
|
||||
params.require(:notification_setting).permit(:level)
|
||||
end
|
||||
end
|
||||
@@ -32,7 +32,7 @@ class Projects::PipelinesController < Projects::ApplicationController
|
||||
end
|
||||
|
||||
def retry
|
||||
pipeline.retry_failed
|
||||
pipeline.retry_failed(current_user)
|
||||
|
||||
redirect_back_or_default default: namespace_project_pipelines_path(project.namespace, project)
|
||||
end
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
class Projects::ProjectMembersController < Projects::ApplicationController
|
||||
include MembershipActions
|
||||
|
||||
# Authorize
|
||||
before_action :authorize_admin_project_member!, except: [:leave, :index]
|
||||
before_action :authorize_admin_project_member!, except: [:index, :leave, :request_access]
|
||||
|
||||
def index
|
||||
@project_members = @project.project_members
|
||||
@project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project)
|
||||
@project_members = @project_members.non_pending unless can?(current_user, :admin_project, @project)
|
||||
|
||||
if params[:search].present?
|
||||
users = @project.users.search(params[:search]).to_a
|
||||
@@ -14,9 +16,10 @@ class Projects::ProjectMembersController < Projects::ApplicationController
|
||||
@project_members = @project_members.order('access_level DESC')
|
||||
|
||||
@group = @project.group
|
||||
|
||||
if @group
|
||||
@group_members = @group.group_members
|
||||
@group_members = @group_members.non_invite unless can?(current_user, :admin_group, @group)
|
||||
@group_members = @group_members.non_pending unless can?(current_user, :admin_group, @group)
|
||||
|
||||
if params[:search].present?
|
||||
users = @group.users.search(params[:search]).to_a
|
||||
@@ -73,26 +76,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def leave
|
||||
@project_member = @project.project_members.find_by(user_id: current_user)
|
||||
|
||||
if can?(current_user, :destroy_project_member, @project_member)
|
||||
@project_member.destroy
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to dashboard_projects_path, notice: "You left the project." }
|
||||
format.js { head :ok }
|
||||
end
|
||||
else
|
||||
if current_user == @project.owner
|
||||
message = 'You can not leave your own project. Transfer or delete the project.'
|
||||
redirect_back_or_default(default: { action: 'index' }, options: { alert: message })
|
||||
else
|
||||
render_403
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def apply_import
|
||||
source_project = Project.find(params[:source_project_id])
|
||||
|
||||
@@ -112,4 +95,11 @@ class Projects::ProjectMembersController < Projects::ApplicationController
|
||||
def member_params
|
||||
params.require(:project_member).permit(:user_id, :access_level)
|
||||
end
|
||||
|
||||
# MembershipActions concern
|
||||
alias_method :membershipable, :project
|
||||
|
||||
def cannot_leave?
|
||||
current_user == @project.owner
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,8 +6,10 @@ class Projects::TagsController < Projects::ApplicationController
|
||||
before_action :authorize_admin_project!, only: [:destroy]
|
||||
|
||||
def index
|
||||
sorted = VersionSorter.rsort(@repository.tag_names)
|
||||
@tags = Kaminari.paginate_array(sorted).page(params[:page])
|
||||
@sort = params[:sort] || 'name'
|
||||
@tags = @repository.tags_sorted_by(@sort)
|
||||
@tags = Kaminari.paginate_array(@tags).page(params[:page])
|
||||
|
||||
@releases = project.releases.where(tag: @tags)
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
class Projects::TodosController < Projects::ApplicationController
|
||||
before_action :authenticate_user!, only: [:create]
|
||||
|
||||
def create
|
||||
todo = TodoService.new.mark_todo(issuable, current_user)
|
||||
|
||||
render json: {
|
||||
count: current_user.todos_pending_count,
|
||||
delete_path: dashboard_todo_path(todo)
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def issuable
|
||||
@issuable ||= begin
|
||||
case params[:issuable_type]
|
||||
when "issue"
|
||||
issue = @project.issues.find(params[:issuable_id])
|
||||
|
||||
if can?(current_user, :read_issue, issue)
|
||||
issue
|
||||
else
|
||||
render_404
|
||||
end
|
||||
when "merge_request"
|
||||
@project.merge_requests.find(params[:issuable_id])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -16,6 +16,9 @@ class Projects::WikisController < Projects::ApplicationController
|
||||
if @page
|
||||
render 'show'
|
||||
elsif file = @project_wiki.find_file(params[:id], params[:version_id])
|
||||
response.headers['Content-Security-Policy'] = "default-src 'none'"
|
||||
response.headers['X-Content-Security-Policy'] = "default-src 'none'"
|
||||
|
||||
if file.on_disk?
|
||||
send_file file.on_disk_path, disposition: 'inline'
|
||||
else
|
||||
|
||||
@@ -7,7 +7,7 @@ class ProjectsController < Projects::ApplicationController
|
||||
before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
|
||||
|
||||
# Authorize
|
||||
before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping]
|
||||
before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export]
|
||||
before_action :event_filter, only: [:show, :activity]
|
||||
|
||||
layout :determine_layout
|
||||
@@ -143,6 +143,7 @@ class ProjectsController < Projects::ApplicationController
|
||||
issues: autocomplete.issues,
|
||||
milestones: autocomplete.milestones,
|
||||
mergerequests: autocomplete.merge_requests,
|
||||
labels: autocomplete.labels,
|
||||
members: participants
|
||||
}
|
||||
|
||||
@@ -185,6 +186,48 @@ class ProjectsController < Projects::ApplicationController
|
||||
)
|
||||
end
|
||||
|
||||
def export
|
||||
@project.add_export_job(current_user: current_user)
|
||||
|
||||
redirect_to(
|
||||
edit_project_path(@project),
|
||||
notice: "Project export started. A download link will be sent by email."
|
||||
)
|
||||
end
|
||||
|
||||
def download_export
|
||||
export_project_path = @project.export_project_path
|
||||
|
||||
if export_project_path
|
||||
send_file export_project_path, disposition: 'attachment'
|
||||
else
|
||||
redirect_to(
|
||||
edit_project_path(@project),
|
||||
alert: "Project export link has expired. Please generate a new export from your project settings."
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def remove_export
|
||||
if @project.remove_exports
|
||||
flash[:notice] = "Project export has been deleted."
|
||||
else
|
||||
flash[:alert] = "Project export could not be deleted."
|
||||
end
|
||||
redirect_to(edit_project_path(@project))
|
||||
end
|
||||
|
||||
def generate_new_export
|
||||
if @project.remove_exports
|
||||
export
|
||||
else
|
||||
redirect_to(
|
||||
edit_project_path(@project),
|
||||
alert: "Project export could not be deleted."
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def toggle_star
|
||||
current_user.toggle_star(@project)
|
||||
@project.reload
|
||||
|
||||
@@ -40,7 +40,7 @@ class SessionsController < Devise::SessionsController
|
||||
# Handle an "initial setup" state, where there's only one user, it's an admin,
|
||||
# and they require a password change.
|
||||
def check_initial_setup
|
||||
return unless User.count == 1
|
||||
return unless User.limit(2).count == 1 # Count as much 2 to know if we have exactly one
|
||||
|
||||
user = User.admins.last
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class NotesFinder
|
||||
when "commit"
|
||||
project.notes.for_commit_id(target_id).non_diff_notes
|
||||
when "issue"
|
||||
project.issues.find(target_id).notes.inc_author
|
||||
project.issues.visible_to_user(current_user).find(target_id).notes.inc_author
|
||||
when "merge_request"
|
||||
project.merge_requests.find(target_id).mr_and_commit_notes.inc_author
|
||||
when "snippet", "project_snippet"
|
||||
|
||||
@@ -51,7 +51,7 @@ class SnippetsFinder
|
||||
snippets = project.snippets.fresh
|
||||
|
||||
if current_user
|
||||
if project.team.member?(current_user.id) || current_user.admin?
|
||||
if project.team.member?(current_user) || current_user.admin?
|
||||
snippets
|
||||
else
|
||||
snippets.public_and_internal
|
||||
|
||||
@@ -36,7 +36,7 @@ class TodosFinder
|
||||
private
|
||||
|
||||
def action_id?
|
||||
action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED, Todo::BUILD_FAILED].include?(action_id.to_i)
|
||||
action_id.present? && [Todo::ASSIGNED, Todo::MENTIONED, Todo::BUILD_FAILED, Todo::MARKED].include?(action_id.to_i)
|
||||
end
|
||||
|
||||
def action_id
|
||||
@@ -123,7 +123,7 @@ class TodosFinder
|
||||
end
|
||||
|
||||
def by_state(items)
|
||||
case params[:state]
|
||||
case params[:state].to_s
|
||||
when 'done'
|
||||
items.done
|
||||
else
|
||||
|
||||
@@ -116,7 +116,7 @@ module BlobHelper
|
||||
end
|
||||
|
||||
def blob_text_viewable?(blob)
|
||||
blob && blob.text? && !blob.lfs_pointer?
|
||||
blob && blob.text? && !blob.lfs_pointer? && !blob.only_display_raw?
|
||||
end
|
||||
|
||||
def blob_size(blob)
|
||||
@@ -180,8 +180,8 @@ module BlobHelper
|
||||
licenses = Licensee::License.all
|
||||
|
||||
@licenses_for_select = {
|
||||
Popular: licenses.select(&:featured).map { |license| [license.name, license.key] },
|
||||
Other: licenses.reject(&:featured).map { |license| [license.name, license.key] }
|
||||
Popular: licenses.select(&:featured).map { |license| { name: license.name, id: license.key } },
|
||||
Other: licenses.reject(&:featured).map { |license| { name: license.name, id: license.key } }
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -14,4 +14,8 @@ module BranchesHelper
|
||||
|
||||
::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name)
|
||||
end
|
||||
|
||||
def project_branches
|
||||
options_for_select(@project.repository.branch_names, @project.default_branch)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,7 +17,25 @@ module ButtonHelper
|
||||
def clipboard_button(data = {})
|
||||
content_tag :button,
|
||||
icon('clipboard'),
|
||||
class: 'btn btn-clipboard',
|
||||
class: "btn btn-clipboard",
|
||||
data: data,
|
||||
type: :button
|
||||
end
|
||||
|
||||
# Output a "Copy to Clipboard" button with a custom CSS class
|
||||
#
|
||||
# data - Data attributes passed to `content_tag`
|
||||
# css_class - Class passed to the `content_tag`
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# # Define the target element
|
||||
# clipboard_button_with_class({clipboard_target: "div#foo"}, css_class: "btn-clipboard")
|
||||
# # => "<button class='btn btn-clipboard' data-clipboard-target='div#foo'>...</button>"
|
||||
def clipboard_button_with_class(data = {}, css_class: 'btn-clipboard')
|
||||
content_tag :button,
|
||||
icon('clipboard'),
|
||||
class: "btn #{css_class}",
|
||||
data: data,
|
||||
type: :button
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user