Commit 35d28045 authored by Phil Hughes's avatar Phil Hughes

Merge branch 'master' into ide-staged-changes

parents cb197ceb a56611e3
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git-2.16-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6"
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.7-golang-1.9-git-2.17-chrome-63.0-node-8.x-yarn-1.2-postgresql-9.6"
.dedicated-runner: &dedicated-runner
retry: 1
......@@ -6,7 +6,7 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git
- gitlab-org
.default-cache: &default-cache
key: "ruby-2.3.6-with-yarn"
key: "ruby-2.3.7-with-yarn"
paths:
- vendor/ruby
- .yarn-cache/
......@@ -364,10 +364,11 @@ update-tests-metadata:
- rspec_flaky/
policy: push
script:
- retry gem install fog-aws mime-types
- retry gem install fog-aws mime-types activesupport
- scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json
- scripts/merge-reports ${KNAPSACK_SPINACH_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/spinach-pg_node_*.json
- scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/all_*_*.json
- FLAKY_RSPEC_GENERATE_REPORT=1 scripts/prune-old-flaky-specs ${FLAKY_RSPEC_SUITE_REPORT_PATH}
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH $KNAPSACK_SPINACH_SUITE_REPORT_PATH'
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $FLAKY_RSPEC_SUITE_REPORT_PATH'
- rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
......@@ -571,7 +572,7 @@ static-analysis:
script:
- scripts/static-analysis
cache:
key: "ruby-2.3.6-with-yarn-and-rubocop"
key: "ruby-2.3.7-with-yarn-and-rubocop"
paths:
- vendor/ruby
- .yarn-cache/
......@@ -720,7 +721,7 @@ codequality:
tags: []
before_script: []
services:
- docker:dind
- docker:stable-dind
variables:
SETUP_DB: "false"
DOCKER_DRIVER: overlay2
......@@ -735,16 +736,50 @@ codequality:
expire_in: 1 week
sast:
<<: *except-docs
image: registry.gitlab.com/gitlab-org/gl-sast:latest
<<: *dedicated-no-docs-no-db-pull-cache-job
image: docker:stable
variables:
CONFIDENCE_LEVEL: 2
SAST_CONFIDENCE_LEVEL: 2
DOCKER_DRIVER: overlay2
allow_failure: true
tags: []
before_script: []
cache: {}
dependencies: []
services:
- docker:stable-dind
script:
- /app/bin/run .
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- docker run
--env SAST_CONFIDENCE_LEVEL="${SAST_CONFIDENCE_LEVEL:-3}"
--volume "$PWD:/code"
--volume /var/run/docker.sock:/var/run/docker.sock
"registry.gitlab.com/gitlab-org/security-products/sast:$SP_VERSION" /app/bin/run /code
artifacts:
paths: [gl-sast-report.json]
dependency_scanning:
<<: *dedicated-no-docs-no-db-pull-cache-job
image: docker:stable
variables:
DOCKER_DRIVER: overlay2
allow_failure: true
tags: []
before_script: []
cache: {}
dependencies: []
services:
- docker:stable-dind
script:
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- docker run
--env DEP_SCAN_DISABLE_REMOTE_CHECKS="${DEP_SCAN_DISABLE_REMOTE_CHECKS:-false}"
--volume "$PWD:/code"
--volume /var/run/docker.sock:/var/run/docker.sock
"registry.gitlab.com/gitlab-org/security-products/dependency-scanning:$SP_VERSION" /code
artifacts:
paths: [gl-dependency-scanning-report.json]
qa:internal:
<<: *dedicated-no-docs-no-db-pull-cache-job
services: []
......
......@@ -45,4 +45,4 @@ When removing columns, tables, indexes or other structures:
- [ ] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
- [ ] Internationalization required/considered
- [ ] If paid feature, have we considered GitLab.com plan and how it works for groups and is there a design for promoting it to users who aren't on the correct plan
- [ ] End-to-end tests pass (`package-qa` manual pipeline job)
- [ ] End-to-end tests pass (`package-and-qa` manual pipeline job)
......@@ -2,6 +2,24 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 10.6.4 (2018-04-09)
### Fixed (8 changes, 1 of them is from the community)
- Correct copy text for the promote milestone and label modals. !17726
- Avoid validation errors when running the Pages domain verification service. !17992
- Fix autolinking URLs containing ampersands. !18045
- Fix exceptions raised when migrating pipeline stages in the background. !18076
- Work around Prometheus Helm chart name changes to fix integration. !18206 (joshlambert)
- Don't show Jump to Discussion button on Issues.
- Fix listing commit branch/tags that contain special characters.
- Fix 404 in group boards when moving issue between lists.
### Performance (1 change)
- Free open file descriptors and libgit2 buffers in UpdatePagesService.
## 10.6.3 (2018-04-03)
### Security (2 changes)
......
......@@ -82,16 +82,9 @@ gem 'net-ldap'
# Git Wiki
# Required manually in config/initializers/gollum.rb to control load order
# Before updating this gem, check if
# https://github.com/gollum/gollum-lib/pull/292 has been merged.
# If it has, then remove the monkey patch for update_page, rename_page and raw_data_in_committer
# in config/initializers/gollum.rb
gem 'gollum-lib', '~> 4.2', require: false
gem 'gitlab-gollum-lib', '~> 4.2'
# Before updating this gem, check if
# https://github.com/gollum/rugged_adapter/pull/28 has been merged.
# If it has, then remove the monkey patch for tree_entry in config/initializers/gollum.rb
gem 'gollum-rugged_adapter', '~> 0.4.4', require: false
gem 'gitlab-gollum-rugged_adapter', '~> 0.4.4', require: false
# Language detection
gem 'github-linguist', '~> 5.3.3', require: 'linguist'
......@@ -441,3 +434,5 @@ gem 'grape_logging', '~> 1.7'
# Asset synchronization
gem 'asset_sync', '~> 2.2.0'
gem 'goldiloader', '~> 2.0'
......@@ -206,7 +206,7 @@ GEM
railties (>= 3.0.0)
faraday (0.12.2)
multipart-post (>= 1.2, < 3)
faraday_middleware (0.11.0.1)
faraday_middleware (0.12.2)
faraday (>= 0.7.4, < 1.0)
faraday_middleware-multi_json (0.0.6)
faraday_middleware
......@@ -298,11 +298,22 @@ GEM
escape_utils (~> 1.1.0)
mime-types (>= 1.19)
rugged (>= 0.25.1)
github-markup (1.6.1)
github-markup (1.7.0)
gitlab-flowdock-git-hook (1.0.1)
flowdock (~> 0.7)
gitlab-grit (>= 2.4.1)
multi_json
gitlab-gollum-lib (4.2.7.1)
gemojione (~> 3.2)
github-markup (~> 1.6)
gollum-grit_adapter (~> 1.0)
nokogiri (>= 1.6.1, < 2.0)
rouge (~> 2.1)
sanitize (~> 2.1)
stringex (~> 2.6)
gitlab-gollum-rugged_adapter (0.4.4)
mime-types (>= 1.15)
rugged (~> 0.25)
gitlab-grit (2.8.2)
charlock_holmes (~> 0.6)
diff-lcs (~> 1.1)
......@@ -320,19 +331,11 @@ GEM
rubyntlm (~> 0.5)
globalid (0.4.1)
activesupport (>= 4.2.0)
goldiloader (2.0.1)
activerecord (>= 4.2, < 5.2)
activesupport (>= 4.2, < 5.2)
gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.1)
gollum-lib (4.2.7)
gemojione (~> 3.2)
github-markup (~> 1.6)
gollum-grit_adapter (~> 1.0)
nokogiri (>= 1.6.1, < 2.0)
rouge (~> 2.1)
sanitize (~> 2.1)
stringex (~> 2.6)
gollum-rugged_adapter (0.4.4)
mime-types (>= 1.15)
rugged (~> 0.25)
gon (6.1.0)
actionpack (>= 3.0)
json
......@@ -587,7 +590,7 @@ GEM
orm_adapter (0.5.0)
os (0.9.6)
parallel (1.12.1)
parser (2.5.0.5)
parser (2.5.1.0)
ast (~> 2.4.0)
parslet (1.5.0)
blankslate (~> 2.0)
......@@ -907,7 +910,7 @@ GEM
state_machines-activerecord (0.5.1)
activerecord (>= 4.1, < 6.0)
state_machines-activemodel (>= 0.5.0)
stringex (2.7.1)
stringex (2.8.4)
sys-filesystem (1.1.6)
ffi
sysexits (1.2.0)
......@@ -1064,11 +1067,12 @@ DEPENDENCIES
gitaly-proto (~> 0.94.0)
github-linguist (~> 5.3.3)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-gollum-lib (~> 4.2)
gitlab-gollum-rugged_adapter (~> 0.4.4)
gitlab-markup (~> 1.6.2)
gitlab-styles (~> 2.3)
gitlab_omniauth-ldap (~> 2.0.4)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4)
goldiloader (~> 2.0)
gon (~> 6.1.0)
google-api-client (~> 0.19.8)
google-protobuf (= 3.5.1)
......
......@@ -321,6 +321,9 @@ GEM
rubyntlm (~> 0.5)
globalid (0.4.1)
activesupport (>= 4.2.0)
goldiloader (2.0.1)
activerecord (>= 4.2, < 5.2)
activesupport (>= 4.2, < 5.2)
gollum-grit_adapter (1.0.1)
gitlab-grit (~> 2.7, >= 2.7.1)
gollum-lib (4.2.7)
......@@ -878,7 +881,7 @@ GEM
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
slack-notifier (1.5.1)
spinach (0.10.1)
spinach (0.8.10)
colorize
gherkin-ruby (>= 0.3.2)
json
......@@ -1072,6 +1075,7 @@ DEPENDENCIES
gitlab-markup (~> 1.6.2)
gitlab-styles (~> 2.3)
gitlab_omniauth-ldap (~> 2.0.4)
goldiloader (~> 2.0)
gollum-lib (~> 4.2)
gollum-rugged_adapter (~> 0.4.4)
gon (~> 6.1.0)
......
......@@ -55,22 +55,20 @@
},
methods: {
successCallback(resp) {
return resp.json().then((response) => {
// depending of the endpoint the response can either bring a `pipelines` key or not.
const pipelines = response.pipelines || response;
this.setCommonData(pipelines);
// depending of the endpoint the response can either bring a `pipelines` key or not.
const pipelines = resp.data.pipelines || resp.data;
this.setCommonData(pipelines);
const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
detail: {
pipelines: response,
},
});
// notifiy to update the count in tabs
if (this.$el.parentElement) {
this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
}
const updatePipelinesEvent = new CustomEvent('update-pipelines-count', {
detail: {
pipelines: resp.data,
},
});
// notifiy to update the count in tabs
if (this.$el.parentElement) {
this.$el.parentElement.dispatchEvent(updatePipelinesEvent);
}
},
},
};
......
import $ from 'jquery';
import _ from 'underscore';
import {
getSelector,
togglePopover,
inserted,
mouseenter,
mouseleave,
} from './feature_highlight_helper';
import {
togglePopover,
mouseenter,
debouncedMouseleave,
} from '../shared/popover';
export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
const $selector = $(getSelector(id));
const $parent = $selector.parent();
const $popoverContent = $parent.siblings('.feature-highlight-popover-content');
const hideOnScroll = togglePopover.bind($selector, false);
const debouncedMouseleave = _.debounce(mouseleave, debounceTimeout);
$selector
// Setup popover
......@@ -29,13 +29,10 @@ export function setupFeatureHighlightPopover(id, debounceTimeout = 300) {
`,
})
.on('mouseenter', mouseenter)
.on('mouseleave', debouncedMouseleave)
.on('mouseleave', debouncedMouseleave(debounceTimeout))
.on('inserted.bs.popover', inserted)
.on('show.bs.popover', () => {
window.addEventListener('scroll', hideOnScroll);
})
.on('hide.bs.popover', () => {
window.removeEventListener('scroll', hideOnScroll);
window.addEventListener('scroll', hideOnScroll, { once: true });
})
// Display feature highlight
.removeAttr('disabled');
......
......@@ -3,20 +3,10 @@ import axios from '../lib/utils/axios_utils';
import { __ } from '../locale';
import Flash from '../flash';
import LazyLoader from '../lazy_loader';
import { togglePopover } from '../shared/popover';
export const getSelector = highlightId => `.js-feature-highlight[data-highlight=${highlightId}]`;
export function togglePopover(show) {
const isAlreadyShown = this.hasClass('js-popover-show');
if ((show && isAlreadyShown) || (!show && !isAlreadyShown)) {
return false;
}
this.popover(show ? 'show' : 'hide');
this.toggleClass('disable-animation js-popover-show', show);
return true;
}
export function dismiss(highlightId) {
axios.post(this.attr('data-dismiss-endpoint'), {
feature_name: highlightId,
......@@ -27,23 +17,6 @@ export function dismiss(highlightId) {
this.hide();
}
export function mouseleave() {
if (!$('.popover:hover').length > 0) {
const $featureHighlight = $(this);
togglePopover.call($featureHighlight, false);
}
}
export function mouseenter() {
const $featureHighlight = $(this);
const showedPopover = togglePopover.call($featureHighlight, true);
if (showedPopover) {
$('.popover')
.on('mouseleave', mouseleave.bind($featureHighlight));
}
}
export function inserted() {
const popoverId = this.getAttribute('aria-describedby');
const highlightId = this.dataset.highlight;
......
......@@ -22,7 +22,7 @@ export default {
...mapState(['rightPanelCollapsed', 'viewer', 'delayViewerUpdated', 'panelResizing']),
...mapGetters(['currentMergeRequest', 'getStagedFile']),
shouldHideEditor() {
return this.file && this.file.binary && !this.file.raw;
return this.file && this.file.binary && !this.file.content;
},
editTabCSS() {
return {
......@@ -217,7 +217,7 @@ export default {
<content-viewer
v-if="shouldHideEditor || file.viewMode === 'preview'"
:content="file.content || file.raw"
:path="file.rawPath"
:path="file.rawPath || file.path"
:file-size="file.size"
:project-path="file.projectId"/>
</div>
......
......@@ -121,8 +121,6 @@ export const updateFilesAfterCommit = (
`/project/${rootState.currentProjectId}/blob/${branch}/${rootGetters.activeFile.path}`,
);
}
dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH);
};
export const commitChanges = ({ commit, state, getters, dispatch, rootState }) => {
......@@ -150,31 +148,31 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState }) =
if (!data.short_id) {
flash(data.message, 'alert', document, null, false, true);
return;
return null;
}
dispatch('setLastCommitMessage', data);
dispatch('updateCommitMessage', '');
return dispatch('updateFilesAfterCommit', {
data,
branch: getters.branchName,
})
.then(() => {
if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH_MR) {
dispatch(
'redirectToUrl',
createNewMergeRequestUrl(
rootState.projects[rootState.currentProjectId].web_url,
getters.branchName,
rootState.currentBranchId,
),
{ root: true },
);
}
if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH_MR) {
dispatch(
'redirectToUrl',
createNewMergeRequestUrl(
rootState.projects[rootState.currentProjectId].web_url,
getters.branchName,
rootState.currentBranchId,
),
{ root: true },
);
} else {
dispatch('updateFilesAfterCommit', {
data,
branch: getters.branchName,
});
}
commit(rootTypes.CLEAR_STAGED_CHANGES, null, { root: true });
dispatch('discardDraft');
commit(rootTypes.CLEAR_STAGED_CHANGES, null, { root: true });
})
.then(() => dispatch('updateCommitAction', consts.COMMIT_TO_CURRENT_BRANCH));
})
.catch(err => {
let errMsg = __('Error committing changes. Please try again.');
......
......@@ -7,11 +7,7 @@ import flash from './flash';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import initChangesDropdown from './init_changes_dropdown';
import bp from './breakpoints';
import {
parseUrlPathname,
handleLocationHash,
isMetaClick,
} from './lib/utils/common_utils';
import { parseUrlPathname, handleLocationHash, isMetaClick } from './lib/utils/common_utils';
import { getLocationHash } from './lib/utils/url_utility';
import initDiscussionTab from './image_diff/init_discussion_tab';
import Diff from './diff';
......@@ -69,11 +65,10 @@ import Notes from './notes';
let location = window.location;
export default class MergeRequestTabs {
constructor({ action, setUrl, stubLocation } = {}) {
const mergeRequestTabs = document.querySelector('.js-tabs-affix');
const navbar = document.querySelector('.navbar-gitlab');
const peek = document.getElementById('peek');
const peek = document.getElementById('js-peek');
const paddingTop = 16;
this.diffsLoaded = false;
......@@ -109,8 +104,7 @@ export default class MergeRequestTabs {
.on('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
.on('click', '.js-show-tab', this.showTab);
$('.merge-request-tabs a[data-toggle="tab"]')
.on('click', this.clickTab);
$('.merge-request-tabs a[data-toggle="tab"]').on('click', this.clickTab);
}
// Used in tests
......@@ -119,8 +113,7 @@ export default class MergeRequestTabs {
.off('shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', this.tabShown)
.off('click', '.js-show-tab', this.showTab);
$('.merge-request-tabs a[data-toggle="tab"]')
.off('click', this.clickTab);
$('.merge-request-tabs a[data-toggle="tab"]').off('click', this.clickTab);
}
destroyPipelinesView() {
......@@ -183,10 +176,7 @@ export default class MergeRequestTabs {
scrollToElement(container) {
if (location.hash) {
const offset = 0 - (
$('.navbar-gitlab').outerHeight() +
$('.js-tabs-affix').outerHeight()
);
const offset = 0 - ($('.navbar-gitlab').outerHeight() + $('.js-tabs-affix').outerHeight());
const $el = $(`${container} ${location.hash}:not(.match)`);
if ($el.length) {
$.scrollTo($el[0], { offset });
......@@ -240,9 +230,13 @@ export default class MergeRequestTabs {
// Turbolinks' history.
//
// See https://github.com/rails/turbolinks/issues/363
window.history.replaceState({
url: newState,
}, document.title, newState);
window.history.replaceState(
{
url: newState,
},
document.title,
newState,
);
return newState;
}
......@@ -258,7 +252,8 @@ export default class MergeRequestTabs {
this.toggleLoading(true);
axios.get(`${source}.json`)
axios
.get(`${source}.json`)
.then(({ data }) => {
document.querySelector('div#commits').innerHTML = data.html;
localTimeAgo($('.js-timeago', 'div#commits'));
......@@ -303,7 +298,8 @@ export default class MergeRequestTabs {
this.toggleLoading(true);
axios.get(`${urlPathname}.json${location.search}`)
axios
.get(`${urlPathname}.json${location.search}`)
.then(({ data }) => {
const $container = $('#diffs');
$container.html(data.html);
......@@ -332,8 +328,7 @@ export default class MergeRequestTabs {
cancelButtons: $(el).find('.js-cancel-fork-suggestion-button'),
suggestionSections: $(el).find('.js-file-fork-suggestion-section'),
actionTextPieces: $(el).find('.js-file-fork-suggestion-section-action'),
})
.init();
}).init();
});
// Scroll any linked note into view
......@@ -388,8 +383,7 @@ export default class MergeRequestTabs {
resetViewContainer() {
if (this.fixedLayoutPref !== null) {
$('.content-wrapper .container-fluid')
.toggleClass('container-limited', this.fixedLayoutPref);
$('.content-wrapper .container-fluid').toggleClass('container-limited', this.fixedLayoutPref);
}
}
......@@ -438,12 +432,11 @@ export default class MergeRequestTabs {
const $diffTabs = $('#diff-notes-app');
$tabs.off('affix.bs.affix affix-top.bs.affix')
$tabs
.off('affix.bs.affix affix-top.bs.affix')
.affix({
offset: {
top: () => (
$diffTabs.offset().top - $tabs.height() - $fixedNav.height()
),
top: () => $diffTabs.offset().top - $tabs.height() - $fixedNav.height(),
},
})
.on('affix.bs.affix', () => $diffTabs.css({ marginTop: $tabs.height() }))
......
import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import flash from './flash';
import { mouseenter, debouncedMouseleave, togglePopover } from './shared/popover';
export default class Milestone {
constructor() {
......@@ -43,4 +44,25 @@ export default class Milestone {
.catch(() => flash('Error loading milestone tab'));
}
}
static initDeprecationMessage() {
const deprecationMesssageContainer = document.querySelector('.js-milestone-deprecation-message');
if (!deprecationMesssageContainer) return;
const deprecationMessage = deprecationMesssageContainer.querySelector('.js-milestone-deprecation-message-template').innerHTML;
const $popover = $('.js-popover-link', deprecationMesssageContainer);
const hideOnScroll = togglePopover.bind($popover, false);
$popover.popover({
content: deprecationMessage,
html: true,
placement: 'bottom',
})
.on('mouseenter', mouseenter)
.on('mouseleave', debouncedMouseleave())
.on('show.bs.popover', () => {
window.addEventListener('scroll', hideOnScroll, { once: true });
});
}
}
......@@ -1190,12 +1190,12 @@ export default class Notes {
addForm = false;
let lineTypeSelector = '';
rowCssToAdd =
'<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content discussion-notes"></div></td></tr>';
'<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content"></div></td></tr>';
// In parallel view, look inside the correct left/right pane
if (this.isParallelView()) {
lineTypeSelector = `.${lineType}`;
rowCssToAdd =
'<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content discussion-notes"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content discussion-notes"></div></td></tr>';
'<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content"></div></td></tr>';
}
const notesContentSelector = `.notes_content${lineTypeSelector} .content`;
let notesContent = targetRow.find(notesContentSelector);
......
......@@ -317,10 +317,10 @@ Please check your network connection and try again.`;
<note-signed-out-widget v-if="!isLoggedIn" />
<discussion-locked-widget
issuable-type="issue"
v-else-if="!canCreateNote"
v-else-if="isLocked(getNoteableData) && !canCreateNote"
/>
<ul
v-else
v-else-if="canCreateNote"
class="notes notes-form timeline">
<li class="timeline-entry">
<div class="timeline-entry-inner">
......
......@@ -40,6 +40,10 @@ export default {
type: Boolean,
required: true,
},
canAwardEmoji: {
type: Boolean,
required: true,
},
canDelete: {
type: Boolean,
required: true,
......@@ -74,9 +78,6 @@ export default {
shouldShowActionsDropdown() {
return this.currentUserId && (this.canEdit || this.canReportAsAbuse);
},
canAddAwardEmoji() {
return this.currentUserId;
},
isAuthoredByCurrentUser() {
return this.authorId === this.currentUserId;
},
......@@ -149,7 +150,7 @@ export default {
</button>
</div>
<div
v-if="canAddAwardEmoji"
v-if="canAwardEmoji"
class="note-actions-item">
<a
v-tooltip
......
......@@ -28,6 +28,10 @@ export default {
type: Number,
required: true,
},
canAwardEmoji: {
type: Boolean,
required: true,
},
},
computed: {
...mapGetters(['getUserData']),
......@@ -67,9 +71,6 @@ export default {
isAuthoredByMe() {
return this.noteAuthorId === this.getUserData.id;
},
isLoggedIn() {
return this.getUserData.id;
},
},
created() {
this.emojiSmiling = emojiSmiling;
......@@ -156,7 +157,7 @@ export default {
return title;
},
handleAward(awardName) {
if (!this.isLoggedIn) {
if (!this.canAwardEmoji) {
return;
}
......@@ -208,7 +209,7 @@ export default {
</span>
</button>
<div
v-if="isLoggedIn"
v-if="canAwardEmoji"
class="award-menu-holder">
<button
v-tooltip
......
......@@ -112,6 +112,7 @@ export default {
:note-author-id="note.author.id"
:awards="note.award_emoji"
:toggle-award-path="note.toggle_award_path"
:can-award-emoji="note.current_user.can_award_emoji"
/>
<note-attachment
v-if="note.attachment"
......
......@@ -258,7 +258,9 @@ Please check your network connection and try again.`;
:key="note.id"
/>
</ul>
<div class="discussion-reply-holder">
<div
:class="{ 'is-replying': isReplying }"
class="discussion-reply-holder">
<template v-if="!isReplying && canReply">
<div
class="btn-group-justified discussion-with-resolve-btn"
......
......@@ -177,6 +177,7 @@ export default {
:note-id="note.id"
:access-level="note.human_access"
:can-edit="note.current_user.can_edit"
:can-award-emoji="note.current_user.can_award_emoji"
:can-delete="note.current_user.can_edit"
:can-report-as-abuse="canReportAsAbuse"
:report-abuse-path="note.report_abuse_path"
......
......@@ -6,4 +6,6 @@ document.addEventListener('DOMContentLoaded', () => {
new Milestone(); // eslint-disable-line no-new
new Sidebar(); // eslint-disable-line no-new
new MountMilestoneSidebar(); // eslint-disable-line no-new
Milestone.initDeprecationMessage();
});
import initMilestonesShow from '~/pages/milestones/shared/init_milestones_show';
import Milestone from '~/milestone';
document.addEventListener('DOMContentLoaded', initMilestonesShow);
document.addEventListener('DOMContentLoaded', () => {
initMilestonesShow();
Milestone.initDeprecationMessage();
});
import initSettingsPanels from '~/settings_panels';
import setupProjectEdit from '~/project_edit';
import initConfirmDangerModal from '~/confirm_danger_modal';
import ProjectNew from '../shared/project_new';
import initProjectLoadingSpinner from '../shared/save_project_loader';
import projectAvatar from '../shared/project_avatar';
import initProjectPermissionsSettings from '../shared/permissions';
document.addEventListener('DOMContentLoaded', () => {
new ProjectNew(); // eslint-disable-line no-new
initProjectLoadingSpinner();
setupProjectEdit();
// Initialize expandable settings panels
initSettingsPanels();
......
import ProjectNew from '../shared/project_new';
import initProjectLoadingSpinner from '../shared/save_project_loader';
import initProjectVisibilitySelector from '../../../project_visibility';
import initProjectNew from '../../../projects/project_new';
document.addEventListener('DOMContentLoaded', () => {
new ProjectNew(); // eslint-disable-line no-new
initProjectLoadingSpinner();
initProjectVisibilitySelector();
initProjectNew.bindEvents();
});
/* eslint-disable func-names, no-var, no-underscore-dangle, prefer-template, prefer-arrow-callback*/
import $ from 'jquery';
import VisibilitySelect from '../../../visibility_select';
function highlightChanges($elm) {
$elm.addClass('highlight-changes');
setTimeout(() => $elm.removeClass('highlight-changes'), 10);
}
export default class ProjectNew {
constructor() {
this.toggleSettings = this.toggleSettings.bind(this);
this.$selects = $('.features select');
this.$repoSelects = this.$selects.filter('.js-repo-select');
this.$projectSelects = this.$selects.not('.js-repo-select');
$('.project-edit-container').on('ajax:before', () => {
$('.project-edit-container').hide();
return $('.save-project-loader').show();
});
this.initVisibilitySelect();
this.toggleSettings();
this.toggleSettingsOnclick();
this.toggleRepoVisibility();
}
initVisibilitySelect() {
const visibilityContainer = document.querySelector('.js-visibility-select');
if (!visibilityContainer) return;
const visibilitySelect = new VisibilitySelect(visibilityContainer);
visibilitySelect.init();
const $visibilitySelect = $(visibilityContainer).find('select');
let projectVisibility = $visibilitySelect.val();
const PROJECT_VISIBILITY_PRIVATE = '0';
$visibilitySelect.on('change', () => {
const newProjectVisibility = $visibilitySelect.val();
if (projectVisibility !== newProjectVisibility) {
this.$projectSelects.each((idx, select) => {
const $select = $(select);
const $options = $select.find('option');
const values = $.map($options, e => e.value);
// if switched to "private", limit visibility options
if (newProjectVisibility === PROJECT_VISIBILITY_PRIVATE) {
if ($select.val() !== values[0] && $select.val() !== values[1]) {
$select.val(values[1]).trigger('change');
highlightChanges($select);
}
$options.slice(2).disable();
}
// if switched from "private", increase visibility for non-disabled options
if (projectVisibility === PROJECT_VISIBILITY_PRIVATE) {
$options.enable();
if ($select.val() !== values[0] && $select.val() !== values[values.length - 1]) {
$select.val(values[values.length - 1]).trigger('change');
highlightChanges($select);
}
}
});
projectVisibility = newProjectVisibility;
}
});
}
toggleSettings() {
this.$selects.each(function () {
var $select = $(this);
var className = $select.data('field')
.replace(/_/g, '-')
.replace('access-level', 'feature');
ProjectNew._showOrHide($select, '.' + className);
});
}
toggleSettingsOnclick() {
this.$selects.on('change', this.toggleSettings);
}
static _showOrHide(checkElement, container) {
const $container = $(container);
if ($(checkElement).val() !== '0') {
return $container.show();
}
return $container.hide();
}
toggleRepoVisibility() {
var $repoAccessLevel = $('.js-repo-access-level select');
var $lfsEnabledOption = $('.js-lfs-enabled select');
var containerRegistry = document.querySelectorAll('.js-container-registry')[0];
var containerRegistryCheckbox = document.getElementById('project_container_registry_enabled');
var prevSelectedVal = parseInt($repoAccessLevel.val(), 10);
this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']")
.nextAll()
.hide();
$repoAccessLevel
.off('change')
.on('change', function () {
var selectedVal = parseInt($repoAccessLevel.val(), 10);
this.$repoSelects.each(function () {
var $this = $(this);
var repoSelectVal = parseInt($this.val(), 10);
$this.find('option').enable();
if (selectedVal < repoSelectVal || repoSelectVal === prevSelectedVal) {
$this.val(selectedVal).trigger('change');
highlightChanges($this);
}
$this.find("option[value='" + selectedVal + "']").nextAll().disable();
});
if (selectedVal) {
this.$repoSelects.removeClass('disabled');
if ($lfsEnabledOption.length) {
$lfsEnabledOption.removeClass('disabled');
highlightChanges($lfsEnabledOption);
}
if (containerRegistry) {
containerRegistry.style.display = '';
}
} else {
this.$repoSelects.addClass('disabled');
if ($lfsEnabledOption.length) {
$lfsEnabledOption.val('false').addClass('disabled');
highlightChanges($lfsEnabledOption);
}
if (containerRegistry) {
containerRegistry.style.display = 'none';
containerRegistryCheckbox.checked = false;
}
}
prevSelectedVal = selectedVal;
}.bind(this));
}
}
import $ from 'jquery';
export default function initProjectLoadingSpinner() {
const $formContainer = $('.project-edit-container');
const $loadingSpinner = $('.save-project-loader');
// show loading spinner when saving
$formContainer.on('ajax:before', () => {
$formContainer.hide();
$loadingSpinner.show();
});
}
import initNotes from '~/init_notes';
import ZenMode from '~/zen_mode';
import LineHighlighter from '../../../../line_highlighter';
import BlobViewer from '../../../../blob/viewer';
import LineHighlighter from '~/line_highlighter';
import BlobViewer from '~/blob/viewer';
import snippetEmbed from '~/snippet/snippet_embed';
document.addEventListener('DOMContentLoaded', () => {
new LineHighlighter(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new
initNotes();
new ZenMode(); // eslint-disable-line no-new
snippetEmbed();
});
......@@ -5,7 +5,7 @@ import AccessorUtilities from '~/lib/utils/accessor';
* Does that setting the current selected tab in the localStorage
*/
export default class SigninTabsMemoizer {
constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) {
constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.new-session-tabs' } = {}) {
this.currentTabKey = currentTabKey;
this.tabSelector = tabSelector;
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
......
import LineHighlighter from '../../../line_highlighter';
import BlobViewer from '../../../blob/viewer';
import ZenMode from '../../../zen_mode';
import initNotes from '../../../init_notes';
import LineHighlighter from '~/line_highlighter';
import BlobViewer from '~/blob/viewer';
import ZenMode from '~/zen_mode';
import initNotes from '~/init_notes';
import snippetEmbed from '~/snippet/snippet_embed';
document.addEventListener('DOMContentLoaded', () => {
new LineHighlighter(); // eslint-disable-line no-new
new BlobViewer(); // eslint-disable-line no-new
initNotes();
new ZenMode(); // eslint-disable-line no-new
snippetEmbed();
});
......@@ -7,10 +7,7 @@
import TablePagination from '../../vue_shared/components/table_pagination.vue';
import NavigationTabs from '../../vue_shared/components/navigation_tabs.vue';
import NavigationControls from './nav_controls.vue';
import {
getParameterByName,
parseQueryStringIntoObject,
} from '../../lib/utils/common_utils';
import { getParameterByName } from '../../lib/utils/common_utils';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
export default {
......@@ -19,10 +16,7 @@
NavigationTabs,
NavigationControls,
},
mixins: [
pipelinesMixin,
CIPaginationMixin,
],
mixins: [pipelinesMixin, CIPaginationMixin],
props: {
store: {
type: Object,
......@@ -147,25 +141,26 @@
*/
shouldRenderTabs() {
const { stateMap } = this.$options;
return this.hasMadeRequest &&
[
stateMap.loading,
stateMap.tableList,
stateMap.error,
stateMap.emptyTab,
].includes(this.stateToRender);
return (
this.hasMadeRequest &&
[stateMap.loading, stateMap.tableList, stateMap.error, stateMap.emptyTab].includes(
this.stateToRender,
)
);
},
shouldRenderButtons() {
return (this.newPipelinePath ||
this.resetCachePath ||
this.ciLintPath) && this.shouldRenderTabs;
return (
(this.newPipelinePath || this.resetCachePath || this.ciLintPath) && this.shouldRenderTabs
);
},
shouldRenderPagination() {
return !this.isLoading &&
return (
!this.isLoading &&
this.state.pipelines.length &&
this.state.pageInfo.total > this.state.pageInfo.perPage;
this.state.pageInfo.total > this.state.pageInfo.perPage
);
},
emptyTabMessage() {
......@@ -229,15 +224,13 @@
},
methods: {
successCallback(resp) {
return resp.json().then((response) => {
// Because we are polling & the user is interacting verify if the response received
// matches the last request made
if (_.isEqual(parseQueryStringIntoObject(resp.url.split('?')[1]), this.requestData)) {
this.store.storeCount(response.count);
this.store.storePagination(resp.headers);
this.setCommonData(response.pipelines);
}
});
// Because we are polling & the user is interacting verify if the response received
// matches the last request made
if (_.isEqual(resp.config.params, this.requestData)) {
this.store.storeCount(resp.data.count);
this.store.storePagination(resp.headers);
this.setCommonData(resp.data.pipelines);
}
},
/**
* Handles URL and query parameter changes.
......@@ -251,8 +244,9 @@
this.updateInternalState(parameters);
// fetch new data
return this.service.getPipelines(this.requestData)
.then((response) => {
return this.service
.getPipelines(this.requestData)
.then(response => {
this.isLoading = false;
this.successCallback(response);
......@@ -271,13 +265,11 @@
handleResetRunnersCache(endpoint) {
this.isResetCacheButtonLoading = true;
this.service.postAction(endpoint)
this.service
.postAction(endpoint)
.then(() => {
this.isResetCacheButtonLoading = false;
createFlash(
s__('Pipelines|Project cache successfully reset.'),
'notice',
);
createFlash(s__('Pipelines|Project cache successfully reset.'), 'notice');
})
.catch(() => {
this.isResetCacheButtonLoading = false;
......
<script>
import $ from 'jquery';
/**
* Renders each stage of the pipeline mini graph.
......@@ -14,15 +13,18 @@
* 4. Commit widget
*/
import $ from 'jquery';
import Flash from '../../flash';
import icon from '../../vue_shared/components/icon.vue';
import loadingIcon from '../../vue_shared/components/loading_icon.vue';
import axios from '../../lib/utils/axios_utils';
import eventHub from '../event_hub';
import Icon from '../../vue_shared/components/icon.vue';
import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
import tooltip from '../../vue_shared/directives/tooltip';
export default {
components: {
loadingIcon,
icon,
LoadingIcon,
Icon,
},
directives: {
......@@ -82,15 +84,15 @@
methods: {
onClickStage() {
if (!this.isDropdownOpen()) {
eventHub.$emit('clickedDropdown');
this.isLoading = true;
this.fetchJobs();
}
},
fetchJobs() {
this.$http.get(this.stage.dropdown_path)
.then(response => response.json())
.then((data) => {
axios.get(this.stage.dropdown_path)
.then(({ data }) => {
this.dropdownContent = data.html;
this.isLoading = false;
})
......@@ -98,8 +100,7 @@
this.closeDropdown();
this.isLoading = false;
const flash = new Flash('Something went wrong on our end.');
return flash;
Flash('Something went wrong on our end.');
});
},
......
// eslint-disable-next-line import/prefer-default-export
export const CANCEL_REQUEST = 'CANCEL_REQUEST';
......@@ -7,6 +7,7 @@ import SvgBlankState from '../components/blank_state.vue';
import LoadingIcon from '../../vue_shared/components/loading_icon.vue';
import PipelinesTableComponent from '../components/pipelines_table.vue';
import eventHub from '../event_hub';
import { CANCEL_REQUEST } from '../constants';
export default {
components: {
......@@ -52,34 +53,58 @@ export default {
});
eventHub.$on('postAction', this.postAction);
eventHub.$on('clickedDropdown', this.updateTable);
},
beforeDestroy() {
eventHub.$off('postAction', this.postAction);
eventHub.$off('clickedDropdown', this.updateTable);
},
destroyed() {
this.poll.stop();
},
methods: {
updateTable() {
// Cancel ongoing request
if (this.isMakingRequest) {
this.service.cancelationSource.cancel(CANCEL_REQUEST);
}
// Stop polling
this.poll.stop();
// Update the table
return this.getPipelines()
.then(() => this.poll.restart());
},
fetchPipelines() {
if (!this.isMakingRequest) {
this.isLoading = true;
this.service.getPipelines(this.requestData)
.then(response => this.successCallback(response))
.catch(() => this.errorCallback());
this.getPipelines();
}
},
getPipelines() {
return this.service.getPipelines(this.requestData)
.then(response => this.successCallback(response))
.catch((error) => this.errorCallback(error));
},
setCommonData(pipelines) {
this.store.storePipelines(pipelines);
this.isLoading = false;
this.updateGraphDropdown = true;
this.hasMadeRequest = true;
// In case the previous polling request returned an error, we need to reset it
if (this.hasError) {
this.hasError = false;
}
},
errorCallback() {
this.hasError = true;
this.isLoading = false;
this.updateGraphDropdown = false;
errorCallback(error) {
this.hasMadeRequest = true;
this.isLoading = false;
if (error && error.message && error.message !== CANCEL_REQUEST) {
this.hasError = true;
this.updateGraphDropdown = false;
}
},
setIsMakingRequest(isMakingRequest) {
this.isMakingRequest = isMakingRequest;
......
......@@ -40,10 +40,8 @@ export default class pipelinesMediator {
}
successCallback(response) {
return response.json().then((data) => {
this.state.isLoading = false;
this.store.storePipeline(data);
});
this.state.isLoading = false;
this.store.storePipeline(response.data);
}
errorCallback() {
......
import Vue from 'vue';
import VueResource from 'vue-resource';
Vue.use(VueResource);
import axios from '../../lib/utils/axios_utils';
export default class PipelineService {
constructor(endpoint) {
this.pipeline = Vue.resource(endpoint);
this.pipeline = endpoint;
}
getPipeline() {
return this.pipeline.get();
return axios.get(this.pipeline);
}
// eslint-disable-next-line
// eslint-disable-next-line class-methods-use-this
postAction(endpoint) {
return Vue.http.post(`${endpoint}.json`);
return axios.post(`${endpoint}.json`);
}
}
/* eslint-disable class-methods-use-this */
import Vue from 'vue';
import VueResource from 'vue-resource';
import '../../vue_shared/vue_resource_interceptor';
Vue.use(VueResource);
import axios from '../../lib/utils/axios_utils';
export default class PipelinesService {
/**
* Commits and merge request endpoints need to be requested with `.json`.
*
* The url provided to request the pipelines in the new merge request
* page already has `.json`.
*
* @param {String} root
*/
* Commits and merge request endpoints need to be requested with `.json`.
*
* The url provided to request the pipelines in the new merge request
* page already has `.json`.
*
* @param {String} root
*/
constructor(root) {
let endpoint;
if (root.indexOf('.json') === -1) {
endpoint = `${root}.json`;
this.endpoint = `${root}.json`;
} else {
endpoint = root;
this.endpoint = root;
}
this.pipelines = Vue.resource(endpoint);
}
getPipelines(data = {}) {
const { scope, page } = data;
return this.pipelines.get({ scope, page });
const CancelToken = axios.CancelToken;
this.cancelationSource = CancelToken.source();
return axios.get(this.endpoint, {
params: { scope, page },
cancelToken: this.cancelationSource.token,
});
}
/**
......@@ -38,7 +35,8 @@ export default class PipelinesService {
* @param {String} endpoint
* @return {Promise}
*/
// eslint-disable-next-line class-methods-use-this
postAction(endpoint) {
return Vue.http.post(`${endpoint}.json`);
return axios.post(`${endpoint}.json`);
}
}
import $ from 'jquery';
import _ from 'underscore';
export function togglePopover(show) {
const isAlreadyShown = this.hasClass('js-popover-show');
if ((show && isAlreadyShown) || (!show && !isAlreadyShown)) {
return false;
}
this.popover(show ? 'show' : 'hide');
this.toggleClass('disable-animation js-popover-show', show);
return true;
}
export function mouseleave() {
if (!$('.popover:hover').length > 0) {
const $popover = $(this);
togglePopover.call($popover, false);
}
}
export function mouseenter() {
const $popover = $(this);
const showedPopover = togglePopover.call($popover, true);
if (showedPopover) {
$('.popover').on('mouseleave', mouseleave.bind($popover));
}
}
export function debouncedMouseleave(debounceTimeout = 300) {
return _.debounce(mouseleave, debounceTimeout);
}
import { visitUrl } from './lib/utils/url_utility';
/**
* Helper function that finds the href of the fiven selector and updates the location.
*
* @param {String} selector
*/
export default (selector) => {
const link = document.querySelector(selector).getAttribute('href');
export default function findAndFollowLink(selector) {
const element = document.querySelector(selector);
const link = element && element.getAttribute('href');
if (link) {
window.location = link;
visitUrl(link);
}
};
}
<script>
export default {
name: 'time-tracking-estimate-only-pane',
name: 'TimeTrackingEstimateOnlyPane',
props: {
timeEstimateHumanReadable: {
type: String,
required: true,
},
},
template: `
<div class="time-tracking-estimate-only-pane">
<span class="bold">
{{ s__('TimeTracking|Estimated:') }}
</span>
{{ timeEstimateHumanReadable }}
</div>
`,
};
</script>
<template>
<div class="time-tracking-estimate-only-pane">
<span class="bold">
{{ s__('TimeTracking|Estimated:') }}
</span>
{{ timeEstimateHumanReadable }}
</div>
</template>
<script>
import { sprintf, s__ } from '../../../locale';
export default {
name: 'time-tracking-help-state',
name: 'TimeTrackingHelpState',
props: {
rootPath: {
type: String,
......@@ -27,26 +28,28 @@ export default {
);
},
},
template: `
<div class="time-tracking-help-state">
<div class="time-tracking-info">
<h4>
{{ __('Track time with quick actions') }}
</h4>
<p>
{{ __('Quick actions can be used in the issues description and comment boxes.') }}
</p>
<p v-html="estimateText">
</p>
<p v-html="spendText">
</p>
<a
class="btn btn-default learn-more-button"
:href="href"
>
{{ __('Learn more') }}
</a>
</div>
</div>
`,
};
</script>
<template>
<div class="time-tracking-help-state">
<div class="time-tracking-info">
<h4>
{{ __('Track time with quick actions') }}
</h4>
<p>
{{ __('Quick actions can be used in the issues description and comment boxes.') }}
</p>
<p v-html="estimateText">
</p>
<p v-html="spendText">
</p>
<a
class="btn btn-default learn-more-button"
:href="href"
>
{{ __('Learn more') }}
</a>
</div>
</div>
</template>
<script>
import timeTrackingHelpState from './help_state';
import TimeTrackingHelpState from './help_state.vue';
import TimeTrackingCollapsedState from './collapsed_state.vue';
import timeTrackingSpentOnlyPane from './spent_only_pane';
import timeTrackingNoTrackingPane from './no_tracking_pane';
import timeTrackingEstimateOnlyPane from './estimate_only_pane';
import TimeTrackingEstimateOnlyPane from './estimate_only_pane.vue';
import TimeTrackingComparisonPane from './comparison_pane.vue';
import eventHub from '../../event_hub';
......@@ -12,11 +12,11 @@ export default {
name: 'IssuableTimeTracker',
components: {
TimeTrackingCollapsedState,
'time-tracking-estimate-only-pane': timeTrackingEstimateOnlyPane,
TimeTrackingEstimateOnlyPane,
'time-tracking-spent-only-pane': timeTrackingSpentOnlyPane,
'time-tracking-no-tracking-pane': timeTrackingNoTrackingPane,
TimeTrackingComparisonPane,
'time-tracking-help-state': timeTrackingHelpState,
TimeTrackingHelpState,
},
props: {
time_estimate: {
......
export default () => {
const { protocol, host, pathname } = location;
const shareBtn = document.querySelector('.js-share-btn');
const embedBtn = document.querySelector('.js-embed-btn');
const snippetUrlArea = document.querySelector('.js-snippet-url-area');
const embedAction = document.querySelector('.js-embed-action');
const url = `${protocol}//${host + pathname}`;
shareBtn.addEventListener('click', () => {
shareBtn.classList.add('is-active');
embedBtn.classList.remove('is-active');
snippetUrlArea.value = url;
embedAction.innerText = 'Share';
});
embedBtn.addEventListener('click', () => {
embedBtn.classList.add('is-active');
shareBtn.classList.remove('is-active');
const scriptTag = `<script src="${url}.js"></script>`;
snippetUrlArea.value = scriptTag;
embedAction.innerText = 'Embed';
});
};
export default class VisibilitySelect {
constructor(container) {
if (!container) throw new Error('VisibilitySelect requires a container element as argument 1');
this.container = container;
this.helpBlock = this.container.querySelector('.help-block');
this.select = this.container.querySelector('select');
}
init() {
if (this.select) {
this.updateHelpText();
this.select.addEventListener('change', this.updateHelpText.bind(this));
} else {
this.helpBlock.textContent = this.container.querySelector('.js-locked').dataset.helpBlock;
}
}
updateHelpText() {
this.helpBlock.textContent = this.select.querySelector('option:checked').dataset.description;
}
}
......@@ -146,8 +146,8 @@ export default {
</p>
<p
v-if="shouldShowMemoryGraph"
class="usage-info js-usage-info">
{{ memoryChangeMessage }}
class="usage-info js-usage-info"
v-html="memoryChangeMessage">
</p>
<p
v-if="shouldShowLoadFailure"
......
<script>
/* eslint-disable vue/require-default-prop */
import pipelineStage from '~/pipelines/components/stage.vue';
import ciIcon from '~/vue_shared/components/ci_icon.vue';
import icon from '~/vue_shared/components/icon.vue';
/* eslint-disable vue/require-default-prop */
import PipelineStage from '~/pipelines/components/stage.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
export default {
name: 'MRWidgetPipeline',
components: {
pipelineStage,
ciIcon,
icon,
export default {
name: 'MRWidgetPipeline',
components: {
PipelineStage,
CiIcon,
Icon,
},
props: {
pipeline: {
type: Object,
required: true,
},
props: {
pipeline: {
type: Object,
required: true,
},
// This prop needs to be camelCase, html attributes are case insensive
// https://vuejs.org/v2/guide/components.html#camelCase-vs-kebab-case
hasCi: {
type: Boolean,
required: false,
},
ciStatus: {
type: String,
required: false,
},
// This prop needs to be camelCase, html attributes are case insensive
// https://vuejs.org/v2/guide/components.html#camelCase-vs-kebab-case
hasCi: {
type: Boolean,
required: false,
},
computed: {
hasPipeline() {
return this.pipeline && Object.keys(this.pipeline).length > 0;
},
hasCIError() {
return this.hasCi && !this.ciStatus;
},
status() {
return this.pipeline.details &&
this.pipeline.details.status ? this.pipeline.details.status : {};
},
hasStages() {
return this.pipeline.details &&
this.pipeline.details.stages &&
this.pipeline.details.stages.length;
},
ciStatus: {
type: String,
required: false,
},
};
},
computed: {
hasPipeline() {
return this.pipeline && Object.keys(this.pipeline).length > 0;
},
hasCIError() {
return this.hasCi && !this.ciStatus;
},
status() {
return this.pipeline.details && this.pipeline.details.status
? this.pipeline.details.status
: {};
},
hasStages() {
return (
this.pipeline.details && this.pipeline.details.stages && this.pipeline.details.stages.length
);
},
hasCommitInfo() {
return this.pipeline.commit && Object.keys(this.pipeline.commit).length > 0;
},
},
};
</script>
<template>
<div
v-if="hasPipeline || hasCIError"
class="mr-widget-heading">
class="mr-widget-heading"
>
<div class="ci-widget media">
<template v-if="hasCIError">
<div class="ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-10">
......@@ -77,13 +82,17 @@
#{{ pipeline.id }}
</a>
{{ pipeline.details.status.label }} for
{{ pipeline.details.status.label }}
<a
:href="pipeline.commit.commit_path"
class="commit-sha js-commit-link"
>
{{ pipeline.commit.short_id }}</a>.
<template v-if="hasCommitInfo">
for
<a
:href="pipeline.commit.commit_path"
class="commit-sha js-commit-link"
>
{{ pipeline.commit.short_id }}</a>.
</template>
<span class="mr-widget-pipeline-graph">
<span
......
......@@ -7,7 +7,10 @@ export default {
statusIcon,
},
props: {
mr: { type: Object, required: true },
mr: {
type: Object,
required: true,
},
},
};
</script>
......@@ -20,13 +23,14 @@ export default {
/>
<div class="media-body space-children">
<span class="bold">
There are unresolved discussions. Please resolve these discussions
{{ s__("mrWidget|There are unresolved discussions. Please resolve these discussions") }}
</span>
<a
v-if="mr.createIssueToResolveDiscussionsPath"
:href="mr.createIssueToResolveDiscussionsPath"
class="btn btn-default btn-xs js-create-issue">
Create an issue to resolve them later
class="btn btn-default btn-xs js-create-issue"
>
{{ s__("mrWidget|Create an issue to resolve them later") }}
</a>
</div>
</div>
......
......@@ -27,7 +27,7 @@ export { default as ConflictsState } from './components/states/mr_widget_conflic
export { default as NothingToMergeState } from './components/states/nothing_to_merge.vue';
export { default as MissingBranchState } from './components/states/mr_widget_missing_branch.vue';
export { default as NotAllowedState } from './components/states/mr_widget_not_allowed.vue';
export { default as ReadyToMergeState } from './components/states/mr_widget_ready_to_merge';
export { default as ReadyToMergeState } from './components/states/ready_to_merge.vue';
export { default as ShaMismatchState } from './components/states/sha_mismatch.vue';
export { default as UnresolvedDiscussionsState } from './components/states/unresolved_discussions.vue';
export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked.vue';
......
......@@ -27,20 +27,22 @@
$(document).off('markdown-preview:hide.vue', this.writeMarkdownTab);
},
methods: {
isMarkdownForm(form) {
return form && !form.find('.js-vue-markdown-field').length;
isValid(form) {
return !form ||
form.find('.js-vue-markdown-field').length ||
$(this.$el).closest('form') === form[0];
},
previewMarkdownTab(event, form) {
if (event.target.blur) event.target.blur();
if (this.isMarkdownForm(form)) return;
if (!this.isValid(form)) return;
this.$emit('preview-markdown');
},
writeMarkdownTab(event, form) {
if (event.target.blur) event.target.blur();
if (this.isMarkdownForm(form)) return;
if (!this.isValid(form)) return;
this.$emit('write-markdown');
},
......
......@@ -9,7 +9,7 @@
lines: {
type: Number,
required: false,
default: 6,
default: 3,
},
},
computed: {
......
......@@ -37,7 +37,11 @@
/*
* Code highlight
*/
@import "highlight/**/*";
@import "highlight/dark";
@import "highlight/monokai";
@import "highlight/solarized_dark";
@import "highlight/solarized_light";
@import "highlight/white";
/*
* Styles for JS behaviors.
......
......@@ -187,12 +187,9 @@ a {
animation: fadeInFull $fade-in-duration 1;
}
.animation-container {
background: $repo-editor-grey;
height: 40px;
overflow: hidden;
position: relative;
&.animation-container-small {
height: 12px;
......@@ -205,60 +202,43 @@ a {
}
}
&::before {
animation-duration: 1s;
animation-fill-mode: forwards;
animation-iteration-count: infinite;
animation-name: blockTextShine;
animation-timing-function: linear;
background-image: $repo-editor-linear-gradient;
background-repeat: no-repeat;
background-size: 800px 45px;
content: ' ';
display: block;
height: 100%;
[class^="skeleton-line-"] {
position: relative;
}
div {
background: $white-light;
height: 6px;
left: 0;
position: absolute;
right: 0;
}
.skeleton-line-1 {
left: 0;
top: 8px;
}
.skeleton-line-2 {
left: 150px;
top: 0;
background-color: $theme-gray-100;
height: 10px;
}
overflow: hidden;
.skeleton-line-3 {
left: 0;
top: 23px;
}
&:not(:last-of-type) {
margin-bottom: 4px;
}
.skeleton-line-4 {
left: 0;
top: 38px;
&::after {
content: ' ';
display: block;
animation: blockTextShine 1s linear infinite forwards;
background-repeat: no-repeat;
background-size: cover;
background-image: linear-gradient(
to right,
$theme-gray-100 0%,
$theme-gray-50 20%,
$theme-gray-100 40%,
$theme-gray-100 100%
);
height: 10px;
}
}
}
.skeleton-line-5 {
left: 200px;
top: 28px;
height: 10px;
}
$skeleton-line-widths: (
156px,
235px,
200px,
);
.skeleton-line-6 {
top: 14px;
left: 230px;
height: 10px;
@for $count from 1 through length($skeleton-line-widths) {
.skeleton-line-#{$count} {
width: nth($skeleton-line-widths, $count);
}
}
......
.banner-callout {
display: flex;
position: relative;
flex-wrap: wrap;
align-items: start;
.banner-close {
position: absolute;
......@@ -16,10 +16,25 @@
}
.banner-graphic {
margin: 20px auto;
margin: 0 $gl-padding $gl-padding 0;
}
&.banner-non-empty-state {
border-bottom: 1px solid $border-color;
}
@media (max-width: $screen-xs-max) {
justify-content: center;
flex-direction: column;
align-items: center;
.banner-title,
.banner-buttons {
text-align: center;
}
.banner-graphic {
margin-left: $gl-padding;
}
}
}
......@@ -422,25 +422,43 @@
}
}
.btn-link.btn-secondary-hover-link {
color: $gl-text-color-secondary;
.btn-link {
padding: 0;
background-color: transparent;
color: $blue-600;
font-weight: normal;
border-radius: 0;
border-color: transparent;
&:hover,
&:active,
&:focus {
color: $gl-link-color;
text-decoration: none;
color: $blue-800;
text-decoration: underline;
background-color: transparent;
border-color: transparent;
}
}
.btn-link.btn-primary-hover-link {
color: inherit;
&.btn-secondary-hover-link {
color: $gl-text-color-secondary;
&:hover,
&:active,
&:focus {
color: $gl-link-color;
text-decoration: none;
&:hover,
&:active,
&:focus {
color: $gl-link-color;
text-decoration: none;
}
}
&.btn-primary-hover-link {
color: inherit;
&:hover,
&:active,
&:focus {
color: $gl-link-color;
text-decoration: none;
}
}
}
......
......@@ -481,7 +481,8 @@
.dropdown-menu-selectable {
li {
a {
a,
button {
padding: 8px 40px;
position: relative;
......
......@@ -29,8 +29,10 @@
}
.snippet-title {
font-size: 24px;
color: $gl-text-color;
font-size: 2em;
font-weight: $gl-font-weight-bold;
min-height: $header-height;
}
.snippet-edited-ago {
......@@ -46,3 +48,26 @@
.snippet-scope-menu .btn-new {
margin-top: 15px;
}
.snippet-embed-input {
height: 35px;
}
.embed-snippet {
padding-right: 0;
padding-top: $gl-padding;
.form-control {
cursor: auto;
width: 101%;
margin-left: -1px;
}
.embed-toggle-list li button {
padding: 8px 40px;
}
.embed-toggle {
height: 35px;
}
}
......@@ -713,20 +713,6 @@ $color-high-score: $green-400;
$color-average-score: $orange-400;
$color-low-score: $red-400;
/*
Repo editor
*/
$repo-editor-grey: #f6f7f9;
$repo-editor-grey-darker: #e9ebee;
$repo-editor-linear-gradient: linear-gradient(
to right,
$repo-editor-grey 0%,
$repo-editor-grey-darker,
20%,
$repo-editor-grey 40%,
$repo-editor-grey 100%
);
/*
Performance Bar
*/
......
/* https://github.com/aahan/pygments-github-style */
/*
* White Syntax Colors
*/
$white-code-color: $gl-text-color;
$white-highlight: #fafe3d;
$white-pre-hll-bg: #f8eec7;
$white-hll-bg: #f8f8f8;
$white-over-bg: #ded7fc;
$white-expanded-border: #e0e0e0;
$white-expanded-bg: #f7f7f7;
$white-c: #998;
$white-err: #a61717;
$white-err-bg: #e3d2d2;
$white-cm: #998;
$white-cp: #999;
$white-c1: #998;
$white-cs: #999;
$white-gd: $black;
$white-gd-bg: #fdd;
$white-gd-x: $black;
$white-gd-x-bg: #faa;
$white-gr: #a00;
$white-gh: #999;
$white-gi: $black;
$white-gi-bg: #dfd;
$white-gi-x: $black;
$white-gi-x-bg: #afa;
$white-go: #888;
$white-gp: #555;
$white-gu: #800080;
$white-gt: #a00;
$white-kt: #458;
$white-m: #099;
$white-s: #d14;
$white-n: #333;
$white-na: teal;
$white-nb: #0086b3;
$white-nc: #458;
$white-no: teal;
$white-ni: purple;
$white-ne: #900;
$white-nf: #900;
$white-nn: #555;
$white-nt: navy;
$white-nv: teal;
$white-w: #bbb;
$white-mf: #099;
$white-mh: #099;
$white-mi: #099;
$white-mo: #099;
$white-sb: #d14;
$white-sc: #d14;
$white-sd: #d14;
$white-s2: #d14;
$white-se: #d14;
$white-sh: #d14;
$white-si: #d14;
$white-sx: #d14;
$white-sr: #009926;
$white-s1: #d14;
$white-ss: #990073;
$white-bp: #999;
$white-vc: teal;
$white-vg: teal;
$white-vi: teal;
$white-il: #099;
$white-gc-color: #999;
$white-gc-bg: #eaf2f5;
@mixin matchLine {
color: $black-transparent;
background-color: $gray-light;
}
.code.white {
// Line numbers
.line-numbers,
.diff-line-num {
background-color: $gray-light;
}
.diff-line-num,
.diff-line-num a {
color: $black-transparent;
}
// Code itself
pre.code,
.diff-line-num {
border-color: $white-normal;
}
&,
pre.code,
.line_holder .line_content {
background-color: $white-light;
color: $white-code-color;
}
// Diff line
.line_holder {
&.match .line_content {
@include matchLine;
}
.diff-line-num {
&.old {
background-color: $line-number-old;
border-color: $line-removed-dark;
a {
color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
}
}
&.new {
background-color: $line-number-new;
border-color: $line-added-dark;
a {
color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
}
}
&.is-over,
&.hll:not(.empty-cell).is-over {
background-color: $white-over-bg;
border-color: darken($white-over-bg, 5%);
a {
color: darken($white-over-bg, 15%);
}
}
&.hll:not(.empty-cell) {
background-color: $line-number-select;
border-color: $line-select-yellow-dark;
}
}
&:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num,
> .line_content {
border-top: 1px solid $white-expanded-border;
}
}
&.diff-expanded {
> .diff-line-num,
> .line_content {
background: $white-expanded-bg;
border-color: $white-expanded-bg;
}
}
.line_content {
&.old {
background-color: $line-removed;
&::before {
color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
}
span.idiff {
background-color: $line-removed-dark;
}
}
&.new {
background-color: $line-added;
&::before {
color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
}
span.idiff {
background-color: $line-added-dark;
}
}
&.match {
@include matchLine;
}
&.hll:not(.empty-cell) {
background-color: $line-select-yellow;
}
}
}
// highlight line via anchor
pre .hll {
background-color: $white-pre-hll-bg !important;
}
// Search result highlight
span.highlight_word {
background-color: $white-highlight !important;
}
// Links to URLs, emails, or dependencies
.line a {
color: $white-nb;
}
.hll { background-color: $white-hll-bg; }
.c { color: $white-c; font-style: italic; }
.err { color: $white-err; background-color: $white-err-bg; }
.k { font-weight: $gl-font-weight-bold; }
.o { font-weight: $gl-font-weight-bold; }
.cm { color: $white-cm; font-style: italic; }
.cp { color: $white-cp; font-weight: $gl-font-weight-bold; }
.c1 { color: $white-c1; font-style: italic; }
.cs { color: $white-cs; font-weight: $gl-font-weight-bold; font-style: italic; }
.gd {
color: $white-gd;
background-color: $white-gd-bg;
.x {
color: $white-gd-x;
background-color: $white-gd-x-bg;
}
}
.ge { font-style: italic; }
.gr { color: $white-gr; }
.gh { color: $white-gh; }
.gi {
color: $white-gi;
background-color: $white-gi-bg;
.x {
color: $white-gi-x;
background-color: $white-gi-x-bg;
}
}
.go { color: $white-go; }
.gp { color: $white-gp; }
.gs { font-weight: $gl-font-weight-bold; }
.gu { color: $white-gu; font-weight: $gl-font-weight-bold; }
.gt { color: $white-gt; }
.kc { font-weight: $gl-font-weight-bold; }
.kd { font-weight: $gl-font-weight-bold; }
.kn { font-weight: $gl-font-weight-bold; }
.kp { font-weight: $gl-font-weight-bold; }
.kr { font-weight: $gl-font-weight-bold; }
.kt { color: $white-kt; font-weight: $gl-font-weight-bold; }
.m { color: $white-m; }
.s { color: $white-s; }
.n { color: $white-n; }
.na { color: $white-na; }
.nb { color: $white-nb; }
.nc { color: $white-nc; font-weight: $gl-font-weight-bold; }
.no { color: $white-no; }
.ni { color: $white-ni; }
.ne { color: $white-ne; font-weight: $gl-font-weight-bold; }
.nf { color: $white-nf; font-weight: $gl-font-weight-bold; }
.nn { color: $white-nn; }
.nt { color: $white-nt; }
.nv { color: $white-nv; }
.ow { font-weight: $gl-font-weight-bold; }
.w { color: $white-w; }
.mf { color: $white-mf; }
.mh { color: $white-mh; }
.mi { color: $white-mi; }
.mo { color: $white-mo; }
.sb { color: $white-sb; }
.sc { color: $white-sc; }
.sd { color: $white-sd; }
.s2 { color: $white-s2; }
.se { color: $white-se; }
.sh { color: $white-sh; }
.si { color: $white-si; }
.sx { color: $white-sx; }
.sr { color: $white-sr; }
.s1 { color: $white-s1; }
.ss { color: $white-ss; }
.bp { color: $white-bp; }
.vc { color: $white-vc; }
.vg { color: $white-vg; }
.vi { color: $white-vi; }
.il { color: $white-il; }
.gc { color: $white-gc-color; background-color: $white-gc-bg; }
@import "white_base";
}
/* https://github.com/aahan/pygments-github-style */
/*
* White Syntax Colors
*/
$white-code-color: $gl-text-color;
$white-highlight: #fafe3d;
$white-pre-hll-bg: #f8eec7;
$white-hll-bg: #f8f8f8;
$white-over-bg: #ded7fc;
$white-expanded-border: #e0e0e0;
$white-expanded-bg: #f7f7f7;
$white-c: #998;
$white-err: #a61717;
$white-err-bg: #e3d2d2;
$white-cm: #998;
$white-cp: #999;
$white-c1: #998;
$white-cs: #999;
$white-gd: $black;
$white-gd-bg: #fdd;
$white-gd-x: $black;
$white-gd-x-bg: #faa;
$white-gr: #a00;
$white-gh: #999;
$white-gi: $black;
$white-gi-bg: #dfd;
$white-gi-x: $black;
$white-gi-x-bg: #afa;
$white-go: #888;
$white-gp: #555;
$white-gu: #800080;
$white-gt: #a00;
$white-kt: #458;
$white-m: #099;
$white-s: #d14;
$white-n: #333;
$white-na: teal;
$white-nb: #0086b3;
$white-nc: #458;
$white-no: teal;
$white-ni: purple;
$white-ne: #900;
$white-nf: #900;
$white-nn: #555;
$white-nt: navy;
$white-nv: teal;
$white-w: #bbb;
$white-mf: #099;
$white-mh: #099;
$white-mi: #099;
$white-mo: #099;
$white-sb: #d14;
$white-sc: #d14;
$white-sd: #d14;
$white-s2: #d14;
$white-se: #d14;
$white-sh: #d14;
$white-si: #d14;
$white-sx: #d14;
$white-sr: #009926;
$white-s1: #d14;
$white-ss: #990073;
$white-bp: #999;
$white-vc: teal;
$white-vg: teal;
$white-vi: teal;
$white-il: #099;
$white-gc-color: #999;
$white-gc-bg: #eaf2f5;
@mixin matchLine {
color: $black-transparent;
background-color: $gray-light;
}
// Line numbers
.line-numbers,
.diff-line-num {
background-color: $gray-light;
}
.diff-line-num,
.diff-line-num a {
color: $black-transparent;
}
// Code itself
pre.code,
.diff-line-num {
border-color: $white-normal;
}
&,
pre.code,
.line_holder .line_content {
background-color: $white-light;
color: $white-code-color;
}
// Diff line
.line_holder {
&.match .line_content {
@include matchLine;
}
.diff-line-num {
&.old {
background-color: $line-number-old;
border-color: $line-removed-dark;
a {
color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
}
}
&.new {
background-color: $line-number-new;
border-color: $line-added-dark;
a {
color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
}
}
&.is-over,
&.hll:not(.empty-cell).is-over {
background-color: $white-over-bg;
border-color: darken($white-over-bg, 5%);
a {
color: darken($white-over-bg, 15%);
}
}
&.hll:not(.empty-cell) {
background-color: $line-number-select;
border-color: $line-select-yellow-dark;
}
}
&:not(.diff-expanded) + .diff-expanded,
&.diff-expanded + .line_holder:not(.diff-expanded) {
> .diff-line-num,
> .line_content {
border-top: 1px solid $white-expanded-border;
}
}
&.diff-expanded {
> .diff-line-num,
> .line_content {
background: $white-expanded-bg;
border-color: $white-expanded-bg;
}
}
.line_content {
&.old {
background-color: $line-removed;
&::before {
color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%);
}
span.idiff {
background-color: $line-removed-dark;
}
}
&.new {
background-color: $line-added;
&::before {
color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%);
}
span.idiff {
background-color: $line-added-dark;
}
}
&.match {
@include matchLine;
}
&.hll:not(.empty-cell) {
background-color: $line-select-yellow;
}
}
}
// highlight line via anchor
pre .hll {
background-color: $white-pre-hll-bg !important;
}
// Search result highlight
span.highlight_word {
background-color: $white-highlight !important;
}
// Links to URLs, emails, or dependencies
.line a {
color: $white-nb;
}
.hll { background-color: $white-hll-bg; }
.c { color: $white-c; font-style: italic; }
.err { color: $white-err; background-color: $white-err-bg; }
.k { font-weight: $gl-font-weight-bold; }
.o { font-weight: $gl-font-weight-bold; }
.cm { color: $white-cm; font-style: italic; }
.cp { color: $white-cp; font-weight: $gl-font-weight-bold; }
.c1 { color: $white-c1; font-style: italic; }
.cs { color: $white-cs; font-weight: $gl-font-weight-bold; font-style: italic; }
.gd {
color: $white-gd;
background-color: $white-gd-bg;
.x {
color: $white-gd-x;
background-color: $white-gd-x-bg;
}
}
.ge { font-style: italic; }
.gr { color: $white-gr; }
.gh { color: $white-gh; }
.gi {
color: $white-gi;
background-color: $white-gi-bg;
.x {
color: $white-gi-x;
background-color: $white-gi-x-bg;
}
}
.go { color: $white-go; }
.gp { color: $white-gp; }
.gs { font-weight: $gl-font-weight-bold; }
.gu { color: $white-gu; font-weight: $gl-font-weight-bold; }
.gt { color: $white-gt; }
.kc { font-weight: $gl-font-weight-bold; }
.kd { font-weight: $gl-font-weight-bold; }
.kn { font-weight: $gl-font-weight-bold; }
.kp { font-weight: $gl-font-weight-bold; }
.kr { font-weight: $gl-font-weight-bold; }
.kt { color: $white-kt; font-weight: $gl-font-weight-bold; }
.m { color: $white-m; }
.s { color: $white-s; }
.n { color: $white-n; }
.na { color: $white-na; }
.nb { color: $white-nb; }
.nc { color: $white-nc; font-weight: $gl-font-weight-bold; }
.no { color: $white-no; }
.ni { color: $white-ni; }
.ne { color: $white-ne; font-weight: $gl-font-weight-bold; }
.nf { color: $white-nf; font-weight: $gl-font-weight-bold; }
.nn { color: $white-nn; }
.nt { color: $white-nt; }
.nv { color: $white-nv; }
.ow { font-weight: $gl-font-weight-bold; }
.w { color: $white-w; }
.mf { color: $white-mf; }
.mh { color: $white-mh; }
.mi { color: $white-mi; }
.mo { color: $white-mo; }
.sb { color: $white-sb; }
.sc { color: $white-sc; }
.sd { color: $white-sd; }
.s2 { color: $white-s2; }
.se { color: $white-se; }
.sh { color: $white-sh; }
.si { color: $white-si; }
.sx { color: $white-sx; }
.sr { color: $white-sr; }
.s1 { color: $white-s1; }
.ss { color: $white-ss; }
.bp { color: $white-bp; }
.vc { color: $white-vc; }
.vg { color: $white-vg; }
.vi { color: $white-vi; }
.il { color: $white-il; }
.gc { color: $white-gc-color; background-color: $white-gc-bg; }
......@@ -813,7 +813,6 @@
}
.discussion-notes {
padding: 0 $gl-padding $gl-padding;
min-height: 35px;
&:first-child {
......
......@@ -154,26 +154,10 @@
a {
width: 100%;
font-size: 18px;
margin-right: 0;
&:hover {
border: 1px solid transparent;
}
}
&.active {
border-bottom: 1px solid $border-color;
a {
border: 0;
border-bottom: 2px solid $link-underline-blue;
margin-right: 0;
color: $black;
&:hover {
border-bottom: 2px solid $link-underline-blue;
}
}
&.active > a {
cursor: default;
}
}
}
......
......@@ -194,3 +194,38 @@
.issuable-row {
background-color: $white-light;
}
.milestone-deprecation-message {
.popover {
padding: 0;
}
.popover-content {
padding: 0;
}
}
.milestone-popover-body {
padding: $gl-padding-8;
background-color: $gray-light;
}
.milestone-popover-footer {
padding: $gl-padding-8 $gl-padding;
border-top: 1px solid $white-dark;
}
.milestone-popover-instructions-list {
padding-left: 2em;
> li {
padding-left: 1em;
}
}
@media (max-width: $screen-xs-max) {
.milestone-banner-text,
.milestone-banner-link {
display: inline;
}
}
......@@ -173,7 +173,11 @@
}
.discussion-form {
padding-top: $gl-padding-top;
background-color: $white-light;
}
.discussion-form-container {
padding: $gl-padding-top $gl-padding $gl-padding;
}
.discussion-notes .disabled-comment {
......@@ -233,7 +237,12 @@
.discussion-body,
.diff-file {
.discussion-reply-holder {
padding-top: $gl-padding;
background-color: $white-light;
padding: 10px 16px;
&.is-replying {
padding-bottom: $gl-padding;
}
}
}
......
......@@ -47,7 +47,7 @@ ul.notes {
}
.timeline-entry-inner {
padding: $gl-padding 0;
padding: $gl-padding $gl-btn-padding;
border-bottom: 1px solid $white-normal;
}
......@@ -94,6 +94,12 @@ ul.notes {
}
}
&.note-discussion {
.timeline-entry-inner {
padding: $gl-padding 10px;
}
}
.editing-spinner {
display: none;
}
......@@ -346,8 +352,6 @@ ul.notes {
}
.discussion-notes {
background-color: $white-light;
&:not(:first-child) {
border-top: 1px solid $white-normal;
margin-top: 20px;
......@@ -359,6 +363,10 @@ ul.notes {
}
}
.notes {
background-color: $white-light;
}
a code {
top: 0;
margin-right: 0;
......@@ -639,6 +647,8 @@ ul.notes {
border-bottom: 1px solid $white-normal;
.timeline-entry-inner {
padding-left: $gl-padding;
padding-right: $gl-padding;
border-bottom: 0;
}
}
......
......@@ -344,7 +344,6 @@
svg {
vertical-align: middle;
margin-right: 3px;
}
.stage-column {
......@@ -495,17 +494,12 @@
svg {
fill: $gl-text-color-secondary;
position: relative;
left: 1px;
top: -1px;
width: 16px;
height: 16px;
}
&.play {
svg {
width: 16px;
height: 16px;
left: 3px;
left: 2px;
}
}
}
......
......@@ -935,11 +935,6 @@ pre.light-well {
}
}
.dropdown-menu-toggle {
width: 100%;
max-width: 300px;
}
.flash-container {
padding: 0;
}
......
......@@ -433,6 +433,7 @@
.projects-sidebar {
display: flex;
flex-direction: column;
height: 100%;
.context-header {
width: auto;
......@@ -442,8 +443,8 @@
.multi-file-commit-panel-inner {
display: flex;
flex: 1;
flex-direction: column;
height: 100%;
}
.multi-file-commit-panel-inner-scroll {
......
@import "framework/variables";
.gitlab-embed-snippets {
@import "highlight/embedded";
@import "framework/images";
$border-style: 1px solid $border-color;
font-family: $regular_font;
font-size: $gl-font-size;
line-height: $code_line_height;
color: $gl-text-color;
margin: 20px;
font-weight: 200;
.gl-snippet-icon {
display: inline-block;
background: url(asset_path('ext_snippet_icons/ext_snippet_icons.png')) no-repeat;
overflow: hidden;
text-align: left;
width: 16px;
height: 16px;
background-size: cover;
&.gl-snippet-icon-doc_code { background-position: 0 0; }
&.gl-snippet-icon-doc_text { background-position: 0 -16px; }
&.gl-snippet-icon-download { background-position: 0 -32px; }
}
.blob-viewer {
background-color: $white-light;
text-align: left;
}
.file-content.code {
border: $border-style;
border-radius: 0 0 4px 4px;
display: flex;
box-shadow: none;
margin: 0;
padding: 0;
table-layout: fixed;
.blob-content {
overflow-x: auto;
pre {
padding: 10px;
border: 0;
border-radius: 0;
font-family: $monospace_font;
font-size: $code_font_size;
line-height: $code_line_height;
margin: 0;
overflow: auto;
overflow-y: hidden;
white-space: pre;
word-wrap: normal;
border-left: $border-style;
}
}
.line-numbers {
padding: 10px;
text-align: right;
float: left;
.diff-line-num {
font-family: $monospace_font;
display: block;
font-size: $code_font_size;
min-height: $code_line_height;
white-space: nowrap;
color: $black-transparent;
min-width: 30px;
}
.diff-line-num:hover {
color: $almost-black;
cursor: pointer;
}
}
}
.file-title-flex-parent {
display: flex;
align-items: center;
justify-content: space-between;
background-color: $gray-light;
border: $border-style;
border-bottom: 0;
padding: $gl-padding-top $gl-padding;
margin: 0;
border-radius: $border-radius-default $border-radius-default 0 0;
.file-header-content {
.file-title-name {
font-weight: $gl-font-weight-bold;
}
.gitlab-embedded-snippets-title {
text-decoration: none;
color: $gl-text-color;
&:hover {
text-decoration: underline;
}
}
.gitlab-logo {
display: inline-block;
padding-left: 5px;
text-decoration: none;
color: $gl-text-color-secondary;
.logo-text {
background: image_url('ext_snippet_icons/logo.png') no-repeat left center;
background-size: 18px;
font-weight: $gl-font-weight-normal;
padding-left: 24px;
}
}
}
img,
.gl-snippet-icon {
display: inline-block;
vertical-align: middle;
}
}
.btn-group {
a.btn {
background-color: $white-light;
text-decoration: none;
padding: 7px 9px;
border: $border-style;
border-right: 0;
&:hover {
background-color: $white-normal;
border-color: $border-white-normal;
text-decoration: none;
}
&:first-child {
border-radius: 3px 0 0 3px;
}
&:last-child {
border-radius: 0 3px 3px 0;
border-right: $border-style;
}
}
}
}
......@@ -56,21 +56,18 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end
def application_setting_params
import_sources = params[:application_setting][:import_sources]
if import_sources.nil?
params[:application_setting][:import_sources] = []
else
import_sources.map! do |source|
source.to_str
end
end
params[:application_setting] ||= {}
enabled_oauth_sign_in_sources = params[:application_setting].delete(:enabled_oauth_sign_in_sources)
if params[:application_setting].key?(:enabled_oauth_sign_in_sources)
enabled_oauth_sign_in_sources = params[:application_setting].delete(:enabled_oauth_sign_in_sources)
enabled_oauth_sign_in_sources&.delete("")
params[:application_setting][:disabled_oauth_sign_in_sources] =
AuthHelper.button_based_providers.map(&:to_s) -
Array(enabled_oauth_sign_in_sources)
params[:application_setting][:disabled_oauth_sign_in_sources] =
AuthHelper.button_based_providers.map(&:to_s) -
Array(enabled_oauth_sign_in_sources)
end
params[:application_setting][:import_sources]&.delete("")
params[:application_setting][:restricted_visibility_levels]&.delete("")
params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file]
......
module ChecksCollaboration
def can_collaborate_with_project?(project, ref: nil)
return true if can?(current_user, :push_code, project)
can_create_merge_request =
can?(current_user, :create_merge_request_in, project) &&
current_user.already_forked?(project)
can_create_merge_request ||
user_access(project).can_push_to_branch?(ref)
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
# enabling this so we can easily cache the user access value as it might be
# used across multiple calls in the view
def user_access(project)
@user_access ||= {}
@user_access[project] ||= Gitlab::UserAccess.new(current_user, project: project)
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end
......@@ -41,7 +41,7 @@ module NotesActions
@note = Notes::CreateService.new(note_project, current_user, create_params).execute
if @note.is_a?(Note)
Notes::RenderService.new(current_user).execute([@note], @project)
Notes::RenderService.new(current_user).execute([@note])
end
respond_to do |format|
......@@ -56,7 +56,7 @@ module NotesActions
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
if @note.is_a?(Note)
Notes::RenderService.new(current_user).execute([@note], @project)
Notes::RenderService.new(current_user).execute([@note])
end
respond_to do |format|
......
......@@ -4,7 +4,7 @@ module RendersNotes
preload_noteable_for_regular_notes(notes)
preload_max_access_for_authors(notes, @project)
preload_first_time_contribution_for_authors(noteable, notes)
Notes::RenderService.new(current_user).execute(notes, @project)
Notes::RenderService.new(current_user).execute(notes)
notes
end
......
......@@ -17,6 +17,10 @@ module SnippetsActions
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def js_request?
request.format.js?
end
private
def convert_line_endings(content)
......
......@@ -173,7 +173,9 @@ class GroupsController < Groups::ApplicationController
.new(@projects, offset: params[:offset].to_i, filter: event_filter)
.to_a
Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?)
Events::RenderService
.new(current_user)
.execute(@events, atom_request: request.format.atom?)
end
def user_actions
......
class Projects::ApplicationController < ApplicationController
include RoutableActions
include ChecksCollaboration
skip_before_action :authenticate_user!
before_action :project
......@@ -31,14 +32,6 @@ class Projects::ApplicationController < ApplicationController
@repository ||= project.repository
end
def can_collaborate_with_project?(project = nil, ref: nil)
project ||= @project
can?(current_user, :push_code, project) ||
(current_user && current_user.already_forked?(project)) ||
user_access(project).can_push_to_branch?(ref)
end
def authorize_action!(action)
unless can?(current_user, action, project)
return access_denied!
......@@ -91,9 +84,4 @@ class Projects::ApplicationController < ApplicationController
def check_issues_available!
return render_404 unless @project.feature_available?(:issues, current_user)
end
def user_access(project)
@user_access ||= {}
@user_access[project] ||= Gitlab::UserAccess.new(current_user, project: project)
end
end
......@@ -34,6 +34,7 @@ class Projects::CommitController < Projects::ApplicationController
def pipelines
@pipelines = @commit.pipelines.order(id: :desc)
@pipelines = @pipelines.where(ref: params[:ref]) if params[:ref]
respond_to do |format|
format.html
......
......@@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_update_issuable!, only: [:edit, :update, :move]
# Allow create a new branch and empty WIP merge request from current issue
before_action :authorize_create_merge_request!, only: [:create_merge_request]
before_action :authorize_create_merge_request_from!, only: [:create_merge_request]
respond_to :html
......
......@@ -5,7 +5,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
skip_before_action :merge_request
before_action :whitelist_query_limiting, only: [:create]
before_action :authorize_create_merge_request!
before_action :authorize_create_merge_request_from!
before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path]
before_action :build_merge_request, except: [:create]
......
......@@ -68,7 +68,7 @@ class Projects::NotesController < Projects::ApplicationController
private
def render_json_with_notes_serializer
Notes::RenderService.new(current_user).execute([note], project)
Notes::RenderService.new(current_user).execute([note])
render json: note_serializer.represent(note)
end
......
......@@ -5,6 +5,8 @@ class Projects::SnippetsController < Projects::ApplicationController
include SnippetsActions
include RendersBlob
skip_before_action :verify_authenticity_token, only: [:show], if: :js_request?
before_action :check_snippets_available!
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
......@@ -71,6 +73,7 @@ class Projects::SnippetsController < Projects::ApplicationController
format.json do
render_blob_json(blob)
end
format.js { render 'shared/snippets/show'}
end
end
......
......@@ -6,6 +6,8 @@ class SnippetsController < ApplicationController
include RendersBlob
include PreviewMarkdown
skip_before_action :verify_authenticity_token, only: [:show], if: :js_request?
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
# Allow read snippet
......@@ -77,6 +79,8 @@ class SnippetsController < ApplicationController
format.json do
render_blob_json(blob)
end
format.js { render 'shared/snippets/show' }
end
end
......
......@@ -159,7 +159,10 @@ class IssuableFinder
finder_options = { include_subgroups: params[:include_subgroups], only_owned: true }
GroupProjectsFinder.new(group: group, current_user: current_user, options: finder_options).execute
else
ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids(items)).execute
opts = { current_user: current_user }
opts[:project_ids_relation] = item_project_ids(items) if items
ProjectsFinder.new(opts).execute
end
@projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil)
......@@ -316,9 +319,9 @@ class IssuableFinder
def by_project(items)
items =
if project?
items.of_projects(projects(items)).references_project
elsif projects(items)
items.merge(projects(items).reorder(nil)).join_project
items.of_projects(projects).references_project
elsif projects
items.merge(projects.reorder(nil)).join_project
else
items.none
end
......
......@@ -12,6 +12,7 @@ class MergeRequestTargetProjectFinder
if @source_project.fork_network
@source_project.fork_network.projects
.public_or_visible_to_user(current_user)
.non_archived
.with_feature_available_for_user(:merge_requests, current_user)
else
Project.where(id: source_project)
......
......@@ -74,10 +74,12 @@ module ApplicationSettingsHelper
css_class = 'btn'
css_class << ' active' unless disabled
checkbox_name = 'application_setting[enabled_oauth_sign_in_sources][]'
name = Gitlab::Auth::OAuth::Provider.label_for(source)
label_tag(checkbox_name, class: css_class) do
check_box_tag(checkbox_name, source, !disabled,
autocomplete: 'off') + Gitlab::Auth::OAuth::Provider.label_for(source)
autocomplete: 'off',
id: name.tr(' ', '_')) + name
end
end
end
......
......@@ -59,7 +59,7 @@ module BlobHelper
button_tag label, class: "#{common_classes} 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_modify_blob?(blob, project, ref)
button_tag label, class: "#{common_classes}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
elsif can?(current_user, :fork_project, project)
elsif can?(current_user, :fork_project, project) && can?(current_user, :create_merge_request_in, project)
edit_fork_button_tag(common_classes, project, label, edit_modify_file_fork_params(action), action)
end
end
......@@ -280,7 +280,7 @@ module BlobHelper
options << link_to("submit an issue", new_project_issue_path(project))
end
merge_project = can?(current_user, :create_merge_request, project) ? project : (current_user && current_user.fork_of(project))
merge_project = merge_request_source_project_for_project(@project)
if merge_project
options << link_to("create a merge request", project_new_merge_request_path(project))
end
......@@ -334,7 +334,7 @@ module BlobHelper
# Web IDE (Beta) requires the user to have this feature enabled
elsif !current_user || (current_user && can_modify_blob?(blob, project, ref))
edit_link_tag(text, edit_path, common_classes)
elsif current_user && can?(current_user, :fork_project, project)
elsif can?(current_user, :fork_project, project) && can?(current_user, :create_merge_request_in, project)
edit_fork_button_tag(common_classes, project, text, edit_blob_fork_params(edit_path))
end
end
......
......@@ -94,7 +94,7 @@ module CiStatusHelper
def render_project_pipeline_status(pipeline_status, tooltip_placement: 'auto left')
project = pipeline_status.project
path = pipelines_project_commit_path(project, pipeline_status.sha)
path = pipelines_project_commit_path(project, pipeline_status.sha, ref: pipeline_status.ref)
render_status_with_link(
'commit',
......@@ -105,7 +105,7 @@ module CiStatusHelper
def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left')
project = commit.project
path = pipelines_project_commit_path(project, commit)
path = pipelines_project_commit_path(project, commit, ref: ref)
render_status_with_link(
'commit',
......
......@@ -163,7 +163,7 @@ module CommitsHelper
tooltip = "#{action.capitalize} this #{commit.change_type_title(current_user)} in a new merge request" if has_tooltip
btn_class = "btn btn-#{btn_class}" unless btn_class.nil?
if can_collaborate_with_project?
if can_collaborate_with_project?(@project)
link_to action.capitalize, "#modal-#{action}-commit", 'data-toggle' => 'modal', 'data-container' => 'body', title: (tooltip if has_tooltip), class: "#{btn_class} #{'has-tooltip' if has_tooltip}"
elsif can?(current_user, :fork_project, @project)
continue_params = {
......
......@@ -3,7 +3,7 @@ module CompareHelper
from.present? &&
to.present? &&
from != to &&
can?(current_user, :create_merge_request, project) &&
can?(current_user, :create_merge_request_from, project) &&
project.repository.branch_exists?(from) &&
project.repository.branch_exists?(to)
end
......
......@@ -43,6 +43,10 @@ module IconsHelper
content_tag(:svg, content_tag(:use, "", { "xlink:href" => "#{sprite_icon_path}##{icon_name}" } ), class: css_classes.empty? ? nil : css_classes)
end
def external_snippet_icon(name)
content_tag(:span, "", class: "gl-snippet-icon gl-snippet-icon-#{name}")
end
def audit_icon(names, options = {})
case names
when "standard"
......
......@@ -82,8 +82,8 @@ module IssuesHelper
names.to_sentence
end
def award_state_class(awards, current_user)
if !current_user
def award_state_class(awardable, awards, current_user)
if !can?(current_user, :award_emoji, awardable)
"disabled"
elsif current_user && awards.find { |a| a.user_id == current_user.id }
"active"
......@@ -126,6 +126,17 @@ module IssuesHelper
link_to link_text, path
end
def show_new_issue_link?(project)
return false unless project
return false if project.archived?
# We want to show the link to users that are not signed in, that way they
# get directed to the sign-in/sign-up flow and afterwards to the new issue page.
return true unless current_user
can?(current_user, :create_issue, project)
end
# Required for Banzai::Filter::IssueReferenceFilter
module_function :url_for_issue
module_function :url_for_internal_issue
......
......@@ -256,7 +256,7 @@ module MarkupHelper
return '' unless html.present?
context.merge!(
current_user: (current_user if defined?(current_user)),
current_user: (current_user if defined?(current_user)),
# RelativeLinkFilter
commit: @commit,
......
......@@ -138,6 +138,18 @@ module MergeRequestsHelper
end
end
def merge_request_source_project_for_project(project = @project)
unless can?(current_user, :create_merge_request_in, project)
return nil
end
if can?(current_user, :create_merge_request_from, project)
project
else
current_user.fork_of(project)
end
end
def merge_params_ee(merge_request)
{}
end
......
......@@ -6,10 +6,6 @@ module NotesHelper
end
end
def note_editable?(note)
Ability.can_edit_note?(current_user, note)
end
def note_supports_quick_actions?(note)
Notes::QuickActionsService.supported?(note)
end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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