Commit 75ded1c7 authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into test-pg-mysql

* upstream/master: (238 commits)
  Periodically clean up temporary upload files to recover storage space
  Add a name field to the group edit form
  Remove the User#is_admin? method
  Fix specs
  Enable RSpec/DescribeSymbol; update .rubocop_todo.yml
  Update rubocop-rspec 1.12.0 -> 1.15.0
  Rename displaying_blame to blame
  Actually include WaitForAjax!
  Don't show Copy contents button on Blame page
  alfredo review changes
  Give explicit height to SVG icons for Safari
  Update MR title change icon
  Put back usernames in activity and profile feed
  Wait for AJAX requests to complete so they don't blow up if they are only handled after DatabaseCleaner has already run
  Revert yarn.lock changes
  add CHANGELOG.md entry for !10522
  upgrade webpack-dev-server to fix issues with SockJS causing odd reload behavior in firefox
  upgrade webpack to v2.3.3 to resolve sourcemap issues
  Rever yarn.lock changes
  Revert yarn.lock file changes
  ...
parents 3aed06cf c98add15
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
"plugins": [ "plugins": [
["istanbul", { ["istanbul", {
"exclude": [ "exclude": [
"app/assets/javascripts/droplab/**/*",
"spec/javascripts/**/*" "spec/javascripts/**/*"
] ]
}], }],
......
...@@ -434,7 +434,7 @@ bundler:audit: ...@@ -434,7 +434,7 @@ bundler:audit:
- master@gitlab/gitlabhq - master@gitlab/gitlabhq
- master@gitlab/gitlab-ee - master@gitlab/gitlab-ee
script: script:
- git fetch origin v8.5.9 - git fetch origin v8.14.10
- git checkout -f FETCH_HEAD - git checkout -f FETCH_HEAD
- cp config/resque.yml.example config/resque.yml - cp config/resque.yml.example config/resque.yml
- sed -i 's/localhost/redis/g' config/resque.yml - sed -i 's/localhost/redis/g' config/resque.yml
......
...@@ -954,6 +954,10 @@ RSpec/DescribeClass: ...@@ -954,6 +954,10 @@ RSpec/DescribeClass:
RSpec/DescribeMethod: RSpec/DescribeMethod:
Enabled: false Enabled: false
# Avoid describing symbols.
RSpec/DescribeSymbol:
Enabled: true
# Checks that the second argument to top level describe is the tested method # Checks that the second argument to top level describe is the tested method
# name. # name.
RSpec/DescribedClass: RSpec/DescribedClass:
......
This diff is collapsed.
...@@ -304,7 +304,7 @@ group :development, :test do ...@@ -304,7 +304,7 @@ group :development, :test do
gem 'spring-commands-spinach', '~> 1.1.0' gem 'spring-commands-spinach', '~> 1.1.0'
gem 'rubocop', '~> 0.47.1', require: false gem 'rubocop', '~> 0.47.1', require: false
gem 'rubocop-rspec', '~> 1.12.0', require: false gem 'rubocop-rspec', '~> 1.15.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false gem 'scss_lint', '~> 0.47.0', require: false
gem 'haml_lint', '~> 0.21.0', require: false gem 'haml_lint', '~> 0.21.0', require: false
gem 'simplecov', '~> 0.14.0', require: false gem 'simplecov', '~> 0.14.0', require: false
...@@ -356,3 +356,5 @@ gem 'sys-filesystem', '~> 1.1.6' ...@@ -356,3 +356,5 @@ gem 'sys-filesystem', '~> 1.1.6'
# Gitaly GRPC client # Gitaly GRPC client
gem 'gitaly', '~> 0.5.0' gem 'gitaly', '~> 0.5.0'
gem 'toml-rb', '~> 0.3.15', require: false
...@@ -117,6 +117,7 @@ GEM ...@@ -117,6 +117,7 @@ GEM
chronic_duration (0.10.6) chronic_duration (0.10.6)
numerizer (~> 0.1.1) numerizer (~> 0.1.1)
chunky_png (1.3.5) chunky_png (1.3.5)
citrus (3.0.2)
cliver (0.3.2) cliver (0.3.2)
coderay (1.1.1) coderay (1.1.1)
coercible (1.0.0) coercible (1.0.0)
...@@ -668,7 +669,7 @@ GEM ...@@ -668,7 +669,7 @@ GEM
rainbow (>= 1.99.1, < 3.0) rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1) unicode-display_width (~> 1.0, >= 1.0.1)
rubocop-rspec (1.12.0) rubocop-rspec (1.15.0)
rubocop (>= 0.42.0) rubocop (>= 0.42.0)
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
...@@ -784,6 +785,8 @@ GEM ...@@ -784,6 +785,8 @@ GEM
tilt (2.0.6) tilt (2.0.6)
timecop (0.8.1) timecop (0.8.1)
timfel-krb5-auth (0.8.3) timfel-krb5-auth (0.8.3)
toml-rb (0.3.15)
citrus (~> 3.0, > 3.0)
tool (0.2.3) tool (0.2.3)
truncato (0.7.8) truncato (0.7.8)
htmlentities (~> 4.3.1) htmlentities (~> 4.3.1)
...@@ -984,7 +987,7 @@ DEPENDENCIES ...@@ -984,7 +987,7 @@ DEPENDENCIES
rspec-retry (~> 0.4.5) rspec-retry (~> 0.4.5)
rspec_profiling (~> 0.0.5) rspec_profiling (~> 0.0.5)
rubocop (~> 0.47.1) rubocop (~> 0.47.1)
rubocop-rspec (~> 1.12.0) rubocop-rspec (~> 1.15.0)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2) ruby-prof (~> 0.16.2)
rufus-scheduler (~> 3.1.10) rufus-scheduler (~> 3.1.10)
...@@ -1015,6 +1018,7 @@ DEPENDENCIES ...@@ -1015,6 +1018,7 @@ DEPENDENCIES
test_after_commit (~> 1.1) test_after_commit (~> 1.1)
thin (~> 1.7.0) thin (~> 1.7.0)
timecop (~> 0.8.0) timecop (~> 0.8.0)
toml-rb (~> 0.3.15)
truncato (~> 0.7.8) truncato (~> 0.7.8)
u2f (~> 0.2.1) u2f (~> 0.2.1)
uglifier (~> 2.7.2) uglifier (~> 2.7.2)
......
...@@ -73,11 +73,20 @@ These types of merge requests need special consideration: ...@@ -73,11 +73,20 @@ These types of merge requests need special consideration:
and a dedicated team with front-end, back-end, and UX. and a dedicated team with front-end, back-end, and UX.
* **Small features**: any other feature request. * **Small features**: any other feature request.
**Large features** must be with a maintainer **by the 1st**. It's OK if they **Large features** must be with a maintainer **by the 1st**. This means that:
aren't completely done, but this allows the maintainer enough time to make the
decision about whether this can make it in before the freeze. If the maintainer * There is a merge request (even if it's WIP).
doesn't think it will make it, they should inform the developers working on it * The person (or people, if it needs a frontend and backend maintainer) who will
and the Product Manager responsible for the feature. ultimately be responsible for merging this have been pinged on the MR.
It's OK if merge request isn't completely done, but this allows the maintainer
enough time to make the decision about whether this can make it in before the
freeze. If the maintainer doesn't think it will make it, they should inform the
developers working on it and the Product Manager responsible for the feature.
The maintainer can also choose to assign a reviewer to perform an initial
review, but this way the maintainer is unlikely to be surprised by receiving an
MR later in the cycle.
**Small features** must be with a reviewer (not necessarily maintainer) **by the **Small features** must be with a reviewer (not necessarily maintainer) **by the
3rd**. 3rd**.
......
...@@ -51,7 +51,7 @@ function renderCategory(name, emojiList, opts = {}) { ...@@ -51,7 +51,7 @@ function renderCategory(name, emojiList, opts = {}) {
<h5 class="emoji-menu-title"> <h5 class="emoji-menu-title">
${name} ${name}
</h5> </h5>
<ul class="clearfix emoji-menu-list ${opts.menuListClass}"> <ul class="clearfix emoji-menu-list ${opts.menuListClass || ''}">
${emojiList.map(emojiName => ` ${emojiList.map(emojiName => `
<li class="emoji-menu-list-item"> <li class="emoji-menu-list-item">
<button class="emoji-menu-btn text-center js-emoji-btn" type="button"> <button class="emoji-menu-btn text-center js-emoji-btn" type="button">
......
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, no-var, consistent-return, max-len */ import autosize from 'vendor/autosize';
/* global autosize */
var autosize = require('vendor/autosize'); $(() => {
const $fields = $('.js-autosize');
(function() { $fields.on('autosize:resized', function resized() {
$(function() { const $field = $(this);
var $fields; $field.data('height', $field.outerHeight());
$fields = $('.js-autosize');
$fields.on('autosize:resized', function() {
var $field;
$field = $(this);
return $field.data('height', $field.outerHeight());
}); });
$fields.on('resize.autosize', function() {
var $field; $fields.on('resize.autosize', function resize() {
$field = $(this); const $field = $(this);
if ($field.data('height') !== $field.outerHeight()) { if ($field.data('height') !== $field.outerHeight()) {
$field.data('height', $field.outerHeight()); $field.data('height', $field.outerHeight());
autosize.destroy($field); autosize.destroy($field);
return $field.css('max-height', window.outerHeight); $field.css('max-height', window.outerHeight);
} }
}); });
autosize($fields); autosize($fields);
autosize.update($fields); autosize.update($fields);
return $fields.css('resize', 'vertical'); $fields.css('resize', 'vertical');
}); });
}).call(window);
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, quotes, no-var, vars-on-top, max-len */
(function() { $(() => {
$(function() { $('body').on('click', '.js-details-target', function target() {
$("body").on("click", ".js-details-target", function() { $(this).closest('.js-details-container').toggleClass('open');
var container;
container = $(this).closest(".js-details-container");
return container.toggleClass("open");
}); });
// Show details content. Hides link after click. // Show details content. Hides link after click.
// //
// %div // %div
// %a.js-details-expand // %a.js-details-expand
// %div.js-details-content // %div.js-details-content
// //
return $("body").on("click", ".js-details-expand", function(e) { $('body').on('click', '.js-details-expand', function expand(e) {
$(this).next('.js-details-content').removeClass("hide"); e.preventDefault();
$(this).next('.js-details-content').removeClass('hide');
$(this).hide(); $(this).hide();
var truncatedItem = $(this).siblings('.js-details-short'); const truncatedItem = $(this).siblings('.js-details-short');
if (truncatedItem.length) { if (truncatedItem.length) {
truncatedItem.addClass("hide"); truncatedItem.addClass('hide');
} }
return e.preventDefault();
});
}); });
}).call(window); });
import './autosize';
import './bind_in_out';
import './details_behavior';
import { installGlEmojiElement } from './gl_emoji';
import './quick_submit';
import './requires_input';
import './toggler_behavior';
installGlEmojiElement();
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, prefer-arrow-callback, camelcase, consistent-return, quotes, object-shorthand, comma-dangle, max-len */ import '../commons/bootstrap';
// Quick Submit behavior // Quick Submit behavior
// //
// When a child field of a form with a `js-quick-submit` class receives a // 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 // "Meta+Enter" (Mac) or "Ctrl+Enter" (Linux/Windows) key combination, the form
// is submitted. // is submitted.
//
import '../commons/bootstrap';
// //
// ### Example Markup // ### Example Markup
// //
...@@ -17,61 +14,59 @@ import '../commons/bootstrap'; ...@@ -17,61 +14,59 @@ import '../commons/bootstrap';
// <input type="submit" value="Submit" /> // <input type="submit" value="Submit" />
// </form> // </form>
// //
(function() {
var isMac, keyCodeIs;
isMac = function() { function isMac() {
return navigator.userAgent.match(/Macintosh/); return navigator.userAgent.match(/Macintosh/);
}; }
keyCodeIs = function(e, keyCode) { function keyCodeIs(e, keyCode) {
if ((e.originalEvent && e.originalEvent.repeat) || e.repeat) { if ((e.originalEvent && e.originalEvent.repeat) || e.repeat) {
return false; return false;
} }
return e.keyCode === keyCode; return e.keyCode === keyCode;
}; }
$(document).on('keydown.quick_submit', '.js-quick-submit', function(e) { $(document).on('keydown.quick_submit', '.js-quick-submit', (e) => {
var $form, $submit_button;
// Enter // Enter
if (!keyCodeIs(e, 13)) { if (!keyCodeIs(e, 13)) {
return; return;
} }
if (!((e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey) || (e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey))) {
const onlyMeta = e.metaKey && !e.altKey && !e.ctrlKey && !e.shiftKey;
const onlyCtrl = e.ctrlKey && !e.altKey && !e.metaKey && !e.shiftKey;
if (!onlyMeta && !onlyCtrl) {
return; return;
} }
e.preventDefault(); e.preventDefault();
$form = $(e.target).closest('form'); const $form = $(e.target).closest('form');
$submit_button = $form.find('input[type=submit], button[type=submit]'); const $submitButton = $form.find('input[type=submit], button[type=submit]');
if ($submit_button.attr('disabled')) {
return; if (!$submitButton.attr('disabled')) {
$submitButton.disable();
$form.submit();
} }
$submit_button.disable(); });
return $form.submit();
});
// If the user tabs to a submit button on a `js-quick-submit` form, display a // 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 // 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) { $(document).on('keyup.quick_submit', '.js-quick-submit input[type=submit], .js-quick-submit button[type=submit]', function displayTooltip(e) {
var $this, title;
// Tab // Tab
if (!keyCodeIs(e, 9)) { if (!keyCodeIs(e, 9)) {
return; return;
} }
if (isMac()) {
title = "You can also press &#8984;-Enter"; const $this = $(this);
} else { const title = isMac() ?
title = "You can also press Ctrl-Enter"; 'You can also press &#8984;-Enter' :
} 'You can also press Ctrl-Enter';
$this = $(this);
return $this.tooltip({ $this.tooltip({
container: 'body', container: 'body',
html: 'true', html: 'true',
placement: 'auto top', placement: 'auto top',
title: title, title,
trigger: 'manual' trigger: 'manual',
}).tooltip('show').one('blur', function() {
return $this.tooltip('hide');
});
}); });
}).call(window); $this.tooltip('show').one('blur', () => $this.tooltip('hide'));
});
/* eslint-disable func-names, space-before-function-paren, one-var, no-var, one-var-declaration-per-line, quotes, prefer-template, prefer-arrow-callback, no-else-return, consistent-return, max-len */ import '../commons/bootstrap';
// Requires Input behavior // Requires Input behavior
// //
// When called on a form with input fields with the `required` attribute, the // When called on a form with input fields with the `required` attribute, the
// form's submit button will be disabled until all required fields have values. // form's submit button will be disabled until all required fields have values.
//
import '../commons/bootstrap';
// //
// ### Example Markup // ### Example Markup
// //
...@@ -14,49 +12,43 @@ import '../commons/bootstrap'; ...@@ -14,49 +12,43 @@ import '../commons/bootstrap';
// <input type="submit" value="Submit"> // <input type="submit" value="Submit">
// </form> // </form>
// //
(function() {
$.fn.requiresInput = function() { $.fn.requiresInput = function requiresInput() {
var $button, $form, fieldSelector, requireInput, required; const $form = $(this);
$form = $(this); const $button = $('button[type=submit], input[type=submit]', $form);
$button = $('button[type=submit], input[type=submit]', $form); const fieldSelector = 'input[required=required], select[required=required], textarea[required=required]';
required = '[required=required]';
fieldSelector = "input" + required + ", select" + required + ", textarea" + required; function requireInput() {
requireInput = function() {
var values;
values = _.map($(fieldSelector, $form), function(field) {
// Collect the input values of *all* required fields // Collect the input values of *all* required fields
return field.value; const values = _.map($(fieldSelector, $form), field => field.value);
});
// Disable the button if any required fields are empty // Disable the button if any required fields are empty
if (values.length && _.any(values, _.isEmpty)) { if (values.length && _.any(values, _.isEmpty)) {
return $button.disable(); $button.disable();
} else { } else {
return $button.enable(); $button.enable();
}
} }
};
// Set initial button state // Set initial button state
requireInput(); requireInput();
return $form.on('change input', fieldSelector, requireInput); $form.on('change input', fieldSelector, requireInput);
}; };
$(function() { // Hide or Show the help block when creating a new project
var $form, hideOrShowHelpBlock; // based on the option selected
$form = $('form.js-requires-input'); function hideOrShowHelpBlock(form) {
$form.requiresInput(); const selected = $('.js-select-namespace option:selected');
// 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');
if (selected.length && selected.data('options-parent') === 'groups') { if (selected.length && selected.data('options-parent') === 'groups') {
return form.find('.help-block').hide(); form.find('.help-block').hide();
} else if (selected.length) { } else if (selected.length) {
return form.find('.help-block').show(); form.find('.help-block').show();
} }
}; }
$(() => {
const $form = $('form.js-requires-input');
$form.requiresInput();
hideOrShowHelpBlock($form); hideOrShowHelpBlock($form);
return $('.select2.js-select-namespace').change(function() { $('.select2.js-select-namespace').change(() => hideOrShowHelpBlock($form));
return hideOrShowHelpBlock($form); });
});
});
}).call(window);
/* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, vars-on-top, no-var, max-len */
(function(w) { // Toggle button. Show/hide content inside parent container.
$(function() { // Button does not change visibility. If button has icon - it changes chevron style.
var toggleContainer = function(container, /* optional */toggleState) { //
var $container = $(container); // %div.js-toggle-container
// %button.js-toggle-button
// %div.js-toggle-content
//
$(() => {
function toggleContainer(container, toggleState) {
const $container = $(container);
$container $container
.find('.js-toggle-button .fa') .find('.js-toggle-button .fa')
...@@ -12,16 +19,9 @@ ...@@ -12,16 +19,9 @@
$container $container
.find('.js-toggle-content') .find('.js-toggle-content')
.toggle(toggleState); .toggle(toggleState);
}; }
// Toggle button. Show/hide content inside parent container. $('body').on('click', '.js-toggle-button', function toggleButton(e) {
// Button does not change visibility. If button has icon - it changes chevron style.
//
// %div.js-toggle-container
// %button.js-toggle-button
// %div.js-toggle-content
//
$('body').on('click', '.js-toggle-button', function(e) {
toggleContainer($(this).closest('.js-toggle-container')); toggleContainer($(this).closest('.js-toggle-container'));
const targetTag = e.currentTarget.tagName.toLowerCase(); const targetTag = e.currentTarget.tagName.toLowerCase();
...@@ -32,13 +32,12 @@ ...@@ -32,13 +32,12 @@
// If we're accessing a permalink, ensure it is not inside a // If we're accessing a permalink, ensure it is not inside a
// closed js-toggle-container! // closed js-toggle-container!
var hash = w.gl.utils.getLocationHash(); const hash = window.gl.utils.getLocationHash();
var anchor = hash && document.getElementById(hash); const anchor = hash && document.getElementById(hash);
var container = anchor && $(anchor).closest('.js-toggle-container'); const container = anchor && $(anchor).closest('.js-toggle-container');
if (container) { if (container) {
toggleContainer(container, true); toggleContainer(container, true);
anchor.scrollIntoView(); anchor.scrollIntoView();
} }
}); });
})(window);
...@@ -38,6 +38,10 @@ $(() => { ...@@ -38,6 +38,10 @@ $(() => {
Store.create(); Store.create();
// hack to allow sidebar scripts like milestone_select manipulate the BoardsStore
gl.issueBoards.boardStoreIssueSet = (...args) => Vue.set(Store.detail.issue, ...args);
gl.issueBoards.boardStoreIssueDelete = (...args) => Vue.delete(Store.detail.issue, ...args);
gl.IssueBoardsApp = new Vue({ gl.IssueBoardsApp = new Vue({
el: $boardApp, el: $boardApp,
components: { components: {
......
This diff is collapsed.
import DropLab from './droplab/drop_lab';
import InputSetter from './droplab/plugins/input_setter';
class CommentTypeToggle {
constructor(opts = {}) {
this.dropdownTrigger = opts.dropdownTrigger;
this.dropdownList = opts.dropdownList;
this.noteTypeInput = opts.noteTypeInput;
this.submitButton = opts.submitButton;
this.closeButton = opts.closeButton;
this.reopenButton = opts.reopenButton;
}
initDroplab() {
this.droplab = new DropLab();
const config = this.setConfig();
this.droplab.init(this.dropdownTrigger, this.dropdownList, [InputSetter], config);
}
setConfig() {
const config = {
InputSetter: [{
input: this.noteTypeInput,
valueAttribute: 'data-value',
},
{
input: this.submitButton,
valueAttribute: 'data-submit-text',
}],
};
if (this.closeButton) {
config.InputSetter.push({
input: this.closeButton,
valueAttribute: 'data-close-text',
}, {
input: this.closeButton,
valueAttribute: 'data-close-text',
inputAttribute: 'data-alternative-text',
});
}
if (this.reopenButton) {
config.InputSetter.push({
input: this.reopenButton,
valueAttribute: 'data-reopen-text',
}, {
input: this.reopenButton,
valueAttribute: 'data-reopen-text',
inputAttribute: 'data-alternative-text',
});
}
return config;
}
}
export default CommentTypeToggle;
...@@ -12,20 +12,18 @@ Vue.use(VueResource); ...@@ -12,20 +12,18 @@ Vue.use(VueResource);
* Renders Pipelines table in pipelines tab in the commits show view. * Renders Pipelines table in pipelines tab in the commits show view.
*/ */
// export for use in merge_request_tabs.js (TODO: remove this hack)
window.gl = window.gl || {};
window.gl.CommitPipelinesTable = CommitPipelinesTable;
$(() => { $(() => {
window.gl = window.gl || {};
gl.commits = gl.commits || {}; gl.commits = gl.commits || {};
gl.commits.pipelines = gl.commits.pipelines || {}; gl.commits.pipelines = gl.commits.pipelines || {};
if (gl.commits.PipelinesTableBundle) {
document.querySelector('#commit-pipeline-table-view').removeChild(this.pipelinesTableBundle.$el);
gl.commits.PipelinesTableBundle.$destroy(true);
}
const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view'); const pipelineTableViewEl = document.querySelector('#commit-pipeline-table-view');
if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) { if (pipelineTableViewEl && pipelineTableViewEl.dataset.disableInitialization === undefined) {
gl.commits.pipelines.PipelinesTableBundle = new CommitPipelinesTable().$mount(); gl.commits.pipelines.PipelinesTableBundle = new CommitPipelinesTable().$mount();
document.querySelector('#commit-pipeline-table-view').appendChild(gl.commits.pipelines.PipelinesTableBundle.$el); pipelineTableViewEl.appendChild(gl.commits.pipelines.PipelinesTableBundle.$el);
} }
}); });
...@@ -42,10 +42,14 @@ import Vue from 'vue'; ...@@ -42,10 +42,14 @@ import Vue from 'vue';
} }
}, },
created() { created() {
if (this.discussionId) {
this.discussion = CommentsStore.state[this.discussionId]; this.discussion = CommentsStore.state[this.discussionId];
}
}, },
mounted: function () { mounted: function () {
const $textarea = $(`#new-discussion-note-form-${this.discussionId} .note-textarea`); if (!this.discussionId) return;
const $textarea = $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`);
this.textareaIsEmpty = $textarea.val() === ''; this.textareaIsEmpty = $textarea.val() === '';
$textarea.on('input.comment-and-resolve-btn', () => { $textarea.on('input.comment-and-resolve-btn', () => {
...@@ -53,7 +57,9 @@ import Vue from 'vue'; ...@@ -53,7 +57,9 @@ import Vue from 'vue';
}); });
}, },
destroyed: function () { destroyed: function () {
$(`#new-discussion-note-form-${this.discussionId} .note-textarea`).off('input.comment-and-resolve-btn'); if (!this.discussionId) return;
$(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off('input.comment-and-resolve-btn');
} }
}); });
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
import Issue from './issue'; import Issue from './issue';
import BindInOut from './behaviors/bind_in_out'; import BindInOut from './behaviors/bind_in_out';
import Group from './group';
import GroupName from './group_name'; import GroupName from './group_name';
import GroupsList from './groups_list'; import GroupsList from './groups_list';
import ProjectsList from './projects_list'; import ProjectsList from './projects_list';
...@@ -44,6 +45,7 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; ...@@ -44,6 +45,7 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater'; import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
import BlobForkSuggestion from './blob/blob_fork_suggestion'; import BlobForkSuggestion from './blob/blob_fork_suggestion';
import UserCallout from './user_callout'; import UserCallout from './user_callout';
import { ProtectedTagCreate, ProtectedTagEditList } from './protected_tags';
const ShortcutsBlob = require('./shortcuts_blob'); const ShortcutsBlob = require('./shortcuts_blob');
...@@ -270,8 +272,9 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -270,8 +272,9 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'groups:create': case 'groups:create':
case 'admin:groups:create': case 'admin:groups:create':
BindInOut.initAll(); BindInOut.initAll();
case 'groups:new': new Group();
case 'admin:groups:new': new GroupAvatar();
break;
case 'groups:edit': case 'groups:edit':
case 'admin:groups:edit': case 'admin:groups:edit':
new GroupAvatar(); new GroupAvatar();
...@@ -329,8 +332,12 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -329,8 +332,12 @@ const ShortcutsBlob = require('./shortcuts_blob');
new Search(); new Search();
break; break;
case 'projects:repository:show': case 'projects:repository:show':
// Initialize Protected Branch Settings
new gl.ProtectedBranchCreate(); new gl.ProtectedBranchCreate();
new gl.ProtectedBranchEditList(); new gl.ProtectedBranchEditList();
// Initialize Protected Tag Settings
new ProtectedTagCreate();
new ProtectedTagEditList();
break; break;
case 'projects:ci_cd:show': case 'projects:ci_cd:show':
new gl.ProjectVariables(); new gl.ProjectVariables();
......
const DATA_TRIGGER = 'data-dropdown-trigger';
const DATA_DROPDOWN = 'data-dropdown';
const SELECTED_CLASS = 'droplab-item-selected';
const ACTIVE_CLASS = 'droplab-item-active';
export {
DATA_TRIGGER,
DATA_DROPDOWN,
SELECTED_CLASS,
ACTIVE_CLASS,
};
/* eslint-disable */
import utils from './utils';
import { SELECTED_CLASS } from './constants';
var DropDown = function(list) {
this.currentIndex = 0;
this.hidden = true;
this.list = typeof list === 'string' ? document.querySelector(list) : list;
this.items = [];
this.eventWrapper = {};
this.getItems();
this.initTemplateString();
this.addEvents();
this.initialState = list.innerHTML;
};
Object.assign(DropDown.prototype, {
getItems: function() {
this.items = [].slice.call(this.list.querySelectorAll('li'));
return this.items;
},
initTemplateString: function() {
var items = this.items || this.getItems();
var templateString = '';
if (items.length > 0) templateString = items[items.length - 1].outerHTML;
this.templateString = templateString;
return this.templateString;
},
clickEvent: function(e) {
if (e.target.tagName === 'UL') return;
var selected = utils.closest(e.target, 'LI');
if (!selected) return;
this.addSelectedClass(selected);
e.preventDefault();
this.hide();
var listEvent = new CustomEvent('click.dl', {
detail: {
list: this,
selected: selected,
data: e.target.dataset,
},
});
this.list.dispatchEvent(listEvent);
},
addSelectedClass: function (selected) {
this.removeSelectedClasses();
selected.classList.add(SELECTED_CLASS);
},
removeSelectedClasses: function () {
const items = this.items || this.getItems();
items.forEach(item => item.classList.remove(SELECTED_CLASS));
},
addEvents: function() {
this.eventWrapper.clickEvent = this.clickEvent.bind(this)
this.list.addEventListener('click', this.eventWrapper.clickEvent);
},
toggle: function() {
this.hidden ? this.show() : this.hide();
},
setData: function(data) {
this.data = data;
this.render(data);
},
addData: function(data) {
this.data = (this.data || []).concat(data);
this.render(this.data);
},
render: function(data) {
const children = data ? data.map(this.renderChildren.bind(this)) : [];
const renderableList = this.list.querySelector('ul[data-dynamic]') || this.list;
renderableList.innerHTML = children.join('');
},
renderChildren: function(data) {
var html = utils.t(this.templateString, data);
var template = document.createElement('div');
template.innerHTML = html;
this.setImagesSrc(template);
template.firstChild.style.display = data.droplab_hidden ? 'none' : 'block';
return template.firstChild.outerHTML;
},
setImagesSrc: function(template) {
const images = [].slice.call(template.querySelectorAll('img[data-src]'));
images.forEach((image) => {
image.src = image.getAttribute('data-src');
image.removeAttribute('data-src');
});
},
show: function() {
if (!this.hidden) return;
this.list.style.display = 'block';
this.currentIndex = 0;
this.hidden = false;
},
hide: function() {
if (this.hidden) return;
this.list.style.display = 'none';
this.currentIndex = 0;
this.hidden = true;
},
toggle: function () {
this.hidden ? this.show() : this.hide();
},
destroy: function() {
this.hide();
this.list.removeEventListener('click', this.eventWrapper.clickEvent);
}
});
export default DropDown;
/* eslint-disable */
import HookButton from './hook_button';
import HookInput from './hook_input';
import utils from './utils';
import Keyboard from './keyboard';
import { DATA_TRIGGER } from './constants';
var DropLab = function() {
this.ready = false;
this.hooks = [];
this.queuedData = [];
this.config = {};
this.eventWrapper = {};
};
Object.assign(DropLab.prototype, {
loadStatic: function(){
var dropdownTriggers = [].slice.apply(document.querySelectorAll(`[${DATA_TRIGGER}]`));
this.addHooks(dropdownTriggers);
},
addData: function () {
var args = [].slice.apply(arguments);
this.applyArgs(args, '_addData');
},
setData: function() {
var args = [].slice.apply(arguments);
this.applyArgs(args, '_setData');
},
destroy: function() {
this.hooks.forEach(hook => hook.destroy());
this.hooks = [];
this.removeEvents();
},
applyArgs: function(args, methodName) {
if (this.ready) return this[methodName].apply(this, args);
this.queuedData = this.queuedData || [];
this.queuedData.push(args);
},
_addData: function(trigger, data) {
this._processData(trigger, data, 'addData');
},
_setData: function(trigger, data) {
this._processData(trigger, data, 'setData');
},
_processData: function(trigger, data, methodName) {
this.hooks.forEach((hook) => {
if (Array.isArray(trigger)) hook.list[methodName](trigger);
if (hook.trigger.id === trigger) hook.list[methodName](data);
});
},
addEvents: function() {
this.eventWrapper.documentClicked = this.documentClicked.bind(this)
document.addEventListener('click', this.eventWrapper.documentClicked);
},
documentClicked: function(e) {
let thisTag = e.target;
if (thisTag.tagName !== 'UL') thisTag = utils.closest(thisTag, 'UL');
if (utils.isDropDownParts(thisTag, this.hooks) || utils.isDropDownParts(e.target, this.hooks)) return;
this.hooks.forEach(hook => hook.list.hide());
},
removeEvents: function(){
document.removeEventListener('click', this.eventWrapper.documentClicked);
},
changeHookList: function(trigger, list, plugins, config) {
const availableTrigger = typeof trigger === 'string' ? document.getElementById(trigger) : trigger;
this.hooks.forEach((hook, i) => {
hook.list.list.dataset.dropdownActive = false;
if (hook.trigger !== availableTrigger) return;
hook.destroy();
this.hooks.splice(i, 1);
this.addHook(availableTrigger, list, plugins, config);
});
},
addHook: function(hook, list, plugins, config) {
const availableHook = typeof hook === 'string' ? document.querySelector(hook) : hook;
let availableList;
if (typeof list === 'string') {
availableList = document.querySelector(list);
} else if (list instanceof Element) {
availableList = list;
} else {
availableList = document.querySelector(hook.dataset[utils.toCamelCase(DATA_TRIGGER)]);
}
availableList.dataset.dropdownActive = true;
const HookObject = availableHook.tagName === 'INPUT' ? HookInput : HookButton;
this.hooks.push(new HookObject(availableHook, availableList, plugins, config));
return this;
},
addHooks: function(hooks, plugins, config) {
hooks.forEach(hook => this.addHook(hook, null, plugins, config));
return this;
},
setConfig: function(obj){
this.config = obj;
},
fireReady: function() {
const readyEvent = new CustomEvent('ready.dl', {
detail: {
dropdown: this,
},
});
document.dispatchEvent(readyEvent);
this.ready = true;
},
init: function (hook, list, plugins, config) {
hook ? this.addHook(hook, list, plugins, config) : this.loadStatic();
this.addEvents();
Keyboard();
this.fireReady();
this.queuedData.forEach(data => this.addData(data));
this.queuedData = [];
return this;
},
});
export default DropLab;
This diff is collapsed.
/* eslint-disable */
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.ajax||(g.ajax = {}));g=(g.datasource||(g.datasource = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global droplab */
require('../window')(function(w){
function droplabAjaxException(message) {
this.message = message;
}
w.droplabAjax = {
_loadUrlData: function _loadUrlData(url) {
var self = this;
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
},
_loadData: function _loadData(data, config, self) {
if (config.loadingTemplate) {
var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) {
dataLoadingTemplate.outerHTML = self.listTemplate;
}
}
if (!self.destroyed) {
self.hook.list[config.method].call(self.hook.list, data);
}
},
init: function init(hook) {
var self = this;
self.destroyed = false;
self.cache = self.cache || {};
var config = hook.config.droplabAjax;
this.hook = hook;
if (!config || !config.endpoint || !config.method) {
return;
}
if (config.method !== 'setData' && config.method !== 'addData') {
return;
}
if (config.loadingTemplate) {
var dynamicList = hook.list.list.querySelector('[data-dynamic]');
var loadingTemplate = document.createElement('div');
loadingTemplate.innerHTML = config.loadingTemplate;
loadingTemplate.setAttribute('data-loading-template', '');
this.listTemplate = dynamicList.outerHTML;
dynamicList.outerHTML = loadingTemplate.outerHTML;
}
if (self.cache[config.endpoint]) {
self._loadData(self.cache[config.endpoint], config, self);
} else {
this._loadUrlData(config.endpoint)
.then(function(d) {
self._loadData(d, config, self);
}, function(xhrError) {
// TODO: properly handle errors due to XHR cancellation
return;
}).catch(function(e) {
throw new droplabAjaxException(e.message || e);
});
}
},
destroy: function() {
var dynamicList = this.hook.list.list.querySelector('[data-dynamic]');
this.destroyed = true;
if (this.listTemplate && dynamicList) {
dynamicList.outerHTML = this.listTemplate;
}
}
};
});
},{"../window":2}],2:[function(require,module,exports){
module.exports = function(callback) {
return (function() {
callback(this);
}).call(null);
};
},{}]},{},[1])(1)
});
/* eslint-disable */
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.ajax||(g.ajax = {}));g=(g.datasource||(g.datasource = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global droplab */
require('../window')(function(w){
w.droplabAjaxFilter = {
init: function(hook) {
this.destroyed = false;
this.hook = hook;
this.notLoading();
this.debounceTriggerWrapper = this.debounceTrigger.bind(this);
this.hook.trigger.addEventListener('keydown.dl', this.debounceTriggerWrapper);
this.hook.trigger.addEventListener('focus', this.debounceTriggerWrapper);
this.trigger(true);
},
notLoading: function notLoading() {
this.loading = false;
},
debounceTrigger: function debounceTrigger(e) {
var NON_CHARACTER_KEYS = [16, 17, 18, 20, 37, 38, 39, 40, 91, 93];
var invalidKeyPressed = NON_CHARACTER_KEYS.indexOf(e.detail.which || e.detail.keyCode) > -1;
var focusEvent = e.type === 'focus';
if (invalidKeyPressed || this.loading) {
return;
}
if (this.timeout) {
clearTimeout(this.timeout);
}
this.timeout = setTimeout(this.trigger.bind(this, focusEvent), 200);
},
trigger: function trigger(getEntireList) {
var config = this.hook.config.droplabAjaxFilter;
var searchValue = this.trigger.value;
if (!config || !config.endpoint || !config.searchKey) {
return;
}
if (config.searchValueFunction) {
searchValue = config.searchValueFunction();
}
if (config.loadingTemplate && this.hook.list.data === undefined ||
this.hook.list.data.length === 0) {
var dynamicList = this.hook.list.list.querySelector('[data-dynamic]');
var loadingTemplate = document.createElement('div');
loadingTemplate.innerHTML = config.loadingTemplate;
loadingTemplate.setAttribute('data-loading-template', true);
this.listTemplate = dynamicList.outerHTML;
dynamicList.outerHTML = loadingTemplate.outerHTML;
}
if (getEntireList) {
searchValue = '';
}
if (config.searchKey === searchValue) {
return this.list.show();
}
this.loading = true;
var params = config.params || {};
params[config.searchKey] = searchValue;
var self = this;
self.cache = self.cache || {};
var url = config.endpoint + this.buildParams(params);
var urlCachedData = self.cache[url];
if (urlCachedData) {
self._loadData(urlCachedData, config, self);
} else {
this._loadUrlData(url)
.then(function(data) {
self._loadData(data, config, self);
}, function(xhrError) {
// TODO: properly handle errors due to XHR cancellation
return;
});
}
},
_loadUrlData: function _loadUrlData(url) {
var self = this;
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
},
_loadData: function _loadData(data, config, self) {
if (config.loadingTemplate && self.hook.list.data === undefined ||
self.hook.list.data.length === 0) {
const dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) {
dataLoadingTemplate.outerHTML = self.listTemplate;
}
}
if (!self.destroyed) {
var hookListChildren = self.hook.list.list.children;
var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
if (onlyDynamicList && data.length === 0) {
self.hook.list.hide();
}
self.hook.list.setData.call(self.hook.list, data);
}
self.notLoading();
self.hook.list.currentIndex = 0;
},
buildParams: function(params) {
if (!params) return '';
var paramsArray = Object.keys(params).map(function(param) {
return param + '=' + (params[param] || '');
});
return '?' + paramsArray.join('&');
},
destroy: function destroy() {
if (this.timeout) {
clearTimeout(this.timeout);
}
this.destroyed = true;
this.hook.trigger.removeEventListener('keydown.dl', this.debounceTriggerWrapper);
this.hook.trigger.removeEventListener('focus', this.debounceTriggerWrapper);
}
};
});
},{"../window":2}],2:[function(require,module,exports){
module.exports = function(callback) {
return (function() {
callback(this);
}).call(null);
};
},{}]},{},[1])(1)
});
/* eslint-disable */
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.droplab||(g.droplab = {}));g=(g.filter||(g.filter = {}));g.js = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* global droplab */
require('../window')(function(w){
w.droplabFilter = {
keydownWrapper: function(e){
var hiddenCount = 0;
var dataHiddenCount = 0;
var list = e.detail.hook.list;
var data = list.data;
var value = e.detail.hook.trigger.value.toLowerCase();
var config = e.detail.hook.config.droplabFilter;
var matches = [];
var filterFunction;
// will only work on dynamically set data
if(!data){
return;
}
if (config && config.filterFunction && typeof config.filterFunction === 'function') {
filterFunction = config.filterFunction;
} else {
filterFunction = function(o){
// cheap string search
o.droplab_hidden = o[config.template].toLowerCase().indexOf(value) === -1;
return o;
};
}
dataHiddenCount = data.filter(function(o) {
return !o.droplab_hidden;
}).length;
matches = data.map(function(o) {
return filterFunction(o, value);
});
hiddenCount = matches.filter(function(o) {
return !o.droplab_hidden;
}).length;
if (dataHiddenCount !== hiddenCount) {
list.render(matches);
list.currentIndex = 0;
}
},
init: function init(hookInput) {
var config = hookInput.config.droplabFilter;
if (!config || (!config.template && !config.filterFunction)) {
return;
}
this.hookInput = hookInput;
this.hookInput.trigger.addEventListener('keyup.dl', this.keydownWrapper);
this.hookInput.trigger.addEventListener('mousedown.dl', this.keydownWrapper);
},
destroy: function destroy(){
this.hookInput.trigger.removeEventListener('keyup.dl', this.keydownWrapper);
this.hookInput.trigger.removeEventListener('mousedown.dl', this.keydownWrapper);
}
};
});
},{"../window":2}],2:[function(require,module,exports){
module.exports = function(callback) {
return (function() {
callback(this);
}).call(null);
};
},{}]},{},[1])(1)
});
/* eslint-disable */
import DropDown from './drop_down';
var Hook = function(trigger, list, plugins, config){
this.trigger = trigger;
this.list = new DropDown(list);
this.type = 'Hook';
this.event = 'click';
this.plugins = plugins || [];
this.config = config || {};
this.id = trigger.id;
};
Object.assign(Hook.prototype, {
addEvents: function(){},
constructor: Hook,
});
export default Hook;
/* eslint-disable */
import Hook from './hook';
var HookButton = function(trigger, list, plugins, config) {
Hook.call(this, trigger, list, plugins, config);
this.type = 'button';
this.event = 'click';
this.eventWrapper = {};
this.addEvents();
this.addPlugins();
};
HookButton.prototype = Object.create(Hook.prototype);
Object.assign(HookButton.prototype, {
addPlugins: function() {
this.plugins.forEach(plugin => plugin.init(this));
},
clicked: function(e){
var buttonEvent = new CustomEvent('click.dl', {
detail: {
hook: this,
},
bubbles: true,
cancelable: true
});
e.target.dispatchEvent(buttonEvent);
this.list.toggle();
},
addEvents: function(){
this.eventWrapper.clicked = this.clicked.bind(this);
this.trigger.addEventListener('click', this.eventWrapper.clicked);
},
removeEvents: function(){
this.trigger.removeEventListener('click', this.eventWrapper.clicked);
},
restoreInitialState: function() {
this.list.list.innerHTML = this.list.initialState;
},
removePlugins: function() {
this.plugins.forEach(plugin => plugin.destroy());
},
destroy: function() {
this.restoreInitialState();
this.removeEvents();
this.removePlugins();
},
constructor: HookButton,
});
export default HookButton;
/* eslint-disable */
import Hook from './hook';
var HookInput = function(trigger, list, plugins, config) {
Hook.call(this, trigger, list, plugins, config);
this.type = 'input';
this.event = 'input';
this.eventWrapper = {};
this.addEvents();
this.addPlugins();
};
Object.assign(HookInput.prototype, {
addPlugins: function() {
this.plugins.forEach(plugin => plugin.init(this));
},
addEvents: function(){
this.eventWrapper.mousedown = this.mousedown.bind(this);
this.eventWrapper.input = this.input.bind(this);
this.eventWrapper.keyup = this.keyup.bind(this);
this.eventWrapper.keydown = this.keydown.bind(this);
this.trigger.addEventListener('mousedown', this.eventWrapper.mousedown);
this.trigger.addEventListener('input', this.eventWrapper.input);
this.trigger.addEventListener('keyup', this.eventWrapper.keyup);
this.trigger.addEventListener('keydown', this.eventWrapper.keydown);
},
removeEvents: function() {
this.hasRemovedEvents = true;
this.trigger.removeEventListener('mousedown', this.eventWrapper.mousedown);
this.trigger.removeEventListener('input', this.eventWrapper.input);
this.trigger.removeEventListener('keyup', this.eventWrapper.keyup);
this.trigger.removeEventListener('keydown', this.eventWrapper.keydown);
},
input: function(e) {
if(this.hasRemovedEvents) return;
this.list.show();
const inputEvent = new CustomEvent('input.dl', {
detail: {
hook: this,
text: e.target.value,
},
bubbles: true,
cancelable: true
});
e.target.dispatchEvent(inputEvent);
},
mousedown: function(e) {
if (this.hasRemovedEvents) return;
const mouseEvent = new CustomEvent('mousedown.dl', {
detail: {
hook: this,
text: e.target.value,
},
bubbles: true,
cancelable: true,
});
e.target.dispatchEvent(mouseEvent);
},
keyup: function(e) {
if (this.hasRemovedEvents) return;
this.keyEvent(e, 'keyup.dl');
},
keydown: function(e) {
if (this.hasRemovedEvents) return;
this.keyEvent(e, 'keydown.dl');
},
keyEvent: function(e, eventName) {
this.list.show();
const keyEvent = new CustomEvent(eventName, {
detail: {
hook: this,
text: e.target.value,
which: e.which,
key: e.key,
},
bubbles: true,
cancelable: true,
});
e.target.dispatchEvent(keyEvent);
},
restoreInitialState: function() {
this.list.list.innerHTML = this.list.initialState;
},
removePlugins: function() {
this.plugins.forEach(plugin => plugin.destroy());
},
destroy: function() {
this.restoreInitialState();
this.removeEvents();
this.removePlugins();
this.list.destroy();
}
});
export default HookInput;
/* eslint-disable */
import { ACTIVE_CLASS } from './constants';
const Keyboard = function () {
var currentKey;
var currentFocus;
var isUpArrow = false;
var isDownArrow = false;
var removeHighlight = function removeHighlight(list) {
var itemElements = Array.prototype.slice.call(list.list.querySelectorAll('li:not(.divider)'), 0);
var listItems = [];
for(var i = 0; i < itemElements.length; i++) {
var listItem = itemElements[i];
listItem.classList.remove(ACTIVE_CLASS);
if (listItem.style.display !== 'none') {
listItems.push(listItem);
}
}
return listItems;
};
var setMenuForArrows = function setMenuForArrows(list) {
var listItems = removeHighlight(list);
if(list.currentIndex>0){
if(!listItems[list.currentIndex-1]){
list.currentIndex = list.currentIndex-1;
}
if (listItems[list.currentIndex-1]) {
var el = listItems[list.currentIndex-1];
var filterDropdownEl = el.closest('.filter-dropdown');
el.classList.add(ACTIVE_CLASS);
if (filterDropdownEl) {
var filterDropdownBottom = filterDropdownEl.offsetHeight;
var elOffsetTop = el.offsetTop - 30;
if (elOffsetTop > filterDropdownBottom) {
filterDropdownEl.scrollTop = elOffsetTop - filterDropdownBottom;
}
}
}
}
};
var mousedown = function mousedown(e) {
var list = e.detail.hook.list;
removeHighlight(list);
list.show();
list.currentIndex = 0;
isUpArrow = false;
isDownArrow = false;
};
var selectItem = function selectItem(list) {
var listItems = removeHighlight(list);
var currentItem = listItems[list.currentIndex-1];
var listEvent = new CustomEvent('click.dl', {
detail: {
list: list,
selected: currentItem,
data: currentItem.dataset,
},
});
list.list.dispatchEvent(listEvent);
list.hide();
}
var keydown = function keydown(e){
var typedOn = e.target;
var list = e.detail.hook.list;
var currentIndex = list.currentIndex;
isUpArrow = false;
isDownArrow = false;
if(e.detail.which){
currentKey = e.detail.which;
if(currentKey === 13){
selectItem(e.detail.hook.list);
return;
}
if(currentKey === 38) {
isUpArrow = true;
}
if(currentKey === 40) {
isDownArrow = true;
}
} else if(e.detail.key) {
currentKey = e.detail.key;
if(currentKey === 'Enter'){
selectItem(e.detail.hook.list);
return;
}
if(currentKey === 'ArrowUp') {
isUpArrow = true;
}
if(currentKey === 'ArrowDown') {
isDownArrow = true;
}
}
if(isUpArrow){ currentIndex--; }
if(isDownArrow){ currentIndex++; }
if(currentIndex < 0){ currentIndex = 0; }
list.currentIndex = currentIndex;
setMenuForArrows(e.detail.hook.list);
};
document.addEventListener('mousedown.dl', mousedown);
document.addEventListener('keydown.dl', keydown);
};
export default Keyboard;
/* eslint-disable */
const Ajax = {
_loadUrlData: function _loadUrlData(url) {
var self = this;
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
},
_loadData: function _loadData(data, config, self) {
if (config.loadingTemplate) {
var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) dataLoadingTemplate.outerHTML = self.listTemplate;
}
if (!self.destroyed) self.hook.list[config.method].call(self.hook.list, data);
},
init: function init(hook) {
var self = this;
self.destroyed = false;
self.cache = self.cache || {};
var config = hook.config.Ajax;
this.hook = hook;
if (!config || !config.endpoint || !config.method) {
return;
}
if (config.method !== 'setData' && config.method !== 'addData') {
return;
}
if (config.loadingTemplate) {
var dynamicList = hook.list.list.querySelector('[data-dynamic]');
var loadingTemplate = document.createElement('div');
loadingTemplate.innerHTML = config.loadingTemplate;
loadingTemplate.setAttribute('data-loading-template', '');
this.listTemplate = dynamicList.outerHTML;
dynamicList.outerHTML = loadingTemplate.outerHTML;
}
if (self.cache[config.endpoint]) {
self._loadData(self.cache[config.endpoint], config, self);
} else {
this._loadUrlData(config.endpoint)
.then(function(d) {
self._loadData(d, config, self);
}, config.onError).catch(config.onError);
}
},
destroy: function() {
this.destroyed = true;
}
};
export default Ajax;
/* eslint-disable */
const AjaxFilter = {
init: function(hook) {
this.destroyed = false;
this.hook = hook;
this.notLoading();
this.eventWrapper = {};
this.eventWrapper.debounceTrigger = this.debounceTrigger.bind(this);
this.hook.trigger.addEventListener('keydown.dl', this.eventWrapper.debounceTrigger);
this.hook.trigger.addEventListener('focus', this.eventWrapper.debounceTrigger);
this.trigger(true);
},
notLoading: function notLoading() {
this.loading = false;
},
debounceTrigger: function debounceTrigger(e) {
var NON_CHARACTER_KEYS = [16, 17, 18, 20, 37, 38, 39, 40, 91, 93];
var invalidKeyPressed = NON_CHARACTER_KEYS.indexOf(e.detail.which || e.detail.keyCode) > -1;
var focusEvent = e.type === 'focus';
if (invalidKeyPressed || this.loading) {
return;
}
if (this.timeout) {
clearTimeout(this.timeout);
}
this.timeout = setTimeout(this.trigger.bind(this, focusEvent), 200);
},
trigger: function trigger(getEntireList) {
var config = this.hook.config.AjaxFilter;
var searchValue = this.trigger.value;
if (!config || !config.endpoint || !config.searchKey) {
return;
}
if (config.searchValueFunction) {
searchValue = config.searchValueFunction();
}
if (config.loadingTemplate && this.hook.list.data === undefined ||
this.hook.list.data.length === 0) {
var dynamicList = this.hook.list.list.querySelector('[data-dynamic]');
var loadingTemplate = document.createElement('div');
loadingTemplate.innerHTML = config.loadingTemplate;
loadingTemplate.setAttribute('data-loading-template', true);
this.listTemplate = dynamicList.outerHTML;
dynamicList.outerHTML = loadingTemplate.outerHTML;
}
if (getEntireList) {
searchValue = '';
}
if (config.searchKey === searchValue) {
return this.list.show();
}
this.loading = true;
var params = config.params || {};
params[config.searchKey] = searchValue;
var self = this;
self.cache = self.cache || {};
var url = config.endpoint + this.buildParams(params);
var urlCachedData = self.cache[url];
if (urlCachedData) {
self._loadData(urlCachedData, config, self);
} else {
this._loadUrlData(url)
.then(function(data) {
self._loadData(data, config, self);
}, config.onError).catch(config.onError);
}
},
_loadUrlData: function _loadUrlData(url) {
var self = this;
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
},
_loadData: function _loadData(data, config, self) {
const list = self.hook.list;
if (config.loadingTemplate && list.data === undefined ||
list.data.length === 0) {
const dataLoadingTemplate = list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) {
dataLoadingTemplate.outerHTML = self.listTemplate;
}
}
if (!self.destroyed) {
var hookListChildren = list.list.children;
var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
if (onlyDynamicList && data.length === 0) {
list.hide();
}
list.setData.call(list, data);
}
self.notLoading();
list.currentIndex = 0;
},
buildParams: function(params) {
if (!params) return '';
var paramsArray = Object.keys(params).map(function(param) {
return param + '=' + (params[param] || '');
});
return '?' + paramsArray.join('&');
},
destroy: function destroy() {
if (this.timeout)clearTimeout(this.timeout);
this.destroyed = true;
this.hook.trigger.removeEventListener('keydown.dl', this.eventWrapper.debounceTrigger);
this.hook.trigger.removeEventListener('focus', this.eventWrapper.debounceTrigger);
}
};
export default AjaxFilter;
/* eslint-disable */
const Filter = {
keydown: function(e){
if (this.destroyed) return;
var hiddenCount = 0;
var dataHiddenCount = 0;
var list = e.detail.hook.list;
var data = list.data;
var value = e.detail.hook.trigger.value.toLowerCase();
var config = e.detail.hook.config.Filter;
var matches = [];
var filterFunction;
// will only work on dynamically set data
if(!data){
return;
}
if (config && config.filterFunction && typeof config.filterFunction === 'function') {
filterFunction = config.filterFunction;
} else {
filterFunction = function(o){
// cheap string search
o.droplab_hidden = o[config.template].toLowerCase().indexOf(value) === -1;
return o;
};
}
dataHiddenCount = data.filter(function(o) {
return !o.droplab_hidden;
}).length;
matches = data.map(function(o) {
return filterFunction(o, value);
});
hiddenCount = matches.filter(function(o) {
return !o.droplab_hidden;
}).length;
if (dataHiddenCount !== hiddenCount) {
list.setData(matches);
list.currentIndex = 0;
}
},
debounceKeydown: function debounceKeydown(e) {
if ([
13, // enter
16, // shift
17, // ctrl
18, // alt
20, // caps lock
37, // left arrow
38, // up arrow
39, // right arrow
40, // down arrow
91, // left window
92, // right window
93, // select
].indexOf(e.detail.which || e.detail.keyCode) > -1) return;
if (this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(this.keydown.bind(this, e), 200);
},
init: function init(hook) {
var config = hook.config.Filter;
if (!config || !config.template) return;
this.hook = hook;
this.destroyed = false;
this.eventWrapper = {};
this.eventWrapper.debounceKeydown = this.debounceKeydown.bind(this);
this.hook.trigger.addEventListener('keydown.dl', this.eventWrapper.debounceKeydown);
this.hook.trigger.addEventListener('mousedown.dl', this.eventWrapper.debounceKeydown);
this.debounceKeydown({ detail: { hook: this.hook } });
},
destroy: function destroy() {
if (this.timeout) clearTimeout(this.timeout);
this.destroyed = true;
this.hook.trigger.removeEventListener('keydown.dl', this.eventWrapper.debounceKeydown);
this.hook.trigger.removeEventListener('mousedown.dl', this.eventWrapper.debounceKeydown);
}
};
export default Filter;
/* eslint-disable */
const InputSetter = {
init(hook) {
this.hook = hook;
this.destroyed = false;
this.config = hook.config.InputSetter || (this.hook.config.InputSetter = {});
this.eventWrapper = {};
this.addEvents();
},
addEvents() {
this.eventWrapper.setInputs = this.setInputs.bind(this);
this.hook.list.list.addEventListener('click.dl', this.eventWrapper.setInputs);
},
removeEvents() {
this.hook.list.list.removeEventListener('click.dl', this.eventWrapper.setInputs);
},
setInputs(e) {
if (this.destroyed) return;
const selectedItem = e.detail.selected;
if (!Array.isArray(this.config)) this.config = [this.config];
this.config.forEach(config => this.setInput(config, selectedItem));
},
setInput(config, selectedItem) {
const input = config.input || this.hook.trigger;
const newValue = selectedItem.getAttribute(config.valueAttribute);
const inputAttribute = config.inputAttribute;
if (input.hasAttribute(inputAttribute)) return input.setAttribute(inputAttribute, newValue);
if (input.tagName === 'INPUT') return input.value = newValue;
return input.textContent = newValue;
},
destroy() {
this.destroyed = true;
this.removeEvents();
},
};
export default InputSetter;
/* eslint-disable */
import { DATA_TRIGGER, DATA_DROPDOWN } from './constants';
const utils = {
toCamelCase(attr) {
return this.camelize(attr.split('-').slice(1).join(' '));
},
t(s, d) {
for (const p in d) {
if (Object.prototype.hasOwnProperty.call(d, p)) {
s = s.replace(new RegExp(`{{${p}}}`, 'g'), d[p]);
}
}
return s;
},
camelize(str) {
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => {
return index === 0 ? letter.toLowerCase() : letter.toUpperCase();
}).replace(/\s+/g, '');
},
closest(thisTag, stopTag) {
while (thisTag && thisTag.tagName !== stopTag && thisTag.tagName !== 'HTML') {
thisTag = thisTag.parentNode;
}
return thisTag;
},
isDropDownParts(target) {
if (!target || target.tagName === 'HTML') return false;
return target.hasAttribute(DATA_TRIGGER) || target.hasAttribute(DATA_DROPDOWN);
},
};
export default utils;
...@@ -55,14 +55,19 @@ window.FilesCommentButton = (function() { ...@@ -55,14 +55,19 @@ window.FilesCommentButton = (function() {
textFileElement = this.getTextFileElement($currentTarget); textFileElement = this.getTextFileElement($currentTarget);
buttonParentElement.append(this.buildButton({ buttonParentElement.append(this.buildButton({
discussionID: lineContentElement.attr('data-discussion-id'),
lineType: lineContentElement.attr('data-line-type'),
noteableType: textFileElement.attr('data-noteable-type'), noteableType: textFileElement.attr('data-noteable-type'),
noteableID: textFileElement.attr('data-noteable-id'), noteableID: textFileElement.attr('data-noteable-id'),
commitID: textFileElement.attr('data-commit-id'), commitID: textFileElement.attr('data-commit-id'),
noteType: lineContentElement.attr('data-note-type'), noteType: lineContentElement.attr('data-note-type'),
position: lineContentElement.attr('data-position'),
lineType: lineContentElement.attr('data-line-type'), // LegacyDiffNote
discussionID: lineContentElement.attr('data-discussion-id'), lineCode: lineContentElement.attr('data-line-code'),
lineCode: lineContentElement.attr('data-line-code')
// DiffNote
position: lineContentElement.attr('data-position')
})); }));
}; };
...@@ -76,14 +81,19 @@ window.FilesCommentButton = (function() { ...@@ -76,14 +81,19 @@ window.FilesCommentButton = (function() {
FilesCommentButton.prototype.buildButton = function(buttonAttributes) { FilesCommentButton.prototype.buildButton = function(buttonAttributes) {
return $commentButtonTemplate.clone().attr({ return $commentButtonTemplate.clone().attr({
'data-discussion-id': buttonAttributes.discussionID,
'data-line-type': buttonAttributes.lineType,
'data-noteable-type': buttonAttributes.noteableType, 'data-noteable-type': buttonAttributes.noteableType,
'data-noteable-id': buttonAttributes.noteableID, 'data-noteable-id': buttonAttributes.noteableID,
'data-commit-id': buttonAttributes.commitID, 'data-commit-id': buttonAttributes.commitID,
'data-note-type': buttonAttributes.noteType, 'data-note-type': buttonAttributes.noteType,
// LegacyDiffNote
'data-line-code': buttonAttributes.lineCode, 'data-line-code': buttonAttributes.lineCode,
'data-position': buttonAttributes.position,
'data-discussion-id': buttonAttributes.discussionID, // DiffNote
'data-line-type': buttonAttributes.lineType 'data-position': buttonAttributes.position
}); });
}; };
...@@ -121,7 +131,7 @@ window.FilesCommentButton = (function() { ...@@ -121,7 +131,7 @@ window.FilesCommentButton = (function() {
}; };
FilesCommentButton.prototype.validateLineContent = function(lineContentElement) { FilesCommentButton.prototype.validateLineContent = function(lineContentElement) {
return lineContentElement.attr('data-discussion-id') && lineContentElement.attr('data-discussion-id') !== ''; return lineContentElement.attr('data-note-type') && lineContentElement.attr('data-note-type') !== '';
}; };
return FilesCommentButton; return FilesCommentButton;
......
require('./filtered_search_dropdown'); import Filter from '~/droplab/plugins/filter';
/* global droplabFilter */ require('./filtered_search_dropdown');
(() => { (() => {
class DropdownHint extends gl.FilteredSearchDropdown { class DropdownHint extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) { constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter); super(droplab, dropdown, input, filter);
this.config = { this.config = {
droplabFilter: { Filter: {
template: 'hint', template: 'hint',
filterFunction: gl.DropdownUtils.filterHint.bind(null, input), filterFunction: gl.DropdownUtils.filterHint.bind(null, input),
}, },
...@@ -69,12 +69,12 @@ require('./filtered_search_dropdown'); ...@@ -69,12 +69,12 @@ require('./filtered_search_dropdown');
} }
}); });
this.droplab.changeHookList(this.hookId, this.dropdown, [droplabFilter], this.config); this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config);
this.droplab.setData(this.hookId, dropdownData); this.droplab.setData(this.hookId, dropdownData);
} }
init() { init() {
this.droplab.addHook(this.input, this.dropdown, [droplabFilter], this.config).init(); this.droplab.addHook(this.input, this.dropdown, [Filter], this.config).init();
} }
} }
......
require('./filtered_search_dropdown'); /* global Flash */
import Ajax from '~/droplab/plugins/ajax';
import Filter from '~/droplab/plugins/filter';
/* global droplabAjax */ require('./filtered_search_dropdown');
/* global droplabFilter */
(() => { (() => {
class DropdownNonUser extends gl.FilteredSearchDropdown { class DropdownNonUser extends gl.FilteredSearchDropdown {
...@@ -9,13 +11,19 @@ require('./filtered_search_dropdown'); ...@@ -9,13 +11,19 @@ require('./filtered_search_dropdown');
super(droplab, dropdown, input, filter); super(droplab, dropdown, input, filter);
this.symbol = symbol; this.symbol = symbol;
this.config = { this.config = {
droplabAjax: { Ajax: {
endpoint, endpoint,
method: 'setData', method: 'setData',
loadingTemplate: this.loadingTemplate, loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
/* eslint-enable no-new */
},
}, },
droplabFilter: { Filter: {
filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input), filterFunction: gl.DropdownUtils.filterWithSymbol.bind(null, this.symbol, input),
template: 'title',
}, },
}; };
} }
...@@ -29,13 +37,13 @@ require('./filtered_search_dropdown'); ...@@ -29,13 +37,13 @@ require('./filtered_search_dropdown');
renderContent(forceShowList = false) { renderContent(forceShowList = false) {
this.droplab this.droplab
.changeHookList(this.hookId, this.dropdown, [droplabAjax, droplabFilter], this.config); .changeHookList(this.hookId, this.dropdown, [Ajax, Filter], this.config);
super.renderContent(forceShowList); super.renderContent(forceShowList);
} }
init() { init() {
this.droplab this.droplab
.addHook(this.input, this.dropdown, [droplabAjax, droplabFilter], this.config).init(); .addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
} }
} }
......
require('./filtered_search_dropdown'); /* global Flash */
import AjaxFilter from '~/droplab/plugins/ajax_filter';
/* global droplabAjaxFilter */ require('./filtered_search_dropdown');
(() => { (() => {
class DropdownUser extends gl.FilteredSearchDropdown { class DropdownUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) { constructor(droplab, dropdown, input, filter) {
super(droplab, dropdown, input, filter); super(droplab, dropdown, input, filter);
this.config = { this.config = {
droplabAjaxFilter: { AjaxFilter: {
endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`, endpoint: `${gon.relative_url_root || ''}/autocomplete/users.json`,
searchKey: 'search', searchKey: 'search',
params: { params: {
...@@ -18,6 +20,11 @@ require('./filtered_search_dropdown'); ...@@ -18,6 +20,11 @@ require('./filtered_search_dropdown');
}, },
searchValueFunction: this.getSearchInput.bind(this), searchValueFunction: this.getSearchInput.bind(this),
loadingTemplate: this.loadingTemplate, loadingTemplate: this.loadingTemplate,
onError() {
/* eslint-disable no-new */
new Flash('An error occured fetching the dropdown data.');
/* eslint-enable no-new */
},
}, },
}; };
} }
...@@ -28,7 +35,7 @@ require('./filtered_search_dropdown'); ...@@ -28,7 +35,7 @@ require('./filtered_search_dropdown');
} }
renderContent(forceShowList = false) { renderContent(forceShowList = false) {
this.droplab.changeHookList(this.hookId, this.dropdown, [droplabAjaxFilter], this.config); this.droplab.changeHookList(this.hookId, this.dropdown, [AjaxFilter], this.config);
super.renderContent(forceShowList); super.renderContent(forceShowList);
} }
...@@ -56,7 +63,7 @@ require('./filtered_search_dropdown'); ...@@ -56,7 +63,7 @@ require('./filtered_search_dropdown');
} }
init() { init() {
this.droplab.addHook(this.input, this.dropdown, [droplabAjaxFilter], this.config).init(); this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init();
} }
} }
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
class FilteredSearchDropdown { class FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) { constructor(droplab, dropdown, input, filter) {
this.droplab = droplab; this.droplab = droplab;
this.hookId = input && input.getAttribute('data-id'); this.hookId = input && input.id;
this.input = input; this.input = input;
this.filter = filter; this.filter = filter;
this.dropdown = dropdown; this.dropdown = dropdown;
......
/* global DropLab */ import DropLab from '~/droplab/drop_lab';
import FilteredSearchContainer from './container'; import FilteredSearchContainer from './container';
(() => { (() => {
......
...@@ -154,7 +154,7 @@ import eventHub from './event_hub'; ...@@ -154,7 +154,7 @@ import eventHub from './event_hub';
if (e.keyCode === 13) { if (e.keyCode === 13) {
const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown]; const dropdown = this.dropdownManager.mapping[this.dropdownManager.currentDropdown];
const dropdownEl = dropdown.element; const dropdownEl = dropdown.element;
const activeElements = dropdownEl.querySelectorAll('.dropdown-active'); const activeElements = dropdownEl.querySelectorAll('.droplab-item-active');
e.preventDefault(); e.preventDefault();
......
...@@ -29,7 +29,8 @@ GLForm.prototype.setupForm = function() { ...@@ -29,7 +29,8 @@ GLForm.prototype.setupForm = function() {
this.form.find('.div-dropzone').remove(); this.form.find('.div-dropzone').remove();
this.form.addClass('gfm-form'); this.form.addClass('gfm-form');
// remove notify commit author checkbox for non-commit notes // remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button')); gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion'));
gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input')); gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input'));
new DropzoneInput(this.form); new DropzoneInput(this.form);
autosize(this.textarea); autosize(this.textarea);
......
export default class Group {
constructor() {
this.groupPath = $('#group_path');
this.groupName = $('#group_name');
this.updateHandler = this.update.bind(this);
this.resetHandler = this.reset.bind(this);
if (this.groupName.val() === '') {
this.groupPath.on('keyup', this.updateHandler);
this.groupName.on('keydown', this.resetHandler);
}
}
update() {
this.groupName.val(this.groupPath.val());
}
reset() {
this.groupPath.off('keyup', this.updateHandler);
this.groupName.off('keydown', this.resetHandler);
}
}
...@@ -37,14 +37,7 @@ import './shortcuts_issuable'; ...@@ -37,14 +37,7 @@ import './shortcuts_issuable';
import './shortcuts_network'; import './shortcuts_network';
// behaviors // behaviors
import './behaviors/autosize'; import './behaviors/';
import './behaviors/details_behavior';
import './behaviors/quick_submit';
import './behaviors/requires_input';
import './behaviors/toggler_behavior';
import './behaviors/bind_in_out';
import { installGlEmojiElement } from './behaviors/gl_emoji';
installGlEmojiElement();
// blob // blob
import './blob/create_branch_dropdown'; import './blob/create_branch_dropdown';
...@@ -75,12 +68,6 @@ import './u2f/error'; ...@@ -75,12 +68,6 @@ import './u2f/error';
import './u2f/register'; import './u2f/register';
import './u2f/util'; import './u2f/util';
// droplab
import './droplab/droplab';
import './droplab/droplab_ajax';
import './droplab/droplab_ajax_filter';
import './droplab/droplab_filter';
// everything else // everything else
import './abuse_reports'; import './abuse_reports';
import './activities'; import './activities';
...@@ -285,7 +272,7 @@ $(function () { ...@@ -285,7 +272,7 @@ $(function () {
// Disable form buttons while a form is submitting // Disable form buttons while a form is submitting
$body.on('ajax:complete, ajax:beforeSend, submit', 'form', function (e) { $body.on('ajax:complete, ajax:beforeSend, submit', 'form', function (e) {
var buttons; var buttons;
buttons = $('[type="submit"]', this); buttons = $('[type="submit"], .js-disable-on-submit', this);
switch (e.type) { switch (e.type) {
case 'ajax:beforeSend': case 'ajax:beforeSend':
case 'submit': case 'submit':
......
...@@ -3,9 +3,6 @@ ...@@ -3,9 +3,6 @@
/* global Flash */ /* global Flash */
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import CommitPipelinesTable from './commit/pipelines/pipelines_table';
import './breakpoints'; import './breakpoints';
import './flash'; import './flash';
...@@ -102,10 +99,10 @@ import './flash'; ...@@ -102,10 +99,10 @@ import './flash';
destroyPipelinesView() { destroyPipelinesView() {
if (this.commitPipelinesTable) { if (this.commitPipelinesTable) {
document.querySelector('#commit-pipeline-table-view')
.removeChild(this.commitPipelinesTable.$el);
this.commitPipelinesTable.$destroy(); this.commitPipelinesTable.$destroy();
this.commitPipelinesTable = null;
document.querySelector('#commit-pipeline-table-view').innerHTML = '';
} }
} }
...@@ -234,7 +231,7 @@ import './flash'; ...@@ -234,7 +231,7 @@ import './flash';
} }
mountPipelinesView() { mountPipelinesView() {
this.commitPipelinesTable = new CommitPipelinesTable().$mount(); this.commitPipelinesTable = new gl.CommitPipelinesTable().$mount();
// $mount(el) replaces the el with the new rendered component. We need it in order to mount // $mount(el) replaces the el with the new rendered component. We need it in order to mount
// it everytime this tab is clicked - https://vuejs.org/v2/api/#vm-mount // it everytime this tab is clicked - https://vuejs.org/v2/api/#vm-mount
document.querySelector('#commit-pipeline-table-view') document.querySelector('#commit-pipeline-table-view')
......
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
/* global Issuable */ /* global Issuable */
/* global ListMilestone */ /* global ListMilestone */
import Vue from 'vue';
(function() { (function() {
this.MilestoneSelect = (function() { this.MilestoneSelect = (function() {
function MilestoneSelect(currentProject, els) { function MilestoneSelect(currentProject, els) {
...@@ -151,12 +149,12 @@ import Vue from 'vue'; ...@@ -151,12 +149,12 @@ import Vue from 'vue';
return $dropdown.closest('form').submit(); return $dropdown.closest('form').submit();
} else if ($dropdown.hasClass('js-issue-board-sidebar')) { } else if ($dropdown.hasClass('js-issue-board-sidebar')) {
if (selected.id !== -1) { if (selected.id !== -1) {
Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'milestone', new ListMilestone({ gl.issueBoards.boardStoreIssueSet('milestone', new ListMilestone({
id: selected.id, id: selected.id,
title: selected.name title: selected.name
})); }));
} else { } else {
Vue.delete(gl.issueBoards.BoardsStore.detail.issue, 'milestone'); gl.issueBoards.boardStoreIssueDelete('milestone');
} }
$dropdown.trigger('loading.gl.dropdown'); $dropdown.trigger('loading.gl.dropdown');
......
This diff is collapsed.
export { default as ProtectedTagCreate } from './protected_tag_create';
export { default as ProtectedTagEditList } from './protected_tag_edit_list';
export default class ProtectedTagAccessDropdown {
constructor(options) {
this.options = options;
this.initDropdown();
}
initDropdown() {
const { onSelect } = this.options;
this.options.$dropdown.glDropdown({
data: this.options.data,
selectable: true,
inputId: this.options.$dropdown.data('input-id'),
fieldName: this.options.$dropdown.data('field-name'),
toggleLabel(item, $el) {
if ($el.is('.is-active')) {
return item.text;
}
return 'Select';
},
clicked(item, $el, e) {
e.preventDefault();
onSelect();
},
});
}
}
import ProtectedTagAccessDropdown from './protected_tag_access_dropdown';
import ProtectedTagDropdown from './protected_tag_dropdown';
export default class ProtectedTagCreate {
constructor() {
this.$form = $('.js-new-protected-tag');
this.buildDropdowns();
}
buildDropdowns() {
const $allowedToCreateDropdown = this.$form.find('.js-allowed-to-create');
// Cache callback
this.onSelectCallback = this.onSelect.bind(this);
// Allowed to Create dropdown
this.protectedTagAccessDropdown = new ProtectedTagAccessDropdown({
$dropdown: $allowedToCreateDropdown,
data: gon.create_access_levels,
onSelect: this.onSelectCallback,
});
// Select default
$allowedToCreateDropdown.data('glDropdown').selectRowAtIndex(0);
// Protected tag dropdown
this.protectedTagDropdown = new ProtectedTagDropdown({
$dropdown: this.$form.find('.js-protected-tag-select'),
onSelect: this.onSelectCallback,
});
}
// This will run after clicked callback
onSelect() {
// Enable submit button
const $tagInput = this.$form.find('input[name="protected_tag[name]"]');
const $allowedToCreateInput = this.$form.find('#create_access_levels_attributes');
this.$form.find('input[type="submit"]').attr('disabled', !($tagInput.val() && $allowedToCreateInput.length));
}
}
export default class ProtectedTagDropdown {
/**
* @param {Object} options containing
* `$dropdown` target element
* `onSelect` event callback
* $dropdown must be an element created using `dropdown_tag()` rails helper
*/
constructor(options) {
this.onSelect = options.onSelect;
this.$dropdown = options.$dropdown;
this.$dropdownContainer = this.$dropdown.parent();
this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer');
this.$protectedTag = this.$dropdownContainer.find('.create-new-protected-tag');
this.buildDropdown();
this.bindEvents();
// Hide footer
this.toggleFooter(true);
}
buildDropdown() {
this.$dropdown.glDropdown({
data: this.getProtectedTags.bind(this),
filterable: true,
remote: false,
search: {
fields: ['title'],
},
selectable: true,
toggleLabel(selected) {
return (selected && 'id' in selected) ? selected.title : 'Protected Tag';
},
fieldName: 'protected_tag[name]',
text(protectedTag) {
return _.escape(protectedTag.title);
},
id(protectedTag) {
return _.escape(protectedTag.id);
},
onFilter: this.toggleCreateNewButton.bind(this),
clicked: (item, $el, e) => {
e.preventDefault();
this.onSelect();
},
});
}
bindEvents() {
this.$protectedTag.on('click', this.onClickCreateWildcard.bind(this));
}
onClickCreateWildcard(e) {
this.$dropdown.data('glDropdown').remote.execute();
this.$dropdown.data('glDropdown').selectRowAtIndex();
e.preventDefault();
}
getProtectedTags(term, callback) {
if (this.selectedTag) {
callback(gon.open_tags.concat(this.selectedTag));
} else {
callback(gon.open_tags);
}
}
toggleCreateNewButton(tagName) {
if (tagName) {
this.selectedTag = {
title: tagName,
id: tagName,
text: tagName,
};
this.$dropdownContainer
.find('.create-new-protected-tag code')
.text(tagName);
}
this.toggleFooter(!tagName);
}
toggleFooter(toggleState) {
this.$dropdownFooter.toggleClass('hidden', toggleState);
}
}
/* eslint-disable no-new */
/* global Flash */
import ProtectedTagAccessDropdown from './protected_tag_access_dropdown';
export default class ProtectedTagEdit {
constructor(options) {
this.$wrap = options.$wrap;
this.$allowedToCreateDropdownButton = this.$wrap.find('.js-allowed-to-create');
this.onSelectCallback = this.onSelect.bind(this);
this.buildDropdowns();
}
buildDropdowns() {
// Allowed to create dropdown
this.protectedTagAccessDropdown = new ProtectedTagAccessDropdown({
$dropdown: this.$allowedToCreateDropdownButton,
data: gon.create_access_levels,
onSelect: this.onSelectCallback,
});
}
onSelect() {
const $allowedToCreateInput = this.$wrap.find(`input[name="${this.$allowedToCreateDropdownButton.data('fieldName')}"]`);
// Do not update if one dropdown has not selected any option
if (!$allowedToCreateInput.length) return;
this.$allowedToCreateDropdownButton.disable();
$.ajax({
type: 'POST',
url: this.$wrap.data('url'),
dataType: 'json',
data: {
_method: 'PATCH',
protected_tag: {
create_access_levels_attributes: [{
id: this.$allowedToCreateDropdownButton.data('access-level-id'),
access_level: $allowedToCreateInput.val(),
}],
},
},
error() {
new Flash('Failed to update tag!', null, $('.js-protected-tags-list'));
},
}).always(() => {
this.$allowedToCreateDropdownButton.enable();
});
}
}
/* eslint-disable no-new */
import ProtectedTagEdit from './protected_tag_edit';
export default class ProtectedTagEditList {
constructor() {
this.$wrap = $('.protected-tags-list');
this.initEditForm();
}
initEditForm() {
this.$wrap.find('.js-protected-tag-edit-form').each((i, el) => {
new ProtectedTagEdit({
$wrap: $(el),
});
});
}
}
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
$.fn.renderGFM = function() { $.fn.renderGFM = function() {
this.find('.js-syntax-highlight').syntaxHighlight(); this.find('.js-syntax-highlight').syntaxHighlight();
this.find('.js-render-math').renderMath(); this.find('.js-render-math').renderMath();
return this;
}; };
$(document).on('ready load', function() { $(document).on('ready load', function() {
......
import Vue from 'vue';
(() => { (() => {
class Subscription { class Subscription {
constructor(containerElm) { constructor(containerElm) {
...@@ -29,8 +27,7 @@ import Vue from 'vue'; ...@@ -29,8 +27,7 @@ import Vue from 'vue';
// hack to allow this to work with the issue boards Vue object // hack to allow this to work with the issue boards Vue object
if (document.querySelector('html').classList.contains('issue-boards-page')) { if (document.querySelector('html').classList.contains('issue-boards-page')) {
Vue.set( gl.issueBoards.boardStoreIssueSet(
gl.issueBoards.BoardsStore.detail.issue,
'subscribed', 'subscribed',
!gl.issueBoards.BoardsStore.detail.issue.subscribed, !gl.issueBoards.BoardsStore.detail.issue.subscribed,
); );
......
...@@ -2,8 +2,6 @@ ...@@ -2,8 +2,6 @@
/* global Issuable */ /* global Issuable */
/* global ListUser */ /* global ListUser */
import Vue from 'vue';
(function() { (function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }, var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; },
slice = [].slice; slice = [].slice;
...@@ -74,7 +72,7 @@ import Vue from 'vue'; ...@@ -74,7 +72,7 @@ import Vue from 'vue';
e.preventDefault(); e.preventDefault();
if ($dropdown.hasClass('js-issue-board-sidebar')) { if ($dropdown.hasClass('js-issue-board-sidebar')) {
Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'assignee', new ListUser({ gl.issueBoards.boardStoreIssueSet('assignee', new ListUser({
id: _this.currentUser.id, id: _this.currentUser.id,
username: _this.currentUser.username, username: _this.currentUser.username,
name: _this.currentUser.name, name: _this.currentUser.name,
...@@ -225,14 +223,14 @@ import Vue from 'vue'; ...@@ -225,14 +223,14 @@ import Vue from 'vue';
return $dropdown.closest('form').submit(); return $dropdown.closest('form').submit();
} else if ($dropdown.hasClass('js-issue-board-sidebar')) { } else if ($dropdown.hasClass('js-issue-board-sidebar')) {
if (user.id) { if (user.id) {
Vue.set(gl.issueBoards.BoardsStore.detail.issue, 'assignee', new ListUser({ gl.issueBoards.boardStoreIssueSet('assignee', new ListUser({
id: user.id, id: user.id,
username: user.username, username: user.username,
name: user.name, name: user.name,
avatar_url: user.avatar_url avatar_url: user.avatar_url
})); }));
} else { } else {
Vue.delete(gl.issueBoards.BoardsStore.detail.issue, 'assignee'); gl.issueBoards.boardStoreIssueDelete('assignee');
} }
updateIssueBoardsIssue(); updateIssueBoardsIssue();
......
...@@ -446,10 +446,8 @@ ...@@ -446,10 +446,8 @@
} }
} }
.filter-dropdown-item.dropdown-active { .filter-dropdown-item.droplab-item-active .btn {
.btn {
@extend %filter-dropdown-item-btn-hover; @extend %filter-dropdown-item-btn-hover;
}
} }
.filter-dropdown-loading { .filter-dropdown-loading {
......
...@@ -57,6 +57,37 @@ ...@@ -57,6 +57,37 @@
margin-right: 5px; margin-right: 5px;
} }
} }
.truncated-info {
text-align: center;
border-bottom: 1px solid;
background-color: $black-transparent;
height: 45px;
&.affix {
top: 0;
}
// with sidebar
&.affix.sidebar-expanded {
right: 312px;
left: 22px;
}
// without sidebar
&.affix.sidebar-collapsed {
right: 20px;
left: 20px;
}
&.affix-top {
position: absolute;
top: 0;
margin: 0 auto;
right: 5px;
left: 5px;
}
}
} }
.scroll-controls { .scroll-controls {
...@@ -186,6 +217,7 @@ ...@@ -186,6 +217,7 @@
white-space: pre; white-space: pre;
overflow-x: auto; overflow-x: auto;
font-size: 12px; font-size: 12px;
position: relative;
.fa-refresh { .fa-refresh {
font-size: 24px; font-size: 24px;
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
svg { svg {
width: 20px; width: 20px;
height: auto; height: 20px;
fill: $gl-text-color-secondary; fill: $gl-text-color-secondary;
} }
......
...@@ -196,6 +196,7 @@ ...@@ -196,6 +196,7 @@
transition: width .3s; transition: width .3s;
background: $gray-light; background: $gray-light;
padding: 10px 20px; padding: 10px 20px;
z-index: 2;
&.right-sidebar-expanded { &.right-sidebar-expanded {
width: $gutter_width; width: $gutter_width;
......
...@@ -310,3 +310,94 @@ ...@@ -310,3 +310,94 @@
margin-bottom: 10px; margin-bottom: 10px;
} }
} }
.comment-type-dropdown {
.comment-btn {
width: auto;
}
.dropdown-toggle {
float: right;
.toggle-icon {
color: $white-light;
padding-right: 2px;
margin-top: 2px;
pointer-events: none;
}
}
.dropdown-menu {
top: initial;
bottom: 40px;
width: 298px;
}
.description {
display: inline-block;
white-space: normal;
margin-left: 8px;
padding-right: 33px;
}
li {
padding-top: 6px;
& > a {
margin: 0;
padding: 0;
color: inherit;
border-radius: 0;
text-overflow: inherit;
&:hover,
&:focus {
background-color: inherit;
color: inherit;
}
}
&:hover,
&:focus {
background-color: $dropdown-hover-color;
color: $white-light;
}
&.droplab-item-selected i {
visibility: visible;
}
i {
visibility: hidden;
}
}
i {
display: inline-block;
vertical-align: top;
padding-top: 2px;
}
.divider {
margin: 0 8px;
padding: 0;
border-top: $gray-darkest;
}
@media (max-width: $screen-xs-max) {
display: flex;
width: 100%;
.comment-btn {
flex-grow: 1;
flex-shrink: 0;
width: auto;
}
.dropdown-toggle {
flex-grow: 0;
flex-shrink: 1;
width: auto;
}
}
}
...@@ -19,7 +19,7 @@ ul.notes { ...@@ -19,7 +19,7 @@ ul.notes {
svg { svg {
width: 18px; width: 18px;
height: auto; height: 18px;
fill: $gray-darkest; fill: $gray-darkest;
position: absolute; position: absolute;
left: 30px; left: 30px;
...@@ -294,6 +294,18 @@ ul.notes { ...@@ -294,6 +294,18 @@ ul.notes {
border-width: 1px; border-width: 1px;
} }
.discussion-notes {
&:not(:first-child) {
border-top: 1px solid $white-normal;
margin-top: 20px;
}
&:not(:last-child) {
border-bottom: 1px solid $white-normal;
margin-bottom: 20px;
}
}
.notes { .notes {
background-color: $white-light; background-color: $white-light;
} }
......
...@@ -744,7 +744,8 @@ pre.light-well { ...@@ -744,7 +744,8 @@ pre.light-well {
text-align: left; text-align: left;
} }
.protected-branches-list { .protected-branches-list,
.protected-tags-list {
margin-bottom: 30px; margin-bottom: 30px;
a { a {
...@@ -776,6 +777,17 @@ pre.light-well { ...@@ -776,6 +777,17 @@ pre.light-well {
} }
} }
.protected-tags-list {
.dropdown-menu-toggle {
width: 100%;
max-width: 300px;
}
.flash-container {
padding: 0;
}
}
.custom-notifications-form { .custom-notifications-form {
.is-loading { .is-loading {
.custom-notification-event-loading { .custom-notification-event-loading {
......
...@@ -6,6 +6,6 @@ class Admin::ApplicationController < ApplicationController ...@@ -6,6 +6,6 @@ class Admin::ApplicationController < ApplicationController
layout 'admin' layout 'admin'
def authenticate_admin! def authenticate_admin!
render_404 unless current_user.is_admin? render_404 unless current_user.admin?
end end
end end
...@@ -21,6 +21,6 @@ class Admin::ImpersonationsController < Admin::ApplicationController ...@@ -21,6 +21,6 @@ class Admin::ImpersonationsController < Admin::ApplicationController
end end
def authenticate_impersonator! def authenticate_impersonator!
render_404 unless impersonator && impersonator.is_admin? && !impersonator.blocked? render_404 unless impersonator && impersonator.admin? && !impersonator.blocked?
end end
end end
module RendersNotes
def prepare_notes_for_rendering(notes)
preload_noteable_for_regular_notes(notes)
preload_max_access_for_authors(notes, @project)
Banzai::NoteRenderer.render(notes, @project, current_user)
notes
end
private
def preload_max_access_for_authors(notes, project)
user_ids = notes.map(&:author_id)
project.team.max_member_access_for_user_ids(user_ids)
end
def preload_noteable_for_regular_notes(notes)
ActiveRecord::Associations::Preloader.new.preload(notes.reject(&:for_commit?), :noteable)
end
end
module RequiresHealthToken
extend ActiveSupport::Concern
included do
before_action :validate_health_check_access!
end
private
def validate_health_check_access!
render_404 unless token_valid?
end
def token_valid?
token = params[:token].presence || request.headers['TOKEN']
token.present? &&
ActiveSupport::SecurityUtils.variable_size_secure_compare(
token,
current_application_settings.health_check_access_token
)
end
def render_404
render file: Rails.root.join('public', '404'), layout: false, status: '404'
end
end
class HealthCheckController < HealthCheck::HealthCheckController class HealthCheckController < HealthCheck::HealthCheckController
before_action :validate_health_check_access! include RequiresHealthToken
private
def validate_health_check_access!
render_404 unless token_valid?
end
def token_valid?
token = params[:token].presence || request.headers['TOKEN']
token.present? &&
ActiveSupport::SecurityUtils.variable_size_secure_compare(
token,
current_application_settings.health_check_access_token
)
end
def render_404
render file: Rails.root.join('public', '404'), layout: false, status: '404'
end
end end
class HealthController < ActionController::Base
protect_from_forgery with: :exception
include RequiresHealthToken
CHECKS = [
Gitlab::HealthChecks::DbCheck,
Gitlab::HealthChecks::RedisCheck,
Gitlab::HealthChecks::FsShardsCheck,
].freeze
def readiness
results = CHECKS.map { |check| [check.name, check.readiness] }
render_check_results(results)
end
def liveness
results = CHECKS.map { |check| [check.name, check.liveness] }
render_check_results(results)
end
def metrics
results = CHECKS.flat_map(&:metrics)
response = results.map(&method(:metric_to_prom_line)).join("\n")
render text: response, content_type: 'text/plain; version=0.0.4'
end
private
def metric_to_prom_line(metric)
labels = metric.labels&.map { |key, value| "#{key}=\"#{value}\"" }&.join(',') || ''
if labels.empty?
"#{metric.name} #{metric.value}"
else
"#{metric.name}{#{labels}} #{metric.value}"
end
end
def render_check_results(results)
flattened = results.flat_map do |name, result|
if result.is_a?(Gitlab::HealthChecks::Result)
[[name, result]]
else
result.map { |r| [name, r] }
end
end
success = flattened.all? { |name, r| r.success }
response = flattened.map do |name, r|
info = { status: r.success ? 'ok' : 'failed' }
info['message'] = r.message if r.message
info[:labels] = r.labels if r.labels
[name, info]
end
render json: response.to_h, status: success ? :ok : :service_unavailable
end
end
...@@ -19,6 +19,11 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -19,6 +19,11 @@ class Projects::BuildsController < Projects::ApplicationController
else else
@builds @builds
end end
@builds = @builds.includes([
{ pipeline: :project },
:project,
:tags
])
@builds = @builds.page(params[:page]).per(30) @builds = @builds.page(params[:page]).per(30)
end end
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
# #
# Not to be confused with CommitsController, plural. # Not to be confused with CommitsController, plural.
class Projects::CommitController < Projects::ApplicationController class Projects::CommitController < Projects::ApplicationController
include RendersNotes
include CreatesCommit include CreatesCommit
include DiffForPath include DiffForPath
include DiffHelper include DiffHelper
...@@ -113,22 +114,19 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -113,22 +114,19 @@ class Projects::CommitController < Projects::ApplicationController
end end
def define_note_vars def define_note_vars
@grouped_diff_discussions = commit.notes.grouped_diff_discussions @noteable = @commit
@notes = commit.notes.non_diff_notes.fresh
Banzai::NoteRenderer.render(
@grouped_diff_discussions.values.flat_map(&:notes) + @notes,
@project,
current_user,
)
@note = @project.build_commit_note(commit) @note = @project.build_commit_note(commit)
@noteable = @commit @new_diff_note_attrs = {
@comments_target = {
noteable_type: 'Commit', noteable_type: 'Commit',
commit_id: @commit.id commit_id: @commit.id
} }
@grouped_diff_discussions = commit.grouped_diff_discussions
@discussions = commit.discussions
@notes = (@grouped_diff_discussions.values.flatten + @discussions).flat_map(&:notes)
@notes = prepare_notes_for_rendering(@notes)
end end
def assign_change_commit_vars def assign_change_commit_vars
......
...@@ -28,7 +28,7 @@ class Projects::DiscussionsController < Projects::ApplicationController ...@@ -28,7 +28,7 @@ class Projects::DiscussionsController < Projects::ApplicationController
end end
def discussion def discussion
@discussion ||= @merge_request.find_diff_discussion(params[:id]) || render_404 @discussion ||= @merge_request.find_discussion(params[:id]) || render_404
end end
def authorize_resolve_discussion! def authorize_resolve_discussion!
......
class Projects::IssuesController < Projects::ApplicationController class Projects::IssuesController < Projects::ApplicationController
include NotesHelper include RendersNotes
include ToggleSubscriptionAction include ToggleSubscriptionAction
include IssuableActions include IssuableActions
include ToggleAwardEmoji include ToggleAwardEmoji
...@@ -84,15 +84,11 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -84,15 +84,11 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def show def show
raw_notes = @issue.notes.inc_relations_for_view.fresh
@notes = Banzai::NoteRenderer.
render(raw_notes, @project, current_user, @path, @project_wiki, @ref)
@note = @project.notes.new(noteable: @issue)
@noteable = @issue @noteable = @issue
@note = @project.notes.new(noteable: @issue)
preload_max_access_for_authors(@notes, @project) @discussions = @issue.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
respond_to do |format| respond_to do |format|
format.html format.html
......
...@@ -3,7 +3,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -3,7 +3,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
include DiffForPath include DiffForPath
include DiffHelper include DiffHelper
include IssuableActions include IssuableActions
include NotesHelper include RendersNotes
include ToggleAwardEmoji include ToggleAwardEmoji
include IssuableCollections include IssuableCollections
...@@ -574,20 +574,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -574,20 +574,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@note = @project.notes.new(noteable: @merge_request) @note = @project.notes.new(noteable: @merge_request)
@discussions = @merge_request.discussions @discussions = @merge_request.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
preload_noteable_for_regular_notes(@discussions.flat_map(&:notes))
# This is not executed lazily
@notes = Banzai::NoteRenderer.render(
@discussions.flat_map(&:notes),
@project,
current_user,
@path,
@project_wiki,
@ref
)
preload_max_access_for_authors(@notes, @project)
end end
def define_widget_vars def define_widget_vars
...@@ -600,22 +587,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -600,22 +587,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def define_diff_comment_vars def define_diff_comment_vars
@comments_target = { @new_diff_note_attrs = {
noteable_type: 'MergeRequest', noteable_type: 'MergeRequest',
noteable_id: @merge_request.id noteable_id: @merge_request.id
} }
@use_legacy_diff_notes = !@merge_request.has_complete_diff_refs? @use_legacy_diff_notes = !@merge_request.has_complete_diff_refs?
@grouped_diff_discussions = @merge_request.notes.inc_relations_for_view.grouped_diff_discussions
@grouped_diff_discussions = @merge_request.grouped_diff_discussions
Banzai::NoteRenderer.render( @notes = prepare_notes_for_rendering(@grouped_diff_discussions.values.flatten.flat_map(&:notes))
@grouped_diff_discussions.values.flat_map(&:notes),
@project,
current_user,
@path,
@project_wiki,
@ref
)
end end
def define_pipelines_vars def define_pipelines_vars
......
...@@ -116,7 +116,7 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -116,7 +116,7 @@ class Projects::PipelinesController < Projects::ApplicationController
end end
def pipeline def pipeline
@pipeline ||= project.pipelines.find_by!(id: params[:id]) @pipeline ||= project.pipelines.find_by!(id: params[:id]).present(current_user: current_user)
end end
def commit def commit
......
...@@ -23,7 +23,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController ...@@ -23,7 +23,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
def update_params def update_params
params.require(:project).permit( params.require(:project).permit(
:runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex, :runners_token, :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:public_builds :public_builds, :auto_cancel_pending_pipelines
) )
end end
end end
class Projects::ProtectedBranchesController < Projects::ApplicationController class Projects::ProtectedBranchesController < Projects::ProtectedRefsController
include RepositorySettingsRedirect protected
# Authorize
before_action :require_non_empty_project
before_action :authorize_admin_project!
before_action :load_protected_branch, only: [:show, :update, :destroy]
layout "project_settings" def project_refs
@project.repository.branches
def index
redirect_to_repository_settings(@project)
end
def create
@protected_branch = ::ProtectedBranches::CreateService.new(@project, current_user, protected_branch_params).execute
unless @protected_branch.persisted?
flash[:alert] = @protected_branches.errors.full_messages.join(', ').html_safe
end
redirect_to_repository_settings(@project)
end end
def show def create_service_class
@matching_branches = @protected_branch.matching(@project.repository.branches) ::ProtectedBranches::CreateService
end end
def update def update_service_class
@protected_branch = ::ProtectedBranches::UpdateService.new(@project, current_user, protected_branch_params).execute(@protected_branch) ::ProtectedBranches::UpdateService
if @protected_branch.valid?
respond_to do |format|
format.json { render json: @protected_branch, status: :ok }
end
else
respond_to do |format|
format.json { render json: @protected_branch.errors, status: :unprocessable_entity }
end
end end
end
def destroy
@protected_branch.destroy
respond_to do |format|
format.html { redirect_to_repository_settings(@project) }
format.js { head :ok }
end
end
private
def load_protected_branch def load_protected_ref
@protected_branch = @project.protected_branches.find(params[:id]) @protected_ref = @project.protected_branches.find(params[:id])
end end
def protected_branch_params def protected_ref_params
params.require(:protected_branch).permit(:name, params.require(:protected_branch).permit(:name,
merge_access_levels_attributes: [:access_level, :id], merge_access_levels_attributes: [:access_level, :id],
push_access_levels_attributes: [:access_level, :id]) push_access_levels_attributes: [:access_level, :id])
......
This diff is collapsed.
This diff is collapsed.
class Projects::SnippetsController < Projects::ApplicationController class Projects::SnippetsController < Projects::ApplicationController
include RendersNotes
include ToggleAwardEmoji include ToggleAwardEmoji
include SpammableActions include SpammableActions
include SnippetsActions include SnippetsActions
...@@ -55,8 +56,10 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -55,8 +56,10 @@ class Projects::SnippetsController < Projects::ApplicationController
def show def show
@note = @project.notes.new(noteable: @snippet) @note = @project.notes.new(noteable: @snippet)
@notes = Banzai::NoteRenderer.render(@snippet.notes.fresh, @project, current_user)
@noteable = @snippet @noteable = @snippet
@discussions = @snippet.discussions
@notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes))
end end
def destroy def destroy
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment