Commit b7b5bd4a authored by Phil Hughes's avatar Phil Hughes

Merge remote-tracking branch...

Merge remote-tracking branch 'origin/28433-internationalise-cycle-analytics-page' into js-translations
parents 7d16537c 7ceb0efc
...@@ -67,6 +67,7 @@ stages: ...@@ -67,6 +67,7 @@ stages:
- export CI_NODE_TOTAL=${JOB_NAME[2]} - export CI_NODE_TOTAL=${JOB_NAME[2]}
- export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export KNAPSACK_GENERATE_REPORT=true - export KNAPSACK_GENERATE_REPORT=true
- export CACHE_CLASSES=true
- cp ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH} - cp ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH}
- knapsack rspec "--color --format documentation" - knapsack rspec "--color --format documentation"
artifacts: artifacts:
...@@ -87,6 +88,7 @@ stages: ...@@ -87,6 +88,7 @@ stages:
- export CI_NODE_TOTAL=${JOB_NAME[2]} - export CI_NODE_TOTAL=${JOB_NAME[2]}
- export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json - export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/${JOB_NAME[0]}_node_${CI_NODE_INDEX}_${CI_NODE_TOTAL}_report.json
- export KNAPSACK_GENERATE_REPORT=true - export KNAPSACK_GENERATE_REPORT=true
- export CACHE_CLASSES=true
- cp ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH} - cp ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} ${KNAPSACK_REPORT_PATH}
- knapsack spinach "-r rerun" || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)' - knapsack spinach "-r rerun" || retry '[[ -e tmp/spinach-rerun.txt ]] && bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)'
artifacts: artifacts:
...@@ -276,7 +278,6 @@ rake karma: ...@@ -276,7 +278,6 @@ rake karma:
cache: cache:
paths: paths:
- vendor/ruby - vendor/ruby
- node_modules
stage: test stage: test
<<: *use-db <<: *use-db
<<: *dedicated-runner <<: *dedicated-runner
...@@ -375,9 +376,6 @@ coverage: ...@@ -375,9 +376,6 @@ coverage:
lint:javascript: lint:javascript:
<<: *dedicated-runner <<: *dedicated-runner
cache:
paths:
- node_modules/
stage: test stage: test
before_script: [] before_script: []
script: script:
...@@ -385,9 +383,6 @@ lint:javascript: ...@@ -385,9 +383,6 @@ lint:javascript:
lint:javascript:report: lint:javascript:report:
<<: *dedicated-runner <<: *dedicated-runner
cache:
paths:
- node_modules/
stage: post-test stage: post-test
before_script: [] before_script: []
script: script:
......
...@@ -57,16 +57,16 @@ star, smile, etc.). Some good tips about code reviews can be found in our ...@@ -57,16 +57,16 @@ star, smile, etc.). Some good tips about code reviews can be found in our
[Code Review Guidelines]: https://docs.gitlab.com/ce/development/code_review.html [Code Review Guidelines]: https://docs.gitlab.com/ce/development/code_review.html
## Feature Freeze ## Feature freeze on the 7th for the release on the 22nd
After the 7th (Pacific Standard Time Zone) of each month, RC1 of the upcoming release is created and deployed to GitLab.com and the stable branch for this release is frozen, which means master is no longer merged into it. After the 7th (Pacific Standard Time Zone) of each month, RC1 of the upcoming release (to be shipped on the 22nd) is created and deployed to GitLab.com and the stable branch for this release is frozen, which means master is no longer merged into it.
Merge requests may still be merged into master during this period, Merge requests may still be merged into master during this period,
but they will go into the _next_ release, unless they are manually cherry-picked into the stable branch. but they will go into the _next_ release, unless they are manually cherry-picked into the stable branch.
By freezing the stable branches 2 weeks prior to a release, we reduce the risk of a last minute merge request potentially breaking things. By freezing the stable branches 2 weeks prior to a release, we reduce the risk of a last minute merge request potentially breaking things.
### Between the 1st and the 7th ### Between the 1st and the 7th
These types of merge requests need special consideration: These types of merge requests for the upcoming release need special consideration:
* **Large features**: a large feature is one that is highlighted in the kick-off * **Large features**: a large feature is one that is highlighted in the kick-off
and the release blogpost; typically this will have its own channel in Slack and the release blogpost; typically this will have its own channel in Slack
...@@ -114,14 +114,15 @@ subsequent EE merge, as we often merge a lot to CE on the release date. For more ...@@ -114,14 +114,15 @@ subsequent EE merge, as we often merge a lot to CE on the release date. For more
information, see information, see
[limit conflicts with EE when developing on CE][limit_ee_conflicts]. [limit conflicts with EE when developing on CE][limit_ee_conflicts].
### Between the 7th and the 22nd ### After the 7th
Once the stable branch is frozen, only fixes for regressions (bugs introduced in that same release) Once the stable branch is frozen, only fixes for regressions (bugs introduced in that same release)
and security issues will be cherry-picked into the stable branch. and security issues will be cherry-picked into the stable branch.
Any merge requests cherry-picked into the stable branch for a previous release will also be picked into the latest stable branch. Any merge requests cherry-picked into the stable branch for a previous release will also be picked into the latest stable branch.
These fixes will be released in the next RC (before the 22nd) or patch release (after the 22nd). These fixes will be shipped in the next RC for that release if it is before the 22nd.
If the fixes are are completed on or after the 22nd, they will be shipped in a patch for that release.
If you think a merge request should go into the upcoming release even though it does not meet these requirements, If you think a merge request should go into an RC or patch even though it does not meet these requirements,
you can ask for an exception to be made. Exceptions require sign-off from 3 people besides the developer: you can ask for an exception to be made. Exceptions require sign-off from 3 people besides the developer:
1. a Release Manager 1. a Release Manager
......
/* global Flash */
import Cookies from 'js-cookie'; import Cookies from 'js-cookie';
import emojiMap from 'emojis/digests.json'; import emojiMap from 'emojis/digests.json';
...@@ -6,6 +8,7 @@ import { glEmojiTag } from './behaviors/gl_emoji'; ...@@ -6,6 +8,7 @@ import { glEmojiTag } from './behaviors/gl_emoji';
import isEmojiNameValid from './behaviors/gl_emoji/is_emoji_name_valid'; import isEmojiNameValid from './behaviors/gl_emoji/is_emoji_name_valid';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd'; const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
const requestAnimationFrame = window.requestAnimationFrame || const requestAnimationFrame = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame || window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame || window.mozRequestAnimationFrame ||
...@@ -103,8 +106,9 @@ function AwardsHandler() { ...@@ -103,8 +106,9 @@ function AwardsHandler() {
const $glEmojiElement = $target.find('gl-emoji'); const $glEmojiElement = $target.find('gl-emoji');
const $spriteIconElement = $target.find('.icon'); const $spriteIconElement = $target.find('.icon');
const emoji = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name'); const emoji = ($glEmojiElement.length ? $glEmojiElement : $spriteIconElement).data('name');
$target.closest('.js-awards-block').addClass('current'); $target.closest('.js-awards-block').addClass('current');
return this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji); this.addAward(this.getVotesBlock(), this.getAwardUrl(), emoji);
}); });
} }
...@@ -124,16 +128,18 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) { ...@@ -124,16 +128,18 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) {
} }
const $menu = $('.emoji-menu'); const $menu = $('.emoji-menu');
const $thumbsBtn = $menu.find('[data-name="thumbsup"], [data-name="thumbsdown"]').parent();
const $userAuthored = this.isUserAuthored($addBtn);
if ($menu.length) { if ($menu.length) {
if ($menu.is('.is-visible')) { if ($menu.is('.is-visible')) {
$addBtn.removeClass('is-active'); $addBtn.removeClass('is-active');
$menu.removeClass('is-visible'); $menu.removeClass('is-visible');
$('#emoji_search').blur(); $('.js-emoji-menu-search').blur();
} else { } else {
$addBtn.addClass('is-active'); $addBtn.addClass('is-active');
this.positionMenu($menu, $addBtn); this.positionMenu($menu, $addBtn);
$menu.addClass('is-visible'); $menu.addClass('is-visible');
$('#emoji_search').focus(); $('.js-emoji-menu-search').focus();
} }
} else { } else {
$addBtn.addClass('is-loading is-active'); $addBtn.addClass('is-loading is-active');
...@@ -143,10 +149,12 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) { ...@@ -143,10 +149,12 @@ AwardsHandler.prototype.showEmojiMenu = function showEmojiMenu($addBtn) {
this.positionMenu($createdMenu, $addBtn); this.positionMenu($createdMenu, $addBtn);
return setTimeout(() => { return setTimeout(() => {
$createdMenu.addClass('is-visible'); $createdMenu.addClass('is-visible');
$('#emoji_search').focus(); $('.js-emoji-menu-search').focus();
}, 200); }, 200);
}); });
} }
$thumbsBtn.toggleClass('disabled', $userAuthored);
}; };
// Create the emoji menu with the first category of emojis. // Create the emoji menu with the first category of emojis.
...@@ -174,7 +182,7 @@ AwardsHandler.prototype.createEmojiMenu = function createEmojiMenu(callback) { ...@@ -174,7 +182,7 @@ AwardsHandler.prototype.createEmojiMenu = function createEmojiMenu(callback) {
const emojiMenuMarkup = ` const emojiMenuMarkup = `
<div class="emoji-menu"> <div class="emoji-menu">
<input type="text" name="emoji_search" id="emoji_search" value="" class="emoji-search search-input form-control" placeholder="Search emoji" /> <input type="text" name="emoji-menu-search" value="" class="js-emoji-menu-search emoji-search search-input form-control" placeholder="Search emoji" />
<div class="emoji-menu-content"> <div class="emoji-menu-content">
${frequentlyUsedCatgegory} ${frequentlyUsedCatgegory}
...@@ -259,7 +267,8 @@ AwardsHandler.prototype.addAward = function addAward( ...@@ -259,7 +267,8 @@ AwardsHandler.prototype.addAward = function addAward(
callback, callback,
) { ) {
const normalizedEmoji = this.normalizeEmojiName(emoji); const normalizedEmoji = this.normalizeEmojiName(emoji);
this.postEmoji(awardUrl, normalizedEmoji, () => { const $emojiButton = this.findEmojiIcon(votesBlock, normalizedEmoji).parent();
this.postEmoji($emojiButton, awardUrl, normalizedEmoji, () => {
this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality); this.addAwardToEmojiBar(votesBlock, normalizedEmoji, checkMutuality);
return typeof callback === 'function' ? callback() : undefined; return typeof callback === 'function' ? callback() : undefined;
}); });
...@@ -324,6 +333,10 @@ AwardsHandler.prototype.isActive = function isActive($emojiButton) { ...@@ -324,6 +333,10 @@ AwardsHandler.prototype.isActive = function isActive($emojiButton) {
return $emojiButton.hasClass('active'); return $emojiButton.hasClass('active');
}; };
AwardsHandler.prototype.isUserAuthored = function isUserAuthored($button) {
return $button.hasClass('js-user-authored');
};
AwardsHandler.prototype.decrementCounter = function decrementCounter($emojiButton, emoji) { AwardsHandler.prototype.decrementCounter = function decrementCounter($emojiButton, emoji) {
const counter = $('.js-counter', $emojiButton); const counter = $('.js-counter', $emojiButton);
const counterNumber = parseInt(counter.text(), 10); const counterNumber = parseInt(counter.text(), 10);
...@@ -428,20 +441,35 @@ AwardsHandler.prototype.createEmoji = function createEmoji(votesBlock, emoji) { ...@@ -428,20 +441,35 @@ AwardsHandler.prototype.createEmoji = function createEmoji(votesBlock, emoji) {
}); });
}; };
AwardsHandler.prototype.postEmoji = function postEmoji(awardUrl, emoji, callback) { AwardsHandler.prototype.postEmoji = function postEmoji($emojiButton, awardUrl, emoji, callback) {
return $.post(awardUrl, { if (this.isUserAuthored($emojiButton)) {
this.userAuthored($emojiButton);
} else {
$.post(awardUrl, {
name: emoji, name: emoji,
}, (data) => { }, (data) => {
if (data.ok) { if (data.ok) {
callback(); callback();
} }
}); }).fail(() => new Flash('Something went wrong on our end.'));
}
}; };
AwardsHandler.prototype.findEmojiIcon = function findEmojiIcon(votesBlock, emoji) { AwardsHandler.prototype.findEmojiIcon = function findEmojiIcon(votesBlock, emoji) {
return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`); return votesBlock.find(`.js-emoji-btn [data-name="${emoji}"]`);
}; };
AwardsHandler.prototype.userAuthored = function userAuthored($emojiButton) {
const oldTitle = this.getAwardTooltip($emojiButton);
const newTitle = 'You cannot vote on your own issue, MR and note';
gl.utils.updateTooltipTitle($emojiButton, newTitle).tooltip('show');
// Restore tooltip back to award list
return setTimeout(() => {
$emojiButton.tooltip('hide');
gl.utils.updateTooltipTitle($emojiButton, oldTitle);
}, 2800);
};
AwardsHandler.prototype.scrollToAwards = function scrollToAwards() { AwardsHandler.prototype.scrollToAwards = function scrollToAwards() {
const options = { const options = {
scrollTop: $('.awards').offset().top - 110, scrollTop: $('.awards').offset().top - 110,
...@@ -474,24 +502,41 @@ AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmoj ...@@ -474,24 +502,41 @@ AwardsHandler.prototype.getFrequentlyUsedEmojis = function getFrequentlyUsedEmoj
}; };
AwardsHandler.prototype.setupSearch = function setupSearch() { AwardsHandler.prototype.setupSearch = function setupSearch() {
this.registerEventListener('on', $('input.emoji-search'), 'input', (e) => { const $search = $('.js-emoji-menu-search');
this.registerEventListener('on', $search, 'input', (e) => {
const term = $(e.target).val().trim(); const term = $(e.target).val().trim();
this.searchEmojis(term);
});
const $menu = $('.emoji-menu');
this.registerEventListener('on', $menu, transitionEndEventString, (e) => {
if (e.target === e.currentTarget) {
// Clear the search
this.searchEmojis('');
}
});
};
AwardsHandler.prototype.searchEmojis = function searchEmojis(term) {
const $search = $('.js-emoji-menu-search');
$search.val(term);
// Clean previous search results // Clean previous search results
$('ul.emoji-menu-search, h5.emoji-search-title').remove(); $('ul.emoji-menu-search, h5.emoji-search-title').remove();
if (term.length > 0) { if (term.length > 0) {
// Generate a search result block // Generate a search result block
const h5 = $('<h5 class="emoji-search-title"/>').text('Search results'); const h5 = $('<h5 class="emoji-search-title"/>').text('Search results');
const foundEmojis = this.searchEmojis(term).show(); const foundEmojis = this.findMatchingEmojiElements(term).show();
const ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis); const ul = $('<ul>').addClass('emoji-menu-list emoji-menu-search').append(foundEmojis);
$('.emoji-menu-content ul, .emoji-menu-content h5').hide(); $('.emoji-menu-content ul, .emoji-menu-content h5').hide();
$('.emoji-menu-content').append(h5).append(ul); $('.emoji-menu-content').append(h5).append(ul);
} else { } else {
$('.emoji-menu-content').children().show(); $('.emoji-menu-content').children().show();
} }
});
}; };
AwardsHandler.prototype.searchEmojis = function searchEmojis(term) { AwardsHandler.prototype.findMatchingEmojiElements = function findMatchingEmojiElements(term) {
const safeTerm = term.toLowerCase(); const safeTerm = term.toLowerCase();
const namesMatchingAlias = []; const namesMatchingAlias = [];
......
...@@ -22,6 +22,7 @@ $(() => { ...@@ -22,6 +22,7 @@ $(() => {
} }
$('body').on('click', '.js-toggle-button', function toggleButton(e) { $('body').on('click', '.js-toggle-button', function toggleButton(e) {
e.target.classList.toggle('open');
toggleContainer($(this).closest('.js-toggle-container')); toggleContainer($(this).closest('.js-toggle-container'));
const targetTag = e.currentTarget.tagName.toLowerCase(); const targetTag = e.currentTarget.tagName.toLowerCase();
......
...@@ -35,7 +35,7 @@ export default class BlobFileDropzone { ...@@ -35,7 +35,7 @@ export default class BlobFileDropzone {
this.removeFile(file); this.removeFile(file);
}); });
this.on('sending', function (file, xhr, formData) { this.on('sending', function (file, xhr, formData) {
formData.append('target_branch', form.find('input[name="target_branch"]').val()); formData.append('branch_name', form.find('input[name="branch_name"]').val());
formData.append('create_merge_request', form.find('.js-create-merge-request').val()); formData.append('create_merge_request', form.find('.js-create-merge-request').val());
formData.append('commit_message', form.find('.js-commit-message').val()); formData.append('commit_message', form.find('.js-commit-message').val());
}); });
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
/* global ListLabel */ /* global ListLabel */
import queryData from '../utils/query_data'; import queryData from '../utils/query_data';
const PER_PAGE = 20;
class List { class List {
constructor (obj) { constructor (obj) {
this.id = obj.id; this.id = obj.id;
...@@ -58,7 +60,9 @@ class List { ...@@ -58,7 +60,9 @@ class List {
nextPage () { nextPage () {
if (this.issuesSize > this.issues.length) { if (this.issuesSize > this.issues.length) {
if (this.issues.length / PER_PAGE >= 1) {
this.page += 1; this.page += 1;
}
return this.getIssues(false); return this.getIssues(false);
} }
...@@ -145,10 +149,7 @@ class List { ...@@ -145,10 +149,7 @@ class List {
} }
updateIssueLabel(issue, listFrom, moveBeforeIid, moveAfterIid) { updateIssueLabel(issue, listFrom, moveBeforeIid, moveAfterIid) {
gl.boardService.moveIssue(issue.id, listFrom.id, this.id, moveBeforeIid, moveAfterIid) gl.boardService.moveIssue(issue.id, listFrom.id, this.id, moveBeforeIid, moveAfterIid);
.then(() => {
listFrom.getIssues(false);
});
} }
findIssue (id) { findIssue (id) {
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
consistent-return, prefer-rest-params */ consistent-return, prefer-rest-params */
/* global Breakpoints */ /* global Breakpoints */
import { bytesToKiB } from './lib/utils/number_utils';
const bind = function (fn, me) { return function () { return fn.apply(me, arguments); }; }; const bind = function (fn, me) { return function () { return fn.apply(me, arguments); }; };
const AUTO_SCROLL_OFFSET = 75; const AUTO_SCROLL_OFFSET = 75;
const DOWN_BUILD_TRACE = '#down-build-trace'; const DOWN_BUILD_TRACE = '#down-build-trace';
...@@ -20,6 +22,7 @@ window.Build = (function () { ...@@ -20,6 +22,7 @@ window.Build = (function () {
this.state = this.options.logState; this.state = this.options.logState;
this.buildStage = this.options.buildStage; this.buildStage = this.options.buildStage;
this.$document = $(document); this.$document = $(document);
this.logBytes = 0;
this.updateDropdown = bind(this.updateDropdown, this); this.updateDropdown = bind(this.updateDropdown, this);
...@@ -98,16 +101,23 @@ window.Build = (function () { ...@@ -98,16 +101,23 @@ window.Build = (function () {
if (log.append) { if (log.append) {
$buildContainer.append(log.html); $buildContainer.append(log.html);
this.logBytes += log.size;
} else { } else {
$buildContainer.html(log.html); $buildContainer.html(log.html);
if (log.truncated) { this.logBytes = log.size;
$('.js-truncated-info-size').html(` ${log.size} `); }
// if the incremental sum of logBytes we received is less than the total
// we need to show a message warning the user about that.
if (this.logBytes < log.total) {
// size is in bytes, we need to calculate KiB
const size = bytesToKiB(this.logBytes);
$('.js-truncated-info-size').html(`${size}`);
this.$truncatedInfo.removeClass('hidden'); this.$truncatedInfo.removeClass('hidden');
this.initAffixTruncatedInfo(); this.initAffixTruncatedInfo();
} else { } else {
this.$truncatedInfo.addClass('hidden'); this.$truncatedInfo.addClass('hidden');
} }
}
this.checkAutoscroll(); this.checkAutoscroll();
......
import CANCELED_SVG from 'icons/_icon_status_canceled_borderless.svg';
import CREATED_SVG from 'icons/_icon_status_created_borderless.svg';
import FAILED_SVG from 'icons/_icon_status_failed_borderless.svg';
import MANUAL_SVG from 'icons/_icon_status_manual_borderless.svg';
import PENDING_SVG from 'icons/_icon_status_pending_borderless.svg';
import RUNNING_SVG from 'icons/_icon_status_running_borderless.svg';
import SKIPPED_SVG from 'icons/_icon_status_skipped_borderless.svg';
import SUCCESS_SVG from 'icons/_icon_status_success_borderless.svg';
import WARNING_SVG from 'icons/_icon_status_warning_borderless.svg';
const StatusIconEntityMap = {
icon_status_canceled: CANCELED_SVG,
icon_status_created: CREATED_SVG,
icon_status_failed: FAILED_SVG,
icon_status_manual: MANUAL_SVG,
icon_status_pending: PENDING_SVG,
icon_status_running: RUNNING_SVG,
icon_status_skipped: SKIPPED_SVG,
icon_status_success: SUCCESS_SVG,
icon_status_warning: WARNING_SVG,
};
export {
CANCELED_SVG,
CREATED_SVG,
FAILED_SVG,
MANUAL_SVG,
PENDING_SVG,
RUNNING_SVG,
SKIPPED_SVG,
SUCCESS_SVG,
WARNING_SVG,
StatusIconEntityMap as default,
};
import Vue from 'vue'; import Vue from 'vue';
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import PipelinesTableComponent from '../../vue_shared/components/pipelines_table'; import PipelinesTableComponent from '../../vue_shared/components/pipelines_table';
import PipelinesService from '../../vue_pipelines_index/services/pipelines_service'; import PipelinesService from '../../pipelines/services/pipelines_service';
import PipelineStore from '../../vue_pipelines_index/stores/pipelines_store'; import PipelineStore from '../../pipelines/stores/pipelines_store';
import eventHub from '../../vue_pipelines_index/event_hub'; import eventHub from '../../pipelines/event_hub';
import EmptyState from '../../vue_pipelines_index/components/empty_state.vue'; import EmptyState from '../../pipelines/components/empty_state.vue';
import ErrorState from '../../vue_pipelines_index/components/error_state.vue'; import ErrorState from '../../pipelines/components/error_state.vue';
import '../../lib/utils/common_utils'; import '../../lib/utils/common_utils';
import '../../vue_shared/vue_resource_interceptor'; import '../../vue_shared/vue_resource_interceptor';
import Poll from '../../lib/utils/poll'; import Poll from '../../lib/utils/poll';
......
...@@ -128,7 +128,7 @@ $(() => { ...@@ -128,7 +128,7 @@ $(() => {
}, },
dismissOverviewDialog() { dismissOverviewDialog() {
this.isOverviewDialogDismissed = true; this.isOverviewDialogDismissed = true;
Cookies.set(OVERVIEW_DIALOG_COOKIE, '1'); Cookies.set(OVERVIEW_DIALOG_COOKIE, '1', { expires: 365 });
}, },
}, },
}); });
......
...@@ -3,8 +3,7 @@ ...@@ -3,8 +3,7 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const CommentAndResolveBtn = Vue.extend({
const CommentAndResolveBtn = Vue.extend({
props: { props: {
discussionId: String, discussionId: String,
}, },
...@@ -61,7 +60,6 @@ import Vue from 'vue'; ...@@ -61,7 +60,6 @@ import Vue from 'vue';
$(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off('input.comment-and-resolve-btn'); $(`.js-discussion-note-form[data-discussion-id=${this.discussionId}] .note-textarea`).off('input.comment-and-resolve-btn');
} }
}); });
Vue.component('comment-and-resolve-btn', CommentAndResolveBtn); Vue.component('comment-and-resolve-btn', CommentAndResolveBtn);
})(window);
...@@ -4,8 +4,7 @@ ...@@ -4,8 +4,7 @@
import Vue from 'vue'; import Vue from 'vue';
import collapseIcon from '../icons/collapse_icon.svg'; import collapseIcon from '../icons/collapse_icon.svg';
(() => { const DiffNoteAvatars = Vue.extend({
const DiffNoteAvatars = Vue.extend({
props: ['discussionId'], props: ['discussionId'],
data() { data() {
return { return {
...@@ -152,7 +151,6 @@ import collapseIcon from '../icons/collapse_icon.svg'; ...@@ -152,7 +151,6 @@ import collapseIcon from '../icons/collapse_icon.svg';
this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible'); this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible');
}, },
}, },
}); });
Vue.component('diff-note-avatars', DiffNoteAvatars); Vue.component('diff-note-avatars', DiffNoteAvatars);
})();
...@@ -4,8 +4,7 @@ ...@@ -4,8 +4,7 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const JumpToDiscussion = Vue.extend({
const JumpToDiscussion = Vue.extend({
mixins: [DiscussionMixins], mixins: [DiscussionMixins],
props: { props: {
discussionId: String discussionId: String
...@@ -189,7 +188,6 @@ import Vue from 'vue'; ...@@ -189,7 +188,6 @@ import Vue from 'vue';
created() { created() {
this.discussion = this.discussions[this.discussionId]; this.discussion = this.discussions[this.discussionId];
}, },
}); });
Vue.component('jump-to-discussion', JumpToDiscussion); Vue.component('jump-to-discussion', JumpToDiscussion);
})();
...@@ -2,8 +2,7 @@ ...@@ -2,8 +2,7 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const NewIssueForDiscussion = Vue.extend({
const NewIssueForDiscussion = Vue.extend({
props: { props: {
discussionId: { discussionId: {
type: String, type: String,
...@@ -24,7 +23,6 @@ import Vue from 'vue'; ...@@ -24,7 +23,6 @@ import Vue from 'vue';
return false; return false;
}, },
}, },
}); });
Vue.component('new-issue-for-discussion-btn', NewIssueForDiscussion); Vue.component('new-issue-for-discussion-btn', NewIssueForDiscussion);
})();
...@@ -5,8 +5,7 @@ ...@@ -5,8 +5,7 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const ResolveBtn = Vue.extend({
const ResolveBtn = Vue.extend({
props: { props: {
noteId: Number, noteId: Number,
discussionId: String, discussionId: String,
...@@ -20,8 +19,7 @@ import Vue from 'vue'; ...@@ -20,8 +19,7 @@ import Vue from 'vue';
data: function () { data: function () {
return { return {
discussions: CommentsStore.state, discussions: CommentsStore.state,
loading: false, loading: false
note: {},
}; };
}, },
watch: { watch: {
...@@ -34,6 +32,9 @@ import Vue from 'vue'; ...@@ -34,6 +32,9 @@ import Vue from 'vue';
discussion: function () { discussion: function () {
return this.discussions[this.discussionId]; return this.discussions[this.discussionId];
}, },
note: function () {
return this.discussion ? this.discussion.getNote(this.noteId) : {};
},
buttonText: function () { buttonText: function () {
if (this.isResolved) { if (this.isResolved) {
return `Resolved by ${this.resolvedByName}`; return `Resolved by ${this.resolvedByName}`;
...@@ -112,10 +113,7 @@ import Vue from 'vue'; ...@@ -112,10 +113,7 @@ import Vue from 'vue';
authorAvatar: this.authorAvatar, authorAvatar: this.authorAvatar,
noteTruncated: this.noteTruncated, noteTruncated: this.noteTruncated,
}); });
this.note = this.discussion.getNote(this.noteId);
} }
}); });
Vue.component('resolve-btn', ResolveBtn); Vue.component('resolve-btn', ResolveBtn);
})();
...@@ -4,8 +4,7 @@ ...@@ -4,8 +4,7 @@
import Vue from 'vue'; import Vue from 'vue';
((w) => { window.ResolveCount = Vue.extend({
w.ResolveCount = Vue.extend({
mixins: [DiscussionMixins], mixins: [DiscussionMixins],
props: { props: {
loggedOut: Boolean loggedOut: Boolean
...@@ -23,5 +22,4 @@ import Vue from 'vue'; ...@@ -23,5 +22,4 @@ import Vue from 'vue';
return this.discussionCount === 1 ? 'discussion' : 'discussions'; return this.discussionCount === 1 ? 'discussion' : 'discussions';
} }
} }
}); });
})(window);
...@@ -4,8 +4,7 @@ ...@@ -4,8 +4,7 @@
import Vue from 'vue'; import Vue from 'vue';
(() => { const ResolveDiscussionBtn = Vue.extend({
const ResolveDiscussionBtn = Vue.extend({
props: { props: {
discussionId: String, discussionId: String,
mergeRequestId: Number, mergeRequestId: Number,
...@@ -56,7 +55,6 @@ import Vue from 'vue'; ...@@ -56,7 +55,6 @@ import Vue from 'vue';
this.discussion = CommentsStore.state[this.discussionId]; this.discussion = CommentsStore.state[this.discussionId];
} }
}); });
Vue.component('resolve-discussion-btn', ResolveDiscussionBtn); Vue.component('resolve-discussion-btn', ResolveDiscussionBtn);
})();
/* eslint-disable object-shorthand, func-names, guard-for-in, no-restricted-syntax, comma-dangle, no-param-reassign, max-len */ /* eslint-disable object-shorthand, func-names, guard-for-in, no-restricted-syntax, comma-dangle, no-param-reassign, max-len */
((w) => { window.DiscussionMixins = {
w.DiscussionMixins = {
computed: { computed: {
discussionCount: function () { discussionCount: function () {
return Object.keys(this.discussions).length; return Object.keys(this.discussions).length;
...@@ -33,5 +32,4 @@ ...@@ -33,5 +32,4 @@
return unresolvedCount; return unresolvedCount;
} }
} }
}; };
})(window);
...@@ -9,10 +9,9 @@ require('../../vue_shared/vue_resource_interceptor'); ...@@ -9,10 +9,9 @@ require('../../vue_shared/vue_resource_interceptor');
Vue.use(VueResource); Vue.use(VueResource);
(() => { window.gl = window.gl || {};
window.gl = window.gl || {};
class ResolveServiceClass { class ResolveServiceClass {
constructor(root) { constructor(root) {
this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve`); this.noteResource = Vue.resource(`${root}/notes{/noteId}/resolve`);
this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve`); this.discussionResource = Vue.resource(`${root}/merge_requests{/mergeRequestId}/discussions{/discussionId}/resolve`);
...@@ -78,7 +77,6 @@ Vue.use(VueResource); ...@@ -78,7 +77,6 @@ Vue.use(VueResource);
discussionId discussionId
}, {}); }, {});
} }
} }
gl.DiffNotesResolveServiceClass = ResolveServiceClass; gl.DiffNotesResolveServiceClass = ResolveServiceClass;
})();
...@@ -3,8 +3,7 @@ ...@@ -3,8 +3,7 @@
import Vue from 'vue'; import Vue from 'vue';
((w) => { window.CommentsStore = {
w.CommentsStore = {
state: {}, state: {},
get: function (discussionId, noteId) { get: function (discussionId, noteId) {
return this.state[discussionId].getNote(noteId); return this.state[discussionId].getNote(noteId);
...@@ -54,5 +53,4 @@ import Vue from 'vue'; ...@@ -54,5 +53,4 @@ import Vue from 'vue';
return ids; return ids;
} }
}; };
})(window);
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
/* global Labels */ /* global Labels */
/* global Shortcuts */ /* global Shortcuts */
/* global Sidebar */ /* global Sidebar */
/* global ShortcutsWiki */
import Issue from './issue'; import Issue from './issue';
...@@ -46,6 +47,7 @@ import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater'; ...@@ -46,6 +47,7 @@ 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'; import { ProtectedTagCreate, ProtectedTagEditList } from './protected_tags';
import ShortcutsWiki from './shortcuts_wiki';
const ShortcutsBlob = require('./shortcuts_blob'); const ShortcutsBlob = require('./shortcuts_blob');
...@@ -148,13 +150,13 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -148,13 +150,13 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'projects:milestones:new': case 'projects:milestones:new':
case 'projects:milestones:edit': case 'projects:milestones:edit':
case 'projects:milestones:update': case 'projects:milestones:update':
case 'groups:milestones:new':
case 'groups:milestones:edit':
case 'groups:milestones:update':
new ZenMode(); new ZenMode();
new gl.DueDateSelectors(); new gl.DueDateSelectors();
new gl.GLForm($('.milestone-form')); new gl.GLForm($('.milestone-form'));
break; break;
case 'groups:milestones:new':
new ZenMode();
break;
case 'projects:compare:show': case 'projects:compare:show':
new gl.Diff(); new gl.Diff();
break; break;
...@@ -365,6 +367,9 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -365,6 +367,9 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'admin': case 'admin':
new Admin(); new Admin();
switch (path[1]) { switch (path[1]) {
case 'cohorts':
new gl.UsagePing();
break;
case 'groups': case 'groups':
new UsersSelect(); new UsersSelect();
break; break;
...@@ -416,7 +421,7 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -416,7 +421,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
break; break;
case 'wikis': case 'wikis':
new gl.Wikis(); new gl.Wikis();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsWiki();
new ZenMode(); new ZenMode();
new gl.GLForm($('.wiki-form')); new gl.GLForm($('.wiki-form'));
break; break;
......
...@@ -2,10 +2,12 @@ const DATA_TRIGGER = 'data-dropdown-trigger'; ...@@ -2,10 +2,12 @@ const DATA_TRIGGER = 'data-dropdown-trigger';
const DATA_DROPDOWN = 'data-dropdown'; const DATA_DROPDOWN = 'data-dropdown';
const SELECTED_CLASS = 'droplab-item-selected'; const SELECTED_CLASS = 'droplab-item-selected';
const ACTIVE_CLASS = 'droplab-item-active'; const ACTIVE_CLASS = 'droplab-item-active';
const IGNORE_CLASS = 'droplab-item-ignore';
export { export {
DATA_TRIGGER, DATA_TRIGGER,
DATA_DROPDOWN, DATA_DROPDOWN,
SELECTED_CLASS, SELECTED_CLASS,
ACTIVE_CLASS, ACTIVE_CLASS,
IGNORE_CLASS,
}; };
/* eslint-disable */ /* eslint-disable */
import utils from './utils'; import utils from './utils';
import { SELECTED_CLASS } from './constants'; import { SELECTED_CLASS, IGNORE_CLASS } from './constants';
var DropDown = function(list) { var DropDown = function(list) {
this.currentIndex = 0; this.currentIndex = 0;
...@@ -36,6 +36,7 @@ Object.assign(DropDown.prototype, { ...@@ -36,6 +36,7 @@ Object.assign(DropDown.prototype, {
clickEvent: function(e) { clickEvent: function(e) {
if (e.target.tagName === 'UL') return; if (e.target.tagName === 'UL') return;
if (e.target.classList.contains(IGNORE_CLASS)) return;
var selected = utils.closest(e.target, 'LI'); var selected = utils.closest(e.target, 'LI');
if (!selected) return; if (!selected) return;
......
...@@ -38,6 +38,9 @@ window.DropzoneInput = (function() { ...@@ -38,6 +38,9 @@ window.DropzoneInput = (function() {
"opacity": 0, "opacity": 0,
"display": "none" "display": "none"
}); });
if (!project_uploads_path) return;
dropzone = form_dropzone.dropzone({ dropzone = form_dropzone.dropzone({
url: project_uploads_path, url: project_uploads_path,
dictDefaultMessage: "", dictDefaultMessage: "",
...@@ -66,7 +69,10 @@ window.DropzoneInput = (function() { ...@@ -66,7 +69,10 @@ window.DropzoneInput = (function() {
form_textarea.focus(); form_textarea.focus();
}, },
success: function(header, response) { success: function(header, response) {
pasteText(response.link.markdown); const processingFileCount = this.getQueuedFiles().length + this.getUploadingFiles().length;
const shouldPad = processingFileCount >= 1;
pasteText(response.link.markdown, shouldPad);
}, },
error: function(temp) { error: function(temp) {
var checkIfMsgExists, errorAlert; var checkIfMsgExists, errorAlert;
...@@ -123,16 +129,19 @@ window.DropzoneInput = (function() { ...@@ -123,16 +129,19 @@ window.DropzoneInput = (function() {
} }
return false; return false;
}; };
pasteText = function(text) { pasteText = function(text, shouldPad) {
var afterSelection, beforeSelection, caretEnd, caretStart, textEnd; var afterSelection, beforeSelection, caretEnd, caretStart, textEnd;
var formattedText = text + "\n\n"; var formattedText = text;
caretStart = $(child)[0].selectionStart; if (shouldPad) formattedText += "\n\n";
caretEnd = $(child)[0].selectionEnd; const textarea = child.get(0);
caretStart = textarea.selectionStart;
caretEnd = textarea.selectionEnd;
textEnd = $(child).val().length; textEnd = $(child).val().length;
beforeSelection = $(child).val().substring(0, caretStart); beforeSelection = $(child).val().substring(0, caretStart);
afterSelection = $(child).val().substring(caretEnd, textEnd); afterSelection = $(child).val().substring(caretEnd, textEnd);
$(child).val(beforeSelection + formattedText + afterSelection); $(child).val(beforeSelection + formattedText + afterSelection);
child.get(0).setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length); textarea.setSelectionRange(caretStart + formattedText.length, caretEnd + formattedText.length);
textarea.style.height = `${textarea.scrollHeight}px`;
return form_textarea.trigger("input"); return form_textarea.trigger("input");
}; };
getFilename = function(e) { getFilename = function(e) {
...@@ -176,7 +185,7 @@ window.DropzoneInput = (function() { ...@@ -176,7 +185,7 @@ window.DropzoneInput = (function() {
}; };
insertToTextArea = function(filename, url) { insertToTextArea = function(filename, url) {
return $(child).val(function(index, val) { return $(child).val(function(index, val) {
return val.replace("{{" + filename + "}}", url + "\n"); return val.replace("{{" + filename + "}}", url);
}); });
}; };
appendToTextArea = function(url) { appendToTextArea = function(url) {
...@@ -211,6 +220,7 @@ window.DropzoneInput = (function() { ...@@ -211,6 +220,7 @@ window.DropzoneInput = (function() {
form.find(".markdown-selector").click(function(e) { form.find(".markdown-selector").click(function(e) {
e.preventDefault(); e.preventDefault();
$(this).closest('.gfm-form').find('.div-dropzone').click(); $(this).closest('.gfm-form').find('.div-dropzone').click();
form_textarea.focus();
}); });
} }
......
...@@ -35,6 +35,8 @@ export default { ...@@ -35,6 +35,8 @@ export default {
onClickAction(endpoint) { onClickAction(endpoint) {
this.isLoading = true; this.isLoading = true;
$(this.$refs.tooltip).tooltip('destroy');
this.service.postAction(endpoint) this.service.postAction(endpoint)
.then(() => { .then(() => {
this.isLoading = false; this.isLoading = false;
...@@ -62,6 +64,7 @@ export default { ...@@ -62,6 +64,7 @@ export default {
class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container has-tooltip" class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container has-tooltip"
data-container="body" data-container="body"
data-toggle="dropdown" data-toggle="dropdown"
ref="tooltip"
:title="title" :title="title"
:aria-label="title" :aria-label="title"
:disabled="isLoading"> :disabled="isLoading">
......
...@@ -21,7 +21,6 @@ export default { ...@@ -21,7 +21,6 @@ export default {
class="btn monitoring-url has-tooltip" class="btn monitoring-url has-tooltip"
data-container="body" data-container="body"
:href="monitoringUrl" :href="monitoringUrl"
target="_blank"
rel="noopener noreferrer nofollow" rel="noopener noreferrer nofollow"
:title="title" :title="title"
:aria-label="title"> :aria-label="title">
......
...@@ -36,6 +36,8 @@ export default { ...@@ -36,6 +36,8 @@ export default {
onClick() { onClick() {
this.isLoading = true; this.isLoading = true;
$(this.$el).tooltip('destroy');
this.service.postAction(this.retryUrl) this.service.postAction(this.retryUrl)
.then(() => { .then(() => {
this.isLoading = false; this.isLoading = false;
......
...@@ -36,6 +36,8 @@ export default { ...@@ -36,6 +36,8 @@ export default {
if (confirm('Are you sure you want to stop this environment?')) { if (confirm('Are you sure you want to stop this environment?')) {
this.isLoading = true; this.isLoading = true;
$(this.$el).tooltip('destroy');
this.service.postAction(this.retryUrl) this.service.postAction(this.retryUrl)
.then(() => { .then(() => {
this.isLoading = false; this.isLoading = false;
......
...@@ -2,8 +2,7 @@ import Filter from '~/droplab/plugins/filter'; ...@@ -2,8 +2,7 @@ import Filter from '~/droplab/plugins/filter';
require('./filtered_search_dropdown'); 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 = {
...@@ -76,8 +75,7 @@ require('./filtered_search_dropdown'); ...@@ -76,8 +75,7 @@ require('./filtered_search_dropdown');
init() { init() {
this.droplab.addHook(this.input, this.dropdown, [Filter], this.config).init(); this.droplab.addHook(this.input, this.dropdown, [Filter], this.config).init();
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.DropdownHint = DropdownHint; gl.DropdownHint = DropdownHint;
})();
...@@ -5,8 +5,7 @@ import Filter from '~/droplab/plugins/filter'; ...@@ -5,8 +5,7 @@ import Filter from '~/droplab/plugins/filter';
require('./filtered_search_dropdown'); require('./filtered_search_dropdown');
(() => { class DropdownNonUser extends gl.FilteredSearchDropdown {
class DropdownNonUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter, endpoint, symbol) { constructor(droplab, dropdown, input, filter, endpoint, symbol) {
super(droplab, dropdown, input, filter); super(droplab, dropdown, input, filter);
this.symbol = symbol; this.symbol = symbol;
...@@ -45,8 +44,7 @@ require('./filtered_search_dropdown'); ...@@ -45,8 +44,7 @@ require('./filtered_search_dropdown');
this.droplab this.droplab
.addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init(); .addHook(this.input, this.dropdown, [Ajax, Filter], this.config).init();
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.DropdownNonUser = DropdownNonUser; gl.DropdownNonUser = DropdownNonUser;
})();
...@@ -4,8 +4,7 @@ import AjaxFilter from '~/droplab/plugins/ajax_filter'; ...@@ -4,8 +4,7 @@ import AjaxFilter from '~/droplab/plugins/ajax_filter';
require('./filtered_search_dropdown'); 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 = {
...@@ -65,8 +64,7 @@ require('./filtered_search_dropdown'); ...@@ -65,8 +64,7 @@ require('./filtered_search_dropdown');
init() { init() {
this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init(); this.droplab.addHook(this.input, this.dropdown, [AjaxFilter], this.config).init();
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.DropdownUser = DropdownUser; gl.DropdownUser = DropdownUser;
})();
import FilteredSearchContainer from './container'; import FilteredSearchContainer from './container';
(() => { class DropdownUtils {
class DropdownUtils {
static getEscapedText(text) { static getEscapedText(text) {
let escapedText = text; let escapedText = text;
const hasSpace = text.indexOf(' ') !== -1; const hasSpace = text.indexOf(' ') !== -1;
...@@ -176,8 +175,7 @@ import FilteredSearchContainer from './container'; ...@@ -176,8 +175,7 @@ import FilteredSearchContainer from './container';
right, right,
}; };
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.DropdownUtils = DropdownUtils; gl.DropdownUtils = DropdownUtils;
})();
(() => { const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger';
const DATA_DROPDOWN_TRIGGER = 'data-dropdown-trigger';
class FilteredSearchDropdown { class FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) { constructor(droplab, dropdown, input, filter) {
this.droplab = droplab; this.droplab = droplab;
this.hookId = input && input.id; this.hookId = input && input.id;
...@@ -117,8 +116,7 @@ ...@@ -117,8 +116,7 @@
hook.list.render(results); hook.list.render(results);
} }
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.FilteredSearchDropdown = FilteredSearchDropdown; gl.FilteredSearchDropdown = FilteredSearchDropdown;
})();
import DropLab from '~/droplab/drop_lab'; import DropLab from '~/droplab/drop_lab';
import FilteredSearchContainer from './container'; import FilteredSearchContainer from './container';
(() => { class FilteredSearchDropdownManager {
class FilteredSearchDropdownManager {
constructor(baseEndpoint = '', page) { constructor(baseEndpoint = '', page) {
this.container = FilteredSearchContainer.container; this.container = FilteredSearchContainer.container;
this.baseEndpoint = baseEndpoint.replace(/\/$/, ''); this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
...@@ -184,8 +183,7 @@ import FilteredSearchContainer from './container'; ...@@ -184,8 +183,7 @@ import FilteredSearchContainer from './container';
destroyDroplab() { destroyDroplab() {
this.droplab.destroy(); this.droplab.destroy();
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.FilteredSearchDropdownManager = FilteredSearchDropdownManager; gl.FilteredSearchDropdownManager = FilteredSearchDropdownManager;
})();
...@@ -6,8 +6,7 @@ import RecentSearchesStore from './stores/recent_searches_store'; ...@@ -6,8 +6,7 @@ import RecentSearchesStore from './stores/recent_searches_store';
import RecentSearchesService from './services/recent_searches_service'; import RecentSearchesService from './services/recent_searches_service';
import eventHub from './event_hub'; import eventHub from './event_hub';
(() => { class FilteredSearchManager {
class FilteredSearchManager {
constructor(page) { constructor(page) {
this.container = FilteredSearchContainer.container; this.container = FilteredSearchContainer.container;
this.filteredSearchInput = this.container.querySelector('.filtered-search'); this.filteredSearchInput = this.container.querySelector('.filtered-search');
...@@ -487,8 +486,7 @@ import eventHub from './event_hub'; ...@@ -487,8 +486,7 @@ import eventHub from './event_hub';
this.filteredSearchInput.dispatchEvent(new CustomEvent('input')); this.filteredSearchInput.dispatchEvent(new CustomEvent('input'));
this.search(); this.search();
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.FilteredSearchManager = FilteredSearchManager; gl.FilteredSearchManager = FilteredSearchManager;
})();
(() => { const tokenKeys = [{
const tokenKeys = [{
key: 'author', key: 'author',
type: 'string', type: 'string',
param: 'username', param: 'username',
symbol: '@', symbol: '@',
}, { }, {
key: 'assignee', key: 'assignee',
type: 'string', type: 'string',
param: 'username', param: 'username',
symbol: '@', symbol: '@',
}, { }, {
key: 'milestone', key: 'milestone',
type: 'string', type: 'string',
param: 'title', param: 'title',
symbol: '%', symbol: '%',
}, { }, {
key: 'label', key: 'label',
type: 'array', type: 'array',
param: 'name[]', param: 'name[]',
symbol: '~', symbol: '~',
}]; }];
const alternativeTokenKeys = [{ const alternativeTokenKeys = [{
key: 'label', key: 'label',
type: 'string', type: 'string',
param: 'name', param: 'name',
symbol: '~', symbol: '~',
}]; }];
const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys); const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys);
const conditions = [{ const conditions = [{
url: 'assignee_id=0', url: 'assignee_id=0',
tokenKey: 'assignee', tokenKey: 'assignee',
value: 'none', value: 'none',
}, { }, {
url: 'milestone_title=No+Milestone', url: 'milestone_title=No+Milestone',
tokenKey: 'milestone', tokenKey: 'milestone',
value: 'none', value: 'none',
}, { }, {
url: 'milestone_title=%23upcoming', url: 'milestone_title=%23upcoming',
tokenKey: 'milestone', tokenKey: 'milestone',
value: 'upcoming', value: 'upcoming',
}, { }, {
url: 'milestone_title=%23started', url: 'milestone_title=%23started',
tokenKey: 'milestone', tokenKey: 'milestone',
value: 'started', value: 'started',
}, { }, {
url: 'label_name[]=No+Label', url: 'label_name[]=No+Label',
tokenKey: 'label', tokenKey: 'label',
value: 'none', value: 'none',
}]; }];
class FilteredSearchTokenKeys { class FilteredSearchTokenKeys {
static get() { static get() {
return tokenKeys; return tokenKeys;
} }
...@@ -93,8 +92,7 @@ ...@@ -93,8 +92,7 @@
return conditions return conditions
.find(condition => condition.tokenKey === key && condition.value === value) || null; .find(condition => condition.tokenKey === key && condition.value === value) || null;
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.FilteredSearchTokenKeys = FilteredSearchTokenKeys; gl.FilteredSearchTokenKeys = FilteredSearchTokenKeys;
})();
require('./filtered_search_token_keys'); require('./filtered_search_token_keys');
(() => { class FilteredSearchTokenizer {
class FilteredSearchTokenizer {
static processTokens(input) { static processTokens(input) {
const allowedKeys = gl.FilteredSearchTokenKeys.get().map(i => i.key); const allowedKeys = gl.FilteredSearchTokenKeys.get().map(i => i.key);
// Regex extracts `(token):(symbol)(value)` // Regex extracts `(token):(symbol)(value)`
...@@ -51,8 +50,7 @@ require('./filtered_search_token_keys'); ...@@ -51,8 +50,7 @@ require('./filtered_search_token_keys');
searchToken, searchToken,
}; };
} }
} }
window.gl = window.gl || {}; window.gl = window.gl || {};
gl.FilteredSearchTokenizer = FilteredSearchTokenizer; gl.FilteredSearchTokenizer = FilteredSearchTokenizer;
})();
import Vue from 'vue'; import Vue from 'vue';
import IssueTitle from './issue_title'; import IssueTitle from './issue_title.vue';
import '../vue_shared/vue_resource_interceptor'; import '../vue_shared/vue_resource_interceptor';
const vueOptions = () => ({ (() => {
el: '.issue-title-entrypoint',
components: {
IssueTitle,
},
data() {
const issueTitleData = document.querySelector('.issue-title-data').dataset; const issueTitleData = document.querySelector('.issue-title-data').dataset;
const { initialTitle, endpoint } = issueTitleData;
return { const vm = new Vue({
initialTitle: issueTitleData.initialTitle, el: '.issue-title-entrypoint',
endpoint: issueTitleData.endpoint, render: createElement => createElement(IssueTitle, {
}; props: {
initialTitle,
endpoint,
}, },
template: ` }),
<IssueTitle });
:initialTitle="initialTitle"
:endpoint="endpoint"
/>
`,
});
(() => new Vue(vueOptions()))(); return vm;
})();
<script>
import Visibility from 'visibilityjs'; import Visibility from 'visibilityjs';
import Poll from './../lib/utils/poll'; import Poll from './../lib/utils/poll';
import Service from './services/index'; import Service from './services/index';
...@@ -72,7 +73,9 @@ export default { ...@@ -72,7 +73,9 @@ export default {
created() { created() {
this.fetch(); this.fetch();
}, },
template: `
<h2 class='title' v-html='title'></h2>
`,
}; };
</script>
<template>
<h2 class="title" v-html="title"></h2>
</template>
...@@ -47,6 +47,10 @@ ...@@ -47,6 +47,10 @@
} }
}; };
gl.utils.updateTooltipTitle = function($tooltipEl, newTitle) {
return $tooltipEl.attr('title', newTitle).tooltip('fixTitle');
};
w.gl.utils.disableButtonIfEmptyField = function(field_selector, button_selector, event_name) { w.gl.utils.disableButtonIfEmptyField = function(field_selector, button_selector, event_name) {
event_name = event_name || 'input'; event_name = event_name || 'input';
var closest_submit, field, that; var closest_submit, field, that;
...@@ -364,9 +368,9 @@ ...@@ -364,9 +368,9 @@
}); });
}; };
w.gl.utils.setFavicon = (iconName) => { w.gl.utils.setFavicon = (faviconPath) => {
if (faviconEl && iconName) { if (faviconEl && faviconPath) {
faviconEl.setAttribute('href', `/assets/${iconName}.ico`); faviconEl.setAttribute('href', faviconPath);
} }
}; };
...@@ -381,8 +385,8 @@ ...@@ -381,8 +385,8 @@
url: pageUrl, url: pageUrl,
dataType: 'json', dataType: 'json',
success: function(data) { success: function(data) {
if (data && data.icon) { if (data && data.favicon) {
gl.utils.setFavicon(`ci_favicons/${data.icon}`); gl.utils.setFavicon(data.favicon);
} else { } else {
gl.utils.resetFavicon(); gl.utils.resetFavicon();
} }
......
/* eslint-disable import/prefer-default-export */
export const BYTES_IN_KIB = 1024;
/* eslint-disable import/prefer-default-export */ import { BYTES_IN_KIB } from './constants';
/** /**
* Function that allows a number with an X amount of decimals * Function that allows a number with an X amount of decimals
...@@ -32,3 +32,13 @@ export function formatRelevantDigits(number) { ...@@ -32,3 +32,13 @@ export function formatRelevantDigits(number) {
} }
return formattedNumber; return formattedNumber;
} }
/**
* Utility function that calculates KiB of the given bytes.
*
* @param {Number} number bytes
* @return {Number} KiB
*/
export function bytesToKiB(number) {
return number / BYTES_IN_KIB;
}
...@@ -165,6 +165,7 @@ import './syntax_highlight'; ...@@ -165,6 +165,7 @@ import './syntax_highlight';
import './task_list'; import './task_list';
import './todos'; import './todos';
import './tree'; import './tree';
import './usage_ping';
import './user'; import './user';
import './user_tabs'; import './user_tabs';
import './username_validator'; import './username_validator';
...@@ -210,6 +211,14 @@ $(function () { ...@@ -210,6 +211,14 @@ $(function () {
} }
}); });
if (bootstrapBreakpoint === 'xs') {
const $rightSidebar = $('aside.right-sidebar, .page-with-sidebar');
$rightSidebar
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed');
}
// prevent default action for disabled buttons // prevent default action for disabled buttons
$('.btn').click(function(e) { $('.btn').click(function(e) {
if ($(this).hasClass('disabled')) { if ($(this).hasClass('disabled')) {
......
...@@ -308,8 +308,10 @@ require('./task_list'); ...@@ -308,8 +308,10 @@ require('./task_list');
if (this.isNewNote(note)) { if (this.isNewNote(note)) {
this.note_ids.push(note.id); this.note_ids.push(note.id);
$notesList = $('ul.main-notes-list');
$notesList.append(note.html).syntaxHighlight(); $notesList = window.$('ul.main-notes-list');
Notes.animateAppendNote(note.html, $notesList);
// Update datetime format on the recent note // Update datetime format on the recent note
gl.utils.localTimeAgo($notesList.find("#note_" + note.id + " .js-timeago"), false); gl.utils.localTimeAgo($notesList.find("#note_" + note.id + " .js-timeago"), false);
this.collapseLongCommitList(); this.collapseLongCommitList();
...@@ -348,7 +350,7 @@ require('./task_list'); ...@@ -348,7 +350,7 @@ require('./task_list');
lineType = this.isParallelView() ? form.find('#line_type').val() : 'old'; lineType = this.isParallelView() ? form.find('#line_type').val() : 'old';
diffAvatarContainer = row.prevAll('.line_holder').first().find('.js-avatar-container.' + lineType + '_line'); diffAvatarContainer = row.prevAll('.line_holder').first().find('.js-avatar-container.' + lineType + '_line');
// is this the first note of discussion? // is this the first note of discussion?
discussionContainer = $(".notes[data-discussion-id='" + note.discussion_id + "']"); discussionContainer = window.$(`.notes[data-discussion-id="${note.discussion_id}"]`);
if (!discussionContainer.length) { if (!discussionContainer.length) {
discussionContainer = form.closest('.discussion').find('.notes'); discussionContainer = form.closest('.discussion').find('.notes');
} }
...@@ -370,14 +372,13 @@ require('./task_list'); ...@@ -370,14 +372,13 @@ require('./task_list');
row.find(contentContainerClass + ' .content').append($notes.closest('.content').children()); row.find(contentContainerClass + ' .content').append($notes.closest('.content').children());
} }
} }
// Init discussion on 'Discussion' page if it is merge request page // Init discussion on 'Discussion' page if it is merge request page
if ($('body').attr('data-page').indexOf('projects:merge_request') === 0 || !note.diff_discussion_html) { if (window.$('body').attr('data-page').indexOf('projects:merge_request') === 0 || !note.diff_discussion_html) {
$('ul.main-notes-list').append($(note.discussion_html).renderGFM()); Notes.animateAppendNote(note.discussion_html, window.$('ul.main-notes-list'));
} }
} else { } else {
// append new note to all matching discussions // append new note to all matching discussions
discussionContainer.append($(note.html).renderGFM()); Notes.animateAppendNote(note.html, discussionContainer);
} }
if (typeof gl.diffNotesCompileComponents !== 'undefined' && note.discussion_resolvable) { if (typeof gl.diffNotesCompileComponents !== 'undefined' && note.discussion_resolvable) {
...@@ -1063,6 +1064,13 @@ require('./task_list'); ...@@ -1063,6 +1064,13 @@ require('./task_list');
return $form; return $form;
}; };
Notes.animateAppendNote = function(noteHTML, $notesList) {
const $note = window.$(noteHTML);
$note.addClass('fade-in').renderGFM();
$notesList.append($note);
};
return Notes; return Notes;
})(); })();
}).call(window); }).call(window);
...@@ -65,6 +65,8 @@ export default { ...@@ -65,6 +65,8 @@ export default {
makeRequest() { makeRequest() {
this.isLoading = true; this.isLoading = true;
$(this.$el).tooltip('destroy');
this.service.postAction(this.endpoint) this.service.postAction(this.endpoint)
.then(() => { .then(() => {
this.isLoading = false; this.isLoading = false;
...@@ -88,9 +90,13 @@ export default { ...@@ -88,9 +90,13 @@ export default {
:aria-label="title" :aria-label="title"
data-container="body" data-container="body"
data-placement="top" data-placement="top"
:disabled="isLoading" :disabled="isLoading">
> <i
<i :class="iconClass" aria-hidden="true"></i> :class="iconClass"
<i class="fa fa-spinner fa-spin" aria-hidden="true" v-if="isLoading"></i> aria-hidden="true" />
<i
class="fa fa-spinner fa-spin"
aria-hidden="true"
v-if="isLoading" />
</button> </button>
</template> </template>
...@@ -28,6 +28,8 @@ export default { ...@@ -28,6 +28,8 @@ export default {
onClickAction(endpoint) { onClickAction(endpoint) {
this.isLoading = true; this.isLoading = true;
$(this.$refs.tooltip).tooltip('destroy');
this.service.postAction(endpoint) this.service.postAction(endpoint)
.then(() => { .then(() => {
this.isLoading = false; this.isLoading = false;
...@@ -57,6 +59,7 @@ export default { ...@@ -57,6 +59,7 @@ export default {
data-toggle="dropdown" data-toggle="dropdown"
data-placement="top" data-placement="top"
aria-label="Manual job" aria-label="Manual job"
ref="tooltip"
:disabled="isLoading"> :disabled="isLoading">
${playIconSvg} ${playIconSvg}
<i <i
......
/* global Flash */ /* global Flash */
import canceledSvg from 'icons/_icon_status_canceled_borderless.svg'; import StatusIconEntityMap from '../../ci_status_icons';
import createdSvg from 'icons/_icon_status_created_borderless.svg';
import failedSvg from 'icons/_icon_status_failed_borderless.svg';
import manualSvg from 'icons/_icon_status_manual_borderless.svg';
import pendingSvg from 'icons/_icon_status_pending_borderless.svg';
import runningSvg from 'icons/_icon_status_running_borderless.svg';
import skippedSvg from 'icons/_icon_status_skipped_borderless.svg';
import successSvg from 'icons/_icon_status_success_borderless.svg';
import warningSvg from 'icons/_icon_status_warning_borderless.svg';
export default { export default {
data() { data() {
const svgsDictionary = {
icon_status_canceled: canceledSvg,
icon_status_created: createdSvg,
icon_status_failed: failedSvg,
icon_status_manual: manualSvg,
icon_status_pending: pendingSvg,
icon_status_running: runningSvg,
icon_status_skipped: skippedSvg,
icon_status_success: successSvg,
icon_status_warning: warningSvg,
};
return { return {
builds: '', builds: '',
spinner: '<span class="fa fa-spinner fa-spin"></span>', spinner: '<span class="fa fa-spinner fa-spin"></span>',
svg: svgsDictionary[this.stage.status.icon],
}; };
}, },
...@@ -89,6 +68,9 @@ export default { ...@@ -89,6 +68,9 @@ export default {
triggerButtonClass() { triggerButtonClass() {
return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`; return `mini-pipeline-graph-dropdown-toggle has-tooltip js-builds-dropdown-button ci-status-icon-${this.stage.status.group}`;
}, },
svgHTML() {
return StatusIconEntityMap[this.stage.status.icon];
},
}, },
template: ` template: `
<div> <div>
...@@ -100,7 +82,7 @@ export default { ...@@ -100,7 +82,7 @@ export default {
data-toggle="dropdown" data-toggle="dropdown"
type="button" type="button"
:aria-label="stage.title"> :aria-label="stage.title">
<span v-html="svg" aria-hidden="true"></span> <span v-html="svgHTML" aria-hidden="true"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i> <i class="fa fa-caret-down" aria-hidden="true"></i>
</button> </button>
<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"> <ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
......
/* eslint-disable class-methods-use-this */
/* global Mousetrap */
/* global ShortcutsNavigation */
import findAndFollowLink from './shortcuts_dashboard_navigation';
export default class ShortcutsWiki extends ShortcutsNavigation {
constructor() {
super();
Mousetrap.bind('e', this.editWiki);
}
editWiki() {
findAndFollowLink('.js-wiki-edit');
}
}
function UsagePing() {
const usageDataUrl = $('.usage-data').data('endpoint');
$.ajax({
type: 'GET',
url: usageDataUrl,
dataType: 'html',
success(html) {
$('.usage-data').html(html);
},
});
}
window.gl = window.gl || {};
window.gl.UsagePing = UsagePing;
...@@ -18,7 +18,7 @@ export default class UserCallout { ...@@ -18,7 +18,7 @@ export default class UserCallout {
dismissCallout(e) { dismissCallout(e) {
const $currentTarget = $(e.currentTarget); const $currentTarget = $(e.currentTarget);
Cookies.set(USER_CALLOUT_COOKIE, 'true'); Cookies.set(USER_CALLOUT_COOKIE, 'true', { expires: 365 });
if ($currentTarget.hasClass('close')) { if ($currentTarget.hasClass('close')) {
this.userCalloutBody.remove(); this.userCalloutBody.remove();
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import AsyncButtonComponent from '../../vue_pipelines_index/components/async_button.vue'; import AsyncButtonComponent from '../../pipelines/components/async_button.vue';
import PipelinesActionsComponent from '../../vue_pipelines_index/components/pipelines_actions'; import PipelinesActionsComponent from '../../pipelines/components/pipelines_actions';
import PipelinesArtifactsComponent from '../../vue_pipelines_index/components/pipelines_artifacts'; import PipelinesArtifactsComponent from '../../pipelines/components/pipelines_artifacts';
import PipelinesStatusComponent from '../../vue_pipelines_index/components/status'; import PipelinesStatusComponent from '../../pipelines/components/status';
import PipelinesStageComponent from '../../vue_pipelines_index/components/stage'; import PipelinesStageComponent from '../../pipelines/components/stage';
import PipelinesUrlComponent from '../../vue_pipelines_index/components/pipeline_url'; import PipelinesUrlComponent from '../../pipelines/components/pipeline_url';
import PipelinesTimeagoComponent from '../../vue_pipelines_index/components/time_ago'; import PipelinesTimeagoComponent from '../../pipelines/components/time_ago';
import CommitComponent from './commit'; import CommitComponent from './commit';
/** /**
......
...@@ -145,3 +145,17 @@ a { ...@@ -145,3 +145,17 @@ a {
.dropdown-menu-nav a { .dropdown-menu-nav a {
transition: none; transition: none;
} }
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.fade-in {
animation: fadeIn $fade-in-duration 1;
}
...@@ -38,6 +38,15 @@ ...@@ -38,6 +38,15 @@
height: 300px; height: 300px;
overflow-y: scroll; overflow-y: scroll;
} }
.disabled {
cursor: default;
opacity: 0.5;
&:hover {
transform: none;
}
}
} }
.emoji-search { .emoji-search {
...@@ -154,6 +163,17 @@ ...@@ -154,6 +163,17 @@
} }
} }
&.user-authored {
cursor: default;
opacity: 0.65;
&:hover,
&:active {
background-color: $white-light;
border-color: $border-color;
}
}
&.btn { &.btn {
&:focus { &:focus {
outline: 0; outline: 0;
......
...@@ -40,6 +40,10 @@ ...@@ -40,6 +40,10 @@
line-height: 24px; line-height: 24px;
} }
.bold {
font-weight: 600;
}
.tab-content { .tab-content {
overflow: visible; overflow: visible;
} }
......
...@@ -564,3 +564,7 @@ ...@@ -564,3 +564,7 @@
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
} }
} }
.droplab-item-ignore {
pointer-events: none;
}
...@@ -155,7 +155,7 @@ header { ...@@ -155,7 +155,7 @@ header {
.header-logo { .header-logo {
display: inline-block; display: inline-block;
margin: 0 7px 0 2px; margin: 0 12px 0 2px;
position: relative; position: relative;
top: 10px; top: 10px;
transition-duration: .3s; transition-duration: .3s;
...@@ -186,7 +186,7 @@ header { ...@@ -186,7 +186,7 @@ header {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
flex: 1 1 auto; flex: 1 1 auto;
padding-top: (($header-height - 19) / 2); padding-top: 14px;
overflow: hidden; overflow: hidden;
} }
...@@ -331,6 +331,14 @@ header { ...@@ -331,6 +331,14 @@ header {
.dropdown-menu-nav { .dropdown-menu-nav {
min-width: 140px; min-width: 140px;
margin-top: -5px; margin-top: -5px;
.current-user {
padding: 5px 18px;
.user-name {
display: block;
}
}
} }
} }
......
...@@ -457,6 +457,11 @@ $label-inverse-bg: #333; ...@@ -457,6 +457,11 @@ $label-inverse-bg: #333;
$label-remove-border: rgba(0, 0, 0, .1); $label-remove-border: rgba(0, 0, 0, .1);
$label-border-radius: 100px; $label-border-radius: 100px;
/*
* Animation
*/
$fade-in-duration: 200ms;
/* /*
* Lint * Lint
*/ */
......
...@@ -61,8 +61,9 @@ ...@@ -61,8 +61,9 @@
.truncated-info { .truncated-info {
text-align: center; text-align: center;
border-bottom: 1px solid; border-bottom: 1px solid;
background-color: $black-transparent; background-color: $black;
height: 45px; height: 45px;
padding: 15px;
&.affix { &.affix {
top: 0; top: 0;
...@@ -87,6 +88,16 @@ ...@@ -87,6 +88,16 @@
right: 5px; right: 5px;
left: 5px; left: 5px;
} }
.truncated-info-size {
margin: 0 5px;
}
.raw-link {
color: inherit;
margin-left: 5px;
text-decoration: underline;
}
} }
} }
......
...@@ -135,7 +135,7 @@ ...@@ -135,7 +135,7 @@
.text-expander { .text-expander {
display: inline-block; display: inline-block;
background: $gray-light; background: $white-light;
color: $gl-text-color-secondary; color: $gl-text-color-secondary;
padding: 0 5px; padding: 0 5px;
cursor: pointer; cursor: pointer;
...@@ -146,6 +146,11 @@ ...@@ -146,6 +146,11 @@
line-height: $gl-font-size; line-height: $gl-font-size;
outline: none; outline: none;
&.open {
background: $gray-light;
box-shadow: inset 0 0 2px rgba($black, 0.2);
}
&:hover { &:hover {
background-color: darken($gray-light, 10%); background-color: darken($gray-light, 10%);
text-decoration: none; text-decoration: none;
......
...@@ -106,6 +106,10 @@ ...@@ -106,6 +106,10 @@
span { span {
white-space: pre-wrap; white-space: pre-wrap;
} }
.line {
word-wrap: break-word;
}
} }
} }
......
...@@ -210,10 +210,6 @@ ...@@ -210,10 +210,6 @@
} }
} }
.bold {
font-weight: 600;
}
.light { .light {
font-weight: normal; font-weight: normal;
} }
......
...@@ -123,6 +123,9 @@ ul.notes { ...@@ -123,6 +123,9 @@ ul.notes {
} }
.note-emoji-button { .note-emoji-button {
position: relative;
line-height: 1;
.fa-spinner { .fa-spinner {
display: none; display: none;
} }
...@@ -352,6 +355,15 @@ ul.notes { ...@@ -352,6 +355,15 @@ ul.notes {
font-size: 14px; font-size: 14px;
} }
.note-header {
display: flex;
justify-content: space-between;
}
.note-header-info {
min-width: 0;
}
.note-headline-light { .note-headline-light {
display: inline; display: inline;
...@@ -371,21 +383,27 @@ ul.notes { ...@@ -371,21 +383,27 @@ ul.notes {
} }
} }
.note-headline-meta {
display: inline-block;
white-space: nowrap;
}
/** /**
* Actions for Discussions/Notes * Actions for Discussions/Notes
*/ */
.discussion-actions, .discussion-actions {
.note-actions {
float: right; float: right;
margin-left: 10px; margin-left: 10px;
color: $gray-darkest; color: $gray-darkest;
} }
.note-actions { .note-actions {
position: absolute; flex-shrink: 0;
right: 0; // For PhantomJS that does not support flex
top: 0; float: right;
margin-left: 10px;
color: $gray-darkest;
.note-action-button { .note-action-button {
margin-left: 8px; margin-left: 8px;
...@@ -428,7 +446,8 @@ ul.notes { ...@@ -428,7 +446,8 @@ ul.notes {
.award-control-icon-positive, .award-control-icon-positive,
.award-control-icon-super-positive { .award-control-icon-super-positive {
position: absolute; position: absolute;
margin-left: -20px; top: 0;
left: 0;
opacity: 0; opacity: 0;
} }
......
...@@ -289,8 +289,12 @@ table.u2f-registrations { ...@@ -289,8 +289,12 @@ table.u2f-registrations {
margin: 0 auto; margin: 0 auto;
.bordered-box { .bordered-box {
border: 1px solid $border-color; border: 1px solid $blue-300;
border-radius: $border-radius-default; border-radius: $border-radius-default;
background-color: $blue-25;
position: relative;
display: flex;
justify-content: center;
} }
.landing { .landing {
...@@ -298,28 +302,59 @@ table.u2f-registrations { ...@@ -298,28 +302,59 @@ table.u2f-registrations {
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
.close { .close {
margin-right: 20px; position: absolute;
} right: 20px;
opacity: 1;
.dismiss-icon { .dismiss-icon {
float: right; float: right;
cursor: pointer; cursor: pointer;
color: $cycle-analytics-dismiss-icon-color; color: $blue-300;
}
&:hover {
background-color: transparent;
border: 0;
.dismiss-icon {
color: $blue-400;
}
}
} }
.svg-container { .svg-container {
text-align: center; margin-right: 30px;
display: inline-block;
svg { svg {
width: 136px; height: 110px;
height: 136px; vertical-align: top;
} }
} }
.user-callout-copy {
display: inline-block;
vertical-align: top;
}
} }
@media(max-width: $screen-xs-max) { @media(max-width: $screen-xs-max) {
.inner-content { text-align: center;
padding-left: 30px;
.bordered-box {
display: block;
}
.landing {
.svg-container,
.user-callout-copy {
margin: 0;
display: block;
svg {
height: 75px;
}
}
} }
} }
} }
...@@ -596,6 +596,10 @@ pre.light-well { ...@@ -596,6 +596,10 @@ pre.light-well {
.avatar-container { .avatar-container {
align-self: flex-start; align-self: flex-start;
> a {
width: 100%;
}
} }
.project-details { .project-details {
...@@ -929,27 +933,23 @@ pre.light-well { ...@@ -929,27 +933,23 @@ pre.light-well {
} }
.variable-key { .variable-key {
width: 300px; max-width: 120px;
max-width: 300px;
overflow: hidden; overflow: hidden;
word-wrap: break-word; word-wrap: break-word;
white-space: nowrap;
// override bootstrap text-overflow: ellipsis;
white-space: normal!important;
@media (max-width: $screen-sm-max) {
width: 150px;
max-width: 150px;
}
} }
.variable-value { .variable-value {
@media(max-width: $screen-xs-max) {
width: 150px;
max-width: 150px; max-width: 150px;
overflow: hidden; overflow: hidden;
word-wrap: break-word; word-wrap: break-word;
white-space: nowrap;
text-overflow: ellipsis;
} }
.variable-menu {
text-align: right;
} }
} }
......
...@@ -17,6 +17,18 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -17,6 +17,18 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end end
end end
def usage_data
respond_to do |format|
format.html do
usage_data = Gitlab::UsageData.data
usage_data_json = params[:pretty] ? JSON.pretty_generate(usage_data) : usage_data.to_json
render html: Gitlab::Highlight.highlight('payload.json', usage_data_json)
end
format.json { render json: Gitlab::UsageData.to_json }
end
end
def reset_runners_token def reset_runners_token
@application_setting.reset_runners_registration_token! @application_setting.reset_runners_registration_token!
flash[:notice] = 'New runners registration token has been generated!' flash[:notice] = 'New runners registration token has been generated!'
...@@ -135,6 +147,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -135,6 +147,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:version_check_enabled, :version_check_enabled,
:terminal_max_session_time, :terminal_max_session_time,
:polling_interval_multiplier, :polling_interval_multiplier,
:usage_ping_enabled,
disabled_oauth_sign_in_sources: [], disabled_oauth_sign_in_sources: [],
import_sources: [], import_sources: [],
......
class Admin::CohortsController < Admin::ApplicationController
def index
if current_application_settings.usage_ping_enabled
cohorts_results = Rails.cache.fetch('cohorts', expires_in: 1.day) do
CohortsService.new.execute
end
@cohorts = CohortsSerializer.new.represent(cohorts_results)
end
end
end
...@@ -7,7 +7,7 @@ class Admin::SpamLogsController < Admin::ApplicationController ...@@ -7,7 +7,7 @@ class Admin::SpamLogsController < Admin::ApplicationController
spam_log = SpamLog.find(params[:id]) spam_log = SpamLog.find(params[:id])
if params[:remove_user] if params[:remove_user]
spam_log.remove_user spam_log.remove_user(deleted_by: current_user)
redirect_to admin_spam_logs_path, notice: "User #{spam_log.user.username} was successfully removed." redirect_to admin_spam_logs_path, notice: "User #{spam_log.user.username} was successfully removed."
else else
spam_log.destroy spam_log.destroy
......
...@@ -269,6 +269,7 @@ class ApplicationController < ActionController::Base ...@@ -269,6 +269,7 @@ class ApplicationController < ActionController::Base
def set_locale def set_locale
requested_locale = current_user&.preferred_language || request.env['HTTP_ACCEPT_LANGUAGE'] || I18n.default_locale requested_locale = current_user&.preferred_language || request.env['HTTP_ACCEPT_LANGUAGE'] || I18n.default_locale
locale = FastGettext.set_locale(requested_locale) locale = FastGettext.set_locale(requested_locale)
I18n.locale = locale I18n.locale = locale
end end
end end
module CreatesCommit module CreatesCommit
extend ActiveSupport::Concern extend ActiveSupport::Concern
def set_start_branch_to_branch_name
branch_exists = @repository.find_branch(@branch_name)
@start_branch = @branch_name if branch_exists
end
def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil) def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil)
set_commit_variables if can?(current_user, :push_code, @project)
@project_to_commit_into = @project
@branch_name ||= @ref
else
@project_to_commit_into = current_user.fork_of(@project)
@branch_name ||= @project_to_commit_into.repository.next_branch('patch')
end
@start_branch ||= @ref || @branch_name
commit_params = @commit_params.merge( commit_params = @commit_params.merge(
start_project: @mr_target_project, start_project: @project,
start_branch: @mr_target_branch, start_branch: @start_branch,
target_branch: @mr_source_branch branch_name: @branch_name
) )
result = service.new( result = service.new(@project_to_commit_into, current_user, commit_params).execute
@mr_source_project, current_user, commit_params).execute
if result[:status] == :success if result[:status] == :success
update_flash_notice(success_notice) update_flash_notice(success_notice)
...@@ -72,30 +84,30 @@ module CreatesCommit ...@@ -72,30 +84,30 @@ module CreatesCommit
def new_merge_request_path def new_merge_request_path
new_namespace_project_merge_request_path( new_namespace_project_merge_request_path(
@mr_source_project.namespace, @project_to_commit_into.namespace,
@mr_source_project, @project_to_commit_into,
merge_request: { merge_request: {
source_project_id: @mr_source_project.id, source_project_id: @project_to_commit_into.id,
target_project_id: @mr_target_project.id, target_project_id: @project.id,
source_branch: @mr_source_branch, source_branch: @branch_name,
target_branch: @mr_target_branch target_branch: @start_branch
} }
) )
end end
def existing_merge_request_path def existing_merge_request_path
namespace_project_merge_request_path(@mr_target_project.namespace, @mr_target_project, @merge_request) namespace_project_merge_request_path(@project.namespace, @project, @merge_request)
end end
def merge_request_exists? def merge_request_exists?
return @merge_request if defined?(@merge_request) return @merge_request if defined?(@merge_request)
@merge_request = MergeRequestsFinder.new(current_user, project_id: @mr_target_project.id).execute.opened. @merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened.
find_by(source_branch: @mr_source_branch, target_branch: @mr_target_branch, source_project_id: @mr_source_project) find_by(source_project_id: @project_to_commit_into, source_branch: @branch_name, target_branch: @start_branch)
end end
def different_project? def different_project?
@mr_source_project != @mr_target_project @project_to_commit_into != @project
end end
def create_merge_request? def create_merge_request?
...@@ -103,22 +115,6 @@ module CreatesCommit ...@@ -103,22 +115,6 @@ module CreatesCommit
# as the target branch in the same project, # as the target branch in the same project,
# we don't want to create a merge request. # we don't want to create a merge request.
params[:create_merge_request].present? && params[:create_merge_request].present? &&
(different_project? || @mr_target_branch != @mr_source_branch) (different_project? || @start_branch != @branch_name)
end
def set_commit_variables
if can?(current_user, :push_code, @project)
@mr_source_project = @project
@target_branch ||= @ref
else
@mr_source_project = current_user.fork_of(@project)
@target_branch ||= @mr_source_project.repository.next_branch('patch')
end
# Merge request to this project
@mr_target_project = @project
@mr_target_branch ||= @ref || @target_branch
@mr_source_branch = @target_branch
end end
end end
...@@ -89,9 +89,4 @@ class Projects::ApplicationController < ApplicationController ...@@ -89,9 +89,4 @@ class Projects::ApplicationController < ApplicationController
def builds_enabled def builds_enabled
return render_404 unless @project.feature_available?(:builds, current_user) return render_404 unless @project.feature_available?(:builds, current_user)
end end
def update_ref
branch_exists = @repository.find_branch(@target_branch)
@ref = @target_branch if branch_exists
end
end end
...@@ -25,10 +25,10 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -25,10 +25,10 @@ class Projects::BlobController < Projects::ApplicationController
end end
def create def create
update_ref set_start_branch_to_branch_name
create_commit(Files::CreateService, success_notice: "The file has been successfully created.", create_commit(Files::CreateService, success_notice: "The file has been successfully created.",
success_path: -> { namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) }, success_path: -> { namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @file_path)) },
failure_view: :new, failure_view: :new,
failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref)) failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref))
end end
...@@ -69,8 +69,8 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -69,8 +69,8 @@ class Projects::BlobController < Projects::ApplicationController
end end
def destroy def destroy
create_commit(Files::DestroyService, success_notice: "The file has been successfully deleted.", create_commit(Files::DeleteService, success_notice: "The file has been successfully deleted.",
success_path: -> { namespace_project_tree_path(@project.namespace, @project, @target_branch) }, success_path: -> { namespace_project_tree_path(@project.namespace, @project, @branch_name) },
failure_view: :show, failure_view: :show,
failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
end end
...@@ -127,16 +127,16 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -127,16 +127,16 @@ class Projects::BlobController < Projects::ApplicationController
def after_edit_path def after_edit_path
from_merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:from_merge_request_iid]) from_merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.find_by(iid: params[:from_merge_request_iid])
if from_merge_request && @target_branch == @ref if from_merge_request && @branch_name == @ref
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
"##{hexdigest(@path)}" "##{hexdigest(@path)}"
else else
namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path)) namespace_project_blob_path(@project.namespace, @project, File.join(@branch_name, @path))
end end
end end
def editor_variables def editor_variables
@target_branch = params[:target_branch] @branch_name = params[:branch_name]
@file_path = @file_path =
if action_name.to_s == 'create' if action_name.to_s == 'create'
......
...@@ -56,9 +56,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -56,9 +56,7 @@ class Projects::CommitController < Projects::ApplicationController
return render_404 if @start_branch.blank? return render_404 if @start_branch.blank?
@target_branch = create_new_branch? ? @commit.revert_branch_name : @start_branch @branch_name = create_new_branch? ? @commit.revert_branch_name : @start_branch
@mr_target_branch = @start_branch
create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully reverted.", create_commit(Commits::RevertService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully reverted.",
success_path: -> { successful_change_path }, failure_path: failed_change_path) success_path: -> { successful_change_path }, failure_path: failed_change_path)
...@@ -69,9 +67,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -69,9 +67,7 @@ class Projects::CommitController < Projects::ApplicationController
return render_404 if @start_branch.blank? return render_404 if @start_branch.blank?
@target_branch = create_new_branch? ? @commit.cherry_pick_branch_name : @start_branch @branch_name = create_new_branch? ? @commit.cherry_pick_branch_name : @start_branch
@mr_target_branch = @start_branch
create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully cherry-picked.", create_commit(Commits::CherryPickService, success_notice: "The #{@commit.change_type_title(current_user)} has been successfully cherry-picked.",
success_path: -> { successful_change_path }, failure_path: failed_change_path) success_path: -> { successful_change_path }, failure_path: failed_change_path)
...@@ -84,7 +80,7 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -84,7 +80,7 @@ class Projects::CommitController < Projects::ApplicationController
end end
def successful_change_path def successful_change_path
referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @target_branch) referenced_merge_request_url || namespace_project_commits_url(@project.namespace, @project, @branch_name)
end end
def failed_change_path def failed_change_path
......
...@@ -5,6 +5,8 @@ class Projects::GitHttpController < Projects::GitHttpClientController ...@@ -5,6 +5,8 @@ class Projects::GitHttpController < Projects::GitHttpClientController
# GET /foo/bar.git/info/refs?service=git-receive-pack (git push) # GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
def info_refs def info_refs
if upload_pack? && upload_pack_allowed? if upload_pack? && upload_pack_allowed?
log_user_activity
render_ok render_ok
elsif receive_pack? && receive_pack_allowed? elsif receive_pack? && receive_pack_allowed?
render_ok render_ok
...@@ -106,4 +108,8 @@ class Projects::GitHttpController < Projects::GitHttpClientController ...@@ -106,4 +108,8 @@ class Projects::GitHttpController < Projects::GitHttpClientController
def access_klass def access_klass
@access_klass ||= wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess @access_klass ||= wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
end end
def log_user_activity
Users::ActivityService.new(user, 'pull').execute
end
end end
...@@ -34,16 +34,16 @@ class Projects::TreeController < Projects::ApplicationController ...@@ -34,16 +34,16 @@ class Projects::TreeController < Projects::ApplicationController
def create_dir def create_dir
return render_404 unless @commit_params.values.all? return render_404 unless @commit_params.values.all?
update_ref set_start_branch_to_branch_name
create_commit(Files::CreateDirService, success_notice: "The directory has been successfully created.", create_commit(Files::CreateDirService, success_notice: "The directory has been successfully created.",
success_path: namespace_project_tree_path(@project.namespace, @project, File.join(@target_branch, @dir_name)), success_path: namespace_project_tree_path(@project.namespace, @project, File.join(@branch_name, @dir_name)),
failure_path: namespace_project_tree_path(@project.namespace, @project, @ref)) failure_path: namespace_project_tree_path(@project.namespace, @project, @ref))
end end
private private
def assign_dir_vars def assign_dir_vars
@target_branch = params[:target_branch] @branch_name = params[:branch_name]
@dir_name = File.join(@path, params[:dir_name]) @dir_name = File.join(@path, params[:dir_name])
@commit_params = { @commit_params = {
......
...@@ -35,6 +35,7 @@ class SessionsController < Devise::SessionsController ...@@ -35,6 +35,7 @@ class SessionsController < Devise::SessionsController
# hide the signed-in notification # hide the signed-in notification
flash[:notice] = nil flash[:notice] = nil
log_audit_event(current_user, with: authentication_method) log_audit_event(current_user, with: authentication_method)
log_user_activity(current_user)
end end
end end
...@@ -123,6 +124,10 @@ class SessionsController < Devise::SessionsController ...@@ -123,6 +124,10 @@ class SessionsController < Devise::SessionsController
for_authentication.security_event for_authentication.security_event
end end
def log_user_activity(user)
Users::ActivityService.new(user, 'login').execute
end
def load_recaptcha def load_recaptcha
Gitlab::Recaptcha.load_configurations! Gitlab::Recaptcha.load_configurations!
end end
......
...@@ -34,7 +34,7 @@ module BlobHelper ...@@ -34,7 +34,7 @@ module BlobHelper
if !on_top_of_branch?(project, ref) if !on_top_of_branch?(project, ref)
button_tag 'Edit', class: "#{common_classes} disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' } button_tag 'Edit', class: "#{common_classes} disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
# This condition applies to anonymous or users who can edit directly # This condition applies to anonymous or users who can edit directly
elsif !current_user || (current_user && can_edit_blob?(blob, project, ref)) elsif !current_user || (current_user && can_modify_blob?(blob, project, ref))
link_to 'Edit', edit_path(project, ref, path, options), class: "#{common_classes} btn-sm" link_to 'Edit', edit_path(project, ref, path, options), class: "#{common_classes} btn-sm"
elsif current_user && can?(current_user, :fork_project, project) elsif current_user && can?(current_user, :fork_project, project)
button_tag 'Edit', class: "#{common_classes} js-edit-blob-link-fork-toggler" button_tag 'Edit', class: "#{common_classes} js-edit-blob-link-fork-toggler"
...@@ -52,7 +52,7 @@ module BlobHelper ...@@ -52,7 +52,7 @@ module BlobHelper
button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' } button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "You can only #{action} files when you are on a branch", data: { container: 'body' }
elsif blob.lfs_pointer? elsif blob.lfs_pointer?
button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' } button_tag label, class: "btn btn-#{btn_class} disabled has-tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref) elsif can_modify_blob?(blob, project, ref)
button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal' button_tag label, class: "btn btn-#{btn_class}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
elsif can?(current_user, :fork_project, project) elsif can?(current_user, :fork_project, project)
continue_params = { continue_params = {
...@@ -90,7 +90,7 @@ module BlobHelper ...@@ -90,7 +90,7 @@ module BlobHelper
) )
end end
def can_edit_blob?(blob, project = @project, ref = @ref) def can_modify_blob?(blob, project = @project, ref = @ref)
!blob.lfs_pointer? && can_edit_tree?(project, ref) !blob.lfs_pointer? && can_edit_tree?(project, ref)
end end
......
...@@ -110,6 +110,14 @@ module IssuesHelper ...@@ -110,6 +110,14 @@ module IssuesHelper
end end
end end
def award_user_authored_class(award)
if award == 'thumbsdown' || award == 'thumbsup'
'user-authored js-user-authored'
else
''
end
end
def awards_sort(awards) def awards_sort(awards)
awards.sort_by do |award, notes| awards.sort_by do |award, notes|
if award == "thumbsup" if award == "thumbsup"
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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