Commit b988faaf authored by James Edwards-Jones's avatar James Edwards-Jones

Merge branch 'master' into 'jej-pages-to-ce'

# Conflicts:
#   db/schema.rb
parents 5af4cae5 53db7d1d
/builds/
/coverage/ /coverage/
/coverage-javascript/ /coverage-javascript/
/node_modules/ /node_modules/
/public/ /public/
/tmp/ /tmp/
/vendor/ /vendor/
/builds/ karma.config.js
webpack.config.js
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
], ],
"rules": { "rules": {
"filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"], "filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"],
"no-multiple-empty-lines": ["error", { "max": 1 }] "no-multiple-empty-lines": ["error", { "max": 1 }],
"import/no-extraneous-dependencies": "off",
"import/no-unresolved": "off"
} }
} }
...@@ -107,11 +107,13 @@ setup-test-env: ...@@ -107,11 +107,13 @@ setup-test-env:
<<: *dedicated-runner <<: *dedicated-runner
stage: prepare stage: prepare
script: script:
- bundle exec rake gitlab:assets:compile 2>/dev/null - npm install
- bundle exec rake gitlab:assets:compile
- bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init' - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init'
artifacts: artifacts:
expire_in: 7d expire_in: 7d
paths: paths:
- node_modules
- public/assets - public/assets
- tmp/tests - tmp/tests
...@@ -232,7 +234,7 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21 ...@@ -232,7 +234,7 @@ spinach 9 10 ruby21: *spinach-knapsack-ruby21
script: script:
- bundle exec $CI_BUILD_NAME - bundle exec $CI_BUILD_NAME
rubocop: rubocop:
<<: *ruby-static-analysis <<: *ruby-static-analysis
<<: *dedicated-runner <<: *dedicated-runner
stage: test stage: test
...@@ -291,18 +293,17 @@ rake db:seed_fu: ...@@ -291,18 +293,17 @@ rake db:seed_fu:
paths: paths:
- log/development.log - log/development.log
teaspoon: karma:
cache: cache:
paths: paths:
- vendor/ruby - vendor/ruby
- node_modules/ - node_modules
stage: test stage: test
<<: *use-db <<: *use-db
<<: *dedicated-runner <<: *dedicated-runner
script: script:
- npm install
- npm link istanbul - npm link istanbul
- bundle exec rake teaspoon - bundle exec rake karma
artifacts: artifacts:
name: coverage-javascript name: coverage-javascript
expire_in: 31d expire_in: 31d
...@@ -444,7 +445,7 @@ pages: ...@@ -444,7 +445,7 @@ pages:
<<: *dedicated-runner <<: *dedicated-runner
dependencies: dependencies:
- coverage - coverage
- teaspoon - karma
- lint:javascript:report - lint:javascript:report
script: script:
- mv public/ .public/ - mv public/ .public/
......
...@@ -88,6 +88,27 @@ contributing to GitLab. ...@@ -88,6 +88,27 @@ contributing to GitLab.
Please see the [UX Guide for GitLab]. Please see the [UX Guide for GitLab].
## Release retrospective and kickoff
### Retrospective
After each release (usually on the 22nd of each month), we have a retrospective
call where we discuss what went well, what went wrong, and what we can improve
for the next release. The [retrospective notes] are public and you are invited
to comment them.
If you're interested, you can even join the [retrospective call][retro-kickoff-call].
### Kickoff
Before working on the next release (usually on the 8th of each month), we have a
kickoff call to explain what we expect to ship in the next release. The
[kickoff notes] are public and you are invited to comment them.
If you're interested, you can even join the [kickoff call][retro-kickoff-call].
[retrospective notes]: https://docs.google.com/document/d/1nEkM_7Dj4bT21GJy0Ut3By76FZqCfLBmFQNVThmW2TY/edit?usp=sharing
[kickoff notes]: https://docs.google.com/document/d/1ElPkZ90A8ey_iOkTvUs_ByMlwKK6NAB2VOK5835wYK0/edit?usp=sharing
[retro-kickoff-call]: https://gitlab.zoom.us/j/918821206
## Issue tracker ## Issue tracker
To get support for your particular problem please use the To get support for your particular problem please use the
......
...@@ -7,7 +7,6 @@ gem 'rails-deprecated_sanitizer', '~> 1.0.3' ...@@ -7,7 +7,6 @@ gem 'rails-deprecated_sanitizer', '~> 1.0.3'
gem 'responders', '~> 2.0' gem 'responders', '~> 2.0'
gem 'sprockets', '~> 3.7.0' gem 'sprockets', '~> 3.7.0'
gem 'sprockets-es6', '~> 0.9.2'
# Default values for AR models # Default values for AR models
gem 'default_value_for', '~> 3.0.0' gem 'default_value_for', '~> 3.0.0'
...@@ -222,10 +221,12 @@ gem 'oj', '~> 2.17.4' ...@@ -222,10 +221,12 @@ gem 'oj', '~> 2.17.4'
gem 'chronic', '~> 0.10.2' gem 'chronic', '~> 0.10.2'
gem 'chronic_duration', '~> 0.10.6' gem 'chronic_duration', '~> 0.10.6'
gem 'webpack-rails', '~> 0.9.9'
gem 'rack-proxy', '~> 0.6.0'
gem 'sass-rails', '~> 5.0.6' gem 'sass-rails', '~> 5.0.6'
gem 'coffee-rails', '~> 4.1.0' gem 'coffee-rails', '~> 4.1.0'
gem 'uglifier', '~> 2.7.2' gem 'uglifier', '~> 2.7.2'
gem 'gitlab-turbolinks-classic', '~> 2.5', '>= 2.5.6'
gem 'addressable', '~> 2.3.8' gem 'addressable', '~> 2.3.8'
gem 'bootstrap-sass', '~> 3.3.0' gem 'bootstrap-sass', '~> 3.3.0'
...@@ -295,13 +296,9 @@ group :development, :test do ...@@ -295,13 +296,9 @@ group :development, :test do
gem 'capybara-screenshot', '~> 1.0.0' gem 'capybara-screenshot', '~> 1.0.0'
gem 'poltergeist', '~> 1.9.0' gem 'poltergeist', '~> 1.9.0'
gem 'teaspoon', '~> 1.1.0'
gem 'teaspoon-jasmine', '~> 2.2.0'
gem 'spring', '~> 1.7.0' gem 'spring', '~> 1.7.0'
gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-rspec', '~> 1.0.4'
gem 'spring-commands-spinach', '~> 1.1.0' gem 'spring-commands-spinach', '~> 1.1.0'
gem 'spring-commands-teaspoon', '~> 0.0.2'
gem 'rubocop', '~> 0.46.0', require: false gem 'rubocop', '~> 0.46.0', require: false
gem 'rubocop-rspec', '~> 1.9.1', require: false gem 'rubocop-rspec', '~> 1.9.1', require: false
......
...@@ -72,10 +72,6 @@ GEM ...@@ -72,10 +72,6 @@ GEM
descendants_tracker (~> 0.0.4) descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0) ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
babel-source (5.8.35)
babel-transpiler (0.7.0)
babel-source (>= 4.0, < 6)
execjs (~> 2.0)
babosa (1.0.2) babosa (1.0.2)
base32 (0.3.2) base32 (0.3.2)
bcrypt (3.1.11) bcrypt (3.1.11)
...@@ -266,8 +262,6 @@ GEM ...@@ -266,8 +262,6 @@ GEM
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab-markup (1.5.1) gitlab-markup (1.5.1)
gitlab-turbolinks-classic (2.5.6)
coffee-rails
gitlab_omniauth-ldap (1.2.1) gitlab_omniauth-ldap (1.2.1)
net-ldap (~> 0.9) net-ldap (~> 0.9)
omniauth (~> 1.0) omniauth (~> 1.0)
...@@ -548,6 +542,8 @@ GEM ...@@ -548,6 +542,8 @@ GEM
rack (>= 1.1) rack (>= 1.1)
rack-protection (1.5.3) rack-protection (1.5.3)
rack rack
rack-proxy (0.6.0)
rack
rack-test (0.6.3) rack-test (0.6.3)
rack (>= 1.0) rack (>= 1.0)
rails (4.2.7.1) rails (4.2.7.1)
...@@ -735,15 +731,9 @@ GEM ...@@ -735,15 +731,9 @@ GEM
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-spinach (1.1.0) spring-commands-spinach (1.1.0)
spring (>= 0.9.1) spring (>= 0.9.1)
spring-commands-teaspoon (0.0.2)
spring (>= 0.9.1)
sprockets (3.7.0) sprockets (3.7.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
rack (> 1, < 3) rack (> 1, < 3)
sprockets-es6 (0.9.2)
babel-source (>= 5.8.11)
babel-transpiler
sprockets (>= 3.0.0)
sprockets-rails (3.1.1) sprockets-rails (3.1.1)
actionpack (>= 4.0) actionpack (>= 4.0)
activesupport (>= 4.0) activesupport (>= 4.0)
...@@ -761,10 +751,6 @@ GEM ...@@ -761,10 +751,6 @@ GEM
sys-filesystem (1.1.6) sys-filesystem (1.1.6)
ffi ffi
sysexits (1.2.0) sysexits (1.2.0)
teaspoon (1.1.5)
railties (>= 3.2.5, < 6)
teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0)
temple (0.7.7) temple (0.7.7)
test_after_commit (1.1.0) test_after_commit (1.1.0)
activerecord (>= 3.2) activerecord (>= 3.2)
...@@ -819,6 +805,8 @@ GEM ...@@ -819,6 +805,8 @@ GEM
webmock (1.21.0) webmock (1.21.0)
addressable (>= 2.3.6) addressable (>= 2.3.6)
crack (>= 0.3.2) crack (>= 0.3.2)
webpack-rails (0.9.9)
rails (>= 3.2.0)
websocket-driver (0.6.3) websocket-driver (0.6.3)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2) websocket-extensions (0.1.2)
...@@ -894,7 +882,6 @@ DEPENDENCIES ...@@ -894,7 +882,6 @@ DEPENDENCIES
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.5.1) gitlab-markup (~> 1.5.1)
gitlab-turbolinks-classic (~> 2.5, >= 2.5.6)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.2) gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.2) gollum-rugged_adapter (~> 0.4.2)
...@@ -958,6 +945,7 @@ DEPENDENCIES ...@@ -958,6 +945,7 @@ DEPENDENCIES
rack-attack (~> 4.4.1) rack-attack (~> 4.4.1)
rack-cors (~> 0.4.0) rack-cors (~> 0.4.0)
rack-oauth2 (~> 1.2.1) rack-oauth2 (~> 1.2.1)
rack-proxy (~> 0.6.0)
rails (= 4.2.7.1) rails (= 4.2.7.1)
rails-deprecated_sanitizer (~> 1.0.3) rails-deprecated_sanitizer (~> 1.0.3)
rainbow (~> 2.1.0) rainbow (~> 2.1.0)
...@@ -999,14 +987,10 @@ DEPENDENCIES ...@@ -999,14 +987,10 @@ DEPENDENCIES
spring (~> 1.7.0) spring (~> 1.7.0)
spring-commands-rspec (~> 1.0.4) spring-commands-rspec (~> 1.0.4)
spring-commands-spinach (~> 1.1.0) spring-commands-spinach (~> 1.1.0)
spring-commands-teaspoon (~> 0.0.2)
sprockets (~> 3.7.0) sprockets (~> 3.7.0)
sprockets-es6 (~> 0.9.2)
stackprof (~> 0.2.10) stackprof (~> 0.2.10)
state_machines-activerecord (~> 0.4.0) state_machines-activerecord (~> 0.4.0)
sys-filesystem (~> 1.1.6) sys-filesystem (~> 1.1.6)
teaspoon (~> 1.1.0)
teaspoon-jasmine (~> 2.2.0)
test_after_commit (~> 1.1) test_after_commit (~> 1.1)
thin (~> 1.7.0) thin (~> 1.7.0)
timecop (~> 0.8.0) timecop (~> 0.8.0)
...@@ -1023,6 +1007,7 @@ DEPENDENCIES ...@@ -1023,6 +1007,7 @@ DEPENDENCIES
vmstat (~> 2.3.0) vmstat (~> 2.3.0)
web-console (~> 2.0) web-console (~> 2.0)
webmock (~> 1.21.0) webmock (~> 1.21.0)
webpack-rails (~> 0.9.9)
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-arrow-callback, camelcase, quotes, comma-dangle, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-arrow-callback, camelcase, quotes, comma-dangle, max-len */
/* global Turbolinks */
(function() { (function() {
this.Admin = (function() { this.Admin = (function() {
...@@ -42,10 +41,10 @@ ...@@ -42,10 +41,10 @@
return $('.change-owner-link').show(); return $('.change-owner-link').show();
}); });
$('li.project_member').bind('ajax:success', function() { $('li.project_member').bind('ajax:success', function() {
return Turbolinks.visit(location.href); return gl.utils.refreshCurrentPage();
}); });
$('li.group_member').bind('ajax:success', function() { $('li.group_member').bind('ajax:success', function() {
return Turbolinks.visit(location.href); return gl.utils.refreshCurrentPage();
}); });
showBlacklistType = function() { showBlacklistType = function() {
if ($("input[name='blacklist_type']:checked").val() === 'file') { if ($("input[name='blacklist_type']:checked").val() === 'file') {
......
/* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len, no-multi-spaces, import/newline-after-import */
/* global bp */ /* global bp */
/* global Cookies */ /* global Cookies */
/* global Flash */ /* global Flash */
...@@ -6,65 +6,60 @@ ...@@ -6,65 +6,60 @@
/* global AwardsHandler */ /* global AwardsHandler */
/* global Aside */ /* global Aside */
// This is a manifest file that'll be compiled into including all the files listed below. function requireAll(context) { return context.keys().map(context); }
// Add new JavaScript code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/assets/application.js window.$ = window.jQuery = require('jquery');
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the require('jquery-ui/ui/autocomplete');
// the compiled file. require('jquery-ui/ui/datepicker');
// require('jquery-ui/ui/draggable');
/*= require jquery2 */ require('jquery-ui/ui/effect-highlight');
/*= require jquery-ui/autocomplete */ require('jquery-ui/ui/sortable');
/*= require jquery-ui/datepicker */ require('jquery-ujs');
/*= require jquery-ui/draggable */ require('vendor/jquery.endless-scroll');
/*= require jquery-ui/effect-highlight */ require('vendor/jquery.highlight');
/*= require jquery-ui/sortable */ require('vendor/jquery.waitforimages');
/*= require jquery_ujs */ require('vendor/jquery.caret');
/*= require jquery.endless-scroll */ require('vendor/jquery.atwho');
/*= require jquery.highlight */ require('vendor/jquery.scrollTo');
/*= require jquery.waitforimages */ window.Cookies = require('vendor/js.cookie');
/*= require jquery.atwho */ require('./autosave');
/*= require jquery.scrollTo */ require('bootstrap/js/affix');
/*= require jquery.turbolinks */ require('bootstrap/js/alert');
/*= require js.cookie */ require('bootstrap/js/button');
/*= require turbolinks */ require('bootstrap/js/collapse');
/*= require autosave */ require('bootstrap/js/dropdown');
/*= require bootstrap/affix */ require('bootstrap/js/modal');
/*= require bootstrap/alert */ require('bootstrap/js/scrollspy');
/*= require bootstrap/button */ require('bootstrap/js/tab');
/*= require bootstrap/collapse */ require('bootstrap/js/transition');
/*= require bootstrap/dropdown */ require('bootstrap/js/tooltip');
/*= require bootstrap/modal */ require('bootstrap/js/popover');
/*= require bootstrap/scrollspy */ require('select2/select2.js');
/*= require bootstrap/tab */ window._ = require('underscore');
/*= require bootstrap/transition */ window.Dropzone = require('dropzone');
/*= require bootstrap/tooltip */ require('mousetrap');
/*= require bootstrap/popover */ require('mousetrap/plugins/pause/mousetrap-pause');
/*= require select2 */ require('./shortcuts');
/*= require underscore */ require('./shortcuts_navigation');
/*= require dropzone */ require('./shortcuts_dashboard_navigation');
/*= require mousetrap */ require('./shortcuts_issuable');
/*= require mousetrap/pause */ require('./shortcuts_network');
/*= require shortcuts */ require('vendor/jquery.nicescroll');
/*= require shortcuts_navigation */ requireAll(require.context('./behaviors', false, /^\.\/.*\.(js|es6)$/));
/*= require shortcuts_dashboard_navigation */ requireAll(require.context('./blob', false, /^\.\/.*\.(js|es6)$/));
/*= require shortcuts_issuable */ requireAll(require.context('./templates', false, /^\.\/.*\.(js|es6)$/));
/*= require shortcuts_network */ requireAll(require.context('./commit', false, /^\.\/.*\.(js|es6)$/));
/*= require jquery.nicescroll */ requireAll(require.context('./extensions', false, /^\.\/.*\.(js|es6)$/));
/*= require date.format */ requireAll(require.context('./lib/utils', false, /^\.\/.*\.(js|es6)$/));
/*= require_directory ./behaviors */ requireAll(require.context('./u2f', false, /^\.\/.*\.(js|es6)$/));
/*= require_directory ./blob */ requireAll(require.context('./droplab', false, /^\.\/.*\.(js|es6)$/));
/*= require_directory ./templates */ requireAll(require.context('.', false, /^\.\/(?!application\.js).*\.(js|es6)$/));
/*= require_directory ./commit */ require('vendor/fuzzaldrin-plus');
/*= require_directory ./extensions */ window.ES6Promise = require('vendor/es6-promise.auto');
/*= require_directory ./lib/utils */ window.ES6Promise.polyfill();
/*= require_directory ./u2f */
/*= require_directory ./droplab */
/*= require_directory . */
/*= require fuzzaldrin-plus */
/*= require es6-promise.auto */
(function () { (function () {
document.addEventListener('page:fetch', function () { document.addEventListener('beforeunload', function () {
// Unbind scroll events // Unbind scroll events
$(document).off('scroll'); $(document).off('scroll');
// Close any open tooltips // Close any open tooltips
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, no-var, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-template, quotes, comma-dangle, no-param-reassign, no-void, brace-style, no-underscore-dangle, no-return-assign, camelcase */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, no-var, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, no-unused-vars, no-else-return, prefer-template, quotes, comma-dangle, no-param-reassign, no-void, brace-style, no-underscore-dangle, no-return-assign, camelcase */
/* global Cookies */ /* global Cookies */
var emojiAliases = require('emoji-aliases');
(function() { (function() {
this.AwardsHandler = (function() { this.AwardsHandler = (function() {
var FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence var FROM_SENTENCE_REGEX = /(?:, and | and |, )/; // For separating lists produced by ruby's Array#toSentence
function AwardsHandler() { function AwardsHandler() {
this.aliases = gl.emojiAliases(); this.aliases = emojiAliases;
$(document).off('click', '.js-add-award').on('click', '.js-add-award', (function(_this) { $(document).off('click', '.js-add-award').on('click', '.js-add-award', (function(_this) {
return function(e) { return function(e) {
e.stopPropagation(); e.stopPropagation();
......
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, consistent-return, max-len */ /* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, consistent-return, max-len */
/* global autosize */ /* global autosize */
/*= require autosize */ var autosize = require('vendor/autosize');
(function() { (function() {
$(function() { $(function() {
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
// "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form // "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form
// is submitted. // is submitted.
// //
/*= require extensions/jquery */ require('../extensions/jquery');
// //
// ### Example Markup // ### Example Markup
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
// When called on a form with input fields with the `required` attribute, the // When called on a form with input fields with the `required` attribute, the
// form's submit button will be disabled until all required fields have values. // form's submit button will be disabled until all required fields have values.
// //
/*= require extensions/jquery */ require('../extensions/jquery');
// //
// ### Example Markup // ### Example Markup
......
/* eslint-disable no-param-reassign, comma-dangle */ /* eslint-disable no-param-reassign, comma-dangle */
/* global Api */ /* global Api */
/*= require blob/template_selector */ require('./template_selector');
((global) => { ((global) => {
class BlobCiYamlSelector extends gl.TemplateSelector { class BlobCiYamlSelector extends gl.TemplateSelector {
requestFile(query) { requestFile(query) {
......
/* global Api */ /* global Api */
/*= require blob/template_selector */
require('./template_selector');
(() => { (() => {
const global = window.gl || (window.gl = {}); const global = window.gl || (window.gl = {});
......
/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params */ /* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params */
/* global Api */ /* global Api */
/*= require blob/template_selector */ require('./template_selector');
(function() { (function() {
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
......
/* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params, comma-dangle */ /* eslint-disable func-names, space-before-function-paren, max-len, one-var, no-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-rest-params, comma-dangle */
/* global Api */ /* global Api */
/*= require blob/template_selector */ require('./template_selector');
(function() { (function() {
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
/* global EditBlob */ /* global EditBlob */
/* global NewCommitForm */ /* global NewCommitForm */
/*= require_tree . */ require('./edit_blob');
(function() { (function() {
$(function() { $(function() {
......
/* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren */ /* eslint-disable one-var, quote-props, comma-dangle, space-before-function-paren, import/newline-after-import, no-multi-spaces, max-len */
/* global Vue */ /* global Vue */
/* global BoardService */ /* global BoardService */
//= require vue function requireAll(context) { return context.keys().map(context); }
//= require vue-resource
//= require Sortable window.Vue = require('vue');
//= require_tree ./models window.Vue.use(require('vue-resource'));
//= require_tree ./stores window.Sortable = require('vendor/Sortable');
//= require_tree ./services requireAll(require.context('./models', true, /^\.\/.*\.(js|es6)$/));
//= require_tree ./mixins requireAll(require.context('./stores', true, /^\.\/.*\.(js|es6)$/));
//= require_tree ./filters requireAll(require.context('./services', true, /^\.\/.*\.(js|es6)$/));
//= require ./components/board requireAll(require.context('./mixins', true, /^\.\/.*\.(js|es6)$/));
//= require ./components/board_sidebar requireAll(require.context('./filters', true, /^\.\/.*\.(js|es6)$/));
//= require ./components/new_list_dropdown require('./components/board');
//= require ./vue_resource_interceptor require('./components/board_sidebar');
require('./components/new_list_dropdown');
require('./components/modal/index');
require('./vue_resource_interceptor');
$(() => { $(() => {
const $boardApp = document.getElementById('board-app'); const $boardApp = document.getElementById('board-app');
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
const ModalStore = gl.issueBoards.ModalStore;
window.gl = window.gl || {}; window.gl = window.gl || {};
...@@ -31,7 +35,8 @@ $(() => { ...@@ -31,7 +35,8 @@ $(() => {
el: $boardApp, el: $boardApp,
components: { components: {
'board': gl.issueBoards.Board, 'board': gl.issueBoards.Board,
'board-sidebar': gl.issueBoards.BoardSidebar 'board-sidebar': gl.issueBoards.BoardSidebar,
'board-add-issues-modal': gl.issueBoards.IssuesModal,
}, },
data: { data: {
state: Store.state, state: Store.state,
...@@ -40,6 +45,8 @@ $(() => { ...@@ -40,6 +45,8 @@ $(() => {
boardId: $boardApp.dataset.boardId, boardId: $boardApp.dataset.boardId,
disabled: $boardApp.dataset.disabled === 'true', disabled: $boardApp.dataset.disabled === 'true',
issueLinkBase: $boardApp.dataset.issueLinkBase, issueLinkBase: $boardApp.dataset.issueLinkBase,
rootPath: $boardApp.dataset.rootPath,
bulkUpdatePath: $boardApp.dataset.bulkUpdatePath,
detailIssue: Store.detail detailIssue: Store.detail
}, },
computed: { computed: {
...@@ -48,7 +55,7 @@ $(() => { ...@@ -48,7 +55,7 @@ $(() => {
}, },
}, },
created () { created () {
gl.boardService = new BoardService(this.endpoint, this.boardId); gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId);
}, },
mounted () { mounted () {
Store.disabled = this.disabled; Store.disabled = this.disabled;
...@@ -59,8 +66,6 @@ $(() => { ...@@ -59,8 +66,6 @@ $(() => {
if (list.type === 'done') { if (list.type === 'done') {
list.position = Infinity; list.position = Infinity;
} else if (list.type === 'backlog') {
list.position = -1;
} }
}); });
...@@ -73,7 +78,7 @@ $(() => { ...@@ -73,7 +78,7 @@ $(() => {
}); });
gl.IssueBoardsSearch = new Vue({ gl.IssueBoardsSearch = new Vue({
el: '#js-boards-search', el: document.getElementById('js-boards-search'),
data: { data: {
filters: Store.state.filters filters: Store.state.filters
}, },
...@@ -81,4 +86,27 @@ $(() => { ...@@ -81,4 +86,27 @@ $(() => {
gl.issueBoards.newListDropdownInit(); gl.issueBoards.newListDropdownInit();
} }
}); });
gl.IssueBoardsModalAddBtn = new Vue({
mixins: [gl.issueBoards.ModalMixins],
el: document.getElementById('js-add-issues-btn'),
data: {
modal: ModalStore.store,
store: Store.state,
},
computed: {
disabled() {
return Store.shouldAddBlankState();
},
},
template: `
<button
class="btn btn-create pull-right prepend-left-10 has-tooltip"
type="button"
:disabled="disabled"
@click="toggleModal(true)">
Add issues
</button>
`,
});
}); });
...@@ -2,9 +2,9 @@ ...@@ -2,9 +2,9 @@
/* global Vue */ /* global Vue */
/* global Sortable */ /* global Sortable */
//= require ./board_blank_state require('./board_blank_state');
//= require ./board_delete require('./board_delete');
//= require ./board_list require('./board_list');
(() => { (() => {
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
...@@ -22,7 +22,8 @@ ...@@ -22,7 +22,8 @@
props: { props: {
list: Object, list: Object,
disabled: Boolean, disabled: Boolean,
issueLinkBase: String issueLinkBase: String,
rootPath: String,
}, },
data () { data () {
return { return {
......
/* eslint-disable comma-dangle, space-before-function-paren, dot-notation */ /* eslint-disable comma-dangle, space-before-function-paren, dot-notation */
/* global Vue */ /* global Vue */
require('./issue_card_inner');
(() => { (() => {
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
...@@ -9,12 +11,16 @@ ...@@ -9,12 +11,16 @@
gl.issueBoards.BoardCard = Vue.extend({ gl.issueBoards.BoardCard = Vue.extend({
template: '#js-board-list-card', template: '#js-board-list-card',
components: {
'issue-card-inner': gl.issueBoards.IssueCardInner,
},
props: { props: {
list: Object, list: Object,
issue: Object, issue: Object,
issueLinkBase: String, issueLinkBase: String,
disabled: Boolean, disabled: Boolean,
index: Number index: Number,
rootPath: String,
}, },
data () { data () {
return { return {
...@@ -28,31 +34,6 @@ ...@@ -28,31 +34,6 @@
} }
}, },
methods: { methods: {
filterByLabel (label, e) {
let labelToggleText = label.title;
const labelIndex = Store.state.filters['label_name'].indexOf(label.title);
$(e.target).tooltip('hide');
if (labelIndex === -1) {
Store.state.filters['label_name'].push(label.title);
$('.labels-filter').prepend(`<input type="hidden" name="label_name[]" value="${label.title}" />`);
} else {
Store.state.filters['label_name'].splice(labelIndex, 1);
labelToggleText = Store.state.filters['label_name'][0];
$(`.labels-filter input[name="label_name[]"][value="${label.title}"]`).remove();
}
const selectedLabels = Store.state.filters['label_name'];
if (selectedLabels.length === 0) {
labelToggleText = 'Label';
} else if (selectedLabels.length > 1) {
labelToggleText = `${selectedLabels[0]} + ${selectedLabels.length - 1} more`;
}
$('.labels-filter .dropdown-toggle-text').text(labelToggleText);
Store.updateFiltersUrl();
},
mouseDown () { mouseDown () {
this.showDetail = true; this.showDetail = true;
}, },
...@@ -71,6 +52,7 @@ ...@@ -71,6 +52,7 @@
Store.detail.issue = {}; Store.detail.issue = {};
} else { } else {
Store.detail.issue = this.issue; Store.detail.issue = this.issue;
Store.detail.list = this.list;
} }
} }
} }
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
/* global Vue */ /* global Vue */
/* global Sortable */ /* global Sortable */
//= require ./board_card require('./board_card');
//= require ./board_new_issue require('./board_new_issue');
(() => { (() => {
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
issues: Array, issues: Array,
loading: Boolean, loading: Boolean,
issueLinkBase: String, issueLinkBase: String,
rootPath: String,
}, },
data () { data () {
return { return {
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
$(this.$refs.submitButton).enable(); $(this.$refs.submitButton).enable();
Store.detail.issue = issue; Store.detail.issue = issue;
Store.detail.list = this.list;
}) })
.catch(() => { .catch(() => {
// Need this because our jQuery very kindly disables buttons on ALL form submissions // Need this because our jQuery very kindly disables buttons on ALL form submissions
......
...@@ -5,6 +5,8 @@ ...@@ -5,6 +5,8 @@
/* global LabelsSelect */ /* global LabelsSelect */
/* global Sidebar */ /* global Sidebar */
require('./sidebar/remove_issue');
(() => { (() => {
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
...@@ -18,7 +20,8 @@ ...@@ -18,7 +20,8 @@
data() { data() {
return { return {
detail: Store.detail, detail: Store.detail,
issue: {} issue: {},
list: {},
}; };
}, },
computed: { computed: {
...@@ -36,6 +39,7 @@ ...@@ -36,6 +39,7 @@
} }
this.issue = this.detail.issue; this.issue = this.detail.issue;
this.list = this.detail.list;
}, },
deep: true deep: true
}, },
...@@ -60,6 +64,9 @@ ...@@ -60,6 +64,9 @@
new LabelsSelect(); new LabelsSelect();
new Sidebar(); new Sidebar();
gl.Subscription.bindAll('.subscription'); gl.Subscription.bindAll('.subscription');
} },
components: {
removeBtn: gl.issueBoards.RemoveIssueBtn,
},
}); });
})(); })();
/* global Vue */
(() => {
const Store = gl.issueBoards.BoardsStore;
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.IssueCardInner = Vue.extend({
props: {
issue: {
type: Object,
required: true,
},
issueLinkBase: {
type: String,
required: true,
},
list: {
type: Object,
required: false,
},
rootPath: {
type: String,
required: true,
},
},
methods: {
showLabel(label) {
if (!this.list) return true;
return !this.list.label || label.id !== this.list.label.id;
},
filterByLabel(label, e) {
let labelToggleText = label.title;
const labelIndex = Store.state.filters.label_name.indexOf(label.title);
$(e.currentTarget).tooltip('hide');
if (labelIndex === -1) {
Store.state.filters.label_name.push(label.title);
$('.labels-filter').prepend(`<input type="hidden" name="label_name[]" value="${label.title}" />`);
} else {
Store.state.filters.label_name.splice(labelIndex, 1);
labelToggleText = Store.state.filters.label_name[0];
$(`.labels-filter input[name="label_name[]"][value="${label.title}"]`).remove();
}
const selectedLabels = Store.state.filters.label_name;
if (selectedLabels.length === 0) {
labelToggleText = 'Label';
} else if (selectedLabels.length > 1) {
labelToggleText = `${selectedLabels[0]} + ${selectedLabels.length - 1} more`;
}
$('.labels-filter .dropdown-toggle-text').text(labelToggleText);
Store.updateFiltersUrl();
},
labelStyle(label) {
return {
backgroundColor: label.color,
color: label.textColor,
};
},
},
template: `
<div>
<h4 class="card-title">
<i
class="fa fa-eye-slash confidential-icon"
v-if="issue.confidential"></i>
<a
:href="issueLinkBase + '/' + issue.id"
:title="issue.title">
{{ issue.title }}
</a>
</h4>
<div class="card-footer">
<span
class="card-number"
v-if="issue.id">
#{{ issue.id }}
</span>
<a
class="card-assignee has-tooltip"
:href="rootPath + issue.assignee.username"
:title="'Assigned to ' + issue.assignee.name"
v-if="issue.assignee"
data-container="body">
<img
class="avatar avatar-inline s20"
:src="issue.assignee.avatar"
width="20"
height="20"
:alt="'Avatar for ' + issue.assignee.name" />
</a>
<button
class="label color-label has-tooltip"
v-for="label in issue.labels"
type="button"
v-if="showLabel(label)"
@click="filterByLabel(label, $event)"
:style="labelStyle(label)"
:title="label.description"
data-container="body">
{{ label.title }}
</button>
</div>
</div>
`,
});
})();
/* global Vue */
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalEmptyState = Vue.extend({
mixins: [gl.issueBoards.ModalMixins],
data() {
return ModalStore.store;
},
props: {
image: {
type: String,
required: true,
},
newIssuePath: {
type: String,
required: true,
},
},
computed: {
contents() {
const obj = {
title: 'You haven\'t added any issues to your project yet',
content: `
An issue can be a bug, a todo or a feature request that needs to be
discussed in a project. Besides, issues are searchable and filterable.
`,
};
if (this.activeTab === 'selected') {
obj.title = 'You haven\'t selected any issues yet';
obj.content = `
Go back to <strong>All issues</strong> and select some issues
to add to your board.
`;
}
return obj;
},
},
template: `
<section class="empty-state">
<div class="row">
<div class="col-xs-12 col-sm-6 col-sm-push-6">
<aside class="svg-content" v-html="image"></aside>
</div>
<div class="col-xs-12 col-sm-6 col-sm-pull-6">
<div class="text-content">
<h4>{{ contents.title }}</h4>
<p v-html="contents.content"></p>
<a
:href="newIssuePath"
class="btn btn-success btn-inverted"
v-if="activeTab === 'all'">
New issue
</a>
<button
type="button"
class="btn btn-default"
@click="changeTab('all')"
v-if="activeTab === 'selected'">
All issues
</button>
</div>
</div>
</div>
</section>
`,
});
})();
/* eslint-disable no-new */
/* global Vue */
/* global Flash */
require('./lists_dropdown');
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalFooter = Vue.extend({
mixins: [gl.issueBoards.ModalMixins],
data() {
return {
modal: ModalStore.store,
state: gl.issueBoards.BoardsStore.state,
};
},
computed: {
submitDisabled() {
return !ModalStore.selectedCount();
},
submitText() {
const count = ModalStore.selectedCount();
return `Add ${count > 0 ? count : ''} ${gl.text.pluralize('issue', count)}`;
},
},
methods: {
addIssues() {
const list = this.modal.selectedList || this.state.lists[0];
const selectedIssues = ModalStore.getSelectedIssues();
const issueIds = selectedIssues.map(issue => issue.globalId);
// Post the data to the backend
gl.boardService.bulkUpdate(issueIds, {
add_label_ids: [list.label.id],
}).catch(() => {
new Flash('Failed to update issues, please try again.', 'alert');
selectedIssues.forEach((issue) => {
list.removeIssue(issue);
list.issuesSize -= 1;
});
});
// Add the issues on the frontend
selectedIssues.forEach((issue) => {
list.addIssue(issue);
list.issuesSize += 1;
});
this.toggleModal(false);
},
},
components: {
'lists-dropdown': gl.issueBoards.ModalFooterListsDropdown,
},
template: `
<footer
class="form-actions add-issues-footer">
<div class="pull-left">
<button
class="btn btn-success"
type="button"
:disabled="submitDisabled"
@click="addIssues">
{{ submitText }}
</button>
<span class="inline add-issues-footer-to-list">
to list
</span>
<lists-dropdown></lists-dropdown>
</div>
<button
class="btn btn-default pull-right"
type="button"
@click="toggleModal(false)">
Cancel
</button>
</footer>
`,
});
})();
/* global Vue */
require('./tabs');
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalHeader = Vue.extend({
mixins: [gl.issueBoards.ModalMixins],
data() {
return ModalStore.store;
},
computed: {
selectAllText() {
if (ModalStore.selectedCount() !== this.issues.length || this.issues.length === 0) {
return 'Select all';
}
return 'Deselect all';
},
showSearch() {
return this.activeTab === 'all' && !this.loading && this.issuesCount > 0;
},
},
methods: {
toggleAll() {
this.$refs.selectAllBtn.blur();
ModalStore.toggleAll();
},
},
components: {
'modal-tabs': gl.issueBoards.ModalTabs,
},
template: `
<div>
<header class="add-issues-header form-actions">
<h2>
Add issues
<button
type="button"
class="close"
data-dismiss="modal"
aria-label="Close"
@click="toggleModal(false)">
<span aria-hidden="true">×</span>
</button>
</h2>
</header>
<modal-tabs v-if="!loading && issuesCount > 0"></modal-tabs>
<div
class="add-issues-search append-bottom-10"
v-if="showSearch">
<input
placeholder="Search issues..."
class="form-control"
type="search"
v-model="searchTerm" />
<button
type="button"
class="btn btn-success btn-inverted prepend-left-10"
ref="selectAllBtn"
@click="toggleAll">
{{ selectAllText }}
</button>
</div>
</div>
`,
});
})();
/* global Vue */
/* global ListIssue */
require('./header');
require('./list');
require('./footer');
require('./empty_state');
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.IssuesModal = Vue.extend({
props: {
blankStateImage: {
type: String,
required: true,
},
newIssuePath: {
type: String,
required: true,
},
issueLinkBase: {
type: String,
required: true,
},
rootPath: {
type: String,
required: true,
},
},
data() {
return ModalStore.store;
},
watch: {
page() {
this.loadIssues();
},
searchTerm() {
this.searchOperation();
},
showAddIssuesModal() {
if (this.showAddIssuesModal && !this.issues.length) {
this.loading = true;
this.loadIssues()
.then(() => {
this.loading = false;
});
} else if (!this.showAddIssuesModal) {
this.issues = [];
this.selectedIssues = [];
this.issuesCount = false;
}
},
},
methods: {
searchOperation: _.debounce(function searchOperationDebounce() {
this.loadIssues(true);
}, 500),
loadIssues(clearIssues = false) {
return gl.boardService.getBacklog({
search: this.searchTerm,
page: this.page,
per: this.perPage,
}).then((res) => {
const data = res.json();
if (clearIssues) {
this.issues = [];
}
data.issues.forEach((issueObj) => {
const issue = new ListIssue(issueObj);
const foundSelectedIssue = ModalStore.findSelectedIssue(issue);
issue.selected = !!foundSelectedIssue;
this.issues.push(issue);
});
this.loadingNewPage = false;
if (!this.issuesCount) {
this.issuesCount = data.size;
}
});
},
},
computed: {
showList() {
if (this.activeTab === 'selected') {
return this.selectedIssues.length > 0;
}
return this.issuesCount > 0;
},
showEmptyState() {
if (!this.loading && this.issuesCount === 0) {
return true;
}
return this.activeTab === 'selected' && this.selectedIssues.length === 0;
},
},
components: {
'modal-header': gl.issueBoards.ModalHeader,
'modal-list': gl.issueBoards.ModalList,
'modal-footer': gl.issueBoards.ModalFooter,
'empty-state': gl.issueBoards.ModalEmptyState,
},
template: `
<div
class="add-issues-modal"
v-if="showAddIssuesModal">
<div class="add-issues-container">
<modal-header></modal-header>
<modal-list
:issue-link-base="issueLinkBase"
:root-path="rootPath"
v-if="!loading && showList"></modal-list>
<empty-state
v-if="showEmptyState"
:image="blankStateImage"
:new-issue-path="newIssuePath"></empty-state>
<section
class="add-issues-list text-center"
v-if="loading">
<div class="add-issues-list-loading">
<i class="fa fa-spinner fa-spin"></i>
</div>
</section>
<modal-footer></modal-footer>
</div>
</div>
`,
});
})();
/* global Vue */
/* global ListIssue */
/* global bp */
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalList = Vue.extend({
props: {
issueLinkBase: {
type: String,
required: true,
},
rootPath: {
type: String,
required: true,
},
},
data() {
return ModalStore.store;
},
watch: {
activeTab() {
if (this.activeTab === 'all') {
ModalStore.purgeUnselectedIssues();
}
},
},
computed: {
loopIssues() {
if (this.activeTab === 'all') {
return this.issues;
}
return this.selectedIssues;
},
groupedIssues() {
const groups = [];
this.loopIssues.forEach((issue, i) => {
const index = i % this.columns;
if (!groups[index]) {
groups.push([]);
}
groups[index].push(issue);
});
return groups;
},
},
methods: {
scrollHandler() {
const currentPage = Math.floor(this.issues.length / this.perPage);
if ((this.scrollTop() > this.scrollHeight() - 100) && !this.loadingNewPage
&& currentPage === this.page) {
this.loadingNewPage = true;
this.page += 1;
}
},
toggleIssue(e, issue) {
if (e.target.tagName !== 'A') {
ModalStore.toggleIssue(issue);
}
},
listHeight() {
return this.$refs.list.getBoundingClientRect().height;
},
scrollHeight() {
return this.$refs.list.scrollHeight;
},
scrollTop() {
return this.$refs.list.scrollTop + this.listHeight();
},
showIssue(issue) {
if (this.activeTab === 'all') return true;
const index = ModalStore.selectedIssueIndex(issue);
return index !== -1;
},
setColumnCount() {
const breakpoint = bp.getBreakpointSize();
if (breakpoint === 'lg' || breakpoint === 'md') {
this.columns = 3;
} else if (breakpoint === 'sm') {
this.columns = 2;
} else {
this.columns = 1;
}
},
},
mounted() {
this.scrollHandlerWrapper = this.scrollHandler.bind(this);
this.setColumnCountWrapper = this.setColumnCount.bind(this);
this.setColumnCount();
this.$refs.list.addEventListener('scroll', this.scrollHandlerWrapper);
window.addEventListener('resize', this.setColumnCountWrapper);
},
beforeDestroy() {
this.$refs.list.removeEventListener('scroll', this.scrollHandlerWrapper);
window.removeEventListener('resize', this.setColumnCountWrapper);
},
components: {
'issue-card-inner': gl.issueBoards.IssueCardInner,
},
template: `
<section
class="add-issues-list add-issues-list-columns"
ref="list">
<div
v-for="group in groupedIssues"
class="add-issues-list-column">
<div
v-for="issue in group"
v-if="showIssue(issue)"
class="card-parent">
<div
class="card"
:class="{ 'is-active': issue.selected }"
@click="toggleIssue($event, issue)">
<issue-card-inner
:issue="issue"
:issue-link-base="issueLinkBase"
:root-path="rootPath">
</issue-card-inner>
<span
:aria-label="'Issue #' + issue.id + ' selected'"
aria-checked="true"
v-if="issue.selected"
class="issue-card-selected text-center">
<i class="fa fa-check"></i>
</span>
</div>
</div>
</div>
</section>
`,
});
})();
/* global Vue */
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalFooterListsDropdown = Vue.extend({
data() {
return {
modal: ModalStore.store,
state: gl.issueBoards.BoardsStore.state,
};
},
computed: {
selected() {
return this.modal.selectedList || this.state.lists[0];
},
},
destroyed() {
this.modal.selectedList = null;
},
template: `
<div class="dropdown inline">
<button
class="dropdown-menu-toggle"
type="button"
data-toggle="dropdown"
aria-expanded="false">
<span
class="dropdown-label-box"
:style="{ backgroundColor: selected.label.color }">
</span>
{{ selected.title }}
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up">
<ul>
<li
v-for="list in state.lists"
v-if="list.type == 'label'">
<a
href="#"
role="button"
:class="{ 'is-active': list.id == selected.id }"
@click.prevent="modal.selectedList = list">
<span
class="dropdown-label-box"
:style="{ backgroundColor: list.label.color }">
</span>
{{ list.title }}
</a>
</li>
</ul>
</div>
</div>
`,
});
})();
/* global Vue */
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalTabs = Vue.extend({
mixins: [gl.issueBoards.ModalMixins],
data() {
return ModalStore.store;
},
computed: {
selectedCount() {
return ModalStore.selectedCount();
},
},
destroyed() {
this.activeTab = 'all';
},
template: `
<div class="top-area prepend-top-10 append-bottom-10">
<ul class="nav-links issues-state-filters">
<li :class="{ 'active': activeTab == 'all' }">
<a
href="#"
role="button"
@click.prevent="changeTab('all')">
All issues
<span class="badge">
{{ issuesCount }}
</span>
</a>
</li>
<li :class="{ 'active': activeTab == 'selected' }">
<a
href="#"
role="button"
@click.prevent="changeTab('selected')">
Selected issues
<span class="badge">
{{ selectedCount }}
</span>
</a>
</li>
</ul>
</div>
`,
});
})();
/* eslint-disable no-new */
/* global Vue */
/* global Flash */
(() => {
const Store = gl.issueBoards.BoardsStore;
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
gl.issueBoards.RemoveIssueBtn = Vue.extend({
props: {
issue: {
type: Object,
required: true,
},
list: {
type: Object,
required: true,
},
},
methods: {
removeIssue() {
const issue = this.issue;
const lists = issue.getLists();
const labelIds = lists.map(list => list.label.id);
// Post the remove data
gl.boardService.bulkUpdate([issue.globalId], {
remove_label_ids: labelIds,
}).catch(() => {
new Flash('Failed to remove issue from board, please try again.', 'alert');
lists.forEach((list) => {
list.addIssue(issue);
});
});
// Remove from the frontend store
lists.forEach((list) => {
list.removeIssue(issue);
});
Store.detail.issue = {};
},
},
template: `
<div
class="block list"
v-if="list.type !== 'done'">
<button
class="btn btn-default btn-block"
type="button"
@click="removeIssue">
Remove from board
</button>
</div>
`,
});
})();
(() => {
const ModalStore = gl.issueBoards.ModalStore;
gl.issueBoards.ModalMixins = {
methods: {
toggleModal(toggle) {
ModalStore.store.showAddIssuesModal = toggle;
},
changeTab(tab) {
ModalStore.store.activeTab = tab;
},
},
};
})();
...@@ -6,12 +6,15 @@ ...@@ -6,12 +6,15 @@
class ListIssue { class ListIssue {
constructor (obj) { constructor (obj) {
this.globalId = obj.id;
this.id = obj.iid; this.id = obj.iid;
this.title = obj.title; this.title = obj.title;
this.confidential = obj.confidential; this.confidential = obj.confidential;
this.dueDate = obj.due_date; this.dueDate = obj.due_date;
this.subscribed = obj.subscribed; this.subscribed = obj.subscribed;
this.labels = []; this.labels = [];
this.selected = false;
this.assignee = false;
if (obj.assignee) { if (obj.assignee) {
this.assignee = new ListUser(obj.assignee); this.assignee = new ListUser(obj.assignee);
......
...@@ -9,7 +9,7 @@ class List { ...@@ -9,7 +9,7 @@ class List {
this.position = obj.position; this.position = obj.position;
this.title = obj.title; this.title = obj.title;
this.type = obj.list_type; this.type = obj.list_type;
this.preset = ['backlog', 'done', 'blank'].indexOf(this.type) > -1; this.preset = ['done', 'blank'].indexOf(this.type) > -1;
this.filters = gl.issueBoards.BoardsStore.state.filters; this.filters = gl.issueBoards.BoardsStore.state.filters;
this.page = 1; this.page = 1;
this.loading = true; this.loading = true;
......
...@@ -2,7 +2,13 @@ ...@@ -2,7 +2,13 @@
/* global Vue */ /* global Vue */
class BoardService { class BoardService {
constructor (root, boardId) { constructor (root, bulkUpdatePath, boardId) {
this.boards = Vue.resource(`${root}{/id}.json`, {}, {
issues: {
method: 'GET',
url: `${root}/${boardId}/issues.json`
}
});
this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, { this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, {
generate: { generate: {
method: 'POST', method: 'POST',
...@@ -10,7 +16,12 @@ class BoardService { ...@@ -10,7 +16,12 @@ class BoardService {
} }
}); });
this.issue = Vue.resource(`${root}/${boardId}/issues{/id}`, {}); this.issue = Vue.resource(`${root}/${boardId}/issues{/id}`, {});
this.issues = Vue.resource(`${root}/${boardId}/lists{/id}/issues`, {}); this.issues = Vue.resource(`${root}/${boardId}/lists{/id}/issues`, {}, {
bulkUpdate: {
method: 'POST',
url: bulkUpdatePath,
},
});
Vue.http.interceptors.push((request, next) => { Vue.http.interceptors.push((request, next) => {
request.headers['X-CSRF-Token'] = $.rails.csrfToken(); request.headers['X-CSRF-Token'] = $.rails.csrfToken();
...@@ -65,6 +76,20 @@ class BoardService { ...@@ -65,6 +76,20 @@ class BoardService {
issue issue
}); });
} }
getBacklog(data) {
return this.boards.issues(data);
}
bulkUpdate(issueIds, extraData = {}) {
const data = {
update: Object.assign(extraData, {
issuable_ids: issueIds.join(','),
}),
};
return this.issues.bulkUpdate(data);
}
} }
window.BoardService = BoardService; window.BoardService = BoardService;
...@@ -34,15 +34,10 @@ ...@@ -34,15 +34,10 @@
}, },
new (listObj) { new (listObj) {
const list = this.addList(listObj); const list = this.addList(listObj);
const backlogList = this.findList('type', 'backlog', 'backlog');
list list
.save() .save()
.then(() => { .then(() => {
// Remove any new issues from the backlog
// as they will be visible in the new list
list.issues.forEach(backlogList.removeIssue.bind(backlogList));
this.state.lists = _.sortBy(this.state.lists, 'position'); this.state.lists = _.sortBy(this.state.lists, 'position');
}); });
this.removeBlankState(); this.removeBlankState();
...@@ -52,7 +47,7 @@ ...@@ -52,7 +47,7 @@
}, },
shouldAddBlankState () { shouldAddBlankState () {
// Decide whether to add the blank state // Decide whether to add the blank state
return !(this.state.lists.filter(list => list.type !== 'backlog' && list.type !== 'done')[0]); return !(this.state.lists.filter(list => list.type !== 'done')[0]);
}, },
addBlankState () { addBlankState () {
if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return; if (!this.shouldAddBlankState() || this.welcomeIsHidden() || this.disabled) return;
...@@ -102,7 +97,7 @@ ...@@ -102,7 +97,7 @@
listTo.addIssue(issue, listFrom, newIndex); listTo.addIssue(issue, listFrom, newIndex);
} }
if (listTo.type === 'done' && listFrom.type !== 'backlog') { if (listTo.type === 'done') {
issueLists.forEach((list) => { issueLists.forEach((list) => {
list.removeIssue(issue); list.removeIssue(issue);
}); });
......
(() => {
window.gl = window.gl || {};
window.gl.issueBoards = window.gl.issueBoards || {};
class ModalStore {
constructor() {
this.store = {
columns: 3,
issues: [],
issuesCount: false,
selectedIssues: [],
showAddIssuesModal: false,
activeTab: 'all',
selectedList: null,
searchTerm: '',
loading: false,
loadingNewPage: false,
page: 1,
perPage: 50,
};
}
selectedCount() {
return this.getSelectedIssues().length;
}
toggleIssue(issueObj) {
const issue = issueObj;
const selected = issue.selected;
issue.selected = !selected;
if (!selected) {
this.addSelectedIssue(issue);
} else {
this.removeSelectedIssue(issue);
}
}
toggleAll() {
const select = this.selectedCount() !== this.store.issues.length;
this.store.issues.forEach((issue) => {
const issueUpdate = issue;
if (issueUpdate.selected !== select) {
issueUpdate.selected = select;
if (select) {
this.addSelectedIssue(issue);
} else {
this.removeSelectedIssue(issue);
}
}
});
}
getSelectedIssues() {
return this.store.selectedIssues.filter(issue => issue.selected);
}
addSelectedIssue(issue) {
const index = this.selectedIssueIndex(issue);
if (index === -1) {
this.store.selectedIssues.push(issue);
}
}
removeSelectedIssue(issue, forcePurge = false) {
if (this.store.activeTab === 'all' || forcePurge) {
this.store.selectedIssues = this.store.selectedIssues
.filter(fIssue => fIssue.id !== issue.id);
}
}
purgeUnselectedIssues() {
this.store.selectedIssues.forEach((issue) => {
if (!issue.selected) {
this.removeSelectedIssue(issue, true);
}
});
}
selectedIssueIndex(issue) {
return this.store.selectedIssues.indexOf(issue);
}
findSelectedIssue(issue) {
return this.store.selectedIssues
.filter(filteredIssue => filteredIssue.id === issue.id)[0];
}
}
gl.issueBoards.ModalStore = new ModalStore();
})();
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
BreakpointInstance.prototype.getBreakpointSize = function() { BreakpointInstance.prototype.getBreakpointSize = function() {
var $visibleDevice; var $visibleDevice;
$visibleDevice = this.visibleDevice; $visibleDevice = this.visibleDevice;
// TODO: Consider refactoring in light of turbolinks removal.
// the page refreshed via turbolinks // the page refreshed via turbolinks
if (!$visibleDevice().length) { if (!$visibleDevice().length) {
this.setup(); this.setup();
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-param-reassign, quotes, yoda, no-else-return, consistent-return, comma-dangle, object-shorthand, prefer-template, one-var, one-var-declaration-per-line, no-unused-vars, max-len, vars-on-top */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, no-param-reassign, quotes, yoda, no-else-return, consistent-return, comma-dangle, object-shorthand, prefer-template, one-var, one-var-declaration-per-line, no-unused-vars, max-len, vars-on-top */
/* global Breakpoints */ /* global Breakpoints */
/* global Turbolinks */
(function() { (function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
...@@ -127,7 +126,7 @@ ...@@ -127,7 +126,7 @@
pageUrl += DOWN_BUILD_TRACE; pageUrl += DOWN_BUILD_TRACE;
} }
return Turbolinks.visit(pageUrl); return gl.utils.visitUrl(pageUrl);
} }
}; };
})(this) })(this)
......
/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */ /* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
/* jshint esversion: 6 */ /* jshint esversion: 6 */
/*= require lib/utils/common_utils */ require('./lib/utils/common_utils');
(() => { (() => {
const gfmRules = { const gfmRules = {
......
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, prefer-arrow-callback, max-len */ /* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, prefer-arrow-callback, max-len */
/* global Clipboard */ /* global Clipboard */
/*= require clipboard */ window.Clipboard = require('vendor/clipboard');
(function() { (function() {
var genericError, genericSuccess, showTooltip; var genericError, genericSuccess, showTooltip;
......
...@@ -2,9 +2,12 @@ ...@@ -2,9 +2,12 @@
/* global Cookies */ /* global Cookies */
/* global Flash */ /* global Flash */
//= require vue window.Vue = require('vue');
//= require_tree ./svg window.Cookies = require('vendor/js.cookie');
//= require_tree .
function requireAll(context) { return context.keys().map(context); }
requireAll(require.context('./svg', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('.', true, /^\.\/(?!cycle_analytics_bundle).*\.(js|es6)$/));
$(() => { $(() => {
const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed'; const OVERVIEW_DIALOG_COOKIE = 'cycle_analytics_help_dismissed';
......
/* eslint-disable class-methods-use-this */ /* eslint-disable class-methods-use-this */
//= require lib/utils/url_utility */ require('./lib/utils/url_utility');
(() => { (() => {
const UNFOLD_COUNT = 20; const UNFOLD_COUNT = 20;
let isBound = false;
class Diff { class Diff {
constructor() { constructor() {
...@@ -17,10 +18,12 @@ ...@@ -17,10 +18,12 @@
$('.content-wrapper .container-fluid').removeClass('container-limited'); $('.content-wrapper .container-fluid').removeClass('container-limited');
} }
$(document) if (!isBound) {
.off('click', '.js-unfold, .diff-line-num a') $(document)
.on('click', '.js-unfold', this.handleClickUnfold.bind(this)) .on('click', '.js-unfold', this.handleClickUnfold.bind(this))
.on('click', '.diff-line-num a', this.handleClickLineNum.bind(this)); .on('click', '.diff-line-num a', this.handleClickLineNum.bind(this));
isBound = true;
}
this.openAnchoredDiff(); this.openAnchoredDiff();
} }
......
/* eslint-disable func-names, comma-dangle, new-cap, no-new */ /* eslint-disable func-names, comma-dangle, new-cap, no-new, import/newline-after-import, no-multi-spaces, max-len */
/* global Vue */ /* global Vue */
/* global ResolveCount */ /* global ResolveCount */
//= require_directory ./models function requireAll(context) { return context.keys().map(context); }
//= require_directory ./stores requireAll(require.context('./models', false, /^\.\/.*\.(js|es6)$/));
//= require_directory ./services requireAll(require.context('./stores', false, /^\.\/.*\.(js|es6)$/));
//= require_directory ./mixins requireAll(require.context('./services', false, /^\.\/.*\.(js|es6)$/));
//= require_directory ./components requireAll(require.context('./mixins', false, /^\.\/.*\.(js|es6)$/));
requireAll(require.context('./components', false, /^\.\/.*\.(js|es6)$/));
$(() => { $(() => {
const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn'; const COMPONENT_SELECTOR = 'resolve-btn, resolve-discussion-btn, jump-to-discussion, comment-and-resolve-btn';
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, one-var, no-var, one-var-declaration-per-line, no-unused-vars, camelcase, quotes, no-useless-concat, prefer-template, quote-props, comma-dangle, object-shorthand, consistent-return, prefer-arrow-callback */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, one-var, no-var, one-var-declaration-per-line, no-unused-vars, camelcase, quotes, no-useless-concat, prefer-template, quote-props, comma-dangle, object-shorthand, consistent-return, prefer-arrow-callback */
/* global Dropzone */ /* global Dropzone */
/*= require preview_markdown */ require('./preview_markdown');
(function() { (function() {
this.DropzoneInput = (function() { this.DropzoneInput = (function() {
......
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
/* global EnvironmentsService */ /* global EnvironmentsService */
/* global Flash */ /* global Flash */
//= require vue window.Vue = require('vue');
//= require vue-resource window.Vue.use(require('vue-resource'));
//= require_tree ../services/ require('../services/environments_service');
//= require ./environment_item require('./environment_item');
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
......
/*= require vue */
/* global Vue */ /* global Vue */
window.Vue = require('vue');
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.environmentsList = window.gl.environmentsList || {}; window.gl.environmentsList = window.gl.environmentsList || {};
......
/*= require vue */
/* global Vue */ /* global Vue */
window.Vue = require('vue');
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.environmentsList = window.gl.environmentsList || {}; window.gl.environmentsList = window.gl.environmentsList || {};
......
/* global Vue */ /* global Vue */
/* global timeago */ /* global timeago */
/*= require timeago */ window.Vue = require('vue');
/*= require lib/utils/text_utility */ window.timeago = require('vendor/timeago');
/*= require vue_common_component/commit */ require('../../lib/utils/text_utility');
/*= require ./environment_actions */ require('../../vue_common_component/commit');
/*= require ./environment_external_url */ require('./environment_actions');
/*= require ./environment_stop */ require('./environment_external_url');
/*= require ./environment_rollback */ require('./environment_stop');
/*= require ./environment_terminal_button */ require('./environment_rollback');
require('./environment_terminal_button');
(() => { (() => {
/** /**
......
/*= require vue */
/* global Vue */ /* global Vue */
window.Vue = require('vue');
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.environmentsList = window.gl.environmentsList || {}; window.gl.environmentsList = window.gl.environmentsList || {};
......
/*= require vue */
/* global Vue */ /* global Vue */
window.Vue = require('vue');
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.environmentsList = window.gl.environmentsList || {}; window.gl.environmentsList = window.gl.environmentsList || {};
......
/*= require vue */
/* global Vue */ /* global Vue */
window.Vue = require('vue');
(() => { (() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.environmentsList = window.gl.environmentsList || {}; window.gl.environmentsList = window.gl.environmentsList || {};
......
//= require vue window.Vue = require('vue');
//= require_tree ./stores/
//= require ./components/environment require('./stores/environments_store');
//= require ./vue_resource_interceptor require('./components/environment');
require('./vue_resource_interceptor');
$(() => { $(() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
......
/* globals Vue */ /* globals Vue */
/* eslint-disable no-unused-vars, no-param-reassign */ /* eslint-disable no-unused-vars, no-param-reassign */
class EnvironmentsService { class EnvironmentsService {
constructor(root) { constructor(root) {
......
/* eslint-disable no-extend-native, func-names, space-before-function-paren, space-infix-ops, max-len */ /* eslint-disable no-extend-native, func-names, space-before-function-paren, space-infix-ops, strict, max-len */
'use strict';
Array.prototype.first = function() { Array.prototype.first = function() {
return this[0]; return this[0];
}; };
......
/*= require filtered_search/filtered_search_dropdown */ require('./filtered_search_dropdown');
/* global droplabFilter */ /* global droplabFilter */
......
/*= require filtered_search/filtered_search_dropdown */ require('./filtered_search_dropdown');
/* global droplabAjax */ /* global droplabAjax */
/* global droplabFilter */ /* global droplabFilter */
......
/*= require filtered_search/filtered_search_dropdown */ require('./filtered_search_dropdown');
/* global droplabAjaxFilter */ /* global droplabAjaxFilter */
......
// This is a manifest file that'll be compiled into including all the files listed below. function requireAll(context) { return context.keys().map(context); }
// Add new JavaScript code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/assets/application.js requireAll(require.context('./', true, /^\.\/(?!filtered_search_bundle).*\.(js|es6)$/));
// 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_tree . */
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
this.setupMapping(); this.setupMapping();
this.cleanupWrapper = this.cleanup.bind(this); this.cleanupWrapper = this.cleanup.bind(this);
document.addEventListener('page:fetch', this.cleanupWrapper); document.addEventListener('beforeunload', this.cleanupWrapper);
} }
cleanup() { cleanup() {
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
this.setupMapping(); this.setupMapping();
document.removeEventListener('page:fetch', this.cleanupWrapper); document.removeEventListener('beforeunload', this.cleanupWrapper);
} }
setupMapping() { setupMapping() {
......
/* global Turbolinks */
(() => { (() => {
class FilteredSearchManager { class FilteredSearchManager {
constructor() { constructor() {
...@@ -15,13 +13,13 @@ ...@@ -15,13 +13,13 @@
this.dropdownManager.setDropdown(); this.dropdownManager.setDropdown();
this.cleanupWrapper = this.cleanup.bind(this); this.cleanupWrapper = this.cleanup.bind(this);
document.addEventListener('page:fetch', this.cleanupWrapper); document.addEventListener('beforeunload', this.cleanupWrapper);
} }
} }
cleanup() { cleanup() {
this.unbindEvents(); this.unbindEvents();
document.removeEventListener('page:fetch', this.cleanupWrapper); document.removeEventListener('beforeunload', this.cleanupWrapper);
} }
bindEvents() { bindEvents() {
...@@ -200,7 +198,9 @@ ...@@ -200,7 +198,9 @@
paths.push(`search=${sanitized}`); paths.push(`search=${sanitized}`);
} }
Turbolinks.visit(`?scope=all&utf8=✓&${paths.join('&')}`); const parameterizedUrl = `?scope=all&utf8=✓&${paths.join('&')}`;
gl.utils.visitUrl(parameterizedUrl);
} }
getUsernameParams() { getUsernameParams() {
......
/* eslint-disable func-names, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, prefer-rest-params, max-len, vars-on-top, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func, no-mixed-operators */ /* eslint-disable func-names, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, prefer-rest-params, max-len, vars-on-top, wrap-iife, no-unused-vars, quotes, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, comma-dangle, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func, no-mixed-operators */
/* global fuzzaldrinPlus */ /* global fuzzaldrinPlus */
/* global Turbolinks */
(function() { (function() {
var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote, var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote,
...@@ -723,7 +722,7 @@ ...@@ -723,7 +722,7 @@
if ($el.length) { if ($el.length) {
var href = $el.attr('href'); var href = $el.attr('href');
if (href && href !== '#') { if (href && href !== '#') {
Turbolinks.visit(href); gl.utils.visitUrl(href);
} else { } else {
$el.first().trigger('click'); $el.first().trigger('click');
} }
......
/* eslint-disable comma-dangle, class-methods-use-this, max-len, space-before-function-paren, arrow-parens, no-param-reassign */ /* eslint-disable comma-dangle, class-methods-use-this, max-len, space-before-function-paren, arrow-parens, no-param-reassign */
//= require gl_field_error require('./gl_field_error');
((global) => { ((global) => {
const customValidationFlag = 'gl-field-error-ignore'; const customValidationFlag = 'gl-field-error-ignore';
......
/* eslint-disable func-names, space-before-function-paren */ // require everything else in this directory
// This is a manifest file that'll be compiled into including all the files listed below. function requireAll(context) { return context.keys().map(context); }
// Add new JavaScript code in separate files in this directory and they'll automatically requireAll(require.context('.', false, /^\.\/(?!graphs_bundle).*\.(js|es6)$/));
// be included in the compiled file accessible from http://example.com/assets/application.js
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
/*= require_tree . */
(function() {
}).call(this);
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
/* global ContributorsStatGraphUtil */ /* global ContributorsStatGraphUtil */
/* global d3 */ /* global d3 */
/*= require d3 */ window.d3 = require('d3');
(function() { (function() {
this.ContributorsStatGraph = (function() { this.ContributorsStatGraph = (function() {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
/* global d3 */ /* global d3 */
/* global ContributorsGraph */ /* global ContributorsGraph */
/*= require d3 */ window.d3 = require('d3');
(function() { (function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }, var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
......
/* eslint-disable no-param-reassign, func-names, no-var, camelcase, no-unused-vars, object-shorthand, space-before-function-paren, no-return-assign, comma-dangle, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, wrap-iife, max-len */ /* eslint-disable no-param-reassign, func-names, no-var, camelcase, no-unused-vars, object-shorthand, space-before-function-paren, no-return-assign, comma-dangle, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, wrap-iife, max-len */
/* global Issuable */ /* global Issuable */
/* global Turbolinks */
((global) => { ((global) => {
var issuable_created; var issuable_created;
...@@ -119,7 +118,7 @@ ...@@ -119,7 +118,7 @@
issuesUrl = formAction; issuesUrl = formAction;
issuesUrl += "" + (formAction.indexOf('?') < 0 ? '?' : '&'); issuesUrl += "" + (formAction.indexOf('?') < 0 ? '?' : '&');
issuesUrl += formData; issuesUrl += formData;
return Turbolinks.visit(issuesUrl); return gl.utils.visitUrl(issuesUrl);
}; };
})(this), })(this),
initResetFilters: function() { initResetFilters: function() {
...@@ -130,7 +129,7 @@ ...@@ -130,7 +129,7 @@
const baseIssuesUrl = target.href; const baseIssuesUrl = target.href;
$form.attr('action', baseIssuesUrl); $form.attr('action', baseIssuesUrl);
Turbolinks.visit(baseIssuesUrl); gl.utils.visitUrl(baseIssuesUrl);
}); });
}, },
initChecks: function() { initChecks: function() {
......
//= require ./time_tracking/time_tracking_bundle require('./time_tracking/time_tracking_bundle');
/* global Vue */ /* global Vue */
//= require lib/utils/pretty_time require('../../../lib/utils/pretty_time');
(() => { (() => {
Vue.component('time-tracking-collapsed-state', { Vue.component('time-tracking-collapsed-state', {
...@@ -39,4 +39,3 @@ ...@@ -39,4 +39,3 @@
`, `,
}); });
})(); })();
/* global Vue */ /* global Vue */
//= require lib/utils/pretty_time require('../../../lib/utils/pretty_time');
(() => { (() => {
const prettyTime = gl.utils.prettyTime; const prettyTime = gl.utils.prettyTime;
......
/* global Vue */ /* global Vue */
//= require ./help_state
//= require ./collapsed_state require('./help_state');
//= require ./spent_only_pane require('./collapsed_state');
//= require ./no_tracking_pane require('./spent_only_pane');
//= require ./estimate_only_pane require('./no_tracking_pane');
//= require ./comparison_pane require('./estimate_only_pane');
require('./comparison_pane');
(() => { (() => {
Vue.component('issuable-time-tracker', { Vue.component('issuable-time-tracker', {
......
/* global Vue */ /* global Vue */
//= require ./components/time_tracker
//= require smart_interval require('./components/time_tracker');
//= require subbable_resource require('../../smart_interval');
require('../../subbable_resource');
(() => { (() => {
/* This Vue instance represents what will become the parent instance for the /* This Vue instance represents what will become the parent instance for the
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */
/* global Flash */ /* global Flash */
/*= require flash */ require('./flash');
/*= require jquery.waitforimages */ require('vendor/jquery.waitforimages');
/*= require task_list */ require('vendor/task_list');
(function() { (function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
......
...@@ -7,19 +7,28 @@ ace_modes = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/mode-*.js'].sort. ...@@ -7,19 +7,28 @@ ace_modes = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/mode-*.js'].sort.
File.basename(file, '.js').sub(/^mode-/, '') File.basename(file, '.js').sub(/^mode-/, '')
end end
%> %>
// Lazy-load configuration when ace.edit is called
(function() { (function() {
window.gon = window.gon || {}; var basePath;
var basePath = (window.gon.relative_url_root || '').replace(/\/$/, '') + '/assets/ace'; var ace = window.ace;
ace.config.set('basePath', basePath); var edit = ace.edit;
ace.edit = function() {
window.gon = window.gon || {};
basePath = (window.gon.relative_url_root || '').replace(/\/$/, '') + '/assets/ace';
ace.config.set('basePath', basePath);
// configure paths for all worker modules // configure paths for all worker modules
<% ace_workers.each do |worker| %> <% ace_workers.each do |worker| %>
ace.config.setModuleUrl('ace/mode/<%= worker %>_worker', basePath + '/worker-<%= worker %>.js'); ace.config.setModuleUrl('ace/mode/<%= worker %>_worker', basePath + '/<%= File.basename(asset_path("ace/worker-#{worker}.js")) %>');
<% end %> <% end %>
// configure paths for all mode modules // configure paths for all mode modules
<% ace_modes.each do |mode| %> <% ace_modes.each do |mode| %>
ace.config.setModuleUrl('ace/mode/<%= mode %>', basePath + '/mode-<%= mode %>.js'); ace.config.setModuleUrl('ace/mode/<%= mode %>', basePath + '/<%= File.basename(asset_path("ace/mode-#{mode}.js")) %>');
<% end %> <% end %>
// restore original method
ace.edit = edit;
return ace.edit.apply(ace, arguments);
};
})(); })();
/* eslint-disable func-names, space-before-function-paren */ /* eslint-disable func-names, space-before-function-paren */
/*= require Chart */ window.Chart = require('vendor/Chart');
(function() {
}).call(this);
/* eslint-disable func-names, space-before-function-paren */ /* eslint-disable func-names, space-before-function-paren */
/*= require d3 */ window.d3 = require('d3');
(function() {
}).call(this);
...@@ -95,7 +95,6 @@ ...@@ -95,7 +95,6 @@
const newState = `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`; const newState = `${copySource}${this.currentLocation.search}${this.currentLocation.hash}`;
history.replaceState({ history.replaceState({
turbolinks: true,
url: newState, url: newState,
}, document.title, newState); }, document.title, newState);
return newState; return newState;
......
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
/* global timeago */ /* global timeago */
/* global dateFormat */ /* global dateFormat */
/*= require timeago */ window.timeago = require('vendor/timeago');
/*= require date.format */ window.dateFormat = require('vendor/date.format');
(function() { (function() {
(function(w) { (function(w) {
......
(function() {
gl.emojiAliases = function() {
return JSON.parse('<%= Gitlab::AwardEmoji.aliases.to_json %>');
};
}).call(this);
...@@ -161,6 +161,9 @@ ...@@ -161,6 +161,9 @@
gl.text.humanize = function(string) { gl.text.humanize = function(string) {
return string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1); return string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
}; };
gl.text.pluralize = function(str, count) {
return str + (count > 1 || count === 0 ? 's' : '');
};
return gl.text.truncate = function(string, maxLength) { return gl.text.truncate = function(string, maxLength) {
return string.substr(0, (maxLength - 3)) + '...'; return string.substr(0, (maxLength - 3)) + '...';
}; };
......
...@@ -76,5 +76,11 @@ ...@@ -76,5 +76,11 @@
hashIndex = url.indexOf('#'); hashIndex = url.indexOf('#');
return hashIndex === -1 ? null : url.substring(hashIndex + 1); return hashIndex === -1 ? null : url.substring(hashIndex + 1);
}; };
w.gl.utils.refreshCurrentPage = () => gl.utils.visitUrl(document.location.href);
w.gl.utils.visitUrl = (url) => {
document.location.href = url;
};
})(window); })(window);
}).call(this); }).call(this);
//= require vue window.Vue = require('vue');
//= require vue-resource window.Vue.use(require('vue-resource'));
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
// //
// Handles single- and multi-line selection and highlight for blob views. // Handles single- and multi-line selection and highlight for blob views.
// //
/*= require jquery.scrollTo */ require('vendor/jquery.scrollTo');
// //
// ### Example Markup // ### Example Markup
...@@ -171,7 +171,6 @@ ...@@ -171,7 +171,6 @@
// This method is stubbed in tests. // This method is stubbed in tests.
LineHighlighter.prototype.__setLocationHash__ = function(value) { LineHighlighter.prototype.__setLocationHash__ = function(value) {
return history.pushState({ return history.pushState({
turbolinks: false,
url: value url: value
// We're using pushState instead of assigning location.hash directly to // We're using pushState instead of assigning location.hash directly to
// prevent the page from scrolling on the hashchange event // prevent the page from scrolling on the hashchange event
......
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback */ /* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback */
/* global Turbolinks */
(function() { (function() {
Turbolinks.enableProgressBar(); window.addEventListener('beforeunload', function() {
$(document).on('page:fetch', function() {
$('.tanuki-logo').addClass('animate'); $('.tanuki-logo').addClass('animate');
}); });
$(document).on('page:change', function() {
$('.tanuki-logo').removeClass('animate');
});
}).call(this); }).call(this);
...@@ -2,14 +2,14 @@ ...@@ -2,14 +2,14 @@
/* global Vue */ /* global Vue */
/* global Flash */ /* global Flash */
//= require vue window.Vue = require('vue');
//= require ./merge_conflict_store require('./merge_conflict_store');
//= require ./merge_conflict_service require('./merge_conflict_service');
//= require ./mixins/line_conflict_utils require('./mixins/line_conflict_utils');
//= require ./mixins/line_conflict_actions require('./mixins/line_conflict_actions');
//= require ./components/diff_file_editor require('./components/diff_file_editor');
//= require ./components/inline_conflict_lines require('./components/inline_conflict_lines');
//= require ./components/parallel_conflict_lines require('./components/parallel_conflict_lines');
$(() => { $(() => {
const INTERACTIVE_RESOLVE_MODE = 'interactive'; const INTERACTIVE_RESOLVE_MODE = 'interactive';
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */
/* global MergeRequestTabs */ /* global MergeRequestTabs */
/*= require jquery.waitforimages */ require('vendor/jquery.waitforimages');
/*= require task_list */ require('vendor/task_list');
/*= require merge_request_tabs */ require('./merge_request_tabs');
(function() { (function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
......
/* eslint-disable no-new, class-methods-use-this */ /* eslint-disable no-new, class-methods-use-this */
/* global Breakpoints */ /* global Breakpoints */
/* global Cookies */ /* global Cookies */
/* global DiffNotesApp */
/* global Flash */ /* global Flash */
/*= require js.cookie */ require('./breakpoints');
/*= require breakpoints */ window.Cookies = require('vendor/js.cookie');
require('./flash');
/* eslint-disable max-len */ /* eslint-disable max-len */
// MergeRequestTabs // MergeRequestTabs
...@@ -184,12 +184,13 @@ ...@@ -184,12 +184,13 @@
// Ensure parameters and hash come along for the ride // Ensure parameters and hash come along for the ride
newState += location.search + location.hash; newState += location.search + location.hash;
// TODO: Consider refactoring in light of turbolinks removal.
// Replace the current history state with the new one without breaking // Replace the current history state with the new one without breaking
// Turbolinks' history. // Turbolinks' history.
// //
// See https://github.com/rails/turbolinks/issues/363 // See https://github.com/rails/turbolinks/issues/363
window.history.replaceState({ window.history.replaceState({
turbolinks: true,
url: newState, url: newState,
}, document.title, newState); }, document.title, newState);
......
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
/* global notify */ /* global notify */
/* global notifyPermissions */ /* global notifyPermissions */
/* global merge_request_widget */ /* global merge_request_widget */
/* global Turbolinks */
require('./smart_interval');
((global) => { ((global) => {
var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i += 1) { if (i in this && this[i] === item) return i; } return -1; }; var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i += 1) { if (i in this && this[i] === item) return i; } return -1; };
...@@ -69,13 +70,13 @@ ...@@ -69,13 +70,13 @@
} }
MergeRequestWidget.prototype.clearEventListeners = function() { MergeRequestWidget.prototype.clearEventListeners = function() {
return $(document).off('page:change.merge_request'); return $(document).off('DOMContentLoaded');
}; };
MergeRequestWidget.prototype.addEventListeners = function() { MergeRequestWidget.prototype.addEventListeners = function() {
var allowedPages; var allowedPages;
allowedPages = ['show', 'commits', 'pipelines', 'changes']; allowedPages = ['show', 'commits', 'pipelines', 'changes'];
$(document).on('page:change.merge_request', (function(_this) { $(document).on('DOMContentLoaded', (function(_this) {
return function() { return function() {
var page; var page;
page = $('body').data('page').split(':').last(); page = $('body').data('page').split(':').last();
......
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
$('.js-rebase-button').html("<i class='fa fa-spinner fa-spin'></i> Rebase in progress"); $('.js-rebase-button').html("<i class='fa fa-spinner fa-spin'></i> Rebase in progress");
}); });
} else { } else {
merge_request_widget.getMergeStatus(); setTimeout(() => merge_request_widget.getMergeStatus(), 200);
} }
}); });
})(); })();
...@@ -2,13 +2,9 @@ ...@@ -2,13 +2,9 @@
/* global Network */ /* global Network */
/* global ShortcutsNetwork */ /* global ShortcutsNetwork */
// This is a manifest file that'll be compiled into including all the files listed below. // require everything else in this directory
// Add new JavaScript code in separate files in this directory and they'll automatically function requireAll(context) { return context.keys().map(context); }
// be included in the compiled file accessible from http://example.com/assets/application.js requireAll(require.context('.', false, /^\.\/(?!network_bundle).*\.(js|es6)$/));
// 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_tree . */
(function() { (function() {
$(function() { $(function() {
......
...@@ -4,13 +4,14 @@ ...@@ -4,13 +4,14 @@
/* global ResolveService */ /* global ResolveService */
/* global mrRefreshWidgetUrl */ /* global mrRefreshWidgetUrl */
/*= require autosave */ require('./autosave');
/*= require autosize */ window.autosize = require('vendor/autosize');
/*= require dropzone */ window.Dropzone = require('dropzone');
/*= require dropzone_input */ require('./dropzone_input');
/*= require gfm_auto_complete */ require('./gfm_auto_complete');
/*= require jquery.atwho */ require('vendor/jquery.caret'); // required by jquery.atwho
/*= require task_list */ require('vendor/jquery.atwho');
require('vendor/task_list');
(function() { (function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
......
/* eslint-disable no-new, guard-for-in, no-restricted-syntax, no-continue, no-param-reassign, max-len */ /* eslint-disable no-new, guard-for-in, no-restricted-syntax, no-continue, no-param-reassign, max-len */
//= require lib/utils/bootstrap_linked_tabs require('./lib/utils/bootstrap_linked_tabs');
((global) => { ((global) => {
class Pipelines { class Pipelines {
......
/* eslint-disable func-names, space-before-function-paren */ // require everything else in this directory
function requireAll(context) { return context.keys().map(context); }
/*= require_tree . */ requireAll(require.context('.', false, /^\.\/(?!profile_bundle).*\.(js|es6)$/));
(function() {
}).call(this);
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, consistent-return, no-new, prefer-arrow-callback, no-return-assign, one-var, one-var-declaration-per-line, object-shorthand, comma-dangle, no-else-return, newline-per-chained-call, no-shadow, vars-on-top, prefer-template, max-len */
/* global Cookies */ /* global Cookies */
/* global Turbolinks */
/* global ProjectSelect */ /* global ProjectSelect */
(function() { (function() {
...@@ -58,8 +57,8 @@ ...@@ -58,8 +57,8 @@
}; };
Project.prototype.initRefSwitcher = function() { Project.prototype.initRefSwitcher = function() {
var refListItem = document.createElement('li'), var refListItem = document.createElement('li');
refLink = document.createElement('a'); var refLink = document.createElement('a');
refLink.href = '#'; refLink.href = '#';
...@@ -118,7 +117,7 @@ ...@@ -118,7 +117,7 @@
var $form = $dropdown.closest('form'); var $form = $dropdown.closest('form');
var action = $form.attr('action'); var action = $form.attr('action');
var divider = action.indexOf('?') < 0 ? '?' : '&'; var divider = action.indexOf('?') < 0 ? '?' : '&';
Turbolinks.visit(action + '' + divider + '' + $form.serialize()); gl.utils.visitUrl(action + '' + divider + '' + $form.serialize());
} }
} }
}); });
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, max-len */
/* global Turbolinks */
(function() { (function() {
this.ProjectImport = (function() { this.ProjectImport = (function() {
function ProjectImport() { function ProjectImport() {
setTimeout(function() { setTimeout(function() {
return Turbolinks.visit(location.href); return gl.utils.visitUrl(location.href);
}, 5000); }, 5000);
} }
......
/*= require_tree . */ // require everything else in this directory
function requireAll(context) { return context.keys().map(context); }
requireAll(require.context('.', false, /^\.\/(?!protected_branches_bundle).*\.(js|es6)$/));
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
this.find('.js-render-math').renderMath(); this.find('.js-render-math').renderMath();
}; };
$(document).on('ready page:load', function() { $(document).on('ready load', function() {
return $('body').renderGFM(); return $('body').renderGFM();
}); });
}).call(this); }).call(this);
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, prefer-arrow-callback, consistent-return, object-shorthand, no-unused-vars, one-var, one-var-declaration-per-line, no-else-return, comma-dangle, max-len */
/* global Mousetrap */ /* global Mousetrap */
/* global Turbolinks */
/* global findFileURL */ /* global findFileURL */
(function() { (function() {
...@@ -23,7 +22,7 @@ ...@@ -23,7 +22,7 @@
Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], this.toggleMarkdownPreview); Mousetrap.bind(['ctrl+shift+p', 'command+shift+p'], this.toggleMarkdownPreview);
if (typeof findFileURL !== "undefined" && findFileURL !== null) { if (typeof findFileURL !== "undefined" && findFileURL !== null) {
Mousetrap.bind('t', function() { Mousetrap.bind('t', function() {
return Turbolinks.visit(findFileURL); return gl.utils.visitUrl(findFileURL);
}); });
} }
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
/* global Shortcuts */ /* global Shortcuts */
/* global Mousetrap */ /* global Mousetrap */
/*= require shortcuts */ require('./shortcuts');
(function() { (function() {
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment