Imported Upstream version 8.8.2+dfsg

This commit is contained in:
Praveen Arimbrathodiyil 2016-06-02 11:05:42 +05:30
parent 02b042a3d7
commit 886c2c3fe4
2092 changed files with 73012 additions and 20518 deletions

20
.csscomb.json Normal file
View file

@ -0,0 +1,20 @@
{
"exclude": [
"app/assets/stylesheets/framework/tw_bootstrap_variables.scss",
"app/assets/stylesheets/framework/fonts.scss"
],
"always-semicolon": true,
"color-case": "lower",
"block-indent": " ",
"color-shorthand": true,
"element-case": "lower",
"space-before-colon": "",
"space-after-colon": " ",
"space-before-combinator": " ",
"space-after-combinator": " ",
"space-between-declarations": "\n",
"space-before-opening-brace": " ",
"space-after-opening-brace": "\n",
"space-before-closing-brace": "\n",
"unitless-zero": true
}

1
.gitignore vendored
View file

@ -15,6 +15,7 @@
.sass-cache/ .sass-cache/
.secret .secret
.vagrant .vagrant
.byebug_history
Vagrantfile Vagrantfile
backups/* backups/*
config/aws.yml config/aws.yml

View file

@ -2,7 +2,6 @@ image: "ruby:2.1"
services: services:
- mysql:latest - mysql:latest
- postgres:latest
- redis:latest - redis:latest
cache: cache:
@ -12,137 +11,119 @@ cache:
variables: variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1" MYSQL_ALLOW_EMPTY_PASSWORD: "1"
# retry tests only in CI environment
RSPEC_RETRY_RETRY_COUNT: "3"
before_script: before_script:
- source ./scripts/prepare_build.sh - source ./scripts/prepare_build.sh
- ruby -v - ruby -v
- which ruby - which ruby
- gem install bundler --no-ri --no-rdoc - retry gem install bundler --no-ri --no-rdoc
- cp config/gitlab.yml.example config/gitlab.yml - cp config/gitlab.yml.example config/gitlab.yml
- touch log/application.log - touch log/application.log
- touch log/test.log - touch log/test.log
- bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}" - retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"
- RAILS_ENV=test bundle exec rake db:drop db:create db:schema:load db:migrate - RAILS_ENV=test bundle exec rake db:drop db:create db:schema:load db:migrate
stages:
- test
- notifications
spec:feature: spec:feature:
stage: test
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
tags:
- ruby
- mysql
spec:api: spec:api:
stage: test
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
tags:
- ruby
- mysql
spec:models: spec:models:
stage: test
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
tags:
- ruby
- mysql
spec:lib: spec:lib:
stage: test
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
tags:
- ruby
- mysql
spec:services: spec:services:
stage: test
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
tags:
- ruby
- mysql
spec:benchmark:
script:
- RAILS_ENV=test bundle exec rake spec:benchmark
tags:
- ruby
- mysql
allow_failure: true
spec:other: spec:other:
stage: test
script: script:
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
tags:
- ruby
- mysql
spinach:project:half: spinach:project:half:
stage: test
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
tags:
- ruby
- mysql
spinach:project:rest: spinach:project:rest:
stage: test
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
tags:
- ruby
- mysql
spinach:other: spinach:other:
stage: test
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
tags:
- ruby
- mysql
teaspoon: teaspoon:
stage: test
script: script:
- RAILS_ENV=test bundle exec teaspoon - RAILS_ENV=test bundle exec teaspoon
tags:
- ruby
- mysql
rubocop: rubocop:
stage: test
script: script:
- bundle exec rubocop - bundle exec rubocop
tags:
- ruby scss-lint:
- mysql stage: test
script:
- bundle exec rake scss_lint
brakeman: brakeman:
stage: test
script: script:
- bundle exec rake brakeman - bundle exec rake brakeman
tags:
- ruby
- mysql
flog: flog:
stage: test
script: script:
- bundle exec rake flog - bundle exec rake flog
tags:
- ruby
- mysql
flay: flay:
stage: test
script: script:
- bundle exec rake flay - bundle exec rake flay
tags:
- ruby
- mysql
bundler:audit: bundler:audit:
stage: test
only:
- master
script: script:
- "bundle exec bundle-audit update" - "bundle exec bundle-audit check --update --ignore OSVDB-115941"
- "bundle exec bundle-audit check"
tags: db-migrate-reset:
- ruby stage: test
- mysql script:
allow_failure: true - RAILS_ENV=test bundle exec rake db:migrate:reset
# Ruby 2.2 jobs # Ruby 2.2 jobs
spec:feature:ruby22: spec:feature:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
@ -153,11 +134,9 @@ spec:feature:ruby22:
key: "ruby22" key: "ruby22"
paths: paths:
- vendor - vendor
tags:
- ruby
- mysql
spec:api:ruby22: spec:api:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
@ -167,11 +146,9 @@ spec:api:ruby22:
key: "ruby22" key: "ruby22"
paths: paths:
- vendor - vendor
tags:
- ruby
- mysql
spec:models:ruby22: spec:models:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
@ -181,11 +158,9 @@ spec:models:ruby22:
key: "ruby22" key: "ruby22"
paths: paths:
- vendor - vendor
tags:
- ruby
- mysql
spec:lib:ruby22: spec:lib:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
@ -195,11 +170,9 @@ spec:lib:ruby22:
key: "ruby22" key: "ruby22"
paths: paths:
- vendor - vendor
tags:
- ruby
- mysql
spec:services:ruby22: spec:services:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
@ -209,26 +182,9 @@ spec:services:ruby22:
key: "ruby22" key: "ruby22"
paths: paths:
- vendor - vendor
tags:
- ruby
- mysql
spec:benchmark:ruby22:
image: ruby:2.2
only:
- master
script:
- RAILS_ENV=test bundle exec rake spec:benchmark
cache:
key: "ruby22"
paths:
- vendor
tags:
- ruby
- mysql
allow_failure: true
spec:other:ruby22: spec:other:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
@ -238,49 +194,53 @@ spec:other:ruby22:
key: "ruby22" key: "ruby22"
paths: paths:
- vendor - vendor
tags:
- ruby
- mysql
spinach:project:half:ruby22: spinach:project:half:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
cache: cache:
key: "ruby22" key: "ruby22"
paths: paths:
- vendor - vendor
tags:
- ruby
- mysql
spinach:project:rest:ruby22: spinach:project:rest:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
cache: cache:
key: "ruby22" key: "ruby22"
paths: paths:
- vendor - vendor
tags:
- ruby
- mysql
spinach:other:ruby22: spinach:other:ruby22:
stage: test
image: ruby:2.2 image: ruby:2.2
only: only:
- master - master
script: script:
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
cache: cache:
key: "ruby22" key: "ruby22"
paths: paths:
- vendor - vendor
tags:
- ruby
- mysql
notify:slack:
stage: notifications
script:
- ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>"
when: on_failure
only:
- master@gitlab-org/gitlab-ce
- tags@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- tags@gitlab-org/gitlab-ee

File diff suppressed because it is too large Load diff

264
.scss-lint.yml Normal file
View file

@ -0,0 +1,264 @@
# Linter Documentation:
# https://github.com/brigade/scss-lint/blob/master/lib/scss_lint/linter/README.md
scss_files: 'app/assets/stylesheets/**/*.scss'
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
# `scss-lint:disable` control comments should be preceded by a comment
# explaining why these linters are being disabled for this file.
# See https://github.com/brigade/scss-lint#disabling-linters-via-source for
# more information.
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: true
# 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
# HEX colors should use three-character values where possible.
HexLength:
enabled: true
# HEX color values should use lower-case colors to differentiate between
# letters and numbers, e.g. `#E3E3E3` vs. `#e3e3e3`.
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
# Indentation should always be done in increments of 2 spaces.
Indentation:
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
# Selectors should always use hyphenated-lowercase, rather than camelCase or
# snake_case.
SelectorFormat:
enabled: false
convention: hyphenated_lowercase
# Prefer the shortest shorthand form possible for properties that support it.
Shorthand:
enabled: true
# Each property should have its own line, except in the special case of
# single line rulesets.
SingleLinePerProperty:
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
# Properties should be formatted with a single space separating the colon
# from the property's value.
SpaceAfterPropertyColon:
enabled: true
# Properties should be formatted with no space between the name and the
# 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: true
# URLs should always be enclosed within quotes.
UrlQuotes:
enabled: true
# 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
# Omit length units on zero values, e.g. `0px` vs. `0`.
ZeroUnit:
enabled: true

492
CHANGELOG
View file

@ -1,21 +1,428 @@
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.6.0 (unreleased) v 8.8.2
- Added remove due date button. !4209
- Fix access to Pipelines by Anonymous user. !4233
- Fix Error 500 when accessing application settings due to nil disabled OAuth sign-in sources. !4242
- Fix Error 500 in CI charts by gracefully handling commits with no durations. !4245
- Fix table UI on CI builds page. !4249
- Fix backups if registry is disabled. !4263
- Fixed issue with merge button color. !4211
- Fixed issue with enter key selecting wrong option in dropdown. !4210
- When creating a .gitignore file a dropdown with templates will be provided. !4075
- Fix concurrent request when updating build log in browser. !4183
v 8.8.1
- Add documentation for the "Health Check" feature
- Allow anonymous users to access a public project's pipelines
- Fix MySQL compatibility in zero downtime migrations helpers
- Fix the CI login to Container Registry (the gitlab-ci-token user)
v 8.8.0
- Implement GFM references for milestones (Alejandro Rodríguez)
- Snippets tab under user profile. !4001 (Long Nguyen)
- Fix error when using link to uploads in global snippets
- Fix Error 500 when attempting to retrieve project license when HEAD points to non-existent ref
- Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen)
- Use a case-insensitive comparison in sanitizing URI schemes
- Toggle sign-up confirmation emails in application settings
- Make it possible to prevent tagged runner from picking untagged jobs
- Added `InlineDiffFilter` to the markdown parser. (Adam Butler)
- Added inline diff styling for `change_title` system notes. (Adam Butler)
- Project#open_branches has been cleaned up and no longer loads entire records into memory.
- Escape HTML in commit titles in system note messages
- Fix scope used when accessing container registry
- Fix creation of Ci::Commit object which can lead to pending, failed in some scenarios
- Improve multiple branch push performance by memoizing permission checking
- Log to application.log when an admin starts and stops impersonating a user
- Changing the confidentiality of an issue now creates a new system note (Alex Moore-Niemi)
- Updated gitlab_git to 10.1.0
- GitAccess#protected_tag? no longer loads all tags just to check if a single one exists
- Reduce delay in destroying a project from 1-minute to immediately
- Make build status canceled if any of the jobs was canceled and none failed
- Upgrade Sidekiq to 4.1.2
- Added /health_check endpoint for checking service status
- Make 'upcoming' filter for milestones work better across projects
- Sanitize repo paths in new project error message
- Bump mail_room to 0.7.0 to fix stuck IDLE connections
- Remove future dates from contribution calendar graph.
- Support e-mail notifications for comments on project snippets
- Fix API leak of notes of unauthorized issues, snippets and merge requests
- Use ActionDispatch Remote IP for Akismet checking
- Fix error when visiting commit builds page before build was updated
- Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project
- Update SVG sanitizer to conform to SVG 1.1
- Speed up push emails with multiple recipients by only generating the email once
- Updated search UI
- Added authentication service for Container Registry
- Display informative message when new milestone is created
- Sanitize milestones and labels titles
- Support multi-line tag messages. !3833 (Calin Seciu)
- Force users to reset their password after an admin changes it
- Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea)
- Added button to toggle whitespaces changes on diff view
- Backport GitHub Enterprise import support from EE
- Create tags using Rugged for performance reasons. !3745
- Allow guests to set notification level in projects
- API: Expose Issue#user_notes_count. !3126 (Anton Popov)
- Don't show forks button when user can't view forks
- Fix atom feed links and rendering
- Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718
- Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes)
- Add eager load paths to help prevent dependency load issues in Sidekiq workers. !3724
- Added multiple colors for labels in dropdowns when dups happen.
- Show commits in the same order as `git log`
- Improve description for the Two-factor Authentication sign-in screen. (Connor Shea)
- API support for the 'since' and 'until' operators on commit requests (Paco Guzman)
- Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko)
- Expire repository exists? and has_visible_content? caches after a push if necessary
- Fix unintentional filtering bug in Issue/MR sorted by milestone due (Takuya Noguchi)
- Fix adding a todo for private group members (Ahmad Sherif)
- Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3
- Total method execution timings are no longer tracked
- Allow Admins to remove the Login with buttons for OAuth services and still be able to import !4034. (Andrei Gliga)
- Add API endpoints for un/subscribing from/to a label. !4051 (Ahmad Sherif)
- Hide left sidebar on phone screens to give more space for content
- Redesign navigation for profile and group pages
- Add counter metrics for rails cache
- Import pull requests from GitHub where the source or target branches were removed
- All Grape API helpers are now instrumented
- Improve Issue formatting for the Slack Service (Jeroen van Baarsen)
- Fixed advice on invalid permissions on upload path !2948 (Ludovic Perrine)
- Allows MR authors to have the source branch removed when merging the MR. !2801 (Jeroen Jacobs)
v 8.7.6
- Fix links on wiki pages for relative url setups. !4131 (Artem Sidorenko)
- Fix import from GitLab.com to a private instance failure. !4181
- Fix external imports not finding the import data. !4106
v 8.7.5
- Fix relative links in wiki pages. !4050
- Fix always showing build notification message when switching between merge requests !4086
- Fix an issue when filtering merge requests with more than one label. !3886
- Fix short note for the default scope on build page (Takuya Noguchi)
v 8.7.4
- Links for Redmine issue references are generated correctly again !4048 (Benedikt Huss)
- Fix setting trusted proxies !3970
- Fix BitBucket importer bug when throwing exceptions !3941
- Use sign out path only if not empty !3989
- Running rake gitlab:db:drop_tables now drops tables with cascade !4020
- Running rake gitlab:db:drop_tables uses "IF EXISTS" as a precaution !4100
- Use a case-insensitive comparison in sanitizing URI schemes
v 8.7.3
- Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented
- Merge request widget displays TeamCity build state and code coverage correctly again.
- Fix the line code when importing PR review comments from GitHub. !4010
- Wikis are now initialized on legacy projects when checking repositories
- Remove animate.css in favor of a smaller subset of animations. !3937 (Connor Shea)
v 8.7.2
- The "New Branch" button is now loaded asynchronously
- Fix error 500 when trying to create a wiki page
- Updated spacing between notification label and button
- Label titles in filters are now escaped properly
v 8.7.1
- Throttle the update of `project.last_activity_at` to 1 minute. !3848
- Fix .gitlab-ci.yml parsing issue when hidde job is a template without script definition. !3849
- Fix license detection to detect all license files, not only known licenses. !3878
- Use the `can?` helper instead of `current_user.can?`. !3882
- Prevent users from deleting Webhooks via API they do not own
- Fix Error 500 due to stale cache when projects are renamed or transferred
- Update width of search box to fix Safari bug. !3900 (Jedidiah)
- Use the `can?` helper instead of `current_user.can?`
v 8.7.0
- Gitlab::GitAccess and Gitlab::GitAccessWiki are now instrumented
- Fix vulnerability that made it possible to gain access to private labels and milestones
- The number of InfluxDB points stored per UDP packet can now be configured
- Fix error when cross-project label reference used with non-existent project
- Transactions for /internal/allowed now have an "action" tag set
- Method instrumentation now uses Module#prepend instead of aliasing methods
- Repository.clean_old_archives is now instrumented
- Add support for environment variables on a job level in CI configuration file
- SQL query counts are now tracked per transaction
- The Projects::HousekeepingService class has extra instrumentation
- All service classes (those residing in app/services) are now instrumented
- Developers can now add custom tags to transactions
- Loading of an issue's referenced merge requests and related branches is now done asynchronously
- Enable gzip for assets, makes the page size significantly smaller. !3544 / !3632 (Connor Shea)
- Add support to cherry-pick any commit into any branch in the web interface (Minqi Pan)
- Project switcher uses new dropdown styling
- Load award emoji images separately unless opening the full picker. Saves several hundred KBs of data for most pages. (Connor Shea)
- Do not include award_emojis in issue and merge_request comment_count !3610 (Lucas Charles)
- Restrict user profiles when public visibility level is restricted.
- Add ability set due date to issues, sort and filter issues by due date (Mehmet Beydogan)
- 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)
- Add setting for customizing the list of trusted proxies !3524
- Allow projects to be transfered to a lower visibility level group
- Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524
- Improved Markdown rendering performance !3389
- Make shared runners text in box configurable
- Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu)
- API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling)
- Expose project badges in project settings
- Make /profile/keys/new redirect to /profile/keys for back-compat. !3717
- Preserve time notes/comments have been updated at when moving issue
- Make HTTP(s) label consistent on clone bar (Stan Hu)
- Add support for `after_script`, requires Runner 1.2 (Kamil Trzciński)
- Expose label description in API (Mariusz Jachimowicz)
- API: Ability to update a group (Robert Schilling)
- API: Ability to move issues (Robert Schilling)
- Fix Error 500 after renaming a project path (Stan Hu)
- Fix a bug whith trailing slash in teamcity_url (Charles May)
- Allow back dating on issues when created or updated through the API
- Allow back dating on issue notes when created through the API
- Propose license template when creating a new LICENSE file
- API: Expose /licenses and /licenses/:key
- 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
- Allow Omniauth providers to be marked as `external` !3657
- Add endpoints to archive or unarchive a project !3372
- Fix a bug whith trailing slash in bamboo_url
- Add links to CI setup documentation from project settings and builds pages
- Display project members page to all members
- Handle nil descriptions in Slack issue messages (Stan Hu)
- Add automated repository integrity checks (OFF by default)
- API: Expose open_issues_count, closed_issues_count, open_merge_requests_count for labels (Robert Schilling)
- API: Ability to star and unstar a project (Robert Schilling)
- Add default scope to projects to exclude projects pending deletion
- Allow to close merge requests which source projects(forks) are deleted.
- Ensure empty recipients are rejected in BuildsEmailService
- Use rugged to change HEAD in Project#change_head (P.S.V.R)
- API: Ability to filter milestones by state `active` and `closed` (Robert Schilling)
- API: Fix milestone filtering by `iid` (Robert Schilling)
- Make before_script and after_script overridable on per-job (Kamil Trzciński)
- 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
- Fix high CPU usage when PostReceive receives refs/merge-requests/<id>
- 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.)
- Allow issues and merge requests to be assigned to the author !2765
- Make Ci::Commit to group only similar builds and make it stateful (ref, tag)
- Gracefully handle notes on deleted commits in merge requests (Stan Hu)
- Decouple membership and notifications
- Fix creation of merge requests for orphaned branches (Stan Hu)
- API: Ability to retrieve a single tag (Robert Schilling)
- While signing up, don't persist the user password across form redisplays
- 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
- Update email confirmation interface
- API: Expose user location (Robert Schilling)
- API: Do not leak group existence via return code (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
- Sanitize branch names created for confidential issues
- 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
- Fix repository cache invalidation issue when project is recreated with an empty repo (Stan Hu)
- Fix: Allow empty recipients list for builds emails service when pushed is added (Frank Groeneveld)
- Improved markdown forms
- Diff design updates (colors, button styles, etc)
- Copying and pasting a diff no longer pastes the line numbers or +/-
- Add null check to formData when updating profile content to fix Firefox bug
- Disable spellcheck and autocorrect for username field in admin page
- Delete tags using Rugged for performance reasons (Robert Schilling)
- Add Slack notifications when Wiki is edited (Sebastian Klier)
- Diffs load at the correct point when linking from from number
- Selected diff rows highlight
- Fix emoji categories in the emoji picker
- API: Properly display annotated tags for GET /projects/:id/repository/tags (Robert Schilling)
- Add encrypted credentials for imported projects and migrate old ones
- Properly format all merge request references with ! rather than # !3740 (Ben Bodenmiller)
- Author and participants are displayed first on users autocompletion
- Show number sign on external issue reference text (Florent Baldino)
- Updated print style for issues
- Use GitHub Issue/PR number as iid to keep references
- Import GitHub labels
- Add option to filter by "Owned projects" on dashboard page
- Import GitHub milestones
- Execute system web hooks on push to the project
- Allow enable/disable push events for system hooks
- Fix GitHub project's link in the import page when provider has a custom URL
- Add RAW build trace output and button on build page
- Add incremental build trace update into CI API
v 8.6.8
- Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API
- Prevent privilege escalation via project webhook API
- Prevent XSS via Git branch and tag names
- Prevent XSS via custom issue tracker URL
- Prevent XSS via `window.opener`
- Prevent XSS via label drop-down
- Prevent information disclosure via milestone API
- Prevent information disclosure via snippet API
- Prevent information disclosure via project labels
- Prevent information disclosure via new merge request page
v 8.6.7
- Fix persistent XSS vulnerability in `commit_person_link` helper
- Fix persistent XSS vulnerability in Label and Milestone dropdowns
- Fix vulnerability that made it possible to enumerate private projects belonging to group
v 8.6.6
- Expire the exists cache before deletion to ensure project dir actually exists (Stan Hu). !3413
- Fix error on language detection when repository has no HEAD (e.g., master branch) (Jeroen Bobbeldijk). !3654
- Fix revoking of authorized OAuth applications (Connor Shea). !3690
- Fix error on language detection when repository has no HEAD (e.g., master branch). !3654 (Jeroen Bobbeldijk)
- Issuable header is consistent between issues and merge requests
- Improved spacing in issuable header on mobile
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
- Prevent tokens in the import URL to be showed by the UI
- Fix bug where wrong commit ID was being used in a merge request diff to show old image (Stan Hu)
- Add confidential issues
- Bump gitlab_git to 9.0.3 (Stan Hu)
- Fix diff image view modes (2-up, swipe, onion skin) not working (Stan Hu)
- Support Golang subpackage fetching (Stan Hu)
- Bump Capybara gem to 2.6.2 (Stan Hu)
- New branch button appears on issues where applicable
- Contributions to forked projects are included in calendar - Contributions to forked projects are included in calendar
- Improve the formatting for the user page bio (Connor Shea) - Improve the formatting for the user page bio (Connor Shea)
- Easily (un)mark merge request as WIP using link
- Use specialized system notes when MR is (un)marked as WIP
- Removed the default password from the initial admin account created during
setup. A password can be provided during setup (see installation docs), or
GitLab will ask the user to create a new one upon first visit.
- Fix issue when pushing to projects ending in .wiki - Fix issue when pushing to projects ending in .wiki
- Fix avatar stretching by providing a cropping feature (Johann Pardanaud) - Properly display YAML front matter in Markdown
- Add support for wiki with UTF-8 page names (Hiroyuki Sato)
- Fix wiki search results point to raw source (Hiroyuki Sato)
- Don't load all of GitLab in mail_room - Don't load all of GitLab in mail_room
- 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
- 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)
- Add label description in tooltip to labels in issue index and sidebar
- Strip leading and trailing spaces in URL validator (evuez) - Strip leading and trailing spaces in URL validator (evuez)
- Add "last_sign_in_at" and "confirmed_at" to GET /users/* API endpoints for admins (evuez)
- Return empty array instead of 404 when commit has no statuses in commit status API - Return empty array instead of 404 when commit has no statuses in commit status API
- Decrease the font size and the padding of the `.anchor` icons used in the README (Roberto Dip)
- Rewrite logo to simplify SVG code (Sean Lang)
- Allow to use YAML anchors when parsing the `.gitlab-ci.yml` (Pascal Bach)
- Ignore jobs that start with `.` (hidden jobs)
- Hide builds from project's settings when the feature is disabled
- Allow to pass name of created artifacts archive in `.gitlab-ci.yml`
- Refactor and greatly improve search performance
- Add support for cross-project label references
- Ensure "new SSH key" email do not ends up as dead Sidekiq jobs
- Update documentation to reflect Guest role not being enforced on internal projects - Update documentation to reflect Guest role not being enforced on internal projects
- Allow search for logged out users - Allow search for logged out users
- Allow to define on which builds the current one depends on
- Allow user subscription to a label: get notified for issues/merge requests related to that label (Timothy Andrew)
- Fix bug where Bitbucket `closed` issues were imported as `opened` (Iuri de Silvio)
- Don't show Issues/MRs from archived projects in Groups view - Don't show Issues/MRs from archived projects in Groups view
- Fix wrong "iid of max iid" in Issuable sidebar for some merged MRs
- Fix empty source_sha on Merge Request when there is no diff (Pierre de La Morinerie)
- Increase the notes polling timeout over time (Roberto Dip) - Increase the notes polling timeout over time (Roberto Dip)
- Add shortcut to toggle markdown preview (Florent Baldino) - Add shortcut to toggle markdown preview (Florent Baldino)
- Show labels in dashboard and group milestone views - Show labels in dashboard and group milestone views
- Fix an issue when the target branch of a MR had been deleted
- Add main language of a project in the list of projects (Tiago Botelho) - 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 - 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
- User deletion is now done in the background so the request can not time out
- 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
v 8.5.12
- Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API
- Prevent privilege escalation via project webhook API
- Prevent XSS via Git branch and tag names
- Prevent XSS via custom issue tracker URL
- Prevent XSS via `window.opener`
- Prevent information disclosure via snippet API
- Prevent information disclosure via project labels
- Prevent information disclosure via new merge request page
v 8.5.11
- Fix persistent XSS vulnerability in `commit_person_link` helper
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 v 8.5.8
- Bump Git version requirement to 2.7.4 - Bump Git version requirement to 2.7.4
@ -37,6 +444,10 @@ v 8.5.4
v 8.5.3 v 8.5.3
- Flush repository caches before renaming projects - Flush repository caches before renaming projects
- Sort starred projects on dashboard based on last activity by default
- Show commit message in JIRA mention comment
- Makes issue page and merge request page usable on mobile browsers.
- Improved UI for profile settings
v 8.5.2 v 8.5.2
- Fix sidebar overlapping content when screen width was below 1200px - Fix sidebar overlapping content when screen width was below 1200px
@ -72,10 +483,12 @@ v 8.5.1
- Changed padding & background color for highlighted notes - Changed padding & background color for highlighted notes
- Re-add the newrelic_rpm gem which was removed without any deprecation or warning (Stan Hu) - Re-add the newrelic_rpm gem which was removed without any deprecation or warning (Stan Hu)
- Update sentry-raven gem to 0.15.6 - Update sentry-raven gem to 0.15.6
- Add build coverage in project's builds page (Steffen Köhler)
- Changed # to ! for merge requests in activity view
v 8.5.0 v 8.5.0
- Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu) - Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu)
- Cache various Repository methods to improve performance (Yorick Peterse) - Cache various Repository methods to improve performance
- Fix duplicated branch creation/deletion Webhooks/service notifications when using Web UI (Stan Hu) - Fix duplicated branch creation/deletion Webhooks/service notifications when using Web UI (Stan Hu)
- Ensure rake tasks that don't need a DB connection can be run without one - Ensure rake tasks that don't need a DB connection can be run without one
- Update New Relic gem to 3.14.1.311 (Stan Hu) - Update New Relic gem to 3.14.1.311 (Stan Hu)
@ -152,6 +565,32 @@ v 8.5.0
- Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul) - Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul)
- Add Todos - Add Todos
v 8.4.10
- Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API
- Prevent privilege escalation via project webhook API
- Prevent XSS via Git branch and tag names
- Prevent XSS via custom issue tracker URL
- Prevent XSS via `window.opener`
- Prevent information disclosure via snippet API
- Prevent information disclosure via project labels
- Prevent information disclosure via new merge request page
v 8.4.9
- Fix persistent XSS vulnerability in `commit_person_link` helper
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
v 8.4.4 v 8.4.4
- Update omniauth-saml gem to 1.4.2 - Update omniauth-saml gem to 1.4.2
- Prevent long-running backup tasks from timing out the database connection - Prevent long-running backup tasks from timing out the database connection
@ -262,6 +701,27 @@ v 8.4.0
- Add IP check against DNSBLs at account sign-up - Add IP check against DNSBLs at account sign-up
- Added cache:key to .gitlab-ci.yml allowing to fine tune the caching - Added cache:key to .gitlab-ci.yml allowing to fine tune the caching
v 8.3.9
- Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API
- Prevent privilege escalation via project webhook API
- Prevent XSS via custom issue tracker URL
- Prevent XSS via `window.opener`
- Prevent information disclosure via project labels
- Prevent information disclosure via new merge request page
v 8.3.8
- Fix persistent XSS vulnerability in `commit_person_link` helper
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 v 8.3.4
- Use gitlab-workhorse 0.5.4 (fixes API routing bug) - Use gitlab-workhorse 0.5.4 (fixes API routing bug)
@ -359,6 +819,17 @@ v 8.3.0
- Expose Git's version in the admin area - Expose Git's version in the admin area
- Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye) - Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye)
v 8.2.5
- Prevent privilege escalation via "impersonate" feature
- Prevent privilege escalation via notes API
- Prevent privilege escalation via project webhook API
- Prevent XSS via `window.opener`
- Prevent information disclosure via project labels
- Prevent information disclosure via new merge request page
v 8.2.4
- Bump Git version requirement to 2.7.4
v 8.2.3 v 8.2.3
- Fix application settings cache not expiring after changes (Stan Hu) - Fix application settings cache not expiring after changes (Stan Hu)
- Fix Error 500s when creating global milestones with Unicode characters (Stan Hu) - Fix Error 500s when creating global milestones with Unicode characters (Stan Hu)
@ -454,7 +925,7 @@ v 8.1.3
- Use issue editor as cross reference comment author when issue is edited with a new mention - Use issue editor as cross reference comment author when issue is edited with a new mention
- Add Facebook authentication - Add Facebook authentication
v 8.1.2 v 8.1.1
- Fix cloning Wiki repositories via HTTP (Stan Hu) - Fix cloning Wiki repositories via HTTP (Stan Hu)
- Add migration to remove satellites directory - Add migration to remove satellites directory
- Fix specific runners visibility - Fix specific runners visibility
@ -1079,20 +1550,17 @@ v 7.10.0
- Fix stuck Merge Request merging events from old installations (Ben Bodenmiller) - Fix stuck Merge Request merging events from old installations (Ben Bodenmiller)
- Fix merge request comments on files with multiple commits - Fix merge request comments on files with multiple commits
- Fix Resource Owner Password Authentication Flow - Fix Resource Owner Password Authentication Flow
v 7.9.4
- Security: Fix project import URL regex to prevent arbitary local repos from being imported
- Fixed issue where only 25 commits would load in file listings
- Fix LDAP identities after config update
v 7.9.3
- Contains no changes
- Add icons to Add dropdown items. - Add icons to Add dropdown items.
- Allow admin to create public deploy keys that are accessible to any project. - Allow admin to create public deploy keys that are accessible to any project.
- Warn when gitlab-shell version doesn't match requirement. - Warn when gitlab-shell version doesn't match requirement.
- Skip email confirmation when set by admin or via LDAP. - Skip email confirmation when set by admin or via LDAP.
- Only allow users to reference groups, projects, issues, MRs, commits they have access to. - Only allow users to reference groups, projects, issues, MRs, commits they have access to.
v 7.9.4
- Security: Fix project import URL regex to prevent arbitary local repos from being imported
- Fixed issue where only 25 commits would load in file listings
- Fix LDAP identities after config update
v 7.9.3 v 7.9.3
- Contains no changes - Contains no changes

View file

@ -8,11 +8,15 @@
- [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests) - [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests)
- [Helping others](#helping-others) - [Helping others](#helping-others)
- [I want to contribute!](#i-want-to-contribute) - [I want to contribute!](#i-want-to-contribute)
- [Implement design & UI elements](#implement-design-ui-elements)
- [Design reference](#design-reference)
- [UI development kit](#ui-development-kit)
- [Issue tracker](#issue-tracker) - [Issue tracker](#issue-tracker)
- [Feature proposals](#feature-proposals) - [Feature proposals](#feature-proposals)
- [Issue tracker guidelines](#issue-tracker-guidelines) - [Issue tracker guidelines](#issue-tracker-guidelines)
- [Issue weight](#issue-weight) - [Issue weight](#issue-weight)
- [Regression issues](#regression-issues) - [Regression issues](#regression-issues)
- [Technical debt](#technical-debt)
- [Merge requests](#merge-requests) - [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines) - [Merge request guidelines](#merge-request-guidelines)
- [Merge request description format](#merge-request-description-format) - [Merge request description format](#merge-request-description-format)
@ -34,7 +38,7 @@ source edition, and GitLab Enterprise Edition (EE) which is our commercial
edition. Throughout this guide you will see references to CE and EE for edition. Throughout this guide you will see references to CE and EE for
abbreviation. abbreviation.
If you have read this guide and want to know how the GitLab [core-team][] If you have read this guide and want to know how the GitLab [core team]
operates please see [the GitLab contributing process](PROCESS.md). operates please see [the GitLab contributing process](PROCESS.md).
## Contributor license agreement ## Contributor license agreement
@ -68,10 +72,10 @@ for audiences of all ages.
## Helping others ## Helping others
Please help other GitLab users when you can. The channels people will reach out Please help other GitLab users when you can. The channels people will reach out
on can be found on the [getting help page][]. on can be found on the [getting help page][getting-help].
Sign up for the mailing list, answer GitLab questions on StackOverflow or Sign up for the mailing list, answer GitLab questions on StackOverflow or
respond in the IRC channel. You can also sign up on [CodeTriage][] to help with respond in the IRC channel. You can also sign up on [CodeTriage][codetriage] to help with
the remaining issues on the GitHub issue tracker. the remaining issues on the GitHub issue tracker.
## I want to contribute! ## I want to contribute!
@ -83,6 +87,22 @@ GitLab.
This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs]. This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs].
## Implement design & UI elements
### Design reference
The GitLab design reference can be found in the [gitlab-design] project.
The designs are made using Antetype (`.atype` files). You can use the
[free Antetype viewer (Mac OSX only)] or grab an exported PNG from the design
(the PNG is 1:1).
The current designs can be found in the [`gitlab1.atype` file].
### UI development kit
Implemented UI elements can also be found at https://gitlab.com/help/ui. Please
note that this page isn't comprehensive at this time.
## Issue tracker ## Issue tracker
To get support for your particular problem please use the To get support for your particular problem please use the
@ -115,12 +135,23 @@ For feature proposals for EE, open an issue on the
In order to help track the feature proposals, we have created a In order to help track the feature proposals, we have created a
[`feature proposal`][fpl] label. For the time being, users that are not members [`feature proposal`][fpl] label. For the time being, users that are not members
of the project cannot add labels. You can instead ask one of the [core team][] of the project cannot add labels. You can instead ask one of the [core team]
members to add the label `feature proposal` to the issue. members to add the label `feature proposal` to the issue or add the following
code snippet right after your description in a new line: `~"feature proposal"`.
Please keep feature proposals as small and simple as possible, complex ones Please keep feature proposals as small and simple as possible, complex ones
might be edited to make them small and simple. might be edited to make them small and simple.
You are encouraged to use the template below for feature proposals.
```
## Description including problem, use cases, benefits, and/or goals
## Proposal
## Links / references
```
For changes in the interface, it can be helpful to create a mockup first. For changes in the interface, it can be helpful to create a mockup first.
If you want to create something yourself, consider opening an issue first to If you want to create something yourself, consider opening an issue first to
discuss whether it is interesting to include this in GitLab. discuss whether it is interesting to include this in GitLab.
@ -223,6 +254,28 @@ addressed.
[8.3 Regressions]: https://gitlab.com/gitlab-org/gitlab-ce/issues/4127 [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 [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 ## Merge requests
We welcome merge requests with fixes and improvements to GitLab code, tests, We welcome merge requests with fixes and improvements to GitLab code, tests,
@ -281,6 +334,7 @@ request is as follows:
[shell command guidelines](doc/development/shell_commands.md) [shell command guidelines](doc/development/shell_commands.md)
1. If your code creates new files on disk please read the 1. If your code creates new files on disk please read the
[shared files guidelines](doc/development/shared_files.md). [shared files guidelines](doc/development/shared_files.md).
1. When writing commit messages please follow [these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) [guidelines](http://chris.beams.io/posts/git-commit/).
The **official merge window** is in the beginning of the month from the 1st to The **official merge window** is in the beginning of the month from the 1st to
the 7th day of the month. This is the best time to submit an MR and get the 7th day of the month. This is the best time to submit an MR and get
@ -299,13 +353,13 @@ to us than having a minimal commit log. The smaller an MR is the more likely it
is it will be merged (quickly). After that you can send more MRs to enhance it. is it will be merged (quickly). After that you can send more MRs to enhance it.
For examples of feedback on merge requests please look at already For examples of feedback on merge requests please look at already
[closed merge requests][]. If you would like quick feedback on your merge [closed merge requests][closed-merge-requests]. If you would like quick feedback
request feel free to mention one of the Merge Marshalls of the [core team][]. on your merge request feel free to mention one of the Merge Marshalls in the
[core team] or one of the [Merge request coaches](https://about.gitlab.com/team/).
Please ensure that your merge request meets the contribution acceptance criteria. Please ensure that your merge request meets the contribution acceptance criteria.
When having your code reviewed and when reviewing merge requests please take the When having your code reviewed and when reviewing merge requests please take the
[thoughtbot code review guidelines](https://github.com/thoughtbot/guides/tree/master/code-review) [code review guidelines](doc/development/code_review.md) into account.
into account.
### Merge request description format ### Merge request description format
@ -343,7 +397,8 @@ description area. Copy-paste it to retain the markdown format.
to a new table or remove an old table) to aid retrying on failure to a new table or remove an old table) to aid retrying on failure
1. Keeps the GitLab code base clean and well structured 1. Keeps the GitLab code base clean and well structured
1. Contains functionality we think other users will benefit from too 1. Contains functionality we think other users will benefit from too
1. Doesn't add configuration options since they complicate future changes 1. Doesn't add configuration options or settings options since they complicate
making and testing future changes
1. Changes after submitting the merge request should be in separate commits 1. Changes after submitting the merge request should be in separate commits
(no squashing). If necessary, you will be asked to squash when the review is (no squashing). If necessary, you will be asked to squash when the review is
over, before merging. over, before merging.
@ -369,7 +424,7 @@ Like all merge requests the target should be master so all bugfixes are in maste
## Definition of done ## Definition of done
If you contribute to GitLab please know that changes involve more than just If you contribute to GitLab please know that changes involve more than just
code. We have the following [definition of done][]. Please ensure you support code. We have the following [definition of done][definition-of-done]. Please ensure you support
the feature you contribute through all of these steps. the feature you contribute through all of these steps.
1. Description explaining the relevancy (see following item) 1. Description explaining the relevancy (see following item)
@ -404,8 +459,9 @@ merge request:
- multi-line method chaining style **Option B**: dot `.` on previous line - multi-line method chaining style **Option B**: dot `.` on previous line
- string literal quoting style **Option A**: single quoted by default - string literal quoting style **Option A**: single quoted by default
1. [Rails](https://github.com/bbatsov/rails-style-guide) 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. [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. [Shell commands](doc/development/shell_commands.md) created by GitLab
contributors to enhance security contributors to enhance security
1. [Database Migrations](doc/development/migration_style_guide.md) 1. [Database Migrations](doc/development/migration_style_guide.md)
@ -448,12 +504,12 @@ when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior can be Instances of abusive, harassing, or otherwise unacceptable behavior can be
reported by emailing `contact@gitlab.com`. reported by emailing `contact@gitlab.com`.
This Code of Conduct is adapted from the [Contributor Covenant][], version 1.1.0, This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant], version 1.1.0,
available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/). available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
[core-team]: https://about.gitlab.com/core-team/ [core team]: https://about.gitlab.com/core-team/
[getting help page]: https://about.gitlab.com/getting-help/ [getting-help]: https://about.gitlab.com/getting-help/
[Codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq [codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up-for-grabs [up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up-for-grabs
[medium-up-for-grabs]: https://medium.com/@kentcdodds/first-timers-only-78281ea47455 [medium-up-for-grabs]: https://medium.com/@kentcdodds/first-timers-only-78281ea47455
[ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues [ce-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/issues
@ -467,9 +523,13 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[github-mr-tracker]: https://github.com/gitlabhq/gitlabhq/pulls [github-mr-tracker]: https://github.com/gitlabhq/gitlabhq/pulls
[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit [gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit
[git-squash]: https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits [git-squash]: https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
[closed merge requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed [closed-merge-requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed
[definition of done]: http://guide.agilealliance.org/guide/definition-of-done.html [definition-of-done]: http://guide.agilealliance.org/guide/definition-of-done.html
[Contributor Covenant]: http://contributor-covenant.org [contributor-covenant]: http://contributor-covenant.org
[rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout [rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout
[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming [rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide" [doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
[scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide"
[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design
[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12
[`gitlab1.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/gitlab1.atype/

View file

@ -1 +1 @@
2.6.10 2.7.2

View file

@ -1 +1 @@
0.6.4 0.7.1

79
Gemfile
View file

@ -1,14 +1,14 @@
source "https://rubygems.org" source "https://rubygems.org"
gem 'rails', '4.2.5.2' gem 'rails', '4.2.6'
gem 'rails-deprecated_sanitizer', '~> 1.0.3' gem 'rails-deprecated_sanitizer', '~> 1.0.3'
# Responders respond_to and respond_with # Responders respond_to and respond_with
gem 'responders', '~> 2.0' gem 'responders', '~> 2.0'
# Specify a sprockets version due to security issue # Specify a sprockets version due to increased performance
# See https://groups.google.com/forum/#!topic/rubyonrails-security/doAVp0YaTqY # See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069
gem 'sprockets', '~> 2.12.3' gem 'sprockets', '~> 3.6.0'
# Default values for AR models # Default values for AR models
gem "default_value_for", "~> 3.0.0" gem "default_value_for", "~> 3.0.0"
@ -19,9 +19,10 @@ gem "pg", '~> 0.18.2', group: :postgres
# Authentication libraries # Authentication libraries
gem 'devise', '~> 3.5.4' gem 'devise', '~> 3.5.4'
gem 'doorkeeper', '~> 3.1'
gem 'devise-async', '~> 0.9.0' gem 'devise-async', '~> 0.9.0'
gem 'doorkeeper', '~> 2.2.0'
gem 'omniauth', '~> 1.3.1' gem 'omniauth', '~> 1.3.1'
gem 'omniauth-auth0', '~> 1.4.1'
gem 'omniauth-azure-oauth2', '~> 0.0.6' gem 'omniauth-azure-oauth2', '~> 0.0.6'
gem 'omniauth-bitbucket', '~> 0.0.2' gem 'omniauth-bitbucket', '~> 0.0.2'
gem 'omniauth-cas3', '~> 1.1.2' gem 'omniauth-cas3', '~> 1.1.2'
@ -30,11 +31,12 @@ gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.0' gem 'omniauth-gitlab', '~> 1.0.0'
gem 'omniauth-google-oauth2', '~> 0.2.0' gem 'omniauth-google-oauth2', '~> 0.2.0'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-saml', '~> 1.4.2' gem 'omniauth-saml', '~> 1.5.0'
gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0' gem 'omniauth_crowd', '~> 2.2.0'
gem 'rack-oauth2', '~> 1.2.1' gem 'rack-oauth2', '~> 1.2.1'
gem 'jwt'
# Spam and anti-bot protection # Spam and anti-bot protection
gem 'recaptcha', require: 'recaptcha/rails' gem 'recaptcha', require: 'recaptcha/rails'
@ -50,7 +52,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem "gitlab_git", '~> 8.2' gem "gitlab_git", '~> 10.0'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
@ -58,7 +60,9 @@ gem "gitlab_git", '~> 8.2'
gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: "omniauth-ldap" gem 'gitlab_omniauth-ldap', '~> 1.2.1', require: "omniauth-ldap"
# Git Wiki # Git Wiki
gem 'gollum-lib', '~> 4.1.0' # Required manually in config/initializers/gollum.rb to control load order
gem 'gollum-lib', '~> 4.1.0', require: false
gem 'gollum-rugged_adapter', '~> 0.4.2', require: false
# Language detection # Language detection
gem "github-linguist", "~> 4.7.0", require: "linguist" gem "github-linguist", "~> 4.7.0", require: "linguist"
@ -75,7 +79,7 @@ gem "kaminari", "~> 0.16.3"
gem "haml-rails", '~> 0.9.0' gem "haml-rails", '~> 0.9.0'
# Files attachments # Files attachments
gem "carrierwave", '~> 0.9.0' gem "carrierwave", '~> 0.10.0'
# Drag and Drop UI # Drag and Drop UI
gem 'dropzonejs-rails', '~> 0.7.1' gem 'dropzonejs-rails', '~> 0.7.1'
@ -112,7 +116,7 @@ gem 'diffy', '~> 3.0.3'
# Application server # Application server
group :unicorn do group :unicorn do
gem "unicorn", '~> 4.8.2' gem "unicorn", '~> 4.9.0'
gem 'unicorn-worker-killer', '~> 0.4.2' gem 'unicorn-worker-killer', '~> 0.4.2'
end end
@ -146,6 +150,10 @@ gem 'version_sorter', '~> 2.0.0'
# Cache # Cache
gem "redis-rails", '~> 4.0.0' gem "redis-rails", '~> 4.0.0'
# Redis
gem 'redis', '~> 3.2'
gem 'connection_pool', '~> 2.0'
# Campfire integration # Campfire integration
gem 'tinder', '~> 1.10.0' gem 'tinder', '~> 1.10.0'
@ -171,7 +179,7 @@ gem 'ruby-fogbugz', '~> 0.2.1'
gem 'd3_rails', '~> 3.5.0' gem 'd3_rails', '~> 3.5.0'
#cal-heatmap #cal-heatmap
gem 'cal-heatmap-rails', '~> 3.5.0' gem 'cal-heatmap-rails', '~> 3.6.0'
# underscore-rails # underscore-rails
gem "underscore-rails", "~> 1.8.0" gem "underscore-rails", "~> 1.8.0"
@ -183,11 +191,14 @@ gem 'babosa', '~> 1.0.2'
# Sanitizes SVG input # Sanitizes SVG input
gem "loofah", "~> 2.0.3" gem "loofah", "~> 2.0.3"
# Working with license
gem 'licensee', '~> 8.0.0'
# Protect against bruteforcing # Protect against bruteforcing
gem "rack-attack", '~> 4.3.1' gem "rack-attack", '~> 4.3.1'
# Ace editor # Ace editor
gem 'ace-rails-ap', '~> 2.0.1' gem 'ace-rails-ap', '~> 4.0.2'
# Keyboard shortcuts # Keyboard shortcuts
gem 'mousetrap-rails', '~> 1.4.6' gem 'mousetrap-rails', '~> 1.4.6'
@ -207,33 +218,32 @@ gem 'font-awesome-rails', '~> 4.2'
gem 'gitlab_emoji', '~> 0.3.0' gem 'gitlab_emoji', '~> 0.3.0'
gem 'gon', '~> 6.0.1' gem 'gon', '~> 6.0.1'
gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.0.0' gem 'jquery-rails', '~> 4.1.0'
gem 'jquery-scrollto-rails', '~> 1.4.3'
gem 'jquery-ui-rails', '~> 5.0.0' gem 'jquery-ui-rails', '~> 5.0.0'
gem 'nprogress-rails', '~> 0.1.6.7'
gem 'raphael-rails', '~> 2.1.2' 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 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1' gem 'virtus', '~> 1.0.1'
gem 'net-ssh', '~> 3.0.1' gem 'net-ssh', '~> 3.0.1'
gem 'base32', '~> 0.3.0'
# Sentry integration # Sentry integration
gem 'sentry-raven', '~> 0.15' gem 'sentry-raven', '~> 0.15'
gem 'premailer-rails', '~> 1.9.0'
# Metrics # Metrics
group :metrics do group :metrics do
gem 'allocations', '~> 1.0', require: false, platform: :mri gem 'allocations', '~> 1.0', require: false, platform: :mri
gem 'method_source', '~> 0.8', require: false gem 'method_source', '~> 0.8', require: false
gem 'influxdb', '~> 0.2', require: false gem 'influxdb', '~> 0.2', require: false
gem 'connection_pool', '~> 2.0', require: false
end end
group :development do group :development do
gem "foreman" gem "foreman"
gem 'brakeman', '~> 3.1.0', require: false gem 'brakeman', '~> 3.2.0', require: false
gem "annotate", "~> 2.6.0" gem 'letter_opener_web', '~> 1.3.0'
gem "letter_opener", '~> 1.1.2'
gem 'quiet_assets', '~> 1.0.2' gem 'quiet_assets', '~> 1.0.2'
gem 'rerun', '~> 0.11.0' gem 'rerun', '~> 0.11.0'
gem 'bullet', require: false gem 'bullet', require: false
@ -259,9 +269,11 @@ group :development, :test do
gem 'fuubar', '~> 2.0.0' gem 'fuubar', '~> 2.0.0'
gem 'database_cleaner', '~> 1.4.0' gem 'database_cleaner', '~> 1.4.0'
gem 'factory_girl_rails', '~> 4.3.0' gem 'factory_girl_rails', '~> 4.6.0'
gem 'rspec-rails', '~> 3.3.0' gem 'rspec-rails', '~> 3.4.0'
gem 'rspec-retry'
gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rails', '~> 0.2.1'
gem 'spinach-rerun-reporter', '~> 0.0.2'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826) # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.7.0' gem 'minitest', '~> 5.7.0'
@ -269,21 +281,22 @@ group :development, :test do
# Generate Fake data # Generate Fake data
gem 'ffaker', '~> 2.0.0' gem 'ffaker', '~> 2.0.0'
gem 'capybara', '~> 2.4.0' gem 'capybara', '~> 2.6.2'
gem 'capybara-screenshot', '~> 1.0.0' gem 'capybara-screenshot', '~> 1.0.0'
gem 'poltergeist', '~> 1.8.1' gem 'poltergeist', '~> 1.9.0'
gem 'teaspoon', '~> 1.0.0' gem 'teaspoon', '~> 1.1.0'
gem 'teaspoon-jasmine', '~> 2.2.0' gem 'teaspoon-jasmine', '~> 2.2.0'
gem 'spring', '~> 1.3.6' gem 'spring', '~> 1.7.0'
gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.0.0' gem 'spring-commands-spinach', '~> 1.1.0'
gem 'spring-commands-teaspoon', '~> 0.0.2' gem 'spring-commands-teaspoon', '~> 0.0.2'
gem 'rubocop', '~> 0.35.0', require: false gem 'rubocop', '~> 0.38.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false
gem 'coveralls', '~> 0.8.2', 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 'flog', require: false
gem 'flay', require: false gem 'flay', require: false
gem 'bundler-audit', require: false gem 'bundler-audit', require: false
@ -305,14 +318,13 @@ end
gem "newrelic_rpm", '~> 3.14' gem "newrelic_rpm", '~> 3.14'
gem 'octokit', '~> 3.8.0' gem 'octokit', '~> 4.3.0'
gem "mail_room", "~> 0.6.1" gem "mail_room", "~> 0.7"
gem 'email_reply_parser', '~> 0.5.8' gem 'email_reply_parser', '~> 0.5.8'
## CI ## CI
gem 'activerecord-deprecated_finders', '~> 1.0.3'
gem 'activerecord-session_store', '~> 0.1.0' gem 'activerecord-session_store', '~> 0.1.0'
gem "nested_form", '~> 0.3.2' gem "nested_form", '~> 0.3.2'
@ -321,3 +333,6 @@ gem 'oauth2', '~> 1.0.0'
# Soft deletion # Soft deletion
gem "paranoia", "~> 2.0" gem "paranoia", "~> 2.0"
# Health check
gem 'health_check', '~> 1.5.1'

View file

@ -3,42 +3,41 @@ GEM
specs: specs:
CFPropertyList (2.3.2) CFPropertyList (2.3.2)
RedCloth (4.2.9) RedCloth (4.2.9)
ace-rails-ap (2.0.1) ace-rails-ap (4.0.2)
actionmailer (4.2.5.2) actionmailer (4.2.6)
actionpack (= 4.2.5.2) actionpack (= 4.2.6)
actionview (= 4.2.5.2) actionview (= 4.2.6)
activejob (= 4.2.5.2) activejob (= 4.2.6)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
actionpack (4.2.5.2) actionpack (4.2.6)
actionview (= 4.2.5.2) actionview (= 4.2.6)
activesupport (= 4.2.5.2) activesupport (= 4.2.6)
rack (~> 1.6) rack (~> 1.6)
rack-test (~> 0.6.2) rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (4.2.5.2) actionview (4.2.6)
activesupport (= 4.2.5.2) activesupport (= 4.2.6)
builder (~> 3.1) builder (~> 3.1)
erubis (~> 2.7.0) erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5) rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
activejob (4.2.5.2) activejob (4.2.6)
activesupport (= 4.2.5.2) activesupport (= 4.2.6)
globalid (>= 0.3.0) globalid (>= 0.3.0)
activemodel (4.2.5.2) activemodel (4.2.6)
activesupport (= 4.2.5.2) activesupport (= 4.2.6)
builder (~> 3.1) builder (~> 3.1)
activerecord (4.2.5.2) activerecord (4.2.6)
activemodel (= 4.2.5.2) activemodel (= 4.2.6)
activesupport (= 4.2.5.2) activesupport (= 4.2.6)
arel (~> 6.0) arel (~> 6.0)
activerecord-deprecated_finders (1.0.4)
activerecord-session_store (0.1.2) activerecord-session_store (0.1.2)
actionpack (>= 4.0.0, < 5) actionpack (>= 4.0.0, < 5)
activerecord (>= 4.0.0, < 5) activerecord (>= 4.0.0, < 5)
railties (>= 4.0.0, < 5) railties (>= 4.0.0, < 5)
activesupport (4.2.5.2) activesupport (4.2.6)
i18n (~> 0.7) i18n (~> 0.7)
json (~> 1.7, >= 1.7.7) json (~> 1.7, >= 1.7.7)
minitest (~> 5.1) minitest (~> 5.1)
@ -51,9 +50,6 @@ GEM
activerecord (>= 3.0) activerecord (>= 3.0)
akismet (2.0.0) akismet (2.0.0)
allocations (1.0.4) allocations (1.0.4)
annotate (2.6.10)
activerecord (>= 3.2, <= 4.3)
rake (~> 10.4)
arel (6.0.3) arel (6.0.3)
asana (0.4.0) asana (0.4.0)
faraday (~> 0.9) faraday (~> 0.9)
@ -61,9 +57,7 @@ GEM
faraday_middleware-multi_json (~> 0.0) faraday_middleware-multi_json (~> 0.0)
oauth2 (~> 1.0) oauth2 (~> 1.0)
asciidoctor (1.5.3) asciidoctor (1.5.3)
ast (2.1.0) ast (2.2.0)
astrolabe (1.3.1)
parser (~> 2.2)
attr_encrypted (1.3.4) attr_encrypted (1.3.4)
encryptor (>= 1.3.0) encryptor (>= 1.3.0)
attr_required (1.0.0) attr_required (1.0.0)
@ -76,6 +70,7 @@ GEM
ice_nine (~> 0.11.0) ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
babosa (1.0.2) babosa (1.0.2)
base32 (0.3.2)
bcrypt (3.1.10) bcrypt (3.1.10)
benchmark-ips (2.3.0) benchmark-ips (2.3.0)
better_errors (1.0.1) better_errors (1.0.1)
@ -86,29 +81,28 @@ GEM
bootstrap-sass (3.3.6) bootstrap-sass (3.3.6)
autoprefixer-rails (>= 5.2.1) autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4) sass (>= 3.3.4)
brakeman (3.1.4) brakeman (3.2.1)
erubis (~> 2.6) erubis (~> 2.6)
fastercsv (~> 1.5)
haml (>= 3.0, < 5.0) haml (>= 3.0, < 5.0)
highline (>= 1.6.20, < 2.0) highline (>= 1.6.20, < 2.0)
multi_json (~> 1.2) ruby2ruby (~> 2.3.0)
ruby2ruby (>= 2.1.1, < 2.3.0) ruby_parser (~> 3.8.1)
ruby_parser (~> 3.7.0)
safe_yaml (>= 1.0) safe_yaml (>= 1.0)
sass (~> 3.0) sass (~> 3.0)
slim (>= 1.3.6, < 4.0) slim (>= 1.3.6, < 4.0)
terminal-table (~> 1.4) terminal-table (~> 1.4)
browser (1.0.1) browser (1.0.1)
builder (3.2.2) builder (3.2.2)
bullet (4.14.10) bullet (5.0.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
uniform_notifier (~> 1.9.0) uniform_notifier (~> 1.9.0)
bundler-audit (0.4.0) bundler-audit (0.5.0)
bundler (~> 1.2) bundler (~> 1.2)
thor (~> 0.18) thor (~> 0.18)
byebug (8.2.1) byebug (8.2.1)
cal-heatmap-rails (3.5.1) cal-heatmap-rails (3.6.0)
capybara (2.4.4) capybara (2.6.2)
addressable
mime-types (>= 1.16) mime-types (>= 1.16)
nokogiri (>= 1.3.3) nokogiri (>= 1.3.3)
rack (>= 1.0.0) rack (>= 1.0.0)
@ -117,10 +111,11 @@ GEM
capybara-screenshot (1.0.11) capybara-screenshot (1.0.11)
capybara (>= 1.0, < 3) capybara (>= 1.0, < 3)
launchy launchy
carrierwave (0.9.0) carrierwave (0.10.0)
activemodel (>= 3.2.0) activemodel (>= 3.2.0)
activesupport (>= 3.2.0) activesupport (>= 3.2.0)
json (>= 1.7) json (>= 1.7)
mime-types (>= 1.16)
cause (0.1) cause (0.1)
charlock_holmes (0.7.3) charlock_holmes (0.7.3)
chunky_png (1.3.5) chunky_png (1.3.5)
@ -128,26 +123,27 @@ GEM
coderay (1.1.0) coderay (1.1.0)
coercible (1.0.0) coercible (1.0.0)
descendants_tracker (~> 0.0.1) descendants_tracker (~> 0.0.1)
coffee-rails (4.1.0) coffee-rails (4.1.1)
coffee-script (>= 2.2.0) 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 (2.4.1)
coffee-script-source coffee-script-source
execjs execjs
coffee-script-source (1.10.0) coffee-script-source (1.10.0)
colorize (0.7.7) colorize (0.7.7)
concurrent-ruby (1.0.0) concurrent-ruby (1.0.2)
connection_pool (2.2.0) connection_pool (2.2.0)
coveralls (0.8.9) coveralls (0.8.13)
json (~> 1.8) json (~> 1.8)
rest-client (>= 1.6.8, < 2) simplecov (~> 0.11.0)
simplecov (~> 0.10.0)
term-ansicolor (~> 1.3) term-ansicolor (~> 1.3)
thor (~> 0.19.1) thor (~> 0.19.1)
tins (~> 1.6.0) tins (~> 1.6.0)
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
creole (0.5.0) creole (0.5.0)
css_parser (1.4.1)
addressable
d3_rails (3.5.11) d3_rails (3.5.11)
railties (>= 3.1.0) railties (>= 3.1.0)
daemons (1.2.3) daemons (1.2.3)
@ -176,9 +172,7 @@ GEM
diff-lcs (1.2.5) diff-lcs (1.2.5)
diffy (3.0.7) diffy (3.0.7)
docile (1.1.5) docile (1.1.5)
domain_name (0.5.25) doorkeeper (3.1.0)
unf (>= 0.0.5, < 1.0.0)
doorkeeper (2.2.2)
railties (>= 3.2) railties (>= 3.2)
dropzonejs-rails (0.7.2) dropzonejs-rails (0.7.2)
rails (> 3.1) rails (> 3.1)
@ -189,15 +183,15 @@ GEM
encryptor (1.3.0) encryptor (1.3.0)
equalizer (0.0.11) equalizer (0.0.11)
erubis (2.7.0) erubis (2.7.0)
escape_utils (1.1.0) escape_utils (1.1.1)
eventmachine (1.0.8) eventmachine (1.0.8)
excon (0.45.4) excon (0.45.4)
execjs (2.6.0) execjs (2.6.0)
expression_parser (0.9.0) expression_parser (0.9.0)
factory_girl (4.3.0) factory_girl (4.5.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
factory_girl_rails (4.3.0) factory_girl_rails (4.6.0)
factory_girl (~> 4.3.0) factory_girl (~> 4.5.0)
railties (>= 3.0.0) railties (>= 3.0.0)
faraday (0.9.2) faraday (0.9.2)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
@ -206,7 +200,6 @@ GEM
faraday_middleware-multi_json (0.0.6) faraday_middleware-multi_json (0.0.6)
faraday_middleware faraday_middleware
multi_json multi_json
fastercsv (1.5.5)
ffaker (2.0.0) ffaker (2.0.0)
ffi (1.9.10) ffi (1.9.10)
fission (0.5.0) fission (0.5.0)
@ -326,8 +319,8 @@ GEM
fog-xml (0.1.2) fog-xml (0.1.2)
fog-core fog-core
nokogiri (~> 1.5, >= 1.5.11) nokogiri (~> 1.5, >= 1.5.11)
font-awesome-rails (4.5.0.0) font-awesome-rails (4.5.0.1)
railties (>= 3.2, < 5.0) railties (>= 3.2, < 5.1)
foreman (0.78.0) foreman (0.78.0)
thor (~> 0.19.1) thor (~> 0.19.1)
formatador (0.2.5) formatador (0.2.5)
@ -340,7 +333,7 @@ GEM
json json
get_process_mem (0.2.0) get_process_mem (0.2.0)
gherkin-ruby (0.3.2) gherkin-ruby (0.3.2)
github-linguist (4.7.5) github-linguist (4.7.6)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
escape_utils (~> 1.1.0) escape_utils (~> 1.1.0)
mime-types (>= 1.19) mime-types (>= 1.19)
@ -350,18 +343,18 @@ GEM
flowdock (~> 0.7) flowdock (~> 0.7)
gitlab-grit (>= 2.4.1) gitlab-grit (>= 2.4.1)
multi_json multi_json
gitlab-grit (2.7.3) gitlab-grit (2.8.1)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
diff-lcs (~> 1.1) diff-lcs (~> 1.1)
mime-types (~> 1.15) mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab_emoji (0.3.1) gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1) gemojione (~> 2.2, >= 2.2.1)
gitlab_git (8.2.0) gitlab_git (10.1.0)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
rugged (~> 0.24.0b13) rugged (~> 0.24.0)
gitlab_meta (7.0) gitlab_meta (7.0)
gitlab_omniauth-ldap (1.2.1) gitlab_omniauth-ldap (1.2.1)
net-ldap (~> 0.9) net-ldap (~> 0.9)
@ -379,6 +372,9 @@ GEM
rouge (~> 1.9) rouge (~> 1.9)
sanitize (~> 2.1.0) sanitize (~> 2.1.0)
stringex (~> 2.5.1) stringex (~> 2.5.1)
gollum-rugged_adapter (0.4.2)
mime-types (>= 1.15)
rugged (~> 0.24.0, >= 0.21.3)
gon (6.0.1) gon (6.0.1)
actionpack (>= 3.0) actionpack (>= 3.0)
json json
@ -406,8 +402,9 @@ GEM
html2haml (>= 1.0.1) html2haml (>= 1.0.1)
railties (>= 4.0.1) railties (>= 4.0.1)
hashie (3.4.3) hashie (3.4.3)
health_check (1.5.1)
rails (>= 2.3.0)
highline (1.7.8) highline (1.7.8)
hike (1.2.3)
hipchat (1.5.2) hipchat (1.5.2)
httparty httparty
mimemagic mimemagic
@ -419,8 +416,7 @@ GEM
haml (~> 4.0.0) haml (~> 4.0.0)
nokogiri (~> 1.6.0) nokogiri (~> 1.6.0)
ruby_parser (~> 3.5) ruby_parser (~> 3.5)
http-cookie (1.0.2) htmlentities (4.3.4)
domain_name (~> 0.5)
http_parser.rb (0.5.3) http_parser.rb (0.5.3)
httparty (0.13.7) httparty (0.13.7)
json (~> 1.8) json (~> 1.8)
@ -434,12 +430,10 @@ GEM
json json
ipaddress (0.8.2) ipaddress (0.8.2)
jquery-atwho-rails (1.3.2) jquery-atwho-rails (1.3.2)
jquery-rails (4.0.5) jquery-rails (4.1.1)
rails-dom-testing (~> 1.0) rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0) railties (>= 4.2.0)
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
jquery-scrollto-rails (1.4.3)
railties (> 3.1, < 5.0)
jquery-turbolinks (2.1.0) jquery-turbolinks (2.1.0)
railties (>= 3.1.0) railties (>= 3.1.0)
turbolinks turbolinks
@ -453,8 +447,14 @@ GEM
kgio (2.10.0) kgio (2.10.0)
launchy (2.4.3) launchy (2.4.3)
addressable (~> 2.3) addressable (~> 2.3)
letter_opener (1.1.2) letter_opener (1.4.1)
launchy (~> 2.2) launchy (~> 2.2)
letter_opener_web (1.3.0)
actionmailer (>= 3.2)
letter_opener (~> 1.0)
railties (>= 3.2)
licensee (8.0.0)
rugged (>= 0.24b)
listen (3.0.5) listen (3.0.5)
rb-fsevent (>= 0.9.3) rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9) rb-inotify (>= 0.9)
@ -462,11 +462,11 @@ GEM
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
macaddr (1.7.1) macaddr (1.7.1)
systemu (~> 2.6.2) systemu (~> 2.6.2)
mail (2.6.3) mail (2.6.4)
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 4)
mail_room (0.6.1) mail_room (0.7.0)
method_source (0.8.2) method_source (0.8.2)
mime-types (1.25.1) mime-types (2.99.1)
mimemagic (0.3.0) mimemagic (0.3.0)
mini_portile2 (2.0.0) mini_portile2 (2.0.0)
minitest (5.7.0) minitest (5.7.0)
@ -478,11 +478,9 @@ GEM
nested_form (0.3.2) nested_form (0.3.2)
net-ldap (0.12.1) net-ldap (0.12.1)
net-ssh (3.0.1) net-ssh (3.0.1)
netrc (0.11.0)
newrelic_rpm (3.14.1.311) newrelic_rpm (3.14.1.311)
nokogiri (1.6.7.2) nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2) mini_portile2 (~> 2.0.0.rc2)
nprogress-rails (0.1.6.7)
oauth (0.4.7) oauth (0.4.7)
oauth2 (1.0.0) oauth2 (1.0.0)
faraday (>= 0.8, < 0.10) faraday (>= 0.8, < 0.10)
@ -490,11 +488,13 @@ GEM
multi_json (~> 1.3) multi_json (~> 1.3)
multi_xml (~> 0.5) multi_xml (~> 0.5)
rack (~> 1.2) rack (~> 1.2)
octokit (3.8.0) octokit (4.3.0)
sawyer (~> 0.6.0, >= 0.5.3) sawyer (~> 0.7.0, >= 0.5.3)
omniauth (1.3.1) omniauth (1.3.1)
hashie (>= 1.2, < 4) hashie (>= 1.2, < 4)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
omniauth-auth0 (1.4.1)
omniauth-oauth2 (~> 1.1)
omniauth-azure-oauth2 (0.0.6) omniauth-azure-oauth2 (0.0.6)
jwt (~> 1.0) jwt (~> 1.0)
omniauth (~> 1.0) omniauth (~> 1.0)
@ -532,8 +532,8 @@ GEM
omniauth-oauth2 (1.3.1) omniauth-oauth2 (1.3.1)
oauth2 (~> 1.0) oauth2 (~> 1.0)
omniauth (~> 1.2) omniauth (~> 1.2)
omniauth-saml (1.4.2) omniauth-saml (1.5.0)
omniauth (~> 1.1) omniauth (~> 1.3)
ruby-saml (~> 1.1, >= 1.1.1) ruby-saml (~> 1.1, >= 1.1.1)
omniauth-shibboleth (1.2.1) omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0) omniauth (>= 1.0.0)
@ -549,16 +549,22 @@ GEM
orm_adapter (0.5.0) orm_adapter (0.5.0)
paranoia (2.1.4) paranoia (2.1.4)
activerecord (~> 4.0) activerecord (~> 4.0)
parser (2.2.3.0) parser (2.3.0.6)
ast (>= 1.1, < 3.0) ast (~> 2.2)
pg (0.18.4) pg (0.18.4)
poltergeist (1.8.1) poltergeist (1.9.0)
capybara (~> 2.1) capybara (~> 2.1)
cliver (~> 0.3.1) cliver (~> 0.3.1)
multi_json (~> 1.0) multi_json (~> 1.0)
websocket-driver (>= 0.2.0) websocket-driver (>= 0.2.0)
posix-spawn (0.3.11) posix-spawn (0.3.11)
powerpack (0.1.1) powerpack (0.1.1)
premailer (1.8.6)
css_parser (>= 1.3.6)
htmlentities (>= 4.0.0)
premailer-rails (1.9.2)
actionmailer (>= 3, < 6)
premailer (~> 1.7, >= 1.7.9)
pry (0.10.3) pry (0.10.3)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.8.1) method_source (~> 0.8.1)
@ -586,16 +592,16 @@ GEM
rack rack
rack-test (0.6.3) rack-test (0.6.3)
rack (>= 1.0) rack (>= 1.0)
rails (4.2.5.2) rails (4.2.6)
actionmailer (= 4.2.5.2) actionmailer (= 4.2.6)
actionpack (= 4.2.5.2) actionpack (= 4.2.6)
actionview (= 4.2.5.2) actionview (= 4.2.6)
activejob (= 4.2.5.2) activejob (= 4.2.6)
activemodel (= 4.2.5.2) activemodel (= 4.2.6)
activerecord (= 4.2.5.2) activerecord (= 4.2.6)
activesupport (= 4.2.5.2) activesupport (= 4.2.6)
bundler (>= 1.3.0, < 2.0) bundler (>= 1.3.0, < 2.0)
railties (= 4.2.5.2) railties (= 4.2.6)
sprockets-rails sprockets-rails
rails-deprecated_sanitizer (1.0.3) rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha) activesupport (>= 4.2.0.alpha)
@ -605,12 +611,12 @@ GEM
rails-deprecated_sanitizer (>= 1.0.1) rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.3) rails-html-sanitizer (1.0.3)
loofah (~> 2.0) loofah (~> 2.0)
railties (4.2.5.2) railties (4.2.6)
actionpack (= 4.2.5.2) actionpack (= 4.2.6)
activesupport (= 4.2.5.2) activesupport (= 4.2.6)
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rainbow (2.0.0) rainbow (2.1.0)
raindrops (0.15.0) raindrops (0.15.0)
rake (10.5.0) rake (10.5.0)
raphael-rails (2.1.2) raphael-rails (2.1.2)
@ -624,7 +630,7 @@ GEM
recaptcha (1.0.2) recaptcha (1.0.2)
json json
redcarpet (3.3.3) redcarpet (3.3.3)
redis (3.2.2) redis (3.3.0)
redis-actionpack (4.0.1) redis-actionpack (4.0.1)
actionpack (~> 4) actionpack (~> 4)
redis-rack (~> 1.5.0) redis-rack (~> 1.5.0)
@ -643,15 +649,11 @@ GEM
redis-store (~> 1.1.0) redis-store (~> 1.1.0)
redis-store (1.1.7) redis-store (1.1.7)
redis (>= 2.2) redis (>= 2.2)
request_store (1.2.1) request_store (1.3.0)
rerun (0.11.0) rerun (0.11.0)
listen (~> 3.0) listen (~> 3.0)
responders (2.1.1) responders (2.1.1)
railties (>= 4.2.0, < 5.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) rinku (1.7.3)
rotp (2.1.1) rotp (2.1.1)
rouge (1.10.1) rouge (1.10.1)
@ -659,62 +661,66 @@ GEM
chunky_png chunky_png
rqrcode-rails3 (0.1.7) rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2) rqrcode (>= 0.4.2)
rspec (3.3.0) rspec (3.4.0)
rspec-core (~> 3.3.0) rspec-core (~> 3.4.0)
rspec-expectations (~> 3.3.0) rspec-expectations (~> 3.4.0)
rspec-mocks (~> 3.3.0) rspec-mocks (~> 3.4.0)
rspec-core (3.3.2) rspec-core (3.4.4)
rspec-support (~> 3.3.0) rspec-support (~> 3.4.0)
rspec-expectations (3.3.1) rspec-expectations (3.4.0)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.3.0) rspec-support (~> 3.4.0)
rspec-mocks (3.3.2) rspec-mocks (3.4.1)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.3.0) rspec-support (~> 3.4.0)
rspec-rails (3.3.3) rspec-rails (3.4.2)
actionpack (>= 3.0, < 4.3) actionpack (>= 3.0, < 4.3)
activesupport (>= 3.0, < 4.3) activesupport (>= 3.0, < 4.3)
railties (>= 3.0, < 4.3) railties (>= 3.0, < 4.3)
rspec-core (~> 3.3.0) rspec-core (~> 3.4.0)
rspec-expectations (~> 3.3.0) rspec-expectations (~> 3.4.0)
rspec-mocks (~> 3.3.0) rspec-mocks (~> 3.4.0)
rspec-support (~> 3.3.0) rspec-support (~> 3.4.0)
rspec-support (3.3.0) rspec-retry (0.4.5)
rubocop (0.35.1) rspec-core
astrolabe (~> 1.3) rspec-support (3.4.1)
parser (>= 2.2.3.0, < 3.0) rubocop (0.38.0)
parser (>= 2.3.0.6, < 3.0)
powerpack (~> 0.1) powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0) rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
tins (<= 1.6.0) unicode-display_width (~> 1.0, >= 1.0.1)
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
ruby-progressbar (1.7.5) ruby-progressbar (1.7.5)
ruby-saml (1.1.1) ruby-saml (1.1.2)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
uuid (~> 2.3) uuid (~> 2.3)
ruby2ruby (2.2.0) ruby2ruby (2.3.0)
ruby_parser (~> 3.1) ruby_parser (~> 3.1)
sexp_processor (~> 4.0) sexp_processor (~> 4.0)
ruby_parser (3.7.2) ruby_parser (3.8.1)
sexp_processor (~> 4.1) sexp_processor (~> 4.1)
rubyntlm (0.5.2) rubyntlm (0.5.2)
rubypants (0.2.0) rubypants (0.2.0)
rufus-scheduler (3.1.10) rufus-scheduler (3.1.10)
rugged (0.24.0b13) rugged (0.24.0)
safe_yaml (1.0.4) safe_yaml (1.0.4)
sanitize (2.1.0) sanitize (2.1.0)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
sass (3.4.20) sass (3.4.21)
sass-rails (5.0.4) sass-rails (5.0.4)
railties (>= 4.0.0, < 5.0) railties (>= 4.0.0, < 5.0)
sass (~> 3.1) sass (~> 3.1)
sprockets (>= 2.8, < 4.0) sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0) sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3) tilt (>= 1.1, < 3)
sawyer (0.6.0) sawyer (0.7.0)
addressable (~> 2.3.5) addressable (>= 2.3.5, < 2.5)
faraday (~> 0.8, < 0.10) faraday (~> 0.8, < 0.10)
scss_lint (0.47.1)
rake (>= 0.9, < 11)
sass (~> 3.4.15)
sdoc (0.3.20) sdoc (0.3.20)
json (>= 1.1.3) json (>= 1.1.3)
rdoc (~> 3.10) rdoc (~> 3.10)
@ -726,22 +732,21 @@ GEM
sentry-raven (0.15.6) sentry-raven (0.15.6)
faraday (>= 0.7.6) faraday (>= 0.7.6)
settingslogic (2.0.9) settingslogic (2.0.9)
sexp_processor (4.6.0) sexp_processor (4.7.0)
sham_rack (1.3.6) sham_rack (1.3.6)
rack rack
shoulda-matchers (2.8.0) shoulda-matchers (2.8.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
sidekiq (4.0.1) sidekiq (4.1.2)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0) connection_pool (~> 2.2, >= 2.2.0)
json (~> 1.0)
redis (~> 3.2, >= 3.2.1) redis (~> 3.2, >= 3.2.1)
sidekiq-cron (0.4.0) sidekiq-cron (0.4.0)
redis-namespace (>= 1.5.2) redis-namespace (>= 1.5.2)
rufus-scheduler (>= 2.0.24) rufus-scheduler (>= 2.0.24)
sidekiq (>= 4.0.0) sidekiq (>= 4.0.0)
simple_oauth (0.1.9) simple_oauth (0.1.9)
simplecov (0.10.0) simplecov (0.11.2)
docile (~> 1.1.0) docile (~> 1.1.0)
json (~> 1.8) json (~> 1.8)
simplecov-html (~> 0.10.0) simplecov-html (~> 0.10.0)
@ -764,22 +769,22 @@ GEM
capybara (>= 2.0.0) capybara (>= 2.0.0)
railties (>= 3) railties (>= 3)
spinach (>= 0.4) spinach (>= 0.4)
spring (1.3.6) spinach-rerun-reporter (0.0.2)
spinach (~> 0.8)
spring (1.7.1)
spring-commands-rspec (1.0.4) spring-commands-rspec (1.0.4)
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-spinach (1.0.0) spring-commands-spinach (1.1.0)
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-teaspoon (0.0.2) spring-commands-teaspoon (0.0.2)
spring (>= 0.9.1) spring (>= 0.9.1)
sprockets (2.12.4) sprockets (3.6.0)
hike (~> 1.2) concurrent-ruby (~> 1.0)
multi_json (~> 1.0) rack (> 1, < 3)
rack (~> 1.0) sprockets-rails (3.0.4)
tilt (~> 1.1, != 1.3.0) actionpack (>= 4.0)
sprockets-rails (2.3.3) activesupport (>= 4.0)
actionpack (>= 3.0) sprockets (>= 3.0.0)
activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0)
state_machines (0.4.0) state_machines (0.4.0)
state_machines-activemodel (0.3.0) state_machines-activemodel (0.3.0)
activemodel (~> 4.1) activemodel (~> 4.1)
@ -791,8 +796,8 @@ GEM
systemu (2.6.5) systemu (2.6.5)
task_list (1.0.2) task_list (1.0.2)
html-pipeline html-pipeline
teaspoon (1.0.2) teaspoon (1.1.5)
railties (>= 3.2.5, < 5) railties (>= 3.2.5, < 6)
teaspoon-jasmine (2.2.0) teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0) teaspoon (>= 1.0.0)
temple (0.7.6) temple (0.7.6)
@ -807,7 +812,7 @@ GEM
rack (~> 1.0) rack (~> 1.0)
thor (0.19.1) thor (0.19.1)
thread_safe (0.3.5) thread_safe (0.3.5)
tilt (1.4.1) tilt (2.0.2)
timfel-krb5-auth (0.8.3) timfel-krb5-auth (0.8.3)
tinder (1.10.1) tinder (1.10.1)
eventmachine (~> 1.0) eventmachine (~> 1.0)
@ -833,8 +838,9 @@ GEM
underscore-rails (1.8.3) underscore-rails (1.8.3)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.1) unf_ext (0.0.7.2)
unicorn (4.8.3) unicode-display_width (1.0.2)
unicorn (4.9.0)
kgio (~> 2.6) kgio (~> 2.6)
rack rack
raindrops (~> 0.7) raindrops (~> 0.7)
@ -852,7 +858,7 @@ GEM
equalizer (~> 0.0, >= 0.0.9) equalizer (~> 0.0, >= 0.0.9)
warden (1.2.4) warden (1.2.4)
rack (>= 1.0) rack (>= 1.0)
web-console (2.2.1) web-console (2.3.0)
activemodel (>= 4.0) activemodel (>= 4.0)
binding_of_caller (>= 0.7.2) binding_of_caller (>= 0.7.2)
railties (>= 4.0) railties (>= 4.0)
@ -876,33 +882,32 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
RedCloth (~> 4.2.9) RedCloth (~> 4.2.9)
ace-rails-ap (~> 2.0.1) ace-rails-ap (~> 4.0.2)
activerecord-deprecated_finders (~> 1.0.3)
activerecord-session_store (~> 0.1.0) activerecord-session_store (~> 0.1.0)
acts-as-taggable-on (~> 3.4) acts-as-taggable-on (~> 3.4)
addressable (~> 2.3.8) addressable (~> 2.3.8)
after_commit_queue after_commit_queue
akismet (~> 2.0) akismet (~> 2.0)
allocations (~> 1.0) allocations (~> 1.0)
annotate (~> 2.6.0)
asana (~> 0.4.0) asana (~> 0.4.0)
asciidoctor (~> 1.5.2) asciidoctor (~> 1.5.2)
attr_encrypted (~> 1.3.4) attr_encrypted (~> 1.3.4)
awesome_print (~> 1.2.0) awesome_print (~> 1.2.0)
babosa (~> 1.0.2) babosa (~> 1.0.2)
base32 (~> 0.3.0)
benchmark-ips benchmark-ips
better_errors (~> 1.0.1) better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2) binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0) bootstrap-sass (~> 3.3.0)
brakeman (~> 3.1.0) brakeman (~> 3.2.0)
browser (~> 1.0.0) browser (~> 1.0.0)
bullet bullet
bundler-audit bundler-audit
byebug byebug
cal-heatmap-rails (~> 3.5.0) cal-heatmap-rails (~> 3.6.0)
capybara (~> 2.4.0) capybara (~> 2.6.2)
capybara-screenshot (~> 1.0.0) capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.9.0) carrierwave (~> 0.10.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
coffee-rails (~> 4.1.0) coffee-rails (~> 4.1.0)
colorize (~> 0.7.0) colorize (~> 0.7.0)
@ -916,11 +921,11 @@ DEPENDENCIES
devise-async (~> 0.9.0) devise-async (~> 0.9.0)
devise-two-factor (~> 2.0.0) devise-two-factor (~> 2.0.0)
diffy (~> 3.0.3) diffy (~> 3.0.3)
doorkeeper (~> 2.2.0) doorkeeper (~> 3.1)
dropzonejs-rails (~> 0.7.1) dropzonejs-rails (~> 0.7.1)
email_reply_parser (~> 0.5.8) email_reply_parser (~> 0.5.8)
email_spec (~> 1.6.0) email_spec (~> 1.6.0)
factory_girl_rails (~> 4.3.0) factory_girl_rails (~> 4.6.0)
ffaker (~> 2.0.0) ffaker (~> 2.0.0)
flay flay
flog flog
@ -933,27 +938,30 @@ DEPENDENCIES
github-markup (~> 1.3.1) github-markup (~> 1.3.1)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab_emoji (~> 0.3.0) gitlab_emoji (~> 0.3.0)
gitlab_git (~> 8.2) gitlab_git (~> 10.0)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.1.0) gollum-lib (~> 4.1.0)
gollum-rugged_adapter (~> 0.4.2)
gon (~> 6.0.1) gon (~> 6.0.1)
grape (~> 0.13.0) grape (~> 0.13.0)
grape-entity (~> 0.4.2) grape-entity (~> 0.4.2)
haml-rails (~> 0.9.0) haml-rails (~> 0.9.0)
health_check (~> 1.5.1)
hipchat (~> 1.5.0) hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0) html-pipeline (~> 1.11.0)
httparty (~> 0.13.3) httparty (~> 0.13.3)
influxdb (~> 0.2) influxdb (~> 0.2)
jquery-atwho-rails (~> 1.3.2) jquery-atwho-rails (~> 1.3.2)
jquery-rails (~> 4.0.0) jquery-rails (~> 4.1.0)
jquery-scrollto-rails (~> 1.4.3)
jquery-turbolinks (~> 2.1.0) jquery-turbolinks (~> 2.1.0)
jquery-ui-rails (~> 5.0.0) jquery-ui-rails (~> 5.0.0)
jwt
kaminari (~> 0.16.3) kaminari (~> 0.16.3)
letter_opener (~> 1.1.2) letter_opener_web (~> 1.3.0)
licensee (~> 8.0.0)
loofah (~> 2.0.3) loofah (~> 2.0.3)
mail_room (~> 0.6.1) mail_room (~> 0.7)
method_source (~> 0.8) method_source (~> 0.8)
minitest (~> 5.7.0) minitest (~> 5.7.0)
mousetrap-rails (~> 1.4.6) mousetrap-rails (~> 1.4.6)
@ -962,10 +970,10 @@ DEPENDENCIES
net-ssh (~> 3.0.1) net-ssh (~> 3.0.1)
newrelic_rpm (~> 3.14) newrelic_rpm (~> 3.14)
nokogiri (~> 1.6.7, >= 1.6.7.2) nokogiri (~> 1.6.7, >= 1.6.7.2)
nprogress-rails (~> 0.1.6.7)
oauth2 (~> 1.0.0) oauth2 (~> 1.0.0)
octokit (~> 3.8.0) octokit (~> 4.3.0)
omniauth (~> 1.3.1) omniauth (~> 1.3.1)
omniauth-auth0 (~> 1.4.1)
omniauth-azure-oauth2 (~> 0.0.6) omniauth-azure-oauth2 (~> 0.0.6)
omniauth-bitbucket (~> 0.0.2) omniauth-bitbucket (~> 0.0.2)
omniauth-cas3 (~> 1.1.2) omniauth-cas3 (~> 1.1.2)
@ -974,38 +982,42 @@ DEPENDENCIES
omniauth-gitlab (~> 1.0.0) omniauth-gitlab (~> 1.0.0)
omniauth-google-oauth2 (~> 0.2.0) omniauth-google-oauth2 (~> 0.2.0)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-saml (~> 1.4.2) omniauth-saml (~> 1.5.0)
omniauth-shibboleth (~> 1.2.0) omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0) omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
org-ruby (~> 0.9.12) org-ruby (~> 0.9.12)
paranoia (~> 2.0) paranoia (~> 2.0)
pg (~> 0.18.2) pg (~> 0.18.2)
poltergeist (~> 1.8.1) poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.0)
pry-rails pry-rails
quiet_assets (~> 1.0.2) quiet_assets (~> 1.0.2)
rack-attack (~> 4.3.1) rack-attack (~> 4.3.1)
rack-cors (~> 0.4.0) rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1) rack-oauth2 (~> 1.2.1)
rails (= 4.2.5.2) rails (= 4.2.6)
rails-deprecated_sanitizer (~> 1.0.3) rails-deprecated_sanitizer (~> 1.0.3)
raphael-rails (~> 2.1.2) raphael-rails (~> 2.1.2)
rblineprof rblineprof
rdoc (~> 3.6) rdoc (~> 3.6)
recaptcha recaptcha
redcarpet (~> 3.3.3) redcarpet (~> 3.3.3)
redis (~> 3.2)
redis-namespace redis-namespace
redis-rails (~> 4.0.0) redis-rails (~> 4.0.0)
request_store (~> 1.2.0) request_store (~> 1.3.0)
rerun (~> 0.11.0) rerun (~> 0.11.0)
responders (~> 2.0) responders (~> 2.0)
rouge (~> 1.10.1) rouge (~> 1.10.1)
rqrcode-rails3 (~> 0.1.7) rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.3.0) rspec-rails (~> 3.4.0)
rubocop (~> 0.35.0) rspec-retry
rubocop (~> 0.38.0)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
sanitize (~> 2.0) sanitize (~> 2.0)
sass-rails (~> 5.0.0) sass-rails (~> 5.0.0)
scss_lint (~> 0.47.0)
sdoc (~> 0.3.20) sdoc (~> 0.3.20)
seed-fu (~> 2.3.5) seed-fu (~> 2.3.5)
select2-rails (~> 3.5.9) select2-rails (~> 3.5.9)
@ -1015,19 +1027,20 @@ DEPENDENCIES
shoulda-matchers (~> 2.8.0) shoulda-matchers (~> 2.8.0)
sidekiq (~> 4.0) sidekiq (~> 4.0)
sidekiq-cron (~> 0.4.0) sidekiq-cron (~> 0.4.0)
simplecov (~> 0.10.0) simplecov (~> 0.11.0)
sinatra (~> 1.4.4) sinatra (~> 1.4.4)
six (~> 0.2.0) six (~> 0.2.0)
slack-notifier (~> 1.2.0) slack-notifier (~> 1.2.0)
spinach-rails (~> 0.2.1) spinach-rails (~> 0.2.1)
spring (~> 1.3.6) spinach-rerun-reporter (~> 0.0.2)
spring (~> 1.7.0)
spring-commands-rspec (~> 1.0.4) spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.0.0) spring-commands-spinach (~> 1.1.0)
spring-commands-teaspoon (~> 0.0.2) spring-commands-teaspoon (~> 0.0.2)
sprockets (~> 2.12.3) sprockets (~> 3.6.0)
state_machines-activerecord (~> 0.3.0) state_machines-activerecord (~> 0.3.0)
task_list (~> 1.0.2) task_list (~> 1.0.2)
teaspoon (~> 1.0.0) teaspoon (~> 1.1.0)
teaspoon-jasmine (~> 2.2.0) teaspoon-jasmine (~> 2.2.0)
test_after_commit (~> 0.4.2) test_after_commit (~> 0.4.2)
thin (~> 1.6.1) thin (~> 1.6.1)
@ -1036,7 +1049,7 @@ DEPENDENCIES
uglifier (~> 2.7.2) uglifier (~> 2.7.2)
underscore-rails (~> 1.8.0) underscore-rails (~> 1.8.0)
unf (~> 0.1.4) unf (~> 0.1.4)
unicorn (~> 4.8.2) unicorn (~> 4.9.0)
unicorn-worker-killer (~> 0.4.2) unicorn-worker-killer (~> 0.4.2)
version_sorter (~> 2.0.0) version_sorter (~> 2.0.0)
virtus (~> 1.0.1) virtus (~> 1.0.1)
@ -1045,4 +1058,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.11.2 1.12.3

View file

@ -2,23 +2,39 @@
## Purpose of describing the contributing process ## Purpose of describing the contributing process
Below we describe the contributing process to GitLab for two reasons. So that contributors know what to expect from maintainers (possible responses, friendly treatment, etc.). And so that maintainers know what to expect from contributors (use the latest version, ensure that the issue is addressed, friendly treatment, etc.). Below we describe the contributing process to GitLab for two reasons. So that
contributors know what to expect from maintainers (possible responses, friendly
treatment, etc.). And so that maintainers know what to expect from contributors
(use the latest version, ensure that the issue is addressed, friendly treatment,
etc.).
## Common actions ## Common actions
### Issue team ### Issue team
- Looks for issues without [workflow labels](#how-we-handle-issues) and triages issue
- Closes invalid issues with a comment (duplicates, [fixed in newer version](#issue-fixed-in-newer-version), [issue report for old version](#issue-report-for-old-version), not a problem in GitLab, etc.) - Looks for issues without [workflow labels](#how-we-handle-issues) and triages
- Asks for feedback from issue reporter ([invalid issue reports](#improperly-formatted-issue), [format code](#code-format), etc.) issue
- Monitors all issues for feedback (but especially ones commented on since automatically watching them) - Closes invalid issues with a comment (duplicates,
[fixed in newer version](#issue-fixed-in-newer-version),
[issue report for old version](#issue-report-for-old-version), not a problem
in GitLab, etc.)
- Asks for feedback from issue reporter
([invalid issue reports](#improperly-formatted-issue),
[format code](#code-format), etc.)
- Monitors all issues for feedback (but especially ones commented on since
automatically watching them)
- Closes issues with no feedback from the reporter for two weeks - Closes issues with no feedback from the reporter for two weeks
### Merge marshal ### Merge marshall & merge request coach
- Responds to merge requests the issue team mentions them in and monitors for new merge requests - Responds to merge requests the issue team mentions them in and monitors for
- Provides feedback to the merge request submitter to improve the merge request (style, tests, etc.) new merge requests
- Mark merge requests 'ready-for-merge' when they meet the contribution guidelines - Provides feedback to the merge request submitter to improve the merge request
- Mention developer(s) based on the [list of members and their specialities](https://about.gitlab.com/core-team/) (style, tests, etc.)
- Mark merge requests `Ready for Merge` when they meet the
[contribution acceptance criteria]
- Mention developer(s) based on the
[list of members and their specialities][team]
- Closes merge requests with no feedback from the reporter for two weeks - Closes merge requests with no feedback from the reporter for two weeks
## Priorities of the issue team ## Priorities of the issue team
@ -30,29 +46,41 @@ Below we describe the contributing process to GitLab for two reasons. So that co
## Mentioning people ## Mentioning people
The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](https://about.gitlab.com/core-team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person. The most important thing is making sure valid issues receive feedback from the
development team. Therefore the priority is mentioning developers that can help
on those issue. Please select someone with relevant experience from
[GitLab core team][core-team]. If there is nobody mentioned with that expertise
look in the commit history for the affected files to find someone. Avoid
mentioning the lead developer, this is the person that is least likely to give a
timely response. If the involvement of the lead developer is needed the other
core team members will mention this person.
## Workflow labels ## Workflow labels
Workflow labels are purposely not very detailed since that would be hard to keep updated as you would need to re-evaluate them after every comment. We optionally use functional labels on demand when want to group related issues to get an overview (for example all issues related to RVM, to tackle them in one go) and to add details to the issue. Workflow labels are purposely not very detailed since that would be hard to keep
updated as you would need to re-evaluate them after every comment. We optionally
use functional labels on demand when we want to group related issues to get an
overview (for example all issues related to RVM, to tackle them in one go) and
to add details to the issue.
- *Awaiting feedback*: Feedback pending from the reporter - ~"Awaiting Feedback" Feedback pending from the reporter
- *Awaiting confirmation of fix*: The issue should already be solved in **master** (generally you can avoid this workflow item and just close the issue right away) - ~UX needs help from a UX designer
- *Attached MR*: There is a MR attached and the discussion should happen there - ~Frontend needs help from a Front-end engineer. Please follow the
- We need to let issues stay in sync with the MR's. We can do this with a "Closing #XXXX" or "Fixes #XXXX" comment in the MR. We can't close the issue when there is a merge request because sometimes a MR is not good and we just close the MR, then the issue must stay. ["Implement design & UI elements" guidelines].
- *Developer*: needs help from a developer - ~up-for-grabs is an issue suitable for first-time contributors, of reasonable difficulty and size. Not exclusive with other labels.
- *UX* needs needs help from a UX designer - ~"feature proposal" is a proposal for a new feature for GitLab. People are encouraged to vote
- *Frontend* needs help from a Front-end engineer
- *Graphics* needs help from a Graphics designer
- *up-for-grabs* is an issue suitable for first-time contributors, of reasonable difficulty and size. Not exclusive with other labels.
- *feature proposal* is a proposal for a new feature for GitLab. People are encouraged to vote
in support or comment for further detail. Do not use `feature request`. in support or comment for further detail. Do not use `feature request`.
- ~bug is an issue reporting undesirable or incorrect behavior.
- ~customer is an issue reported by enterprise subscribers. This label should
be accompanied by *bug* or *feature proposal* labels.
Example workflow: when a UX designer provided a design but it needs frontend work they remove the UX label and add the frontend label. Example workflow: when a UX designer provided a design but it needs frontend work they remove the UX label and add the frontend label.
## Functional labels ## Functional labels
These labels describe what development specialities are involved such as: PostgreSQL, UX, LDAP. These labels describe what development specialities are involved such as: `CI`,
`Core`, `Documentation`, `Frontend`, `Issues`, `Merge Requests`, `Omnibus`,
`Release`, `Repository`, `UX`.
## Assigning issues ## Assigning issues
@ -60,21 +88,48 @@ If an issue is complex and needs the attention of a specific person, assignment
## Label colors ## Label colors
- Light orange `#fef2c0`: workflow labels for issue team members (awaiting feedback, awaiting confirmation of fix) - Light orange `#fef2c0`: workflow labels for issue team members (awaiting
- Bright orange `#eb6420`: workflow labels for core team members (attached MR, awaiting developer action/feedback) feedback, awaiting confirmation of fix)
- Bright orange `#eb6420`: workflow labels for core team members (attached MR,
awaiting developer action/feedback)
- Light blue `#82C5FF`: functional labels - Light blue `#82C5FF`: functional labels
- Green labels `#009800`: issues that can generally be ignored. For example, issues given the following labels normally can be closed immediately: - Green labels `#009800`: issues that can generally be ignored. For example,
- Support (see copy & paste response: [Support requests and configuration questions](#support-requests-and-configuration-questions) issues given the following labels normally can be closed immediately:
- Support (see copy & paste response:
[Support requests and configuration questions](#support-requests-and-configuration-questions)
## Be kind ## Be kind
Be kind to people trying to contribute. Be aware that people may be a non-native English speaker, they might not understand things or they might be very sensitive as to how you word things. Use Emoji to express your feelings (heart, star, smile, etc.). Some good tips about giving feedback to merge requests is in the [Thoughtbot code review guide](https://github.com/thoughtbot/guides/tree/master/code-review). Be kind to people trying to contribute. Be aware that people may be a non-native
English speaker, they might not understand things or they might be very
sensitive as to how you word things. Use Emoji to express your feelings (heart,
star, smile, etc.). Some good tips about giving feedback to merge requests is in
the [Thoughtbot code review guide].
## Feature Freeze
5 working days before the 22nd the stable branches for the upcoming release will
be frozen for major changes. Merge requests may still be merged into master
during this period. By freezing the stable branches prior to a release there's
no need to worry about last minute merge requests potentially breaking a lot of
things.
What is considered to be a major change is determined on a case by case basis as
this definition depends very much on the context of changes. For example, a 5
line change might have a big impact on the entire application. Ultimately the
decision will be made by those reviewing a merge request and the release
manager.
During the feature freeze all merge requests that are meant to go into the next
release should have the correct milestone assigned _and_ have the label
~"Pick into Stable" set. Merge requests without a milestone and this label will
not be merged into any stable branches.
## Copy & paste responses ## Copy & paste responses
### Improperly formatted issue ### Improperly formatted issue
Thanks for the issue report. Please reformat your issue to conform to the issue tracker guidelines found in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). Thanks for the issue report. Please reformat your issue to conform to the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
### Issue report for old version ### Issue report for old version
@ -110,11 +165,11 @@ This merge request has been closed because a request for more information has no
### Accepting merge requests ### Accepting merge requests
Is there an issue on the [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) Is there an issue on the
that is similar to this? \[issue tracker\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues) that is
Could you please link it here? similar to this? Could you please link it here?
Please be aware that new functionality that is not marked Please be aware that new functionality that is not marked
[accepting merge requests](https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_id=&scope=all&sort=created_desc&state=opened&utf8=%E2%9C%93&assignee_id=&author_id=&milestone_title=&label_name=Accepting+Merge+Requests) \[accepting merge requests\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_id=&scope=all&sort=created_desc&state=opened&utf8=%E2%9C%93&assignee_id=&author_id=&milestone_title=&label_name=Accepting+Merge+Requests)
might not make it into GitLab. might not make it into GitLab.
### Only accepting merge requests with green tests ### Only accepting merge requests with green tests
@ -129,4 +184,10 @@ rebase with master to see if that solves the issue.
We are currently in the process of closing down the issue tracker on GitHub, to We are currently in the process of closing down the issue tracker on GitHub, to
prevent duplication with the GitLab.com issue tracker. prevent duplication with the GitLab.com issue tracker.
Since this is an older issue I'll be closing this for now. If you think this is Since this is an older issue I'll be closing this for now. If you think this is
still an issue I encourage you to open it on the \[GitLab.com issue tracker\](https://gitlab.com/gitlab-org/gitlab-ce/issues). still an issue I encourage you to open it on the \[GitLab.com issue tracker\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues).
[core-team]: https://about.gitlab.com/core-team/
[team]: https://about.gitlab.com/team/
[contribution acceptance criteria]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria
["Implement design & UI elements" guidelines]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#implement-design-ui-elements
[Thoughtbot code review guide]: https://github.com/thoughtbot/guides/tree/master/code-review

View file

@ -1,6 +1,6 @@
# GitLab # GitLab
[![build status](https://ci.gitlab.com/projects/1/status.svg?ref=master)](https://ci.gitlab.com/projects/1?ref=master) [![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master)
[![Build Status](https://semaphoreci.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/400484/shields_badge.svg)](https://semaphoreci.com/gitlabhq/gitlabhq) [![Build Status](https://semaphoreci.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/400484/shields_badge.svg)](https://semaphoreci.com/gitlabhq/gitlabhq)
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
[![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.svg?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master) [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.svg?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master)
@ -20,6 +20,10 @@ To see how GitLab looks please see the [features page on our website](https://ab
- Completely free and open source (MIT Expat license) - Completely free and open source (MIT Expat license)
- Powered by [Ruby on Rails](https://github.com/rails/rails) - Powered by [Ruby on Rails](https://github.com/rails/rails)
## Hiring
We're hiring developers, support people, and production engineers all the time, please see our [jobs page](https://about.gitlab.com/jobs/).
## Editions ## Editions
There are two editions of GitLab: There are two editions of GitLab:
@ -31,11 +35,11 @@ There are two editions of GitLab:
On [about.gitlab.com](https://about.gitlab.com/) you can find more information about: On [about.gitlab.com](https://about.gitlab.com/) you can find more information about:
- [Subscriptions](https://about.gitlab.com/subscription/) - [Subscriptions](https://about.gitlab.com/pricing/)
- [Consultancy](https://about.gitlab.com/consultancy/) - [Consultancy](https://about.gitlab.com/consultancy/)
- [Community](https://about.gitlab.com/community/) - [Community](https://about.gitlab.com/community/)
- [Hosted GitLab.com](https://about.gitlab.com/gitlab-com/) use GitLab as a free service - [Hosted GitLab.com](https://about.gitlab.com/gitlab-com/) use GitLab as a free service
- [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/) with additional features aimed at larger organizations. - [GitLab Enterprise Edition](https://about.gitlab.com/features/#enterprise) with additional features aimed at larger organizations.
- [GitLab CI](https://about.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab. - [GitLab CI](https://about.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab.
## Requirements ## Requirements
@ -80,7 +84,7 @@ There are a lot of [third-party applications integrating with GitLab](https://ab
## GitLab release cycle ## GitLab release cycle
For more information about the release process see the [release documentation](http://doc.gitlab.com/ce/release/). For more information about the release process see the [release documentation](https://gitlab.com/gitlab-org/release-tools/blob/master/README.md).
## Upgrading ## Upgrading

View file

@ -1 +1 @@
8.5.8 8.8.2

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

View file

@ -1,7 +1,7 @@
class @Activities class @Activities
constructor: -> constructor: ->
Pager.init 20, true Pager.init 20, true
$(".event-filter a").bind "click", (event) => $(".event-filter-link").on "click", (event) =>
event.preventDefault() event.preventDefault()
@toggleFilter($(event.currentTarget)) @toggleFilter($(event.currentTarget))
@reloadActivities() @reloadActivities()
@ -12,18 +12,10 @@ class @Activities
toggleFilter: (sender) -> toggleFilter: (sender) ->
sender.closest('li').toggleClass "active" $('.event-filter .active').removeClass "active"
event_filters = $.cookie("event_filter") event_filters = $.cookie("event_filter")
filter = sender.attr("id").split("_")[0] filter = sender.attr("id").split("_")[0]
if event_filters $.cookie "event_filter", (if event_filters isnt filter then filter else ""), { path: '/' }
event_filters = event_filters.split(",")
else
event_filters = new Array()
index = event_filters.indexOf(filter) if event_filters isnt filter
if index is -1 sender.closest('li').toggleClass "active"
event_filters.push filter
else
event_filters.splice index, 1
$.cookie "event_filter", event_filters.join(","), { path: '/' }

View file

@ -1,12 +1,15 @@
@Api = @Api =
groups_path: "/api/:version/groups.json" groupsPath: "/api/:version/groups.json"
group_path: "/api/:version/groups/:id.json" groupPath: "/api/:version/groups/:id.json"
namespaces_path: "/api/:version/namespaces.json" namespacesPath: "/api/:version/namespaces.json"
group_projects_path: "/api/:version/groups/:id/projects.json" groupProjectsPath: "/api/:version/groups/:id/projects.json"
projects_path: "/api/:version/projects.json" projectsPath: "/api/:version/projects.json"
labelsPath: "/api/:version/projects/:id/labels"
licensePath: "/api/:version/licenses/:key"
gitignorePath: "/api/:version/gitignores/:key"
group: (group_id, callback) -> group: (group_id, callback) ->
url = Api.buildUrl(Api.group_path) url = Api.buildUrl(Api.groupPath)
url = url.replace(':id', group_id) url = url.replace(':id', group_id)
$.ajax( $.ajax(
@ -20,7 +23,7 @@
# Return groups list. Filtered by query # Return groups list. Filtered by query
# Only active groups retrieved # Only active groups retrieved
groups: (query, skip_ldap, callback) -> groups: (query, skip_ldap, callback) ->
url = Api.buildUrl(Api.groups_path) url = Api.buildUrl(Api.groupsPath)
$.ajax( $.ajax(
url: url url: url
@ -34,7 +37,7 @@
# Return namespaces list. Filtered by query # Return namespaces list. Filtered by query
namespaces: (query, callback) -> namespaces: (query, callback) ->
url = Api.buildUrl(Api.namespaces_path) url = Api.buildUrl(Api.namespacesPath)
$.ajax( $.ajax(
url: url url: url
@ -48,7 +51,7 @@
# Return projects list. Filtered by query # Return projects list. Filtered by query
projects: (query, order, callback) -> projects: (query, order, callback) ->
url = Api.buildUrl(Api.projects_path) url = Api.buildUrl(Api.projectsPath)
$.ajax( $.ajax(
url: url url: url
@ -61,9 +64,24 @@
).done (projects) -> ).done (projects) ->
callback(projects) callback(projects)
newLabel: (project_id, data, callback) ->
url = Api.buildUrl(Api.labelsPath)
url = url.replace(':id', project_id)
data.private_token = gon.api_token
$.ajax(
url: url
type: "POST"
data: data
dataType: "json"
).done (label) ->
callback(label)
.error (message) ->
callback(message.responseJSON)
# Return group projects list. Filtered by query # Return group projects list. Filtered by query
groupProjects: (group_id, query, callback) -> groupProjects: (group_id, query, callback) ->
url = Api.buildUrl(Api.group_projects_path) url = Api.buildUrl(Api.groupProjectsPath)
url = url.replace(':id', group_id) url = url.replace(':id', group_id)
$.ajax( $.ajax(
@ -76,6 +94,22 @@
).done (projects) -> ).done (projects) ->
callback(projects) callback(projects)
# Return text for a specific license
licenseText: (key, data, callback) ->
url = Api.buildUrl(Api.licensePath).replace(':key', key)
$.ajax(
url: url
data: data
).done (license) ->
callback(license)
gitignoreText: (key, callback) ->
url = Api.buildUrl(Api.gitignorePath).replace(':key', key)
$.get url, (gitignore) ->
callback(gitignore)
buildUrl: (url) -> buildUrl: (url) ->
url = gon.relative_url_root + url if gon.relative_url_root? url = gon.relative_url_root + url if gon.relative_url_root?
return url.replace(':version', gon.api_version) return url.replace(':version', gon.api_version)

View file

@ -7,6 +7,7 @@
#= require jquery #= require jquery
#= require jquery-ui/autocomplete #= require jquery-ui/autocomplete
#= require jquery-ui/datepicker #= require jquery-ui/datepicker
#= require jquery-ui/draggable
#= require jquery-ui/effect-highlight #= require jquery-ui/effect-highlight
#= require jquery-ui/sortable #= require jquery-ui/sortable
#= require jquery_ujs #= require jquery_ujs
@ -21,7 +22,17 @@
#= require cal-heatmap #= require cal-heatmap
#= require turbolinks #= require turbolinks
#= require autosave #= require autosave
#= require bootstrap #= require bootstrap/affix
#= require bootstrap/alert
#= require bootstrap/button
#= require bootstrap/collapse
#= require bootstrap/dropdown
#= require bootstrap/modal
#= require bootstrap/scrollspy
#= require bootstrap/tab
#= require bootstrap/transition
#= require bootstrap/tooltip
#= require bootstrap/popover
#= require select2 #= require select2
#= require raphael #= require raphael
#= require g.raphael #= require g.raphael
@ -31,8 +42,6 @@
#= require ace/ace #= require ace/ace
#= require ace/ext-searchbox #= require ace/ext-searchbox
#= require underscore #= require underscore
#= require nprogress
#= require nprogress-turbolinks
#= require dropzone #= require dropzone
#= require mousetrap #= require mousetrap
#= require mousetrap/pause #= require mousetrap/pause
@ -42,8 +51,10 @@
#= require shortcuts_issuable #= require shortcuts_issuable
#= require shortcuts_network #= require shortcuts_network
#= require jquery.nicescroll #= require jquery.nicescroll
#= require date.format
#= require_tree . #= require_tree .
#= require fuzzaldrin-plus #= require fuzzaldrin-plus
#= require cropper
window.slugify = (text) -> window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
@ -109,6 +120,8 @@ window.onload = ->
setTimeout shiftWindow, 100 setTimeout shiftWindow, 100
$ -> $ ->
bootstrapBreakpoint = bp.getBreakpointSize()
$(".nicescroll").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF") $(".nicescroll").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF")
# Click a .js-select-on-focus field, select the contents # Click a .js-select-on-focus field, select the contents
@ -138,7 +151,7 @@ $ ->
# Initialize tooltips # Initialize tooltips
$('body').tooltip( $('body').tooltip(
selector: '.has_tooltip, [data-toggle="tooltip"]' selector: '.has-tooltip, [data-toggle="tooltip"]'
placement: (_, el) -> placement: (_, el) ->
$el = $(el) $el = $(el)
$el.data('placement') || 'bottom' $el.data('placement') || 'bottom'
@ -161,7 +174,7 @@ $ ->
$('.trigger-submit').on 'change', -> $('.trigger-submit').on 'change', ->
$(@).parents('form').submit() $(@).parents('form').submit()
$('abbr.timeago, .js-timeago').timeago() gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true)
# Flash # Flash
if (flash = $(".flash-container")).length > 0 if (flash = $(".flash-container")).length > 0
@ -191,6 +204,7 @@ $ ->
$('.header-content .title').toggle() $('.header-content .title').toggle()
$('.header-content .navbar-collapse').toggle() $('.header-content .navbar-collapse').toggle()
$('.navbar-toggle').toggleClass('active') $('.navbar-toggle').toggleClass('active')
$('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
# Show/hide comments on diff # Show/hide comments on diff
$("body").on "click", ".js-toggle-diff-comments", (e) -> $("body").on "click", ".js-toggle-diff-comments", (e) ->
@ -217,75 +231,61 @@ $ ->
$this = $(this) $this = $(this)
$this.attr 'value', $this.val() $this.attr 'value', $this.val()
$sidebarGutterToggle = $('.js-sidebar-toggle')
$navIconToggle = $('.toggle-nav-collapse')
$(document) $(document)
.off 'breakpoint:change' .off 'breakpoint:change'
.on 'breakpoint:change', (e, breakpoint) -> .on 'breakpoint:change', (e, breakpoint) ->
if breakpoint is 'sm' or breakpoint is 'xs' if breakpoint is 'sm' or breakpoint is 'xs'
$gutterIcon = $('.gutter-toggle').find('i') $gutterIcon = $sidebarGutterToggle.find('i')
if $gutterIcon.hasClass('fa-angle-double-right') if $gutterIcon.hasClass('fa-angle-double-right')
$gutterIcon.closest('a').trigger('click') $sidebarGutterToggle.trigger('click')
$navIcon = $navIconToggle.find('.fa')
if $navIcon.hasClass('fa-angle-left')
$navIconToggle.trigger('click')
$(document) $(document)
.off 'click', 'aside .gutter-toggle' .off 'click', '.js-sidebar-toggle'
.on 'click', 'aside .gutter-toggle', (e) -> .on 'click', '.js-sidebar-toggle', (e, triggered) ->
e.preventDefault() e.preventDefault()
$this = $(this) $this = $(this)
$thisIcon = $this.find 'i' $thisIcon = $this.find 'i'
$allGutterToggleIcons = $('.js-sidebar-toggle i')
if $thisIcon.hasClass('fa-angle-double-right') if $thisIcon.hasClass('fa-angle-double-right')
$thisIcon $allGutterToggleIcons
.removeClass('fa-angle-double-right') .removeClass('fa-angle-double-right')
.addClass('fa-angle-double-left') .addClass('fa-angle-double-left')
$this $('aside.right-sidebar')
.closest('aside')
.removeClass('right-sidebar-expanded') .removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed') .addClass('right-sidebar-collapsed')
$('.page-with-sidebar') $('.page-with-sidebar')
.removeClass('right-sidebar-expanded') .removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed') .addClass('right-sidebar-collapsed')
else else
$thisIcon $allGutterToggleIcons
.removeClass('fa-angle-double-left') .removeClass('fa-angle-double-left')
.addClass('fa-angle-double-right') .addClass('fa-angle-double-right')
$this $('aside.right-sidebar')
.closest('aside')
.removeClass('right-sidebar-collapsed') .removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded') .addClass('right-sidebar-expanded')
$('.page-with-sidebar') $('.page-with-sidebar')
.removeClass('right-sidebar-collapsed') .removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded') .addClass('right-sidebar-expanded')
if not triggered
$.cookie("collapsed_gutter", $.cookie("collapsed_gutter",
$('.right-sidebar') $('.right-sidebar')
.hasClass('right-sidebar-collapsed'), { path: '/' }) .hasClass('right-sidebar-collapsed'), { path: '/' })
bootstrapBreakpoint = undefined;
checkBootstrapBreakpoints = ->
if $('.device-xs').is(':visible')
bootstrapBreakpoint = "xs"
else if $('.device-sm').is(':visible')
bootstrapBreakpoint = "sm"
else if $('.device-md').is(':visible')
bootstrapBreakpoint = "md"
else if $('.device-lg').is(':visible')
bootstrapBreakpoint = "lg"
setBootstrapBreakpoints = ->
if $('.device-xs').length
return
$("body")
.append('<div class="device-xs visible-xs"></div>'+
'<div class="device-sm visible-sm"></div>'+
'<div class="device-md visible-md"></div>'+
'<div class="device-lg visible-lg"></div>')
checkBootstrapBreakpoints()
fitSidebarForSize = -> fitSidebarForSize = ->
oldBootstrapBreakpoint = bootstrapBreakpoint oldBootstrapBreakpoint = bootstrapBreakpoint
checkBootstrapBreakpoints() bootstrapBreakpoint = bp.getBreakpointSize()
if bootstrapBreakpoint != oldBootstrapBreakpoint if bootstrapBreakpoint != oldBootstrapBreakpoint
$(document).trigger('breakpoint:change', [bootstrapBreakpoint]) $(document).trigger('breakpoint:change', [bootstrapBreakpoint])
checkInitialSidebarSize = -> checkInitialSidebarSize = ->
bootstrapBreakpoint = bp.getBreakpointSize()
if bootstrapBreakpoint is "xs" or "sm" if bootstrapBreakpoint is "xs" or "sm"
$(document).trigger('breakpoint:change', [bootstrapBreakpoint]) $(document).trigger('breakpoint:change', [bootstrapBreakpoint])
@ -294,6 +294,5 @@ $ ->
.on "resize", (e) -> .on "resize", (e) ->
fitSidebarForSize() fitSidebarForSize()
setBootstrapBreakpoints()
checkInitialSidebarSize() checkInitialSidebarSize()
new Aside() new Aside()

View file

@ -5,7 +5,6 @@ class @Aside
e.preventDefault() e.preventDefault()
btn = $(e.currentTarget) btn = $(e.currentTarget)
icon = btn.find('i') icon = btn.find('i')
console.log('1')
if icon.hasClass('fa-angle-left') if icon.hasClass('fa-angle-left')
btn.parent().find('section').hide() btn.parent().find('section').hide()

View file

@ -16,7 +16,7 @@ class @Autosave
try try
text = window.localStorage.getItem @key text = window.localStorage.getItem @key
catch catch e
return return
@field.val text if text?.length > 0 @field.val text if text?.length > 0

View file

@ -1,37 +1,77 @@
class @AwardsHandler class @AwardsHandler
constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) -> constructor: (@getEmojisUrl, @postEmojiUrl, @noteableType, @noteableId, @unicodes) ->
$(".add-award").click (event)-> $('.js-add-award').on 'click', (event) =>
event.stopPropagation() event.stopPropagation()
event.preventDefault() event.preventDefault()
$(".emoji-menu").show()
$("#emoji_search").focus()
$("html").on 'click', (event) -> @showEmojiMenu()
if !$(event.target).closest(".emoji-menu").length
if $(".emoji-menu").is(":visible") $('html').on 'click', (event) ->
$(".emoji-menu").hide() if !$(event.target).closest('.emoji-menu').length
if $('.emoji-menu').is(':visible')
$('.emoji-menu').removeClass 'is-visible'
$('.awards')
.off 'click'
.on 'click', '.js-emoji-btn', @handleClick
@renderFrequentlyUsedBlock() @renderFrequentlyUsedBlock()
handleClick: (e) ->
e.preventDefault()
emoji = $(this)
.find('.icon')
.data 'emoji'
if emoji is 'thumbsup' and awardsHandler.didUserClickEmoji $(this), 'thumbsdown'
awardsHandler.addAward 'thumbsdown'
else if emoji is 'thumbsdown' and awardsHandler.didUserClickEmoji $(this), 'thumbsup'
awardsHandler.addAward 'thumbsup'
awardsHandler.addAward emoji
$(this).trigger 'blur'
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'
$('.emoji-menu').removeClass 'is-visible'
$('#emoji_search').blur()
else
$('.emoji-menu').addClass 'is-visible'
$('#emoji_search').focus()
else
$('.js-add-award').addClass 'is-loading'
$.get @getEmojisUrl, (response) =>
$('.js-add-award').removeClass 'is-loading'
$('.js-award-holder').append response
setTimeout =>
$('.emoji-menu').addClass 'is-visible'
$('#emoji_search').focus()
@setupSearch() @setupSearch()
, 200
addAward: (emoji) -> addAward: (emoji) ->
emoji = @normilizeEmojiName(emoji)
@postEmoji emoji, => @postEmoji emoji, =>
@addAwardToEmojiBar(emoji) @addAwardToEmojiBar(emoji)
$(".emoji-menu").hide() $('.emoji-menu').removeClass 'is-visible'
addAwardToEmojiBar: (emoji) -> addAwardToEmojiBar: (emoji) ->
@addEmojiToFrequentlyUsedList(emoji) @addEmojiToFrequentlyUsedList(emoji)
emoji = @normilizeEmojiName(emoji)
if @exist(emoji) if @exist(emoji)
if @isActive(emoji) if @isActive(emoji)
@decrementCounter(emoji) @decrementCounter(emoji)
else else
counter = @findEmojiIcon(emoji).siblings(".counter") counter = @findEmojiIcon(emoji).siblings('.js-counter')
counter.text(parseInt(counter.text()) + 1) counter.text(parseInt(counter.text()) + 1)
counter.parent().addClass("active") counter.parent().addClass('active')
@addMeToAuthorList(emoji) @addMeToAuthorList(emoji)
else else
@createEmoji(emoji) @createEmoji(emoji)
@ -40,43 +80,47 @@ class @AwardsHandler
@findEmojiIcon(emoji).length > 0 @findEmojiIcon(emoji).length > 0
isActive: (emoji) -> isActive: (emoji) ->
@findEmojiIcon(emoji).parent().hasClass("active") @findEmojiIcon(emoji).parent().hasClass('active')
decrementCounter: (emoji) -> decrementCounter: (emoji) ->
counter = @findEmojiIcon(emoji).siblings(".counter") counter = @findEmojiIcon(emoji).siblings('.js-counter')
emojiIcon = counter.parent() emojiIcon = counter.parent()
if parseInt(counter.text()) > 1 if parseInt(counter.text()) > 1
counter.text(parseInt(counter.text()) - 1) counter.text(parseInt(counter.text()) - 1)
emojiIcon.removeClass("active") emojiIcon.removeClass('active')
@removeMeFromAuthorList(emoji) @removeMeFromAuthorList(emoji)
else if emoji == "thumbsup" || emoji == "thumbsdown" else if emoji == 'thumbsup' || emoji == 'thumbsdown'
emojiIcon.tooltip("destroy") emojiIcon.tooltip('destroy')
counter.text(0) counter.text(0)
emojiIcon.removeClass("active") emojiIcon.removeClass('active')
@removeMeFromAuthorList(emoji) @removeMeFromAuthorList(emoji)
else else
emojiIcon.tooltip("destroy") emojiIcon.tooltip('destroy')
emojiIcon.remove() emojiIcon.remove()
removeMeFromAuthorList: (emoji) -> removeMeFromAuthorList: (emoji) ->
award_block = @findEmojiIcon(emoji).parent() awardBlock = @findEmojiIcon(emoji).parent()
authors = award_block.attr("data-original-title").split(", ") authors = awardBlock
authors.splice(authors.indexOf("me"),1) .attr('data-original-title')
award_block.closest(".award").attr("data-original-title", authors.join(", ")) .split(', ')
@resetTooltip(award_block) authors.splice(authors.indexOf('me'),1)
awardBlock
.closest('.js-emoji-btn')
.attr('data-original-title', authors.join(', '))
@resetTooltip(awardBlock)
addMeToAuthorList: (emoji) -> addMeToAuthorList: (emoji) ->
award_block = @findEmojiIcon(emoji).parent() awardBlock = @findEmojiIcon(emoji).parent()
origTitle = award_block.attr("data-original-title").trim() origTitle = awardBlock.attr('data-original-title').trim()
authors = [] authors = []
if origTitle if origTitle
authors = origTitle.split(', ') authors = origTitle.split(', ')
authors.push("me") authors.push('me')
award_block.attr("title", authors.join(", ")) awardBlock.attr('data-original-title', authors.join(', '))
@resetTooltip(award_block) @resetTooltip(awardBlock)
resetTooltip: (award) -> resetTooltip: (award) ->
award.tooltip("destroy") award.tooltip('destroy')
# "destroy" call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout. # "destroy" call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout.
setTimeout (-> setTimeout (->
@ -88,83 +132,84 @@ class @AwardsHandler
emojiCssClass = @resolveNameToCssClass(emoji) emojiCssClass = @resolveNameToCssClass(emoji)
nodes = [] nodes = []
nodes.push("<div class='award active' title='me'>") nodes.push(
nodes.push("<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>") "<button class='btn award-control js-emoji-btn has-tooltip active' data-original-title='me'>",
nodes.push("<div class='counter'>1</div>") "<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>",
nodes.push("</div>") "<span class='award-control-text js-counter'>1</span>",
"</button>"
)
emoji_node = $(nodes.join("\n")).insertBefore(".awards-controls").find(".emoji-icon").data("emoji", emoji) $(nodes.join("\n"))
.insertBefore('.js-award-holder')
$(".award").tooltip() .find('.emoji-icon')
.data('emoji', emoji)
$('.award-control').tooltip()
resolveNameToCssClass: (emoji) -> resolveNameToCssClass: (emoji) ->
emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']") emojiIcon = $(".emoji-menu-content [data-emoji='#{emoji}']")
if emoji_icon.length > 0 if emojiIcon.length > 0
unicodeName = emoji_icon.data("unicode-name") unicodeName = emojiIcon.data('unicode-name')
else else
# Find by alias # Find by alias
unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data("unicode-name") unicodeName = $(".emoji-menu-content [data-aliases*=':#{emoji}:']").data('unicode-name')
"emoji-#{unicodeName}" "emoji-#{unicodeName}"
postEmoji: (emoji, callback) -> postEmoji: (emoji, callback) ->
$.post @post_emoji_url, { note: { $.post @postEmojiUrl, { note: {
note: ":#{emoji}:" note: ":#{emoji}:"
noteable_type: @noteable_type noteable_type: @noteableType
noteable_id: @noteable_id noteable_id: @noteableId
}},(data) -> }},(data) ->
if data.ok if data.ok
callback.call() callback.call()
findEmojiIcon: (emoji) -> findEmojiIcon: (emoji) ->
$(".award [data-emoji='#{emoji}']") $(".awards > .js-emoji-btn [data-emoji='#{emoji}']")
scrollToAwards: -> scrollToAwards: ->
$('body, html').animate({ $('body, html').animate({
scrollTop: $('.awards').offset().top - 80 scrollTop: $('.awards').offset().top - 80
}, 200) }, 200)
normilizeEmojiName: (emoji) ->
@aliases[emoji] || emoji
addEmojiToFrequentlyUsedList: (emoji) -> addEmojiToFrequentlyUsedList: (emoji) ->
frequently_used_emojis = @getFrequentlyUsedEmojis() frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
frequently_used_emojis.push(emoji) frequentlyUsedEmojis.push(emoji)
$.cookie('frequently_used_emojis', frequently_used_emojis.join(","), { expires: 365 }) $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 })
getFrequentlyUsedEmojis: -> getFrequentlyUsedEmojis: ->
frequently_used_emojis = ($.cookie('frequently_used_emojis') || "").split(",") frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') || '').split(',')
_.compact(_.uniq(frequently_used_emojis)) _.compact(_.uniq(frequentlyUsedEmojis))
renderFrequentlyUsedBlock: -> renderFrequentlyUsedBlock: ->
if $.cookie('frequently_used_emojis') if $.cookie('frequently_used_emojis')
frequently_used_emojis = @getFrequentlyUsedEmojis() frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
ul = $("<ul>") ul = $('<ul>')
for emoji in frequently_used_emojis for emoji in frequentlyUsedEmojis
do (emoji) -> do (emoji) ->
$(".emoji-menu-content [data-emoji='#{emoji}']").closest("li").clone().appendTo(ul) $(".emoji-menu-content [data-emoji='#{emoji}']").closest('li').clone().appendTo(ul)
$("input.emoji-search").after(ul).after($("<h5>").text("Frequently used")) $('input.emoji-search').after(ul).after($('<h5>').text('Frequently used'))
setupSearch: -> setupSearch: ->
$("input.emoji-search").keyup (ev) => $('input.emoji-search').keyup (ev) =>
term = $(ev.target).val() term = $(ev.target).val()
# Clean previous search results # Clean previous search results
$("ul.emoji-search,h5.emoji-search").remove() $('ul.emoji-menu-search, h5.emoji-search').remove()
if term if term
# Generate a search result block # Generate a search result block
h5 = $("<h5>").text("Search results").addClass("emoji-search") h5 = $('<h5>').text('Search results').addClass('emoji-search')
found_emojis = @searchEmojis(term).show() foundEmojis = @searchEmojis(term).show()
ul = $("<ul>").addClass("emoji-search").append(found_emojis) ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis)
$(".emoji-menu-content ul, .emoji-menu-content h5").hide() $('.emoji-menu-content ul, .emoji-menu-content h5').hide()
$(".emoji-menu-content").append(h5).append(ul) $('.emoji-menu-content').append(h5).append(ul)
else else
$(".emoji-menu-content").children().show() $('.emoji-menu-content').children().show()
searchEmojis: (term)-> searchEmojis: (term)->
$(".emoji-menu-content [data-emoji*='#{term}']").closest("li").clone() $(".emoji-menu-content [data-emoji*='#{term}']").closest("li").clone()

View file

@ -1,29 +1,56 @@
# Quick Submit behavior # Quick Submit behavior
# #
# When an input field with the `js-quick-submit` class receives a "Meta+Enter" # When a child field of a form with a `js-quick-submit` class receives a
# (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, its parent form is # "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form
# submitted. # is submitted.
# #
#= require extensions/jquery #= require extensions/jquery
# #
# ### Example Markup # ### Example Markup
# #
# <form action="/foo"> # <form action="/foo" class="js-quick-submit">
# <input type="text" class="js-quick-submit" /> # <input type="text" />
# <textarea class="js-quick-submit"></textarea> # <textarea></textarea>
# <input type="submit" value="Submit" />
# </form> # </form>
# #
$(document).on 'keydown.quick_submit', '.js-quick-submit', (e) -> isMac = ->
return if (e.originalEvent && e.originalEvent.repeat) || e.repeat navigator.userAgent.match(/Macintosh/)
return unless e.keyCode == 13 # Enter
if navigator.userAgent.match(/Macintosh/) keyCodeIs = (e, keyCode) ->
return unless (e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey) return false if (e.originalEvent && e.originalEvent.repeat) || e.repeat
else return e.keyCode == keyCode
return unless (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey)
$(document).on 'keydown.quick_submit', '.js-quick-submit', (e) ->
return unless keyCodeIs(e, 13) # Enter
return unless (e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey) || (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey)
e.preventDefault() e.preventDefault()
$form = $(e.target).closest('form') $form = $(e.target).closest('form')
$form.find('input[type=submit], button[type=submit]').disable() $submit_button = $form.find('input[type=submit], button[type=submit]')
return if $submit_button.attr('disabled')
$submit_button.disable()
$form.submit() $form.submit()
# If the user tabs to a submit button on a `js-quick-submit` form, display a
# tooltip to let them know they could've used the hotkey
$(document).on 'keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', (e) ->
return unless keyCodeIs(e, 9) # Tab
if isMac()
title = "You can also press &#8984;-Enter"
else
title = "You can also press Ctrl-Enter"
$this = $(@)
$this.tooltip(
container: 'body'
html: 'true'
placement: 'auto top'
title: title
trigger: 'manual'
).tooltip('show').one('blur', -> $this.tooltip('hide'))

View file

@ -35,4 +35,18 @@ $.fn.requiresInput = ->
$form.on 'change input', fieldSelector, requireInput $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)

View file

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

View file

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

View file

@ -1,44 +1,40 @@
class @EditBlob class @EditBlob
constructor: (assets_path, mode)-> constructor: (assets_path, ace_mode = null) ->
ace.config.set "modePath", assets_path + '/ace' ace.config.set "modePath", "#{assets_path}/ace"
ace.config.loadModule "ace/ext/searchbox" ace.config.loadModule "ace/ext/searchbox"
if mode @editor = ace.edit("editor")
ace_mode = mode @editor.focus()
editor = ace.edit("editor") @editor.getSession().setMode "ace/mode/#{ace_mode}" if ace_mode
editor.focus()
@editor = editor
if ace_mode
editor.getSession().setMode "ace/mode/" + ace_mode
# Before a form submission, move the content from the Ace editor into the # Before a form submission, move the content from the Ace editor into the
# submitted textarea # submitted textarea
$('form').submit -> $('form').submit =>
$("#file-content").val(editor.getValue()) $("#file-content").val(@editor.getValue())
editModePanes = $(".js-edit-mode-pane") @initModePanesAndLinks()
editModeLinks = $(".js-edit-mode a") new BlobLicenseSelector(@editor)
editModeLinks.click (event) -> new BlobGitignoreSelectors(editor: @editor)
initModePanesAndLinks: ->
@$editModePanes = $(".js-edit-mode-pane")
@$editModeLinks = $(".js-edit-mode a")
@$editModeLinks.click @editModeLinkClickHandler
editModeLinkClickHandler: (event) =>
event.preventDefault() event.preventDefault()
currentLink = $(this) currentLink = $(event.target)
paneId = currentLink.attr("href") paneId = currentLink.attr("href")
currentPane = editModePanes.filter(paneId) currentPane = @$editModePanes.filter(paneId)
editModeLinks.parent().removeClass "active hover" @$editModeLinks.parent().removeClass "active hover"
currentLink.parent().addClass "active hover" currentLink.parent().addClass "active hover"
editModePanes.hide() @$editModePanes.hide()
if paneId is "#preview"
currentPane.fadeIn 200 currentPane.fadeIn 200
if paneId is "#preview"
$.post currentLink.data("preview-url"), $.post currentLink.data("preview-url"),
content: editor.getValue() content: @editor.getValue()
, (response) -> , (response) ->
currentPane.empty().append response currentPane.empty().append response
currentPane.syntaxHighlight() currentPane.syntaxHighlight()
return
else else
currentPane.fadeIn 200 @editor.focus()
editor.focus()
return
editor: ->
return @editor

View file

@ -1,20 +0,0 @@
class @NewBlob
constructor: (assets_path, mode)->
ace.config.set "modePath", assets_path + '/ace'
ace.config.loadModule "ace/ext/searchbox"
if mode
ace_mode = mode
editor = ace.edit("editor")
editor.focus()
@editor = editor
if ace_mode
editor.getSession().setMode "ace/mode/" + ace_mode
# Before a form submission, move the content from the Ace editor into the
# submitted textarea
$('form').submit ->
$("#file-content").val(editor.getValue())
editor: ->
return @editor

View file

@ -0,0 +1,37 @@
class @Breakpoints
instance = null;
class BreakpointInstance
BREAKPOINTS = ["xs", "sm", "md", "lg"]
constructor: ->
@setup()
setup: ->
allDeviceSelector = BREAKPOINTS.map (breakpoint) ->
".device-#{breakpoint}"
return if $(allDeviceSelector.join(",")).length
# Create all the elements
els = $.map BREAKPOINTS, (breakpoint) ->
"<div class='device-#{breakpoint} visible-#{breakpoint}'></div>"
$("body").append els.join('')
visibleDevice: ->
allDeviceSelector = BREAKPOINTS.map (breakpoint) ->
".device-#{breakpoint}"
$(allDeviceSelector.join(",")).filter(":visible")
getBreakpointSize: ->
$visibleDevice = @visibleDevice
# the page refreshed via turbolinks
if not $visibleDevice().length
@setup()
$visibleDevice = @visibleDevice()
return $visibleDevice.attr("class").split("visible-")[1]
@get: ->
return instance ?= new BreakpointInstance
$ =>
@bp = Breakpoints.get()

View file

@ -1,34 +1,6 @@
# This is a manifest file that'll be compiled into application.js, which will include all the files
# listed below.
#
# Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
# or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
#
# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
# the compiled file.
#
# WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
# GO AFTER THE REQUIRES BELOW.
#
#= require pager #= require pager
#= require jquery_nested_form #= require jquery_nested_form
#= require_tree . #= require_tree .
#
$(document).on 'click', '.edit-runner-link', (event) ->
event.preventDefault()
descr = $(this).closest('.runner-description').first()
descr.addClass('hide')
form = descr.next('.runner-description-form')
descrInput = form.find('input.description')
originalValue = descrInput.val()
form.removeClass('hide')
form.find('.cancel').on 'click', (event) ->
event.preventDefault()
form.addClass('hide')
descrInput.val(originalValue)
descr.removeClass('hide')
$(document).on 'click', '.assign-all-runner', -> $(document).on 'click', '.assign-all-runner', ->
$(this).replaceWith('<i class="fa fa-refresh fa-spin"></i> Assign in progress..') $(this).replaceWith('<i class="fa fa-refresh fa-spin"></i> Assign in progress..')

View file

@ -1,9 +1,14 @@
class CiBuild class CiBuild
@interval: null @interval: null
@state: null
constructor: (build_url, build_status) -> constructor: (build_url, build_status, build_state) ->
clearInterval(CiBuild.interval) clearInterval(CiBuild.interval)
@state = build_state
@initScrollButtonAffix()
if build_status == "running" || build_status == "pending" if build_status == "running" || build_status == "pending"
# #
# Bind autoscroll button to follow build output # Bind autoscroll button to follow build output
@ -23,19 +28,37 @@ class CiBuild
# #
CiBuild.interval = setInterval => CiBuild.interval = setInterval =>
if window.location.href.split("#").first() is build_url if window.location.href.split("#").first() is build_url
last_state = @state
$.ajax $.ajax
url: build_url url: build_url + "/trace.json?state=" + encodeURIComponent(@state)
dataType: "json" dataType: "json"
success: (build) => success: (log) =>
if build.status == "running" return unless last_state is @state
$('#build-trace code').html build.trace_html
if log.state and log.status is "running"
@state = log.state
if log.append
$('.fa-refresh').before log.html
else
$('#build-trace code').html log.html
$('#build-trace code').append '<i class="fa fa-refresh fa-spin"/>' $('#build-trace code').append '<i class="fa fa-refresh fa-spin"/>'
@checkAutoscroll() @checkAutoscroll()
else if build.status != build_status else if log.status isnt build_status
Turbolinks.visit build_url Turbolinks.visit build_url
, 4000 , 4000
checkAutoscroll: -> checkAutoscroll: ->
$("html,body").scrollTop $("#build-trace").height() if "enabled" is $("#autoscroll-button").data("state") $("html,body").scrollTop $("#build-trace").height() if "enabled" is $("#autoscroll-button").data("state")
initScrollButtonAffix: ->
$buildScroll = $('#js-build-scroll')
$body = $('body')
$buildTrace = $('#build-trace')
$buildScroll.affix(
offset:
bottom: ->
$body.outerHeight() - ($buildTrace.outerHeight() + $buildTrace.offset().top)
)
@CiBuild = CiBuild @CiBuild = CiBuild

View file

@ -1,7 +1,7 @@
class @CommitsList class @CommitsList
@timer = null @timer = null
@init: (ref, limit) -> @init: (limit) ->
$("body").on "click", ".day-commits-table li.commit", (event) -> $("body").on "click", ".day-commits-table li.commit", (event) ->
if event.target.nodeName != "A" if event.target.nodeName != "A"
location.href = $(this).attr("url") location.href = $(this).attr("url")

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()
)

View file

@ -1,31 +0,0 @@
@Dashboard =
init: ->
$(".projects-list-filter").off('keyup')
this.initSearch()
initSearch: ->
@timer = null
$(".projects-list-filter").on('keyup', ->
clearTimeout(@timer)
@timer = setTimeout(Dashboard.filterResults, 500)
)
filterResults: =>
$('.projects-list-holder').fadeTo(250, 0.5)
form = null
form = $("form#project-filter-form")
search = $(".projects-list-filter").val()
project_filter_url = form.attr('action') + '?' + form.serialize()
$.ajax
type: "GET"
url: form.attr('action')
data: form.serialize()
complete: ->
$('.projects-list-holder').fadeTo(250, 1)
success: (data) ->
$('.projects-list-holder').replaceWith(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: project_filter_url}, document.title, project_filter_url
dataType: "json"

View file

@ -14,41 +14,41 @@ class Dispatcher
path = page.split(':') path = page.split(':')
shortcut_handler = null shortcut_handler = null
switch page switch page
when 'explore:projects:index', 'explore:projects:starred', 'explore:projects:trending'
Dashboard.init()
when 'projects:issues:index' when 'projects:issues:index'
Issues.init() Issues.init()
Issuable.init()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
when 'projects:issues:show' when 'projects:issues:show'
new Issue() new Issue()
shortcut_handler = new ShortcutsIssuable() shortcut_handler = new ShortcutsIssuable()
new ZenMode() new ZenMode()
when 'projects:milestones:show' when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
new Milestone() new Milestone()
when 'dashboard:todos:index'
new Todos()
when 'projects:milestones:new', 'projects:milestones:edit' when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode() new ZenMode()
new DropzoneInput($('.milestone-form')) new GLForm($('.milestone-form'))
when 'groups:milestones:new' when 'groups:milestones:new'
new ZenMode() new ZenMode()
when 'projects:compare:show' when 'projects:compare:show'
new Diff() new Diff()
when 'projects:issues:new','projects:issues:edit' when 'projects:issues:new','projects:issues:edit'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new DropzoneInput($('.issue-form')) new GLForm($('.issue-form'))
new IssuableForm($('.issue-form')) new IssuableForm($('.issue-form'))
when 'projects:merge_requests:new', 'projects:merge_requests:edit' when 'projects:merge_requests:new', 'projects:merge_requests:edit'
new Diff() new Diff()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new DropzoneInput($('.merge-request-form')) new GLForm($('.merge-request-form'))
new IssuableForm($('.merge-request-form')) new IssuableForm($('.merge-request-form'))
when 'projects:tags:new' when 'projects:tags:new'
new ZenMode() new ZenMode()
new DropzoneInput($('.tag-form')) new GLForm($('.tag-form'))
when 'projects:releases:edit' when 'projects:releases:edit'
new ZenMode() new ZenMode()
new DropzoneInput($('.release-form')) new GLForm($('.release-form'))
when 'projects:merge_requests:show' when 'projects:merge_requests:show'
new Diff() new Diff()
shortcut_handler = new ShortcutsIssuable(true) shortcut_handler = new ShortcutsIssuable(true)
@ -58,9 +58,7 @@ class Dispatcher
new ZenMode() new ZenMode()
when 'projects:merge_requests:index' when 'projects:merge_requests:index'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
MergeRequests.init() Issuable.init()
when 'dashboard:show', 'root:show'
Dashboard.init()
when 'dashboard:activity' when 'dashboard:activity'
new Activities() new Activities()
when 'dashboard:projects:starred' when 'dashboard:projects:starred'
@ -76,8 +74,11 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
when 'projects:show' when 'projects:show'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
when 'groups:show'
new TreeView() if $('#tree-slider').length
when 'groups:activity'
new Activities() new Activities()
when 'groups:show'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
when 'groups:group_members:index' when 'groups:group_members:index'
new GroupMembers() new GroupMembers()
@ -92,7 +93,7 @@ class Dispatcher
new TreeView() new TreeView()
when 'projects:find_file:show' when 'projects:find_file:show'
shortcut_handler = true shortcut_handler = true
when 'projects:blob:show' when 'projects:blob:show', 'projects:blame:show'
new LineHighlighter() new LineHighlighter()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
when 'projects:labels:new', 'projects:labels:edit' when 'projects:labels:new', 'projects:labels:edit'
@ -105,9 +106,10 @@ class Dispatcher
new ProjectFork() new ProjectFork()
when 'projects:artifacts:browse' when 'projects:artifacts:browse'
new BuildArtifacts() new BuildArtifacts()
when 'users:show' when 'projects:group_links:index'
new User() new GroupsSelect()
new Activities() when 'search:show'
new Search()
switch path.first() switch path.first()
when 'admin' when 'admin'
@ -138,7 +140,7 @@ class Dispatcher
new Wikis() new Wikis()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new ZenMode() new ZenMode()
new DropzoneInput($('.wiki-form')) new GLForm($('.wiki-form'))
when 'snippets' when 'snippets'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new ZenMode() if path[2] == 'show' new ZenMode() if path[2] == 'show'
@ -147,15 +149,11 @@ class Dispatcher
when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches' when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
# If we haven't installed a custom shortcut handler, install the default one # If we haven't installed a custom shortcut handler, install the default one
if not shortcut_handler if not shortcut_handler
new Shortcuts() new Shortcuts()
initSearch: -> 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

View file

@ -15,11 +15,13 @@ class @DropzoneInput
project_uploads_path = window.project_uploads_path or null project_uploads_path = window.project_uploads_path or null
max_file_size = gon.max_file_size or 10 max_file_size = gon.max_file_size or 10
form_textarea = $(form).find("textarea.markdown-area") form_textarea = $(form).find(".js-gfm-input")
form_textarea.wrap "<div class=\"div-dropzone\"></div>" form_textarea.wrap "<div class=\"div-dropzone\"></div>"
form_textarea.on 'paste', (event) => form_textarea.on 'paste', (event) =>
handlePaste(event) handlePaste(event)
$mdArea = $(form_textarea).closest('.md-area')
$(form).setupMarkdownPreview() $(form).setupMarkdownPreview()
form_dropzone = $(form).find('.div-dropzone') form_dropzone = $(form).find('.div-dropzone')
@ -49,17 +51,17 @@ class @DropzoneInput
$(".div-dropzone-alert").alert "close" $(".div-dropzone-alert").alert "close"
dragover: -> dragover: ->
form_textarea.addClass "div-dropzone-focus" $mdArea.addClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0.7 form.find(".div-dropzone-hover").css "opacity", 0.7
return return
dragleave: -> dragleave: ->
form_textarea.removeClass "div-dropzone-focus" $mdArea.removeClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0 form.find(".div-dropzone-hover").css "opacity", 0
return return
drop: -> drop: ->
form_textarea.removeClass "div-dropzone-focus" $mdArea.removeClass 'is-dropzone-hover'
form.find(".div-dropzone-hover").css "opacity", 0 form.find(".div-dropzone-hover").css "opacity", 0
form_textarea.focus() form_textarea.focus()
return return

View file

@ -0,0 +1,81 @@
class @DueDateSelect
constructor: ->
$loading = $('.js-issuable-update .due_date')
.find('.block-loading')
.hide()
$('.js-due-date-select').each (i, dropdown) ->
$dropdown = $(dropdown)
$dropdownParent = $dropdown.closest('.dropdown')
$datePicker = $dropdownParent.find('.js-due-date-calendar')
$block = $dropdown.closest('.block')
$selectbox = $dropdown.closest('.selectbox')
$value = $block.find('.value')
$valueContent = $block.find('.value-content')
$sidebarValue = $('.js-due-date-sidebar-value', $block)
fieldName = $dropdown.data('field-name')
abilityName = $dropdown.data('ability-name')
issueUpdateURL = $dropdown.data('issue-update')
$dropdown.glDropdown(
hidden: ->
$selectbox.hide()
$value.removeAttr('style')
)
addDueDate = (isDropdown) ->
# Create the post date
value = $("input[name='#{fieldName}']").val()
if value isnt ''
date = new Date value.replace(new RegExp('-', 'g'), ',')
mediumDate = $.datepicker.formatDate 'M d, yy', date
else
mediumDate = 'None'
data = {}
data[abilityName] = {}
data[abilityName].due_date = value
$.ajax(
type: 'PUT'
url: issueUpdateURL
data: data
beforeSend: ->
$loading.fadeIn()
if isDropdown
$dropdown.trigger('loading.gl.dropdown')
$selectbox.hide()
$value.removeAttr('style')
$valueContent.html(mediumDate)
$sidebarValue.html(mediumDate)
if value isnt ''
$('.js-remove-due-date-holder').removeClass 'hidden'
else
$('.js-remove-due-date-holder').addClass 'hidden'
).done (data) ->
if isDropdown
$dropdown.trigger('loaded.gl.dropdown')
$dropdown.dropdown('toggle')
$loading.fadeOut()
$block.on 'click', '.js-remove-due-date', (e) ->
e.preventDefault()
$("input[name='#{fieldName}']").val ''
addDueDate(false)
$datePicker.datepicker(
dateFormat: 'yy-mm-dd',
defaultDate: $("input[name='#{fieldName}']").val()
altField: "input[name='#{fieldName}']"
onSelect: ->
addDueDate(true)
)
$(document)
.off 'click', '.ui-datepicker-header a'
.on 'click', '.ui-datepicker-header a', (e) ->
e.stopImmediatePropagation()

View file

@ -2,6 +2,8 @@
window.GitLab ?= {} window.GitLab ?= {}
GitLab.GfmAutoComplete = GitLab.GfmAutoComplete =
dataLoading: false
dataSource: '' dataSource: ''
# Emoji # Emoji
@ -16,18 +18,46 @@ GitLab.GfmAutoComplete =
Issues: Issues:
template: '<li><small>${id}</small> ${title}</li>' template: '<li><small>${id}</small> ${title}</li>'
# Add GFM auto-completion to all input fields, that accept GFM input. # Milestones
setup: -> Milestones:
input = $('.js-gfm-input') template: '<li>${title}</li>'
# Add GFM auto-completion to all input fields, that accept GFM input.
setup: (wrap) ->
@input = $('.js-gfm-input')
# destroy previous instances
@destroyAtWho()
# set up instances
@setupAtWho()
if @dataSource
if !@dataLoading
@dataLoading = true
# We should wait until initializations are done
# and only trigger the last .setup since
# The previous .dataSource belongs to the previous issuable
# and the last one will have the **proper** .dataSource property
# TODO: Make this a singleton and turn off events when moving to another page
setTimeout( =>
fetch = @fetchData(@dataSource)
fetch.done (data) =>
@dataLoading = false
@loadData(data)
, 1000)
setupAtWho: ->
# Emoji # Emoji
input.atwho @input.atwho
at: ':' at: ':'
displayTpl: @Emoji.template displayTpl: @Emoji.template
insertTpl: ':${name}:' insertTpl: ':${name}:'
# Team Members # Team Members
input.atwho @input.atwho
at: '@' at: '@'
displayTpl: @Members.template displayTpl: @Members.template
insertTpl: '${atwho-at}${username}' insertTpl: '${atwho-at}${username}'
@ -42,7 +72,7 @@ GitLab.GfmAutoComplete =
title: sanitize(title) title: sanitize(title)
search: sanitize("#{m.username} #{m.name}") search: sanitize("#{m.username} #{m.name}")
input.atwho @input.atwho
at: '#' at: '#'
alias: 'issues' alias: 'issues'
searchKey: 'search' searchKey: 'search'
@ -55,7 +85,20 @@ GitLab.GfmAutoComplete =
title: sanitize(i.title) title: sanitize(i.title)
search: "#{i.iid} #{i.title}" search: "#{i.iid} #{i.title}"
input.atwho @input.atwho
at: '%'
alias: 'milestones'
searchKey: 'search'
displayTpl: @Milestones.template
insertTpl: '${atwho-at}"${title}"'
callbacks:
beforeSave: (milestones) ->
$.map milestones, (m) ->
id: m.iid
title: sanitize(m.title)
search: "#{m.title}"
@input.atwho
at: '!' at: '!'
alias: 'mergerequests' alias: 'mergerequests'
searchKey: 'search' searchKey: 'search'
@ -68,13 +111,20 @@ GitLab.GfmAutoComplete =
title: sanitize(m.title) title: sanitize(m.title)
search: "#{m.iid} #{m.title}" search: "#{m.iid} #{m.title}"
if @dataSource destroyAtWho: ->
$.getJSON(@dataSource).done (data) -> @input.atwho('destroy')
fetchData: (dataSource) ->
$.getJSON(dataSource)
loadData: (data) ->
# load members # load members
input.atwho 'load', '@', data.members @input.atwho 'load', '@', data.members
# load issues # load issues
input.atwho 'load', 'issues', data.issues @input.atwho 'load', 'issues', data.issues
# load milestones
@input.atwho 'load', 'milestones', data.milestones
# load merge requests # load merge requests
input.atwho 'load', 'mergerequests', data.mergerequests @input.atwho 'load', 'mergerequests', data.mergerequests
# load emojis # load emojis
input.atwho 'load', ':', data.emojis @input.atwho 'load', ':', data.emojis

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))

View file

@ -0,0 +1,550 @@
class GitLabDropdownFilter
BLUR_KEYCODES = [27, 40]
ARROW_KEY_CODES = [38, 40]
HAS_VALUE_CLASS = "has-value"
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) =>
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
return false
clearTimeout timeout
timeout = setTimeout =>
blur_field = @shouldBlur keyCode
search_text = @input.val()
if blur_field and @filterInputBlur
@input.blur()
if @options.remote
@options.query search_text, (data) =>
@options.callback(data)
else
@filter search_text
, 250
shouldBlur: (keyCode) ->
return BLUR_KEYCODES.indexOf(keyCode) >= 0
filter: (search_text) ->
data = @options.data()
if data?
results = data
if search_text isnt ''
# When data is an array of objects therefore [object Array] e.g.
# [
# { prop: 'foo' },
# { prop: 'baz' }
# ]
if _.isArray(data)
results = fuzzaldrinPlus.filter(data, search_text,
key: @options.keys
)
else
# If data is grouped therefore an [object Object]. e.g.
# {
# groupName1: [
# { prop: 'foo' },
# { prop: 'baz' }
# ],
# groupName2: [
# { prop: 'abc' },
# { prop: 'def' }
# ]
# }
if gl.utils.isObject data
results = {}
for key, group of data
tmp = fuzzaldrinPlus.filter(group, search_text,
key: @options.keys
)
if tmp.length
results[key] = tmp.map (item) -> item
@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) ->
execute: ->
if typeof @dataEndpoint is "string"
@fetchData()
else if typeof @dataEndpoint is "function"
if @options.beforeSend
@options.beforeSend()
# Fetch the data by calling the data funcfion
@dataEndpoint "", (data) =>
if @options.success
@options.success(data)
if @options.beforeSend
@options.beforeSend()
# Fetch the data through ajax if the data is a string
fetchData: ->
$.ajax(
url: @dataEndpoint,
dataType: @options.dataType,
beforeSend: =>
if @options.beforeSend
@options.beforeSend()
success: (data) =>
if @options.success
@options.success(data)
)
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 = @
selector = $(@el).data "target"
@dropdown = if selector? then $(selector) else $(@el).parent()
# Set Defaults
{
# If no input is passed create a default one
@filterInput = @getElement(FILTER_INPUT)
@highlight = false
@filterInputBlur = 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
# If we provided data
# data could be an array of objects or a group of arrays
if _.isObject(@options.data) and not _.isFunction(@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
if @options.filterable
@filterInput.trigger 'keyup'
}
# Init filterable
if @options.filterable
@filter = new GitLabDropdownFilter @filterInput,
filterInputBlur: @filterInputBlur
remote: @options.filterRemote
query: @options.data
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
# Event listeners
@dropdown.on "shown.bs.dropdown", @opened
@dropdown.on "hidden.bs.dropdown", @hidden
@dropdown.on "click", ".dropdown-menu, .dropdown-menu-close", @shouldPropagate
@dropdown.on 'keyup', (e) =>
if e.which is 27 # Escape key
$('.dropdown-menu-close', @dropdown).trigger 'click'
if @dropdown.find(".dropdown-toggle-page").length
@dropdown.find(".dropdown-toggle-page, .dropdown-menu-back").on "click", (e) =>
e.preventDefault()
e.stopPropagation()
@togglePage()
if @options.selectable
selector = ".dropdown-content a"
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content a"
@dropdown.on "click", selector, (e) ->
$el = $(@)
selected = self.rowClicked $el
if 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
togglePage: ->
menu = $('.dropdown-menu', @dropdown)
if menu.hasClass(PAGE_TWO_CLASS)
if @remote
@remote.execute()
menu.toggleClass PAGE_TWO_CLASS
# Focus first visible input on active page
@dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus()
parseData: (data) ->
@renderedData = data
if @options.filterable and data.length is 0
# render no matching results
html = [@noResults()]
else
# Handle array groups
if gl.utils.isObject data
html = []
for name, groupData of data
# Add header for each group
html.push(@renderItem(header: name, name))
@renderData(groupData, name)
.map (item) ->
html.push item
else
# Render each row
html = @renderData(data)
# Render the full menu
full_html = @renderMenu(html.join(""))
@appendMenu(full_html)
renderData: (data, group = false) ->
data.map (obj, index) =>
return @renderItem(obj, group, index)
shouldPropagate: (e) =>
if @options.multiSelect
$target = $(e.target)
if not $target.hasClass('dropdown-menu-close') and not $target.hasClass('dropdown-menu-close-icon') and not $target.data('is-link')
e.stopPropagation()
return false
else
return true
opened: =>
@addArrowKeyEvent()
contentHtml = $('.dropdown-content', @dropdown).html()
if @remote && contentHtml is ""
@remote.execute()
if @options.filterable
@filterInput.focus()
@dropdown.trigger('shown.gl.dropdown')
hidden: (e) =>
@removeArrayKeyEvent()
if @options.filterable
@dropdown
.find(".dropdown-input-field")
.blur()
.val("")
.trigger("keyup")
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) ->
menu_html = ""
if @options.renderMenu
menu_html = @options.renderMenu(html)
else
menu_html = "<ul>#{html}</ul>"
return menu_html
# Append the menu into the dropdown
appendMenu: (html) ->
selector = '.dropdown-content'
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one .dropdown-content"
$(selector, @dropdown).html html
# Render the row
renderItem: (data, group = false, index = false) ->
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
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"
if @highlight
text = @highlightTextMatches(text, @filterInput.val())
if group
groupAttrs = "data-group='#{group}' data-index='#{index}'"
else
groupAttrs = ''
html = "<li>
<a href='#{url}' #{groupAttrs} 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 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
if @renderedData
groupName = el.data('group')
if groupName
selectedIndex = el.data('index')
selectedObject = @renderedData[groupName][selectedIndex]
else
selectedIndex = el.closest('li').index()
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()
# Toggle the dropdown label
if @options.toggleLabel
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel
else
selectedObject
else
if not @options.multiSelect or el.hasClass('dropdown-clear-active')
@dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
@dropdown.parent().find("input[name='#{fieldName}']").remove()
if !value?
field.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 and fieldName
# Create hidden input for form
input = "<input type='hidden' name='#{fieldName}' value='#{value}' />"
if @options.inputId?
input = $(input)
.attr('id', @options.inputId)
@dropdown.before input
else
field.val value
return selectedObject
selectRowAtIndex: (index) ->
selector = ".dropdown-content li:not(.divider):eq(#{index}) a"
if @dropdown.find(".dropdown-toggle-page").length
selector = ".dropdown-page-one #{selector}"
# 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 if currentIndex < 0 then 0 else 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 ->
if (!$.data @, 'glDropdown')
$.data(@, 'glDropdown', new GitLabDropdown @, opts)

View file

@ -0,0 +1,51 @@
class @GLForm
constructor: (@form) ->
@textarea = @form.find('textarea.js-gfm-input')
# Before we start, we should clean up any previous data for this form
@destroy()
# Setup the form
@setupForm()
@form.data 'gl-form', @
destroy: ->
# Clean form listeners
@clearEventListeners()
@form.data 'gl-form', null
setupForm: ->
isNewForm = @form.is(':not(.gfm-form)')
@form.removeClass 'js-new-note-form'
if isNewForm
@form.find('.div-dropzone').remove()
@form.addClass('gfm-form')
disableButtonIfEmptyField @form.find('.js-note-text'), @form.find('.js-comment-button')
# remove notify commit author checkbox for non-commit notes
GitLab.GfmAutoComplete.setup()
new DropzoneInput(@form)
autosize(@textarea)
# form and textarea event listeners
@addEventListeners()
# hide discard button
@form.find('.js-note-discard').hide()
@form.show()
clearEventListeners: ->
@textarea.off 'focus'
@textarea.off 'blur'
addEventListeners: ->
@textarea.on 'focus', ->
$(@).closest('.md-area').addClass 'is-focused'
@textarea.on 'blur', ->
$(@).closest('.md-area').removeClass 'is-focused'

View file

@ -4,18 +4,33 @@ class @ImporterStatus
this.setAutoUpdate() this.setAutoUpdate()
initStatusPage: -> initStatusPage: ->
$(".js-add-to-import").click (event) => $('.js-add-to-import')
.off 'click'
.on 'click', (e) =>
new_namespace = null new_namespace = null
tr = $(event.currentTarget).closest("tr") $btn = $(e.currentTarget)
id = tr.attr("id").replace("repo_", "") $tr = $btn.closest('tr')
if tr.find(".import-target input").length > 0 id = $tr.attr('id').replace('repo_', '')
new_namespace = tr.find(".import-target input").prop("value") if $tr.find('.import-target input').length > 0
tr.find(".import-target").empty().append(new_namespace + "/" + tr.find(".import-target").data("project_name")) new_namespace = $tr.find('.import-target input').prop('value')
$tr.find('.import-target').empty().append("#{new_namespace} / #{$tr.find('.import-target').data('project_name')}")
$btn
.disable()
.addClass 'is-loading'
$.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script' $.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script'
$(".js-import-all").click (event) => $('.js-import-all')
$(".js-add-to-import").each -> .off 'click'
$(this).click() .on 'click', (e) ->
$btn = $(@)
$btn
.disable()
.addClass 'is-loading'
$('.js-add-to-import').each ->
$(this).trigger('click')
setAutoUpdate: -> setAutoUpdate: ->
setInterval (=> setInterval (=>

View file

@ -0,0 +1,84 @@
@Issuable =
init: ->
Issuable.initTemplates()
Issuable.initSearch()
initTemplates: ->
Issuable.labelRow = _.template(
'<% _.each(labels, function(label){ %>
<span class="label-row">
<a href="#"><span class="label color-label has-tooltip" style="background-color: <%= label.color %>; color: <%= label.text_color %>" title="<%= _.escape(label.description) %>" data-container="body"><%= _.escape(label.title) %></span></a>
</span>
<% }); %>'
)
initSearch: ->
@timer = null
$('#issue_search')
.off 'keyup'
.on 'keyup', ->
clearTimeout(@timer)
@timer = setTimeout( ->
Issuable.filterResults $('#issue_search_form')
, 500)
toggleLabelFilters: ->
$filteredLabels = $('.filtered-labels')
if $filteredLabels.find('.label-row').length > 0
$filteredLabels.removeClass('hidden')
else
$filteredLabels.addClass('hidden')
filterResults: (form) =>
formData = form.serialize()
$('.issues-holder, .merge-requests-holder').css('opacity', '0.5')
formAction = form.attr('action')
issuesUrl = formAction
issuesUrl += ("#{if formAction.indexOf('?') < 0 then '?' else '&'}")
issuesUrl += formData
$.ajax
type: 'GET'
url: formAction
data: formData
complete: ->
$('.issues-holder, .merge-requests-holder').css('opacity', '1.0')
success: (data) ->
$('.issues-holder, .merge-requests-holder').html(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: issuesUrl}, document.title, issuesUrl
Issuable.reload()
Issuable.updateStateFilters()
$filteredLabels = $('.filtered-labels')
if typeof Issuable.labelRow is 'function'
$filteredLabels.html(Issuable.labelRow(data))
Issuable.toggleLabelFilters()
dataType: "json"
reload: ->
if Issues.created
Issues.initChecks()
$('#filter_issue_search').val($('#issue_search').val())
updateStateFilters: ->
stateFilters = $('.issues-state-filters')
newParams = {}
paramKeys = ['author_id', 'milestone_title', 'assignee_id', 'issue_search']
for paramKey in paramKeys
newParams[paramKey] = gl.utils.getParameterValues(paramKey)[0] or ''
if stateFilters.length
stateFilters.find('a').each ->
initialUrl = gl.utils.removeParamQueryString($(this).attr('href'), 'label_name[]')
labelNameValues = gl.utils.getParameterValues('label_name[]')
if labelNameValues
labelNameQueryString = ("label_name[]=#{value}" for value in labelNameValues).join('&')
newUrl = "#{gl.utils.mergeUrlParams(newParams, initialUrl)}&#{labelNameQueryString}"
else
newUrl = gl.utils.mergeUrlParams(newParams, initialUrl)
$(this).attr 'href', newUrl

View file

@ -1,8 +1,7 @@
#= require jquery.waitforimages
class @IssuableContext class @IssuableContext
constructor: -> constructor: (currentUser) ->
new UsersSelect() @initParticipants()
new UsersSelect(currentUser)
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true}) $('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
$(".issuable-sidebar .inline-update").on "change", "select", -> $(".issuable-sidebar .inline-update").on "change", "select", ->
@ -10,10 +9,52 @@ class @IssuableContext
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", -> $(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit() $(this).submit()
$(document).on "click",".edit-link", (e) -> $(document)
block = $(@).parents('.block') .off 'click', '.issuable-sidebar .dropdown-content a'
block.find('.selectbox').show() .on 'click', '.issuable-sidebar .dropdown-content a', (e) ->
block.find('.value').hide() e.preventDefault()
block.find('.js-select2').select2("open")
$(document)
.off 'click', '.edit-link'
.on 'click', '.edit-link', (e) ->
e.preventDefault()
$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() $(".right-sidebar").niceScroll()
initParticipants: ->
_this = @
$(document).on "click", ".js-participants-more", @toggleHiddenParticipants
$(".js-participants-author").each (i) ->
if i >= _this.PARTICIPANTS_ROW_COUNT
$(@)
.addClass "js-participants-hidden"
.hide()
toggleHiddenParticipants: (e) ->
e.preventDefault()
currentText = $(this).text().trim()
lessText = $(this).data("less-text")
originalText = $(this).data("original-text")
if currentText is originalText
$(this).text(lessText)
else
$(this).text(originalText)
$(".js-participants-hidden").toggle()

View file

@ -1,4 +1,7 @@
class @IssuableForm class @IssuableForm
issueMoveConfirmMsg: 'Are you sure you want to move this issue to another project?'
wipRegex: /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i
constructor: (@form) -> constructor: (@form) ->
GitLab.GfmAutoComplete.setup() GitLab.GfmAutoComplete.setup()
new UsersSelect() new UsersSelect()
@ -6,14 +9,17 @@ class @IssuableForm
@titleField = @form.find("input[name*='[title]']") @titleField = @form.find("input[name*='[title]']")
@descriptionField = @form.find("textarea[name*='[description]']") @descriptionField = @form.find("textarea[name*='[description]']")
@issueMoveField = @form.find("#move_to_project_id")
return unless @titleField.length && @descriptionField.length return unless @titleField.length && @descriptionField.length
@initAutosave() @initAutosave()
@form.on "submit", @resetAutosave @form.on "submit", @handleSubmit
@form.on "click", ".btn-cancel", @resetAutosave @form.on "click", ".btn-cancel", @resetAutosave
@initWip()
initAutosave: -> initAutosave: ->
new Autosave @titleField, [ new Autosave @titleField, [
document.location.pathname, document.location.pathname,
@ -27,6 +33,50 @@ class @IssuableForm
"description" "description"
] ]
handleSubmit: =>
if (parseInt(@issueMoveField?.val()) ? 0) > 0
return false unless confirm(@issueMoveConfirmMsg)
@resetAutosave()
resetAutosave: => resetAutosave: =>
@titleField.data("autosave").reset() @titleField.data("autosave").reset()
@descriptionField.data("autosave").reset() @descriptionField.data("autosave").reset()
initWip: ->
@$wipExplanation = @form.find(".js-wip-explanation")
@$noWipExplanation = @form.find(".js-no-wip-explanation")
return unless @$wipExplanation.length and @$noWipExplanation.length
@form.on "click", ".js-toggle-wip", @toggleWip
@titleField.on "keyup blur", @renderWipExplanation
@renderWipExplanation()
workInProgress: ->
@wipRegex.test @titleField.val()
renderWipExplanation: =>
if @workInProgress()
@$wipExplanation.show()
@$noWipExplanation.hide()
else
@$wipExplanation.hide()
@$noWipExplanation.show()
toggleWip: (event) =>
event.preventDefault()
if @workInProgress()
@removeWip()
else
@addWip()
@renderWipExplanation()
removeWip: ->
@titleField.val @titleField.val().replace(@wipRegex, "")
addWip: ->
@titleField.val "WIP: #{@titleField.val()}"

View file

@ -6,24 +6,13 @@ class @Issue
constructor: -> constructor: ->
# Prevent duplicate event bindings # Prevent duplicate event bindings
@disableTaskList() @disableTaskList()
@fixAffixScroll()
if $('a.btn-close').length if $('a.btn-close').length
@initTaskList() @initTaskList()
@initIssueBtnEventListeners() @initIssueBtnEventListeners()
fixAffixScroll: -> @initMergeRequests()
fixAffix = -> @initRelatedBranches()
$discussion = $('.issuable-discussion') @initCanCreateBranch()
$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: -> initTaskList: ->
$('.detail-page-description .js-task-list-container').taskList('enable') $('.detail-page-description .js-task-list-container').taskList('enable')
@ -49,7 +38,7 @@ class @Issue
issueStatus = if isClose then 'close' else 'open' issueStatus = if isClose then 'close' else 'open'
new Flash(issueFailMessage, 'alert') new Flash(issueFailMessage, 'alert')
success: (data, textStatus, jqXHR) -> success: (data, textStatus, jqXHR) ->
if data.saved if 'id' of data
$(document).trigger('issuable:change'); $(document).trigger('issuable:change');
if isClose if isClose
$('a.btn-close').addClass('hidden') $('a.btn-close').addClass('hidden')
@ -84,3 +73,45 @@ class @Issue
type: 'PATCH' type: 'PATCH'
url: $('form.js-issuable-update').attr('action') url: $('form.js-issuable-update').attr('action')
data: patchData data: patchData
initMergeRequests: ->
$container = $('#merge-requests')
$.getJSON($container.data('url'))
.error ->
new Flash('Failed to load referenced merge requests', 'alert')
.success (data) ->
if 'html' of data
$container.html(data.html)
initRelatedBranches: ->
$container = $('#related-branches')
$.getJSON($container.data('url'))
.error ->
new Flash('Failed to load related branches', 'alert')
.success (data) ->
if 'html' of data
$container.html(data.html)
initCanCreateBranch: ->
$container = $('div#new-branch')
# If the user doesn't have the required permissions the container isn't
# rendered at all.
return unless $container
$.getJSON($container.data('path'))
.error ->
$container.find('.checking').hide()
$container.find('.unavailable').show()
new Flash('Failed to check if a new branch can be created.', 'alert')
.success (data) ->
if data.can_create_branch
$container.find('.checking').hide()
$container.find('.available').show()
$container.find('a').attr('disabled', false)
else
$container.find('.checking').hide()
$container.find('.unavailable').show()

View file

@ -0,0 +1,11 @@
class @IssueStatusSelect
constructor: ->
$('.js-issue-status').each (i, el) ->
fieldName = $(el).data("field-name")
$(el).glDropdown(
selectable: true
fieldName: fieldName
id: (obj, el) ->
$(el).data("id")
)

View file

@ -1,7 +1,6 @@
@Issues = @Issues =
init: -> init: ->
Issues.initSearch() Issues.created = true
Issues.initSelects()
Issues.initChecks() Issues.initChecks()
$("body").on "ajax:success", ".close_issue, .reopen_issue", -> $("body").on "ajax:success", ".close_issue, .reopen_issue", ->
@ -16,19 +15,6 @@
else else
$(this).html totalIssues - 1 $(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: -> initChecks: ->
$(".check_all_issues").click -> $(".check_all_issues").click ->
$(".selected_issue").prop("checked", @checked) $(".selected_issue").prop("checked", @checked)
@ -36,32 +22,6 @@
$(".selected_issue").bind "change", Issues.checkChanged $(".selected_issue").bind "change", Issues.checkChanged
# Make sure we trigger ajax request only after user stop typing
initSearch: ->
@timer = null
$("#issue_search").keyup ->
clearTimeout(@timer)
@timer = setTimeout(Issues.filterResults, 500)
filterResults: =>
form = $("#issue_search_form")
search = $("#issue_search").val()
$('.issues-holder').css("opacity", '0.5')
issues_url = form.attr('action') + '?' + form.serialize()
$.ajax
type: "GET"
url: form.attr('action')
data: form.serialize()
complete: ->
$('.issues-holder').css("opacity", '1.0')
success: (data) ->
$('.issues-holder').html(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: issues_url}, document.title, issues_url
Issues.reload()
dataType: "json"
checkChanged: -> checkChanged: ->
checked_issues = $(".selected_issue:checked") checked_issues = $(".selected_issue:checked")
if checked_issues.length > 0 if checked_issues.length > 0

View file

@ -0,0 +1,301 @@
class @LabelsSelect
constructor: ->
$('.js-label-select').each (i, dropdown) ->
$dropdown = $(dropdown)
projectId = $dropdown.data('project-id')
labelUrl = $dropdown.data('labels')
issueUpdateURL = $dropdown.data('issueUpdate')
selectedLabel = $dropdown.data('selected')
if selectedLabel? and not $dropdown.hasClass 'js-multiselect'
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')
$form = $dropdown.closest('form')
$sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon span')
$value = $block.find('.value')
$newLabelError = $('.js-label-error')
$colorPreview = $('.js-dropdown-label-color-preview')
$newLabelCreateButton = $('.js-new-label-btn')
$newLabelError.hide()
$loading = $block.find('.block-loading').fadeOut()
issueURLSplit = issueUpdateURL.split('/') if issueUpdateURL?
if issueUpdateURL
labelHTMLTemplate = _.template(
'<% _.each(labels, function(label){ %>
<a href="<%= ["",issueURLSplit[1], issueURLSplit[2],""].join("/") %>issues?label_name[]=<%= _.escape(label.title) %>">
<span class="label has-tooltip color-label" title="<%= _.escape(label.description) %>" style="background-color: <%= label.color %>; color: <%= label.text_color %>;">
<%= _.escape(label.title) %>
</span>
</a>
<% }); %>'
)
labelNoneHTMLTemplate = _.template('<div class="light">None</div>')
if newLabelField.length
# Suggested colors in the dropdown to chose from pre-chosen colors
$('.suggest-colors-dropdown a').on "click", (e) ->
e.preventDefault()
e.stopPropagation()
newColorField
.val($(this).data('color'))
.trigger('change')
$colorPreview
.css 'background-color', $(this).data('color')
.parent()
.addClass 'is-active'
# 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()
$newLabelCreateButton.enable()
else
$newLabelCreateButton.disable()
saveLabel = ->
# Create new label with API
Api.newLabel projectId, {
name: newLabelField.val()
color: newColorField.val()
}, (label) ->
$newLabelCreateButton.enable()
if label.message?
$newLabelError
.text label.message
.show()
else
$('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
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()
saveLabel()
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)
$('.has-tooltip', $value).tooltip(container: 'body')
$value
.find('a')
.each((i) ->
setTimeout(=>
gl.animate.animate($(@), 'pulse')
,200 * i
)
)
$dropdown.glDropdown(
data: (term, callback) ->
$.ajax(
url: labelUrl
).done (data) ->
data = _.chain data
.groupBy (label) ->
label.title
.map (label) ->
color = _.map label, (dup) ->
dup.color
return {
id: label[0].id
title: label[0].title
color: color
duplicate: color.length > 1
}
.value()
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'
callback data
renderRow: (label) ->
removesAll = label.id is 0 or not label.id?
selectedClass = []
if $form.find("input[type='hidden']\
[name='#{$dropdown.data('fieldName')}']\
[value='#{this.id(label)}']").length
selectedClass.push 'is-active'
if $dropdown.hasClass('js-multiselect') and removesAll
selectedClass.push 'dropdown-clear-active'
if label.duplicate
spacing = 100 / label.color.length
# Reduce the colors to 4
label.color = label.color.filter (color, i) ->
i < 4
color = _.map(label.color, (color, i) ->
percentFirst = Math.floor(spacing * i)
percentSecond = Math.floor(spacing * (i + 1))
"#{color} #{percentFirst}%,#{color} #{percentSecond}% "
).join(',')
color = "linear-gradient(#{color})"
else
if label.color?
color = label.color[0]
if color
colorEl = "<span class='dropdown-label-box' style='background: #{color}'></span>"
else
colorEl = ''
"<li>
<a href='#' class='#{selectedClass.join(' ')}'>
#{colorEl}
#{_.escape(label.title)}
</a>
</li>"
filterable: true
search:
fields: ['title']
selectable: true
toggleLabel: (selected, el) ->
selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active')
if selected and selected.title?
if selected_labels.length > 1
"#{selected.title} +#{selected_labels.length - 1} more"
else
selected.title
else if not selected and selected_labels.length isnt 0
if selected_labels.length > 1
"#{$(selected_labels[0]).text()} +#{selected_labels.length - 1} more"
else if selected_labels.length is 1
$(selected_labels).text()
else
defaultLabel
fieldName: $dropdown.data('field-name')
id: (label) ->
if $dropdown.hasClass("js-filter-submit") and not label.isAny?
_.escape label.title
else
label.id
hidden: ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is 'projects:merge_requests:index'
$selectbox.hide()
# display:block overrides the hide-collapse rule
$value.removeAttr('style')
if $dropdown.hasClass 'js-multiselect'
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
selectedLabels = $dropdown
.closest('form')
.find("input:hidden[name='#{$dropdown.data('fieldName')}']")
Issuable.filterResults $dropdown.closest('form')
else if $dropdown.hasClass('js-filter-submit')
$dropdown.closest('form').submit()
else
saveLabelData()
multiSelect: $dropdown.hasClass 'js-multiselect'
clicked: (label) ->
page = $('body').data 'page'
isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is 'projects:merge_requests:index'
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
if not $dropdown.hasClass 'js-multiselect'
selectedLabel = label.title
Issuable.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit()
else
if $dropdown.hasClass 'js-multiselect'
return
else
saveLabelData()
)

View file

@ -0,0 +1,39 @@
((w) ->
if not w.gl? then w.gl = {}
if not gl.animate? then gl.animate = {}
gl.animate.animate = ($el, animation, options, done) ->
if options?.cssStart?
$el.css(options.cssStart)
$el
.removeClass(animation + ' animated')
.addClass(animation + ' animated')
.one 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', ->
$(this).removeClass(animation + ' animated')
if done?
done()
if options?.cssEnd?
$el.css(options.cssEnd)
return
return
gl.animate.animateEach = ($els, animation, time, options, done) ->
dfd = $.Deferred()
if not $els.length
dfd.resolve()
$els.each((i) ->
setTimeout(=>
$this = $(@)
gl.animate.animate($this, animation, options, =>
if i is $els.length - 1
dfd.resolve()
if done?
done()
)
,time * i
)
return
)
return dfd.promise()
return
) window

View file

@ -0,0 +1,17 @@
((w) ->
w.gl ?= {}
w.gl.utils ?= {}
w.gl.utils.formatDate = (datetime) ->
dateFormat(datetime, 'mmm d, yyyy h:MMtt Z')
w.gl.utils.localTimeAgo = ($timeagoEls, setTimeago = true) ->
$timeagoEls.each( ->
$el = $(@)
$el.attr('title', gl.utils.formatDate($el.attr('datetime')))
)
$timeagoEls.timeago() if setTimeago
) window

View file

@ -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

View file

@ -0,0 +1,9 @@
((w) ->
w.gl ?= {}
w.gl.utils ?= {}
w.gl.utils.isObject = (obj) ->
obj? and (obj.constructor is Object)
) window

View file

@ -0,0 +1,43 @@
((w) ->
w.gl ?= {}
w.gl.utils ?= {}
# Returns an array containing the value(s) of the
# of the key passed as an argument
w.gl.utils.getParameterValues = (sParam) ->
sPageURL = decodeURIComponent(window.location.search.substring(1))
sURLVariables = sPageURL.split('&')
sParameterName = undefined
values = []
i = 0
while i < sURLVariables.length
sParameterName = sURLVariables[i].split('=')
if sParameterName[0] is sParam
values.push(sParameterName[1])
i++
values
# #
# @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
# removes parameter query string from url. returns the modified url
w.gl.utils.removeParamQueryString = (url, param) ->
url = decodeURIComponent(url)
urlVariables = url.split('&')
(
variables for variables in urlVariables when variables.indexOf(param) is -1
).join('&')
) window

View file

@ -1,4 +1,4 @@
NProgress.configure(showSpinner: false) Turbolinks.enableProgressBar();
defaultClass = 'tanuki-shape' defaultClass = 'tanuki-shape'
pieces = [ pieces = [

View file

@ -6,6 +6,7 @@
class @MarkdownPreview class @MarkdownPreview
# Minimum number of users referenced before triggering a warning # Minimum number of users referenced before triggering a warning
referenceThreshold: 10 referenceThreshold: 10
ajaxCache: {}
showPreview: (form) -> showPreview: (form) ->
preview = form.find('.js-md-preview') preview = form.find('.js-md-preview')
@ -24,12 +25,16 @@ class @MarkdownPreview
renderMarkdown: (text, success) -> renderMarkdown: (text, success) ->
return unless window.markdown_preview_path return unless window.markdown_preview_path
return success(@ajaxCache.response) if text == @ajaxCache.text
$.ajax $.ajax
type: 'POST' type: 'POST'
url: window.markdown_preview_path url: window.markdown_preview_path
data: { text: text } data: { text: text }
dataType: 'json' dataType: 'json'
success: success success: (response) =>
@ajaxCache = text: text, response: response
success(response)
hideReferencedUsers: (form) -> hideReferencedUsers: (form) ->
referencedUsers = form.find('.referenced-users') referencedUsers = form.find('.referenced-users')
@ -49,6 +54,7 @@ markdownPreview = new MarkdownPreview()
previewButtonSelector = '.js-md-preview-button' previewButtonSelector = '.js-md-preview-button'
writeButtonSelector = '.js-md-write-button' writeButtonSelector = '.js-md-write-button'
lastTextareaPreviewed = null
$.fn.setupMarkdownPreview = -> $.fn.setupMarkdownPreview = ->
$form = $(this) $form = $(this)
@ -58,10 +64,10 @@ $.fn.setupMarkdownPreview = ->
form_textarea.on 'input', -> markdownPreview.hideReferencedUsers($form) form_textarea.on 'input', -> markdownPreview.hideReferencedUsers($form)
form_textarea.on 'blur', -> markdownPreview.showPreview($form) form_textarea.on 'blur', -> markdownPreview.showPreview($form)
$(document).on 'click', previewButtonSelector, (e) -> $(document).on 'markdown-preview:show', (e, $form) ->
e.preventDefault() return unless $form
$form = $(this).closest('form') lastTextareaPreviewed = $form.find('textarea.markdown-area')
# toggle tabs # toggle tabs
$form.find(writeButtonSelector).parent().removeClass('active') $form.find(writeButtonSelector).parent().removeClass('active')
@ -73,10 +79,10 @@ $(document).on 'click', previewButtonSelector, (e) ->
markdownPreview.showPreview($form) markdownPreview.showPreview($form)
$(document).on 'click', writeButtonSelector, (e) -> $(document).on 'markdown-preview:hide', (e, $form) ->
e.preventDefault() return unless $form
$form = $(this).closest('form') lastTextareaPreviewed = null
# toggle tabs # toggle tabs
$form.find(writeButtonSelector).parent().addClass('active') $form.find(writeButtonSelector).parent().addClass('active')
@ -84,4 +90,30 @@ $(document).on 'click', writeButtonSelector, (e) ->
# toggle content # toggle content
$form.find('.md-write-holder').show() $form.find('.md-write-holder').show()
$form.find('textarea.markdown-area').focus()
$form.find('.md-preview-holder').hide() $form.find('.md-preview-holder').hide()
$(document).on 'markdown-preview:toggle', (e, keyboardEvent) ->
$target = $(keyboardEvent.target)
if $target.is('textarea.markdown-area')
$(document).triggerHandler('markdown-preview:show', [$target.closest('form')])
keyboardEvent.preventDefault()
else if lastTextareaPreviewed
$target = lastTextareaPreviewed
$(document).triggerHandler('markdown-preview:hide', [$target.closest('form')])
keyboardEvent.preventDefault()
$(document).on 'click', previewButtonSelector, (e) ->
e.preventDefault()
$form = $(this).closest('form')
$(document).triggerHandler('markdown-preview:show', [$form])
$(document).on 'click', writeButtonSelector, (e) ->
e.preventDefault()
$form = $(this).closest('form')
$(document).triggerHandler('markdown-preview:hide', [$form])

View file

@ -15,8 +15,6 @@ class @MergeRequest
this.$('.show-all-commits').on 'click', => this.$('.show-all-commits').on 'click', =>
this.showAllCommits() this.showAllCommits()
@fixAffixScroll();
@initTabs() @initTabs()
# Prevent duplicate event bindings # Prevent duplicate event bindings
@ -30,20 +28,6 @@ class @MergeRequest
$: (selector) -> $: (selector) ->
this.$el.find(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: -> initTabs: ->
if @opts.action != 'new' if @opts.action != 'new'
# `MergeRequests#new` has no tab-persisting or lazy-loading behavior # `MergeRequests#new` has no tab-persisting or lazy-loading behavior

View file

@ -3,6 +3,8 @@
# Handles persisting and restoring the current tab selection and lazily-loading # Handles persisting and restoring the current tab selection and lazily-loading
# content on the MergeRequests#show page. # content on the MergeRequests#show page.
# #
#= require jquery.cookie
#
# ### Example Markup # ### Example Markup
# #
# <ul class="nav-links merge-request-tabs"> # <ul class="nav-links merge-request-tabs">
@ -68,17 +70,25 @@ class @MergeRequestTabs
if action == 'commits' if action == 'commits'
@loadCommits($target.attr('href')) @loadCommits($target.attr('href'))
@expandView()
else if action == 'diffs' else if action == 'diffs'
@loadDiff($target.attr('href')) @loadDiff($target.attr('href'))
if bp? and bp.getBreakpointSize() isnt 'lg'
@shrinkView()
else if action == 'builds' else if action == 'builds'
@loadBuilds($target.attr('href')) @loadBuilds($target.attr('href'))
@expandView()
else
@expandView()
@setCurrentAction(action) @setCurrentAction(action)
scrollToElement: (container) -> scrollToElement: (container) ->
if window.location.hash if window.location.hash
$el = $("div#{container} #{window.location.hash}") navBarHeight = $('.navbar-gitlab').outerHeight()
$('body').scrollTo($el.offset().top) if $el.length
$el = $("#{container} #{window.location.hash}:not(.match)")
$.scrollTo("#{container} #{window.location.hash}:not(.match)", offset: -navBarHeight) if $el.length
# Activate a tab based on the current action # Activate a tab based on the current action
activateTab: (action) -> activateTab: (action) ->
@ -134,7 +144,7 @@ class @MergeRequestTabs
url: "#{source}.json" url: "#{source}.json"
success: (data) => success: (data) =>
document.querySelector("div#commits").innerHTML = data.html document.querySelector("div#commits").innerHTML = data.html
$('.js-timeago').timeago() gl.utils.localTimeAgo($('.js-timeago', 'div#commits'))
@commitsLoaded = true @commitsLoaded = true
@scrollToElement("#commits") @scrollToElement("#commits")
@ -144,11 +154,39 @@ class @MergeRequestTabs
@_get @_get
url: "#{source}.json" + @_location.search url: "#{source}.json" + @_location.search
success: (data) => success: (data) =>
document.querySelector("div#diffs").innerHTML = data.html $('#diffs').html data.html
$('div#diffs .js-syntax-highlight').syntaxHighlight() gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'))
$('#diffs .js-syntax-highlight').syntaxHighlight()
@expandViewContainer() if @diffViewType() is 'parallel' @expandViewContainer() if @diffViewType() is 'parallel'
@diffsLoaded = true @diffsLoaded = true
@scrollToElement("#diffs") @scrollToElement("#diffs")
@highlighSelectedLine()
$(document)
.off 'click', '.diff-line-num a'
.on 'click', '.diff-line-num a', (e) =>
e.preventDefault()
window.location.hash = $(e.currentTarget).attr 'href'
@highlighSelectedLine()
@scrollToElement("#diffs")
highlighSelectedLine: ->
$('.hll').removeClass 'hll'
locationHash = window.location.hash
if locationHash isnt ''
hashClassString = ".#{locationHash.replace('#', '')}"
$diffLine = $("#{locationHash}:not(.match)", $('#diffs'))
if not $diffLine.is 'tr'
$diffLine = $('#diffs').find("td#{locationHash}, td#{hashClassString}")
else
$diffLine = $diffLine.find('td')
if $diffLine.length
$diffLine.addClass 'hll'
diffLineTop = $diffLine.offset().top
navBarHeight = $('.navbar-gitlab').outerHeight()
loadBuilds: (source) -> loadBuilds: (source) ->
return if @buildsLoaded return if @buildsLoaded
@ -157,7 +195,7 @@ class @MergeRequestTabs
url: "#{source}.json" url: "#{source}.json"
success: (data) => success: (data) =>
document.querySelector("div#builds").innerHTML = data.html document.querySelector("div#builds").innerHTML = data.html
$('.js-timeago').timeago() gl.utils.localTimeAgo($('.js-timeago', 'div#builds'))
@buildsLoaded = true @buildsLoaded = true
@scrollToElement("#builds") @scrollToElement("#builds")
@ -185,3 +223,26 @@ class @MergeRequestTabs
expandViewContainer: -> expandViewContainer: ->
$('.container-fluid').removeClass('container-limited') $('.container-fluid').removeClass('container-limited')
shrinkView: ->
$gutterIcon = $('.js-sidebar-toggle i:visible')
# Wait until listeners are set
setTimeout( ->
# Only when sidebar is expanded
if $gutterIcon.is('.fa-angle-double-right')
$gutterIcon.closest('a').trigger('click', [true])
, 0)
# Expand the issuable sidebar unless the user explicitly collapsed it
expandView: ->
return if $.cookie('collapsed_gutter') == 'true'
$gutterIcon = $('.js-sidebar-toggle i:visible')
# Wait until listeners are set
setTimeout( ->
# Only when sidebar is collapsed
if $gutterIcon.is('.fa-angle-double-left')
$gutterIcon.closest('a').trigger('click', [true])
, 0)

View file

@ -2,13 +2,30 @@ class @MergeRequestWidget
# Initialize MergeRequestWidget behavior # Initialize MergeRequestWidget behavior
# #
# check_enable - Boolean, whether to check automerge status # check_enable - Boolean, whether to check automerge status
# url_to_automerge_check - String, URL to use to check automerge status # merge_check_url - String, URL to use to check automerge status
# current_status - String, current automerge status # ci_status_url - String, URL to use to check CI status
# ci_enable - Boolean, whether a CI service is enabled
# url_to_ci_check - String, URL to use to check CI status
# #
constructor: (@opts) -> constructor: (@opts) ->
modal = $('#modal_merge_info').modal(show: false) $('#modal_merge_info').modal(show: false)
@firstCICheck = true
@readyForCICheck = false
clearInterval @fetchBuildStatusInterval
@clearEventListeners()
@addEventListeners()
@getCIStatus(false)
@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)-> mergeInProgress: (deleteSourceBranch = false)->
$.ajax $.ajax
@ -27,18 +44,68 @@ class @MergeRequestWidget
dataType: 'json' dataType: 'json'
getMergeStatus: -> getMergeStatus: ->
$.get @opts.url_to_automerge_check, (data) -> $.get @opts.merge_check_url, (data) ->
$('.mr-state-widget').replaceWith(data) $('.mr-state-widget').replaceWith(data)
getCiStatus: -> ciLabelForStatus: (status) ->
if @opts.ci_enable if status is 'success'
$.get @opts.url_to_ci_check, (data) => 'passed'
this.showCiState data.status else
if data.coverage status
this.showCiCoverage data.coverage
, 'json'
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 data.status is ''
return
if @firstCICheck || data.status isnt @opts.ci_status and data.status?
@opts.ci_status = data.status
@showCIStatus data.status
if data.coverage
@showCICoverage data.coverage
# The first check should only update the UI, a notification
# should only be displayed on status changes
if showNotification and not @firstCICheck
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
)
@firstCICheck = false
showCIStatus: (state) ->
$('.ci_widget').hide() $('.ci_widget').hide()
allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"] allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"]
if state in allowed_states if state in allowed_states
@ -46,15 +113,19 @@ class @MergeRequestWidget
switch state switch state
when "failed", "canceled", "not_found" when "failed", "canceled", "not_found"
@setMergeButtonClass('btn-danger') @setMergeButtonClass('btn-danger')
when "running", "pending" when "running"
@setMergeButtonClass('btn-warning') @setMergeButtonClass('btn-warning')
when "success"
@setMergeButtonClass('btn-create')
else else
$('.ci_widget.ci-error').show() $('.ci_widget.ci-error').show()
@setMergeButtonClass('btn-danger') @setMergeButtonClass('btn-danger')
showCiCoverage: (coverage) -> showCICoverage: (coverage) ->
text = 'Coverage ' + coverage + '%' text = 'Coverage ' + coverage + '%'
$('.ci_widget:visible .ci-coverage').text(text) $('.ci_widget:visible .ci-coverage').text(text)
setMergeButtonClass: (css_class) -> setMergeButtonClass: (css_class) ->
$('.accept_merge_request').removeClass("btn-create").addClass(css_class) $('.js-merge-button')
.removeClass('btn-danger btn-warning btn-create')
.addClass(css_class)

View file

@ -1,35 +0,0 @@
#
# * Filter merge requests
#
@MergeRequests =
init: ->
MergeRequests.initSearch()
# Make sure we trigger ajax request only after user stop typing
initSearch: ->
@timer = null
$("#issue_search").keyup ->
clearTimeout(@timer)
@timer = setTimeout(MergeRequests.filterResults, 500)
filterResults: =>
form = $("#issue_search_form")
search = $("#issue_search").val()
$('.merge-requests-holder').css("opacity", '0.5')
issues_url = form.attr('action') + '?' + form.serialize()
$.ajax
type: "GET"
url: form.attr('action')
data: form.serialize()
complete: ->
$('.merge-requests-holder').css("opacity", '1.0')
success: (data) ->
$('.merge-requests-holder').html(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: issues_url}, document.title, issues_url
MergeRequests.reload()
dataType: "json"
reload: ->
$('#filter_issue_search').val($('#issue_search').val())

View file

@ -69,7 +69,7 @@ class @Milestone
@bindIssuesSorting() @bindIssuesSorting()
@bindMergeRequestSorting() @bindMergeRequestSorting()
@bindTabsSwitching @bindTabsSwitching()
bindIssuesSorting: -> bindIssuesSorting: ->
$("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable( $("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable(
@ -104,7 +104,7 @@ class @Milestone
).disableSelection() ).disableSelection()
bindMergeRequestSorting: -> bindTabsSwitching: ->
$('a[data-toggle="tab"]').on 'show.bs.tab', (e) -> $('a[data-toggle="tab"]').on 'show.bs.tab', (e) ->
currentTabClass = $(e.target).data('show') currentTabClass = $(e.target).data('show')
previousTabClass = $(e.relatedTarget).data('show') previousTabClass = $(e.relatedTarget).data('show')
@ -113,6 +113,7 @@ class @Milestone
$(currentTabClass).removeClass('hidden') $(currentTabClass).removeClass('hidden')
$(currentTabClass).show() $(currentTabClass).show()
bindMergeRequestSorting: ->
$("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable( $("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable(
connectWith: ".merge_requests-sortable-list", connectWith: ".merge_requests-sortable-list",
dropOnEmpty: true, dropOnEmpty: true,

View file

@ -0,0 +1,130 @@
class @MilestoneSelect
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 %>"><%= _.escape(title) %></a>'
)
milestoneLinkNoneTemplate = '<div class="light">None</div>'
$dropdown.glDropdown(
data: (term, callback) ->
$.ajax(
url: milestonesUrl
).done (data) ->
extraOptions = []
if showAny
extraOptions.push(
id: 0
name: ''
title: 'Any Milestone'
)
if showNo
extraOptions.push(
id: -1
name: 'No Milestone'
title: 'No Milestone'
)
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']
selectable: true
toggleLabel: (selected) ->
if selected && 'id' of selected
selected.title
else
defaultLabel
fieldName: $dropdown.data('field-name')
text: (milestone) ->
_.escape(milestone.title)
id: (milestone) ->
if !useId
milestone.name
else
milestone.id
isSelected: (milestone) ->
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 = ''
Issuable.filterResults $dropdown.closest('form')
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')
)

View file

@ -16,11 +16,13 @@ class @Notes
@view = view @view = view
@noteable_url = document.URL @noteable_url = document.URL
@notesCountBadge ||= $(".issuable-details").find(".notes-tab .badge") @notesCountBadge ||= $(".issuable-details").find(".notes-tab .badge")
@basePollingInterval = 15000
@maxPollingSteps = 4
@initRefresh()
@setupMainTargetNoteForm()
@cleanBinding() @cleanBinding()
@addBinding() @addBinding()
@setPollingInterval()
@setupMainTargetNoteForm()
@initTaskList() @initTaskList()
addBinding: -> addBinding: ->
@ -28,8 +30,11 @@ class @Notes
$(document).on "ajax:success", ".js-main-target-form", @addNote $(document).on "ajax:success", ".js-main-target-form", @addNote
$(document).on "ajax:success", ".js-discussion-note-form", @addDiscussionNote $(document).on "ajax:success", ".js-discussion-note-form", @addDiscussionNote
# catch note ajax errors
$(document).on "ajax:error", ".js-main-target-form", @addNoteError
# change note in UI after update # change note in UI after update
$(document).on "ajax:success", "form.edit_note", @updateNote $(document).on "ajax:success", "form.edit-note", @updateNote
# Edit note link # Edit note link
$(document).on "click", ".js-note-edit", @showEditForm $(document).on "click", ".js-note-edit", @showEditForm
@ -37,7 +42,7 @@ class @Notes
# Reopen and close actions for Issue/MR combined with note form submit # Reopen and close actions for Issue/MR combined with note form submit
$(document).on "click", ".js-comment-button", @updateCloseButton $(document).on "click", ".js-comment-button", @updateCloseButton
$(document).on "keyup", ".js-note-text", @updateTargetButtons $(document).on "keyup input", ".js-note-text", @updateTargetButtons
# remove a note (in general) # remove a note (in general)
$(document).on "click", ".js-note-delete", @removeNote $(document).on "click", ".js-note-delete", @removeNote
@ -49,6 +54,9 @@ class @Notes
$(document).on "ajax:complete", ".js-main-target-form", @reenableTargetFormSubmitButton $(document).on "ajax:complete", ".js-main-target-form", @reenableTargetFormSubmitButton
$(document).on "ajax:success", ".js-main-target-form", @resetMainTargetForm $(document).on "ajax:success", ".js-main-target-form", @resetMainTargetForm
# reset main target form when clicking discard
$(document).on "click", ".js-note-discard", @resetMainTargetForm
# update the file name when an attachment is selected # update the file name when an attachment is selected
$(document).on "change", ".js-note-attachment-input", @updateFormAttachment $(document).on "change", ".js-note-attachment-input", @updateFormAttachment
@ -67,10 +75,13 @@ class @Notes
# when issue status changes, we need to refresh data # when issue status changes, we need to refresh data
$(document).on "issuable:change", @refresh $(document).on "issuable:change", @refresh
# when a key is clicked on the notes
$(document).on "keydown", ".js-note-text", @keydownNoteText
cleanBinding: -> cleanBinding: ->
$(document).off "ajax:success", ".js-main-target-form" $(document).off "ajax:success", ".js-main-target-form"
$(document).off "ajax:success", ".js-discussion-note-form" $(document).off "ajax:success", ".js-discussion-note-form"
$(document).off "ajax:success", "form.edit_note" $(document).off "ajax:success", "form.edit-note"
$(document).off "click", ".js-note-edit" $(document).off "click", ".js-note-edit"
$(document).off "click", ".note-edit-cancel" $(document).off "click", ".note-edit-cancel"
$(document).off "click", ".js-note-delete" $(document).off "click", ".js-note-delete"
@ -83,17 +94,29 @@ class @Notes
$(document).off "keyup", ".js-note-text" $(document).off "keyup", ".js-note-text"
$(document).off "click", ".js-note-target-reopen" $(document).off "click", ".js-note-target-reopen"
$(document).off "click", ".js-note-target-close" $(document).off "click", ".js-note-target-close"
$(document).off "click", ".js-note-discard"
$(document).off "keydown", ".js-note-text"
$('.note .js-task-list-container').taskList('disable') $('.note .js-task-list-container').taskList('disable')
$(document).off 'tasklist:changed', '.note .js-task-list-container' $(document).off 'tasklist:changed', '.note .js-task-list-container'
keydownNoteText: (e) ->
$this = $(this)
if $this.val() is '' and e.which is 38 #aka the up key
myLastNote = $("li.note[data-author-id='#{gon.current_user_id}'][data-editable]:last")
if myLastNote.length
myLastNoteEditBtn = myLastNote.find('.js-note-edit')
myLastNoteEditBtn.trigger('click', [true, myLastNote])
initRefresh: -> initRefresh: ->
clearInterval(Notes.interval) clearInterval(Notes.interval)
Notes.interval = setInterval => Notes.interval = setInterval =>
@refresh() @refresh()
, 15000 , @pollingInterval
refresh: -> refresh: ->
return if @refreshing is true
refreshing = true
if not document.hidden and document.URL.indexOf(@noteable_url) is 0 if not document.hidden and document.URL.indexOf(@noteable_url) is 0
@getContent() @getContent()
@ -105,12 +128,31 @@ class @Notes
success: (data) => success: (data) =>
notes = data.notes notes = data.notes
@last_fetched_at = data.last_fetched_at @last_fetched_at = data.last_fetched_at
@setPollingInterval(data.notes.length)
$.each notes, (i, note) => $.each notes, (i, note) =>
if note.discussion_with_diff_html? if note.discussion_with_diff_html?
@renderDiscussionNote(note) @renderDiscussionNote(note)
else else
@renderNote(note) @renderNote(note)
always: =>
@refreshing = false
###
Increase @pollingInterval up to 120 seconds on every function call,
if `shouldReset` has a truthy value, 'null' or 'undefined' the variable
will reset to @basePollingInterval.
Note: this function is used to gradually increase the polling interval
if there aren't new notes coming from the server
###
setPollingInterval: (shouldReset = true) ->
nthInterval = @basePollingInterval * Math.pow(2, @maxPollingSteps - 1)
if shouldReset
@pollingInterval = @basePollingInterval
else if @pollingInterval < nthInterval
@pollingInterval *= 2
@initRefresh()
### ###
Render note in main comments area. Render note in main comments area.
@ -125,17 +167,23 @@ class @Notes
return return
if note.award if note.award
awards_handler.addAwardToEmojiBar(note.note) awardsHandler.addAwardToEmojiBar(note.note)
awards_handler.scrollToAwards() awardsHandler.scrollToAwards()
# render note if it not present in loaded list # render note if it not present in loaded list
# or skip if rendered # or skip if rendered
else if @isNewNote(note) else if @isNewNote(note)
@note_ids.push(note.id) @note_ids.push(note.id)
$('ul.main-notes-list') $notesList = $('ul.main-notes-list')
$notesList
.append(note.html) .append(note.html)
.syntaxHighlight() .syntaxHighlight()
# Update datetime format on the recent note
gl.utils.localTimeAgo($notesList.find("#note_#{note.id} .js-timeago"), false)
@initTaskList() @initTaskList()
@updateNotesCount(1) @updateNotesCount(1)
@ -187,6 +235,8 @@ class @Notes
# append new note to all matching discussions # append new note to all matching discussions
discussionContainer.append note_html discussionContainer.append note_html
gl.utils.localTimeAgo($('.js-timeago', note_html), false)
@updateNotesCount(1) @updateNotesCount(1)
### ###
@ -196,7 +246,7 @@ class @Notes
Resets text and preview. Resets text and preview.
Resets buttons. Resets buttons.
### ###
resetMainTargetForm: -> resetMainTargetForm: (e) =>
form = $(".js-main-target-form") form = $(".js-main-target-form")
# remove validation errors # remove validation errors
@ -208,6 +258,8 @@ class @Notes
form.find(".js-note-text").data("autosave").reset() form.find(".js-note-text").data("autosave").reset()
@updateTargetButtons(e)
reenableTargetFormSubmitButton: -> reenableTargetFormSubmitButton: ->
form = $(".js-main-target-form") form = $(".js-main-target-form")
@ -219,13 +271,11 @@ class @Notes
Sets some hidden fields in the form. Sets some hidden fields in the form.
### ###
setupMainTargetNoteForm: -> setupMainTargetNoteForm: ->
# find the form # find the form
form = $(".js-new-note-form") form = $(".js-new-note-form")
# insert the form after the button # Set a global clone of the form for later cloning
form.clone().replaceAll $(".js-main-target-form") @formClone = form.clone()
form = form.prev("form")
# show the form # show the form
@setupNoteForm(form) @setupNoteForm(form)
@ -234,9 +284,8 @@ class @Notes
form.removeClass "js-new-note-form" form.removeClass "js-new-note-form"
form.addClass "js-main-target-form" form.addClass "js-main-target-form"
# remove unnecessary fields and buttons
form.find("#note_line_code").remove() form.find("#note_line_code").remove()
form.find(".js-close-discussion-note-form").remove() form.find("#note_type").remove()
### ###
General note form setup. General note form setup.
@ -247,23 +296,10 @@ class @Notes
show the form show the form
### ###
setupNoteForm: (form) -> setupNoteForm: (form) ->
disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button") new GLForm form
form.removeClass "js-new-note-form"
form.find('.div-dropzone').remove()
# setup preview buttons
form.find(".js-md-write-button, .js-md-preview-button").tooltip placement: "left"
previewButton = form.find(".js-md-preview-button")
textarea = form.find(".js-note-text") textarea = form.find(".js-note-text")
textarea.on "input", ->
if $(this).val().trim() isnt ""
previewButton.removeClass("turn-off").addClass "turn-on"
else
previewButton.removeClass("turn-on").addClass "turn-off"
autosize(textarea)
new Autosave textarea, [ new Autosave textarea, [
"Note" "Note"
form.find("#note_commit_id").val() form.find("#note_commit_id").val()
@ -272,12 +308,6 @@ class @Notes
form.find("#note_noteable_id").val() form.find("#note_noteable_id").val()
] ]
# 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()
### ###
Called in response to the new note form being submitted Called in response to the new note form being submitted
@ -286,6 +316,10 @@ class @Notes
addNote: (xhr, note, status) => addNote: (xhr, note, status) =>
@renderNote(note) @renderNote(note)
addNoteError: (xhr, note, status) =>
flash = new Flash('Your comment could not be submitted! Please check your network connection and try again.', 'alert')
flash.pinTo('.md-area')
### ###
Called in response to the new note form being submitted Called in response to the new note form being submitted
@ -305,6 +339,9 @@ class @Notes
updateNote: (_xhr, note, _status) => updateNote: (_xhr, note, _status) =>
# Convert returned HTML to a jQuery object so we can modify it further # Convert returned HTML to a jQuery object so we can modify it further
$html = $(note.html) $html = $(note.html)
gl.utils.localTimeAgo($('.js-timeago', $html))
$html.syntaxHighlight() $html.syntaxHighlight()
$html.find('.js-task-list-container').taskList('enable') $html.find('.js-task-list-container').taskList('enable')
@ -319,36 +356,38 @@ class @Notes
Adds a hidden div with the original content of the note to fill the edit note form with Adds a hidden div with the original content of the note to fill the edit note form with
if the user cancels if the user cancels
### ###
showEditForm: (e) -> showEditForm: (e, scrollTo, myLastNote) ->
e.preventDefault() e.preventDefault()
note = $(this).closest(".note") note = $(this).closest(".note")
note.find(".note-body > .note-text").hide() note.addClass "is-editting"
note.find(".note-header").hide() form = note.find(".note-edit-form")
base_form = note.find(".note-edit-form")
form = base_form.clone().insertAfter(base_form) form.addClass('current-note-edit-form')
form.addClass('current-note-edit-form gfm-form')
form.find('.div-dropzone').remove()
# Show the attachment delete link # Show the attachment delete link
note.find(".js-note-attachment-delete").show() note.find(".js-note-attachment-delete").show()
# Setup markdown form done = ($noteText) ->
GitLab.GfmAutoComplete.setup() # Neat little trick to put the cursor at the end
new DropzoneInput(form) noteTextVal = $noteText.val()
$noteText.val('').val(noteTextVal);
form.show() new GLForm form
textarea = form.find("textarea") if scrollTo? and myLastNote?
textarea.focus() # scroll to the bottom
autosize(textarea) # so the open of the last element doesn't make a jump
$('html, body').scrollTop($(document).height());
# HACK (rspeicher/DouweM): Work around a Chrome 43 bug(?). $('html, body').animate({
# The textarea has the correct value, Chrome just won't show it unless we scrollTop: myLastNote.offset().top - 150
# modify it, so let's clear it and re-set it! }, 500, ->
value = textarea.val() $noteText = form.find(".js-note-text")
textarea.val "" $noteText.focus()
textarea.val value done($noteText)
);
disableButtonIfEmptyField textarea, form.find(".js-comment-button") else
$noteText = form.find('.js-note-text')
$noteText.focus()
done($noteText)
### ###
Called in response to clicking the edit note link Called in response to clicking the edit note link
@ -358,9 +397,9 @@ class @Notes
cancelEdit: (e) -> cancelEdit: (e) ->
e.preventDefault() e.preventDefault()
note = $(this).closest(".note") note = $(this).closest(".note")
note.find(".note-body > .note-text").show() note.removeClass "is-editting"
note.find(".note-header").show() note.find(".current-note-edit-form")
note.find(".current-note-edit-form").remove() .removeClass("current-note-edit-form")
### ###
Called in response to deleting a note of any kind. Called in response to deleting a note of any kind.
@ -413,15 +452,15 @@ class @Notes
Shows the note form below the notes. Shows the note form below the notes.
### ###
replyToDiscussionNote: (e) => replyToDiscussionNote: (e) =>
form = $(".js-new-note-form") form = @formClone.clone()
replyLink = $(e.target).closest(".js-discussion-reply-button") replyLink = $(e.target).closest(".js-discussion-reply-button")
replyLink.hide() replyLink.hide()
# insert the form after the button # insert the form after the button
form.clone().insertAfter replyLink replyLink.after form
# show the form # show the form
@setupDiscussionNoteForm(replyLink, replyLink.next("form")) @setupDiscussionNoteForm(replyLink, form)
### ###
Shows the diff or discussion form and does some setup on it. Shows the diff or discussion form and does some setup on it.
@ -434,14 +473,22 @@ class @Notes
setupDiscussionNoteForm: (dataHolder, form) => setupDiscussionNoteForm: (dataHolder, form) =>
# setup note target # setup note target
form.attr 'id', "new-discussion-note-form-#{dataHolder.data("discussionId")}" form.attr 'id', "new-discussion-note-form-#{dataHolder.data("discussionId")}"
form.find("#note_type").val dataHolder.data("noteType")
form.find("#line_type").val dataHolder.data("lineType") form.find("#line_type").val dataHolder.data("lineType")
form.find("#note_commit_id").val dataHolder.data("commitId") form.find("#note_commit_id").val dataHolder.data("commitId")
form.find("#note_line_code").val dataHolder.data("lineCode") form.find("#note_line_code").val dataHolder.data("lineCode")
form.find("#note_noteable_type").val dataHolder.data("noteableType") form.find("#note_noteable_type").val dataHolder.data("noteableType")
form.find("#note_noteable_id").val dataHolder.data("noteableId") form.find("#note_noteable_id").val dataHolder.data("noteableId")
form.find('.js-note-discard')
.show()
.removeClass('js-note-discard')
.addClass('js-close-discussion-note-form')
.text(form.find('.js-close-discussion-note-form').data('cancel-text'))
@setupNoteForm form @setupNoteForm form
form.find(".js-note-text").focus() 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. Called when clicking on the "add a comment" button on the side of a diff line.
@ -451,9 +498,8 @@ class @Notes
### ###
addDiffNote: (e) => addDiffNote: (e) =>
e.preventDefault() e.preventDefault()
link = e.currentTarget $link = $(e.currentTarget)
form = $(".js-new-note-form") row = $link.closest("tr")
row = $(link).closest("tr")
nextRow = row.next() nextRow = row.next()
hasNotes = nextRow.is(".notes_holder") hasNotes = nextRow.is(".notes_holder")
addForm = false addForm = false
@ -462,7 +508,7 @@ class @Notes
# In parallel view, look inside the correct left/right pane # In parallel view, look inside the correct left/right pane
if @isParallelView() if @isParallelView()
lineType = $(link).data("lineType") lineType = $link.data("lineType")
targetContent += "." + 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>" 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>"
@ -484,11 +530,11 @@ class @Notes
addForm = true addForm = true
if addForm if addForm
newForm = form.clone() newForm = @formClone.clone()
newForm.appendTo row.next().find(targetContent) newForm.appendTo row.next().find(targetContent)
# show the form # show the form
@setupDiscussionNoteForm $(link), newForm @setupDiscussionNoteForm $link, newForm
### ###
Called in response to "cancel" on a diff note form. Called in response to "cancel" on a diff note form.
@ -499,6 +545,9 @@ class @Notes
removeDiscussionNoteForm: (form)-> removeDiscussionNoteForm: (form)->
row = form.closest("tr") row = form.closest("tr")
glForm = form.data 'gl-form'
glForm.destroy()
form.find(".js-note-text").data("autosave").reset() form.find(".js-note-text").data("autosave").reset()
# show the reply button (will only work for replies) # show the reply button (will only work for replies)
@ -510,10 +559,8 @@ class @Notes
# only remove the form # only remove the form
form.remove() form.remove()
cancelDiscussionForm: (e) => cancelDiscussionForm: (e) =>
e.preventDefault() e.preventDefault()
form = $(".js-new-note-form")
form = $(e.target).closest(".js-discussion-note-form") form = $(e.target).closest(".js-discussion-note-form")
@removeDiscussionNoteForm(form) @removeDiscussionNoteForm(form)
@ -538,21 +585,52 @@ class @Notes
updateCloseButton: (e) => updateCloseButton: (e) =>
textarea = $(e.target) textarea = $(e.target)
form = textarea.parents('form') form = textarea.parents('form')
form.find('.js-note-target-close').text('Close') closebtn = form.find('.js-note-target-close')
closebtn.text(closebtn.data('original-text'))
updateTargetButtons: (e) => updateTargetButtons: (e) =>
textarea = $(e.target) textarea = $(e.target)
form = textarea.parents('form') form = textarea.parents('form')
reopenbtn = form.find('.js-note-target-reopen')
closebtn = form.find('.js-note-target-close')
discardbtn = form.find('.js-note-discard')
if textarea.val().trim().length > 0 if textarea.val().trim().length > 0
form.find('.js-note-target-reopen').text('Comment & reopen') reopentext = reopenbtn.data('alternative-text')
form.find('.js-note-target-close').text('Comment & close') closetext = closebtn.data('alternative-text')
form.find('.js-note-target-reopen').addClass('btn-comment-and-reopen')
form.find('.js-note-target-close').addClass('btn-comment-and-close') if reopenbtn.text() isnt reopentext
reopenbtn.text(reopentext)
if closebtn.text() isnt closetext
closebtn.text(closetext)
if reopenbtn.is(':not(.btn-comment-and-reopen)')
reopenbtn.addClass('btn-comment-and-reopen')
if closebtn.is(':not(.btn-comment-and-close)')
closebtn.addClass('btn-comment-and-close')
if discardbtn.is(':hidden')
discardbtn.show()
else else
form.find('.js-note-target-reopen').text('Reopen') reopentext = reopenbtn.data('original-text')
form.find('.js-note-target-close').text('Close') closetext = closebtn.data('original-text')
form.find('.js-note-target-reopen').removeClass('btn-comment-and-reopen')
form.find('.js-note-target-close').removeClass('btn-comment-and-close') if reopenbtn.text() isnt reopentext
reopenbtn.text(reopentext)
if closebtn.text() isnt closetext
closebtn.text(closetext)
if reopenbtn.is('.btn-comment-and-reopen')
reopenbtn.removeClass('btn-comment-and-reopen')
if closebtn.is('.btn-comment-and-close')
closebtn.removeClass('btn-comment-and-close')
if discardbtn.is(':visible')
discardbtn.hide()
initTaskList: -> initTaskList: ->
@enableTaskList() @enableTaskList()

View file

@ -1,6 +1,7 @@
@Pager = @Pager =
init: (@limit = 0, preload, @disable = false) -> init: (@limit = 0, preload, @disable = false) ->
@loading = $(".loading") @loading = $('.loading').first()
if preload if preload
@offset = 0 @offset = 0
@getOld() @getOld()

View file

@ -1,26 +1,76 @@
class @Profile class @Profile
constructor: -> constructor: (opts = {}) ->
{
@form = $('.edit-user')
} = opts
# Automatically submit the Preferences form when any of its radio buttons change # Automatically submit the Preferences form when any of its radio buttons change
$('.js-preferences-form').on 'change.preference', 'input[type=radio]', -> $('.js-preferences-form').on 'change.preference', 'input[type=radio]', ->
$(this).parents('form').submit() $(this).parents('form').submit()
$('.update-username form').on 'ajax:before', -> $('.update-username').on 'ajax:before', ->
$('.loading-gif').show() $('.loading-username').show()
$(this).find('.update-success').hide() $(this).find('.update-success').hide()
$(this).find('.update-failed').hide() $(this).find('.update-failed').hide()
$('.update-username form').on 'ajax:complete', -> $('.update-username').on 'ajax:complete', ->
$('.loading-username').hide()
$(this).find('.btn-save').enable() $(this).find('.btn-save').enable()
$(this).find('.loading-gif').hide() $(this).find('.loading-gif').hide()
$('.update-notifications').on 'ajax:complete', -> $('.update-notifications').on 'ajax:success', (e, data) ->
$(this).find('.btn-save').enable() if data.saved
new Flash("Notification settings saved", "notice")
else
new Flash("Failed to save new settings", "alert")
$('.js-choose-user-avatar-button').bind "click", -> @bindEvents()
form = $(this).closest("form")
form.find(".js-user-avatar-input").click()
$('.js-user-avatar-input').bind "change", -> cropOpts =
form = $(this).closest("form") filename: '.js-avatar-filename'
filename = $(this).val().replace(/^.*[\\\/]/, '') previewImage: '.avatar-image .avatar'
form.find(".js-avatar-filename").text(filename) 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])
avatarBlob = @avatarGlCrop.getBlob()
formData.append('user[avatar]', avatarBlob, 'avatar.png') if avatarBlob?
$.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
$(document).on 'focusout.ssh_key', '#key_key', ->
$title = $('#key_title')
comment = $(@).val().match(/^\S+ \S+ (.+)\n?$/)
if comment && comment.length > 1 && $title.val() == ''
$title.val(comment[1]).change()

View file

@ -11,7 +11,6 @@ class @Project
$(@).toggleClass('active') $(@).toggleClass('active')
url = $("#project_clone").val() url = $("#project_clone").val()
console.log("url",url)
# Update the input field # Update the input field
$('#project_clone').val(url) $('#project_clone').val(url)
@ -38,19 +37,20 @@ class @Project
$('.update-notification').on 'click', (e) -> $('.update-notification').on 'click', (e) ->
e.preventDefault() e.preventDefault()
notification_level = $(@).data 'notification-level' notification_level = $(@).data 'notification-level'
$('#notification_level').val(notification_level) label = $(@).data 'notification-title'
$('#notification_setting_level').val(notification_level)
$('#notification-form').submit() $('#notification-form').submit()
label = null
switch notification_level
when 0 then label = ' Disabled '
when 1 then label = ' Participating '
when 2 then label = ' Watching '
when 3 then label = ' Global '
when 4 then label = ' On Mention '
$('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>") $('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>")
$(@).parents('ul').find('li.active').removeClass 'active' $(@).parents('ul').find('li.active').removeClass 'active'
$(@).parent().addClass 'active' $(@).parent().addClass 'active'
$('#notification-form').on 'ajax:success', (e, data) ->
if data.saved
new Flash("Notification settings saved", "notice")
else
new Flash("Failed to save new settings", "alert")
@projectSelectDropdown() @projectSelectDropdown()
projectSelectDropdown: -> projectSelectDropdown: ->

View file

@ -3,3 +3,16 @@ class @ProjectNew
$('.project-edit-container').on 'ajax:before', => $('.project-edit-container').on 'ajax:before', =>
$('.project-edit-container').hide() $('.project-edit-container').hide()
$('.save-project-loader').show() $('.save-project-loader').show()
@toggleSettings()
@toggleSettingsOnclick()
toggleSettings: ->
checked = $("#project_builds_enabled").prop("checked")
if checked
$('.builds-feature').show()
else
$('.builds-feature').hide()
toggleSettingsOnclick: ->
$("#project_builds_enabled").on 'click', @toggleSettings

View file

@ -1,5 +1,37 @@
class @ProjectSelect class @ProjectSelect
constructor: -> constructor: ->
$('.js-projects-dropdown-toggle').each (i, dropdown) ->
$dropdown = $(dropdown)
$dropdown.glDropdown(
filterable: true
filterRemote: true
search:
fields: ['name_with_namespace']
data: (term, callback) ->
finalCallback = (projects) ->
callback projects
if @includeGroups
projectsCallback = (projects) ->
groupsCallback = (groups) ->
data = groups.concat(projects)
finalCallback(data)
Api.groups term, false, groupsCallback
else
projectsCallback = finalCallback
if @groupId
Api.groupProjects @groupId, term, projectsCallback
else
Api.projects term, @orderBy, projectsCallback
url: (project) ->
project.web_url
text: (project) ->
project.name_with_namespace
)
$('.ajax-project-select').each (i, select) -> $('.ajax-project-select').each (i, select) ->
@groupId = $(select).data('group-id') @groupId = $(select).data('group-id')
@includeGroups = $(select).data('include-groups') @includeGroups = $(select).data('include-groups')

View file

@ -1,32 +1,37 @@
class @ProjectsList @ProjectsList =
constructor: -> init: ->
$(".projects-list .js-expand").on 'click', (e) -> $(".projects-list-filter").off('keyup')
e.preventDefault() this.initSearch()
$projectsList = $(this).closest('.projects-list') this.initPagination()
ProjectsList.showPagination($projectsList)
$projectsList.find('li.bottom').hide()
$("#filter_projects").on 'keyup', -> initSearch: ->
ProjectsList.filter_results($("#filter_projects")) @timer = null
$(".projects-list-filter").on('keyup', ->
clearTimeout(@timer)
@timer = setTimeout(ProjectsList.filterResults, 500)
)
@showPagination: ($projectsList) -> filterResults: =>
$projectsList.find('li').show() $('.projects-list-holder').fadeTo(250, 0.5)
$('.gl-pagination').show()
@filter_results: ($element) -> form = null
terms = $element.val() form = $("form#project-filter-form")
filterSelector = $element.data('filter-selector') || 'span.filter-title' search = $(".projects-list-filter").val()
$projectsList = $('.projects-list') project_filter_url = form.attr('action') + '?' + form.serialize()
if not terms $.ajax
ProjectsList.showPagination($projectsList) type: "GET"
else url: form.attr('action')
$projectsList.find('li').each (index) -> data: form.serialize()
$this = $(this) complete: ->
name = $this.find(filterSelector).text() $('.projects-list-holder').fadeTo(250, 1)
success: (data) ->
$('.projects-list-holder').replaceWith(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: project_filter_url}, document.title, project_filter_url
dataType: "json"
if name.toLowerCase().indexOf(terms.toLowerCase()) == -1 initPagination: ->
$this.hide() $('.projects-list-holder .pagination').on('ajax:success', (e, data) ->
else $('.projects-list-holder').replaceWith(data.html)
$this.show() )
$('.gl-pagination').hide()

View file

@ -0,0 +1,87 @@
class @Sidebar
constructor: (currentUser) ->
@sidebar = $('aside')
@addEventListeners()
addEventListeners: ->
@sidebar.on('click', '.sidebar-collapsed-icon', @, @sidebarCollapseClicked)
$('.dropdown').on('hidden.gl.dropdown', @, @onSidebarDropdownHidden)
$('.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) ->
sidebar = e.data
e.preventDefault()
$block = $(@).closest('.block')
sidebar.openDropdown($block);
openDropdown: (blockOrName) ->
$block = if _.isString(blockOrName) then @getBlock(blockOrName) else blockOrName
$block.find('.edit-link').trigger('click')
if not @isOpen()
@setCollapseAfterUpdate($block)
@toggleSidebar('open')
setCollapseAfterUpdate: ($block) ->
$block.addClass('collapse-after-update')
$('.page-with-sidebar').addClass('with-overlay')
onSidebarDropdownHidden: (e) ->
sidebar = e.data
e.preventDefault()
$block = $(@).closest('.block')
sidebar.sidebarDropdownHidden($block)
sidebarDropdownHidden: ($block) ->
if $block.hasClass('collapse-after-update')
$block.removeClass('collapse-after-update')
$('.page-with-sidebar').removeClass('with-overlay')
@toggleSidebar('hide')
triggerOpenSidebar: ->
@sidebar
.find('.js-sidebar-toggle')
.trigger('click')
toggleSidebar: (action = 'toggle') ->
if action is 'toggle'
@triggerOpenSidebar()
if action is 'open'
@triggerOpenSidebar() if not @isOpen()
if action is 'hide'
@triggerOpenSidebar() is @isOpen()
isOpen: ->
@sidebar.is('.right-sidebar-expanded')
getBlock: (name) ->
@sidebar.find(".block.#{name}")

View file

@ -0,0 +1,75 @@
class @Search
constructor: ->
$groupDropdown = $('.js-search-group-dropdown')
$projectDropdown = $('.js-search-project-dropdown')
@eventListeners()
$groupDropdown.glDropdown(
selectable: true
filterable: true
fieldName: 'group_id'
data: (term, callback) ->
Api.groups term, null, (data) ->
data.unshift(
name: 'Any'
)
data.splice 1, 0, 'divider'
callback(data)
id: (obj) ->
obj.id
text: (obj) ->
obj.name
toggleLabel: (obj) ->
"#{$groupDropdown.data('default-label')} #{obj.name}"
clicked: =>
@submitSearch()
)
$projectDropdown.glDropdown(
selectable: true
filterable: true
fieldName: 'project_id'
data: (term, callback) ->
Api.projects term, 'id', (data) ->
data.unshift(
name_with_namespace: 'Any'
)
data.splice 1, 0, 'divider'
callback(data)
id: (obj) ->
obj.id
text: (obj) ->
obj.name_with_namespace
toggleLabel: (obj) ->
"#{$projectDropdown.data('default-label')} #{obj.name_with_namespace}"
clicked: =>
@submitSearch()
)
eventListeners: ->
$(document)
.off 'keyup', '.js-search-input'
.on 'keyup', '.js-search-input', @searchKeyUp
$(document)
.off 'click', '.js-search-clear'
.on 'click', '.js-search-clear', @clearSearchField
submitSearch: ->
$('.js-search-form').submit()
searchKeyUp: ->
$input = $(@)
if $input.val() is ''
$('.js-search-clear').addClass 'hidden'
else
$('.js-search-clear').removeClass 'hidden'
clearSearchField: ->
$('.js-search-input')
.val ''
.trigger 'keyup'
.focus()

View file

@ -1,11 +1,296 @@
class @SearchAutocomplete 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 KEYCODE =
source: search_autocomplete_path + query ESCAPE: 27
minLength: 1 BACKSPACE: 8
select: (event, ui) -> ENTER: 13
location.href = ui.item.url
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()

View file

@ -2,21 +2,27 @@ class @Shortcuts
constructor: -> constructor: ->
@enabledHelp = [] @enabledHelp = []
Mousetrap.reset() Mousetrap.reset()
Mousetrap.bind('?', @selectiveHelp) Mousetrap.bind('?', @onToggleHelp)
Mousetrap.bind('s', Shortcuts.focusSearch) Mousetrap.bind('s', Shortcuts.focusSearch)
Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview)
Mousetrap.bind('t', -> Turbolinks.visit(findFileURL)) if findFileURL? Mousetrap.bind('t', -> Turbolinks.visit(findFileURL)) if findFileURL?
selectiveHelp: (e) => onToggleHelp: (e) =>
Shortcuts.showHelp(e, @enabledHelp) e.preventDefault()
@toggleHelp(@enabledHelp)
toggleMarkdownPreview: (e) =>
$(document).triggerHandler('markdown-preview:toggle', [e])
toggleHelp: (location) ->
$modal = $('#modal-shortcuts')
if $modal.length
$modal.modal('toggle')
return
@showHelp: (e, location) ->
if $('#modal-shortcuts').length > 0
$('#modal-shortcuts').modal('show')
else
url = '/help/shortcuts'
url = gon.relative_url_root + url if gon.relative_url_root?
$.ajax( $.ajax(
url: url, url: gon.shortcuts_path,
dataType: 'script', dataType: 'script',
success: (e) -> success: (e) ->
if location and location.length > 0 if location and location.length > 0
@ -25,7 +31,6 @@ class @Shortcuts
$('.hidden-shortcut').show() $('.hidden-shortcut').show()
$('.js-more-help-button').remove() $('.js-more-help-button').remove()
) )
e.preventDefault()
@focusSearch: (e) -> @focusSearch: (e) ->
$('#search').focus() $('#search').focus()
@ -35,3 +40,14 @@ $(document).on 'click.more_help', '.js-more-help-button', (e) ->
$(@).remove() $(@).remove()
$('.hidden-shortcut').show() $('.hidden-shortcut').show()
e.preventDefault() e.preventDefault()
Mousetrap.stopCallback = (->
defaultStopCallback = Mousetrap.stopCallback
return (e, element, combo) ->
# allowed shortcuts if textarea, input, contenteditable are focused
if ['ctrl+shift+p', 'command+shift+p'].indexOf(combo) != -1
return false
else
return defaultStopCallback.apply(@, arguments)
)()

View file

@ -4,14 +4,8 @@
class @ShortcutsIssuable extends ShortcutsNavigation class @ShortcutsIssuable extends ShortcutsNavigation
constructor: (isMergeRequest) -> constructor: (isMergeRequest) ->
super() super()
Mousetrap.bind('a', -> Mousetrap.bind('a', @openSidebarDropdown.bind(@, 'assignee'))
$('.block.assignee .edit-link').trigger('click') Mousetrap.bind('m', @openSidebarDropdown.bind(@, 'milestone'))
return false
)
Mousetrap.bind('m', ->
$('.block.milestone .edit-link').trigger('click')
return false
)
Mousetrap.bind('r', => Mousetrap.bind('r', =>
@replyWithSelectedText() @replyWithSelectedText()
return false return false
@ -24,7 +18,11 @@ class @ShortcutsIssuable extends ShortcutsNavigation
@nextIssue() @nextIssue()
return false return false
) )
Mousetrap.bind('e', =>
@editIssue()
return false
)
Mousetrap.bind('l', @openSidebarDropdown.bind(@, 'labels'))
if isMergeRequest if isMergeRequest
@enabledHelp.push('.hidden-shortcut.merge_requests') @enabledHelp.push('.hidden-shortcut.merge_requests')
@ -63,3 +61,11 @@ class @ShortcutsIssuable extends ShortcutsNavigation
# Focus the input field # Focus the input field
replyField.focus() replyField.focus()
editIssue: ->
$editBtn = $('.issuable-edit')
Turbolinks.visit($editBtn.attr('href'))
openSidebarDropdown: (name) ->
sidebar.openDropdown(name)
return false

View file

@ -14,6 +14,7 @@ class @ShortcutsNavigation extends Shortcuts
Mousetrap.bind('g m', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests')) Mousetrap.bind('g m', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests'))
Mousetrap.bind('g w', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki')) Mousetrap.bind('g w', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki'))
Mousetrap.bind('g s', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets')) Mousetrap.bind('g s', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets'))
Mousetrap.bind('i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-new-issue'))
@enabledHelp.push('.hidden-shortcut.project') @enabledHelp.push('.hidden-shortcut.project')
@findAndFollowLink: (selector) -> @findAndFollowLink: (selector) ->

View file

@ -1,11 +1,9 @@
$(document).on("click", '.toggle-nav-collapse', (e) ->
e.preventDefault()
collapsed = 'page-sidebar-collapsed' collapsed = 'page-sidebar-collapsed'
expanded = 'page-sidebar-expanded' expanded = 'page-sidebar-expanded'
toggleSidebar = ->
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}") $('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
$('header').toggleClass("header-collapsed header-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") $('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' }) $.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
@ -14,4 +12,15 @@ $(document).on("click", '.toggle-nav-collapse', (e) ->
niceScrollBars.updateScrollBar(); niceScrollBars.updateScrollBar();
), 300 ), 300
$(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', (e) ->
e.preventDefault()
toggleSidebar()
) )
$ ->
size = bp.getBreakpointSize()
if size is "xs" or size is "sm"
if $('.page-with-sidebar').hasClass(expanded)
toggleSidebar()

View file

@ -1,17 +1,21 @@
class @Subscription class @Subscription
constructor: (url) -> constructor: (container) ->
$(".subscribe-button").unbind("click").click (event)=> $container = $(container)
@url = $container.attr('data-url')
@subscribe_button = $container.find('.js-subscribe-button')
@subscription_status = $container.find('.subscription-status')
@subscribe_button.unbind('click').click(@toggleSubscription)
toggleSubscription: (event) =>
btn = $(event.currentTarget) btn = $(event.currentTarget)
action = btn.find("span").text() action = btn.find('span').text()
current_status = $(".subscription-status").attr("data-status") current_status = @subscription_status.attr('data-status')
btn.prop("disabled", true) btn.addClass('disabled')
$.post url, =>
btn.prop("disabled", false)
status = if current_status == "subscribed" then "unsubscribed" else "subscribed"
$(".subscription-status").attr("data-status", status)
action = if status == "subscribed" then "Unsubscribe" else "Subscribe"
btn.find("span").text(action)
$(".subscription-status>div").toggleClass("hidden")
$.post @url, =>
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'
btn.find('span').text(action)
@subscription_status.find('>div').toggleClass('hidden')

View file

@ -0,0 +1,110 @@
class @Todos
constructor: (opts = {}) ->
{
@el = $('.js-todos-options')
} = opts
@perPage = @el.data('perPage')
@clearListeners()
@initBtnListeners()
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()
e.stopImmediatePropagation()
$this = $(e.currentTarget)
$this.disable()
$.ajax
type: 'POST'
url: $this.attr('href')
dataType: 'json'
data: '_method': 'delete'
success: (data) =>
@redirectIfNeeded data.count
@clearDone $this.closest('li')
@updateBadges data
allDoneClicked: (e) =>
e.preventDefault()
e.stopImmediatePropagation()
$this = $(e.currentTarget)
$this.disable()
$.ajax
type: 'POST'
url: $this.attr('href')
dataType: 'json'
data: '_method': 'delete'
success: (data) =>
$this.remove()
$('.js-todos-list').remove()
@updateBadges data
clearDone: ($row) ->
$ul = $row.closest('ul')
$row.remove()
if not $ul.find('li').length
$ul.parents('.panel').remove()
updateBadges: (data) ->
$('.todos-pending .badge, .todos-pending-count').text data.count
$('.todos-done .badge').text data.done_count
getTotalPages: ->
@el.data('totalPages')
getCurrentPage: ->
@el.data('currentPage')
getTodosPerPage: ->
@el.data('perPage')
redirectIfNeeded: (total) ->
currPages = @getTotalPages()
currPage = @getCurrentPage()
# Refresh if no remaining Todos
if not total
location.reload()
return
# Do nothing if no pagination
return if not currPages
newPages = Math.ceil(total / @getTodosPerPage())
url = location.href # Includes query strings
# If new total of pages is different than we have now
if newPages isnt currPages
# Redirect to previous page if there's one available
if currPages > 1 and currPage is currPages
pageParams =
page: currPages - 1
url = gl.utils.mergeUrlParams(pageParams, url)
Turbolinks.visit(url)
goToTodoUrl: (e)->
todoLink = $(this).data('url')
return unless todoLink
# Allow Meta-Click or Mouse3-click to open in a new tab
if e.metaKey or e.which is 2
e.preventDefault()
window.open(todoLink,'_blank')
else
Turbolinks.visit(todoLink)

View file

@ -1,10 +1,17 @@
class @User class @User
constructor: -> constructor: (@opts) ->
$('.profile-groups-avatars').tooltip("placement": "top") $('.profile-groups-avatars').tooltip("placement": "top")
new ProjectsList()
@initTabs()
$('.hide-project-limit-message').on 'click', (e) -> $('.hide-project-limit-message').on 'click', (e) ->
path = '/' path = '/'
$.cookie('hide_project_limit_message', 'false', { path: path }) $.cookie('hide_project_limit_message', 'false', { path: path })
$(@).parents('.project-limit-message').remove() $(@).parents('.project-limit-message').remove()
e.preventDefault() e.preventDefault()
initTabs: ->
new UserTabs(
parentEl: '.user-profile'
action: @opts.action
)

View file

@ -0,0 +1,153 @@
# UserTabs
#
# Handles persisting and restoring the current tab selection and lazily-loading
# content on the Users#show page.
#
# ### Example Markup
#
# <ul class="nav-links">
# <li class="activity-tab active">
# <a data-action="activity" data-target="#activity" data-toggle="tab" href="/u/username">
# Activity
# </a>
# </li>
# <li class="groups-tab">
# <a data-action="groups" data-target="#groups" data-toggle="tab" href="/u/username/groups">
# Groups
# </a>
# </li>
# <li class="contributed-tab">
# <a data-action="contributed" data-target="#contributed" data-toggle="tab" href="/u/username/contributed">
# Contributed projects
# </a>
# </li>
# <li class="projects-tab">
# <a data-action="projects" data-target="#projects" data-toggle="tab" href="/u/username/projects">
# Personal projects
# </a>
# </li>
# <li class="snippets-tab">
# <a data-action="snippets" data-target="#snippets" data-toggle="tab" href="/u/username/snippets">
# </a>
# </li>
# </ul>
#
# <div class="tab-content">
# <div class="tab-pane" id="activity">
# Activity Content
# </div>
# <div class="tab-pane" id="groups">
# Groups Content
# </div>
# <div class="tab-pane" id="contributed">
# Contributed projects content
# </div>
# <div class="tab-pane" id="projects">
# Projects content
# </div>
# <div class="tab-pane" id="snippets">
# Snippets content
# </div>
# </div>
#
# <div class="loading-status">
# <div class="loading">
# Loading Animation
# </div>
# </div>
#
class @UserTabs
constructor: (opts) ->
{
@action = 'activity'
@defaultAction = 'activity'
@parentEl = $(document)
} = opts
# Make jQuery object if selector is provided
@parentEl = $(@parentEl) if typeof @parentEl is 'string'
# Store the `location` object, allowing for easier stubbing in tests
@_location = location
# Set tab states
@loaded = {}
for item in @parentEl.find('.nav-links a')
@loaded[$(item).attr 'data-action'] = false
# Actions
@actions = Object.keys @loaded
@bindEvents()
# Set active tab
@action = @defaultAction if @action is 'show'
@activateTab(@action)
bindEvents: ->
# Toggle event listeners
@parentEl
.off 'shown.bs.tab', '.nav-links a[data-toggle="tab"]'
.on 'shown.bs.tab', '.nav-links a[data-toggle="tab"]', @tabShown
tabShown: (event) =>
$target = $(event.target)
action = $target.data('action')
source = $target.attr('href')
@setTab(source, action)
@setCurrentAction(action)
activateTab: (action) ->
@parentEl.find(".nav-links .js-#{action}-tab a").tab('show')
setTab: (source, action) ->
return if @loaded[action] is true
if action is 'activity'
@loadActivities(source)
if action in ['groups', 'contributed', 'projects', 'snippets']
@loadTab(source, action)
loadTab: (source, action) ->
$.ajax
beforeSend: => @toggleLoading(true)
complete: => @toggleLoading(false)
dataType: 'json'
type: 'GET'
url: "#{source}.json"
success: (data) =>
tabSelector = 'div#' + action
@parentEl.find(tabSelector).html(data.html)
@loaded[action] = true
loadActivities: (source) ->
return if @loaded['activity'] is true
$calendarWrap = @parentEl.find('.user-calendar')
$calendarWrap.load($calendarWrap.data('href'))
new Activities()
@loaded['activity'] = true
toggleLoading: (status) ->
@parentEl.find('.loading-status .loading').toggle(status)
setCurrentAction: (action) ->
# Remove possible actions from URL
regExp = new RegExp('\/(' + @actions.join('|') + ')(\.html)?\/?$')
new_state = @_location.pathname
new_state = new_state.replace(/\/+$/, "") # remove trailing slashes
new_state = new_state.replace(regExp, '')
# Append the new action if we're on a tab other than 'activity'
unless action == @defaultAction
new_state += "/#{action}"
# Ensure parameters and hash come along for the ride
new_state += @_location.search + @_location.hash
history.replaceState {turbolinks: true, url: new_state}, document.title, new_state
new_state

View file

@ -1,12 +1,214 @@
class @UsersSelect class @UsersSelect
constructor: -> constructor: (currentUser) ->
@usersPath = "/autocomplete/users.json" @usersPath = "/autocomplete/users.json"
@userPath = "/autocomplete/users/:id.json" @userPath = "/autocomplete/users/:id.json"
if currentUser?
@currentUser = JSON.parse(currentUser)
$('.js-user-search').each (i, dropdown) =>
$dropdown = $(dropdown)
@projectId = $dropdown.data('project-id')
@showCurrentUser = $dropdown.data('current-user')
showNullUser = $dropdown.data('null-user')
showAnyUser = $dropdown.data('any-user')
firstUser = $dropdown.data('first-user')
@authorId = $dropdown.data('author-id')
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) =>
@users term, (users) =>
if term.length is 0
showDivider = 0
if firstUser
# Move current user to the front of the list
for obj, index in users
if obj.username == firstUser
users.splice(index, 1)
users.unshift(obj)
break
if showNullUser
showDivider += 1
users.unshift(
beforeDivider: true
name: 'Unassigned',
id: 0
)
if showAnyUser
showDivider += 1
name = showAnyUser
name = 'Any User' if name == true
anyUser = {
beforeDivider: true
name: name,
id: null
}
users.unshift(anyUser)
if showDivider
users.splice(showDivider, 0, "divider")
# Send the data back
callback users
filterable: true
filterRemote: true
search:
fields: ['name', 'username']
selectable: true
fieldName: $dropdown.data('field-name')
toggleLabel: (selected) ->
if selected && 'id' of selected
selected.name
else
defaultLabel
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
Issuable.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 user.beforeDivider?
"<li>
<a href='#' class='#{selected}'>
#{user.name}
</a>
</li>"
else
if avatar
img = "<img src='#{avatar}' class='avatar avatar-inline' width='30' />"
# 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>"
listWithUserName = "<span class='dropdown-menu-user-username'>
#{username}
</span>"
listClosingTags = "</a>
</li>"
if username is ''
listWithUserName = ''
listWithName + listWithUserName + listClosingTags
)
$('.ajax-users-select').each (i, select) => $('.ajax-users-select').each (i, select) =>
@projectId = $(select).data('project-id') @projectId = $(select).data('project-id')
@groupId = $(select).data('group-id') @groupId = $(select).data('group-id')
@showCurrentUser = $(select).data('current-user') @showCurrentUser = $(select).data('current-user')
@authorId = $(select).data('author-id')
showNullUser = $(select).data('null-user') showNullUser = $(select).data('null-user')
showAnyUser = $(select).data('any-user') showAnyUser = $(select).data('any-user')
showEmailUser = $(select).data('email-user') showEmailUser = $(select).data('email-user')
@ -112,6 +314,7 @@ class @UsersSelect
project_id: @projectId project_id: @projectId
group_id: @groupId group_id: @groupId
current_user: @showCurrentUser current_user: @showCurrentUser
author_id: @authorId
dataType: "json" dataType: "json"
).done (users) -> ).done (users) ->
callback(users) callback(users)

View file

@ -42,7 +42,7 @@ class @ZenMode
$(e.currentTarget).trigger('zen_mode:leave') $(e.currentTarget).trigger('zen_mode:leave')
$(document).on 'zen_mode:enter', (e) => $(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) => $(document).on 'zen_mode:leave', (e) =>
@exit() @exit()

View file

@ -9,6 +9,7 @@
*= require_self *= require_self
*= require dropzone/basic *= require dropzone/basic
*= require cal-heatmap *= require cal-heatmap
*= require cropper.css
*/ */
/* /*
@ -24,12 +25,6 @@
*/ */
@import "framework"; @import "framework";
/*
* NProgress load bar css
*/
@import 'nprogress';
@import 'nprogress-bootstrap';
/* /*
* Font icons * Font icons
*/ */

