diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 85730e1b68..5ef3081395 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,7 @@ image: "ruby:2.1" services: - mysql:latest - - redis:latest + - redis:alpine cache: key: "ruby21" @@ -13,100 +13,187 @@ variables: MYSQL_ALLOW_EMPTY_PASSWORD: "1" # retry tests only in CI environment RSPEC_RETRY_RETRY_COUNT: "3" + RAILS_ENV: "test" + SIMPLECOV: "true" + USE_DB: "true" + USE_BUNDLE_INSTALL: "true" before_script: - source ./scripts/prepare_build.sh - - ruby -v - - which ruby - - retry gem install bundler --no-ri --no-rdoc - cp config/gitlab.yml.example config/gitlab.yml - - touch log/application.log - - touch log/test.log - - retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}" - - RAILS_ENV=test bundle exec rake db:drop db:create db:schema:load db:migrate + - bundle --version + - '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"' + - retry gem install knapsack + - '[ "$USE_DB" != "true" ] || bundle exec rake db:drop db:create db:schema:load db:migrate' stages: +- prepare - test -- notifications +- post-test -spec:feature: +# Prepare and merge knapsack tests + +.knapsack-state: &knapsack-state + services: [] + variables: + USE_DB: "false" + USE_BUNDLE_INSTALL: "false" + cache: + key: "knapsack" + paths: + - knapsack/ + artifacts: + paths: + - knapsack/ + +knapsack: + <<: *knapsack-state + stage: prepare + script: + - mkdir -p knapsack/ + - '[[ -f knapsack/rspec_report.json ]] || echo "{}" > knapsack/rspec_report.json' + - '[[ -f knapsack/spinach_report.json ]] || echo "{}" > knapsack/spinach_report.json' + +update-knapsack: + <<: *knapsack-state + stage: post-test + script: + - scripts/merge-reports knapsack/rspec_report.json knapsack/rspec_node_*.json + - scripts/merge-reports knapsack/spinach_report.json knapsack/spinach_node_*.json + - rm -f knapsack/*_node_*.json + +# Execute all testing suites + +.rspec-knapsack: &rspec-knapsack stage: test script: - - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null - - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature + - bundle exec rake assets:precompile 2>/dev/null + - JOB_NAME=( $CI_BUILD_NAME ) + - export CI_NODE_INDEX=${JOB_NAME[1]} + - export CI_NODE_TOTAL=${JOB_NAME[2]} + - export KNAPSACK_REPORT_PATH=knapsack/rspec_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json + - export KNAPSACK_GENERATE_REPORT=true + - cp knapsack/rspec_report.json ${KNAPSACK_REPORT_PATH} + - knapsack rspec + artifacts: + paths: + - knapsack/ -spec:api: +.spinach-knapsack: &spinach-knapsack stage: test script: - - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api + - bundle exec rake assets:precompile 2>/dev/null + - JOB_NAME=( $CI_BUILD_NAME ) + - export CI_NODE_INDEX=${JOB_NAME[1]} + - export CI_NODE_TOTAL=${JOB_NAME[2]} + - export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json + - export KNAPSACK_GENERATE_REPORT=true + - cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH} + - knapsack spinach "-r rerun" + # retry failed tests 3 times + - retry '[ ! -e tmp/spinach-rerun.txt ] || bin/spinach -r rerun $(cat tmp/spinach-rerun.txt)' + artifacts: + paths: + - knapsack/ -spec:models: +rspec 0 20: *rspec-knapsack +rspec 1 20: *rspec-knapsack +rspec 2 20: *rspec-knapsack +rspec 3 20: *rspec-knapsack +rspec 4 20: *rspec-knapsack +rspec 5 20: *rspec-knapsack +rspec 6 20: *rspec-knapsack +rspec 7 20: *rspec-knapsack +rspec 8 20: *rspec-knapsack +rspec 9 20: *rspec-knapsack +rspec 10 20: *rspec-knapsack +rspec 11 20: *rspec-knapsack +rspec 12 20: *rspec-knapsack +rspec 13 20: *rspec-knapsack +rspec 14 20: *rspec-knapsack +rspec 15 20: *rspec-knapsack +rspec 16 20: *rspec-knapsack +rspec 17 20: *rspec-knapsack +rspec 18 20: *rspec-knapsack +rspec 19 20: *rspec-knapsack + +spinach 0 10: *spinach-knapsack +spinach 1 10: *spinach-knapsack +spinach 2 10: *spinach-knapsack +spinach 3 10: *spinach-knapsack +spinach 4 10: *spinach-knapsack +spinach 5 10: *spinach-knapsack +spinach 6 10: *spinach-knapsack +spinach 7 10: *spinach-knapsack +spinach 8 10: *spinach-knapsack +spinach 9 10: *spinach-knapsack + +# Execute all testing suites against Ruby 2.2 + +.ruby-22: &ruby-22 + image: "ruby:2.2" + only: + - master + cache: + key: "ruby22" + paths: + - vendor + +.rspec-knapsack-ruby22: &rspec-knapsack-ruby22 + <<: *rspec-knapsack + <<: *ruby-22 + +.spinach-knapsack-ruby22: &spinach-knapsack-ruby22 + <<: *spinach-knapsack + <<: *ruby-22 + +rspec 0 20 ruby22: *rspec-knapsack-ruby22 +rspec 1 20 ruby22: *rspec-knapsack-ruby22 +rspec 2 20 ruby22: *rspec-knapsack-ruby22 +rspec 3 20 ruby22: *rspec-knapsack-ruby22 +rspec 4 20 ruby22: *rspec-knapsack-ruby22 +rspec 5 20 ruby22: *rspec-knapsack-ruby22 +rspec 6 20 ruby22: *rspec-knapsack-ruby22 +rspec 7 20 ruby22: *rspec-knapsack-ruby22 +rspec 8 20 ruby22: *rspec-knapsack-ruby22 +rspec 9 20 ruby22: *rspec-knapsack-ruby22 +rspec 10 20 ruby22: *rspec-knapsack-ruby22 +rspec 11 20 ruby22: *rspec-knapsack-ruby22 +rspec 12 20 ruby22: *rspec-knapsack-ruby22 +rspec 13 20 ruby22: *rspec-knapsack-ruby22 +rspec 14 20 ruby22: *rspec-knapsack-ruby22 +rspec 15 20 ruby22: *rspec-knapsack-ruby22 +rspec 16 20 ruby22: *rspec-knapsack-ruby22 +rspec 17 20 ruby22: *rspec-knapsack-ruby22 +rspec 18 20 ruby22: *rspec-knapsack-ruby22 +rspec 19 20 ruby22: *rspec-knapsack-ruby22 + +spinach 0 10 ruby22: *spinach-knapsack-ruby22 +spinach 1 10 ruby22: *spinach-knapsack-ruby22 +spinach 2 10 ruby22: *spinach-knapsack-ruby22 +spinach 3 10 ruby22: *spinach-knapsack-ruby22 +spinach 4 10 ruby22: *spinach-knapsack-ruby22 +spinach 5 10 ruby22: *spinach-knapsack-ruby22 +spinach 6 10 ruby22: *spinach-knapsack-ruby22 +spinach 7 10 ruby22: *spinach-knapsack-ruby22 +spinach 8 10 ruby22: *spinach-knapsack-ruby22 +spinach 9 10 ruby22: *spinach-knapsack-ruby22 + +# Other generic tests + +.exec: &exec stage: test script: - - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models + - bundle exec $CI_BUILD_NAME -spec:lib: - stage: test - script: - - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib - -spec:services: - stage: test - script: - - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services - -spec:other: - stage: test - script: - - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other - -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 - -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 - -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 - -teaspoon: - stage: test - script: - - RAILS_ENV=test bundle exec teaspoon - -rubocop: - stage: test - script: - - bundle exec rubocop - -scss-lint: - stage: test - script: - - bundle exec rake scss_lint - -brakeman: - stage: test - script: - - bundle exec rake brakeman - -flog: - stage: test - script: - - bundle exec rake flog - -flay: - stage: test - script: - - bundle exec rake flay +teaspoon: *exec +rubocop: *exec +rake scss_lint: *exec +rake brakeman: *exec +rake flog: *exec +rake flay: *exec +rake db:migrate:reset: *exec +license_finder: *exec bundler:audit: stage: test @@ -115,127 +202,10 @@ bundler:audit: script: - "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 - script: - - RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null - - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature - cache: - key: "ruby22" - paths: - - vendor - -spec:api:ruby22: - stage: test - image: ruby:2.2 - only: - - master - script: - - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api - cache: - key: "ruby22" - paths: - - vendor - -spec:models:ruby22: - stage: test - image: ruby:2.2 - only: - - master - script: - - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models - cache: - key: "ruby22" - paths: - - vendor - -spec:lib:ruby22: - stage: test - image: ruby:2.2 - only: - - master - script: - - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib - cache: - key: "ruby22" - paths: - - vendor - -spec:services:ruby22: - stage: test - image: ruby:2.2 - only: - - master - script: - - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services - cache: - key: "ruby22" - paths: - - vendor - -spec:other:ruby22: - stage: test - image: ruby:2.2 - only: - - master - script: - - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other - cache: - key: "ruby22" - paths: - - vendor - -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 - -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 - -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 +# Notify slack in the end notify:slack: - stage: notifications + stage: post-test script: - ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See " when: on_failure diff --git a/.rubocop.yml b/.rubocop.yml index 84a8015b41..eb51a04c0e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -194,7 +194,7 @@ Style/EmptyLines: # Keep blank lines around access modifiers. Style/EmptyLinesAroundAccessModifier: - Enabled: false + Enabled: true # Keeps track of empty lines around block bodies. Style/EmptyLinesAroundBlockBody: @@ -771,7 +771,7 @@ Metrics/PerceivedComplexity: # Checks for ambiguous operators in the first argument of a method invocation # without parentheses. Lint/AmbiguousOperator: - Enabled: false + Enabled: true # Checks for ambiguous regexp literals in the first argument of a method # invocation without parentheses. diff --git a/.vagrant_enabled b/.vagrant_enabled new file mode 100644 index 0000000000..e69de29bb2 diff --git a/CHANGELOG b/CHANGELOG index d1cde40c1c..5136756079 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,24 +1,35 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.9.0 (unreleased) + - Bulk assign/unassign labels to issues. + - Ability to prioritize labels !4009 / !3205 (Thijs Wouters) - Allow enabling wiki page events from Webhook management UI + - Bump rouge to 1.11.0 - Make EmailsOnPushWorker use Sidekiq mailers queue - Fix wiki page events' webhook to point to the wiki repository - Fix issue todo not remove when leave project !4150 (Long Nguyen) + - Bump recaptcha gem to 3.0.0 to remove deprecated stoken support - Allow forking projects with restricted visibility level - Improve note validation to prevent errors when creating invalid note via API + - Reduce number of fog gem dependencies - Remove project notification settings associated with deleted projects - Fix 404 page when viewing TODOs that contain milestones or labels in different projects - Redesign navigation for project pages - Fix groups API to list only user's accessible projects - Redesign account and email confirmation emails + - Bump nokogiri to 1.6.8 - Use gitlab-shell v3.0.0 + - Use Knapsack to evenly distribute tests across multiple nodes + - Add `sha` parameter to MR merge API, to ensure only reviewed changes are merged + - Don't allow MRs to be merged when commits were added since the last review / page load - Add DB index on users.state - Add rake task 'gitlab:db:configure' for conditionally seeding or migrating the database - Changed the Slack build message to use the singular duration if necessary (Aran Koning) - Fix issues filter when ordering by milestone - Todos will display target state if issuable target is 'Closed' or 'Merged' - Fix bug when sorting issues by milestone due date and filtering by two or more labels + - Add support for using Yubikeys (U2F) for two-factor authentication + - Link to blank group icon doesn't throw a 404 anymore - Remove 'main language' feature - Pipelines can be canceled only when there are running builds - Use downcased path to container repository as this is expected path by Docker @@ -26,13 +37,46 @@ v 8.9.0 (unreleased) - Measure queue duration between gitlab-workhorse and Rails - Make authentication service for Container Registry to be compatible with < Docker 1.11 - Add Application Setting to configure Container Registry token expire delay (default 5min) + - Cache assigned issue and merge request counts in sidebar nav + - Cache project build count in sidebar nav + - Reduce number of queries needed to render issue labels in the sidebar + - Improve error handling importing projects + - Remove duplicated notification settings + - Put project Files and Commits tabs under Code tab + - Replace Colorize with Rainbow for coloring console output in Rake tasks. + - An indicator is now displayed at the top of the comment field for confidential issues. + - RepositoryCheck::SingleRepositoryWorker public and private methods are now instrumented + +v 8.8.4 (unreleased) + - Ensure branch cleanup regardless of whether the GitHub import process succeeds + - Fix issue with arrow keys not working in search autocomplete dropdown + - Fix todos page throwing errors when you have a project pending deletion + - Reduce number of SQL queries when rendering user references + - Upgrade to jQuery 2 + - Remove prev/next buttons on issues and merge requests + - Import GitHub repositories respecting the API rate limit + - Fix importer for GitHub comments on diff + - Disable Webhooks before proceeding with the GitHub import + - Added descriptions to notification settings dropdown v 8.8.3 - - Fix incorrect links on pipeline page when merge request created from fork - - Fix gitlab importer failing to import new projects due to missing credentials - - Fix import URL migration not rescuing with the correct Error - - In search results, only show notes on confidential issues that the user has access to - - Fix health check access token changing due to old application settings being used + - Fix 404 page when viewing TODOs that contain milestones or labels in different projects. !4312 + - Fixed JS error when trying to remove discussion form. !4303 + - Fixed issue with button color when no CI enabled. !4287 + - Fixed potential issue with 2 CI status polling events happening. !3869 + - Improve design of Pipeline view. !4230 + - Fix gitlab importer failing to import new projects due to missing credentials. !4301 + - Fix import URL migration not rescuing with the correct Error. !4321 + - Fix health check access token changing due to old application settings being used. !4332 + - Make authentication service for Container Registry to be compatible with Docker versions before 1.11. !4363 + - Add Application Setting to configure Container Registry token expire delay (default 5 min). !4364 + - Pass the "Remember me" value to the 2FA token form. !4369 + - Fix incorrect links on pipeline page when merge request created from fork. !4376 + - Use downcased path to container repository as this is expected path by Docker. !4420 + - Fix wiki project clone address error (chujinjin). !4429 + - Fix serious performance bug with rendering Markdown with InlineDiffFilter. !4392 + - Fix missing number on generated ordered list element. !4437 + - Prevent disclosure of notes on confidential issues in search results. v 8.8.2 - Added remove due date button. !4209 @@ -134,6 +178,7 @@ v 8.7.6 - Fix import from GitLab.com to a private instance failure. !4181 - Fix external imports not finding the import data. !4106 - Fix notification delay when changing status of an issue + - Bump Workhorse to 0.7.5 so it can serve raw diffs v 8.7.5 - Fix relative links in wiki pages. !4050 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a15f8c4fec..f447221477 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,7 +96,7 @@ 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]. +The current designs can be found in the [`gitlab8.atype` file]. ### UI development kit @@ -308,7 +308,7 @@ tests are least likely to receive timely feedback. The workflow to make a merge request is as follows: 1. Fork the project into your personal space on GitLab.com -1. Create a feature branch +1. Create a feature branch, branch away from `master`. 1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code 1. Add your changes to the [CHANGELOG](CHANGELOG) 1. If you are writing documentation, make sure to read the [documentation styleguide][doc-styleguide] @@ -405,6 +405,7 @@ description area. Copy-paste it to retain the markdown format. entire line to follow it. This prevents linting tools from generating warnings. - Don't touch neighbouring lines. As an exception, automatic mass refactoring modifications may leave style non-compliant. +1. If the merge request adds any new libraries (gems, JavaScript libraries, etc.), they should conform to our [Licensing guidelines][license-finder-doc]. See the instructions in that document for help if your MR fails the "license-finder" test with a "Dependencies that need approval" error. ## Changes for Stable Releases @@ -530,4 +531,5 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor [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/ +[`gitlab8.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/current/ +[license-finder-doc]: doc/development/licensing.md diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 0a1ffad4b4..8bd6ba8c5c 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.7.4 +0.7.5 diff --git a/Gemfile b/Gemfile index 0ab78c2a73..f2ac70831f 100644 --- a/Gemfile +++ b/Gemfile @@ -38,16 +38,17 @@ gem 'rack-oauth2', '~> 1.2.1' gem 'jwt' # Spam and anti-bot protection -gem 'recaptcha', require: 'recaptcha/rails' +gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails' gem 'akismet', '~> 2.0' # Two-factor authentication gem 'devise-two-factor', '~> 3.0.0' gem 'rqrcode-rails3', '~> 0.1.7' gem 'attr_encrypted', '~> 3.0.0' +gem 'u2f', '~> 0.2.1' # Browser detection -gem "browser", '~> 1.0.0' +gem "browser", '~> 2.0.3' # Extracting information from a git repository # Provide access to Gitlab::Git library @@ -83,8 +84,14 @@ gem "carrierwave", '~> 0.10.0' # Drag and Drop UI gem 'dropzonejs-rails', '~> 0.7.1' +# for backups +gem 'fog-aws', '~> 0.9' +gem 'fog-core', '~> 1.40' +gem 'fog-local', '~> 0.3' +gem 'fog-google', '~> 0.3' +gem 'fog-openstack', '~> 0.1' + # for aws storage -gem "fog", "~> 1.36.0" gem "unf", '~> 0.1.4' # Authorization @@ -104,7 +111,7 @@ gem 'org-ruby', '~> 0.9.12' gem 'creole', '~> 0.5.0' gem 'wikicloth', '0.8.1' gem 'asciidoctor', '~> 1.5.2' -gem 'rouge', '~> 1.10.1' +gem 'rouge', '~> 1.11' # See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s # and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM @@ -137,7 +144,7 @@ gem 'redis-namespace' gem "httparty", '~> 0.13.3' # Colored output to console -gem "colorize", '~> 0.7.0' +gem "rainbow", '~> 2.1.0' # GitLab settings gem 'settingslogic', '~> 2.0.9' @@ -299,6 +306,9 @@ group :development, :test do gem 'bundler-audit', require: false gem 'benchmark-ips', require: false + + gem "license_finder", require: false + gem 'knapsack' end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 4e000fa5b5..28de59beec 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,6 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (2.3.2) RedCloth (4.2.9) ace-rails-ap (4.0.2) actionmailer (4.2.6) @@ -93,7 +92,7 @@ GEM sass (~> 3.0) slim (>= 1.3.6, < 4.0) terminal-table (~> 1.4) - browser (1.0.1) + browser (2.0.3) builder (3.2.2) bullet (5.0.0) activesupport (>= 3.0.0) @@ -183,7 +182,7 @@ GEM erubis (2.7.0) escape_utils (1.1.1) eventmachine (1.0.8) - excon (0.45.4) + excon (0.49.0) execjs (2.6.0) expression_parser (0.9.0) factory_girl (4.5.0) @@ -200,8 +199,6 @@ GEM multi_json ffaker (2.0.0) ffi (1.9.10) - fission (0.5.0) - CFPropertyList (~> 2.2) flay (2.6.1) ruby_parser (~> 3.0) sexp_processor (~> 4.0) @@ -211,109 +208,28 @@ GEM flowdock (0.7.1) httparty (~> 0.7) multi_json - fog (1.36.0) - fog-aliyun (>= 0.1.0) - fog-atmos - fog-aws (>= 0.6.0) - fog-brightbox (~> 0.4) - fog-core (~> 1.32) - fog-dynect (~> 0.0.2) - fog-ecloud (~> 0.1) - fog-google (<= 0.1.0) - fog-json - fog-local - fog-powerdns (>= 0.1.1) - fog-profitbricks - fog-radosgw (>= 0.0.2) - fog-riakcs - fog-sakuracloud (>= 0.0.4) - fog-serverlove - fog-softlayer - fog-storm_on_demand - fog-terremark - fog-vmfusion - fog-voxel - fog-xenserver - fog-xml (~> 0.1.1) - ipaddress (~> 0.5) - nokogiri (~> 1.5, >= 1.5.11) - fog-aliyun (0.1.0) - fog-core (~> 1.27) - fog-json (~> 1.0) - ipaddress (~> 0.8) - xml-simple (~> 1.1) - fog-atmos (0.1.0) - fog-core - fog-xml - fog-aws (0.8.1) + fog-aws (0.9.2) fog-core (~> 1.27) fog-json (~> 1.0) fog-xml (~> 0.1) ipaddress (~> 0.8) - fog-brightbox (0.10.1) - fog-core (~> 1.22) - fog-json - inflecto (~> 0.0.2) - fog-core (1.35.0) + fog-core (1.40.0) builder - excon (~> 0.45) + excon (~> 0.49) formatador (~> 0.2) - fog-dynect (0.0.2) - fog-core - fog-json - fog-xml - fog-ecloud (0.3.0) - fog-core - fog-xml - fog-google (0.1.0) + fog-google (0.3.2) fog-core fog-json fog-xml fog-json (1.0.2) fog-core (~> 1.0) multi_json (~> 1.10) - fog-local (0.2.1) + fog-local (0.3.0) fog-core (~> 1.27) - fog-powerdns (0.1.1) - fog-core (~> 1.27) - fog-json (~> 1.0) - fog-xml (~> 0.1) - fog-profitbricks (0.0.5) - fog-core - fog-xml - nokogiri - fog-radosgw (0.0.5) - fog-core (>= 1.21.0) - fog-json - fog-xml (>= 0.0.1) - fog-riakcs (0.1.0) - fog-core - fog-json - fog-xml - fog-sakuracloud (1.7.5) - fog-core - fog-json - fog-serverlove (0.1.2) - fog-core - fog-json - fog-softlayer (1.0.3) - fog-core - fog-json - fog-storm_on_demand (0.1.1) - fog-core - fog-json - fog-terremark (0.1.0) - fog-core - fog-xml - fog-vmfusion (0.1.0) - fission - fog-core - fog-voxel (0.1.0) - fog-core - fog-xml - fog-xenserver (0.2.2) - fog-core - fog-xml + fog-openstack (0.1.6) + fog-core (>= 1.39) + fog-json (>= 1.0) + ipaddress (>= 0.8) fog-xml (0.1.2) fog-core nokogiri (~> 1.5, >= 1.5.11) @@ -422,11 +338,10 @@ GEM httpclient (2.7.0.1) i18n (0.7.0) ice_nine (0.11.1) - inflecto (0.0.2) influxdb (0.2.3) cause json - ipaddress (0.8.2) + ipaddress (0.8.3) jquery-atwho-rails (1.3.2) jquery-rails (4.1.1) rails-dom-testing (>= 1, < 3) @@ -443,6 +358,9 @@ GEM actionpack (>= 3.0.0) activesupport (>= 3.0.0) kgio (2.10.0) + knapsack (1.11.0) + rake + timecop (>= 0.1.0) launchy (2.4.3) addressable (~> 2.3) letter_opener (1.4.1) @@ -451,6 +369,12 @@ GEM actionmailer (>= 3.2) letter_opener (~> 1.0) railties (>= 3.2) + license_finder (2.1.0) + bundler + httparty + rubyzip + thor + xml-simple licensee (8.0.0) rugged (>= 0.24b) listen (3.0.5) @@ -466,7 +390,7 @@ GEM method_source (0.8.2) mime-types (2.99.1) mimemagic (0.3.0) - mini_portile2 (2.0.0) + mini_portile2 (2.1.0) minitest (5.7.0) mousetrap-rails (1.4.6) multi_json (1.11.2) @@ -477,8 +401,9 @@ GEM net-ldap (0.12.1) net-ssh (3.0.1) newrelic_rpm (3.14.1.311) - nokogiri (1.6.7.2) - mini_portile2 (~> 2.0.0.rc2) + nokogiri (1.6.8) + mini_portile2 (~> 2.1.0) + pkg-config (~> 1.1.7) oauth (0.4.7) oauth2 (1.0.0) faraday (>= 0.8, < 0.10) @@ -550,6 +475,7 @@ GEM parser (2.3.1.0) ast (~> 2.2) pg (0.18.4) + pkg-config (1.1.7) poltergeist (1.9.0) capybara (~> 2.1) cliver (~> 0.3.1) @@ -625,7 +551,7 @@ GEM debugger-ruby_core_source (~> 1.3) rdoc (3.12.2) json (~> 1.4) - recaptcha (1.0.2) + recaptcha (3.0.0) json redcarpet (3.3.3) redis (3.3.0) @@ -654,7 +580,7 @@ GEM railties (>= 4.2.0, < 5.1) rinku (1.7.3) rotp (2.1.2) - rouge (1.10.1) + rouge (1.11.0) rqrcode (0.7.0) chunky_png rqrcode-rails3 (0.1.7) @@ -703,6 +629,7 @@ GEM sexp_processor (~> 4.1) rubyntlm (0.5.2) rubypants (0.2.0) + rubyzip (1.2.0) rufus-scheduler (3.1.10) rugged (0.24.0) safe_yaml (1.0.4) @@ -813,6 +740,7 @@ GEM thor (0.19.1) thread_safe (0.3.5) tilt (2.0.2) + timecop (0.8.1) timfel-krb5-auth (0.8.3) tinder (1.10.1) eventmachine (~> 1.0) @@ -832,6 +760,7 @@ GEM simple_oauth (~> 0.1.4) tzinfo (1.2.2) thread_safe (~> 0.1) + u2f (0.2.1) uglifier (2.7.2) execjs (>= 0.3.0) json (>= 1.8.0) @@ -900,7 +829,7 @@ DEPENDENCIES binding_of_caller (~> 0.7.2) bootstrap-sass (~> 3.3.0) brakeman (~> 3.2.0) - browser (~> 1.0.0) + browser (~> 2.0.3) bullet bundler-audit byebug @@ -909,7 +838,6 @@ DEPENDENCIES carrierwave (~> 0.10.0) charlock_holmes (~> 0.7.3) coffee-rails (~> 4.1.0) - colorize (~> 0.7.0) connection_pool (~> 2.0) coveralls (~> 0.8.2) creole (~> 0.5.0) @@ -927,7 +855,11 @@ DEPENDENCIES ffaker (~> 2.0.0) flay flog - fog (~> 1.36.0) + fog-aws (~> 0.9) + fog-core (~> 1.40) + fog-google (~> 0.3) + fog-local (~> 0.3) + fog-openstack (~> 0.1) font-awesome-rails (~> 4.2) foreman fuubar (~> 2.0.0) @@ -956,7 +888,9 @@ DEPENDENCIES jquery-ui-rails (~> 5.0.0) jwt kaminari (~> 0.17.0) + knapsack letter_opener_web (~> 1.3.0) + license_finder licensee (~> 8.0.0) loofah (~> 2.0.3) mail_room (~> 0.7) @@ -996,10 +930,11 @@ DEPENDENCIES rack-oauth2 (~> 1.2.1) rails (= 4.2.6) rails-deprecated_sanitizer (~> 1.0.3) + rainbow (~> 2.1.0) raphael-rails (~> 2.1.2) rblineprof rdoc (~> 3.6) - recaptcha + recaptcha (~> 3.0) redcarpet (~> 3.3.3) redis (~> 3.2) redis-namespace @@ -1007,7 +942,7 @@ DEPENDENCIES request_store (~> 1.3.0) rerun (~> 0.11.0) responders (~> 2.0) - rouge (~> 1.10.1) + rouge (~> 1.11) rqrcode-rails3 (~> 0.1.7) rspec-rails (~> 3.4.0) rspec-retry @@ -1045,6 +980,7 @@ DEPENDENCIES thin (~> 1.6.1) tinder (~> 1.10.0) turbolinks (~> 2.5.0) + u2f (~> 0.2.1) uglifier (~> 2.7.2) underscore-rails (~> 1.8.0) unf (~> 0.1.4) @@ -1057,4 +993,4 @@ DEPENDENCIES wikicloth (= 0.8.1) BUNDLED WITH - 1.12.4 + 1.12.5 diff --git a/Rakefile b/Rakefile index 5dd389d567..85fff2d51e 100755 --- a/Rakefile +++ b/Rakefile @@ -8,3 +8,5 @@ relative_url_conf = File.expand_path('../config/initializers/relative_url', __FI require relative_url_conf if File.exist?("#{relative_url_conf}.rb") Gitlab::Application.load_tasks + +Knapsack.load_tasks if defined?(Knapsack) diff --git a/app/assets/javascripts/LabelManager.js.coffee b/app/assets/javascripts/LabelManager.js.coffee new file mode 100644 index 0000000000..365a062bb8 --- /dev/null +++ b/app/assets/javascripts/LabelManager.js.coffee @@ -0,0 +1,84 @@ +class @LabelManager + errorMessage: 'Unable to update label prioritization at this time' + + constructor: (opts = {}) -> + # Defaults + { + @togglePriorityButton = $('.js-toggle-priority') + @prioritizedLabels = $('.js-prioritized-labels') + @otherLabels = $('.js-other-labels') + } = opts + + @prioritizedLabels.sortable( + items: 'li' + placeholder: 'list-placeholder' + axis: 'y' + update: @onPrioritySortUpdate.bind(@) + ) + + @bindEvents() + + bindEvents: -> + @togglePriorityButton.on 'click', @, @onTogglePriorityClick + + onTogglePriorityClick: (e) -> + e.preventDefault() + _this = e.data + $btn = $(e.currentTarget) + $label = $("##{$btn.data('domId')}") + action = if $btn.parents('.js-prioritized-labels').length then 'remove' else 'add' + _this.toggleLabelPriority($label, action) + + toggleLabelPriority: ($label, action, persistState = true) -> + _this = @ + url = $label.find('.js-toggle-priority').data 'url' + + $target = @prioritizedLabels + $from = @otherLabels + + # Optimistic update + if action is 'remove' + $target = @otherLabels + $from = @prioritizedLabels + + if $from.find('li').length is 1 + $from.find('.empty-message').show() + + if not $target.find('li').length + $target.find('.empty-message').hide() + + $label.detach().appendTo($target) + + # Return if we are not persisting state + return unless persistState + + if action is 'remove' + xhr = $.ajax url: url, type: 'DELETE' + else + xhr = @savePrioritySort($label, action) + + xhr.fail @rollbackLabelPosition.bind(@, $label, action) + + onPrioritySortUpdate: -> + xhr = @savePrioritySort() + + xhr.fail -> + new Flash(@errorMessage, 'alert') + + savePrioritySort: () -> + $.post + url: @prioritizedLabels.data('url') + data: + label_ids: @getSortedLabelsIds() + + rollbackLabelPosition: ($label, originalAction)-> + action = if originalAction is 'remove' then 'add' else 'remove' + @toggleLabelPriority($label, action, false) + + new Flash(@errorMessage, 'alert') + + getSortedLabelsIds: -> + sortedIds = [] + @prioritizedLabels.find('li').each -> + sortedIds.push $(@).data 'id' + sortedIds diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 18c1aa0d4e..ebf425550e 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -4,7 +4,7 @@ # It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the # the compiled file. # -#= require jquery +#= require jquery2 #= require jquery-ui/autocomplete #= require jquery-ui/datepicker #= require jquery-ui/draggable @@ -56,9 +56,11 @@ #= require_directory ./commit #= require_directory ./extensions #= require_directory ./lib +#= require_directory ./u2f #= require_directory . #= require fuzzaldrin-plus #= require cropper +#= require u2f window.slugify = (text) -> text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase() diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index bf95e06b4e..efa8f6cd01 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -1,201 +1,352 @@ class @AwardsHandler - constructor: (@getEmojisUrl, @postEmojiUrl, @noteableType, @noteableId, @unicodes) -> - $('.js-add-award').on 'click', (event) => - event.stopPropagation() - event.preventDefault() - @showEmojiMenu() + constructor: -> - $('html').on 'click', (event) -> - if !$(event.target).closest('.emoji-menu').length + @aliases = gl.emojiAliases() + + $(document) + .off 'click', '.js-add-award' + .on 'click', '.js-add-award', (e) => + e.stopPropagation() + e.preventDefault() + + @showEmojiMenu $(e.currentTarget) + + $('html').on 'click', (e) -> + $target = $ e.target + + unless $target.closest('.emoji-menu-content').length + $('.js-awards-block.current').removeClass 'current' + + unless $target.closest('.emoji-menu').length if $('.emoji-menu').is(':visible') + $('.js-add-award.is-active').removeClass 'is-active' $('.emoji-menu').removeClass 'is-visible' - $('.awards') - .off 'click' - .on 'click', '.js-emoji-btn', @handleClick + $(document) + .off 'click', '.js-emoji-btn' + .on 'click', '.js-emoji-btn', (e) => + e.preventDefault() - @renderFrequentlyUsedBlock() + $target = $ e.currentTarget + emoji = $target.find('.icon').data 'emoji' - handleClick: (e) -> - e.preventDefault() - emoji = $(this) - .find('.icon') - .data 'emoji' + $target.closest('.js-awards-block').addClass 'current' + @addAward @getVotesBlock(), @getAwardUrl(), 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' + showEmojiMenu: ($addBtn) -> - awardsHandler.addAward emoji + $menu = $ '.emoji-menu' - $(this).trigger 'blur' + if $addBtn.hasClass 'js-note-emoji' + $addBtn.parents('.note').find('.js-awards-block').addClass 'current' + else + $addBtn.closest('.js-awards-block').addClass 'current' - 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 + if $menu.length + $holder = $addBtn.closest('.js-award-holder') - showEmojiMenu: -> - if $('.emoji-menu').length - if $('.emoji-menu').is '.is-visible' - $('.emoji-menu').removeClass 'is-visible' + if $menu.is '.is-visible' + $addBtn.removeClass 'is-active' + $menu.removeClass 'is-visible' $('#emoji_search').blur() else - $('.emoji-menu').addClass 'is-visible' + $addBtn.addClass 'is-active' + @positionMenu($menu, $addBtn) + + $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 + $addBtn.addClass 'is-loading is-active' + url = @getAwardMenuUrl() + + @createEmojiMenu url, => + $addBtn.removeClass 'is-loading' + $menu = $('.emoji-menu') + @positionMenu($menu, $addBtn) + @renderFrequentlyUsedBlock() + setTimeout => - $('.emoji-menu').addClass 'is-visible' + $menu.addClass 'is-visible' $('#emoji_search').focus() @setupSearch() , 200 - addAward: (emoji) -> - @postEmoji emoji, => - @addAwardToEmojiBar(emoji) + + createEmojiMenu: (awardMenuUrl, callback) -> + + $.get awardMenuUrl, (response) -> + $('body').append response + callback() + + + positionMenu: ($menu, $addBtn) -> + + position = $addBtn.data('position') + + # The menu could potentially be off-screen or in a hidden overflow element + # So we position the element absolute in the body + css = + top: "#{$addBtn.offset().top + $addBtn.outerHeight()}px" + + if position? and position is 'right' + css.left = "#{($addBtn.offset().left - $menu.outerWidth()) + 20}px" + $menu.addClass 'is-aligned-right' + else + css.left = "#{$addBtn.offset().left}px" + $menu.removeClass 'is-aligned-right' + + $menu.css(css) + + + addAward: (votesBlock, awardUrl, emoji, checkMutuality = yes, callback) -> + + emoji = @normilizeEmojiName emoji + + @postEmoji awardUrl, emoji, => + @addAwardToEmojiBar votesBlock, emoji, checkMutuality + callback?() $('.emoji-menu').removeClass 'is-visible' - addAwardToEmojiBar: (emoji) -> - @addEmojiToFrequentlyUsedList(emoji) - if @exist(emoji) - if @isActive(emoji) - @decrementCounter(emoji) + addAwardToEmojiBar: (votesBlock, emoji, checkForMutuality = yes) -> + + @checkMutuality votesBlock, emoji if checkForMutuality + @addEmojiToFrequentlyUsedList emoji + + emoji = @normilizeEmojiName emoji + $emojiButton = @findEmojiIcon(votesBlock, emoji).parent() + + if $emojiButton.length > 0 + if @isActive $emojiButton + @decrementCounter $emojiButton, emoji else - counter = @findEmojiIcon(emoji).siblings('.js-counter') - counter.text(parseInt(counter.text()) + 1) - counter.parent().addClass('active') - @addMeToAuthorList(emoji) + counter = $emojiButton.find '.js-counter' + counter.text parseInt(counter.text()) + 1 + $emojiButton.addClass 'active' + @addMeToUserList votesBlock, emoji + @animateEmoji $emojiButton else - @createEmoji(emoji) + votesBlock.removeClass 'hidden' + @createEmoji votesBlock, emoji - exist: (emoji) -> - @findEmojiIcon(emoji).length > 0 - isActive: (emoji) -> - @findEmojiIcon(emoji).parent().hasClass('active') + getVotesBlock: -> - decrementCounter: (emoji) -> - counter = @findEmojiIcon(emoji).siblings('.js-counter') - emojiIcon = counter.parent() - if parseInt(counter.text()) > 1 - counter.text(parseInt(counter.text()) - 1) - emojiIcon.removeClass('active') - @removeMeFromAuthorList(emoji) - else if emoji == 'thumbsup' || emoji == 'thumbsdown' - emojiIcon.tooltip('destroy') - counter.text(0) - emojiIcon.removeClass('active') - @removeMeFromAuthorList(emoji) + currentBlock = $ '.js-awards-block.current' + return if currentBlock.length then currentBlock else $('.js-awards-block').eq 0 + + + getAwardUrl: -> return @getVotesBlock().data 'award-url' + + + checkMutuality: (votesBlock, emoji) -> + + awardUrl = @getAwardUrl() + + if emoji in [ 'thumbsup', 'thumbsdown' ] + mutualVote = if emoji is 'thumbsup' then 'thumbsdown' else 'thumbsup' + $emojiButton = votesBlock.find("[data-emoji=#{mutualVote}]").parent() + isAlreadyVoted = $emojiButton.hasClass 'active' + + if isAlreadyVoted + @showEmojiLoader $emojiButton + @addAward votesBlock, awardUrl, mutualVote, no, -> + $emojiButton.removeClass 'is-loading' + + + showEmojiLoader: ($emojiButton) -> + + $loader = $emojiButton.find '.fa-spinner' + + unless $loader.length + $emojiButton.append '' + + $emojiButton.addClass 'is-loading' + + + isActive: ($emojiButton) -> $emojiButton.hasClass 'active' + + + decrementCounter: ($emojiButton, emoji) -> + + counter = $ '.js-counter', $emojiButton + counterNumber = parseInt counter.text(), 10 + + if counterNumber > 1 + counter.text counterNumber - 1 + @removeMeFromUserList $emojiButton, emoji + else if emoji is 'thumbsup' or emoji is 'thumbsdown' + $emojiButton.tooltip 'destroy' + counter.text '0' + @removeMeFromUserList $emojiButton, emoji + @removeEmoji $emojiButton if $emojiButton.parents('.note').length else - emojiIcon.tooltip('destroy') - emojiIcon.remove() + @removeEmoji $emojiButton + + $emojiButton.removeClass 'active' + + + removeEmoji: ($emojiButton) -> + + $emojiButton.tooltip('destroy') + $emojiButton.remove() + + $votesBlock = @getVotesBlock() + + if $votesBlock.find('.js-emoji-btn').length is 0 + $votesBlock.addClass 'hidden' + + + getAwardTooltip: ($awardBlock) -> + + return $awardBlock.attr('data-original-title') or $awardBlock.attr('data-title') or '' + + + removeMeFromUserList: ($emojiButton, emoji) -> + + awardBlock = $emojiButton + originalTitle = @getAwardTooltip awardBlock + + authors = originalTitle.split ', ' + authors.splice authors.indexOf('me'), 1 + + newAuthors = authors.join ', ' - removeMeFromAuthorList: (emoji) -> - 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) + .closest '.js-emoji-btn' + .removeData 'original-title' + .attr 'data-original-title', newAuthors + + @resetTooltip awardBlock + + + addMeToUserList: (votesBlock, emoji) -> + + awardBlock = @findEmojiIcon(votesBlock, emoji).parent() + origTitle = @getAwardTooltip awardBlock + users = [] - addMeToAuthorList: (emoji) -> - awardBlock = @findEmojiIcon(emoji).parent() - origTitle = awardBlock.attr('data-original-title').trim() - authors = [] if origTitle - authors = origTitle.split(', ') - authors.push('me') - awardBlock.attr('data-original-title', authors.join(', ')) - @resetTooltip(awardBlock) + users = origTitle.trim().split ', ' + + users.push 'me' + awardBlock.attr 'title', users.join ', ' + + @resetTooltip awardBlock + resetTooltip: (award) -> - award.tooltip('destroy') - # "destroy" call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout. - setTimeout (-> - award.tooltip() - ), 200 + award.tooltip 'destroy' + + # 'destroy' call is asynchronous and there is no appropriate callback on it, this is why we need to set timeout. + cb = -> award.tooltip() + setTimeout cb, 200 - createEmoji: (emoji) -> - emojiCssClass = @resolveNameToCssClass(emoji) + createEmoji_: (votesBlock, emoji) -> - nodes = [] - nodes.push( - "" - ) + emojiCssClass = @resolveNameToCssClass emoji + buttonHtml = "" - $(nodes.join("\n")) - .insertBefore('.js-award-holder') - .find('.emoji-icon') - .data('emoji', emoji) + $emojiButton = $ buttonHtml + $emojiButton + .insertBefore votesBlock.find '.js-award-holder' + .find '.emoji-icon' + .data 'emoji', emoji + + @animateEmoji $emojiButton $('.award-control').tooltip() + votesBlock.removeClass 'current' + + + animateEmoji: ($emoji) -> + + className = 'pulse animated' + + $emoji.addClass className + setTimeout (-> $emoji.removeClass className), 321 + + + createEmoji: (votesBlock, emoji) -> + + if $('.emoji-menu').length + return @createEmoji_ votesBlock, emoji + + @createEmojiMenu @getAwardMenuUrl(), => @createEmoji_ votesBlock, emoji + + + getAwardMenuUrl: -> return gl.awardMenuUrl + resolveNameToCssClass: (emoji) -> - emojiIcon = $(".emoji-menu-content [data-emoji='#{emoji}']") + + emojiIcon = $ ".emoji-menu-content [data-emoji='#{emoji}']" if emojiIcon.length > 0 - unicodeName = emojiIcon.data('unicode-name') + 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}" + return "emoji-#{unicodeName}" - postEmoji: (emoji, callback) -> - $.post @postEmojiUrl, { note: { - note: ":#{emoji}:" - noteable_type: @noteableType - noteable_id: @noteableId - }},(data) -> - if data.ok - callback.call() - findEmojiIcon: (emoji) -> - $(".awards > .js-emoji-btn [data-emoji='#{emoji}']") + postEmoji: (awardUrl, emoji, callback) -> + + $.post awardUrl, { name: emoji }, (data) -> + callback() if data.ok + + + findEmojiIcon: (votesBlock, emoji) -> + + return votesBlock.find ".js-emoji-btn [data-emoji='#{emoji}']" + scrollToAwards: -> - $('body, html').animate({ - scrollTop: $('.awards').offset().top - 80 - }, 200) + + options = scrollTop: $('.awards').offset().top - 110 + $('body, html').animate options, 200 + + + normilizeEmojiName: (emoji) -> return @aliases[emoji] or emoji + addEmojiToFrequentlyUsedList: (emoji) -> + frequentlyUsedEmojis = @getFrequentlyUsedEmojis() - frequentlyUsedEmojis.push(emoji) - $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 }) + frequentlyUsedEmojis.push emoji + $.cookie 'frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 } + getFrequentlyUsedEmojis: -> - frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') || '').split(',') - _.compact(_.uniq(frequentlyUsedEmojis)) + + frequentlyUsedEmojis = ($.cookie('frequently_used_emojis') or '').split(',') + return _.compact _.uniq frequentlyUsedEmojis + renderFrequentlyUsedBlock: -> - if $.cookie('frequently_used_emojis') + + if $.cookie 'frequently_used_emojis' frequentlyUsedEmojis = @getFrequentlyUsedEmojis() - ul = $('