Imported Upstream version 8.8.2+dfsg
This commit is contained in:
parent
02b042a3d7
commit
886c2c3fe4
2092 changed files with 73012 additions and 20518 deletions
20
.csscomb.json
Normal file
20
.csscomb.json
Normal 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
1
.gitignore
vendored
|
@ -15,6 +15,7 @@
|
|||
.sass-cache/
|
||||
.secret
|
||||
.vagrant
|
||||
.byebug_history
|
||||
Vagrantfile
|
||||
backups/*
|
||||
config/aws.yml
|
||||
|
|
164
.gitlab-ci.yml
164
.gitlab-ci.yml
|
@ -2,7 +2,6 @@ image: "ruby:2.1"
|
|||
|
||||
services:
|
||||
- mysql:latest
|
||||
- postgres:latest
|
||||
- redis:latest
|
||||
|
||||
cache:
|
||||
|
@ -12,140 +11,122 @@ cache:
|
|||
|
||||
variables:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
|
||||
# retry tests only in CI environment
|
||||
RSPEC_RETRY_RETRY_COUNT: "3"
|
||||
|
||||
before_script:
|
||||
- source ./scripts/prepare_build.sh
|
||||
- ruby -v
|
||||
- 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
|
||||
- touch log/application.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
|
||||
|
||||
stages:
|
||||
- test
|
||||
- notifications
|
||||
|
||||
spec:feature:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:api:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:models:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:lib:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:services:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:benchmark:
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rake spec:benchmark
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
allow_failure: true
|
||||
|
||||
spec:other:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spinach:project:half:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spinach:project:rest:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spinach:other:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
teaspoon:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec teaspoon
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
rubocop:
|
||||
stage: test
|
||||
script:
|
||||
- bundle exec rubocop
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
scss-lint:
|
||||
stage: test
|
||||
script:
|
||||
- bundle exec rake scss_lint
|
||||
|
||||
brakeman:
|
||||
stage: test
|
||||
script:
|
||||
- bundle exec rake brakeman
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
flog:
|
||||
stage: test
|
||||
script:
|
||||
- bundle exec rake flog
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
flay:
|
||||
stage: test
|
||||
script:
|
||||
- bundle exec rake flay
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
bundler:audit:
|
||||
stage: test
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- "bundle exec bundle-audit update"
|
||||
- "bundle exec bundle-audit check"
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
allow_failure: true
|
||||
- "bundle exec bundle-audit check --update --ignore OSVDB-115941"
|
||||
|
||||
db-migrate-reset:
|
||||
stage: test
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rake db:migrate:reset
|
||||
|
||||
# Ruby 2.2 jobs
|
||||
|
||||
spec:feature:ruby22:
|
||||
stage: test
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
|
||||
|
@ -153,11 +134,9 @@ spec:feature:ruby22:
|
|||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:api:ruby22:
|
||||
stage: test
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
|
@ -167,11 +146,9 @@ spec:api:ruby22:
|
|||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:models:ruby22:
|
||||
stage: test
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
|
@ -181,11 +158,9 @@ spec:models:ruby22:
|
|||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:lib:ruby22:
|
||||
stage: test
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
|
@ -195,11 +170,9 @@ spec:lib:ruby22:
|
|||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:services:ruby22:
|
||||
stage: test
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
|
@ -209,26 +182,9 @@ spec:services:ruby22:
|
|||
key: "ruby22"
|
||||
paths:
|
||||
- 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:
|
||||
stage: test
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
|
@ -238,49 +194,53 @@ spec:other:ruby22:
|
|||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spinach:project:half:ruby22:
|
||||
stage: test
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
|
||||
cache:
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spinach:project:rest:ruby22:
|
||||
stage: test
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
|
||||
cache:
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spinach:other:ruby22:
|
||||
stage: test
|
||||
image: ruby:2.2
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
|
||||
cache:
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- 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
|
||||
|
|
2076
.rubocop.yml
2076
.rubocop.yml
File diff suppressed because it is too large
Load diff
264
.scss-lint.yml
Normal file
264
.scss-lint.yml
Normal 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
|
520
CHANGELOG
520
CHANGELOG
|
@ -1,21 +1,428 @@
|
|||
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
|
||||
- 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 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
|
||||
- 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)
|
||||
- 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
|
||||
- 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
|
||||
- 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
|
||||
- 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)
|
||||
- Add shortcut to toggle markdown preview (Florent Baldino)
|
||||
- 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 #upcoming filter to Milestone filter (Tiago Botelho)
|
||||
- Add ability to show archived projects on dashboard, explore and group pages
|
||||
- Remove fork link closes all merge requests opened on source project (Florent Baldino)
|
||||
- Move group activity to separate page
|
||||
- Create external users which are excluded of internal and private projects unless access was explicitly granted
|
||||
- Continue parameters are checked to ensure redirection goes to the same instance
|
||||
- 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
|
||||
- Bump Git version requirement to 2.7.4
|
||||
|
@ -37,6 +444,10 @@ v 8.5.4
|
|||
|
||||
v 8.5.3
|
||||
- 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
|
||||
- Fix sidebar overlapping content when screen width was below 1200px
|
||||
|
@ -72,11 +483,13 @@ v 8.5.1
|
|||
- Changed padding & background color for highlighted notes
|
||||
- Re-add the newrelic_rpm gem which was removed without any deprecation or warning (Stan Hu)
|
||||
- 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
|
||||
- Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu)
|
||||
- Cache various Repository methods to improve performance (Yorick Peterse)
|
||||
- Fix duplicated branch creation/deletion Web hooks/service notifications when using Web UI (Stan Hu)
|
||||
- Cache various Repository methods to improve performance
|
||||
- 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
|
||||
- Update New Relic gem to 3.14.1.311 (Stan Hu)
|
||||
- Add "visibility" flag to GET /projects api endpoint
|
||||
|
@ -152,6 +565,32 @@ v 8.5.0
|
|||
- Show label row when filtering issues or merge requests by label (Nuttanart Pornprasitsakul)
|
||||
- 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
|
||||
- Update omniauth-saml gem to 1.4.2
|
||||
- Prevent long-running backup tasks from timing out the database connection
|
||||
|
@ -206,7 +645,7 @@ v 8.4.0
|
|||
- Add housekeeping function to project settings page
|
||||
- The default GitLab logo now acts as a loading indicator
|
||||
- Fix caching issue where build status was not updating in project dashboard (Stan Hu)
|
||||
- Accept 2xx status codes for successful Web hook triggers (Stan Hu)
|
||||
- Accept 2xx status codes for successful Webhook triggers (Stan Hu)
|
||||
- Fix missing date of month in network graph when commits span a month (Stan Hu)
|
||||
- Expire view caches when application settings change (e.g. Gravatar disabled) (Stan Hu)
|
||||
- Don't notify users twice if they are both project watchers and subscribers (Stan Hu)
|
||||
|
@ -262,6 +701,27 @@ v 8.4.0
|
|||
- Add IP check against DNSBLs at account sign-up
|
||||
- Added cache:key to .gitlab-ci.yml allowing to fine tune the caching
|
||||
|
||||
v 8.3.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
|
||||
- Use gitlab-workhorse 0.5.4 (fixes API routing bug)
|
||||
|
||||
|
@ -306,7 +766,7 @@ v 8.3.0
|
|||
- Fix broken group avatar upload under "New group" (Stan Hu)
|
||||
- Update project repositorize size and commit count during import:repos task (Stan Hu)
|
||||
- Fix API setting of 'public' attribute to false will make a project private (Stan Hu)
|
||||
- Handle and report SSL errors in Web hook test (Stan Hu)
|
||||
- Handle and report SSL errors in Webhook test (Stan Hu)
|
||||
- Bump Redis requirement to 2.8 for Sidekiq 4 (Stan Hu)
|
||||
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
|
||||
- WIP identifier on merge requests no longer requires trailing space
|
||||
|
@ -359,6 +819,17 @@ v 8.3.0
|
|||
- Expose Git's version in the admin area
|
||||
- Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye)
|
||||
|
||||
v 8.2.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
|
||||
- Fix application settings cache not expiring after changes (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
|
||||
- Add Facebook authentication
|
||||
|
||||
v 8.1.2
|
||||
v 8.1.1
|
||||
- Fix cloning Wiki repositories via HTTP (Stan Hu)
|
||||
- Add migration to remove satellites directory
|
||||
- Fix specific runners visibility
|
||||
|
@ -526,7 +997,7 @@ v 8.1.0
|
|||
- Ensure code blocks are properly highlighted after a note is updated
|
||||
- Fix wrong access level badge on MR comments
|
||||
- Hide password in the service settings form
|
||||
- Move CI web hooks page to project settings area
|
||||
- Move CI webhooks page to project settings area
|
||||
- Fix User Identities API. It now allows you to properly create or update user's identities.
|
||||
- Add user preference to change layout width (Peter Göbel)
|
||||
- Use commit status in merge request widget as preferred source of CI status
|
||||
|
@ -569,7 +1040,7 @@ v 8.0.3
|
|||
- Fix URL shown in Slack notifications
|
||||
- Fix bug where projects would appear to be stuck in the forked import state (Stan Hu)
|
||||
- Fix Error 500 in creating merge requests with > 1000 diffs (Stan Hu)
|
||||
- Add work_in_progress key to MR web hooks (Ben Boeckel)
|
||||
- Add work_in_progress key to MR webhooks (Ben Boeckel)
|
||||
|
||||
v 8.0.2
|
||||
- Fix default avatar not rendering in network graph (Stan Hu)
|
||||
|
@ -860,7 +1331,7 @@ v 7.12.0
|
|||
- Fix milestone "Browse Issues" button.
|
||||
- Set milestone on new issue when creating issue from index with milestone filter active.
|
||||
- Make namespace API available to all users (Stan Hu)
|
||||
- Add web hook support for note events (Stan Hu)
|
||||
- Add webhook support for note events (Stan Hu)
|
||||
- Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu)
|
||||
- Remove Rack Attack monkey patches and bump to version 4.3.0 (Stan Hu)
|
||||
- Fix clone URL losing selection after a single click in Safari and Chrome (Stan Hu)
|
||||
|
@ -967,7 +1438,7 @@ v 7.11.0
|
|||
- Add "Create Merge Request" buttons to commits and branches pages and push event.
|
||||
- Show user roles by comments.
|
||||
- Fix automatic blocking of auto-created users from Active Directory.
|
||||
- Call merge request web hook for each new commits (Arthur Gautier)
|
||||
- Call merge request webhook for each new commits (Arthur Gautier)
|
||||
- Use SIGKILL by default in Sidekiq::MemoryKiller
|
||||
- Fix mentioning of private groups.
|
||||
- Add style for <kbd> element in markdown
|
||||
|
@ -1079,20 +1550,17 @@ v 7.10.0
|
|||
- Fix stuck Merge Request merging events from old installations (Ben Bodenmiller)
|
||||
- Fix merge request comments on files with multiple commits
|
||||
- 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.
|
||||
- Allow admin to create public deploy keys that are accessible to any project.
|
||||
- Warn when gitlab-shell version doesn't match requirement.
|
||||
- Skip email confirmation when set by admin or via LDAP.
|
||||
- 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
|
||||
- Contains no changes
|
||||
|
||||
|
@ -1141,7 +1609,7 @@ v 7.9.0
|
|||
- Add brakeman (security scanner for Ruby on Rails)
|
||||
- Slack username and channel options
|
||||
- Add grouped milestones from all projects to dashboard.
|
||||
- Web hook sends pusher email as well as commiter
|
||||
- Webhook sends pusher email as well as commiter
|
||||
- Add Bitbucket omniauth provider.
|
||||
- Add Bitbucket importer.
|
||||
- Support referencing issues to a project whose name starts with a digit
|
||||
|
@ -1264,7 +1732,7 @@ v 7.8.0
|
|||
- Allow notification email to be set separately from primary email.
|
||||
- API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger)
|
||||
- Don't have Markdown preview fail for long comments/wiki pages.
|
||||
- When test web hook - show error message instead of 500 error page if connection to hook url was reset
|
||||
- When test webhook - show error message instead of 500 error page if connection to hook url was reset
|
||||
- Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov)
|
||||
- Added persistent collapse button for left side nav bar (Jason Blanchard)
|
||||
- Prevent losing unsaved comments by automatically restoring them when comment page is loaded again.
|
||||
|
@ -1281,7 +1749,7 @@ v 7.8.0
|
|||
- Show projects user contributed to on user page. Show stars near project on user page.
|
||||
- Improve database performance for GitLab
|
||||
- Add Asana service (Jeremy Benoist)
|
||||
- Improve project web hooks with extra data
|
||||
- Improve project webhooks with extra data
|
||||
|
||||
v 7.7.2
|
||||
- Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch
|
||||
|
@ -1766,7 +2234,7 @@ v 6.4.0
|
|||
- Side-by-side diff view (Steven Thonus)
|
||||
- Internal projects (Jason Hollingsworth)
|
||||
- Allow removal of avatar (Drew Blessing)
|
||||
- Project web hooks now support issues and merge request events
|
||||
- Project webhooks now support issues and merge request events
|
||||
- Visiting project page while not logged in will redirect to sign-in instead of 404 (Jason Hollingsworth)
|
||||
- Expire event cache on avatar creation/removal (Drew Blessing)
|
||||
- Archiving old projects (Steven Thonus)
|
||||
|
@ -1836,7 +2304,7 @@ v 6.2.0
|
|||
- Added search for projects by name to api (Izaak Alpert)
|
||||
- Make default user theme configurable (Izaak Alpert)
|
||||
- Update logic for validates_merge_request for tree of MR (Andrew Kumanyaev)
|
||||
- Rake tasks for web hooks management (Jonhnny Weslley)
|
||||
- Rake tasks for webhooks management (Jonhnny Weslley)
|
||||
- Extended User API to expose admin and can_create_group for user creation/updating (Boyan Tabakov)
|
||||
- API: Remove group
|
||||
- API: Remove project
|
||||
|
@ -2039,7 +2507,7 @@ v 4.2.0
|
|||
- Async gitolite calls
|
||||
- added satellites logs
|
||||
- can_create_group, can_create_team booleans for User
|
||||
- Process web hooks async
|
||||
- Process webhooks async
|
||||
- GFM: Fix images escaped inside links
|
||||
- Network graph improved
|
||||
- Switchable branches for network graph
|
||||
|
@ -2073,7 +2541,7 @@ v 4.1.0
|
|||
|
||||
v 4.0.0
|
||||
- Remove project code and path from API. Use id instead
|
||||
- Return valid cloneable url to repo for web hook
|
||||
- Return valid cloneable url to repo for webhook
|
||||
- Fixed backup issue
|
||||
- Reorganized settings
|
||||
- Fixed commits compare
|
||||
|
|
134
CONTRIBUTING.md
134
CONTRIBUTING.md
|
@ -3,24 +3,28 @@
|
|||
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
|
||||
|
||||
- [Contribute to GitLab](#contribute-to-gitlab)
|
||||
- [Contributor license agreement](#contributor-license-agreement)
|
||||
- [Security vulnerability disclosure](#security-vulnerability-disclosure)
|
||||
- [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests)
|
||||
- [Helping others](#helping-others)
|
||||
- [I want to contribute!](#i-want-to-contribute)
|
||||
- [Issue tracker](#issue-tracker)
|
||||
- [Feature proposals](#feature-proposals)
|
||||
- [Issue tracker guidelines](#issue-tracker-guidelines)
|
||||
- [Issue weight](#issue-weight)
|
||||
- [Regression issues](#regression-issues)
|
||||
- [Merge requests](#merge-requests)
|
||||
- [Merge request guidelines](#merge-request-guidelines)
|
||||
- [Merge request description format](#merge-request-description-format)
|
||||
- [Contribution acceptance criteria](#contribution-acceptance-criteria)
|
||||
- [Changes for Stable Releases](#changes-for-stable-releases)
|
||||
- [Definition of done](#definition-of-done)
|
||||
- [Style guides](#style-guides)
|
||||
- [Code of conduct](#code-of-conduct)
|
||||
- [Contributor license agreement](#contributor-license-agreement)
|
||||
- [Security vulnerability disclosure](#security-vulnerability-disclosure)
|
||||
- [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests)
|
||||
- [Helping others](#helping-others)
|
||||
- [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)
|
||||
- [Feature proposals](#feature-proposals)
|
||||
- [Issue tracker guidelines](#issue-tracker-guidelines)
|
||||
- [Issue weight](#issue-weight)
|
||||
- [Regression issues](#regression-issues)
|
||||
- [Technical debt](#technical-debt)
|
||||
- [Merge requests](#merge-requests)
|
||||
- [Merge request guidelines](#merge-request-guidelines)
|
||||
- [Merge request description format](#merge-request-description-format)
|
||||
- [Contribution acceptance criteria](#contribution-acceptance-criteria)
|
||||
- [Changes for Stable Releases](#changes-for-stable-releases)
|
||||
- [Definition of done](#definition-of-done)
|
||||
- [Style guides](#style-guides)
|
||||
- [Code of conduct](#code-of-conduct)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
|
@ -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
|
||||
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).
|
||||
|
||||
## Contributor license agreement
|
||||
|
@ -68,10 +72,10 @@ for audiences of all ages.
|
|||
## Helping others
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
## I want to contribute!
|
||||
|
@ -83,6 +87,22 @@ GitLab.
|
|||
|
||||
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
|
||||
|
||||
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
|
||||
[`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][]
|
||||
members to add the label `feature proposal` to the issue.
|
||||
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 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
|
||||
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.
|
||||
If you want to create something yourself, consider opening an issue first to
|
||||
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
|
||||
[update the notes]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/pro-tips.md#update-the-regression-issue
|
||||
|
||||
### Technical debt
|
||||
|
||||
In order to track things that can be improved in GitLab's codebase, we created
|
||||
the ~"technical debt" label in [GitLab's issue tracker][ce-tracker].
|
||||
|
||||
This label should be added to issues that describe things that can be improved,
|
||||
shortcuts that have been taken, code that needs refactoring, features that need
|
||||
additional attention, and all other things that have been left behind due to
|
||||
high velocity of development.
|
||||
|
||||
Everyone can create an issue, though you may need to ask for adding a specific
|
||||
label, if you do not have permissions to do it by yourself. Additional labels
|
||||
can be combined with the `technical debt` label, to make it easier to schedule
|
||||
the improvements for a release.
|
||||
|
||||
Issues tagged with the `technical debt` label have the same priority like issues
|
||||
that describe a new feature to be introduced in GitLab, and should be scheduled
|
||||
for a release by the appropriate person.
|
||||
|
||||
Make sure to mention the merge request that the `technical debt` issue is
|
||||
associated with in the description of the issue.
|
||||
|
||||
## Merge requests
|
||||
|
||||
We welcome merge requests with fixes and improvements to GitLab code, tests,
|
||||
|
@ -281,6 +334,7 @@ request is as follows:
|
|||
[shell command guidelines](doc/development/shell_commands.md)
|
||||
1. If your code creates new files on disk please read the
|
||||
[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 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.
|
||||
|
||||
For examples of feedback on merge requests please look at already
|
||||
[closed merge requests][]. If you would like quick feedback on your merge
|
||||
request feel free to mention one of the Merge Marshalls of the [core team][].
|
||||
[closed merge requests][closed-merge-requests]. If you would like quick feedback
|
||||
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.
|
||||
|
||||
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)
|
||||
into account.
|
||||
[code review guidelines](doc/development/code_review.md) into account.
|
||||
|
||||
### 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
|
||||
1. Keeps the GitLab code base clean and well structured
|
||||
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
|
||||
(no squashing). If necessary, you will be asked to squash when the review is
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
- string literal quoting style **Option A**: single quoted by default
|
||||
1. [Rails](https://github.com/bbatsov/rails-style-guide)
|
||||
1. [Testing](https://github.com/thoughtbot/guides/tree/master/style/testing)
|
||||
1. [Testing](doc/development/testing.md)
|
||||
1. [CoffeeScript](https://github.com/thoughtbot/guides/tree/master/style/coffeescript)
|
||||
1. [SCSS styleguide][scss-styleguide]
|
||||
1. [Shell commands](doc/development/shell_commands.md) created by GitLab
|
||||
contributors to enhance security
|
||||
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
|
||||
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/).
|
||||
|
||||
[core-team]: https://about.gitlab.com/core-team/
|
||||
[getting help page]: https://about.gitlab.com/getting-help/
|
||||
[Codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
|
||||
[core team]: https://about.gitlab.com/core-team/
|
||||
[getting-help]: https://about.gitlab.com/getting-help/
|
||||
[codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
|
||||
[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
|
||||
[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
|
||||
[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit
|
||||
[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
|
||||
[definition of done]: http://guide.agilealliance.org/guide/definition-of-done.html
|
||||
[Contributor Covenant]: http://contributor-covenant.org
|
||||
[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
|
||||
[contributor-covenant]: http://contributor-covenant.org
|
||||
[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
|
||||
[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/
|
||||
|
|
|
@ -1 +1 @@
|
|||
2.6.10
|
||||
2.7.2
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.6.4
|
||||
0.7.1
|
||||
|
|
83
Gemfile
83
Gemfile
|
@ -1,14 +1,14 @@
|
|||
source "https://rubygems.org"
|
||||
|
||||
gem 'rails', '4.2.5.2'
|
||||
gem 'rails', '4.2.6'
|
||||
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
|
||||
|
||||
# Responders respond_to and respond_with
|
||||
gem 'responders', '~> 2.0'
|
||||
|
||||
# Specify a sprockets version due to security issue
|
||||
# See https://groups.google.com/forum/#!topic/rubyonrails-security/doAVp0YaTqY
|
||||
gem 'sprockets', '~> 2.12.3'
|
||||
# Specify a sprockets version due to increased performance
|
||||
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069
|
||||
gem 'sprockets', '~> 3.6.0'
|
||||
|
||||
# Default values for AR models
|
||||
gem "default_value_for", "~> 3.0.0"
|
||||
|
@ -19,9 +19,10 @@ gem "pg", '~> 0.18.2', group: :postgres
|
|||
|
||||
# Authentication libraries
|
||||
gem 'devise', '~> 3.5.4'
|
||||
gem 'doorkeeper', '~> 3.1'
|
||||
gem 'devise-async', '~> 0.9.0'
|
||||
gem 'doorkeeper', '~> 2.2.0'
|
||||
gem 'omniauth', '~> 1.3.1'
|
||||
gem 'omniauth-auth0', '~> 1.4.1'
|
||||
gem 'omniauth-azure-oauth2', '~> 0.0.6'
|
||||
gem 'omniauth-bitbucket', '~> 0.0.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-google-oauth2', '~> 0.2.0'
|
||||
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-twitter', '~> 1.2.0'
|
||||
gem 'omniauth_crowd', '~> 2.2.0'
|
||||
gem 'rack-oauth2', '~> 1.2.1'
|
||||
gem 'jwt'
|
||||
|
||||
# Spam and anti-bot protection
|
||||
gem 'recaptcha', require: 'recaptcha/rails'
|
||||
|
@ -50,7 +52,7 @@ gem "browser", '~> 1.0.0'
|
|||
|
||||
# Extracting information from a git repository
|
||||
# Provide access to Gitlab::Git library
|
||||
gem "gitlab_git", '~> 8.2'
|
||||
gem "gitlab_git", '~> 10.0'
|
||||
|
||||
# LDAP Auth
|
||||
# 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"
|
||||
|
||||
# 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
|
||||
gem "github-linguist", "~> 4.7.0", require: "linguist"
|
||||
|
@ -75,7 +79,7 @@ gem "kaminari", "~> 0.16.3"
|
|||
gem "haml-rails", '~> 0.9.0'
|
||||
|
||||
# Files attachments
|
||||
gem "carrierwave", '~> 0.9.0'
|
||||
gem "carrierwave", '~> 0.10.0'
|
||||
|
||||
# Drag and Drop UI
|
||||
gem 'dropzonejs-rails', '~> 0.7.1'
|
||||
|
@ -112,7 +116,7 @@ gem 'diffy', '~> 3.0.3'
|
|||
|
||||
# Application server
|
||||
group :unicorn do
|
||||
gem "unicorn", '~> 4.8.2'
|
||||
gem "unicorn", '~> 4.9.0'
|
||||
gem 'unicorn-worker-killer', '~> 0.4.2'
|
||||
end
|
||||
|
||||
|
@ -146,6 +150,10 @@ gem 'version_sorter', '~> 2.0.0'
|
|||
# Cache
|
||||
gem "redis-rails", '~> 4.0.0'
|
||||
|
||||
# Redis
|
||||
gem 'redis', '~> 3.2'
|
||||
gem 'connection_pool', '~> 2.0'
|
||||
|
||||
# Campfire integration
|
||||
gem 'tinder', '~> 1.10.0'
|
||||
|
||||
|
@ -171,7 +179,7 @@ gem 'ruby-fogbugz', '~> 0.2.1'
|
|||
gem 'd3_rails', '~> 3.5.0'
|
||||
|
||||
#cal-heatmap
|
||||
gem 'cal-heatmap-rails', '~> 3.5.0'
|
||||
gem 'cal-heatmap-rails', '~> 3.6.0'
|
||||
|
||||
# underscore-rails
|
||||
gem "underscore-rails", "~> 1.8.0"
|
||||
|
@ -183,11 +191,14 @@ gem 'babosa', '~> 1.0.2'
|
|||
# Sanitizes SVG input
|
||||
gem "loofah", "~> 2.0.3"
|
||||
|
||||
# Working with license
|
||||
gem 'licensee', '~> 8.0.0'
|
||||
|
||||
# Protect against bruteforcing
|
||||
gem "rack-attack", '~> 4.3.1'
|
||||
|
||||
# Ace editor
|
||||
gem 'ace-rails-ap', '~> 2.0.1'
|
||||
gem 'ace-rails-ap', '~> 4.0.2'
|
||||
|
||||
# Keyboard shortcuts
|
||||
gem 'mousetrap-rails', '~> 1.4.6'
|
||||
|
@ -207,33 +218,32 @@ gem 'font-awesome-rails', '~> 4.2'
|
|||
gem 'gitlab_emoji', '~> 0.3.0'
|
||||
gem 'gon', '~> 6.0.1'
|
||||
gem 'jquery-atwho-rails', '~> 1.3.2'
|
||||
gem 'jquery-rails', '~> 4.0.0'
|
||||
gem 'jquery-scrollto-rails', '~> 1.4.3'
|
||||
gem 'jquery-rails', '~> 4.1.0'
|
||||
gem 'jquery-ui-rails', '~> 5.0.0'
|
||||
gem 'nprogress-rails', '~> 0.1.6.7'
|
||||
gem 'raphael-rails', '~> 2.1.2'
|
||||
gem 'request_store', '~> 1.2.0'
|
||||
gem 'request_store', '~> 1.3.0'
|
||||
gem 'select2-rails', '~> 3.5.9'
|
||||
gem 'virtus', '~> 1.0.1'
|
||||
gem 'net-ssh', '~> 3.0.1'
|
||||
gem 'base32', '~> 0.3.0'
|
||||
|
||||
# Sentry integration
|
||||
gem 'sentry-raven', '~> 0.15'
|
||||
|
||||
gem 'premailer-rails', '~> 1.9.0'
|
||||
|
||||
# Metrics
|
||||
group :metrics do
|
||||
gem 'allocations', '~> 1.0', require: false, platform: :mri
|
||||
gem 'method_source', '~> 0.8', require: false
|
||||
gem 'influxdb', '~> 0.2', require: false
|
||||
gem 'connection_pool', '~> 2.0', require: false
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem "foreman"
|
||||
gem 'brakeman', '~> 3.1.0', require: false
|
||||
gem 'brakeman', '~> 3.2.0', require: false
|
||||
|
||||
gem "annotate", "~> 2.6.0"
|
||||
gem "letter_opener", '~> 1.1.2'
|
||||
gem 'letter_opener_web', '~> 1.3.0'
|
||||
gem 'quiet_assets', '~> 1.0.2'
|
||||
gem 'rerun', '~> 0.11.0'
|
||||
gem 'bullet', require: false
|
||||
|
@ -258,10 +268,12 @@ group :development, :test do
|
|||
gem 'awesome_print', '~> 1.2.0', require: false
|
||||
gem 'fuubar', '~> 2.0.0'
|
||||
|
||||
gem 'database_cleaner', '~> 1.4.0'
|
||||
gem 'factory_girl_rails', '~> 4.3.0'
|
||||
gem 'rspec-rails', '~> 3.3.0'
|
||||
gem 'spinach-rails', '~> 0.2.1'
|
||||
gem 'database_cleaner', '~> 1.4.0'
|
||||
gem 'factory_girl_rails', '~> 4.6.0'
|
||||
gem 'rspec-rails', '~> 3.4.0'
|
||||
gem 'rspec-retry'
|
||||
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)
|
||||
gem 'minitest', '~> 5.7.0'
|
||||
|
@ -269,21 +281,22 @@ group :development, :test do
|
|||
# Generate Fake data
|
||||
gem 'ffaker', '~> 2.0.0'
|
||||
|
||||
gem 'capybara', '~> 2.4.0'
|
||||
gem 'capybara', '~> 2.6.2'
|
||||
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 'spring', '~> 1.3.6'
|
||||
gem 'spring', '~> 1.7.0'
|
||||
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 '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 'simplecov', '~> 0.10.0', require: false
|
||||
gem 'simplecov', '~> 0.11.0', require: false
|
||||
gem 'flog', require: false
|
||||
gem 'flay', require: false
|
||||
gem 'bundler-audit', require: false
|
||||
|
@ -305,14 +318,13 @@ end
|
|||
|
||||
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'
|
||||
|
||||
## CI
|
||||
gem 'activerecord-deprecated_finders', '~> 1.0.3'
|
||||
gem 'activerecord-session_store', '~> 0.1.0'
|
||||
gem "nested_form", '~> 0.3.2'
|
||||
|
||||
|
@ -321,3 +333,6 @@ gem 'oauth2', '~> 1.0.0'
|
|||
|
||||
# Soft deletion
|
||||
gem "paranoia", "~> 2.0"
|
||||
|
||||
# Health check
|
||||
gem 'health_check', '~> 1.5.1'
|
||||
|
|
359
Gemfile.lock
359
Gemfile.lock
|
@ -3,42 +3,41 @@ GEM
|
|||
specs:
|
||||
CFPropertyList (2.3.2)
|
||||
RedCloth (4.2.9)
|
||||
ace-rails-ap (2.0.1)
|
||||
actionmailer (4.2.5.2)
|
||||
actionpack (= 4.2.5.2)
|
||||
actionview (= 4.2.5.2)
|
||||
activejob (= 4.2.5.2)
|
||||
ace-rails-ap (4.0.2)
|
||||
actionmailer (4.2.6)
|
||||
actionpack (= 4.2.6)
|
||||
actionview (= 4.2.6)
|
||||
activejob (= 4.2.6)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
actionpack (4.2.5.2)
|
||||
actionview (= 4.2.5.2)
|
||||
activesupport (= 4.2.5.2)
|
||||
actionpack (4.2.6)
|
||||
actionview (= 4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
rack (~> 1.6)
|
||||
rack-test (~> 0.6.2)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (4.2.5.2)
|
||||
activesupport (= 4.2.5.2)
|
||||
actionview (4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
builder (~> 3.1)
|
||||
erubis (~> 2.7.0)
|
||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
activejob (4.2.5.2)
|
||||
activesupport (= 4.2.5.2)
|
||||
activejob (4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
globalid (>= 0.3.0)
|
||||
activemodel (4.2.5.2)
|
||||
activesupport (= 4.2.5.2)
|
||||
activemodel (4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
builder (~> 3.1)
|
||||
activerecord (4.2.5.2)
|
||||
activemodel (= 4.2.5.2)
|
||||
activesupport (= 4.2.5.2)
|
||||
activerecord (4.2.6)
|
||||
activemodel (= 4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
arel (~> 6.0)
|
||||
activerecord-deprecated_finders (1.0.4)
|
||||
activerecord-session_store (0.1.2)
|
||||
actionpack (>= 4.0.0, < 5)
|
||||
activerecord (>= 4.0.0, < 5)
|
||||
railties (>= 4.0.0, < 5)
|
||||
activesupport (4.2.5.2)
|
||||
activesupport (4.2.6)
|
||||
i18n (~> 0.7)
|
||||
json (~> 1.7, >= 1.7.7)
|
||||
minitest (~> 5.1)
|
||||
|
@ -51,9 +50,6 @@ GEM
|
|||
activerecord (>= 3.0)
|
||||
akismet (2.0.0)
|
||||
allocations (1.0.4)
|
||||
annotate (2.6.10)
|
||||
activerecord (>= 3.2, <= 4.3)
|
||||
rake (~> 10.4)
|
||||
arel (6.0.3)
|
||||
asana (0.4.0)
|
||||
faraday (~> 0.9)
|
||||
|
@ -61,9 +57,7 @@ GEM
|
|||
faraday_middleware-multi_json (~> 0.0)
|
||||
oauth2 (~> 1.0)
|
||||
asciidoctor (1.5.3)
|
||||
ast (2.1.0)
|
||||
astrolabe (1.3.1)
|
||||
parser (~> 2.2)
|
||||
ast (2.2.0)
|
||||
attr_encrypted (1.3.4)
|
||||
encryptor (>= 1.3.0)
|
||||
attr_required (1.0.0)
|
||||
|
@ -76,6 +70,7 @@ GEM
|
|||
ice_nine (~> 0.11.0)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
babosa (1.0.2)
|
||||
base32 (0.3.2)
|
||||
bcrypt (3.1.10)
|
||||
benchmark-ips (2.3.0)
|
||||
better_errors (1.0.1)
|
||||
|
@ -86,29 +81,28 @@ GEM
|
|||
bootstrap-sass (3.3.6)
|
||||
autoprefixer-rails (>= 5.2.1)
|
||||
sass (>= 3.3.4)
|
||||
brakeman (3.1.4)
|
||||
brakeman (3.2.1)
|
||||
erubis (~> 2.6)
|
||||
fastercsv (~> 1.5)
|
||||
haml (>= 3.0, < 5.0)
|
||||
highline (>= 1.6.20, < 2.0)
|
||||
multi_json (~> 1.2)
|
||||
ruby2ruby (>= 2.1.1, < 2.3.0)
|
||||
ruby_parser (~> 3.7.0)
|
||||
ruby2ruby (~> 2.3.0)
|
||||
ruby_parser (~> 3.8.1)
|
||||
safe_yaml (>= 1.0)
|
||||
sass (~> 3.0)
|
||||
slim (>= 1.3.6, < 4.0)
|
||||
terminal-table (~> 1.4)
|
||||
browser (1.0.1)
|
||||
builder (3.2.2)
|
||||
bullet (4.14.10)
|
||||
bullet (5.0.0)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.9.0)
|
||||
bundler-audit (0.4.0)
|
||||
bundler-audit (0.5.0)
|
||||
bundler (~> 1.2)
|
||||
thor (~> 0.18)
|
||||
byebug (8.2.1)
|
||||
cal-heatmap-rails (3.5.1)
|
||||
capybara (2.4.4)
|
||||
cal-heatmap-rails (3.6.0)
|
||||
capybara (2.6.2)
|
||||
addressable
|
||||
mime-types (>= 1.16)
|
||||
nokogiri (>= 1.3.3)
|
||||
rack (>= 1.0.0)
|
||||
|
@ -117,10 +111,11 @@ GEM
|
|||
capybara-screenshot (1.0.11)
|
||||
capybara (>= 1.0, < 3)
|
||||
launchy
|
||||
carrierwave (0.9.0)
|
||||
carrierwave (0.10.0)
|
||||
activemodel (>= 3.2.0)
|
||||
activesupport (>= 3.2.0)
|
||||
json (>= 1.7)
|
||||
mime-types (>= 1.16)
|
||||
cause (0.1)
|
||||
charlock_holmes (0.7.3)
|
||||
chunky_png (1.3.5)
|
||||
|
@ -128,26 +123,27 @@ GEM
|
|||
coderay (1.1.0)
|
||||
coercible (1.0.0)
|
||||
descendants_tracker (~> 0.0.1)
|
||||
coffee-rails (4.1.0)
|
||||
coffee-rails (4.1.1)
|
||||
coffee-script (>= 2.2.0)
|
||||
railties (>= 4.0.0, < 5.0)
|
||||
railties (>= 4.0.0, < 5.1.x)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.10.0)
|
||||
colorize (0.7.7)
|
||||
concurrent-ruby (1.0.0)
|
||||
concurrent-ruby (1.0.2)
|
||||
connection_pool (2.2.0)
|
||||
coveralls (0.8.9)
|
||||
coveralls (0.8.13)
|
||||
json (~> 1.8)
|
||||
rest-client (>= 1.6.8, < 2)
|
||||
simplecov (~> 0.10.0)
|
||||
simplecov (~> 0.11.0)
|
||||
term-ansicolor (~> 1.3)
|
||||
thor (~> 0.19.1)
|
||||
tins (~> 1.6.0)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
creole (0.5.0)
|
||||
css_parser (1.4.1)
|
||||
addressable
|
||||
d3_rails (3.5.11)
|
||||
railties (>= 3.1.0)
|
||||
daemons (1.2.3)
|
||||
|
@ -176,9 +172,7 @@ GEM
|
|||
diff-lcs (1.2.5)
|
||||
diffy (3.0.7)
|
||||
docile (1.1.5)
|
||||
domain_name (0.5.25)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
doorkeeper (2.2.2)
|
||||
doorkeeper (3.1.0)
|
||||
railties (>= 3.2)
|
||||
dropzonejs-rails (0.7.2)
|
||||
rails (> 3.1)
|
||||
|
@ -189,15 +183,15 @@ GEM
|
|||
encryptor (1.3.0)
|
||||
equalizer (0.0.11)
|
||||
erubis (2.7.0)
|
||||
escape_utils (1.1.0)
|
||||
escape_utils (1.1.1)
|
||||
eventmachine (1.0.8)
|
||||
excon (0.45.4)
|
||||
execjs (2.6.0)
|
||||
expression_parser (0.9.0)
|
||||
factory_girl (4.3.0)
|
||||
factory_girl (4.5.0)
|
||||
activesupport (>= 3.0.0)
|
||||
factory_girl_rails (4.3.0)
|
||||
factory_girl (~> 4.3.0)
|
||||
factory_girl_rails (4.6.0)
|
||||
factory_girl (~> 4.5.0)
|
||||
railties (>= 3.0.0)
|
||||
faraday (0.9.2)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
|
@ -206,7 +200,6 @@ GEM
|
|||
faraday_middleware-multi_json (0.0.6)
|
||||
faraday_middleware
|
||||
multi_json
|
||||
fastercsv (1.5.5)
|
||||
ffaker (2.0.0)
|
||||
ffi (1.9.10)
|
||||
fission (0.5.0)
|
||||
|
@ -326,8 +319,8 @@ GEM
|
|||
fog-xml (0.1.2)
|
||||
fog-core
|
||||
nokogiri (~> 1.5, >= 1.5.11)
|
||||
font-awesome-rails (4.5.0.0)
|
||||
railties (>= 3.2, < 5.0)
|
||||
font-awesome-rails (4.5.0.1)
|
||||
railties (>= 3.2, < 5.1)
|
||||
foreman (0.78.0)
|
||||
thor (~> 0.19.1)
|
||||
formatador (0.2.5)
|
||||
|
@ -340,7 +333,7 @@ GEM
|
|||
json
|
||||
get_process_mem (0.2.0)
|
||||
gherkin-ruby (0.3.2)
|
||||
github-linguist (4.7.5)
|
||||
github-linguist (4.7.6)
|
||||
charlock_holmes (~> 0.7.3)
|
||||
escape_utils (~> 1.1.0)
|
||||
mime-types (>= 1.19)
|
||||
|
@ -350,18 +343,18 @@ GEM
|
|||
flowdock (~> 0.7)
|
||||
gitlab-grit (>= 2.4.1)
|
||||
multi_json
|
||||
gitlab-grit (2.7.3)
|
||||
gitlab-grit (2.8.1)
|
||||
charlock_holmes (~> 0.6)
|
||||
diff-lcs (~> 1.1)
|
||||
mime-types (~> 1.15)
|
||||
mime-types (>= 1.16, < 3)
|
||||
posix-spawn (~> 0.3)
|
||||
gitlab_emoji (0.3.1)
|
||||
gemojione (~> 2.2, >= 2.2.1)
|
||||
gitlab_git (8.2.0)
|
||||
gitlab_git (10.1.0)
|
||||
activesupport (~> 4.0)
|
||||
charlock_holmes (~> 0.7.3)
|
||||
github-linguist (~> 4.7.0)
|
||||
rugged (~> 0.24.0b13)
|
||||
rugged (~> 0.24.0)
|
||||
gitlab_meta (7.0)
|
||||
gitlab_omniauth-ldap (1.2.1)
|
||||
net-ldap (~> 0.9)
|
||||
|
@ -379,6 +372,9 @@ GEM
|
|||
rouge (~> 1.9)
|
||||
sanitize (~> 2.1.0)
|
||||
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)
|
||||
actionpack (>= 3.0)
|
||||
json
|
||||
|
@ -406,8 +402,9 @@ GEM
|
|||
html2haml (>= 1.0.1)
|
||||
railties (>= 4.0.1)
|
||||
hashie (3.4.3)
|
||||
health_check (1.5.1)
|
||||
rails (>= 2.3.0)
|
||||
highline (1.7.8)
|
||||
hike (1.2.3)
|
||||
hipchat (1.5.2)
|
||||
httparty
|
||||
mimemagic
|
||||
|
@ -419,8 +416,7 @@ GEM
|
|||
haml (~> 4.0.0)
|
||||
nokogiri (~> 1.6.0)
|
||||
ruby_parser (~> 3.5)
|
||||
http-cookie (1.0.2)
|
||||
domain_name (~> 0.5)
|
||||
htmlentities (4.3.4)
|
||||
http_parser.rb (0.5.3)
|
||||
httparty (0.13.7)
|
||||
json (~> 1.8)
|
||||
|
@ -434,12 +430,10 @@ GEM
|
|||
json
|
||||
ipaddress (0.8.2)
|
||||
jquery-atwho-rails (1.3.2)
|
||||
jquery-rails (4.0.5)
|
||||
rails-dom-testing (~> 1.0)
|
||||
jquery-rails (4.1.1)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
jquery-scrollto-rails (1.4.3)
|
||||
railties (> 3.1, < 5.0)
|
||||
jquery-turbolinks (2.1.0)
|
||||
railties (>= 3.1.0)
|
||||
turbolinks
|
||||
|
@ -453,8 +447,14 @@ GEM
|
|||
kgio (2.10.0)
|
||||
launchy (2.4.3)
|
||||
addressable (~> 2.3)
|
||||
letter_opener (1.1.2)
|
||||
letter_opener (1.4.1)
|
||||
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)
|
||||
rb-fsevent (>= 0.9.3)
|
||||
rb-inotify (>= 0.9)
|
||||
|
@ -462,11 +462,11 @@ GEM
|
|||
nokogiri (>= 1.5.9)
|
||||
macaddr (1.7.1)
|
||||
systemu (~> 2.6.2)
|
||||
mail (2.6.3)
|
||||
mime-types (>= 1.16, < 3)
|
||||
mail_room (0.6.1)
|
||||
mail (2.6.4)
|
||||
mime-types (>= 1.16, < 4)
|
||||
mail_room (0.7.0)
|
||||
method_source (0.8.2)
|
||||
mime-types (1.25.1)
|
||||
mime-types (2.99.1)
|
||||
mimemagic (0.3.0)
|
||||
mini_portile2 (2.0.0)
|
||||
minitest (5.7.0)
|
||||
|
@ -478,11 +478,9 @@ GEM
|
|||
nested_form (0.3.2)
|
||||
net-ldap (0.12.1)
|
||||
net-ssh (3.0.1)
|
||||
netrc (0.11.0)
|
||||
newrelic_rpm (3.14.1.311)
|
||||
nokogiri (1.6.7.2)
|
||||
mini_portile2 (~> 2.0.0.rc2)
|
||||
nprogress-rails (0.1.6.7)
|
||||
oauth (0.4.7)
|
||||
oauth2 (1.0.0)
|
||||
faraday (>= 0.8, < 0.10)
|
||||
|
@ -490,11 +488,13 @@ GEM
|
|||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (~> 1.2)
|
||||
octokit (3.8.0)
|
||||
sawyer (~> 0.6.0, >= 0.5.3)
|
||||
octokit (4.3.0)
|
||||
sawyer (~> 0.7.0, >= 0.5.3)
|
||||
omniauth (1.3.1)
|
||||
hashie (>= 1.2, < 4)
|
||||
rack (>= 1.0, < 3)
|
||||
omniauth-auth0 (1.4.1)
|
||||
omniauth-oauth2 (~> 1.1)
|
||||
omniauth-azure-oauth2 (0.0.6)
|
||||
jwt (~> 1.0)
|
||||
omniauth (~> 1.0)
|
||||
|
@ -532,8 +532,8 @@ GEM
|
|||
omniauth-oauth2 (1.3.1)
|
||||
oauth2 (~> 1.0)
|
||||
omniauth (~> 1.2)
|
||||
omniauth-saml (1.4.2)
|
||||
omniauth (~> 1.1)
|
||||
omniauth-saml (1.5.0)
|
||||
omniauth (~> 1.3)
|
||||
ruby-saml (~> 1.1, >= 1.1.1)
|
||||
omniauth-shibboleth (1.2.1)
|
||||
omniauth (>= 1.0.0)
|
||||
|
@ -549,16 +549,22 @@ GEM
|
|||
orm_adapter (0.5.0)
|
||||
paranoia (2.1.4)
|
||||
activerecord (~> 4.0)
|
||||
parser (2.2.3.0)
|
||||
ast (>= 1.1, < 3.0)
|
||||
parser (2.3.0.6)
|
||||
ast (~> 2.2)
|
||||
pg (0.18.4)
|
||||
poltergeist (1.8.1)
|
||||
poltergeist (1.9.0)
|
||||
capybara (~> 2.1)
|
||||
cliver (~> 0.3.1)
|
||||
multi_json (~> 1.0)
|
||||
websocket-driver (>= 0.2.0)
|
||||
posix-spawn (0.3.11)
|
||||
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)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
|
@ -586,16 +592,16 @@ GEM
|
|||
rack
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rails (4.2.5.2)
|
||||
actionmailer (= 4.2.5.2)
|
||||
actionpack (= 4.2.5.2)
|
||||
actionview (= 4.2.5.2)
|
||||
activejob (= 4.2.5.2)
|
||||
activemodel (= 4.2.5.2)
|
||||
activerecord (= 4.2.5.2)
|
||||
activesupport (= 4.2.5.2)
|
||||
rails (4.2.6)
|
||||
actionmailer (= 4.2.6)
|
||||
actionpack (= 4.2.6)
|
||||
actionview (= 4.2.6)
|
||||
activejob (= 4.2.6)
|
||||
activemodel (= 4.2.6)
|
||||
activerecord (= 4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 4.2.5.2)
|
||||
railties (= 4.2.6)
|
||||
sprockets-rails
|
||||
rails-deprecated_sanitizer (1.0.3)
|
||||
activesupport (>= 4.2.0.alpha)
|
||||
|
@ -605,12 +611,12 @@ GEM
|
|||
rails-deprecated_sanitizer (>= 1.0.1)
|
||||
rails-html-sanitizer (1.0.3)
|
||||
loofah (~> 2.0)
|
||||
railties (4.2.5.2)
|
||||
actionpack (= 4.2.5.2)
|
||||
activesupport (= 4.2.5.2)
|
||||
railties (4.2.6)
|
||||
actionpack (= 4.2.6)
|
||||
activesupport (= 4.2.6)
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rainbow (2.0.0)
|
||||
rainbow (2.1.0)
|
||||
raindrops (0.15.0)
|
||||
rake (10.5.0)
|
||||
raphael-rails (2.1.2)
|
||||
|
@ -624,7 +630,7 @@ GEM
|
|||
recaptcha (1.0.2)
|
||||
json
|
||||
redcarpet (3.3.3)
|
||||
redis (3.2.2)
|
||||
redis (3.3.0)
|
||||
redis-actionpack (4.0.1)
|
||||
actionpack (~> 4)
|
||||
redis-rack (~> 1.5.0)
|
||||
|
@ -643,15 +649,11 @@ GEM
|
|||
redis-store (~> 1.1.0)
|
||||
redis-store (1.1.7)
|
||||
redis (>= 2.2)
|
||||
request_store (1.2.1)
|
||||
request_store (1.3.0)
|
||||
rerun (0.11.0)
|
||||
listen (~> 3.0)
|
||||
responders (2.1.1)
|
||||
railties (>= 4.2.0, < 5.1)
|
||||
rest-client (1.8.0)
|
||||
http-cookie (>= 1.0.2, < 2.0)
|
||||
mime-types (>= 1.16, < 3.0)
|
||||
netrc (~> 0.7)
|
||||
rinku (1.7.3)
|
||||
rotp (2.1.1)
|
||||
rouge (1.10.1)
|
||||
|
@ -659,62 +661,66 @@ GEM
|
|||
chunky_png
|
||||
rqrcode-rails3 (0.1.7)
|
||||
rqrcode (>= 0.4.2)
|
||||
rspec (3.3.0)
|
||||
rspec-core (~> 3.3.0)
|
||||
rspec-expectations (~> 3.3.0)
|
||||
rspec-mocks (~> 3.3.0)
|
||||
rspec-core (3.3.2)
|
||||
rspec-support (~> 3.3.0)
|
||||
rspec-expectations (3.3.1)
|
||||
rspec (3.4.0)
|
||||
rspec-core (~> 3.4.0)
|
||||
rspec-expectations (~> 3.4.0)
|
||||
rspec-mocks (~> 3.4.0)
|
||||
rspec-core (3.4.4)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-expectations (3.4.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.3.0)
|
||||
rspec-mocks (3.3.2)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-mocks (3.4.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.3.0)
|
||||
rspec-rails (3.3.3)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-rails (3.4.2)
|
||||
actionpack (>= 3.0, < 4.3)
|
||||
activesupport (>= 3.0, < 4.3)
|
||||
railties (>= 3.0, < 4.3)
|
||||
rspec-core (~> 3.3.0)
|
||||
rspec-expectations (~> 3.3.0)
|
||||
rspec-mocks (~> 3.3.0)
|
||||
rspec-support (~> 3.3.0)
|
||||
rspec-support (3.3.0)
|
||||
rubocop (0.35.1)
|
||||
astrolabe (~> 1.3)
|
||||
parser (>= 2.2.3.0, < 3.0)
|
||||
rspec-core (~> 3.4.0)
|
||||
rspec-expectations (~> 3.4.0)
|
||||
rspec-mocks (~> 3.4.0)
|
||||
rspec-support (~> 3.4.0)
|
||||
rspec-retry (0.4.5)
|
||||
rspec-core
|
||||
rspec-support (3.4.1)
|
||||
rubocop (0.38.0)
|
||||
parser (>= 2.3.0.6, < 3.0)
|
||||
powerpack (~> 0.1)
|
||||
rainbow (>= 1.99.1, < 3.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
tins (<= 1.6.0)
|
||||
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||
ruby-fogbugz (0.2.1)
|
||||
crack (~> 0.4)
|
||||
ruby-progressbar (1.7.5)
|
||||
ruby-saml (1.1.1)
|
||||
ruby-saml (1.1.2)
|
||||
nokogiri (>= 1.5.10)
|
||||
uuid (~> 2.3)
|
||||
ruby2ruby (2.2.0)
|
||||
ruby2ruby (2.3.0)
|
||||
ruby_parser (~> 3.1)
|
||||
sexp_processor (~> 4.0)
|
||||
ruby_parser (3.7.2)
|
||||
ruby_parser (3.8.1)
|
||||
sexp_processor (~> 4.1)
|
||||
rubyntlm (0.5.2)
|
||||
rubypants (0.2.0)
|
||||
rufus-scheduler (3.1.10)
|
||||
rugged (0.24.0b13)
|
||||
rugged (0.24.0)
|
||||
safe_yaml (1.0.4)
|
||||
sanitize (2.1.0)
|
||||
nokogiri (>= 1.4.4)
|
||||
sass (3.4.20)
|
||||
sass (3.4.21)
|
||||
sass-rails (5.0.4)
|
||||
railties (>= 4.0.0, < 5.0)
|
||||
sass (~> 3.1)
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
sprockets-rails (>= 2.0, < 4.0)
|
||||
tilt (>= 1.1, < 3)
|
||||
sawyer (0.6.0)
|
||||
addressable (~> 2.3.5)
|
||||
sawyer (0.7.0)
|
||||
addressable (>= 2.3.5, < 2.5)
|
||||
faraday (~> 0.8, < 0.10)
|
||||
scss_lint (0.47.1)
|
||||
rake (>= 0.9, < 11)
|
||||
sass (~> 3.4.15)
|
||||
sdoc (0.3.20)
|
||||
json (>= 1.1.3)
|
||||
rdoc (~> 3.10)
|
||||
|
@ -726,22 +732,21 @@ GEM
|
|||
sentry-raven (0.15.6)
|
||||
faraday (>= 0.7.6)
|
||||
settingslogic (2.0.9)
|
||||
sexp_processor (4.6.0)
|
||||
sexp_processor (4.7.0)
|
||||
sham_rack (1.3.6)
|
||||
rack
|
||||
shoulda-matchers (2.8.0)
|
||||
activesupport (>= 3.0.0)
|
||||
sidekiq (4.0.1)
|
||||
sidekiq (4.1.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
connection_pool (~> 2.2, >= 2.2.0)
|
||||
json (~> 1.0)
|
||||
redis (~> 3.2, >= 3.2.1)
|
||||
sidekiq-cron (0.4.0)
|
||||
redis-namespace (>= 1.5.2)
|
||||
rufus-scheduler (>= 2.0.24)
|
||||
sidekiq (>= 4.0.0)
|
||||
simple_oauth (0.1.9)
|
||||
simplecov (0.10.0)
|
||||
simplecov (0.11.2)
|
||||
docile (~> 1.1.0)
|
||||
json (~> 1.8)
|
||||
simplecov-html (~> 0.10.0)
|
||||
|
@ -764,22 +769,22 @@ GEM
|
|||
capybara (>= 2.0.0)
|
||||
railties (>= 3)
|
||||
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 (>= 0.9.1)
|
||||
spring-commands-spinach (1.0.0)
|
||||
spring-commands-spinach (1.1.0)
|
||||
spring (>= 0.9.1)
|
||||
spring-commands-teaspoon (0.0.2)
|
||||
spring (>= 0.9.1)
|
||||
sprockets (2.12.4)
|
||||
hike (~> 1.2)
|
||||
multi_json (~> 1.0)
|
||||
rack (~> 1.0)
|
||||
tilt (~> 1.1, != 1.3.0)
|
||||
sprockets-rails (2.3.3)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
sprockets (3.6.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.0.4)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
state_machines (0.4.0)
|
||||
state_machines-activemodel (0.3.0)
|
||||
activemodel (~> 4.1)
|
||||
|
@ -791,8 +796,8 @@ GEM
|
|||
systemu (2.6.5)
|
||||
task_list (1.0.2)
|
||||
html-pipeline
|
||||
teaspoon (1.0.2)
|
||||
railties (>= 3.2.5, < 5)
|
||||
teaspoon (1.1.5)
|
||||
railties (>= 3.2.5, < 6)
|
||||
teaspoon-jasmine (2.2.0)
|
||||
teaspoon (>= 1.0.0)
|
||||
temple (0.7.6)
|
||||
|
@ -807,7 +812,7 @@ GEM
|
|||
rack (~> 1.0)
|
||||
thor (0.19.1)
|
||||
thread_safe (0.3.5)
|
||||
tilt (1.4.1)
|
||||
tilt (2.0.2)
|
||||
timfel-krb5-auth (0.8.3)
|
||||
tinder (1.10.1)
|
||||
eventmachine (~> 1.0)
|
||||
|
@ -833,8 +838,9 @@ GEM
|
|||
underscore-rails (1.8.3)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.1)
|
||||
unicorn (4.8.3)
|
||||
unf_ext (0.0.7.2)
|
||||
unicode-display_width (1.0.2)
|
||||
unicorn (4.9.0)
|
||||
kgio (~> 2.6)
|
||||
rack
|
||||
raindrops (~> 0.7)
|
||||
|
@ -852,7 +858,7 @@ GEM
|
|||
equalizer (~> 0.0, >= 0.0.9)
|
||||
warden (1.2.4)
|
||||
rack (>= 1.0)
|
||||
web-console (2.2.1)
|
||||
web-console (2.3.0)
|
||||
activemodel (>= 4.0)
|
||||
binding_of_caller (>= 0.7.2)
|
||||
railties (>= 4.0)
|
||||
|
@ -876,33 +882,32 @@ PLATFORMS
|
|||
|
||||
DEPENDENCIES
|
||||
RedCloth (~> 4.2.9)
|
||||
ace-rails-ap (~> 2.0.1)
|
||||
activerecord-deprecated_finders (~> 1.0.3)
|
||||
ace-rails-ap (~> 4.0.2)
|
||||
activerecord-session_store (~> 0.1.0)
|
||||
acts-as-taggable-on (~> 3.4)
|
||||
addressable (~> 2.3.8)
|
||||
after_commit_queue
|
||||
akismet (~> 2.0)
|
||||
allocations (~> 1.0)
|
||||
annotate (~> 2.6.0)
|
||||
asana (~> 0.4.0)
|
||||
asciidoctor (~> 1.5.2)
|
||||
attr_encrypted (~> 1.3.4)
|
||||
awesome_print (~> 1.2.0)
|
||||
babosa (~> 1.0.2)
|
||||
base32 (~> 0.3.0)
|
||||
benchmark-ips
|
||||
better_errors (~> 1.0.1)
|
||||
binding_of_caller (~> 0.7.2)
|
||||
bootstrap-sass (~> 3.3.0)
|
||||
brakeman (~> 3.1.0)
|
||||
brakeman (~> 3.2.0)
|
||||
browser (~> 1.0.0)
|
||||
bullet
|
||||
bundler-audit
|
||||
byebug
|
||||
cal-heatmap-rails (~> 3.5.0)
|
||||
capybara (~> 2.4.0)
|
||||
cal-heatmap-rails (~> 3.6.0)
|
||||
capybara (~> 2.6.2)
|
||||
capybara-screenshot (~> 1.0.0)
|
||||
carrierwave (~> 0.9.0)
|
||||
carrierwave (~> 0.10.0)
|
||||
charlock_holmes (~> 0.7.3)
|
||||
coffee-rails (~> 4.1.0)
|
||||
colorize (~> 0.7.0)
|
||||
|
@ -916,11 +921,11 @@ DEPENDENCIES
|
|||
devise-async (~> 0.9.0)
|
||||
devise-two-factor (~> 2.0.0)
|
||||
diffy (~> 3.0.3)
|
||||
doorkeeper (~> 2.2.0)
|
||||
doorkeeper (~> 3.1)
|
||||
dropzonejs-rails (~> 0.7.1)
|
||||
email_reply_parser (~> 0.5.8)
|
||||
email_spec (~> 1.6.0)
|
||||
factory_girl_rails (~> 4.3.0)
|
||||
factory_girl_rails (~> 4.6.0)
|
||||
ffaker (~> 2.0.0)
|
||||
flay
|
||||
flog
|
||||
|
@ -933,27 +938,30 @@ DEPENDENCIES
|
|||
github-markup (~> 1.3.1)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab_emoji (~> 0.3.0)
|
||||
gitlab_git (~> 8.2)
|
||||
gitlab_git (~> 10.0)
|
||||
gitlab_meta (= 7.0)
|
||||
gitlab_omniauth-ldap (~> 1.2.1)
|
||||
gollum-lib (~> 4.1.0)
|
||||
gollum-rugged_adapter (~> 0.4.2)
|
||||
gon (~> 6.0.1)
|
||||
grape (~> 0.13.0)
|
||||
grape-entity (~> 0.4.2)
|
||||
haml-rails (~> 0.9.0)
|
||||
health_check (~> 1.5.1)
|
||||
hipchat (~> 1.5.0)
|
||||
html-pipeline (~> 1.11.0)
|
||||
httparty (~> 0.13.3)
|
||||
influxdb (~> 0.2)
|
||||
jquery-atwho-rails (~> 1.3.2)
|
||||
jquery-rails (~> 4.0.0)
|
||||
jquery-scrollto-rails (~> 1.4.3)
|
||||
jquery-rails (~> 4.1.0)
|
||||
jquery-turbolinks (~> 2.1.0)
|
||||
jquery-ui-rails (~> 5.0.0)
|
||||
jwt
|
||||
kaminari (~> 0.16.3)
|
||||
letter_opener (~> 1.1.2)
|
||||
letter_opener_web (~> 1.3.0)
|
||||
licensee (~> 8.0.0)
|
||||
loofah (~> 2.0.3)
|
||||
mail_room (~> 0.6.1)
|
||||
mail_room (~> 0.7)
|
||||
method_source (~> 0.8)
|
||||
minitest (~> 5.7.0)
|
||||
mousetrap-rails (~> 1.4.6)
|
||||
|
@ -962,10 +970,10 @@ DEPENDENCIES
|
|||
net-ssh (~> 3.0.1)
|
||||
newrelic_rpm (~> 3.14)
|
||||
nokogiri (~> 1.6.7, >= 1.6.7.2)
|
||||
nprogress-rails (~> 0.1.6.7)
|
||||
oauth2 (~> 1.0.0)
|
||||
octokit (~> 3.8.0)
|
||||
octokit (~> 4.3.0)
|
||||
omniauth (~> 1.3.1)
|
||||
omniauth-auth0 (~> 1.4.1)
|
||||
omniauth-azure-oauth2 (~> 0.0.6)
|
||||
omniauth-bitbucket (~> 0.0.2)
|
||||
omniauth-cas3 (~> 1.1.2)
|
||||
|
@ -974,38 +982,42 @@ DEPENDENCIES
|
|||
omniauth-gitlab (~> 1.0.0)
|
||||
omniauth-google-oauth2 (~> 0.2.0)
|
||||
omniauth-kerberos (~> 0.3.0)
|
||||
omniauth-saml (~> 1.4.2)
|
||||
omniauth-saml (~> 1.5.0)
|
||||
omniauth-shibboleth (~> 1.2.0)
|
||||
omniauth-twitter (~> 1.2.0)
|
||||
omniauth_crowd (~> 2.2.0)
|
||||
org-ruby (~> 0.9.12)
|
||||
paranoia (~> 2.0)
|
||||
pg (~> 0.18.2)
|
||||
poltergeist (~> 1.8.1)
|
||||
poltergeist (~> 1.9.0)
|
||||
premailer-rails (~> 1.9.0)
|
||||
pry-rails
|
||||
quiet_assets (~> 1.0.2)
|
||||
rack-attack (~> 4.3.1)
|
||||
rack-cors (~> 0.4.0)
|
||||
rack-oauth2 (~> 1.2.1)
|
||||
rails (= 4.2.5.2)
|
||||
rails (= 4.2.6)
|
||||
rails-deprecated_sanitizer (~> 1.0.3)
|
||||
raphael-rails (~> 2.1.2)
|
||||
rblineprof
|
||||
rdoc (~> 3.6)
|
||||
recaptcha
|
||||
redcarpet (~> 3.3.3)
|
||||
redis (~> 3.2)
|
||||
redis-namespace
|
||||
redis-rails (~> 4.0.0)
|
||||
request_store (~> 1.2.0)
|
||||
request_store (~> 1.3.0)
|
||||
rerun (~> 0.11.0)
|
||||
responders (~> 2.0)
|
||||
rouge (~> 1.10.1)
|
||||
rqrcode-rails3 (~> 0.1.7)
|
||||
rspec-rails (~> 3.3.0)
|
||||
rubocop (~> 0.35.0)
|
||||
rspec-rails (~> 3.4.0)
|
||||
rspec-retry
|
||||
rubocop (~> 0.38.0)
|
||||
ruby-fogbugz (~> 0.2.1)
|
||||
sanitize (~> 2.0)
|
||||
sass-rails (~> 5.0.0)
|
||||
scss_lint (~> 0.47.0)
|
||||
sdoc (~> 0.3.20)
|
||||
seed-fu (~> 2.3.5)
|
||||
select2-rails (~> 3.5.9)
|
||||
|
@ -1015,19 +1027,20 @@ DEPENDENCIES
|
|||
shoulda-matchers (~> 2.8.0)
|
||||
sidekiq (~> 4.0)
|
||||
sidekiq-cron (~> 0.4.0)
|
||||
simplecov (~> 0.10.0)
|
||||
simplecov (~> 0.11.0)
|
||||
sinatra (~> 1.4.4)
|
||||
six (~> 0.2.0)
|
||||
slack-notifier (~> 1.2.0)
|
||||
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-spinach (~> 1.0.0)
|
||||
spring-commands-spinach (~> 1.1.0)
|
||||
spring-commands-teaspoon (~> 0.0.2)
|
||||
sprockets (~> 2.12.3)
|
||||
sprockets (~> 3.6.0)
|
||||
state_machines-activerecord (~> 0.3.0)
|
||||
task_list (~> 1.0.2)
|
||||
teaspoon (~> 1.0.0)
|
||||
teaspoon (~> 1.1.0)
|
||||
teaspoon-jasmine (~> 2.2.0)
|
||||
test_after_commit (~> 0.4.2)
|
||||
thin (~> 1.6.1)
|
||||
|
@ -1036,7 +1049,7 @@ DEPENDENCIES
|
|||
uglifier (~> 2.7.2)
|
||||
underscore-rails (~> 1.8.0)
|
||||
unf (~> 0.1.4)
|
||||
unicorn (~> 4.8.2)
|
||||
unicorn (~> 4.9.0)
|
||||
unicorn-worker-killer (~> 0.4.2)
|
||||
version_sorter (~> 2.0.0)
|
||||
virtus (~> 1.0.1)
|
||||
|
@ -1045,4 +1058,4 @@ DEPENDENCIES
|
|||
wikicloth (= 0.8.1)
|
||||
|
||||
BUNDLED WITH
|
||||
1.11.2
|
||||
1.12.3
|
||||
|
|
131
PROCESS.md
131
PROCESS.md
|
@ -2,23 +2,39 @@
|
|||
|
||||
## 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
|
||||
|
||||
### 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.)
|
||||
- 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)
|
||||
|
||||
- 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.)
|
||||
- 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
|
||||
|
||||
### Merge marshal
|
||||
### Merge marshall & merge request coach
|
||||
|
||||
- Responds to merge requests the issue team mentions them in and monitors for new merge requests
|
||||
- Provides feedback to the merge request submitter to improve the merge request (style, tests, etc.)
|
||||
- Mark merge requests 'ready-for-merge' when they meet the contribution guidelines
|
||||
- Mention developer(s) based on the [list of members and their specialities](https://about.gitlab.com/core-team/)
|
||||
- Responds to merge requests the issue team mentions them in and monitors for
|
||||
new merge requests
|
||||
- Provides feedback to the merge request submitter to improve the merge request
|
||||
(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
|
||||
|
||||
## 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
|
||||
|
||||
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 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 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)
|
||||
- *Attached MR*: There is a MR attached and the discussion should happen there
|
||||
- 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.
|
||||
- *Developer*: needs help from a developer
|
||||
- *UX* needs needs help from a UX designer
|
||||
- *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
|
||||
- ~"Awaiting Feedback" Feedback pending from the reporter
|
||||
- ~UX needs help from a UX designer
|
||||
- ~Frontend needs help from a Front-end engineer. Please follow the
|
||||
["Implement design & UI elements" guidelines].
|
||||
- ~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`.
|
||||
- ~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.
|
||||
|
||||
## 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
|
||||
|
||||
|
@ -60,21 +88,48 @@ If an issue is complex and needs the attention of a specific person, assignment
|
|||
|
||||
## Label colors
|
||||
|
||||
- Light orange `#fef2c0`: workflow labels for issue team members (awaiting 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
|
||||
- Green labels `#009800`: issues that can generally be ignored. For example, 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)
|
||||
- Light orange `#fef2c0`: workflow labels for issue team members (awaiting
|
||||
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
|
||||
- Green labels `#009800`: issues that can generally be ignored. For example,
|
||||
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 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
|
||||
|
||||
### 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
|
||||
|
||||
|
@ -110,11 +165,11 @@ This merge request has been closed because a request for more information has no
|
|||
|
||||
### Accepting merge requests
|
||||
|
||||
Is there an issue on the [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues)
|
||||
that is similar to this?
|
||||
Could you please link it here?
|
||||
Is there an issue on the
|
||||
\[issue tracker\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues) that is
|
||||
similar to this? Could you please link it here?
|
||||
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.
|
||||
|
||||
### 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
|
||||
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
|
||||
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
|
||||
|
|
12
README.md
12
README.md
|
@ -1,6 +1,6 @@
|
|||
# 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)
|
||||
[![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)
|
||||
|
@ -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)
|
||||
- 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
|
||||
|
||||
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:
|
||||
|
||||
- [Subscriptions](https://about.gitlab.com/subscription/)
|
||||
- [Subscriptions](https://about.gitlab.com/pricing/)
|
||||
- [Consultancy](https://about.gitlab.com/consultancy/)
|
||||
- [Community](https://about.gitlab.com/community/)
|
||||
- [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.
|
||||
|
||||
## Requirements
|
||||
|
@ -80,7 +84,7 @@ There are a lot of [third-party applications integrating with GitLab](https://ab
|
|||
|
||||
## 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
|
||||
|
||||
|
|
2
VERSION
2
VERSION
|
@ -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 |
|
@ -1,7 +1,7 @@
|
|||
class @Activities
|
||||
constructor: ->
|
||||
Pager.init 20, true
|
||||
$(".event-filter a").bind "click", (event) =>
|
||||
$(".event-filter-link").on "click", (event) =>
|
||||
event.preventDefault()
|
||||
@toggleFilter($(event.currentTarget))
|
||||
@reloadActivities()
|
||||
|
@ -12,18 +12,10 @@ class @Activities
|
|||
|
||||
|
||||
toggleFilter: (sender) ->
|
||||
sender.closest('li').toggleClass "active"
|
||||
$('.event-filter .active').removeClass "active"
|
||||
event_filters = $.cookie("event_filter")
|
||||
filter = sender.attr("id").split("_")[0]
|
||||
if event_filters
|
||||
event_filters = event_filters.split(",")
|
||||
else
|
||||
event_filters = new Array()
|
||||
$.cookie "event_filter", (if event_filters isnt filter then filter else ""), { path: '/' }
|
||||
|
||||
index = event_filters.indexOf(filter)
|
||||
if index is -1
|
||||
event_filters.push filter
|
||||
else
|
||||
event_filters.splice index, 1
|
||||
|
||||
$.cookie "event_filter", event_filters.join(","), { path: '/' }
|
||||
if event_filters isnt filter
|
||||
sender.closest('li').toggleClass "active"
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
@Api =
|
||||
groups_path: "/api/:version/groups.json"
|
||||
group_path: "/api/:version/groups/:id.json"
|
||||
namespaces_path: "/api/:version/namespaces.json"
|
||||
group_projects_path: "/api/:version/groups/:id/projects.json"
|
||||
projects_path: "/api/:version/projects.json"
|
||||
groupsPath: "/api/:version/groups.json"
|
||||
groupPath: "/api/:version/groups/:id.json"
|
||||
namespacesPath: "/api/:version/namespaces.json"
|
||||
groupProjectsPath: "/api/:version/groups/:id/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) ->
|
||||
url = Api.buildUrl(Api.group_path)
|
||||
url = Api.buildUrl(Api.groupPath)
|
||||
url = url.replace(':id', group_id)
|
||||
|
||||
$.ajax(
|
||||
|
@ -20,7 +23,7 @@
|
|||
# Return groups list. Filtered by query
|
||||
# Only active groups retrieved
|
||||
groups: (query, skip_ldap, callback) ->
|
||||
url = Api.buildUrl(Api.groups_path)
|
||||
url = Api.buildUrl(Api.groupsPath)
|
||||
|
||||
$.ajax(
|
||||
url: url
|
||||
|
@ -34,7 +37,7 @@
|
|||
|
||||
# Return namespaces list. Filtered by query
|
||||
namespaces: (query, callback) ->
|
||||
url = Api.buildUrl(Api.namespaces_path)
|
||||
url = Api.buildUrl(Api.namespacesPath)
|
||||
|
||||
$.ajax(
|
||||
url: url
|
||||
|
@ -48,7 +51,7 @@
|
|||
|
||||
# Return projects list. Filtered by query
|
||||
projects: (query, order, callback) ->
|
||||
url = Api.buildUrl(Api.projects_path)
|
||||
url = Api.buildUrl(Api.projectsPath)
|
||||
|
||||
$.ajax(
|
||||
url: url
|
||||
|
@ -61,9 +64,24 @@
|
|||
).done (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
|
||||
groupProjects: (group_id, query, callback) ->
|
||||
url = Api.buildUrl(Api.group_projects_path)
|
||||
url = Api.buildUrl(Api.groupProjectsPath)
|
||||
url = url.replace(':id', group_id)
|
||||
|
||||
$.ajax(
|
||||
|
@ -76,6 +94,22 @@
|
|||
).done (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) ->
|
||||
url = gon.relative_url_root + url if gon.relative_url_root?
|
||||
return url.replace(':version', gon.api_version)
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#= require jquery
|
||||
#= require jquery-ui/autocomplete
|
||||
#= require jquery-ui/datepicker
|
||||
#= require jquery-ui/draggable
|
||||
#= require jquery-ui/effect-highlight
|
||||
#= require jquery-ui/sortable
|
||||
#= require jquery_ujs
|
||||
|
@ -21,7 +22,17 @@
|
|||
#= require cal-heatmap
|
||||
#= require turbolinks
|
||||
#= 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 raphael
|
||||
#= require g.raphael
|
||||
|
@ -31,8 +42,6 @@
|
|||
#= require ace/ace
|
||||
#= require ace/ext-searchbox
|
||||
#= require underscore
|
||||
#= require nprogress
|
||||
#= require nprogress-turbolinks
|
||||
#= require dropzone
|
||||
#= require mousetrap
|
||||
#= require mousetrap/pause
|
||||
|
@ -42,8 +51,10 @@
|
|||
#= require shortcuts_issuable
|
||||
#= require shortcuts_network
|
||||
#= require jquery.nicescroll
|
||||
#= require date.format
|
||||
#= require_tree .
|
||||
#= require fuzzaldrin-plus
|
||||
#= require cropper
|
||||
|
||||
window.slugify = (text) ->
|
||||
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
|
||||
|
@ -109,6 +120,8 @@ window.onload = ->
|
|||
setTimeout shiftWindow, 100
|
||||
|
||||
$ ->
|
||||
bootstrapBreakpoint = bp.getBreakpointSize()
|
||||
|
||||
$(".nicescroll").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF")
|
||||
|
||||
# Click a .js-select-on-focus field, select the contents
|
||||
|
@ -138,7 +151,7 @@ $ ->
|
|||
|
||||
# Initialize tooltips
|
||||
$('body').tooltip(
|
||||
selector: '.has_tooltip, [data-toggle="tooltip"]'
|
||||
selector: '.has-tooltip, [data-toggle="tooltip"]'
|
||||
placement: (_, el) ->
|
||||
$el = $(el)
|
||||
$el.data('placement') || 'bottom'
|
||||
|
@ -161,7 +174,7 @@ $ ->
|
|||
$('.trigger-submit').on 'change', ->
|
||||
$(@).parents('form').submit()
|
||||
|
||||
$('abbr.timeago, .js-timeago').timeago()
|
||||
gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true)
|
||||
|
||||
# Flash
|
||||
if (flash = $(".flash-container")).length > 0
|
||||
|
@ -191,6 +204,7 @@ $ ->
|
|||
$('.header-content .title').toggle()
|
||||
$('.header-content .navbar-collapse').toggle()
|
||||
$('.navbar-toggle').toggleClass('active')
|
||||
$('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left")
|
||||
|
||||
# Show/hide comments on diff
|
||||
$("body").on "click", ".js-toggle-diff-comments", (e) ->
|
||||
|
@ -210,82 +224,68 @@ $ ->
|
|||
$this = $(this)
|
||||
$this.attr 'value', $this.val()
|
||||
return
|
||||
|
||||
|
||||
$(document)
|
||||
.off 'keyup', 'input[type="search"]'
|
||||
.on 'keyup', 'input[type="search"]' , (e) ->
|
||||
$this = $(this)
|
||||
$this.attr 'value', $this.val()
|
||||
|
||||
$sidebarGutterToggle = $('.js-sidebar-toggle')
|
||||
$navIconToggle = $('.toggle-nav-collapse')
|
||||
|
||||
$(document)
|
||||
.off 'breakpoint:change'
|
||||
.on 'breakpoint:change', (e, breakpoint) ->
|
||||
if breakpoint is 'sm' or breakpoint is 'xs'
|
||||
$gutterIcon = $('.gutter-toggle').find('i')
|
||||
$gutterIcon = $sidebarGutterToggle.find('i')
|
||||
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)
|
||||
.off 'click', 'aside .gutter-toggle'
|
||||
.on 'click', 'aside .gutter-toggle', (e) ->
|
||||
.off 'click', '.js-sidebar-toggle'
|
||||
.on 'click', '.js-sidebar-toggle', (e, triggered) ->
|
||||
e.preventDefault()
|
||||
$this = $(this)
|
||||
$thisIcon = $this.find 'i'
|
||||
$allGutterToggleIcons = $('.js-sidebar-toggle i')
|
||||
if $thisIcon.hasClass('fa-angle-double-right')
|
||||
$thisIcon
|
||||
$allGutterToggleIcons
|
||||
.removeClass('fa-angle-double-right')
|
||||
.addClass('fa-angle-double-left')
|
||||
$this
|
||||
.closest('aside')
|
||||
$('aside.right-sidebar')
|
||||
.removeClass('right-sidebar-expanded')
|
||||
.addClass('right-sidebar-collapsed')
|
||||
$('.page-with-sidebar')
|
||||
.removeClass('right-sidebar-expanded')
|
||||
.addClass('right-sidebar-collapsed')
|
||||
else
|
||||
$thisIcon
|
||||
$allGutterToggleIcons
|
||||
.removeClass('fa-angle-double-left')
|
||||
.addClass('fa-angle-double-right')
|
||||
$this
|
||||
.closest('aside')
|
||||
$('aside.right-sidebar')
|
||||
.removeClass('right-sidebar-collapsed')
|
||||
.addClass('right-sidebar-expanded')
|
||||
$('.page-with-sidebar')
|
||||
.removeClass('right-sidebar-collapsed')
|
||||
.addClass('right-sidebar-expanded')
|
||||
$.cookie("collapsed_gutter",
|
||||
$('.right-sidebar')
|
||||
.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()
|
||||
if not triggered
|
||||
$.cookie("collapsed_gutter",
|
||||
$('.right-sidebar')
|
||||
.hasClass('right-sidebar-collapsed'), { path: '/' })
|
||||
|
||||
fitSidebarForSize = ->
|
||||
oldBootstrapBreakpoint = bootstrapBreakpoint
|
||||
checkBootstrapBreakpoints()
|
||||
bootstrapBreakpoint = bp.getBreakpointSize()
|
||||
if bootstrapBreakpoint != oldBootstrapBreakpoint
|
||||
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
|
||||
|
||||
checkInitialSidebarSize = ->
|
||||
bootstrapBreakpoint = bp.getBreakpointSize()
|
||||
if bootstrapBreakpoint is "xs" or "sm"
|
||||
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
|
||||
|
||||
|
@ -294,6 +294,5 @@ $ ->
|
|||
.on "resize", (e) ->
|
||||
fitSidebarForSize()
|
||||
|
||||
setBootstrapBreakpoints()
|
||||
checkInitialSidebarSize()
|
||||
new Aside()
|
||||
|
|
|
@ -5,7 +5,6 @@ class @Aside
|
|||
e.preventDefault()
|
||||
btn = $(e.currentTarget)
|
||||
icon = btn.find('i')
|
||||
console.log('1')
|
||||
|
||||
if icon.hasClass('fa-angle-left')
|
||||
btn.parent().find('section').hide()
|
||||
|
|
|
@ -16,11 +16,11 @@ class @Autosave
|
|||
|
||||
try
|
||||
text = window.localStorage.getItem @key
|
||||
catch
|
||||
catch e
|
||||
return
|
||||
|
||||
@field.val text if text?.length > 0
|
||||
@field.trigger "input"
|
||||
@field.trigger "input"
|
||||
|
||||
save: ->
|
||||
return unless window.localStorage?
|
||||
|
@ -35,5 +35,5 @@ class @Autosave
|
|||
reset: ->
|
||||
return unless window.localStorage?
|
||||
|
||||
try
|
||||
try
|
||||
window.localStorage.removeItem @key
|
||||
|
|
|
@ -1,37 +1,77 @@
|
|||
class @AwardsHandler
|
||||
constructor: (@post_emoji_url, @noteable_type, @noteable_id, @aliases) ->
|
||||
$(".add-award").click (event)->
|
||||
constructor: (@getEmojisUrl, @postEmojiUrl, @noteableType, @noteableId, @unicodes) ->
|
||||
$('.js-add-award').on 'click', (event) =>
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
$(".emoji-menu").show()
|
||||
$("#emoji_search").focus()
|
||||
|
||||
$("html").on 'click', (event) ->
|
||||
if !$(event.target).closest(".emoji-menu").length
|
||||
if $(".emoji-menu").is(":visible")
|
||||
$(".emoji-menu").hide()
|
||||
@showEmojiMenu()
|
||||
|
||||
$('html').on 'click', (event) ->
|
||||
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()
|
||||
@setupSearch()
|
||||
|
||||
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()
|
||||
, 200
|
||||
|
||||
addAward: (emoji) ->
|
||||
emoji = @normilizeEmojiName(emoji)
|
||||
@postEmoji emoji, =>
|
||||
@addAwardToEmojiBar(emoji)
|
||||
|
||||
$(".emoji-menu").hide()
|
||||
$('.emoji-menu').removeClass 'is-visible'
|
||||
|
||||
addAwardToEmojiBar: (emoji) ->
|
||||
@addEmojiToFrequentlyUsedList(emoji)
|
||||
|
||||
emoji = @normilizeEmojiName(emoji)
|
||||
if @exist(emoji)
|
||||
if @isActive(emoji)
|
||||
@decrementCounter(emoji)
|
||||
else
|
||||
counter = @findEmojiIcon(emoji).siblings(".counter")
|
||||
counter = @findEmojiIcon(emoji).siblings('.js-counter')
|
||||
counter.text(parseInt(counter.text()) + 1)
|
||||
counter.parent().addClass("active")
|
||||
counter.parent().addClass('active')
|
||||
@addMeToAuthorList(emoji)
|
||||
else
|
||||
@createEmoji(emoji)
|
||||
|
@ -40,43 +80,47 @@ class @AwardsHandler
|
|||
@findEmojiIcon(emoji).length > 0
|
||||
|
||||
isActive: (emoji) ->
|
||||
@findEmojiIcon(emoji).parent().hasClass("active")
|
||||
@findEmojiIcon(emoji).parent().hasClass('active')
|
||||
|
||||
decrementCounter: (emoji) ->
|
||||
counter = @findEmojiIcon(emoji).siblings(".counter")
|
||||
counter = @findEmojiIcon(emoji).siblings('.js-counter')
|
||||
emojiIcon = counter.parent()
|
||||
if parseInt(counter.text()) > 1
|
||||
counter.text(parseInt(counter.text()) - 1)
|
||||
emojiIcon.removeClass("active")
|
||||
emojiIcon.removeClass('active')
|
||||
@removeMeFromAuthorList(emoji)
|
||||
else if emoji == "thumbsup" || emoji == "thumbsdown"
|
||||
emojiIcon.tooltip("destroy")
|
||||
else if emoji == 'thumbsup' || emoji == 'thumbsdown'
|
||||
emojiIcon.tooltip('destroy')
|
||||
counter.text(0)
|
||||
emojiIcon.removeClass("active")
|
||||
emojiIcon.removeClass('active')
|
||||
@removeMeFromAuthorList(emoji)
|
||||
else
|
||||
emojiIcon.tooltip("destroy")
|
||||
emojiIcon.tooltip('destroy')
|
||||
emojiIcon.remove()
|
||||
|
||||
removeMeFromAuthorList: (emoji) ->
|
||||
award_block = @findEmojiIcon(emoji).parent()
|
||||
authors = award_block.attr("data-original-title").split(", ")
|
||||
authors.splice(authors.indexOf("me"),1)
|
||||
award_block.closest(".award").attr("data-original-title", authors.join(", "))
|
||||
@resetTooltip(award_block)
|
||||
awardBlock = @findEmojiIcon(emoji).parent()
|
||||
authors = awardBlock
|
||||
.attr('data-original-title')
|
||||
.split(', ')
|
||||
authors.splice(authors.indexOf('me'),1)
|
||||
awardBlock
|
||||
.closest('.js-emoji-btn')
|
||||
.attr('data-original-title', authors.join(', '))
|
||||
@resetTooltip(awardBlock)
|
||||
|
||||
addMeToAuthorList: (emoji) ->
|
||||
award_block = @findEmojiIcon(emoji).parent()
|
||||
origTitle = award_block.attr("data-original-title").trim()
|
||||
awardBlock = @findEmojiIcon(emoji).parent()
|
||||
origTitle = awardBlock.attr('data-original-title').trim()
|
||||
authors = []
|
||||
if origTitle
|
||||
authors = origTitle.split(', ')
|
||||
authors.push("me")
|
||||
award_block.attr("title", authors.join(", "))
|
||||
@resetTooltip(award_block)
|
||||
authors.push('me')
|
||||
awardBlock.attr('data-original-title', authors.join(', '))
|
||||
@resetTooltip(awardBlock)
|
||||
|
||||
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.
|
||||
setTimeout (->
|
||||
|
@ -88,83 +132,84 @@ class @AwardsHandler
|
|||
emojiCssClass = @resolveNameToCssClass(emoji)
|
||||
|
||||
nodes = []
|
||||
nodes.push("<div class='award active' title='me'>")
|
||||
nodes.push("<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>")
|
||||
nodes.push("<div class='counter'>1</div>")
|
||||
nodes.push("</div>")
|
||||
nodes.push(
|
||||
"<button class='btn award-control js-emoji-btn has-tooltip active' data-original-title='me'>",
|
||||
"<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>",
|
||||
"<span class='award-control-text js-counter'>1</span>",
|
||||
"</button>"
|
||||
)
|
||||
|
||||
emoji_node = $(nodes.join("\n")).insertBefore(".awards-controls").find(".emoji-icon").data("emoji", emoji)
|
||||
|
||||
$(".award").tooltip()
|
||||
$(nodes.join("\n"))
|
||||
.insertBefore('.js-award-holder')
|
||||
.find('.emoji-icon')
|
||||
.data('emoji', emoji)
|
||||
$('.award-control').tooltip()
|
||||
|
||||
resolveNameToCssClass: (emoji) ->
|
||||
emoji_icon = $(".emoji-menu-content [data-emoji='#{emoji}']")
|
||||
emojiIcon = $(".emoji-menu-content [data-emoji='#{emoji}']")
|
||||
|
||||
if emoji_icon.length > 0
|
||||
unicodeName = emoji_icon.data("unicode-name")
|
||||
if emojiIcon.length > 0
|
||||
unicodeName = emojiIcon.data('unicode-name')
|
||||
else
|
||||
# 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}"
|
||||
|
||||
postEmoji: (emoji, callback) ->
|
||||
$.post @post_emoji_url, { note: {
|
||||
$.post @postEmojiUrl, { note: {
|
||||
note: ":#{emoji}:"
|
||||
noteable_type: @noteable_type
|
||||
noteable_id: @noteable_id
|
||||
noteable_type: @noteableType
|
||||
noteable_id: @noteableId
|
||||
}},(data) ->
|
||||
if data.ok
|
||||
callback.call()
|
||||
|
||||
findEmojiIcon: (emoji) ->
|
||||
$(".award [data-emoji='#{emoji}']")
|
||||
$(".awards > .js-emoji-btn [data-emoji='#{emoji}']")
|
||||
|
||||
scrollToAwards: ->
|
||||
$('body, html').animate({
|
||||
scrollTop: $('.awards').offset().top - 80
|
||||
}, 200)
|
||||
|
||||
normilizeEmojiName: (emoji) ->
|
||||
@aliases[emoji] || emoji
|
||||
|
||||
addEmojiToFrequentlyUsedList: (emoji) ->
|
||||
frequently_used_emojis = @getFrequentlyUsedEmojis()
|
||||
frequently_used_emojis.push(emoji)
|
||||
$.cookie('frequently_used_emojis', frequently_used_emojis.join(","), { expires: 365 })
|
||||
frequentlyUsedEmojis = @getFrequentlyUsedEmojis()
|
||||
frequentlyUsedEmojis.push(emoji)
|
||||
$.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 })
|
||||
|
||||
getFrequentlyUsedEmojis: ->
|
||||
frequently_used_emojis = ($.cookie('frequently_used_emojis') || "").split(",")
|
||||
_.compact(_.uniq(frequently_used_emojis))
|
||||
frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') || '').split(',')
|
||||
_.compact(_.uniq(frequentlyUsedEmojis))
|
||||
|
||||
renderFrequentlyUsedBlock: ->
|
||||
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) ->
|
||||
$(".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: ->
|
||||
$("input.emoji-search").keyup (ev) =>
|
||||
$('input.emoji-search').keyup (ev) =>
|
||||
term = $(ev.target).val()
|
||||
|
||||
# Clean previous search results
|
||||
$("ul.emoji-search,h5.emoji-search").remove()
|
||||
$('ul.emoji-menu-search, h5.emoji-search').remove()
|
||||
|
||||
if term
|
||||
# Generate a search result block
|
||||
h5 = $("<h5>").text("Search results").addClass("emoji-search")
|
||||
found_emojis = @searchEmojis(term).show()
|
||||
ul = $("<ul>").addClass("emoji-search").append(found_emojis)
|
||||
$(".emoji-menu-content ul, .emoji-menu-content h5").hide()
|
||||
$(".emoji-menu-content").append(h5).append(ul)
|
||||
h5 = $('<h5>').text('Search results').addClass('emoji-search')
|
||||
foundEmojis = @searchEmojis(term).show()
|
||||
ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis)
|
||||
$('.emoji-menu-content ul, .emoji-menu-content h5').hide()
|
||||
$('.emoji-menu-content').append(h5).append(ul)
|
||||
else
|
||||
$(".emoji-menu-content").children().show()
|
||||
$('.emoji-menu-content').children().show()
|
||||
|
||||
searchEmojis: (term)->
|
||||
$(".emoji-menu-content [data-emoji*='#{term}']").closest("li").clone()
|
||||
|
|
|
@ -1,29 +1,56 @@
|
|||
# Quick Submit behavior
|
||||
#
|
||||
# When an input field with the `js-quick-submit` class receives a "Meta+Enter"
|
||||
# (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, its parent form is
|
||||
# submitted.
|
||||
# When a child field of a form with a `js-quick-submit` class receives a
|
||||
# "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form
|
||||
# is submitted.
|
||||
#
|
||||
#= require extensions/jquery
|
||||
#
|
||||
# ### Example Markup
|
||||
#
|
||||
# <form action="/foo">
|
||||
# <input type="text" class="js-quick-submit" />
|
||||
# <textarea class="js-quick-submit"></textarea>
|
||||
# <form action="/foo" class="js-quick-submit">
|
||||
# <input type="text" />
|
||||
# <textarea></textarea>
|
||||
# <input type="submit" value="Submit" />
|
||||
# </form>
|
||||
#
|
||||
$(document).on 'keydown.quick_submit', '.js-quick-submit', (e) ->
|
||||
return if (e.originalEvent && e.originalEvent.repeat) || e.repeat
|
||||
return unless e.keyCode == 13 # Enter
|
||||
isMac = ->
|
||||
navigator.userAgent.match(/Macintosh/)
|
||||
|
||||
if navigator.userAgent.match(/Macintosh/)
|
||||
return unless (e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey)
|
||||
else
|
||||
return unless (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey)
|
||||
keyCodeIs = (e, keyCode) ->
|
||||
return false if (e.originalEvent && e.originalEvent.repeat) || e.repeat
|
||||
return e.keyCode == keyCode
|
||||
|
||||
$(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()
|
||||
|
||||
$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()
|
||||
|
||||
# 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 ⌘-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'))
|
||||
|
|
|
@ -35,4 +35,18 @@ $.fn.requiresInput = ->
|
|||
$form.on 'change input', fieldSelector, requireInput
|
||||
|
||||
$ ->
|
||||
$('form.js-requires-input').requiresInput()
|
||||
$form = $('form.js-requires-input')
|
||||
$form.requiresInput()
|
||||
|
||||
# Hide or Show the help block when creating a new project
|
||||
# based on the option selected
|
||||
hideOrShowHelpBlock = (form) ->
|
||||
selected = $('.js-select-namespace option:selected')
|
||||
if selected.length and selected.data('options-parent') is 'groups'
|
||||
return form.find('.help-block').hide()
|
||||
else if selected.length
|
||||
form.find('.help-block').show()
|
||||
|
||||
hideOrShowHelpBlock($form)
|
||||
|
||||
$('.select2.js-select-namespace').change -> hideOrShowHelpBlock($form)
|
||||
|
|
|
@ -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
|
||||
)
|
30
app/assets/javascripts/blob/blob_license_selector.js.coffee
Normal file
30
app/assets/javascripts/blob/blob_license_selector.js.coffee
Normal 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()
|
|
@ -1,44 +1,40 @@
|
|||
class @EditBlob
|
||||
constructor: (assets_path, mode)->
|
||||
ace.config.set "modePath", assets_path + '/ace'
|
||||
constructor: (assets_path, ace_mode = null) ->
|
||||
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
|
||||
@editor = ace.edit("editor")
|
||||
@editor.focus()
|
||||
@editor.getSession().setMode "ace/mode/#{ace_mode}" if ace_mode
|
||||
|
||||
# Before a form submission, move the content from the Ace editor into the
|
||||
# submitted textarea
|
||||
$('form').submit ->
|
||||
$("#file-content").val(editor.getValue())
|
||||
$('form').submit =>
|
||||
$("#file-content").val(@editor.getValue())
|
||||
|
||||
editModePanes = $(".js-edit-mode-pane")
|
||||
editModeLinks = $(".js-edit-mode a")
|
||||
editModeLinks.click (event) ->
|
||||
event.preventDefault()
|
||||
currentLink = $(this)
|
||||
paneId = currentLink.attr("href")
|
||||
currentPane = editModePanes.filter(paneId)
|
||||
editModeLinks.parent().removeClass "active hover"
|
||||
currentLink.parent().addClass "active hover"
|
||||
editModePanes.hide()
|
||||
if paneId is "#preview"
|
||||
currentPane.fadeIn 200
|
||||
$.post currentLink.data("preview-url"),
|
||||
content: editor.getValue()
|
||||
, (response) ->
|
||||
currentPane.empty().append response
|
||||
currentPane.syntaxHighlight()
|
||||
return
|
||||
@initModePanesAndLinks()
|
||||
new BlobLicenseSelector(@editor)
|
||||
new BlobGitignoreSelectors(editor: @editor)
|
||||
|
||||
else
|
||||
currentPane.fadeIn 200
|
||||
editor.focus()
|
||||
return
|
||||
initModePanesAndLinks: ->
|
||||
@$editModePanes = $(".js-edit-mode-pane")
|
||||
@$editModeLinks = $(".js-edit-mode a")
|
||||
@$editModeLinks.click @editModeLinkClickHandler
|
||||
|
||||
editor: ->
|
||||
return @editor
|
||||
editModeLinkClickHandler: (event) =>
|
||||
event.preventDefault()
|
||||
currentLink = $(event.target)
|
||||
paneId = currentLink.attr("href")
|
||||
currentPane = @$editModePanes.filter(paneId)
|
||||
@$editModeLinks.parent().removeClass "active hover"
|
||||
currentLink.parent().addClass "active hover"
|
||||
@$editModePanes.hide()
|
||||
currentPane.fadeIn 200
|
||||
if paneId is "#preview"
|
||||
$.post currentLink.data("preview-url"),
|
||||
content: @editor.getValue()
|
||||
, (response) ->
|
||||
currentPane.empty().append response
|
||||
currentPane.syntaxHighlight()
|
||||
|
||||
else
|
||||
@editor.focus()
|
||||
|
|
|
@ -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
|
37
app/assets/javascripts/breakpoints.coffee
Normal file
37
app/assets/javascripts/breakpoints.coffee
Normal 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()
|
|
@ -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 jquery_nested_form
|
||||
#= 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', ->
|
||||
$(this).replaceWith('<i class="fa fa-refresh fa-spin"></i> Assign in progress..')
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
class CiBuild
|
||||
@interval: null
|
||||
@state: null
|
||||
|
||||
constructor: (build_url, build_status) ->
|
||||
constructor: (build_url, build_status, build_state) ->
|
||||
clearInterval(CiBuild.interval)
|
||||
|
||||
@state = build_state
|
||||
|
||||
@initScrollButtonAffix()
|
||||
|
||||
if build_status == "running" || build_status == "pending"
|
||||
#
|
||||
# Bind autoscroll button to follow build output
|
||||
|
@ -23,19 +28,37 @@ class CiBuild
|
|||
#
|
||||
CiBuild.interval = setInterval =>
|
||||
if window.location.href.split("#").first() is build_url
|
||||
last_state = @state
|
||||
$.ajax
|
||||
url: build_url
|
||||
url: build_url + "/trace.json?state=" + encodeURIComponent(@state)
|
||||
dataType: "json"
|
||||
success: (build) =>
|
||||
if build.status == "running"
|
||||
$('#build-trace code').html build.trace_html
|
||||
$('#build-trace code').append '<i class="fa fa-refresh fa-spin"/>'
|
||||
success: (log) =>
|
||||
return unless last_state is @state
|
||||
|
||||
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"/>'
|
||||
@checkAutoscroll()
|
||||
else if build.status != build_status
|
||||
else if log.status isnt build_status
|
||||
Turbolinks.visit build_url
|
||||
, 4000
|
||||
|
||||
checkAutoscroll: ->
|
||||
$("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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
class @CommitsList
|
||||
@timer = null
|
||||
|
||||
@init: (ref, limit) ->
|
||||
@init: (limit) ->
|
||||
$("body").on "click", ".day-commits-table li.commit", (event) ->
|
||||
if event.target.nodeName != "A"
|
||||
location.href = $(this).attr("url")
|
||||
|
|
67
app/assets/javascripts/compare.js.coffee
Normal file
67
app/assets/javascripts/compare.js.coffee
Normal 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()
|
||||
)
|
|
@ -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"
|
|
@ -14,41 +14,41 @@ class Dispatcher
|
|||
|
||||
path = page.split(':')
|
||||
shortcut_handler = null
|
||||
|
||||
switch page
|
||||
when 'explore:projects:index', 'explore:projects:starred', 'explore:projects:trending'
|
||||
Dashboard.init()
|
||||
when 'projects:issues:index'
|
||||
Issues.init()
|
||||
Issuable.init()
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
when 'projects:issues:show'
|
||||
new Issue()
|
||||
shortcut_handler = new ShortcutsIssuable()
|
||||
new ZenMode()
|
||||
when 'projects:milestones:show'
|
||||
when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
|
||||
new Milestone()
|
||||
when 'dashboard:todos:index'
|
||||
new Todos()
|
||||
when 'projects:milestones:new', 'projects:milestones:edit'
|
||||
new ZenMode()
|
||||
new DropzoneInput($('.milestone-form'))
|
||||
new GLForm($('.milestone-form'))
|
||||
when 'groups:milestones:new'
|
||||
new ZenMode()
|
||||
when 'projects:compare:show'
|
||||
new Diff()
|
||||
when 'projects:issues:new','projects:issues:edit'
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
new DropzoneInput($('.issue-form'))
|
||||
new GLForm($('.issue-form'))
|
||||
new IssuableForm($('.issue-form'))
|
||||
when 'projects:merge_requests:new', 'projects:merge_requests:edit'
|
||||
new Diff()
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
new DropzoneInput($('.merge-request-form'))
|
||||
new GLForm($('.merge-request-form'))
|
||||
new IssuableForm($('.merge-request-form'))
|
||||
when 'projects:tags:new'
|
||||
new ZenMode()
|
||||
new DropzoneInput($('.tag-form'))
|
||||
new GLForm($('.tag-form'))
|
||||
when 'projects:releases:edit'
|
||||
new ZenMode()
|
||||
new DropzoneInput($('.release-form'))
|
||||
new GLForm($('.release-form'))
|
||||
when 'projects:merge_requests:show'
|
||||
new Diff()
|
||||
shortcut_handler = new ShortcutsIssuable(true)
|
||||
|
@ -58,9 +58,7 @@ class Dispatcher
|
|||
new ZenMode()
|
||||
when 'projects:merge_requests:index'
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
MergeRequests.init()
|
||||
when 'dashboard:show', 'root:show'
|
||||
Dashboard.init()
|
||||
Issuable.init()
|
||||
when 'dashboard:activity'
|
||||
new Activities()
|
||||
when 'dashboard:projects:starred'
|
||||
|
@ -76,8 +74,11 @@ class Dispatcher
|
|||
shortcut_handler = new ShortcutsNavigation()
|
||||
when 'projects:show'
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
when 'groups:show'
|
||||
|
||||
new TreeView() if $('#tree-slider').length
|
||||
when 'groups:activity'
|
||||
new Activities()
|
||||
when 'groups:show'
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
when 'groups:group_members:index'
|
||||
new GroupMembers()
|
||||
|
@ -92,7 +93,7 @@ class Dispatcher
|
|||
new TreeView()
|
||||
when 'projects:find_file:show'
|
||||
shortcut_handler = true
|
||||
when 'projects:blob:show'
|
||||
when 'projects:blob:show', 'projects:blame:show'
|
||||
new LineHighlighter()
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
when 'projects:labels:new', 'projects:labels:edit'
|
||||
|
@ -105,9 +106,10 @@ class Dispatcher
|
|||
new ProjectFork()
|
||||
when 'projects:artifacts:browse'
|
||||
new BuildArtifacts()
|
||||
when 'users:show'
|
||||
new User()
|
||||
new Activities()
|
||||
when 'projects:group_links:index'
|
||||
new GroupsSelect()
|
||||
when 'search:show'
|
||||
new Search()
|
||||
|
||||
switch path.first()
|
||||
when 'admin'
|
||||
|
@ -138,7 +140,7 @@ class Dispatcher
|
|||
new Wikis()
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
new ZenMode()
|
||||
new DropzoneInput($('.wiki-form'))
|
||||
new GLForm($('.wiki-form'))
|
||||
when 'snippets'
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
new ZenMode() if path[2] == 'show'
|
||||
|
@ -147,15 +149,11 @@ class Dispatcher
|
|||
when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
|
||||
|
||||
# If we haven't installed a custom shortcut handler, install the default one
|
||||
if not shortcut_handler
|
||||
new Shortcuts()
|
||||
|
||||
initSearch: ->
|
||||
opts = $('.search-autocomplete-opts')
|
||||
path = opts.data('autocomplete-path')
|
||||
project_id = opts.data('autocomplete-project-id')
|
||||
project_ref = opts.data('autocomplete-project-ref')
|
||||
|
||||
new SearchAutocomplete(path, project_id, project_ref)
|
||||
# Only when search form is present
|
||||
new SearchAutocomplete() if $('.search').length
|
||||
|
|
|
@ -15,11 +15,13 @@ class @DropzoneInput
|
|||
project_uploads_path = window.project_uploads_path or null
|
||||
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.on 'paste', (event) =>
|
||||
handlePaste(event)
|
||||
|
||||
$mdArea = $(form_textarea).closest('.md-area')
|
||||
|
||||
$(form).setupMarkdownPreview()
|
||||
|
||||
form_dropzone = $(form).find('.div-dropzone')
|
||||
|
@ -49,17 +51,17 @@ class @DropzoneInput
|
|||
$(".div-dropzone-alert").alert "close"
|
||||
|
||||
dragover: ->
|
||||
form_textarea.addClass "div-dropzone-focus"
|
||||
$mdArea.addClass 'is-dropzone-hover'
|
||||
form.find(".div-dropzone-hover").css "opacity", 0.7
|
||||
return
|
||||
|
||||
dragleave: ->
|
||||
form_textarea.removeClass "div-dropzone-focus"
|
||||
$mdArea.removeClass 'is-dropzone-hover'
|
||||
form.find(".div-dropzone-hover").css "opacity", 0
|
||||
return
|
||||
|
||||
drop: ->
|
||||
form_textarea.removeClass "div-dropzone-focus"
|
||||
$mdArea.removeClass 'is-dropzone-hover'
|
||||
form.find(".div-dropzone-hover").css "opacity", 0
|
||||
form_textarea.focus()
|
||||
return
|
||||
|
|
81
app/assets/javascripts/due_date_select.js.coffee
Normal file
81
app/assets/javascripts/due_date_select.js.coffee
Normal 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()
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
window.GitLab ?= {}
|
||||
GitLab.GfmAutoComplete =
|
||||
dataLoading: false
|
||||
|
||||
dataSource: ''
|
||||
|
||||
# Emoji
|
||||
|
@ -16,18 +18,46 @@ GitLab.GfmAutoComplete =
|
|||
Issues:
|
||||
template: '<li><small>${id}</small> ${title}</li>'
|
||||
|
||||
# Add GFM auto-completion to all input fields, that accept GFM input.
|
||||
setup: ->
|
||||
input = $('.js-gfm-input')
|
||||
# Milestones
|
||||
Milestones:
|
||||
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
|
||||
input.atwho
|
||||
@input.atwho
|
||||
at: ':'
|
||||
displayTpl: @Emoji.template
|
||||
insertTpl: ':${name}:'
|
||||
|
||||
# Team Members
|
||||
input.atwho
|
||||
@input.atwho
|
||||
at: '@'
|
||||
displayTpl: @Members.template
|
||||
insertTpl: '${atwho-at}${username}'
|
||||
|
@ -42,7 +72,7 @@ GitLab.GfmAutoComplete =
|
|||
title: sanitize(title)
|
||||
search: sanitize("#{m.username} #{m.name}")
|
||||
|
||||
input.atwho
|
||||
@input.atwho
|
||||
at: '#'
|
||||
alias: 'issues'
|
||||
searchKey: 'search'
|
||||
|
@ -55,7 +85,20 @@ GitLab.GfmAutoComplete =
|
|||
title: sanitize(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: '!'
|
||||
alias: 'mergerequests'
|
||||
searchKey: 'search'
|
||||
|
@ -68,13 +111,20 @@ GitLab.GfmAutoComplete =
|
|||
title: sanitize(m.title)
|
||||
search: "#{m.iid} #{m.title}"
|
||||
|
||||
if @dataSource
|
||||
$.getJSON(@dataSource).done (data) ->
|
||||
# load members
|
||||
input.atwho 'load', '@', data.members
|
||||
# load issues
|
||||
input.atwho 'load', 'issues', data.issues
|
||||
# load merge requests
|
||||
input.atwho 'load', 'mergerequests', data.mergerequests
|
||||
# load emojis
|
||||
input.atwho 'load', ':', data.emojis
|
||||
destroyAtWho: ->
|
||||
@input.atwho('destroy')
|
||||
|
||||
fetchData: (dataSource) ->
|
||||
$.getJSON(dataSource)
|
||||
|
||||
loadData: (data) ->
|
||||
# load members
|
||||
@input.atwho 'load', '@', data.members
|
||||
# load issues
|
||||
@input.atwho 'load', 'issues', data.issues
|
||||
# load milestones
|
||||
@input.atwho 'load', 'milestones', data.milestones
|
||||
# load merge requests
|
||||
@input.atwho 'load', 'mergerequests', data.mergerequests
|
||||
# load emojis
|
||||
@input.atwho 'load', ':', data.emojis
|
||||
|
|
152
app/assets/javascripts/gl_crop.js.coffee
Normal file
152
app/assets/javascripts/gl_crop.js.coffee
Normal 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))
|
550
app/assets/javascripts/gl_dropdown.js.coffee
Normal file
550
app/assets/javascripts/gl_dropdown.js.coffee
Normal 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)
|
51
app/assets/javascripts/gl_form.js.coffee
Normal file
51
app/assets/javascripts/gl_form.js.coffee
Normal 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'
|
|
@ -4,18 +4,33 @@ class @ImporterStatus
|
|||
this.setAutoUpdate()
|
||||
|
||||
initStatusPage: ->
|
||||
$(".js-add-to-import").click (event) =>
|
||||
new_namespace = null
|
||||
tr = $(event.currentTarget).closest("tr")
|
||||
id = tr.attr("id").replace("repo_", "")
|
||||
if tr.find(".import-target input").length > 0
|
||||
new_namespace = tr.find(".import-target input").prop("value")
|
||||
tr.find(".import-target").empty().append(new_namespace + "/" + tr.find(".import-target").data("project_name"))
|
||||
$.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script'
|
||||
$('.js-add-to-import')
|
||||
.off 'click'
|
||||
.on 'click', (e) =>
|
||||
new_namespace = null
|
||||
$btn = $(e.currentTarget)
|
||||
$tr = $btn.closest('tr')
|
||||
id = $tr.attr('id').replace('repo_', '')
|
||||
if $tr.find('.import-target input').length > 0
|
||||
new_namespace = $tr.find('.import-target input').prop('value')
|
||||
$tr.find('.import-target').empty().append("#{new_namespace} / #{$tr.find('.import-target').data('project_name')}")
|
||||
|
||||
$(".js-import-all").click (event) =>
|
||||
$(".js-add-to-import").each ->
|
||||
$(this).click()
|
||||
$btn
|
||||
.disable()
|
||||
.addClass 'is-loading'
|
||||
|
||||
$.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script'
|
||||
|
||||
$('.js-import-all')
|
||||
.off 'click'
|
||||
.on 'click', (e) ->
|
||||
$btn = $(@)
|
||||
$btn
|
||||
.disable()
|
||||
.addClass 'is-loading'
|
||||
|
||||
$('.js-add-to-import').each ->
|
||||
$(this).trigger('click')
|
||||
|
||||
setAutoUpdate: ->
|
||||
setInterval (=>
|
||||
|
|
84
app/assets/javascripts/issuable.js.coffee
Normal file
84
app/assets/javascripts/issuable.js.coffee
Normal 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
|
|
@ -1,8 +1,7 @@
|
|||
#= require jquery.waitforimages
|
||||
|
||||
class @IssuableContext
|
||||
constructor: ->
|
||||
new UsersSelect()
|
||||
constructor: (currentUser) ->
|
||||
@initParticipants()
|
||||
new UsersSelect(currentUser)
|
||||
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
|
||||
|
||||
$(".issuable-sidebar .inline-update").on "change", "select", ->
|
||||
|
@ -10,10 +9,52 @@ class @IssuableContext
|
|||
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
|
||||
$(this).submit()
|
||||
|
||||
$(document).on "click",".edit-link", (e) ->
|
||||
block = $(@).parents('.block')
|
||||
block.find('.selectbox').show()
|
||||
block.find('.value').hide()
|
||||
block.find('.js-select2').select2("open")
|
||||
$(document)
|
||||
.off 'click', '.issuable-sidebar .dropdown-content a'
|
||||
.on 'click', '.issuable-sidebar .dropdown-content a', (e) ->
|
||||
e.preventDefault()
|
||||
|
||||
$(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()
|
||||
|
||||
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()
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
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) ->
|
||||
GitLab.GfmAutoComplete.setup()
|
||||
new UsersSelect()
|
||||
|
@ -6,14 +9,17 @@ class @IssuableForm
|
|||
|
||||
@titleField = @form.find("input[name*='[title]']")
|
||||
@descriptionField = @form.find("textarea[name*='[description]']")
|
||||
@issueMoveField = @form.find("#move_to_project_id")
|
||||
|
||||
return unless @titleField.length && @descriptionField.length
|
||||
|
||||
@initAutosave()
|
||||
|
||||
@form.on "submit", @resetAutosave
|
||||
@form.on "submit", @handleSubmit
|
||||
@form.on "click", ".btn-cancel", @resetAutosave
|
||||
|
||||
@initWip()
|
||||
|
||||
initAutosave: ->
|
||||
new Autosave @titleField, [
|
||||
document.location.pathname,
|
||||
|
@ -27,6 +33,50 @@ class @IssuableForm
|
|||
"description"
|
||||
]
|
||||
|
||||
handleSubmit: =>
|
||||
if (parseInt(@issueMoveField?.val()) ? 0) > 0
|
||||
return false unless confirm(@issueMoveConfirmMsg)
|
||||
|
||||
@resetAutosave()
|
||||
|
||||
resetAutosave: =>
|
||||
@titleField.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()}"
|
||||
|
|
|
@ -6,24 +6,13 @@ class @Issue
|
|||
constructor: ->
|
||||
# Prevent duplicate event bindings
|
||||
@disableTaskList()
|
||||
@fixAffixScroll()
|
||||
if $('a.btn-close').length
|
||||
@initTaskList()
|
||||
@initIssueBtnEventListeners()
|
||||
|
||||
fixAffixScroll: ->
|
||||
fixAffix = ->
|
||||
$discussion = $('.issuable-discussion')
|
||||
$sidebar = $('.issuable-sidebar')
|
||||
if $sidebar.hasClass('no-affix')
|
||||
$sidebar.removeClass(['affix-top','affix'])
|
||||
discussionHeight = $discussion.height()
|
||||
sidebarHeight = $sidebar.height()
|
||||
if sidebarHeight > discussionHeight
|
||||
$discussion.height(sidebarHeight + 50)
|
||||
$sidebar.addClass('no-affix')
|
||||
$(window).on('resize', fixAffix)
|
||||
fixAffix()
|
||||
@initMergeRequests()
|
||||
@initRelatedBranches()
|
||||
@initCanCreateBranch()
|
||||
|
||||
initTaskList: ->
|
||||
$('.detail-page-description .js-task-list-container').taskList('enable')
|
||||
|
@ -49,7 +38,7 @@ class @Issue
|
|||
issueStatus = if isClose then 'close' else 'open'
|
||||
new Flash(issueFailMessage, 'alert')
|
||||
success: (data, textStatus, jqXHR) ->
|
||||
if data.saved
|
||||
if 'id' of data
|
||||
$(document).trigger('issuable:change');
|
||||
if isClose
|
||||
$('a.btn-close').addClass('hidden')
|
||||
|
@ -84,3 +73,45 @@ class @Issue
|
|||
type: 'PATCH'
|
||||
url: $('form.js-issuable-update').attr('action')
|
||||
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()
|
||||
|
|
11
app/assets/javascripts/issue_status_select.js.coffee
Normal file
11
app/assets/javascripts/issue_status_select.js.coffee
Normal 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")
|
||||
)
|
|
@ -1,7 +1,6 @@
|
|||
@Issues =
|
||||
init: ->
|
||||
Issues.initSearch()
|
||||
Issues.initSelects()
|
||||
Issues.created = true
|
||||
Issues.initChecks()
|
||||
|
||||
$("body").on "ajax:success", ".close_issue, .reopen_issue", ->
|
||||
|
@ -16,19 +15,6 @@
|
|||
else
|
||||
$(this).html totalIssues - 1
|
||||
|
||||
reload: ->
|
||||
Issues.initSelects()
|
||||
Issues.initChecks()
|
||||
$('#filter_issue_search').val($('#issue_search').val())
|
||||
|
||||
initSelects: ->
|
||||
$("select#update_state_event").select2(width: 'resolve', dropdownAutoWidth: true)
|
||||
$("select#update_assignee_id").select2(width: 'resolve', dropdownAutoWidth: true)
|
||||
$("select#update_milestone_id").select2(width: 'resolve', dropdownAutoWidth: true)
|
||||
$("select#label_name").select2(width: 'resolve', dropdownAutoWidth: true)
|
||||
$("#milestone_id, #assignee_id, #label_name").on "change", ->
|
||||
$(this).closest("form").submit()
|
||||
|
||||
initChecks: ->
|
||||
$(".check_all_issues").click ->
|
||||
$(".selected_issue").prop("checked", @checked)
|
||||
|
@ -36,32 +22,6 @@
|
|||
|
||||
$(".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: ->
|
||||
checked_issues = $(".selected_issue:checked")
|
||||
if checked_issues.length > 0
|
||||
|
|
301
app/assets/javascripts/labels_select.js.coffee
Normal file
301
app/assets/javascripts/labels_select.js.coffee
Normal 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()
|
||||
)
|
39
app/assets/javascripts/lib/animate.js.coffee
Normal file
39
app/assets/javascripts/lib/animate.js.coffee
Normal 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
|
17
app/assets/javascripts/lib/datetime_utility.js.coffee
Normal file
17
app/assets/javascripts/lib/datetime_utility.js.coffee
Normal 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
|
35
app/assets/javascripts/lib/notify.js.coffee
Normal file
35
app/assets/javascripts/lib/notify.js.coffee
Normal 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
|
9
app/assets/javascripts/lib/type_utility.js.coffee
Normal file
9
app/assets/javascripts/lib/type_utility.js.coffee
Normal file
|
@ -0,0 +1,9 @@
|
|||
((w) ->
|
||||
|
||||
w.gl ?= {}
|
||||
w.gl.utils ?= {}
|
||||
|
||||
w.gl.utils.isObject = (obj) ->
|
||||
obj? and (obj.constructor is Object)
|
||||
|
||||
) window
|
43
app/assets/javascripts/lib/url_utility.js.coffee
Normal file
43
app/assets/javascripts/lib/url_utility.js.coffee
Normal 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
|
|
@ -1,4 +1,4 @@
|
|||
NProgress.configure(showSpinner: false)
|
||||
Turbolinks.enableProgressBar();
|
||||
|
||||
defaultClass = 'tanuki-shape'
|
||||
pieces = [
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
class @MarkdownPreview
|
||||
# Minimum number of users referenced before triggering a warning
|
||||
referenceThreshold: 10
|
||||
ajaxCache: {}
|
||||
|
||||
showPreview: (form) ->
|
||||
preview = form.find('.js-md-preview')
|
||||
|
@ -24,12 +25,16 @@ class @MarkdownPreview
|
|||
renderMarkdown: (text, success) ->
|
||||
return unless window.markdown_preview_path
|
||||
|
||||
return success(@ajaxCache.response) if text == @ajaxCache.text
|
||||
|
||||
$.ajax
|
||||
type: 'POST'
|
||||
url: window.markdown_preview_path
|
||||
data: { text: text }
|
||||
dataType: 'json'
|
||||
success: success
|
||||
success: (response) =>
|
||||
@ajaxCache = text: text, response: response
|
||||
success(response)
|
||||
|
||||
hideReferencedUsers: (form) ->
|
||||
referencedUsers = form.find('.referenced-users')
|
||||
|
@ -49,6 +54,7 @@ markdownPreview = new MarkdownPreview()
|
|||
|
||||
previewButtonSelector = '.js-md-preview-button'
|
||||
writeButtonSelector = '.js-md-write-button'
|
||||
lastTextareaPreviewed = null
|
||||
|
||||
$.fn.setupMarkdownPreview = ->
|
||||
$form = $(this)
|
||||
|
@ -58,10 +64,10 @@ $.fn.setupMarkdownPreview = ->
|
|||
form_textarea.on 'input', -> markdownPreview.hideReferencedUsers($form)
|
||||
form_textarea.on 'blur', -> markdownPreview.showPreview($form)
|
||||
|
||||
$(document).on 'click', previewButtonSelector, (e) ->
|
||||
e.preventDefault()
|
||||
$(document).on 'markdown-preview:show', (e, $form) ->
|
||||
return unless $form
|
||||
|
||||
$form = $(this).closest('form')
|
||||
lastTextareaPreviewed = $form.find('textarea.markdown-area')
|
||||
|
||||
# toggle tabs
|
||||
$form.find(writeButtonSelector).parent().removeClass('active')
|
||||
|
@ -73,10 +79,10 @@ $(document).on 'click', previewButtonSelector, (e) ->
|
|||
|
||||
markdownPreview.showPreview($form)
|
||||
|
||||
$(document).on 'click', writeButtonSelector, (e) ->
|
||||
e.preventDefault()
|
||||
$(document).on 'markdown-preview:hide', (e, $form) ->
|
||||
return unless $form
|
||||
|
||||
$form = $(this).closest('form')
|
||||
lastTextareaPreviewed = null
|
||||
|
||||
# toggle tabs
|
||||
$form.find(writeButtonSelector).parent().addClass('active')
|
||||
|
@ -84,4 +90,30 @@ $(document).on 'click', writeButtonSelector, (e) ->
|
|||
|
||||
# toggle content
|
||||
$form.find('.md-write-holder').show()
|
||||
$form.find('textarea.markdown-area').focus()
|
||||
$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])
|
||||
|
|
|
@ -15,8 +15,6 @@ class @MergeRequest
|
|||
this.$('.show-all-commits').on 'click', =>
|
||||
this.showAllCommits()
|
||||
|
||||
@fixAffixScroll();
|
||||
|
||||
@initTabs()
|
||||
|
||||
# Prevent duplicate event bindings
|
||||
|
@ -30,20 +28,6 @@ class @MergeRequest
|
|||
$: (selector) ->
|
||||
this.$el.find(selector)
|
||||
|
||||
fixAffixScroll: ->
|
||||
fixAffix = ->
|
||||
$discussion = $('.issuable-discussion')
|
||||
$sidebar = $('.issuable-sidebar')
|
||||
if $sidebar.hasClass('no-affix')
|
||||
$sidebar.removeClass(['affix-top','affix'])
|
||||
discussionHeight = $discussion.height()
|
||||
sidebarHeight = $sidebar.height()
|
||||
if sidebarHeight > discussionHeight
|
||||
$discussion.height(sidebarHeight + 50)
|
||||
$sidebar.addClass('no-affix')
|
||||
$(window).on('resize', fixAffix)
|
||||
fixAffix()
|
||||
|
||||
initTabs: ->
|
||||
if @opts.action != 'new'
|
||||
# `MergeRequests#new` has no tab-persisting or lazy-loading behavior
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
# Handles persisting and restoring the current tab selection and lazily-loading
|
||||
# content on the MergeRequests#show page.
|
||||
#
|
||||
#= require jquery.cookie
|
||||
#
|
||||
# ### Example Markup
|
||||
#
|
||||
# <ul class="nav-links merge-request-tabs">
|
||||
|
@ -68,17 +70,25 @@ class @MergeRequestTabs
|
|||
|
||||
if action == 'commits'
|
||||
@loadCommits($target.attr('href'))
|
||||
@expandView()
|
||||
else if action == 'diffs'
|
||||
@loadDiff($target.attr('href'))
|
||||
if bp? and bp.getBreakpointSize() isnt 'lg'
|
||||
@shrinkView()
|
||||
else if action == 'builds'
|
||||
@loadBuilds($target.attr('href'))
|
||||
@expandView()
|
||||
else
|
||||
@expandView()
|
||||
|
||||
@setCurrentAction(action)
|
||||
|
||||
scrollToElement: (container) ->
|
||||
if window.location.hash
|
||||
$el = $("div#{container} #{window.location.hash}")
|
||||
$('body').scrollTo($el.offset().top) if $el.length
|
||||
navBarHeight = $('.navbar-gitlab').outerHeight()
|
||||
|
||||
$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
|
||||
activateTab: (action) ->
|
||||
|
@ -134,7 +144,7 @@ class @MergeRequestTabs
|
|||
url: "#{source}.json"
|
||||
success: (data) =>
|
||||
document.querySelector("div#commits").innerHTML = data.html
|
||||
$('.js-timeago').timeago()
|
||||
gl.utils.localTimeAgo($('.js-timeago', 'div#commits'))
|
||||
@commitsLoaded = true
|
||||
@scrollToElement("#commits")
|
||||
|
||||
|
@ -144,11 +154,39 @@ class @MergeRequestTabs
|
|||
@_get
|
||||
url: "#{source}.json" + @_location.search
|
||||
success: (data) =>
|
||||
document.querySelector("div#diffs").innerHTML = data.html
|
||||
$('div#diffs .js-syntax-highlight').syntaxHighlight()
|
||||
$('#diffs').html data.html
|
||||
gl.utils.localTimeAgo($('.js-timeago', 'div#diffs'))
|
||||
$('#diffs .js-syntax-highlight').syntaxHighlight()
|
||||
@expandViewContainer() if @diffViewType() is 'parallel'
|
||||
@diffsLoaded = true
|
||||
@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) ->
|
||||
return if @buildsLoaded
|
||||
|
@ -157,7 +195,7 @@ class @MergeRequestTabs
|
|||
url: "#{source}.json"
|
||||
success: (data) =>
|
||||
document.querySelector("div#builds").innerHTML = data.html
|
||||
$('.js-timeago').timeago()
|
||||
gl.utils.localTimeAgo($('.js-timeago', 'div#builds'))
|
||||
@buildsLoaded = true
|
||||
@scrollToElement("#builds")
|
||||
|
||||
|
@ -185,3 +223,26 @@ class @MergeRequestTabs
|
|||
|
||||
expandViewContainer: ->
|
||||
$('.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)
|
||||
|
|
|
@ -2,13 +2,30 @@ class @MergeRequestWidget
|
|||
# Initialize MergeRequestWidget behavior
|
||||
#
|
||||
# check_enable - Boolean, whether to check automerge status
|
||||
# url_to_automerge_check - String, URL to use to check automerge status
|
||||
# current_status - String, current automerge status
|
||||
# ci_enable - Boolean, whether a CI service is enabled
|
||||
# url_to_ci_check - String, URL to use to check CI status
|
||||
# merge_check_url - String, URL to use to check automerge status
|
||||
# ci_status_url - String, URL to use to check CI status
|
||||
#
|
||||
|
||||
constructor: (@opts) ->
|
||||
modal = $('#modal_merge_info').modal(show: false)
|
||||
$('#modal_merge_info').modal(show: false)
|
||||
@firstCICheck = true
|
||||
@readyForCICheck = 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)->
|
||||
$.ajax
|
||||
|
@ -27,18 +44,68 @@ class @MergeRequestWidget
|
|||
dataType: 'json'
|
||||
|
||||
getMergeStatus: ->
|
||||
$.get @opts.url_to_automerge_check, (data) ->
|
||||
$.get @opts.merge_check_url, (data) ->
|
||||
$('.mr-state-widget').replaceWith(data)
|
||||
|
||||
getCiStatus: ->
|
||||
if @opts.ci_enable
|
||||
$.get @opts.url_to_ci_check, (data) =>
|
||||
this.showCiState data.status
|
||||
if data.coverage
|
||||
this.showCiCoverage data.coverage
|
||||
, 'json'
|
||||
ciLabelForStatus: (status) ->
|
||||
if status is 'success'
|
||||
'passed'
|
||||
else
|
||||
status
|
||||
|
||||
showCiState: (state) ->
|
||||
pollCIStatus: ->
|
||||
@fetchBuildStatusInterval = setInterval ( =>
|
||||
return if not @readyForCICheck
|
||||
|
||||
@getCIStatus(true)
|
||||
|
||||
@readyForCICheck = false
|
||||
), 10000
|
||||
|
||||
getCIStatus: (showNotification) ->
|
||||
_this = @
|
||||
$('.ci-widget-fetching').show()
|
||||
|
||||
$.getJSON @opts.ci_status_url, (data) =>
|
||||
@readyForCICheck = true
|
||||
|
||||
if 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()
|
||||
allowed_states = ["failed", "canceled", "running", "pending", "success", "skipped", "not_found"]
|
||||
if state in allowed_states
|
||||
|
@ -46,15 +113,19 @@ class @MergeRequestWidget
|
|||
switch state
|
||||
when "failed", "canceled", "not_found"
|
||||
@setMergeButtonClass('btn-danger')
|
||||
when "running", "pending"
|
||||
when "running"
|
||||
@setMergeButtonClass('btn-warning')
|
||||
when "success"
|
||||
@setMergeButtonClass('btn-create')
|
||||
else
|
||||
$('.ci_widget.ci-error').show()
|
||||
@setMergeButtonClass('btn-danger')
|
||||
|
||||
showCiCoverage: (coverage) ->
|
||||
showCICoverage: (coverage) ->
|
||||
text = 'Coverage ' + coverage + '%'
|
||||
$('.ci_widget:visible .ci-coverage').text(text)
|
||||
|
||||
setMergeButtonClass: (css_class) ->
|
||||
$('.accept_merge_request').removeClass("btn-create").addClass(css_class)
|
||||
$('.js-merge-button')
|
||||
.removeClass('btn-danger btn-warning btn-create')
|
||||
.addClass(css_class)
|
||||
|
|
|
@ -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())
|
|
@ -69,7 +69,7 @@ class @Milestone
|
|||
|
||||
@bindIssuesSorting()
|
||||
@bindMergeRequestSorting()
|
||||
@bindTabsSwitching
|
||||
@bindTabsSwitching()
|
||||
|
||||
bindIssuesSorting: ->
|
||||
$("#issues-list-unassigned, #issues-list-ongoing, #issues-list-closed").sortable(
|
||||
|
@ -104,7 +104,7 @@ class @Milestone
|
|||
|
||||
).disableSelection()
|
||||
|
||||
bindMergeRequestSorting: ->
|
||||
bindTabsSwitching: ->
|
||||
$('a[data-toggle="tab"]').on 'show.bs.tab', (e) ->
|
||||
currentTabClass = $(e.target).data('show')
|
||||
previousTabClass = $(e.relatedTarget).data('show')
|
||||
|
@ -112,7 +112,8 @@ class @Milestone
|
|||
$(previousTabClass).hide()
|
||||
$(currentTabClass).removeClass('hidden')
|
||||
$(currentTabClass).show()
|
||||
|
||||
|
||||
bindMergeRequestSorting: ->
|
||||
$("#merge_requests-list-unassigned, #merge_requests-list-ongoing, #merge_requests-list-closed").sortable(
|
||||
connectWith: ".merge_requests-sortable-list",
|
||||
dropOnEmpty: true,
|
||||
|
|
130
app/assets/javascripts/milestone_select.js.coffee
Normal file
130
app/assets/javascripts/milestone_select.js.coffee
Normal 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')
|
||||
)
|
|
@ -16,11 +16,13 @@ class @Notes
|
|||
@view = view
|
||||
@noteable_url = document.URL
|
||||
@notesCountBadge ||= $(".issuable-details").find(".notes-tab .badge")
|
||||
@basePollingInterval = 15000
|
||||
@maxPollingSteps = 4
|
||||
|
||||
@initRefresh()
|
||||
@setupMainTargetNoteForm()
|
||||
@cleanBinding()
|
||||
@addBinding()
|
||||
@setPollingInterval()
|
||||
@setupMainTargetNoteForm()
|
||||
@initTaskList()
|
||||
|
||||
addBinding: ->
|
||||
|
@ -28,8 +30,11 @@ class @Notes
|
|||
$(document).on "ajax:success", ".js-main-target-form", @addNote
|
||||
$(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
|
||||
$(document).on "ajax:success", "form.edit_note", @updateNote
|
||||
$(document).on "ajax:success", "form.edit-note", @updateNote
|
||||
|
||||
# Edit note link
|
||||
$(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
|
||||
$(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)
|
||||
$(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: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
|
||||
$(document).on "change", ".js-note-attachment-input", @updateFormAttachment
|
||||
|
||||
|
@ -67,10 +75,13 @@ class @Notes
|
|||
# when issue status changes, we need to refresh data
|
||||
$(document).on "issuable:change", @refresh
|
||||
|
||||
# when a key is clicked on the notes
|
||||
$(document).on "keydown", ".js-note-text", @keydownNoteText
|
||||
|
||||
cleanBinding: ->
|
||||
$(document).off "ajax:success", ".js-main-target-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", ".note-edit-cancel"
|
||||
$(document).off "click", ".js-note-delete"
|
||||
|
@ -83,17 +94,29 @@ class @Notes
|
|||
$(document).off "keyup", ".js-note-text"
|
||||
$(document).off "click", ".js-note-target-reopen"
|
||||
$(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')
|
||||
$(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: ->
|
||||
clearInterval(Notes.interval)
|
||||
Notes.interval = setInterval =>
|
||||
@refresh()
|
||||
, 15000
|
||||
, @pollingInterval
|
||||
|
||||
refresh: ->
|
||||
return if @refreshing is true
|
||||
refreshing = true
|
||||
if not document.hidden and document.URL.indexOf(@noteable_url) is 0
|
||||
@getContent()
|
||||
|
||||
|
@ -105,12 +128,31 @@ class @Notes
|
|||
success: (data) =>
|
||||
notes = data.notes
|
||||
@last_fetched_at = data.last_fetched_at
|
||||
@setPollingInterval(data.notes.length)
|
||||
$.each notes, (i, note) =>
|
||||
if note.discussion_with_diff_html?
|
||||
@renderDiscussionNote(note)
|
||||
else
|
||||
@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.
|
||||
|
@ -125,17 +167,23 @@ class @Notes
|
|||
return
|
||||
|
||||
if note.award
|
||||
awards_handler.addAwardToEmojiBar(note.note)
|
||||
awards_handler.scrollToAwards()
|
||||
awardsHandler.addAwardToEmojiBar(note.note)
|
||||
awardsHandler.scrollToAwards()
|
||||
|
||||
# render note if it not present in loaded list
|
||||
# or skip if rendered
|
||||
else if @isNewNote(note)
|
||||
@note_ids.push(note.id)
|
||||
|
||||
$('ul.main-notes-list')
|
||||
$notesList = $('ul.main-notes-list')
|
||||
|
||||
$notesList
|
||||
.append(note.html)
|
||||
.syntaxHighlight()
|
||||
|
||||
# Update datetime format on the recent note
|
||||
gl.utils.localTimeAgo($notesList.find("#note_#{note.id} .js-timeago"), false)
|
||||
|
||||
@initTaskList()
|
||||
@updateNotesCount(1)
|
||||
|
||||
|
@ -187,6 +235,8 @@ class @Notes
|
|||
# append new note to all matching discussions
|
||||
discussionContainer.append note_html
|
||||
|
||||
gl.utils.localTimeAgo($('.js-timeago', note_html), false)
|
||||
|
||||
@updateNotesCount(1)
|
||||
|
||||
###
|
||||
|
@ -196,7 +246,7 @@ class @Notes
|
|||
Resets text and preview.
|
||||
Resets buttons.
|
||||
###
|
||||
resetMainTargetForm: ->
|
||||
resetMainTargetForm: (e) =>
|
||||
form = $(".js-main-target-form")
|
||||
|
||||
# remove validation errors
|
||||
|
@ -208,6 +258,8 @@ class @Notes
|
|||
|
||||
form.find(".js-note-text").data("autosave").reset()
|
||||
|
||||
@updateTargetButtons(e)
|
||||
|
||||
reenableTargetFormSubmitButton: ->
|
||||
form = $(".js-main-target-form")
|
||||
|
||||
|
@ -219,13 +271,11 @@ class @Notes
|
|||
Sets some hidden fields in the form.
|
||||
###
|
||||
setupMainTargetNoteForm: ->
|
||||
|
||||
# find the form
|
||||
form = $(".js-new-note-form")
|
||||
|
||||
# insert the form after the button
|
||||
form.clone().replaceAll $(".js-main-target-form")
|
||||
form = form.prev("form")
|
||||
# Set a global clone of the form for later cloning
|
||||
@formClone = form.clone()
|
||||
|
||||
# show the form
|
||||
@setupNoteForm(form)
|
||||
|
@ -234,9 +284,8 @@ class @Notes
|
|||
form.removeClass "js-new-note-form"
|
||||
form.addClass "js-main-target-form"
|
||||
|
||||
# remove unnecessary fields and buttons
|
||||
form.find("#note_line_code").remove()
|
||||
form.find(".js-close-discussion-note-form").remove()
|
||||
form.find("#note_type").remove()
|
||||
|
||||
###
|
||||
General note form setup.
|
||||
|
@ -247,23 +296,10 @@ class @Notes
|
|||
show the form
|
||||
###
|
||||
setupNoteForm: (form) ->
|
||||
disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button")
|
||||
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")
|
||||
new GLForm form
|
||||
|
||||
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, [
|
||||
"Note"
|
||||
form.find("#note_commit_id").val()
|
||||
|
@ -272,12 +308,6 @@ class @Notes
|
|||
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
|
||||
|
||||
|
@ -286,6 +316,10 @@ class @Notes
|
|||
addNote: (xhr, note, status) =>
|
||||
@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
|
||||
|
||||
|
@ -305,6 +339,9 @@ class @Notes
|
|||
updateNote: (_xhr, note, _status) =>
|
||||
# Convert returned HTML to a jQuery object so we can modify it further
|
||||
$html = $(note.html)
|
||||
|
||||
gl.utils.localTimeAgo($('.js-timeago', $html))
|
||||
|
||||
$html.syntaxHighlight()
|
||||
$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
|
||||
if the user cancels
|
||||
###
|
||||
showEditForm: (e) ->
|
||||
showEditForm: (e, scrollTo, myLastNote) ->
|
||||
e.preventDefault()
|
||||
note = $(this).closest(".note")
|
||||
note.find(".note-body > .note-text").hide()
|
||||
note.find(".note-header").hide()
|
||||
base_form = note.find(".note-edit-form")
|
||||
form = base_form.clone().insertAfter(base_form)
|
||||
form.addClass('current-note-edit-form gfm-form')
|
||||
form.find('.div-dropzone').remove()
|
||||
note.addClass "is-editting"
|
||||
form = note.find(".note-edit-form")
|
||||
|
||||
form.addClass('current-note-edit-form')
|
||||
|
||||
# Show the attachment delete link
|
||||
note.find(".js-note-attachment-delete").show()
|
||||
|
||||
# Setup markdown form
|
||||
GitLab.GfmAutoComplete.setup()
|
||||
new DropzoneInput(form)
|
||||
done = ($noteText) ->
|
||||
# Neat little trick to put the cursor at the end
|
||||
noteTextVal = $noteText.val()
|
||||
$noteText.val('').val(noteTextVal);
|
||||
|
||||
form.show()
|
||||
textarea = form.find("textarea")
|
||||
textarea.focus()
|
||||
autosize(textarea)
|
||||
|
||||
# HACK (rspeicher/DouweM): Work around a Chrome 43 bug(?).
|
||||
# The textarea has the correct value, Chrome just won't show it unless we
|
||||
# modify it, so let's clear it and re-set it!
|
||||
value = textarea.val()
|
||||
textarea.val ""
|
||||
textarea.val value
|
||||
|
||||
disableButtonIfEmptyField textarea, form.find(".js-comment-button")
|
||||
new GLForm form
|
||||
if scrollTo? and myLastNote?
|
||||
# scroll to the bottom
|
||||
# so the open of the last element doesn't make a jump
|
||||
$('html, body').scrollTop($(document).height());
|
||||
$('html, body').animate({
|
||||
scrollTop: myLastNote.offset().top - 150
|
||||
}, 500, ->
|
||||
$noteText = form.find(".js-note-text")
|
||||
$noteText.focus()
|
||||
done($noteText)
|
||||
);
|
||||
else
|
||||
$noteText = form.find('.js-note-text')
|
||||
$noteText.focus()
|
||||
done($noteText)
|
||||
|
||||
###
|
||||
Called in response to clicking the edit note link
|
||||
|
@ -358,9 +397,9 @@ class @Notes
|
|||
cancelEdit: (e) ->
|
||||
e.preventDefault()
|
||||
note = $(this).closest(".note")
|
||||
note.find(".note-body > .note-text").show()
|
||||
note.find(".note-header").show()
|
||||
note.find(".current-note-edit-form").remove()
|
||||
note.removeClass "is-editting"
|
||||
note.find(".current-note-edit-form")
|
||||
.removeClass("current-note-edit-form")
|
||||
|
||||
###
|
||||
Called in response to deleting a note of any kind.
|
||||
|
@ -413,15 +452,15 @@ class @Notes
|
|||
Shows the note form below the notes.
|
||||
###
|
||||
replyToDiscussionNote: (e) =>
|
||||
form = $(".js-new-note-form")
|
||||
form = @formClone.clone()
|
||||
replyLink = $(e.target).closest(".js-discussion-reply-button")
|
||||
replyLink.hide()
|
||||
|
||||
# insert the form after the button
|
||||
form.clone().insertAfter replyLink
|
||||
replyLink.after form
|
||||
|
||||
# show the form
|
||||
@setupDiscussionNoteForm(replyLink, replyLink.next("form"))
|
||||
@setupDiscussionNoteForm(replyLink, form)
|
||||
|
||||
###
|
||||
Shows the diff or discussion form and does some setup on it.
|
||||
|
@ -434,14 +473,22 @@ class @Notes
|
|||
setupDiscussionNoteForm: (dataHolder, form) =>
|
||||
# setup note target
|
||||
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("#note_commit_id").val dataHolder.data("commitId")
|
||||
form.find("#note_line_code").val dataHolder.data("lineCode")
|
||||
form.find("#note_noteable_type").val dataHolder.data("noteableType")
|
||||
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
|
||||
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.
|
||||
|
@ -451,9 +498,8 @@ class @Notes
|
|||
###
|
||||
addDiffNote: (e) =>
|
||||
e.preventDefault()
|
||||
link = e.currentTarget
|
||||
form = $(".js-new-note-form")
|
||||
row = $(link).closest("tr")
|
||||
$link = $(e.currentTarget)
|
||||
row = $link.closest("tr")
|
||||
nextRow = row.next()
|
||||
hasNotes = nextRow.is(".notes_holder")
|
||||
addForm = false
|
||||
|
@ -462,7 +508,7 @@ class @Notes
|
|||
|
||||
# In parallel view, look inside the correct left/right pane
|
||||
if @isParallelView()
|
||||
lineType = $(link).data("lineType")
|
||||
lineType = $link.data("lineType")
|
||||
targetContent += "." + lineType
|
||||
rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\"></td><td class=\"notes_content parallel old\"></td><td class=\"notes_line\"></td><td class=\"notes_content parallel new\"></td></tr>"
|
||||
|
||||
|
@ -484,11 +530,11 @@ class @Notes
|
|||
addForm = true
|
||||
|
||||
if addForm
|
||||
newForm = form.clone()
|
||||
newForm = @formClone.clone()
|
||||
newForm.appendTo row.next().find(targetContent)
|
||||
|
||||
# show the form
|
||||
@setupDiscussionNoteForm $(link), newForm
|
||||
@setupDiscussionNoteForm $link, newForm
|
||||
|
||||
###
|
||||
Called in response to "cancel" on a diff note form.
|
||||
|
@ -499,6 +545,9 @@ class @Notes
|
|||
removeDiscussionNoteForm: (form)->
|
||||
row = form.closest("tr")
|
||||
|
||||
glForm = form.data 'gl-form'
|
||||
glForm.destroy()
|
||||
|
||||
form.find(".js-note-text").data("autosave").reset()
|
||||
|
||||
# show the reply button (will only work for replies)
|
||||
|
@ -510,10 +559,8 @@ class @Notes
|
|||
# only remove the form
|
||||
form.remove()
|
||||
|
||||
|
||||
cancelDiscussionForm: (e) =>
|
||||
e.preventDefault()
|
||||
form = $(".js-new-note-form")
|
||||
form = $(e.target).closest(".js-discussion-note-form")
|
||||
@removeDiscussionNoteForm(form)
|
||||
|
||||
|
@ -538,21 +585,52 @@ class @Notes
|
|||
updateCloseButton: (e) =>
|
||||
textarea = $(e.target)
|
||||
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) =>
|
||||
textarea = $(e.target)
|
||||
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
|
||||
form.find('.js-note-target-reopen').text('Comment & reopen')
|
||||
form.find('.js-note-target-close').text('Comment & close')
|
||||
form.find('.js-note-target-reopen').addClass('btn-comment-and-reopen')
|
||||
form.find('.js-note-target-close').addClass('btn-comment-and-close')
|
||||
reopentext = reopenbtn.data('alternative-text')
|
||||
closetext = closebtn.data('alternative-text')
|
||||
|
||||
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
|
||||
form.find('.js-note-target-reopen').text('Reopen')
|
||||
form.find('.js-note-target-close').text('Close')
|
||||
form.find('.js-note-target-reopen').removeClass('btn-comment-and-reopen')
|
||||
form.find('.js-note-target-close').removeClass('btn-comment-and-close')
|
||||
reopentext = reopenbtn.data('original-text')
|
||||
closetext = closebtn.data('original-text')
|
||||
|
||||
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: ->
|
||||
@enableTaskList()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
@Pager =
|
||||
init: (@limit = 0, preload, @disable = false) ->
|
||||
@loading = $(".loading")
|
||||
@loading = $('.loading').first()
|
||||
|
||||
if preload
|
||||
@offset = 0
|
||||
@getOld()
|
||||
|
|
|
@ -1,26 +1,76 @@
|
|||
class @Profile
|
||||
constructor: ->
|
||||
constructor: (opts = {}) ->
|
||||
{
|
||||
@form = $('.edit-user')
|
||||
} = opts
|
||||
|
||||
# Automatically submit the Preferences form when any of its radio buttons change
|
||||
$('.js-preferences-form').on 'change.preference', 'input[type=radio]', ->
|
||||
$(this).parents('form').submit()
|
||||
|
||||
$('.update-username form').on 'ajax:before', ->
|
||||
$('.loading-gif').show()
|
||||
$('.update-username').on 'ajax:before', ->
|
||||
$('.loading-username').show()
|
||||
$(this).find('.update-success').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('.loading-gif').hide()
|
||||
|
||||
$('.update-notifications').on 'ajax:complete', ->
|
||||
$(this).find('.btn-save').enable()
|
||||
$('.update-notifications').on 'ajax:success', (e, data) ->
|
||||
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", ->
|
||||
form = $(this).closest("form")
|
||||
form.find(".js-user-avatar-input").click()
|
||||
@bindEvents()
|
||||
|
||||
$('.js-user-avatar-input').bind "change", ->
|
||||
form = $(this).closest("form")
|
||||
filename = $(this).val().replace(/^.*[\\\/]/, '')
|
||||
form.find(".js-avatar-filename").text(filename)
|
||||
cropOpts =
|
||||
filename: '.js-avatar-filename'
|
||||
previewImage: '.avatar-image .avatar'
|
||||
modalCrop: '.modal-profile-crop'
|
||||
pickImageEl: '.js-choose-user-avatar-button'
|
||||
uploadImageBtn: '.js-upload-user-avatar'
|
||||
modalCropImg: '.modal-profile-crop-image'
|
||||
|
||||
@avatarGlCrop = $('.js-user-avatar-input').glCrop(cropOpts).data 'glcrop'
|
||||
|
||||
bindEvents: ->
|
||||
@form.on 'submit', @onSubmitForm
|
||||
|
||||
onSubmitForm: (e) =>
|
||||
e.preventDefault()
|
||||
@saveForm()
|
||||
|
||||
saveForm: ->
|
||||
self = @
|
||||
formData = new FormData(@form[0])
|
||||
|
||||
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()
|
||||
|
|
|
@ -11,7 +11,6 @@ class @Project
|
|||
$(@).toggleClass('active')
|
||||
|
||||
url = $("#project_clone").val()
|
||||
console.log("url",url)
|
||||
|
||||
# Update the input field
|
||||
$('#project_clone').val(url)
|
||||
|
@ -38,19 +37,20 @@ class @Project
|
|||
$('.update-notification').on 'click', (e) ->
|
||||
e.preventDefault()
|
||||
notification_level = $(@).data 'notification-level'
|
||||
$('#notification_level').val(notification_level)
|
||||
label = $(@).data 'notification-title'
|
||||
$('#notification_setting_level').val(notification_level)
|
||||
$('#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>")
|
||||
$(@).parents('ul').find('li.active').removeClass 'active'
|
||||
$(@).parent().addClass 'active'
|
||||
|
||||
$('#notification-form').on 'ajax:success', (e, data) ->
|
||||
if data.saved
|
||||
new Flash("Notification settings saved", "notice")
|
||||
else
|
||||
new Flash("Failed to save new settings", "alert")
|
||||
|
||||
|
||||
@projectSelectDropdown()
|
||||
|
||||
projectSelectDropdown: ->
|
||||
|
|
|
@ -3,3 +3,16 @@ class @ProjectNew
|
|||
$('.project-edit-container').on 'ajax:before', =>
|
||||
$('.project-edit-container').hide()
|
||||
$('.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
|
||||
|
|
|
@ -1,5 +1,37 @@
|
|||
class @ProjectSelect
|
||||
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) ->
|
||||
@groupId = $(select).data('group-id')
|
||||
@includeGroups = $(select).data('include-groups')
|
||||
|
|
|
@ -1,32 +1,37 @@
|
|||
class @ProjectsList
|
||||
constructor: ->
|
||||
$(".projects-list .js-expand").on 'click', (e) ->
|
||||
e.preventDefault()
|
||||
$projectsList = $(this).closest('.projects-list')
|
||||
ProjectsList.showPagination($projectsList)
|
||||
$projectsList.find('li.bottom').hide()
|
||||
@ProjectsList =
|
||||
init: ->
|
||||
$(".projects-list-filter").off('keyup')
|
||||
this.initSearch()
|
||||
this.initPagination()
|
||||
|
||||
$("#filter_projects").on 'keyup', ->
|
||||
ProjectsList.filter_results($("#filter_projects"))
|
||||
initSearch: ->
|
||||
@timer = null
|
||||
$(".projects-list-filter").on('keyup', ->
|
||||
clearTimeout(@timer)
|
||||
@timer = setTimeout(ProjectsList.filterResults, 500)
|
||||
)
|
||||
|
||||
@showPagination: ($projectsList) ->
|
||||
$projectsList.find('li').show()
|
||||
$('.gl-pagination').show()
|
||||
filterResults: =>
|
||||
$('.projects-list-holder').fadeTo(250, 0.5)
|
||||
|
||||
@filter_results: ($element) ->
|
||||
terms = $element.val()
|
||||
filterSelector = $element.data('filter-selector') || 'span.filter-title'
|
||||
$projectsList = $('.projects-list')
|
||||
form = null
|
||||
form = $("form#project-filter-form")
|
||||
search = $(".projects-list-filter").val()
|
||||
project_filter_url = form.attr('action') + '?' + form.serialize()
|
||||
|
||||
if not terms
|
||||
ProjectsList.showPagination($projectsList)
|
||||
else
|
||||
$projectsList.find('li').each (index) ->
|
||||
$this = $(this)
|
||||
name = $this.find(filterSelector).text()
|
||||
$.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"
|
||||
|
||||
if name.toLowerCase().indexOf(terms.toLowerCase()) == -1
|
||||
$this.hide()
|
||||
else
|
||||
$this.show()
|
||||
$('.gl-pagination').hide()
|
||||
initPagination: ->
|
||||
$('.projects-list-holder .pagination').on('ajax:success', (e, data) ->
|
||||
$('.projects-list-holder').replaceWith(data.html)
|
||||
)
|
||||
|
|
87
app/assets/javascripts/right_sidebar.js.coffee
Normal file
87
app/assets/javascripts/right_sidebar.js.coffee
Normal 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}")
|
||||
|
||||
|
75
app/assets/javascripts/search.js.coffee
Normal file
75
app/assets/javascripts/search.js.coffee
Normal 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()
|
|
@ -1,11 +1,296 @@
|
|||
class @SearchAutocomplete
|
||||
constructor: (search_autocomplete_path, project_id, project_ref) ->
|
||||
project_id = '' unless project_id
|
||||
project_ref = '' unless project_ref
|
||||
query = "?project_id=" + project_id + "&project_ref=" + project_ref
|
||||
|
||||
$("#search").autocomplete
|
||||
source: search_autocomplete_path + query
|
||||
minLength: 1
|
||||
select: (event, ui) ->
|
||||
location.href = ui.item.url
|
||||
KEYCODE =
|
||||
ESCAPE: 27
|
||||
BACKSPACE: 8
|
||||
ENTER: 13
|
||||
|
||||
constructor: (opts = {}) ->
|
||||
{
|
||||
@wrap = $('.search')
|
||||
|
||||
@optsEl = @wrap.find('.search-autocomplete-opts')
|
||||
@autocompletePath = @optsEl.data('autocomplete-path')
|
||||
@projectId = @optsEl.data('autocomplete-project-id') || ''
|
||||
@projectRef = @optsEl.data('autocomplete-project-ref') || ''
|
||||
|
||||
} = opts
|
||||
|
||||
# Dropdown Element
|
||||
@dropdown = @wrap.find('.dropdown')
|
||||
@dropdownContent = @dropdown.find('.dropdown-content')
|
||||
|
||||
@locationBadgeEl = @getElement('.search-location-badge')
|
||||
@locationText = @getElement('.location-text')
|
||||
@scopeInputEl = @getElement('#scope')
|
||||
@searchInput = @getElement('.search-input')
|
||||
@projectInputEl = @getElement('#search_project_id')
|
||||
@groupInputEl = @getElement('#group_id')
|
||||
@searchCodeInputEl = @getElement('#search_code')
|
||||
@repositoryInputEl = @getElement('#repository_ref')
|
||||
@clearInput = @getElement('.js-clear-input')
|
||||
|
||||
@saveOriginalState()
|
||||
|
||||
# Only when user is logged in
|
||||
@createAutocomplete() if gon.current_user_id
|
||||
|
||||
@searchInput.addClass('disabled')
|
||||
|
||||
@saveTextLength()
|
||||
|
||||
@bindEvents()
|
||||
|
||||
# Finds an element inside wrapper element
|
||||
getElement: (selector) ->
|
||||
@wrap.find(selector)
|
||||
|
||||
saveOriginalState: ->
|
||||
@originalState = @serializeState()
|
||||
|
||||
saveTextLength: ->
|
||||
@lastTextLength = @searchInput.val().length
|
||||
|
||||
createAutocomplete: ->
|
||||
@searchInput.glDropdown
|
||||
filterInputBlur: false
|
||||
filterable: true
|
||||
filterRemote: true
|
||||
highlight: true
|
||||
enterCallback: false
|
||||
filterInput: 'input#search'
|
||||
search:
|
||||
fields: ['text']
|
||||
data: @getData.bind(@)
|
||||
selectable: true
|
||||
clicked: @onClick.bind(@)
|
||||
|
||||
getData: (term, callback) ->
|
||||
_this = @
|
||||
|
||||
# Do not trigger request if input is empty
|
||||
return if @searchInput.val() is ''
|
||||
|
||||
# Prevent multiple ajax calls
|
||||
return if @loadingSuggestions
|
||||
|
||||
@loadingSuggestions = true
|
||||
|
||||
jqXHR = $.get(@autocompletePath, {
|
||||
project_id: @projectId
|
||||
project_ref: @projectRef
|
||||
term: term
|
||||
}, (response) ->
|
||||
# Hide dropdown menu if no suggestions returns
|
||||
if !response.length
|
||||
_this.disableAutocomplete()
|
||||
return
|
||||
|
||||
data = []
|
||||
|
||||
# List results
|
||||
firstCategory = true
|
||||
for suggestion in response
|
||||
|
||||
# Add group header before list each group
|
||||
if lastCategory isnt suggestion.category
|
||||
data.push 'separator' if !firstCategory
|
||||
|
||||
firstCategory = false if firstCategory
|
||||
|
||||
data.push
|
||||
header: suggestion.category
|
||||
|
||||
lastCategory = suggestion.category
|
||||
|
||||
data.push
|
||||
id: "#{suggestion.category.toLowerCase()}-#{suggestion.id}"
|
||||
category: suggestion.category
|
||||
text: suggestion.label
|
||||
url: suggestion.url
|
||||
|
||||
# Add option to proceed with the search
|
||||
if data.length
|
||||
data.push('separator')
|
||||
data.push
|
||||
text: "Result name contains \"#{term}\""
|
||||
url: "/search?\
|
||||
search=#{term}\
|
||||
&project_id=#{_this.projectInputEl.val()}\
|
||||
&group_id=#{_this.groupInputEl.val()}"
|
||||
|
||||
callback(data)
|
||||
).always ->
|
||||
_this.loadingSuggestions = false
|
||||
|
||||
serializeState: ->
|
||||
{
|
||||
# Search Criteria
|
||||
search_project_id: @projectInputEl.val()
|
||||
group_id: @groupInputEl.val()
|
||||
search_code: @searchCodeInputEl.val()
|
||||
repository_ref: @repositoryInputEl.val()
|
||||
scope: @scopeInputEl.val()
|
||||
|
||||
# Location badge
|
||||
_location: @locationText.text()
|
||||
}
|
||||
|
||||
bindEvents: ->
|
||||
$(document).on 'click', @onDocumentClick
|
||||
@searchInput.on 'keydown', @onSearchInputKeyDown
|
||||
@searchInput.on 'keyup', @onSearchInputKeyUp
|
||||
@searchInput.on 'click', @onSearchInputClick
|
||||
@searchInput.on 'focus', @onSearchInputFocus
|
||||
@clearInput.on 'click', @onClearInputClick
|
||||
|
||||
onDocumentClick: (e) =>
|
||||
# If clicking outside the search box
|
||||
# And search input is not focused
|
||||
# And we are not clicking inside a suggestion
|
||||
if not $.contains(@dropdown[0], e.target) and @isFocused and not $(e.target).parents('ul').length
|
||||
@onSearchInputBlur()
|
||||
|
||||
enableAutocomplete: ->
|
||||
# No need to enable anything if user is not logged in
|
||||
return if !gon.current_user_id
|
||||
|
||||
_this = @
|
||||
@loadingSuggestions = false
|
||||
|
||||
@dropdown.addClass('open')
|
||||
@searchInput.removeClass('disabled')
|
||||
|
||||
onSearchInputKeyDown: =>
|
||||
# Saves last length of the entered text
|
||||
@saveTextLength()
|
||||
|
||||
onSearchInputKeyUp: (e) =>
|
||||
switch e.keyCode
|
||||
when KEYCODE.BACKSPACE
|
||||
# when trying to remove the location badge
|
||||
if @lastTextLength is 0 and @badgePresent()
|
||||
@removeLocationBadge()
|
||||
|
||||
# When removing the last character and no badge is present
|
||||
if @lastTextLength is 1
|
||||
@disableAutocomplete()
|
||||
|
||||
# When removing any character from existin value
|
||||
if @lastTextLength > 1
|
||||
@enableAutocomplete()
|
||||
|
||||
when KEYCODE.ESCAPE
|
||||
@restoreOriginalState()
|
||||
|
||||
else
|
||||
# Handle the case when deleting the input value other than backspace
|
||||
# e.g. Pressing ctrl + backspace or ctrl + x
|
||||
if @searchInput.val() is ''
|
||||
@disableAutocomplete()
|
||||
else
|
||||
# We should display the menu only when input is not empty
|
||||
@enableAutocomplete()
|
||||
|
||||
@wrap.toggleClass 'has-value', !!e.target.value
|
||||
|
||||
# Avoid falsy value to be returned
|
||||
return
|
||||
|
||||
onSearchInputClick: (e) =>
|
||||
# Prevents closing the dropdown menu
|
||||
e.stopImmediatePropagation()
|
||||
|
||||
onSearchInputFocus: =>
|
||||
@isFocused = true
|
||||
@wrap.addClass('search-active')
|
||||
|
||||
onClearInputClick: (e) =>
|
||||
e.preventDefault()
|
||||
@searchInput.val('').focus()
|
||||
|
||||
onSearchInputBlur: (e) =>
|
||||
@isFocused = false
|
||||
@wrap.removeClass('search-active')
|
||||
|
||||
# If input is blank then restore state
|
||||
if @searchInput.val() is ''
|
||||
@restoreOriginalState()
|
||||
|
||||
addLocationBadge: (item) ->
|
||||
category = if item.category? then "#{item.category}: " else ''
|
||||
value = if item.value? then item.value else ''
|
||||
|
||||
html = "<span class='location-badge'>
|
||||
<i class='location-text'>#{category}#{value}</i>
|
||||
</span>"
|
||||
@locationBadgeEl.html(html)
|
||||
@wrap.addClass('has-location-badge')
|
||||
|
||||
restoreOriginalState: ->
|
||||
inputs = Object.keys @originalState
|
||||
|
||||
for input in inputs
|
||||
@getElement("##{input}").val(@originalState[input])
|
||||
|
||||
|
||||
if @originalState._location is ''
|
||||
@locationBadgeEl.empty()
|
||||
else
|
||||
@addLocationBadge(
|
||||
value: @originalState._location
|
||||
)
|
||||
|
||||
@dropdown.removeClass 'open'
|
||||
|
||||
badgePresent: ->
|
||||
@locationBadgeEl.children().length
|
||||
|
||||
resetSearchState: ->
|
||||
inputs = Object.keys @originalState
|
||||
|
||||
for input in inputs
|
||||
|
||||
# _location isnt a input
|
||||
break if input is '_location'
|
||||
|
||||
@getElement("##{input}").val('')
|
||||
|
||||
removeLocationBadge: ->
|
||||
@locationBadgeEl.empty()
|
||||
|
||||
# Reset state
|
||||
@resetSearchState()
|
||||
|
||||
@wrap.removeClass('has-location-badge')
|
||||
|
||||
disableAutocomplete: ->
|
||||
@searchInput.addClass('disabled')
|
||||
@dropdown.removeClass('open')
|
||||
@restoreMenu()
|
||||
|
||||
restoreMenu: ->
|
||||
html = "<ul>
|
||||
<li><a class='dropdown-menu-empty-link is-focused'>Loading...</a></li>
|
||||
</ul>"
|
||||
@dropdownContent.html(html)
|
||||
|
||||
onClick: (item, $el, e) ->
|
||||
if location.pathname.indexOf(item.url) isnt -1
|
||||
e.preventDefault()
|
||||
if not @badgePresent
|
||||
if item.category is 'Projects'
|
||||
@projectInputEl.val(item.id)
|
||||
@addLocationBadge(
|
||||
value: 'This project'
|
||||
)
|
||||
|
||||
if item.category is 'Groups'
|
||||
@groupInputEl.val(item.id)
|
||||
@addLocationBadge(
|
||||
value: 'This group'
|
||||
)
|
||||
|
||||
$el.removeClass('is-active')
|
||||
@disableAutocomplete()
|
||||
@searchInput.val('').focus()
|
||||
|
|
|
@ -2,30 +2,35 @@ class @Shortcuts
|
|||
constructor: ->
|
||||
@enabledHelp = []
|
||||
Mousetrap.reset()
|
||||
Mousetrap.bind('?', @selectiveHelp)
|
||||
Mousetrap.bind('?', @onToggleHelp)
|
||||
Mousetrap.bind('s', Shortcuts.focusSearch)
|
||||
Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], @toggleMarkdownPreview)
|
||||
Mousetrap.bind('t', -> Turbolinks.visit(findFileURL)) if findFileURL?
|
||||
|
||||
selectiveHelp: (e) =>
|
||||
Shortcuts.showHelp(e, @enabledHelp)
|
||||
onToggleHelp: (e) =>
|
||||
e.preventDefault()
|
||||
@toggleHelp(@enabledHelp)
|
||||
|
||||
@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(
|
||||
url: url,
|
||||
dataType: 'script',
|
||||
success: (e) ->
|
||||
if location and location.length > 0
|
||||
$(l).show() for l in location
|
||||
else
|
||||
$('.hidden-shortcut').show()
|
||||
$('.js-more-help-button').remove()
|
||||
)
|
||||
e.preventDefault()
|
||||
toggleMarkdownPreview: (e) =>
|
||||
$(document).triggerHandler('markdown-preview:toggle', [e])
|
||||
|
||||
toggleHelp: (location) ->
|
||||
$modal = $('#modal-shortcuts')
|
||||
|
||||
if $modal.length
|
||||
$modal.modal('toggle')
|
||||
return
|
||||
|
||||
$.ajax(
|
||||
url: gon.shortcuts_path,
|
||||
dataType: 'script',
|
||||
success: (e) ->
|
||||
if location and location.length > 0
|
||||
$(l).show() for l in location
|
||||
else
|
||||
$('.hidden-shortcut').show()
|
||||
$('.js-more-help-button').remove()
|
||||
)
|
||||
|
||||
@focusSearch: (e) ->
|
||||
$('#search').focus()
|
||||
|
@ -35,3 +40,14 @@ $(document).on 'click.more_help', '.js-more-help-button', (e) ->
|
|||
$(@).remove()
|
||||
$('.hidden-shortcut').show()
|
||||
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)
|
||||
)()
|
||||
|
|
|
@ -4,14 +4,8 @@
|
|||
class @ShortcutsIssuable extends ShortcutsNavigation
|
||||
constructor: (isMergeRequest) ->
|
||||
super()
|
||||
Mousetrap.bind('a', ->
|
||||
$('.block.assignee .edit-link').trigger('click')
|
||||
return false
|
||||
)
|
||||
Mousetrap.bind('m', ->
|
||||
$('.block.milestone .edit-link').trigger('click')
|
||||
return false
|
||||
)
|
||||
Mousetrap.bind('a', @openSidebarDropdown.bind(@, 'assignee'))
|
||||
Mousetrap.bind('m', @openSidebarDropdown.bind(@, 'milestone'))
|
||||
Mousetrap.bind('r', =>
|
||||
@replyWithSelectedText()
|
||||
return false
|
||||
|
@ -24,7 +18,11 @@ class @ShortcutsIssuable extends ShortcutsNavigation
|
|||
@nextIssue()
|
||||
return false
|
||||
)
|
||||
|
||||
Mousetrap.bind('e', =>
|
||||
@editIssue()
|
||||
return false
|
||||
)
|
||||
Mousetrap.bind('l', @openSidebarDropdown.bind(@, 'labels'))
|
||||
|
||||
if isMergeRequest
|
||||
@enabledHelp.push('.hidden-shortcut.merge_requests')
|
||||
|
@ -63,3 +61,11 @@ class @ShortcutsIssuable extends ShortcutsNavigation
|
|||
|
||||
# Focus the input field
|
||||
replyField.focus()
|
||||
|
||||
editIssue: ->
|
||||
$editBtn = $('.issuable-edit')
|
||||
Turbolinks.visit($editBtn.attr('href'))
|
||||
|
||||
openSidebarDropdown: (name) ->
|
||||
sidebar.openDropdown(name)
|
||||
return false
|
||||
|
|
|
@ -14,6 +14,7 @@ class @ShortcutsNavigation extends Shortcuts
|
|||
Mousetrap.bind('g m', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests'))
|
||||
Mousetrap.bind('g w', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki'))
|
||||
Mousetrap.bind('g s', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets'))
|
||||
Mousetrap.bind('i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-new-issue'))
|
||||
@enabledHelp.push('.hidden-shortcut.project')
|
||||
|
||||
@findAndFollowLink: (selector) ->
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
$(document).on("click", '.toggle-nav-collapse', (e) ->
|
||||
e.preventDefault()
|
||||
collapsed = 'page-sidebar-collapsed'
|
||||
expanded = 'page-sidebar-expanded'
|
||||
collapsed = 'page-sidebar-collapsed'
|
||||
expanded = 'page-sidebar-expanded'
|
||||
|
||||
toggleSidebar = ->
|
||||
$('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}")
|
||||
$('header').toggleClass("header-collapsed header-expanded")
|
||||
$('.sidebar-wrapper').toggleClass("sidebar-collapsed sidebar-expanded")
|
||||
$('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left")
|
||||
$.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' })
|
||||
|
||||
|
@ -14,4 +12,15 @@ $(document).on("click", '.toggle-nav-collapse', (e) ->
|
|||
niceScrollBars.updateScrollBar();
|
||||
), 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()
|
||||
|
|
|
@ -95,4 +95,4 @@ window.ContributorsStatGraphUtil =
|
|||
if date_range is null || date_range[0] <= new Date(date) <= date_range[1]
|
||||
true
|
||||
else
|
||||
false
|
||||
false
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
class @Subscription
|
||||
constructor: (url) ->
|
||||
$(".subscribe-button").unbind("click").click (event)=>
|
||||
btn = $(event.currentTarget)
|
||||
action = btn.find("span").text()
|
||||
current_status = $(".subscription-status").attr("data-status")
|
||||
btn.prop("disabled", true)
|
||||
|
||||
$.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")
|
||||
constructor: (container) ->
|
||||
$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)
|
||||
action = btn.find('span').text()
|
||||
current_status = @subscription_status.attr('data-status')
|
||||
btn.addClass('disabled')
|
||||
|
||||
$.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')
|
||||
|
|
110
app/assets/javascripts/todos.js.coffee
Normal file
110
app/assets/javascripts/todos.js.coffee
Normal 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)
|
|
@ -1,10 +1,17 @@
|
|||
class @User
|
||||
constructor: ->
|
||||
constructor: (@opts) ->
|
||||
$('.profile-groups-avatars').tooltip("placement": "top")
|
||||
new ProjectsList()
|
||||
|
||||
@initTabs()
|
||||
|
||||
$('.hide-project-limit-message').on 'click', (e) ->
|
||||
path = '/'
|
||||
$.cookie('hide_project_limit_message', 'false', { path: path })
|
||||
$(@).parents('.project-limit-message').remove()
|
||||
e.preventDefault()
|
||||
|
||||
initTabs: ->
|
||||
new UserTabs(
|
||||
parentEl: '.user-profile'
|
||||
action: @opts.action
|
||||
)
|
||||
|
|
153
app/assets/javascripts/user_tabs.js.coffee
Normal file
153
app/assets/javascripts/user_tabs.js.coffee
Normal 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
|
|
@ -1,12 +1,214 @@
|
|||
class @UsersSelect
|
||||
constructor: ->
|
||||
constructor: (currentUser) ->
|
||||
@usersPath = "/autocomplete/users.json"
|
||||
@userPath = "/autocomplete/users/:id.json"
|
||||
if currentUser?
|
||||
@currentUser = JSON.parse(currentUser)
|
||||
|
||||
$('.js-user-search').each (i, dropdown) =>
|
||||
$dropdown = $(dropdown)
|
||||
@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) =>
|
||||
@projectId = $(select).data('project-id')
|
||||
@groupId = $(select).data('group-id')
|
||||
@showCurrentUser = $(select).data('current-user')
|
||||
@authorId = $(select).data('author-id')
|
||||
showNullUser = $(select).data('null-user')
|
||||
showAnyUser = $(select).data('any-user')
|
||||
showEmailUser = $(select).data('email-user')
|
||||
|
@ -112,6 +314,7 @@ class @UsersSelect
|
|||
project_id: @projectId
|
||||
group_id: @groupId
|
||||
current_user: @showCurrentUser
|
||||
author_id: @authorId
|
||||
dataType: "json"
|
||||
).done (users) ->
|
||||
callback(users)
|
||||
|
|
|
@ -42,7 +42,7 @@ class @ZenMode
|
|||
$(e.currentTarget).trigger('zen_mode:leave')
|
||||
|
||||
$(document).on 'zen_mode:enter', (e) =>
|
||||
@enter(e.target.parentNode)
|
||||
@enter($(e.target).closest('.md-area').find('.zen-backdrop'))
|
||||
$(document).on 'zen_mode:leave', (e) =>
|
||||
@exit()
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
*= require_self
|
||||
*= require dropzone/basic
|
||||
*= require cal-heatmap
|
||||
*= require cropper.css
|
||||
*/
|
||||
|
||||
/*
|
||||
|
@ -24,12 +25,6 @@
|
|||
*/
|
||||
@import "framework";
|
||||
|
||||
/*
|
||||
* NProgress load bar css
|
||||
*/
|
||||
@import 'nprogress';
|
||||
@import 'nprogress-bootstrap';
|
||||
|
||||
/*
|
||||
* Font icons
|
||||
*/
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
|
||||
// Toggle between two states.
|
||||
.js-toggler-container {
|
||||
.turn-on { display: block; }
|
||||
.turn-on { display: block; }
|
||||
.turn-off { display: none; }
|
||||
&.on {
|
||||
.turn-on { display: none; }
|
||||
.turn-on { display: none; }
|
||||
.turn-off { display: block; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
@import 'framework/tw_bootstrap';
|
||||
@import "framework/layout";
|
||||
|
||||
@import "framework/animations.scss";
|
||||
@import "framework/avatar.scss";
|
||||
@import "framework/blocks.scss";
|
||||
@import "framework/buttons.scss";
|
||||
@import "framework/calendar.scss";
|
||||
@import "framework/callout.scss";
|
||||
@import "framework/common.scss";
|
||||
@import "framework/dropdowns.scss";
|
||||
@import "framework/files.scss";
|
||||
@import "framework/filters.scss";
|
||||
@import "framework/flash.scss";
|
||||
|
@ -24,8 +26,10 @@
|
|||
@import "framework/lists.scss";
|
||||
@import "framework/markdown_area.scss";
|
||||
@import "framework/mobile.scss";
|
||||
@import "framework/modal.scss";
|
||||
@import "framework/nav.scss";
|
||||
@import "framework/pagination.scss";
|
||||
@import "framework/progress.scss";
|
||||
@import "framework/panels.scss";
|
||||
@import "framework/selects.scss";
|
||||
@import "framework/sidebar.scss";
|
||||
|
|
72
app/assets/stylesheets/framework/animations.scss
Normal file
72
app/assets/stylesheets/framework/animations.scss
Normal 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;
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
}
|
||||
|
||||
&.group-avatar, &.project-avatar, &.avatar-tile {
|
||||
@include border-radius(0px);
|
||||
@include border-radius(0);
|
||||
}
|
||||
|
||||
&.s16 { width: 16px; height: 16px; margin-right: 6px; }
|
||||
|
@ -28,6 +28,7 @@
|
|||
&.s46 { width: 46px; height: 46px; margin-right: 15px; }
|
||||
&.s48 { width: 48px; height: 48px; margin-right: 10px; }
|
||||
&.s60 { width: 60px; height: 60px; margin-right: 12px; }
|
||||
&.s70 { width: 70px; height: 70px; margin-right: 14px; }
|
||||
&.s90 { width: 90px; height: 90px; margin-right: 15px; }
|
||||
&.s110 { width: 110px; height: 110px; margin-right: 15px; }
|
||||
&.s140 { width: 140px; height: 140px; margin-right: 20px; }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.light-well {
|
||||
background-color: #f8fafc;
|
||||
background-color: $background-color;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
|
@ -18,12 +18,12 @@
|
|||
line-height: 36px;
|
||||
}
|
||||
|
||||
.gray-content-block {
|
||||
.row-content-block {
|
||||
margin-top: 0;
|
||||
margin-bottom: -$gl-padding;
|
||||
background-color: $background-color;
|
||||
padding: $gl-padding;
|
||||
margin-bottom: 0px;
|
||||
margin-bottom: 0;
|
||||
border-top: 1px solid $border-color;
|
||||
border-bottom: 1px solid $border-color;
|
||||
color: $gl-gray;
|
||||
|
@ -66,7 +66,7 @@
|
|||
}
|
||||
|
||||
.oneline {
|
||||
line-height: 42px;
|
||||
line-height: 35px;
|
||||
}
|
||||
|
||||
> p:last-child {
|
||||
|
@ -81,6 +81,11 @@
|
|||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.build-content {
|
||||
background-color: $white-light;
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
||||
.cover-block {
|
||||
|
@ -107,15 +112,37 @@
|
|||
margin: 0;
|
||||
font-size: 23px;
|
||||
font-weight: normal;
|
||||
margin: 16px 0 5px 0;
|
||||
margin: 16px 0 5px;
|
||||
color: #4c4e54;
|
||||
font-size: 23px;
|
||||
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 {
|
||||
padding: 0 $gl-padding 3px;
|
||||
color: $gl-text-color;
|
||||
|
||||
&.username:last-child {
|
||||
padding-bottom: $gl-padding;
|
||||
}
|
||||
}
|
||||
|
||||
.cover-controls {
|
||||
|
@ -128,6 +155,41 @@
|
|||
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 {
|
||||
|
@ -153,3 +215,7 @@
|
|||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.content-block-small {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
&:focus,
|
||||
&:active {
|
||||
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;
|
||||
}
|
||||
|
||||
&:active {
|
||||
@include box-shadow (inset 0 0 4px rgba(0, 0, 0, 0.12));
|
||||
&:active,
|
||||
&.active {
|
||||
@include box-shadow ($gl-btn-active-background);
|
||||
|
||||
background-color: $dark;
|
||||
border-color: $border-dark;
|
||||
|
@ -37,37 +39,43 @@
|
|||
}
|
||||
|
||||
@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 {
|
||||
@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 {
|
||||
@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 {
|
||||
@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 {
|
||||
@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 {
|
||||
@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 {
|
||||
@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 {
|
||||
@include btn-default;
|
||||
@include btn-white;
|
||||
|
||||
color: $gl-text-color;
|
||||
|
||||
&:focus:active {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&.btn-small,
|
||||
&.btn-sm {
|
||||
padding: 4px 10px;
|
||||
|
@ -121,7 +129,7 @@
|
|||
margin-right: 7px;
|
||||
float: left;
|
||||
&:last-child {
|
||||
margin-right: 0px;
|
||||
margin-right: 0;
|
||||
}
|
||||
&.btn-xs {
|
||||
margin-right: 3px;
|
||||
|
@ -130,6 +138,31 @@
|
|||
&.disabled {
|
||||
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 {
|
||||
|
@ -146,7 +179,7 @@
|
|||
margin-right: 7px;
|
||||
float: left;
|
||||
&:last-child {
|
||||
margin-right: 0px;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -179,9 +212,49 @@
|
|||
}
|
||||
|
||||
.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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
.calender-block {
|
||||
@media (min-width: $screen-sm-min) and (max-width: $screen-lg-min) {
|
||||
overflow-x: scroll;
|
||||
}
|
||||
}
|
||||
|
||||
.user-calendar-activities {
|
||||
.calendar_onclick_hr {
|
||||
padding: 0;
|
||||
|
@ -33,19 +39,23 @@
|
|||
}
|
||||
|
||||
.q2 {
|
||||
fill: #ACD5F2 !important;
|
||||
fill: #acd5f2 !important;
|
||||
}
|
||||
|
||||
.q3 {
|
||||
fill: #7FA8D1 !important;
|
||||
fill: #7fa8d1 !important;
|
||||
}
|
||||
|
||||
.q4 {
|
||||
fill: #49729B !important;
|
||||
fill: #49729b !important;
|
||||
}
|
||||
|
||||
.q5 {
|
||||
fill: #254E77 !important;
|
||||
fill: #254e77 !important;
|
||||
}
|
||||
|
||||
.future {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.domain-background {
|
||||
|
|
|
@ -39,6 +39,6 @@
|
|||
}
|
||||
.bs-callout-success {
|
||||
background-color: #dff0d8;
|
||||
border-color: #5cA64d;
|
||||
border-color: #5ca64d;
|
||||
color: #3c763d;
|
||||
}
|
||||
|
|
|
@ -1,21 +1,28 @@
|
|||
/** COLORS **/
|
||||
.cgray { color: $gl-gray; }
|
||||
.clgray { color: #BBB }
|
||||
.clgray { color: #bbb }
|
||||
.cred { color: $gl-text-red; }
|
||||
.cgreen { color: $gl-text-green; }
|
||||
.cdark { color: #444 }
|
||||
|
||||
/** COMMON CLASSES **/
|
||||
.prepend-top-10 { margin-top:10px }
|
||||
.prepend-top-0 { margin-top: 0; }
|
||||
.prepend-top-5 { margin-top: 5px; }
|
||||
.prepend-top-10 { margin-top: 10px }
|
||||
.prepend-top-default { margin-top: $gl-padding !important; }
|
||||
.prepend-top-20 { margin-top:20px }
|
||||
.prepend-left-10 { margin-left:10px }
|
||||
.prepend-left-20 { margin-left:20px }
|
||||
.append-right-10 { margin-right:10px }
|
||||
.append-right-20 { margin-right:20px }
|
||||
.append-bottom-10 { margin-bottom:10px }
|
||||
.append-bottom-15 { margin-bottom:15px }
|
||||
.append-bottom-20 { margin-bottom:20px }
|
||||
.prepend-top-20 { margin-top: 20px }
|
||||
.prepend-left-5 { margin-left: 5px }
|
||||
.prepend-left-10 { margin-left: 10px }
|
||||
.prepend-left-default { margin-left: $gl-padding; }
|
||||
.prepend-left-20 { margin-left: 20px }
|
||||
.append-right-5 { margin-right: 5px }
|
||||
.append-right-10 { margin-right: 10px }
|
||||
.append-right-default { margin-right: $gl-padding; }
|
||||
.append-right-20 { margin-right: 20px }
|
||||
.append-bottom-0 { margin-bottom: 0 }
|
||||
.append-bottom-10 { margin-bottom: 10px }
|
||||
.append-bottom-15 { margin-bottom: 15px }
|
||||
.append-bottom-20 { margin-bottom: 20px }
|
||||
.append-bottom-default { margin-bottom: $gl-padding; }
|
||||
.inline { display: inline-block }
|
||||
.center { text-align: center }
|
||||
|
@ -45,7 +52,7 @@ pre {
|
|||
}
|
||||
|
||||
&.well-pre {
|
||||
border: 1px solid #EEE;
|
||||
border: 1px solid #eee;
|
||||
background: #f9f9f9;
|
||||
border-radius: 0;
|
||||
color: #555;
|
||||
|
@ -56,21 +63,6 @@ hr {
|
|||
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 {
|
||||
@include str-truncated;
|
||||
}
|
||||
|
@ -112,7 +104,7 @@ span.update-author {
|
|||
}
|
||||
|
||||
.user-mention {
|
||||
color: #2FA0BB;
|
||||
color: #2fa0bb;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
@ -130,23 +122,16 @@ p.time {
|
|||
text-shadow: none;
|
||||
}
|
||||
|
||||
.thin_area{
|
||||
.thin_area {
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
// Fixes alignment on notes.
|
||||
.new_note {
|
||||
label {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
// Fix issue with notes & lists creating a bunch of bottom borders.
|
||||
li.note {
|
||||
img { max-width:100% }
|
||||
img { max-width: 100% }
|
||||
.note-title {
|
||||
li {
|
||||
border-bottom:none !important;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,7 +142,7 @@ li.note {
|
|||
}
|
||||
}
|
||||
|
||||
.wiki_content code, .readme code{
|
||||
.wiki_content code, .readme code {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
|
@ -196,9 +181,9 @@ li.note {
|
|||
|
||||
.error-message {
|
||||
padding: 10px;
|
||||
background: #C67;
|
||||
background: #c67;
|
||||
margin: 0;
|
||||
color: #FFF;
|
||||
color: #fff;
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
|
@ -209,7 +194,7 @@ li.note {
|
|||
.browser-alert {
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
background: #C67;
|
||||
background: #c67;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
a {
|
||||
|
@ -280,7 +265,7 @@ img.emoji {
|
|||
|
||||
table {
|
||||
td.permission-x {
|
||||
background: #D9EDF7 !important;
|
||||
background: #d9edf7 !important;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
@ -289,7 +274,7 @@ table {
|
|||
float: left;
|
||||
text-align: center;
|
||||
font-size: 32px;
|
||||
color: #AAA;
|
||||
color: #aaa;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
|
@ -301,8 +286,11 @@ table {
|
|||
}
|
||||
|
||||
.btn-sign-in {
|
||||
margin-top: 8px;
|
||||
text-shadow: none;
|
||||
|
||||
@media (min-width: $screen-sm-min) {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.side-filters {
|
||||
|
@ -356,7 +344,7 @@ table {
|
|||
|
||||
.profiler-button,
|
||||
.profiler-controls {
|
||||
border-color: #EEE !important;
|
||||
border-color: #eee !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -384,7 +372,7 @@ table {
|
|||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 250px !important;
|
||||
min-width: 250px;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
|
523
app/assets/stylesheets/framework/dropdowns.scss
Normal file
523
app/assets/stylesheets/framework/dropdowns.scss
Normal 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;
|
||||
}
|
||||
}
|
|
@ -3,12 +3,10 @@
|
|||
*
|
||||
*/
|
||||
.file-holder {
|
||||
border: none;
|
||||
border: 1px solid $border-color;
|
||||
|
||||
&.readme-holder {
|
||||
margin-top: 10px;
|
||||
border-bottom: 0;
|
||||
margin: $gl-padding-top 0;
|
||||
}
|
||||
|
||||
table {
|
||||
|
@ -17,11 +15,13 @@
|
|||
|
||||
.file-title {
|
||||
position: relative;
|
||||
background: $background-color;
|
||||
background-color: $background-color;
|
||||
border-bottom: 1px solid $border-color;
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
padding: 10px $gl-padding;
|
||||
word-wrap: break-word;
|
||||
border-radius: 3px 3px 0 0;
|
||||
|
||||
.file-actions {
|
||||
float: right;
|
||||
|
@ -30,24 +30,14 @@
|
|||
right: 15px;
|
||||
|
||||
.btn {
|
||||
padding: 0px 10px;
|
||||
padding: 0 10px;
|
||||
font-size: 13px;
|
||||
line-height: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
.filename {
|
||||
&.old {
|
||||
span.idiff {
|
||||
background-color: #f8cbcb;
|
||||
}
|
||||
}
|
||||
|
||||
&.new {
|
||||
span.idiff {
|
||||
background-color: #a6f3a6;
|
||||
}
|
||||
}
|
||||
a:not(.btn) {
|
||||
color: $gl-dark-link-color;
|
||||
}
|
||||
|
||||
.left-options {
|
||||
|
@ -78,13 +68,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.blob_file {
|
||||
|
||||
}
|
||||
|
||||
&.blob-no-preview {
|
||||
background: #eee;
|
||||
text-shadow: 0 1px 2px #FFF;
|
||||
text-shadow: 0 1px 2px #fff;
|
||||
padding: 100px 0;
|
||||
}
|
||||
|
||||
|
@ -124,7 +110,12 @@
|
|||
}
|
||||
td.line-numbers {
|
||||
float: none;
|
||||
border-left: 1px solid #DDD;
|
||||
border-left: 1px solid #ddd;
|
||||
|
||||
i {
|
||||
float: none;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
td.lines {
|
||||
padding: 0;
|
||||
|
@ -169,6 +160,7 @@
|
|||
*/
|
||||
&.code {
|
||||
padding: 0;
|
||||
-webkit-overflow-scrolling: auto; // See https://gitlab.com/gitlab-org/gitlab-ce/issues/13987
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,30 +1,21 @@
|
|||
.filter-item {
|
||||
margin-right: 6px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
@media (min-width: $screen-sm-min) {
|
||||
.issues-filters,
|
||||
.issues_bulk_update {
|
||||
select, .select2-container {
|
||||
width: 120px !important;
|
||||
display: inline-block;
|
||||
.dropdown-menu-toggle {
|
||||
width: 132px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.issues-filters,
|
||||
.issues_bulk_update {
|
||||
select, .select2-container {
|
||||
width: 150px !important;
|
||||
display: inline-block;
|
||||
}
|
||||
@media (max-width: $screen-xs-max) {
|
||||
.filter-item {
|
||||
display: block;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.issues-filters,
|
||||
.issues_bulk_update {
|
||||
.select2-container .select2-choice {
|
||||
color: #444 !important;
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue