Merge remote-tracking branch 'upstream/master' into pipeline-emails
* upstream/master: (671 commits) Only check :can_resolve permission if the note is resolvable Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint Improve PipelinesFinder spec so that it does not depend on hard-coded database IDs Add changelogs/unreleased/.gitkeep Archive CHANGELOG entries prior to 8.0 in changelogs/archive.md Request only the LDAP attributes we need Add notification_settings API calls Use NotificationSetting::EMAIL_EVENTS for params Pull import sources from Gitlab::ImportSources Fix API issues sorting Use gitlab-workhorse 0.8.1 Update CHANGELOG Avoid conflict with admin labels when importing GitHub labels Search should compare only the lowercase versions of the project names Enable pipeline events by default Minor update on CI docs examples Move write_note into SlashCommandsHelper and update other dependent specs Wait for logout message in login_helpers Use wait_for_ajax to avoid database deadlocks after specs are cleaned Fix spec failures with spec/features/issues/user_uses_slash_commands_spec.rb Require comments must be included before code ...
This commit is contained in:
commit
985252126d
|
@ -48,3 +48,4 @@
|
|||
/vendor/bundle/*
|
||||
/builds/*
|
||||
/shared/*
|
||||
/.gitlab_workhorse_secret
|
||||
|
|
|
@ -82,7 +82,7 @@ update-knapsack:
|
|||
- export KNAPSACK_REPORT_PATH=knapsack/rspec_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
|
||||
- export KNAPSACK_GENERATE_REPORT=true
|
||||
- cp knapsack/rspec_report.json ${KNAPSACK_REPORT_PATH}
|
||||
- knapsack rspec
|
||||
- knapsack rspec "--color --format documentation"
|
||||
artifacts:
|
||||
expire_in: 31d
|
||||
paths:
|
||||
|
@ -248,6 +248,21 @@ bundler:audit:
|
|||
script:
|
||||
- "bundle exec bundle-audit check --update --ignore OSVDB-115941"
|
||||
|
||||
migration paths:
|
||||
stage: test
|
||||
<<: *use-db
|
||||
only:
|
||||
- master@gitlab-org/gitlab-ce
|
||||
script:
|
||||
- git checkout HEAD .
|
||||
- git fetch --tags
|
||||
- git checkout v8.5.9
|
||||
- 'echo test: unix:/var/opt/gitlab/redis/redis.socket > config/resque.yml'
|
||||
- bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}" --retry=3
|
||||
- rake db:drop db:create db:schema:load db:seed_fu
|
||||
- git checkout $CI_BUILD_REF
|
||||
- rake db:migrate
|
||||
|
||||
coverage:
|
||||
stage: post-test
|
||||
services: []
|
||||
|
@ -263,7 +278,6 @@ coverage:
|
|||
- coverage/index.html
|
||||
- coverage/assets/
|
||||
|
||||
|
||||
# Notify slack in the end
|
||||
|
||||
notify:slack:
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
### Summary
|
||||
|
||||
(Summarize the bug encountered concisely)
|
||||
|
||||
### Steps to reproduce
|
||||
|
||||
(How one can reproduce the issue - this is very important)
|
||||
|
||||
### Expected behavior
|
||||
|
||||
(What you should see instead)
|
||||
|
||||
### Actual behavior
|
||||
|
||||
(What actually happens)
|
||||
|
||||
### Relevant logs and/or screenshots
|
||||
|
||||
(Paste any relevant logs - please use code blocks (```) to format console output,
|
||||
logs, and code as it's very hard to read otherwise.)
|
||||
|
||||
### Output of checks
|
||||
|
||||
#### Results of GitLab application Check
|
||||
|
||||
(For installations with omnibus-gitlab package run and paste the output of:
|
||||
`sudo gitlab-rake gitlab:check SANITIZE=true`)
|
||||
|
||||
(For installations from source run and paste the output of:
|
||||
`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`)
|
||||
|
||||
(we will only investigate if the tests are passing)
|
||||
|
||||
#### Results of GitLab environment info
|
||||
|
||||
(For installations with omnibus-gitlab package run and paste the output of:
|
||||
`sudo gitlab-rake gitlab:env:info`)
|
||||
|
||||
(For installations from source run and paste the output of:
|
||||
`sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`)
|
||||
|
||||
### Possible fixes
|
||||
|
||||
(If you can, link to the line of code that might be responsible for the problem)
|
|
@ -0,0 +1,7 @@
|
|||
### Description
|
||||
|
||||
(Include problem, use cases, benefits, and/or goals)
|
||||
|
||||
### Proposal
|
||||
|
||||
### Links / references
|
|
@ -0,0 +1,14 @@
|
|||
See the general Documentation guidelines http://docs.gitlab.com/ce/development/doc_styleguide.html.
|
||||
|
||||
## What does this MR do?
|
||||
|
||||
(briefly describe what this MR is about)
|
||||
|
||||
## Moving docs to a new location?
|
||||
|
||||
See the guidelines: http://docs.gitlab.com/ce/development/doc_styleguide.html#changing-document-location
|
||||
|
||||
- [ ] Make sure the old link is not removed and has its contents replaced with a link to the new location.
|
||||
- [ ] Make sure internal links pointing to the document in question are not broken.
|
||||
- [ ] Search and replace any links referring to old docs in GitLab Rails app, specifically under the `app/views/` directory.
|
||||
- [ ] If working on CE, submit an MR to EE with the changes as well.
|
|
@ -5,8 +5,8 @@ require:
|
|||
inherit_from: .rubocop_todo.yml
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.1
|
||||
# Cop names are not displayed in offense messages by default. Change behavior
|
||||
TargetRubyVersion: 2.3
|
||||
# Cop names are not d§splayed in offense messages by default. Change behavior
|
||||
# by overriding DisplayCopNames, or by giving the -D/--display-cop-names
|
||||
# option.
|
||||
DisplayCopNames: true
|
||||
|
@ -192,6 +192,9 @@ Style/FlipFlop:
|
|||
Style/For:
|
||||
Enabled: true
|
||||
|
||||
# Checks if there is a magic comment to enforce string literals
|
||||
Style/FrozenStringLiteralComment:
|
||||
Enabled: false
|
||||
# Do not introduce global variables.
|
||||
Style/GlobalVars:
|
||||
Enabled: true
|
||||
|
|
|
@ -129,7 +129,7 @@ request that potentially fixes it.
|
|||
|
||||
### Feature proposals
|
||||
|
||||
To create a feature proposal for CE and CI, open an issue on the
|
||||
To create a feature proposal for CE, open an issue on the
|
||||
[issue tracker of CE][ce-tracker].
|
||||
|
||||
For feature proposals for EE, open an issue on the
|
||||
|
@ -144,16 +144,7 @@ code snippet right after your description in a new line: `~"feature proposal"`.
|
|||
Please keep feature proposals as small and simple as possible, complex ones
|
||||
might be edited to make them small and simple.
|
||||
|
||||
You are encouraged to use the template below for feature proposals.
|
||||
|
||||
```
|
||||
## Description
|
||||
Include problem, use cases, benefits, and/or goals
|
||||
|
||||
## Proposal
|
||||
|
||||
## Links / references
|
||||
```
|
||||
Please submit Feature Proposals using the ['Feature Proposal' issue template](.gitlab/issue_templates/Feature Proposal.md) provided on the issue tracker.
|
||||
|
||||
For changes in the interface, it can be helpful to create a mockup first.
|
||||
If you want to create something yourself, consider opening an issue first to
|
||||
|
@ -166,55 +157,11 @@ submitting your own, there's a good chance somebody else had the same issue or
|
|||
feature proposal. Show your support with an award emoji and/or join the
|
||||
discussion.
|
||||
|
||||
Please submit bugs using the following template in the issue description area.
|
||||
Please submit bugs using the ['Bug' issue template](.gitlab/issue_templates/Bug.md) provided on the issue tracker.
|
||||
The text in the parenthesis is there to help you with what to include. Omit it
|
||||
when submitting the actual issue. You can copy-paste it and then edit as you
|
||||
see fit.
|
||||
|
||||
```
|
||||
## Summary
|
||||
|
||||
(Summarize your issue in one sentence - what goes wrong, what did you expect to happen)
|
||||
|
||||
## Steps to reproduce
|
||||
|
||||
(How one can reproduce the issue - this is very important)
|
||||
|
||||
## Expected behavior
|
||||
|
||||
(What you should see instead)
|
||||
|
||||
## Relevant logs and/or screenshots
|
||||
|
||||
(Paste any relevant logs - please use code blocks (```) to format console output,
|
||||
logs, and code as it's very hard to read otherwise.)
|
||||
|
||||
## Output of checks
|
||||
|
||||
### Results of GitLab Application Check
|
||||
|
||||
(For installations with omnibus-gitlab package run and paste the output of:
|
||||
sudo gitlab-rake gitlab:check SANITIZE=true)
|
||||
|
||||
(For installations from source run and paste the output of:
|
||||
sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true)
|
||||
|
||||
(we will only investigate if the tests are passing)
|
||||
|
||||
### Results of GitLab Environment Info
|
||||
|
||||
(For installations with omnibus-gitlab package run and paste the output of:
|
||||
sudo gitlab-rake gitlab:env:info)
|
||||
|
||||
(For installations from source run and paste the output of:
|
||||
sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production)
|
||||
|
||||
## Possible fixes
|
||||
|
||||
(If you can, link to the line of code that might be responsible for the problem)
|
||||
|
||||
```
|
||||
|
||||
### Issue weight
|
||||
|
||||
Issue weight allows us to get an idea of the amount of work required to solve
|
||||
|
@ -340,6 +287,8 @@ request is as follows:
|
|||
migrations on a fresh database before the MR is reviewed. If the review leads
|
||||
to large changes in the MR, do this again once the review is complete.
|
||||
1. For more complex migrations, write tests.
|
||||
1. Merge requests **must** adhere to the [merge request performance
|
||||
guidelines](doc/development/merge_request_performance_guidelines.md).
|
||||
|
||||
The **official merge window** is in the beginning of the month from the 1st to
|
||||
the 7th day of the month. This is the best time to submit an MR and get
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.7.11
|
||||
0.8.1
|
||||
|
|
7
Gemfile
7
Gemfile
|
@ -53,7 +53,7 @@ gem 'browser', '~> 2.2'
|
|||
|
||||
# Extracting information from a git repository
|
||||
# Provide access to Gitlab::Git library
|
||||
gem 'gitlab_git', '~> 10.4.7'
|
||||
gem 'gitlab_git', '~> 10.6.3'
|
||||
|
||||
# LDAP Auth
|
||||
# GitLab fork with several improvements to original library. For full list of changes
|
||||
|
@ -97,9 +97,6 @@ gem 'fog-rackspace', '~> 0.1.1'
|
|||
# for aws storage
|
||||
gem 'unf', '~> 0.1.4'
|
||||
|
||||
# Authorization
|
||||
gem 'six', '~> 0.2.0'
|
||||
|
||||
# Seed data
|
||||
gem 'seed-fu', '~> 2.3.5'
|
||||
|
||||
|
@ -349,5 +346,5 @@ gem 'paranoia', '~> 2.0'
|
|||
gem 'health_check', '~> 2.1.0'
|
||||
|
||||
# System information
|
||||
gem 'vmstat', '~> 2.1.1'
|
||||
gem 'vmstat', '~> 2.2'
|
||||
gem 'sys-filesystem', '~> 1.1.6'
|
||||
|
|
12
Gemfile.lock
12
Gemfile.lock
|
@ -279,7 +279,7 @@ GEM
|
|||
diff-lcs (~> 1.1)
|
||||
mime-types (>= 1.16, < 3)
|
||||
posix-spawn (~> 0.3)
|
||||
gitlab_git (10.4.7)
|
||||
gitlab_git (10.6.3)
|
||||
activesupport (~> 4.0)
|
||||
charlock_holmes (~> 0.7.3)
|
||||
github-linguist (~> 4.7.0)
|
||||
|
@ -584,7 +584,7 @@ GEM
|
|||
railties (>= 4.2.0, < 5.1)
|
||||
rinku (2.0.0)
|
||||
rotp (2.1.2)
|
||||
rouge (2.0.5)
|
||||
rouge (2.0.6)
|
||||
rqrcode (0.7.0)
|
||||
chunky_png
|
||||
rqrcode-rails3 (0.1.7)
|
||||
|
@ -683,7 +683,6 @@ GEM
|
|||
rack (~> 1.5)
|
||||
rack-protection (~> 1.4)
|
||||
tilt (>= 1.3, < 3)
|
||||
six (0.2.0)
|
||||
slack-notifier (1.2.1)
|
||||
slop (3.6.0)
|
||||
spinach (0.8.10)
|
||||
|
@ -772,7 +771,7 @@ GEM
|
|||
coercible (~> 1.0)
|
||||
descendants_tracker (~> 0.0, >= 0.0.3)
|
||||
equalizer (~> 0.0, >= 0.0.9)
|
||||
vmstat (2.1.1)
|
||||
vmstat (2.2.0)
|
||||
warden (1.2.6)
|
||||
rack (>= 1.0)
|
||||
web-console (2.3.0)
|
||||
|
@ -859,7 +858,7 @@ DEPENDENCIES
|
|||
github-linguist (~> 4.7.0)
|
||||
github-markup (~> 1.4)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab_git (~> 10.4.7)
|
||||
gitlab_git (~> 10.6.3)
|
||||
gitlab_meta (= 7.0)
|
||||
gitlab_omniauth-ldap (~> 1.2.1)
|
||||
gollum-lib (~> 4.2)
|
||||
|
@ -954,7 +953,6 @@ DEPENDENCIES
|
|||
sidekiq-cron (~> 0.4.0)
|
||||
simplecov (= 0.12.0)
|
||||
sinatra (~> 1.4.4)
|
||||
six (~> 0.2.0)
|
||||
slack-notifier (~> 1.2.0)
|
||||
spinach-rails (~> 0.2.1)
|
||||
spinach-rerun-reporter (~> 0.0.2)
|
||||
|
@ -980,7 +978,7 @@ DEPENDENCIES
|
|||
unicorn-worker-killer (~> 0.4.2)
|
||||
version_sorter (~> 2.1.0)
|
||||
virtus (~> 1.0.1)
|
||||
vmstat (~> 2.1.1)
|
||||
vmstat (~> 2.2)
|
||||
web-console (~> 2.0)
|
||||
webmock (~> 1.21.0)
|
||||
wikicloth (= 0.8.1)
|
||||
|
|
|
@ -50,7 +50,7 @@ etc.).
|
|||
|
||||
The most important thing is making sure valid issues receive feedback from the
|
||||
development team. Therefore the priority is mentioning developers that can help
|
||||
on those issue. Please select someone with relevant experience from
|
||||
on those issues. Please select someone with relevant experience from
|
||||
[GitLab core team][core-team]. If there is nobody mentioned with that expertise
|
||||
look in the commit history for the affected files to find someone. Avoid
|
||||
mentioning the lead developer, this is the person that is least likely to give a
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 729 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="#333" fill-rule="evenodd" d="M9.683 6.676l-.047-.048C8.27 5.26 6.07 5.243 4.726 6.588l-2.29 2.29c-1.344 1.344-1.328 3.544.04 4.91 1.366 1.368 3.564 1.385 4.908.04l1.753-1.752c-.695.074-1.457-.078-2.176-.444L5.934 12.66c-.634.634-1.67.625-2.312-.017-.642-.643-.65-1.677-.017-2.312L6.035 7.9c.634-.634 1.67-.625 2.312.017.024.024.048.05.07.075l.003-.002c.36.36.943.366 1.3.01.355-.356.35-.938-.01-1.3l-.027-.024zM6.58 9.586l.048.05c1.367 1.366 3.565 1.384 4.91.04l2.29-2.292c1.344-1.343 1.328-3.542-.04-4.91-1.366-1.366-3.564-1.384-4.908-.04L7.127 4.187c.695-.074 1.457.078 2.176.444l1.028-1.027c.635-.634 1.67-.624 2.313.017.643.644.652 1.678.018 2.312l-2.43 2.432c-.635.634-1.67.624-2.313-.018-.024-.024-.048-.05-.07-.075l-.003.004c-.36-.362-.943-.367-1.3-.01-.355.355-.35.937.01 1.3.01.007.018.015.027.023z"/></svg>
|
After Width: | Height: | Size: 911 B |
|
@ -3,6 +3,7 @@
|
|||
LabelManager.prototype.errorMessage = 'Unable to update label prioritization at this time';
|
||||
|
||||
function LabelManager(opts) {
|
||||
// Defaults
|
||||
var ref, ref1, ref2;
|
||||
if (opts == null) {
|
||||
opts = {};
|
||||
|
@ -28,6 +29,7 @@
|
|||
$btn = $(e.currentTarget);
|
||||
$label = $("#" + ($btn.data('domId')));
|
||||
action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add';
|
||||
// Make sure tooltip will hide
|
||||
$tooltip = $("#" + ($btn.find('.has-tooltip:visible').attr('aria-describedby')));
|
||||
$tooltip.tooltip('destroy');
|
||||
return _this.toggleLabelPriority($label, action);
|
||||
|
@ -42,6 +44,7 @@
|
|||
url = $label.find('.js-toggle-priority').data('url');
|
||||
$target = this.prioritizedLabels;
|
||||
$from = this.otherLabels;
|
||||
// Optimistic update
|
||||
if (action === 'remove') {
|
||||
$target = this.otherLabels;
|
||||
$from = this.prioritizedLabels;
|
||||
|
@ -53,6 +56,7 @@
|
|||
$target.find('.empty-message').addClass('hidden');
|
||||
}
|
||||
$label.detach().appendTo($target);
|
||||
// Return if we are not persisting state
|
||||
if (!persistState) {
|
||||
return;
|
||||
}
|
||||
|
@ -61,6 +65,7 @@
|
|||
url: url,
|
||||
type: 'DELETE'
|
||||
});
|
||||
// Restore empty message
|
||||
if (!$from.find('li').length) {
|
||||
$from.find('.empty-message').removeClass('hidden');
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
window.gl = window.gl || {};
|
||||
((global) => {
|
||||
const MAX_MESSAGE_LENGTH = 500;
|
||||
const MESSAGE_CELL_SELECTOR = '.abuse-reports .message';
|
||||
|
@ -36,4 +35,4 @@ window.gl = window.gl || {};
|
|||
}
|
||||
|
||||
global.AbuseReports = AbuseReports;
|
||||
})(window.gl);
|
||||
})(window.gl || (window.gl = {}));
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
}
|
||||
|
||||
Activities.prototype.updateTooltips = function() {
|
||||
return gl.utils.localTimeAgo($('.js-timeago', '#activity'));
|
||||
return gl.utils.localTimeAgo($('.js-timeago', '.content_list'));
|
||||
};
|
||||
|
||||
Activities.prototype.reloadActivities = function() {
|
||||
|
@ -26,7 +26,7 @@
|
|||
event_filters = $.cookie("event_filter");
|
||||
filter = sender.attr("id").split("_")[0];
|
||||
$.cookie("event_filter", (event_filters !== filter ? filter : ""), {
|
||||
path: '/'
|
||||
path: gon.relative_url_root || '/'
|
||||
});
|
||||
if (event_filters !== filter) {
|
||||
return sender.closest('li').toggleClass("active");
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
return callback(group);
|
||||
});
|
||||
},
|
||||
// Return groups list. Filtered by query
|
||||
// Only active groups retrieved
|
||||
groups: function(query, skip_ldap, callback) {
|
||||
var url = Api.buildUrl(Api.groupsPath);
|
||||
return $.ajax({
|
||||
|
@ -38,6 +40,7 @@
|
|||
return callback(groups);
|
||||
});
|
||||
},
|
||||
// Return namespaces list. Filtered by query
|
||||
namespaces: function(query, callback) {
|
||||
var url = Api.buildUrl(Api.namespacesPath);
|
||||
return $.ajax({
|
||||
|
@ -52,6 +55,7 @@
|
|||
return callback(namespaces);
|
||||
});
|
||||
},
|
||||
// Return projects list. Filtered by query
|
||||
projects: function(query, order, callback) {
|
||||
var url = Api.buildUrl(Api.projectsPath);
|
||||
return $.ajax({
|
||||
|
@ -82,6 +86,7 @@
|
|||
return callback(message.responseJSON);
|
||||
});
|
||||
},
|
||||
// Return group projects list. Filtered by query
|
||||
groupProjects: function(group_id, query, callback) {
|
||||
var url = Api.buildUrl(Api.groupProjectsPath)
|
||||
.replace(':id', group_id);
|
||||
|
@ -97,6 +102,7 @@
|
|||
return callback(projects);
|
||||
});
|
||||
},
|
||||
// Return text for a specific license
|
||||
licenseText: function(key, data, callback) {
|
||||
var url = Api.buildUrl(Api.licensePath)
|
||||
.replace(':key', key);
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
// This is a manifest file that'll be compiled into including all the files listed below.
|
||||
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
|
||||
// be included in the compiled file accessible from http://example.com/assets/application.js
|
||||
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
||||
// the compiled file.
|
||||
//
|
||||
/*= require jquery2 */
|
||||
/*= require jquery-ui/autocomplete */
|
||||
/*= require jquery-ui/datepicker */
|
||||
|
@ -76,6 +82,7 @@
|
|||
}
|
||||
};
|
||||
|
||||
// Disable button if text field is empty
|
||||
window.disableButtonIfEmptyField = function(field_selector, button_selector) {
|
||||
var closest_submit, field;
|
||||
field = $(field_selector);
|
||||
|
@ -92,6 +99,7 @@
|
|||
});
|
||||
};
|
||||
|
||||
// Disable button if any input field with given selector is empty
|
||||
window.disableButtonIfAnyEmptyField = function(form, form_selector, button_selector) {
|
||||
var closest_submit, updateButtons;
|
||||
closest_submit = form.find(button_selector);
|
||||
|
@ -128,6 +136,8 @@
|
|||
window.addEventListener("hashchange", shiftWindow);
|
||||
|
||||
window.onload = function() {
|
||||
// Scroll the window to avoid the topnav bar
|
||||
// https://github.com/twitter/bootstrap/issues/1768
|
||||
if (location.hash) {
|
||||
return setTimeout(shiftWindow, 100);
|
||||
}
|
||||
|
@ -149,6 +159,8 @@
|
|||
return $(this).select().one('mouseup', function(e) {
|
||||
return e.preventDefault();
|
||||
});
|
||||
// Click a .js-select-on-focus field, select the contents
|
||||
// Prevent a mouseup event from deselecting the input
|
||||
});
|
||||
$('.remove-row').bind('ajax:success', function() {
|
||||
$(this).tooltip('destroy')
|
||||
|
@ -163,6 +175,7 @@
|
|||
});
|
||||
$('select.select2').select2({
|
||||
width: 'resolve',
|
||||
// Initialize select2 selects
|
||||
dropdownAutoWidth: true
|
||||
});
|
||||
$('.js-select2').bind('select2-close', function() {
|
||||
|
@ -170,25 +183,28 @@
|
|||
$('.select2-container-active').removeClass('select2-container-active');
|
||||
return $(':focus').blur();
|
||||
}), 1);
|
||||
// Close select2 on escape
|
||||
});
|
||||
// Initialize tooltips
|
||||
$body.tooltip({
|
||||
selector: '.has-tooltip, [data-toggle="tooltip"]',
|
||||
placement: function(_, el) {
|
||||
var $el;
|
||||
$el = $(el);
|
||||
return $el.data('placement') || 'bottom';
|
||||
return $(el).data('placement') || 'bottom';
|
||||
}
|
||||
});
|
||||
$('.trigger-submit').on('change', function() {
|
||||
return $(this).parents('form').submit();
|
||||
// Form submitter
|
||||
});
|
||||
gl.utils.localTimeAgo($('abbr.timeago, .js-timeago'), true);
|
||||
// Flash
|
||||
if ((flash = $(".flash-container")).length > 0) {
|
||||
flash.click(function() {
|
||||
return $(this).fadeOut();
|
||||
});
|
||||
flash.show();
|
||||
}
|
||||
// Disable form buttons while a form is submitting
|
||||
$body.on('ajax:complete, ajax:beforeSend, submit', 'form', function(e) {
|
||||
var buttons;
|
||||
buttons = $('[type="submit"]', this);
|
||||
|
@ -209,6 +225,7 @@
|
|||
}
|
||||
});
|
||||
$('.account-box').hover(function() {
|
||||
// Show/Hide the profile menu when hovering the account box
|
||||
return $(this).toggleClass('hover');
|
||||
});
|
||||
$document.on('click', '.diff-content .js-show-suppressed-diff', function() {
|
||||
|
@ -216,6 +233,7 @@
|
|||
$container = $(this).parent();
|
||||
$container.next('table').show();
|
||||
return $container.remove();
|
||||
// Commit show suppressed diff
|
||||
});
|
||||
$('.navbar-toggle').on('click', function() {
|
||||
$('.header-content .title').toggle();
|
||||
|
@ -223,6 +241,7 @@
|
|||
$('.header-content .navbar-collapse').toggle();
|
||||
return $('.navbar-toggle').toggleClass('active');
|
||||
});
|
||||
// Show/hide comments on diff
|
||||
$body.on("click", ".js-toggle-diff-comments", function(e) {
|
||||
var $this = $(this);
|
||||
$this.toggleClass('active');
|
||||
|
@ -286,42 +305,9 @@
|
|||
gl.awardsHandler = new AwardsHandler();
|
||||
checkInitialSidebarSize();
|
||||
new Aside();
|
||||
if ($window.width() < 1024 && $.cookie('pin_nav') === 'true') {
|
||||
$.cookie('pin_nav', 'false', {
|
||||
path: '/',
|
||||
expires: 365 * 10
|
||||
});
|
||||
$('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded').removeClass('page-sidebar-pinned');
|
||||
$('.navbar-fixed-top').removeClass('header-pinned-nav');
|
||||
}
|
||||
$document.off('click', '.js-nav-pin').on('click', '.js-nav-pin', function(e) {
|
||||
var $page, $pinBtn, $tooltip, $topNav, doPinNav, tooltipText;
|
||||
e.preventDefault();
|
||||
$pinBtn = $(e.currentTarget);
|
||||
$page = $('.page-with-sidebar');
|
||||
$topNav = $('.navbar-fixed-top');
|
||||
$tooltip = $("#" + ($pinBtn.attr('aria-describedby')));
|
||||
doPinNav = !$page.is('.page-sidebar-pinned');
|
||||
tooltipText = 'Pin navigation';
|
||||
$(this).toggleClass('is-active');
|
||||
if (doPinNav) {
|
||||
$page.addClass('page-sidebar-pinned');
|
||||
$topNav.addClass('header-pinned-nav');
|
||||
} else {
|
||||
$tooltip.remove();
|
||||
$page.removeClass('page-sidebar-pinned').toggleClass('page-sidebar-collapsed page-sidebar-expanded');
|
||||
$topNav.removeClass('header-pinned-nav').toggleClass('header-collapsed header-expanded');
|
||||
}
|
||||
$.cookie('pin_nav', doPinNav, {
|
||||
path: '/',
|
||||
expires: 365 * 10
|
||||
});
|
||||
if ($.cookie('pin_nav') === 'true' || doPinNav) {
|
||||
tooltipText = 'Unpin navigation';
|
||||
}
|
||||
$tooltip.find('.tooltip-inner').text(tooltipText);
|
||||
return $pinBtn.attr('title', tooltipText).tooltip('fixTitle');
|
||||
});
|
||||
|
||||
// bind sidebar events
|
||||
new gl.Sidebar();
|
||||
|
||||
// Custom time ago
|
||||
gl.utils.shortTimeAgo($('.js-short-timeago'));
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
}
|
||||
|
||||
Autosave.prototype.restore = function() {
|
||||
var e, error, text;
|
||||
var e, text;
|
||||
if (window.localStorage == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -41,7 +41,7 @@
|
|||
if ((text != null ? text.length : void 0) > 0) {
|
||||
try {
|
||||
return window.localStorage.setItem(this.key, text);
|
||||
} catch (undefined) {}
|
||||
} catch (error) {}
|
||||
} else {
|
||||
return this.reset();
|
||||
}
|
||||
|
@ -53,7 +53,7 @@
|
|||
}
|
||||
try {
|
||||
return window.localStorage.removeItem(this.key);
|
||||
} catch (undefined) {}
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
return Autosave;
|
||||
|
|
|
@ -86,6 +86,8 @@
|
|||
AwardsHandler.prototype.positionMenu = function($menu, $addBtn) {
|
||||
var css, position;
|
||||
position = $addBtn.data('position');
|
||||
// The menu could potentially be off-screen or in a hidden overflow element
|
||||
// So we position the element absolute in the body
|
||||
css = {
|
||||
top: ($addBtn.offset().top + $addBtn.outerHeight()) + "px"
|
||||
};
|
||||
|
@ -255,12 +257,12 @@
|
|||
};
|
||||
|
||||
AwardsHandler.prototype.animateEmoji = function($emoji) {
|
||||
var className;
|
||||
className = 'pulse animated';
|
||||
var className = 'pulse animated once short';
|
||||
$emoji.addClass(className);
|
||||
return setTimeout((function() {
|
||||
return $emoji.removeClass(className);
|
||||
}), 321);
|
||||
|
||||
$emoji.on('webkitAnimationEnd animationEnd', function() {
|
||||
$(this).removeClass(className);
|
||||
});
|
||||
};
|
||||
|
||||
AwardsHandler.prototype.createEmoji = function(votesBlock, emoji) {
|
||||
|
@ -284,6 +286,7 @@
|
|||
if (emojiIcon.length > 0) {
|
||||
unicodeName = emojiIcon.data('unicode-name');
|
||||
} else {
|
||||
// Find by alias
|
||||
unicodeName = $(".emoji-menu-content [data-aliases*=':" + emoji + ":']").data('unicode-name');
|
||||
}
|
||||
return "emoji-" + unicodeName;
|
||||
|
@ -320,6 +323,7 @@
|
|||
frequentlyUsedEmojis = this.getFrequentlyUsedEmojis();
|
||||
frequentlyUsedEmojis.push(emoji);
|
||||
return $.cookie('frequently_used_emojis', frequentlyUsedEmojis.join(','), {
|
||||
path: gon.relative_url_root || '/',
|
||||
expires: 365
|
||||
});
|
||||
};
|
||||
|
@ -349,8 +353,10 @@
|
|||
return function(ev) {
|
||||
var found_emojis, h5, term, ul;
|
||||
term = $(ev.target).val();
|
||||
// Clean previous search results
|
||||
$('ul.emoji-menu-search, h5.emoji-search').remove();
|
||||
if (term) {
|
||||
// Generate a search result block
|
||||
h5 = $('<h5>').text('Search results');
|
||||
found_emojis = _this.searchEmojis(term).show();
|
||||
ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(found_emojis);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
|
||||
/*= require jquery.ba-resize */
|
||||
|
||||
|
||||
/*= require autosize */
|
||||
|
||||
(function() {
|
||||
|
|
|
@ -5,6 +5,12 @@
|
|||
container = $(this).closest(".js-details-container");
|
||||
return container.toggleClass("open");
|
||||
});
|
||||
// Show details content. Hides link after click.
|
||||
//
|
||||
// %div
|
||||
// %a.js-details-expand
|
||||
// %div.js-details-content
|
||||
//
|
||||
return $("body").on("click", ".js-details-expand", function(e) {
|
||||
$(this).next('.js-details-content').removeClass("hide");
|
||||
$(this).hide();
|
||||
|
|
|
@ -1,6 +1,20 @@
|
|||
|
||||
// Quick Submit behavior
|
||||
//
|
||||
// When a child field of a form with a `js-quick-submit` class receives a
|
||||
// "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form
|
||||
// is submitted.
|
||||
//
|
||||
/*= require extensions/jquery */
|
||||
|
||||
//
|
||||
// ### Example Markup
|
||||
//
|
||||
// <form action="/foo" class="js-quick-submit">
|
||||
// <input type="text" />
|
||||
// <textarea></textarea>
|
||||
// <input type="submit" value="Submit" />
|
||||
// </form>
|
||||
//
|
||||
(function() {
|
||||
var isMac, keyCodeIs;
|
||||
|
||||
|
@ -17,6 +31,7 @@
|
|||
|
||||
$(document).on('keydown.quick_submit', '.js-quick-submit', function(e) {
|
||||
var $form, $submit_button;
|
||||
// Enter
|
||||
if (!keyCodeIs(e, 13)) {
|
||||
return;
|
||||
}
|
||||
|
@ -33,8 +48,11 @@
|
|||
return $form.submit();
|
||||
});
|
||||
|
||||
// If the user tabs to a submit button on a `js-quick-submit` form, display a
|
||||
// tooltip to let them know they could've used the hotkey
|
||||
$(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', function(e) {
|
||||
var $this, title;
|
||||
// Tab
|
||||
if (!keyCodeIs(e, 9)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
|
||||
// Requires Input behavior
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
/*= require extensions/jquery */
|
||||
|
||||
//
|
||||
// ### Example Markup
|
||||
//
|
||||
// <form class="js-requires-input">
|
||||
// <input type="text" required="required">
|
||||
// <input type="submit" value="Submit">
|
||||
// </form>
|
||||
//
|
||||
(function() {
|
||||
$.fn.requiresInput = function() {
|
||||
var $button, $form, fieldSelector, requireInput, required;
|
||||
|
@ -11,14 +23,17 @@
|
|||
requireInput = function() {
|
||||
var values;
|
||||
values = _.map($(fieldSelector, $form), function(field) {
|
||||
// Collect the input values of *all* required fields
|
||||
return field.value;
|
||||
});
|
||||
// Disable the button if any required fields are empty
|
||||
if (values.length && _.any(values, _.isEmpty)) {
|
||||
return $button.disable();
|
||||
} else {
|
||||
return $button.enable();
|
||||
}
|
||||
};
|
||||
// Set initial button state
|
||||
requireInput();
|
||||
return $form.on('change input', fieldSelector, requireInput);
|
||||
};
|
||||
|
@ -27,6 +42,8 @@
|
|||
var $form, hideOrShowHelpBlock;
|
||||
$form = $('form.js-requires-input');
|
||||
$form.requiresInput();
|
||||
// Hide or Show the help block when creating a new project
|
||||
// based on the option selected
|
||||
hideOrShowHelpBlock = function(form) {
|
||||
var selected;
|
||||
selected = $('.js-select-namespace option:selected');
|
||||
|
|
|
@ -1,10 +1,33 @@
|
|||
(function() {
|
||||
(function(w) {
|
||||
$(function() {
|
||||
return $("body").on("click", ".js-toggle-button", function(e) {
|
||||
$(this).find('i').toggleClass('fa fa-chevron-down').toggleClass('fa fa-chevron-up');
|
||||
$(this).closest(".js-toggle-container").find(".js-toggle-content").toggle();
|
||||
return e.preventDefault();
|
||||
// Toggle button. Show/hide content inside parent container.
|
||||
// Button does not change visibility. If button has icon - it changes chevron style.
|
||||
//
|
||||
// %div.js-toggle-container
|
||||
// %a.js-toggle-button
|
||||
// %div.js-toggle-content
|
||||
//
|
||||
$('body').on('click', '.js-toggle-button', function(e) {
|
||||
e.preventDefault();
|
||||
$(this)
|
||||
.find('.fa')
|
||||
.toggleClass('fa-chevron-down fa-chevron-up')
|
||||
.end()
|
||||
.closest('.js-toggle-container')
|
||||
.find('.js-toggle-content')
|
||||
.toggle()
|
||||
;
|
||||
});
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
// If we're accessing a permalink, ensure it is not inside a
|
||||
// closed js-toggle-container!
|
||||
var hash = w.gl.utils.getLocationHash();
|
||||
var anchor = hash && document.getElementById(hash);
|
||||
var container = anchor && $(anchor).closest('.js-toggle-container');
|
||||
|
||||
if (container && container.find('.js-toggle-content').is(':hidden')) {
|
||||
container.find('.js-toggle-button').trigger('click');
|
||||
anchor.scrollIntoView();
|
||||
}
|
||||
});
|
||||
})(window);
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
autoDiscover: false,
|
||||
autoProcessQueue: false,
|
||||
url: form.attr('action'),
|
||||
// Rails uses a hidden input field for PUT
|
||||
// http://stackoverflow.com/questions/21056482/how-to-set-method-put-in-form-tag-in-rails
|
||||
method: method,
|
||||
clickable: true,
|
||||
uploadMultiple: false,
|
||||
|
@ -36,6 +38,7 @@
|
|||
formData.append('commit_message', form.find('.js-commit-message').val());
|
||||
});
|
||||
},
|
||||
// Override behavior of adding error underneath preview
|
||||
error: function(file, errorMessage) {
|
||||
var stripped;
|
||||
stripped = $("<div/>").html(errorMessage).text();
|
||||
|
|
|
@ -66,6 +66,9 @@
|
|||
// be added by all subclasses.
|
||||
};
|
||||
|
||||
// To be implemented on the extending class
|
||||
// e.g.
|
||||
// Api.gitignoreText item.name, @requestFileSuccess.bind(@)
|
||||
TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) {
|
||||
this.editor.setValue(file.content, 1);
|
||||
if (!skipFocus) this.editor.focus();
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
return function() {
|
||||
return $("#file-content").val(_this.editor.getValue());
|
||||
};
|
||||
// Before a form submission, move the content from the Ace editor into the
|
||||
// submitted textarea
|
||||
})(this));
|
||||
this.initModePanesAndLinks();
|
||||
new BlobLicenseSelectors({
|
||||
|
|
|
@ -54,4 +54,11 @@ $(() => {
|
|||
});
|
||||
}
|
||||
});
|
||||
|
||||
gl.IssueBoardsSearch = new Vue({
|
||||
el: '#js-boards-seach',
|
||||
data: {
|
||||
filters: Store.state.filters
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,15 +21,10 @@
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
query: '',
|
||||
filters: Store.state.filters
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
query () {
|
||||
this.list.filters = this.getFilterData();
|
||||
this.list.getIssues(true);
|
||||
},
|
||||
filters: {
|
||||
handler () {
|
||||
this.list.page = 1;
|
||||
|
@ -38,16 +33,6 @@
|
|||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getFilterData () {
|
||||
const filters = this.filters;
|
||||
let queryData = { search: this.query };
|
||||
|
||||
Object.keys(filters).forEach((key) => { queryData[key] = filters[key]; });
|
||||
|
||||
return queryData;
|
||||
}
|
||||
},
|
||||
ready () {
|
||||
const options = gl.issueBoards.getBoardSortableDefaultOptions({
|
||||
disabled: this.disabled,
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
data () {
|
||||
return {
|
||||
scrollOffset: 250,
|
||||
filters: Store.state.filters
|
||||
filters: Store.state.filters,
|
||||
showCount: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
|
@ -30,6 +31,15 @@
|
|||
this.$els.list.scrollTop = 0;
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
issues () {
|
||||
this.$nextTick(() => {
|
||||
if (this.scrollHeight() > this.listHeight()) {
|
||||
this.showCount = true;
|
||||
} else {
|
||||
this.showCount = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -58,6 +68,7 @@
|
|||
group: 'issues',
|
||||
sort: false,
|
||||
disabled: this.disabled,
|
||||
filter: '.board-list-count',
|
||||
onStart: (e) => {
|
||||
const card = this.$refs.issue[e.oldIndex];
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ class List {
|
|||
this.loading = true;
|
||||
this.loadingMore = false;
|
||||
this.issues = [];
|
||||
this.issuesSize = 0;
|
||||
|
||||
if (obj.label) {
|
||||
this.label = new ListLabel(obj.label);
|
||||
|
@ -51,17 +52,13 @@ class List {
|
|||
}
|
||||
|
||||
nextPage () {
|
||||
if (Math.floor(this.issues.length / 20) === this.page) {
|
||||
if (this.issuesSize > this.issues.length) {
|
||||
this.page++;
|
||||
|
||||
return this.getIssues(false);
|
||||
}
|
||||
}
|
||||
|
||||
canSearch () {
|
||||
return this.type === 'backlog';
|
||||
}
|
||||
|
||||
getIssues (emptyIssues = true) {
|
||||
const filters = this.filters;
|
||||
let data = { page: this.page };
|
||||
|
@ -80,12 +77,13 @@ class List {
|
|||
.then((resp) => {
|
||||
const data = resp.json();
|
||||
this.loading = false;
|
||||
this.issuesSize = data.size;
|
||||
|
||||
if (emptyIssues) {
|
||||
this.issues = [];
|
||||
}
|
||||
|
||||
this.createIssues(data);
|
||||
this.createIssues(data.issues);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -96,14 +94,20 @@ class List {
|
|||
}
|
||||
|
||||
addIssue (issue, listFrom) {
|
||||
this.issues.push(issue);
|
||||
if (!this.findIssue(issue.id)) {
|
||||
this.issues.push(issue);
|
||||
|
||||
if (this.label) {
|
||||
issue.addLabel(this.label);
|
||||
}
|
||||
if (this.label) {
|
||||
issue.addLabel(this.label);
|
||||
}
|
||||
|
||||
if (listFrom) {
|
||||
gl.boardService.moveIssue(issue.id, listFrom.id, this.id);
|
||||
if (listFrom) {
|
||||
this.issuesSize++;
|
||||
gl.boardService.moveIssue(issue.id, listFrom.id, this.id)
|
||||
.then(() => {
|
||||
listFrom.getIssues(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,6 +120,7 @@ class List {
|
|||
const matchesRemove = removeIssue.id === issue.id;
|
||||
|
||||
if (matchesRemove) {
|
||||
this.issuesSize--;
|
||||
issue.removeLabel(this.label);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
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[]')
|
||||
label_name: gl.utils.getParameterValues('label_name[]'),
|
||||
search: ''
|
||||
};
|
||||
},
|
||||
addList (listObj) {
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
Vue.http.interceptors.push((request, next) => {
|
||||
Vue.http.interceptors.push((request, next) => {
|
||||
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
|
||||
|
||||
Vue.nextTick(() => {
|
||||
setTimeout(() => {
|
||||
Vue.activeResources--;
|
||||
}, 500);
|
||||
next(function (response) {
|
||||
Vue.activeResources--;
|
||||
});
|
||||
next();
|
||||
});
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
if ($(allDeviceSelector.join(",")).length) {
|
||||
return;
|
||||
}
|
||||
// Create all the elements
|
||||
els = $.map(BREAKPOINTS, function(breakpoint) {
|
||||
return "<div class='device-" + breakpoint + " visible-" + breakpoint + "'></div>";
|
||||
});
|
||||
|
@ -40,6 +41,7 @@
|
|||
BreakpointInstance.prototype.getBreakpointSize = function() {
|
||||
var $visibleDevice;
|
||||
$visibleDevice = this.visibleDevice;
|
||||
// the page refreshed via turbolinks
|
||||
if (!$visibleDevice().length) {
|
||||
this.setup();
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
this.toggleSidebar = bind(this.toggleSidebar, this);
|
||||
this.updateDropdown = bind(this.updateDropdown, this);
|
||||
clearInterval(Build.interval);
|
||||
// Init breakpoint checker
|
||||
this.bp = Breakpoints.get();
|
||||
$('.js-build-sidebar').niceScroll();
|
||||
|
||||
|
@ -42,6 +43,9 @@
|
|||
$(this).data("state", "enabled");
|
||||
return $(this).text("disable autoscroll");
|
||||
}
|
||||
//
|
||||
// Bind autoscroll button to follow build output
|
||||
//
|
||||
});
|
||||
Build.interval = setInterval((function(_this) {
|
||||
return function() {
|
||||
|
@ -49,17 +53,23 @@
|
|||
return _this.getBuildTrace();
|
||||
}
|
||||
};
|
||||
//
|
||||
// Check for new build output if user still watching build page
|
||||
// Only valid for runnig build when output changes during time
|
||||
//
|
||||
})(this), 4000);
|
||||
}
|
||||
}
|
||||
|
||||
Build.prototype.getInitialBuildTrace = function() {
|
||||
var removeRefreshStatuses = ['success', 'failed', 'canceled', 'skipped']
|
||||
|
||||
return $.ajax({
|
||||
url: this.build_url,
|
||||
dataType: 'json',
|
||||
success: function(build_data) {
|
||||
$('.js-build-output').html(build_data.trace_html);
|
||||
if (build_data.status === 'success' || build_data.status === 'failed') {
|
||||
if (removeRefreshStatuses.indexOf(build_data.status) >= 0) {
|
||||
return $('.js-build-refresh').remove();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
$(function(){
|
||||
$('.reveal-variables').off('click').on('click',function(){
|
||||
$('.js-build').toggle().niceScroll();
|
||||
$(this).hide();
|
||||
});
|
||||
});
|
|
@ -2,6 +2,7 @@
|
|||
this.ImageFile = (function() {
|
||||
var prepareFrames;
|
||||
|
||||
// Width where images must fits in, for 2-up this gets divided by 2
|
||||
ImageFile.availWidth = 900;
|
||||
|
||||
ImageFile.viewModes = ['two-up', 'swipe'];
|
||||
|
@ -9,6 +10,7 @@
|
|||
function ImageFile(file) {
|
||||
this.file = file;
|
||||
this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) {
|
||||
// Determine if old and new file has same dimensions, if not show 'two-up' view
|
||||
return function(deletedWidth, deletedHeight) {
|
||||
return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) {
|
||||
if (width === deletedWidth && height === deletedHeight) {
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
CommitsList.content.html(data.html);
|
||||
return history.replaceState({
|
||||
page: commitsUrl
|
||||
// Change url so if user reload a page - search results are saved
|
||||
}, document.title, commitsUrl);
|
||||
},
|
||||
dataType: "json"
|
||||
|
|
|
@ -6,14 +6,19 @@
|
|||
|
||||
genericSuccess = function(e) {
|
||||
showTooltip(e.trigger, 'Copied!');
|
||||
// Clear the selection and blur the trigger so it loses its border
|
||||
e.clearSelection();
|
||||
return $(e.trigger).blur();
|
||||
};
|
||||
|
||||
// Safari doesn't support `execCommand`, so instead we inform the user to
|
||||
// copy manually.
|
||||
//
|
||||
// See http://clipboardjs.com/#browser-support
|
||||
genericError = function(e) {
|
||||
var key;
|
||||
if (/Mac/i.test(navigator.userAgent)) {
|
||||
key = '⌘';
|
||||
key = '⌘'; // Command
|
||||
} else {
|
||||
key = 'Ctrl';
|
||||
}
|
||||
|
|
|
@ -39,6 +39,9 @@
|
|||
bottom: unfoldBottom,
|
||||
offset: offset,
|
||||
unfold: unfold,
|
||||
// indent is used to compensate for single space indent to fit
|
||||
// '+' and '-' prepended to diff lines,
|
||||
// see https://gitlab.com/gitlab-org/gitlab-ce/issues/707
|
||||
indent: 1,
|
||||
view: file.data('view')
|
||||
};
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
case 'projects:boards:show':
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
break;
|
||||
case 'projects:merge_requests:index':
|
||||
case 'projects:issues:index':
|
||||
Issuable.init();
|
||||
new IssuableBulkActions();
|
||||
|
@ -93,10 +94,6 @@
|
|||
break;
|
||||
case "projects:merge_requests:conflicts":
|
||||
window.mcui = new MergeConflictResolver()
|
||||
case 'projects:merge_requests:index':
|
||||
shortcut_handler = new ShortcutsNavigation();
|
||||
Issuable.init();
|
||||
break;
|
||||
case 'dashboard:activity':
|
||||
new Activities();
|
||||
break;
|
||||
|
@ -167,6 +164,8 @@
|
|||
}
|
||||
break;
|
||||
case 'projects:network:show':
|
||||
// Ensure we don't create a particular shortcut handler here. This is
|
||||
// already created, where the network graph is created.
|
||||
shortcut_handler = true;
|
||||
break;
|
||||
case 'projects:forks:new':
|
||||
|
@ -199,6 +198,7 @@
|
|||
break;
|
||||
case 'labels':
|
||||
switch (path[2]) {
|
||||
case 'new':
|
||||
case 'edit':
|
||||
new Labels();
|
||||
}
|
||||
|
@ -262,12 +262,14 @@
|
|||
shortcut_handler = new ShortcutsNavigation();
|
||||
}
|
||||
}
|
||||
// If we haven't installed a custom shortcut handler, install the default one
|
||||
if (!shortcut_handler) {
|
||||
return new Shortcuts();
|
||||
}
|
||||
};
|
||||
|
||||
Dispatcher.prototype.initSearch = function() {
|
||||
// Only when search form is present
|
||||
if ($('.search').length) {
|
||||
return new SearchAutocomplete();
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
this.DueDateSelect = (function() {
|
||||
function DueDateSelect() {
|
||||
var $datePicker, $dueDate, $loading;
|
||||
// Milestone edit/new form
|
||||
$datePicker = $('.datepicker');
|
||||
if ($datePicker.length) {
|
||||
$dueDate = $('#milestone_due_date');
|
||||
|
@ -16,6 +17,7 @@
|
|||
e.preventDefault();
|
||||
return $.datepicker._clearDate($datePicker);
|
||||
});
|
||||
// Issuable sidebar
|
||||
$loading = $('.js-issuable-update .due_date').find('.block-loading').hide();
|
||||
$('.js-due-date-select').each(function(i, dropdown) {
|
||||
var $block, $dropdown, $dropdownParent, $selectbox, $sidebarValue, $value, $valueContent, abilityName, addDueDate, fieldName, issueUpdateURL;
|
||||
|
@ -38,6 +40,7 @@
|
|||
});
|
||||
addDueDate = function(isDropdown) {
|
||||
var data, date, mediumDate, value;
|
||||
// Create the post date
|
||||
value = $("input[name='" + fieldName + "']").val();
|
||||
if (value !== '') {
|
||||
date = new Date(value.replace(new RegExp('-', 'g'), ','));
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// Disable an element and add the 'disabled' Bootstrap class
|
||||
(function() {
|
||||
$.fn.extend({
|
||||
disable: function() {
|
||||
|
@ -5,6 +6,7 @@
|
|||
}
|
||||
});
|
||||
|
||||
// Enable an element and remove the 'disabled' Bootstrap class
|
||||
$.fn.extend({
|
||||
enable: function() {
|
||||
return $(this).removeAttr('disabled').removeClass('disabled');
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// Creates the variables for setting up GFM auto-completion
|
||||
(function() {
|
||||
if (window.GitLab == null) {
|
||||
window.GitLab = {};
|
||||
|
@ -8,18 +9,22 @@
|
|||
dataLoaded: false,
|
||||
cachedData: {},
|
||||
dataSource: '',
|
||||
// Emoji
|
||||
Emoji: {
|
||||
template: '<li>${name} <img alt="${name}" height="20" src="${path}" width="20" /></li>'
|
||||
},
|
||||
// Team Members
|
||||
Members: {
|
||||
template: '<li>${username} <small>${title}</small></li>'
|
||||
},
|
||||
Labels: {
|
||||
template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>'
|
||||
},
|
||||
// Issues and MergeRequests
|
||||
Issues: {
|
||||
template: '<li><small>${id}</small> ${title}</li>'
|
||||
},
|
||||
// Milestones
|
||||
Milestones: {
|
||||
template: '<li>${title}</li>'
|
||||
},
|
||||
|
@ -48,8 +53,11 @@
|
|||
}
|
||||
},
|
||||
setup: function(input) {
|
||||
// Add GFM auto-completion to all input fields, that accept GFM input.
|
||||
this.input = input || $('.js-gfm-input');
|
||||
// destroy previous instances
|
||||
this.destroyAtWho();
|
||||
// set up instances
|
||||
this.setupAtWho();
|
||||
if (this.dataSource) {
|
||||
if (!this.dataLoading && !this.cachedData) {
|
||||
|
@ -63,6 +71,11 @@
|
|||
return _this.loadData(data);
|
||||
});
|
||||
};
|
||||
// We should wait until initializations are done
|
||||
// and only trigger the last .setup since
|
||||
// The previous .dataSource belongs to the previous issuable
|
||||
// and the last one will have the **proper** .dataSource property
|
||||
// TODO: Make this a singleton and turn off events when moving to another page
|
||||
})(this), 1000);
|
||||
}
|
||||
if (this.cachedData != null) {
|
||||
|
@ -71,6 +84,7 @@
|
|||
}
|
||||
},
|
||||
setupAtWho: function() {
|
||||
// Emoji
|
||||
this.input.atwho({
|
||||
at: ':',
|
||||
displayTpl: (function(_this) {
|
||||
|
@ -90,6 +104,7 @@
|
|||
beforeInsert: this.DefaultOptions.beforeInsert
|
||||
}
|
||||
});
|
||||
// Team Members
|
||||
this.input.atwho({
|
||||
at: '@',
|
||||
displayTpl: (function(_this) {
|
||||
|
@ -321,13 +336,22 @@
|
|||
loadData: function(data) {
|
||||
this.cachedData = data;
|
||||
this.dataLoaded = true;
|
||||
// load members
|
||||
this.input.atwho('load', '@', data.members);
|
||||
// load issues
|
||||
this.input.atwho('load', 'issues', data.issues);
|
||||
// load milestones
|
||||
this.input.atwho('load', 'milestones', data.milestones);
|
||||
// load merge requests
|
||||
this.input.atwho('load', 'mergerequests', data.mergerequests);
|
||||
// load emojis
|
||||
this.input.atwho('load', ':', data.emojis);
|
||||
// load labels
|
||||
this.input.atwho('load', '~', data.labels);
|
||||
// load commands
|
||||
this.input.atwho('load', '/', data.commands);
|
||||
// This trigger at.js again
|
||||
// otherwise we would be stuck with loading until the user types
|
||||
return $(':focus').trigger('keyup');
|
||||
}
|
||||
};
|
||||
|
|
|
@ -21,12 +21,14 @@
|
|||
$clearButton = $inputContainer.find('.js-dropdown-input-clear');
|
||||
this.indeterminateIds = [];
|
||||
$clearButton.on('click', (function(_this) {
|
||||
// Clear click
|
||||
return function(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return _this.input.val('').trigger('keyup').focus();
|
||||
};
|
||||
})(this));
|
||||
// Key events
|
||||
timeout = "";
|
||||
this.input
|
||||
.on('keydown', function (e) {
|
||||
|
@ -49,6 +51,7 @@
|
|||
if (keyCode === 13 && !options.elIsInput) {
|
||||
return false;
|
||||
}
|
||||
// Only filter asynchronously only if option remote is set
|
||||
if (this.options.remote) {
|
||||
clearTimeout(timeout);
|
||||
return timeout = setTimeout(function() {
|
||||
|
@ -79,11 +82,27 @@
|
|||
if ((data != null) && !this.options.filterByText) {
|
||||
results = data;
|
||||
if (search_text !== '') {
|
||||
// When data is an array of objects therefore [object Array] e.g.
|
||||
// [
|
||||
// { prop: 'foo' },
|
||||
// { prop: 'baz' }
|
||||
// ]
|
||||
if (_.isArray(data)) {
|
||||
results = fuzzaldrinPlus.filter(data, search_text, {
|
||||
key: this.options.keys
|
||||
});
|
||||
} else {
|
||||
// If data is grouped therefore an [object Object]. e.g.
|
||||
// {
|
||||
// groupName1: [
|
||||
// { prop: 'foo' },
|
||||
// { prop: 'baz' }
|
||||
// ],
|
||||
// groupName2: [
|
||||
// { prop: 'abc' },
|
||||
// { prop: 'def' }
|
||||
// ]
|
||||
// }
|
||||
if (gl.utils.isObject(data)) {
|
||||
results = {};
|
||||
for (key in data) {
|
||||
|
@ -117,7 +136,7 @@
|
|||
}
|
||||
});
|
||||
} else {
|
||||
return elements.show();
|
||||
return elements.show().removeClass('option-hidden');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -140,6 +159,7 @@
|
|||
this.options.beforeSend();
|
||||
}
|
||||
return this.dataEndpoint("", (function(_this) {
|
||||
// Fetch the data by calling the data funcfion
|
||||
return function(data) {
|
||||
if (_this.options.success) {
|
||||
_this.options.success(data);
|
||||
|
@ -171,6 +191,7 @@
|
|||
};
|
||||
})(this)
|
||||
});
|
||||
// Fetch the data through ajax if the data is a string
|
||||
};
|
||||
|
||||
return GitLabDropdownRemote;
|
||||
|
@ -190,9 +211,9 @@
|
|||
|
||||
currentIndex = -1;
|
||||
|
||||
NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link, .option-hidden';
|
||||
NON_SELECTABLE_CLASSES = '.divider, .separator, .dropdown-header, .dropdown-menu-empty-link';
|
||||
|
||||
SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ")";
|
||||
SELECTABLE_CLASSES = ".dropdown-content li:not(" + NON_SELECTABLE_CLASSES + ", .option-hidden)";
|
||||
|
||||
CURSOR_SELECT_SCROLL_PADDING = 5
|
||||
|
||||
|
@ -209,13 +230,18 @@
|
|||
self = this;
|
||||
selector = $(this.el).data("target");
|
||||
this.dropdown = selector != null ? $(selector) : $(this.el).parent();
|
||||
// Set Defaults
|
||||
ref = this.options, this.filterInput = (ref1 = ref.filterInput) != null ? ref1 : this.getElement(FILTER_INPUT), this.highlight = (ref2 = ref.highlight) != null ? ref2 : false, this.filterInputBlur = (ref3 = ref.filterInputBlur) != null ? ref3 : true;
|
||||
// If no input is passed create a default one
|
||||
self = this;
|
||||
// If selector was passed
|
||||
if (_.isString(this.filterInput)) {
|
||||
this.filterInput = this.getElement(this.filterInput);
|
||||
}
|
||||
searchFields = this.options.search ? this.options.search.fields : [];
|
||||
if (this.options.data) {
|
||||
// If we provided data
|
||||
// data could be an array of objects or a group of arrays
|
||||
if (_.isObject(this.options.data) && !_.isFunction(this.options.data)) {
|
||||
this.fullData = this.options.data;
|
||||
currentIndex = -1;
|
||||
|
@ -232,10 +258,12 @@
|
|||
return _this.filter.input.trigger('keyup');
|
||||
}
|
||||
};
|
||||
// Remote data
|
||||
})(this)
|
||||
});
|
||||
}
|
||||
}
|
||||
// Init filterable
|
||||
if (this.options.filterable) {
|
||||
this.filter = new GitLabDropdownFilter(this.filterInput, {
|
||||
elIsInput: $(this.el).is('input'),
|
||||
|
@ -278,12 +306,14 @@
|
|||
})(this)
|
||||
});
|
||||
}
|
||||
// Event listeners
|
||||
this.dropdown.on("shown.bs.dropdown", this.opened);
|
||||
this.dropdown.on("hidden.bs.dropdown", this.hidden);
|
||||
$(this.el).on("update.label", this.updateLabel);
|
||||
this.dropdown.on("click", ".dropdown-menu, .dropdown-menu-close", this.shouldPropagate);
|
||||
this.dropdown.on('keyup', (function(_this) {
|
||||
return function(e) {
|
||||
// Escape key
|
||||
if (e.which === 27) {
|
||||
return $('.dropdown-menu-close', _this.dropdown).trigger('click');
|
||||
}
|
||||
|
@ -327,6 +357,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Finds an element inside wrapper element
|
||||
GitLabDropdown.prototype.getElement = function(selector) {
|
||||
return this.dropdown.find(selector);
|
||||
};
|
||||
|
@ -344,6 +375,7 @@
|
|||
}
|
||||
}
|
||||
menu.toggleClass(PAGE_TWO_CLASS);
|
||||
// Focus first visible input on active page
|
||||
return this.dropdown.find('[class^="dropdown-page-"]:visible :text:visible:first').focus();
|
||||
};
|
||||
|
||||
|
@ -351,23 +383,28 @@
|
|||
var full_html, groupData, html, name;
|
||||
this.renderedData = data;
|
||||
if (this.options.filterable && data.length === 0) {
|
||||
// render no matching results
|
||||
html = [this.noResults()];
|
||||
} else {
|
||||
// Handle array groups
|
||||
if (gl.utils.isObject(data)) {
|
||||
html = [];
|
||||
for (name in data) {
|
||||
groupData = data[name];
|
||||
html.push(this.renderItem({
|
||||
header: name
|
||||
// Add header for each group
|
||||
}, name));
|
||||
this.renderData(groupData, name).map(function(item) {
|
||||
return html.push(item);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Render each row
|
||||
html = this.renderData(data);
|
||||
}
|
||||
}
|
||||
// Render the full menu
|
||||
full_html = this.renderMenu(html);
|
||||
return this.appendMenu(full_html);
|
||||
};
|
||||
|
@ -406,6 +443,7 @@
|
|||
if (this.options.setActiveIds) {
|
||||
this.options.setActiveIds.call(this);
|
||||
}
|
||||
// Makes indeterminate items effective
|
||||
if (this.fullData && this.dropdown.find('.dropdown-menu-toggle').hasClass('js-filter-bulk-update')) {
|
||||
this.parseData(this.fullData);
|
||||
}
|
||||
|
@ -427,6 +465,8 @@
|
|||
if (this.options.filterable) {
|
||||
$input.blur().val("");
|
||||
}
|
||||
// Triggering 'keyup' will re-render the dropdown which is not always required
|
||||
// specially if we want to keep the state of the dropdown needed for bulk-assignment
|
||||
if (!this.options.persistWhenHide) {
|
||||
$input.trigger("keyup");
|
||||
}
|
||||
|
@ -439,6 +479,7 @@
|
|||
return this.dropdown.trigger('hidden.gl.dropdown');
|
||||
};
|
||||
|
||||
// Render the full menu
|
||||
GitLabDropdown.prototype.renderMenu = function(html) {
|
||||
var menu_html;
|
||||
menu_html = "";
|
||||
|
@ -450,6 +491,7 @@
|
|||
return menu_html;
|
||||
};
|
||||
|
||||
// Append the menu into the dropdown
|
||||
GitLabDropdown.prototype.appendMenu = function(html) {
|
||||
var selector;
|
||||
selector = '.dropdown-content';
|
||||
|
@ -465,19 +507,24 @@
|
|||
group = false;
|
||||
}
|
||||
if (index == null) {
|
||||
// Render the row
|
||||
index = false;
|
||||
}
|
||||
html = "";
|
||||
// Divider
|
||||
if (data === "divider") {
|
||||
return "<li class='divider'></li>";
|
||||
}
|
||||
// Separator is a full-width divider
|
||||
if (data === "separator") {
|
||||
return "<li class='separator'></li>";
|
||||
}
|
||||
// Header
|
||||
if (data.header != null) {
|
||||
return _.template('<li class="dropdown-header"><%- header %></li>')({ header: data.header });
|
||||
}
|
||||
if (this.options.renderRow) {
|
||||
// Call the render function
|
||||
html = this.options.renderRow.call(this.options, data, this);
|
||||
} else {
|
||||
if (!selected) {
|
||||
|
@ -489,11 +536,13 @@
|
|||
selected = true;
|
||||
}
|
||||
}
|
||||
// Set URL
|
||||
if (this.options.url != null) {
|
||||
url = this.options.url(data);
|
||||
} else {
|
||||
url = data.url != null ? data.url : '#';
|
||||
}
|
||||
// Set Text
|
||||
if (this.options.text != null) {
|
||||
text = this.options.text(data);
|
||||
} else {
|
||||
|
@ -556,7 +605,7 @@
|
|||
if (isInput) {
|
||||
field = $(this.el);
|
||||
} else {
|
||||
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
|
||||
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + escape(value) + "']");
|
||||
}
|
||||
if (el.hasClass(ACTIVE_CLASS)) {
|
||||
el.removeClass(ACTIVE_CLASS);
|
||||
|
@ -565,10 +614,6 @@
|
|||
} else {
|
||||
field.remove();
|
||||
}
|
||||
if (this.options.toggleLabel) {
|
||||
this.updateLabel(selectedObject, el, this);
|
||||
}
|
||||
return selectedObject;
|
||||
} else if (el.hasClass(INDETERMINATE_CLASS)) {
|
||||
el.addClass(ACTIVE_CLASS);
|
||||
el.removeClass(INDETERMINATE_CLASS);
|
||||
|
@ -578,7 +623,6 @@
|
|||
if (!field.length && fieldName) {
|
||||
this.addInput(fieldName, value, selectedObject);
|
||||
}
|
||||
return selectedObject;
|
||||
} else {
|
||||
if (!this.options.multiSelect || el.hasClass('dropdown-clear-active')) {
|
||||
this.dropdown.find("." + ACTIVE_CLASS).removeClass(ACTIVE_CLASS);
|
||||
|
@ -589,10 +633,8 @@
|
|||
if (value == null) {
|
||||
field.remove();
|
||||
}
|
||||
// Toggle active class for the tick mark
|
||||
el.addClass(ACTIVE_CLASS);
|
||||
if (this.options.toggleLabel) {
|
||||
this.updateLabel(selectedObject, el, this);
|
||||
}
|
||||
if (value != null) {
|
||||
if (!field.length && fieldName) {
|
||||
this.addInput(fieldName, value, selectedObject);
|
||||
|
@ -600,12 +642,19 @@
|
|||
field.val(value).trigger('change');
|
||||
}
|
||||
}
|
||||
return selectedObject;
|
||||
}
|
||||
|
||||
// Update label right after input has been added
|
||||
if (this.options.toggleLabel) {
|
||||
this.updateLabel(selectedObject, el, this);
|
||||
}
|
||||
|
||||
return selectedObject;
|
||||
};
|
||||
|
||||
GitLabDropdown.prototype.addInput = function(fieldName, value, selectedObject) {
|
||||
var $input;
|
||||
// Create hidden input for form
|
||||
$input = $('<input>').attr('type', 'hidden').attr('name', fieldName).val(value);
|
||||
if (this.options.inputId != null) {
|
||||
$input.attr('id', this.options.inputId);
|
||||
|
@ -627,6 +676,7 @@
|
|||
if (this.dropdown.find(".dropdown-toggle-page").length) {
|
||||
selector = ".dropdown-page-one " + selector;
|
||||
}
|
||||
// simulate a click on the first link
|
||||
$el = $(selector, this.dropdown);
|
||||
if ($el.length) {
|
||||
var href = $el.attr('href');
|
||||
|
@ -655,11 +705,15 @@
|
|||
e.stopImmediatePropagation();
|
||||
PREV_INDEX = currentIndex;
|
||||
$listItems = $(selector, _this.dropdown);
|
||||
// if @options.filterable
|
||||
// $input.blur()
|
||||
if (currentKeyCode === 40) {
|
||||
// Move down
|
||||
if (currentIndex < ($listItems.length - 1)) {
|
||||
currentIndex += 1;
|
||||
}
|
||||
} else if (currentKeyCode === 38) {
|
||||
// Move up
|
||||
if (currentIndex > 0) {
|
||||
currentIndex -= 1;
|
||||
}
|
||||
|
@ -687,24 +741,32 @@
|
|||
|
||||
GitLabDropdown.prototype.highlightRowAtIndex = function($listItems, index) {
|
||||
var $dropdownContent, $listItem, dropdownContentBottom, dropdownContentHeight, dropdownContentTop, dropdownScrollTop, listItemBottom, listItemHeight, listItemTop;
|
||||
// Remove the class for the previously focused row
|
||||
$('.is-focused', this.dropdown).removeClass('is-focused');
|
||||
// Update the class for the row at the specific index
|
||||
$listItem = $listItems.eq(index);
|
||||
$listItem.find('a:first-child').addClass("is-focused");
|
||||
// Dropdown content scroll area
|
||||
$dropdownContent = $listItem.closest('.dropdown-content');
|
||||
dropdownScrollTop = $dropdownContent.scrollTop();
|
||||
dropdownContentHeight = $dropdownContent.outerHeight();
|
||||
dropdownContentTop = $dropdownContent.prop('offsetTop');
|
||||
dropdownContentBottom = dropdownContentTop + dropdownContentHeight;
|
||||
// Get the offset bottom of the list item
|
||||
listItemHeight = $listItem.outerHeight();
|
||||
listItemTop = $listItem.prop('offsetTop');
|
||||
listItemBottom = listItemTop + listItemHeight;
|
||||
if (!index) {
|
||||
// Scroll the dropdown content to the top
|
||||
$dropdownContent.scrollTop(0)
|
||||
} else if (index === ($listItems.length - 1)) {
|
||||
// Scroll the dropdown content to the bottom
|
||||
$dropdownContent.scrollTop($dropdownContent.prop('scrollHeight'));
|
||||
} else if (listItemBottom > (dropdownContentBottom + dropdownScrollTop)) {
|
||||
// Scroll the dropdown content down
|
||||
$dropdownContent.scrollTop(listItemBottom - dropdownContentBottom + CURSOR_SELECT_SCROLL_PADDING);
|
||||
} else if (listItemTop < (dropdownContentTop + dropdownScrollTop)) {
|
||||
// Scroll the dropdown content up
|
||||
return $dropdownContent.scrollTop(listItemTop - dropdownContentTop - CURSOR_SELECT_SCROLL_PADDING);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,12 +3,15 @@
|
|||
function GLForm(form) {
|
||||
this.form = form;
|
||||
this.textarea = this.form.find('textarea.js-gfm-input');
|
||||
// Before we start, we should clean up any previous data for this form
|
||||
this.destroy();
|
||||
// Setup the form
|
||||
this.setupForm();
|
||||
this.form.data('gl-form', this);
|
||||
}
|
||||
|
||||
GLForm.prototype.destroy = function() {
|
||||
// Clean form listeners
|
||||
this.clearEventListeners();
|
||||
return this.form.data('gl-form', null);
|
||||
};
|
||||
|
@ -21,12 +24,15 @@
|
|||
this.form.find('.div-dropzone').remove();
|
||||
this.form.addClass('gfm-form');
|
||||
disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button'));
|
||||
// remove notify commit author checkbox for non-commit notes
|
||||
GitLab.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
|
||||
new DropzoneInput(this.form);
|
||||
autosize(this.textarea);
|
||||
// form and textarea event listeners
|
||||
this.addEventListeners();
|
||||
gl.text.init(this.form);
|
||||
}
|
||||
// hide discard button
|
||||
this.form.find('.js-note-discard').hide();
|
||||
return this.form.show();
|
||||
};
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
|
||||
// This is a manifest file that'll be compiled into including all the files listed below.
|
||||
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
|
||||
// be included in the compiled file accessible from http://example.com/assets/application.js
|
||||
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
||||
// the compiled file.
|
||||
//
|
||||
/*= require_tree . */
|
||||
|
||||
(function() {
|
||||
|
||||
|
||||
}).call(this);
|
||||
|
|
|
@ -204,6 +204,7 @@
|
|||
|
||||
function ContributorsAuthorGraph(data1) {
|
||||
this.data = data1;
|
||||
// Don't split graph size in half for mobile devices.
|
||||
if ($(window).width() < 768) {
|
||||
this.width = $('.content').width() - 80;
|
||||
} else {
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
return _this.formatSelection.apply(_this, args);
|
||||
},
|
||||
dropdownCssClass: "ajax-groups-dropdown",
|
||||
// we do not want to escape markup since we are displaying html in results
|
||||
escapeMarkup: function(m) {
|
||||
return m;
|
||||
}
|
||||
|
|
|
@ -10,21 +10,24 @@
|
|||
ImporterStatus.prototype.initStatusPage = function() {
|
||||
$('.js-add-to-import').off('click').on('click', (function(_this) {
|
||||
return function(e) {
|
||||
var $btn, $namespace_input, $target_field, $tr, id, new_namespace;
|
||||
var $btn, $namespace_input, $target_field, $tr, id, target_namespace;
|
||||
$btn = $(e.currentTarget);
|
||||
$tr = $btn.closest('tr');
|
||||
$target_field = $tr.find('.import-target');
|
||||
$namespace_input = $target_field.find('input');
|
||||
id = $tr.attr('id').replace('repo_', '');
|
||||
new_namespace = null;
|
||||
target_namespace = null;
|
||||
|
||||
if ($namespace_input.length > 0) {
|
||||
new_namespace = $namespace_input.prop('value');
|
||||
$target_field.empty().append(new_namespace + "/" + ($target_field.data('project_name')));
|
||||
target_namespace = $namespace_input.prop('value');
|
||||
$target_field.empty().append(target_namespace + "/" + ($target_field.data('project_name')));
|
||||
}
|
||||
|
||||
$btn.disable().addClass('is-loading');
|
||||
|
||||
return $.post(_this.import_url, {
|
||||
repo_id: id,
|
||||
new_namespace: new_namespace
|
||||
target_namespace: target_namespace
|
||||
}, {
|
||||
dataType: 'script'
|
||||
});
|
||||
|
@ -70,7 +73,7 @@
|
|||
if ($('.js-importer-status').length) {
|
||||
var jobsImportPath = $('.js-importer-status').data('jobs-import-path');
|
||||
var importPath = $('.js-importer-status').data('import-path');
|
||||
|
||||
|
||||
new ImporterStatus(jobsImportPath, importPath);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
Issuable.initTemplates();
|
||||
Issuable.initSearch();
|
||||
Issuable.initChecks();
|
||||
Issuable.initResetFilters();
|
||||
return Issuable.initLabelFilterRemove();
|
||||
},
|
||||
initTemplates: function() {
|
||||
|
@ -37,9 +38,11 @@
|
|||
return $(document).off('click', '.js-label-filter-remove').on('click', '.js-label-filter-remove', function(e) {
|
||||
var $button;
|
||||
$button = $(this);
|
||||
// Remove the label input box
|
||||
$('input[name="label_name[]"]').filter(function() {
|
||||
return this.value === $button.data('label');
|
||||
}).remove();
|
||||
// Submit the form to get new data
|
||||
Issuable.filterResults($('.filter-form'));
|
||||
return $('.js-label-select').trigger('update.label');
|
||||
});
|
||||
|
@ -55,6 +58,17 @@
|
|||
return Turbolinks.visit(issuesUrl);
|
||||
};
|
||||
})(this),
|
||||
initResetFilters: function() {
|
||||
$('.reset-filters').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
const target = e.target;
|
||||
const $form = $(target).parents('.js-filter-form');
|
||||
const baseIssuesUrl = target.href;
|
||||
|
||||
$form.attr('action', baseIssuesUrl);
|
||||
Turbolinks.visit(baseIssuesUrl);
|
||||
});
|
||||
},
|
||||
initChecks: function() {
|
||||
this.issuableBulkActions = $('.bulk-update').data('bulkActions');
|
||||
$('.check_all_issues').off('click').on('click', function() {
|
||||
|
@ -64,19 +78,22 @@
|
|||
return $('.selected_issue').off('change').on('change', Issuable.checkChanged.bind(this));
|
||||
},
|
||||
checkChanged: function() {
|
||||
var checked_issues, ids;
|
||||
checked_issues = $('.selected_issue:checked');
|
||||
if (checked_issues.length > 0) {
|
||||
ids = $.map(checked_issues, function(value) {
|
||||
const $checkedIssues = $('.selected_issue:checked');
|
||||
const $updateIssuesIds = $('#update_issuable_ids');
|
||||
const $issuesOtherFilters = $('.issues-other-filters');
|
||||
const $issuesBulkUpdate = $('.issues_bulk_update');
|
||||
|
||||
if ($checkedIssues.length > 0) {
|
||||
let ids = $.map($checkedIssues, function(value) {
|
||||
return $(value).data('id');
|
||||
});
|
||||
$('#update_issues_ids').val(ids);
|
||||
$('.issues-other-filters').hide();
|
||||
$('.issues_bulk_update').show();
|
||||
$updateIssuesIds.val(ids);
|
||||
$issuesOtherFilters.hide();
|
||||
$issuesBulkUpdate.show();
|
||||
} else {
|
||||
$('#update_issues_ids').val([]);
|
||||
$('.issues_bulk_update').hide();
|
||||
$('.issues-other-filters').show();
|
||||
$updateIssuesIds.val([]);
|
||||
$issuesBulkUpdate.hide();
|
||||
$issuesOtherFilters.show();
|
||||
this.issuableBulkActions.willUpdateLabels = false;
|
||||
}
|
||||
return true;
|
|
@ -1,10 +1,6 @@
|
|||
|
||||
/*= require flash */
|
||||
|
||||
|
||||
/*= require jquery.waitforimages */
|
||||
|
||||
|
||||
/*= require task_list */
|
||||
|
||||
(function() {
|
||||
|
@ -13,6 +9,7 @@
|
|||
this.Issue = (function() {
|
||||
function Issue() {
|
||||
this.submitNoteForm = bind(this.submitNoteForm, this);
|
||||
// Prevent duplicate event bindings
|
||||
this.disableTaskList();
|
||||
if ($('a.btn-close').length) {
|
||||
this.initTaskList();
|
||||
|
@ -99,6 +96,8 @@
|
|||
url: $('form.js-issuable-update').attr('action'),
|
||||
data: patchData
|
||||
});
|
||||
// TODO (rspeicher): Make the issue description inline-editable like a note so
|
||||
// that we can re-use its form here
|
||||
};
|
||||
|
||||
Issue.prototype.initMergeRequests = function() {
|
||||
|
@ -127,7 +126,9 @@
|
|||
|
||||
Issue.prototype.initCanCreateBranch = function() {
|
||||
var $container;
|
||||
$container = $('div#new-branch');
|
||||
$container = $('#new-branch');
|
||||
// If the user doesn't have the required permissions the container isn't
|
||||
// rendered at all.
|
||||
if ($container.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -139,7 +140,6 @@
|
|||
if (data.can_create_branch) {
|
||||
$container.find('.checking').hide();
|
||||
$container.find('.available').show();
|
||||
return $container.find('a').attr('disabled', false);
|
||||
} else {
|
||||
$container.find('.checking').hide();
|
||||
return $container.find('.unavailable').show();
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
(function() {
|
||||
this.IssuableBulkActions = (function() {
|
||||
function IssuableBulkActions(opts) {
|
||||
// Set defaults
|
||||
var ref, ref1, ref2;
|
||||
if (opts == null) {
|
||||
opts = {};
|
||||
}
|
||||
this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issues-list .issue');
|
||||
this.container = (ref = opts.container) != null ? ref : $('.content'), this.form = (ref1 = opts.form) != null ? ref1 : this.getElement('.bulk-update'), this.issues = (ref2 = opts.issues) != null ? ref2 : this.getElement('.issuable-list > li');
|
||||
// Save instance
|
||||
this.form.data('bulkActions', this);
|
||||
this.willUpdateLabels = false;
|
||||
this.bindEvents();
|
||||
// Fixes bulk-assign not working when navigating through pages
|
||||
Issuable.initChecks();
|
||||
}
|
||||
|
||||
|
@ -86,6 +89,7 @@
|
|||
ref1 = this.getLabelsFromSelection();
|
||||
for (j = 0, len1 = ref1.length; j < len1; j++) {
|
||||
id = ref1[j];
|
||||
// Only the ones that we are not going to keep
|
||||
if (labelsToKeep.indexOf(id) === -1) {
|
||||
result.push(id);
|
||||
}
|
||||
|
@ -106,7 +110,7 @@
|
|||
state_event: this.form.find('input[name="update[state_event]"]').val(),
|
||||
assignee_id: this.form.find('input[name="update[assignee_id]"]').val(),
|
||||
milestone_id: this.form.find('input[name="update[milestone_id]"]').val(),
|
||||
issues_ids: this.form.find('input[name="update[issues_ids]"]').val(),
|
||||
issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(),
|
||||
subscription_event: this.form.find('input[name="update[subscription_event]"]').val(),
|
||||
add_label_ids: [],
|
||||
remove_label_ids: []
|
||||
|
@ -147,6 +151,8 @@
|
|||
indeterminatedLabels = this.getUnmarkedIndeterminedLabels();
|
||||
labelsToApply = this.getLabelsToApply();
|
||||
indeterminatedLabels.map(function(id) {
|
||||
// We need to exclude label IDs that will be applied
|
||||
// By not doing this will cause issues from selection to not add labels at all
|
||||
if (labelsToApply.indexOf(id) === -1) {
|
||||
return result.push(id);
|
||||
}
|
||||
|
|
|
@ -26,13 +26,16 @@
|
|||
var previewColor;
|
||||
previewColor = $('input#label_color').val();
|
||||
return $('div.label-color-preview').css('background-color', previewColor);
|
||||
// Updates the the preview color with the hex-color input
|
||||
};
|
||||
|
||||
// Updates the preview color with a click on a suggested color
|
||||
Labels.prototype.setSuggestedColor = function(e) {
|
||||
var color;
|
||||
color = $(e.currentTarget).data('color');
|
||||
$('input#label_color').val(color);
|
||||
this.updateColorPreview();
|
||||
// Notify the form, that color has changed
|
||||
$('.label-form').trigger('keyup');
|
||||
return e.preventDefault();
|
||||
};
|
||||
|
|
|
@ -156,15 +156,17 @@
|
|||
selectedClass.push('is-indeterminate');
|
||||
}
|
||||
if (active.indexOf(label.id) !== -1) {
|
||||
// Remove is-indeterminate class if the item will be marked as active
|
||||
i = selectedClass.indexOf('is-indeterminate');
|
||||
if (i !== -1) {
|
||||
selectedClass.splice(i, 1);
|
||||
}
|
||||
selectedClass.push('is-active');
|
||||
// Add input manually
|
||||
instance.addInput(this.fieldName, label.id);
|
||||
}
|
||||
}
|
||||
if ($form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + (this.id(label)) + "']").length) {
|
||||
if ($form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + escape(this.id(label)) + "']").length) {
|
||||
selectedClass.push('is-active');
|
||||
}
|
||||
if ($dropdown.hasClass('js-multiselect') && removesAll) {
|
||||
|
@ -172,6 +174,7 @@
|
|||
}
|
||||
if (label.duplicate) {
|
||||
spacing = 100 / label.color.length;
|
||||
// Reduce the colors to 4
|
||||
label.color = label.color.filter(function(color, i) {
|
||||
return i < 4;
|
||||
});
|
||||
|
@ -192,11 +195,13 @@
|
|||
} else {
|
||||
colorEl = '';
|
||||
}
|
||||
// We need to identify which items are actually labels
|
||||
if (label.id) {
|
||||
selectedClass.push('label-item');
|
||||
$a.attr('data-label-id', label.id);
|
||||
}
|
||||
$a.addClass(selectedClass.join(' ')).html(colorEl + " " + label.title);
|
||||
// Return generated html
|
||||
return $li.html($a).prop('outerHTML');
|
||||
},
|
||||
persistWhenHide: $dropdown.data('persistWhenHide'),
|
||||
|
@ -238,6 +243,7 @@
|
|||
isIssueIndex = page === 'projects:issues:index';
|
||||
isMRIndex = page === 'projects:merge_requests:index';
|
||||
$selectbox.hide();
|
||||
// display:block overrides the hide-collapse rule
|
||||
$value.removeAttr('style');
|
||||
if (page === 'projects:boards:show') {
|
||||
return;
|
||||
|
@ -255,6 +261,7 @@
|
|||
}
|
||||
}
|
||||
if ($dropdown.hasClass('js-filter-bulk-update')) {
|
||||
// If we are persisting state we need the classes
|
||||
if (!this.options.persistWhenHide) {
|
||||
return $dropdown.parent().find('.is-active, .is-indeterminate').removeClass();
|
||||
}
|
||||
|
@ -324,7 +331,9 @@
|
|||
if ($('.selected_issue:checked').length) {
|
||||
return;
|
||||
}
|
||||
// Remove inputs
|
||||
$('.issues_bulk_update .labels-filter input[type="hidden"]').remove();
|
||||
// Also restore button text
|
||||
return $('.issues_bulk_update .labels-filter .dropdown-toggle-text').text('Label');
|
||||
};
|
||||
|
||||
|
|
|
@ -3,5 +3,4 @@
|
|||
|
||||
(function() {
|
||||
|
||||
|
||||
}).call(this);
|
||||
|
|
|
@ -3,5 +3,4 @@
|
|||
|
||||
(function() {
|
||||
|
||||
|
||||
}).call(this);
|
||||
|
|
|
@ -3,5 +3,4 @@
|
|||
|
||||
(function() {
|
||||
|
||||
|
||||
}).call(this);
|
||||
|
|
|
@ -1,13 +1,8 @@
|
|||
|
||||
/*= require raphael */
|
||||
|
||||
|
||||
/*= require g.raphael */
|
||||
|
||||
|
||||
/*= require g.bar */
|
||||
|
||||
(function() {
|
||||
|
||||
|
||||
}).call(this);
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
if (setTimeago) {
|
||||
$timeagoEls.timeago();
|
||||
$timeagoEls.tooltip('destroy');
|
||||
// Recreate with custom template
|
||||
return $timeagoEls.tooltip({
|
||||
template: '<div class="tooltip local-timeago" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
|
||||
});
|
||||
|
@ -67,6 +68,14 @@
|
|||
$.timeago.settings.strings = tmpLocale;
|
||||
};
|
||||
|
||||
w.gl.utils.getDayDifference = function(a, b) {
|
||||
var millisecondsPerDay = 1000 * 60 * 60 * 24;
|
||||
var date1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
|
||||
var date2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
|
||||
|
||||
return Math.floor((date2 - date1) / millisecondsPerDay);
|
||||
}
|
||||
|
||||
})(window);
|
||||
|
||||
}).call(this);
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
gl.emojiAliases = ->
|
||||
JSON.parse('<%= Gitlab::AwardEmoji.aliases.to_json %>')
|
|
@ -0,0 +1,6 @@
|
|||
(function() {
|
||||
gl.emojiAliases = function() {
|
||||
return JSON.parse('<%= Gitlab::AwardEmoji.aliases.to_json %>');
|
||||
};
|
||||
|
||||
}).call(this);
|
|
@ -6,6 +6,7 @@
|
|||
notification = new Notification(message, opts);
|
||||
setTimeout(function() {
|
||||
return notification.close();
|
||||
// Hide the notification after X amount of seconds
|
||||
}, 8000);
|
||||
if (onclick) {
|
||||
return notification.onclick = onclick;
|
||||
|
@ -22,12 +23,16 @@
|
|||
body: body,
|
||||
icon: icon
|
||||
};
|
||||
// Let's check if the browser supports notifications
|
||||
if (!('Notification' in window)) {
|
||||
|
||||
// do nothing
|
||||
} else if (Notification.permission === 'granted') {
|
||||
// If it's okay let's create a notification
|
||||
return notificationGranted(message, opts, onclick);
|
||||
} else if (Notification.permission !== 'denied') {
|
||||
return Notification.requestPermission(function(permission) {
|
||||
// If the user accepts, let's create a notification
|
||||
if (permission === 'granted') {
|
||||
return notificationGranted(message, opts, onclick);
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
lineBefore = this.lineBefore(text, textArea);
|
||||
lineAfter = this.lineAfter(text, textArea);
|
||||
if (lineBefore === blockTag && lineAfter === blockTag) {
|
||||
// To remove the block tag we have to select the line before & after
|
||||
if (blockTag != null) {
|
||||
textArea.selectionStart = textArea.selectionStart - (blockTag.length + 1);
|
||||
textArea.selectionEnd = textArea.selectionEnd + (blockTag.length + 1);
|
||||
|
@ -63,11 +64,11 @@
|
|||
if (!inserted) {
|
||||
try {
|
||||
document.execCommand("ms-beginUndoUnit");
|
||||
} catch (undefined) {}
|
||||
} catch (error) {}
|
||||
textArea.value = this.replaceRange(text, textArea.selectionStart, textArea.selectionEnd, insertText);
|
||||
try {
|
||||
document.execCommand("ms-endUndoUnit");
|
||||
} catch (undefined) {}
|
||||
} catch (error) {}
|
||||
}
|
||||
return this.moveCursor(textArea, tag, wrap);
|
||||
};
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
if ((base = w.gl).utils == null) {
|
||||
base.utils = {};
|
||||
}
|
||||
// Returns an array containing the value(s) of the
|
||||
// of the key passed as an argument
|
||||
w.gl.utils.getParameterValues = function(sParam) {
|
||||
var i, sPageURL, sParameterName, sURLVariables, values;
|
||||
sPageURL = decodeURIComponent(window.location.search.substring(1));
|
||||
|
@ -23,6 +25,8 @@
|
|||
}
|
||||
return values;
|
||||
};
|
||||
// @param {Object} params - url keys and value to merge
|
||||
// @param {String} url
|
||||
w.gl.utils.mergeUrlParams = function(params, url) {
|
||||
var lastChar, newUrl, paramName, paramValue, pattern;
|
||||
newUrl = decodeURIComponent(url);
|
||||
|
@ -37,13 +41,15 @@
|
|||
newUrl = "" + newUrl + (newUrl.indexOf('?') > 0 ? '&' : '?') + paramName + "=" + paramValue;
|
||||
}
|
||||
}
|
||||
// Remove a trailing ampersand
|
||||
lastChar = newUrl[newUrl.length - 1];
|
||||
if (lastChar === '&') {
|
||||
newUrl = newUrl.slice(0, -1);
|
||||
}
|
||||
return newUrl;
|
||||
};
|
||||
return w.gl.utils.removeParamQueryString = function(url, param) {
|
||||
// removes parameter query string from url. returns the modified url
|
||||
w.gl.utils.removeParamQueryString = function(url, param) {
|
||||
var urlVariables, variables;
|
||||
url = decodeURIComponent(url);
|
||||
urlVariables = url.split('&');
|
||||
|
@ -59,6 +65,16 @@
|
|||
return results;
|
||||
})()).join('&');
|
||||
};
|
||||
w.gl.utils.getLocationHash = function(url) {
|
||||
var hashIndex;
|
||||
if (typeof url === 'undefined') {
|
||||
// Note: We can't use window.location.hash here because it's
|
||||
// not consistent across browsers - Firefox will pre-decode it
|
||||
url = window.location.href;
|
||||
}
|
||||
hashIndex = url.indexOf('#');
|
||||
return hashIndex === -1 ? null : url.substring(hashIndex + 1);
|
||||
};
|
||||
})(window);
|
||||
|
||||
}).call(this);
|
||||
|
|
|
@ -1,17 +1,49 @@
|
|||
|
||||
// LineHighlighter
|
||||
//
|
||||
// Handles single- and multi-line selection and highlight for blob views.
|
||||
//
|
||||
/*= require jquery.scrollTo */
|
||||
|
||||
//
|
||||
// ### Example Markup
|
||||
//
|
||||
// <div id="blob-content-holder">
|
||||
// <div class="file-content">
|
||||
// <div class="line-numbers">
|
||||
// <a href="#L1" id="L1" data-line-number="1">1</a>
|
||||
// <a href="#L2" id="L2" data-line-number="2">2</a>
|
||||
// <a href="#L3" id="L3" data-line-number="3">3</a>
|
||||
// <a href="#L4" id="L4" data-line-number="4">4</a>
|
||||
// <a href="#L5" id="L5" data-line-number="5">5</a>
|
||||
// </div>
|
||||
// <pre class="code highlight">
|
||||
// <code>
|
||||
// <span id="LC1" class="line">...</span>
|
||||
// <span id="LC2" class="line">...</span>
|
||||
// <span id="LC3" class="line">...</span>
|
||||
// <span id="LC4" class="line">...</span>
|
||||
// <span id="LC5" class="line">...</span>
|
||||
// </code>
|
||||
// </pre>
|
||||
// </div>
|
||||
// </div>
|
||||
//
|
||||
(function() {
|
||||
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
|
||||
this.LineHighlighter = (function() {
|
||||
// CSS class applied to highlighted lines
|
||||
LineHighlighter.prototype.highlightClass = 'hll';
|
||||
|
||||
// Internal copy of location.hash so we're not dependent on `location` in tests
|
||||
LineHighlighter.prototype._hash = '';
|
||||
|
||||
function LineHighlighter(hash) {
|
||||
var range;
|
||||
if (hash == null) {
|
||||
// Initialize a LineHighlighter object
|
||||
//
|
||||
// hash - String URL hash for dependency injection in tests
|
||||
hash = location.hash;
|
||||
}
|
||||
this.setHash = bind(this.setHash, this);
|
||||
|
@ -24,6 +56,8 @@
|
|||
if (range[0]) {
|
||||
this.highlightRange(range);
|
||||
$.scrollTo("#L" + range[0], {
|
||||
// Scroll to the first highlighted line on initial load
|
||||
// Offset -50 for the sticky top bar, and another -100 for some context
|
||||
offset: -150
|
||||
});
|
||||
}
|
||||
|
@ -32,6 +66,12 @@
|
|||
|
||||
LineHighlighter.prototype.bindEvents = function() {
|
||||
$('#blob-content-holder').on('mousedown', '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.
|
||||
return $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) {
|
||||
return event.preventDefault();
|
||||
});
|
||||
|
@ -44,6 +84,8 @@
|
|||
lineNumber = $(event.target).closest('a').data('line-number');
|
||||
current = this.hashToRange(this._hash);
|
||||
if (!(current[0] && event.shiftKey)) {
|
||||
// If there's no current selection, or there is but Shift wasn't held,
|
||||
// treat this like a single-line selection.
|
||||
this.setHash(lineNumber);
|
||||
return this.highlightLine(lineNumber);
|
||||
} else if (event.shiftKey) {
|
||||
|
@ -59,10 +101,23 @@
|
|||
|
||||
LineHighlighter.prototype.clearHighlight = function() {
|
||||
return $("." + this.highlightClass).removeClass(this.highlightClass);
|
||||
// Unhighlight previously highlighted lines
|
||||
};
|
||||
|
||||
// Convert a URL hash String into line numbers
|
||||
//
|
||||
// hash - Hash String
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// hashToRange('#L5') # => [5, null]
|
||||
// hashToRange('#L5-15') # => [5, 15]
|
||||
// hashToRange('#foo') # => [null, null]
|
||||
//
|
||||
// Returns an Array
|
||||
LineHighlighter.prototype.hashToRange = function(hash) {
|
||||
var first, last, matches;
|
||||
//?L(\d+)(?:-(\d+))?$/)
|
||||
matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/);
|
||||
if (matches && matches.length) {
|
||||
first = parseInt(matches[1]);
|
||||
|
@ -73,10 +128,16 @@
|
|||
}
|
||||
};
|
||||
|
||||
// Highlight a single line
|
||||
//
|
||||
// lineNumber - Line number to highlight
|
||||
LineHighlighter.prototype.highlightLine = function(lineNumber) {
|
||||
return $("#LC" + lineNumber).addClass(this.highlightClass);
|
||||
};
|
||||
|
||||
// Highlight all lines within a range
|
||||
//
|
||||
// range - Array containing the starting and ending line numbers
|
||||
LineHighlighter.prototype.highlightRange = function(range) {
|
||||
var i, lineNumber, ref, ref1, results;
|
||||
if (range[1]) {
|
||||
|
@ -90,6 +151,7 @@
|
|||
}
|
||||
};
|
||||
|
||||
// Set the URL hash string
|
||||
LineHighlighter.prototype.setHash = function(firstLineNumber, lastLineNumber) {
|
||||
var hash;
|
||||
if (lastLineNumber) {
|
||||
|
@ -101,10 +163,15 @@
|
|||
return this.__setLocationHash__(hash);
|
||||
};
|
||||
|
||||
// Make the actual hash change in the browser
|
||||
//
|
||||
// This method is stubbed in tests.
|
||||
LineHighlighter.prototype.__setLocationHash__ = function(value) {
|
||||
return history.pushState({
|
||||
turbolinks: false,
|
||||
url: value
|
||||
// We're using pushState instead of assigning location.hash directly to
|
||||
// prevent the page from scrolling on the hashchange event
|
||||
}, document.title, value);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,54 +1,12 @@
|
|||
(function() {
|
||||
var clearHighlights, currentTimer, defaultClass, delay, firstPiece, pieceIndex, pieces, start, stop, work;
|
||||
|
||||
Turbolinks.enableProgressBar();
|
||||
|
||||
defaultClass = 'tanuki-shape';
|
||||
$(document).on('page:fetch', function() {
|
||||
$('.tanuki-logo').addClass('animate');
|
||||
});
|
||||
|
||||
pieces = ['path#tanuki-right-cheek', 'path#tanuki-right-eye, path#tanuki-right-ear', 'path#tanuki-nose', 'path#tanuki-left-eye, path#tanuki-left-ear', 'path#tanuki-left-cheek'];
|
||||
|
||||
pieceIndex = 0;
|
||||
|
||||
firstPiece = pieces[0];
|
||||
|
||||
currentTimer = null;
|
||||
|
||||
delay = 150;
|
||||
|
||||
clearHighlights = function() {
|
||||
return $("." + defaultClass + ".highlight").attr('class', defaultClass);
|
||||
};
|
||||
|
||||
start = function() {
|
||||
clearHighlights();
|
||||
pieceIndex = 0;
|
||||
if (pieces[0] !== firstPiece) {
|
||||
pieces.reverse();
|
||||
}
|
||||
if (currentTimer) {
|
||||
clearInterval(currentTimer);
|
||||
}
|
||||
return currentTimer = setInterval(work, delay);
|
||||
};
|
||||
|
||||
stop = function() {
|
||||
clearInterval(currentTimer);
|
||||
return clearHighlights();
|
||||
};
|
||||
|
||||
work = function() {
|
||||
clearHighlights();
|
||||
$(pieces[pieceIndex]).attr('class', defaultClass + " highlight");
|
||||
if (pieceIndex === pieces.length - 1) {
|
||||
pieceIndex = 0;
|
||||
return pieces.reverse();
|
||||
} else {
|
||||
return pieceIndex++;
|
||||
}
|
||||
};
|
||||
|
||||
$(document).on('page:fetch', start);
|
||||
|
||||
$(document).on('page:change', stop);
|
||||
$(document).on('page:change', function() {
|
||||
$('.tanuki-logo').removeClass('animate');
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
|
|
|
@ -75,10 +75,8 @@ class MergeConflictResolver {
|
|||
window.location.href = data.redirect_to;
|
||||
})
|
||||
.error(() => {
|
||||
new Flash('Something went wrong!');
|
||||
})
|
||||
.always(() => {
|
||||
this.vue.isSubmitting = false;
|
||||
new Flash('Something went wrong!');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
|
||||
/*= require jquery.waitforimages */
|
||||
|
||||
|
||||
/*= require task_list */
|
||||
|
||||
|
||||
/*= require merge_request_tabs */
|
||||
|
||||
(function() {
|
||||
|
@ -12,6 +8,11 @@
|
|||
|
||||
this.MergeRequest = (function() {
|
||||
function MergeRequest(opts) {
|
||||
// Initialize MergeRequest behavior
|
||||
//
|
||||
// Options:
|
||||
// action - String, current controller action
|
||||
//
|
||||
this.opts = opts != null ? opts : {};
|
||||
this.submitNoteForm = bind(this.submitNoteForm, this);
|
||||
this.$el = $('.merge-request');
|
||||
|
@ -21,6 +22,7 @@
|
|||
};
|
||||
})(this));
|
||||
this.initTabs();
|
||||
// Prevent duplicate event bindings
|
||||
this.disableTaskList();
|
||||
this.initMRBtnListeners();
|
||||
if ($("a.btn-close").length) {
|
||||
|
@ -28,14 +30,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Local jQuery finder
|
||||
MergeRequest.prototype.$ = function(selector) {
|
||||
return this.$el.find(selector);
|
||||
};
|
||||
|
||||
MergeRequest.prototype.initTabs = function() {
|
||||
if (this.opts.action !== 'new') {
|
||||
// `MergeRequests#new` has no tab-persisting or lazy-loading behavior
|
||||
window.mrTabs = new MergeRequestTabs(this.opts);
|
||||
} else {
|
||||
// Show the first tab (Commits)
|
||||
return $('.merge-request-tabs a[data-toggle="tab"]:first').tab('show');
|
||||
}
|
||||
};
|
||||
|
@ -96,6 +101,8 @@
|
|||
url: $('form.js-issuable-update').attr('action'),
|
||||
data: patchData
|
||||
});
|
||||
// TODO (rspeicher): Make the merge request description inline-editable like a
|
||||
// note so that we can re-use its form here
|
||||
};
|
||||
|
||||
return MergeRequest;
|
||||
|
|
|
@ -1,6 +1,49 @@
|
|||
|
||||
// MergeRequestTabs
|
||||
//
|
||||
// Handles persisting and restoring the current tab selection and lazily-loading
|
||||
// content on the MergeRequests#show page.
|
||||
//
|
||||
/*= require jquery.cookie */
|
||||
|
||||
//
|
||||
// ### Example Markup
|
||||
//
|
||||
// <ul class="nav-links merge-request-tabs">
|
||||
// <li class="notes-tab active">
|
||||
// <a data-action="notes" data-target="#notes" data-toggle="tab" href="/foo/bar/merge_requests/1">
|
||||
// Discussion
|
||||
// </a>
|
||||
// </li>
|
||||
// <li class="commits-tab">
|
||||
// <a data-action="commits" data-target="#commits" data-toggle="tab" href="/foo/bar/merge_requests/1/commits">
|
||||
// Commits
|
||||
// </a>
|
||||
// </li>
|
||||
// <li class="diffs-tab">
|
||||
// <a data-action="diffs" data-target="#diffs" data-toggle="tab" href="/foo/bar/merge_requests/1/diffs">
|
||||
// Diffs
|
||||
// </a>
|
||||
// </li>
|
||||
// </ul>
|
||||
//
|
||||
// <div class="tab-content">
|
||||
// <div class="notes tab-pane active" id="notes">
|
||||
// Notes Content
|
||||
// </div>
|
||||
// <div class="commits tab-pane" id="commits">
|
||||
// Commits Content
|
||||
// </div>
|
||||
// <div class="diffs tab-pane" id="diffs">
|
||||
// Diffs Content
|
||||
// </div>
|
||||
// </div>
|
||||
//
|
||||
// <div class="mr-loading-status">
|
||||
// <div class="loading">
|
||||
// Loading Animation
|
||||
// </div>
|
||||
// </div>
|
||||
//
|
||||
(function() {
|
||||
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
|
||||
|
@ -19,6 +62,7 @@
|
|||
this.setCurrentAction = bind(this.setCurrentAction, this);
|
||||
this.tabShown = bind(this.tabShown, this);
|
||||
this.showTab = bind(this.showTab, this);
|
||||
// Store the `location` object, allowing for easier stubbing in tests
|
||||
this._location = location;
|
||||
this.bindEvents();
|
||||
this.activateTab(this.opts.action);
|
||||
|
@ -77,6 +121,7 @@
|
|||
}
|
||||
};
|
||||
|
||||
// Activate a tab based on the current action
|
||||
MergeRequestTabs.prototype.activateTab = function(action) {
|
||||
if (action === 'show') {
|
||||
action = 'notes';
|
||||
|
@ -84,20 +129,48 @@
|
|||
return $(".merge-request-tabs a[data-action='" + action + "']").tab('show');
|
||||
};
|
||||
|
||||
// Replaces the current Merge Request-specific action in the URL with a new one
|
||||
//
|
||||
// If the action is "notes", the URL is reset to the standard
|
||||
// `MergeRequests#show` route.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// location.pathname # => "/namespace/project/merge_requests/1"
|
||||
// setCurrentAction('diffs')
|
||||
// location.pathname # => "/namespace/project/merge_requests/1/diffs"
|
||||
//
|
||||
// location.pathname # => "/namespace/project/merge_requests/1/diffs"
|
||||
// setCurrentAction('notes')
|
||||
// location.pathname # => "/namespace/project/merge_requests/1"
|
||||
//
|
||||
// location.pathname # => "/namespace/project/merge_requests/1/diffs"
|
||||
// setCurrentAction('commits')
|
||||
// location.pathname # => "/namespace/project/merge_requests/1/commits"
|
||||
//
|
||||
// Returns the new URL String
|
||||
MergeRequestTabs.prototype.setCurrentAction = function(action) {
|
||||
var new_state;
|
||||
// Normalize action, just to be safe
|
||||
if (action === 'show') {
|
||||
action = 'notes';
|
||||
}
|
||||
this.currentAction = action;
|
||||
// Remove a trailing '/commits' or '/diffs'
|
||||
new_state = this._location.pathname.replace(/\/(commits|diffs|builds|pipelines)(\.html)?\/?$/, '');
|
||||
// Append the new action if we're on a tab other than 'notes'
|
||||
if (action !== 'notes') {
|
||||
new_state += "/" + action;
|
||||
}
|
||||
// Ensure parameters and hash come along for the ride
|
||||
new_state += this._location.search + this._location.hash;
|
||||
history.replaceState({
|
||||
turbolinks: true,
|
||||
url: new_state
|
||||
// Replace the current history state with the new one without breaking
|
||||
// Turbolinks' history.
|
||||
//
|
||||
// See https://github.com/rails/turbolinks/issues/363
|
||||
}, document.title, new_state);
|
||||
return new_state;
|
||||
};
|
||||
|
@ -206,6 +279,9 @@
|
|||
});
|
||||
};
|
||||
|
||||
// Show or hide the loading spinner
|
||||
//
|
||||
// status - Boolean, true to show, false to hide
|
||||
MergeRequestTabs.prototype.toggleLoading = function(status) {
|
||||
return $('.mr-loading-status .loading').toggle(status);
|
||||
};
|
||||
|
@ -232,6 +308,7 @@
|
|||
|
||||
MergeRequestTabs.prototype.diffViewType = function() {
|
||||
return $('.inline-parallel-buttons a.active').data('view-type');
|
||||
// Returns diff view type
|
||||
};
|
||||
|
||||
MergeRequestTabs.prototype.expandViewContainer = function() {
|
||||
|
@ -245,6 +322,8 @@
|
|||
if ($gutterIcon.is('.fa-angle-double-right')) {
|
||||
return $gutterIcon.closest('a').trigger('click', [true]);
|
||||
}
|
||||
// Wait until listeners are set
|
||||
// Only when sidebar is expanded
|
||||
}, 0);
|
||||
};
|
||||
|
||||
|
@ -259,6 +338,9 @@
|
|||
return $gutterIcon.closest('a').trigger('click', [true]);
|
||||
}
|
||||
}, 0);
|
||||
// Expand the issuable sidebar unless the user explicitly collapsed it
|
||||
// Wait until listeners are set
|
||||
// Only when sidebar is collapsed
|
||||
};
|
||||
|
||||
return MergeRequestTabs;
|
||||
|
|
|
@ -3,6 +3,12 @@
|
|||
|
||||
this.MergeRequestWidget = (function() {
|
||||
function MergeRequestWidget(opts) {
|
||||
// Initialize MergeRequestWidget behavior
|
||||
//
|
||||
// check_enable - Boolean, whether to check automerge status
|
||||
// merge_check_url - String, URL to use to check automerge status
|
||||
// ci_status_url - String, URL to use to check CI status
|
||||
//
|
||||
this.opts = opts;
|
||||
$('#modal_merge_info').modal({
|
||||
show: false
|
||||
|
@ -118,6 +124,8 @@
|
|||
if (data.coverage) {
|
||||
_this.showCICoverage(data.coverage);
|
||||
}
|
||||
// The first check should only update the UI, a notification
|
||||
// should only be displayed on status changes
|
||||
if (showNotification && !_this.firstCICheck) {
|
||||
status = _this.ciLabelForStatus(data.status);
|
||||
if (status === "preparing") {
|
||||
|
|
|
@ -110,6 +110,7 @@
|
|||
},
|
||||
update: function(event, ui) {
|
||||
var data;
|
||||
// Prevents sorting from container which element has been removed.
|
||||
if ($(this).find(ui.item).length > 0) {
|
||||
data = $(this).sortable("serialize");
|
||||
return Milestone.sortIssues(data);
|
||||
|
|
|
@ -92,6 +92,7 @@
|
|||
},
|
||||
hidden: function() {
|
||||
$selectbox.hide();
|
||||
// display:block overrides the hide-collapse rule
|
||||
return $value.css('display', '');
|
||||
},
|
||||
clicked: function(selected, $el, e) {
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
results = [];
|
||||
while (k < this.mspace) {
|
||||
this.colors.push(Raphael.getColor(.8));
|
||||
// Skipping a few colors in the spectrum to get more contrast between colors
|
||||
Raphael.getColor();
|
||||
Raphael.getColor();
|
||||
results.push(k++);
|
||||
|
@ -112,6 +113,7 @@
|
|||
for (mm = j = 0, len = ref.length; j < len; mm = ++j) {
|
||||
day = ref[mm];
|
||||
if (cuday !== day[0] || cumonth !== day[1]) {
|
||||
// Dates
|
||||
r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({
|
||||
font: "12px Monaco, monospace",
|
||||
fill: "#BBB"
|
||||
|
@ -119,6 +121,7 @@
|
|||
cuday = day[0];
|
||||
}
|
||||
if (cumonth !== day[1]) {
|
||||
// Months
|
||||
r.text(20, this.offsetY + this.unitTime * mm, day[1]).attr({
|
||||
font: "12px Monaco, monospace",
|
||||
fill: "#EEE"
|
||||
|
@ -207,6 +210,7 @@
|
|||
}
|
||||
r = this.r;
|
||||
shortrefs = commit.refs;
|
||||
// Truncate if longer than 15 chars
|
||||
if (shortrefs.length > 17) {
|
||||
shortrefs = shortrefs.substr(0, 15) + "…";
|
||||
}
|
||||
|
@ -217,6 +221,7 @@
|
|||
title: commit.refs
|
||||
});
|
||||
textbox = text.getBBox();
|
||||
// Create rectangle based on the size of the textbox
|
||||
rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({
|
||||
fill: "#000",
|
||||
"fill-opacity": .5,
|
||||
|
@ -229,6 +234,7 @@
|
|||
});
|
||||
label = r.set(rect, text);
|
||||
label.transform(["t", -rect.getBBox().width - 15, 0]);
|
||||
// Set text to front
|
||||
return text.toFront();
|
||||
};
|
||||
|
||||
|
@ -283,11 +289,13 @@
|
|||
parentY = this.offsetY + this.unitTime * parentCommit.time;
|
||||
parentX1 = this.offsetX + this.unitSpace * (this.mspace - parentCommit.space);
|
||||
parentX2 = this.offsetX + this.unitSpace * (this.mspace - parent[1]);
|
||||
// Set line color
|
||||
if (parentCommit.space <= commit.space) {
|
||||
color = this.colors[commit.space];
|
||||
} else {
|
||||
color = this.colors[parentCommit.space];
|
||||
}
|
||||
// Build line shape
|
||||
if (parent[1] === commit.space) {
|
||||
offset = [0, 5];
|
||||
arrow = "l-2,5,4,0,-2,-5,0,5";
|
||||
|
@ -298,13 +306,17 @@
|
|||
offset = [-3, 3];
|
||||
arrow = "l-5,0,2,4,3,-4,-4,2";
|
||||
}
|
||||
// Start point
|
||||
route = ["M", x + offset[0], y + offset[1]];
|
||||
// Add arrow if not first parent
|
||||
if (i > 0) {
|
||||
route.push(arrow);
|
||||
}
|
||||
// Circumvent if overlap
|
||||
if (commit.space !== parentCommit.space || commit.space !== parent[1]) {
|
||||
route.push("L", parentX2, y + 10, "L", parentX2, parentY - 5);
|
||||
}
|
||||
// End point
|
||||
route.push("L", parentX1, parentY);
|
||||
results.push(r.path(route).attr({
|
||||
stroke: color,
|
||||
|
@ -325,6 +337,7 @@
|
|||
"fill-opacity": .5,
|
||||
stroke: "none"
|
||||
});
|
||||
// Displayed in the center
|
||||
return this.element.scrollTop(y - this.graphHeight / 2);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
|
||||
// This is a manifest file that'll be compiled into including all the files listed below.
|
||||
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
|
||||
// be included in the compiled file accessible from http://example.com/assets/application.js
|
||||
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
||||
// the compiled file.
|
||||
//
|
||||
/*= require_tree . */
|
||||
|
||||
(function() {
|
||||
|
|
|
@ -1,22 +1,10 @@
|
|||
|
||||
/*= require autosave */
|
||||
|
||||
|
||||
/*= require autosize */
|
||||
|
||||
|
||||
/*= require dropzone */
|
||||
|
||||
|
||||
/*= require dropzone_input */
|
||||
|
||||
|
||||
/*= require gfm_auto_complete */
|
||||
|
||||
|
||||
/*= require jquery.atwho */
|
||||
|
||||
|
||||
/*= require task_list */
|
||||
|
||||
(function() {
|
||||
|
@ -60,26 +48,43 @@
|
|||
}
|
||||
|
||||
Notes.prototype.addBinding = function() {
|
||||
// add note to UI after creation
|
||||
$(document).on("ajax:success", ".js-main-target-form", this.addNote);
|
||||
$(document).on("ajax:success", ".js-discussion-note-form", this.addDiscussionNote);
|
||||
// catch note ajax errors
|
||||
$(document).on("ajax:error", ".js-main-target-form", this.addNoteError);
|
||||
// change note in UI after update
|
||||
$(document).on("ajax:success", "form.edit-note", this.updateNote);
|
||||
// Edit note link
|
||||
$(document).on("click", ".js-note-edit", this.showEditForm);
|
||||
$(document).on("click", ".note-edit-cancel", this.cancelEdit);
|
||||
// Reopen and close actions for Issue/MR combined with note form submit
|
||||
$(document).on("click", ".js-comment-button", this.updateCloseButton);
|
||||
$(document).on("keyup input", ".js-note-text", this.updateTargetButtons);
|
||||
// resolve a discussion
|
||||
$(document).on('click', '.js-comment-resolve-button', this.resolveDiscussion);
|
||||
// remove a note (in general)
|
||||
$(document).on("click", ".js-note-delete", this.removeNote);
|
||||
// delete note attachment
|
||||
$(document).on("click", ".js-note-attachment-delete", this.removeAttachment);
|
||||
// reset main target form after submit
|
||||
$(document).on("ajax:complete", ".js-main-target-form", this.reenableTargetFormSubmitButton);
|
||||
$(document).on("ajax:success", ".js-main-target-form", this.resetMainTargetForm);
|
||||
// reset main target form when clicking discard
|
||||
$(document).on("click", ".js-note-discard", this.resetMainTargetForm);
|
||||
// update the file name when an attachment is selected
|
||||
$(document).on("change", ".js-note-attachment-input", this.updateFormAttachment);
|
||||
// reply to diff/discussion notes
|
||||
$(document).on("click", ".js-discussion-reply-button", this.replyToDiscussionNote);
|
||||
// add diff note
|
||||
$(document).on("click", ".js-add-diff-note-button", this.addDiffNote);
|
||||
// hide diff note form
|
||||
$(document).on("click", ".js-close-discussion-note-form", this.cancelDiscussionForm);
|
||||
// fetch notes when tab becomes visible
|
||||
$(document).on("visibilitychange", this.visibilityChange);
|
||||
// when issue status changes, we need to refresh data
|
||||
$(document).on("issuable:change", this.refresh);
|
||||
// when a key is clicked on the notes
|
||||
return $(document).on("keydown", ".js-note-text", this.keydownNoteText);
|
||||
};
|
||||
|
||||
|
@ -112,6 +117,7 @@
|
|||
return;
|
||||
}
|
||||
$textarea = $(e.target);
|
||||
// Edit previous note when UP arrow is hit
|
||||
switch (e.which) {
|
||||
case 38:
|
||||
if ($textarea.val() !== '') {
|
||||
|
@ -123,6 +129,7 @@
|
|||
return myLastNoteEditBtn.trigger('click', [true, myLastNote]);
|
||||
}
|
||||
break;
|
||||
// Cancel creating diff note or editing any note when ESCAPE is hit
|
||||
case 27:
|
||||
discussionNoteForm = $textarea.closest('.js-discussion-note-form');
|
||||
if (discussionNoteForm.length) {
|
||||
|
@ -247,10 +254,13 @@
|
|||
votesBlock = $('.js-awards-block').eq(0);
|
||||
gl.awardsHandler.addAwardToEmojiBar(votesBlock, note.name);
|
||||
return gl.awardsHandler.scrollToAwards();
|
||||
// render note if it not present in loaded list
|
||||
// or skip if rendered
|
||||
} else if (this.isNewNote(note)) {
|
||||
this.note_ids.push(note.id);
|
||||
$notesList = $('ul.main-notes-list');
|
||||
$notesList.append(note.html).syntaxHighlight();
|
||||
// Update datetime format on the recent note
|
||||
gl.utils.localTimeAgo($notesList.find("#note_" + note.id + " .js-timeago"), false);
|
||||
this.initTaskList();
|
||||
this.refresh();
|
||||
|
@ -291,19 +301,26 @@
|
|||
row = form.closest("tr");
|
||||
note_html = $(note.html);
|
||||
note_html.syntaxHighlight();
|
||||
// is this the first note of discussion?
|
||||
discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']");
|
||||
if ((note.original_discussion_id != null) && discussionContainer.length === 0) {
|
||||
discussionContainer = $(".notes[data-discussion-id='" + note.original_discussion_id + "']");
|
||||
}
|
||||
if (discussionContainer.length === 0) {
|
||||
// insert the note and the reply button after the temp row
|
||||
row.after(note.diff_discussion_html);
|
||||
// remove the note (will be added again below)
|
||||
row.next().find(".note").remove();
|
||||
// Before that, the container didn't exist
|
||||
discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']");
|
||||
// Add note to 'Changes' page discussions
|
||||
discussionContainer.append(note_html);
|
||||
// Init discussion on 'Discussion' page if it is merge request page
|
||||
if ($('body').attr('data-page').indexOf('projects:merge_request') === 0) {
|
||||
$('ul.main-notes-list').append(note.discussion_html).syntaxHighlight();
|
||||
}
|
||||
} else {
|
||||
// append new note to all matching discussions
|
||||
discussionContainer.append(note_html);
|
||||
}
|
||||
|
||||
|
@ -327,11 +344,18 @@
|
|||
Notes.prototype.resetMainTargetForm = function(e) {
|
||||
var form;
|
||||
form = $(".js-main-target-form");
|
||||
// remove validation errors
|
||||
form.find(".js-errors").remove();
|
||||
// reset text and preview
|
||||
form.find(".js-md-write-button").click();
|
||||
form.find(".js-note-text").val("").trigger("input");
|
||||
form.find(".js-note-text").data("autosave").reset();
|
||||
return this.updateTargetButtons(e);
|
||||
|
||||
var event = document.createEvent('Event');
|
||||
event.initEvent('autosize:update', true, false);
|
||||
form.find('.js-autosize')[0].dispatchEvent(event);
|
||||
|
||||
this.updateTargetButtons(e);
|
||||
};
|
||||
|
||||
Notes.prototype.reenableTargetFormSubmitButton = function() {
|
||||
|
@ -349,9 +373,13 @@
|
|||
|
||||
Notes.prototype.setupMainTargetNoteForm = function() {
|
||||
var form;
|
||||
// find the form
|
||||
form = $(".js-new-note-form");
|
||||
// Set a global clone of the form for later cloning
|
||||
this.formClone = form.clone();
|
||||
// show the form
|
||||
this.setupNoteForm(form);
|
||||
// fix classes
|
||||
form.removeClass("js-new-note-form");
|
||||
form.addClass("js-main-target-form");
|
||||
form.find("#note_line_code").remove();
|
||||
|
@ -416,6 +444,7 @@
|
|||
}
|
||||
|
||||
this.renderDiscussionNote(note);
|
||||
// cleanup after successfully creating a diff/discussion note
|
||||
this.removeDiscussionNoteForm($form);
|
||||
};
|
||||
|
||||
|
@ -428,10 +457,12 @@
|
|||
|
||||
Notes.prototype.updateNote = function(_xhr, note, _status) {
|
||||
var $html, $note_li;
|
||||
// Convert returned HTML to a jQuery object so we can modify it further
|
||||
$html = $(note.html);
|
||||
gl.utils.localTimeAgo($('.js-timeago', $html));
|
||||
$html.syntaxHighlight();
|
||||
$html.find('.js-task-list-container').taskList('enable');
|
||||
// Find the note's `li` element by ID and replace it with the updated HTML
|
||||
$note_li = $('.note-row-' + note.id);
|
||||
|
||||
$note_li.replaceWith($html);
|
||||
|
@ -456,15 +487,20 @@
|
|||
note.addClass("is-editting");
|
||||
form = note.find(".note-edit-form");
|
||||
form.addClass('current-note-edit-form');
|
||||
// Show the attachment delete link
|
||||
note.find(".js-note-attachment-delete").show();
|
||||
done = function($noteText) {
|
||||
var noteTextVal;
|
||||
// Neat little trick to put the cursor at the end
|
||||
noteTextVal = $noteText.val();
|
||||
// Store the original note text in a data attribute to retrieve if a user cancels edit.
|
||||
form.find('form.edit-note').data('original-note', noteTextVal);
|
||||
return $noteText.val('').val(noteTextVal);
|
||||
};
|
||||
new GLForm(form);
|
||||
if ((scrollTo != null) && (myLastNote != null)) {
|
||||
// scroll to the bottom
|
||||
// so the open of the last element doesn't make a jump
|
||||
$('html, body').scrollTop($(document).height());
|
||||
return $('html, body').animate({
|
||||
scrollTop: myLastNote.offset().top - 150
|
||||
|
@ -500,6 +536,7 @@
|
|||
form = note.find(".current-note-edit-form");
|
||||
note.removeClass("is-editting");
|
||||
form.removeClass("current-note-edit-form");
|
||||
// Replace markdown textarea text with original note text.
|
||||
return form.find(".js-note-text").val(form.find('form.edit-note').data('original-note'));
|
||||
};
|
||||
|
||||
|
@ -515,6 +552,9 @@
|
|||
var noteId;
|
||||
noteId = $(e.currentTarget).closest(".note").attr("id");
|
||||
$(".note[id='" + noteId + "']").each((function(_this) {
|
||||
// A same note appears in the "Discussion" and in the "Changes" tab, we have
|
||||
// to remove all. Using $(".note[id='noteId']") ensure we get all the notes,
|
||||
// where $("#noteId") would return only one.
|
||||
return function(i, el) {
|
||||
var note, notes;
|
||||
note = $(el);
|
||||
|
@ -528,13 +568,17 @@
|
|||
}
|
||||
}
|
||||
|
||||
// check if this is the last note for this line
|
||||
if (notes.find(".note").length === 1) {
|
||||
// "Discussions" tab
|
||||
notes.closest(".timeline-entry").remove();
|
||||
// "Changes" tab / commit view
|
||||
notes.closest("tr").remove();
|
||||
}
|
||||
return note.remove();
|
||||
};
|
||||
})(this));
|
||||
// Decrement the "Discussions" counter only once
|
||||
return this.updateNotesCount(-1);
|
||||
};
|
||||
|
||||
|
@ -566,10 +610,12 @@
|
|||
var form, replyLink;
|
||||
form = this.formClone.clone();
|
||||
replyLink = $(e.target).closest(".js-discussion-reply-button");
|
||||
// insert the form after the button
|
||||
replyLink
|
||||
.closest('.discussion-reply-holder')
|
||||
.hide()
|
||||
.after(form);
|
||||
// show the form
|
||||
return this.setupDiscussionNoteForm(replyLink, form);
|
||||
};
|
||||
|
||||
|
@ -584,6 +630,7 @@
|
|||
*/
|
||||
|
||||
Notes.prototype.setupDiscussionNoteForm = function(dataHolder, form) {
|
||||
// setup note target
|
||||
form.attr('id', "new-discussion-note-form-" + (dataHolder.data("discussionId")));
|
||||
form.attr("data-line-code", dataHolder.data("lineCode"));
|
||||
form.find("#note_type").val(dataHolder.data("noteType"));
|
||||
|
@ -631,6 +678,7 @@
|
|||
addForm = false;
|
||||
notesContentSelector = ".notes_content";
|
||||
rowCssToAdd = "<tr class=\"notes_holder js-temp-notes-holder\"><td class=\"notes_line\" colspan=\"2\"></td><td class=\"notes_content\"><div class=\"content\"></div></td></tr>";
|
||||
// In parallel view, look inside the correct left/right pane
|
||||
if (this.isParallelView()) {
|
||||
lineType = $link.data("lineType");
|
||||
notesContentSelector += "." + lineType;
|
||||
|
@ -647,6 +695,7 @@
|
|||
e.target = replyButton[0];
|
||||
$.proxy(this.replyToDiscussionNote, replyButton[0], e).call();
|
||||
} else {
|
||||
// In parallel view, the form may not be present in one of the panes
|
||||
noteForm = notesContent.find(".js-discussion-note-form");
|
||||
if (noteForm.length === 0) {
|
||||
addForm = true;
|
||||
|
@ -654,6 +703,7 @@
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// add a notes row and insert the form
|
||||
row.after(rowCssToAdd);
|
||||
nextRow = row.next();
|
||||
notesContent = nextRow.find(notesContentSelector);
|
||||
|
@ -662,6 +712,7 @@
|
|||
if (addForm) {
|
||||
newForm = this.formClone.clone();
|
||||
newForm.appendTo(notesContent);
|
||||
// show the form
|
||||
return this.setupDiscussionNoteForm($link, newForm);
|
||||
}
|
||||
};
|
||||
|
@ -680,12 +731,15 @@
|
|||
glForm = form.data('gl-form');
|
||||
glForm.destroy();
|
||||
form.find(".js-note-text").data("autosave").reset();
|
||||
// show the reply button (will only work for replies)
|
||||
form
|
||||
.prev('.discussion-reply-holder')
|
||||
.show();
|
||||
if (row.is(".js-temp-notes-holder")) {
|
||||
// remove temporary row for diff lines
|
||||
return row.remove();
|
||||
} else {
|
||||
// only remove the form
|
||||
return form.remove();
|
||||
}
|
||||
};
|
||||
|
@ -707,6 +761,7 @@
|
|||
Notes.prototype.updateFormAttachment = function() {
|
||||
var filename, form;
|
||||
form = $(this).closest("form");
|
||||
// get only the basename
|
||||
filename = $(this).val().replace(/^.*[\\\/]/, "");
|
||||
return form.find(".js-attachment-filename").text(filename);
|
||||
};
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
// MarkdownPreview
|
||||
//
|
||||
// Handles toggling the "Write" and "Preview" tab clicks, rendering the preview,
|
||||
// and showing a warning when more than `x` users are referenced.
|
||||
//
|
||||
(function() {
|
||||
var lastTextareaPreviewed, markdownPreview, previewButtonSelector, writeButtonSelector;
|
||||
|
||||
this.MarkdownPreview = (function() {
|
||||
function MarkdownPreview() {}
|
||||
|
||||
// Minimum number of users referenced before triggering a warning
|
||||
MarkdownPreview.prototype.referenceThreshold = 10;
|
||||
|
||||
MarkdownPreview.prototype.ajaxCache = {};
|
||||
|
@ -101,8 +107,10 @@
|
|||
return;
|
||||
}
|
||||
lastTextareaPreviewed = $form.find('textarea.markdown-area');
|
||||
// toggle tabs
|
||||
$form.find(writeButtonSelector).parent().removeClass('active');
|
||||
$form.find(previewButtonSelector).parent().addClass('active');
|
||||
// toggle content
|
||||
$form.find('.md-write-holder').hide();
|
||||
$form.find('.md-preview-holder').show();
|
||||
return markdownPreview.showPreview($form);
|
||||
|
@ -113,8 +121,10 @@
|
|||
return;
|
||||
}
|
||||
lastTextareaPreviewed = null;
|
||||
// toggle tabs
|
||||
$form.find(writeButtonSelector).parent().addClass('active');
|
||||
$form.find(previewButtonSelector).parent().removeClass('active');
|
||||
// toggle content
|
||||
$form.find('.md-write-holder').show();
|
||||
$form.find('textarea.markdown-area').focus();
|
||||
return $form.find('.md-preview-holder').hide();
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
GitLabCrop = (function() {
|
||||
var FILENAMEREGEX;
|
||||
|
||||
// Matches everything but the file name
|
||||
FILENAMEREGEX = /^.*[\\\/]/;
|
||||
|
||||
function GitLabCrop(input, opts) {
|
||||
|
@ -17,11 +18,18 @@
|
|||
this.onModalShow = bind(this.onModalShow, this);
|
||||
this.onPickImageClick = bind(this.onPickImageClick, this);
|
||||
this.fileInput = $(input);
|
||||
// We should rename to avoid spec to fail
|
||||
// Form will submit the proper input filed with a file using FormData
|
||||
this.fileInput.attr('name', (this.fileInput.attr('name')) + "-trigger").attr('id', (this.fileInput.attr('id')) + "-trigger");
|
||||
// Set defaults
|
||||
this.exportWidth = (ref = opts.exportWidth) != null ? ref : 200, this.exportHeight = (ref1 = opts.exportHeight) != null ? ref1 : 200, this.cropBoxWidth = (ref2 = opts.cropBoxWidth) != null ? ref2 : 200, this.cropBoxHeight = (ref3 = opts.cropBoxHeight) != null ? ref3 : 200, this.form = (ref4 = opts.form) != null ? ref4 : this.fileInput.parents('form'), this.filename = opts.filename, this.previewImage = opts.previewImage, this.modalCrop = opts.modalCrop, this.pickImageEl = opts.pickImageEl, this.uploadImageBtn = opts.uploadImageBtn, this.modalCropImg = opts.modalCropImg;
|
||||
// Required params
|
||||
// Ensure needed elements are jquery objects
|
||||
// If selector is provided we will convert them to a jQuery Object
|
||||
this.filename = this.getElement(this.filename);
|
||||
this.previewImage = this.getElement(this.previewImage);
|
||||
this.pickImageEl = this.getElement(this.pickImageEl);
|
||||
// Modal elements usually are outside the @form element
|
||||
this.modalCrop = _.isString(this.modalCrop) ? $(this.modalCrop) : this.modalCrop;
|
||||
this.uploadImageBtn = _.isString(this.uploadImageBtn) ? $(this.uploadImageBtn) : this.uploadImageBtn;
|
||||
this.modalCropImg = _.isString(this.modalCropImg) ? $(this.modalCropImg) : this.modalCropImg;
|
||||
|
@ -93,8 +101,8 @@
|
|||
return this.modalCropImg.attr('src', '').cropper('destroy');
|
||||
};
|
||||
|
||||
GitLabCrop.prototype.onUploadImageBtnClick = function(e) {
|
||||
e.preventDefault();
|
||||
GitLabCrop.prototype.onUploadImageBtnClick = function(e) { // Remove attached image
|
||||
e.preventDefault(); // Destroy cropper instance
|
||||
this.setBlob();
|
||||
this.setPreview();
|
||||
this.modalCrop.modal('hide');
|
||||
|
|
|
@ -11,9 +11,11 @@
|
|||
this.form = (ref = opts.form) != null ? ref : $('.edit-user');
|
||||
$('.js-preferences-form').on('change.preference', 'input[type=radio]', function() {
|
||||
return $(this).parents('form').submit();
|
||||
// Automatically submit the Preferences form when any of its radio buttons change
|
||||
});
|
||||
$('#user_notification_email').on('change', function() {
|
||||
return $(this).parents('form').submit();
|
||||
// Automatically submit email form when it changes
|
||||
});
|
||||
$('.update-username').on('ajax:before', function() {
|
||||
$('.loading-username').show();
|
||||
|
@ -76,6 +78,7 @@
|
|||
},
|
||||
complete: function() {
|
||||
window.scrollTo(0, 0);
|
||||
// Enable submit button after requests ends
|
||||
return self.form.find(':input[disabled]').enable();
|
||||
}
|
||||
});
|
||||
|
@ -93,6 +96,7 @@
|
|||
if (comment && comment.length > 1 && $title.val() === '') {
|
||||
return $title.val(comment[1]).change();
|
||||
}
|
||||
// Extract the SSH Key title from its comment
|
||||
});
|
||||
if (gl.utils.getPagePath() === 'profiles') {
|
||||
return new Profile();
|
||||
|
|
|
@ -3,5 +3,4 @@
|
|||
|
||||
(function() {
|
||||
|
||||
|
||||
}).call(this);
|
||||
|
|
|
@ -11,25 +11,27 @@
|
|||
url = $("#project_clone").val();
|
||||
$('#project_clone').val(url);
|
||||
return $('.clone').text(url);
|
||||
// Git protocol switcher
|
||||
// Remove the active class for all buttons (ssh, http, kerberos if shown)
|
||||
// Add the active class for the clicked button
|
||||
// Update the input field
|
||||
// Update the command line instructions
|
||||
});
|
||||
// Ref switcher
|
||||
this.initRefSwitcher();
|
||||
$('.project-refs-select').on('change', function() {
|
||||
return $(this).parents('form').submit();
|
||||
});
|
||||
$('.hide-no-ssh-message').on('click', function(e) {
|
||||
var path;
|
||||
path = '/';
|
||||
$.cookie('hide_no_ssh_message', 'false', {
|
||||
path: path
|
||||
path: gon.relative_url_root || '/'
|
||||
});
|
||||
$(this).parents('.no-ssh-key-message').remove();
|
||||
return e.preventDefault();
|
||||
});
|
||||
$('.hide-no-password-message').on('click', function(e) {
|
||||
var path;
|
||||
path = '/';
|
||||
$.cookie('hide_no_password_message', 'false', {
|
||||
path: path
|
||||
path: gon.relative_url_root || '/'
|
||||
});
|
||||
$(this).parents('.no-password-message').remove();
|
||||
return e.preventDefault();
|
||||
|
|
|
@ -13,8 +13,11 @@
|
|||
this.selectRowUp = bind(this.selectRowUp, this);
|
||||
this.filePaths = {};
|
||||
this.inputElement = this.element.find(".file-finder-input");
|
||||
// init event
|
||||
this.initEvent();
|
||||
// focus text input box
|
||||
this.inputElement.focus();
|
||||
// load file list
|
||||
this.load(this.options.url);
|
||||
}
|
||||
|
||||
|
@ -42,6 +45,7 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
// init event
|
||||
};
|
||||
|
||||
ProjectFindFile.prototype.findFile = function() {
|
||||
|
@ -49,8 +53,10 @@
|
|||
searchText = this.inputElement.val();
|
||||
result = searchText.length > 0 ? fuzzaldrinPlus.filter(this.filePaths, searchText) : this.filePaths;
|
||||
return this.renderList(result, searchText);
|
||||
// find file
|
||||
};
|
||||
|
||||
// files pathes load
|
||||
ProjectFindFile.prototype.load = function(url) {
|
||||
return $.ajax({
|
||||
url: url,
|
||||
|
@ -67,6 +73,7 @@
|
|||
});
|
||||
};
|
||||
|
||||
// render result
|
||||
ProjectFindFile.prototype.renderList = function(filePaths, searchText) {
|
||||
var blobItemUrl, filePath, html, i, j, len, matches, results;
|
||||
this.element.find(".tree-table > tbody").empty();
|
||||
|
@ -86,6 +93,7 @@
|
|||
return results;
|
||||
};
|
||||
|
||||
// highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
|
||||
highlighter = function(element, text, matches) {
|
||||
var highlightText, j, lastIndex, len, matchIndex, matchedChars, unmatched;
|
||||
lastIndex = 0;
|
||||
|
@ -110,6 +118,7 @@
|
|||
return element.append(document.createTextNode(text.substring(lastIndex)));
|
||||
};
|
||||
|
||||
// make tbody row html
|
||||
ProjectFindFile.prototype.makeHtml = function(filePath, matches, blobItemUrl) {
|
||||
var $tr;
|
||||
$tr = $("<tr class='tree-item'><td class='tree-item-file-name'><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'><a></a></span></td></tr>");
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
this.ProjectNew = (function() {
|
||||
function ProjectNew() {
|
||||
this.toggleSettings = bind(this.toggleSettings, this);
|
||||
this.$selects = $('.features select');
|
||||
|
||||
$('.project-edit-container').on('ajax:before', (function(_this) {
|
||||
return function() {
|
||||
$('.project-edit-container').hide();
|
||||
|
@ -15,18 +17,24 @@
|
|||
}
|
||||
|
||||
ProjectNew.prototype.toggleSettings = function() {
|
||||
this._showOrHide('#project_builds_enabled', '.builds-feature');
|
||||
return this._showOrHide('#project_merge_requests_enabled', '.merge-requests-feature');
|
||||
var self = this;
|
||||
|
||||
this.$selects.each(function () {
|
||||
var $select = $(this),
|
||||
className = $select.data('field').replace(/_/g, '-')
|
||||
.replace('access-level', 'feature');
|
||||
self._showOrHide($select, '.' + className);
|
||||
});
|
||||
};
|
||||
|
||||
ProjectNew.prototype.toggleSettingsOnclick = function() {
|
||||
return $('#project_builds_enabled, #project_merge_requests_enabled').on('click', this.toggleSettings);
|
||||
this.$selects.on('change', this.toggleSettings);
|
||||
};
|
||||
|
||||
ProjectNew.prototype._showOrHide = function(checkElement, container) {
|
||||
var $container;
|
||||
$container = $(container);
|
||||
if ($(checkElement).prop('checked')) {
|
||||
var $container = $(container);
|
||||
|
||||
if ($(checkElement).val() !== '0') {
|
||||
return $container.show();
|
||||
} else {
|
||||
return $container.hide();
|
||||
|
|
|
@ -7,3 +7,5 @@
|
|||
})();
|
||||
|
||||
}).call(this);
|
||||
|
||||
// I kept class for future
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
$('.projects-list-holder').replaceWith(data.html);
|
||||
return history.replaceState({
|
||||
page: project_filter_url
|
||||
// Change url so if user reload a page - search results are saved
|
||||
}, document.title, project_filter_url);
|
||||
},
|
||||
dataType: "json"
|
||||
|
|
|
@ -45,6 +45,7 @@ class ProtectedBranchDropdown {
|
|||
}
|
||||
|
||||
onClickCreateWildcard() {
|
||||
// Refresh the dropdown's data, which ends up calling `getProtectedBranches`
|
||||
this.$dropdown.data('glDropdown').remote.execute();
|
||||
this.$dropdown.data('glDropdown').selectRowAtIndex(0);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
}
|
||||
if (!triggered) {
|
||||
return $.cookie("collapsed_gutter", $('.right-sidebar').hasClass('right-sidebar-collapsed'), {
|
||||
path: '/'
|
||||
path: gon.relative_url_root || '/'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
this.onSearchInputKeyUp = bind(this.onSearchInputKeyUp, this);
|
||||
this.onSearchInputKeyDown = bind(this.onSearchInputKeyDown, this);
|
||||
this.wrap = (ref = opts.wrap) != null ? ref : $('.search'), this.optsEl = (ref1 = opts.optsEl) != null ? ref1 : this.wrap.find('.search-autocomplete-opts'), this.autocompletePath = (ref2 = opts.autocompletePath) != null ? ref2 : this.optsEl.data('autocomplete-path'), this.projectId = (ref3 = opts.projectId) != null ? ref3 : this.optsEl.data('autocomplete-project-id') || '', this.projectRef = (ref4 = opts.projectRef) != null ? ref4 : this.optsEl.data('autocomplete-project-ref') || '';
|
||||
// Dropdown Element
|
||||
this.dropdown = this.wrap.find('.dropdown');
|
||||
this.dropdownContent = this.dropdown.find('.dropdown-content');
|
||||
this.locationBadgeEl = this.getElement('.location-badge');
|
||||
|
@ -35,6 +36,7 @@
|
|||
this.repositoryInputEl = this.getElement('#repository_ref');
|
||||
this.clearInput = this.getElement('.js-clear-input');
|
||||
this.saveOriginalState();
|
||||
// Only when user is logged in
|
||||
if (gon.current_user_id) {
|
||||
this.createAutocomplete();
|
||||
}
|
||||
|
@ -43,6 +45,7 @@
|
|||
this.bindEvents();
|
||||
}
|
||||
|
||||
// Finds an element inside wrapper element
|
||||
SearchAutocomplete.prototype.getElement = function(selector) {
|
||||
return this.wrap.find(selector);
|
||||
};
|
||||
|
@ -82,6 +85,7 @@
|
|||
}
|
||||
return;
|
||||
}
|
||||
// Prevent multiple ajax calls
|
||||
if (this.loadingSuggestions) {
|
||||
return;
|
||||
}
|
||||
|
@ -92,14 +96,17 @@
|
|||
term: term
|
||||
}, function(response) {
|
||||
var data, firstCategory, i, lastCategory, len, suggestion;
|
||||
// Hide dropdown menu if no suggestions returns
|
||||
if (!response.length) {
|
||||
_this.disableAutocomplete();
|
||||
return;
|
||||
}
|
||||
data = [];
|
||||
// List results
|
||||
firstCategory = true;
|
||||
for (i = 0, len = response.length; i < len; i++) {
|
||||
suggestion = response[i];
|
||||
// Add group header before list each group
|
||||
if (lastCategory !== suggestion.category) {
|
||||
if (!firstCategory) {
|
||||
data.push('separator');
|
||||
|
@ -119,6 +126,7 @@
|
|||
url: suggestion.url
|
||||
});
|
||||
}
|
||||
// Add option to proceed with the search
|
||||
if (data.length) {
|
||||
data.push('separator');
|
||||
data.push({
|
||||
|
@ -169,11 +177,13 @@
|
|||
|
||||
SearchAutocomplete.prototype.serializeState = function() {
|
||||
return {
|
||||
// Search Criteria
|
||||
search_project_id: this.projectInputEl.val(),
|
||||
group_id: this.groupInputEl.val(),
|
||||
search_code: this.searchCodeInputEl.val(),
|
||||
repository_ref: this.repositoryInputEl.val(),
|
||||
scope: this.scopeInputEl.val(),
|
||||
// Location badge
|
||||
_location: this.locationBadgeEl.text()
|
||||
};
|
||||
};
|
||||
|
@ -194,6 +204,7 @@
|
|||
|
||||
SearchAutocomplete.prototype.enableAutocomplete = function() {
|
||||
var _this;
|
||||
// No need to enable anything if user is not logged in
|
||||
if (!gon.current_user_id) {
|
||||
return;
|
||||
}
|
||||
|
@ -206,18 +217,22 @@
|
|||
};
|
||||
|
||||
SearchAutocomplete.prototype.onSearchInputKeyDown = function() {
|
||||
// Saves last length of the entered text
|
||||
return this.saveTextLength();
|
||||
};
|
||||
|
||||
SearchAutocomplete.prototype.onSearchInputKeyUp = function(e) {
|
||||
switch (e.keyCode) {
|
||||
case KEYCODE.BACKSPACE:
|
||||
// when trying to remove the location badge
|
||||
if (this.lastTextLength === 0 && this.badgePresent()) {
|
||||
this.removeLocationBadge();
|
||||
}
|
||||
// When removing the last character and no badge is present
|
||||
if (this.lastTextLength === 1) {
|
||||
this.disableAutocomplete();
|
||||
}
|
||||
// When removing any character from existin value
|
||||
if (this.lastTextLength > 1) {
|
||||
this.enableAutocomplete();
|
||||
}
|
||||
|
@ -232,9 +247,12 @@
|
|||
case KEYCODE.DOWN:
|
||||
return;
|
||||
default:
|
||||
// Handle the case when deleting the input value other than backspace
|
||||
// e.g. Pressing ctrl + backspace or ctrl + x
|
||||
if (this.searchInput.val() === '') {
|
||||
this.disableAutocomplete();
|
||||
} else {
|
||||
// We should display the menu only when input is not empty
|
||||
if (e.keyCode !== KEYCODE.ENTER) {
|
||||
this.enableAutocomplete();
|
||||
}
|
||||
|
@ -243,7 +261,9 @@
|
|||
this.wrap.toggleClass('has-value', !!e.target.value);
|
||||
};
|
||||
|
||||
// Avoid falsy value to be returned
|
||||
SearchAutocomplete.prototype.onSearchInputClick = function(e) {
|
||||
// Prevents closing the dropdown menu
|
||||
return e.stopImmediatePropagation();
|
||||
};
|
||||
|
||||
|
@ -267,6 +287,7 @@
|
|||
SearchAutocomplete.prototype.onSearchInputBlur = function(e) {
|
||||
this.isFocused = false;
|
||||
this.wrap.removeClass('search-active');
|
||||
// If input is blank then restore state
|
||||
if (this.searchInput.val() === '') {
|
||||
return this.restoreOriginalState();
|
||||
}
|
||||
|
@ -311,6 +332,7 @@
|
|||
results = [];
|
||||
for (i = 0, len = inputs.length; i < len; i++) {
|
||||
input = inputs[i];
|
||||
// _location isnt a input
|
||||
if (input === '_location') {
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
var defaultStopCallback;
|
||||
defaultStopCallback = Mousetrap.stopCallback;
|
||||
return function(e, element, combo) {
|
||||
// allowed shortcuts if textarea, input, contenteditable are focused
|
||||
if (['ctrl+shift+p', 'command+shift+p'].indexOf(combo) !== -1) {
|
||||
return false;
|
||||
} else {
|
||||
|
|
|
@ -14,8 +14,10 @@
|
|||
ShortcutsFindFile.__super__.constructor.call(this);
|
||||
_oldStopCallback = Mousetrap.stopCallback;
|
||||
Mousetrap.stopCallback = (function(_this) {
|
||||
// override to fire shortcuts action when focus in textbox
|
||||
return function(event, element, combo) {
|
||||
if (element === _this.projectFindFile.inputElement[0] && (combo === 'up' || combo === 'down' || combo === 'esc' || combo === 'enter')) {
|
||||
// when press up/down key in textbox, cusor prevent to move to home/end
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
|
||||
/*= require mousetrap */
|
||||
|
||||
|
||||
/*= require shortcuts_navigation */
|
||||
|
||||
(function() {
|
||||
|
@ -43,16 +41,20 @@
|
|||
if (selected.trim() === "") {
|
||||
return;
|
||||
}
|
||||
// Put a '>' character before each non-empty line in the selection
|
||||
quote = _.map(selected.split("\n"), function(val) {
|
||||
if (val.trim() !== '') {
|
||||
return "> " + val + "\n";
|
||||
}
|
||||
});
|
||||
// If replyField already has some content, add a newline before our quote
|
||||
separator = replyField.val().trim() !== "" && "\n" || '';
|
||||
replyField.val(function(_, current) {
|
||||
return current + separator + quote.join('') + "\n";
|
||||
});
|
||||
// Trigger autosave for the added text
|
||||
replyField.trigger('input');
|
||||
// Focus the input field
|
||||
return replyField.focus();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
(function() {
|
||||
var collapsed, expanded, toggleSidebar;
|
||||
|
||||
collapsed = 'page-sidebar-collapsed';
|
||||
|
||||
expanded = 'page-sidebar-expanded';
|
||||
|
||||
toggleSidebar = function() {
|
||||
$('.page-with-sidebar').toggleClass(collapsed + " " + expanded);
|
||||
$('.navbar-fixed-top').toggleClass("header-collapsed header-expanded");
|
||||
if ($.cookie('pin_nav') === 'true') {
|
||||
$('.navbar-fixed-top').toggleClass('header-pinned-nav');
|
||||
$('.page-with-sidebar').toggleClass('page-sidebar-pinned');
|
||||
}
|
||||
return setTimeout((function() {
|
||||
var niceScrollBars;
|
||||
niceScrollBars = $('.nav-sidebar').niceScroll();
|
||||
return niceScrollBars.updateScrollBar();
|
||||
}), 300);
|
||||
};
|
||||
|
||||
$(document).off('click', 'body').on('click', 'body', function(e) {
|
||||
var $nav, $target, $toggle, pageExpanded;
|
||||
if ($.cookie('pin_nav') !== 'true') {
|
||||
$target = $(e.target);
|
||||
$nav = $target.closest('.sidebar-wrapper');
|
||||
pageExpanded = $('.page-with-sidebar').hasClass('page-sidebar-expanded');
|
||||
$toggle = $target.closest('.toggle-nav-collapse, .side-nav-toggle');
|
||||
if ($nav.length === 0 && pageExpanded && $toggle.length === 0) {
|
||||
$('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded');
|
||||
return $('.navbar-fixed-top').toggleClass('header-collapsed header-expanded');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', function(e) {
|
||||
e.preventDefault();
|
||||
return toggleSidebar();
|
||||
});
|
||||
|
||||
}).call(this);
|
|
@ -0,0 +1,93 @@
|
|||
((global) => {
|
||||
let singleton;
|
||||
|
||||
const pinnedStateCookie = 'pin_nav';
|
||||
const sidebarBreakpoint = 1024;
|
||||
|
||||
const pageSelector = '.page-with-sidebar';
|
||||
const navbarSelector = '.navbar-fixed-top';
|
||||
const sidebarWrapperSelector = '.sidebar-wrapper';
|
||||
const sidebarContentSelector = '.nav-sidebar';
|
||||
|
||||
const pinnedToggleSelector = '.js-nav-pin';
|
||||
const sidebarToggleSelector = '.toggle-nav-collapse, .side-nav-toggle';
|
||||
|
||||
const pinnedPageClass = 'page-sidebar-pinned';
|
||||
const expandedPageClass = 'page-sidebar-expanded';
|
||||
|
||||
const pinnedNavbarClass = 'header-sidebar-pinned';
|
||||
const expandedNavbarClass = 'header-sidebar-expanded';
|
||||
|
||||
class Sidebar {
|
||||
constructor() {
|
||||
if (!singleton) {
|
||||
singleton = this;
|
||||
singleton.init();
|
||||
}
|
||||
return singleton;
|
||||
}
|
||||
|
||||
init() {
|
||||
this.isPinned = $.cookie(pinnedStateCookie) === 'true';
|
||||
this.isExpanded = (
|
||||
window.innerWidth >= sidebarBreakpoint &&
|
||||
$(pageSelector).hasClass(expandedPageClass)
|
||||
);
|
||||
$(document)
|
||||
.on('click', sidebarToggleSelector, () => this.toggleSidebar())
|
||||
.on('click', pinnedToggleSelector, () => this.togglePinnedState())
|
||||
.on('click', 'html, body', (e) => this.handleClickEvent(e))
|
||||
.on('page:change', () => this.renderState());
|
||||
this.renderState();
|
||||
}
|
||||
|
||||
handleClickEvent(e) {
|
||||
if (this.isExpanded && (!this.isPinned || window.innerWidth < sidebarBreakpoint)) {
|
||||
const $target = $(e.target);
|
||||
const targetIsToggle = $target.closest(sidebarToggleSelector).length > 0;
|
||||
const targetIsSidebar = $target.closest(sidebarWrapperSelector).length > 0;
|
||||
if (!targetIsToggle && (!targetIsSidebar || $target.closest('a'))) {
|
||||
this.toggleSidebar();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleSidebar() {
|
||||
this.isExpanded = !this.isExpanded;
|
||||
this.renderState();
|
||||
}
|
||||
|
||||
togglePinnedState() {
|
||||
this.isPinned = !this.isPinned;
|
||||
if (!this.isPinned) {
|
||||
this.isExpanded = false;
|
||||
}
|
||||
$.cookie(pinnedStateCookie, this.isPinned ? 'true' : 'false', {
|
||||
path: gon.relative_url_root || '/',
|
||||
expires: 3650
|
||||
});
|
||||
this.renderState();
|
||||
}
|
||||
|
||||
renderState() {
|
||||
$(pageSelector)
|
||||
.toggleClass(pinnedPageClass, this.isPinned && this.isExpanded)
|
||||
.toggleClass(expandedPageClass, this.isExpanded);
|
||||
$(navbarSelector)
|
||||
.toggleClass(pinnedNavbarClass, this.isPinned && this.isExpanded)
|
||||
.toggleClass(expandedNavbarClass, this.isExpanded);
|
||||
|
||||
const $pinnedToggle = $(pinnedToggleSelector);
|
||||
const tooltipText = this.isPinned ? 'Unpin navigation' : 'Pin navigation';
|
||||
const tooltipState = $pinnedToggle.attr('aria-describedby') && this.isExpanded ? 'show' : 'hide';
|
||||
$pinnedToggle.attr('title', tooltipText).tooltip('fixTitle').tooltip(tooltipState);
|
||||
|
||||
if (this.isExpanded) {
|
||||
setTimeout(() => $(sidebarContentSelector).niceScroll().updateScrollBar(), 200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global.Sidebar = Sidebar;
|
||||
|
||||
})(window.gl || (window.gl = {}));
|
|
@ -0,0 +1,11 @@
|
|||
(global => {
|
||||
global.gl = global.gl || {};
|
||||
|
||||
gl.SnippetsList = function() {
|
||||
var $holder = $('.snippets-list-holder');
|
||||
|
||||
$holder.find('.pagination').on('ajax:success', (e, data) => {
|
||||
$holder.replaceWith(data.html);
|
||||
});
|
||||
}
|
||||
})(window);
|
|
@ -1,9 +1,20 @@
|
|||
// Syntax Highlighter
|
||||
//
|
||||
// Applies a syntax highlighting color scheme CSS class to any element with the
|
||||
// `js-syntax-highlight` class
|
||||
//
|
||||
// ### Example Markup
|
||||
//
|
||||
// <div class="js-syntax-highlight"></div>
|
||||
//
|
||||
(function() {
|
||||
$.fn.syntaxHighlight = function() {
|
||||
var $children;
|
||||
if ($(this).hasClass('js-syntax-highlight')) {
|
||||
// Given the element itself, apply highlighting
|
||||
return $(this).addClass(gon.user_color_scheme);
|
||||
} else {
|
||||
// Given a parent element, recurse to any of its applicable children
|
||||
$children = $(this).find('.js-syntax-highlight');
|
||||
if ($children.length) {
|
||||
return $children.syntaxHighlight();
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
this.perPage = this.el.data('perPage');
|
||||
this.clearListeners();
|
||||
this.initBtnListeners();
|
||||
this.initFilters();
|
||||
}
|
||||
|
||||
Todos.prototype.clearListeners = function() {
|
||||
|
@ -27,6 +28,31 @@
|
|||
return $('.todo').on('click', this.goToTodoUrl);
|
||||
};
|
||||
|
||||
Todos.prototype.initFilters = function() {
|
||||
new UsersSelect();
|
||||
this.initFilterDropdown($('.js-project-search'), 'project_id', ['text']);
|
||||
this.initFilterDropdown($('.js-type-search'), 'type');
|
||||
this.initFilterDropdown($('.js-action-search'), 'action_id');
|
||||
|
||||
$('form.filter-form').on('submit', function (event) {
|
||||
event.preventDefault();
|
||||
Turbolinks.visit(this.action + '&' + $(this).serialize());
|
||||
});
|
||||
};
|
||||
|
||||
Todos.prototype.initFilterDropdown = function($dropdown, fieldName, searchFields) {
|
||||
$dropdown.glDropdown({
|
||||
selectable: true,
|
||||
filterable: searchFields ? true : false,
|
||||
fieldName: fieldName,
|
||||
search: { fields: searchFields },
|
||||
data: $dropdown.data('data'),
|
||||
clicked: function() {
|
||||
return $dropdown.closest('form.filter-form').submit();
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
Todos.prototype.doneClicked = function(e) {
|
||||
var $this;
|
||||
e.preventDefault();
|
||||
|
@ -66,7 +92,7 @@
|
|||
success: (function(_this) {
|
||||
return function(data) {
|
||||
$this.remove();
|
||||
$('.js-todos-list').remove();
|
||||
$('.prepend-top-default').html('<div class="nothing-here-block">You\'re all done!</div>');
|
||||
return _this.updateBadges(data);
|
||||
};
|
||||
})(this)
|
||||
|
@ -103,16 +129,21 @@
|
|||
var currPage, currPages, newPages, pageParams, url;
|
||||
currPages = this.getTotalPages();
|
||||
currPage = this.getCurrentPage();
|
||||
// Refresh if no remaining Todos
|
||||
if (!total) {
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
// Do nothing if no pagination
|
||||
if (!currPages) {
|
||||
return;
|
||||
}
|
||||
newPages = Math.ceil(total / this.getTodosPerPage());
|
||||
// Includes query strings
|
||||
url = location.href;
|
||||
// If new total of pages is different than we have now
|
||||
if (newPages !== currPages) {
|
||||
// Redirect to previous page if there's one available
|
||||
if (currPages > 1 && currPage === currPages) {
|
||||
pageParams = {
|
||||
page: currPages - 1
|
||||
|
@ -129,6 +160,7 @@
|
|||
if (!todoLink) {
|
||||
return;
|
||||
}
|
||||
// Allow Meta-Click or Mouse3-click to open in a new tab
|
||||
if (e.metaKey || e.which === 2) {
|
||||
e.preventDefault();
|
||||
return window.open(todoLink, '_blank');
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
this.TreeView = (function() {
|
||||
function TreeView() {
|
||||
this.initKeyNav();
|
||||
// Code browser tree slider
|
||||
// Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
|
||||
$(".tree-content-holder .tree-item").on('click', function(e) {
|
||||
var $clickedEl, path;
|
||||
$clickedEl = $(e.target);
|
||||
|
@ -15,6 +17,7 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
// Show the "Loading commit data" for only the first element
|
||||
$('span.log_loading:first').removeClass('hide');
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
// Authenticate U2F (universal 2nd factor) devices for users to authenticate with.
|
||||
//
|
||||
// State Flow #1: setup -> in_progress -> authenticated -> POST to server
|
||||
// State Flow #2: setup -> in_progress -> error -> setup
|
||||
(function() {
|
||||
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
||||
|
||||
|
@ -15,6 +19,17 @@
|
|||
this.appId = u2fParams.app_id;
|
||||
this.challenge = u2fParams.challenge;
|
||||
this.signRequests = u2fParams.sign_requests.map(function(request) {
|
||||
// The U2F Javascript API v1.1 requires a single challenge, with
|
||||
// _no challenges per-request_. The U2F Javascript API v1.0 requires a
|
||||
// challenge per-request, which is done by copying the single challenge
|
||||
// into every request.
|
||||
//
|
||||
// In either case, we don't need the per-request challenges that the server
|
||||
// has generated, so we can remove them.
|
||||
//
|
||||
// Note: The server library fixes this behaviour in (unreleased) version 1.0.0.
|
||||
// This can be removed once we upgrade.
|
||||
// https://github.com/castle/ruby-u2f/commit/103f428071a81cd3d5f80c2e77d522d5029946a4
|
||||
return _(request).omit('challenge');
|
||||
});
|
||||
}
|
||||
|
@ -41,6 +56,7 @@
|
|||
})(this), 10);
|
||||
};
|
||||
|
||||
// Rendering #
|
||||
U2FAuthenticate.prototype.templates = {
|
||||
"notSupported": "#js-authenticate-u2f-not-supported",
|
||||
"setup": '#js-authenticate-u2f-setup',
|
||||
|
@ -75,6 +91,8 @@
|
|||
|
||||
U2FAuthenticate.prototype.renderAuthenticated = function(deviceResponse) {
|
||||
this.renderTemplate('authenticated');
|
||||
// Prefer to do this instead of interpolating using Underscore templates
|
||||
// because of JSON escaping issues.
|
||||
return this.container.find("#js-device-response").val(deviceResponse);
|
||||
};
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue