Merge branch 'master' of https://gitlab.com/gitlab-org/gitlab-ce into git-http-controller

This commit is contained in:
Jacob Vosmaer
2016-04-12 18:23:42 +02:00
493 changed files with 21382 additions and 3636 deletions
+1 -78
View File
@@ -2,7 +2,6 @@ image: "ruby:2.1"
services:
- mysql:latest
- postgres:latest
- redis:latest
cache:
@@ -35,134 +34,86 @@ spec:feature:
script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
tags:
- ruby
- mysql
spec:api:
stage: test
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
tags:
- ruby
- mysql
spec:models:
stage: test
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
tags:
- ruby
- mysql
spec:lib:
stage: test
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
tags:
- ruby
- mysql
spec:services:
stage: test
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
tags:
- ruby
- mysql
spec:other:
stage: test
script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
tags:
- ruby
- mysql
spinach:project:half:
stage: test
script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
tags:
- ruby
- mysql
spinach:project:rest:
stage: test
script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
tags:
- ruby
- mysql
spinach:other:
stage: test
script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
tags:
- ruby
- mysql
teaspoon:
stage: test
script:
- RAILS_ENV=test bundle exec teaspoon
tags:
- ruby
- mysql
rubocop:
stage: test
script:
- bundle exec rubocop
tags:
- ruby
- mysql
scss-lint:
stage: test
script:
- bundle exec rake scss_lint
tags:
- ruby
brakeman:
stage: test
script:
- bundle exec rake brakeman
tags:
- ruby
- mysql
flog:
stage: test
script:
- bundle exec rake flog
tags:
- ruby
- mysql
flay:
stage: test
script:
- bundle exec rake flay
tags:
- ruby
- mysql
bundler:audit:
stage: test
only:
- master
script:
- "bundle exec bundle-audit update"
- "bundle exec bundle-audit check --ignore OSVDB-115941"
tags:
- ruby
- mysql
- "bundle exec bundle-audit check --update --ignore OSVDB-115941"
# Ruby 2.2 jobs
@@ -178,9 +129,6 @@ spec:feature:ruby22:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
spec:api:ruby22:
stage: test
@@ -193,9 +141,6 @@ spec:api:ruby22:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
spec:models:ruby22:
stage: test
@@ -208,9 +153,6 @@ spec:models:ruby22:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
spec:lib:ruby22:
stage: test
@@ -223,9 +165,6 @@ spec:lib:ruby22:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
spec:services:ruby22:
stage: test
@@ -238,9 +177,6 @@ spec:services:ruby22:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
spec:other:ruby22:
stage: test
@@ -253,9 +189,6 @@ spec:other:ruby22:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
spinach:project:half:ruby22:
stage: test
@@ -269,9 +202,6 @@ spinach:project:half:ruby22:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
spinach:project:rest:ruby22:
stage: test
@@ -285,9 +215,6 @@ spinach:project:rest:ruby22:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
spinach:other:ruby22:
stage: test
@@ -301,10 +228,6 @@ spinach:other:ruby22:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
notify:slack:
stage: notifications
+1 -1
View File
@@ -691,7 +691,7 @@ Style/ZeroLengthPredicate:
# branches, and conditions.
Metrics/AbcSize:
Enabled: true
Max: 70
Max: 60
# Avoid excessive block nesting.
Metrics/BlockNesting:
+106
View File
@@ -7,21 +7,44 @@ exclude:
- 'app/assets/stylesheets/pages/emojis.scss'
linters:
# Reports when you use improper spacing around ! (the "bang") in !default,
# !global, !important, and !optional flags.
BangFormat:
enabled: false
# Whether or not to prefer `border: 0` over `border: none`.
BorderZero:
enabled: false
# Reports when you define a rule set using a selector with chained classes
# (a.k.a. adjoining classes).
ChainedClasses:
enabled: false
# Prefer hexadecimal color codes over color keywords.
# (e.g. `color: green` is a color keyword)
ColorKeyword:
enabled: false
# Prefer color literals (keywords or hexadecimal codes) to be used only in
# variable declarations. They should be referred to via variables everywhere
# else.
ColorVariable:
enabled: false
# Which form of comments to prefer in CSS.
Comment:
enabled: false
# Reports @debug statements (which you probably left behind accidentally).
DebugStatement:
enabled: false
# Rule sets should be ordered as follows:
# - @extend declarations
# - @include declarations without inner @content
# - properties, @include declarations with inner @content
# - nested rule sets.
DeclarationOrder:
enabled: false
@@ -32,15 +55,25 @@ linters:
DisableLinterReason:
enabled: true
# Reports when you define the same property twice in a single rule set.
DuplicateProperty:
enabled: false
# Separate rule, function, and mixin declarations with empty lines.
EmptyLineBetweenBlocks:
enabled: false
# Reports when you have an empty rule set.
EmptyRule:
enabled: false
# Reports when you have an @extend directive.
ExtendDirective:
enabled: false
# Files should always have a final newline. This results in better diffs
# when adding lines to the file, since SCM systems such as git won't
# think that you touched the last line.
FinalNewline:
enabled: false
@@ -53,12 +86,17 @@ linters:
HexNotation:
enabled: true
# Avoid using ID selectors.
IdSelector:
enabled: false
# The basenames of @imported SCSS partials should not begin with an
# underscore and should not include the filename extension.
ImportPath:
enabled: false
# Avoid using !important in properties. It is usually indicative of a
# misunderstanding of CSS specificity and can lead to brittle code.
ImportantRule:
enabled: false
@@ -67,33 +105,51 @@ linters:
enabled: true
width: 2
# Don't write leading zeros for numeric values with a decimal point.
LeadingZero:
enabled: false
# Reports when you define the same selector twice in a single sheet.
MergeableSelector:
enabled: false
# Functions, mixins, variables, and placeholders should be declared
# with all lowercase letters and hyphens instead of underscores.
NameFormat:
enabled: false
# Avoid nesting selectors too deeply.
NestingDepth:
enabled: false
# Always use placeholder selectors in @extend.
PlaceholderInExtend:
enabled: false
# Sort properties in a strict order.
PropertySortOrder:
enabled: false
# Reports when you use an unknown or disabled CSS property
# (ignoring vendor-prefixed properties).
PropertySpelling:
enabled: false
# Configure which units are allowed for property values.
PropertyUnits:
enabled: false
# Pseudo-elements, like ::before, and ::first-letter, should be declared
# with two colons. Pseudo-classes, like :hover and :first-child, should
# be declared with one colon.
PseudoElement:
enabled: false
# Avoid qualifying elements in selectors (also known as "tag-qualifying").
QualifyingElement:
enabled: false
# Don't write selectors with a depth of applicability greater than 3.
SelectorDepth:
enabled: false
@@ -113,9 +169,12 @@ linters:
enabled: true
allow_single_line_rule_sets: true
# Split selectors onto separate lines after each comma, and have each
# individual selector occupy a single line.
SingleLinePerSelector:
enabled: false
# Commas in lists should be followed by a space.
SpaceAfterComma:
enabled: false
@@ -128,28 +187,75 @@ linters:
# colon.
SpaceAfterPropertyName:
enabled: true
# Variables should be formatted with a single space separating the colon
# from the variable's value.
SpaceAfterVariableColon:
enabled: false
# Variables should be formatted with no space between the name and the
# colon.
SpaceAfterVariableName:
enabled: false
# Operators should be formatted with a single space on both sides of an
# infix operator.
SpaceAroundOperator:
enabled: false
# Opening braces should be preceded by a single space.
SpaceBeforeBrace:
enabled: true
# Parentheses should not be padded with spaces.
SpaceBetweenParens:
enabled: false
# Enforces that string literals should be written with a consistent form
# of quotes (single or double).
StringQuotes:
enabled: false
# Property values, @extend, @include, and @import directives, and variable
# declarations should always end with a semicolon.
TrailingSemicolon:
enabled: false
# Reports lines containing trailing whitespace.
TrailingWhitespace:
enabled: false
# Don't write trailing zeros for numeric values with a decimal point.
TrailingZero:
enabled: false
# Don't use the `all` keyword to specify transition properties.
TransitionAll:
enabled: false
# Numeric values should not contain unnecessary fractional portions.
UnnecessaryMantissa:
enabled: false
# Do not use parent selector references (&) when they would otherwise
# be unnecessary.
UnnecessaryParentReference:
enabled: false
# URLs should be valid and not contain protocols or domain names.
UrlFormat:
enabled: false
# URLs should always be enclosed within quotes.
UrlQuotes:
enabled: false
# Properties, like color and font, are easier to read and maintain
# when defined using variables rather than literals.
VariableForProperty:
enabled: false
# Avoid vendor prefixes. Or rather: don't write them yourself.
VendorPrefix:
enabled: false
+130 -6
View File
@@ -1,12 +1,113 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.7.0 (unreleased)
- All service classes (those residing in app/services) are now instrumented (Yorick Peterse)
- Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea)
- Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea)
- All images in discussions and wikis now link to their source files !3464 (Connor Shea).
- Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu)
- Improved Markdown rendering performance !3389 (Yorick Peterse)
- Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu)
- Expose project badges in project settings
- Preserve time notes/comments have been updated at when moving issue
- Make HTTP(s) label consistent on clone bar (Stan Hu)
- Expose label description in API (Mariusz Jachimowicz)
- Allow back dating on issues when created through the API
- Fix Error 500 after renaming a project path (Stan Hu)
- Fix avatar stretching by providing a cropping feature
- API: Expose `subscribed` for issues and merge requests (Robert Schilling)
- Allow SAML to handle external users based on user's information !3530
- Add endpoints to archive or unarchive a project !3372
- Add links to CI setup documentation from project settings and builds pages
- Handle nil descriptions in Slack issue messages (Stan Hu)
- API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling)
- Add default scope to projects to exclude projects pending deletion
- Ensure empty recipients are rejected in BuildsEmailService
- API: Ability to filter milestones by state `active` and `closed` (Robert Schilling)
- API: Fix milestone filtering by `iid` (Robert Schilling)
- API: Delete notes of issues, snippets, and merge requests (Robert Schilling)
- Implement 'Groups View' as an option for dashboard preferences !3379 (Elias W.)
- Better errors handling when creating milestones inside groups
- Hide `Create a group` help block when creating a new project in a group
- Implement 'TODOs View' as an option for dashboard preferences !3379 (Elias W.)
- Gracefully handle notes on deleted commits in merge requests (Stan Hu)
- Fix creation of merge requests for orphaned branches (Stan Hu)
- API: Ability to retrieve a single tag (Robert Schilling)
- Fall back to `In-Reply-To` and `References` headers when sub-addressing is not available (David Padilla)
- Remove "Congratulations!" tweet button on newly-created project. (Connor Shea)
- Fix admin/projects when using visibility levels on search (PotHix)
- Build status notifications
- API: Expose user location (Robert Schilling)
- ClosingIssueExtractor regex now also works with colons. e.g. "Fixes: #1234" !3591
- Update number of Todos in the sidebar when it's marked as "Done". !3600
- API: Expose 'updated_at' for issue, snippet, and merge request notes (Robert Schilling)
- API: User can leave a project through the API when not master or owner. !3613
v 8.6.1 (unreleased)
- Do not allow to move issue if it has not been persisted
- Fix an issue causing the Dashboard/Milestones page to be blank
- Fix build dependencies, when the dependency is a string
v 8.6.6
- Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk)
v 8.6.5
- Fix importing from GitHub Enterprise. !3529
- Perform the language detection after updating merge requests in `GitPushService`, leading to faster visual feedback for the end-user. !3533
- Check permissions when user attempts to import members from another project. !3535
- Only update repository language if it is not set to improve performance. !3556
- Return status code 303 after a branch DELETE operation to avoid project deletion (Stan Hu). !3583
- Unblock user when active_directory is disabled and it can be found !3550
- Fix a 2FA authentication spoofing vulnerability.
v 8.6.4
- Don't attempt to fetch any tags from a forked repo (Stan Hu)
- Redesign the Labels page
v 8.6.3
- Mentions on confidential issues doesn't create todos for non-members. !3374
- Destroy related todos when an Issue/MR is deleted. !3376
- Fix error 500 when target is nil on todo list. !3376
- Fix copying uploads when moving issue to another project. !3382
- Ensuring Merge Request API returns boolean values for work_in_progress (Abhi Rao). !3432
- Fix raw/rendered diff producing different results on merge requests. !3450
- Fix commit comment alignment (Stan Hu). !3466
- Fix Error 500 when searching for a comment in a project snippet. !3468
- Allow temporary email as notification email. !3477
- Fix issue with dropdowns not selecting values. !3478
- Update gitlab-shell version and doc to 2.6.12. gitlab-org/gitlab-ee!280
v 8.6.2
- Fix dropdown alignment. !3298
- Fix issuable sidebar overlaps on tablet. !3299
- Make dropdowns pixel perfect. !3337
- Fix order of steps to prevent PostgreSQL errors when running migration. !3355
- Fix bold text in issuable sidebar. !3358
- Fix error with anonymous token in applications settings. !3362
- Fix the milestone 'upcoming' filter. !3364 + !3368
- Fix comments on confidential issues showing up in activity feed to non-members. !3375
- Fix `NoMethodError` when visiting CI root path at `/ci`. !3377
- Add a tooltip to new branch button in issue page. !3380
- Fix an issue hiding the password form when signed-in with a linked account. !3381
- Add links to CI setup documentation from project settings and builds pages. !3384
- Fix an issue with width of project select dropdown. !3386
- Remove redundant `require`s from Banzai files. !3391
- Fix error 500 with cancel button on issuable edit form. !3392 + !3417
- Fix background when editing a highlighted note. !3423
- Remove tabstop from the WIP toggle links. !3426
- Ensure private project snippets are not viewable by unauthorized people.
- Gracefully handle notes on deleted commits in merge requests (Stan Hu). !3402
- Fixed issue with notification settings not saving. !3452
v 8.6.1
- Add option to reload the schema before restoring a database backup. !2807
- Display navigation controls on mobile. !3214
- Fixed bug where participants would not work correctly on merge requests. !3329
- Fix sorting issues by votes on the groups issues page results in SQL errors. !3333
- Restrict notifications for confidential issues. !3334
- Do not allow to move issue if it has not been persisted. !3340
- Add a confirmation step before deleting an issuable. !3341
- Fixes issue with signin button overflowing on mobile. !3342
- Auto collapses the navigation sidebar when resizing. !3343
- Fix build dependencies, when the dependency is a string. !3344
- Shows error messages when trying to create label in dropdown menu. !3345
- Fixes issue with assign milestone not loading milestone list. !3346
- Fix an issue causing the Dashboard/Milestones page to be blank. !3348
v 8.6.0
- Add ability to move issue to another project
@@ -33,7 +134,6 @@ v 8.6.0
- Add information about `image` and `services` field at `job` level in the `.gitlab-ci.yml` documentation (Pat Turner)
- HTTP error pages work independently from location and config (Artem Sidorenko)
- Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set
- Add option to reload the schema before restoring a database backup. !2807
- Memoize @group in Admin::GroupsController (Yatish Mehta)
- Indicate how much an MR diverged from the target branch (Pierre de La Morinerie)
- Added omniauth-auth0 Gem (Daniel Carraro)
@@ -65,6 +165,7 @@ v 8.6.0
- Add main language of a project in the list of projects (Tiago Botelho)
- Add #upcoming filter to Milestone filter (Tiago Botelho)
- Add ability to show archived projects on dashboard, explore and group pages
- Remove fork link closes all merge requests opened on source project (Florent Baldino)
- Move group activity to separate page
- Create external users which are excluded of internal and private projects unless access was explicitly granted
- Continue parameters are checked to ensure redirection goes to the same instance
@@ -72,7 +173,12 @@ v 8.6.0
- Canceled builds are now ignored in compound build status if marked as `allowed to fail`
- Trigger a todo for mentions on commits page
- Let project owners and admins soft delete issues and merge requests
- Fix sorting issues by votes on the groups issues page results in SQL errors
v 8.5.10
- Fix a 2FA authentication spoofing vulnerability.
v 8.5.9
- Don't attempt to fetch any tags from a forked repo (Stan Hu).
v 8.5.8
- Bump Git version requirement to 2.7.4
@@ -215,6 +321,15 @@ v 8.5.0
- Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul)
- Add Todos
v 8.4.8
- Fix a 2FA authentication spoofing vulnerability.
v 8.4.7
- Don't attempt to fetch any tags from a forked repo (Stan Hu).
v 8.4.6
- Bump Git version requirement to 2.7.4
v 8.4.5
- No CE-specific changes
@@ -328,6 +443,15 @@ 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.7
- Fix a 2FA authentication spoofing vulnerability.
v 8.3.6
- Don't attempt to fetch any tags from a forked repo (Stan Hu).
v 8.3.5
- Bump Git version requirement to 2.7.4
v 8.3.4
- Use gitlab-workhorse 0.5.4 (fixes API routing bug)
+24 -1
View File
@@ -16,6 +16,7 @@
- [Issue tracker guidelines](#issue-tracker-guidelines)
- [Issue weight](#issue-weight)
- [Regression issues](#regression-issues)
- [Technical debt](#technical-debt)
- [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines)
- [Merge request description format](#merge-request-description-format)
@@ -242,6 +243,28 @@ addressed.
[8.3 Regressions]: https://gitlab.com/gitlab-org/gitlab-ce/issues/4127
[update the notes]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/pro-tips.md#update-the-regression-issue
### Technical debt
In order to track things that can be improved in GitLab's codebase, we created
the ~"technical debt" label in [GitLab's issue tracker][ce-tracker].
This label should be added to issues that describe things that can be improved,
shortcuts that have been taken, code that needs refactoring, features that need
additional attention, and all other things that have been left behind due to
high velocity of development.
Everyone can create an issue, though you may need to ask for adding a specific
label, if you do not have permissions to do it by yourself. Additional labels
can be combined with the `technical debt` label, to make it easier to schedule
the improvements for a release.
Issues tagged with the `technical debt` label have the same priority like issues
that describe a new feature to be introduced in GitLab, and should be scheduled
for a release by the appropriate person.
Make sure to mention the merge request that the `technical debt` issue is
associated with in the description of the issue.
## Merge requests
We welcome merge requests with fixes and improvements to GitLab code, tests,
@@ -425,7 +448,7 @@ merge request:
- multi-line method chaining style **Option B**: dot `.` on previous line
- string literal quoting style **Option A**: single quoted by default
1. [Rails](https://github.com/bbatsov/rails-style-guide)
1. [Testing](https://github.com/thoughtbot/guides/tree/master/style/testing)
1. [Testing](doc/development/testing.md)
1. [CoffeeScript](https://github.com/thoughtbot/guides/tree/master/style/coffeescript)
1. [SCSS styleguide][scss-styleguide]
1. [Shell commands](doc/development/shell_commands.md) created by GitLab
+1 -1
View File
@@ -1 +1 @@
2.6.11
2.7.2
+11 -8
View File
@@ -1,6 +1,6 @@
source "https://rubygems.org"
gem 'rails', '4.2.5.2'
gem 'rails', '4.2.6'
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with
@@ -8,7 +8,7 @@ gem 'responders', '~> 2.0'
# Specify a sprockets version due to increased performance
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069
gem 'sprockets', '~> 3.3.5'
gem 'sprockets', '~> 3.6.0'
# Default values for AR models
gem "default_value_for", "~> 3.0.0"
@@ -149,6 +149,10 @@ gem 'version_sorter', '~> 2.0.0'
# Cache
gem "redis-rails", '~> 4.0.0'
# Redis
gem 'redis', '~> 3.2'
gem 'connection_pool', '~> 2.0'
# Campfire integration
gem 'tinder', '~> 1.10.0'
@@ -214,7 +218,7 @@ gem 'jquery-rails', '~> 4.0.0'
gem 'jquery-scrollto-rails', '~> 1.4.3'
gem 'jquery-ui-rails', '~> 5.0.0'
gem 'raphael-rails', '~> 2.1.2'
gem 'request_store', '~> 1.2.0'
gem 'request_store', '~> 1.3.0'
gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1'
@@ -229,14 +233,13 @@ group :metrics do
gem 'allocations', '~> 1.0', require: false, platform: :mri
gem 'method_source', '~> 0.8', require: false
gem 'influxdb', '~> 0.2', require: false
gem 'connection_pool', '~> 2.0', require: false
end
group :development do
gem "foreman"
gem 'brakeman', '~> 3.1.0', require: false
gem 'brakeman', '~> 3.2.0', require: false
gem "annotate", "~> 2.6.0"
gem "annotate", "~> 2.7.0"
gem "letter_opener", '~> 1.1.2'
gem 'quiet_assets', '~> 1.0.2'
gem 'rerun', '~> 0.11.0'
@@ -279,7 +282,7 @@ group :development, :test do
gem 'capybara-screenshot', '~> 1.0.0'
gem 'poltergeist', '~> 1.9.0'
gem 'teaspoon', '~> 1.0.0'
gem 'teaspoon', '~> 1.1.0'
gem 'teaspoon-jasmine', '~> 2.2.0'
gem 'spring', '~> 1.6.4'
@@ -290,7 +293,7 @@ group :development, :test do
gem 'rubocop', '~> 0.38.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false
gem 'coveralls', '~> 0.8.2', require: false
gem 'simplecov', '~> 0.10.0', require: false
gem 'simplecov', '~> 0.11.0', require: false
gem 'flog', require: false
gem 'flay', require: false
gem 'bundler-audit', require: false
+74 -82
View File
@@ -4,41 +4,41 @@ GEM
CFPropertyList (2.3.2)
RedCloth (4.2.9)
ace-rails-ap (2.0.1)
actionmailer (4.2.5.2)
actionpack (= 4.2.5.2)
actionview (= 4.2.5.2)
activejob (= 4.2.5.2)
actionmailer (4.2.6)
actionpack (= 4.2.6)
actionview (= 4.2.6)
activejob (= 4.2.6)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (4.2.5.2)
actionview (= 4.2.5.2)
activesupport (= 4.2.5.2)
actionpack (4.2.6)
actionview (= 4.2.6)
activesupport (= 4.2.6)
rack (~> 1.6)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (4.2.5.2)
activesupport (= 4.2.5.2)
actionview (4.2.6)
activesupport (= 4.2.6)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
activejob (4.2.5.2)
activesupport (= 4.2.5.2)
activejob (4.2.6)
activesupport (= 4.2.6)
globalid (>= 0.3.0)
activemodel (4.2.5.2)
activesupport (= 4.2.5.2)
activemodel (4.2.6)
activesupport (= 4.2.6)
builder (~> 3.1)
activerecord (4.2.5.2)
activemodel (= 4.2.5.2)
activesupport (= 4.2.5.2)
activerecord (4.2.6)
activemodel (= 4.2.6)
activesupport (= 4.2.6)
arel (~> 6.0)
activerecord-deprecated_finders (1.0.4)
activerecord-session_store (0.1.2)
actionpack (>= 4.0.0, < 5)
activerecord (>= 4.0.0, < 5)
railties (>= 4.0.0, < 5)
activesupport (4.2.5.2)
activesupport (4.2.6)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
@@ -51,8 +51,8 @@ GEM
activerecord (>= 3.0)
akismet (2.0.0)
allocations (1.0.4)
annotate (2.6.10)
activerecord (>= 3.2, <= 4.3)
annotate (2.7.0)
activerecord (>= 3.2, < 6.0)
rake (~> 10.4)
arel (6.0.3)
asana (0.4.0)
@@ -84,24 +84,22 @@ GEM
bootstrap-sass (3.3.6)
autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4)
brakeman (3.1.4)
brakeman (3.2.1)
erubis (~> 2.6)
fastercsv (~> 1.5)
haml (>= 3.0, < 5.0)
highline (>= 1.6.20, < 2.0)
multi_json (~> 1.2)
ruby2ruby (>= 2.1.1, < 2.3.0)
ruby_parser (~> 3.7.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)
browser (1.0.1)
builder (3.2.2)
bullet (4.14.10)
bullet (5.0.0)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.9.0)
bundler-audit (0.4.0)
bundler-audit (0.5.0)
bundler (~> 1.2)
thor (~> 0.18)
byebug (8.2.1)
@@ -128,9 +126,9 @@ GEM
coderay (1.1.0)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
coffee-rails (4.1.0)
coffee-rails (4.1.1)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.0)
railties (>= 4.0.0, < 5.1.x)
coffee-script (2.4.1)
coffee-script-source
execjs
@@ -138,17 +136,16 @@ GEM
colorize (0.7.7)
concurrent-ruby (1.0.0)
connection_pool (2.2.0)
coveralls (0.8.9)
coveralls (0.8.13)
json (~> 1.8)
rest-client (>= 1.6.8, < 2)
simplecov (~> 0.10.0)
simplecov (~> 0.11.0)
term-ansicolor (~> 1.3)
thor (~> 0.19.1)
tins (~> 1.6.0)
crack (0.4.3)
safe_yaml (~> 1.0.0)
creole (0.5.0)
css_parser (1.3.7)
css_parser (1.4.1)
addressable
d3_rails (3.5.11)
railties (>= 3.1.0)
@@ -178,8 +175,6 @@ GEM
diff-lcs (1.2.5)
diffy (3.0.7)
docile (1.1.5)
domain_name (0.5.25)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (2.2.2)
railties (>= 3.2)
dropzonejs-rails (0.7.2)
@@ -208,7 +203,6 @@ GEM
faraday_middleware-multi_json (0.0.6)
faraday_middleware
multi_json
fastercsv (1.5.5)
ffaker (2.0.0)
ffi (1.9.10)
fission (0.5.0)
@@ -328,8 +322,8 @@ GEM
fog-xml (0.1.2)
fog-core
nokogiri (~> 1.5, >= 1.5.11)
font-awesome-rails (4.5.0.0)
railties (>= 3.2, < 5.0)
font-awesome-rails (4.5.0.1)
railties (>= 3.2, < 5.1)
foreman (0.78.0)
thor (~> 0.19.1)
formatador (0.2.5)
@@ -424,8 +418,6 @@ GEM
nokogiri (~> 1.6.0)
ruby_parser (~> 3.5)
htmlentities (4.3.4)
http-cookie (1.0.2)
domain_name (~> 0.5)
http_parser.rb (0.5.3)
httparty (0.13.7)
json (~> 1.8)
@@ -467,8 +459,8 @@ GEM
nokogiri (>= 1.5.9)
macaddr (1.7.1)
systemu (~> 2.6.2)
mail (2.6.3)
mime-types (>= 1.16, < 3)
mail (2.6.4)
mime-types (>= 1.16, < 4)
mail_room (0.6.1)
method_source (0.8.2)
mime-types (1.25.1)
@@ -483,7 +475,6 @@ GEM
nested_form (0.3.2)
net-ldap (0.12.1)
net-ssh (3.0.1)
netrc (0.11.0)
newrelic_rpm (3.14.1.311)
nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2)
@@ -568,8 +559,8 @@ GEM
premailer (1.8.6)
css_parser (>= 1.3.6)
htmlentities (>= 4.0.0)
premailer-rails (1.9.0)
actionmailer (>= 3, < 5)
premailer-rails (1.9.2)
actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9)
pry (0.10.3)
coderay (~> 1.1.0)
@@ -598,16 +589,16 @@ GEM
rack
rack-test (0.6.3)
rack (>= 1.0)
rails (4.2.5.2)
actionmailer (= 4.2.5.2)
actionpack (= 4.2.5.2)
actionview (= 4.2.5.2)
activejob (= 4.2.5.2)
activemodel (= 4.2.5.2)
activerecord (= 4.2.5.2)
activesupport (= 4.2.5.2)
rails (4.2.6)
actionmailer (= 4.2.6)
actionpack (= 4.2.6)
actionview (= 4.2.6)
activejob (= 4.2.6)
activemodel (= 4.2.6)
activerecord (= 4.2.6)
activesupport (= 4.2.6)
bundler (>= 1.3.0, < 2.0)
railties (= 4.2.5.2)
railties (= 4.2.6)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
@@ -617,9 +608,9 @@ GEM
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3)
loofah (~> 2.0)
railties (4.2.5.2)
actionpack (= 4.2.5.2)
activesupport (= 4.2.5.2)
railties (4.2.6)
actionpack (= 4.2.6)
activesupport (= 4.2.6)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.1.0)
@@ -655,15 +646,11 @@ GEM
redis-store (~> 1.1.0)
redis-store (1.1.7)
redis (>= 2.2)
request_store (1.2.1)
request_store (1.3.0)
rerun (0.11.0)
listen (~> 3.0)
responders (2.1.1)
railties (>= 4.2.0, < 5.1)
rest-client (1.8.0)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 3.0)
netrc (~> 0.7)
rinku (1.7.3)
rotp (2.1.1)
rouge (1.10.1)
@@ -706,10 +693,10 @@ GEM
ruby-saml (1.1.2)
nokogiri (>= 1.5.10)
uuid (~> 2.3)
ruby2ruby (2.2.0)
ruby2ruby (2.3.0)
ruby_parser (~> 3.1)
sexp_processor (~> 4.0)
ruby_parser (3.7.2)
ruby_parser (3.8.1)
sexp_processor (~> 4.1)
rubyntlm (0.5.2)
rubypants (0.2.0)
@@ -718,7 +705,7 @@ GEM
safe_yaml (1.0.4)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
sass (3.4.20)
sass (3.4.21)
sass-rails (5.0.4)
railties (>= 4.0.0, < 5.0)
sass (~> 3.1)
@@ -742,7 +729,7 @@ GEM
sentry-raven (0.15.6)
faraday (>= 0.7.6)
settingslogic (2.0.9)
sexp_processor (4.6.0)
sexp_processor (4.7.0)
sham_rack (1.3.6)
rack
shoulda-matchers (2.8.0)
@@ -757,7 +744,7 @@ GEM
rufus-scheduler (>= 2.0.24)
sidekiq (>= 4.0.0)
simple_oauth (0.1.9)
simplecov (0.10.0)
simplecov (0.11.2)
docile (~> 1.1.0)
json (~> 1.8)
simplecov-html (~> 0.10.0)
@@ -789,12 +776,13 @@ GEM
spring (>= 0.9.1)
spring-commands-teaspoon (0.0.2)
spring (>= 0.9.1)
sprockets (3.3.5)
sprockets (3.6.0)
concurrent-ruby (~> 1.0)
rack (> 1, < 3)
sprockets-rails (2.3.3)
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0)
sprockets-rails (3.0.4)
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
state_machines (0.4.0)
state_machines-activemodel (0.3.0)
activemodel (~> 4.1)
@@ -806,8 +794,8 @@ GEM
systemu (2.6.5)
task_list (1.0.2)
html-pipeline
teaspoon (1.0.2)
railties (>= 3.2.5, < 5)
teaspoon (1.1.5)
railties (>= 3.2.5, < 6)
teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0)
temple (0.7.6)
@@ -848,7 +836,7 @@ GEM
underscore-rails (1.8.3)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.1)
unf_ext (0.0.7.2)
unicode-display_width (1.0.2)
unicorn (4.9.0)
kgio (~> 2.6)
@@ -868,7 +856,7 @@ GEM
equalizer (~> 0.0, >= 0.0.9)
warden (1.2.4)
rack (>= 1.0)
web-console (2.2.1)
web-console (2.3.0)
activemodel (>= 4.0)
binding_of_caller (>= 0.7.2)
railties (>= 4.0)
@@ -900,7 +888,7 @@ DEPENDENCIES
after_commit_queue
akismet (~> 2.0)
allocations (~> 1.0)
annotate (~> 2.6.0)
annotate (~> 2.7.0)
asana (~> 0.4.0)
asciidoctor (~> 1.5.2)
attr_encrypted (~> 1.3.4)
@@ -910,7 +898,7 @@ DEPENDENCIES
better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0)
brakeman (~> 3.1.0)
brakeman (~> 3.2.0)
browser (~> 1.0.0)
bullet
bundler-audit
@@ -1005,16 +993,17 @@ DEPENDENCIES
rack-attack (~> 4.3.1)
rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1)
rails (= 4.2.5.2)
rails (= 4.2.6)
rails-deprecated_sanitizer (~> 1.0.3)
raphael-rails (~> 2.1.2)
rblineprof
rdoc (~> 3.6)
recaptcha
redcarpet (~> 3.3.3)
redis (~> 3.2)
redis-namespace
redis-rails (~> 4.0.0)
request_store (~> 1.2.0)
request_store (~> 1.3.0)
rerun (~> 0.11.0)
responders (~> 2.0)
rouge (~> 1.10.1)
@@ -1035,7 +1024,7 @@ DEPENDENCIES
shoulda-matchers (~> 2.8.0)
sidekiq (~> 4.0)
sidekiq-cron (~> 0.4.0)
simplecov (~> 0.10.0)
simplecov (~> 0.11.0)
sinatra (~> 1.4.4)
six (~> 0.2.0)
slack-notifier (~> 1.2.0)
@@ -1045,10 +1034,10 @@ DEPENDENCIES
spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.0.0)
spring-commands-teaspoon (~> 0.0.2)
sprockets (~> 3.3.5)
sprockets (~> 3.6.0)
state_machines-activerecord (~> 0.3.0)
task_list (~> 1.0.2)
teaspoon (~> 1.0.0)
teaspoon (~> 1.1.0)
teaspoon-jasmine (~> 2.2.0)
test_after_commit (~> 0.4.2)
thin (~> 1.6.1)
@@ -1064,3 +1053,6 @@ DEPENDENCIES
web-console (~> 2.0)
webmock (~> 1.21.0)
wikicloth (= 0.8.1)
BUNDLED WITH
1.11.2
+1 -1
View File
@@ -1 +1 @@
8.6.0-pre
8.7.0-pre
@@ -43,6 +43,7 @@
#= require jquery.nicescroll
#= require_tree .
#= require fuzzaldrin-plus
#= require cropper
window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
+15 -4
View File
@@ -1,5 +1,5 @@
class @AwardsHandler
constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
constructor: (@get_emojis_url, @post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
$(".js-add-award").on "click", (event) =>
event.stopPropagation()
event.preventDefault()
@@ -22,8 +22,19 @@ class @AwardsHandler
emoji = $(this)
.find(".icon")
.data "emoji"
if emoji is "thumbsup" and awards_handler.didUserClickEmoji $(this), "thumbsdown"
awards_handler.addAward "thumbsdown"
else if emoji is "thumbsdown" and awards_handler.didUserClickEmoji $(this), "thumbsup"
awards_handler.addAward "thumbsup"
awards_handler.addAward emoji
didUserClickEmoji: (that, emoji) ->
if $(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title")
$(that).siblings("button:has([data-emoji=#{emoji}])").attr("data-original-title").indexOf('me') > -1
showEmojiMenu: ->
if $(".emoji-menu").length
if $(".emoji-menu").is ".is-visible"
@@ -34,7 +45,7 @@ class @AwardsHandler
$("#emoji_search").focus()
else
$('.js-add-award').addClass "is-loading"
$.get "/emojis", (response) =>
$.get @get_emojis_url, (response) =>
$('.js-add-award').removeClass "is-loading"
$(".js-award-holder").append response
setTimeout =>
@@ -105,7 +116,7 @@ class @AwardsHandler
if origTitle
authors = origTitle.split(', ')
authors.push("me")
award_block.attr("title", authors.join(", "))
award_block.attr("data-original-title", authors.join(", "))
@resetTooltip(award_block)
resetTooltip: (award) ->
@@ -122,7 +133,7 @@ class @AwardsHandler
nodes = []
nodes.push(
"<button class='btn award-control js-emoji-btn has-tooltip active' title='me'>",
"<button class='btn award-control js-emoji-btn has-tooltip active' data-original-title='me'>",
"<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>",
"<span class='award-control-text js-counter'>1</span>",
"</button>"
@@ -35,4 +35,18 @@ $.fn.requiresInput = ->
$form.on 'change input', fieldSelector, requireInput
$ ->
$('form.js-requires-input').requiresInput()
$form = $('form.js-requires-input')
$form.requiresInput()
# Hide or Show the help block when creating a new project
# based on the option selected
hideOrShowHelpBlock = (form) ->
selected = $('.js-select-namespace option:selected')
if selected.length and selected.data('options-parent') is 'groups'
return form.find('.help-block').hide()
else if selected.length
form.find('.help-block').show()
hideOrShowHelpBlock($form)
$('.select2.js-select-namespace').change -> hideOrShowHelpBlock($form)
+67
View File
@@ -0,0 +1,67 @@
class @Compare
constructor: (@opts) ->
@source_loading = $ ".js-source-loading"
@target_loading = $ ".js-target-loading"
$('.js-compare-dropdown').each (i, dropdown) =>
$dropdown = $(dropdown)
$dropdown.glDropdown(
selectable: true
fieldName: $dropdown.data 'field-name'
filterable: true
id: (obj, $el) ->
$el.data 'id'
toggleLabel: (obj, $el) ->
$el.text().trim()
clicked: (e, el) =>
if $dropdown.is '.js-target-branch'
@getTargetHtml()
else if $dropdown.is '.js-source-branch'
@getSourceHtml()
else if $dropdown.is '.js-target-project'
@getTargetProject()
)
@initialState()
initialState: ->
@getSourceHtml()
@getTargetHtml()
getTargetProject: ->
$.ajax(
url: @opts.targetProjectUrl
data:
target_project_id: $("input[name='merge_request[target_project_id]']").val()
beforeSend: ->
$('.mr_target_commit').empty()
success: (html) ->
$('.js-target-branch-dropdown .dropdown-content').html html
)
getSourceHtml: ->
@sendAjax(@opts.sourceBranchUrl, @source_loading, '.mr_source_commit',
ref: $("input[name='merge_request[source_branch]']").val()
)
getTargetHtml: ->
@sendAjax(@opts.targetBranchUrl, @target_loading, '.mr_target_commit',
target_project_id: $("input[name='merge_request[target_project_id]']").val()
ref: $("input[name='merge_request[target_branch]']").val()
)
sendAjax: (url, loading, target, data) ->
$target = $(target)
$.ajax(
url: url
data: data
beforeSend: ->
loading.show()
$target.empty()
success: (html) ->
loading.hide()
$target.html html
$('.js-timeago', $target).timeago()
)
+2 -6
View File
@@ -146,15 +146,11 @@ class Dispatcher
when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
shortcut_handler = new ShortcutsNavigation()
# If we haven't installed a custom shortcut handler, install the default one
if not shortcut_handler
new Shortcuts()
initSearch: ->
opts = $('.search-autocomplete-opts')
path = opts.data('autocomplete-path')
project_id = opts.data('autocomplete-project-id')
project_ref = opts.data('autocomplete-project-ref')
new SearchAutocomplete(path, project_id, project_ref)
# Only when search form is present
new SearchAutocomplete() if $('.search').length
+152
View File
@@ -0,0 +1,152 @@
class GitLabCrop
# Matches everything but the file name
FILENAMEREGEX = /^.*[\\\/]/
constructor: (input, opts = {}) ->
@fileInput = $(input)
# We should rename to avoid spec to fail
# Form will submit the proper input filed with a file using FormData
@fileInput
.attr('name', "#{@fileInput.attr('name')}-trigger")
.attr('id', "#{@fileInput.attr('id')}-trigger")
# Set defaults
{
@exportWidth = 200
@exportHeight = 200
@cropBoxWidth = 200
@cropBoxHeight = 200
@form = @fileInput.parents('form')
# Required params
@filename
@previewImage
@modalCrop
@pickImageEl
@uploadImageBtn
@modalCropImg
} = opts
# Ensure needed elements are jquery objects
# If selector is provided we will convert them to a jQuery Object
@filename = @getElement(@filename)
@previewImage = @getElement(@previewImage)
@pickImageEl = @getElement(@pickImageEl)
# Modal elements usually are outside the @form element
@modalCrop = if _.isString(@modalCrop) then $(@modalCrop) else @modalCrop
@uploadImageBtn = if _.isString(@uploadImageBtn) then $(@uploadImageBtn) else @uploadImageBtn
@modalCropImg = if _.isString(@modalCropImg) then $(@modalCropImg) else @modalCropImg
@cropActionsBtn = @modalCrop.find('[data-method]')
@bindEvents()
getElement: (selector) ->
$(selector, @form)
bindEvents: ->
_this = @
@fileInput.on 'change', (e) ->
_this.onFileInputChange(e, @)
@pickImageEl.on 'click', @onPickImageClick
@modalCrop.on 'shown.bs.modal', @onModalShow
@modalCrop.on 'hidden.bs.modal', @onModalHide
@uploadImageBtn.on 'click', @onUploadImageBtnClick
@cropActionsBtn.on 'click', (e) ->
btn = @
_this.onActionBtnClick(btn)
@croppedImageBlob = null
onPickImageClick: =>
@fileInput.trigger('click')
onModalShow: =>
_this = @
@modalCropImg.cropper(
viewMode: 1
center: false
aspectRatio: 1
modal: true
scalable: false
rotatable: false
zoomable: true
dragMode: 'move'
guides: false
zoomOnTouch: false
zoomOnWheel: false
cropBoxMovable: false
cropBoxResizable: false
toggleDragModeOnDblclick: false
built: ->
$image = $(@)
container = $image.cropper 'getContainerData'
cropBoxWidth = _this.cropBoxWidth;
cropBoxHeight = _this.cropBoxHeight;
$image.cropper('setCropBoxData',
width: cropBoxWidth,
height: cropBoxHeight,
left: (container.width - cropBoxWidth) / 2,
top: (container.height - cropBoxHeight) / 2
)
)
onModalHide: =>
@modalCropImg
.attr('src', '') # Remove attached image
.cropper('destroy') # Destroy cropper instance
onUploadImageBtnClick: (e) =>
e.preventDefault()
@setBlob()
@setPreview()
@modalCrop.modal('hide')
@fileInput.val('')
onActionBtnClick: (btn) ->
data = $(btn).data()
if @modalCropImg.data('cropper') && data.method
result = @modalCropImg.cropper data.method, data.option
onFileInputChange: (e, input) ->
@readFile(input)
readFile: (input) ->
_this = @
reader = new FileReader
reader.onload = ->
_this.modalCropImg.attr('src', reader.result)
_this.modalCrop.modal('show')
reader.readAsDataURL(input.files[0])
dataURLtoBlob: (dataURL) ->
binary = atob(dataURL.split(',')[1])
array = []
for v, k in binary
array.push(binary.charCodeAt(k))
new Blob([new Uint8Array(array)], type: 'image/png')
setPreview: ->
@previewImage.attr('src', @dataURL)
filename = @fileInput.val().replace(FILENAMEREGEX, '')
@filename.text(filename)
setBlob: ->
@dataURL = @modalCropImg.cropper('getCroppedCanvas',
width: 200
height: 200
).toDataURL('image/png')
@croppedImageBlob = @dataURLtoBlob(@dataURL)
getBlob: ->
@croppedImageBlob
$.fn.glCrop = (opts) ->
return @.each ->
$(@).data('glcrop', new GitLabCrop(@, opts))
+285 -72
View File
@@ -1,23 +1,48 @@
class GitLabDropdownFilter
BLUR_KEYCODES = [27, 40]
ARROW_KEY_CODES = [38, 40]
HAS_VALUE_CLASS = "has-value"
constructor: (@dropdown, @options) ->
@input = @dropdown.find(".dropdown-input .dropdown-input-field")
constructor: (@input, @options) ->
{
@filterInputBlur = true
} = @options
$inputContainer = @input.parent()
$clearButton = $inputContainer.find('.js-dropdown-input-clear')
# Clear click
$clearButton.on 'click', (e) =>
e.preventDefault()
e.stopPropagation()
@input
.val('')
.trigger('keyup')
.focus()
# Key events
timeout = ""
@input.on "keyup", (e) =>
if e.keyCode is 13 && @input.val() isnt ""
keyCode = e.which
return if ARROW_KEY_CODES.indexOf(keyCode) >= 0
if @input.val() isnt "" and !$inputContainer.hasClass HAS_VALUE_CLASS
$inputContainer.addClass HAS_VALUE_CLASS
else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS
$inputContainer.removeClass HAS_VALUE_CLASS
if keyCode is 13 and @input.val() isnt ""
if @options.enterCallback
@options.enterCallback()
return
clearTimeout timeout
timeout = setTimeout =>
blur_field = @shouldBlur e.keyCode
blur_field = @shouldBlur keyCode
search_text = @input.val()
if blur_field
if blur_field and @filterInputBlur
@input.blur()
if @options.remote
@@ -32,14 +57,30 @@ class GitLabDropdownFilter
filter: (search_text) ->
data = @options.data()
results = data
if search_text isnt ""
results = fuzzaldrinPlus.filter(data, search_text,
key: @options.keys
)
if data?
results = data
@options.callback results
if search_text isnt ''
results = fuzzaldrinPlus.filter(data, search_text,
key: @options.keys
)
@options.callback results
else
elements = @options.elements()
if search_text
elements.each ->
$el = $(@)
matches = fuzzaldrinPlus.match($el.text().trim(), search_text)
if matches.length
$el.show()
else
$el.hide()
else
elements.show()
class GitLabDropdownRemote
constructor: (@dataEndpoint, @options) ->
@@ -76,39 +117,74 @@ class GitLabDropdown
LOADING_CLASS = "is-loading"
PAGE_TWO_CLASS = "is-page-two"
ACTIVE_CLASS = "is-active"
currentIndex = -1
FILTER_INPUT = '.dropdown-input .dropdown-input-field'
constructor: (@el, @options) ->
self = @
@dropdown = $(@el).parent()
search_fields = if @options.search then @options.search.fields else [];
# Set Defaults
{
# If no input is passed create a default one
@filterInput = @getElement(FILTER_INPUT)
@highlight = false
@filterInputBlur = true
@enterCallback = true
} = @options
self = @
# If selector was passed
if _.isString(@filterInput)
@filterInput = @getElement(@filterInput)
searchFields = if @options.search then @options.search.fields else [];
if @options.data
# Remote data
@remote = new GitLabDropdownRemote @options.data, {
dataType: @options.dataType,
beforeSend: @toggleLoading.bind(@)
success: (data) =>
@fullData = data
# If data is an array
if _.isArray @options.data
@fullData = @options.data
@parseData @options.data
else
# Remote data
@remote = new GitLabDropdownRemote @options.data, {
dataType: @options.dataType,
beforeSend: @toggleLoading.bind(@)
success: (data) =>
@fullData = data
@parseData @fullData
}
@parseData @fullData
}
# Init filiterable
# Init filterable
if @options.filterable
@filter = new GitLabDropdownFilter @dropdown,
@filter = new GitLabDropdownFilter @filterInput,
filterInputBlur: @filterInputBlur
remote: @options.filterRemote
query: @options.data
keys: @options.search.fields
keys: searchFields
elements: =>
selector = '.dropdown-content li:not(.divider)'
if @dropdown.find('.dropdown-toggle-page').length
selector = ".dropdown-page-one #{selector}"
return $(selector)
data: =>
return @fullData
callback: (data) =>
currentIndex = -1
@parseData data
enterCallback: =>
@selectFirstRow()
if @enterCallback
@selectRowAtIndex 0
# Event listeners
@dropdown.on "shown.bs.dropdown", @opened
@dropdown.on "hidden.bs.dropdown", @hidden
@dropdown.on "click", ".dropdown-menu, .dropdown-menu-close", @shouldPropagate
if @dropdown.find(".dropdown-toggle-page").length
@dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) =>
@@ -124,10 +200,15 @@ class GitLabDropdown
selector = ".dropdown-page-one .dropdown-content a"
@dropdown.on "click", selector, (e) ->
self.rowClicked $(@)
$el = $(@)
selected = self.rowClicked $el
if self.options.clicked
self.options.clicked()
self.options.clicked(selected, $el, e)
# Finds an element inside wrapper element
getElement: (selector) ->
@dropdown.find selector
toggleLoading: ->
$('.dropdown-menu', @dropdown).toggleClass LOADING_CLASS
@@ -157,15 +238,29 @@ class GitLabDropdown
@appendMenu(full_html)
shouldPropagate: (e) =>
if @options.multiSelect
$target = $(e.target)
if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon')
e.stopPropagation()
return false
else
return true
opened: =>
@addArrowKeyEvent()
contentHtml = $('.dropdown-content', @dropdown).html()
if @remote && contentHtml is ""
@remote.execute()
if @options.filterable
@dropdown.find(".dropdown-input-field").focus()
@filterInput.focus()
hidden: =>
@dropdown.trigger('shown.gl.dropdown')
hidden: (e) =>
@removeArrayKeyEvent()
if @options.filterable
@dropdown
.find(".dropdown-input-field")
@@ -176,6 +271,11 @@ class GitLabDropdown
if @dropdown.find(".dropdown-toggle-page").length
$('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS
if @options.hidden
@options.hidden.call(@,e)
@dropdown.trigger('hidden.gl.dropdown')
# Render the full menu
renderMenu: (html) ->
@@ -200,81 +300,194 @@ class GitLabDropdown
renderItem: (data) ->
html = ""
# Divider
return "<li class='divider'></li>" if data is "divider"
# Separator is a full-width divider
return "<li class='separator'></li>" if data is "separator"
# Header
return "<li class='dropdown-header'>#{data.header}</li>" if data.header?
if @options.renderRow
# Call the render function
html = @options.renderRow(data)
else
selected = if @options.isSelected then @options.isSelected(data) else false
url = if @options.url then @options.url(data) else "#"
text = if @options.text then @options.text(data) else ""
if not selected
value = if @options.id then @options.id(data) else data.id
fieldName = @options.fieldName
field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
if field.length
selected = true
# Set URL
if @options.url?
url = @options.url(data)
else
url = if data.url? then data.url else '#'
# Set Text
if @options.text?
text = @options.text(data)
else
text = if data.text? then data.text else ''
cssClass = "";
if selected
cssClass = "is-active"
html = "<li>"
html += "<a href='#{url}' class='#{cssClass}'>"
html += text
html += "</a>"
html += "</li>"
if @highlight
text = @highlightTextMatches(text, @filterInput.val())
html = "<li>
<a href='#{url}' class='#{cssClass}'>
#{text}
</a>
</li>"
return html
highlightTextMatches: (text, term) ->
occurrences = fuzzaldrinPlus.match(text, term)
text.split('').map((character, i) ->
if i in occurrences then "<b>#{character}</b>" else character
).join('')
noResults: ->
html = "<li>"
html += "<a href='#' class='is-focused'>"
html += "No matching results."
html += "</a>"
html += "</li>"
html = "<li class='dropdown-menu-empty-link'>
<a href='#' class='is-focused'>
No matching results.
</a>
</li>"
highlightRow: (index) ->
if @filterInput.val() isnt ""
selector = '.dropdown-content li:first-child a'
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content li:first-child a"
@getElement(selector).addClass 'is-focused'
rowClicked: (el) ->
fieldName = @options.fieldName
field = @dropdown.parent().find("input[name='#{fieldName}']")
selectedIndex = el.parent().index()
if @renderedData
selectedObject = @renderedData[selectedIndex]
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
field = @dropdown.parent().find("input[name='#{fieldName}'][value='#{value}']")
if el.hasClass(ACTIVE_CLASS)
el.removeClass(ACTIVE_CLASS)
field.remove()
else
fieldName = @options.fieldName
selectedIndex = el.parent().index()
if @renderedData
selectedObject = @renderedData[selectedIndex]
value = if @options.id then @options.id(selectedObject, el) else selectedObject.id
if !value?
field.remove()
if @options.multiSelect
oldValue = field.val()
if oldValue
value = "#{oldValue},#{value}"
else
@dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
# Toggle active class for the tick mark
el.toggleClass "is-active"
# Toggle the dropdown label
if @options.toggleLabel
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject)
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel
else
selectedObject
else
if !value?
field.remove()
if not @options.multiSelect
@dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
@dropdown.parent().find("input[name='#{fieldName}']").remove()
# Toggle active class for the tick mark
el.addClass ACTIVE_CLASS
# Toggle the dropdown label
if @options.toggleLabel
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject, el)
if value?
if !field.length
if !field.length and fieldName
# Create hidden input for form
input = "<input type='hidden' name='#{fieldName}' />"
input = "<input type='hidden' name='#{fieldName}' value='#{value}' />"
if @options.inputId?
input = $(input)
.attr('id', @options.inputId)
@dropdown.before input
else
field.val value
@dropdown.parent().find("input[name='#{fieldName}']").val value
return selectedObject
selectRowAtIndex: (index) ->
selector = ".dropdown-content li:not(.divider):eq(#{index}) a"
selectFirstRow: ->
selector = '.dropdown-content li:first-child a'
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content li:first-child a"
selector = ".dropdown-page-one #{selector}"
# similute a click on the first link
$(selector).trigger "click"
# simulate a click on the first link
$(selector, @dropdown).trigger "click"
addArrowKeyEvent: ->
ARROW_KEY_CODES = [38, 40]
$input = @dropdown.find(".dropdown-input-field")
selector = '.dropdown-content li:not(.divider)'
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one #{selector}"
$('body').on 'keydown', (e) =>
currentKeyCode = e.which
if ARROW_KEY_CODES.indexOf(currentKeyCode) >= 0
e.preventDefault()
e.stopImmediatePropagation()
PREV_INDEX = currentIndex
$listItems = $(selector, @dropdown)
# if @options.filterable
# $input.blur()
if currentKeyCode is 40
# Move down
currentIndex += 1 if currentIndex < ($listItems.length - 1)
else if currentKeyCode is 38
# Move up
currentIndex -= 1 if currentIndex > 0
@highlightRowAtIndex($listItems, currentIndex) if currentIndex isnt PREV_INDEX
return false
if currentKeyCode is 13
@selectRowAtIndex currentIndex
removeArrayKeyEvent: ->
$('body').off 'keydown'
highlightRowAtIndex: ($listItems, index) ->
# Remove the class for the previously focused row
$('.is-focused', @dropdown).removeClass 'is-focused'
# Update the class for the row at the specific index
$listItem = $listItems.eq(index)
$listItem.find('a:first-child').addClass "is-focused"
# Dropdown content scroll area
$dropdownContent = $listItem.closest('.dropdown-content')
dropdownScrollTop = $dropdownContent.scrollTop()
dropdownContentHeight = $dropdownContent.outerHeight()
dropdownContentTop = $dropdownContent.prop('offsetTop')
dropdownContentBottom = dropdownContentTop + dropdownContentHeight
# Get the offset bottom of the list item
listItemHeight = $listItem.outerHeight()
listItemTop = $listItem.prop('offsetTop')
listItemBottom = listItemTop + listItemHeight
if listItemBottom > dropdownContentBottom + dropdownScrollTop
# Scroll the dropdown content down
$dropdownContent.scrollTop(listItemBottom - dropdownContentBottom)
else if listItemTop < dropdownContentTop + dropdownScrollTop
# Scroll the dropdown content up
$dropdownContent.scrollTop(listItemTop - dropdownContentTop)
$.fn.glDropdown = (opts) ->
return @.each ->
new GitLabDropdown @, opts
if (!$.data @, 'glDropdown')
$.data(@, 'glDropdown', new GitLabDropdown @, opts)
@@ -1,8 +1,7 @@
class @IssuableContext
constructor: ->
constructor: (currentUser) ->
@initParticipants()
new UsersSelect()
new UsersSelect(currentUser)
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
$(".issuable-sidebar .inline-update").on "change", "select", ->
@@ -10,11 +9,21 @@ class @IssuableContext
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit()
$(document).on "click",".edit-link", (e) ->
block = $(@).parents('.block')
block.find('.selectbox').show()
block.find('.value').hide()
block.find('.js-select2').select2("open")
$(document).off("click", ".edit-link").on "click",".edit-link", (e) ->
$block = $(@).parents('.block')
$selectbox = $block.find('.selectbox')
if $selectbox.is(':visible')
$selectbox.hide()
$block.find('.value').show()
else
$selectbox.show()
$block.find('.value').hide()
if $selectbox.is(':visible')
setTimeout (->
$block.find('.dropdown-menu-toggle').trigger 'click'
), 0
$(".right-sidebar").niceScroll()
+1 -16
View File
@@ -6,25 +6,10 @@ class @Issue
constructor: ->
# Prevent duplicate event bindings
@disableTaskList()
@fixAffixScroll()
if $('a.btn-close').length
@initTaskList()
@initIssueBtnEventListeners()
fixAffixScroll: ->
fixAffix = ->
$discussion = $('.issuable-discussion')
$sidebar = $('.issuable-sidebar')
if $sidebar.hasClass('no-affix')
$sidebar.removeClass(['affix-top','affix'])
discussionHeight = $discussion.height()
sidebarHeight = $sidebar.height()
if sidebarHeight > discussionHeight
$discussion.height(sidebarHeight + 50)
$sidebar.addClass('no-affix')
$(window).on('resize', fixAffix)
fixAffix()
initTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('enable')
$(document).on 'tasklist:changed', '.detail-page-description .js-task-list-container', @updateTaskList
@@ -49,7 +34,7 @@ class @Issue
issueStatus = if isClose then 'close' else 'open'
new Flash(issueFailMessage, 'alert')
success: (data, textStatus, jqXHR) ->
if data.saved
if 'id' of data
$(document).trigger('issuable:change');
if isClose
$('a.btn-close').addClass('hidden')
+15 -10
View File
@@ -1,7 +1,6 @@
@Issues =
init: ->
Issues.initSearch()
Issues.initSelects()
Issues.initChecks()
$("body").on "ajax:success", ".close_issue, .reopen_issue", ->
@@ -17,18 +16,9 @@
$(this).html totalIssues - 1
reload: ->
Issues.initSelects()
Issues.initChecks()
$('#filter_issue_search').val($('#issue_search').val())
initSelects: ->
$("select#update_state_event").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#update_assignee_id").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#update_milestone_id").select2(width: 'resolve', dropdownAutoWidth: true)
$("select#label_name").select2(width: 'resolve', dropdownAutoWidth: true)
$("#milestone_id, #assignee_id, #label_name").on "change", ->
$(this).closest("form").submit()
initChecks: ->
$(".check_all_issues").click ->
$(".selected_issue").prop("checked", @checked)
@@ -36,6 +26,20 @@
$(".selected_issue").bind "change", Issues.checkChanged
# Update state filters if present in page
updateStateFilters: ->
stateFilters = $('.issues-state-filters')
newParams = {}
paramKeys = ['author_id', 'label_name', 'milestone_title', 'assignee_id', 'issue_search']
for paramKey in paramKeys
newParams[paramKey] = gl.utils.getUrlParameter(paramKey) or ''
if stateFilters.length
stateFilters.find('a').each ->
initialUrl = $(this).attr 'href'
$(this).attr 'href', gl.utils.mergeUrlParams(newParams, initialUrl)
# Make sure we trigger ajax request only after user stop typing
initSearch: ->
@timer = null
@@ -64,6 +68,7 @@
# Change url so if user reload a page - search results are saved
history.replaceState {page: issuesUrl}, document.title, issuesUrl
Issues.reload()
Issues.updateStateFilters()
dataType: "json"
checkChanged: ->
+176 -28
View File
@@ -4,31 +4,81 @@ class @LabelsSelect
$dropdown = $(dropdown)
projectId = $dropdown.data('project-id')
labelUrl = $dropdown.data('labels')
issueUpdateURL = $dropdown.data('issueUpdate')
selectedLabel = $dropdown.data('selected')
if selectedLabel
if selectedLabel?
selectedLabel = selectedLabel.split(',')
newLabelField = $('#new_label_name')
newColorField = $('#new_label_color')
showNo = $dropdown.data('show-no')
showAny = $dropdown.data('show-any')
defaultLabel = $dropdown.data('default-label')
abilityName = $dropdown.data('ability-name')
$selectbox = $dropdown.closest('.selectbox')
$block = $selectbox.closest('.block')
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span')
$value = $block.find('.value')
$loading = $block.find('.block-loading').fadeOut()
if newLabelField.length
$newLabelCreateButton = $('.js-new-label-btn')
$colorPreview = $('.js-dropdown-label-color-preview')
$newLabelError = $dropdown.parent().find('.js-label-error')
$newLabelError.hide()
# Suggested colors in the dropdown to chose from pre-chosen colors
$('.suggest-colors-dropdown a').on 'click', (e) ->
issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL?
if issueUpdateURL
labelHTMLTemplate = _.template(
'<% _.each(labels, function(label){ %>
<a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name=<%= label.title %>">
<span class="label color-label" style="background-color: <%= label.color %>;">
<%= label.title %>
</span>
</a>
<% }); %>'
);
labelNoneHTMLTemplate = _.template('<div class="light">None</div>')
if newLabelField.length and $dropdown.hasClass 'js-extra-options'
$('.suggest-colors-dropdown a').on "click", (e) ->
e.preventDefault()
e.stopPropagation()
newColorField.val $(this).data('color')
$('.js-dropdown-label-color-preview')
newColorField
.val($(this).data('color'))
.trigger('change')
$colorPreview
.css 'background-color', $(this).data('color')
.parent()
.addClass 'is-active'
$('.js-new-label-btn').on 'click', (e) ->
# Cancel button takes back to first page
resetForm = ->
newLabelField
.val ''
.trigger 'change'
newColorField
.val ''
.trigger 'change'
$colorPreview
.css 'background-color', ''
.parent()
.removeClass 'is-active'
$('.dropdown-menu-back').on 'click', ->
resetForm()
$('.js-cancel-label-btn').on 'click', (e) ->
e.preventDefault()
e.stopPropagation()
resetForm()
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
# Listen for change and keyup events on label and color field
# This allows us to enable the button when ready
enableLabelCreateButton = ->
if newLabelField.val() isnt '' and newColorField.val() isnt ''
$newLabelError.hide()
$('.js-new-label-btn').disable()
@@ -47,39 +97,118 @@ class @LabelsSelect
else
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
$newLabelCreateButton.enable()
else
$newLabelCreateButton.disable()
newLabelField.on 'keyup change', enableLabelCreateButton
newColorField.on 'keyup change', enableLabelCreateButton
# Send the API call to create the label
$newLabelCreateButton
.disable()
.on 'click', (e) ->
e.preventDefault()
e.stopPropagation()
if newLabelField.val() isnt '' and newColorField.val() isnt ''
$newLabelError.hide()
$('.js-new-label-btn').disable()
# Create new label with API
Api.newLabel projectId, {
name: newLabelField.val()
color: newColorField.val()
}, (label) ->
$('.js-new-label-btn').enable()
if label.message?
$newLabelError
.text label.message
.show()
else
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
saveLabelData = ->
selected = $dropdown
.closest('.selectbox')
.find("input[name='#{$dropdown.data('field-name')}']")
.map(->
@value
).get()
data = {}
data[abilityName] = {}
data[abilityName].label_ids = selected
if not selected.length
data[abilityName].label_ids = ['']
$loading.fadeIn()
$dropdown.trigger('loading.gl.dropdown')
$.ajax(
type: 'PUT'
url: issueUpdateURL
dataType: 'JSON'
data: data
).done (data) ->
$loading.fadeOut()
$dropdown.trigger('loaded.gl.dropdown')
$selectbox.hide()
data.issueURLSplit = issueURLSplit
labelCount = 0
if data.labels.length
template = labelHTMLTemplate(data)
labelCount = data.labels.length
else
template = labelNoneHTMLTemplate()
$value
.removeAttr('style')
.html(template)
$sidebarCollapsedValue.text(labelCount)
$value
.find('a')
.each((i) ->
setTimeout(=>
glAnimate($(@), 'pulse')
,200 * i
)
)
$dropdown.glDropdown(
data: (term, callback) ->
$.ajax(
url: labelUrl
).done (data) ->
if showNo
data.unshift(
id: 0
title: 'No Label'
)
if $dropdown.hasClass 'js-extra-options'
if showNo
data.unshift(
id: 0
title: 'No Label'
)
if showAny
data.unshift(
isAny: true
title: 'Any Label'
)
if data.length > 2
data.splice 2, 0, 'divider'
if showAny
data.unshift(
isAny: true
title: 'Any Label'
)
if data.length > 2
data.splice 2, 0, 'divider'
callback data
renderRow: (label) ->
if $.isArray(selectedLabel)
selected = ''
$.each selectedLabel, (i, selectedLbl) ->
selectedLbl = selectedLbl.trim()
if selected is '' and label.title is selectedLbl
selected = 'is-active'
else
selected = if label.title is selectedLabel then 'is-active' else ''
selectedClass = ''
if $selectbox.find("input[type='hidden']\
[name='#{$dropdown.data('field-name')}']\
[value='#{label.id}']").length
selectedClass = 'is-active'
color = if label.color? then "<span class='dropdown-label-box' style='background-color: #{label.color}'></span>" else ""
"<li>
<a href='#' class='#{selected}'>
<a href='#' class='#{selectedClass}'>
#{color}
#{label.title}
</a>
</li>"
@@ -87,6 +216,7 @@ class @LabelsSelect
search:
fields: ['title']
selectable: true
toggleLabel: (selected) ->
if selected and selected.title isnt 'Any Label'
selected.title
@@ -96,15 +226,33 @@ class @LabelsSelect
id: (label) ->
if label.isAny?
''
else
else if $dropdown.hasClass "js-filter-submit"
label.title
clicked: ->
else
label.id
hidden: ->
$selectbox.hide()
# display:block overrides the hide-collapse rule
$value.removeAttr('style')
if $dropdown.hasClass 'js-multiselect'
saveLabelData()
multiSelect: $dropdown.hasClass 'js-multiselect'
clicked: (label) ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is page is 'projects:merge_requests:index'
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
selectedLabel = label.title
Issues.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit()
else
if $dropdown.hasClass 'js-multiselect'
return
else
saveLabelData()
)
@@ -0,0 +1,13 @@
((w) ->
w.glAnimate = ($el, animation, done) ->
$el
.removeClass()
.addClass(animation + ' animated')
.one 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', ->
$(this).removeClass()
return
return
return
) window
@@ -0,0 +1,35 @@
((w) ->
notificationGranted = (message, opts, onclick) ->
notification = new Notification(message, opts)
# Hide the notification after X amount of seconds
setTimeout ->
notification.close()
, 8000
if onclick
notification.onclick = onclick
notifyPermissions = ->
if 'Notification' of window
Notification.requestPermission()
notifyMe = (message, body, icon, onclick) ->
opts =
body: body
icon: icon
# Let's check if the browser supports notifications
if !('Notification' of window)
# do nothing
else if Notification.permission == 'granted'
# If it's okay let's create a notification
notificationGranted message, opts, onclick
else if Notification.permission != 'denied'
Notification.requestPermission (permission) ->
# If the user accepts, let's create a notification
if permission == 'granted'
notificationGranted message, opts, onclick
w.notify = notifyMe
w.notifyPermissions = notifyPermissions
) window
@@ -0,0 +1,31 @@
((w) ->
w.gl ?= {}
w.gl.utils ?= {}
w.gl.utils.getUrlParameter = (sParam) ->
sPageURL = decodeURIComponent(window.location.search.substring(1))
sURLVariables = sPageURL.split('&')
sParameterName = undefined
i = 0
while i < sURLVariables.length
sParameterName = sURLVariables[i].split('=')
if sParameterName[0] is sParam
return if sParameterName[1] is undefined then true else sParameterName[1]
i++
# #
# @param {Object} params - url keys and value to merge
# @param {String} url
# #
w.gl.utils.mergeUrlParams = (params, url) ->
newUrl = decodeURIComponent(url)
for paramName, paramValue of params
pattern = new RegExp "\\b(#{paramName}=).*?(&|$)"
if url.search(pattern) >= 0
newUrl = newUrl.replace pattern, "$1#{paramValue}$2"
else
newUrl = "#{newUrl}#{(if newUrl.indexOf('?') > 0 then '&' else '?')}#{paramName}=#{paramValue}"
newUrl
) window
@@ -15,8 +15,6 @@ class @MergeRequest
this.$('.show-all-commits').on 'click', =>
this.showAllCommits()
@fixAffixScroll();
@initTabs()
# Prevent duplicate event bindings
@@ -30,20 +28,6 @@ class @MergeRequest
$: (selector) ->
this.$el.find(selector)
fixAffixScroll: ->
fixAffix = ->
$discussion = $('.issuable-discussion')
$sidebar = $('.issuable-sidebar')
if $sidebar.hasClass('no-affix')
$sidebar.removeClass(['affix-top','affix'])
discussionHeight = $discussion.height()
sidebarHeight = $sidebar.height()
if sidebarHeight > discussionHeight
$discussion.height(sidebarHeight + 50)
$sidebar.addClass('no-affix')
$(window).on('resize', fixAffix)
fixAffix()
initTabs: ->
if @opts.action != 'new'
# `MergeRequests#new` has no tab-persisting or lazy-loading behavior
@@ -73,7 +73,8 @@ class @MergeRequestTabs
@expandView()
else if action == 'diffs'
@loadDiff($target.attr('href'))
@shrinkView()
if bp? and bp.getBreakpointSize() isnt 'lg'
@shrinkView()
else if action == 'builds'
@loadBuilds($target.attr('href'))
@expandView()
@@ -2,13 +2,29 @@ class @MergeRequestWidget
# Initialize MergeRequestWidget behavior
#
# check_enable - Boolean, whether to check automerge status
# url_to_automerge_check - String, URL to use to check automerge status
# current_status - String, current automerge status
# ci_enable - Boolean, whether a CI service is enabled
# url_to_ci_check - String, URL to use to check CI status
# merge_check_url - String, URL to use to check automerge status
# ci_status_url - String, URL to use to check CI status
#
constructor: (@opts) ->
modal = $('#modal_merge_info').modal(show: false)
$('#modal_merge_info').modal(show: false)
@firstCICheck = true
@readyForCICheck = true
clearInterval @fetchBuildStatusInterval
@clearEventListeners()
@addEventListeners()
@pollCIStatus()
notifyPermissions()
clearEventListeners: ->
$(document).off 'page:change.merge_request'
addEventListeners: ->
$(document).on 'page:change.merge_request', =>
if $('body').data('page') isnt 'projects:merge_requests:show'
clearInterval @fetchBuildStatusInterval
@clearEventListeners()
mergeInProgress: (deleteSourceBranch = false)->
$.ajax
@@ -27,18 +43,71 @@ class @MergeRequestWidget
dataType: 'json'
getMergeStatus: ->
$.get @opts.url_to_automerge_check, (data) ->
$.get @opts.merge_check_url, (data) ->
$('.mr-state-widget').replaceWith(data)
getCiStatus: ->
if @opts.ci_enable
$.get @opts.url_to_ci_check, (data) =>
this.showCiState data.status
if data.coverage
this.showCiCoverage data.coverage
, 'json'
ciLabelForStatus: (status) ->
if status is 'success'
'passed'
else
status
showCiState: (state) ->
pollCIStatus: ->
@fetchBuildStatusInterval = setInterval ( =>
return if not @readyForCICheck
@getCIStatus(true)
@readyForCICheck = false
), 10000
getCIStatus: (showNotification) ->
_this = @
$('.ci-widget-fetching').show()
$.getJSON @opts.ci_status_url, (data) =>
@readyForCICheck = true
if @firstCICheck
@firstCICheck = false
@opts.ci_status = data.status
if @opts.ci_status is ''
@opts.ci_status = data.status
return
if data.status isnt @opts.ci_status and data.status?
@showCIStatus data.status
if data.coverage
@showCICoverage data.coverage
if showNotification
status = @ciLabelForStatus(data.status)
if status is "preparing"
title = @opts.ci_title.preparing
status = status.charAt(0).toUpperCase() + status.slice(1);
message = @opts.ci_message.preparing.replace('{{status}}', status)
else
title = @opts.ci_title.normal
message = @opts.ci_message.normal.replace('{{status}}', status)
title = title.replace('{{status}}', status)
message = message.replace('{{sha}}', data.sha)
message = message.replace('{{title}}', data.title)
notify(
title,
message,
@opts.gitlab_icon,
->
@close()
Turbolinks.visit _this.opts.builds_path
)
@opts.ci_status = data.status
showCIStatus: (state) ->
$('.ci_widget').hide()
allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"]
if state in allowed_states
@@ -48,13 +117,17 @@ class @MergeRequestWidget
@setMergeButtonClass('btn-danger')
when "running", "pending"
@setMergeButtonClass('btn-warning')
when "success"
@setMergeButtonClass('btn-create')
else
$('.ci_widget.ci-error').show()
@setMergeButtonClass('btn-danger')
showCiCoverage: (coverage) ->
showCICoverage: (coverage) ->
text = 'Coverage ' + coverage + '%'
$('.ci_widget:visible .ci-coverage').text(text)
setMergeButtonClass: (css_class) ->
$('.accept_merge_request').removeClass("btn-create").addClass(css_class)
$('.accept_merge_request')
.removeClass('btn-danger btn-warning btn-create')
.addClass(css_class)
@@ -1,36 +1,65 @@
class @MilestoneSelect
constructor: ->
constructor: (currentProject) ->
if currentProject?
_this = @
@currentProject = JSON.parse(currentProject)
$('.js-milestone-select').each (i, dropdown) ->
$dropdown = $(dropdown)
projectId = $dropdown.data('project-id')
milestonesUrl = $dropdown.data('milestones')
issueUpdateURL = $dropdown.data('issueUpdate')
selectedMilestone = $dropdown.data('selected')
showNo = $dropdown.data('show-no')
showAny = $dropdown.data('show-any')
showUpcoming = $dropdown.data('show-upcoming')
useId = $dropdown.data('use-id')
defaultLabel = $dropdown.data('default-label')
issuableId = $dropdown.data('issuable-id')
abilityName = $dropdown.data('ability-name')
$selectbox = $dropdown.closest('.selectbox')
$block = $selectbox.closest('.block')
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon')
$value = $block.find('.value')
$loading = $block.find('.block-loading').fadeOut()
if issueUpdateURL
milestoneLinkTemplate = _.template(
'<a href="/<%= namespace %>/<%= path %>/milestones/<%= iid %>"><%= title %></a>'
)
milestoneLinkNoneTemplate = '<div class="light">None</div>'
$dropdown.glDropdown(
data: (term, callback) ->
$.ajax(
url: milestonesUrl
).done (data) ->
if showNo
data.unshift(
id: '0'
title: 'No Milestone'
)
extraOptions = []
if showAny
data.unshift(
isAny: true
extraOptions.push(
id: 0
name: ''
title: 'Any Milestone'
)
if data.length > 2
data.splice 2, 0, 'divider'
if showNo
extraOptions.push(
id: -1
name: 'No Milestone'
title: 'No Milestone'
)
callback(data)
if showUpcoming
extraOptions.push(
id: -2
name: '#upcoming'
title: 'Upcoming'
)
if extraOptions.length > 2
extraOptions.push 'divider'
callback(extraOptions.concat(data))
filterable: true
search:
fields: ['title']
@@ -45,21 +74,57 @@ class @MilestoneSelect
milestone.title
id: (milestone) ->
if !useId
if !milestone.isAny?
milestone.title
else
''
milestone.name
else
milestone.id
isSelected: (milestone) ->
milestone.title is selectedMilestone
clicked: ->
milestone.name is selectedMilestone
hidden: ->
$selectbox.hide()
# display:block overrides the hide-collapse rule
$value.removeAttr('style')
clicked: (selected) ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is page is 'projects:merge_requests:index'
if $dropdown.hasClass 'js-filter-bulk-update'
return
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
if selected.name?
selectedMilestone = selected.name
else
selectedMilestone = ''
Issues.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit'
else if $dropdown.hasClass('js-filter-submit')
$dropdown.closest('form').submit()
else
selected = $selectbox
.find('input[type="hidden"]')
.val()
data = {}
data[abilityName] = {}
data[abilityName].milestone_id = selected
$loading
.fadeIn()
$dropdown.trigger('loading.gl.dropdown')
$.ajax(
type: 'PUT'
url: issueUpdateURL
data: data
).done (data) ->
$dropdown.trigger('loaded.gl.dropdown')
$loading.fadeOut()
$selectbox.hide()
$value.removeAttr('style')
if data.milestone?
data.milestone.namespace = _this.currentProject.namespace
data.milestone.path = _this.currentProject.path
$value.html(milestoneLinkTemplate(data.milestone))
$sidebarCollapsedValue.find('span').text(data.milestone.title)
else
$value.html(milestoneLinkNoneTemplate)
$sidebarCollapsedValue.find('span').text('No')
)
+20 -18
View File
@@ -251,13 +251,11 @@ class @Notes
Sets some hidden fields in the form.
###
setupMainTargetNoteForm: ->
# find the form
form = $(".js-new-note-form")
# insert the form after the button
form.clone().replaceAll $(".js-main-target-form")
form = form.prev("form")
# Set a global clone of the form for later cloning
@formClone = form.clone()
# show the form
@setupNoteForm(form)
@@ -266,9 +264,7 @@ class @Notes
form.removeClass "js-new-note-form"
form.addClass "js-main-target-form"
# remove unnecessary fields and buttons
form.find("#note_line_code").remove()
form.find(".js-close-discussion-note-form").remove()
###
General note form setup.
@@ -297,7 +293,14 @@ class @Notes
else
previewButton.removeClass("turn-on").addClass "turn-off"
textarea.on 'focus', ->
$(this).closest('.md-area').addClass 'is-focused'
textarea.on 'blur', ->
$(this).closest('.md-area').removeClass 'is-focused'
autosize(textarea)
new Autosave textarea, [
"Note"
form.find("#note_commit_id").val()
@@ -307,7 +310,6 @@ class @Notes
]
# remove notify commit author checkbox for non-commit notes
form.find(".js-notify-commit-author").remove() if form.find("#note_noteable_type").val() isnt "Commit"
GitLab.GfmAutoComplete.setup()
new DropzoneInput(form)
form.show()
@@ -455,15 +457,15 @@ class @Notes
Shows the note form below the notes.
###
replyToDiscussionNote: (e) =>
form = $(".js-new-note-form")
form = @formClone.clone()
replyLink = $(e.target).closest(".js-discussion-reply-button")
replyLink.hide()
# insert the form after the button
form.clone().insertAfter replyLink
replyLink.after form
# show the form
@setupDiscussionNoteForm(replyLink, replyLink.next("form"))
@setupDiscussionNoteForm(replyLink, form)
###
Shows the diff or discussion form and does some setup on it.
@@ -488,7 +490,9 @@ class @Notes
.text(form.find('.js-close-discussion-note-form').data('cancel-text'))
@setupNoteForm form
form.find(".js-note-text").focus()
form.addClass "js-discussion-note-form"
form
.removeClass('js-main-target-form')
.addClass("discussion-form js-discussion-note-form")
###
Called when clicking on the "add a comment" button on the side of a diff line.
@@ -498,9 +502,8 @@ class @Notes
###
addDiffNote: (e) =>
e.preventDefault()
link = e.currentTarget
form = $(".js-new-note-form")
row = $(link).closest("tr")
$link = $(e.currentTarget)
row = $link.closest("tr")
nextRow = row.next()
hasNotes = nextRow.is(".notes_holder")
addForm = false
@@ -509,7 +512,7 @@ class @Notes
# In parallel view, look inside the correct left/right pane
if @isParallelView()
lineType = $(link).data("lineType")
lineType = $link.data("lineType")
targetContent += "." + lineType
rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\"></td><td class=\"notes_content parallel old\"></td><td class=\"notes_line\"></td><td class=\"notes_content parallel new\"></td></tr>"
@@ -531,11 +534,11 @@ class @Notes
addForm = true
if addForm
newForm = form.clone()
newForm = @formClone.clone()
newForm.appendTo row.next().find(targetContent)
# show the form
@setupDiscussionNoteForm $(link), newForm
@setupDiscussionNoteForm $link, newForm
###
Called in response to "cancel" on a diff note form.
@@ -560,7 +563,6 @@ class @Notes
cancelDiscussionForm: (e) =>
e.preventDefault()
form = $(".js-new-note-form")
form = $(e.target).closest(".js-discussion-note-form")
@removeDiscussionNoteForm(form)
+44 -8
View File
@@ -1,5 +1,9 @@
class @Profile
constructor: ->
constructor: (opts = {}) ->
{
@form = $('.edit-user')
} = opts
# Automatically submit the Preferences form when any of its radio buttons change
$('.js-preferences-form').on 'change.preference', 'input[type=radio]', ->
$(this).parents('form').submit()
@@ -17,14 +21,46 @@ class @Profile
$('.update-notifications').on 'ajax:complete', ->
$(this).find('.btn-save').enable()
$('.js-choose-user-avatar-button').bind "click", ->
form = $(this).closest("form")
form.find(".js-user-avatar-input").click()
@bindEvents()
$('.js-user-avatar-input').bind "change", ->
form = $(this).closest("form")
filename = $(this).val().replace(/^.*[\\\/]/, '')
form.find(".js-avatar-filename").text(filename)
cropOpts =
filename: '.js-avatar-filename'
previewImage: '.avatar-image .avatar'
modalCrop: '.modal-profile-crop'
pickImageEl: '.js-choose-user-avatar-button'
uploadImageBtn: '.js-upload-user-avatar'
modalCropImg: '.modal-profile-crop-image'
@avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data 'glcrop'
bindEvents: ->
@form.on 'submit', @onSubmitForm
onSubmitForm: (e) =>
e.preventDefault()
@saveForm()
saveForm: ->
self = @
formData = new FormData(@form[0])
formData.append('user[avatar]', @avatarGlCrop.getBlob(), 'avatar.png')
$.ajax
url: @form.attr('action')
type: @form.attr('method')
data: formData
dataType: "json"
processData: false
contentType: false
success: (response) ->
new Flash(response.message, 'notice')
error: (jqXHR) ->
new Flash(jqXHR.responseJSON.message, 'alert')
complete: ->
window.scrollTo 0, 0
# Enable submit button after requests ends
self.form.find(':input[disabled]').enable()
$ ->
# Extract the SSH Key title from its comment
@@ -0,0 +1,55 @@
class @Sidebar
constructor: (currentUser) ->
@addEventListeners()
addEventListeners: ->
$('aside').on('click', '.sidebar-collapsed-icon', @sidebarCollapseClicked)
$('.dropdown').on('hidden.gl.dropdown', @sidebarDropdownHidden)
$('.dropdown').on('loading.gl.dropdown', @sidebarDropdownLoading)
$('.dropdown').on('loaded.gl.dropdown', @sidebarDropdownLoaded)
sidebarDropdownLoading: (e) ->
$sidebarCollapsedIcon = $(@).closest('.block').find('.sidebar-collapsed-icon')
img = $sidebarCollapsedIcon.find('img')
i = $sidebarCollapsedIcon.find('i')
$loading = $('<i class="fa fa-spinner fa-spin"></i>')
if img.length
img.before($loading)
img.hide()
else if i.length
i.before($loading)
i.hide()
sidebarDropdownLoaded: (e) ->
$sidebarCollapsedIcon = $(@).closest('.block').find('.sidebar-collapsed-icon')
img = $sidebarCollapsedIcon.find('img')
$sidebarCollapsedIcon.find('i.fa-spin').remove()
i = $sidebarCollapsedIcon.find('i')
if img.length
img.show()
else
i.show()
sidebarCollapseClicked: (e) ->
e.preventDefault()
$block = $(@).closest('.block')
$('aside')
.find('.gutter-toggle')
.trigger('click')
$editLink = $block.find('.edit-link')
if $editLink.length
$editLink.trigger('click')
$block.addClass('collapse-after-update')
$('.page-with-sidebar').addClass('with-overlay')
sidebarDropdownHidden: (e) ->
$block = $(@).closest('.block')
if $block.hasClass('collapse-after-update')
$block.removeClass('collapse-after-update')
$('.page-with-sidebar').removeClass('with-overlay')
$('aside')
.find('.gutter-toggle')
.trigger('click')
@@ -1,11 +1,296 @@
class @SearchAutocomplete
constructor: (search_autocomplete_path, project_id, project_ref) ->
project_id = '' unless project_id
project_ref = '' unless project_ref
query = "?project_id=" + project_id + "&project_ref=" + project_ref
$("#search").autocomplete
source: search_autocomplete_path + query
minLength: 1
select: (event, ui) ->
location.href = ui.item.url
KEYCODE =
ESCAPE: 27
BACKSPACE: 8
ENTER: 13
constructor: (opts = {}) ->
{
@wrap = $('.search')
@optsEl = @wrap.find('.search-autocomplete-opts')
@autocompletePath = @optsEl.data('autocomplete-path')
@projectId = @optsEl.data('autocomplete-project-id') || ''
@projectRef = @optsEl.data('autocomplete-project-ref') || ''
} = opts
# Dropdown Element
@dropdown = @wrap.find('.dropdown')
@dropdownContent = @dropdown.find('.dropdown-content')
@locationBadgeEl = @getElement('.search-location-badge')
@locationText = @getElement('.location-text')
@scopeInputEl = @getElement('#scope')
@searchInput = @getElement('.search-input')
@projectInputEl = @getElement('#search_project_id')
@groupInputEl = @getElement('#group_id')
@searchCodeInputEl = @getElement('#search_code')
@repositoryInputEl = @getElement('#repository_ref')
@clearInput = @getElement('.js-clear-input')
@saveOriginalState()
# Only when user is logged in
@createAutocomplete() if gon.current_user_id
@searchInput.addClass('disabled')
@saveTextLength()
@bindEvents()
# Finds an element inside wrapper element
getElement: (selector) ->
@wrap.find(selector)
saveOriginalState: ->
@originalState = @serializeState()
saveTextLength: ->
@lastTextLength = @searchInput.val().length
createAutocomplete: ->
@searchInput.glDropdown
filterInputBlur: false
filterable: true
filterRemote: true
highlight: true
enterCallback: false
filterInput: 'input#search'
search:
fields: ['text']
data: @getData.bind(@)
selectable: true
clicked: @onClick.bind(@)
getData: (term, callback) ->
_this = @
# Do not trigger request if input is empty
return if @searchInput.val() is ''
# Prevent multiple ajax calls
return if @loadingSuggestions
@loadingSuggestions = true
jqXHR = $.get(@autocompletePath, {
project_id: @projectId
project_ref: @projectRef
term: term
}, (response) ->
# Hide dropdown menu if no suggestions returns
if !response.length
_this.disableAutocomplete()
return
data = []
# List results
firstCategory = true
for suggestion in response
# Add group header before list each group
if lastCategory isnt suggestion.category
data.push 'separator' if !firstCategory
firstCategory = false if firstCategory
data.push
header: suggestion.category
lastCategory = suggestion.category
data.push
id: "#{suggestion.category.toLowerCase()}-#{suggestion.id}"
category: suggestion.category
text: suggestion.label
url: suggestion.url
# Add option to proceed with the search
if data.length
data.push('separator')
data.push
text: "Result name contains \"#{term}\""
url: "/search?\
search=#{term}\
&project_id=#{_this.projectInputEl.val()}\
&group_id=#{_this.groupInputEl.val()}"
callback(data)
).always ->
_this.loadingSuggestions = false
serializeState: ->
{
# Search Criteria
search_project_id: @projectInputEl.val()
group_id: @groupInputEl.val()
search_code: @searchCodeInputEl.val()
repository_ref: @repositoryInputEl.val()
scope: @scopeInputEl.val()
# Location badge
_location: @locationText.text()
}
bindEvents: ->
$(document).on 'click', @onDocumentClick
@searchInput.on 'keydown', @onSearchInputKeyDown
@searchInput.on 'keyup', @onSearchInputKeyUp
@searchInput.on 'click', @onSearchInputClick
@searchInput.on 'focus', @onSearchInputFocus
@clearInput.on 'click', @onClearInputClick
onDocumentClick: (e) =>
# If clicking outside the search box
# And search input is not focused
# And we are not clicking inside a suggestion
if not $.contains(@dropdown[0], e.target) and @isFocused and not $(e.target).parents('ul').length
@onSearchInputBlur()
enableAutocomplete: ->
# No need to enable anything if user is not logged in
return if !gon.current_user_id
_this = @
@loadingSuggestions = false
@dropdown.addClass('open')
@searchInput.removeClass('disabled')
onSearchInputKeyDown: =>
# Saves last length of the entered text
@saveTextLength()
onSearchInputKeyUp: (e) =>
switch e.keyCode
when KEYCODE.BACKSPACE
# when trying to remove the location badge
if @lastTextLength is 0 and @badgePresent()
@removeLocationBadge()
# When removing the last character and no badge is present
if @lastTextLength is 1
@disableAutocomplete()
# When removing any character from existin value
if @lastTextLength > 1
@enableAutocomplete()
when KEYCODE.ESCAPE
@restoreOriginalState()
else
# Handle the case when deleting the input value other than backspace
# e.g. Pressing ctrl + backspace or ctrl + x
if @searchInput.val() is ''
@disableAutocomplete()
else
# We should display the menu only when input is not empty
@enableAutocomplete()
@wrap.toggleClass 'has-value', !!e.target.value
# Avoid falsy value to be returned
return
onSearchInputClick: (e) =>
# Prevents closing the dropdown menu
e.stopImmediatePropagation()
onSearchInputFocus: =>
@isFocused = true
@wrap.addClass('search-active')
onClearInputClick: (e) =>
e.preventDefault()
@searchInput.val('').focus()
onSearchInputBlur: (e) =>
@isFocused = false
@wrap.removeClass('search-active')
# If input is blank then restore state
if @searchInput.val() is ''
@restoreOriginalState()
addLocationBadge: (item) ->
category = if item.category? then "#{item.category}: " else ''
value = if item.value? then item.value else ''
html = "<span class='location-badge'>
<i class='location-text'>#{category}#{value}</i>
</span>"
@locationBadgeEl.html(html)
@wrap.addClass('has-location-badge')
restoreOriginalState: ->
inputs = Object.keys @originalState
for input in inputs
@getElement("##{input}").val(@originalState[input])
if @originalState._location is ''
@locationBadgeEl.empty()
else
@addLocationBadge(
value: @originalState._location
)
@dropdown.removeClass 'open'
badgePresent: ->
@locationBadgeEl.children().length
resetSearchState: ->
inputs = Object.keys @originalState
for input in inputs
# _location isnt a input
break if input is '_location'
@getElement("##{input}").val('')
removeLocationBadge: ->
@locationBadgeEl.empty()
# Reset state
@resetSearchState()
@wrap.removeClass('has-location-badge')
disableAutocomplete: ->
@searchInput.addClass('disabled')
@dropdown.removeClass('open')
@restoreMenu()
restoreMenu: ->
html = "<ul>
<li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li>
</ul>"
@dropdownContent.html(html)
onClick: (item, $el, e) ->
if location.pathname.indexOf(item.url) isnt -1
e.preventDefault()
if not @badgePresent
if item.category is 'Projects'
@projectInputEl.val(item.id)
@addLocationBadge(
value: 'This project'
)
if item.category is 'Groups'
@groupInputEl.val(item.id)
@addLocationBadge(
value: 'This group'
)
$el.removeClass('is-active')
@disableAutocomplete()
@searchInput.val('').focus()
-1
View File
@@ -4,7 +4,6 @@ expanded = 'page-sidebar-expanded'
toggleSidebar = ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-expanded")
$('.sidebar-wrapper').toggleClass("sidebar-collapsed sidebar-expanded")
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
@@ -10,10 +10,10 @@ class @Subscription
btn = $(event.currentTarget)
action = btn.find('span').text()
current_status = @subscription_status.attr('data-status')
btn.prop('disabled', true)
btn.addClass('disabled')
$.post @url, =>
btn.prop('disabled', false)
btn.removeClass('disabled')
status = if current_status == 'subscribed' then 'unsubscribed' else 'subscribed'
@subscription_status.attr('data-status', status)
action = if status == 'subscribed' then 'Unsubscribe' else 'Subscribe'
+10
View File
@@ -6,10 +6,12 @@ class @Todos
clearListeners: ->
$('.done-todo').off('click')
$('.js-todos-mark-all').off('click')
$('.todo').off('click')
initBtnListeners: ->
$('.done-todo').on('click', @doneClicked)
$('.js-todos-mark-all').on('click', @allDoneClicked)
$('.todo').on('click', @goToTodoUrl)
doneClicked: (e) =>
e.preventDefault()
@@ -54,3 +56,11 @@ class @Todos
updateBadges: (data) ->
$('.todos-pending .badge, .todos-pending-count').text data.count
$('.todos-done .badge').text data.done_count
goToTodoUrl: (e)->
todoLink = $(this).data('url')
if e.metaKey
e.preventDefault()
window.open(todoLink,'_blank')
else
Turbolinks.visit(todoLink)
+121 -9
View File
@@ -1,7 +1,9 @@
class @UsersSelect
constructor: ->
constructor: (currentUser) ->
@usersPath = "/autocomplete/users.json"
@userPath = "/autocomplete/users/:id.json"
if currentUser?
@currentUser = JSON.parse(currentUser)
$('.js-user-search').each (i, dropdown) =>
$dropdown = $(dropdown)
@@ -12,6 +14,81 @@ class @UsersSelect
firstUser = $dropdown.data('first-user')
selectedId = $dropdown.data('selected')
defaultLabel = $dropdown.data('default-label')
issueURL = $dropdown.data('issueUpdate')
$selectbox = $dropdown.closest('.selectbox')
$block = $selectbox.closest('.block')
abilityName = $dropdown.data('ability-name')
$value = $block.find('.value')
$collapsedSidebar = $block.find('.sidebar-collapsed-user')
$loading = $block.find('.block-loading').fadeOut()
$block.on('click', '.js-assign-yourself', (e) =>
e.preventDefault()
assignTo(@currentUser.id)
)
assignTo = (selected) ->
data = {}
data[abilityName] = {}
data[abilityName].assignee_id = selected
$loading
.fadeIn()
$dropdown.trigger('loading.gl.dropdown')
$.ajax(
type: 'PUT'
dataType: 'json'
url: issueURL
data: data
).done (data) ->
$dropdown.trigger('loaded.gl.dropdown')
$loading.fadeOut()
$selectbox.hide()
if data.assignee
user =
name: data.assignee.name
username: data.assignee.username
avatar: data.assignee.avatar_url
else
user =
name: 'Unassigned'
username: ''
avatar: ''
$value.html(assigneeTemplate(user))
$collapsedSidebar.html(collapsedAssigneeTemplate(user))
collapsedAssigneeTemplate = _.template(
'<% if( avatar ) { %>
<a class="author_link" href="/u/<%= username %>">
<img width="24" class="avatar avatar-inline s24" alt="" src="<%= avatar %>">
<span class="author">Toni Boehm</span>
</a>
<% } else { %>
<i class="fa fa-user"></i>
<% } %>'
)
assigneeTemplate = _.template(
'<% if (username) { %>
<a class="author_link " href="/u/<%= username %>">
<% if( avatar ) { %>
<img width="32" class="avatar avatar-inline s32" alt="" src="<%= avatar %>">
<% } %>
<span class="author"><%= name %></span>
<span class="username">
@<%= username %>
</span>
</a>
<% } else { %>
<span class="assign-yourself">
No assignee -
<a href="#" class="js-assign-yourself">
assign yourself
</a>
</span>
<% } %>'
)
$dropdown.glDropdown(
data: (term, callback) =>
@@ -30,6 +107,7 @@ class @UsersSelect
if showNullUser
showDivider += 1
users.unshift(
beforeDivider: true
name: 'Unassigned',
id: 0
)
@@ -39,6 +117,7 @@ class @UsersSelect
name = showAnyUser
name = 'Any User' if name == true
anyUser = {
beforeDivider: true
name: name,
id: null
}
@@ -55,40 +134,73 @@ class @UsersSelect
fields: ['name', 'username']
selectable: true
fieldName: $dropdown.data('field-name')
toggleLabel: (selected) ->
if selected && 'id' of selected
selected.name
else
defaultLabel
clicked: ->
inputId: 'issue_assignee_id'
hidden: (e) ->
$selectbox.hide()
# display:block overrides the hide-collapse rule
$value.removeAttr('style')
clicked: (user) ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is page is 'projects:merge_requests:index'
if $dropdown.hasClass('js-filter-bulk-update')
return
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
selectedId = user.id
Issues.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit()
else
selected = $dropdown
.closest('.selectbox')
.find("input[name='#{$dropdown.data('field-name')}']").val()
assignTo(selected)
renderRow: (user) ->
username = if user.username then "@#{user.username}" else ""
avatar = if user.avatar_url then user.avatar_url else false
selected = if user.id is selectedId then "is-active" else ""
img = ""
if avatar
img = "<img src='#{avatar}' class='avatar avatar-inline' width='30' />"
if user.beforeDivider?
"<li>
<a href='#' class='#{selected}'>
#{user.name}
</a>
</li>"
else
if avatar
img = "<img src='#{avatar}' class='avatar avatar-inline' width='30' />"
"<li>
# split into three parts so we can remove the username section if nessesary
listWithName = "<li>
<a href='#' class='dropdown-menu-user-link #{selected}'>
#{img}
<strong class='dropdown-menu-user-full-name'>
#{user.name}
</strong>
<span class='dropdown-menu-user-username'>
</strong>"
listWithUserName = "<span class='dropdown-menu-user-username'>
#{username}
</span>
</a>
</span>"
listClosingTags = "</a>
</li>"
if username is ''
listWithUserName = ''
listWithName + listWithUserName + listClosingTags
)
$('.ajax-users-select').each (i, select) =>
+1 -1
View File
@@ -42,7 +42,7 @@ class @ZenMode
$(e.currentTarget).trigger('zen_mode:leave')
$(document).on 'zen_mode:enter', (e) =>
@enter(e.target.parentNode)
@enter($(e.target).closest('.md-area').find('.zen-backdrop'))
$(document).on 'zen_mode:leave', (e) =>
@exit()
+2
View File
@@ -9,6 +9,8 @@
*= require_self
*= require dropzone/basic
*= require cal-heatmap
*= require cropper.css
*= require animate
*/
/*
+2 -2
View File
@@ -13,10 +13,10 @@
// Toggle between two states.
.js-toggler-container {
.turn-on { display: block; }
.turn-on { display: block; }
.turn-off { display: none; }
&.on {
.turn-on { display: none; }
.turn-on { display: none; }
.turn-off { display: block; }
}
}
+27 -2
View File
@@ -7,6 +7,7 @@
&:focus,
&:active {
outline: none;
background-color: $btn-active-gray;
@include box-shadow($gl-btn-active-background);
}
}
@@ -27,7 +28,8 @@
color: $color;
}
&:active {
&:active,
&.active {
@include box-shadow ($gl-btn-active-background);
background-color: $dark;
@@ -61,7 +63,7 @@
}
@mixin btn-white {
@include btn-color($white-light, $border-white-light, $white-normal, $border-white-normal, $white-dark, $border-white-dark, #313236);
@include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-white-dark, $btn-white-active);
}
.btn {
@@ -218,3 +220,26 @@
margin-right: 5px;
}
}
.btn-text-field {
width: 100%;
text-align: left;
padding: 6px 16px;
border-color: $border-color;
color: $btn-placeholder-gray;
background-color: $background-color;
&:hover,
&:active,
&:focus {
cursor: text;
box-shadow: none;
border-color: $border-color;
color: $btn-placeholder-gray;
background-color: $background-color;
}
}
.btn-file-option {
background: linear-gradient(180deg, $white-light 25%, $gray-light 100%);
}
@@ -1,3 +1,9 @@
.calender-block {
@media (min-width: $screen-sm-min) and (max-width: $screen-lg-min) {
overflow-x: scroll;
}
}
.user-calendar-activities {
.calendar_onclick_hr {
padding: 0;
+3 -9
View File
@@ -121,17 +121,10 @@ p.time {
text-shadow: none;
}
.thin_area{
.thin_area {
height: 150px;
}
// Fixes alignment on notes.
.new_note {
label {
text-align: left;
}
}
// Fix issue with notes & lists creating a bunch of bottom borders.
li.note {
img { max-width: 100% }
@@ -148,7 +141,7 @@ li.note {
}
}
.wiki_content code, .readme code{
.wiki_content code, .readme code {
background-color: inherit;
}
@@ -378,6 +371,7 @@ table {
position: absolute;
top: 0;
right: 0;
min-width: 250px;
visibility: hidden;
}
}
+80 -31
View File
@@ -42,7 +42,7 @@
font-size: 15px;
text-align: left;
border: 1px solid $dropdown-toggle-border-color;
border-radius: 2px;
border-radius: $dropdown-border-radius;
outline: 0;
text-overflow: ellipsis;
white-space: nowrap;
@@ -75,12 +75,12 @@
width: 240px;
margin-top: 2px;
margin-bottom: 0;
padding: 10px;
font-size: 14px;
font-size: 15px;
font-weight: normal;
padding: 10px 0;
background-color: $dropdown-bg;
border: 1px solid $dropdown-border-color;
border-radius: $border-radius-base;
border-radius: $dropdown-border-radius;
box-shadow: 0 2px 4px $dropdown-shadow-color;
&.is-loading {
@@ -101,9 +101,17 @@
li {
text-align: left;
list-style: none;
padding: 0 10px;
}
.divider {
height: 1px;
margin: 8px 10px;
padding: 0;
background-color: $dropdown-divider-color;
}
.separator {
width: 100%;
height: 1px;
margin-top: 8px;
@@ -130,6 +138,27 @@
text-decoration: none;
outline: 0;
}
&.dropdown-menu-empty-link {
&.is-focused {
background-color: $dropdown-empty-row-bg;
}
}
&.dropdown-menu-user-link {
line-height: 16px;
}
}
.dropdown-header {
color: $dropdown-header-color;
font-size: 13px;
line-height: 22px;
padding: 0 10px 10px;
}
.separator + .dropdown-header {
padding-top: 2px;
}
}
@@ -148,6 +177,10 @@
.dropdown-menu-back {
display: block;
}
.dropdown-content {
padding: 0 10px;
}
}
}
@@ -161,13 +194,13 @@
}
.dropdown-menu-user-link {
padding-top: 7px;
padding-top: 10px;
padding-bottom: 7px;
}
.dropdown-menu-user-full-name {
display: block;
font-weight: 600;
font-weight: 500;
line-height: 16px;
text-overflow: ellipsis;
overflow: hidden;
@@ -183,7 +216,7 @@
}
.dropdown-select {
width: 280px;
width: $dropdown-width;
}
.dropdown-menu-align-right {
@@ -212,20 +245,11 @@
}
}
.dropdown-header {
padding-left: 5px;
padding-right: 5px;
color: $dropdown-header-color;
font-size: 13px;
line-height: 22px;
}
.dropdown-title {
position: relative;
margin-bottom: 10px;
padding-left: 30px;
padding-right: 30px;
padding-bottom: 10px;
padding: 0 25px 15px;
margin: 0 10px 10px;
font-weight: 600;
line-height: 1;
text-align: center;
@@ -237,7 +261,7 @@
.dropdown-title-button {
position: absolute;
top: -1px;
top: 0;
padding: 0;
color: $dropdown-title-btn-color;
font-size: 14px;
@@ -251,25 +275,49 @@
}
.dropdown-menu-close {
right: 0;
right: 5px;
width: 20px;
height: 20px;
top: -1px;
}
.dropdown-menu-back {
left: 0;
left: 7px;
top: 2px;
}
.dropdown-input {
position: relative;
margin-bottom: 10px;
padding: 0 10px;
.fa {
position: absolute;
top: 10px;
right: 10px;
right: 20px;
color: #c7c7c7;
font-size: 12px;
pointer-events: none;
}
.dropdown-input-clear {
display: none;
cursor: pointer;
pointer-events: all;
right: 22px;
top: 9px;
font-size: 14px;
}
&.has-value {
.dropdown-input-clear {
display: block;
}
.dropdown-input-search {
display: none;
}
}
}
.dropdown-input-field {
@@ -286,13 +334,13 @@
border-color: $dropdown-input-focus-border;
box-shadow: 0 0 4px $dropdown-input-focus-shadow;
+ .fa {
~ .fa {
color: $dropdown-link-color;
}
}
&:hover {
+ .fa {
~ .fa {
color: $dropdown-link-color;
}
}
@@ -338,11 +386,12 @@
}
}
.dropdown-menu-labels {
.label {
position: relative;
width: 30px;
margin-right: 5px;
text-indent: -99999px;
}
.dropdown-label-box {
position: relative;
top: 3px;
margin-right: 5px;
display: inline-block;
width: 15px;
height: 15px;
border-radius: $border-radius-base;
}
+8 -4
View File
@@ -3,12 +3,10 @@
*
*/
.file-holder {
border: none;
border: 1px solid $border-color;
&.readme-holder {
margin-top: 10px;
border-bottom: 0;
margin: $gl-padding-top 0;
}
table {
@@ -17,11 +15,13 @@
.file-title {
position: relative;
background: $background-color;
background-color: $background-color;
border-bottom: 1px solid $border-color;
margin: 0;
text-align: left;
padding: 10px $gl-padding;
word-wrap: break-word;
border-radius: 3px 3px 0 0;
.file-actions {
float: right;
@@ -50,6 +50,10 @@
}
}
a:not(.btn) {
color: $gl-dark-link-color;
}
.left-options {
margin-top: -3px;
}
@@ -3,7 +3,7 @@
vertical-align: top;
}
@media (min-width: $screen-sm-min) {
@media (min-width: $screen-sm-min) {
.issues-filters,
.issues_bulk_update {
.dropdown-menu-toggle {
+1 -35
View File
@@ -6,40 +6,6 @@ input {
border-radius: $border-radius-base;
}
input[type='search'] {
background-color: white;
padding-left: 10px;
}
input[type='search'].search-input {
background-repeat: no-repeat;
background-position: 10px;
background-size: 16px;
background-position-x: 30%;
padding-left: 10px;
background-color: $gray-light;
&.search-input[value=""] {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC');
}
&.search-input::-webkit-input-placeholder {
text-align: center;
}
&.search-input:-moz-placeholder { /* Firefox 18- */
text-align: center;
}
&.search-input::-moz-placeholder { /* Firefox 19+ */
text-align: center;
}
&.search-input:-ms-input-placeholder {
text-align: center;
}
}
input[type='text'].danger {
background: #f2dede!important;
border-color: #d66;
@@ -125,7 +91,7 @@ label {
}
.form-control::-webkit-input-placeholder {
color: #7f8fa4;
color: $gl-placeholder-color;
}
.input-group {
+4 -24
View File
@@ -36,7 +36,7 @@ header {
padding: 0;
.nav > li > a {
color: #7f8fa4;
color: $gl-icon-color;
font-size: 18px;
padding: 0;
margin: ($header-height - 28) / 2 0;
@@ -62,7 +62,7 @@ header {
background-color: #eee;
}
&.active {
color: #7f8fa4;
color: $gl-icon-color;
}
}
}
@@ -81,14 +81,14 @@ header {
font-size: 19px;
line-height: $header-height;
font-weight: normal;
color: #4c4e54;
color: $gl-text-color;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: top;
white-space: nowrap;
a {
color: #4c4e54;
color: $gl-text-color;
&:hover {
text-decoration: underline;
}
@@ -117,26 +117,6 @@ header {
}
}
.search {
margin-right: 10px;
margin-left: 10px;
margin-top: ($header-height - 36) / 2;
form {
margin: 0;
padding: 0;
}
.search-input {
width: 220px;
&:focus {
@include box-shadow(none);
outline: none;
}
}
}
.impersonation i {
color: $red-normal;
}
@@ -1,9 +1,7 @@
.div-dropzone-wrapper {
.div-dropzone {
position: relative;
padding: 0;
border: 0;
margin-bottom: 5px;
margin-bottom: -5px;
.div-dropzone-focus {
border-color: #66afe9 !important;
@@ -25,12 +23,10 @@
.div-dropzone-spinner {
position: absolute;
top: 100%;
left: 100%;
margin-top: -1.1em;
margin-left: -1.1em;
bottom: 10px;
right: 5px;
opacity: 0;
font-size: 30px;
font-size: 20px;
transition: opacity 200ms ease-in-out;
}
@@ -65,17 +61,30 @@
position: relative;
}
.md-header {
.nav-links {
.active {
a {
border-bottom-color: #000;
}
}
a {
padding-top: 0;
line-height: 1;
}
}
}
.referenced-users {
color: #4c4e54;
padding-top: 10px;
}
.md-preview-holder {
background: #fff;
border: 1px solid #ddd;
min-height: 169px;
padding: 5px;
box-shadow: none;
min-height: 167px;
padding: 10px 0;
overflow-x: auto;
}
.markdown-area {
+2 -2
View File
@@ -107,7 +107,7 @@
}
.page-title {
.note_created_ago, .new-issue-link {
.note-created-ago, .new-issue-link {
display: none;
}
}
@@ -116,7 +116,7 @@
display: none;
}
aside:not(.right-sidebar){
aside:not(.right-sidebar) {
display: none;
}
+13 -1
View File
@@ -56,6 +56,17 @@
}
}
.nav-search {
display: inline-block;
width: 50%;
padding: 11px 0;
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-min) {
width: 100%;
}
}
.nav-links {
display: inline-block;
width: 50%;
@@ -100,6 +111,7 @@
> form {
display: inline-block;
margin-top: -1px;
}
.icon-label {
@@ -110,7 +122,7 @@
height: 34px;
display: inline-block;
position: relative;
top: 1px;
top: 2px;
margin-right: $gl-padding-top;
/* Medium devices (desktops, 992px and up) */
@@ -44,13 +44,14 @@
@include box-shadow(rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0);
@include border-radius ($border-radius-default);
border: none;
min-width: 175px;
}
.select2-results .select2-result-label {
padding: 10px 15px;
}
.select2-drop{
.select2-drop {
color: #7f8fa4;
}
+69 -85
View File
@@ -1,3 +1,10 @@
#logo {
z-index: 2;
position: absolute;
width: 58px;
cursor: pointer;
}
.page-with-sidebar {
padding-top: $header-height;
transition-duration: .3s;
@@ -18,24 +25,6 @@
position: absolute;
left: 0;
}
#logo {
z-index: 2;
position: absolute;
width: 58px;
cursor: pointer;
}
&.right-sidebar-expanded {
/* Extra small devices (phones, less than 768px) */
/* No media query since this is the default in Bootstrap */
padding-right: 0;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
padding-right: $gutter_width;
}
}
}
.sidebar-wrapper {
@@ -202,53 +191,27 @@
}
}
@mixin expanded-sidebar {
padding-left: $sidebar_collapsed_width;
.collapse-nav a {
width: $sidebar_width;
position: fixed;
bottom: 0;
left: 0;
font-size: 13px;
background: transparent;
height: 40px;
text-align: center;
line-height: 40px;
transition-duration: .3s;
outline: none;
@media (min-width: $screen-md-min) {
padding-left: $sidebar_width;
}
&.right-sidebar-collapsed {
/* Extra small devices (phones, less than 768px) */
padding-right: 0;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
padding-right: $sidebar_collapsed_width;
}
}
.sidebar-wrapper {
width: $sidebar_width;
.nav-sidebar {
width: $sidebar_width;
}
.nav-sidebar li a{
width: 230px;
&.back-link {
i {
opacity: 0;
}
}
}
&:hover {
text-decoration: none;
}
}
@mixin collapsed-sidebar {
.page-sidebar-collapsed {
padding-left: $sidebar_collapsed_width;
&.right-sidebar-collapsed {
/* Extra small devices (phones, less than 768px) */
padding-right: 0;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
padding-right: $sidebar_collapsed_width;
}
}
.sidebar-wrapper {
width: $sidebar_collapsed_width;
@@ -293,35 +256,56 @@
}
}
.collapse-nav a {
width: $sidebar_width;
position: fixed;
bottom: 0;
left: 0;
font-size: 13px;
background: transparent;
height: 40px;
text-align: center;
line-height: 40px;
transition-duration: .3s;
outline: none;
}
.page-sidebar-expanded {
padding-left: $sidebar_collapsed_width;
.collapse-nav a:hover {
text-decoration: none;
background: #f2f6f7;
}
@media (min-width: $screen-md-min) {
padding-left: $sidebar_width;
}
.page-sidebar-collapsed {
/* Extra small devices (phones, less than 768px) */
@include collapsed-sidebar;
padding-right: 0;
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) {
@include collapsed-sidebar;
.sidebar-wrapper {
width: $sidebar_width;
.nav-sidebar {
width: $sidebar_width;
}
.nav-sidebar li a {
width: 230px;
&.back-link {
i {
opacity: 0;
}
}
}
}
}
.page-sidebar-expanded {
@include expanded-sidebar;
.right-sidebar-collapsed {
padding-right: 0;
@media (min-width: $screen-sm-min) {
padding-right: $sidebar_collapsed_width;
}
.sidebar-collapsed-icon {
cursor: pointer;
}
}
.right-sidebar-expanded {
padding-right: 0;
@media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
padding-right: $sidebar_collapsed_width;
}
@media (min-width: $screen-md-min) {
padding-right: $gutter_width;
}
&.with-overlay {
padding-right: $sidebar_collapsed_width;
}
}
@@ -14,10 +14,6 @@
background: $row-hover;
}
&:last-child {
border-bottom: none;
}
.avatar {
margin-right: 15px;
}
@@ -56,8 +56,8 @@ $component-active-bg: $brand-info;
//##
$input-color: $text-color;
$input-border: #e7e9ed;
$input-border-focus: #7f8fa4;
$input-border: $border-color;
$input-border-focus: $focus-border-color;
$legend-color: $text-color;
@@ -138,6 +138,12 @@
}
}
a.no-attachment-icon {
&:before {
display: none;
}
}
/* Link to current header. */
h1, h2, h3, h4, h5, h6 {
position: relative;
@@ -244,7 +250,7 @@ a > code {
* Textareas intended for GFM
*
*/
textarea.js-gfm-input {
.js-gfm-input {
font-family: $monospace_font;
color: $gl-text-color;
}
+64 -17
View File
@@ -10,9 +10,10 @@ $gutter_inner_width: 258px;
/*
* UI elements
*/
$border-color: #efeff1;
$border-color: #e5e5e5;
$focus-border-color: #3aabf0;
$table-border-color: #eef0f2;
$background-color: #faf9f9;
$background-color: #fafafa;
/*
* Text
@@ -26,6 +27,7 @@ $gl-text-orange: #d90;
$gl-link-color: #3084bb;
$gl-dark-link-color: #333;
$gl-placeholder-color: #8f8f8f;
$gl-icon-color: $gl-placeholder-color;
$gl-gray: $gl-text-color;
$gl-header-color: $gl-title-color;
@@ -66,7 +68,7 @@ $header-height: 58px;
$fixed-layout-width: 1280px;
$gl-avatar-size: 40px;
$error-exclamation-point: #e62958;
$border-radius-default: 3px;
$border-radius-default: 2px;
$btn-transparent-color: #8f8f8f;
$ssh-key-icon-color: #8f8f8f;
$ssh-key-icon-size: 18px;
@@ -79,7 +81,7 @@ $provider-btn-not-active-color: #4688f1;
$white-light: #fff;
$white-normal: #ededed;
$white-dark: #ededed;
$white-dark: #ececec;
$gray-light: #faf9f9;
$gray-normal: #f5f5f5;
@@ -102,9 +104,11 @@ $orange-light: rgba(252, 109, 38, 0.80);
$orange-normal: #e75e40;
$orange-dark: #ce5237;
$red-light: #f06559;
$red-normal: #e52c5a;
$red-dark: #d22852;
$red-light: #e52c5a;
$red-normal: #d22852;
$red-dark: darken($red-normal, 5%);
$black-transparent: rgba(0, 0, 0, 0.3);
$border-white-light: #f1f2f4;
$border-white-normal: #d6dae2;
@@ -126,9 +130,9 @@ $border-orange-light: #fc6d26;
$border-orange-normal: #ce5237;
$border-orange-dark: #c14e35;
$border-red-light: #f24f41;
$border-red-normal: #d22852;
$border-red-dark: #ca264f;
$border-red-light: #d22852;
$border-red-normal: #ca264f;
$border-red-dark: darken($border-red-normal, 5%);
$help-well-bg: #fafafa;
$help-well-border: #e5e5e5;
@@ -148,15 +152,22 @@ $gl-success: $green-normal;
$gl-info: $blue-normal;
$gl-warning: $orange-normal;
$gl-danger: $red-normal;
$gl-btn-active-background: rgba(0, 0, 0, 0.12);
$gl-btn-active-gradient: inset 0 0 4px $gl-btn-active-background;
$gl-btn-active-background: rgba(0, 0, 0, 0.16);
$gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background;
/*
* Commit Diff Colors
*/
$added: #63c363;
$deleted: #f77;
$line-added: #ecfdf0;
$line-added-dark: #c7f0d2;
$line-removed: #fbe9eb;
$line-removed-dark: #fac5cd;
$line-number-old: #f9d7dc;
$line-number-new: #ddfbe6;
$match-line: #fafafa;
$table-border-gray: #f0f0f0;
/*
* Fonts
*/
@@ -166,17 +177,20 @@ $regular_font: 'Source Sans Pro', "Helvetica Neue", Helvetica, Arial, sans-serif
/*
* Dropdowns
*/
$dropdown-border-radius: 2px;
$dropdown-width: 300px;
$dropdown-bg: #fff;
$dropdown-link-color: #555;
$dropdown-link-hover-bg: rgba(#000, .04);
$dropdown-link-hover-bg: $row-hover;
$dropdown-empty-row-bg: rgba(#000, .04);
$dropdown-border-color: rgba(#000, .1);
$dropdown-shadow-color: rgba(#000, .1);
$dropdown-divider-color: rgba(#000, .1);
$dropdown-header-color: #959494;
$dropdown-title-btn-color: #bfbfbf;
$dropdown-input-color: #c7c7c7;
$dropdown-input-focus-border: rgb(58, 171, 240);
$dropdown-input-focus-shadow: rgba(#000, .2);
$dropdown-input-color: #555;
$dropdown-input-focus-border: $focus-border-color;
$dropdown-input-focus-shadow: rgba($dropdown-input-focus-border, .4);
$dropdown-loading-bg: rgba(#fff, .6);
$dropdown-toggle-bg: #fff;
@@ -186,9 +200,42 @@ $dropdown-toggle-hover-border-color: darken($dropdown-toggle-border-color, 15%);
$dropdown-toggle-icon-color: #c4c4c4;
$dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color;
/*
* Buttons
*/
$btn-active-gray: #ececec;
$btn-placeholder-gray: #c7c7c7;
$btn-white-active: #848484;
/*
* Award emoji
*/
$award-emoji-menu-bg: #fff;
$award-emoji-menu-border: #f1f2f4;
$award-emoji-new-btn-icon-color: #dcdcdc;
/*
* Search Box
*/
$search-input-border-color: rgba(#4688f1, .8);
$search-input-focus-shadow-color: $dropdown-input-focus-shadow;
$search-input-width: 244px;
$location-badge-color: #aaa;
$location-badge-bg: $gray-normal;
$location-badge-active-bg: #4f91f8;
$location-icon-color: #e7e9ed;
$location-icon-active-color: #807e7e;
/*
* Notes
*/
$notes-light-color: #8e8e8e;
$notes-action-color: #c3c3c3;
$notes-role-color: #8e8e8e;
$notes-role-border-color: #e4e4e4;
$note-disabled-comment-color: #b2b2b2;
$note-form-border-color: #e5e5e5;
$note-toolbar-color: #959494;
$zen-control-hover-color: #111;
+54 -53
View File
@@ -1,61 +1,62 @@
.zennable {
a.js-zen-enter {
color: $gl-gray;
position: absolute;
.zen-backdrop {
&.fullscreen {
background-color: white;
position: fixed;
top: 0;
right: 4px;
line-height: 56px;
}
bottom: 0;
left: 0;
right: 0;
z-index: 1031;
a.js-zen-leave {
display: none;
color: $gl-text-color;
position: absolute;
top: 10px;
right: 10px;
padding: 5px;
font-size: 36px;
&:hover {
color: #111;
textarea {
border: none;
box-shadow: none;
border-radius: 0;
color: #000;
font-size: 20px;
line-height: 26px;
padding: 30px;
display: block;
outline: none;
resize: none;
height: 100vh;
max-width: 900px;
margin: 0 auto;
}
}
.zen-backdrop {
&.fullscreen {
background-color: white;
position: fixed;
.zen-control-leave {
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1031;
textarea {
border: none;
box-shadow: none;
border-radius: 0;
color: #000;
font-size: 20px;
line-height: 26px;
padding: 30px;
display: block;
outline: none;
resize: none;
height: 100vh;
max-width: 900px;
margin: 0 auto;
}
a.js-zen-enter {
display: none;
}
a.js-zen-leave {
display: block;
position: absolute;
top: 0;
}
}
}
}
.zen-cotrol {
padding: 0;
color: #555;
background: none;
border: 0;
}
.zen-control-full {
color: $note-toolbar-color;
&:hover {
color: $gl-link-color;
text-decoration: none;
}
}
.zen-control-leave {
display: none;
color: $gl-text-color;
position: absolute;
right: 10px;
padding: 5px;
font-size: 36px;
&:hover {
color: $zen-control-hover-color;
}
}
@@ -6,7 +6,7 @@
}
.diff-line-num, .diff-line-num a {
color: rgba(0, 0, 0, 0.3);
color: $black-transparent;
}
// Code itself
@@ -30,7 +30,7 @@
}
.line_content.match {
color: rgba(0, 0, 0, 0.3);
color: $black-transparent;
background: rgba(255, 255, 255, 0.4);
}
}
+12 -12
View File
@@ -6,12 +6,12 @@
}
.diff-line-num, .diff-line-num a {
color: rgba(0, 0, 0, 0.3);
color: $black-transparent;
}
// Code itself
pre.code, .diff-line-num {
border-color: $border-color;
border-color: $table-border-gray;
}
&, pre.code, .line_holder .line_content {
@@ -23,36 +23,36 @@
.line_holder {
.diff-line-num {
&.old {
background: #fdd;
border-color: #f1c0c0;
background-color: $line-number-old;
border-color: $line-removed-dark;
}
&.new {
background: #dbffdb;
border-color: #c1e9c1;
background-color: $line-number-new;
border-color: $line-added-dark;
}
}
.line_content {
&.old {
background: #ffecec;
background: $line-removed;
span.idiff {
background-color: #f8cbcb;
background-color: $line-removed-dark;
}
}
&.new {
background: #eaffea;
background-color: $line-added;
span.idiff {
background-color: #a6f3a6;
background-color: $line-added-dark;
}
}
&.match {
color: rgba(0, 0, 0, 0.3);
background: #fafafa;
color: $black-transparent;
background: $match-line;
}
}
}
+1 -1
View File
@@ -37,7 +37,7 @@
height: 300px;
overflow-y: scroll;
input.emoji-search{
input.emoji-search {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAFu0lEQVRIia1WTahkVxH+quqce7vf6zdvJpHoIlkYJ2SiJiIokmQjgoGgIAaEIYuYXWICgojiwkmC4taFwhjcyIDusogEIwwiSSCKPwsdwzAg0SjJ9Izzk5n3+nXfe8+pqizOvd395scfsJqi6dPnnDr11Vc/NJ1OwUTosqJLCmYCHCAC2mSHs+ojZv6AO46Y+20AhIneJsafhPhXVZSXDk7qi+aOLhtQNuBmQtcarAKjTXpn2+l3u2yPunvZSABRucjcAV/eMZuM48/Go/g1d19kc4wq+e8MZjWkbI/P5t2P3RFFbv7SQdyBlBUx8N8OTuqjMcof+N94yMPrY2DMm/ytnb32J0QrY+6AqsHM4Q64O9SKDmerKDD3Oy/tNL9vk342CC8RuU6n0ymCMHb22scu7zQngtASOjUHE1BX4UUAv4b7Ow6qiXCXuz/UdvogAAweDY943/b4cAz0ZlYHXeMsnT07RVb7wMUr8ykI4H5HVkMd5Rcb4/jNURVOL5qErAaAUUdCCIJ5kx5q2nw8m39ImEAAsjpE6PStB0YfMcd1wqqG3Xn7A3PfZyyKnNjaqD4fmE/fCNKshirIyY1xvI+Av6g5QIAIIWX7cJPssboSiBBEeKmsZne0Sb8kzAUWNYyq8NvbDo0fZ6beqxuLmqOOMr/lwOh+YXpXtbjERGja9JyZ9+HxpXKb9Gj5oywRESbj+Cj1ENG1QViTGBl1FbC1We1tbVRfHWIoQkhqH9xbpE92XUbb6VJZ1R4crjRz1JWcDMJvLdoMcyAEhjuwHo8Bfndg3mbszhOY+adVlMtD3po51OwzIQiEaams7oeJhxRw1FFOVpFRRUYIhMBAFRnjOsC8IFHHUA4TQQhgAqpAiIFfGbxkIqj54ayGbL7UoOqHCniAEKHLNr26l+D9wQJzeUwMAnfHvEnLECzZRwRV++d60ptjW9VLZeolEJG6GwCCE0CFVNB+Ay0NEqoQYG4YYFu7B8IEVRt3uRzy/osIoLV9QZimWXGHUMFdmI6M64DUF2Je88R9VZqCSP+QlcF5k+4tCzSsXaqjINuK6UyE0+s/mk6/qFq8oAIL9pqMLhkGsNrOyoOIlszust3aJv0U9+kFdwjTGwWl1YdF+KWlQSZ0Se/psj8yGVdg5tJyfH96EBWmLtoEMwMzMFt031NzGWLLzKhC+KV7H5ZeeaMOPxemma2x68puc0LN3+/u6LJiePS6MKHvn4wu6cPzJj0hsioeMfDrEvjv5r6W9gBvjKJujuKzQ0URIZj75NylvT+mbHfXQa4rwAMaVRTMm/SFyzvNy0yF6+4AM+1ubcSnqkAIUjQKl1RKSbE5jt+vovx1MBqF0WW7/d1Z80ab9BtmuJ3Xk5cJKds9TZt/uLPXvtiTrQ+dIwqfAejUvM1os6FNikXKUHfQ+ekUsXT5u85enJ0CaBSkkGEo1syUQ+DfMdE/4GA1uzupf9zdbzhOmLsF4efHVXjaHHAzmDtGdQRd/Nc5wAEJjNki3XfhyvwVNz80xANrht3LsENY9cBBdN1L9GUyyvFRFZ42t75sBvCQRykbRlU4tT2pPxoCvzx09d4GmPs200M6wKdWSDGK8mppYSWdhAlt0qeaLv+IadXU9/Evq4FAZ8ej+LmtcTxaRX4NWI0Uag5Vg1p5MYg8BnlhXIdPHDow+vTWZvVMVttXDLqkTzZdPj6Qii6cP1cSvIdl3iQkNYyi9HH0I22y+93tY3DcQkTZgQtM+POoCr8x97eylkmtrgKuztrvXJ21x/aNKuqIkZ/fntRfCdcTfhUTAIhRzoDojJD0aSNLLwMzmpT7+JaLtyf1MwDo6qz9djFaUq3t9MlFmy/c1OCSceY9fMsVaL9mvH9ocXdkdWxv1scAePG0THAhMOaLdOw/Gvxfxb1w4eCapyIENUcV5M3/u8FitAxZ25P6GAHT3UX39Srw+QOb1ZffA98Dl2Wy1BYkAAAAAElFTkSuQmCC");
background-repeat: no-repeat;
background-position: right 5px center;
@@ -42,7 +42,7 @@
}
}
.loading{
.loading {
font-size: 20px;
}
+6 -4
View File
@@ -1,15 +1,15 @@
.commit-title{
.commit-title {
display: block;
}
.commit-author, .commit-committer{
.commit-author, .commit-committer {
display: block;
color: #999;
font-weight: normal;
font-style: italic;
}
.commit-author strong, .commit-committer strong{
.commit-author strong, .commit-committer strong {
font-weight: bold;
font-style: normal;
}
@@ -20,6 +20,8 @@
margin: 0;
padding: 0;
margin-top: 10px;
word-break: normal;
white-space: pre-wrap;
}
.commit-info-row {
@@ -74,7 +76,7 @@
color: $gl-text-red;
}
}
.edit-file{
.edit-file {
a {
color: $gl-text-color;
}
+10 -2
View File
@@ -1,4 +1,4 @@
.commits-compare-switch{
.commits-compare-switch {
@include btn-default;
@include btn-white;
background: image-url("switch_icon.png") no-repeat center center;
@@ -47,6 +47,7 @@ li.commit {
.commit_short_id {
min-width: 65px;
color: $gl-dark-link-color;
font-family: $monospace_font;
}
@@ -88,17 +89,24 @@ li.commit {
padding: 0;
margin: 0;
}
a {
color: $gl-dark-link-color;
}
}
.commit-row-info {
color: $gl-gray;
line-height: 24px;
font-size: 13px;
a {
color: $gl-gray;
}
.avatar {
margin-right: 8px;
}
.committed_ago {
display: inline-block;
}
@@ -33,8 +33,12 @@
.description {
margin-top: 6px;
p:last-child {
margin-bottom: 0;
p {
overflow-x: auto;
&:last-child {
margin-bottom: 0;
}
}
}
}
+22
View File
@@ -2,6 +2,7 @@
.diff-file {
border: 1px solid $border-color;
margin-bottom: $gl-padding;
border-radius: 3px;
.diff-header {
position: relative;
@@ -10,6 +11,7 @@
padding: 10px 16px;
color: #555;
z-index: 10;
border-radius: 3px 3px 0 0;
.diff-title {
font-family: $monospace_font;
@@ -31,6 +33,7 @@
overflow-y: hidden;
background: #fff;
color: #333;
border-radius: 0 0 3px 3px;
.unfold {
cursor: pointer;
@@ -59,9 +62,14 @@
border-collapse: separate;
margin: 0;
padding: 0;
.line_holder td {
line-height: $code_line_height;
font-size: $code_font_size;
span {
white-space: pre;
}
}
}
@@ -104,6 +112,10 @@
display: table-cell;
}
}
.text-file.diff-wrap-lines table .line_holder td span {
white-space: pre-wrap;
}
}
.image {
background: #ddd;
@@ -316,6 +328,16 @@
float: right;
}
.diffs {
.content-block {
border-bottom: none;
}
}
.files-changed {
border-bottom: none;
}
// Mobile
@media (max-width: 480px) {
.diff-title {
+1 -1
View File
@@ -1,5 +1,5 @@
.file-editor {
#editor{
#editor {
border: none;
@include border-radius(0);
height: 500px;
-4
View File
@@ -43,10 +43,6 @@
.md {
color: #7f8fa4;
font-size: $gl-font-size;
iframe.twitter-share-button {
vertical-align: bottom;
}
}
pre {
+3
View File
@@ -59,6 +59,9 @@
position: relative;
overflow-y: auto;
padding: 15px;
.form-actions {
margin: -$gl-padding+1;
}
}
body.modal-open {
+20 -1
View File
@@ -30,6 +30,10 @@
}
.issuable-sidebar {
a {
color: inherit;
}
.block {
@include clearfix;
padding: $gl-padding 0;
@@ -89,7 +93,7 @@
}
.cross-project-reference {
color: $gl-link-color;
color: inherit;
span {
white-space: nowrap;
@@ -133,6 +137,12 @@
.value {
line-height: 1;
.assign-yourself {
margin-top: 10px;
font-weight: normal;
display: block;
}
}
.bold {
@@ -252,6 +262,15 @@
text-decoration: none;
}
}
.dropdown-menu-toggle {
width: 100%;
padding-top: 6px;
}
.open .dropdown-menu {
width: 100%;
}
}
.btn-default.gutter-toggle {
+85 -10
View File
@@ -9,29 +9,55 @@
}
&.suggest-colors-dropdown {
margin-bottom: 5px;
margin-top: 10px;
margin-bottom: 10px;
border-radius: $border-radius-base;
overflow: hidden;
a {
@include border-radius(0);
width: 36.7px;
width: (100% / 7);
margin-right: 0;
margin-bottom: -5px;
}
}
}
.dropdown-label-color-preview {
display: none;
margin-top: 5px;
width: 100%;
height: 25px;
&.is-active {
display: block;
.dropdown-new-label {
.dropdown-content {
max-height: 260px;
}
}
.dropdown-label-color-input {
position: relative;
margin-bottom: 10px;
&.is-active {
padding-left: 32px;
}
}
.dropdown-label-color-preview {
position: absolute;
left: 0;
top: 0;
width: 32px;
height: 32px;
border-top-left-radius: $border-radius-base;
border-bottom-left-radius: $border-radius-base;
}
.label-row {
.label-name {
display: inline-block;
width: 200px;
@media (max-width: $screen-xs-min) {
display: block;
}
}
.label {
padding: 9px;
font-size: 14px;
@@ -52,3 +78,52 @@
background-color: $gl-danger;
color: $white-light;
}
.manage-labels-list {
.prepend-left-10 {
display: inline-block;
width: 40%;
vertical-align: middle;
@media (max-width: $screen-xs-min) {
display: block;
width: 100%;
margin-left: 0;
padding: 10px 0;
}
}
.pull-info-right {
float: right;
@media (max-width: $screen-xs-min) {
float: none;
}
.action-buttons {
border-color: transparent;
padding: 6px;
color: $gl-text-color;
&.subscribe-button {
padding-left: 0;
}
}
i {
color: $gl-text-color;
}
.append-right-20 {
a {
color: $gl-text-color;
}
@media (max-width: $screen-xs-min) {
display: block;
margin-bottom: 10px;
}
}
}
}
+2 -2
View File
@@ -1,9 +1,9 @@
.ci-body {
.incorrect-syntax{
.incorrect-syntax {
font-size: 19px;
color: red;
}
.correct-syntax{
.correct-syntax {
font-size: 19px;
color: #47a447;
}
+1 -1
View File
@@ -36,7 +36,7 @@
}
}
.login-box{
.login-box {
background: #fafafa;
border-radius: 10px;
box-shadow: 0 0 2px #ccc;
@@ -123,6 +123,8 @@
.mr_source_commit,
.mr_target_commit {
margin-bottom: 0;
.commit {
margin: 0;
padding: 2px 0;
@@ -174,10 +176,6 @@
display: none;
}
.merge-request-form .select2-container {
width: 250px !important;
}
#modal_merge_info .modal-dialog {
width: 600px;
@@ -195,38 +193,81 @@
line-height: 31px;
}
.disabled-comment-area {
padding: 16px 0;
.disabled-profile {
width: 40px;
height: 40px;
background: $border-gray-dark;
border-radius: 20px;
display: inline-block;
margin-right: 10px;
}
.disabled-comment {
background: $gray-light;
display: inline-block;
vertical-align: top;
height: 200px;
border-radius: 4px;
border: 1px solid $border-gray-normal;
padding-top: 90px;
text-align: center;
right: 20px;
position: absolute;
left: 70px;
margin-bottom: 20px;
span {
color: #b2b2b2;
a {
color: $md-link-color;
}
}
.builds {
.table-holder {
overflow-x: scroll;
}
}
.panel-new-merge-request {
.panel-heading {
padding: 5px 10px;
font-weight: 600;
line-height: 25px;
}
.panel-body {
padding: 10px 5px;
}
.panel-footer {
padding: 5px 10px;
}
.commit {
.commit-row-title {
margin-bottom: 4px;
}
.avatar {
width: 20px;
height: 20px;
margin-right: 5px;
}
.commit-row-info {
line-height: 20px;
}
}
.btn-clipboard {
margin-right: 5px;
padding: 0;
background: transparent;
}
.ci-status-link {
margin-right: 5px;
}
}
.merge-request-select {
padding-left: 5px;
padding-right: 5px;
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
@media (min-width: $screen-sm-min) {
float: left;
width: 50%;
margin-bottom: 0;
}
.dropdown-menu-toggle {
width: 100%;
}
.dropdown-menu {
left: 5px;
right: 5px;
width: auto;
}
}
.issuable-form-select-holder {
display: inline-block;
width: 250px;
}
+77 -57
View File
@@ -1,14 +1,10 @@
/**
* Note Form
*/
.comment-btn {
@extend .btn-create;
}
.reply-btn {
@extend .btn-primary;
margin: 10px $gl-padding;
}
.diff-file .diff-content {
tr.line_holder:hover > td .line_note_link {
opacity: 1.0;
@@ -17,16 +13,17 @@
}
.diff-file,
.discussion {
.new_note {
.new-note {
margin: 0;
border: none;
}
}
.new_note {
.new-note {
display: none;
}
.new_note, .note-edit-form {
.new-note, .note-edit-form {
.note-form-actions {
margin-top: $gl-padding;
}
@@ -40,19 +37,16 @@
img {
max-width: 100%;
}
.note_text {
width: 100%;
}
.comment-hints {
margin-top: -12px;
}
}
/* loading indicator */
.notes-busy {
margin: 18px;
.note-textarea {
padding: 10px 0;
font-family: $regular_font;
border: 0;
&:focus {
outline: 0;
}
}
.note-image-attach {
@@ -62,40 +56,29 @@
}
.common-note-form {
margin: 0;
background: #fff;
padding: $gl-padding;
margin-left: -$gl-padding;
margin-right: -$gl-padding;
margin-bottom: -$gl-padding;
}
.md-area {
padding: $gl-padding-top $gl-padding;
border: 1px solid $note-form-border-color;
border-radius: $border-radius-base;
.note-form-actions {
background: #fff;
&.is-focused {
border-color: $focus-border-color;
box-shadow: 0 0 2px rgba(#000, .2),
0 0 4px rgba($focus-border-color, .4);
.note-form-option {
margin-top: 8px;
margin-left: 30px;
@extend .pull-left;
}
.js-notify-commit-author {
float: left;
}
.write-preview-btn {
// makes the "absolute" position for links relative to this
position: relative;
// preview/edit buttons
> a {
position: absolute;
right: 5px;
top: 8px;
.comment-toolbar,
.nav-links {
border-color: $focus-border-color;
}
}
}
}
.discussion-form {
padding: $gl-padding-top $gl-padding;
background-color: #fff;
}
.note-edit-form {
display: none;
font-size: 15px;
@@ -130,13 +113,12 @@
.discussion-body,
.diff-file {
.notes .note {
border-color: #ddd;
padding: 10px 15px;
}
.discussion-reply-holder {
background: $background-color;
border-top: 1px solid $border-color;
background-color: $white-light;
padding: 10px 16px;
}
}
@@ -154,11 +136,49 @@
}
}
.comment-hints {
color: #999;
background: #fff;
padding: 7px;
margin-top: -7px;
border: 1px solid $border-color;
font-size: 13px;
.comment-toolbar {
padding-top: $gl-padding-top;
color: $note-toolbar-color;
border-top: 1px solid $border-color;
}
.toolbar-button {
padding: 0;
background: none;
border: 0;
font-size: 14px;
line-height: 16px;
&:hover,
&:focus {
color: $gl-link-color;
outline: 0;
}
@media (min-width: $screen-md-min) {
float: left;
margin-right: $gl-padding;
&:last-child {
float: right;
margin-right: 0;
}
}
}
.toolbar-button-icon {
position: relative;
top: 1px;
margin-right: 3px;
color: inherit;
font-size: 16px;
}
.toolbar-text {
font-size: 14px;
line-height: 16px;
@media (min-width: $screen-md-min) {
float: left;
}
}
+103 -71
View File
@@ -20,9 +20,15 @@ ul.notes {
.timeline-content {
margin-left: 55px;
&.timeline-content-form {
@media (max-width: $screen-sm-max) {
margin-left: 0;
}
}
}
.note_created_ago, .note-updated-at {
.note-created-ago, .note-updated-at {
white-space: nowrap;
}
@@ -39,53 +45,6 @@ ul.notes {
}
}
.discussion-header,
.note-header {
@extend .cgray;
a:hover {
text-decoration: none;
}
.avatar {
float: left;
margin-right: 10px;
}
.discussion-last-update,
.note-last-update {
&:before {
content: "\00b7";
}
a {
color: $gl-gray;
&:hover {
text-decoration: underline;
}
}
}
.author {
color: #4c4e54;
margin-right: 3px;
&:hover {
color: $gl-link-color;
}
}
.author-username {
}
.note-role {
float: right;
margin-top: 1px;
border: 1px solid #bbb;
background-color: transparent;
color: $gl-gray;
}
}
.discussion-body {
padding-top: 15px;
}
@@ -99,6 +58,7 @@ ul.notes {
.note {
display: block;
position: relative;
border-bottom: 1px solid $table-border-gray;
&.is-editting {
.note-header,
@@ -123,7 +83,7 @@ ul.notes {
// On diffs code should wrap nicely and not overflow
pre {
code {
white-space: pre-wrap;
white-space: pre;
}
}
@@ -158,9 +118,6 @@ ul.notes {
padding-bottom: 3px;
}
&:last-child {
border-bottom: 1px solid $border-color;
}
}
}
@@ -178,14 +135,14 @@ ul.notes {
font-family: $regular_font;
td {
border: 1px solid #ddd;
border: 1px solid $table-border-gray;
border-left: none;
&.notes_line {
vertical-align: middle;
text-align: center;
padding: 10px 0;
background: #fff;
background: $background-color;
color: $text-color;
}
&.notes_line2 {
@@ -196,42 +153,99 @@ ul.notes {
&.notes_content {
background-color: #fff;
border-width: 1px 0;
padding-top: 0;
padding: 0;
vertical-align: top;
&.parallel{
&.parallel {
border-width: 1px;
}
}
}
}
.discussion-header,
.note-header {
a {
color: inherit;
&:hover {
color: $gl-link-color;
text-decoration: none;
}
}
}
.note-headline-light,
.discussion-headline-light {
color: $notes-light-color;
}
/**
* Actions for Discussions/Notes
*/
.discussion,
.note {
.discussion-actions,
.note-actions {
float: right;
margin-left: 10px;
.discussion-actions,
.note-actions {
float: right;
margin-left: 10px;
color: $notes-action-color;
}
a {
margin-left: 5px;
color: $gl-gray;
.note-action-button,
.discussion-action-button {
display: inline-block;
margin-left: 10px;
line-height: 24px;
i.fa {
font-size: 16px;
line-height: 16px;
}
.fa {
color: $notes-action-color;
position: relative;
top: 1px;
font-size: 17px;
}
&.js-note-delete {
i {
&:hover {
@extend .cgray;
&.danger { @extend .cred; }
color: $gl-text-red;
}
}
}
&.js-note-edit {
i {
&:hover {
color: $gl-link-color;
}
}
}
}
.discussion-toggle-button {
line-height: 20px;
font-size: 13px;
.fa {
margin-right: 3px;
font-size: 10px;
line-height: 18px;
vertical-align: top;
}
}
.note-role {
position: relative;
top: -2px;
display: inline-block;
padding-left: 4px;
padding-right: 4px;
color: $notes-role-color;
font-size: 12px;
line-height: 20px;
border: 1px solid $notes-role-border-color;
border-radius: $border-radius-base;
}
.diff-file .note .note-actions {
right: 0;
top: 0;
@@ -280,3 +294,21 @@ ul.notes {
}
}
}
.disabled-comment {
margin-left: -$gl-padding-top;
margin-right: -$gl-padding-top;
background-color: $gray-light;
border-radius: $border-radius-base;
border: 1px solid $border-gray-normal;
color: $note-disabled-comment-color;
line-height: 200px;
.disabled-comment-text {
line-height: normal;
}
a {
color: $gl-link-color;
}
}
+21
View File
@@ -197,3 +197,24 @@
width: 105px;
}
}
.modal-profile-crop {
.modal-dialog {
width: 380px;
@media (max-width: $screen-sm-min) {
width: auto;
}
}
.profile-crop-image-container {
height: 300px;
margin: 0 auto;
}
.crop-controls {
padding: 10px 0 0;
text-align: center;
}
}
+8 -4
View File
@@ -162,7 +162,7 @@
margin-right: 12px;
a {
margin: -1px !important;
margin: -1px;
}
}
@@ -222,13 +222,17 @@
padding: 0;
background: transparent;
border: none;
line-height: 42px;
line-height: 36px;
margin: 0;
> li + li:before {
padding: 0 3px;
color: #999;
}
a {
color: $gl-dark-link-color;
}
}
.last-push-widget {
@@ -311,7 +315,7 @@ pre.light-well {
}
.git-empty {
margin: 0 7px;
margin: 0 7px 7px;
h5 {
color: #5c5d5e;
@@ -397,7 +401,7 @@ pre.light-well {
}
.commit_short_id {
margin-right: 5px;
margin: 0 5px;
color: $gl-link-color;
font-weight: 600;
}
+142
View File
@@ -21,3 +21,145 @@
}
}
.search {
margin-right: 10px;
margin-left: 10px;
margin-top: ($header-height - 35) / 2;
form {
@extend .form-control;
margin: 0;
padding: 4px;
width: $search-input-width;
line-height: 24px;
}
.location-text {
font-style: normal;
}
.search-input {
border: none;
font-size: 14px;
outline: none;
padding: 0;
margin-left: 5px;
line-height: 25px;
width: 98%;
}
.location-badge {
line-height: 25px;
padding: 0 5px;
border-radius: $border-radius-default;
font-size: 14px;
font-style: normal;
color: $location-badge-color;
display: inline-block;
background-color: $location-badge-bg;
vertical-align: top;
}
.search-input-container {
display: -webkit-flex;
display: flex;
position: relative;
}
.search-location-badge, .search-input-wrap {
// Fallback if flexbox is not supported
display: inline-block;
}
.search-input-wrap {
width: 100%;
.search-icon, .clear-icon {
position: absolute;
right: 5px;
top: 0;
color: $location-icon-color;
&:before {
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
}
}
.search-icon {
@extend .fa-search;
@include transition(color .15s);
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
.clear-icon {
@extend .fa-times;
display: none;
}
// Rewrite position. Dropdown menu should be relative to .search-input-container
.dropdown {
position: static;
}
.dropdown-header {
text-transform: uppercase;
font-size: 11px;
}
// Custom dropdown positioning
.dropdown-menu {
top: 30px;
left: -5px;
padding: 0;
ul {
padding: 10px 0;
}
}
.dropdown-content {
max-height: 350px;
}
}
&.search-active {
form {
@extend .form-control:focus;
border-color: $dropdown-input-focus-border;
box-shadow: 0 0 4px $search-input-focus-shadow-color;
}
.location-badge {
@include transition(all .15s);
background-color: $location-badge-active-bg;
color: $white-light;
}
.search-input-wrap {
i {
color: $location-icon-active-color;
}
}
}
&.has-value {
.search-icon {
display: none;
}
.clear-icon {
cursor: pointer;
display: block;
}
}
&.has-location-badge {
.search-input-wrap {
width: 78%;
}
}
}
+49 -45
View File
@@ -1,54 +1,58 @@
.ci-status {
padding: 2px 7px;
margin-right: 5px;
border: 1px solid #eee;
white-space: nowrap;
@include border-radius(4px);
.container-fluid {
.ci-status {
padding: 2px 7px;
margin-right: 5px;
border: 1px solid #eee;
white-space: nowrap;
@include border-radius(4px);
&:hover {
text-decoration: none;
&:hover {
text-decoration: none;
}
&.ci-failed {
color: $gl-danger;
border-color: $gl-danger;
}
&.ci-success {
color: $gl-success;
border-color: $gl-success;
}
&.ci-info {
color: $gl-info;
border-color: $gl-info;
}
&.ci-canceled,
&.ci-skipped,
&.ci-disabled {
color: $gl-gray;
border-color: $gl-gray;
}
&.ci-pending,
&.ci-running {
color: $gl-warning;
border-color: $gl-warning;
}
}
&.ci-failed {
color: $gl-danger;
border-color: $gl-danger;
}
&.ci-success {
.ci-status-icon-success {
color: $gl-success;
border-color: $gl-success;
}
&.ci-info {
color: $gl-info;
border-color: $gl-info;
.ci-status-icon-failed {
color: $gl-danger;
}
&.ci-disabled {
color: $gl-gray;
border-color: $gl-gray;
}
&.ci-pending,
&.ci-running {
.ci-status-icon-running,
.ci-status-icon-pending {
color: $gl-warning;
border-color: $gl-warning;
}
.ci-status-icon-canceled,
.ci-status-icon-disabled,
.ci-status-icon-not-found,
.ci-status-icon-skipped {
color: $gl-gray;
}
}
.ci-status-icon-success {
@extend .cgreen;
}
.ci-status-icon-failed {
@extend .cred;
}
.ci-status-icon-running,
.ci-status-icon-pending {
// These are standard text color
}
.ci-status-icon-canceled,
.ci-status-icon-disabled,
.ci-status-icon-not-found,
.ci-status-icon-skipped {
@extend .cgray;
}
+7 -1
View File
@@ -6,13 +6,19 @@
.navbar-nav {
li {
.badge.todos-pending-count {
background-color: #7f8fa4;
background-color: $gl-icon-color;
margin-top: -5px;
font-weight: normal;
}
}
}
.todo {
&:hover {
cursor: pointer;
}
}
.todo-item {
.todo-title {
@include str-truncated(calc(100% - 174px));
@@ -52,7 +52,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:require_two_factor_authentication,
:two_factor_grace_period,
:gravatar_enabled,
:twitter_sharing_enabled,
:sign_in_text,
:help_page_text,
:home_page_url,
+1 -1
View File
@@ -5,7 +5,7 @@ class Admin::ProjectsController < Admin::ApplicationController
def index
@projects = Project.all
@projects = @projects.in_namespace(params[:namespace_id]) if params[:namespace_id].present?
@projects = @projects.where("visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present?
@projects = @projects.where("projects.visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present?
@projects = @projects.with_push if params[:with_push].present?
@projects = @projects.abandoned if params[:abandoned].present?
@projects = @projects.non_archived unless params[:with_archived].present?
+10
View File
@@ -47,6 +47,16 @@ class ApplicationController < ActionController::Base
email: current_user.email,
username: current_user.username,
)
Raven.tags_context(program: sentry_program_context)
end
end
def sentry_program_context
if Sidekiq.server?
'sidekiq'
else
'rails'
end
end
+9 -1
View File
@@ -1,11 +1,15 @@
module Ci
class ProjectsController < Ci::ApplicationController
before_action :project
before_action :authorize_read_project!, except: [:badge]
before_action :no_cache, only: [:badge]
before_action :authorize_read_project!, except: [:badge, :index]
skip_before_action :authenticate_user!, only: [:badge]
protect_from_forgery
def index
redirect_to root_path
end
def show
# Temporary compatibility with CI badges pointing to CI project page
redirect_to namespace_project_path(project.namespace, project)
@@ -35,5 +39,9 @@ module Ci
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
def authorize_read_project!
return access_denied! unless can?(current_user, :read_project, project)
end
end
end
@@ -18,14 +18,14 @@ class Groups::MilestonesController < Groups::ApplicationController
end
def create
project_ids = params[:milestone][:project_ids]
project_ids = params[:milestone][:project_ids].reject(&:blank?)
title = milestone_params[:title]
@projects.where(id: project_ids).each do |project|
Milestones::CreateService.new(project, current_user, milestone_params).execute
if create_milestones(project_ids)
redirect_to milestone_path(title)
else
render_new_with_error(project_ids.empty?)
end
redirect_to milestone_path(title)
end
def show
@@ -41,6 +41,27 @@ class Groups::MilestonesController < Groups::ApplicationController
private
def create_milestones(project_ids)
return false unless project_ids.present?
ActiveRecord::Base.transaction do
@projects.where(id: project_ids).each do |project|
Milestones::CreateService.new(project, current_user, milestone_params).execute
end
end
true
rescue ActiveRecord::ActiveRecordError => e
flash.now[:alert] = "An error occurred while creating the milestone: #{e.message}"
false
end
def render_new_with_error(empty_project_ids)
@milestone = Milestone.new(milestone_params)
@milestone.errors.add(:project_id, "Please select at least one project.") if empty_project_ids
render :new
end
def authorize_admin_milestones!
return render_404 unless can?(current_user, :admin_milestones, group)
end
@@ -55,11 +55,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
else
saml_user = Gitlab::Saml::User.new(oauth)
saml_user.save
saml_user.save if saml_user.changed?
@user = saml_user.gl_user
continue_login_process
end
rescue Gitlab::OAuth::SignupDisabledError
handle_signup_error
end
def omniauth_error
@@ -92,16 +94,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
continue_login_process
end
rescue Gitlab::OAuth::SignupDisabledError
label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed."
if current_application_settings.signup_enabled?
message << " Create a GitLab account first, and then connect it to your #{label} account."
end
flash[:notice] = message
redirect_to new_user_session_path
handle_signup_error
end
def handle_service_ticket provider, ticket
@@ -122,6 +115,19 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
end
def handle_signup_error
label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed."
if current_application_settings.signup_enabled?
message << " Create a GitLab account first, and then connect it to your #{label} account."
end
flash[:notice] = message
redirect_to new_user_session_path
end
def oauth
@oauth ||= request.env['omniauth.auth']
end
+9 -8
View File
@@ -11,15 +11,16 @@ class ProfilesController < Profiles::ApplicationController
def update
user_params.except!(:email) if @user.ldap_user?
if @user.update_attributes(user_params)
flash[:notice] = "Profile was successfully updated"
else
messages = @user.errors.full_messages.uniq.join('. ')
flash[:alert] = "Failed to update profile. #{messages}"
end
respond_to do |format|
format.html { redirect_back_or_default(default: { action: 'show' }) }
if @user.update_attributes(user_params)
message = "Profile was successfully updated"
format.html { redirect_back_or_default(default: { action: 'show' }, options: { notice: message }) }
format.json { render json: { message: message } }
else
message = @user.errors.full_messages.uniq.join('. ')
format.html { redirect_back_or_default(default: { action: 'show' }, options: { alert: "Failed to update profile. #{message}" }) }
format.json { render json: { message: message }, status: :unprocessable_entity }
end
end
end
@@ -80,7 +80,9 @@ class Projects::ApplicationController < ApplicationController
end
def require_non_empty_project
redirect_to namespace_project_path(@project.namespace, @project) if @project.empty_repo?
# Be sure to return status code 303 to avoid a double DELETE:
# http://api.rubyonrails.org/classes/ActionController/Redirecting.html
redirect_to namespace_project_path(@project.namespace, @project), status: 303 if @project.empty_repo?
end
def require_branch_head
+11 -3
View File
@@ -1,12 +1,20 @@
class Projects::BadgesController < Projects::ApplicationController
before_action :no_cache_headers
layout 'project_settings'
before_action :authorize_admin_project!, only: [:index]
before_action :no_cache_headers, except: [:index]
def index
@ref = params[:ref] || @project.default_branch || 'master'
@build_badge = Gitlab::Badge::Build.new(@project, @ref)
end
def build
badge = Gitlab::Badge::Build.new(project, params[:ref])
respond_to do |format|
format.html { render_404 }
format.svg do
image = Ci::ImageForBuildService.new.execute(project, ref: params[:ref])
send_file(image.path, filename: image.name, disposition: 'inline', type: 'image/svg+xml')
send_data(badge.data, type: badge.type, disposition: 'inline')
end
end
end
@@ -48,7 +48,7 @@ class Projects::BranchesController < Projects::ApplicationController
respond_to do |format|
format.html do
redirect_to namespace_project_branches_path(@project.namespace,
@project)
@project), status: 303
end
format.js { render status: status[:return_code] }
end
@@ -68,7 +68,13 @@ class Projects::IssuesController < Projects::ApplicationController
@merge_requests = @issue.referenced_merge_requests(current_user)
@related_branches = @issue.related_branches - @merge_requests.map(&:source_branch)
respond_with(@issue)
respond_to do |format|
format.html
format.json do
render json: @issue.to_json(include: [:milestone, :labels])
end
end
end
def create
@@ -107,10 +113,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
format.json do
render json: {
saved: @issue.valid?,
assignee_avatar_url: @issue.assignee.try(:avatar_url)
}
render json: @issue.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
end
end
end
@@ -57,8 +57,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
respond_to do |format|
format.html
format.json { render json: @merge_request }
format.diff { render text: @merge_request.to_diff(current_user) }
format.patch { render text: @merge_request.to_patch(current_user) }
format.diff { render text: @merge_request.to_diff }
format.patch { render text: @merge_request.to_patch }
end
end
@@ -154,10 +154,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request.target_project, @merge_request])
end
format.json do
render json: {
saved: @merge_request.valid?,
assignee_avatar_url: @merge_request.assignee.try(:avatar_url)
}
render json: @merge_request.to_json(include: [:milestone, :labels, assignee: { methods: :avatar_url }])
end
end
else
@@ -210,31 +207,41 @@ class Projects::MergeRequestsController < Projects::ApplicationController
#This is always source
@source_project = @merge_request.nil? ? @project : @merge_request.source_project
@commit = @repository.commit(params[:ref]) if params[:ref].present?
render layout: false
end
def branch_to
@target_project = selected_target_project
@commit = @target_project.commit(params[:ref]) if params[:ref].present?
render layout: false
end
def update_branches
@target_project = selected_target_project
@target_branches = @target_project.repository.branch_names
respond_to do |format|
format.js
end
render layout: false
end
def ci_status
ci_service = @merge_request.source_project.ci_service
status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch)
ci_commit = @merge_request.ci_commit
if ci_commit
status = ci_commit.status
coverage = ci_commit.try(:coverage)
else
ci_service = @merge_request.source_project.ci_service
status = ci_service.commit_status(merge_request.last_commit.sha, merge_request.source_branch) if ci_service
if ci_service.respond_to?(:commit_coverage)
coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch)
if ci_service.respond_to?(:commit_coverage)
coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch)
end
end
status = "preparing" if status.nil?
response = {
title: merge_request.title,
sha: merge_request.last_commit_short_sha,
status: status,
coverage: coverage
}
@@ -19,13 +19,12 @@ class Projects::MilestonesController < Projects::ApplicationController
end
@milestones = @milestones.includes(:project)
respond_to do |format|
format.html do
@milestones = @milestones.page(params[:page])
end
format.json do
render json: @milestones
render json: @milestones.to_json(methods: :name)
end
end
end
+1 -2
View File
@@ -39,8 +39,7 @@ class Projects::NotesController < Projects::ApplicationController
def destroy
if note.editable?
note.destroy
note.reset_events_cache
Notes::DeleteService.new(project, current_user).execute(note)
end
respond_to do |format|
@@ -94,9 +94,14 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
def apply_import
giver = Project.find(params[:source_project_id])
status = @project.team.import(giver, current_user)
notice = status ? "Successfully imported" : "Import failed"
source_project = Project.find(params[:source_project_id])
if can?(current_user, :read_project_member, source_project)
status = @project.team.import(source_project, current_user)
notice = status ? "Successfully imported" : "Import failed"
else
return render_404
end
redirect_to(namespace_project_project_members_path(project.namespace, project),
notice: notice)
@@ -24,6 +24,8 @@ class Projects::RefsController < Projects::ApplicationController
namespace_project_find_file_path(@project.namespace, @project, @id)
when "graphs_commits"
commits_namespace_project_graph_path(@project.namespace, @project, @id)
when "badges"
namespace_project_badges_path(@project.namespace, @project, ref: @id)
else
namespace_project_commits_path(@project.namespace, @project, @id)
end
@@ -3,7 +3,7 @@ class Projects::SnippetsController < Projects::ApplicationController
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
# Allow read any snippet
before_action :authorize_read_project_snippet!
before_action :authorize_read_project_snippet!, except: [:new, :create, :index]
# Allow write(create) snippet
before_action :authorize_create_project_snippet!, only: [:new, :create]
@@ -81,6 +81,10 @@ class Projects::SnippetsController < Projects::ApplicationController
@snippet ||= @project.snippets.find(params[:id])
end
def authorize_read_project_snippet!
return render_404 unless can?(current_user, :read_project_snippet, @snippet)
end
def authorize_update_project_snippet!
return render_404 unless can?(current_user, :update_project_snippet, @snippet)
end
@@ -88,6 +88,20 @@ class Projects::WikisController < Projects::ApplicationController
)
end
def markdown_preview
text = params[:text]
ext = Gitlab::ReferenceExtractor.new(@project, current_user, current_user)
ext.analyze(text)
render json: {
body: view_context.markdown(text, pipeline: :wiki, project_wiki: @project_wiki),
references: {
users: ext.users.map(&:username)
}
}
end
def git_access
end
+5 -13
View File
@@ -40,6 +40,9 @@ class ProjectsController < Projects::ApplicationController
def update
status = ::Projects::UpdateService.new(@project, current_user, project_params).execute
# Refresh the repo in case anything changed
@repository = project.repository
respond_to do |format|
if status
flash[:notice] = "Project '#{@project.name}' was successfully updated."
@@ -71,7 +74,7 @@ class ProjectsController < Projects::ApplicationController
def remove_fork
return access_denied! unless can?(current_user, :remove_fork_project, @project)
if @project.unlink_fork
if ::Projects::UnlinkForkService.new(@project, current_user).execute
flash[:notice] = 'The fork relationship has been removed.'
end
end
@@ -138,7 +141,7 @@ class ProjectsController < Projects::ApplicationController
participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id)
@suggestions = {
emojis: autocomplete_emojis,
emojis: AwardEmoji.urls,
issues: autocomplete.issues,
mergerequests: autocomplete.merge_requests,
members: participants
@@ -235,17 +238,6 @@ class ProjectsController < Projects::ApplicationController
)
end
def autocomplete_emojis
Rails.cache.fetch("autocomplete-emoji-#{Gemojione::VERSION}") do
Emoji.emojis.map do |name, emoji|
{
name: name,
path: view_context.image_url("#{emoji["unicode"]}.png")
}
end
end
end
def repo_exists?
project.repository_exists? && !project.empty_repo?
end
+4
View File
@@ -26,6 +26,10 @@ class RootController < Dashboard::ProjectsController
redirect_to activity_dashboard_path
when 'starred_project_activity'
redirect_to activity_dashboard_path(filter: 'starred')
when 'groups'
redirect_to dashboard_groups_path
when 'todos'
redirect_to dashboard_todos_path
else
return
end
+9 -6
View File
@@ -5,7 +5,8 @@ class SessionsController < Devise::SessionsController
skip_before_action :check_2fa_requirement, only: [:destroy]
prepend_before_action :check_initial_setup, only: [:new]
prepend_before_action :authenticate_with_two_factor, only: [:create]
prepend_before_action :authenticate_with_two_factor,
if: :two_factor_enabled?, only: [:create]
prepend_before_action :store_redirect_path, only: [:new]
before_action :auto_sign_in_with_provider, only: [:new]
@@ -56,10 +57,10 @@ class SessionsController < Devise::SessionsController
end
def find_user
if user_params[:login]
User.by_login(user_params[:login])
elsif user_params[:otp_attempt] && session[:otp_user_id]
if session[:otp_user_id]
User.find(session[:otp_user_id])
elsif user_params[:login]
User.by_login(user_params[:login])
end
end
@@ -83,11 +84,13 @@ class SessionsController < Devise::SessionsController
end
end
def two_factor_enabled?
find_user.try(:two_factor_enabled?)
end
def authenticate_with_two_factor
user = self.resource = find_user
return unless user && user.two_factor_enabled?
if user_params[:otp_attempt].present? && session[:otp_user_id]
if valid_otp_attempt?(user)
# Remove any lingering user data from login

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