View file

@ -5,12 +5,14 @@
@import 'framework/tw_bootstrap'; @import 'framework/tw_bootstrap';
@import "framework/layout"; @import "framework/layout";
@import "framework/animations.scss";
@import "framework/avatar.scss"; @import "framework/avatar.scss";
@import "framework/blocks.scss"; @import "framework/blocks.scss";
@import "framework/buttons.scss"; @import "framework/buttons.scss";
@import "framework/calendar.scss"; @import "framework/calendar.scss";
@import "framework/callout.scss"; @import "framework/callout.scss";
@import "framework/common.scss"; @import "framework/common.scss";
@import "framework/dropdowns.scss";
@import "framework/files.scss"; @import "framework/files.scss";
@import "framework/filters.scss"; @import "framework/filters.scss";
@import "framework/flash.scss"; @import "framework/flash.scss";
@ -24,8 +26,10 @@
@import "framework/lists.scss"; @import "framework/lists.scss";
@import "framework/markdown_area.scss"; @import "framework/markdown_area.scss";
@import "framework/mobile.scss"; @import "framework/mobile.scss";
@import "framework/modal.scss";
@import "framework/nav.scss"; @import "framework/nav.scss";
@import "framework/pagination.scss"; @import "framework/pagination.scss";
@import "framework/progress.scss";
@import "framework/panels.scss"; @import "framework/panels.scss";
@import "framework/selects.scss"; @import "framework/selects.scss";
@import "framework/sidebar.scss"; @import "framework/sidebar.scss";

