Commit b5c80f99 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'master' into 27574-pipelines-empty-state

* master: (209 commits)
  Fix Slack link when on notify
  Use Enumerable#index_by where possible
  Protect against unknown emojis in frequently used list
  Fix config option to disable Prometheus
  Fix double click token name
  fix describe block to make the karma reporter happy
  removes n+1 query from tags and branches indexes
  Fix broken links limit lines to 80 chars
  Add prometheus memory requirements, include additional detail on disabling prometheus in docs.
  Add `requirements: { id: %r{[^/]+} }` for all projects and groups namespaced API routes
  Expand on the good changelog/bad changelog documentation examples
  Add policy for closing stale merge requests
  Fix spec
  Use code icon for Raw
  Fix spec
  Add copy button to blob header and use icon for Raw button
  Fix archive prefix bug for refs containing dots
  Include routes when loading user projects
  Fixed search not working in issue boards modal
  Document a New Branch feature for Bare Projects
  ...
parents 6fb66321 4bf4612c
...@@ -2,3 +2,4 @@ ...@@ -2,3 +2,4 @@
lib/gitlab/sanitizers/svg/whitelist.rb lib/gitlab/sanitizers/svg/whitelist.rb
lib/gitlab/diff/position_tracer.rb lib/gitlab/diff/position_tracer.rb
app/policies/project_policy.rb app/policies/project_policy.rb
app/models/concerns/relative_positioning.rb
*.js.es6 gitlab-language=javascript
...@@ -7,8 +7,6 @@ cache: ...@@ -7,8 +7,6 @@ cache:
variables: variables:
MYSQL_ALLOW_EMPTY_PASSWORD: "1" MYSQL_ALLOW_EMPTY_PASSWORD: "1"
# retry tests only in CI environment
RSPEC_RETRY_RETRY_COUNT: "3"
RAILS_ENV: "test" RAILS_ENV: "test"
SIMPLECOV: "true" SIMPLECOV: "true"
SETUP_DB: "true" SETUP_DB: "true"
...@@ -60,7 +58,7 @@ stages: ...@@ -60,7 +58,7 @@ stages:
<<: *dedicated-runner <<: *dedicated-runner
<<: *use-db <<: *use-db
script: script:
- JOB_NAME=( $CI_BUILD_NAME ) - JOB_NAME=( $CI_JOB_NAME )
- export CI_NODE_INDEX=${JOB_NAME[1]} - export CI_NODE_INDEX=${JOB_NAME[1]}
- export CI_NODE_TOTAL=${JOB_NAME[2]} - export CI_NODE_TOTAL=${JOB_NAME[2]}
- export KNAPSACK_REPORT_PATH=knapsack/rspec_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_REPORT_PATH=knapsack/rspec_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
...@@ -69,16 +67,18 @@ stages: ...@@ -69,16 +67,18 @@ stages:
- knapsack rspec "--color --format documentation" - knapsack rspec "--color --format documentation"
artifacts: artifacts:
expire_in: 31d expire_in: 31d
when: always
paths: paths:
- knapsack/
- coverage/ - coverage/
- knapsack/
- tmp/capybara/
.spinach-knapsack: &spinach-knapsack .spinach-knapsack: &spinach-knapsack
stage: test stage: test
<<: *dedicated-runner <<: *dedicated-runner
<<: *use-db <<: *use-db
script: script:
- JOB_NAME=( $CI_BUILD_NAME ) - JOB_NAME=( $CI_JOB_NAME )
- export CI_NODE_INDEX=${JOB_NAME[1]} - export CI_NODE_INDEX=${JOB_NAME[1]}
- export CI_NODE_TOTAL=${JOB_NAME[2]} - export CI_NODE_TOTAL=${JOB_NAME[2]}
- export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_REPORT_PATH=knapsack/spinach_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
...@@ -87,9 +87,11 @@ stages: ...@@ -87,9 +87,11 @@ stages:
- knapsack spinach "-r rerun" || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)' - knapsack spinach "-r rerun" || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)'
artifacts: artifacts:
expire_in: 31d expire_in: 31d
when: always
paths: paths:
- knapsack/
- coverage/ - coverage/
- knapsack/
- tmp/capybara/
# Prepare and merge knapsack tests # Prepare and merge knapsack tests
...@@ -178,7 +180,7 @@ spinach 9 10: *spinach-knapsack ...@@ -178,7 +180,7 @@ spinach 9 10: *spinach-knapsack
<<: *dedicated-runner <<: *dedicated-runner
stage: test stage: test
script: script:
- bundle exec $CI_BUILD_NAME - bundle exec $CI_JOB_NAME
rubocop: rubocop:
<<: *ruby-static-analysis <<: *ruby-static-analysis
...@@ -209,7 +211,7 @@ rake ee_compat_check: ...@@ -209,7 +211,7 @@ rake ee_compat_check:
- ee_compat_check/repo/ - ee_compat_check/repo/
- vendor/ruby - vendor/ruby
artifacts: artifacts:
name: "${CI_BUILD_NAME}_${CI_BUILD_REF_NAME}_${CI_BUILD_REF}" name: "${CI_JOB_NAME}_${CI_COMIT_REF_NAME}_${CI_COMMIT_SHA}"
when: on_failure when: on_failure
expire_in: 10d expire_in: 10d
paths: paths:
...@@ -222,6 +224,14 @@ rake db:migrate:reset: ...@@ -222,6 +224,14 @@ rake db:migrate:reset:
script: script:
- bundle exec rake db:migrate:reset - bundle exec rake db:migrate:reset
rake db:rollback:
stage: test
<<: *use-db
<<: *dedicated-runner
script:
- bundle exec rake db:rollback STEP=120
- bundle exec rake db:migrate
rake db:seed_fu: rake db:seed_fu:
stage: test stage: test
<<: *use-db <<: *use-db
...@@ -320,7 +330,7 @@ migration paths: ...@@ -320,7 +330,7 @@ migration paths:
- sed -i 's/localhost/redis/g' config/resque.yml - sed -i 's/localhost/redis/g' config/resque.yml
- bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3 - bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3
- bundle exec rake db:drop db:create db:schema:load db:seed_fu - bundle exec rake db:drop db:create db:schema:load db:seed_fu
- git checkout $CI_BUILD_REF - git checkout $CI_COMMIT_SHA
- source scripts/prepare_build.sh - source scripts/prepare_build.sh
- bundle exec rake db:migrate - bundle exec rake db:migrate
...@@ -358,7 +368,7 @@ lint:javascript:report: ...@@ -358,7 +368,7 @@ lint:javascript:report:
stage: post-test stage: post-test
before_script: [] before_script: []
script: script:
- find app/ spec/ -name '*.js' -or -name '*.js.es6' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files - find app/ spec/ -name '*.js' -exec sed --in-place 's|/\* eslint-disable .*\*/||' {} \; # run report over all files
- yarn run eslint-report || true # ignore exit code - yarn run eslint-report || true # ignore exit code
artifacts: artifacts:
name: eslint-report name: eslint-report
...@@ -384,7 +394,6 @@ trigger_docs: ...@@ -384,7 +394,6 @@ trigger_docs:
- master@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ce
# Notify slack in the end # Notify slack in the end
notify:slack: notify:slack:
stage: post-test stage: post-test
<<: *dedicated-runner <<: *dedicated-runner
...@@ -392,7 +401,7 @@ notify:slack: ...@@ -392,7 +401,7 @@ notify:slack:
SETUP_DB: "false" SETUP_DB: "false"
USE_BUNDLE_INSTALL: "false" USE_BUNDLE_INSTALL: "false"
script: script:
- ./scripts/notify_slack.sh "#development" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/pipelines>" - ./scripts/notify_slack.sh "#development" "Build on \`$CI_COMMIT_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_COMMIT_SHA"/pipelines>"
when: on_failure when: on_failure
only: only:
- master@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ce
......
...@@ -78,6 +78,13 @@ towards getting your issue resolved. ...@@ -78,6 +78,13 @@ towards getting your issue resolved.
Issues and merge requests should be in English and contain appropriate language Issues and merge requests should be in English and contain appropriate language
for audiences of all ages. for audiences of all ages.
If a contributor is no longer actively working on a submitted merge request
we can decide that the merge request will be finished by one of our
[Merge request coaches][team] or close the merge request. We make this decision
based on how important the change is for our product vision. If a Merge request
coach is going to finish the merge request we assign the
~"coach will finish" label.
## Helping others ## Helping others
Please help other GitLab users when you can. The channels people will reach out Please help other GitLab users when you can. The channels people will reach out
...@@ -399,6 +406,12 @@ There are a few rules to get your merge request accepted: ...@@ -399,6 +406,12 @@ There are a few rules to get your merge request accepted:
1. Contains functionality we think other users will benefit from too 1. Contains functionality we think other users will benefit from too
1. Doesn't add configuration options or settings options since they complicate 1. Doesn't add configuration options or settings options since they complicate
making and testing future changes making and testing future changes
1. Changes do not adversely degrade performance.
- Avoid repeated polling of endpoints that require a significant amount of overhead
- Check for N+1 queries via the SQL log or [`QueryRecorder`](https://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html)
- Avoid repeated access of filesystem
1. If you need polling to support real-time features, please use
[polling with ETag caching][polling-etag].
1. Changes after submitting the merge request should be in separate commits 1. Changes after submitting the merge request should be in separate commits
(no squashing). If necessary, you will be asked to squash when the review is (no squashing). If necessary, you will be asked to squash when the review is
over, before merging. over, before merging.
...@@ -434,6 +447,7 @@ the feature you contribute through all of these steps. ...@@ -434,6 +447,7 @@ the feature you contribute through all of these steps.
1. Description explaining the relevancy (see following item) 1. Description explaining the relevancy (see following item)
1. Working and clean code that is commented where needed 1. Working and clean code that is commented where needed
1. Unit and integration tests that pass on the CI server 1. Unit and integration tests that pass on the CI server
1. Performance/scalability implications have been considered, addressed, and tested
1. [Documented][doc-styleguide] in the /doc directory 1. [Documented][doc-styleguide] in the /doc directory
1. Changelog entry added 1. Changelog entry added
1. Reviewed and any concerns are addressed 1. Reviewed and any concerns are addressed
...@@ -540,6 +554,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor ...@@ -540,6 +554,7 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[UX Guide for GitLab]: http://docs.gitlab.com/ce/development/ux_guide/ [UX Guide for GitLab]: http://docs.gitlab.com/ce/development/ux_guide/
[license-finder-doc]: doc/development/licensing.md [license-finder-doc]: doc/development/licensing.md
[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues [GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues
[polling-etag]: https://docs.gitlab.com/ce/development/polling.html
[^1]: Specs other than JavaScript specs are considered backend code. Haml [^1]: Specs other than JavaScript specs are considered backend code. Haml
changes are considered backend code if they include Ruby code other than just changes are considered backend code if they include Ruby code other than just
......
/* eslint-disable no-param-reassign */ const MAX_MESSAGE_LENGTH = 500;
const MESSAGE_CELL_SELECTOR = '.abuse-reports .message';
((global) => { class AbuseReports {
const MAX_MESSAGE_LENGTH = 500;
const MESSAGE_CELL_SELECTOR = '.abuse-reports .message';
class AbuseReports {
constructor() { constructor() {
$(MESSAGE_CELL_SELECTOR).each(this.truncateLongMessage); $(MESSAGE_CELL_SELECTOR).each(this.truncateLongMessage);
$(document) $(document)
...@@ -18,7 +15,7 @@ ...@@ -18,7 +15,7 @@
if (reportMessage.length > MAX_MESSAGE_LENGTH) { if (reportMessage.length > MAX_MESSAGE_LENGTH) {
$messageCellElement.data('original-message', reportMessage); $messageCellElement.data('original-message', reportMessage);
$messageCellElement.data('message-truncated', 'true'); $messageCellElement.data('message-truncated', 'true');
$messageCellElement.text(global.text.truncate(reportMessage, MAX_MESSAGE_LENGTH)); $messageCellElement.text(window.gl.text.truncate(reportMessage, MAX_MESSAGE_LENGTH));
} }
} }
...@@ -34,7 +31,7 @@ ...@@ -34,7 +31,7 @@
$messageCellElement.text(`${originalMessage.substr(0, (MAX_MESSAGE_LENGTH - 3))}...`); $messageCellElement.text(`${originalMessage.substr(0, (MAX_MESSAGE_LENGTH - 3))}...`);
} }
} }
} }
global.AbuseReports = AbuseReports; window.gl = window.gl || {};
})(window.gl || (window.gl = {})); window.gl.AbuseReports = AbuseReports;
...@@ -2,8 +2,7 @@ ...@@ -2,8 +2,7 @@
/* global Pager */ /* global Pager */
/* global Cookies */ /* global Cookies */
((global) => { class Activities {
class Activities {
constructor() { constructor() {
Pager.init(20, true, false, this.updateTooltips); Pager.init(20, true, false, this.updateTooltips);
$('.event-filter-link').on('click', (e) => { $('.event-filter-link').on('click', (e) => {
...@@ -31,7 +30,7 @@ ...@@ -31,7 +30,7 @@
$sender.closest('li').toggleClass('active'); $sender.closest('li').toggleClass('active');
} }
} }
global.Activities = Activities; window.gl = window.gl || {};
})(window.gl || (window.gl = {})); window.gl.Activities = Activities;
/* 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 */
(function() { window.Admin = (function() {
this.Admin = (function() {
function Admin() { function Admin() {
var modal, showBlacklistType; var modal, showBlacklistType;
$('input#user_force_random_password').on('change', function(elem) { $('input#user_force_random_password').on('change', function(elem) {
...@@ -60,5 +59,4 @@ ...@@ -60,5 +59,4 @@
} }
return Admin; return Admin;
})(); })();
}).call(window);
/* eslint-disable func-names, space-before-function-paren, quotes, object-shorthand, camelcase, no-var, comma-dangle, prefer-arrow-callback, quote-props, no-param-reassign, max-len */ /* eslint-disable func-names, space-before-function-paren, quotes, object-shorthand, camelcase, no-var, comma-dangle, prefer-arrow-callback, quote-props, no-param-reassign, max-len */
(function() { var Api = {
var Api = {
groupsPath: "/api/:version/groups.json", groupsPath: "/api/:version/groups.json",
groupPath: "/api/:version/groups/:id.json", groupPath: "/api/:version/groups/:id.json",
namespacesPath: "/api/:version/namespaces.json", namespacesPath: "/api/:version/namespaces.json",
...@@ -52,15 +51,15 @@ ...@@ -52,15 +51,15 @@
}); });
}, },
// Return projects list. Filtered by query // Return projects list. Filtered by query
projects: function(query, order, callback) { projects: function(query, options, callback) {
var url = Api.buildUrl(Api.projectsPath); var url = Api.buildUrl(Api.projectsPath);
return $.ajax({ return $.ajax({
url: url, url: url,
data: { data: $.extend({
search: query, search: query,
order_by: order, per_page: 20,
per_page: 20 membership: true
}, }, options),
dataType: "json" dataType: "json"
}).done(function(projects) { }).done(function(projects) {
return callback(projects); return callback(projects);
...@@ -144,7 +143,6 @@ ...@@ -144,7 +143,6 @@
} }
return url.replace(':version', gon.api_version); return url.replace(':version', gon.api_version);
} }
}; };
window.Api = Api; window.Api = Api;
}).call(window);
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, prefer-arrow-callback, no-var, one-var, one-var-declaration-per-line, no-else-return, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, prefer-arrow-callback, no-var, one-var, one-var-declaration-per-line, no-else-return, max-len */
(function() {
this.Aside = (function() { window.Aside = (function() {
function Aside() { function Aside() {
$(document).off("click", "a.show-aside"); $(document).off("click", "a.show-aside");
$(document).on("click", 'a.show-aside', function(e) { $(document).on("click", 'a.show-aside', function(e) {
...@@ -21,5 +21,4 @@ ...@@ -21,5 +21,4 @@
} }
return Aside; return Aside;
})(); })();
}).call(window);
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, quotes, prefer-template, no-var, one-var, no-unused-vars, one-var-declaration-per-line, no-void, consistent-return, no-empty, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-param-reassign, quotes, prefer-template, no-var, one-var, no-unused-vars, one-var-declaration-per-line, no-void, consistent-return, no-empty, max-len */
(function() {
this.Autosave = (function() { window.Autosave = (function() {
function Autosave(field, key) { function Autosave(field, key) {
this.field = field; this.field = field;
if (key.join != null) { if (key.join != null) {
...@@ -58,5 +58,4 @@ ...@@ -58,5 +58,4 @@
}; };
return Autosave; return Autosave;
})(); })();
}).call(window);
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
import emojiMap from 'emojis/digests.json'; import emojiMap from 'emojis/digests.json';
import emojiAliases from 'emojis/aliases.json'; import emojiAliases from 'emojis/aliases.json';
import { glEmojiTag } from './behaviors/gl_emoji'; import { glEmojiTag } from './behaviors/gl_emoji';
import isEmojiNameValid from './behaviors/gl_emoji/is_emoji_name_valid';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const requestAnimationFrame = window.requestAnimationFrame || const requestAnimationFrame = window.requestAnimationFrame ||
...@@ -454,14 +455,21 @@ AwardsHandler.prototype.normalizeEmojiName = function normalizeEmojiName(emoji) ...@@ -454,14 +455,21 @@ AwardsHandler.prototype.normalizeEmojiName = function normalizeEmojiName(emoji)
AwardsHandler AwardsHandler
.prototype .prototype
.addEmojiToFrequentlyUsedList = function addEmojiToFrequentlyUsedList(emoji) { .addEmojiToFrequentlyUsedList = function addEmojiToFrequentlyUsedList(emoji) {
const frequentlyUsedEmojis = this.getFrequentlyUsedEmojis(); if (isEmojiNameValid(emoji)) {
frequentlyUsedEmojis.push(emoji); this.frequentlyUsedEmojis = _.uniq(this.getFrequentlyUsedEmojis().concat(emoji));
Cookies.set('frequently_used_emojis', frequentlyUsedEmojis.join(','), { expires: 365 }); Cookies.set('frequently_used_emojis', this.frequentlyUsedEmojis.join(','), { expires: 365 });
}
}; };
AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmojis() { AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmojis() {
const frequentlyUsedEmojis = (Cookies.get('frequently_used_emojis') || '').split(','); return this.frequentlyUsedEmojis || (() => {
return _.compact(_.uniq(frequentlyUsedEmojis)); const frequentlyUsedEmojis = _.uniq((Cookies.get('frequently_used_emojis') || '').split(','));
this.frequentlyUsedEmojis = frequentlyUsedEmojis.filter(
inputName => isEmojiNameValid(inputName),
);
return this.frequentlyUsedEmojis;
})();
}; };
AwardsHandler.prototype.setupSearch = function setupSearch() { AwardsHandler.prototype.setupSearch = function setupSearch() {
......
...@@ -13,9 +13,14 @@ function emojiImageTag(name, src) { ...@@ -13,9 +13,14 @@ function emojiImageTag(name, src) {
} }
function assembleFallbackImageSrc(inputName) { function assembleFallbackImageSrc(inputName) {
const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ? let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
emojiAliases[inputName] : inputName; emojiAliases[inputName] : inputName;
const emojiInfo = emojiMap[name]; let emojiInfo = emojiMap[name];
// Fallback to question mark for unknown emojis
if (!emojiInfo) {
name = 'grey_question';
emojiInfo = emojiMap[name];
}
const fallbackImageSrc = `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${emojiInfo.digest}.png`; const fallbackImageSrc = `${gon.asset_host || ''}${gon.relative_url_root || ''}/assets/emoji/${name}-${emojiInfo.digest}.png`;
return fallbackImageSrc; return fallbackImageSrc;
...@@ -26,9 +31,15 @@ const glEmojiTagDefaults = { ...@@ -26,9 +31,15 @@ const glEmojiTagDefaults = {
}; };
function glEmojiTag(inputName, options) { function glEmojiTag(inputName, options) {
const opts = Object.assign({}, glEmojiTagDefaults, options); const opts = Object.assign({}, glEmojiTagDefaults, options);
const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ? let name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
emojiAliases[inputName] : inputName; emojiAliases[inputName] : inputName;
const emojiInfo = emojiMap[name]; let emojiInfo = emojiMap[name];
// Fallback to question mark for unknown emojis
if (!emojiInfo) {
name = 'grey_question';
emojiInfo = emojiMap[name];
}
const fallbackImageSrc = assembleFallbackImageSrc(name); const fallbackImageSrc = assembleFallbackImageSrc(name);
const fallbackSpriteClass = `emoji-${name}`; const fallbackSpriteClass = `emoji-${name}`;
......
import emojiMap from 'emojis/digests.json';
import emojiAliases from 'emojis/aliases.json';
function isEmojiNameValid(inputName) {
const name = Object.prototype.hasOwnProperty.call(emojiAliases, inputName) ?
emojiAliases[inputName] : inputName;
return name && emojiMap[name];
}
export default isEmojiNameValid;
...@@ -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'); import '../commons/bootstrap';
// //
// ### 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'); import '../commons/bootstrap';
// //
// ### Example Markup // ### Example Markup
......
...@@ -21,8 +21,13 @@ ...@@ -21,8 +21,13 @@
// %a.js-toggle-button // %a.js-toggle-button
// %div.js-toggle-content // %div.js-toggle-content
// //
$('body').on('click', '.js-toggle-button', function() { $('body').on('click', '.js-toggle-button', function(e) {
toggleContainer($(this).closest('.js-toggle-container')); toggleContainer($(this).closest('.js-toggle-container'));
const targetTag = e.target.tagName.toLowerCase();
if (targetTag === 'a' || targetTag === 'button') {
e.preventDefault();
}
}); });
// If we're accessing a permalink, ensure it is not inside a // If we're accessing a permalink, ensure it is not inside a
......
const lineNumberRe = /^L[0-9]+/;
const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => {
const hash = gl.utils.getLocationHash();
if (hash && lineNumberRe.test(hash)) {
const hashUrlString = `#${hash}`;
[].concat(Array.prototype.slice.call(linksToUpdate)).forEach((permalinkButton) => {
const baseHref = permalinkButton.getAttribute('data-original-href') || (() => {
const href = permalinkButton.getAttribute('href');
permalinkButton.setAttribute('data-original-href', href);
return href;
})();
permalinkButton.setAttribute('href', `${baseHref}${hashUrlString}`);
});
}
};
function BlobLinePermalinkUpdater(blobContentHolder, lineNumberSelector, elementsToUpdate) {
const updateBlameAndBlobPermalinkCb = () => {
// Wait for the hash to update from the LineHighlighter callback
setTimeout(() => {
updateLineNumbersOnBlobPermalinks(elementsToUpdate);
}, 0);
};
blobContentHolder.addEventListener('click', (e) => {
if (e.target.matches(lineNumberSelector)) {
updateBlameAndBlobPermalinkCb();
}
});
updateBlameAndBlobPermalinkCb();
}
export default BlobLinePermalinkUpdater;
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
/* global Vue */ /* global Vue */
/* global BoardService */ /* global BoardService */
import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub';
window.Vue = require('vue'); window.Vue = require('vue');
window.Vue.use(require('vue-resource')); window.Vue.use(require('vue-resource'));
require('./models/issue'); require('./models/issue');
...@@ -59,6 +62,14 @@ $(() => { ...@@ -59,6 +62,14 @@ $(() => {
}, },
created () { created () {
gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId); gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId);
this.filterManager = new FilteredSearchBoards(Store.filter, true);
// Listen for updateTokens event
eventHub.$on('updateTokens', this.updateTokens);
},
beforeDestroy() {
eventHub.$off('updateTokens', this.updateTokens);
}, },
mounted () { mounted () {
Store.disabled = this.disabled; Store.disabled = this.disabled;
...@@ -77,11 +88,16 @@ $(() => { ...@@ -77,11 +88,16 @@ $(() => {
Store.addBlankState(); Store.addBlankState();
this.loading = false; this.loading = false;
}); });
},
methods: {
updateTokens() {
this.filterManager.updateTokens();
} }
},
}); });
gl.IssueBoardsSearch = new Vue({ gl.IssueBoardsSearch = new Vue({
el: document.getElementById('js-boards-search'), el: document.getElementById('js-add-list'),
data: { data: {
filters: Store.state.filters filters: Store.state.filters
}, },
......
...@@ -28,16 +28,16 @@ require('./board_list'); ...@@ -28,16 +28,16 @@ require('./board_list');
data () { data () {
return { return {
detailIssue: Store.detail, detailIssue: Store.detail,
filters: Store.state.filters, filter: Store.filter,
}; };
}, },
watch: { watch: {
filters: { filter: {
handler () { handler() {
this.list.page = 1; this.list.page = 1;
this.list.getIssues(true); this.list.getIssues(true);
}, },
deep: true deep: true,
}, },
detailIssue: { detailIssue: {
handler () { handler () {
......
...@@ -17,7 +17,8 @@ export default { ...@@ -17,7 +17,8 @@ export default {
:list="list" :list="list"
:issue="issue" :issue="issue"
:issue-link-base="issueLinkBase" :issue-link-base="issueLinkBase"
:root-path="rootPath" /> :root-path="rootPath"
:update-filters="true" />
</li> </li>
`, `,
components: { components: {
......
/* global Vue */ /* global Vue */
import eventHub from '../eventhub';
(() => { (() => {
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
...@@ -23,6 +25,11 @@ ...@@ -23,6 +25,11 @@
type: String, type: String,
required: true, required: true,
}, },
updateFilters: {
type: Boolean,
required: false,
default: false,
},
}, },
methods: { methods: {
showLabel(label) { showLabel(label) {
...@@ -31,29 +38,25 @@ ...@@ -31,29 +38,25 @@
return !this.list.label || label.id !== this.list.label.id; return !this.list.label || label.id !== this.list.label.id;
}, },
filterByLabel(label, e) { filterByLabel(label, e) {
let labelToggleText = label.title; if (!this.updateFilters) return;
const labelIndex = Store.state.filters.label_name.indexOf(label.title);
const filterPath = gl.issueBoards.BoardsStore.filter.path.split('&');
const labelTitle = encodeURIComponent(label.title);
const param = `label_name[]=${labelTitle}`;
const labelIndex = filterPath.indexOf(param);
$(e.currentTarget).tooltip('hide'); $(e.currentTarget).tooltip('hide');
if (labelIndex === -1) { if (labelIndex === -1) {
Store.state.filters.label_name.push(label.title); filterPath.push(param);
$('.labels-filter').prepend(`<input type="hidden" name="label_name[]" value="${label.title}" />`);
} else { } else {
Store.state.filters.label_name.splice(labelIndex, 1); filterPath.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; gl.issueBoards.BoardsStore.filter.path = filterPath.join('&');
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(); Store.updateFiltersUrl();
eventHub.$emit('updateTokens');
}, },
labelStyle(label) { labelStyle(label) {
return { return {
......
/* global Vue */ import FilteredSearchBoards from '../../filtered_search_boards';
const userFilter = require('./filters/user'); import FilteredSearchContainer from '../../../filtered_search/container';
const milestoneFilter = require('./filters/milestone');
const labelFilter = require('./filters/label');
module.exports = Vue.extend({ export default {
name: 'modal-filters', name: 'modal-filters',
props: { props: {
projectId: { store: {
type: Number, type: Object,
required: true, required: true,
}, },
milestonePath: {
type: String,
required: true,
},
labelPath: {
type: String,
required: true,
}, },
mounted() {
FilteredSearchContainer.container = this.$el;
this.filteredSearch = new FilteredSearchBoards(this.store);
this.filteredSearch.removeTokens();
}, },
destroyed() { beforeDestroy() {
gl.issueBoards.ModalStore.setDefaultFilter(); this.filteredSearch.cleanup();
}, FilteredSearchContainer.container = document;
components: { this.store.path = '';
userFilter,
milestoneFilter,
labelFilter,
}, },
template: ` template: '#js-board-modal-filter',
<div class="modal-filters"> };
<user-filter
dropdown-class-name="dropdown-menu-author"
toggle-class-name="js-user-search js-author-search"
toggle-label="Author"
field-name="author_id"
:project-id="projectId"></user-filter>
<user-filter
dropdown-class-name="dropdown-menu-author"
toggle-class-name="js-assignee-search"
toggle-label="Assignee"
field-name="assignee_id"
:null-user="true"
:project-id="projectId"></user-filter>
<milestone-filter :milestone-path="milestonePath"></milestone-filter>
<label-filter :label-path="labelPath"></label-filter>
</div>
`,
});
/* eslint-disable no-new */
/* global Vue */
/* global LabelsSelect */
module.exports = Vue.extend({
name: 'filter-label',
props: {
labelPath: {
type: String,
required: true,
},
},
mounted() {
new LabelsSelect(this.$refs.dropdown);
},
template: `
<div class="dropdown">
<button
class="dropdown-menu-toggle js-label-select js-multiselect js-extra-options"
type="button"
data-toggle="dropdown"
data-show-any="true"
data-show-no="true"
:data-labels="labelPath"
ref="dropdown">
<span class="dropdown-toggle-text">
Label
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-select dropdown-menu-paging dropdown-menu-labels dropdown-menu-selectable">
<div class="dropdown-title">
Filter by label
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
placeholder="Search"
autocomplete="off" />
<i class="fa fa-search dropdown-input-search"></i>
<i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`,
});
/* eslint-disable no-new */
/* global Vue */
/* global MilestoneSelect */
module.exports = Vue.extend({
name: 'filter-milestone',
props: {
milestonePath: {
type: String,
required: true,
},
},
mounted() {
new MilestoneSelect(null, this.$refs.dropdown);
},
template: `
<div class="dropdown">
<button
class="dropdown-menu-toggle js-milestone-select"
type="button"
data-toggle="dropdown"
data-show-any="true"
data-show-upcoming="true"
data-field-name="milestone_title"
:data-milestones="milestonePath"
ref="dropdown">
<span class="dropdown-toggle-text">
Milestone
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div class="dropdown-menu dropdown-select dropdown-menu-selectable dropdown-menu-milestone">
<div class="dropdown-title">
<span>Filter by milestone</span>
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
placeholder="Search milestones"
autocomplete="off" />
<i class="fa fa-search dropdown-input-search"></i>
<i role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`,
});
/* eslint-disable no-new */
/* global Vue */
/* global UsersSelect */
module.exports = Vue.extend({
name: 'filter-user',
props: {
toggleClassName: {
type: String,
required: true,
},
dropdownClassName: {
type: String,
required: false,
default: '',
},
toggleLabel: {
type: String,
required: true,
},
fieldName: {
type: String,
required: true,
},
nullUser: {
type: Boolean,
required: false,
default: false,
},
projectId: {
type: Number,
required: true,
},
},
mounted() {
new UsersSelect(null, this.$refs.dropdown);
},
computed: {
currentUsername() {
return gon.current_username;
},
dropdownTitle() {
return `Filter by ${this.toggleLabel.toLowerCase()}`;
},
inputPlaceholder() {
return `Search ${this.toggleLabel.toLowerCase()}`;
},
},
template: `
<div class="dropdown">
<button
class="dropdown-menu-toggle js-user-search"
:class="toggleClassName"
type="button"
data-toggle="dropdown"
data-current-user="true"
:data-any-user="'Any ' + toggleLabel"
:data-null-user="nullUser"
:data-field-name="fieldName"
:data-project-id="projectId"
:data-first-user="currentUsername"
ref="dropdown">
<span class="dropdown-toggle-text">
{{ toggleLabel }}
</span>
<i class="fa fa-chevron-down"></i>
</button>
<div
class="dropdown-menu dropdown-select dropdown-menu-user dropdown-menu-selectable"
:class="dropdownClassName">
<div class="dropdown-title">
{{ dropdownTitle }}
<button
class="dropdown-title-button dropdown-menu-close"
aria-label="Close"
type="button">
<i class="fa fa-times dropdown-menu-close-icon"></i>
</button>
</div>
<div class="dropdown-input">
<input
type="search"
class="dropdown-input-field"
autocomplete="off"
:placeholder="inputPlaceholder" />
<i class="fa fa-search dropdown-input-search"></i>
<i
role="button"
class="fa fa-times dropdown-input-clear js-dropdown-input-clear">
</i>
</div>
<div class="dropdown-content"></div>
<div class="dropdown-loading"><i class="fa fa-spinner fa-spin"></i></div>
</div>
</div>
`,
});
/* global Vue */ import Vue from 'vue';
import modalFilters from './filters';
require('./tabs'); require('./tabs');
const modalFilters = require('./filters');
(() => { (() => {
const ModalStore = gl.issueBoards.ModalStore; const ModalStore = gl.issueBoards.ModalStore;
...@@ -66,16 +67,7 @@ const modalFilters = require('./filters'); ...@@ -66,16 +67,7 @@ const modalFilters = require('./filters');
<div <div
class="add-issues-search append-bottom-10" class="add-issues-search append-bottom-10"
v-if="showSearch"> v-if="showSearch">
<modal-filters <modal-filters :store="filter" />
:project-id="projectId"
:milestone-path="milestonePath"
:label-path="labelPath">
</modal-filters>
<input
placeholder="Search issues..."
class="form-control"
type="search"
v-model="searchTerm" />
<button <button
type="button" type="button"
class="btn btn-success btn-inverted prepend-left-10" class="btn btn-success btn-inverted prepend-left-10"
......
/* global Vue */ /* global Vue */
/* global ListIssue */ /* global ListIssue */
import queryData from '../../utils/query_data';
require('./header'); require('./header');
require('./list'); require('./list');
...@@ -47,9 +48,6 @@ require('./empty_state'); ...@@ -47,9 +48,6 @@ require('./empty_state');
page() { page() {
this.loadIssues(); this.loadIssues();
}, },
searchTerm() {
this.searchOperation();
},
showAddIssuesModal() { showAddIssuesModal() {
if (this.showAddIssuesModal && !this.issues.length) { if (this.showAddIssuesModal && !this.issues.length) {
this.loading = true; this.loading = true;
...@@ -72,19 +70,13 @@ require('./empty_state'); ...@@ -72,19 +70,13 @@ require('./empty_state');
}, },
}, },
methods: { methods: {
searchOperation: _.debounce(function searchOperationDebounce() {
this.loadIssues(true);
}, 500),
loadIssues(clearIssues = false) { loadIssues(clearIssues = false) {
if (!this.showAddIssuesModal) return false; if (!this.showAddIssuesModal) return false;
const queryData = Object.assign({}, this.filter, { return gl.boardService.getBacklog(queryData(this.filter.path, {
search: this.searchTerm,
page: this.page, page: this.page,
per: this.perPage, per: this.perPage,
}); })).then((res) => {
return gl.boardService.getBacklog(queryData).then((res) => {
const data = res.json(); const data = res.json();
if (clearIssues) { if (clearIssues) {
......
import Vue from 'vue';
export default new Vue();
/* eslint-disable class-methods-use-this */
import FilteredSearchContainer from '../filtered_search/container';
export default class FilteredSearchBoards extends gl.FilteredSearchManager {
constructor(store, updateUrl = false) {
super('boards');
this.store = store;
this.updateUrl = updateUrl;
// Issue boards is slightly different, we handle all the requests async
// instead or reloading the page, we just re-fire the list ajax requests
this.isHandledAsync = true;
}
updateObject(path) {
this.store.path = path.substr(1);
if (this.updateUrl) {
gl.issueBoards.BoardsStore.updateFiltersUrl();
}
}
removeTokens() {
const tokens = FilteredSearchContainer.container.querySelectorAll('.js-visual-token');
// Remove all the tokens as they will be replaced by the search manager
[].forEach.call(tokens, (el) => {
el.parentNode.removeChild(el);
});
}
updateTokens() {
this.removeTokens();
this.loadSearchParamsFromURL();
// Get the placeholder back if search is empty
this.filteredSearchInput.dispatchEvent(new Event('input'));
}
}
/* eslint-disable space-before-function-paren, no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len, no-unused-vars */ /* eslint-disable space-before-function-paren, no-underscore-dangle, class-methods-use-this, consistent-return, no-shadow, no-param-reassign, max-len, no-unused-vars */
/* global ListIssue */ /* global ListIssue */
/* global ListLabel */ /* global ListLabel */
import queryData from '../utils/query_data';
class List { class List {
constructor (obj) { constructor (obj) {
...@@ -10,7 +11,6 @@ class List { ...@@ -10,7 +11,6 @@ class List {
this.title = obj.title; this.title = obj.title;
this.type = obj.list_type; this.type = obj.list_type;
this.preset = ['done', 'blank'].indexOf(this.type) > -1; this.preset = ['done', 'blank'].indexOf(this.type) > -1;
this.filters = gl.issueBoards.BoardsStore.state.filters;
this.page = 1; this.page = 1;
this.loading = true; this.loading = true;
this.loadingMore = false; this.loadingMore = false;
...@@ -65,12 +65,9 @@ class List { ...@@ -65,12 +65,9 @@ class List {
} }
getIssues (emptyIssues = true) { getIssues (emptyIssues = true) {
const filters = this.filters; const data = queryData(gl.issueBoards.BoardsStore.filter.path, { page: this.page });
const data = { page: this.page };
Object.keys(filters).forEach((key) => { data[key] = filters[key]; }); if (this.label && data.label_name) {
if (this.label) {
data.label_name = data.label_name.filter(label => label !== this.label.title); data.label_name = data.label_name.filter(label => label !== this.label.title);
} }
......
...@@ -8,6 +8,9 @@ ...@@ -8,6 +8,9 @@
gl.issueBoards.BoardsStore = { gl.issueBoards.BoardsStore = {
disabled: false, disabled: false,
filter: {
path: '',
},
state: {}, state: {},
detail: { detail: {
issue: {} issue: {}
...@@ -18,13 +21,7 @@ ...@@ -18,13 +21,7 @@
}, },
create () { create () {
this.state.lists = []; this.state.lists = [];
this.state.filters = { this.filter.path = gl.utils.getUrlParamsArray().join('&');
author_id: gl.utils.getParameterValues('author_id')[0],
assignee_id: gl.utils.getParameterValues('assignee_id')[0],
milestone_title: gl.utils.getParameterValues('milestone_title')[0],
label_name: gl.utils.getParameterValues('label_name[]'),
search: ''
};
}, },
addList (listObj) { addList (listObj) {
const list = new List(listObj); const list = new List(listObj);
...@@ -123,7 +120,7 @@ ...@@ -123,7 +120,7 @@
})[0]; })[0];
}, },
updateFiltersUrl () { updateFiltersUrl () {
history.pushState(null, null, `?${$.param(this.state.filters)}`); history.pushState(null, null, `?${this.filter.path}`);
} }
}; };
})(); })();
...@@ -17,17 +17,9 @@ ...@@ -17,17 +17,9 @@
loadingNewPage: false, loadingNewPage: false,
page: 1, page: 1,
perPage: 50, perPage: 50,
}; filter: {
path: '',
this.setDefaultFilter(); },
}
setDefaultFilter() {
this.store.filter = {
author_id: '',
assignee_id: '',
milestone_title: '',
label_name: [],
}; };
} }
......
export default (path, extraData) => path.split('&').reduce((dataParam, filterParam) => {
if (filterParam === '') return dataParam;
const data = dataParam;
const paramSplit = filterParam.split('=');
const paramKeyNormalized = paramSplit[0].replace('[]', '');
const isArray = paramSplit[0].indexOf('[]');
const value = decodeURIComponent(paramSplit[1]).replace(/\+/g, ' ');
if (isArray !== -1) {
if (!data[paramKeyNormalized]) {
data[paramKeyNormalized] = [];
}
data[paramKeyNormalized].push(value);
} else {
data[paramKeyNormalized] = value;
}
return data;
}, extraData);
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, quotes, no-shadow, prefer-arrow-callback, prefer-template, consistent-return, no-return-assign, new-parens, no-param-reassign, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, quotes, no-shadow, prefer-arrow-callback, prefer-template, consistent-return, no-return-assign, new-parens, no-param-reassign, max-len */
(function() { var Breakpoints = (function() {
var Breakpoints = (function() {
var BreakpointInstance, instance; var BreakpointInstance, instance;
function Breakpoints() {} function Breakpoints() {}
...@@ -60,13 +59,8 @@ ...@@ -60,13 +59,8 @@
}; };
return Breakpoints; return Breakpoints;
})(); })();
$((function(_this) { $(() => { window.bp = Breakpoints.get(); });
return function() {
return _this.bp = Breakpoints.get();
};
})(this));
window.Breakpoints = Breakpoints; window.Breakpoints = Breakpoints;
}).call(window);
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, no-else-return, object-shorthand, comma-dangle, max-len */ /* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, quotes, no-else-return, object-shorthand, comma-dangle, max-len */
(function() {
$(function() { $(function() {
var previewPath; var previewPath;
$('input#broadcast_message_color').on('input', function() { $('input#broadcast_message_color').on('input', function() {
var previewColor; var previewColor;
...@@ -30,5 +30,4 @@ ...@@ -30,5 +30,4 @@
}); });
} }
}); });
}); });
}).call(window);
/* 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 */
(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); }; }; var AUTO_SCROLL_OFFSET = 75;
var AUTO_SCROLL_OFFSET = 75; var DOWN_BUILD_TRACE = '#down-build-trace';
var DOWN_BUILD_TRACE = '#down-build-trace';
this.Build = (function() { window.Build = (function() {
Build.timeout = null; Build.timeout = null;
Build.state = null; Build.state = null;
...@@ -281,5 +280,4 @@ ...@@ -281,5 +280,4 @@
}; };
return Build; return Build;
})(); })();
}).call(window);
/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, no-return-assign, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-unused-vars, no-return-assign, max-len */
(function() {
this.BuildArtifacts = (function() { window.BuildArtifacts = (function() {
function BuildArtifacts() { function BuildArtifacts() {
this.disablePropagation(); this.disablePropagation();
this.setupEntryClick(); this.setupEntryClick();
...@@ -22,5 +22,4 @@ ...@@ -22,5 +22,4 @@
}; };
return BuildArtifacts; return BuildArtifacts;
})(); })();
}).call(window);
(() => {
window.gl = window.gl || {};
class CILintEditor { window.gl = window.gl || {};
class CILintEditor {
constructor() { constructor() {
this.editor = window.ace.edit('ci-editor'); this.editor = window.ace.edit('ci-editor');
this.textarea = document.querySelector('#content'); this.textarea = document.querySelector('#content');
...@@ -12,7 +12,6 @@ ...@@ -12,7 +12,6 @@
this.textarea.value = content; this.textarea.value = content;
}); });
} }
} }
gl.CILintEditor = CILintEditor; gl.CILintEditor = CILintEditor;
})();
/* eslint-disable func-names, space-before-function-paren, wrap-iife */ /* eslint-disable func-names, space-before-function-paren, wrap-iife */
/* global CommitFile */ /* global CommitFile */
(function() { window.Commit = (function() {
this.Commit = (function() {
function Commit() { function Commit() {
$('.files .diff-file').each(function() { $('.files .diff-file').each(function() {
return new CommitFile(this); return new CommitFile(this);
...@@ -10,5 +9,4 @@ ...@@ -10,5 +9,4 @@
} }
return Commit; return Commit;
})(); })();
}).call(window);
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, max-len, prefer-arrow-callback */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, consistent-return, no-return-assign, no-param-reassign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, prefer-template, object-shorthand, comma-dangle, max-len, prefer-arrow-callback */
/* global Pager */ /* global Pager */
(function() { window.CommitsList = (function() {
this.CommitsList = (function() {
var CommitsList = {}; var CommitsList = {};
CommitsList.timer = null; CommitsList.timer = null;
...@@ -64,5 +63,4 @@ ...@@ -64,5 +63,4 @@
}; };
return CommitsList; return CommitsList;
})(); })();
}).call(window);
import 'jquery'; import $ from 'jquery';
// bootstrap jQuery plugins // bootstrap jQuery plugins
import 'bootstrap-sass/assets/javascripts/bootstrap/affix'; import 'bootstrap-sass/assets/javascripts/bootstrap/affix';
...@@ -8,3 +8,9 @@ import 'bootstrap-sass/assets/javascripts/bootstrap/modal'; ...@@ -8,3 +8,9 @@ import 'bootstrap-sass/assets/javascripts/bootstrap/modal';
import 'bootstrap-sass/assets/javascripts/bootstrap/tab'; import 'bootstrap-sass/assets/javascripts/bootstrap/tab';
import 'bootstrap-sass/assets/javascripts/bootstrap/transition'; import 'bootstrap-sass/assets/javascripts/bootstrap/transition';
import 'bootstrap-sass/assets/javascripts/bootstrap/tooltip'; import 'bootstrap-sass/assets/javascripts/bootstrap/tooltip';
// custom jQuery functions
$.fn.extend({
disable() { return $(this).attr('disabled', 'disabled').addClass('disabled'); },
enable() { return $(this).removeAttr('disabled').removeClass('disabled'); },
});
import './polyfills';
import './jquery'; import './jquery';
import './bootstrap'; import './bootstrap';
// ECMAScript polyfills
import 'core-js/fn/array/find';
import 'core-js/fn/object/assign';
import 'core-js/fn/promise';
import 'core-js/fn/string/code-point-at';
import 'core-js/fn/string/from-code-point';
// Browser polyfills
import './polyfills/custom_event';
import './polyfills/element';
if (typeof window.CustomEvent !== 'function') {
window.CustomEvent = function CustomEvent(event, params) {
const evt = document.createEvent('CustomEvent');
const evtParams = params || { bubbles: false, cancelable: false, detail: undefined };
evt.initCustomEvent(event, evtParams.bubbles, evtParams.cancelable, evtParams.detail);
return evt;
};
window.CustomEvent.prototype = Event;
}
/* global Element */ Element.prototype.closest = Element.prototype.closest ||
/* eslint-disable consistent-return, max-len, no-empty, func-names */ function closest(selector, selectedElement = this) {
if (!selectedElement) return null;
Element.prototype.closest = Element.prototype.closest || function closest(selector, selectedElement = this) { return selectedElement.matches(selector) ?
if (!selectedElement) return; selectedElement :
return selectedElement.matches(selector) ? selectedElement : Element.prototype.closest(selector, selectedElement.parentElement); Element.prototype.closest(selector, selectedElement.parentElement);
}; };
Element.prototype.matches = Element.prototype.matches || Element.prototype.matches = Element.prototype.matches ||
Element.prototype.matchesSelector || Element.prototype.matchesSelector ||
...@@ -12,9 +12,9 @@ Element.prototype.matches = Element.prototype.matches || ...@@ -12,9 +12,9 @@ Element.prototype.matches = Element.prototype.matches ||
Element.prototype.msMatchesSelector || Element.prototype.msMatchesSelector ||
Element.prototype.oMatchesSelector || Element.prototype.oMatchesSelector ||
Element.prototype.webkitMatchesSelector || Element.prototype.webkitMatchesSelector ||
function (s) { function matches(selector) {
const matches = (this.document || this.ownerDocument).querySelectorAll(s); const elms = (this.document || this.ownerDocument).querySelectorAll(selector);
let i = matches.length - 1; let i = elms.length - 1;
while (i >= 0 && matches.item(i) !== this) { i -= 1; } while (i >= 0 && elms.item(i) !== this) { i -= 1; }
return i > -1; return i > -1;
}; };
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, object-shorthand, consistent-return, no-unused-vars, comma-dangle, vars-on-top, prefer-template, max-len */
(function() {
this.Compare = (function() { window.Compare = (function() {
function Compare(opts) { function Compare(opts) {
this.opts = opts; this.opts = opts;
this.source_loading = $(".js-source-loading"); this.source_loading = $(".js-source-loading");
...@@ -87,5 +87,4 @@ ...@@ -87,5 +87,4 @@
}; };
return Compare; return Compare;
})(); })();
}).call(window);
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, wrap-iife, max-len */ /* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, object-shorthand, comma-dangle, prefer-arrow-callback, no-else-return, newline-per-chained-call, wrap-iife, max-len */
(function() { window.CompareAutocomplete = (function() {
this.CompareAutocomplete = (function() {
function CompareAutocomplete() { function CompareAutocomplete() {
this.initDropdown(); this.initDropdown();
} }
...@@ -19,7 +18,8 @@ ...@@ -19,7 +18,8 @@
return $.ajax({ return $.ajax({
url: $dropdown.data('refs-url'), url: $dropdown.data('refs-url'),
data: { data: {
ref: $dropdown.data('ref') ref: $dropdown.data('ref'),
search: term,
} }
}).done(function(refs) { }).done(function(refs) {
return callback(refs); return callback(refs);
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
}, },
selectable: true, selectable: true,
filterable: true, filterable: true,
filterByText: true, filterRemote: true,
fieldName: $dropdown.data('field-name'), fieldName: $dropdown.data('field-name'),
filterInput: 'input[type="search"]', filterInput: 'input[type="search"]',
renderRow: function(ref) { renderRow: function(ref) {
...@@ -65,5 +65,4 @@ ...@@ -65,5 +65,4 @@
}; };
return CompareAutocomplete; return CompareAutocomplete;
})(); })();
}).call(window);
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, camelcase, one-var-declaration-per-line, no-else-return, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, camelcase, one-var-declaration-per-line, no-else-return, max-len */
(function() {
this.ConfirmDangerModal = (function() { window.ConfirmDangerModal = (function() {
function ConfirmDangerModal(form, text) { function ConfirmDangerModal(form, text) {
var project_path, submit; var project_path, submit;
this.form = form; this.form = form;
...@@ -27,5 +27,4 @@ ...@@ -27,5 +27,4 @@
} }
return ConfirmDangerModal; return ConfirmDangerModal;
})(); })();
}).call(window);
/* 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 */
require('./lib/utils/common_utils'); require('./lib/utils/common_utils');
(() => { const gfmRules = {
const gfmRules = {
// The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert // The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
// GitLab Flavored Markdown (GFM) to HTML. // GitLab Flavored Markdown (GFM) to HTML.
// These handlers consequently convert that same HTML to GFM to be copied to the clipboard. // These handlers consequently convert that same HTML to GFM to be copied to the clipboard.
...@@ -120,10 +118,10 @@ require('./lib/utils/common_utils'); ...@@ -120,10 +118,10 @@ require('./lib/utils/common_utils');
}, },
SyntaxHighlightFilter: { SyntaxHighlightFilter: {
'pre.code.highlight'(el, t) { 'pre.code.highlight'(el, t) {
const text = t.trim(); const text = t.trimRight();
let lang = el.getAttribute('lang'); let lang = el.getAttribute('lang');
if (lang === 'plaintext') { if (!lang || lang === 'plaintext') {
lang = ''; lang = '';
} }
...@@ -159,7 +157,7 @@ require('./lib/utils/common_utils'); ...@@ -159,7 +157,7 @@ require('./lib/utils/common_utils');
const backticks = Array(backtickCount + 1).join('`'); const backticks = Array(backtickCount + 1).join('`');
const spaceOrNoSpace = backtickCount > 1 ? ' ' : ''; const spaceOrNoSpace = backtickCount > 1 ? ' ' : '';
return backticks + spaceOrNoSpace + text + spaceOrNoSpace + backticks; return backticks + spaceOrNoSpace + text.trim() + spaceOrNoSpace + backticks;
}, },
'blockquote'(el, text) { 'blockquote'(el, text) {
return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n'); return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
...@@ -271,32 +269,33 @@ require('./lib/utils/common_utils'); ...@@ -271,32 +269,33 @@ require('./lib/utils/common_utils');
return `| ${cells.join(' | ')} |`; return `| ${cells.join(' | ')} |`;
}, },
}, },
}; };
class CopyAsGFM { class CopyAsGFM {
constructor() { constructor() {
$(document).on('copy', '.md, .wiki', this.handleCopy); $(document).on('copy', '.md, .wiki', (e) => { this.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
$(document).on('paste', '.js-gfm-input', this.handlePaste); $(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { this.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
$(document).on('paste', '.js-gfm-input', this.pasteGFM.bind(this));
} }
handleCopy(e) { copyAsGFM(e, transformer) {
const clipboardData = e.originalEvent.clipboardData; const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return; if (!clipboardData) return;
const documentFragment = window.gl.utils.getSelectedFragment(); const documentFragment = window.gl.utils.getSelectedFragment();
if (!documentFragment) return; if (!documentFragment) return;
// If the documentFragment contains more than just Markdown, don't copy as GFM. const el = transformer(documentFragment.cloneNode(true));
if (documentFragment.querySelector('.md, .wiki')) return; if (!el) return;
e.preventDefault(); e.preventDefault();
clipboardData.setData('text/plain', documentFragment.textContent); e.stopPropagation();
const gfm = CopyAsGFM.nodeToGFM(documentFragment); clipboardData.setData('text/plain', el.textContent);
clipboardData.setData('text/x-gfm', gfm); clipboardData.setData('text/x-gfm', CopyAsGFM.nodeToGFM(el));
} }
handlePaste(e) { pasteGFM(e) {
const clipboardData = e.originalEvent.clipboardData; const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return; if (!clipboardData) return;
...@@ -308,7 +307,47 @@ require('./lib/utils/common_utils'); ...@@ -308,7 +307,47 @@ require('./lib/utils/common_utils');
window.gl.utils.insertText(e.target, gfm); window.gl.utils.insertText(e.target, gfm);
} }
static transformGFMSelection(documentFragment) {
// If the documentFragment contains more than just Markdown, don't copy as GFM.
if (documentFragment.querySelector('.md, .wiki')) return null;
return documentFragment;
}
static transformCodeSelection(documentFragment) {
const lineEls = documentFragment.querySelectorAll('.line');
let codeEl;
if (lineEls.length > 1) {
codeEl = document.createElement('pre');
codeEl.className = 'code highlight';
const lang = lineEls[0].getAttribute('lang');
if (lang) {
codeEl.setAttribute('lang', lang);
}
} else {
codeEl = document.createElement('code');
}
if (lineEls.length > 0) {
for (let i = 0; i < lineEls.length; i += 1) {
const lineEl = lineEls[i];
codeEl.appendChild(lineEl);
codeEl.appendChild(document.createTextNode('\n'));
}
} else {
codeEl.appendChild(documentFragment);
}
return codeEl;
}
static nodeToGFM(node) { static nodeToGFM(node) {
if (node.nodeType === Node.COMMENT_NODE) {
return '';
}
if (node.nodeType === Node.TEXT_NODE) { if (node.nodeType === Node.TEXT_NODE) {
return node.textContent; return node.textContent;
} }
...@@ -355,10 +394,9 @@ require('./lib/utils/common_utils'); ...@@ -355,10 +394,9 @@ require('./lib/utils/common_utils');
return clonedParentNode.innerText || clonedParentNode.textContent; return clonedParentNode.innerText || clonedParentNode.textContent;
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.CopyAsGFM = CopyAsGFM; window.gl.CopyAsGFM = CopyAsGFM;
new CopyAsGFM(); new CopyAsGFM();
})();
/* 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 */
window.Clipboard = require('vendor/clipboard'); import Clipboard from 'vendor/clipboard';
(function() { var genericError, genericSuccess, showTooltip;
var genericError, genericSuccess, showTooltip;
genericSuccess = function(e) { genericSuccess = function(e) {
showTooltip(e.trigger, 'Copied'); showTooltip(e.trigger, 'Copied');
// Clear the selection and blur the trigger so it loses its border // Clear the selection and blur the trigger so it loses its border
e.clearSelection(); e.clearSelection();
return $(e.trigger).blur(); return $(e.trigger).blur();
}; };
// Safari doesn't support `execCommand`, so instead we inform the user to // Safari doesn't support `execCommand`, so instead we inform the user to
// copy manually. // copy manually.
// //
// See http://clipboardjs.com/#browser-support // See http://clipboardjs.com/#browser-support
genericError = function(e) { genericError = function(e) {
var key; var key;
if (/Mac/i.test(navigator.userAgent)) { if (/Mac/i.test(navigator.userAgent)) {
key = '&#8984;'; // Command key = '&#8984;'; // Command
...@@ -25,9 +23,9 @@ window.Clipboard = require('vendor/clipboard'); ...@@ -25,9 +23,9 @@ window.Clipboard = require('vendor/clipboard');
key = 'Ctrl'; key = 'Ctrl';
} }
return showTooltip(e.trigger, "Press " + key + "-C to copy"); return showTooltip(e.trigger, "Press " + key + "-C to copy");
}; };
showTooltip = function(target, title) { showTooltip = function(target, title) {
var $target = $(target); var $target = $(target);
var originalTitle = $target.data('original-title'); var originalTitle = $target.data('original-title');
...@@ -37,13 +35,12 @@ window.Clipboard = require('vendor/clipboard'); ...@@ -37,13 +35,12 @@ window.Clipboard = require('vendor/clipboard');
.tooltip('show') .tooltip('show')
.attr('title', originalTitle) .attr('title', originalTitle)
.tooltip('fixTitle'); .tooltip('fixTitle');
}; };
$(function() { $(function() {
var clipboard; var clipboard;
clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]'); clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
clipboard.on('success', genericSuccess); clipboard.on('success', genericSuccess);
return clipboard.on('error', genericError); return clipboard.on('error', genericError);
}); });
}).call(window);
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-param-reassign, wrap-iife, max-len */ /* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-param-reassign, wrap-iife, max-len */
/* global Api */ /* global Api */
(function (w) { class CreateLabelDropdown {
class CreateLabelDropdown {
constructor ($el, namespacePath, projectPath) { constructor ($el, namespacePath, projectPath) {
this.$el = $el; this.$el = $el;
this.namespacePath = namespacePath; this.namespacePath = namespacePath;
...@@ -122,11 +121,7 @@ ...@@ -122,11 +121,7 @@
} }
}); });
} }
} }
if (!w.gl) {
w.gl = {};
}
gl.CreateLabelDropdown = CreateLabelDropdown; window.gl = window.gl || {};
})(window); gl.CreateLabelDropdown = CreateLabelDropdown;
...@@ -2,11 +2,10 @@ ...@@ -2,11 +2,10 @@
require('./lib/utils/url_utility'); require('./lib/utils/url_utility');
(() => { const UNFOLD_COUNT = 20;
const UNFOLD_COUNT = 20; let isBound = false;
let isBound = false;
class Diff { class Diff {
constructor() { constructor() {
const $diffFile = $('.files .diff-file'); const $diffFile = $('.files .diff-file');
$diffFile.singleFileDiff(); $diffFile.singleFileDiff();
...@@ -123,8 +122,7 @@ require('./lib/utils/url_utility'); ...@@ -123,8 +122,7 @@ require('./lib/utils/url_utility');
.addClass('hll'); .addClass('hll');
} }
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.Diff = Diff; window.gl.Diff = Diff;
})();
/* global Vue */
/* global CommentsStore */
(() => {
const NewIssueForDiscussion = Vue.extend({
props: {
discussionId: {
type: String,
required: true,
},
},
data() {
return {
discussions: CommentsStore.state,
};
},
computed: {
discussion() {
return this.discussions[this.discussionId];
},
showButton() {
if (this.discussion) return !this.discussion.isResolved();
return false;
},
},
});
Vue.component('new-issue-for-discussion-btn', NewIssueForDiscussion);
})();
...@@ -14,10 +14,11 @@ require('./components/resolve_btn'); ...@@ -14,10 +14,11 @@ require('./components/resolve_btn');
require('./components/resolve_count'); require('./components/resolve_count');
require('./components/resolve_discussion_btn'); require('./components/resolve_discussion_btn');
require('./components/diff_note_avatars'); require('./components/diff_note_avatars');
require('./components/new_issue_for_discussion');
$(() => { $(() => {
const projectPath = document.querySelector('.merge-request').dataset.projectPath; const projectPath = document.querySelector('.merge-request').dataset.projectPath;
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, new-issue-for-discussion-btn';
window.gl = window.gl || {}; window.gl = window.gl || {};
window.gl.diffNoteApps = {}; window.gl.diffNoteApps = {};
......
...@@ -37,9 +37,11 @@ import PrometheusGraph from './monitoring/prometheus_graph'; // TODO: Maybe Make ...@@ -37,9 +37,11 @@ import PrometheusGraph from './monitoring/prometheus_graph'; // TODO: Maybe Make
import Issue from './issue'; import Issue from './issue';
import BindInOut from './behaviors/bind_in_out'; import BindInOut from './behaviors/bind_in_out';
import GroupName from './group_name';
import GroupsList from './groups_list'; import GroupsList from './groups_list';
import ProjectsList from './projects_list'; import ProjectsList from './projects_list';
import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
const ShortcutsBlob = require('./shortcuts_blob'); const ShortcutsBlob = require('./shortcuts_blob');
const UserCallout = require('./user_callout'); const UserCallout = require('./user_callout');
...@@ -66,6 +68,25 @@ const UserCallout = require('./user_callout'); ...@@ -66,6 +68,25 @@ const UserCallout = require('./user_callout');
} }
path = page.split(':'); path = page.split(':');
shortcut_handler = null; shortcut_handler = null;
function initBlob() {
new LineHighlighter();
new BlobLinePermalinkUpdater(
document.querySelector('#blob-content-holder'),
'.diff-line-num[data-line-number]',
document.querySelectorAll('.js-data-file-blob-permalink-url, .js-blob-blame-link'),
);
shortcut_handler = new ShortcutsNavigation();
fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
new ShortcutsBlob({
skipResetBindings: true,
fileBlobPermalinkUrl,
});
}
switch (page) { switch (page) {
case 'sessions:new': case 'sessions:new':
new UsernameValidator(); new UsernameValidator();
...@@ -180,10 +201,13 @@ const UserCallout = require('./user_callout'); ...@@ -180,10 +201,13 @@ const UserCallout = require('./user_callout');
new gl.Diff(); new gl.Diff();
new ZenMode(); new ZenMode();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
new MiniPipelineGraph({
container: '.js-commit-pipeline-graph',
}).bindEvents();
break; break;
case 'projects:commit:pipelines': case 'projects:commit:pipelines':
new MiniPipelineGraph({ new MiniPipelineGraph({
container: '.js-pipeline-table', container: '.js-commit-pipeline-graph',
}).bindEvents(); }).bindEvents();
break; break;
case 'projects:commits:show': case 'projects:commits:show':
...@@ -258,27 +282,13 @@ const UserCallout = require('./user_callout'); ...@@ -258,27 +282,13 @@ const UserCallout = require('./user_callout');
break; break;
case 'projects:blob:show': case 'projects:blob:show':
gl.TargetBranchDropDown.bootstrap(); gl.TargetBranchDropDown.bootstrap();
new LineHighlighter(); initBlob();
shortcut_handler = new ShortcutsNavigation();
fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
new ShortcutsBlob({
skipResetBindings: true,
fileBlobPermalinkUrl,
});
break; break;
case 'projects:blob:edit': case 'projects:blob:edit':
gl.TargetBranchDropDown.bootstrap(); gl.TargetBranchDropDown.bootstrap();
break; break;
case 'projects:blame:show': case 'projects:blame:show':
new LineHighlighter(); initBlob();
shortcut_handler = new ShortcutsNavigation();
fileBlobPermalinkUrlElement = document.querySelector('.js-data-file-blob-permalink-url');
fileBlobPermalinkUrl = fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
new ShortcutsBlob({
skipResetBindings: true,
fileBlobPermalinkUrl,
});
break; break;
case 'groups:labels:new': case 'groups:labels:new':
case 'groups:labels:edit': case 'groups:labels:edit':
...@@ -362,6 +372,9 @@ const UserCallout = require('./user_callout'); ...@@ -362,6 +372,9 @@ const UserCallout = require('./user_callout');
shortcut_handler = new ShortcutsDashboardNavigation(); shortcut_handler = new ShortcutsDashboardNavigation();
new UserCallout(); new UserCallout();
break; break;
case 'groups':
new GroupName();
break;
case 'profiles': case 'profiles':
new NotificationsForm(); new NotificationsForm();
new NotificationsDropdown(); new NotificationsDropdown();
...@@ -369,6 +382,7 @@ const UserCallout = require('./user_callout'); ...@@ -369,6 +382,7 @@ const UserCallout = require('./user_callout');
case 'projects': case 'projects':
new Project(); new Project();
new ProjectAvatar(); new ProjectAvatar();
new GroupName();
switch (path[1]) { switch (path[1]) {
case 'compare': case 'compare':
new CompareAutocomplete(); new CompareAutocomplete();
......
...@@ -74,6 +74,9 @@ require('../window')(function(w){ ...@@ -74,6 +74,9 @@ require('../window')(function(w){
this._loadUrlData(config.endpoint) this._loadUrlData(config.endpoint)
.then(function(d) { .then(function(d) {
self._loadData(d, config, self); self._loadData(d, config, self);
}, function(xhrError) {
// TODO: properly handle errors due to XHR cancellation
return;
}).catch(function(e) { }).catch(function(e) {
throw new droplabAjaxException(e.message || e); throw new droplabAjaxException(e.message || e);
}); });
......
...@@ -82,6 +82,9 @@ require('../window')(function(w){ ...@@ -82,6 +82,9 @@ require('../window')(function(w){
this._loadUrlData(url) this._loadUrlData(url)
.then(function(data) { .then(function(data) {
self._loadData(data, config, self); self._loadData(data, config, self);
}, function(xhrError) {
// TODO: properly handle errors due to XHR cancellation
return;
}); });
} }
}, },
......
...@@ -3,8 +3,7 @@ ...@@ -3,8 +3,7 @@
require('./preview_markdown'); require('./preview_markdown');
(function() { window.DropzoneInput = (function() {
this.DropzoneInput = (function() {
function DropzoneInput(form) { function DropzoneInput(form) {
var $mdArea, alertAttr, alertClass, appendToTextArea, btnAlert, child, closeAlertMessage, closeSpinner, divAlert, divHover, divSpinner, dropzone, form_dropzone, form_textarea, getFilename, handlePaste, iconPaperclip, iconSpinner, insertToTextArea, isImage, max_file_size, pasteText, project_uploads_path, showError, showSpinner, uploadFile, uploadProgress; var $mdArea, alertAttr, alertClass, appendToTextArea, btnAlert, child, closeAlertMessage, closeSpinner, divAlert, divHover, divSpinner, dropzone, form_dropzone, form_textarea, getFilename, handlePaste, iconPaperclip, iconSpinner, insertToTextArea, isImage, max_file_size, pasteText, project_uploads_path, showError, showSpinner, uploadFile, uploadProgress;
Dropzone.autoDiscover = false; Dropzone.autoDiscover = false;
...@@ -216,5 +215,4 @@ require('./preview_markdown'); ...@@ -216,5 +215,4 @@ require('./preview_markdown');
} }
return DropzoneInput; return DropzoneInput;
})(); })();
}).call(window);
...@@ -2,8 +2,7 @@ ...@@ -2,8 +2,7 @@
/* global dateFormat */ /* global dateFormat */
/* global Pikaday */ /* global Pikaday */
(function(global) { class DueDateSelect {
class DueDateSelect {
constructor({ $dropdown, $loading } = {}) { constructor({ $dropdown, $loading } = {}) {
const $dropdownParent = $dropdown.closest('.dropdown'); const $dropdownParent = $dropdown.closest('.dropdown');
const $block = $dropdown.closest('.block'); const $block = $dropdown.closest('.block');
...@@ -156,9 +155,9 @@ ...@@ -156,9 +155,9 @@
return this.$loading.fadeOut(); return this.$loading.fadeOut();
}); });
} }
} }
class DueDateSelectors { class DueDateSelectors {
constructor() { constructor() {
this.initMilestoneDatePicker(); this.initMilestoneDatePicker();
this.initIssuableSelect(); this.initIssuableSelect();
...@@ -198,7 +197,7 @@ ...@@ -198,7 +197,7 @@
}); });
}); });
} }
} }
global.DueDateSelectors = DueDateSelectors; window.gl = window.gl || {};
})(window.gl || (window.gl = {})); window.gl.DueDateSelectors = DueDateSelectors;
/* eslint-disable no-param-reassign, no-new */ /* eslint-disable no-param-reassign, no-new */
/* global Flash */ /* global Flash */
import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from './environments_table';
import EnvironmentsStore from '../stores/environments_store';
import eventHub from '../event_hub';
const Vue = window.Vue = require('vue'); const Vue = window.Vue = require('vue');
window.Vue.use(require('vue-resource')); window.Vue.use(require('vue-resource'));
const EnvironmentsService = require('../services/environments_service');
const EnvironmentTable = require('./environments_table');
const EnvironmentsStore = require('../stores/environments_store');
require('../../vue_shared/components/table_pagination'); require('../../vue_shared/components/table_pagination');
require('../../lib/utils/common_utils'); require('../../lib/utils/common_utils');
require('../../vue_shared/vue_resource_interceptor'); require('../../vue_shared/vue_resource_interceptor');
module.exports = Vue.component('environment-component', { export default Vue.component('environment-component', {
components: { components: {
'environment-table': EnvironmentTable, 'environment-table': EnvironmentTable,
...@@ -66,33 +67,15 @@ module.exports = Vue.component('environment-component', { ...@@ -66,33 +67,15 @@ module.exports = Vue.component('environment-component', {
* Toggles loading property. * Toggles loading property.
*/ */
created() { created() {
const scope = gl.utils.getParameterByName('scope') || this.visibility; this.service = new EnvironmentsService(this.endpoint);
const pageNumber = gl.utils.getParameterByName('page') || this.pageNumber;
const endpoint = `${this.endpoint}?scope=${scope}&page=${pageNumber}`;
const service = new EnvironmentsService(endpoint); this.fetchEnvironments();
this.isLoading = true; eventHub.$on('refreshEnvironments', this.fetchEnvironments);
},
return service.get() beforeDestroyed() {
.then(resp => ({ eventHub.$off('refreshEnvironments');
headers: resp.headers,
body: resp.json(),
}))
.then((response) => {
this.store.storeAvailableCount(response.body.available_count);
this.store.storeStoppedCount(response.body.stopped_count);
this.store.storeEnvironments(response.body.environments);
this.store.setPagination(response.headers);
})
.then(() => {
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
new Flash('An error occurred while fetching the environments.', 'alert');
});
}, },
methods: { methods: {
...@@ -112,6 +95,32 @@ module.exports = Vue.component('environment-component', { ...@@ -112,6 +95,32 @@ module.exports = Vue.component('environment-component', {
gl.utils.visitUrl(param); gl.utils.visitUrl(param);
return param; return param;
}, },
fetchEnvironments() {
const scope = gl.utils.getParameterByName('scope') || this.visibility;
const pageNumber = gl.utils.getParameterByName('page') || this.pageNumber;
this.isLoading = true;
return this.service.get(scope, pageNumber)
.then(resp => ({
headers: resp.headers,
body: resp.json(),
}))
.then((response) => {
this.store.storeAvailableCount(response.body.available_count);
this.store.storeStoppedCount(response.body.stopped_count);
this.store.storeEnvironments(response.body.environments);
this.store.setPagination(response.headers);
})
.then(() => {
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
new Flash('An error occurred while fetching the environments.');
});
},
}, },
template: ` template: `
...@@ -144,7 +153,7 @@ module.exports = Vue.component('environment-component', { ...@@ -144,7 +153,7 @@ module.exports = Vue.component('environment-component', {
<div class="content-list environments-container"> <div class="content-list environments-container">
<div class="environments-list-loading text-center" v-if="isLoading"> <div class="environments-list-loading text-center" v-if="isLoading">
<i class="fa fa-spinner fa-spin"></i> <i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</div> </div>
<div class="blank-state blank-state-no-icon" <div class="blank-state blank-state-no-icon"
...@@ -173,7 +182,8 @@ module.exports = Vue.component('environment-component', { ...@@ -173,7 +182,8 @@ module.exports = Vue.component('environment-component', {
<environment-table <environment-table
:environments="state.environments" :environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed" :can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed"/> :can-read-environment="canReadEnvironmentParsed"
:service="service"/>
</div> </div>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1" <table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
......
const Vue = require('vue'); /* global Flash */
const playIconSvg = require('icons/_icon_play.svg'); /* eslint-disable no-new */
module.exports = Vue.component('actions-component', { import playIconSvg from 'icons/_icon_play.svg';
import eventHub from '../event_hub';
export default {
props: { props: {
actions: { actions: {
type: Array, type: Array,
required: false, required: false,
default: () => [], default: () => [],
}, },
service: {
type: Object,
required: true,
},
}, },
data() { data() {
return { playIconSvg }; return {
playIconSvg,
isLoading: false,
};
},
methods: {
onClickAction(endpoint) {
this.isLoading = true;
this.service.postAction(endpoint)
.then(() => {
this.isLoading = false;
eventHub.$emit('refreshEnvironments');
})
.catch(() => {
this.isLoading = false;
new Flash('An error occured while making the request.');
});
},
}, },
template: ` template: `
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button class="dropdown btn btn-default dropdown-new" data-toggle="dropdown"> <button
class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container"
data-toggle="dropdown"
:disabled="isLoading">
<span> <span>
<span class="js-dropdown-play-icon-container" v-html="playIconSvg"></span> <span v-html="playIconSvg"></span>
<i class="fa fa-caret-down"></i> <i class="fa fa-caret-down" aria-hidden="true"></i>
<i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</span> </span>
<ul class="dropdown-menu dropdown-menu-align-right"> <ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="action in actions"> <li v-for="action in actions">
<a :href="action.play_path" <button
data-method="post" @click="onClickAction(action.play_path)"
rel="nofollow" class="js-manual-action-link no-btn">
class="js-manual-action-link">
${playIconSvg} ${playIconSvg}
<span> <span>
{{action.name}} {{action.name}}
</span> </span>
</a> </button>
</li> </li>
</ul> </ul>
</button> </button>
</div> </div>
`, `,
}); };
/** /**
* Renders the external url link in environments table. * Renders the external url link in environments table.
*/ */
const Vue = require('vue'); export default {
module.exports = Vue.component('external-url-component', {
props: { props: {
externalUrl: { externalUrl: {
type: String, type: String,
...@@ -12,8 +10,12 @@ module.exports = Vue.component('external-url-component', { ...@@ -12,8 +10,12 @@ module.exports = Vue.component('external-url-component', {
}, },
template: ` template: `
<a class="btn external_url" :href="externalUrl" target="_blank"> <a
<i class="fa fa-external-link"></i> class="btn external_url"
:href="externalUrl"
target="_blank"
title="Environment external URL">
<i class="fa fa-external-link" aria-hidden="true"></i>
</a> </a>
`, `,
}); };
const Vue = require('vue'); import Timeago from 'timeago.js';
const Timeago = require('timeago.js'); import ActionsComponent from './environment_actions';
import ExternalUrlComponent from './environment_external_url';
require('../../lib/utils/text_utility'); import StopComponent from './environment_stop';
require('../../vue_shared/components/commit'); import RollbackComponent from './environment_rollback';
const ActionsComponent = require('./environment_actions'); import TerminalButtonComponent from './environment_terminal_button';
const ExternalUrlComponent = require('./environment_external_url'); import '../../lib/utils/text_utility';
const StopComponent = require('./environment_stop'); import '../../vue_shared/components/commit';
const RollbackComponent = require('./environment_rollback');
const TerminalButtonComponent = require('./environment_terminal_button');
/** /**
* Envrionment Item Component * Envrionment Item Component
...@@ -17,7 +15,7 @@ const TerminalButtonComponent = require('./environment_terminal_button'); ...@@ -17,7 +15,7 @@ const TerminalButtonComponent = require('./environment_terminal_button');
const timeagoInstance = new Timeago(); const timeagoInstance = new Timeago();
module.exports = Vue.component('environment-item', { export default {
components: { components: {
'commit-component': gl.CommitComponent, 'commit-component': gl.CommitComponent,
...@@ -46,6 +44,11 @@ module.exports = Vue.component('environment-item', { ...@@ -46,6 +44,11 @@ module.exports = Vue.component('environment-item', {
required: false, required: false,
default: false, default: false,
}, },
service: {
type: Object,
required: true,
},
}, },
computed: { computed: {
...@@ -489,22 +492,25 @@ module.exports = Vue.component('environment-item', { ...@@ -489,22 +492,25 @@ module.exports = Vue.component('environment-item', {
<td class="environments-actions"> <td class="environments-actions">
<div v-if="!model.isFolder" class="btn-group pull-right" role="group"> <div v-if="!model.isFolder" class="btn-group pull-right" role="group">
<actions-component v-if="hasManualActions && canCreateDeployment" <actions-component v-if="hasManualActions && canCreateDeployment"
:service="service"
:actions="manualActions"/> :actions="manualActions"/>
<external-url-component v-if="externalURL && canReadEnvironment" <external-url-component v-if="externalURL && canReadEnvironment"
:external-url="externalURL"/> :external-url="externalURL"/>
<stop-component v-if="hasStopAction && canCreateDeployment" <stop-component v-if="hasStopAction && canCreateDeployment"
:stop-url="model.stop_path"/> :stop-url="model.stop_path"
:service="service"/>
<terminal-button-component v-if="model && model.terminal_path" <terminal-button-component v-if="model && model.terminal_path"
:terminal-path="model.terminal_path"/> :terminal-path="model.terminal_path"/>
<rollback-component v-if="canRetry && canCreateDeployment" <rollback-component v-if="canRetry && canCreateDeployment"
:is-last-deployment="isLastDeployment" :is-last-deployment="isLastDeployment"
:retry-url="retryUrl"/> :retry-url="retryUrl"
:service="service"/>
</div> </div>
</td> </td>
</tr> </tr>
`, `,
}); };
/* global Flash */
/* eslint-disable no-new */
/** /**
* Renders Rollback or Re deploy button in environments table depending * Renders Rollback or Re deploy button in environments table depending
* of the provided property `isLastDeployment` * of the provided property `isLastDeployment`.
*
* Makes a post request when the button is clicked.
*/ */
const Vue = require('vue'); import eventHub from '../event_hub';
module.exports = Vue.component('rollback-component', { export default {
props: { props: {
retryUrl: { retryUrl: {
type: String, type: String,
...@@ -15,16 +19,49 @@ module.exports = Vue.component('rollback-component', { ...@@ -15,16 +19,49 @@ module.exports = Vue.component('rollback-component', {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
service: {
type: Object,
required: true,
},
},
data() {
return {
isLoading: false,
};
},
methods: {
onClick() {
this.isLoading = true;
this.service.postAction(this.retryUrl)
.then(() => {
this.isLoading = false;
eventHub.$emit('refreshEnvironments');
})
.catch(() => {
this.isLoading = false;
new Flash('An error occured while making the request.');
});
},
}, },
template: ` template: `
<a class="btn" :href="retryUrl" data-method="post" rel="nofollow"> <button type="button"
class="btn"
@click="onClick"
:disabled="isLoading">
<span v-if="isLastDeployment"> <span v-if="isLastDeployment">
Re-deploy Re-deploy
</span> </span>
<span v-else> <span v-else>
Rollback Rollback
</span> </span>
</a>
<i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</button>
`, `,
}); };
/* global Flash */
/* eslint-disable no-new, no-alert */
/** /**
* Renders the stop "button" that allows stop an environment. * Renders the stop "button" that allows stop an environment.
* Used in environments table. * Used in environments table.
*/ */
const Vue = require('vue'); import eventHub from '../event_hub';
module.exports = Vue.component('stop-component', { export default {
props: { props: {
stopUrl: { stopUrl: {
type: String, type: String,
default: '', default: '',
}, },
service: {
type: Object,
required: true,
},
},
data() {
return {
isLoading: false,
};
},
methods: {
onClick() {
if (confirm('Are you sure you want to stop this environment?')) {
this.isLoading = true;
this.service.postAction(this.retryUrl)
.then(() => {
this.isLoading = false;
eventHub.$emit('refreshEnvironments');
})
.catch(() => {
this.isLoading = false;
new Flash('An error occured while making the request.', 'alert');
});
}
},
}, },
template: ` template: `
<a class="btn stop-env-link" <button type="button"
:href="stopUrl" class="btn stop-env-link"
data-confirm="Are you sure you want to stop this environment?" @click="onClick"
data-method="post" :disabled="isLoading"
rel="nofollow"> title="Stop Environment">
<i class="fa fa-stop stop-env-icon" aria-hidden="true"></i> <i class="fa fa-stop stop-env-icon" aria-hidden="true"></i>
</a> <i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</button>
`, `,
}); };
...@@ -2,13 +2,13 @@ ...@@ -2,13 +2,13 @@
* Renders a terminal button to open a web terminal. * Renders a terminal button to open a web terminal.
* Used in environments table. * Used in environments table.
*/ */
const Vue = require('vue'); import terminalIconSvg from 'icons/_icon_terminal.svg';
const terminalIconSvg = require('icons/_icon_terminal.svg');
module.exports = Vue.component('terminal-button-component', { export default {
props: { props: {
terminalPath: { terminalPath: {
type: String, type: String,
required: false,
default: '', default: '',
}, },
}, },
...@@ -19,8 +19,9 @@ module.exports = Vue.component('terminal-button-component', { ...@@ -19,8 +19,9 @@ module.exports = Vue.component('terminal-button-component', {
template: ` template: `
<a class="btn terminal-button" <a class="btn terminal-button"
title="Open web terminal"
:href="terminalPath"> :href="terminalPath">
${terminalIconSvg} ${terminalIconSvg}
</a> </a>
`, `,
}); };
/** /**
* Render environments table. * Render environments table.
*/ */
const Vue = require('vue'); import EnvironmentItem from './environment_item';
const EnvironmentItem = require('./environment_item');
module.exports = Vue.component('environment-table-component', {
export default {
components: { components: {
'environment-item': EnvironmentItem, 'environment-item': EnvironmentItem,
}, },
...@@ -28,6 +26,11 @@ module.exports = Vue.component('environment-table-component', { ...@@ -28,6 +26,11 @@ module.exports = Vue.component('environment-table-component', {
required: false, required: false,
default: false, default: false,
}, },
service: {
type: Object,
required: true,
},
}, },
template: ` template: `
...@@ -48,9 +51,10 @@ module.exports = Vue.component('environment-table-component', { ...@@ -48,9 +51,10 @@ module.exports = Vue.component('environment-table-component', {
<tr is="environment-item" <tr is="environment-item"
:model="model" :model="model"
:can-create-deployment="canCreateDeployment" :can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"></tr> :can-read-environment="canReadEnvironment"
:service="service"></tr>
</template> </template>
</tbody> </tbody>
</table> </table>
`, `,
}); };
const EnvironmentsComponent = require('./components/environment'); import EnvironmentsComponent from './components/environment';
$(() => { $(() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
......
import Vue from 'vue';
export default new Vue();
const EnvironmentsFolderComponent = require('./environments_folder_view'); import EnvironmentsFolderComponent from './environments_folder_view';
$(() => { $(() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
......
/* eslint-disable no-param-reassign, no-new */ /* eslint-disable no-param-reassign, no-new */
/* global Flash */ /* global Flash */
import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from '../components/environments_table';
import EnvironmentsStore from '../stores/environments_store';
const Vue = window.Vue = require('vue'); const Vue = window.Vue = require('vue');
window.Vue.use(require('vue-resource')); window.Vue.use(require('vue-resource'));
const EnvironmentsService = require('../services/environments_service');
const EnvironmentTable = require('../components/environments_table');
const EnvironmentsStore = require('../stores/environments_store');
require('../../vue_shared/components/table_pagination'); require('../../vue_shared/components/table_pagination');
require('../../lib/utils/common_utils'); require('../../lib/utils/common_utils');
require('../../vue_shared/vue_resource_interceptor'); require('../../vue_shared/vue_resource_interceptor');
module.exports = Vue.component('environment-folder-view', { export default Vue.component('environment-folder-view', {
components: { components: {
'environment-table': EnvironmentTable, 'environment-table': EnvironmentTable,
...@@ -88,11 +88,11 @@ module.exports = Vue.component('environment-folder-view', { ...@@ -88,11 +88,11 @@ module.exports = Vue.component('environment-folder-view', {
const endpoint = `${this.endpoint}?scope=${scope}&page=${pageNumber}`; const endpoint = `${this.endpoint}?scope=${scope}&page=${pageNumber}`;
const service = new EnvironmentsService(endpoint); this.service = new EnvironmentsService(endpoint);
this.isLoading = true; this.isLoading = true;
return service.get() return this.service.get()
.then(resp => ({ .then(resp => ({
headers: resp.headers, headers: resp.headers,
body: resp.json(), body: resp.json(),
...@@ -168,13 +168,12 @@ module.exports = Vue.component('environment-folder-view', { ...@@ -168,13 +168,12 @@ module.exports = Vue.component('environment-folder-view', {
:can-read-environment="canReadEnvironmentParsed" :can-read-environment="canReadEnvironmentParsed"
:play-icon-svg="playIconSvg" :play-icon-svg="playIconSvg"
:terminal-icon-svg="terminalIconSvg" :terminal-icon-svg="terminalIconSvg"
:commit-icon-svg="commitIconSvg"> :commit-icon-svg="commitIconSvg"
</environment-table> :service="service"/>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1" <table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
:change="changePage" :change="changePage"
:pageInfo="state.paginationInformation"> :pageInfo="state.paginationInformation"/>
</table-pagination>
</div> </div>
</div> </div>
</div> </div>
......
const Vue = require('vue'); /* eslint-disable class-methods-use-this */
import Vue from 'vue';
class EnvironmentsService { export default class EnvironmentsService {
constructor(endpoint) { constructor(endpoint) {
this.environments = Vue.resource(endpoint); this.environments = Vue.resource(endpoint);
} }
get() { get(scope, page) {
return this.environments.get(); return this.environments.get({ scope, page });
} }
}
module.exports = EnvironmentsService; postAction(endpoint) {
return Vue.http.post(endpoint, {}, { emulateJSON: true });
}
}
require('~/lib/utils/common_utils'); import '~/lib/utils/common_utils';
/** /**
* Environments Store. * Environments Store.
* *
* Stores received environments, count of stopped environments and count of * Stores received environments, count of stopped environments and count of
* available environments. * available environments.
*/ */
class EnvironmentsStore { export default class EnvironmentsStore {
constructor() { constructor() {
this.state = {}; this.state = {};
this.state.environments = []; this.state.environments = [];
...@@ -86,5 +87,3 @@ class EnvironmentsStore { ...@@ -86,5 +87,3 @@ class EnvironmentsStore {
return count; return count;
} }
} }
module.exports = EnvironmentsStore;
/* eslint-disable no-extend-native, func-names, space-before-function-paren, space-infix-ops, strict, max-len */ // TODO: remove this
'use strict'; // eslint-disable-next-line no-extend-native
Array.prototype.first = function first() {
Array.prototype.first = function() {
return this[0]; return this[0];
}; };
Array.prototype.last = function() { // eslint-disable-next-line no-extend-native
return this[this.length-1]; Array.prototype.last = function last() {
}; return this[this.length - 1];
Array.prototype.find = Array.prototype.find || function(predicate, ...args) {
if (!this) throw new TypeError('Array.prototype.find called on null or undefined');
if (typeof predicate !== 'function') throw new TypeError('predicate must be a function');
const list = Object(this);
const thisArg = args[1];
let value = {};
for (let i = 0; i < list.length; i += 1) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) return value;
}
return undefined;
}; };
/* global CustomEvent */
/* eslint-disable no-global-assign */
// Custom event support for IE
CustomEvent = function CustomEvent(event, parameters) {
const params = parameters || { bubbles: false, cancelable: false, detail: undefined };
const evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
};
CustomEvent.prototype = window.Event.prototype;
/* eslint-disable func-names, space-before-function-paren, object-shorthand, comma-dangle, max-len */
// Disable an element and add the 'disabled' Bootstrap class
(function() {
$.fn.extend({
disable: function() {
return $(this).attr('disabled', 'disabled').addClass('disabled');
}
});
// Enable an element and remove the 'disabled' Bootstrap class
$.fn.extend({
enable: function() {
return $(this).removeAttr('disabled').removeClass('disabled');
}
});
}).call(window);
/* eslint-disable no-restricted-syntax */
// Adapted from https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill
if (typeof Object.assign !== 'function') {
Object.assign = function assign(target, ...args) {
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}
const to = Object(target);
for (let index = 0; index < args.length; index += 1) {
const nextSource = args[index];
if (nextSource != null) { // Skip over if undefined or null
for (const nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
};
}
import 'string.prototype.codepointat';
import 'string.fromcodepoint';
...@@ -2,11 +2,10 @@ ...@@ -2,11 +2,10 @@
/* global FilesCommentButton */ /* global FilesCommentButton */
/* global notes */ /* global notes */
(function() { let $commentButtonTemplate;
let $commentButtonTemplate; var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
this.FilesCommentButton = (function() { window.FilesCommentButton = (function() {
var COMMENT_BUTTON_CLASS, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS; var COMMENT_BUTTON_CLASS, EMPTY_CELL_CLASS, LINE_COLUMN_CLASSES, LINE_CONTENT_CLASS, LINE_HOLDER_CLASS, LINE_NUMBER_CLASS, OLD_LINE_CLASS, TEXT_FILE_SELECTOR, UNFOLDABLE_LINE_CLASS;
COMMENT_BUTTON_CLASS = '.add-diff-note'; COMMENT_BUTTON_CLASS = '.add-diff-note';
...@@ -126,9 +125,9 @@ ...@@ -126,9 +125,9 @@
}; };
return FilesCommentButton; return FilesCommentButton;
})(); })();
$.fn.filesCommentButton = function() { $.fn.filesCommentButton = function() {
$commentButtonTemplate = $('<button name="button" type="submit" class="add-diff-note js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>'); $commentButtonTemplate = $('<button name="button" type="submit" class="add-diff-note js-add-diff-note-button" title="Add a comment to this line"><i class="fa fa-comment-o"></i></button>');
if (!(this && (this.parent().data('can-create-note') != null))) { if (!(this && (this.parent().data('can-create-note') != null))) {
...@@ -139,5 +138,4 @@ ...@@ -139,5 +138,4 @@
return $.data(this, 'filesCommentButton', new FilesCommentButton($(this))); return $.data(this, 'filesCommentButton', new FilesCommentButton($(this)));
} }
}); });
}; };
}).call(window);
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
* Makes search request for content when user types a value in the search input. * Makes search request for content when user types a value in the search input.
* Updates the html content of the page with the received one. * Updates the html content of the page with the received one.
*/ */
export default class FilterableList { export default class FilterableList {
constructor(form, filter, holder) { constructor(form, filter, holder) {
this.filterForm = form; this.filterForm = form;
......
/* eslint-disable class-methods-use-this */
let container = document;
class FilteredSearchContainerClass {
set container(containerParam) {
container = containerParam;
}
get container() {
return container;
}
}
export default new FilteredSearchContainerClass();
...@@ -45,7 +45,7 @@ require('./filtered_search_dropdown'); ...@@ -45,7 +45,7 @@ require('./filtered_search_dropdown');
gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' ')); gl.FilteredSearchVisualTokens.addSearchVisualToken(searchTerms.join(' '));
} }
gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', '')); gl.FilteredSearchDropdownManager.addWordToInput(token.replace(':', ''), '', false, this.container);
} }
this.dismissDropdown(); this.dismissDropdown();
this.dispatchInputEvent(); this.dispatchInputEvent();
...@@ -57,13 +57,15 @@ require('./filtered_search_dropdown'); ...@@ -57,13 +57,15 @@ require('./filtered_search_dropdown');
const dropdownData = []; const dropdownData = [];
[].forEach.call(this.input.closest('.filtered-search-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => { [].forEach.call(this.input.closest('.filtered-search-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
const { icon, hint, tag } = dropdownMenu.dataset; const { icon, hint, tag, type } = dropdownMenu.dataset;
if (icon && hint && tag) { if (icon && hint && tag) {
dropdownData.push({ dropdownData.push(
Object.assign({
icon: `fa-${icon}`, icon: `fa-${icon}`,
hint, hint,
tag: `&lt;${tag}&gt;`, tag: `&lt;${tag}&gt;`,
}); }, type && { type }),
);
} }
}); });
......
import FilteredSearchContainer from './container';
(() => { (() => {
class DropdownUtils { class DropdownUtils {
static getEscapedText(text) { static getEscapedText(text) {
...@@ -51,14 +53,18 @@ ...@@ -51,14 +53,18 @@
static filterHint(input, item) { static filterHint(input, item) {
const updatedItem = item; const updatedItem = item;
const searchInput = gl.DropdownUtils.getSearchInput(input); const searchInput = gl.DropdownUtils.getSearchQuery(input);
let { lastToken } = gl.FilteredSearchTokenizer.processTokens(searchInput); const { lastToken, tokens } = gl.FilteredSearchTokenizer.processTokens(searchInput);
lastToken = lastToken.key || lastToken || ''; const lastKey = lastToken.key || lastToken || '';
const allowMultiple = item.type === 'array';
if (!lastToken || searchInput.split('').last() === ' ') { const itemInExistingTokens = tokens.some(t => t.key === item.hint);
if (!allowMultiple && itemInExistingTokens) {
updatedItem.droplab_hidden = true;
} else if (!lastKey || searchInput.split('').last() === ' ') {
updatedItem.droplab_hidden = false; updatedItem.droplab_hidden = false;
} else if (lastToken) { } else if (lastKey) {
const split = lastToken.split(':'); const split = lastKey.split(':');
const tokenName = split[0].split(' ').last(); const tokenName = split[0].split(' ').last();
const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1; const match = updatedItem.hint.indexOf(tokenName.toLowerCase()) === -1;
...@@ -81,7 +87,8 @@ ...@@ -81,7 +87,8 @@
// Determines the full search query (visual tokens + input) // Determines the full search query (visual tokens + input)
static getSearchQuery(untilInput = false) { static getSearchQuery(untilInput = false) {
const tokens = [].slice.call(document.querySelectorAll('.tokens-container li')); const container = FilteredSearchContainer.container;
const tokens = [].slice.call(container.querySelectorAll('.tokens-container li'));
const values = []; const values = [];
if (untilInput) { if (untilInput) {
...@@ -110,7 +117,7 @@ ...@@ -110,7 +117,7 @@
const { isLastVisualTokenValid } = const { isLastVisualTokenValid } =
gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
const input = document.querySelector('.filtered-search'); const input = FilteredSearchContainer.container.querySelector('.filtered-search');
const inputValue = input && input.value; const inputValue = input && input.value;
if (isLastVisualTokenValid) { if (isLastVisualTokenValid) {
......
/* global DropLab */ /* global DropLab */
import FilteredSearchContainer from './container';
(() => { (() => {
class FilteredSearchDropdownManager { class FilteredSearchDropdownManager {
constructor(baseEndpoint = '', page) { constructor(baseEndpoint = '', page) {
this.container = FilteredSearchContainer.container;
this.baseEndpoint = baseEndpoint.replace(/\/$/, ''); this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
this.tokenizer = gl.FilteredSearchTokenizer; this.tokenizer = gl.FilteredSearchTokenizer;
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys; this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
this.filteredSearchInput = document.querySelector('.filtered-search'); this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.page = page; this.page = page;
this.setupMapping(); this.setupMapping();
...@@ -31,35 +33,35 @@ ...@@ -31,35 +33,35 @@
author: { author: {
reference: null, reference: null,
gl: 'DropdownUser', gl: 'DropdownUser',
element: document.querySelector('#js-dropdown-author'), element: this.container.querySelector('#js-dropdown-author'),
}, },
assignee: { assignee: {
reference: null, reference: null,
gl: 'DropdownUser', gl: 'DropdownUser',
element: document.querySelector('#js-dropdown-assignee'), element: this.container.querySelector('#js-dropdown-assignee'),
}, },
milestone: { milestone: {
reference: null, reference: null,
gl: 'DropdownNonUser', gl: 'DropdownNonUser',
extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'], extraArguments: [`${this.baseEndpoint}/milestones.json`, '%'],
element: document.querySelector('#js-dropdown-milestone'), element: this.container.querySelector('#js-dropdown-milestone'),
}, },
label: { label: {
reference: null, reference: null,
gl: 'DropdownNonUser', gl: 'DropdownNonUser',
extraArguments: [`${this.baseEndpoint}/labels.json`, '~'], extraArguments: [`${this.baseEndpoint}/labels.json`, '~'],
element: document.querySelector('#js-dropdown-label'), element: this.container.querySelector('#js-dropdown-label'),
}, },
hint: { hint: {
reference: null, reference: null,
gl: 'DropdownHint', gl: 'DropdownHint',
element: document.querySelector('#js-dropdown-hint'), element: this.container.querySelector('#js-dropdown-hint'),
}, },
}; };
} }
static addWordToInput(tokenName, tokenValue = '', clicked = false) { static addWordToInput(tokenName, tokenValue = '', clicked = false) {
const input = document.querySelector('.filtered-search'); const input = FilteredSearchContainer.container.querySelector('.filtered-search');
gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue); gl.FilteredSearchVisualTokens.addFilterVisualToken(tokenName, tokenValue);
input.value = ''; input.value = '';
...@@ -75,13 +77,13 @@ ...@@ -75,13 +77,13 @@
updateDropdownOffset(key) { updateDropdownOffset(key) {
// Always align dropdown with the input field // Always align dropdown with the input field
let offset = this.filteredSearchInput.getBoundingClientRect().left - document.querySelector('.scroll-container').getBoundingClientRect().left; let offset = this.filteredSearchInput.getBoundingClientRect().left - this.container.querySelector('.scroll-container').getBoundingClientRect().left;
const maxInputWidth = 240; const maxInputWidth = 240;
const currentDropdownWidth = this.mapping[key].element.clientWidth || maxInputWidth; const currentDropdownWidth = this.mapping[key].element.clientWidth || maxInputWidth;
// Make sure offset never exceeds the input container // Make sure offset never exceeds the input container
const offsetMaxWidth = document.querySelector('.scroll-container').clientWidth - currentDropdownWidth; const offsetMaxWidth = this.container.querySelector('.scroll-container').clientWidth - currentDropdownWidth;
if (offsetMaxWidth < offset) { if (offsetMaxWidth < offset) {
offset = offsetMaxWidth; offset = offsetMaxWidth;
} }
...@@ -162,6 +164,10 @@ ...@@ -162,6 +164,10 @@
} }
resetDropdowns() { resetDropdowns() {
if (!this.currentDropdown) {
return;
}
// Force current dropdown to hide // Force current dropdown to hide
this.mapping[this.currentDropdown].reference.hideDropdown(); this.mapping[this.currentDropdown].reference.hideDropdown();
......
import FilteredSearchContainer from './container';
(() => { (() => {
class FilteredSearchManager { class FilteredSearchManager {
constructor(page) { constructor(page) {
this.filteredSearchInput = document.querySelector('.filtered-search'); this.container = FilteredSearchContainer.container;
this.clearSearchButton = document.querySelector('.clear-search'); this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.tokensContainer = document.querySelector('.tokens-container'); this.clearSearchButton = this.container.querySelector('.clear-search');
this.tokensContainer = this.container.querySelector('.tokens-container');
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys; this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
if (this.filteredSearchInput) { if (this.filteredSearchInput) {
...@@ -38,7 +41,8 @@ ...@@ -38,7 +41,8 @@
this.editTokenWrapper = this.editToken.bind(this); this.editTokenWrapper = this.editToken.bind(this);
this.tokenChange = this.tokenChange.bind(this); this.tokenChange = this.tokenChange.bind(this);
this.filteredSearchInput.form.addEventListener('submit', this.handleFormSubmit); this.filteredSearchInputForm = this.filteredSearchInput.form;
this.filteredSearchInputForm.addEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper); this.filteredSearchInput.addEventListener('input', this.setDropdownWrapper);
this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper); this.filteredSearchInput.addEventListener('input', this.toggleClearSearchButtonWrapper);
this.filteredSearchInput.addEventListener('input', this.handleInputPlaceholderWrapper); this.filteredSearchInput.addEventListener('input', this.handleInputPlaceholderWrapper);
...@@ -56,7 +60,7 @@ ...@@ -56,7 +60,7 @@
} }
unbindEvents() { unbindEvents() {
this.filteredSearchInput.form.removeEventListener('submit', this.handleFormSubmit); this.filteredSearchInputForm.removeEventListener('submit', this.handleFormSubmit);
this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper); this.filteredSearchInput.removeEventListener('input', this.setDropdownWrapper);
this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper); this.filteredSearchInput.removeEventListener('input', this.toggleClearSearchButtonWrapper);
this.filteredSearchInput.removeEventListener('input', this.handleInputPlaceholderWrapper); this.filteredSearchInput.removeEventListener('input', this.handleInputPlaceholderWrapper);
...@@ -105,8 +109,15 @@ ...@@ -105,8 +109,15 @@
e.preventDefault(); e.preventDefault();
if (!activeElements.length) { if (!activeElements.length) {
if (this.isHandledAsync) {
e.stopImmediatePropagation();
this.filteredSearchInput.blur();
this.dropdownManager.resetDropdowns();
} else {
// Prevent droplab from opening dropdown // Prevent droplab from opening dropdown
this.dropdownManager.destroyDroplab(); this.dropdownManager.destroyDroplab();
}
this.search(); this.search();
} }
...@@ -124,7 +135,7 @@ ...@@ -124,7 +135,7 @@
} }
unselectEditTokens(e) { unselectEditTokens(e) {
const inputContainer = document.querySelector('.filtered-search-input-container'); const inputContainer = this.container.querySelector('.filtered-search-input-container');
const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target); const isElementInFilteredSearch = inputContainer && inputContainer.contains(e.target);
const isElementInFilterDropdown = e.target.closest('.filter-dropdown') !== null; const isElementInFilterDropdown = e.target.closest('.filter-dropdown') !== null;
const isElementTokensContainer = e.target.classList.contains('tokens-container'); const isElementTokensContainer = e.target.classList.contains('tokens-container');
...@@ -199,6 +210,10 @@ ...@@ -199,6 +210,10 @@
this.handleInputPlaceholder(); this.handleInputPlaceholder();
this.dropdownManager.resetDropdowns(); this.dropdownManager.resetDropdowns();
if (this.isHandledAsync) {
this.search();
}
} }
handleInputVisualToken() { handleInputVisualToken() {
...@@ -345,8 +360,12 @@ ...@@ -345,8 +360,12 @@
const parameterizedUrl = `?scope=all&utf8=✓&${paths.join('&')}`; const parameterizedUrl = `?scope=all&utf8=✓&${paths.join('&')}`;
if (this.updateObject) {
this.updateObject(parameterizedUrl);
} else {
gl.utils.visitUrl(parameterizedUrl); gl.utils.visitUrl(parameterizedUrl);
} }
}
getUsernameParams() { getUsernameParams() {
const usernamesById = {}; const usernamesById = {};
......
...@@ -42,6 +42,10 @@ ...@@ -42,6 +42,10 @@
url: 'milestone_title=%23upcoming', url: 'milestone_title=%23upcoming',
tokenKey: 'milestone', tokenKey: 'milestone',
value: 'upcoming', value: 'upcoming',
}, {
url: 'milestone_title=%23started',
tokenKey: 'milestone',
value: 'started',
}, { }, {
url: 'label_name[]=No+Label', url: 'label_name[]=No+Label',
tokenKey: 'label', tokenKey: 'label',
......
import FilteredSearchContainer from './container';
class FilteredSearchVisualTokens { class FilteredSearchVisualTokens {
static getLastVisualTokenBeforeInput() { static getLastVisualTokenBeforeInput() {
const inputLi = document.querySelector('.input-token'); const inputLi = FilteredSearchContainer.container.querySelector('.input-token');
const lastVisualToken = inputLi && inputLi.previousElementSibling; const lastVisualToken = inputLi && inputLi.previousElementSibling;
return { return {
...@@ -10,7 +12,7 @@ class FilteredSearchVisualTokens { ...@@ -10,7 +12,7 @@ class FilteredSearchVisualTokens {
} }
static unselectTokens() { static unselectTokens() {
const otherTokens = document.querySelectorAll('.js-visual-token .selectable.selected'); const otherTokens = FilteredSearchContainer.container.querySelectorAll('.js-visual-token .selectable.selected');
[].forEach.call(otherTokens, t => t.classList.remove('selected')); [].forEach.call(otherTokens, t => t.classList.remove('selected'));
} }
...@@ -24,7 +26,7 @@ class FilteredSearchVisualTokens { ...@@ -24,7 +26,7 @@ class FilteredSearchVisualTokens {
} }
static removeSelectedToken() { static removeSelectedToken() {
const selected = document.querySelector('.js-visual-token .selected'); const selected = FilteredSearchContainer.container.querySelector('.js-visual-token .selected');
if (selected) { if (selected) {
const li = selected.closest('.js-visual-token'); const li = selected.closest('.js-visual-token');
...@@ -54,8 +56,8 @@ class FilteredSearchVisualTokens { ...@@ -54,8 +56,8 @@ class FilteredSearchVisualTokens {
} }
li.querySelector('.name').innerText = name; li.querySelector('.name').innerText = name;
const tokensContainer = document.querySelector('.tokens-container'); const tokensContainer = FilteredSearchContainer.container.querySelector('.tokens-container');
const input = document.querySelector('.filtered-search'); const input = FilteredSearchContainer.container.querySelector('.filtered-search');
tokensContainer.insertBefore(li, input.parentElement); tokensContainer.insertBefore(li, input.parentElement);
} }
...@@ -77,14 +79,14 @@ class FilteredSearchVisualTokens { ...@@ -77,14 +79,14 @@ class FilteredSearchVisualTokens {
const addVisualTokenElement = FilteredSearchVisualTokens.addVisualTokenElement; const addVisualTokenElement = FilteredSearchVisualTokens.addVisualTokenElement;
if (isLastVisualTokenValid) { if (isLastVisualTokenValid) {
addVisualTokenElement(tokenName, tokenValue); addVisualTokenElement(tokenName, tokenValue, false);
} else { } else {
const previousTokenName = lastVisualToken.querySelector('.name').innerText; const previousTokenName = lastVisualToken.querySelector('.name').innerText;
const tokensContainer = document.querySelector('.tokens-container'); const tokensContainer = FilteredSearchContainer.container.querySelector('.tokens-container');
tokensContainer.removeChild(lastVisualToken); tokensContainer.removeChild(lastVisualToken);
const value = tokenValue || tokenName; const value = tokenValue || tokenName;
addVisualTokenElement(previousTokenName, value); addVisualTokenElement(previousTokenName, value, false);
} }
} }
...@@ -129,7 +131,7 @@ class FilteredSearchVisualTokens { ...@@ -129,7 +131,7 @@ class FilteredSearchVisualTokens {
} }
static tokenizeInput() { static tokenizeInput() {
const input = document.querySelector('.filtered-search'); const input = FilteredSearchContainer.container.querySelector('.filtered-search');
const { isLastVisualTokenValid } = const { isLastVisualTokenValid } =
gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput(); gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
...@@ -145,7 +147,7 @@ class FilteredSearchVisualTokens { ...@@ -145,7 +147,7 @@ class FilteredSearchVisualTokens {
} }
static editToken(token) { static editToken(token) {
const input = document.querySelector('.filtered-search'); const input = FilteredSearchContainer.container.querySelector('.filtered-search');
FilteredSearchVisualTokens.tokenizeInput(); FilteredSearchVisualTokens.tokenizeInput();
...@@ -157,7 +159,7 @@ class FilteredSearchVisualTokens { ...@@ -157,7 +159,7 @@ class FilteredSearchVisualTokens {
const name = token.querySelector('.name'); const name = token.querySelector('.name');
const value = token.querySelector('.value'); const value = token.querySelector('.value');
if (token.classList.contains('filtered-search-token')) { if (token.classList.contains('filtered-search-token') && value) {
FilteredSearchVisualTokens.addFilterVisualToken(name.innerText); FilteredSearchVisualTokens.addFilterVisualToken(name.innerText);
input.value = value.innerText; input.value = value.innerText;
} else { } else {
...@@ -174,9 +176,9 @@ class FilteredSearchVisualTokens { ...@@ -174,9 +176,9 @@ class FilteredSearchVisualTokens {
} }
static moveInputToTheRight() { static moveInputToTheRight() {
const input = document.querySelector('.filtered-search'); const input = FilteredSearchContainer.container.querySelector('.filtered-search');
const inputLi = input.parentElement; const inputLi = input.parentElement;
const tokenContainer = document.querySelector('.tokens-container'); const tokenContainer = FilteredSearchContainer.container.querySelector('.tokens-container');
FilteredSearchVisualTokens.tokenizeInput(); FilteredSearchVisualTokens.tokenizeInput();
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, no-param-reassign, quotes, quote-props, prefer-template, comma-dangle, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, no-param-reassign, quotes, quote-props, prefer-template, comma-dangle, max-len */
(function() {
this.Flash = (function() { window.Flash = (function() {
var hideFlash; var hideFlash;
hideFlash = function() { hideFlash = function() {
...@@ -38,5 +38,4 @@ ...@@ -38,5 +38,4 @@
} }
return Flash; return Flash;
})(); })();
}).call(window);
...@@ -5,16 +5,13 @@ import emojiAliases from 'emojis/aliases.json'; ...@@ -5,16 +5,13 @@ import emojiAliases from 'emojis/aliases.json';
import { glEmojiTag } from '~/behaviors/gl_emoji'; import { glEmojiTag } from '~/behaviors/gl_emoji';
// Creates the variables for setting up GFM auto-completion // Creates the variables for setting up GFM auto-completion
(function() { window.gl = window.gl || {};
if (window.gl == null) {
window.gl = {};
}
function sanitize(str) { function sanitize(str) {
return str.replace(/<(?:.|\n)*?>/gm, ''); return str.replace(/<(?:.|\n)*?>/gm, '');
} }
window.gl.GfmAutoComplete = { window.gl.GfmAutoComplete = {
dataSources: {}, dataSources: {},
defaultLoadingData: ['loading'], defaultLoadingData: ['loading'],
cachedData: {}, cachedData: {},
...@@ -390,5 +387,4 @@ import { glEmojiTag } from '~/behaviors/gl_emoji'; ...@@ -390,5 +387,4 @@ import { glEmojiTag } from '~/behaviors/gl_emoji';
return dataToInspect && return dataToInspect &&
(dataToInspect === loadingState || dataToInspect.name === loadingState); (dataToInspect === loadingState || dataToInspect.name === loadingState);
} }
}; };
}).call(window);
/* 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 */
(function() { var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote,
var GitLabDropdown, GitLabDropdownFilter, GitLabDropdownRemote,
bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }, bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
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; }; 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; };
GitLabDropdownFilter = (function() { GitLabDropdownFilter = (function() {
var ARROW_KEY_CODES, BLUR_KEYCODES, HAS_VALUE_CLASS; var ARROW_KEY_CODES, BLUR_KEYCODES, HAS_VALUE_CLASS;
BLUR_KEYCODES = [27, 40]; BLUR_KEYCODES = [27, 40];
...@@ -135,9 +134,9 @@ ...@@ -135,9 +134,9 @@
}; };
return GitLabDropdownFilter; return GitLabDropdownFilter;
})(); })();
GitLabDropdownRemote = (function() { GitLabDropdownRemote = (function() {
function GitLabDropdownRemote(dataEndpoint, options) { function GitLabDropdownRemote(dataEndpoint, options) {
this.dataEndpoint = dataEndpoint; this.dataEndpoint = dataEndpoint;
this.options = options; this.options = options;
...@@ -187,9 +186,9 @@ ...@@ -187,9 +186,9 @@
}; };
return GitLabDropdownRemote; return GitLabDropdownRemote;
})(); })();
GitLabDropdown = (function() { GitLabDropdown = (function() {
var ACTIVE_CLASS, FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, NON_SELECTABLE_CLASSES, SELECTABLE_CLASSES, CURSOR_SELECT_SCROLL_PADDING, currentIndex; var ACTIVE_CLASS, FILTER_INPUT, INDETERMINATE_CLASS, LOADING_CLASS, PAGE_TWO_CLASS, NON_SELECTABLE_CLASSES, SELECTABLE_CLASSES, CURSOR_SELECT_SCROLL_PADDING, currentIndex;
LOADING_CLASS = "is-loading"; LOADING_CLASS = "is-loading";
...@@ -838,13 +837,12 @@ ...@@ -838,13 +837,12 @@
}; };
return GitLabDropdown; return GitLabDropdown;
})(); })();
$.fn.glDropdown = function(opts) { $.fn.glDropdown = function(opts) {
return this.each(function() { return this.each(function() {
if (!$.data(this, 'glDropdown')) { if (!$.data(this, 'glDropdown')) {
return $.data(this, 'glDropdown', new GitLabDropdown(this, opts)); return $.data(this, 'glDropdown', new GitLabDropdown(this, opts));
} }
}); });
}; };
}).call(window);
/* eslint-disable no-param-reassign */ /**
((global) => {
/*
* This class overrides the browser's validation error bubbles, displaying custom * This class overrides the browser's validation error bubbles, displaying custom
* error messages for invalid fields instead. To begin validating any form, add the * error messages for invalid fields instead. To begin validating any form, add the
* class `gl-show-field-errors` to the form element, and ensure error messages are * class `gl-show-field-errors` to the form element, and ensure error messages are
...@@ -41,22 +39,22 @@ ...@@ -41,22 +39,22 @@
* // Error message now injected here * // Error message now injected here
* </form> * </form>
* *
* */ */
/* /**
* Regex Patterns in use: * Regex Patterns in use:
* *
* Only alphanumeric: : "[a-zA-Z0-9]+" * Only alphanumeric: : "[a-zA-Z0-9]+"
* No special characters : "[a-zA-Z0-9-_]+", * No special characters : "[a-zA-Z0-9-_]+",
* *
* */ */
const errorMessageClass = 'gl-field-error'; const errorMessageClass = 'gl-field-error';
const inputErrorClass = 'gl-field-error-outline'; const inputErrorClass = 'gl-field-error-outline';
const errorAnchorSelector = '.gl-field-error-anchor'; const errorAnchorSelector = '.gl-field-error-anchor';
const ignoreInputSelector = '.gl-field-error-ignore'; const ignoreInputSelector = '.gl-field-error-ignore';
class GlFieldError { class GlFieldError {
constructor({ input, formErrors }) { constructor({ input, formErrors }) {
this.inputElement = $(input); this.inputElement = $(input);
this.inputDomElement = this.inputElement.get(0); this.inputDomElement = this.inputElement.get(0);
...@@ -158,7 +156,7 @@ ...@@ -158,7 +156,7 @@
this.scopedSiblings.hide(); this.scopedSiblings.hide();
this.fieldErrorElement.hide(); this.fieldErrorElement.hide();
} }
} }
global.GlFieldError = GlFieldError; window.gl = window.gl || {};
})(window.gl || (window.gl = {})); window.gl.GlFieldError = GlFieldError;
...@@ -2,10 +2,9 @@ ...@@ -2,10 +2,9 @@
require('./gl_field_error'); require('./gl_field_error');
((global) => { const customValidationFlag = 'gl-field-error-ignore';
const customValidationFlag = 'gl-field-error-ignore';
class GlFieldErrors { class GlFieldErrors {
constructor(form) { constructor(form) {
this.form = $(form); this.form = $(form);
this.state = { this.state = {
...@@ -22,7 +21,7 @@ require('./gl_field_error'); ...@@ -22,7 +21,7 @@ require('./gl_field_error');
this.state.inputs = this.form.find(validateSelectors).toArray() this.state.inputs = this.form.find(validateSelectors).toArray()
.filter((input) => !input.classList.contains(customValidationFlag)) .filter((input) => !input.classList.contains(customValidationFlag))
.map((input) => new global.GlFieldError({ input, formErrors: this })); .map((input) => new window.gl.GlFieldError({ input, formErrors: this }));
this.form.on('submit', this.catchInvalidFormSubmit); this.form.on('submit', this.catchInvalidFormSubmit);
} }
...@@ -42,7 +41,7 @@ require('./gl_field_error'); ...@@ -42,7 +41,7 @@ require('./gl_field_error');
const firstInvalid = this.state.inputs.filter((input) => !input.inputDomElement.validity.valid)[0]; const firstInvalid = this.state.inputs.filter((input) => !input.inputDomElement.validity.valid)[0];
firstInvalid.inputElement.focus(); firstInvalid.inputElement.focus();
} }
} }
global.GlFieldErrors = GlFieldErrors; window.gl = window.gl || {};
})(window.gl || (window.gl = {})); window.gl.GlFieldErrors = GlFieldErrors;
...@@ -3,10 +3,9 @@ ...@@ -3,10 +3,9 @@
/* global DropzoneInput */ /* global DropzoneInput */
/* global autosize */ /* global autosize */
(() => { window.gl = window.gl || {};
const global = window.gl || (window.gl = {});
function GLForm(form) { function GLForm(form) {
this.form = form; this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input'); this.textarea = this.form.find('textarea.js-gfm-input');
// Before we start, we should clean up any previous data for this form // Before we start, we should clean up any previous data for this form
...@@ -14,15 +13,15 @@ ...@@ -14,15 +13,15 @@
// Setup the form // Setup the form
this.setupForm(); this.setupForm();
this.form.data('gl-form', this); this.form.data('gl-form', this);
} }
GLForm.prototype.destroy = function() { GLForm.prototype.destroy = function() {
// Clean form listeners // Clean form listeners
this.clearEventListeners(); this.clearEventListeners();
return this.form.data('gl-form', null); return this.form.data('gl-form', null);
}; };
GLForm.prototype.setupForm = function() { GLForm.prototype.setupForm = function() {
var isNewForm; var isNewForm;
isNewForm = this.form.is(':not(.gfm-form)'); isNewForm = this.form.is(':not(.gfm-form)');
this.form.removeClass('js-new-note-form'); this.form.removeClass('js-new-note-form');
...@@ -42,9 +41,9 @@ ...@@ -42,9 +41,9 @@
this.form.find('.js-note-discard').hide(); this.form.find('.js-note-discard').hide();
this.form.show(); this.form.show();
if (this.isAutosizeable) this.setupAutosize(); if (this.isAutosizeable) this.setupAutosize();
}; };
GLForm.prototype.setupAutosize = function () { GLForm.prototype.setupAutosize = function () {
this.textarea.off('autosize:resized') this.textarea.off('autosize:resized')
.on('autosize:resized', this.setHeightData.bind(this)); .on('autosize:resized', this.setHeightData.bind(this));
...@@ -55,13 +54,13 @@ ...@@ -55,13 +54,13 @@
autosize(this.textarea); autosize(this.textarea);
this.textarea.css('resize', 'vertical'); this.textarea.css('resize', 'vertical');
}, 0); }, 0);
}; };
GLForm.prototype.setHeightData = function () { GLForm.prototype.setHeightData = function () {
this.textarea.data('height', this.textarea.outerHeight()); this.textarea.data('height', this.textarea.outerHeight());
}; };
GLForm.prototype.destroyAutosize = function () { GLForm.prototype.destroyAutosize = function () {
const outerHeight = this.textarea.outerHeight(); const outerHeight = this.textarea.outerHeight();
if (this.textarea.data('height') === outerHeight) return; if (this.textarea.data('height') === outerHeight) return;
...@@ -71,22 +70,21 @@ ...@@ -71,22 +70,21 @@
this.textarea.data('height', outerHeight); this.textarea.data('height', outerHeight);
this.textarea.outerHeight(outerHeight); this.textarea.outerHeight(outerHeight);
this.textarea.css('max-height', window.outerHeight); this.textarea.css('max-height', window.outerHeight);
}; };
GLForm.prototype.clearEventListeners = function() { GLForm.prototype.clearEventListeners = function() {
this.textarea.off('focus'); this.textarea.off('focus');
this.textarea.off('blur'); this.textarea.off('blur');
return gl.text.removeListeners(this.form); return gl.text.removeListeners(this.form);
}; };
GLForm.prototype.addEventListeners = function() { GLForm.prototype.addEventListeners = function() {
this.textarea.on('focus', function() { this.textarea.on('focus', function() {
return $(this).closest('.md-area').addClass('is-focused'); return $(this).closest('.md-area').addClass('is-focused');
}); });
return this.textarea.on('blur', function() { return this.textarea.on('blur', function() {
return $(this).closest('.md-area').removeClass('is-focused'); return $(this).closest('.md-area').removeClass('is-focused');
}); });
}; };
global.GLForm = GLForm; window.gl.GLForm = GLForm;
})();
/* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, one-var, one-var-declaration-per-line, no-useless-escape, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, quotes, no-var, one-var, one-var-declaration-per-line, no-useless-escape, max-len */
(function() {
this.GroupAvatar = (function() { window.GroupAvatar = (function() {
function GroupAvatar() { function GroupAvatar() {
$('.js-choose-group-avatar-button').on("click", function() { $('.js-choose-group-avatar-button').on("click", function() {
var form; var form;
...@@ -16,5 +16,4 @@ ...@@ -16,5 +16,4 @@
} }
return GroupAvatar; return GroupAvatar;
})(); })();
}).call(window);
/* eslint-disable func-names, object-shorthand, comma-dangle, wrap-iife, space-before-function-paren, no-param-reassign, max-len */ /* eslint-disable func-names, object-shorthand, comma-dangle, wrap-iife, space-before-function-paren, no-param-reassign, max-len */
(function(global) { class GroupLabelSubscription {
class GroupLabelSubscription {
constructor(container) { constructor(container) {
const $container = $(container); const $container = $(container);
this.$dropdown = $container.find('.dropdown'); this.$dropdown = $container.find('.dropdown');
...@@ -47,7 +46,7 @@ ...@@ -47,7 +46,7 @@
this.$subscribeButtons.toggleClass('hidden'); this.$subscribeButtons.toggleClass('hidden');
this.$unsubscribeButtons.toggleClass('hidden'); this.$unsubscribeButtons.toggleClass('hidden');
} }
} }
global.GroupLabelSubscription = GroupLabelSubscription; window.gl = window.gl || {};
})(window.gl || (window.gl = {})); window.gl.GroupLabelSubscription = GroupLabelSubscription;
const GROUP_LIMIT = 2;
export default class GroupName {
constructor() {
this.titleContainer = document.querySelector('.title');
this.groups = document.querySelectorAll('.group-path');
this.groupTitle = document.querySelector('.group-title');
this.toggle = null;
this.isHidden = false;
this.init();
}
init() {
if (this.groups.length > GROUP_LIMIT) {
this.groups[this.groups.length - 1].classList.remove('hidable');
this.addToggle();
}
this.render();
}
addToggle() {
const header = document.querySelector('.header-content');
this.toggle = document.createElement('button');
this.toggle.className = 'text-expander group-name-toggle';
this.toggle.setAttribute('aria-label', 'Toggle full path');
this.toggle.innerHTML = '...';
this.toggle.addEventListener('click', this.toggleGroups.bind(this));
header.insertBefore(this.toggle, this.titleContainer);
this.toggleGroups();
}
toggleGroups() {
this.isHidden = !this.isHidden;
this.groupTitle.classList.toggle('is-hidden');
}
render() {
this.titleContainer.classList.remove('initializing');
}
}
/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, one-var, camelcase, one-var-declaration-per-line, quotes, object-shorthand, prefer-arrow-callback, comma-dangle, consistent-return, yoda, prefer-rest-params, prefer-spread, no-unused-vars, prefer-template, max-len */ /* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, one-var, camelcase, one-var-declaration-per-line, quotes, object-shorthand, prefer-arrow-callback, comma-dangle, consistent-return, yoda, prefer-rest-params, prefer-spread, no-unused-vars, prefer-template, max-len */
/* global Api */ /* global Api */
(function() { var slice = [].slice;
var slice = [].slice;
this.GroupsSelect = (function() { window.GroupsSelect = (function() {
function GroupsSelect() { function GroupsSelect() {
$('.ajax-groups-select').each((function(_this) { $('.ajax-groups-select').each((function(_this) {
return function(i, select) { return function(i, select) {
...@@ -67,5 +66,4 @@ ...@@ -67,5 +66,4 @@
}; };
return GroupsSelect; return GroupsSelect;
})(); })();
}).call(window);
/* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, no-var, max-len */ /* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var */
(function() {
$(document).on('todo:toggle', function(e, count) { $(document).on('todo:toggle', function(e, count) {
var $todoPendingCount = $('.todos-pending-count'); var $todoPendingCount = $('.todos-pending-count');
$todoPendingCount.text(gl.text.highCountTrim(count)); $todoPendingCount.text(gl.text.highCountTrim(count));
$todoPendingCount.toggleClass('hidden', count === 0); $todoPendingCount.toggleClass('hidden', count === 0);
}); });
})();
...@@ -353,31 +353,17 @@ ...@@ -353,31 +353,17 @@
return; return;
} }
if ($('html').hasClass('issue-boards-page') && !$dropdown.hasClass('js-issue-board-sidebar') && if ($dropdown.closest('.add-issues-modal').length) {
!$dropdown.closest('.add-issues-modal').length) {
boardsModel = gl.issueBoards.BoardsStore.state.filters;
} else if ($dropdown.closest('.add-issues-modal').length) {
boardsModel = gl.issueBoards.ModalStore.store.filter; boardsModel = gl.issueBoards.ModalStore.store.filter;
} }
if (boardsModel) { if (boardsModel) {
if (label.isAny) { if (label.isAny) {
boardsModel['label_name'] = []; boardsModel['label_name'] = [];
} } else if ($el.hasClass('is-active')) {
else if ($el.hasClass('is-active')) {
boardsModel['label_name'].push(label.title); boardsModel['label_name'].push(label.title);
} }
else {
var filters = boardsModel['label_name'];
filters = filters.filter(function (filteredLabel) {
return filteredLabel !== label.title;
});
boardsModel['label_name'] = filters;
}
if (!$dropdown.closest('.add-issues-modal').length) {
gl.issueBoards.BoardsStore.updateFiltersUrl();
}
e.preventDefault(); e.preventDefault();
return; return;
} }
......
...@@ -67,17 +67,7 @@ require('vendor/jquery.scrollTo'); ...@@ -67,17 +67,7 @@ require('vendor/jquery.scrollTo');
} }
LineHighlighter.prototype.bindEvents = function() { LineHighlighter.prototype.bindEvents = function() {
$('#blob-content-holder').on('mousedown', 'a[data-line-number]', this.clickHandler); $('#blob-content-holder').on('click', 'a[data-line-number]', this.clickHandler);
// While it may seem odd to bind to the mousedown event and then throw away
// the click event, there is a method to our madness.
//
// If not done this way, the line number anchor will sometimes keep its
// active state even when the event is cancelled, resulting in an ugly border
// around the link and/or a persisted underline text decoration.
$('#blob-content-holder').on('click', 'a[data-line-number]', function(event) {
event.preventDefault();
event.stopPropagation();
});
}; };
LineHighlighter.prototype.clickHandler = function(event) { LineHighlighter.prototype.clickHandler = function(event) {
......
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.
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