View file

@ -0,0 +1,72 @@
// This file is based off animate.css 3.5.1, available here:
// https://github.com/daneden/animate.css/blob/3.5.1/animate.css
//
// animate.css - http://daneden.me/animate
// Version - 3.5.1
// Licensed under the MIT license - http://opensource.org/licenses/MIT
//
// Copyright (c) 2016 Daniel Eden
.animated {
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
.animated.infinite {
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
}
.animated.hinge {
-webkit-animation-duration: 2s;
animation-duration: 2s;
}
.animated.flipOutX,
.animated.flipOutY,
.animated.bounceIn,
.animated.bounceOut {
-webkit-animation-duration: .75s;
animation-duration: .75s;
}
@-webkit-keyframes pulse {
from {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
50% {
-webkit-transform: scale3d(1.05, 1.05, 1.05);
transform: scale3d(1.05, 1.05, 1.05);
}
to {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
@keyframes pulse {
from {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
50% {
-webkit-transform: scale3d(1.05, 1.05, 1.05);
transform: scale3d(1.05, 1.05, 1.05);
}
to {
-webkit-transform: scale3d(1, 1, 1);
transform: scale3d(1, 1, 1);
}
}
.pulse {
-webkit-animation-name: pulse;
animation-name: pulse;
}

View file

@ -16,7 +16,7 @@
} }
&.group-avatar, &.project-avatar, &.avatar-tile { &.group-avatar, &.project-avatar, &.avatar-tile {
@include border-radius(0px); @include border-radius(0);
} }
&.s16 { width: 16px; height: 16px; margin-right: 6px; } &.s16 { width: 16px; height: 16px; margin-right: 6px; }
@ -28,6 +28,7 @@
&.s46 { width: 46px; height: 46px; margin-right: 15px; } &.s46 { width: 46px; height: 46px; margin-right: 15px; }
&.s48 { width: 48px; height: 48px; margin-right: 10px; } &.s48 { width: 48px; height: 48px; margin-right: 10px; }
&.s60 { width: 60px; height: 60px; margin-right: 12px; } &.s60 { width: 60px; height: 60px; margin-right: 12px; }
&.s70 { width: 70px; height: 70px; margin-right: 14px; }
&.s90 { width: 90px; height: 90px; margin-right: 15px; } &.s90 { width: 90px; height: 90px; margin-right: 15px; }
&.s110 { width: 110px; height: 110px; margin-right: 15px; } &.s110 { width: 110px; height: 110px; margin-right: 15px; }
&.s140 { width: 140px; height: 140px; margin-right: 20px; } &.s140 { width: 140px; height: 140px; margin-right: 20px; }

View file

@ -1,5 +1,5 @@
.light-well { .light-well {
background-color: #f8fafc; background-color: $background-color;
padding: 15px; padding: 15px;
} }
@ -18,12 +18,12 @@
line-height: 36px; line-height: 36px;
} }
.gray-content-block { .row-content-block {
margin-top: 0; margin-top: 0;
margin-bottom: -$gl-padding; margin-bottom: -$gl-padding;
background-color: $background-color; background-color: $background-color;
padding: $gl-padding; padding: $gl-padding;
margin-bottom: 0px; margin-bottom: 0;
border-top: 1px solid $border-color; border-top: 1px solid $border-color;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
color: $gl-gray; color: $gl-gray;
@ -66,7 +66,7 @@
} }
.oneline { .oneline {
line-height: 42px; line-height: 35px;
} }
> p:last-child { > p:last-child {
@ -81,6 +81,11 @@
margin-left: 10px; margin-left: 10px;
} }
} }
&.build-content {
background-color: $white-light;
border-top: none;
}
} }
.cover-block { .cover-block {
@ -107,15 +112,37 @@
margin: 0; margin: 0;
font-size: 23px; font-size: 23px;
font-weight: normal; font-weight: normal;
margin: 16px 0 5px 0; margin: 16px 0 5px;
color: #4c4e54; color: #4c4e54;
font-size: 23px; font-size: 23px;
line-height: 1.1; line-height: 1.1;
h1 {
color: $gl-gray-dark;
margin-bottom: 6px;
font-size: 23px;
}
.visibility-icon {
display: inline-block;
margin-left: 5px;
font-size: 18px;
color: $gray;
}
p {
padding: 0 $gl-padding;
color: #5c5d5e;
}
} }
.cover-desc { .cover-desc {
padding: 0 $gl-padding 3px; padding: 0 $gl-padding 3px;
color: $gl-text-color; color: $gl-text-color;
&.username:last-child {
padding-bottom: $gl-padding;
}
} }
.cover-controls { .cover-controls {
@ -128,6 +155,41 @@
right: auto; right: auto;
} }
} }
&.groups-cover-block {
background: $white-light;
border-bottom: 1px solid $border-color;
text-align: left;
padding: 24px 0;
.group-info {
.cover-title {
margin-top: 9px;
}
p {
margin-bottom: 0;
}
}
@media (max-width: $screen-xs-max) {
text-align: center;
.avatar {
float: none;
}
}
}
.group-info {
h1 {
display: inline;
font-weight: normal;
font-size: 24px;
color: $gl-title-color;
}
}
} }
.block-connector { .block-connector {
@ -153,3 +215,7 @@
float: right; float: right;
} }
} }
.content-block-small {
padding: 10px 0;
}

View file

@ -7,7 +7,8 @@
&:focus, &:focus,
&:active { &:active {
outline: none; outline: none;
@include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12)); background-color: $btn-active-gray;
@include box-shadow($gl-btn-active-background);
} }
} }
@ -27,8 +28,9 @@
color: $color; color: $color;
} }
&:active { &:active,
@include box-shadow (inset 0 0 4px rgba(0, 0, 0, 0.12)); &.active {
@include box-shadow ($gl-btn-active-background);
background-color: $dark; background-color: $dark;
border-color: $border-dark; border-color: $border-dark;
@ -37,37 +39,43 @@
} }
@mixin btn-green { @mixin btn-green {
@include btn-color($green-light, $border-green-light, $green-normal, $border-green-normal, $green-dark, $border-green-dark, #FFFFFF); @include btn-color($green-light, $border-green-light, $green-normal, $border-green-normal, $green-dark, $border-green-dark, #fff);
} }
@mixin btn-blue { @mixin btn-blue {
@include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, #FFFFFF); @include btn-color($blue-light, $border-blue-light, $blue-normal, $border-blue-normal, $blue-dark, $border-blue-dark, #fff);
} }
@mixin btn-blue-medium { @mixin btn-blue-medium {
@include btn-color($blue-medium-light, $border-blue-light, $blue-medium, $border-blue-normal, $blue-medium-dark, $border-blue-dark, #FFFFFF); @include btn-color($blue-medium-light, $border-blue-light, $blue-medium, $border-blue-normal, $blue-medium-dark, $border-blue-dark, #fff);
} }
@mixin btn-orange { @mixin btn-orange {
@include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, #FFFFFF); @include btn-color($orange-light, $border-orange-light, $orange-normal, $border-orange-normal, $orange-dark, $border-orange-dark, #fff);
} }
@mixin btn-red { @mixin btn-red {
@include btn-color($red-light, $border-red-light, $red-normal, $border-red-normal, $red-dark, $border-red-dark, #FFFFFF); @include btn-color($red-light, $border-red-light, $red-normal, $border-red-normal, $red-dark, $border-red-dark, #fff);
} }
@mixin btn-gray { @mixin btn-gray {
@include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, #313236); @include btn-color($gray-light, $border-gray-light, $gray-normal, $border-gray-light, $gray-dark, $border-gray-dark, $gl-gray-dark);
} }
@mixin btn-white { @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 { .btn {
@include btn-default; @include btn-default;
@include btn-white; @include btn-white;
color: $gl-text-color;
&:focus:active {
outline: 0;
}
&.btn-small, &.btn-small,
&.btn-sm { &.btn-sm {
padding: 4px 10px; padding: 4px 10px;
@ -121,7 +129,7 @@
margin-right: 7px; margin-right: 7px;
float: left; float: left;
&:last-child { &:last-child {
margin-right: 0px; margin-right: 0;
} }
&.btn-xs { &.btn-xs {
margin-right: 3px; margin-right: 3px;
@ -130,6 +138,31 @@
&.disabled { &.disabled {
pointer-events: auto !important; pointer-events: auto !important;
} }
&[disabled] {
pointer-events: none !important;
}
.caret {
margin-left: 5px;
}
}
.btn-lg {
padding: 12px 20px;
}
.btn-transparent {
color: $btn-transparent-color;
background-color: transparent;
border: 0;
&:hover,
&:active,
&:focus {
background-color: transparent;
box-shadow: none;
}
} }
.btn-block { .btn-block {
@ -146,7 +179,7 @@
margin-right: 7px; margin-right: 7px;
float: left; float: left;
&:last-child { &:last-child {
margin-right: 0px; margin-right: 0;
} }
} }
} }
@ -179,9 +212,49 @@
} }
.active { .active {
@include box-shadow(inset 0 0 4px rgba(0, 0, 0, 0.12)); @include box-shadow($gl-btn-active-background);
border: 1px solid #c6cacf !important; border: 1px solid #c6cacf !important;
background-color: #e4e7ed !important; background-color: #e4e7ed !important;
} }
} }
.btn-loading {
&:not(.disabled) .fa {
display: none;
}
.fa {
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%);
}
.btn-build {
margin-left: 10px;
i {
color: $gl-icon-color;
}
}

View file

@ -1,3 +1,9 @@
.calender-block {
@media (min-width: $screen-sm-min) and (max-width: $screen-lg-min) {
overflow-x: scroll;
}
}
.user-calendar-activities { .user-calendar-activities {
.calendar_onclick_hr { .calendar_onclick_hr {
padding: 0; padding: 0;
@ -33,19 +39,23 @@
} }
.q2 { .q2 {
fill: #ACD5F2 !important; fill: #acd5f2 !important;
} }
.q3 { .q3 {
fill: #7FA8D1 !important; fill: #7fa8d1 !important;
} }
.q4 { .q4 {
fill: #49729B !important; fill: #49729b !important;
} }
.q5 { .q5 {
fill: #254E77 !important; fill: #254e77 !important;
}
.future {
visibility: hidden;
} }
.domain-background { .domain-background {

View file

@ -39,6 +39,6 @@
} }
.bs-callout-success { .bs-callout-success {
background-color: #dff0d8; background-color: #dff0d8;
border-color: #5cA64d; border-color: #5ca64d;
color: #3c763d; color: #3c763d;
} }

View file

@ -1,18 +1,25 @@
/** COLORS **/ /** COLORS **/
.cgray { color: $gl-gray; } .cgray { color: $gl-gray; }
.clgray { color: #BBB } .clgray { color: #bbb }
.cred { color: $gl-text-red; } .cred { color: $gl-text-red; }
.cgreen { color: $gl-text-green; } .cgreen { color: $gl-text-green; }
.cdark { color: #444 } .cdark { color: #444 }
/** COMMON CLASSES **/ /** COMMON CLASSES **/
.prepend-top-0 { margin-top: 0; }
.prepend-top-5 { margin-top: 5px; }
.prepend-top-10 { margin-top: 10px } .prepend-top-10 { margin-top: 10px }
.prepend-top-default { margin-top: $gl-padding !important; } .prepend-top-default { margin-top: $gl-padding !important; }
.prepend-top-20 { margin-top: 20px } .prepend-top-20 { margin-top: 20px }
.prepend-left-5 { margin-left: 5px }
.prepend-left-10 { margin-left: 10px } .prepend-left-10 { margin-left: 10px }
.prepend-left-default { margin-left: $gl-padding; }
.prepend-left-20 { margin-left: 20px } .prepend-left-20 { margin-left: 20px }
.append-right-5 { margin-right: 5px }
.append-right-10 { margin-right: 10px } .append-right-10 { margin-right: 10px }
.append-right-default { margin-right: $gl-padding; }
.append-right-20 { margin-right: 20px } .append-right-20 { margin-right: 20px }
.append-bottom-0 { margin-bottom: 0 }
.append-bottom-10 { margin-bottom: 10px } .append-bottom-10 { margin-bottom: 10px }
.append-bottom-15 { margin-bottom: 15px } .append-bottom-15 { margin-bottom: 15px }
.append-bottom-20 { margin-bottom: 20px } .append-bottom-20 { margin-bottom: 20px }
@ -45,7 +52,7 @@ pre {
} }
&.well-pre { &.well-pre {
border: 1px solid #EEE; border: 1px solid #eee;
background: #f9f9f9; background: #f9f9f9;
border-radius: 0; border-radius: 0;
color: #555; color: #555;
@ -56,21 +63,6 @@ hr {
margin: $gl-padding 0; margin: $gl-padding 0;
} }
.dropdown-menu > li > a {
text-shadow: none;
}
.dropdown-menu-align-right {
left: auto;
right: 0px;
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background: $gl-primary;
color: #FFF;
}
.str-truncated { .str-truncated {
@include str-truncated; @include str-truncated;
} }
@ -112,7 +104,7 @@ span.update-author {
} }
.user-mention { .user-mention {
color: #2FA0BB; color: #2fa0bb;
font-weight: bold; font-weight: bold;
} }
@ -134,13 +126,6 @@ p.time {
height: 150px; height: 150px;
} }
// Fixes alignment on notes.
.new_note {
label {
text-align: left;
}
}
// Fix issue with notes & lists creating a bunch of bottom borders. // Fix issue with notes & lists creating a bunch of bottom borders.
li.note { li.note {
img { max-width: 100% } img { max-width: 100% }
@ -196,9 +181,9 @@ li.note {
.error-message { .error-message {
padding: 10px; padding: 10px;
background: #C67; background: #c67;
margin: 0; margin: 0;
color: #FFF; color: #fff;
a { a {
color: #fff; color: #fff;
@ -209,7 +194,7 @@ li.note {
.browser-alert { .browser-alert {
padding: 10px; padding: 10px;
text-align: center; text-align: center;
background: #C67; background: #c67;
color: #fff; color: #fff;
font-weight: bold; font-weight: bold;
a { a {
@ -280,7 +265,7 @@ img.emoji {
table { table {
td.permission-x { td.permission-x {
background: #D9EDF7 !important; background: #d9edf7 !important;
text-align: center; text-align: center;
} }
} }
@ -289,7 +274,7 @@ table {
float: left; float: left;
text-align: center; text-align: center;
font-size: 32px; font-size: 32px;
color: #AAA; color: #aaa;
width: 60px; width: 60px;
} }
@ -301,8 +286,11 @@ table {
} }
.btn-sign-in { .btn-sign-in {
margin-top: 8px;
text-shadow: none; text-shadow: none;
@media (min-width: $screen-sm-min) {
margin-top: 8px;
}
} }
.side-filters { .side-filters {
@ -356,7 +344,7 @@ table {
.profiler-button, .profiler-button,
.profiler-controls { .profiler-controls {
border-color: #EEE !important; border-color: #eee !important;
} }
} }
@ -384,7 +372,7 @@ table {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
width: 250px !important; min-width: 250px;
visibility: hidden; visibility: hidden;
} }
} }

View file

@ -0,0 +1,523 @@
.caret {
display: inline-block;
width: 0;
height: 0;
margin-left: 2px;
vertical-align: middle;
border-top: $caret-width-base dashed;
border-right: $caret-width-base solid transparent;
border-left: $caret-width-base solid transparent;
}
.btn-group {
.caret {
margin-left: 0;
}
}
.dropdown {
position: relative;
}
.open {
.dropdown-menu {
display: block;
}
.dropdown-menu-toggle {
border-color: $dropdown-toggle-hover-border-color;
.fa {
color: $dropdown-toggle-hover-icon-color;
}
}
}
.dropdown-menu-toggle {
position: relative;
width: 160px;
padding: 6px 20px 6px 10px;
background-color: $dropdown-toggle-bg;
color: $dropdown-toggle-color;
font-size: 15px;
text-align: left;
border: 1px solid $dropdown-toggle-border-color;
border-radius: $border-radius-base;
outline: 0;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
.fa {
position: absolute;
top: 50%;
right: 6px;
margin-top: -4px;
color: $dropdown-toggle-icon-color;
font-size: 10px;
}
&:hover, {
border-color: $dropdown-toggle-hover-border-color;
.fa {
color: $dropdown-toggle-hover-icon-color;
}
}
}
.dropdown-menu {
display: none;
position: absolute;
top: 100%;
left: 0;
z-index: 9;
width: 240px;
margin-top: 2px;
margin-bottom: 0;
font-size: 15px;
font-weight: normal;
padding: 10px 0;
background-color: $dropdown-bg;
border: 1px solid $dropdown-border-color;
border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color;
&.is-loading {
.dropdown-content {
display: none;
}
.dropdown-loading {
display: block;
}
}
ul {
margin: 0;
padding: 0;
}
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;
margin-bottom: 8px;
background-color: $dropdown-divider-color;
}
a {
display: block;
position: relative;
padding-left: 10px;
padding-right: 10px;
color: $dropdown-link-color;
line-height: 34px;
text-overflow: ellipsis;
border-radius: 2px;
white-space: nowrap;
overflow: hidden;
&:hover,
&:focus,
&.is-focused {
background-color: $dropdown-link-hover-bg;
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;
}
}
.dropdown-menu-paging {
.dropdown-page-two,
.dropdown-menu-back {
display: none;
}
&.is-page-two {
.dropdown-page-one {
display: none;
}
.dropdown-page-two,
.dropdown-menu-back {
display: block;
}
.dropdown-content {
padding: 0 10px;
}
}
}
.dropdown-menu-user {
.avatar {
float: left;
width: 30px;
height: 30px;
margin: 0 10px 0 0;
}
}
.dropdown-menu-user-link {
padding-top: 10px;
padding-bottom: 7px;
}
.dropdown-menu-user-full-name {
display: block;
font-weight: 500;
line-height: 16px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.dropdown-menu-user-username {
display: block;
line-height: 16px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.dropdown-select {
width: $dropdown-width;
}
.dropdown-menu-align-right {
left: auto;
right: 0;
}
.dropdown-menu-selectable {
a {
padding-left: 25px;
&.is-active {
&::before {
content: "\f00c";
position: absolute;
left: 5px;
top: 50%;
margin-top: -7px;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
}
}
.dropdown-title {
position: relative;
padding: 0 25px 10px;
margin: 0 10px 10px;
font-weight: 600;
line-height: 1;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
border-bottom: 1px solid $dropdown-divider-color;
overflow: hidden;
}
.dropdown-title-button {
position: absolute;
top: 0;
padding: 0;
color: $dropdown-title-btn-color;
font-size: 14px;
border: 0;
background: none;
outline: 0;
&:hover {
color: darken($dropdown-title-btn-color, 15%);
}
}
.dropdown-menu-close {
right: 5px;
width: 20px;
height: 20px;
top: -3px;
}
.dropdown-menu-back {
left: 7px;
top: 2px;
}
.dropdown-input {
position: relative;
margin-bottom: 10px;
padding: 0 10px;
.fa {
position: absolute;
top: 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, .default-dropdown-input {
width: 100%;
padding: 0 7px;
color: $dropdown-input-color;
line-height: 30px;
border: 1px solid $dropdown-divider-color;
border-radius: 2px;
outline: 0;
&:focus {
color: $dropdown-link-color;
border-color: $dropdown-input-focus-border;
box-shadow: 0 0 4px $dropdown-input-focus-shadow;
~ .fa {
color: $dropdown-link-color;
}
}
&:hover {
~ .fa {
color: $dropdown-link-color;
}
}
}
.dropdown-content {
max-height: 215px;
overflow-y: scroll;
}
.dropdown-footer {
padding-top: 10px;
margin-top: 10px;
font-size: 13px;
border-top: 1px solid $dropdown-divider-color;
}
.dropdown-due-date-footer {
padding-top: 0;
margin-left: 10px;
margin-right: 10px;
border-top: 0;
}
.dropdown-footer-list {
font-size: 14px;
a {
padding-left: 10px;
}
}
.dropdown-loading {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: none;
z-index: 9;
background-color: $dropdown-loading-bg;
font-size: 28px;
.fa {
position: absolute;
top: 50%;
left: 50%;
margin-top: -14px;
margin-left: -14px;
}
}
.dropdown-label-box {
position: relative;
top: 3px;
margin-right: 5px;
display: inline-block;
width: 15px;
height: 15px;
border-radius: $border-radius-base;
}
.dropdown-menu-due-date {
.dropdown-content {
max-height: 230px;
}
.ui-widget {
table {
margin: 0;
}
&.ui-datepicker-inline {
padding: 0 10px;
border: 0;
width: 100%;
}
.ui-datepicker-header {
padding: 0 8px 10px;
border: 0;
.ui-icon {
background: none;
font-size: 20px;
text-indent: 0;
&:before {
display: block;
position: relative;
top: -2px;
color: $dropdown-title-btn-color;
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
}
.ui-state-active,
.ui-state-hover {
color: $md-link-color;
background-color: $calendar-hover-bg;
}
.ui-datepicker-prev,
.ui-datepicker-next {
top: 0;
height: 15px;
cursor: pointer;
&:hover {
background-color: transparent;
border: 0;
.ui-icon:before {
color: $md-link-color;
}
}
}
.ui-datepicker-prev {
left: 0;
.ui-icon:before {
content: '\f104';
text-align: left;
}
}
.ui-datepicker-next {
right: 0;
.ui-icon:before {
content: '\f105';
text-align: right;
}
}
td {
padding: 0;
border: 1px solid $calendar-border-color;
&:first-child {
border-left: 0;
}
&:last-child {
border-right: 0;
}
a {
line-height: 17px;
border: 0;
border-radius: 0;
}
}
.ui-datepicker-title {
color: $gl-gray;
font-size: 15px;
line-height: 1;
font-weight: normal;
}
}
th {
padding: 2px 0;
color: $calendar-header-color;
font-weight: normal;
text-transform: lowercase;
border-top: 1px solid $calendar-border-color;
}
.ui-datepicker-unselectable {
background-color: $calendar-unselectable-bg;
}
}

View file

@ -3,12 +3,10 @@
* *
*/ */
.file-holder { .file-holder {
border: none;
border: 1px solid $border-color; border: 1px solid $border-color;
&.readme-holder { &.readme-holder {
margin-top: 10px; margin: $gl-padding-top 0;
border-bottom: 0;
} }
table { table {
@ -17,11 +15,13 @@
.file-title { .file-title {
position: relative; position: relative;
background: $background-color; background-color: $background-color;
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
margin: 0; margin: 0;
text-align: left; text-align: left;
padding: 10px $gl-padding; padding: 10px $gl-padding;
word-wrap: break-word;
border-radius: 3px 3px 0 0;
.file-actions { .file-actions {
float: right; float: right;
@ -30,24 +30,14 @@
right: 15px; right: 15px;
.btn { .btn {
padding: 0px 10px; padding: 0 10px;
font-size: 13px; font-size: 13px;
line-height: 28px; line-height: 28px;
} }
} }
.filename { a:not(.btn) {
&.old { color: $gl-dark-link-color;
span.idiff {
background-color: #f8cbcb;
}
}
&.new {
span.idiff {
background-color: #a6f3a6;
}
}
} }
.left-options { .left-options {
@ -78,13 +68,9 @@
} }
} }
&.blob_file {
}
&.blob-no-preview { &.blob-no-preview {
background: #eee; background: #eee;
text-shadow: 0 1px 2px #FFF; text-shadow: 0 1px 2px #fff;
padding: 100px 0; padding: 100px 0;
} }
@ -124,7 +110,12 @@
} }
td.line-numbers { td.line-numbers {
float: none; float: none;
border-left: 1px solid #DDD; border-left: 1px solid #ddd;
i {
float: none;
margin-right: 0;
}
} }
td.lines { td.lines {
padding: 0; padding: 0;
@ -169,6 +160,7 @@
*/ */
&.code { &.code {
padding: 0; padding: 0;
-webkit-overflow-scrolling: auto; // See https://gitlab.com/gitlab-org/gitlab-ce/issues/13987
} }
} }
} }

View file

@ -1,30 +1,21 @@
.filter-item { .filter-item {
margin-right: 6px; margin-right: 6px;
vertical-align: top;
} }
@media (min-width: 800px) { @media (min-width: $screen-sm-min) {
.issues-filters, .issues-filters,
.issues_bulk_update { .issues_bulk_update {
select, .select2-container { .dropdown-menu-toggle {
width: 120px !important; width: 132px;
display: inline-block;
} }
} }
} }
@media (min-width: 1200px) { @media (max-width: $screen-xs-max) {
.issues-filters, .filter-item {
.issues_bulk_update { display: block;
select, .select2-container { margin: 0 0 10px;
width: 150px !important;
display: inline-block;
}
} }
} }
.issues-filters,
.issues_bulk_update {
.select2-container .select2-choice {
color: #444 !important;
}
}

View file

@ -1,3 +1,7 @@
// Disabling "SpaceAfterPropertyColon" linter because the linter doesn't like
// the way the `src` property is formatted in this file.
// scss-lint:disable SpaceAfterPropertyColon
/* latin-ext */ /* latin-ext */
@font-face { @font-face {
font-family: 'Source Sans Pro'; font-family: 'Source Sans Pro';

View file

@ -6,43 +6,9 @@ input {
border-radius: $border-radius-base; 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 { input[type='text'].danger {
background: #F2DEDE!important; background: #f2dede!important;
border-color: #D66; border-color: #d66;
text-shadow: 0 1px 1px #fff text-shadow: 0 1px 1px #fff
} }
@ -69,6 +35,10 @@ label {
&.inline-label { &.inline-label {
margin: 0; margin: 0;
} }
&.label-light {
font-weight: 600;
}
} }
.inline-input-group { .inline-input-group {
@ -108,6 +78,24 @@ label {
border-radius: 3px; border-radius: 3px;
} }
.select-wrapper {
position: relative;
.caret {
position: absolute;
right: 10px;
top: $gl-padding;
color: $gray-darkest;
pointer-events: none;
}
}
.select-control {
padding-left: 10px;
padding-right: 10px;
-webkit-appearance: none;
}
.form-control-inline { .form-control-inline {
display: inline; display: inline;
} }
@ -121,7 +109,7 @@ label {
} }
.form-control::-webkit-input-placeholder { .form-control::-webkit-input-placeholder {
color: #7f8fa4; color: $gl-placeholder-color;
} }
.input-group { .input-group {

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